add resource and document constraints
This commit is contained in:
parent
a38bd8a4d7
commit
6e0049c4d2
@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
@ -95,6 +96,15 @@ func (b *Block) NewConfiguration(uri *string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Block) NewConfigurationFromParsedURI(uri *url.URL) (err error) {
|
||||||
|
if uri == nil {
|
||||||
|
b.Values, err = b.ConfigurationTypes.New(fmt.Sprintf("%s://", b.Type))
|
||||||
|
} else {
|
||||||
|
b.Values, err = b.ConfigurationTypes.NewFromParsedURI(uri)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Block) GetValue(key string) (any, error) {
|
func (b *Block) GetValue(key string) (any, error) {
|
||||||
return b.Values.GetValue(key)
|
return b.Values.GetValue(key)
|
||||||
}
|
}
|
||||||
@ -125,6 +135,20 @@ func (b *Block) SetURI(uri string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Block) SetParsedURI(uri *url.URL) (err error) {
|
||||||
|
if b.Values == nil {
|
||||||
|
if err = b.NewConfigurationFromParsedURI(uri); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b.Values == nil {
|
||||||
|
return fmt.Errorf("%w - %s", data.ErrUnknownConfigurationType, uri)
|
||||||
|
}
|
||||||
|
b.Type = TypeName(b.Values.Type())
|
||||||
|
_,err = b.Values.Read(context.Background())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Block) UnmarshalValue(value *BlockType) error {
|
func (b *Block) UnmarshalValue(value *BlockType) error {
|
||||||
if b.ConfigurationTypes == nil {
|
if b.ConfigurationTypes == nil {
|
||||||
panic(fmt.Errorf("Undefined type registry: unable to create new configuration %s", value.Type))
|
panic(fmt.Errorf("Undefined type registry: unable to create new configuration %s", value.Type))
|
||||||
@ -145,6 +169,9 @@ func (b *Block) UnmarshalValue(value *BlockType) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Block) UnmarshalYAML(value *yaml.Node) error {
|
func (b *Block) UnmarshalYAML(value *yaml.Node) error {
|
||||||
|
if b.ConfigurationTypes == nil {
|
||||||
|
b.ConfigurationTypes = DocumentRegistry.ConfigurationTypes
|
||||||
|
}
|
||||||
t := &BlockType{}
|
t := &BlockType{}
|
||||||
if unmarshalConfigurationTypeErr := value.Decode(t); unmarshalConfigurationTypeErr != nil {
|
if unmarshalConfigurationTypeErr := value.Decode(t); unmarshalConfigurationTypeErr != nil {
|
||||||
return unmarshalConfigurationTypeErr
|
return unmarshalConfigurationTypeErr
|
||||||
@ -168,6 +195,9 @@ func (b *Block) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Block) UnmarshalJSON(jsonData []byte) error {
|
func (b *Block) UnmarshalJSON(jsonData []byte) error {
|
||||||
|
if b.ConfigurationTypes == nil {
|
||||||
|
b.ConfigurationTypes = DocumentRegistry.ConfigurationTypes
|
||||||
|
}
|
||||||
t := &BlockType{}
|
t := &BlockType{}
|
||||||
if unmarshalConfigurationTypeErr := json.Unmarshal(jsonData, t); unmarshalConfigurationTypeErr != nil {
|
if unmarshalConfigurationTypeErr := json.Unmarshal(jsonData, t); unmarshalConfigurationTypeErr != nil {
|
||||||
return unmarshalConfigurationTypeErr
|
return unmarshalConfigurationTypeErr
|
||||||
@ -186,7 +216,3 @@ func (b *Block) UnmarshalJSON(jsonData []byte) error {
|
|||||||
_, readErr := b.Values.Read(context.Background())
|
_, readErr := b.Values.Read(context.Background())
|
||||||
return readErr
|
return readErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Block) MarshalYAML() (any, error) {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
@ -5,27 +5,14 @@ package folio
|
|||||||
import (
|
import (
|
||||||
_ "fmt"
|
_ "fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
"strings"
|
"strings"
|
||||||
|
"decl/internal/data"
|
||||||
|
"decl/internal/codec"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var TempDir string
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
var err error
|
|
||||||
TempDir, err = os.MkdirTemp("", "testconfig")
|
|
||||||
if err != nil || TempDir == "" {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rc := m.Run()
|
|
||||||
|
|
||||||
os.RemoveAll(TempDir)
|
|
||||||
os.Exit(rc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewBlock(t *testing.T) {
|
func TestNewBlock(t *testing.T) {
|
||||||
configYaml := `
|
configYaml := `
|
||||||
name: "foo"
|
name: "foo"
|
||||||
@ -36,14 +23,21 @@ values:
|
|||||||
docReader := strings.NewReader(configYaml)
|
docReader := strings.NewReader(configYaml)
|
||||||
|
|
||||||
block := NewBlock()
|
block := NewBlock()
|
||||||
|
block.ConfigurationTypes = TestConfigurationTypes
|
||||||
|
slog.Info("TestNewBlock()", "block", block, "types", block.ConfigurationTypes)
|
||||||
|
|
||||||
assert.NotNil(t, block)
|
assert.NotNil(t, block)
|
||||||
assert.Nil(t, block.Load(docReader))
|
assert.Nil(t, block.LoadReader(io.NopCloser(docReader), codec.FormatYaml))
|
||||||
|
|
||||||
assert.Equal(t, "foo", block.Name)
|
assert.Equal(t, "foo", block.Name)
|
||||||
|
|
||||||
|
block.Values.(*MockQuuz).InjectGetValue = func(key string) (any, error) { return "test", nil }
|
||||||
val, err := block.GetValue("http_user")
|
val, err := block.GetValue("http_user")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "test", val)
|
assert.Equal(t, "test", val)
|
||||||
|
|
||||||
|
block.Values.(*MockQuuz).InjectGetValue = func(key string) (any, error) { return nil, data.ErrUnknownConfigurationKey }
|
||||||
missingVal, missingErr := block.GetValue("content")
|
missingVal, missingErr := block.GetValue("content")
|
||||||
assert.ErrorIs(t, missingErr, ErrUnknownConfigurationKey)
|
assert.ErrorIs(t, missingErr, data.ErrUnknownConfigurationKey)
|
||||||
assert.Nil(t, missingVal)
|
assert.Nil(t, missingVal)
|
||||||
}
|
}
|
||||||
|
83
internal/folio/constraint.go
Normal file
83
internal/folio/constraint.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"decl/internal/data"
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dependencies describe a requirement on a given system property
|
||||||
|
|
||||||
|
// system properties:
|
||||||
|
// loaded from config
|
||||||
|
//
|
||||||
|
// deps assigned to decl
|
||||||
|
// match values in document configurations
|
||||||
|
// match values in all configurations?
|
||||||
|
//
|
||||||
|
// documents/facter.jx.yaml -> facts -> Get(key)
|
||||||
|
//
|
||||||
|
// ConfigMapper? -> DocumentRegistry
|
||||||
|
|
||||||
|
// system:
|
||||||
|
// arch: amd64
|
||||||
|
// foo: bar
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrConstraintFailure = errors.New("Constraint failure")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Constraint map[string]any
|
||||||
|
|
||||||
|
//func Compare[aV, bV map[K]V, K, V comparable](a aV, b bV) (matched bool) {
|
||||||
|
func CompareMap(a, b any) (matched bool) {
|
||||||
|
for k,v := range a.(Constraint) {
|
||||||
|
if bv, exists := b.(map[string]any)[k]; exists && bv == v {
|
||||||
|
matched = true
|
||||||
|
} else {
|
||||||
|
matched = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Constraint) CompareValues(a, b any) (matched bool) {
|
||||||
|
matched = true
|
||||||
|
slog.Info("Constraint.CompareValues()", "a", a, "b", b)
|
||||||
|
switch btype := b.(type) {
|
||||||
|
case map[string]string:
|
||||||
|
return CompareMap(a, b)
|
||||||
|
case map[string]any:
|
||||||
|
return CompareMap(a, b)
|
||||||
|
case []string:
|
||||||
|
acmp := a.([]string)
|
||||||
|
if len(acmp) > len(btype) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i,v := range acmp {
|
||||||
|
if v != btype[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Constraint) Check(configs data.ConfigurationValueGetter) (matched bool) {
|
||||||
|
matched = true
|
||||||
|
for k,v := range c {
|
||||||
|
slog.Info("Constraint.Check()", "constraint", c, "config", configs)
|
||||||
|
|
||||||
|
if configValue, err := configs.GetValue(k); err != nil || ! c.CompareValues(v, configValue) {
|
||||||
|
slog.Info("Constraint.Check() - FAILURE", "configvalue", configValue, "constraint", v, "error", err)
|
||||||
|
matched = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -15,6 +15,8 @@ _ "gitea.rosskeen.house/rosskeen.house/machine"
|
|||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/schema"
|
"decl/internal/schema"
|
||||||
|
"net/url"
|
||||||
|
"runtime/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigName string
|
type ConfigName string
|
||||||
@ -23,6 +25,9 @@ type DeclarationType struct {
|
|||||||
Type TypeName `json:"type" yaml:"type"`
|
Type TypeName `json:"type" yaml:"type"`
|
||||||
Transition string `json:"transition,omitempty" yaml:"transition,omitempty"`
|
Transition string `json:"transition,omitempty" yaml:"transition,omitempty"`
|
||||||
Config ConfigName `json:"config,omitempty" yaml:"config,omitempty"`
|
Config ConfigName `json:"config,omitempty" yaml:"config,omitempty"`
|
||||||
|
OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"`
|
||||||
|
Error string `json:"error,omitempty" yaml:"error,omitempty"`
|
||||||
|
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Declaration struct {
|
type Declaration struct {
|
||||||
@ -30,6 +35,9 @@ type Declaration struct {
|
|||||||
Transition string `json:"transition,omitempty" yaml:"transition,omitempty"`
|
Transition string `json:"transition,omitempty" yaml:"transition,omitempty"`
|
||||||
Attributes data.Resource `json:"attributes" yaml:"attributes"`
|
Attributes data.Resource `json:"attributes" yaml:"attributes"`
|
||||||
Config ConfigName `json:"config,omitempty" yaml:"config,omitempty"`
|
Config ConfigName `json:"config,omitempty" yaml:"config,omitempty"`
|
||||||
|
OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"`
|
||||||
|
Error string `json:"error,omitempty" yaml:"error,omitempty"`
|
||||||
|
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
||||||
// runtime luaruntime.LuaRunner
|
// runtime luaruntime.LuaRunner
|
||||||
document *Document
|
document *Document
|
||||||
configBlock data.Block
|
configBlock data.Block
|
||||||
@ -69,10 +77,11 @@ func (d *Declaration) ResolveId(ctx context.Context) string {
|
|||||||
func (d *Declaration) Clone() data.Declaration {
|
func (d *Declaration) Clone() data.Declaration {
|
||||||
return &Declaration {
|
return &Declaration {
|
||||||
Type: d.Type,
|
Type: d.Type,
|
||||||
Transition: d.Transition,
|
Transition: d.Transition,
|
||||||
Attributes: d.Attributes.Clone(),
|
Attributes: d.Attributes.Clone(),
|
||||||
//runtime: luaruntime.New(),
|
//runtime: luaruntime.New(),
|
||||||
Config: d.Config,
|
Config: d.Config,
|
||||||
|
Requires: d.Requires,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +121,17 @@ func (d *Declaration) Validate() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Declaration) NewResourceFromParsedURI(u *url.URL) (err error) {
|
||||||
|
if u == nil {
|
||||||
|
d.Attributes, err = d.ResourceTypes.NewFromParsedURI(&url.URL{ Scheme: string(d.Type) })
|
||||||
|
} else {
|
||||||
|
if d.Attributes, err = d.ResourceTypes.NewFromParsedURI(u); err == nil {
|
||||||
|
err = d.Attributes.SetParsedURI(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Declaration) NewResource(uri *string) (err error) {
|
func (d *Declaration) NewResource(uri *string) (err error) {
|
||||||
if d.ResourceTypes == nil {
|
if d.ResourceTypes == nil {
|
||||||
panic(fmt.Errorf("Undefined type registry: unable to create new resource %s", *uri))
|
panic(fmt.Errorf("Undefined type registry: unable to create new resource %s", *uri))
|
||||||
@ -119,7 +139,9 @@ func (d *Declaration) NewResource(uri *string) (err error) {
|
|||||||
if uri == nil {
|
if uri == nil {
|
||||||
d.Attributes, err = d.ResourceTypes.New(fmt.Sprintf("%s://", d.Type))
|
d.Attributes, err = d.ResourceTypes.New(fmt.Sprintf("%s://", d.Type))
|
||||||
} else {
|
} else {
|
||||||
d.Attributes, err = d.ResourceTypes.New(*uri)
|
if d.Attributes, err = d.ResourceTypes.New(*uri); err == nil {
|
||||||
|
err = d.Attributes.SetURI(*uri)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -131,8 +153,12 @@ func (d *Declaration) Resource() data.Resource {
|
|||||||
func (d *Declaration) Apply() (result error) {
|
func (d *Declaration) Apply() (result error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
|
slog.Info("Declaration.Apply()", "error", r, "stacktrace", string(debug.Stack()))
|
||||||
result = fmt.Errorf("%s", r)
|
result = fmt.Errorf("%s", r)
|
||||||
}
|
}
|
||||||
|
if result != nil {
|
||||||
|
d.Error = result.Error()
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
stater := d.Attributes.StateMachine()
|
stater := d.Attributes.StateMachine()
|
||||||
@ -158,10 +184,17 @@ func (d *Declaration) Apply() (result error) {
|
|||||||
case "create", "present":
|
case "create", "present":
|
||||||
if stater.CurrentState() == "absent" || stater.CurrentState() == "unknown" {
|
if stater.CurrentState() == "absent" || stater.CurrentState() == "unknown" {
|
||||||
if result = stater.Trigger("create"); result != nil {
|
if result = stater.Trigger("create"); result != nil {
|
||||||
|
slog.Info("Declaration.Apply()", "trigger", "create", "state", stater.CurrentState(), "error", result, "declaration", d)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = stater.Trigger("read")
|
result = stater.Trigger("read")
|
||||||
|
currentState := stater.CurrentState()
|
||||||
|
switch currentState {
|
||||||
|
case "create", "present":
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Failed to create resource: %s - state: %s, err: %s", d.URI(), currentState, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -179,9 +212,12 @@ func (d *Declaration) SetConfig(configDoc data.Document) {
|
|||||||
func (d *Declaration) SetURI(uri string) (err error) {
|
func (d *Declaration) SetURI(uri string) (err error) {
|
||||||
slog.Info("Declaration.SetURI()", "uri", uri, "declaration", d)
|
slog.Info("Declaration.SetURI()", "uri", uri, "declaration", d)
|
||||||
if d.Attributes == nil {
|
if d.Attributes == nil {
|
||||||
if err = d.NewResource(&uri); err != nil {
|
err = d.NewResource(&uri)
|
||||||
return
|
} else {
|
||||||
}
|
err = d.Attributes.SetURI(uri)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if d.Attributes == nil {
|
if d.Attributes == nil {
|
||||||
return fmt.Errorf("%w: %s", ErrUnknownResourceType, uri)
|
return fmt.Errorf("%w: %s", ErrUnknownResourceType, uri)
|
||||||
@ -192,6 +228,24 @@ func (d *Declaration) SetURI(uri string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Declaration) SetParsedURI(uri *url.URL) (err error) {
|
||||||
|
slog.Info("Declaration.SetParsedURI()", "uri", uri, "declaration", d)
|
||||||
|
if d.Attributes == nil {
|
||||||
|
err = d.NewResourceFromParsedURI(uri)
|
||||||
|
} else {
|
||||||
|
err = d.Attributes.SetParsedURI(uri)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.Attributes == nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrUnknownResourceType, uri)
|
||||||
|
}
|
||||||
|
d.Type = TypeName(d.Attributes.Type())
|
||||||
|
_, err = d.Attributes.Read(context.Background()) // fix context
|
||||||
|
slog.Info("Declaration.SetURI() - read", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Declaration) UnmarshalValue(value *DeclarationType) error {
|
func (d *Declaration) UnmarshalValue(value *DeclarationType) error {
|
||||||
slog.Info("Declaration.UnmarshalValue", "declaration", d, "value", value, "addr", d)
|
slog.Info("Declaration.UnmarshalValue", "declaration", d, "value", value, "addr", d)
|
||||||
@ -201,6 +255,9 @@ func (d *Declaration) UnmarshalValue(value *DeclarationType) error {
|
|||||||
d.Type = value.Type
|
d.Type = value.Type
|
||||||
d.Transition = value.Transition
|
d.Transition = value.Transition
|
||||||
d.Config = value.Config
|
d.Config = value.Config
|
||||||
|
d.OnError = value.OnError
|
||||||
|
d.Error = value.Error
|
||||||
|
d.Requires = value.Requires
|
||||||
newResource, resourceErr := d.ResourceTypes.New(fmt.Sprintf("%s://", value.Type))
|
newResource, resourceErr := d.ResourceTypes.New(fmt.Sprintf("%s://", value.Type))
|
||||||
if resourceErr != nil {
|
if resourceErr != nil {
|
||||||
slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr)
|
slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr)
|
||||||
|
45
internal/folio/dependencies.go
Normal file
45
internal/folio/dependencies.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dependencies describe a requirement on a given system property
|
||||||
|
|
||||||
|
// system properties:
|
||||||
|
// loaded from config
|
||||||
|
//
|
||||||
|
// deps assigned to decl
|
||||||
|
// match values in document configurations
|
||||||
|
// match values in all configurations?
|
||||||
|
//
|
||||||
|
// documents/facter.jx.yaml -> facts -> Get(key)
|
||||||
|
//
|
||||||
|
// ConfigMapper? -> DocumentRegistry
|
||||||
|
|
||||||
|
// requires:
|
||||||
|
// arch: amd64
|
||||||
|
// foo: bar
|
||||||
|
|
||||||
|
type Dependencies map[string]Constraint
|
||||||
|
|
||||||
|
func (d Dependencies) Check() (matched bool) {
|
||||||
|
matched = true
|
||||||
|
for k,v := range d {
|
||||||
|
b, z := DocumentRegistry.ConfigNameMap.Get(k)
|
||||||
|
slog.Info("Dependencies.Check()", "key", k, "value", v, "block", b, "ok", z)
|
||||||
|
if configBlock, ok := DocumentRegistry.ConfigNameMap.Get(k); ok {
|
||||||
|
if ! v.Check(configBlock) {
|
||||||
|
matched = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
matched = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
_ "net/url"
|
"net/url"
|
||||||
"github.com/sters/yaml-diff/yamldiff"
|
"github.com/sters/yaml-diff/yamldiff"
|
||||||
"strings"
|
"strings"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
@ -24,18 +24,21 @@ type DocumentType struct {
|
|||||||
Schema URI `json:"schema,omitempty" yaml:"schema,omitempty"`
|
Schema URI `json:"schema,omitempty" yaml:"schema,omitempty"`
|
||||||
URI URI `json:"source,omitempty" yaml:"source,omitempty"`
|
URI URI `json:"source,omitempty" yaml:"source,omitempty"`
|
||||||
Format codec.Format `json:"format,omitempty" yaml:"format,omitempty"`
|
Format codec.Format `json:"format,omitempty" yaml:"format,omitempty"`
|
||||||
|
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Document struct {
|
type Document struct {
|
||||||
Schema URI `json:"schema,omitempty" yaml:"schema,omitempty"`
|
Schema URI `json:"schema,omitempty" yaml:"schema,omitempty"`
|
||||||
URI URI `json:"source,omitempty" yaml:"source,omitempty"`
|
URI URI `json:"source,omitempty" yaml:"source,omitempty"`
|
||||||
Format codec.Format `json:"format,omitempty" yaml:"format,omitempty"`
|
Format codec.Format `json:"format,omitempty" yaml:"format,omitempty"`
|
||||||
|
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
||||||
uris mapper.Store[string, data.Declaration]
|
uris mapper.Store[string, data.Declaration]
|
||||||
ResourceDeclarations []*Declaration `json:"resources" yaml:"resources"`
|
ResourceDeclarations []*Declaration `json:"resources,omitempty" yaml:"resources,omitempty"`
|
||||||
configNames mapper.Store[string, data.Block]
|
configNames mapper.Store[string, data.Block] `json:"-" yaml:"-"`
|
||||||
Configurations []*Block `json:"configurations,omitempty" yaml:"configurations,omitempty"`
|
Configurations []*Block `json:"configurations,omitempty" yaml:"configurations,omitempty"`
|
||||||
config data.Document
|
config data.Document
|
||||||
Registry *Registry `json:"-" yaml:"-"`
|
Registry *Registry `json:"-" yaml:"-"`
|
||||||
|
failedResources int `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDocument(r *Registry) *Document {
|
func NewDocument(r *Registry) *Document {
|
||||||
@ -90,7 +93,11 @@ func (d *Document) GetResource(uri string) *Declaration {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Clone() *Document {
|
func (d *Document) Failures() int {
|
||||||
|
return d.failedResources
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Document) Clone() data.Document {
|
||||||
clone := NewDocument(d.Registry)
|
clone := NewDocument(d.Registry)
|
||||||
clone.config = d.config
|
clone.config = d.config
|
||||||
|
|
||||||
@ -105,6 +112,7 @@ func (d *Document) Clone() *Document {
|
|||||||
clone.ResourceDeclarations[i].SetDocument(clone)
|
clone.ResourceDeclarations[i].SetDocument(clone)
|
||||||
clone.ResourceDeclarations[i].SetConfig(d.config)
|
clone.ResourceDeclarations[i].SetConfig(d.config)
|
||||||
}
|
}
|
||||||
|
clone.Requires = d.Requires
|
||||||
return clone
|
return clone
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +130,22 @@ func (d *Document) assignResourcesDocument() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Document) assignConfigurationsDocument() {
|
||||||
|
slog.Info("Document.assignConfigurationsDocument()", "configurations", d.Configurations, "len", len(d.Configurations))
|
||||||
|
for i := range d.Configurations {
|
||||||
|
if d.Configurations[i] == nil {
|
||||||
|
d.Configurations[i] = NewBlock()
|
||||||
|
}
|
||||||
|
slog.Info("Document.assignConfigurationsDocument()", "configuration", d.Configurations[i])
|
||||||
|
//d.Configurations[i].SetDocument(d)
|
||||||
|
slog.Info("Document.assignConfigurationsDocument()", "configuration", d.Configurations[i])
|
||||||
|
//d.MapConfigurationURI(d.Configurations[i].URI(), d.Configurations[i])
|
||||||
|
d.configNames[d.Configurations[i].Name] = d.Configurations[i]
|
||||||
|
d.Registry.ConfigurationMap[d.Configurations[i]] = d
|
||||||
|
d.Registry.ConfigNameMap[d.Configurations[i].Name] = d.Configurations[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Document) LoadString(docData string, f codec.Format) (err error) {
|
func (d *Document) LoadString(docData string, f codec.Format) (err error) {
|
||||||
err = f.StringDecoder(docData).Decode(d)
|
err = f.StringDecoder(docData).Decode(d)
|
||||||
return
|
return
|
||||||
@ -148,7 +172,7 @@ func (d *Document) GetSchemaFiles() (schemaFs fs.FS) {
|
|||||||
|
|
||||||
func (d *Document) Validate() error {
|
func (d *Document) Validate() error {
|
||||||
jsonDocument, jsonErr := d.JSON()
|
jsonDocument, jsonErr := d.JSON()
|
||||||
slog.Info("document.Validate() json", "json", jsonDocument, "err", jsonErr)
|
slog.Info("document.Validate() 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))
|
||||||
@ -189,6 +213,10 @@ func (d *Document) Len() int {
|
|||||||
return len(d.ResourceDeclarations)
|
return len(d.ResourceDeclarations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Document) CheckConstraints() bool {
|
||||||
|
return d.Requires.Check()
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Document) ResolveIds(ctx context.Context) {
|
func (d *Document) ResolveIds(ctx context.Context) {
|
||||||
for i := range d.ResourceDeclarations {
|
for i := range d.ResourceDeclarations {
|
||||||
d.ResourceDeclarations[i].ResolveId(ctx)
|
d.ResourceDeclarations[i].ResolveId(ctx)
|
||||||
@ -213,9 +241,24 @@ func (d *Document) Apply(state string) error {
|
|||||||
d.ResourceDeclarations[idx].Transition = state
|
d.ResourceDeclarations[idx].Transition = state
|
||||||
}
|
}
|
||||||
d.ResourceDeclarations[idx].SetConfig(d.config)
|
d.ResourceDeclarations[idx].SetConfig(d.config)
|
||||||
if e := d.ResourceDeclarations[idx].Apply(); e != nil {
|
|
||||||
slog.Error("Document.Apply() error applying resource", "index", idx, "uri", d.ResourceDeclarations[idx].Resource().URI(), "resource", d.ResourceDeclarations[idx].Resource(), "error", e)
|
slog.Info("Document.Apply() applying resource", "index", idx, "uri", d.ResourceDeclarations[idx].Resource().URI(), "resource", d.ResourceDeclarations[idx].Resource())
|
||||||
return e
|
|
||||||
|
if d.ResourceDeclarations[idx].Requires.Check() {
|
||||||
|
|
||||||
|
if e := d.ResourceDeclarations[idx].Apply(); e != nil {
|
||||||
|
slog.Error("Document.Apply() error applying resource", "index", idx, "uri", d.ResourceDeclarations[idx].Resource().URI())
|
||||||
|
|
||||||
|
d.ResourceDeclarations[idx].Error = e.Error()
|
||||||
|
switch d.ResourceDeclarations[idx].OnError.GetStrategy() {
|
||||||
|
case OnErrorStop:
|
||||||
|
return e
|
||||||
|
case OnErrorFail:
|
||||||
|
d.failedResources++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d.ResourceDeclarations[idx].Error = fmt.Sprintf("Constraint failure: %s", d.ResourceDeclarations[idx].Requires)
|
||||||
}
|
}
|
||||||
if i >= len(d.ResourceDeclarations) - 1 {
|
if i >= len(d.ResourceDeclarations) - 1 {
|
||||||
break
|
break
|
||||||
@ -240,6 +283,12 @@ func (d *Document) Generate(w io.Writer) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (d *Document) MapConfigurationURI(uri string, block data.Block) {
|
||||||
|
d.configUris[uri] = block
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (d *Document) MapResourceURI(uri string, declaration data.Declaration) {
|
func (d *Document) MapResourceURI(uri string, declaration data.Declaration) {
|
||||||
d.uris[uri] = declaration
|
d.uris[uri] = declaration
|
||||||
}
|
}
|
||||||
@ -247,39 +296,90 @@ func (d *Document) MapResourceURI(uri string, declaration data.Declaration) {
|
|||||||
func (d *Document) AddDeclaration(declaration data.Declaration) {
|
func (d *Document) AddDeclaration(declaration data.Declaration) {
|
||||||
uri := declaration.URI()
|
uri := declaration.URI()
|
||||||
decl := declaration.(*Declaration)
|
decl := declaration.(*Declaration)
|
||||||
d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
if decl.Requires.Check() {
|
||||||
d.MapResourceURI(uri, declaration)
|
|
||||||
decl.SetDocument(d)
|
d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
||||||
d.Registry.DeclarationMap[decl] = d
|
|
||||||
|
d.MapResourceURI(uri, declaration)
|
||||||
|
decl.SetDocument(d)
|
||||||
|
d.Registry.DeclarationMap[decl] = d
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) AddResourceDeclaration(resourceType string, resourceDeclaration data.Resource) {
|
func (d *Document) AddResourceDeclaration(resourceType string, resourceDeclaration data.Resource) {
|
||||||
slog.Info("Document.AddResourceDeclaration()", "type", resourceType, "resource", resourceDeclaration)
|
slog.Info("Document.AddResourceDeclaration()", "type", resourceType, "resource", resourceDeclaration)
|
||||||
decl := NewDeclarationFromDocument(d)
|
decl := NewDeclarationFromDocument(d)
|
||||||
decl.Type = TypeName(resourceType)
|
decl.Type = TypeName(resourceType)
|
||||||
decl.Attributes = resourceDeclaration
|
if decl.Requires.Check() {
|
||||||
d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
decl.Attributes = resourceDeclaration
|
||||||
d.MapResourceURI(decl.Attributes.URI(), decl)
|
d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
||||||
decl.SetDocument(d)
|
d.MapResourceURI(decl.Attributes.URI(), decl)
|
||||||
d.Registry.DeclarationMap[decl] = d
|
decl.SetDocument(d)
|
||||||
|
d.Registry.DeclarationMap[decl] = d
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX NewResource is not commonly used by the underlying resource Read() is no longer called so it needs more testing
|
// XXX NewResource is not commonly used by the underlying resource Read() is no longer called so it needs more testing
|
||||||
func (d *Document) NewResource(uri string) (newResource data.Resource, err error) {
|
func (d *Document) NewResource(uri string) (newResource data.Resource, err error) {
|
||||||
|
|
||||||
decl := NewDeclarationFromDocument(d)
|
decl := NewDeclarationFromDocument(d)
|
||||||
|
|
||||||
if err = decl.NewResource(&uri); err != nil {
|
if err = decl.NewResource(&uri); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if decl.Attributes == nil {
|
if decl.Attributes == nil {
|
||||||
err = fmt.Errorf("%w: %s", ErrUnknownResourceType, uri)
|
err = fmt.Errorf("%w: %s", ErrUnknownResourceType, uri)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
decl.Type = TypeName(decl.Attributes.Type())
|
decl.Type = TypeName(decl.Attributes.Type())
|
||||||
d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
|
||||||
d.MapResourceURI(decl.Attributes.URI(), decl)
|
if decl.Requires.Check() {
|
||||||
decl.SetDocument(d)
|
slog.Info("Document.NewResource()", "type", decl.Type, "declaration", decl)
|
||||||
d.Registry.DeclarationMap[decl] = d
|
|
||||||
newResource = decl.Attributes
|
d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
||||||
|
d.MapResourceURI(decl.Attributes.URI(), decl)
|
||||||
|
decl.SetDocument(d)
|
||||||
|
d.Registry.DeclarationMap[decl] = d
|
||||||
|
newResource = decl.Attributes
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%w: %s", ErrConstraintFailure, uri)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Document) NewResourceFromURI(uri URI) (newResource data.Resource, err error) {
|
||||||
|
return d.NewResourceFromParsedURI(uri.Parse())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Document) NewResourceFromParsedURI(uri *url.URL) (newResource data.Resource, err error) {
|
||||||
|
if uri == nil {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrUnknownResourceType, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
decl := NewDeclarationFromDocument(d)
|
||||||
|
if err = decl.NewResourceFromParsedURI(uri); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if decl.Attributes == nil {
|
||||||
|
err = fmt.Errorf("%w: %s", ErrUnknownResourceType, uri)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decl.Type = TypeName(decl.Attributes.Type())
|
||||||
|
|
||||||
|
if decl.Requires.Check() {
|
||||||
|
d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
||||||
|
|
||||||
|
d.MapResourceURI(decl.Attributes.URI(), decl)
|
||||||
|
decl.SetDocument(d)
|
||||||
|
d.Registry.DeclarationMap[decl] = d
|
||||||
|
newResource = decl.Attributes
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%w: %s", ErrConstraintFailure, uri)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,10 +388,15 @@ func (d *Document) AddResource(uri string) error {
|
|||||||
if e := decl.SetURI(uri); e != nil {
|
if e := decl.SetURI(uri); e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
|
||||||
d.MapResourceURI(decl.Attributes.URI(), decl)
|
if decl.Requires.Check() {
|
||||||
decl.SetDocument(d)
|
d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
||||||
d.Registry.DeclarationMap[decl] = d
|
d.MapResourceURI(decl.Attributes.URI(), decl)
|
||||||
|
decl.SetDocument(d)
|
||||||
|
d.Registry.DeclarationMap[decl] = d
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("%w: %s", ErrConstraintFailure, uri)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,21 +445,19 @@ func (d *Document) HasConfig(name string) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) GetConfig(name string) *Block {
|
func (d *Document) GetConfig(name string) data.Block {
|
||||||
return d.configNames[name].(*Block)
|
return d.configNames[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) AppendConfigurations(docs []data.Document) {
|
func (d *Document) AppendConfigurations(docs []data.Document) {
|
||||||
if docs != nil {
|
for _, doc := range docs {
|
||||||
for _, doc := range docs {
|
for _, config := range doc.(*Document).Configurations {
|
||||||
for _, config := range doc.(*Document).Configurations {
|
d.AddConfigurationBlock(config.Name, config.Type, config.Values)
|
||||||
d.AddConfigurationBlock(config.Name, config.Type, config.Values)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Diff(with *Document, output io.Writer) (returnOutput string, diffErr error) {
|
func (d *Document) Diff(with data.Document, output io.Writer) (returnOutput string, diffErr error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
returnOutput = ""
|
returnOutput = ""
|
||||||
@ -408,6 +511,7 @@ func (d *Document) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
if unmarshalResourcesErr := value.Decode((*decodeDocument)(d)); unmarshalResourcesErr != nil {
|
if unmarshalResourcesErr := value.Decode((*decodeDocument)(d)); unmarshalResourcesErr != nil {
|
||||||
return unmarshalResourcesErr
|
return unmarshalResourcesErr
|
||||||
}
|
}
|
||||||
|
d.assignConfigurationsDocument()
|
||||||
d.assignResourcesDocument()
|
d.assignResourcesDocument()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -418,6 +522,7 @@ func (d *Document) UnmarshalJSON(data []byte) error {
|
|||||||
if unmarshalDocumentErr := json.Unmarshal(data, t); unmarshalDocumentErr != nil {
|
if unmarshalDocumentErr := json.Unmarshal(data, t); unmarshalDocumentErr != nil {
|
||||||
return unmarshalDocumentErr
|
return unmarshalDocumentErr
|
||||||
}
|
}
|
||||||
|
d.assignConfigurationsDocument()
|
||||||
d.assignResourcesDocument()
|
d.assignResourcesDocument()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
TestResourceTypes *types.Types[data.Resource] = types.New[data.Resource]()
|
TestResourceTypes *types.Types[data.Resource] = types.New[data.Resource]()
|
||||||
|
TestConfigurationTypes *types.Types[data.Configuration] = types.New[data.Configuration]()
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewDocument(t *testing.T) {
|
func TestNewDocument(t *testing.T) {
|
||||||
|
@ -28,6 +28,7 @@ func TestMain(m *testing.M) {
|
|||||||
ProcessTestUserName, ProcessTestGroupName = ProcessUserName()
|
ProcessTestUserName, ProcessTestGroupName = ProcessUserName()
|
||||||
|
|
||||||
RegisterMocks()
|
RegisterMocks()
|
||||||
|
RegisterConfigurationMocks()
|
||||||
|
|
||||||
rc := m.Run()
|
rc := m.Run()
|
||||||
|
|
||||||
|
106
internal/folio/mock_configuration_test.go
Normal file
106
internal/folio/mock_configuration_test.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "gopkg.in/yaml.v3"
|
||||||
|
"encoding/json"
|
||||||
|
_ "fmt"
|
||||||
|
"decl/internal/data"
|
||||||
|
"decl/internal/codec"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockConfiguration struct {
|
||||||
|
InjectURI func() string `json:"-" yaml:"-"`
|
||||||
|
InjectType func() string `json:"-" yaml:"-"`
|
||||||
|
InjectResolveId func(ctx context.Context) string `json:"-" yaml:"-"`
|
||||||
|
InjectValidate func() error `json:"-" yaml:"-"`
|
||||||
|
InjectJSON func() ([]byte, error) `json:"-" yaml:"-"`
|
||||||
|
InjectYAML func() ([]byte, error) `json:"-" yaml:"-"`
|
||||||
|
InjectPB func() ([]byte, error) `json:"-" yaml:"-"`
|
||||||
|
InjectGenerate func(w io.Writer) (error) `json:"-" yaml:"-"`
|
||||||
|
InjectLoadString func(string, codec.Format) (error) `json:"-" yaml:"-"`
|
||||||
|
InjectLoad func([]byte, codec.Format) (error) `json:"-" yaml:"-"`
|
||||||
|
InjectLoadReader func(io.ReadCloser, codec.Format) (error) `json:"-" yaml:"-"`
|
||||||
|
InjectRead func(context.Context) ([]byte, error) `json:"-" yaml:"-"`
|
||||||
|
InjectGetValue func(key string) (any, error) `json:"-" yaml:"-"`
|
||||||
|
InjectHas func(key string) (bool) `json:"-" yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) Clone() data.Configuration {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) SetURI(uri string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) SetParsedURI(uri *url.URL) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) URI() string {
|
||||||
|
return m.InjectURI()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) ResolveId(ctx context.Context) string {
|
||||||
|
return m.InjectResolveId(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) JSON() ([]byte, error) {
|
||||||
|
return m.InjectJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) YAML() ([]byte, error) {
|
||||||
|
return m.InjectYAML()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) PB() ([]byte, error) {
|
||||||
|
return m.InjectPB()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) Generate(w io.Writer) (error) {
|
||||||
|
return m.InjectGenerate(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) LoadString(docData string, format codec.Format) (error) {
|
||||||
|
return m.InjectLoadString(docData, format)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) Load(docData []byte, format codec.Format) (error) {
|
||||||
|
return m.InjectLoad(docData, format)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) LoadReader(r io.ReadCloser, format codec.Format) (error) {
|
||||||
|
return m.InjectLoadReader(r, format)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) Read(ctx context.Context) ([]byte, error) {
|
||||||
|
return m.InjectRead(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) Validate() error {
|
||||||
|
return m.InjectValidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) Type() string {
|
||||||
|
return m.InjectType()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) UnmarshalJSON(data []byte) error {
|
||||||
|
if err := json.Unmarshal(data, m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) GetValue(key string) (any, error) {
|
||||||
|
return m.InjectGetValue(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConfiguration) Has(key string) (bool) {
|
||||||
|
return m.InjectHas(key)
|
||||||
|
}
|
48
internal/folio/mock_quuz_configuration_test.go
Normal file
48
internal/folio/mock_quuz_configuration_test.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "gopkg.in/yaml.v3"
|
||||||
|
"decl/internal/codec"
|
||||||
|
"decl/internal/data"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterConfigurationMocks() {
|
||||||
|
TestConfigurationTypes.Register([]string{"generic"}, func(u *url.URL) data.Configuration {
|
||||||
|
q := NewQuuzConfiguration()
|
||||||
|
q.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
|
return q
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockQuuz struct {
|
||||||
|
*MockConfiguration `json:"-" yaml:"-"`
|
||||||
|
Name string `json:"name" yaml:"name"`
|
||||||
|
Value string `json:"value" yaml:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockConfiguration(typename string) *MockConfiguration {
|
||||||
|
return &MockConfiguration {
|
||||||
|
InjectType: func() string { return typename },
|
||||||
|
InjectResolveId: func(ctx context.Context) string { return "bar" },
|
||||||
|
InjectLoadString: func(string, codec.Format) (error) { return nil },
|
||||||
|
InjectLoad: func([]byte, codec.Format) (error) { return nil },
|
||||||
|
InjectLoadReader: func(io.ReadCloser, codec.Format) (error) { return nil },
|
||||||
|
InjectValidate: func() error { return nil },
|
||||||
|
InjectRead: func(context.Context) ([]byte, error) { return nil, nil },
|
||||||
|
InjectGetValue: func(key string) (any, error) { return nil, nil },
|
||||||
|
InjectURI: func() string { return fmt.Sprintf("%s://bar", typename) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQuuzConfiguration() *MockQuuz {
|
||||||
|
q := &MockQuuz {}
|
||||||
|
q.MockConfiguration = NewMockConfiguration("generic")
|
||||||
|
return q
|
||||||
|
}
|
@ -11,6 +11,7 @@ _ "fmt"
|
|||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MockResource struct {
|
type MockResource struct {
|
||||||
@ -53,6 +54,10 @@ func (m *MockResource) SetURI(uri string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockResource) SetParsedURI(uri *url.URL) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MockResource) URI() string {
|
func (m *MockResource) URI() string {
|
||||||
return m.InjectURI()
|
return m.InjectURI()
|
||||||
}
|
}
|
||||||
|
76
internal/folio/onerror.go
Normal file
76
internal/folio/onerror.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"encoding/json"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidOnErrorStrategy = errors.New("Invalid OnError strategy")
|
||||||
|
)
|
||||||
|
|
||||||
|
type OnError string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OnErrorStop = "stop"
|
||||||
|
OnErrorFail = "fail" // set error value and return the error
|
||||||
|
OnErrorSkip = "skip" // set error value and continue processing resources
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewOnError() OnError {
|
||||||
|
return OnErrorFail
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OnError) Strategy() string {
|
||||||
|
switch o {
|
||||||
|
case OnErrorStop, OnErrorFail, OnErrorSkip:
|
||||||
|
return string(o)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OnError) GetStrategy() OnError {
|
||||||
|
switch o {
|
||||||
|
case OnErrorStop, OnErrorFail, OnErrorSkip:
|
||||||
|
return o
|
||||||
|
default:
|
||||||
|
slog.Warn("OnError.GetStrategy() - invalid value, defaulting to OnErrorFail", "strategy", o)
|
||||||
|
return OnErrorFail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OnError) Validate() error {
|
||||||
|
switch o {
|
||||||
|
case OnErrorStop, OnErrorFail, OnErrorSkip:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return ErrInvalidOnErrorStrategy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnError) UnmarshalValue(value string) (err error) {
|
||||||
|
if err = OnError(value).Validate(); err == nil {
|
||||||
|
*o = OnError(value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnError) UnmarshalJSON(jsonData []byte) error {
|
||||||
|
var s string
|
||||||
|
if unmarshalErr := json.Unmarshal(jsonData, &s); unmarshalErr != nil {
|
||||||
|
return unmarshalErr
|
||||||
|
}
|
||||||
|
return o.UnmarshalValue(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnError) UnmarshalYAML(value *yaml.Node) error {
|
||||||
|
var s string
|
||||||
|
if err := value.Decode(&s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return o.UnmarshalValue(s)
|
||||||
|
}
|
21
internal/folio/onerror_test.go
Normal file
21
internal/folio/onerror_test.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOnErrorStrategies(t *testing.T) {
|
||||||
|
for _, v := range []struct{ strategy OnError; expected OnError; validate error }{
|
||||||
|
{ strategy: OnErrorFail, expected: "fail", validate: nil },
|
||||||
|
{ strategy: OnErrorSkip, expected: "skip", validate: nil },
|
||||||
|
{ strategy: OnError("unknown"), expected: "", validate: ErrInvalidOnErrorStrategy },
|
||||||
|
}{
|
||||||
|
o := v.strategy
|
||||||
|
assert.Equal(t, v.expected, OnError(o.Strategy()))
|
||||||
|
assert.ErrorIs(t, o.Validate(), v.validate)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,12 +5,13 @@ package folio
|
|||||||
import (
|
import (
|
||||||
_ "errors"
|
_ "errors"
|
||||||
_ "fmt"
|
_ "fmt"
|
||||||
_ "net/url"
|
"net/url"
|
||||||
_ "strings"
|
_ "strings"
|
||||||
"decl/internal/types"
|
"decl/internal/types"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/mapper"
|
"decl/internal/mapper"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"log/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -24,6 +25,7 @@ type Registry struct {
|
|||||||
Documents []*Document
|
Documents []*Document
|
||||||
UriMap mapper.Store[URI, *Document]
|
UriMap mapper.Store[URI, *Document]
|
||||||
DeclarationMap mapper.Store[*Declaration, *Document]
|
DeclarationMap mapper.Store[*Declaration, *Document]
|
||||||
|
ConfigNameMap mapper.Store[string, *Block]
|
||||||
ConfigurationMap mapper.Store[*Block, *Document]
|
ConfigurationMap mapper.Store[*Block, *Document]
|
||||||
DefaultSchema URI
|
DefaultSchema URI
|
||||||
}
|
}
|
||||||
@ -36,6 +38,7 @@ func NewRegistry() *Registry {
|
|||||||
Documents: make([]*Document, 0, 10),
|
Documents: make([]*Document, 0, 10),
|
||||||
UriMap: mapper.New[URI, *Document](),
|
UriMap: mapper.New[URI, *Document](),
|
||||||
DeclarationMap: mapper.New[*Declaration, *Document](),
|
DeclarationMap: mapper.New[*Declaration, *Document](),
|
||||||
|
ConfigNameMap: mapper.New[string, *Block](),
|
||||||
ConfigurationMap: mapper.New[*Block, *Document](),
|
ConfigurationMap: mapper.New[*Block, *Document](),
|
||||||
Schemas: mapper.New[URI, fs.FS](),
|
Schemas: mapper.New[URI, fs.FS](),
|
||||||
DefaultSchema: schemaFilesUri,
|
DefaultSchema: schemaFilesUri,
|
||||||
@ -52,8 +55,21 @@ func (r *Registry) Has(key *Declaration) (bool) {
|
|||||||
return r.DeclarationMap.Has(key)
|
return r.DeclarationMap.Has(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Registry) HasDocument(key URI) bool {
|
||||||
|
return r.UriMap.Has(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) GetDocument(key URI) (*Document, bool) {
|
||||||
|
return r.UriMap.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) SetDocument(key URI, value *Document) {
|
||||||
|
r.UriMap.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Registry) NewDocument(uri URI) (doc *Document) {
|
func (r *Registry) NewDocument(uri URI) (doc *Document) {
|
||||||
doc = NewDocument(r)
|
doc = NewDocument(r)
|
||||||
|
doc.URI = uri
|
||||||
r.Documents = append(r.Documents, doc)
|
r.Documents = append(r.Documents, doc)
|
||||||
if uri != "" {
|
if uri != "" {
|
||||||
r.UriMap[uri] = doc
|
r.UriMap[uri] = doc
|
||||||
@ -61,15 +77,64 @@ func (r *Registry) NewDocument(uri URI) (doc *Document) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Registry) Load(uri URI) (documents []data.Document, err error) {
|
func (r *Registry) AppendParsedURI(uri *url.URL, documents []data.Document) (addedDocuments []data.Document, err error) {
|
||||||
var extractor data.Converter
|
var convertUri data.Converter
|
||||||
var sourceResource data.Resource
|
var sourceResource data.Resource
|
||||||
if extractor, err = r.ConverterTypes.New(string(uri)); err == nil {
|
|
||||||
if sourceResource, err = uri.NewResource(nil); err == nil {
|
slog.Info("folio.Registry.AppendParsedURI()", "uri", uri, "converter", r.ConverterTypes)
|
||||||
documents, err = extractor.(data.ManyExtractor).ExtractMany(sourceResource, nil)
|
if convertUri, err = r.ConverterTypes.NewFromParsedURI(uri); err == nil {
|
||||||
|
if sourceResource, err = NewResourceFromParsedURI(uri, nil); err == nil {
|
||||||
|
switch extractor := convertUri.(type) {
|
||||||
|
case data.ManyExtractor:
|
||||||
|
var docs []data.Document
|
||||||
|
docs, err = extractor.ExtractMany(sourceResource, nil)
|
||||||
|
slog.Info("folio.Registry.Append() - ExtractMany", "uri", uri, "source", sourceResource, "docs", docs, "error", err)
|
||||||
|
documents = append(documents, docs...)
|
||||||
|
case data.Extractor:
|
||||||
|
var singleDocument data.Document
|
||||||
|
singleDocument, err = extractor.Extract(sourceResource, nil)
|
||||||
|
slog.Info("folio.Registry.Append() - Extract", "uri", uri, "source", sourceResource, "doc", singleDocument, "error", err)
|
||||||
|
documents = append(documents, singleDocument)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
slog.Info("folio.Registry.Append()", "uri", uri, "converter", r.ConverterTypes, "error", err)
|
||||||
|
addedDocuments = documents
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Registry) Append(uri URI, documents []data.Document) (addedDocuments []data.Document, err error) {
|
||||||
|
var convertUri data.Converter
|
||||||
|
var sourceResource data.Resource
|
||||||
|
|
||||||
|
slog.Info("folio.Registry.Append()", "uri", uri, "converter", r.ConverterTypes)
|
||||||
|
if convertUri, err = r.ConverterTypes.New(string(uri)); err == nil {
|
||||||
|
if sourceResource, err = uri.NewResource(nil); err == nil {
|
||||||
|
switch extractor := convertUri.(type) {
|
||||||
|
case data.ManyExtractor:
|
||||||
|
var docs []data.Document
|
||||||
|
docs, err = extractor.ExtractMany(sourceResource, nil)
|
||||||
|
slog.Info("folio.Registry.Append() - ExtractMany", "uri", uri, "source", sourceResource, "docs", docs, "error", err)
|
||||||
|
documents = append(documents, docs...)
|
||||||
|
case data.Extractor:
|
||||||
|
var singleDocument data.Document
|
||||||
|
singleDocument, err = extractor.Extract(sourceResource, nil)
|
||||||
|
slog.Info("folio.Registry.Append() - Extract", "uri", uri, "source", sourceResource, "doc", singleDocument, "error", err)
|
||||||
|
documents = append(documents, singleDocument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slog.Info("folio.Registry.Append()", "uri", uri, "converter", r.ConverterTypes, "error", err)
|
||||||
|
addedDocuments = documents
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) Load(uri URI) (documents []data.Document, err error) {
|
||||||
|
documents = make([]data.Document, 0, 10)
|
||||||
|
return r.Append(uri, documents)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) LoadFromURL(uri *url.URL) (documents []data.Document, err error) {
|
||||||
|
documents = make([]data.Document, 0, 10)
|
||||||
|
return r.AppendParsedURI(uri, documents)
|
||||||
|
}
|
||||||
|
26
internal/folio/resource.go
Normal file
26
internal/folio/resource.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"decl/internal/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Create a new resource using a parsed URI */
|
||||||
|
func NewResourceFromParsedURI(u *url.URL, document data.Document) (newResource data.Resource, err error) {
|
||||||
|
if document == nil {
|
||||||
|
declaration := NewDeclaration()
|
||||||
|
if err = declaration.NewResourceFromParsedURI(u); err == nil {
|
||||||
|
return declaration.Attributes, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newResource, err = document.NewResourceFromParsedURI(u)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -32,6 +32,9 @@ 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 data.ResourceMapper) ContentReadWriter {
|
||||||
|
if string(r) == "" {
|
||||||
|
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(string(r)); ok {
|
||||||
|
@ -8,5 +8,6 @@ import (
|
|||||||
|
|
||||||
var schemaFilesUri URI = "file://folio/schemas/*.schema.json"
|
var schemaFilesUri URI = "file://folio/schemas/*.schema.json"
|
||||||
|
|
||||||
|
//go:embed schemas/config/*.schema.json
|
||||||
//go:embed schemas/*.schema.json
|
//go:embed schemas/*.schema.json
|
||||||
var schemaFiles embed.FS
|
var schemaFiles embed.FS
|
||||||
|
@ -14,6 +14,15 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Config name"
|
"description": "Config name"
|
||||||
},
|
},
|
||||||
|
"onerror": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "error handling strategy",
|
||||||
|
"enum": [
|
||||||
|
"stop",
|
||||||
|
"fail",
|
||||||
|
"skip"
|
||||||
|
]
|
||||||
|
},
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{ "$ref": "bar.schema.json" }
|
{ "$ref": "bar.schema.json" }
|
||||||
|
8
internal/folio/schemas/codec.schema.json
Normal file
8
internal/folio/schemas/codec.schema.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$id": "codec.schema.json",
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "codec",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Supported serialization encode/decode formats",
|
||||||
|
"enum": [ "yaml", "json", "protobuf" ]
|
||||||
|
}
|
25
internal/folio/schemas/config/block.schema.json
Normal file
25
internal/folio/schemas/config/block.schema.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$id": "block.schema.json",
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "block",
|
||||||
|
"type": "object",
|
||||||
|
"required": [ "name", "values" ],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Config block name",
|
||||||
|
"minLength": 2
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Config type name.",
|
||||||
|
"enum": [ "system", "generic", "exec", "certificate" ]
|
||||||
|
},
|
||||||
|
"values": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "object" },
|
||||||
|
{ "$ref": "certificate.schema.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
internal/folio/schemas/config/certificate.schema.json
Normal file
62
internal/folio/schemas/config/certificate.schema.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"$id": "certificate.schema.json",
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "certificate",
|
||||||
|
"type": "object",
|
||||||
|
"required": [ "path", "filetype" ],
|
||||||
|
"properties": {
|
||||||
|
"SerialNumber": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Serial number",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"Issuer": {
|
||||||
|
"$ref": "pkixname.schema.json"
|
||||||
|
},
|
||||||
|
"Subject": {
|
||||||
|
"$ref": "pkixname.schema.json"
|
||||||
|
},
|
||||||
|
"NotBefore": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Cert is not valid before time in YYYY-MM-DDTHH:MM:SS.sssssssssZ format."
|
||||||
|
},
|
||||||
|
"NotAfter": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Cert is not valid after time in YYYY-MM-DDTHH:MM:SS.sssssssssZ format."
|
||||||
|
},
|
||||||
|
"KeyUsage": {
|
||||||
|
"type": "integer",
|
||||||
|
"enum": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
9
|
||||||
|
],
|
||||||
|
"description": "Actions valid for a key. E.g. 1 = KeyUsageDigitalSignature"
|
||||||
|
},
|
||||||
|
"ExtKeyUsage": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 13
|
||||||
|
},
|
||||||
|
"description": "Extended set of actions valid for a key"
|
||||||
|
},
|
||||||
|
"BasicConstraintsValid": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "BasicConstraintsValid indicates whether IsCA, MaxPathLen, and MaxPathLenZero are valid"
|
||||||
|
},
|
||||||
|
"IsCA": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
internal/folio/schemas/config/pkixname.schema.json
Normal file
65
internal/folio/schemas/config/pkixname.schema.json
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"$id": "pkixname.schema.json",
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "pkixname",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Country": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Country name",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Organization": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Organization name",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"OrganizationalUnit": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Organizational Unit name",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Locality": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Locality name",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Province": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Province name",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"StreetAddress": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Street address",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PostalCode": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Postal Code",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SerialNumber": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"CommonName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
internal/folio/schemas/constraints.schema.json
Normal file
9
internal/folio/schemas/constraints.schema.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"$id": "constraints.schema.json",
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "constraints",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": ["string", "number", "object", "array", "boolean", "null"]
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,15 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Config name"
|
"description": "Config name"
|
||||||
},
|
},
|
||||||
|
"onerror": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "error handling strategy",
|
||||||
|
"enum": [
|
||||||
|
"stop",
|
||||||
|
"fail",
|
||||||
|
"skip"
|
||||||
|
]
|
||||||
|
},
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{ "$ref": "bar.schema.json" }
|
{ "$ref": "bar.schema.json" }
|
||||||
|
9
internal/folio/schemas/dependencies.schema.json
Normal file
9
internal/folio/schemas/dependencies.schema.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"$id": "dependencies.schema.json",
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "dependencies",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "constraints.schema.json"
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,27 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "document",
|
"title": "document",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [ "resources" ],
|
"required": [],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"source": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Document URI"
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"$ref": "codec.schema.json"
|
||||||
|
},
|
||||||
|
"requires": {
|
||||||
|
"$ref": "dependencies.schema.json"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Configurations list",
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "config/block.schema.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"resources": {
|
"resources": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Resources list",
|
"description": "Resources list",
|
||||||
@ -17,4 +36,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
internal/folio/signature.go
Normal file
35
internal/folio/signature.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"decl/internal/data"
|
||||||
|
"decl/internal/signature"
|
||||||
|
"errors"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidSignature error = errors.New("Invalid signature")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Signature []byte
|
||||||
|
|
||||||
|
func (s Signature) Verify(res data.ContentHasher) error {
|
||||||
|
i := &signature.Ident{}
|
||||||
|
return i.VerifySum(res.Hash(), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Signature) SetHexString(sig string) error {
|
||||||
|
if v, e := hex.DecodeString(sig); e == nil {
|
||||||
|
*s = v
|
||||||
|
} else {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Signature) String() string {
|
||||||
|
return hex.EncodeToString(*s)
|
||||||
|
}
|
@ -8,6 +8,7 @@ import (
|
|||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/identifier"
|
"decl/internal/identifier"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -17,6 +18,10 @@ var (
|
|||||||
|
|
||||||
type URI identifier.ID
|
type URI identifier.ID
|
||||||
|
|
||||||
|
func NewURI(u *url.URL) URI {
|
||||||
|
return URI(u.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (u URI) NewResource(document data.Document) (newResource data.Resource, err error) {
|
func (u URI) NewResource(document data.Document) (newResource data.Resource, err error) {
|
||||||
if document == nil {
|
if document == nil {
|
||||||
declaration := NewDeclaration()
|
declaration := NewDeclaration()
|
||||||
@ -62,3 +67,18 @@ func (u URI) Extension() (string, string) {
|
|||||||
return (identifier.ID)(u).Extension()
|
return (identifier.ID)(u).Extension()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u URI) ContentType() string {
|
||||||
|
var ext strings.Builder
|
||||||
|
exttype, fileext := u.Extension()
|
||||||
|
if fileext == "" {
|
||||||
|
return exttype
|
||||||
|
}
|
||||||
|
ext.WriteString(exttype)
|
||||||
|
ext.WriteRune('.')
|
||||||
|
ext.WriteString(fileext)
|
||||||
|
return ext.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u URI) IsEmpty() bool {
|
||||||
|
return (identifier.ID)(u).IsEmpty()
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user