moved extractor/emitter code to the fan pkg
This commit is contained in:
parent
4eec426853
commit
38f8831275
@ -1,134 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"log/slog"
|
|
||||||
"decl/internal/codec"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BlockType struct {
|
|
||||||
Name string `json:"name" yaml:"name"`
|
|
||||||
Type TypeName `json:"type" yaml:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Block struct {
|
|
||||||
Name string `json:"name" yaml:"name"`
|
|
||||||
Type TypeName `json:"type" yaml:"type"`
|
|
||||||
Values Configuration `json:"values" yaml:"values"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBlock() *Block {
|
|
||||||
return &Block{ Type: "generic" }
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Block) Clone() *Block {
|
|
||||||
return &Block {
|
|
||||||
Type: b.Type,
|
|
||||||
Values: b.Values.Clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Block) Load(r io.Reader) error {
|
|
||||||
return codec.NewYAMLDecoder(r).Decode(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Block) LoadBlock(yamlBlock string) (err error) {
|
|
||||||
err = codec.NewYAMLStringDecoder(yamlBlock).Decode(b)
|
|
||||||
slog.Info("LoadBlock()", "yaml", yamlBlock, "object", b, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Block) NewConfiguration() error {
|
|
||||||
uri := fmt.Sprintf("%s://", b.Type)
|
|
||||||
newConfig, err := ConfigTypes.New(uri)
|
|
||||||
b.Values = newConfig
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Block) GetValue(key string) (any, error) {
|
|
||||||
return b.Values.GetValue(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Block) Configuration() Configuration {
|
|
||||||
return b.Values
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Block) SetURI(uri string) (e error) {
|
|
||||||
b.Values = NewConfiguration(uri)
|
|
||||||
if b.Values == nil {
|
|
||||||
return ErrUnknownConfigurationType
|
|
||||||
}
|
|
||||||
b.Type = TypeName(b.Values.Type())
|
|
||||||
_,e = b.Values.Read(context.Background())
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (b *Block) UnmarshalValue(value *BlockType) error {
|
|
||||||
b.Name = value.Name
|
|
||||||
if value.Type == "" {
|
|
||||||
b.Type = "generic"
|
|
||||||
} else {
|
|
||||||
b.Type = value.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
newConfig, configErr := ConfigTypes.New(fmt.Sprintf("%s://", b.Type))
|
|
||||||
if configErr != nil {
|
|
||||||
return configErr
|
|
||||||
}
|
|
||||||
b.Values = newConfig
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Block) UnmarshalYAML(value *yaml.Node) error {
|
|
||||||
t := &BlockType{}
|
|
||||||
if unmarshalConfigurationTypeErr := value.Decode(t); unmarshalConfigurationTypeErr != nil {
|
|
||||||
return unmarshalConfigurationTypeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.UnmarshalValue(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
configurationVals := struct {
|
|
||||||
Values yaml.Node `json:"values"`
|
|
||||||
}{}
|
|
||||||
if unmarshalValuesErr := value.Decode(&configurationVals); unmarshalValuesErr != nil {
|
|
||||||
return unmarshalValuesErr
|
|
||||||
}
|
|
||||||
if unmarshalConfigurationErr := configurationVals.Values.Decode(b.Values); unmarshalConfigurationErr != nil {
|
|
||||||
return unmarshalConfigurationErr
|
|
||||||
}
|
|
||||||
_, readErr := b.Values.Read(context.Background())
|
|
||||||
return readErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Block) UnmarshalJSON(data []byte) error {
|
|
||||||
t := &BlockType{}
|
|
||||||
if unmarshalConfigurationTypeErr := json.Unmarshal(data, t); unmarshalConfigurationTypeErr != nil {
|
|
||||||
return unmarshalConfigurationTypeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.UnmarshalValue(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
configurationVals := struct {
|
|
||||||
Values Configuration `json:"values"`
|
|
||||||
}{Values: b.Values}
|
|
||||||
if unmarshalValuesErr := json.Unmarshal(data, &configurationVals); unmarshalValuesErr != nil {
|
|
||||||
return unmarshalValuesErr
|
|
||||||
}
|
|
||||||
_, readErr := b.Values.Read(context.Background())
|
|
||||||
return readErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Block) MarshalYAML() (any, error) {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "fmt"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
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) {
|
|
||||||
configYaml := `
|
|
||||||
name: "foo"
|
|
||||||
values:
|
|
||||||
http_user: "test"
|
|
||||||
http_pass: "password"
|
|
||||||
`
|
|
||||||
docReader := strings.NewReader(configYaml)
|
|
||||||
|
|
||||||
block := NewBlock()
|
|
||||||
assert.NotNil(t, block)
|
|
||||||
assert.Nil(t, block.Load(docReader))
|
|
||||||
assert.Equal(t, "foo", block.Name)
|
|
||||||
val, err := block.GetValue("http_user")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, "test", val)
|
|
||||||
|
|
||||||
missingVal, missingErr := block.GetValue("content")
|
|
||||||
assert.ErrorIs(t, missingErr, ErrUnknownConfigurationKey)
|
|
||||||
assert.Nil(t, missingVal)
|
|
||||||
}
|
|
@ -5,15 +5,18 @@ package config
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
|
"decl/internal/data"
|
||||||
|
"decl/internal/folio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ConfigTypes.Register([]string{"certificate"}, func(u *url.URL) Configuration {
|
folio.DocumentRegistry.ConfigurationTypes.Register([]string{"certificate"}, func(u *url.URL) data.Configuration {
|
||||||
c := NewCertificate()
|
c := NewCertificate()
|
||||||
return c
|
return c
|
||||||
})
|
})
|
||||||
@ -26,6 +29,18 @@ func NewCertificate() *Certificate {
|
|||||||
return &c
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Certificate) URI() string {
|
||||||
|
return fmt.Sprintf("%s://%s", c.Type(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Certificate) SetURI(uri string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Certificate) SetParsedURI(uri *url.URL) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Certificate) Read(ctx context.Context) ([]byte, error) {
|
func (c *Certificate) Read(ctx context.Context) ([]byte, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -61,7 +76,7 @@ func (c *Certificate) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Certificate) Clone() Configuration {
|
func (c *Certificate) Clone() data.Configuration {
|
||||||
jsonGeneric, _ := json.Marshal(c)
|
jsonGeneric, _ := json.Marshal(c)
|
||||||
clone := NewCertificate()
|
clone := NewCertificate()
|
||||||
if unmarshalErr := json.Unmarshal(jsonGeneric, &clone); unmarshalErr != nil {
|
if unmarshalErr := json.Unmarshal(jsonGeneric, &clone); unmarshalErr != nil {
|
||||||
@ -77,7 +92,12 @@ func (c *Certificate) Type() string {
|
|||||||
func (c *Certificate) GetValue(name string) (result any, err error) {
|
func (c *Certificate) GetValue(name string) (result any, err error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
if result, ok = (*c)[name]; !ok {
|
if result, ok = (*c)[name]; !ok {
|
||||||
err = ErrUnknownConfigurationKey
|
err = data.ErrUnknownConfigurationKey
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Certificate) Has(key string) (ok bool) {
|
||||||
|
_, ok = (*c)[key]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "context"
|
|
||||||
_ "encoding/json"
|
|
||||||
_ "fmt"
|
|
||||||
_ "gopkg.in/yaml.v3"
|
|
||||||
_ "net/url"
|
|
||||||
_ "regexp"
|
|
||||||
_ "strings"
|
|
||||||
_ "os"
|
|
||||||
_ "io"
|
|
||||||
_ "compress/gzip"
|
|
||||||
_ "archive/tar"
|
|
||||||
_ "errors"
|
|
||||||
_ "path/filepath"
|
|
||||||
_ "decl/internal/codec"
|
|
||||||
"embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigurationSelector func(b *Block) bool
|
|
||||||
|
|
||||||
type ConfigSource interface {
|
|
||||||
Type() string
|
|
||||||
|
|
||||||
Extract(filter ConfigurationSelector) ([]*Document, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfigSource(uri string) ConfigSource {
|
|
||||||
s, e := ConfigSourceTypes.New(uri)
|
|
||||||
if e == nil {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:embed configs/*.yaml
|
|
||||||
var configFiles embed.FS
|
|
||||||
|
|
||||||
func Configurations() ([]*Document, error) {
|
|
||||||
fs := NewConfigFS(configFiles)
|
|
||||||
return fs.Extract(nil)
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "context"
|
|
||||||
_ "encoding/json"
|
|
||||||
_ "fmt"
|
|
||||||
_ "gopkg.in/yaml.v3"
|
|
||||||
_ "net/url"
|
|
||||||
_ "regexp"
|
|
||||||
_ "strings"
|
|
||||||
_ "os"
|
|
||||||
_ "io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigTarget interface {
|
|
||||||
Type() string
|
|
||||||
|
|
||||||
EmitResources(documents []*Document, filter ConfigurationSelector) error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfigTarget(uri string) ConfigTarget {
|
|
||||||
s, e := ConfigTargetTypes.New(uri)
|
|
||||||
if e == nil {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
_ "net/url"
|
|
||||||
_ "decl/internal/codec"
|
|
||||||
_ "io"
|
|
||||||
"decl/internal/types"
|
|
||||||
"decl/internal/data"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrUnknownConfigurationType = errors.New("Unknown configuration type")
|
|
||||||
ErrUnknownConfigurationKey = errors.New("Unknown configuration key")
|
|
||||||
ConfigTypes *types.Types[Configuration] = types.New[Configuration]()
|
|
||||||
ConfigSourceTypes *types.Types[ConfigSource] = types.New[ConfigSource]()
|
|
||||||
ConfigTargetTypes *types.Types[ConfigTarget] = types.New[ConfigTarget]()
|
|
||||||
)
|
|
||||||
|
|
||||||
type TypeName string //`json:"type"`
|
|
||||||
|
|
||||||
type Configuration interface {
|
|
||||||
Type() string
|
|
||||||
data.Reader
|
|
||||||
GetValue(name string) (any, error)
|
|
||||||
Clone() Configuration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfiguration(uri string) Configuration {
|
|
||||||
c, e := ConfigTypes.New(uri)
|
|
||||||
if e == nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *TypeName) UnmarshalJSON(b []byte) error {
|
|
||||||
ConfigTypeName := strings.Trim(string(b), "\"")
|
|
||||||
if ConfigTypes.Has(ConfigTypeName) {
|
|
||||||
*n = TypeName(ConfigTypeName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w: %s", ErrUnknownConfigurationType, ConfigTypeName)
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "context"
|
|
||||||
_ "fmt"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
_ "log"
|
|
||||||
_ "os"
|
|
||||||
_ "path/filepath"
|
|
||||||
_ "strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewConfiguration(t *testing.T) {
|
|
||||||
configurationUri := "generic://"
|
|
||||||
testConfig := NewConfiguration(configurationUri)
|
|
||||||
assert.NotNil(t, testConfig)
|
|
||||||
v, _ := testConfig.GetValue("foo")
|
|
||||||
assert.Nil(t, v)
|
|
||||||
}
|
|
@ -1,202 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
_ "net/url"
|
|
||||||
"github.com/sters/yaml-diff/yamldiff"
|
|
||||||
"strings"
|
|
||||||
"decl/internal/codec"
|
|
||||||
_ "context"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrConfigUndefinedName = errors.New("Config block is missing a defined name")
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigNamesMap[Value any] map[string]Value
|
|
||||||
|
|
||||||
type Document struct {
|
|
||||||
names ConfigNamesMap[*Block]
|
|
||||||
ConfigBlocks []Block `json:"configurations" yaml:"configurations"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDocument() *Document {
|
|
||||||
return &Document{ names: make(ConfigNamesMap[*Block]) }
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) Filter(filter ConfigurationSelector) []*Block {
|
|
||||||
configurations := make([]*Block, 0, len(d.ConfigBlocks))
|
|
||||||
for i := range d.ConfigBlocks {
|
|
||||||
filterConfig := &d.ConfigBlocks[i]
|
|
||||||
if filter == nil || filter(filterConfig) {
|
|
||||||
configurations = append(configurations, &d.ConfigBlocks[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return configurations
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) Clone() *Document {
|
|
||||||
clone := NewDocument()
|
|
||||||
clone.ConfigBlocks = make([]Block, len(d.ConfigBlocks))
|
|
||||||
for i, res := range d.ConfigBlocks {
|
|
||||||
clone.ConfigBlocks[i] = *res.Clone()
|
|
||||||
}
|
|
||||||
return clone
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) Load(r io.Reader) error {
|
|
||||||
c := codec.NewYAMLDecoder(r)
|
|
||||||
return c.Decode(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) Validate() error {
|
|
||||||
jsonDocument, jsonErr := d.JSON()
|
|
||||||
if jsonErr == nil {
|
|
||||||
s := NewSchema("document")
|
|
||||||
err := s.Validate(string(jsonDocument))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) Configurations() []Block {
|
|
||||||
return d.ConfigBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) Generate(w io.Writer) error {
|
|
||||||
e := codec.NewYAMLEncoder(w)
|
|
||||||
err := e.Encode(d);
|
|
||||||
if err == nil {
|
|
||||||
return e.Close()
|
|
||||||
}
|
|
||||||
e.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) Append(doc *Document) {
|
|
||||||
if doc != nil {
|
|
||||||
for i := range doc.ConfigBlocks {
|
|
||||||
slog.Info("Document.Append()", "doc", doc, "block", doc.ConfigBlocks[i], "targetdoc", d)
|
|
||||||
d.AddConfigurationBlock(doc.ConfigBlocks[i].Name, doc.ConfigBlocks[i].Type, doc.ConfigBlocks[i].Values)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) AddConfigurationBlock(configurationName string, configurationType TypeName, configuration Configuration) {
|
|
||||||
cfg := NewBlock()
|
|
||||||
cfg.Name = configurationName
|
|
||||||
cfg.Type = configurationType
|
|
||||||
cfg.Values = configuration
|
|
||||||
d.names[cfg.Name] = cfg
|
|
||||||
d.ConfigBlocks = append(d.ConfigBlocks, *cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) AddConfiguration(uri string) error {
|
|
||||||
cfg := NewBlock()
|
|
||||||
if e := cfg.SetURI(uri); e != nil {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
if cfg.Name == "" {
|
|
||||||
return ErrConfigUndefinedName
|
|
||||||
}
|
|
||||||
d.names[cfg.Name] = cfg
|
|
||||||
d.ConfigBlocks = append(d.ConfigBlocks, *cfg)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) Has(name string) bool {
|
|
||||||
_, ok := d.names[name]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) Get(name string) *Block {
|
|
||||||
return d.names[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) JSON() ([]byte, error) {
|
|
||||||
return json.Marshal(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) YAML() ([]byte, error) {
|
|
||||||
return yaml.Marshal(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) IndexName() error {
|
|
||||||
for _, b := range d.ConfigBlocks {
|
|
||||||
d.names[b.Name] = &b
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) UnmarshalYAML(value *yaml.Node) error {
|
|
||||||
documentBlocks := struct {
|
|
||||||
ConfigBlocks *[]Block `json:"configurations" yaml:"configurations"`
|
|
||||||
}{ ConfigBlocks: &d.ConfigBlocks }
|
|
||||||
if unmarshalDocumentErr := value.Decode(documentBlocks); unmarshalDocumentErr != nil {
|
|
||||||
return unmarshalDocumentErr
|
|
||||||
}
|
|
||||||
return d.IndexName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) UnmarshalJSON(data []byte) error {
|
|
||||||
documentBlocks := struct {
|
|
||||||
ConfigBlocks *[]Block `json:"configurations" yaml:"configurations"`
|
|
||||||
}{ ConfigBlocks: &d.ConfigBlocks }
|
|
||||||
if unmarshalDocumentErr := json.Unmarshal(data, &documentBlocks); unmarshalDocumentErr != nil {
|
|
||||||
return unmarshalDocumentErr
|
|
||||||
}
|
|
||||||
return d.IndexName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) Diff(with *Document, output io.Writer) (returnOutput string, diffErr error) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
returnOutput = ""
|
|
||||||
diffErr = fmt.Errorf("%s", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
slog.Info("Document.Diff()")
|
|
||||||
opts := []yamldiff.DoOptionFunc{}
|
|
||||||
if output == nil {
|
|
||||||
output = &strings.Builder{}
|
|
||||||
}
|
|
||||||
ydata, yerr := d.YAML()
|
|
||||||
if yerr != nil {
|
|
||||||
return "", yerr
|
|
||||||
}
|
|
||||||
yamlDiff,yamlDiffErr := yamldiff.Load(string(ydata))
|
|
||||||
if yamlDiffErr != nil {
|
|
||||||
return "", yamlDiffErr
|
|
||||||
}
|
|
||||||
|
|
||||||
wdata,werr := with.YAML()
|
|
||||||
if werr != nil {
|
|
||||||
return "", werr
|
|
||||||
}
|
|
||||||
withDiff,withDiffErr := yamldiff.Load(string(wdata))
|
|
||||||
if withDiffErr != nil {
|
|
||||||
return "", withDiffErr
|
|
||||||
}
|
|
||||||
|
|
||||||
for _,docDiffResults := range yamldiff.Do(yamlDiff, withDiff, opts...) {
|
|
||||||
slog.Info("Diff()", "diff", docDiffResults, "dump", docDiffResults.Dump())
|
|
||||||
_,e := output.Write([]byte(docDiffResults.Dump()))
|
|
||||||
if e != nil {
|
|
||||||
return "", e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
slog.Info("Document.Diff() ", "document.yaml", ydata, "with.yaml", wdata)
|
|
||||||
if stringOutput, ok := output.(*strings.Builder); ok {
|
|
||||||
return stringOutput.String(), nil
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewDocument(t *testing.T) {
|
|
||||||
d := NewDocument()
|
|
||||||
assert.NotNil(t, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDocumentLoader(t *testing.T) {
|
|
||||||
document := `
|
|
||||||
---
|
|
||||||
configurations:
|
|
||||||
- type: generic
|
|
||||||
name: global
|
|
||||||
values:
|
|
||||||
install_dir: /opt/jx
|
|
||||||
- name: system
|
|
||||||
values:
|
|
||||||
dist: ubuntu
|
|
||||||
release: focal
|
|
||||||
`
|
|
||||||
d := NewDocument()
|
|
||||||
assert.NotNil(t, d)
|
|
||||||
|
|
||||||
docReader := strings.NewReader(document)
|
|
||||||
|
|
||||||
e := d.Load(docReader)
|
|
||||||
assert.Nil(t, e)
|
|
||||||
|
|
||||||
configurations := d.Configurations()
|
|
||||||
assert.Equal(t, 2, len(configurations))
|
|
||||||
|
|
||||||
b := d.Get("system")
|
|
||||||
assert.NotNil(t, b)
|
|
||||||
cfg := b.Configuration()
|
|
||||||
value, valueErr := cfg.GetValue("dist")
|
|
||||||
assert.Nil(t, valueErr)
|
|
||||||
assert.Equal(t, "ubuntu", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDocumentJSONSchema(t *testing.T) {
|
|
||||||
document := NewDocument()
|
|
||||||
document.ConfigBlocks = []Block{}
|
|
||||||
e := document.Validate()
|
|
||||||
assert.Nil(t, e)
|
|
||||||
}
|
|
@ -5,15 +5,18 @@ package config
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"decl/internal/command"
|
"decl/internal/command"
|
||||||
|
"decl/internal/data"
|
||||||
|
"decl/internal/folio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ConfigTypes.Register([]string{"exec"}, func(u *url.URL) Configuration {
|
folio.DocumentRegistry.ConfigurationTypes.Register([]string{"exec"}, func(u *url.URL) data.Configuration {
|
||||||
x := NewExec()
|
x := NewExec()
|
||||||
return x
|
return x
|
||||||
})
|
})
|
||||||
@ -32,6 +35,18 @@ func NewExec() *Exec {
|
|||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Exec) SetURI(uri string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Exec) SetParsedURI(uri *url.URL) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Exec) URI() string {
|
||||||
|
return fmt.Sprintf("%s://%s", x.Type(), x.Path)
|
||||||
|
}
|
||||||
|
|
||||||
func (x *Exec) Read(ctx context.Context) ([]byte, error) {
|
func (x *Exec) Read(ctx context.Context) ([]byte, error) {
|
||||||
out, err := x.ReadCommand.Execute(x)
|
out, err := x.ReadCommand.Execute(x)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -78,7 +93,7 @@ func (x *Exec) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (x *Exec) Clone() Configuration {
|
func (x *Exec) Clone() data.Configuration {
|
||||||
clone := NewExec()
|
clone := NewExec()
|
||||||
clone.Path = x.Path
|
clone.Path = x.Path
|
||||||
clone.Args = x.Args
|
clone.Args = x.Args
|
||||||
@ -95,11 +110,15 @@ func (x *Exec) Type() string {
|
|||||||
func (x *Exec) GetValue(name string) (result any, err error) {
|
func (x *Exec) GetValue(name string) (result any, err error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
if result, ok = x.Values[name]; !ok {
|
if result, ok = x.Values[name]; !ok {
|
||||||
err = ErrUnknownConfigurationKey
|
err = data.ErrUnknownConfigurationKey
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Exec) Has(key string) (ok bool) {
|
||||||
|
_, ok = x.Values[key]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (ex *Exec) NewReadConfigCommand() {
|
func (ex *Exec) NewReadConfigCommand() {
|
||||||
ex.ReadCommand = command.NewCommand()
|
ex.ReadCommand = command.NewCommand()
|
||||||
|
@ -1,143 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "context"
|
|
||||||
_ "encoding/json"
|
|
||||||
_ "fmt"
|
|
||||||
_ "gopkg.in/yaml.v3"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
|
||||||
"decl/internal/transport"
|
|
||||||
"decl/internal/codec"
|
|
||||||
_ "os"
|
|
||||||
"io"
|
|
||||||
"errors"
|
|
||||||
"log/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigFile struct {
|
|
||||||
Path string `yaml:"path" json:"path"`
|
|
||||||
Format codec.Format `yaml:"format" json:"format"`
|
|
||||||
reader *transport.Reader `yaml:"-" json:"-"`
|
|
||||||
writer *transport.Writer `yaml:"-" json:"-"`
|
|
||||||
encoder codec.Encoder `yaml:"-" json:"-"`
|
|
||||||
decoder codec.Decoder `yaml:"-" json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfigFile() *ConfigFile {
|
|
||||||
return &ConfigFile{ Format: codec.FormatYaml }
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfigFileFromURI(u *url.URL) *ConfigFile {
|
|
||||||
t := NewConfigFile()
|
|
||||||
if u.Scheme == "file" {
|
|
||||||
t.Path,_ = filepath.Abs(filepath.Join(u.Hostname(), u.Path))
|
|
||||||
} else {
|
|
||||||
t.Path = filepath.Join(u.Hostname(), u.Path)
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfigFileSource(u *url.URL) *ConfigFile {
|
|
||||||
t := NewConfigFileFromURI(u)
|
|
||||||
t.reader,_ = transport.NewReader(u)
|
|
||||||
contentType := codec.Format(t.reader.ContentType())
|
|
||||||
if contentType.Validate() == nil {
|
|
||||||
if formatErr := t.Format.Set(t.reader.ContentType()); formatErr != nil {
|
|
||||||
panic(formatErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.decoder = codec.NewDecoder(t.reader, t.Format)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfigFileTarget(u *url.URL) *ConfigFile {
|
|
||||||
t := NewConfigFileFromURI(u)
|
|
||||||
t.writer,_ = transport.NewWriter(u)
|
|
||||||
contentType := codec.Format(t.writer.ContentType())
|
|
||||||
if contentType.Validate() == nil {
|
|
||||||
if formatErr := t.Format.Set(t.writer.ContentType()); formatErr != nil {
|
|
||||||
panic(formatErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.encoder = codec.NewEncoder(t.writer, t.Format)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ConfigSourceTypes.Register([]string{"file"}, func(u *url.URL) ConfigSource {
|
|
||||||
return NewConfigFileSource(u)
|
|
||||||
})
|
|
||||||
|
|
||||||
ConfigSourceTypes.Register([]string{"pb","pb.gz","json","json.gz","yaml","yml","yaml.gz","yml.gz"}, func(u *url.URL) ConfigSource {
|
|
||||||
return NewConfigFileSource(u)
|
|
||||||
})
|
|
||||||
|
|
||||||
ConfigTargetTypes.Register([]string{"file"}, func(u *url.URL) ConfigTarget {
|
|
||||||
return NewConfigFileTarget(u)
|
|
||||||
})
|
|
||||||
|
|
||||||
ConfigTargetTypes.Register([]string{"pb","pb.gz","json","json.gz","yaml","yml","yaml.gz","yml.gz"}, func(u *url.URL) ConfigTarget {
|
|
||||||
return NewConfigFileTarget(u)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (c *ConfigFile) Type() string { return "file" }
|
|
||||||
|
|
||||||
func (c *ConfigFile) Extract(filter ConfigurationSelector) ([]*Document, error) {
|
|
||||||
documents := make([]*Document, 0, 100)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
c.reader.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
slog.Info("Extract()", "documents", documents)
|
|
||||||
index := 0
|
|
||||||
for {
|
|
||||||
doc := NewDocument()
|
|
||||||
e := c.decoder.Decode(doc)
|
|
||||||
if errors.Is(e, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if e != nil {
|
|
||||||
return documents, e
|
|
||||||
}
|
|
||||||
slog.Info("Extract()", "res", doc.ConfigBlocks[0].Values)
|
|
||||||
if validationErr := doc.Validate(); validationErr != nil {
|
|
||||||
return documents, validationErr
|
|
||||||
}
|
|
||||||
documents = append(documents, doc)
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
return documents, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigFile) EmitResources(documents []*Document, filter ConfigurationSelector) (error) {
|
|
||||||
defer func() {
|
|
||||||
c.encoder.Close()
|
|
||||||
c.writer.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for _, doc := range documents {
|
|
||||||
emitDoc := NewDocument()
|
|
||||||
if validationErr := doc.Validate(); validationErr != nil {
|
|
||||||
return validationErr
|
|
||||||
}
|
|
||||||
for _, block := range doc.Filter(filter) {
|
|
||||||
emitDoc.ConfigBlocks = append(emitDoc.ConfigBlocks, *block)
|
|
||||||
}
|
|
||||||
slog.Info("EmitResources", "doctarget", c, "encoder", c.encoder, "emit", emitDoc)
|
|
||||||
if documentErr := c.encoder.Encode(emitDoc); documentErr != nil {
|
|
||||||
slog.Info("EmitResources", "err", documentErr)
|
|
||||||
return documentErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigFile) Close() (error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "context"
|
|
||||||
_ "encoding/json"
|
|
||||||
_ "fmt"
|
|
||||||
_ "gopkg.in/yaml.v3"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
|
||||||
"decl/internal/codec"
|
|
||||||
"os"
|
|
||||||
"io"
|
|
||||||
"errors"
|
|
||||||
"io/fs"
|
|
||||||
"log/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
type ConfigFS struct {
|
|
||||||
Path string `yaml:"path" json:"path"`
|
|
||||||
subDirsStack []fs.FS `yaml:"-" json:"-"`
|
|
||||||
fsys fs.FS `yaml:"-" json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfigFS(fsys fs.FS) *ConfigFS {
|
|
||||||
return &ConfigFS{
|
|
||||||
subDirsStack: make([]fs.FS, 0, 100),
|
|
||||||
fsys: fsys,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ConfigSourceTypes.Register([]string{"fs"}, func(u *url.URL) ConfigSource {
|
|
||||||
|
|
||||||
t := NewConfigFS(nil)
|
|
||||||
t.Path,_ = filepath.Abs(filepath.Join(u.Hostname(), u.Path))
|
|
||||||
t.fsys = os.DirFS(t.Path)
|
|
||||||
return t
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigFS) Type() string { return "fs" }
|
|
||||||
|
|
||||||
func (c *ConfigFS) ExtractDirectory(fsys fs.FS) ([]*Document, error) {
|
|
||||||
documents := make([]*Document, 0, 100)
|
|
||||||
|
|
||||||
files, err := fs.ReadDir(fsys, ".")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _,file := range files {
|
|
||||||
|
|
||||||
slog.Info("ConfigFS.ExtractDirectory", "file", file)
|
|
||||||
if file.IsDir() {
|
|
||||||
dir, subErr := fs.Sub(fsys, file.Name())
|
|
||||||
if subErr != nil {
|
|
||||||
return nil, subErr
|
|
||||||
}
|
|
||||||
c.subDirsStack = append(c.subDirsStack, dir)
|
|
||||||
} else {
|
|
||||||
fileHandle, fileErr := fsys.Open(file.Name())
|
|
||||||
if fileErr != nil {
|
|
||||||
return nil, fileErr
|
|
||||||
}
|
|
||||||
decoder := codec.NewYAMLDecoder(fileHandle)
|
|
||||||
doc := NewDocument()
|
|
||||||
e := decoder.Decode(doc)
|
|
||||||
if errors.Is(e, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if e != nil {
|
|
||||||
return documents, e
|
|
||||||
}
|
|
||||||
slog.Info("ConfigFS.ExtractDirectory", "doc", doc)
|
|
||||||
if validationErr := doc.Validate(); validationErr != nil {
|
|
||||||
return documents, validationErr
|
|
||||||
}
|
|
||||||
documents = append(documents, doc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return documents, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigFS) Extract(filter ConfigurationSelector) ([]*Document, error) {
|
|
||||||
documents := make([]*Document, 0, 100)
|
|
||||||
|
|
||||||
path := c.fsys
|
|
||||||
c.subDirsStack = append(c.subDirsStack, path)
|
|
||||||
|
|
||||||
for {
|
|
||||||
if len(c.subDirsStack) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var dirPath fs.FS
|
|
||||||
dirPath, c.subDirsStack = c.subDirsStack[len(c.subDirsStack) - 1], c.subDirsStack[:len(c.subDirsStack) - 1]
|
|
||||||
docs, dirErr := c.ExtractDirectory(dirPath)
|
|
||||||
if dirErr != nil {
|
|
||||||
return documents, dirErr
|
|
||||||
}
|
|
||||||
|
|
||||||
documents = append(documents, docs...)
|
|
||||||
}
|
|
||||||
return documents, nil
|
|
||||||
}
|
|
||||||
|
|
@ -6,10 +6,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"fmt"
|
||||||
|
"decl/internal/data"
|
||||||
|
"decl/internal/folio"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ConfigTypes.Register([]string{"generic"}, func(u *url.URL) Configuration {
|
folio.DocumentRegistry.ConfigurationTypes.Register([]string{"generic"}, func(u *url.URL) data.Configuration {
|
||||||
g := NewGeneric[any]()
|
g := NewGeneric[any]()
|
||||||
return g
|
return g
|
||||||
})
|
})
|
||||||
@ -22,7 +25,19 @@ func NewGeneric[Value any]() *Generic[Value] {
|
|||||||
return &g
|
return &g
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Generic[Value]) Clone() Configuration {
|
func (g *Generic[Value]) URI() string {
|
||||||
|
return fmt.Sprintf("%s://%s", g.Type(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generic[Value]) SetURI(uri string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generic[Value]) SetParsedURI(uri *url.URL) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generic[Value]) Clone() data.Configuration {
|
||||||
jsonGeneric, _ := json.Marshal(g)
|
jsonGeneric, _ := json.Marshal(g)
|
||||||
clone := NewGeneric[Value]()
|
clone := NewGeneric[Value]()
|
||||||
if unmarshalErr := json.Unmarshal(jsonGeneric, clone); unmarshalErr != nil {
|
if unmarshalErr := json.Unmarshal(jsonGeneric, clone); unmarshalErr != nil {
|
||||||
@ -42,7 +57,12 @@ func (g *Generic[Value]) Read(context.Context) ([]byte, error) {
|
|||||||
func (g *Generic[Value]) GetValue(name string) (result any, err error) {
|
func (g *Generic[Value]) GetValue(name string) (result any, err error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
if result, ok = (*g)[name]; !ok {
|
if result, ok = (*g)[name]; !ok {
|
||||||
err = ErrUnknownConfigurationKey
|
err = data.ErrUnknownConfigurationKey
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Generic[Value]) Has(key string) (ok bool) {
|
||||||
|
_, ok = (*g)[key]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -75,7 +75,7 @@ func (s *System) Read(context.Context) ([]byte, error) {
|
|||||||
func (s *System) GetValue(name string) (result any, err error) {
|
func (s *System) GetValue(name string) (result any, err error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
if result, ok = (*s)[name]; !ok {
|
if result, ok = (*s)[name]; !ok {
|
||||||
err = ErrUnknownConfigurationKey
|
err = data.ErrUnknownConfigurationKey
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user