add ref attribute type
This commit is contained in:
parent
fb544a455c
commit
2d4234c6a0
13
internal/data/originator.go
Normal file
13
internal/data/originator.go
Normal 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
|
||||
}
|
@ -38,7 +38,6 @@ type Resource interface {
|
||||
Crudder
|
||||
Validator
|
||||
Clone() Resource
|
||||
SetResourceMapper(ResourceMapper)
|
||||
}
|
||||
|
||||
type Declaration interface {
|
||||
|
@ -73,7 +73,7 @@ func (d *Declaration) SetDocument(newDocument *Document) {
|
||||
d.document = newDocument
|
||||
d.SetConfig(d.document.config)
|
||||
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 {
|
||||
@ -209,6 +209,11 @@ func (d *Declaration) Apply(stateTransition string) (result error) {
|
||||
return result
|
||||
}
|
||||
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:
|
||||
return fmt.Errorf("%w: %s on %s", ErrUnknownStateTransition, stateTransition, d.Attributes.URI())
|
||||
case "create", "present":
|
||||
@ -249,7 +254,7 @@ func (d *Declaration) SetConfig(configDoc data.Document) {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (d *Declaration) GetContentReadWriter() data.ContentReadWriter {
|
||||
return d.Resource().(data.ContentReadWriter)
|
||||
}
|
||||
|
||||
/*
|
||||
func (l *LuaWorker) Receive(m message.Envelope) {
|
||||
s := m.Sender()
|
||||
|
@ -37,7 +37,7 @@ type Document struct {
|
||||
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
||||
Imports []URI `json:"imports,omitempty" yaml:"imports,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"`
|
||||
configNames mapper.Store[string, data.Block] `json:"-" yaml:"-"`
|
||||
Configurations []*Block `json:"configurations,omitempty" yaml:"configurations,omitempty"`
|
||||
@ -57,7 +57,7 @@ func NewDocument(r *Registry) *Document {
|
||||
return &Document{
|
||||
Registry: r,
|
||||
Format: codec.FormatYaml,
|
||||
uris: mapper.New[string, data.Declaration](),
|
||||
uris: mapper.New[URI, *Declaration](),
|
||||
configNames: mapper.New[string, data.Block](),
|
||||
importPaths: NewSearchPath(configImportPath.GetStringSlice()),
|
||||
}
|
||||
@ -111,24 +111,24 @@ func (d *Document) Filter(filter data.DeclarationSelector) []data.Declaration {
|
||||
}
|
||||
|
||||
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) {
|
||||
return d.uris.Get(key)
|
||||
return d.uris.Get(URI(key))
|
||||
}
|
||||
|
||||
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) {
|
||||
d.uris.Delete(key)
|
||||
d.uris.Delete(URI(key))
|
||||
}
|
||||
|
||||
func (d *Document) GetResource(uri string) *Declaration {
|
||||
if decl, ok := d.uris[uri]; ok {
|
||||
return decl.(*Declaration)
|
||||
if decl, ok := d.uris[URI(uri)]; ok {
|
||||
return decl
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -236,17 +236,17 @@ func (d *Document) GetSchemaFiles() (schemaFs fs.FS) {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (d *Document) Validate() error {
|
||||
jsonDocument, jsonErr := d.JSON()
|
||||
slog.Info("Document.Validate() json", "err", jsonErr)
|
||||
slog.Info("Document.Validate() convert to json", "err", jsonErr)
|
||||
if jsonErr == nil {
|
||||
s := schema.New("document", d.GetSchemaFiles())
|
||||
err := s.Validate(string(jsonDocument))
|
||||
slog.Info("Document.Validate()", "error", err)
|
||||
slog.Info("Document.Validate() validate schema", "error", err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -356,12 +356,12 @@ func (d *Document) MapConfigurationURI(uri string, block data.Block) {
|
||||
}
|
||||
*/
|
||||
|
||||
func (d *Document) MapResourceURI(uri string, declaration data.Declaration) {
|
||||
d.uris[uri] = declaration
|
||||
func (d *Document) MapResourceURI(uri string, declaration *Declaration) {
|
||||
d.uris[URI(uri)] = declaration
|
||||
}
|
||||
|
||||
func (d *Document) UnMapResourceURI(uri string) {
|
||||
d.uris.Delete(uri)
|
||||
d.uris.Delete(URI(uri))
|
||||
}
|
||||
|
||||
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.MapResourceURI(uri, declaration)
|
||||
d.MapResourceURI(uri, decl)
|
||||
decl.SetDocument(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 {
|
||||
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) {
|
||||
type decodeDocument Document
|
||||
/*
|
||||
t := &DocumentType{}
|
||||
if unmarshalDocumentErr := value.Decode(t); unmarshalDocumentErr != nil {
|
||||
return unmarshalDocumentErr
|
||||
}
|
||||
|
||||
*/
|
||||
if unmarshalResourcesErr := value.Decode((*decodeDocument)(d)); unmarshalResourcesErr != nil {
|
||||
return unmarshalResourcesErr
|
||||
}
|
||||
err = d.loadImports()
|
||||
d.assignConfigurationsDocument()
|
||||
d.assignResourcesDocument()
|
||||
err = d.UnmarshalLoadDependencies()
|
||||
return
|
||||
}
|
||||
|
||||
@ -622,12 +638,39 @@ func (d *Document) UnmarshalJSON(data []byte) (err error) {
|
||||
if unmarshalDocumentErr := json.Unmarshal(data, t); unmarshalDocumentErr != nil {
|
||||
return unmarshalDocumentErr
|
||||
}
|
||||
err = d.loadImports()
|
||||
d.assignConfigurationsDocument()
|
||||
d.assignResourcesDocument()
|
||||
err = d.UnmarshalLoadDependencies()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Document) AddError(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)
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ _ "gopkg.in/yaml.v3"
|
||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||
"decl/internal/codec"
|
||||
"decl/internal/data"
|
||||
"decl/internal/transport"
|
||||
"io"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
@ -89,9 +90,10 @@ func NewMockResource(typename string, stater machine.Stater) (m *MockResource) {
|
||||
InjectUpdate: func(context.Context) error { return nil },
|
||||
InjectDelete: func(context.Context) error { return nil },
|
||||
InjectUseConfig: func(data.ConfigurationValueGetter) {},
|
||||
InjectSetResourceMapper: func(data.ResourceMapper) {},
|
||||
InjectSetResourceMapper: func(ResourceMapper) {},
|
||||
InjectURI: func() string { return fmt.Sprintf("%s://bar", typename) },
|
||||
InjectNotify: func(*machine.EventMessage) {},
|
||||
InjectContentReaderStream: func() (*transport.Reader, error) { return nil, nil },
|
||||
}
|
||||
m.InjectInit = func(u data.URIParser) error {
|
||||
if u != nil {
|
||||
|
@ -11,6 +11,7 @@ _ "fmt"
|
||||
"decl/internal/data"
|
||||
"decl/internal/codec"
|
||||
"io"
|
||||
"decl/internal/transport"
|
||||
)
|
||||
|
||||
type MockResource struct {
|
||||
@ -34,9 +35,11 @@ type MockResource struct {
|
||||
InjectUpdate func(context.Context) error `json:"-" yaml:"-"`
|
||||
InjectDelete func(context.Context) error `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:"-"`
|
||||
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 {
|
||||
@ -67,7 +70,7 @@ func (m *MockResource) ResolveId(ctx context.Context) string {
|
||||
return m.InjectResolveId(ctx)
|
||||
}
|
||||
|
||||
func (m *MockResource) SetResourceMapper(rm data.ResourceMapper) {
|
||||
func (m *MockResource) SetResourceMapper(rm ResourceMapper) {
|
||||
m.InjectSetResourceMapper(rm)
|
||||
}
|
||||
|
||||
@ -139,6 +142,14 @@ func (m *MockResource) Notify(em *machine.EventMessage) {
|
||||
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 {
|
||||
if err := json.Unmarshal(data, m); err != nil {
|
||||
return err
|
||||
|
229
internal/folio/ref.go
Normal file
229
internal/folio/ref.go
Normal 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)
|
||||
}
|
67
internal/folio/ref_test.go
Normal file
67
internal/folio/ref_test.go
Normal 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))
|
||||
}
|
@ -60,7 +60,8 @@ func (r *Registry) HasDocument(key URI) 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) {
|
||||
@ -72,7 +73,7 @@ func (r *Registry) NewDocument(uri URI) (doc *Document) {
|
||||
doc.SetURI(string(uri))
|
||||
r.Documents = append(r.Documents, doc)
|
||||
if uri != "" {
|
||||
r.UriMap[uri] = doc
|
||||
r.UriMap.Set(uri, doc)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ package folio
|
||||
import (
|
||||
"decl/internal/transport"
|
||||
"decl/internal/data"
|
||||
"decl/internal/mapper"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
@ -31,25 +32,26 @@ type ContentReadWriter interface {
|
||||
type ResourceReference URI
|
||||
|
||||
// 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) == "" {
|
||||
return nil
|
||||
}
|
||||
slog.Info("ResourceReference.Lookup()", "resourcereference", r, "resourcemapper", look)
|
||||
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 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)
|
||||
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)
|
||||
return v.(*Declaration).Attributes
|
||||
return v.Attributes
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -74,3 +76,7 @@ func (r ResourceReference) ContentWriterStream() (*transport.Writer, error) {
|
||||
func (r ResourceReference) IsEmpty() bool {
|
||||
return URI(r).IsEmpty()
|
||||
}
|
||||
|
||||
func (r *ResourceReference) FindIn(s *SearchPath) {
|
||||
(*URI)(r).FindIn(s)
|
||||
}
|
||||
|
@ -6,19 +6,18 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"fmt"
|
||||
"decl/internal/data"
|
||||
"decl/internal/mapper"
|
||||
)
|
||||
|
||||
func TestResourceReference(t *testing.T) {
|
||||
f := NewFooResource()
|
||||
resourceMapper := mapper.New[string, data.Declaration]()
|
||||
resourceMapper := mapper.New[URI, *Declaration]()
|
||||
f.Name = string(TempDir)
|
||||
f.Size = 10
|
||||
d := NewDeclaration()
|
||||
d.Type = "foo"
|
||||
d.Attributes = f
|
||||
resourceMapper[d.URI()] = d
|
||||
resourceMapper[URI(d.URI())] = d
|
||||
|
||||
var foo ResourceReference = ResourceReference(fmt.Sprintf("foo://%s", string(TempDir)))
|
||||
u := foo.Parse()
|
||||
|
@ -7,8 +7,8 @@
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "foo name",
|
||||
"minLength": 1
|
||||
"description": "foo name",
|
||||
"minLength": 1
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
|
17
internal/folio/schemas/ref.schema.json
Normal file
17
internal/folio/schemas/ref.schema.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
@ -214,8 +214,8 @@ attributes:
|
||||
_, readErr := contextFile.Resource().Read(context.Background())
|
||||
assert.Nil(t, readErr)
|
||||
|
||||
c.Resources = data.NewResourceMapper()
|
||||
c.Resources.Set(contextDirUri, contextFile)
|
||||
c.Resources = folio.NewResourceMapper()
|
||||
c.Resources.Set(folio.URI(contextDirUri), contextFile)
|
||||
|
||||
d, contextErr := c.ContextDocument()
|
||||
assert.Nil(t, contextErr)
|
||||
|
@ -5,6 +5,7 @@ package resource
|
||||
import (
|
||||
"context"
|
||||
"decl/tests/mocks"
|
||||
"decl/internal/codec"
|
||||
_ "encoding/json"
|
||||
_ "fmt"
|
||||
"github.com/docker/docker/api/types"
|
||||
@ -20,6 +21,7 @@ _ "os"
|
||||
"strings"
|
||||
"testing"
|
||||
"bytes"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewContainerResource(t *testing.T) {
|
||||
@ -47,6 +49,9 @@ func TestReadContainer(t *testing.T) {
|
||||
ID: "123456789abc",
|
||||
Name: "test",
|
||||
Image: "alpine",
|
||||
State: &types.ContainerState{
|
||||
Status: "running",
|
||||
},
|
||||
}}, nil
|
||||
},
|
||||
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)
|
||||
assert.NotEqual(t, nil, c)
|
||||
assert.NotNil(t, c)
|
||||
|
||||
e := c.LoadDecl(decl)
|
||||
assert.Equal(t, nil, e)
|
||||
assert.Nil(t, e)
|
||||
assert.Equal(t, "testcontainer", c.Name)
|
||||
|
||||
resourceYaml, readContainerErr := c.Read(ctx)
|
||||
assert.Equal(t, nil, readContainerErr)
|
||||
assert.Nil(t, readContainerErr)
|
||||
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{
|
||||
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
|
||||
@ -148,3 +154,119 @@ func TestContainerLogOutput(t *testing.T) {
|
||||
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())
|
||||
}
|
||||
|
@ -35,7 +35,16 @@ func TestExecApplyResourceTransformation(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) {
|
||||
|
34
internal/resource/mock_buffer_resource_test.go
Normal file
34
internal/resource/mock_buffer_resource_test.go
Normal 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
|
||||
},
|
||||
}
|
||||
}
|
@ -9,25 +9,32 @@ _ "gopkg.in/yaml.v3"
|
||||
_ "fmt"
|
||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||
"decl/internal/data"
|
||||
"decl/internal/codec"
|
||||
"decl/internal/transport"
|
||||
"io"
|
||||
)
|
||||
|
||||
type MockResource struct {
|
||||
InjectURI func() string
|
||||
InjectType func() string
|
||||
InjectResolveId func(ctx context.Context) string
|
||||
InjectLoadDecl func(string) error
|
||||
InjectValidate func() error
|
||||
InjectApply func() error
|
||||
InjectRead func(context.Context) ([]byte, error)
|
||||
InjectStateMachine func() machine.Stater
|
||||
*Common `json:",inline" yaml:",inline"`
|
||||
InjectURI func() string
|
||||
InjectType func() string
|
||||
InjectResolveId func(ctx context.Context) string
|
||||
InjectLoadDecl func(string) error
|
||||
InjectValidate func() error
|
||||
InjectApply func() error
|
||||
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 {
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockResource) StateMachine() machine.Stater {
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockResource) SetURI(uri string) error {
|
||||
@ -35,15 +42,15 @@ func (m *MockResource) SetURI(uri string) error {
|
||||
}
|
||||
|
||||
func (m *MockResource) URI() string {
|
||||
return m.InjectURI()
|
||||
return m.InjectURI()
|
||||
}
|
||||
|
||||
func (m *MockResource) ResolveId(ctx context.Context) string {
|
||||
return m.InjectResolveId(ctx)
|
||||
return m.InjectResolveId(ctx)
|
||||
}
|
||||
|
||||
func (m *MockResource) LoadDecl(yamlResourceDeclaration string) error {
|
||||
return m.InjectLoadDecl(yamlResourceDeclaration)
|
||||
return m.InjectLoadDecl(yamlResourceDeclaration)
|
||||
}
|
||||
|
||||
func (m *MockResource) Validate() error {
|
||||
@ -51,20 +58,81 @@ func (m *MockResource) Validate() error {
|
||||
}
|
||||
|
||||
func (m *MockResource) Apply() error {
|
||||
return m.InjectApply()
|
||||
return m.InjectApply()
|
||||
}
|
||||
|
||||
func (m *MockResource) Read(ctx context.Context) ([]byte, error) {
|
||||
return m.InjectRead(ctx)
|
||||
return m.InjectRead(ctx)
|
||||
}
|
||||
|
||||
func (m *MockResource) Type() string {
|
||||
return m.InjectType()
|
||||
return m.InjectType()
|
||||
}
|
||||
|
||||
func (m *MockResource) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, m); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
if err := json.Unmarshal(data, m); err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -27,8 +27,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSignatureFailedDecodingSignature error = errors.New("Failed decoding signature")
|
||||
ErrSignatureWriterFailed error = errors.New("Failed creating signature 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 (
|
||||
@ -48,14 +51,17 @@ func init() {
|
||||
}
|
||||
|
||||
type OpenPGPSignature struct {
|
||||
*Common `json:",inline" yaml:",inline"`
|
||||
stater machine.Stater `json:"-" yaml:"-"`
|
||||
Signature string `json:"signature,omitempty" yaml:"signature,omitempty"`
|
||||
KeyRingRef folio.ResourceReference `json:"keyringref,omitempty" yaml:"keyringref,omitempty"`
|
||||
SourceRef folio.ResourceReference `json:"soureref,omitempty" yaml:"sourceref,omitempty"`
|
||||
SignatureRef folio.ResourceReference `json:"signatureref,omitempty" yaml:"signatureref,omitempty"`
|
||||
*Common `json:"-,inline" yaml:"-,inline"`
|
||||
stater machine.Stater `json:"-" yaml:"-"`
|
||||
Signature string `json:"signature,omitempty" yaml:"signature,omitempty"`
|
||||
Signed string `json:"signed,omitempty" yaml:"signed,omitempty"`
|
||||
KeyRingRef folio.Ref `json:"keyringref,omitempty" yaml:"keyringref,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
|
||||
|
||||
entityList openpgp.EntityList
|
||||
}
|
||||
|
||||
@ -90,41 +96,94 @@ func (o *OpenPGPSignature) StateMachine() machine.Stater {
|
||||
}
|
||||
|
||||
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) {
|
||||
if o.KeyRingRef.IsEmpty() {
|
||||
var keyringConfig any
|
||||
if keyringConfig, err = o.config.GetValue("keyring"); err == nil {
|
||||
o.entityList = keyringConfig.(openpgp.EntityList)
|
||||
if len(o.entityList) < 1 {
|
||||
slog.Info("OpenPGPSignature.SigningEntity() - Loading KeyRing", "keyring", o.KeyRingRef)
|
||||
if o.KeyRingRef.IsEmpty() {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
func (o *OpenPGPSignature) Sign(message io.Reader, w io.Writer) (err error) {
|
||||
var writer io.WriteCloser
|
||||
entity := o.entityList[0]
|
||||
|
||||
if writer, err = openpgp.Sign(w, entity, nil, o.Config()); err == nil {
|
||||
defer writer.Close()
|
||||
_, err = io.Copy(writer, message)
|
||||
} else {
|
||||
if err = openpgp.DetachSign(w, entity, message, o.Config()); err != nil {
|
||||
err = fmt.Errorf("%w: %w", ErrSignatureWriterFailed, err)
|
||||
}
|
||||
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) {
|
||||
if err = o.SigningEntity(); err == nil {
|
||||
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
|
||||
|
||||
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 {
|
||||
err = fmt.Errorf("%w: %w", ErrArmoredWriterFailed, err)
|
||||
}
|
||||
defer armoredWriter.Close()
|
||||
|
||||
err = o.Sign(sourceReadStream, armoredWriter)
|
||||
armoredWriter.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -196,8 +255,10 @@ func (o *OpenPGPSignature) Notify(m *machine.EventMessage) {
|
||||
_ = o.AddError(triggerErr)
|
||||
}
|
||||
} else {
|
||||
slog.Info("OpenPGPSignature.Notify()", "dest", "start_read", "err", readErr)
|
||||
_ = o.AddError(readErr)
|
||||
if o.IsResourceInconsistent() {
|
||||
slog.Info("OpenPGPSignature.Notify()", "dest", "read-failed", "machine", o.StateMachine())
|
||||
if triggerErr := o.StateMachine().Trigger("read-failed"); triggerErr == nil {
|
||||
panic(readErr)
|
||||
} else {
|
||||
@ -320,16 +381,16 @@ func (o *OpenPGPSignature) ResolveId(ctx context.Context) string {
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) GetContentSourceRef() string {
|
||||
return string(o.SignatureRef)
|
||||
return o.SignatureRef.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) {
|
||||
err = fmt.Errorf("%w: %s", ErrResourceStateAbsent, o.SignatureRef)
|
||||
if len(o.SignatureRef) > 0 {
|
||||
err = fmt.Errorf("%w: %s", ErrResourceStateAbsent, o.SignatureRef.Uri)
|
||||
if len(o.SignatureRef.Uri) > 0 {
|
||||
rs, _ := o.ContentReaderStream()
|
||||
defer rs.Close()
|
||||
return rs.Stat()
|
||||
@ -346,6 +407,16 @@ func (o *OpenPGPSignature) ReadStat() (err error) {
|
||||
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 {
|
||||
return o.Create(ctx)
|
||||
}
|
||||
@ -354,7 +425,69 @@ func (o *OpenPGPSignature) Delete(ctx context.Context) error {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -10,21 +10,208 @@ import (
|
||||
"decl/internal/data"
|
||||
"decl/internal/folio"
|
||||
"decl/internal/codec"
|
||||
"decl/internal/ext"
|
||||
"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) {
|
||||
uri := "openpgp-keyring://TestUser1/TestUser1/testuser@rosskeen.house"
|
||||
var mockKeyRingPassphraseConfig MockConfigValueGetter = func(key string) (any, error) {
|
||||
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.NewResource(&uri)
|
||||
|
||||
ctx := context.Background()
|
||||
declarationAttributes := fmt.Sprintf(`
|
||||
name: TestUser1
|
||||
comment: TestUser1
|
||||
email: testuser@rosskeen.house
|
||||
keyringref: file://%s/keyring.asc
|
||||
`, string(TempDir))
|
||||
name: %s
|
||||
comment: %s
|
||||
email: %s
|
||||
keyringref: file://%s/keyring_%s.asc
|
||||
`, 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()
|
||||
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)
|
||||
}))
|
||||
|
||||
if err := testKeyRing.Create(ctx); err != nil {
|
||||
if _, err := testKeyRing.Read(ctx); err != nil {
|
||||
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) {
|
||||
assert.NotNil(t, NewOpenPGPSignature())
|
||||
}
|
||||
|
||||
func TestCreateSignature(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
func TestSigningEntity(t *testing.T) {
|
||||
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()
|
||||
|
||||
assert.Nil(t, TempDir.CreateFile("test.txt", "test data"))
|
||||
testUserKeyRingResource := folio.Dereference(keyRingUri, m)
|
||||
assert.NotNil(t, testUserKeyRingResource)
|
||||
assert.NotNil(t, testUserKeyRingResource.Resource().(*OpenPGPKeyRing).entityList[0].PrivateKey)
|
||||
|
||||
declarationAttributes := fmt.Sprintf(`
|
||||
keyringref: %s
|
||||
sourceref: file://%s/test.txt
|
||||
keyringref:
|
||||
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))
|
||||
|
||||
testSignature := NewOpenPGPSignature()
|
||||
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)
|
||||
|
||||
err := testSignature.Create(ctx)
|
||||
assert.Nil(t, err)
|
||||
/*
|
||||
|
||||
assert.Greater(t, len(testSignature.entityList), 0)
|
||||
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser1 (TestUser1) <testuser@rosskeen.house>")
|
||||
assert.Contains(t, testSignature.Signature, "-----END PGP PUBLIC KEY BLOCK-----")
|
||||
assert.Contains(t, testSignature.Signature, "-----END PGP PRIVATE KEY BLOCK-----")
|
||||
*/
|
||||
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser3 (TestUser3) <testuser3@rosskeen.house>")
|
||||
assert.NotContains(t, testSignature.Signature, "-----END PGP PUBLIC 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) {
|
||||
ctx := context.Background()
|
||||
|
||||
declarationAttributes := `
|
||||
assert.Nil(t, TempDir.CreateFile("read-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/read-test.txt
|
||||
signature: |-
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
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
|
||||
+aAQq8u60fWheEIG+xaV3T01zrNRUo6I7xu5kA==
|
||||
=yFbn
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
`
|
||||
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)
|
||||
@ -136,7 +504,175 @@ func TestReadSignature(t *testing.T) {
|
||||
assert.NotNil(t, y)
|
||||
|
||||
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))
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
|
@ -12,32 +12,15 @@ _ "gopkg.in/yaml.v3"
|
||||
_ "log"
|
||||
_ "os"
|
||||
"decl/internal/transport"
|
||||
"decl/internal/ext"
|
||||
"decl/internal/codec"
|
||||
"decl/internal/data"
|
||||
"decl/internal/folio"
|
||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||
"strings"
|
||||
"testing"
|
||||
"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)
|
||||
|
||||
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) Validate() (error) { return nil }
|
||||
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) {
|
||||
r := NewPKI()
|
||||
@ -90,29 +93,19 @@ func TestPKIEncodeKeys(t *testing.T) {
|
||||
r.PublicKey()
|
||||
assert.NotNil(t, r.publicKey)
|
||||
|
||||
r.Resources = TestResourceMapper(func(key string) (data.Declaration, bool) {
|
||||
switch key {
|
||||
case "buffer://privatekey":
|
||||
return StringContentReadWriter(func() (any, error) {
|
||||
w := &transport.Writer{}
|
||||
w.SetStream(ext.WriteNopCloser(&privateTarget))
|
||||
return w, nil
|
||||
}), true
|
||||
case "buffer://publickey":
|
||||
return StringContentReadWriter(func() (any, error) {
|
||||
w := &transport.Writer{}
|
||||
w.SetStream(ext.WriteNopCloser(&publicTarget))
|
||||
return w, nil
|
||||
}), 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.Resources = folio.NewResourceMapper()
|
||||
|
||||
privateKeyBuffer := folio.NewDeclaration()
|
||||
privateKeyBuffer.Attributes = NewMockBufferResource("privatekey", &privateTarget)
|
||||
r.Resources.Set("buffer://privatekey", privateKeyBuffer)
|
||||
|
||||
publicKeyBuffer := folio.NewDeclaration()
|
||||
publicKeyBuffer.Attributes = NewMockBufferResource("publickey", &publicTarget)
|
||||
r.Resources.Set("buffer://publickey", publicKeyBuffer)
|
||||
|
||||
certKeyBuffer := folio.NewDeclaration()
|
||||
certKeyBuffer.Attributes = NewMockBufferResource("certificate", &certTarget)
|
||||
r.Resources.Set("buffer://certificate", certKeyBuffer)
|
||||
|
||||
r.PrivateKeyRef = folio.ResourceReference("buffer://privatekey")
|
||||
r.PublicKeyRef = folio.ResourceReference("buffer://publickey")
|
||||
|
Loading…
Reference in New Issue
Block a user