add ref attribute type

This commit is contained in:
Matthew Rich 2025-08-25 03:51:34 +00:00
parent fb544a455c
commit 2d4234c6a0
21 changed files with 1497 additions and 206 deletions

View File

@ -0,0 +1,13 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package data
import (
)
var (
)
type Originator interface {
GetContentReadWriter() ContentReadWriter
}

View File

@ -38,7 +38,6 @@ type Resource interface {
Crudder Crudder
Validator Validator
Clone() Resource Clone() Resource
SetResourceMapper(ResourceMapper)
} }
type Declaration interface { type Declaration interface {

View File

@ -73,7 +73,7 @@ func (d *Declaration) SetDocument(newDocument *Document) {
d.document = newDocument d.document = newDocument
d.SetConfig(d.document.config) d.SetConfig(d.document.config)
d.ResourceTypes = d.document.Types() d.ResourceTypes = d.document.Types()
d.Attributes.SetResourceMapper(d.document.uris) d.Attributes.(ResourceMapSetter).SetResourceMapper(d.document.uris)
} }
func (d *Declaration) ResolveId(ctx context.Context) string { func (d *Declaration) ResolveId(ctx context.Context) string {
@ -209,6 +209,11 @@ func (d *Declaration) Apply(stateTransition string) (result error) {
return result return result
} }
result = stater.Trigger("read") result = stater.Trigger("read")
case "restart": // XXX should only work for a process type resource
if result = stater.Trigger("restart"); result != nil {
return result
}
result = stater.Trigger("read")
default: default:
return fmt.Errorf("%w: %s on %s", ErrUnknownStateTransition, stateTransition, d.Attributes.URI()) return fmt.Errorf("%w: %s on %s", ErrUnknownStateTransition, stateTransition, d.Attributes.URI())
case "create", "present": case "create", "present":
@ -249,7 +254,7 @@ func (d *Declaration) SetConfig(configDoc data.Document) {
return return
} }
if d.Config != "" { // XXX if d.Config != "" { // XXX
panic(fmt.Sprintf("failed setting config: %s", d.Config)) panic(fmt.Errorf("%w: failed setting config: %s", data.ErrConfigUndefined, d.Config))
} }
} }
@ -396,6 +401,10 @@ func (d *Declaration) UnmarshalJSON(jsonData []byte) (err error) {
return return
} }
func (d *Declaration) GetContentReadWriter() data.ContentReadWriter {
return d.Resource().(data.ContentReadWriter)
}
/* /*
func (l *LuaWorker) Receive(m message.Envelope) { func (l *LuaWorker) Receive(m message.Envelope) {
s := m.Sender() s := m.Sender()

View File

@ -37,7 +37,7 @@ type Document struct {
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"` Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
Imports []URI `json:"imports,omitempty" yaml:"imports,omitempty"` Imports []URI `json:"imports,omitempty" yaml:"imports,omitempty"`
Errors []string `json:"error,omitempty" yaml:"error,omitempty"` Errors []string `json:"error,omitempty" yaml:"error,omitempty"`
uris mapper.Store[string, data.Declaration] uris mapper.Store[URI, *Declaration]
ResourceDeclarations []*Declaration `json:"resources,omitempty" yaml:"resources,omitempty"` ResourceDeclarations []*Declaration `json:"resources,omitempty" yaml:"resources,omitempty"`
configNames mapper.Store[string, data.Block] `json:"-" yaml:"-"` configNames mapper.Store[string, data.Block] `json:"-" yaml:"-"`
Configurations []*Block `json:"configurations,omitempty" yaml:"configurations,omitempty"` Configurations []*Block `json:"configurations,omitempty" yaml:"configurations,omitempty"`
@ -57,7 +57,7 @@ func NewDocument(r *Registry) *Document {
return &Document{ return &Document{
Registry: r, Registry: r,
Format: codec.FormatYaml, Format: codec.FormatYaml,
uris: mapper.New[string, data.Declaration](), uris: mapper.New[URI, *Declaration](),
configNames: mapper.New[string, data.Block](), configNames: mapper.New[string, data.Block](),
importPaths: NewSearchPath(configImportPath.GetStringSlice()), importPaths: NewSearchPath(configImportPath.GetStringSlice()),
} }
@ -111,24 +111,24 @@ func (d *Document) Filter(filter data.DeclarationSelector) []data.Declaration {
} }
func (d *Document) Has(key string) bool { func (d *Document) Has(key string) bool {
return d.uris.Has(key) return d.uris.Has(URI(key))
} }
func (d *Document) Get(key string) (any, bool) { func (d *Document) Get(key string) (any, bool) {
return d.uris.Get(key) return d.uris.Get(URI(key))
} }
func (d *Document) Set(key string, value any) { func (d *Document) Set(key string, value any) {
d.uris.Set(key, value.(data.Declaration)) d.uris.Set(URI(key), value.(*Declaration))
} }
func (d *Document) Delete(key string) { func (d *Document) Delete(key string) {
d.uris.Delete(key) d.uris.Delete(URI(key))
} }
func (d *Document) GetResource(uri string) *Declaration { func (d *Document) GetResource(uri string) *Declaration {
if decl, ok := d.uris[uri]; ok { if decl, ok := d.uris[URI(uri)]; ok {
return decl.(*Declaration) return decl
} }
return nil return nil
} }
@ -236,17 +236,17 @@ func (d *Document) GetSchemaFiles() (schemaFs fs.FS) {
return return
} }
schemaFs, _ = d.Registry.Schemas.Get(d.Registry.DefaultSchema) schemaFs, _ = d.Registry.Schemas.Get(d.Registry.DefaultSchema)
slog.Info("Document.GetSchemaFiles()", "schemaFs", schemaFs) slog.Info("Document.GetSchemaFiles() default schema", "schema", d.Registry.DefaultSchema, "schemaFs", schemaFs)
return return
} }
func (d *Document) Validate() error { func (d *Document) Validate() error {
jsonDocument, jsonErr := d.JSON() jsonDocument, jsonErr := d.JSON()
slog.Info("Document.Validate() json", "err", jsonErr) slog.Info("Document.Validate() convert to json", "err", jsonErr)
if jsonErr == nil { if jsonErr == nil {
s := schema.New("document", d.GetSchemaFiles()) s := schema.New("document", d.GetSchemaFiles())
err := s.Validate(string(jsonDocument)) err := s.Validate(string(jsonDocument))
slog.Info("Document.Validate()", "error", err) slog.Info("Document.Validate() validate schema", "error", err)
if err != nil { if err != nil {
return err return err
} }
@ -356,12 +356,12 @@ func (d *Document) MapConfigurationURI(uri string, block data.Block) {
} }
*/ */
func (d *Document) MapResourceURI(uri string, declaration data.Declaration) { func (d *Document) MapResourceURI(uri string, declaration *Declaration) {
d.uris[uri] = declaration d.uris[URI(uri)] = declaration
} }
func (d *Document) UnMapResourceURI(uri string) { func (d *Document) UnMapResourceURI(uri string) {
d.uris.Delete(uri) d.uris.Delete(URI(uri))
} }
func (d *Document) AddDeclaration(declaration data.Declaration) { func (d *Document) AddDeclaration(declaration data.Declaration) {
@ -371,7 +371,7 @@ func (d *Document) AddDeclaration(declaration data.Declaration) {
d.ResourceDeclarations = append(d.ResourceDeclarations, decl) d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
d.MapResourceURI(uri, declaration) d.MapResourceURI(uri, decl)
decl.SetDocument(d) decl.SetDocument(d)
d.Registry.DeclarationMap[decl] = d d.Registry.DeclarationMap[decl] = d
} }
@ -597,22 +597,38 @@ func (d *Document) Diff(with data.Document, output io.Writer) (returnOutput stri
/* /*
func (d *Document) UnmarshalValue(value *DocumentType) error { func (d *Document) UnmarshalValue(value *DocumentType) error {
d.Requires = value.Requires d.Requires = value.Requires
} }
*/ */
func (d *Document) UnmarshalLoadDependencies() (err error) {
defer func() {
if r := recover(); r != nil {
if err != nil {
err = fmt.Errorf("%s - %w", r, err)
} else {
err = fmt.Errorf("%s", r)
}
}
}()
err = d.loadImports()
d.assignConfigurationsDocument()
d.assignResourcesDocument()
return
}
func (d *Document) UnmarshalYAML(value *yaml.Node) (err error) { func (d *Document) UnmarshalYAML(value *yaml.Node) (err error) {
type decodeDocument Document type decodeDocument Document
/*
t := &DocumentType{} t := &DocumentType{}
if unmarshalDocumentErr := value.Decode(t); unmarshalDocumentErr != nil { if unmarshalDocumentErr := value.Decode(t); unmarshalDocumentErr != nil {
return unmarshalDocumentErr return unmarshalDocumentErr
} }
*/
if unmarshalResourcesErr := value.Decode((*decodeDocument)(d)); unmarshalResourcesErr != nil { if unmarshalResourcesErr := value.Decode((*decodeDocument)(d)); unmarshalResourcesErr != nil {
return unmarshalResourcesErr return unmarshalResourcesErr
} }
err = d.loadImports() err = d.UnmarshalLoadDependencies()
d.assignConfigurationsDocument()
d.assignResourcesDocument()
return return
} }
@ -622,12 +638,39 @@ func (d *Document) UnmarshalJSON(data []byte) (err error) {
if unmarshalDocumentErr := json.Unmarshal(data, t); unmarshalDocumentErr != nil { if unmarshalDocumentErr := json.Unmarshal(data, t); unmarshalDocumentErr != nil {
return unmarshalDocumentErr return unmarshalDocumentErr
} }
err = d.loadImports() err = d.UnmarshalLoadDependencies()
d.assignConfigurationsDocument()
d.assignResourcesDocument()
return return
} }
func (d *Document) AddError(e error) { func (d *Document) AddError(e error) {
d.Errors = append(d.Errors, e.Error()) d.Errors = append(d.Errors, e.Error())
} }
func (d *Document) GetContentReadWriter() data.ContentReadWriter {
return d.URI
}
func (d *Document) GetContent(w io.Writer) (contentReader io.ReadCloser, err error) {
slog.Info("Document.GetContent()")
var buf strings.Builder
err = codec.FormatYaml.Serialize(d, &buf)
contentReader = io.NopCloser(strings.NewReader(buf.String()))
if w != nil {
copyBuffer := make([]byte, 32 * 1024)
_, writeErr := io.CopyBuffer(w, contentReader, copyBuffer)
if writeErr != nil {
return nil, fmt.Errorf("File.GetContent(): CopyBuffer failed %v %v: %w", w, contentReader, writeErr)
}
return nil, nil
}
return
}
func (d *Document) SetContent(r io.Reader) error {
return d.LoadReader(io.NopCloser(r), codec.FormatYaml)
}

View File

@ -8,6 +8,7 @@ _ "gopkg.in/yaml.v3"
"gitea.rosskeen.house/rosskeen.house/machine" "gitea.rosskeen.house/rosskeen.house/machine"
"decl/internal/codec" "decl/internal/codec"
"decl/internal/data" "decl/internal/data"
"decl/internal/transport"
"io" "io"
"net/url" "net/url"
"path/filepath" "path/filepath"
@ -89,9 +90,10 @@ func NewMockResource(typename string, stater machine.Stater) (m *MockResource) {
InjectUpdate: func(context.Context) error { return nil }, InjectUpdate: func(context.Context) error { return nil },
InjectDelete: func(context.Context) error { return nil }, InjectDelete: func(context.Context) error { return nil },
InjectUseConfig: func(data.ConfigurationValueGetter) {}, InjectUseConfig: func(data.ConfigurationValueGetter) {},
InjectSetResourceMapper: func(data.ResourceMapper) {}, InjectSetResourceMapper: func(ResourceMapper) {},
InjectURI: func() string { return fmt.Sprintf("%s://bar", typename) }, InjectURI: func() string { return fmt.Sprintf("%s://bar", typename) },
InjectNotify: func(*machine.EventMessage) {}, InjectNotify: func(*machine.EventMessage) {},
InjectContentReaderStream: func() (*transport.Reader, error) { return nil, nil },
} }
m.InjectInit = func(u data.URIParser) error { m.InjectInit = func(u data.URIParser) error {
if u != nil { if u != nil {

View File

@ -11,6 +11,7 @@ _ "fmt"
"decl/internal/data" "decl/internal/data"
"decl/internal/codec" "decl/internal/codec"
"io" "io"
"decl/internal/transport"
) )
type MockResource struct { type MockResource struct {
@ -34,9 +35,11 @@ type MockResource struct {
InjectUpdate func(context.Context) error `json:"-" yaml:"-"` InjectUpdate func(context.Context) error `json:"-" yaml:"-"`
InjectDelete func(context.Context) error `json:"-" yaml:"-"` InjectDelete func(context.Context) error `json:"-" yaml:"-"`
InjectStateMachine func() machine.Stater `json:"-" yaml:"-"` InjectStateMachine func() machine.Stater `json:"-" yaml:"-"`
InjectSetResourceMapper func(data.ResourceMapper) `json:"-" yaml:"-"` InjectSetResourceMapper func(ResourceMapper) `json:"-" yaml:"-"`
InjectUseConfig func(data.ConfigurationValueGetter) `json:"-" yaml:"-"` InjectUseConfig func(data.ConfigurationValueGetter) `json:"-" yaml:"-"`
InjectNotify func(*machine.EventMessage) `json:"-" yaml:"-"` InjectNotify func(*machine.EventMessage) `json:"-" yaml:"-"`
InjectContentReaderStream func() (*transport.Reader, error) `json:"-" yaml:"-"`
InjectContentWriterStream func() (*transport.Writer, error) `json:"-" yaml:"-"`
} }
func (m *MockResource) Clone() data.Resource { func (m *MockResource) Clone() data.Resource {
@ -67,7 +70,7 @@ func (m *MockResource) ResolveId(ctx context.Context) string {
return m.InjectResolveId(ctx) return m.InjectResolveId(ctx)
} }
func (m *MockResource) SetResourceMapper(rm data.ResourceMapper) { func (m *MockResource) SetResourceMapper(rm ResourceMapper) {
m.InjectSetResourceMapper(rm) m.InjectSetResourceMapper(rm)
} }
@ -139,6 +142,14 @@ func (m *MockResource) Notify(em *machine.EventMessage) {
m.InjectNotify(em) m.InjectNotify(em)
} }
func (m *MockResource) ContentReaderStream() (*transport.Reader, error) {
return m.InjectContentReaderStream()
}
func (m *MockResource) ContentWriterStream() (*transport.Writer, error) {
return m.InjectContentWriterStream()
}
func (m *MockResource) UnmarshalJSON(data []byte) error { func (m *MockResource) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, m); err != nil { if err := json.Unmarshal(data, m); err != nil {
return err return err

229
internal/folio/ref.go Normal file
View File

@ -0,0 +1,229 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package folio
import (
"encoding/json"
"gopkg.in/yaml.v3"
"decl/internal/mapper"
"decl/internal/data"
"decl/internal/transport"
"net/url"
"log/slog"
"errors"
"fmt"
"io"
)
var (
ErrRefMapperMismatch = errors.New("Ref type does not the provided mapper")
)
type Ref struct {
Uri URI `json:"uri" yaml:"uri"`
RefType ReferenceType `json:"type,omitempty" yaml:"type,omitempty"`
documentMapper mapper.Store[URI, *Document]
resourceMapper mapper.Store[URI, *Declaration]
}
type decodeRef Ref
func NewRef() *Ref {
return &Ref {
}
}
func (r *ReferenceType) UnmarshalValue(value string) error {
switch value {
case string(ReferenceTypeResource), string(ReferenceTypeDocument):
*r = ReferenceType(value)
default:
*r = ReferenceTypeResource
//return ErrInvalidReferenceType
}
return nil
}
func (r *ReferenceType) UnmarshalJSON(jsonData []byte) error {
var s string
if unmarshalReferenceTypeErr := json.Unmarshal(jsonData, &s); unmarshalReferenceTypeErr != nil {
return unmarshalReferenceTypeErr
}
return r.UnmarshalValue(s)
}
func (r *ReferenceType) UnmarshalYAML(value *yaml.Node) error {
var s string
if err := value.Decode(&s); err != nil {
return err
}
return r.UnmarshalValue(s)
}
func (r *Ref) UnmarshalValue(value *decodeRef) error {
slog.Info("Ref.UnmarshalValue", "decode", value)
r.Uri = value.Uri
switch value.RefType {
case ReferenceTypeResource:
r.RefType = value.RefType
case ReferenceTypeDocument:
r.RefType = value.RefType
r.SetMapper(DocumentRegistry.UriMap)
default:
r.RefType = ReferenceTypeResource
//return ErrInvalidReferenceType
}
slog.Info("Ref.UnmarshalValue", "stored", *r)
return nil
}
func (r *Ref) UnmarshalJSON(jsonData []byte) error {
decodeJsonToRef := &decodeRef{}
if unmarshalReferenceTypeErr := json.Unmarshal(jsonData, decodeJsonToRef); unmarshalReferenceTypeErr != nil {
return unmarshalReferenceTypeErr
}
return r.UnmarshalValue(decodeJsonToRef)
}
func (r *Ref) UnmarshalYAML(value *yaml.Node) error {
decodeYamlToRef := &decodeRef{}
if err := value.Decode(decodeYamlToRef); err != nil {
return err
}
return r.UnmarshalValue(decodeYamlToRef)
}
func (r *Ref) SetMapper(m any) error {
var ok bool
switch r.RefType {
case ReferenceTypeResource:
if r.resourceMapper, ok = m.(mapper.Store[URI, *Declaration]); ! ok {
return fmt.Errorf("%w - %T is not a %s", ErrRefMapperMismatch, m, r.RefType)
}
case ReferenceTypeDocument:
if r.documentMapper, ok = m.(mapper.Store[URI, *Document]); ! ok {
return fmt.Errorf("%w - %T is not a %s", ErrRefMapperMismatch, m, r.RefType)
}
}
return nil
}
func Dereference[T ReferenceTypes](uri URI, look mapper.Map[URI, T]) T {
if uri != "" && look != nil {
if v, ok := look.Get(uri); ok {
slog.Info("Ref::Dereference()", "value", v, "mapper", look)
return v
}
}
return nil
}
func (r *Ref) Lookup(look any) ContentReadWriter {
slog.Info("Ref.Lookup()", "ref", r, "mapper", look)
switch r.RefType {
case ReferenceTypeResource:
if resourceDeclaration := Dereference(r.Uri, look.(mapper.Store[URI, *Declaration])); resourceDeclaration != nil {
return resourceDeclaration.GetContentReadWriter()
}
case ReferenceTypeDocument:
if document := Dereference(r.Uri, look.(mapper.Store[URI, *Document])); document != nil {
return document.GetContentReadWriter()
}
}
return r.Uri
}
func (r *Ref) Dereference(look any) any {
switch r.RefType {
case ReferenceTypeResource:
if look == nil {
return Dereference(r.Uri, r.resourceMapper)
} else {
return Dereference(r.Uri, look.(mapper.Store[URI, *Declaration]))
}
case ReferenceTypeDocument:
if look == nil {
return Dereference(r.Uri, r.documentMapper)
} else {
return Dereference(r.Uri, look.(mapper.Store[URI, *Document]))
}
}
return nil
}
func (r *Ref) DereferenceDefault() any {
switch r.RefType {
case ReferenceTypeResource:
return Dereference(r.Uri, r.resourceMapper)
case ReferenceTypeDocument:
return Dereference(r.Uri, r.documentMapper)
}
return nil
}
func (r *Ref) Parse() data.URIParser {
return r.Uri.Parse()
}
func (r *Ref) Exists() bool {
return r.Uri.Exists()
}
func (r *Ref) ContentReaderStream() (*transport.Reader, error) {
return r.Uri.ContentReaderStream()
}
func (r *Ref) ContentWriterStream() (*transport.Writer, error) {
return r.Uri.ContentWriterStream()
}
func (r *Ref) GetContent(w io.Writer) (contentReader io.ReadCloser, err error) {
target := r.DereferenceDefault()
if targetContent, ok := target.(data.ContentGetSetter); ok {
return targetContent.GetContent(w)
}
return nil, fmt.Errorf("Ref target does not support ContentGetSetter: %s, %s, %#v", r.RefType, r.Uri, target)
}
func (r *Ref) SetContent(contentReader io.Reader) error {
target := r.DereferenceDefault()
if targetContent, ok := target.(data.ContentGetSetter); ok {
return targetContent.SetContent(contentReader)
}
return fmt.Errorf("Ref target does not support ContentGetSetter: %s, %s, %#v", r.RefType, r.Uri, target)
}
func (r *Ref) Reader() (reader io.ReadCloser, err error) {
switch r.RefType {
case ReferenceTypeResource:
return r.ContentReaderStream()
case ReferenceTypeDocument:
reader, err = r.GetContent(nil)
if reader == nil {
return r.ContentReaderStream()
}
return
}
return nil, ErrInvalidReferenceType
}
func (r *Ref) String() string {
return r.Uri.String()
}
func (r *Ref) SetURL(url *url.URL) {
r.Uri.SetURL(url)
}
func (r *Ref) Extension() (string, string) {
return r.Uri.Extension()
}
func (r *Ref) IsEmpty() bool {
return r.Uri.IsEmpty()
}
func (r *Ref) FindIn(s *SearchPath) {
r.Uri.FindIn(s)
}

View File

@ -0,0 +1,67 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package folio
import (
"github.com/stretchr/testify/assert"
"testing"
"fmt"
_ "decl/internal/data"
"decl/internal/mapper"
"decl/internal/codec"
"log/slog"
"strings"
"io"
)
func TestReference(t *testing.T) {
f := NewFooResource()
resourceMapper := mapper.New[URI, *Declaration]()
f.Name = string(TempDir)
f.Size = 10
f.MockResource.InjectURI = func() string { return fmt.Sprintf("%s://%s", "foo", f.Name) }
d := NewDeclaration()
d.Type = "foo"
d.Attributes = f
resourceMapper[URI(d.URI())] = d
slog.Info("TestReference", "declaration", d, "mapper", resourceMapper)
var fooRef *Ref = NewRef()
fooRef.RefType = ReferenceTypeResource
fooRef.Uri = URI(fmt.Sprintf("foo://%s", string(TempDir)))
u := fooRef.Uri.Parse().URL()
assert.Equal(t, "foo", u.Scheme)
assert.True(t, fooRef.Uri.Exists())
fromRef := fooRef.Lookup(resourceMapper)
assert.NotNil(t, fromRef)
}
func TestDocumentRef(t *testing.T) {
docUri := fmt.Sprintf("file://%s/doc.yaml", TempDir)
DocumentRegistry.ResourceTypes = TestResourceTypes
document := `
---
resources:
- type: foo
attributes:
name: "testfoo"
size: 10022
`
TempDir.CreateFile("doc.yaml", document)
d := DocumentRegistry.NewDocument(URI(docUri))
assert.NotNil(t, d)
docReader := io.NopCloser(strings.NewReader(document))
e := d.LoadReader(docReader, codec.FormatYaml)
assert.Nil(t, e)
var docRef *Ref = NewRef()
docRef.RefType = ReferenceTypeDocument
docRef.Uri = URI(docUri)
u := docRef.Uri.Parse().URL()
assert.Equal(t, "file", u.Scheme)
assert.Equal(t, d, docRef.Dereference(DocumentRegistry.UriMap).(*Document))
}

View File

@ -60,7 +60,8 @@ func (r *Registry) HasDocument(key URI) bool {
} }
func (r *Registry) GetDocument(key URI) (*Document, bool) { func (r *Registry) GetDocument(key URI) (*Document, bool) {
return r.UriMap.Get(key) document, result := r.UriMap.Get(key)
return document, result
} }
func (r *Registry) SetDocument(key URI, value *Document) { func (r *Registry) SetDocument(key URI, value *Document) {
@ -72,7 +73,7 @@ func (r *Registry) NewDocument(uri URI) (doc *Document) {
doc.SetURI(string(uri)) doc.SetURI(string(uri))
r.Documents = append(r.Documents, doc) r.Documents = append(r.Documents, doc)
if uri != "" { if uri != "" {
r.UriMap[uri] = doc r.UriMap.Set(uri, doc)
} }
return return
} }

View File

@ -5,6 +5,7 @@ package folio
import ( import (
"decl/internal/transport" "decl/internal/transport"
"decl/internal/data" "decl/internal/data"
"decl/internal/mapper"
"errors" "errors"
"log/slog" "log/slog"
"net/url" "net/url"
@ -31,25 +32,26 @@ type ContentReadWriter interface {
type ResourceReference URI type ResourceReference URI
// Return a Content ReadWriter for the resource referred to. // Return a Content ReadWriter for the resource referred to.
func (r ResourceReference) Lookup(look data.ResourceMapper) ContentReadWriter { func (r ResourceReference) Lookup(look mapper.Map[URI, *Declaration]) ContentReadWriter {
if string(r) == "" { if string(r) == "" {
return nil return nil
} }
slog.Info("ResourceReference.Lookup()", "resourcereference", r, "resourcemapper", look) slog.Info("ResourceReference.Lookup()", "resourcereference", r, "resourcemapper", look)
if look != nil { if look != nil {
if v,ok := look.Get(string(r)); ok { if v,ok := look.Get(URI(r)); ok {
slog.Info("ResourceReference.Lookup()", "resourcereference", r, "result", v.Resource())
return v.Resource().(ContentReadWriter) return v.Resource().(ContentReadWriter)
} }
} }
return r return r
} }
func (r ResourceReference) Dereference(look data.ResourceMapper) data.Resource { func (r ResourceReference) Dereference(look mapper.Map[URI, *Declaration]) data.Resource {
slog.Info("ResourceReference.Dereference()", "resourcereference", r, "resourcemapper", look) slog.Info("ResourceReference.Dereference()", "resourcereference", r, "resourcemapper", look)
if look != nil { if look != nil {
if v,ok := look.Get(string(r)); ok { if v,ok := look.Get(URI(r)); ok {
slog.Info("ResourceReference.Dereference()", "resourcereference", r, "result", v) slog.Info("ResourceReference.Dereference()", "resourcereference", r, "result", v)
return v.(*Declaration).Attributes return v.Attributes
} }
} }
return nil return nil
@ -74,3 +76,7 @@ func (r ResourceReference) ContentWriterStream() (*transport.Writer, error) {
func (r ResourceReference) IsEmpty() bool { func (r ResourceReference) IsEmpty() bool {
return URI(r).IsEmpty() return URI(r).IsEmpty()
} }
func (r *ResourceReference) FindIn(s *SearchPath) {
(*URI)(r).FindIn(s)
}

View File

@ -6,19 +6,18 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
"fmt" "fmt"
"decl/internal/data"
"decl/internal/mapper" "decl/internal/mapper"
) )
func TestResourceReference(t *testing.T) { func TestResourceReference(t *testing.T) {
f := NewFooResource() f := NewFooResource()
resourceMapper := mapper.New[string, data.Declaration]() resourceMapper := mapper.New[URI, *Declaration]()
f.Name = string(TempDir) f.Name = string(TempDir)
f.Size = 10 f.Size = 10
d := NewDeclaration() d := NewDeclaration()
d.Type = "foo" d.Type = "foo"
d.Attributes = f d.Attributes = f
resourceMapper[d.URI()] = d resourceMapper[URI(d.URI())] = d
var foo ResourceReference = ResourceReference(fmt.Sprintf("foo://%s", string(TempDir))) var foo ResourceReference = ResourceReference(fmt.Sprintf("foo://%s", string(TempDir)))
u := foo.Parse() u := foo.Parse()

View File

@ -7,8 +7,8 @@
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
"description": "foo name", "description": "foo name",
"minLength": 1 "minLength": 1
}, },
"size": { "size": {
"type": "integer", "type": "integer",

View File

@ -0,0 +1,17 @@
{
"$id": "ref.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ref",
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Type of object referred to",
"enum": [ "resource", "document" ]
},
"uri": {
"type": "string",
"description": "URI of the object referred to"
}
}
}

View File

@ -214,8 +214,8 @@ attributes:
_, readErr := contextFile.Resource().Read(context.Background()) _, readErr := contextFile.Resource().Read(context.Background())
assert.Nil(t, readErr) assert.Nil(t, readErr)
c.Resources = data.NewResourceMapper() c.Resources = folio.NewResourceMapper()
c.Resources.Set(contextDirUri, contextFile) c.Resources.Set(folio.URI(contextDirUri), contextFile)
d, contextErr := c.ContextDocument() d, contextErr := c.ContextDocument()
assert.Nil(t, contextErr) assert.Nil(t, contextErr)

View File

@ -5,6 +5,7 @@ package resource
import ( import (
"context" "context"
"decl/tests/mocks" "decl/tests/mocks"
"decl/internal/codec"
_ "encoding/json" _ "encoding/json"
_ "fmt" _ "fmt"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -20,6 +21,7 @@ _ "os"
"strings" "strings"
"testing" "testing"
"bytes" "bytes"
"time"
) )
func TestNewContainerResource(t *testing.T) { func TestNewContainerResource(t *testing.T) {
@ -47,6 +49,9 @@ func TestReadContainer(t *testing.T) {
ID: "123456789abc", ID: "123456789abc",
Name: "test", Name: "test",
Image: "alpine", Image: "alpine",
State: &types.ContainerState{
Status: "running",
},
}}, nil }}, nil
}, },
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) { InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
@ -62,18 +67,19 @@ func TestReadContainer(t *testing.T) {
} }
c := NewContainer(m) c := NewContainer(m)
assert.NotEqual(t, nil, c) assert.NotNil(t, c)
e := c.LoadDecl(decl) e := c.LoadDecl(decl)
assert.Equal(t, nil, e) assert.Nil(t, e)
assert.Equal(t, "testcontainer", c.Name) assert.Equal(t, "testcontainer", c.Name)
resourceYaml, readContainerErr := c.Read(ctx) resourceYaml, readContainerErr := c.Read(ctx)
assert.Equal(t, nil, readContainerErr) assert.Nil(t, readContainerErr)
assert.Greater(t, len(resourceYaml), 0) assert.Greater(t, len(resourceYaml), 0)
assert.Equal(t, "running", c.State)
} }
func TestCreateContainer(t *testing.T) { func TestCreateDeleteContainer(t *testing.T) {
m := &mocks.MockContainerClient{ m := &mocks.MockContainerClient{
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) { InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
@ -148,3 +154,119 @@ func TestContainerLogOutput(t *testing.T) {
assert.Equal(t, "done.", c.Stdout) assert.Equal(t, "done.", c.Stdout)
} }
func TestWaitContainer(t *testing.T) {
mockState := &types.ContainerState{
Status: "",
}
m := &mocks.MockContainerClient{
InjectContainerInspect: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
return types.ContainerJSON{
ContainerJSONBase: &types.ContainerJSONBase{
ID: "abcdef012",
Name: "testcontainer",
Image: "alpine",
State: mockState,
}}, nil
},
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
mockState.Status = "created"
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
},
InjectContainerStop: func(context.Context, string, container.StopOptions) error {
return nil
},
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
return nil
},
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
var res container.WaitResponse
resChan := make(chan container.WaitResponse)
errChan := make(chan error, 1)
go func() { resChan <- res }()
return resChan, errChan
},
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("done.")), nil
},
}
c := NewContainer(m)
assert.Nil(t, c.LoadString(`
name: "testcontainer"
image: "alpine"
state: present
`, codec.FormatYaml))
assert.Nil(t, c.Apply())
assert.Equal(t, "testcontainer", c.Name)
assert.Nil(t, c.wait(context.Background(), func(state *types.ContainerState) bool {
return state.Status == "running"
}))
}
func TestRestartContainer(t *testing.T) {
mockState := &types.ContainerState{
Status: "",
}
m := &mocks.MockContainerClient{
InjectContainerInspect: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
return types.ContainerJSON{
ContainerJSONBase: &types.ContainerJSONBase{
ID: "abcdef012",
Name: "testcontainer",
Image: "alpine",
State: mockState,
}}, nil
},
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
mockState.Status = "created"
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
},
InjectContainerStop: func(context.Context, string, container.StopOptions) error {
return nil
},
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
return nil
},
InjectContainerRestart: func(ctx context.Context, containerID string, options container.StopOptions) error {
go func() {
time.Sleep(100 * time.Millisecond)
mockState.Status = "running"
mockState.Running = true
}()
return nil
},
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
var res container.WaitResponse
resChan := make(chan container.WaitResponse)
errChan := make(chan error, 1)
go func() { resChan <- res }()
return resChan, errChan
},
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("done.")), nil
},
}
c := NewContainer(m)
assert.Nil(t, c.LoadString(`
name: "testcontainer"
image: "alpine"
state: present
`, codec.FormatYaml))
assert.Equal(t, "testcontainer", c.Name)
assert.Nil(t, c.Apply())
c.State = "present" // overwrite the state
c.StateMachine().Trigger("restart")
assert.Equal(t, "running", c.State)
assert.Nil(t, c.Apply())
}

View File

@ -35,7 +35,16 @@ func TestExecApplyResourceTransformation(t *testing.T) {
} }
func TestReadExec(t *testing.T) { func TestReadExec(t *testing.T) {
x := NewExec()
decl := `
read:
path: ls
args:
- -al
`
assert.Nil(t, x.LoadDecl(decl))
assert.Equal(t, "ls", x.ReadTemplate.Path)
assert.Equal(t, command.CommandArg("-al"), x.ReadTemplate.Args[0])
} }
func TestReadExecError(t *testing.T) { func TestReadExecError(t *testing.T) {

View File

@ -0,0 +1,34 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package resource
import (
"context"
_ "gopkg.in/yaml.v3"
"gitea.rosskeen.house/rosskeen.house/machine"
"decl/internal/transport"
"decl/internal/ext"
"strings"
"io"
)
func NewMockBufferResource(id string, target *strings.Builder) *MockResource {
return &MockResource {
InjectType: func() string { return "buffer" },
InjectResolveId: func(ctx context.Context) string { return id },
InjectRead: func(ctx context.Context) ([]byte, error) { return nil,nil },
InjectLoadDecl: func(string) error { return nil },
InjectApply: func() error { return nil },
InjectStateMachine: func() machine.Stater { return nil },
InjectContentWriterStream: func() (*transport.Writer, error) {
w := &transport.Writer{}
w.SetStream(ext.WriteNopCloser(target))
return w, nil
},
InjectContentReaderStream: func() (*transport.Reader, error) {
r := &transport.Reader{}
r.SetStream(io.NopCloser(strings.NewReader(target.String())))
return r, nil
},
}
}

View File

@ -9,25 +9,32 @@ _ "gopkg.in/yaml.v3"
_ "fmt" _ "fmt"
"gitea.rosskeen.house/rosskeen.house/machine" "gitea.rosskeen.house/rosskeen.house/machine"
"decl/internal/data" "decl/internal/data"
"decl/internal/codec"
"decl/internal/transport"
"io"
) )
type MockResource struct { type MockResource struct {
InjectURI func() string *Common `json:",inline" yaml:",inline"`
InjectType func() string InjectURI func() string
InjectResolveId func(ctx context.Context) string InjectType func() string
InjectLoadDecl func(string) error InjectResolveId func(ctx context.Context) string
InjectValidate func() error InjectLoadDecl func(string) error
InjectApply func() error InjectValidate func() error
InjectRead func(context.Context) ([]byte, error) InjectApply func() error
InjectStateMachine func() machine.Stater InjectRead func(context.Context) ([]byte, error)
InjectStateMachine func() machine.Stater
InjectContentType func() string
InjectContentReaderStream func() (*transport.Reader, error)
InjectContentWriterStream func() (*transport.Writer, error)
} }
func (m *MockResource) Clone() data.Resource { func (m *MockResource) Clone() data.Resource {
return nil return nil
} }
func (m *MockResource) StateMachine() machine.Stater { func (m *MockResource) StateMachine() machine.Stater {
return nil return nil
} }
func (m *MockResource) SetURI(uri string) error { func (m *MockResource) SetURI(uri string) error {
@ -35,15 +42,15 @@ func (m *MockResource) SetURI(uri string) error {
} }
func (m *MockResource) URI() string { func (m *MockResource) URI() string {
return m.InjectURI() return m.InjectURI()
} }
func (m *MockResource) ResolveId(ctx context.Context) string { func (m *MockResource) ResolveId(ctx context.Context) string {
return m.InjectResolveId(ctx) return m.InjectResolveId(ctx)
} }
func (m *MockResource) LoadDecl(yamlResourceDeclaration string) error { func (m *MockResource) LoadDecl(yamlResourceDeclaration string) error {
return m.InjectLoadDecl(yamlResourceDeclaration) return m.InjectLoadDecl(yamlResourceDeclaration)
} }
func (m *MockResource) Validate() error { func (m *MockResource) Validate() error {
@ -51,20 +58,81 @@ func (m *MockResource) Validate() error {
} }
func (m *MockResource) Apply() error { func (m *MockResource) Apply() error {
return m.InjectApply() return m.InjectApply()
} }
func (m *MockResource) Read(ctx context.Context) ([]byte, error) { func (m *MockResource) Read(ctx context.Context) ([]byte, error) {
return m.InjectRead(ctx) return m.InjectRead(ctx)
} }
func (m *MockResource) Type() string { func (m *MockResource) Type() string {
return m.InjectType() return m.InjectType()
} }
func (m *MockResource) UnmarshalJSON(data []byte) error { func (m *MockResource) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, m); err != nil { if err := json.Unmarshal(data, m); err != nil {
return err return err
} }
return nil return nil
}
/*
func (m *MockResource) SetParsedURI(URIParser) error {
return nil
}
*/
func (m *MockResource) UseConfig(config data.ConfigurationValueGetter) {
}
func (m *MockResource) LoadString(string, codec.Format) (error) {
return nil
}
func (m *MockResource) Load([]byte, codec.Format) (error) {
return nil
}
func (m *MockResource) LoadReader(io.ReadCloser, codec.Format) (error) {
return nil
}
func (m *MockResource) Create(context.Context) error {
return nil
}
func (m *MockResource) Update(context.Context) error {
return nil
}
func (m *MockResource) Delete(context.Context) error {
return nil
}
func (m *MockResource) ReadStat() error {
return nil
}
func (m *MockResource) ContentType() string {
if m.InjectContentType == nil {
return ""
}
return m.InjectContentType()
}
func (m *MockResource) ContentReaderStream() (*transport.Reader, error) {
return m.InjectContentReaderStream()
}
func (m *MockResource) ContentWriterStream() (*transport.Writer, error) {
return m.InjectContentWriterStream()
}
func (m *MockResource) GetContent(w io.Writer) (contentReader io.ReadCloser, err error) {
return nil, nil
}
func (m *MockResource) SetContent(r io.Reader) error {
return nil
} }

View File

@ -27,8 +27,11 @@ import (
) )
var ( var (
ErrSignatureFailedDecodingSignature error = errors.New("Failed decoding signature")
ErrSignatureWriterFailed error = errors.New("Failed creating signature writer") ErrSignatureWriterFailed error = errors.New("Failed creating signature writer")
ErrArmoredWriterFailed error = errors.New("Failed to create armored writer") ErrArmoredWriterFailed error = errors.New("Failed to create armored writer")
ErrSignatureVerificationUnknownEntity error = errors.New("Signature uses unknown entity")
ErrSignatureMissing error = errors.New("Signature value undefined")
) )
const ( const (
@ -48,14 +51,17 @@ func init() {
} }
type OpenPGPSignature struct { type OpenPGPSignature struct {
*Common `json:",inline" yaml:",inline"` *Common `json:"-,inline" yaml:"-,inline"`
stater machine.Stater `json:"-" yaml:"-"` stater machine.Stater `json:"-" yaml:"-"`
Signature string `json:"signature,omitempty" yaml:"signature,omitempty"` Signature string `json:"signature,omitempty" yaml:"signature,omitempty"`
KeyRingRef folio.ResourceReference `json:"keyringref,omitempty" yaml:"keyringref,omitempty"` Signed string `json:"signed,omitempty" yaml:"signed,omitempty"`
SourceRef folio.ResourceReference `json:"soureref,omitempty" yaml:"sourceref,omitempty"` KeyRingRef folio.Ref `json:"keyringref,omitempty" yaml:"keyringref,omitempty"`
SignatureRef folio.ResourceReference `json:"signatureref,omitempty" yaml:"signatureref,omitempty"` SourceRef folio.Ref `json:"soureref,omitempty" yaml:"sourceref,omitempty"`
SignatureRef folio.Ref `json:"signatureref,omitempty" yaml:"signatureref,omitempty"`
signatureBlock *armor.Block
message *openpgp.MessageDetails message *openpgp.MessageDetails
entityList openpgp.EntityList entityList openpgp.EntityList
} }
@ -90,41 +96,94 @@ func (o *OpenPGPSignature) StateMachine() machine.Stater {
} }
func (o *OpenPGPSignature) URI() string { func (o *OpenPGPSignature) URI() string {
return string(o.Common.URI()) parsedSourceUri := o.SourceRef.Uri.Parse().URL()
return fmt.Sprintf("%s://%s", o.Type(), parsedSourceUri.Host + parsedSourceUri.RequestURI())
}
func (o *OpenPGPSignature) DecryptPrivateKey(entity *openpgp.Entity) error {
if o.config != nil {
passphraseConfig, _ := o.config.GetValue("passphrase")
passphrase := []byte(passphraseConfig.(string))
if len(passphrase) > 0 {
slog.Info("OpenPGPSignature.DecryptPrivateKey", "passphrase", passphrase, "entity", entity)
if decryptErr := entity.PrivateKey.Decrypt(passphrase); decryptErr != nil {
return fmt.Errorf("%w private key: %w", ErrOpenPGPDecryptionFailure, decryptErr)
}
for _, subkey := range entity.Subkeys {
if decryptErr := subkey.PrivateKey.Encrypt(passphrase); decryptErr != nil {
return fmt.Errorf("%w subkey (private key): %w", ErrOpenPGPDecryptionFailure, decryptErr)
}
}
}
}
return nil
} }
func (o *OpenPGPSignature) SigningEntity() (err error) { func (o *OpenPGPSignature) SigningEntity() (err error) {
if o.KeyRingRef.IsEmpty() { if len(o.entityList) < 1 {
var keyringConfig any slog.Info("OpenPGPSignature.SigningEntity() - Loading KeyRing", "keyring", o.KeyRingRef)
if keyringConfig, err = o.config.GetValue("keyring"); err == nil { if o.KeyRingRef.IsEmpty() {
o.entityList = keyringConfig.(openpgp.EntityList) var keyringConfig any
if keyringConfig, err = o.config.GetValue("keyring"); err == nil {
o.entityList = keyringConfig.(openpgp.EntityList)
}
} else {
ringFileStream, _ := o.KeyRingRef.Lookup(o.Resources).ContentReaderStream()
defer ringFileStream.Close()
o.entityList, err = openpgp.ReadArmoredKeyRing(ringFileStream)
slog.Info("OpenPGPSignature.SigningEntity()", "entities", o.entityList[0])
}
for i := range o.entityList {
if decryptErr := o.DecryptPrivateKey(o.entityList[i]); decryptErr != nil {
err = decryptErr
}
} }
} else {
ringFileStream, _ := o.KeyRingRef.Lookup(o.Resources).ContentReaderStream()
defer ringFileStream.Close()
o.entityList, err = openpgp.ReadArmoredKeyRing(ringFileStream)
} }
return return
} }
func (o *OpenPGPSignature) Sign(message io.Reader, w io.Writer) (err error) { func (o *OpenPGPSignature) Sign(message io.Reader, w io.Writer) (err error) {
var writer io.WriteCloser
entity := o.entityList[0] entity := o.entityList[0]
if err = openpgp.DetachSign(w, entity, message, o.Config()); err != nil {
if writer, err = openpgp.Sign(w, entity, nil, o.Config()); err == nil {
defer writer.Close()
_, err = io.Copy(writer, message)
} else {
err = fmt.Errorf("%w: %w", ErrSignatureWriterFailed, err) err = fmt.Errorf("%w: %w", ErrSignatureWriterFailed, err)
} }
return return
} }
func (o *OpenPGPSignature) Verify(message io.Reader) (err error) {
if len(o.Signature) < 1 || o.signatureBlock == nil {
return fmt.Errorf("%w - %d", ErrSignatureMissing, len(o.Signature))
}
slog.Info("OpenPGPSignature.Verify()", "signature", o.Signature, "block", o.signatureBlock)
var entity *openpgp.Entity
entity, err = openpgp.CheckDetachedSignature(o.entityList, message, o.signatureBlock.Body, o.Config())
if entity == nil {
slog.Info("OpenPGPSignature.Verify() check signature failed", "signature", o.signatureBlock, "err", err)
return fmt.Errorf("%w: %w", ErrSignatureVerificationUnknownEntity, err)
}
return
}
func (o *OpenPGPSignature) Create(ctx context.Context) (err error) { func (o *OpenPGPSignature) Create(ctx context.Context) (err error) {
if err = o.SigningEntity(); err == nil { if err = o.SigningEntity(); err == nil {
var sourceReadStream io.ReadCloser var sourceReadStream io.ReadCloser
sourceReadStream, err = o.SourceRef.Lookup(o.Resources).ContentReaderStream()
switch o.SourceRef.RefType {
case folio.ReferenceTypeDocument:
//sourceReadStream, err = o.SourceRef.Lookup(folio.DocumentRegistry.UriMap).ContentReaderStream()
sourceReadStream, err = o.SourceRef.Reader()
srcData, _ := io.ReadAll(sourceReadStream)
sourceReadStream.Close()
o.Signed = string(srcData)
sourceReadStream, err = o.SourceRef.Reader()
default:
sourceReadStream, err = o.SourceRef.Lookup(o.Resources).ContentReaderStream()
}
defer sourceReadStream.Close()
var signatureStream, armoredWriter io.WriteCloser var signatureStream, armoredWriter io.WriteCloser
if o.SignatureRef.IsEmpty() { if o.SignatureRef.IsEmpty() {
@ -138,9 +197,9 @@ func (o *OpenPGPSignature) Create(ctx context.Context) (err error) {
if armoredWriter, err = armor.Encode(signatureStream, openpgp.SignatureType, nil); err != nil { if armoredWriter, err = armor.Encode(signatureStream, openpgp.SignatureType, nil); err != nil {
err = fmt.Errorf("%w: %w", ErrArmoredWriterFailed, err) err = fmt.Errorf("%w: %w", ErrArmoredWriterFailed, err)
} }
defer armoredWriter.Close()
err = o.Sign(sourceReadStream, armoredWriter) err = o.Sign(sourceReadStream, armoredWriter)
armoredWriter.Close()
} }
return return
} }
@ -196,8 +255,10 @@ func (o *OpenPGPSignature) Notify(m *machine.EventMessage) {
_ = o.AddError(triggerErr) _ = o.AddError(triggerErr)
} }
} else { } else {
slog.Info("OpenPGPSignature.Notify()", "dest", "start_read", "err", readErr)
_ = o.AddError(readErr) _ = o.AddError(readErr)
if o.IsResourceInconsistent() { if o.IsResourceInconsistent() {
slog.Info("OpenPGPSignature.Notify()", "dest", "read-failed", "machine", o.StateMachine())
if triggerErr := o.StateMachine().Trigger("read-failed"); triggerErr == nil { if triggerErr := o.StateMachine().Trigger("read-failed"); triggerErr == nil {
panic(readErr) panic(readErr)
} else { } else {
@ -320,16 +381,16 @@ func (o *OpenPGPSignature) ResolveId(ctx context.Context) string {
} }
func (o *OpenPGPSignature) GetContentSourceRef() string { func (o *OpenPGPSignature) GetContentSourceRef() string {
return string(o.SignatureRef) return o.SignatureRef.String()
} }
func (o *OpenPGPSignature) SetContentSourceRef(uri string) { func (o *OpenPGPSignature) SetContentSourceRef(uri string) {
o.SignatureRef = folio.ResourceReference(uri) o.SignatureRef.Uri = folio.URI(uri)
} }
func (o *OpenPGPSignature) SignatureRefStat() (info fs.FileInfo, err error) { func (o *OpenPGPSignature) SignatureRefStat() (info fs.FileInfo, err error) {
err = fmt.Errorf("%w: %s", ErrResourceStateAbsent, o.SignatureRef) err = fmt.Errorf("%w: %s", ErrResourceStateAbsent, o.SignatureRef.Uri)
if len(o.SignatureRef) > 0 { if len(o.SignatureRef.Uri) > 0 {
rs, _ := o.ContentReaderStream() rs, _ := o.ContentReaderStream()
defer rs.Close() defer rs.Close()
return rs.Stat() return rs.Stat()
@ -346,6 +407,16 @@ func (o *OpenPGPSignature) ReadStat() (err error) {
return err return err
} }
func (o *OpenPGPSignature) SourceRefStat() (info fs.FileInfo, err error) {
err = fmt.Errorf("%w: %s", ErrResourceStateAbsent, o.SourceRef.Uri)
if ! o.SourceRef.IsEmpty() {
rs, _ := o.SourceRef.ContentReaderStream()
defer rs.Close()
return rs.Stat()
}
return
}
func (o *OpenPGPSignature) Update(ctx context.Context) error { func (o *OpenPGPSignature) Update(ctx context.Context) error {
return o.Create(ctx) return o.Create(ctx)
} }
@ -354,7 +425,69 @@ func (o *OpenPGPSignature) Delete(ctx context.Context) error {
return os.Remove(o.Common.Path) return os.Remove(o.Common.Path)
} }
func (o *OpenPGPSignature) Read(ctx context.Context) ([]byte, error) { func (o *OpenPGPSignature) readSignatureRef() (err error) {
// signatureref
// XXX which takes precedence: the value of Signature from yaml or the value of Signature loaded from SignatureRef?
if (! o.SignatureRef.IsEmpty()) && o.SignatureRef.Exists() {
signatureReader, _ := o.SignatureRef.Lookup(o.Resources).ContentReaderStream()
defer signatureReader.Close()
SignatureData, readErr := io.ReadAll(signatureReader)
if readErr != nil {
return readErr
}
o.Signature = string(SignatureData)
}
return nil
}
func (o *OpenPGPSignature) DecodeSignatureBlock() (err error) {
slog.Info("OpenPGPSignature.DecodeSignatureBlock()", "signature", o.Signature)
if o.signatureBlock, err = armor.Decode(bytes.NewReader([]byte(o.Signature))); o.signatureBlock == nil || err != nil {
err = ErrSignatureFailedDecodingSignature
}
return
}
// Read
// - signature(string/ref) content
// - keyringref -> loads signing entity
// - sourceref to generate a signature (create)
func (o *OpenPGPSignature) Read(ctx context.Context) (yamlData []byte, err error) {
if len(o.Signature) < 1 {
if err = o.readSignatureRef(); err != nil {
panic(err)
}
}
if err = o.DecodeSignatureBlock(); err != nil {
panic(err)
}
slog.Info("OpenPGPSignature.Read() - decodesignatureblock", "sourceref", o.SourceRef, "entityList", o.entityList, "signatureblock", o.signatureBlock)
// sourceref
if _, sourceRefStatErr := o.SourceRefStat(); sourceRefStatErr != nil {
return nil, sourceRefStatErr
}
slog.Info("OpenPGPSignature.Read() - sourceref", "sourceref", o.SourceRef, "entityList", o.entityList, "signatureblock", o.signatureBlock)
if err = o.SigningEntity(); err != nil {
return nil, err
}
slog.Info("OpenPGPSignature.Read() - reading", "sourceref", o.SourceRef, "entityList", o.entityList, "signatureblock", o.signatureBlock)
slog.Info("OpenPGPSignature.Read() - reading sourceref", "openpgp-signature", o)
if sourceRefReader, sourceRefReaderErr := o.SourceRef.Reader(); sourceRefReaderErr != nil {
return nil, sourceRefReaderErr
} else {
defer sourceRefReader.Close()
if err = o.Verify(sourceRefReader); err != nil {
return nil, err
}
}
o.Common.State = "present"
slog.Info("OpenPGPSignature.Read()", "openpgp-signature", o)
return yaml.Marshal(o) return yaml.Marshal(o)
} }

View File

@ -10,21 +10,208 @@ import (
"decl/internal/data" "decl/internal/data"
"decl/internal/folio" "decl/internal/folio"
"decl/internal/codec" "decl/internal/codec"
"decl/internal/ext"
"log" "log"
"io"
"os"
"bytes"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
"log/slog"
"path/filepath"
) )
func NewTestUserKeys() (data.ResourceMapper, folio.URI) { var mockKeyRingPassphraseConfig MockConfigValueGetter = func(key string) (any, error) {
uri := "openpgp-keyring://TestUser1/TestUser1/testuser@rosskeen.house" switch key {
case "passphrase":
return "foo", nil
}
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
}
func DataKeyRing(uri folio.URI) (folio.ReferenceMapper[*folio.Declaration], folio.URI) {
var err error
var reader io.ReadCloser
searchPath := folio.NewSearchPath(folio.ConfigKey("system.importpath").GetStringSlice())
if u := uri.Parse().(*folio.ParsedURI); u != nil {
projectPath := filepath.Dir(filepath.Join(u.Hostname(), u.Path))
if err := searchPath.AddPath(projectPath); err != nil {
panic(err)
}
}
if reader, err = uri.ContentReaderStream(); err == nil {
keyRingDecl := folio.NewDeclaration()
if err = keyRingDecl.LoadReader(reader, codec.FormatYaml); err == nil {
keyRing := keyRingDecl.Resource()
testKeyRingResource := keyRing.(*OpenPGPKeyRing)
testKeyRingResource.KeyRingRef.FindIn(searchPath)
keyRing.UseConfig(mockKeyRingPassphraseConfig)
keyRing.Read(context.Background())
if testKeyRingResource.entityList[0].PrivateKey == nil {
log.Fatal("Keyring does not contain a private key")
}
testKeyRingResource.Resources = folio.NewResourceMapper()
testKeyRingResource.Resources.Set(folio.URI(keyRing.URI()), keyRingDecl)
return testKeyRingResource.Resources, folio.URI(testKeyRingResource.URI())
}
}
log.Fatal(err)
return nil, ""
}
func NewTestUserKeys(name string, comment string, email string) (folio.ReferenceMapper[*folio.Declaration], folio.URI) {
uri := fmt.Sprintf("openpgp-keyring://%s/%s/%s", name, comment, email)
keyRingDecl := folio.NewDeclaration() keyRingDecl := folio.NewDeclaration()
keyRingDecl.NewResource(&uri) keyRingDecl.NewResource(&uri)
ctx := context.Background() ctx := context.Background()
declarationAttributes := fmt.Sprintf(` declarationAttributes := fmt.Sprintf(`
name: TestUser1 name: %s
comment: TestUser1 comment: %s
email: testuser@rosskeen.house email: %s
keyringref: file://%s/keyring.asc keyringref: file://%s/keyring_%s.asc
`, string(TempDir)) `, name, comment, email, string(TempDir), name)
testKeyRing := keyRingDecl.Resource()
if e := testKeyRing.LoadString(declarationAttributes, codec.FormatYaml); e != nil {
log.Fatal(e)
}
testKeyRing.UseConfig(mockKeyRingPassphraseConfig)
if err := testKeyRing.Create(ctx); err != nil {
log.Fatal(err)
}
testKeyRingResource := testKeyRing.(*OpenPGPKeyRing)
if testKeyRingResource.entityList[0].PrivateKey == nil {
log.Fatal("Keyring does not contain a private key")
}
testKeyRingResource.Resources = folio.NewResourceMapper()
testKeyRingResource.Resources.Set(folio.URI(uri), keyRingDecl)
return testKeyRingResource.Resources, folio.URI(uri)
}
func KeyRingTestUser1() (folio.ReferenceMapper[*folio.Declaration], folio.URI) {
name := "TestUser1"
comment := "TestUser1"
email := "testuser@rosskeen.house"
if e := TempDir.CreateFile("keyring_TestUser1.asc", `
-----BEGIN PGP PRIVATE KEY BLOCK-----
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
=BAfD
-----END PGP PRIVATE KEY BLOCK-----
-----BEGIN PGP PUBLIC KEY BLOCK-----
xsBNBGhI/VUBCADDVSm3mKY5JsncMMJFV0zELMrmip7dkK3vvMvVWVmMHiC4akDH
WPxUdWNQPjE2e5HGF9Ebg0c7gu634VG470MnTzFdPV6f5zA+yJfdKrLq7gpl9QGW
jFLIeK/l4xc+MIpOE1rD9WqYUUw2IYY8YANYq4yB36rq41VuZps/adI9Go5IhfcU
3SVb7o7pa/gWE0FVu9ze31j2agC8FIKHgB++7bmYgbAQz5Qi1qgtG0Kn25QUacJ6
Akm2+h4w3SQCR6HLRV2BO29x9mFBszf2KQ7DW2VNiGyUuQQ3m8v2ZidG/11ff6U6
ad5tvr/8sYr5jOnKEJyDP9v9yQ04cU94GmsPABEBAAHNL1Rlc3RVc2VyMSAoVGVz
dFVzZXIxKSA8dGVzdHVzZXJAcm9zc2tlZW4uaG91c2U+wsCKBBMBCAA+BQJoSP1V
CRDfB5I2FtNl1xYhBADOnA8Aah3FXyl/m98HkjYW02XXAhsDAh4BAhkBAgsHAhUI
AxYAAgMnBwIAAGKLB/9whwgfLkoTYI+Q0Te2uZXl7FPyW0t4tbUSoI1aiVw+ymND
V+wuqYsjYDob2MyK/TAFkSCqZhBIRbGLRJtwzQwkF/amGuelHSSBX3LdxK/sp2UU
+zv/NBEP1LlUNKchuxpdBPYjKHdbOLPoKqFfdCujSxpWTIeKCwvnDtuP+PlsjUgc
afxMx+cVwe77AZ1Fi5CBD9Dr7nNsLovywrSiszyVh5eT0pbcu8Elf5oYQNT1q3ms
aqrw95shlGQiwVgrfTvRCDeZnKafHwMUoh48otsNI9aS0HpChi+JNd+PLe/Wlsj5
x+q0sHc+5/24zQvN0Wx9fPE4/7z8Pr+CUhAxjI7hzsBNBGhI/VUBCACfahoi2GRx
OfOqJ4ZN8Te+4bJeOQdzTofAisfCU736q+MvEKtAuY72vzWdERWpn7XABmtkfII8
Xyya8iHKuEGCETy0YoZ15GLBgSHhLnlN2U/4BkrUjGvUjZWquW22gfFB1q8ZdSGw
pg8hrspY/b+nnAGdawq+hfE6YRQTmG5tkBrctxfxglBnKzezmL1a3arTHWf2SRlL
eAWNI4sACryP/5plD6+9Z9YXTKeDlXnga6BzB80y7F2g0cPdtX20dWAGx4irq2I2
SxlABvWMZrdm2Upr6yLHD2zTpbf1FkKFUw9j0lrL1fMq0/j9zbKJsTh6CtOlF04s
AeoEV9SZ36GvABEBAAHCwHYEGAEIACoFAmhI/VUJEN8HkjYW02XXFiEEAM6cDwBq
HcVfKX+b3weSNhbTZdcCGwwAAFxWB/9Af7qpYKldDz5iNYQluFoPfyHAVlOpiakf
hvnu7hijTEgB1frByKu2m+k4m7gANBgfWldsYi1feWBVn6fspXuCc0k7opmQgol7
ueTrsGnNB7U0l14HcKSTLZ2c/Jt7ZS6aXXjxpYPq0/5lt8+5sRkXxHI9HhAx6gK/
RJCNITRt/V/gT4YWWbz8VHgkPIfHFsjr8brVhm4V/+JKZAG4eSNQp4QrTpdIjdfa
1rd91qffyrWhPNTQErE2jFOXSU/OojdDjGJ7pwr0qj4FHACVL3/oqYog2NrUWf92
QcwwWrEZNZUpJEMUB5q+tOoZ40ll0A8lsvABULZDqWveYpRQF+rQ
=jBvZ
-----END PGP PUBLIC KEY BLOCK-----
`); e != nil {
log.Fatal(e)
}
uri := fmt.Sprintf("openpgp-keyring://%s/%s/%s", name, comment, email)
keyRingDecl := folio.NewDeclaration()
keyRingDecl.NewResource(&uri)
ctx := context.Background()
declarationAttributes := fmt.Sprintf(`
name: %s
comment: %s
email: %s
keyringref: file://%s/keyring_%s.asc
`, name, comment, email, string(TempDir), name)
testKeyRing := keyRingDecl.Resource() testKeyRing := keyRingDecl.Resource()
if e := testKeyRing.LoadString(declarationAttributes, codec.FormatYaml); e != nil { if e := testKeyRing.LoadString(declarationAttributes, codec.FormatYaml); e != nil {
@ -39,96 +226,277 @@ func NewTestUserKeys() (data.ResourceMapper, folio.URI) {
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key) return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
})) }))
if err := testKeyRing.Create(ctx); err != nil { if _, err := testKeyRing.Read(ctx); err != nil {
log.Fatal(err) log.Fatal(err)
} }
return TestResourceMapper(func(key string) (data.Declaration, bool) {
return keyRingDecl, true
}), folio.URI(uri)
testKeyRingResource := testKeyRing.(*OpenPGPKeyRing)
testKeyRingResource.Resources = folio.NewResourceMapper()
if testKeyRingResource.entityList[0].PrivateKey == nil {
log.Fatal("Keyring does not contain a private key")
}
/*
return TestRefMapper[*folio.Declaration](func(key string) (*folio.Declaration, bool) {
switch key {
case uri:
slog.Info("KEYRING", "name", name, "addr", testKeyRing.(*OpenPGPKeyRing).entityList[0].PrivateKey)
return keyRingDecl, true
}
return nil, false
}), folio.URI(uri)
*/
testKeyRingResource.Resources.Set(folio.URI(uri), keyRingDecl)
return testKeyRingResource.Resources, folio.URI(uri)
}
func DataTestKeyRing(keyRingPath string) (folio.ReferenceMapper[*folio.Declaration], folio.URI) {
keyData, err := os.ReadFile(keyRingPath)
if err != nil {
panic(err)
}
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(keyData))
if err != nil {
panic(err)
}
var name, comment, email string
for _, entity := range keyring {
for _, identity := range entity.Identities {
if identity.UserId != nil {
name = identity.UserId.Name
comment = identity.UserId.Comment
email = identity.UserId.Email
}
}
}
uri := fmt.Sprintf("openpgp-keyring://%s/%s/%s", name, comment, email)
keyRingDecl := folio.NewDeclaration()
keyRingDecl.NewResource(&uri)
ctx := context.Background()
declarationAttributes := fmt.Sprintf(`
name: %s
comment: %s
email: %s
keyringref: file://%s
`, name, comment, email, keyRingPath)
testKeyRing := keyRingDecl.Resource()
if e := testKeyRing.LoadString(declarationAttributes, codec.FormatYaml); e != nil {
log.Fatal(e)
}
testKeyRing.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
switch key {
case "passphrase":
return "foo", nil
}
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
}))
if _, err := testKeyRing.Read(ctx); err != nil {
log.Fatal(err)
}
testKeyRingResource := testKeyRing.(*OpenPGPKeyRing)
testKeyRingResource.Resources = folio.NewResourceMapper()
if testKeyRingResource.entityList[0].PrivateKey == nil {
log.Fatal("Keyring does not contain a private key")
}
testKeyRingResource.Resources.Set(folio.URI(uri), keyRingDecl)
return testKeyRingResource.Resources, folio.URI(uri)
} }
func TestNewOpenPGPSignatureResource(t *testing.T) { func TestNewOpenPGPSignatureResource(t *testing.T) {
assert.NotNil(t, NewOpenPGPSignature()) assert.NotNil(t, NewOpenPGPSignature())
} }
func TestCreateSignature(t *testing.T) { func TestSigningEntity(t *testing.T) {
ctx := context.Background() m, keyRingUri := NewTestUserKeys("TestUser5", "TestUser5", "testuser5@rosskeen.house")
assert.NotNil(t, m)
assert.Equal(t, folio.URI("openpgp-keyring://TestUser5/TestUser5/testuser5@rosskeen.house"), keyRingUri)
assert.Nil(t, TempDir.CreateFile("signing-test.txt", "test data"))
assert.FileExists(t, fmt.Sprintf("%s/keyring_TestUser5.asc", TempDir))
m, keyRingUri := NewTestUserKeys() testUserKeyRingResource := folio.Dereference(keyRingUri, m)
assert.NotNil(t, testUserKeyRingResource)
assert.Nil(t, TempDir.CreateFile("test.txt", "test data")) assert.NotNil(t, testUserKeyRingResource.Resource().(*OpenPGPKeyRing).entityList[0].PrivateKey)
declarationAttributes := fmt.Sprintf(` declarationAttributes := fmt.Sprintf(`
keyringref: %s keyringref:
sourceref: file://%s/test.txt uri: %s
sourceref:
uri: file://%s/signing-test.txt
`, string(keyRingUri), string(TempDir))
testSignature := NewOpenPGPSignature()
e := testSignature.LoadString(declarationAttributes, codec.FormatYaml)
assert.Nil(t, e)
testSignature.Resources = m
testSignature.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
switch key {
case "passphrase":
return "foo", nil
}
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
}))
assert.False(t, testSignature.KeyRingRef.IsEmpty())
slog.Info("TestSigningEntity", "keyringref", testSignature.KeyRingRef, "resourcemap", testSignature.Resources)
keyRingResource := testSignature.KeyRingRef.Lookup(testSignature.Resources)
assert.NotNil(t, keyRingResource)
ringFileStream, streamErr := keyRingResource.ContentReaderStream()
assert.Nil(t, streamErr)
assert.NotNil(t, ringFileStream)
assert.Nil(t, testSignature.SigningEntity())
assert.NotNil(t, testSignature.entityList)
assert.Len(t, testSignature.entityList, 1)
assert.NotNil(t, testSignature.entityList[0].PrivateKey)
}
func TestSign(t *testing.T) {
//ctx := context.Background()
m, keyRingUri := NewTestUserKeys("TestUser2", "TestUser2", "testuser2@rosskeen.house")
assert.NotNil(t, m)
assert.Equal(t, folio.URI("openpgp-keyring://TestUser2/TestUser2/testuser2@rosskeen.house"), keyRingUri)
assert.Nil(t, TempDir.CreateFile("sign-test.txt", "test data"))
declarationAttributes := fmt.Sprintf(`
keyringref:
uri: %s
sourceref:
uri: file://%s/sign-test.txt
`, string(keyRingUri), string(TempDir)) `, string(keyRingUri), string(TempDir))
testSignature := NewOpenPGPSignature() testSignature := NewOpenPGPSignature()
testSignature.Resources = m testSignature.Resources = m
e := testSignature.LoadDecl(declarationAttributes)
assert.Nil(t, testSignature.LoadString(declarationAttributes, codec.FormatYaml))
testSignature.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
switch key {
case "passphrase":
return "foo", nil
}
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
}))
assert.Nil(t, testSignature.SigningEntity())
sourceReadStream, err := testSignature.SourceRef.Lookup(testSignature.Resources).ContentReaderStream()
var signatureStream, armoredWriter io.WriteCloser
assert.True(t, testSignature.SignatureRef.IsEmpty())
var signatureContent bytes.Buffer
signatureStream = ext.WriteNopCloser(&signatureContent)
defer func() { testSignature.Signature = signatureContent.String() }()
if armoredWriter, err = armor.Encode(signatureStream, openpgp.SignatureType, nil); err != nil {
err = fmt.Errorf("%w: %w", ErrArmoredWriterFailed, err)
}
defer armoredWriter.Close()
slog.Info("TESTSIGN KEYRING", "name", "TestUser2", "addr", testSignature.KeyRingRef.Lookup(testSignature.Resources).(*OpenPGPKeyRing))
assert.Len(t, testSignature.entityList, 1)
slog.Info("Signing Entity", "entity", testSignature.entityList[0])
assert.NotNil(t, testSignature.entityList[0].PrivateKey)
assert.Nil(t, testSignature.Sign(sourceReadStream, armoredWriter))
}
func TestCreateSignature(t *testing.T) {
ctx := context.Background()
m, keyRingUri := NewTestUserKeys("TestUser3", "TestUser3", "testuser3@rosskeen.house")
assert.NotNil(t, m)
assert.Equal(t, folio.URI("openpgp-keyring://TestUser3/TestUser3/testuser3@rosskeen.house"), keyRingUri)
assert.Nil(t, TempDir.CreateFile("test3.txt", "test data\n"))
declarationAttributes := fmt.Sprintf(`
keyringref:
uri: %s
sourceref:
uri: file://%s/test3.txt
`, string(keyRingUri), string(TempDir))
testSignature := NewOpenPGPSignature()
testSignature.Resources = m
testSignature.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
switch key {
case "passphrase":
return "foo", nil
}
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
}))
e := testSignature.LoadString(declarationAttributes, codec.FormatYaml)
assert.Nil(t, e) assert.Nil(t, e)
err := testSignature.Create(ctx) err := testSignature.Create(ctx)
assert.Nil(t, err) assert.Nil(t, err)
/*
assert.Greater(t, len(testSignature.entityList), 0) assert.Greater(t, len(testSignature.entityList), 0)
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser1 (TestUser1) <testuser@rosskeen.house>") assert.Contains(t, testSignature.entityList[0].Identities, "TestUser3 (TestUser3) <testuser3@rosskeen.house>")
assert.Contains(t, testSignature.Signature, "-----END PGP PUBLIC KEY BLOCK-----") assert.NotContains(t, testSignature.Signature, "-----END PGP PUBLIC KEY BLOCK-----")
assert.Contains(t, testSignature.Signature, "-----END PGP PRIVATE KEY BLOCK-----") assert.NotContains(t, testSignature.Signature, "-----END PGP PRIVATE KEY BLOCK-----")
*/ assert.Contains(t, testSignature.Signature, "-----END PGP SIGNATURE-----")
} }
/*
func TestReadSignature(t *testing.T) { func TestReadSignature(t *testing.T) {
ctx := context.Background() ctx := context.Background()
declarationAttributes := ` assert.Nil(t, TempDir.CreateFile("read-test.txt", "test data\n"))
signature: |-
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGNBGctCH8BDADGmdabVG6gDuSRk0razrEMEproTMT5w9zUMWH5uUeLY9eM9g5S m, keyRingUri := KeyRingTestUser1()
/5I062ume5jj6MIC1lq7tqJXh3Zwcv7Lf7ER1SWa1h6BGruHaF4o9GiR01FHfyVM assert.NotNil(t, m)
YTMTkMxFi1wnY87Mr0f+EIv1i9u2nD1o9moBXzEXT0JFFGyla8DvnblQhLhrwfNl assert.Equal(t, folio.URI("openpgp-keyring://TestUser1/TestUser1/testuser@rosskeen.house"), keyRingUri)
lN0L2LQJDTVnrPj4eMaFshqP2UdqNiYjR2qfLyCH/ZZhxg++G2KJhNzlkOzqZZJk
iYwfEUvGg/PzdCsSOYEvSureI0bF1hKBGK+RpOY0sKcvSY0xiY1YXEzJSau5cnFe declarationAttributes := fmt.Sprintf(`
/mdwC7YleZiKsGOyBsbRFn7FUXM4eM7CkDISjZMmKDBzbvzgFXsUG2upgC+B7fvi keyringref:
pTpgQxQk1Xi3+4bNQmgupJEUrP0XlIFoBVJdoTb0wcs8JUNDfc6ESZB+eA1OJdR+ uri: %s
xiag1XwN3PKcwzmoZoZ71oT/eqAOufwhWXFJ+StPqwd+BVpK1YwbG0jRagNlM/29 sourceref:
+Rzh2A70qwCcCXcAEQEAAbQwVGVzdFVzZXIgKFRlc3RVc2VyKSA8bWF0dGhld3Jp uri: file://%s/read-test.txt
Y2guY29uZkBnbWFpbC5jb20+iQHOBBMBCgA4FiEErhhqUPYtSfwcGHCb+/Evfjwu signature: |-
gEkFAmctCH8CGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQ+/EvfjwugElu -----BEGIN PGP SIGNATURE-----
cwv/ZB9xlMf8C9CBOVX8nvU1HiWvmJqlvcePobQBc7Y54pWuK+giv+/SE3bEx6x/
Vb0aWrJ52CBod6R1YfyPW+F58W9kADIPFRkH/bXExj+WMrXZU4J8Gz5nCxECK6PB iQEzBAABCgAdFiEEAM6cDwBqHcVfKX+b3weSNhbTZdcFAmhQtmMACgkQ3weSNhbT
CR8xh/T9lbvDt1q7JeP4+ldzZJoSLxAK6D5EeYTC8OKXVMuTgHBmwtiTC+Hyja3+ ZddNPAf/ZSKVOUXVlIBgXMsw2RqVWYkOzyuWADeOq6P1UHlRN0xxSmOFnHukxstL
HV1MZwx7SnnXmX5dRtPq8z1F1shoM4UTLEaolA6r3XQKwfsP9c6LS2VUc+Yft4eN zTa7KSpf5y3/DKRSTWldAy8WYFr0mx5hoDy5c83OYZo2oUtFsVYifol4JfZeF+ij
6JCz9+fa/N9bMgIS6Az23JDYJWynmmPx82Y/uqiSxXL9qljOUsgR/QK9OaLL8fFH 6a2trW17VjtKYzwpSTt/SAaTxKj/6oVevpWykOPfg6V3CCLfnVEuBMhotU91ngbg
UD6Ob+TnjH/cPBoESXrslFcwKZWMsAxJ9w6K/HJT+Fm+8XcbN3awoXcEtLAeKirL wh3R3K7LrVVJ6kYI4RKUeBIx280JZMG0sZ/AuwUdWG6KFavGMbHVzHHD2PMwT53t
z7borUsseCXJqY4epHfbvhx7NjhxElspY2A51l6oX4OoVyFL3163anxwzEEXgMRk xJ5kzWyS9ZWws8r/B33GM9szHplXulhETsd1S3x0/R2MoVcHeAaAa6JwD0y0Kxu6
+pPGlzw55cq/iN48qURetgs94Vdr4HCNJFY8+CLUyNqPQHaVXA6nUndL2wqfOqwj MuQM3twP32lK4UUqNx4oPHzU5gbirA==
82R0uQGNBGctCH8BDAC/uHoD/vw8dOQt/cHyObaAEunN3Xy2MOtpY7TRh9AdrNKY =+dVO
O0hEFQvllf8iEzW4WjiIXCzNyWzY53AD6k1kWg5tW0/6hLxk9YMUnUhi6MSD17zj -----END PGP SIGNATURE-----
QQMR8XRUNuadVh8G0INJnvXVhgJXSQmKCn+4e6e1/gYKvHq9uEYf4N1BSazlCH/e ` , string(keyRingUri), string(TempDir))
ZEhHTzI8WLtZeG+rM1wBW/3KuRrLDP9WUHamzp+0bL5OKvEhetZQZQxPr9wYccAh
bPU9MeatkAn6CwbeCOxUGUbwC0rzMVL3CPvOjhPFWGJaqi4H4ZdSSKN/vceXyfWh
CvzzJR/v0jzwJaE6sxIdIu1ylRKXN+hZ7Eqn7ZDurWgVxAH9o0jXkBNGsmZlqdRx
J+86/aGpSlNXZZO6o4xznV9Xd8ssuvwMLKN3qwVYEcbFOTdgeRw8dJo8fx4Y14tZ
RQUVPLh2iI4ykjFnBJFfOExAEKHQauLgQ6iXRsetgTb5RvUevOvIOJJTZGrqrhxt
7lHYlDfxS7zJL9ygldMAEQEAAYkBtgQYAQoAIBYhBK4YalD2LUn8HBhwm/vxL348
LoBJBQJnLQh/AhsMAAoJEPvxL348LoBJ+5oMALOv9RIyhNvyeJ4y7TLpOervh/0C
EfvIxYEVtDTFZlqfkuovhF1Cbgu+PP9iG2JU0FYHsNisf+1XSMKHX0DIm9gWWZaZ
J1CElJ4vsQ0t/4ypSrP7cZB6FokrQBcglpB9mVg0meVzCmZOJfVL+s+gCycshSZR
msw9Y3tN72JMAGdxHXtr1DTL3uDbl12Bz+egYNrqmugX9Jc9HiWG51XO9SDtztG0
KtVLcBA6X4Avc940Q4d4BofmOT4ajAAnysnR84UvTTSaAr9m/xvyKNEuS4YLysaC
gOG8nDFxujEkfO5FW+N1r5hFd2owt8Ige4e59wPRu5RVycPF3+JnxM70wFxQPkO3
lDtVTMG9vZyRkxRyKeqFo0z4msbc9WHwdvI6l/h7h2v6E6VbMe2sX/k+CxNyTPBX
sn7sjApKUjVpdXtHbu81ELhAbVPJPpMlkTdUwUUUfPD7uBoyRQbEQwgpbPQrEqmE
+aAQq8u60fWheEIG+xaV3T01zrNRUo6I7xu5kA==
=yFbn
-----END PGP PUBLIC KEY BLOCK-----
`
testSignature := NewOpenPGPSignature() testSignature := NewOpenPGPSignature()
testSignature.Resources = m
testSignature.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
switch key {
case "passphrase":
return "foo", nil
}
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
}))
e := testSignature.LoadDecl(declarationAttributes) e := testSignature.LoadDecl(declarationAttributes)
assert.Nil(t, e) assert.Nil(t, e)
y, err := testSignature.Read(ctx) y, err := testSignature.Read(ctx)
@ -136,7 +504,175 @@ func TestReadSignature(t *testing.T) {
assert.NotNil(t, y) assert.NotNil(t, y)
assert.Greater(t, len(testSignature.entityList), 0) assert.Greater(t, len(testSignature.entityList), 0)
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser (TestUser) <matthewrich.conf@gmail.com>") assert.Contains(t, testSignature.entityList[0].Identities, "TestUser1 (TestUser1) <testuser@rosskeen.house>")
}
func TestSignatureBlock(t *testing.T) {
assert.Nil(t, TempDir.CreateFile("block-test.txt", "test data\n"))
m, keyRingUri := KeyRingTestUser1()
assert.NotNil(t, m)
assert.Equal(t, folio.URI("openpgp-keyring://TestUser1/TestUser1/testuser@rosskeen.house"), keyRingUri)
declarationAttributes := fmt.Sprintf(`
keyringref:
uri: %s
sourceref:
uri: file://%s/block-test.txt
signature: |-
-----BEGIN PGP SIGNATURE-----
iQEzBAABCgAdFiEEAM6cDwBqHcVfKX+b3weSNhbTZdcFAmhQtmMACgkQ3weSNhbT
ZddNPAf/ZSKVOUXVlIBgXMsw2RqVWYkOzyuWADeOq6P1UHlRN0xxSmOFnHukxstL
zTa7KSpf5y3/DKRSTWldAy8WYFr0mx5hoDy5c83OYZo2oUtFsVYifol4JfZeF+ij
6a2trW17VjtKYzwpSTt/SAaTxKj/6oVevpWykOPfg6V3CCLfnVEuBMhotU91ngbg
wh3R3K7LrVVJ6kYI4RKUeBIx280JZMG0sZ/AuwUdWG6KFavGMbHVzHHD2PMwT53t
xJ5kzWyS9ZWws8r/B33GM9szHplXulhETsd1S3x0/R2MoVcHeAaAa6JwD0y0Kxu6
MuQM3twP32lK4UUqNx4oPHzU5gbirA==
=+dVO
-----END PGP SIGNATURE-----
` , string(keyRingUri), string(TempDir))
testSignature := NewOpenPGPSignature()
testSignature.Resources = m
testSignature.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
switch key {
case "passphrase":
return "foo", nil
}
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
}))
e := testSignature.LoadDecl(declarationAttributes)
assert.Nil(t, e)
err := testSignature.DecodeSignatureBlock()
assert.Nil(t, err)
}
func TestVerifySignature(t *testing.T) {
ctx := context.Background()
assert.Nil(t, TempDir.CreateFile("verify-test.txt", "test data\n"))
m, keyRingUri := KeyRingTestUser1()
assert.NotNil(t, m)
assert.Equal(t, folio.URI("openpgp-keyring://TestUser1/TestUser1/testuser@rosskeen.house"), keyRingUri)
declarationAttributes := fmt.Sprintf(`
keyringref:
uri: %s
sourceref:
uri: file://%s/verify-test.txt
signature: |-
-----BEGIN PGP SIGNATURE-----
iQEzBAABCgAdFiEEAM6cDwBqHcVfKX+b3weSNhbTZdcFAmhQtmMACgkQ3weSNhbT
ZddNPAf/ZSKVOUXVlIBgXMsw2RqVWYkOzyuWADeOq6P1UHlRN0xxSmOFnHukxstL
zTa7KSpf5y3/DKRSTWldAy8WYFr0mx5hoDy5c83OYZo2oUtFsVYifol4JfZeF+ij
6a2trW17VjtKYzwpSTt/SAaTxKj/6oVevpWykOPfg6V3CCLfnVEuBMhotU91ngbg
wh3R3K7LrVVJ6kYI4RKUeBIx280JZMG0sZ/AuwUdWG6KFavGMbHVzHHD2PMwT53t
xJ5kzWyS9ZWws8r/B33GM9szHplXulhETsd1S3x0/R2MoVcHeAaAa6JwD0y0Kxu6
MuQM3twP32lK4UUqNx4oPHzU5gbirA==
=+dVO
-----END PGP SIGNATURE-----
` , string(keyRingUri), string(TempDir))
testSignature := NewOpenPGPSignature()
testSignature.Resources = m
testSignature.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
switch key {
case "passphrase":
return "foo", nil
}
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
}))
e := testSignature.LoadDecl(declarationAttributes)
assert.Nil(t, e)
y, err := testSignature.Read(ctx)
assert.Nil(t, err)
assert.NotNil(t, y)
assert.Greater(t, len(testSignature.entityList), 0)
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser1 (TestUser1) <testuser@rosskeen.house>")
assert.Equal(t, "DF07923616D365D7", fmt.Sprintf("%X", testSignature.entityList[0].PrimaryKey.KeyId))
sourceReadStream, sourceReadStreamErr := testSignature.SourceRef.ContentReaderStream()
assert.Nil(t, sourceReadStreamErr)
assert.Nil(t, testSignature.DecodeSignatureBlock())
assert.Nil(t, testSignature.Verify(sourceReadStream))
sourceReadStream.Close()
assert.Nil(t, testSignature.DecodeSignatureBlock())
sourceReadStream2, sourceReadStreamErr2 := testSignature.SourceRef.ContentReaderStream()
assert.Nil(t, sourceReadStreamErr2)
assert.Nil(t, testSignature.Verify(sourceReadStream2))
}
func TestVerifyDocumentSignature(t *testing.T) {
ctx := context.Background()
assert.Nil(t, TempDir.CreateFile("verify-test.txt", "test data\n"))
m, keyRingUri := DataKeyRing("file://../../tests/data/openpgp-keyring/keyring_jx.yaml")
assert.NotNil(t, m)
assert.Equal(t, folio.URI("openpgp-keyring://TestUser1/TestUser1/testuser@rosskeen.house"), keyRingUri)
// load a document
docURI := folio.URI("file://../../examples/signed/ubuntu-dev-read.jx.yaml")
r, readerErr := docURI.ContentReaderStream()
assert.Nil(t, readerErr)
assert.NotNil(t, r)
doc := folio.DocumentRegistry.NewDocument(docURI)
assert.Nil(t, doc.LoadReader(r, codec.FormatYaml))
declarationAttributes := fmt.Sprintf(`
keyringref:
uri: %s
sourceref:
uri: %s
type: document
` , string(keyRingUri), string(docURI))
testSignature := NewOpenPGPSignature()
testSignature.Resources = m
testSignature.UseConfig(mockKeyRingPassphraseConfig)
e := testSignature.LoadDecl(declarationAttributes)
assert.Nil(t, e)
createErr := testSignature.Create(ctx)
assert.Nil(t, createErr)
assert.Greater(t, len(testSignature.entityList), 0)
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser1 (TestUser1) <testuser@rosskeen.house>")
y, err := testSignature.Read(ctx) // XXX throws an error if no signature is present
assert.Nil(t, err)
assert.NotNil(t, y)
assert.Greater(t, len(testSignature.entityList), 0)
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser1 (TestUser1) <testuser@rosskeen.house>")
assert.Equal(t, "FAED7F3BB05EF687", fmt.Sprintf("%X", testSignature.entityList[0].PrimaryKey.KeyId))
sourceReadStream, sourceReadStreamErr := testSignature.SourceRef.Reader()
assert.Nil(t, sourceReadStreamErr)
assert.Nil(t, testSignature.DecodeSignatureBlock())
assert.Nil(t, testSignature.Verify(sourceReadStream))
sourceReadStream.Close()
assert.Nil(t, testSignature.DecodeSignatureBlock())
sourceReadStream2, sourceReadStreamErr2 := testSignature.SourceRef.Reader()
assert.Nil(t, sourceReadStreamErr2)
assert.Nil(t, testSignature.Verify(sourceReadStream2))
} }
*/

View File

@ -12,32 +12,15 @@ _ "gopkg.in/yaml.v3"
_ "log" _ "log"
_ "os" _ "os"
"decl/internal/transport" "decl/internal/transport"
"decl/internal/ext"
"decl/internal/codec" "decl/internal/codec"
"decl/internal/data" "decl/internal/data"
"decl/internal/folio" "decl/internal/folio"
"gitea.rosskeen.house/rosskeen.house/machine"
"strings" "strings"
"testing" "testing"
"path/filepath" "path/filepath"
) )
type TestResourceMapper func(key string) (data.Declaration, bool)
func (rm TestResourceMapper) Get(key string) (data.Declaration, bool) {
return rm(key)
}
func (rm TestResourceMapper) Has(key string) (ok bool) {
_, ok = rm(key)
return
}
func (rm TestResourceMapper) Set(key string, value data.Declaration) {
}
func (rm TestResourceMapper) Delete(key string) {
}
type StringContentReadWriter func() (any, error) type StringContentReadWriter func() (any, error)
func (s StringContentReadWriter) ContentWriterStream() (*transport.Writer, error) { func (s StringContentReadWriter) ContentWriterStream() (*transport.Writer, error) {
@ -62,8 +45,28 @@ func (s StringContentReadWriter) SetParsedURI(uri data.URIParser) (error) { retu
func (s StringContentReadWriter) URI() (string) { return "" } func (s StringContentReadWriter) URI() (string) { return "" }
func (s StringContentReadWriter) Validate() (error) { return nil } func (s StringContentReadWriter) Validate() (error) { return nil }
func (s StringContentReadWriter) ResourceType() data.TypeName { return "" } func (s StringContentReadWriter) ResourceType() data.TypeName { return "" }
func (s StringContentReadWriter) Resource() data.Resource { return nil } func (s StringContentReadWriter) Resource() data.Resource { return stringContentReadWriterResource { StringContentReadWriter: s } }
type stringContentReadWriterResource struct {
StringContentReadWriter
}
//URI() string { return "" }
//SetParsedURI(URIParser) error { return nil }
func (s stringContentReadWriterResource) Type() string { return "buffer" }
func (s stringContentReadWriterResource) StateMachine() machine.Stater { return nil }
func (s stringContentReadWriterResource) UseConfig(config data.ConfigurationValueGetter) { return }
//ResolveId(context.Context) string { return }
//LoadString(string, codec.Format) (error) { return }
//Load([]byte, codec.Format) (error) { return }
//LoadReader(io.ReadCloser, codec.Format) (error) { return }
func (s stringContentReadWriterResource) Apply() error { return nil }
func (s stringContentReadWriterResource) Create(context.Context) error { return nil }
func (s stringContentReadWriterResource) Read(context.Context) ([]byte, error) { return nil, nil }
func (s stringContentReadWriterResource) Update(context.Context) error { return nil }
func (s stringContentReadWriterResource) Delete(context.Context) error { return nil }
//Validate() error { return }
func (s stringContentReadWriterResource) Clone() data.Resource { return nil }
func (s stringContentReadWriterResource) SetResourceMapper(folio.ResourceMapper) { return }
func TestNewPKIKeysResource(t *testing.T) { func TestNewPKIKeysResource(t *testing.T) {
r := NewPKI() r := NewPKI()
@ -90,29 +93,19 @@ func TestPKIEncodeKeys(t *testing.T) {
r.PublicKey() r.PublicKey()
assert.NotNil(t, r.publicKey) assert.NotNil(t, r.publicKey)
r.Resources = TestResourceMapper(func(key string) (data.Declaration, bool) { r.Resources = folio.NewResourceMapper()
switch key {
case "buffer://privatekey": privateKeyBuffer := folio.NewDeclaration()
return StringContentReadWriter(func() (any, error) { privateKeyBuffer.Attributes = NewMockBufferResource("privatekey", &privateTarget)
w := &transport.Writer{} r.Resources.Set("buffer://privatekey", privateKeyBuffer)
w.SetStream(ext.WriteNopCloser(&privateTarget))
return w, nil publicKeyBuffer := folio.NewDeclaration()
}), true publicKeyBuffer.Attributes = NewMockBufferResource("publickey", &publicTarget)
case "buffer://publickey": r.Resources.Set("buffer://publickey", publicKeyBuffer)
return StringContentReadWriter(func() (any, error) {
w := &transport.Writer{} certKeyBuffer := folio.NewDeclaration()
w.SetStream(ext.WriteNopCloser(&publicTarget)) certKeyBuffer.Attributes = NewMockBufferResource("certificate", &certTarget)
return w, nil r.Resources.Set("buffer://certificate", certKeyBuffer)
}), true
case "buffer://certificate":
return StringContentReadWriter(func() (any, error) {
w := &transport.Writer{}
w.SetStream(ext.WriteNopCloser(&certTarget))
return w, nil
}), true
}
return nil, false
})
r.PrivateKeyRef = folio.ResourceReference("buffer://privatekey") r.PrivateKeyRef = folio.ResourceReference("buffer://privatekey")
r.PublicKeyRef = folio.ResourceReference("buffer://publickey") r.PublicKeyRef = folio.ResourceReference("buffer://publickey")