add support of import search paths [doublejynx/jx#7]
Some checks failed
Lint / golangci-lint (push) Failing after 10m1s
Declarative Tests / test (push) Failing after 14s

This commit is contained in:
Matthew Rich 2024-10-16 10:26:42 -07:00
parent b61010b99a
commit 8feb7b8d56
59 changed files with 1255 additions and 817 deletions

View File

@ -10,10 +10,10 @@ _ "decl/internal/config"
_ "decl/internal/resource" _ "decl/internal/resource"
_ "decl/internal/fan" _ "decl/internal/fan"
"decl/internal/builtin" "decl/internal/builtin"
_ "errors" _ "errors"
"flag" "flag"
"fmt" "fmt"
_ "gopkg.in/yaml.v3" _ "gopkg.in/yaml.v3"
"io" "io"
"log/slog" "log/slog"
"os" "os"
@ -159,7 +159,7 @@ func main() {
DefaultConfigurations, configErr := builtin.BuiltInDocuments() DefaultConfigurations, configErr := builtin.BuiltInDocuments()
if configErr != nil { if configErr != nil {
slog.Error("Failed loading default configuration", "error", configErr) slog.Warn("Failed loading default configuration", "error", configErr)
} }
ConfigDoc.AppendConfigurations(DefaultConfigurations) ConfigDoc.AppendConfigurations(DefaultConfigurations)

View File

@ -25,6 +25,15 @@ resources:
group: "jx" group: "jx"
mode: "0770" mode: "0770"
filetype: directory filetype: directory
- type: file
transition: update
config: confdir
attributes:
path: "lib"
owner: "root"
group: "jx"
mode: "0770"
filetype: directory
- type: file - type: file
transition: update transition: update
config: confdir config: confdir

View File

@ -0,0 +1,16 @@
imports:
- file://documents/config.jx.yaml
configurations:
- name: bindir
values:
prefix: /usr/local/bin
resources:
- type: file
transition: update
config: bindir
attributes:
path: "jx"
owner: "root"
group: "root"
mode: "0755"
sourceref: file://jx

View File

@ -126,7 +126,7 @@ func (a *App) ImportResource(ctx context.Context, uri string) (err error) {
a.Documents = append(a.Documents, folio.DocumentRegistry.NewDocument("")) a.Documents = append(a.Documents, folio.DocumentRegistry.NewDocument(""))
} }
resourceURI := folio.URI(uri) resourceURI := folio.URI(uri)
u := resourceURI.Parse() u := resourceURI.Parse().URL()
if u == nil { if u == nil {
return fmt.Errorf("Failed adding resource: %s", uri) return fmt.Errorf("Failed adding resource: %s", uri)
} }
@ -147,9 +147,19 @@ func (a *App) ImportResource(ctx context.Context, uri string) (err error) {
} }
func (a *App) ImportSource(uri string) (loadedDocuments []data.Document, err error) { func (a *App) ImportSource(uri string) (loadedDocuments []data.Document, err error) {
if loadedDocuments, err = folio.DocumentRegistry.Load(folio.URI(uri)); err == nil && loadedDocuments != nil { if source := folio.URI(uri).Parse().URL(); source != nil {
a.Documents = append(a.Documents, loadedDocuments...) if source.Scheme == "" {
source.Scheme = "file"
}
slog.Info("Client.ImportSource()", "uri", uri, "source", source, "error", err)
if loadedDocuments, err = folio.DocumentRegistry.LoadFromParsedURI(source); err == nil && loadedDocuments != nil {
a.Documents = append(a.Documents, loadedDocuments...)
}
} else {
err = folio.ErrInvalidURI
} }
slog.Info("Client.ImportSource()", "uri", uri, "error", err) slog.Info("Client.ImportSource()", "uri", uri, "error", err)
return return
} }

View File

@ -37,7 +37,7 @@ func (c *Certificate) SetURI(uri string) error {
return nil return nil
} }
func (c *Certificate) SetParsedURI(uri *url.URL) error { func (c *Certificate) SetParsedURI(uri data.URIParser) error {
return nil return nil
} }

View File

@ -39,7 +39,7 @@ func (x *Exec) SetURI(uri string) error {
return nil return nil
} }
func (x *Exec) SetParsedURI(uri *url.URL) error { func (x *Exec) SetParsedURI(uri data.URIParser) error {
return nil return nil
} }

View File

@ -35,7 +35,7 @@ func (g *Generic[Value]) SetURI(uri string) error {
return nil return nil
} }
func (g *Generic[Value]) SetParsedURI(uri *url.URL) error { func (g *Generic[Value]) SetParsedURI(uri data.URIParser) error {
return nil return nil
} }

View File

@ -37,6 +37,9 @@ func NewSystem() *System {
s[k] = v s[k] = v
} }
s.CurrentUser() s.CurrentUser()
s["importpath"] = []string {
"/etc/jx/lib",
}
return &s return &s
} }
@ -59,7 +62,7 @@ func (s *System) SetURI(uri string) error {
return nil return nil
} }
func (s *System) SetParsedURI(uri *url.URL) error { func (s *System) SetParsedURI(uri data.URIParser) error {
return nil return nil
} }

View File

@ -8,7 +8,6 @@ import (
"decl/internal/codec" "decl/internal/codec"
"io" "io"
"decl/internal/mapper" "decl/internal/mapper"
"net/url"
) )
var ( var (
@ -44,7 +43,7 @@ type Document interface {
mapper.Mapper mapper.Mapper
NewResource(uri string) (Resource, error) NewResource(uri string) (Resource, error)
NewResourceFromParsedURI(uri *url.URL) (Resource, error) NewResourceFromParsedURI(uri URIParser) (Resource, error)
AddDeclaration(Declaration) AddDeclaration(Declaration)
AddResourceDeclaration(resourceType string, resourceDeclaration Resource) AddResourceDeclaration(resourceType string, resourceDeclaration Resource)

View File

@ -5,16 +5,34 @@ package data
import ( import (
"errors" "errors"
"net/url" "net/url"
"decl/internal/transport"
) )
var ( var (
ErrInvalidURI error = errors.New("Invalid URI") ErrInvalidURI error = errors.New("Invalid URI")
) )
type URIParser interface {
URL() *url.URL
NewResource(document Document) (newResource Resource, err error)
ConstructResource(res Resource) (err error)
Converter() (converter Converter, err error)
Exists() bool
ContentReaderStream() (*transport.Reader, error)
ContentWriterStream() (*transport.Writer, error)
String() string
SetURL(url *url.URL)
Extension() (string, string)
ContentType() string
IsEmpty() bool
}
type Identifier interface { type Identifier interface {
URI() string URI() string
SetURI(string) error SetParsedURI(URIParser) error
SetParsedURI(*url.URL) error
} }
type DocumentElement interface { type DocumentElement interface {

View File

@ -22,6 +22,11 @@ type StateTransformer interface {
Apply() error Apply() error
} }
// Used by the resource factory to initialize new resources.
type ResourceInitializer interface {
Init(uri URIParser) error
}
type Resource interface { type Resource interface {
Identifier Identifier
Type() string Type() string

View File

@ -11,6 +11,7 @@ type Factory[Product comparable] func(*url.URL) Product
type TypesRegistry[Product comparable] interface { type TypesRegistry[Product comparable] interface {
New(uri string) (result Product, err error) New(uri string) (result Product, err error)
NewFromParsedURI(uri *url.URL) (result Product, err error) NewFromParsedURI(uri *url.URL) (result Product, err error)
NewFromType(typename string) (result Product, err error)
Has(typename string) bool Has(typename string) bool
//Get(string) Factory[Product] //Get(string) Factory[Product]
} }

63
internal/ds/orderedset.go Normal file
View File

@ -0,0 +1,63 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package ds
import (
"log/slog"
)
type OrderedSet[Value comparable] struct {
Values []*Value
elements map[Value]int
}
func NewOrderedSet[Value comparable]() *OrderedSet[Value] {
return &OrderedSet[Value]{ elements: make(map[Value]int), Values: make([]*Value, 0, 10) }
}
func (s *OrderedSet[Value]) Add(value Value) {
slog.Info("OrderedSet.Add", "key", value, "s", s)
s.Values = append(s.Values, &value)
s.elements[value] = len(s.Values)
slog.Info("OrderedSet.Add", "key", value, "s", s, "v", &s.Values)
}
func (s *OrderedSet[Value]) Delete(key Value) {
slog.Info("OrderedSet.Delete", "key", key, "s", s, "size", len(s.Values))
if i, ok := s.elements[key]; ok {
i--
s.Values[i] = nil
delete(s.elements, key)
}
}
func (s *OrderedSet[Value]) Contains(value Value) (result bool) {
slog.Info("OrderedSet.Contains", "key", value, "s", s, "size", len(s.Values), "v", &s.Values)
_, result = s.elements[value]
return
}
func (s *OrderedSet[Value]) Len() int {
return len(s.elements)
}
func (s *OrderedSet[Value]) AddItems(value []Value) {
for _, v := range value {
s.Add(v)
}
}
func (s *OrderedSet[Value]) Items() []*Value {
slog.Info("OrderedSet.Items - start", "s", s)
result := make([]*Value, 0, len(s.elements) - 1)
for _, v := range s.Values {
slog.Info("OrderedSet.Items", "value", v)
if v != nil {
result = append(result, v)
s.elements[*v] = len(result)
}
}
slog.Info("OrderedSet.Items", "s", s, "result", result)
s.Values = result
return result
}

View File

@ -0,0 +1,51 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package ds
import (
"github.com/stretchr/testify/assert"
"testing"
"log/slog"
)
var (
)
func TestNewOrderedSet(t *testing.T) {
s := NewOrderedSet[string]()
assert.NotNil(t, s)
testValues := []string{
"foo",
"bar",
"baz",
"quuz",
}
for _,value := range testValues {
s.Add(value)
slog.Info("TestNewOrderedSet - ADD", "item", value, "s", s)
assert.True(t, s.Contains(value))
slog.Info("TestNewOrderedSet - CONTAINS", "s", s)
for x, item := range s.Items() {
slog.Info("TestNewOrderedSet", "item", item, "s", s)
assert.Equal(t, testValues[x], *item)
}
}
s.Delete("bar")
expectedValues := []string {
"foo",
"baz",
"quuz",
}
for x, item := range s.Items() {
assert.Equal(t, expectedValues[x], *item)
}
}

34
internal/ds/set.go Normal file
View File

@ -0,0 +1,34 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package ds
import (
)
type Set[Value comparable] map[Value]bool
func NewSet[Value comparable]() Set[Value] {
return make(map[Value]bool)
}
func (s Set[Value]) Add(value Value) {
s[value] = true
}
func (s Set[Value]) Delete(value Value) {
delete(s, value)
}
func (s Set[Value]) Contains(value Value) bool {
return s[value]
}
func (s Set[Value]) Len() int {
return len(s)
}
func (s Set[Value]) AddSlice(value []Value) {
for _, v := range value {
s.Add(v)
}
}

24
internal/ds/set_test.go Normal file
View File

@ -0,0 +1,24 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package ds
import (
"github.com/stretchr/testify/assert"
"testing"
)
var (
)
func TestNewSet(t *testing.T) {
s := NewSet[string]()
assert.NotNil(t, s)
s["foo"] = true
assert.True(t, s.Contains("foo"))
s.Add("bar")
assert.True(t, s.Contains("bar"))
}

26
internal/ext/file.go Normal file
View File

@ -0,0 +1,26 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package ext
import (
"os"
"path/filepath"
)
type FilePath string
func (f *FilePath) Exists() bool {
_, err := os.Stat(string(*f))
return !os.IsNotExist(err)
}
func (f *FilePath) Add(relative string) {
newPath := filepath.Join(string(*f), relative)
*f = FilePath(newPath)
}
func (f FilePath) Abs() FilePath {
result, _ := filepath.Abs(string(f))
return FilePath(result)
}

View File

@ -105,12 +105,6 @@ func (d *Dir) Emit(document data.Document, filter data.ElementSelector) (resourc
return nil, ErrEmptyDocument return nil, ErrEmptyDocument
} }
dirFileDeclaration := folio.NewDeclaration()
dirFileDeclaration.Type = "file"
if err = dirFileDeclaration.NewResource(nil); err != nil {
return
}
parentPaths := make(map[string]int) parentPaths := make(map[string]int)
var containingDirectoryPath string var containingDirectoryPath string
for _,res := range document.Filter(func(d data.Declaration) bool { for _,res := range document.Filter(func(d data.Declaration) bool {
@ -130,8 +124,12 @@ func (d *Dir) Emit(document data.Document, filter data.ElementSelector) (resourc
containingDirectoryPath, _ = d.isParent(&parentPaths, parent, containingDirectoryPath) containingDirectoryPath, _ = d.isParent(&parentPaths, parent, containingDirectoryPath)
} }
uri := fmt.Sprintf("file://%s", containingDirectoryPath) uri := fmt.Sprintf("file://%s", containingDirectoryPath)
if err = dirFileDeclaration.SetURI(uri); err != nil {
dirFileDeclaration := folio.NewDeclaration()
dirFileDeclaration.Type = "file"
if err = dirFileDeclaration.NewResource(&uri); err != nil {
return return
} }

View File

@ -189,7 +189,7 @@ func (j *JxFile) ExtractMany(resourceSource data.Resource, filter data.ElementSe
func (j *JxFile) targetResource() (target data.Resource, err error) { func (j *JxFile) targetResource() (target data.Resource, err error) {
if j.emitResource == nil { if j.emitResource == nil {
targetUrl := j.Uri.Parse() targetUrl := j.Uri.Parse().URL()
targetUrl.Scheme = "file" targetUrl.Scheme = "file"
q := targetUrl.Query() q := targetUrl.Query()
q.Set("format", string(j.Format)) q.Set("format", string(j.Format))

View File

@ -15,6 +15,32 @@ import (
"decl/internal/schema" "decl/internal/schema"
) )
type ConfigKey string
// Lookup a config value using block.key identifier (E.g. system.GOOS)
func (c ConfigKey) GetValue() (value any, err error) {
fields := strings.SplitN(string(c), ".", 2)
if configBlock, ok := DocumentRegistry.ConfigNameMap.Get(fields[0]); ok && len(fields) > 1 {
return configBlock.GetValue(fields[1])
} else {
return nil, fmt.Errorf("%w - %s", data.ErrUnknownConfigurationType, c)
}
}
func (c ConfigKey) Get() any {
if v, err := c.GetValue(); err == nil {
return v
}
return nil
}
func (c ConfigKey) GetStringSlice() []string {
if v, err := c.GetValue(); err == nil {
return v.([]string)
}
return nil
}
type BlockType struct { type BlockType struct {
Name string `json:"name" yaml:"name"` Name string `json:"name" yaml:"name"`
Type TypeName `json:"type" yaml:"type"` Type TypeName `json:"type" yaml:"type"`
@ -135,9 +161,9 @@ func (b *Block) SetURI(uri string) (err error) {
return return
} }
func (b *Block) SetParsedURI(uri *url.URL) (err error) { func (b *Block) SetParsedURI(uri data.URIParser) (err error) {
if b.Values == nil { if b.Values == nil {
if err = b.NewConfigurationFromParsedURI(uri); err != nil { if err = b.NewConfigurationFromParsedURI(uri.URL()); err != nil {
return return
} }
} }

View File

@ -11,12 +11,10 @@ _ "errors"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"log/slog" "log/slog"
_ "gitea.rosskeen.house/rosskeen.house/machine" _ "gitea.rosskeen.house/rosskeen.house/machine"
//_ "gitea.rosskeen.house/pylon/luaruntime" "gitea.rosskeen.house/pylon/luaruntime"
"decl/internal/codec" "decl/internal/codec"
"decl/internal/data" "decl/internal/data"
"decl/internal/schema" "decl/internal/schema"
"net/url"
"runtime/debug"
"errors" "errors"
) )
@ -33,6 +31,7 @@ type DeclarationType struct {
OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"` OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"`
Error string `json:"error,omitempty" yaml:"error,omitempty"` Error string `json:"error,omitempty" yaml:"error,omitempty"`
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"` Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
On *Events `json:"on,omitempty" yaml:"on,omitempty"`
} }
type Declaration struct { type Declaration struct {
@ -43,14 +42,15 @@ type Declaration struct {
OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"` OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"`
Error string `json:"error,omitempty" yaml:"error,omitempty"` Error string `json:"error,omitempty" yaml:"error,omitempty"`
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"` Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
// runtime luaruntime.LuaRunner On *Events `json:"on,omitempty" yaml:"on,omitempty"`
runtime luaruntime.LuaRunner
document *Document document *Document
configBlock data.Block configBlock data.Block
ResourceTypes data.TypesRegistry[data.Resource] `json:"-" yaml:"-"` ResourceTypes data.TypesRegistry[data.Resource] `json:"-" yaml:"-"`
} }
func NewDeclaration() *Declaration { func NewDeclaration() *Declaration {
return &Declaration{ ResourceTypes: DocumentRegistry.ResourceTypes } return &Declaration{ ResourceTypes: DocumentRegistry.ResourceTypes, runtime: luaruntime.New() }
} }
func NewDeclarationFromDocument(document *Document) *Declaration { func NewDeclarationFromDocument(document *Document) *Declaration {
@ -98,6 +98,7 @@ func (d *Declaration) Clone() data.Declaration {
//runtime: luaruntime.New(), //runtime: luaruntime.New(),
Config: d.Config, Config: d.Config,
Requires: d.Requires, Requires: d.Requires,
On: d.On,
} }
} }
@ -137,11 +138,12 @@ func (d *Declaration) Validate() (err error) {
return err return err
} }
func (d *Declaration) NewResourceFromParsedURI(u *url.URL) (err error) { func (d *Declaration) NewResourceFromParsedURI(u data.URIParser) (err error) {
if u == nil { if u == nil {
d.Attributes, err = d.ResourceTypes.NewFromParsedURI(&url.URL{ Scheme: string(d.Type) }) d.Attributes, err = d.ResourceTypes.NewFromType(string(d.Type))
} else { } else {
if d.Attributes, err = d.ResourceTypes.NewFromParsedURI(u); err == nil { parsed := u.URL()
if d.Attributes, err = d.ResourceTypes.NewFromParsedURI(parsed); err == nil {
err = d.Attributes.SetParsedURI(u) err = d.Attributes.SetParsedURI(u)
} }
} }
@ -149,15 +151,17 @@ func (d *Declaration) NewResourceFromParsedURI(u *url.URL) (err error) {
} }
func (d *Declaration) NewResource(uri *string) (err error) { func (d *Declaration) NewResource(uri *string) (err error) {
slog.Info("Declaration.NewResource()")
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))
} }
if uri == nil { if uri == nil {
d.Attributes, err = d.ResourceTypes.New(fmt.Sprintf("%s://", d.Type)) d.Attributes, err = d.ResourceTypes.NewFromType(string(d.Type))
} else { } else {
if d.Attributes, err = d.ResourceTypes.New(*uri); err == nil { slog.Info("Declaration.NewResource()", "uri", *uri)
err = d.Attributes.SetURI(*uri) parsedURI := URI(*uri).Parse()
} d.Attributes, err = d.ResourceTypes.NewFromParsedURI(parsedURI.URL())
} }
return return
} }
@ -169,7 +173,6 @@ func (d *Declaration) Resource() data.Resource {
func (d *Declaration) Apply(stateTransition string) (result error) { func (d *Declaration) Apply(stateTransition string) (result error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
slog.Debug("Declaration.Apply()", "stacktrace", string(debug.Stack()))
slog.Info("Declaration.Apply()", "error", r, "resourceerror", d.Error) slog.Info("Declaration.Apply()", "error", r, "resourceerror", d.Error)
if d.Error != "" { if d.Error != "" {
result = fmt.Errorf("%s - %s", r, d.Error) result = fmt.Errorf("%s - %s", r, d.Error)
@ -254,7 +257,7 @@ func (d *Declaration) SetURI(uri string) (err error) {
if d.Attributes == nil { if d.Attributes == nil {
err = d.NewResource(&uri) err = d.NewResource(&uri)
} else { } else {
err = d.Attributes.SetURI(uri) err = d.Attributes.SetParsedURI(URI(uri).Parse())
} }
if err != nil { if err != nil {
return err return err
@ -268,7 +271,7 @@ func (d *Declaration) SetURI(uri string) (err error) {
return return
} }
func (d *Declaration) SetParsedURI(uri *url.URL) (err error) { func (d *Declaration) SetParsedURI(uri data.URIParser) (err error) {
slog.Info("Declaration.SetParsedURI()", "uri", uri, "declaration", d) slog.Info("Declaration.SetParsedURI()", "uri", uri, "declaration", d)
if d.Attributes == nil { if d.Attributes == nil {
err = d.NewResourceFromParsedURI(uri) err = d.NewResourceFromParsedURI(uri)
@ -298,7 +301,9 @@ func (d *Declaration) UnmarshalValue(value *DeclarationType) error {
d.OnError = value.OnError d.OnError = value.OnError
d.Error = value.Error d.Error = value.Error
d.Requires = value.Requires d.Requires = value.Requires
newResource, resourceErr := d.ResourceTypes.New(fmt.Sprintf("%s://", value.Type)) d.On = value.On
newResource, resourceErr := d.ResourceTypes.NewFromType(string(value.Type))
slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr, "type", value.Type, "resource", newResource, "resourcetypes", d.ResourceTypes) slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr, "type", value.Type, "resource", newResource, "resourcetypes", d.ResourceTypes)
if resourceErr != nil { if resourceErr != nil {
slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr) slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr)
@ -306,10 +311,30 @@ func (d *Declaration) UnmarshalValue(value *DeclarationType) error {
} }
d.Attributes = newResource d.Attributes = newResource
d.configBlock = d.Config.GetBlock() d.configBlock = d.Config.GetBlock()
if d.On != nil {
if handler, ok := (*d.On)[EventTypeLoad]; ok {
stackSize := d.runtime.Api().GetTop()
if e := d.runtime.LoadScriptFromString(string(handler)); e != nil {
d.Error = e.Error()
}
returnsCount := d.runtime.Api().GetTop() - stackSize
if ! d.runtime.Api().IsNil(-1) {
if returnsCount == 0 {
// return nil
} else {
if lr,le := d.runtime.CopyReturnValuesFromCall(int(returnsCount)); le == nil {
slog.Info("Event.Load", "result", lr, "error", le)
}
}
}
}
}
return nil return nil
} }
func (d *Declaration) UnmarshalYAML(value *yaml.Node) error { func (d *Declaration) UnmarshalYAML(value *yaml.Node) (err error) {
if d.ResourceTypes == nil { if d.ResourceTypes == nil {
d.ResourceTypes = DocumentRegistry.ResourceTypes d.ResourceTypes = DocumentRegistry.ResourceTypes
} }
@ -331,10 +356,17 @@ func (d *Declaration) UnmarshalYAML(value *yaml.Node) error {
if unmarshalResourceErr := resourceAttrs.Attributes.Decode(d.Attributes); unmarshalResourceErr != nil { if unmarshalResourceErr := resourceAttrs.Attributes.Decode(d.Attributes); unmarshalResourceErr != nil {
return unmarshalResourceErr return unmarshalResourceErr
} }
return nil
if i, ok := d.Attributes.(data.ResourceInitializer); ok {
err = i.Init(nil)
} else {
err = fmt.Errorf("failed to execute init")
}
return
} }
func (d *Declaration) UnmarshalJSON(jsonData []byte) error { func (d *Declaration) UnmarshalJSON(jsonData []byte) (err error) {
if d.ResourceTypes == nil { if d.ResourceTypes == nil {
d.ResourceTypes = DocumentRegistry.ResourceTypes d.ResourceTypes = DocumentRegistry.ResourceTypes
} }
@ -354,24 +386,14 @@ func (d *Declaration) UnmarshalJSON(jsonData []byte) error {
return unmarshalAttributesErr return unmarshalAttributesErr
} }
return nil if i, ok := d.Attributes.(data.ResourceInitializer); ok {
} err = i.Init(nil)
} else {
err = fmt.Errorf("failed to execute init")
}
/* return
func (d *Declaration) MarshalJSON() ([]byte, error) {
buf := new(bytes.Buffer)
buf.WriteByte('"')
buf.WriteString("value"))
buf.WriteByte('"')
return buf.Bytes(), nil
} }
*/
/*
func (d *Declaration) MarshalYAML() (any, error) {
return d, nil
}
*/
/* /*
func (l *LuaWorker) Receive(m message.Envelope) { func (l *LuaWorker) Receive(m message.Envelope) {

View File

@ -57,6 +57,7 @@ func TestNewResourceDeclarationType(t *testing.T) {
e := resourceDeclaration.LoadString(decl, codec.FormatYaml) e := resourceDeclaration.LoadString(decl, codec.FormatYaml)
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, TypeName("foo"), resourceDeclaration.Type) assert.Equal(t, TypeName("foo"), resourceDeclaration.Type)
assert.NotNil(t, resourceDeclaration.Attributes) assert.NotNil(t, resourceDeclaration.Attributes)
} }

View File

@ -10,7 +10,6 @@ import (
"io/fs" "io/fs"
_ "os" _ "os"
"log/slog" "log/slog"
"net/url"
"github.com/sters/yaml-diff/yamldiff" "github.com/sters/yaml-diff/yamldiff"
"strings" "strings"
"decl/internal/codec" "decl/internal/codec"
@ -19,6 +18,7 @@ _ "decl/internal/types"
"decl/internal/data" "decl/internal/data"
"decl/internal/schema" "decl/internal/schema"
"context" "context"
"path/filepath"
) )
type DocumentType struct { type DocumentType struct {
@ -44,13 +44,23 @@ type Document struct {
config data.Document config data.Document
Registry *Registry `json:"-" yaml:"-"` Registry *Registry `json:"-" yaml:"-"`
failedResources int `json:"-" yaml:"-"` failedResources int `json:"-" yaml:"-"`
importPaths *SearchPath `json:"-" yaml:"-"`
} }
func NewDocument(r *Registry) *Document { func NewDocument(r *Registry) *Document {
if r == nil { if r == nil {
r = DocumentRegistry r = DocumentRegistry
} }
return &Document{ Registry: r, Format: codec.FormatYaml, uris: mapper.New[string, data.Declaration](), configNames: mapper.New[string, data.Block]() }
var configImportPath ConfigKey = "system.importpath"
return &Document{
Registry: r,
Format: codec.FormatYaml,
uris: mapper.New[string, data.Declaration](),
configNames: mapper.New[string, data.Block](),
importPaths: NewSearchPath(configImportPath.GetStringSlice()),
}
} }
func (d *Document) GetURI() string { func (d *Document) GetURI() string {
@ -59,6 +69,19 @@ func (d *Document) GetURI() string {
func (d *Document) SetURI(uri string) { func (d *Document) SetURI(uri string) {
d.URI = URI(uri) d.URI = URI(uri)
d.AddProjectPath()
}
func (d *Document) AddProjectPath() {
exists := d.URI.Exists()
if exists {
if u := d.URI.Parse().(*ParsedURI); u != nil {
projectPath := filepath.Dir(filepath.Join(u.Hostname(), u.Path))
if err := d.importPaths.AddPath(projectPath); err != nil {
panic(err)
}
}
}
} }
func (d *Document) Types() data.TypesRegistry[data.Resource] { func (d *Document) Types() data.TypesRegistry[data.Resource] {
@ -146,7 +169,15 @@ func (d *Document) ImportedDocuments() (documents []data.Document) {
func (d *Document) loadImports() (err error) { func (d *Document) loadImports() (err error) {
for _, uri := range d.Imports { for _, uri := range d.Imports {
if ! DocumentRegistry.HasDocument(uri) { if ! DocumentRegistry.HasDocument(uri) {
if _, err = DocumentRegistry.Load(uri); err != nil { var load URI = uri
if ! load.Exists() {
foundURI := d.importPaths.FindURI(load)
if foundURI != "" {
load = foundURI
}
}
slog.Info("Document.loadImports()", "load", load, "uri", uri, "importpaths", d.importPaths, "doc", d.URI)
if _, err = DocumentRegistry.Load(load); err != nil {
return return
} }
} }
@ -390,7 +421,7 @@ func (d *Document) NewResourceFromURI(uri URI) (newResource data.Resource, err e
return d.NewResourceFromParsedURI(uri.Parse()) return d.NewResourceFromParsedURI(uri.Parse())
} }
func (d *Document) NewResourceFromParsedURI(uri *url.URL) (newResource data.Resource, err error) { func (d *Document) NewResourceFromParsedURI(uri data.URIParser) (newResource data.Resource, err error) {
if uri == nil { if uri == nil {
return nil, fmt.Errorf("%w: %s", ErrUnknownResourceType, uri) return nil, fmt.Errorf("%w: %s", ErrUnknownResourceType, uri)
} }

View File

@ -84,6 +84,9 @@ resources:
f.Name = "mytestresource" f.Name = "mytestresource"
f.Size = 3 f.Size = 3
assert.Nil(t, f.Init(nil))
d.AddResourceDeclaration("foo", f) d.AddResourceDeclaration("foo", f)
ey := d.Generate(&documentYaml) ey := d.Generate(&documentYaml)

View File

@ -3,14 +3,13 @@
package folio package folio
import ( import (
"context" "context"
_ "gopkg.in/yaml.v3" _ "gopkg.in/yaml.v3"
"encoding/json" "encoding/json"
_ "fmt" _ "fmt"
"decl/internal/data" "decl/internal/data"
"decl/internal/codec" "decl/internal/codec"
"io" "io"
"net/url"
) )
type MockConfiguration struct { type MockConfiguration struct {
@ -38,7 +37,7 @@ func (m *MockConfiguration) SetURI(uri string) error {
return nil return nil
} }
func (m *MockConfiguration) SetParsedURI(uri *url.URL) error { func (m *MockConfiguration) SetParsedURI(uri data.URIParser) error {
return nil return nil
} }

View File

@ -17,22 +17,30 @@ _ "gopkg.in/yaml.v3"
func RegisterMocks() { func RegisterMocks() {
TestResourceTypes.Register([]string{"foo"}, func(u *url.URL) data.Resource { TestResourceTypes.Register([]string{"foo"}, func(u *url.URL) data.Resource {
f := NewFooResource() f := NewFooResource()
f.Name = filepath.Join(u.Hostname(), u.Path) if u != nil {
f.Name = filepath.Join(u.Hostname(), u.Path)
}
return f return f
}) })
TestResourceTypes.Register([]string{"bar"}, func(u *url.URL) data.Resource { TestResourceTypes.Register([]string{"bar"}, func(u *url.URL) data.Resource {
f := NewBarResource() f := NewBarResource()
f.Name = filepath.Join(u.Hostname(), u.Path) if u != nil {
f.Name = filepath.Join(u.Hostname(), u.Path)
}
return f return f
}) })
TestResourceTypes.Register([]string{"testuser"}, func(u *url.URL) data.Resource { TestResourceTypes.Register([]string{"testuser"}, func(u *url.URL) data.Resource {
f := NewTestuserResource() f := NewTestuserResource()
f.Name = filepath.Join(u.Hostname(), u.Path) if u != nil {
f.Name = filepath.Join(u.Hostname(), u.Path)
}
return f return f
}) })
TestResourceTypes.Register([]string{"file"}, func(u *url.URL) data.Resource { TestResourceTypes.Register([]string{"file"}, func(u *url.URL) data.Resource {
f := NewFileResource() f := NewFileResource()
f.Name = filepath.Join(u.Hostname(), u.Path) if u != nil {
f.Name = filepath.Join(u.Hostname(), u.Path)
}
return f return f
}) })
} }
@ -40,23 +48,20 @@ func RegisterMocks() {
type MockFoo struct { type MockFoo struct {
stater machine.Stater `json:"-" yaml:"-"` stater machine.Stater `json:"-" yaml:"-"`
*MockResource `json:"-" yaml:"-"` *MockResource `json:",inline" yaml:",inline"`
Name string `json:"name" yaml:"name"`
Size int `json:"size" yaml:"size"` Size int `json:"size" yaml:"size"`
} }
type MockBar struct { type MockBar struct {
stater machine.Stater `json:"-" yaml:"-"` stater machine.Stater `json:"-" yaml:"-"`
*MockResource `json:"-" yaml:"-"` *MockResource `json:",inline" yaml:",inline"`
Name string `json:"name" yaml:"name"`
Size int `json:"size,omitempty" yaml:"size,omitempty"` Size int `json:"size,omitempty" yaml:"size,omitempty"`
Owner string `json:"owner,omitempty" yaml:"owner,omitempty"` Owner string `json:"owner,omitempty" yaml:"owner,omitempty"`
} }
type MockTestuser struct { type MockTestuser struct {
stater machine.Stater `json:"-" yaml:"-"` stater machine.Stater `json:"-" yaml:"-"`
*MockResource `json:"-" yaml:"-"` *MockResource `json:",inline" yaml:",inline"`
Name string `json:"name" yaml:"name"`
Uid string `json:"uid" yaml:"uid"` Uid string `json:"uid" yaml:"uid"`
Group string `json:"group" yaml:"group"` Group string `json:"group" yaml:"group"`
Home string `json:"home" yaml:"home"` Home string `json:"home" yaml:"home"`
@ -64,13 +69,12 @@ type MockTestuser struct {
type MockFile struct { type MockFile struct {
stater machine.Stater `json:"-" yaml:"-"` stater machine.Stater `json:"-" yaml:"-"`
*MockResource `json:"-" yaml:"-"` *MockResource `json:",inline" yaml:",inline"`
Name string `json:"name" yaml:"name"`
Size int `json:"size" yaml:"size"` Size int `json:"size" yaml:"size"`
} }
func NewMockResource(typename string, stater machine.Stater) *MockResource { func NewMockResource(typename string, stater machine.Stater) (m *MockResource) {
return &MockResource { m = &MockResource {
InjectType: func() string { return typename }, InjectType: func() string { return typename },
InjectResolveId: func(ctx context.Context) string { return "bar" }, InjectResolveId: func(ctx context.Context) string { return "bar" },
InjectLoadDecl: func(string) error { return nil }, InjectLoadDecl: func(string) error { return nil },
@ -89,6 +93,13 @@ func NewMockResource(typename string, stater machine.Stater) *MockResource {
InjectURI: func() string { return fmt.Sprintf("%s://bar", typename) }, InjectURI: func() string { return fmt.Sprintf("%s://bar", typename) },
InjectNotify: func(*machine.EventMessage) {}, InjectNotify: func(*machine.EventMessage) {},
} }
m.InjectInit = func(u data.URIParser) error {
if u != nil {
m.Name = filepath.Join(u.URL().Hostname(), u.URL().Path)
}
return nil
}
return m
} }
func NewFooResource() *MockFoo { func NewFooResource() *MockFoo {

View File

@ -3,18 +3,19 @@
package folio package folio
import ( import (
"context" "context"
_ "gopkg.in/yaml.v3" _ "gopkg.in/yaml.v3"
"encoding/json" "encoding/json"
_ "fmt" _ "fmt"
"gitea.rosskeen.house/rosskeen.house/machine" "gitea.rosskeen.house/rosskeen.house/machine"
"decl/internal/data" "decl/internal/data"
"decl/internal/codec" "decl/internal/codec"
"io" "io"
"net/url"
) )
type MockResource struct { type MockResource struct {
Name string `json:"name" yaml:"name"`
InjectInit func(data.URIParser) error `json:"-" yaml:"-"`
InjectURI func() string `json:"-" yaml:"-"` InjectURI func() string `json:"-" yaml:"-"`
InjectType func() string `json:"-" yaml:"-"` InjectType func() string `json:"-" yaml:"-"`
InjectResolveId func(ctx context.Context) string `json:"-" yaml:"-"` InjectResolveId func(ctx context.Context) string `json:"-" yaml:"-"`
@ -54,7 +55,7 @@ func (m *MockResource) SetURI(uri string) error {
return nil return nil
} }
func (m *MockResource) SetParsedURI(uri *url.URL) error { func (m *MockResource) SetParsedURI(uri data.URIParser) error {
return nil return nil
} }
@ -126,6 +127,10 @@ func (m *MockResource) Apply() error {
return m.InjectApply() return m.InjectApply()
} }
func (m *MockResource) Init(u data.URIParser) (error) {
return m.InjectInit(u)
}
func (m *MockResource) Type() string { func (m *MockResource) Type() string {
return m.InjectType() return m.InjectType()
} }

102
internal/folio/parseduri.go Normal file
View File

@ -0,0 +1,102 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package folio
import (
"net/url"
"decl/internal/transport"
"decl/internal/data"
"decl/internal/identifier"
"strings"
)
var (
)
type ParsedURI url.URL
func NewParsedURI(uri URI) *ParsedURI {
return uri.Parse().(*ParsedURI)
}
func CastParsedURI(u *url.URL) *ParsedURI {
return (*ParsedURI)(u)
}
func (uri *ParsedURI) URL() *url.URL {
return (*url.URL)(uri)
}
func (uri *ParsedURI) Hostname() string {
return (*url.URL)(uri).Hostname()
}
func (uri *ParsedURI) NewResource(document data.Document) (newResource data.Resource, err error) {
if document == nil {
declaration := NewDeclaration()
if err = declaration.NewResourceFromParsedURI(uri); err == nil {
return declaration.Attributes, err
}
} else {
newResource, err = document.NewResourceFromParsedURI(uri)
return
}
return
}
func (uri *ParsedURI) ConstructResource(res data.Resource) (err error) {
if ri, ok := res.(data.ResourceInitializer); ok {
err = ri.Init(uri)
} else {
err = res.SetParsedURI(uri)
}
return
}
func (uri *ParsedURI) Converter() (converter data.Converter, err error) {
return DocumentRegistry.ConverterTypes.NewFromParsedURI(uri.URL())
}
func (uri *ParsedURI) Exists() bool {
return transport.Exists(uri.URL())
}
func (uri *ParsedURI) ContentReaderStream() (*transport.Reader, error) {
return transport.NewReader(uri.URL())
}
func (uri *ParsedURI) ContentWriterStream() (*transport.Writer, error) {
return transport.NewWriter(uri.URL())
}
func (uri *ParsedURI) String() string {
return uri.URL().String()
}
func (uri *ParsedURI) SetURL(url *url.URL) {
*uri = *(*ParsedURI)(url)
}
func (uri *ParsedURI) Extension() (string, string) {
return (identifier.ID)(uri.Path).Extension()
}
func (uri *ParsedURI) ContentType() string {
var ext strings.Builder
exttype, fileext := uri.Extension()
if fileext == "" {
return exttype
}
ext.WriteString(exttype)
ext.WriteRune('.')
ext.WriteString(fileext)
return ext.String()
}
func (uri *ParsedURI) IsEmpty() bool {
if uri == nil || (identifier.ID)(uri.String()).IsEmpty() {
return true
}
return false
}

View File

@ -69,7 +69,7 @@ func (r *Registry) SetDocument(key URI, value *Document) {
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 doc.SetURI(string(uri))
r.Documents = append(r.Documents, doc) r.Documents = append(r.Documents, doc)
if uri != "" { if uri != "" {
r.UriMap[uri] = doc r.UriMap[uri] = doc
@ -148,3 +148,8 @@ func (r *Registry) LoadFromURL(uri *url.URL) (documents []data.Document, err err
documents = make([]data.Document, 0, 10) documents = make([]data.Document, 0, 10)
return r.AppendParsedURI(uri, documents) return r.AppendParsedURI(uri, documents)
} }
func (r *Registry) LoadFromParsedURI(uri *url.URL) (documents []data.Document, err error) {
documents = make([]data.Document, 0, 10)
return r.AppendParsedURI(uri, documents)
}

View File

@ -15,11 +15,11 @@ var (
func NewResourceFromParsedURI(u *url.URL, document data.Document) (newResource data.Resource, err error) { func NewResourceFromParsedURI(u *url.URL, document data.Document) (newResource data.Resource, err error) {
if document == nil { if document == nil {
declaration := NewDeclaration() declaration := NewDeclaration()
if err = declaration.NewResourceFromParsedURI(u); err == nil { if err = declaration.NewResourceFromParsedURI((*ParsedURI)(u)); err == nil {
return declaration.Attributes, err return declaration.Attributes, err
} }
} else { } else {
newResource, err = document.NewResourceFromParsedURI(u) newResource, err = document.NewResourceFromParsedURI((*ParsedURI)(u))
return return
} }
return return

View File

@ -56,7 +56,7 @@ func (r ResourceReference) Dereference(look data.ResourceMapper) data.Resource {
} }
func (r ResourceReference) Parse() *url.URL { func (r ResourceReference) Parse() *url.URL {
return URI(r).Parse() return URI(r).Parse().URL()
} }
func (r ResourceReference) Exists() bool { func (r ResourceReference) Exists() bool {

View File

@ -0,0 +1,65 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package folio
import (
"decl/internal/ext"
"decl/internal/ds"
"errors"
"fmt"
"log/slog"
"path/filepath"
"decl/internal/data"
)
var (
ErrSearchPathNotExist error = errors.New("Search path does not exist.")
)
// A search path should find a file in list of paths
type SearchPath struct {
paths *ds.OrderedSet[string]
}
func NewSearchPath(paths []string) (s *SearchPath) {
s = &SearchPath{ paths: ds.NewOrderedSet[string]() }
s.paths.AddItems(paths)
return
}
func (s *SearchPath) AddPath(path string) error {
if newPath := ext.FilePath(path).Abs(); newPath.Exists() {
s.paths.Add(string(newPath))
} else {
return fmt.Errorf("%w: %s", ErrSearchPathNotExist, path)
}
return nil
}
func (s *SearchPath) Find(relative string) string {
pathValues := s.paths.Items()
for i := len(pathValues) - 1; i >= 0; i-- {
p := *pathValues[i]
searchPath := ext.FilePath(p)
searchPath.Add(relative)
slog.Info("SearchPath.Find()", "searchpath", p, "file", relative, "target", searchPath)
if searchPath.Exists() {
return string(searchPath)
}
}
return ""
}
func (s *SearchPath) FindParsedURI(uri data.URIParser) (result URI) {
u := uri.URL()
filePath := filepath.Join(u.Hostname(), u.Path)
if absPath := s.Find(filePath); absPath != "" {
result = URI(fmt.Sprintf("%s://%s", u.Scheme, string(absPath)))
}
return
}
func (s *SearchPath) FindURI(uri URI) (result URI) {
return s.FindParsedURI(uri.Parse())
}

View File

@ -0,0 +1,24 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package folio
import (
"github.com/stretchr/testify/assert"
"testing"
"os"
)
func TestSearchPath(t *testing.T) {
assert.Nil(t, TempDir.CreateFile("test.jx.yaml", ""))
sp := NewSearchPath(nil)
assert.NotNil(t, sp)
assert.Nil(t, sp.AddPath(string(TempDir)))
absPath := sp.Find("test.jx.yaml")
_, err := os.Stat(absPath)
assert.True(t, !os.IsNotExist(err))
}

View File

@ -35,14 +35,24 @@ func (u URI) NewResource(document data.Document) (newResource data.Resource, err
return return
} }
func (u URI) ConstructResource(res data.Resource) (err error) {
parsedURI := u.Parse()
if ri, ok := res.(data.ResourceInitializer); ok {
err = ri.Init(parsedURI)
} else {
err = res.SetParsedURI(parsedURI)
}
return
}
func (u URI) Converter() (converter data.Converter, err error) { func (u URI) Converter() (converter data.Converter, err error) {
return DocumentRegistry.ConverterTypes.New(string(u)) return DocumentRegistry.ConverterTypes.New(string(u))
} }
func (u URI) Parse() *url.URL { func (u URI) Parse() data.URIParser {
url, e := url.Parse(string(u)) url, e := url.Parse(string(u))
if e == nil { if e == nil {
return url return (*ParsedURI)(url)
} }
return nil return nil
} }

View File

@ -10,17 +10,17 @@ import (
func TestURI(t *testing.T) { func TestURI(t *testing.T) {
var file URI = URI(fmt.Sprintf("file://%s", TempDir)) var file URI = URI(fmt.Sprintf("file://%s", TempDir))
u := file.Parse() u := file.Parse().URL()
assert.Equal(t, "file", u.Scheme) assert.Equal(t, "file", u.Scheme)
assert.True(t, file.Exists()) assert.True(t, file.Exists())
file = URI(fmt.Sprintf("0file:_/%s", TempDir)) file = URI(fmt.Sprintf("0file:_/%s", TempDir))
u = file.Parse() x := file.Parse()
assert.Nil(t, u) assert.Nil(t, x)
} }
func TestURISetURL(t *testing.T) { func TestURISetURL(t *testing.T) {
var file URI = URI(fmt.Sprintf("file://%s", TempDir)) var file URI = URI(fmt.Sprintf("file://%s", TempDir))
u := file.Parse() u := file.Parse().URL()
var fileFromURL URI var fileFromURL URI
fileFromURL.SetURL(u) fileFromURL.SetURL(u)
assert.Equal(t, fileFromURL, file) assert.Equal(t, fileFromURL, file)

View File

@ -20,9 +20,9 @@ type Common struct {
NormalizePath UriNormalize `json:"-" yaml:"-"` NormalizePath UriNormalize `json:"-" yaml:"-"`
includeQueryParamsInURI bool `json:"-" yaml:"-"` includeQueryParamsInURI bool `json:"-" yaml:"-"`
resourceType TypeName `json:"-" yaml:"-"` resourceType TypeName `json:"-" yaml:"-"`
Uri folio.URI `json:"uri,omitempty" yaml:"uri,omitempty"`
parsedURI *url.URL `json:"-" yaml:"-"` parsedURI *url.URL `json:"-" yaml:"-"`
Path string `json:"path,omitempty" yaml:"path,omitempty"` Path string `json:"path,omitempty" yaml:"path,omitempty"`
absPath string `json:"-" yaml:"-"`
exttype string `json:"-" yaml:"-"` exttype string `json:"-" yaml:"-"`
fileext string `json:"-" yaml:"-"` fileext string `json:"-" yaml:"-"`
@ -57,9 +57,13 @@ func (c *Common) SetResourceMapper(resources data.ResourceMapper) {
func (c *Common) Clone() *Common { func (c *Common) Clone() *Common {
return &Common { return &Common {
Uri: c.Uri, SchemeCheck: c.SchemeCheck,
NormalizePath: c.NormalizePath,
includeQueryParamsInURI: c.includeQueryParamsInURI,
resourceType: c.resourceType,
parsedURI: c.parsedURI, parsedURI: c.parsedURI,
Path: c.Path, Path: c.Path,
absPath: c.absPath,
exttype: c.exttype, exttype: c.exttype,
fileext: c.fileext, fileext: c.fileext,
normalizePath: c.normalizePath, normalizePath: c.normalizePath,
@ -77,41 +81,35 @@ func (c *Common) URIPath() string {
return c.Path return c.Path
} }
func (c *Common) URI() string { func (c *Common) URI() folio.URI {
return string(c.Uri) slog.Info("Common.URI", "parsed", c.parsedURI, "debug", debug.Stack())
return folio.URI(c.parsedURI.String())
} }
func (c *Common) SetURI(uri string) (err error) { func (c *Common) SetParsedURI(u data.URIParser) (err error) {
c.SetURIFromString(uri)
err = c.SetParsedURI(c.Uri.Parse())
return
}
func (c *Common) SetURIFromString(uri string) {
c.Uri = folio.URI(uri)
c.exttype, c.fileext = c.Uri.Extension()
}
func (c *Common) SetParsedURI(u *url.URL) (err error) {
if u != nil { if u != nil {
slog.Info("Common.SetParsedURI()", "parsed", u, "uri", c.Uri)
if c.Uri.IsEmpty() { slog.Info("Common.SetParsedURI()", "parsed", u, "uri", c)
c.SetURIFromString(u.String())
} c.parsedURI = u.URL()
c.parsedURI = u
c.exttype, c.fileext = u.Extension()
if c.SchemeCheck(c.parsedURI.Scheme) { if c.SchemeCheck(c.parsedURI.Scheme) {
if c.includeQueryParamsInURI { if c.includeQueryParamsInURI {
c.Path = filepath.Join(c.parsedURI.Hostname(), c.parsedURI.RequestURI()) c.Path = filepath.Join(c.parsedURI.Hostname(), c.parsedURI.RequestURI())
} else { } else {
c.Path = filepath.Join(c.parsedURI.Hostname(), c.parsedURI.Path) c.Path = filepath.Join(c.parsedURI.Hostname(), c.parsedURI.Path)
} }
if c.absPath, err = filepath.Abs(c.Path); err != nil {
return
}
if err = c.NormalizePath(); err != nil { if err = c.NormalizePath(); err != nil {
return return
} }
return return
} }
} }
err = fmt.Errorf("%w: %s is not a %s resource, parsed: %t", ErrInvalidResourceURI, c.Uri, c.Type(), (u != nil)) err = fmt.Errorf("%w: %s is not a %s resource, parsed: %t", ErrInvalidResourceURI, c.URI(), c.Type(), (u != nil))
return return
} }
@ -120,26 +118,27 @@ func (c *Common) UseConfig(config data.ConfigurationValueGetter) {
} }
func (c *Common) ResolveId(ctx context.Context) string { func (c *Common) ResolveId(ctx context.Context) string {
if e := c.NormalizePath(); e != nil { var err error
panic(e) if c.absPath, err = filepath.Abs(c.Path); err != nil {
panic(err)
}
if err = c.NormalizePath(); err != nil {
panic(err)
} }
return c.Path return c.Path
} }
func (c *Common) NormalizeFilePath() error { // Common path normalization for a file resource.
func (c *Common) NormalizeFilePath() (err error) {
if c.config != nil { if c.config != nil {
if prefixPath, configErr := c.config.GetValue("prefix"); configErr == nil { if prefixPath, configErr := c.config.GetValue("prefix"); configErr == nil {
c.Path = filepath.Join(prefixPath.(string), c.Path) c.Path = filepath.Join(prefixPath.(string), c.Path)
} }
} }
if c.normalizePath { if c.normalizePath {
filePath, fileAbsErr := filepath.Abs(c.Path) c.Path = c.absPath
if fileAbsErr == nil {
c.Path = filePath
}
return fileAbsErr
} }
return nil return
} }
func (c *Common) Type() string { return string(c.resourceType) } func (c *Common) Type() string { return string(c.resourceType) }

View File

@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
"net/url" "net/url"
"decl/internal/folio"
) )
func TestNewCommon(t *testing.T) { func TestNewCommon(t *testing.T) {
@ -16,16 +17,15 @@ func TestNewCommon(t *testing.T) {
func TestCommon(t *testing.T) { func TestCommon(t *testing.T) {
expectedCommon := NewCommon(FileTypeName, false) expectedCommon := NewCommon(FileTypeName, false)
expectedCommon.resourceType = "file" expectedCommon.resourceType = "file"
expectedCommon.Uri = "file:///tmp/foo"
expectedCommon.parsedURI = &url.URL{ Scheme: "file", Path: "/tmp/foo"} expectedCommon.parsedURI = &url.URL{ Scheme: "file", Path: "/tmp/foo"}
expectedCommon.Path = "/tmp/foo" expectedCommon.Path = "/tmp/foo"
for _, v := range []struct{ uri string; expected *Common }{ for _, v := range []struct{ uri folio.URI; expected *Common }{
{ uri: "file:///tmp/foo", expected: expectedCommon }, { uri: "file:///tmp/foo", expected: expectedCommon },
}{ }{
c := NewCommon(FileTypeName, false) c := NewCommon(FileTypeName, false)
c.resourceType = "file" c.resourceType = "file"
assert.Nil(t, c.SetURI(v.uri)) assert.Nil(t, c.SetParsedURI(v.uri.Parse()))
assert.Equal(t, v.expected.resourceType , c.resourceType) assert.Equal(t, v.expected.resourceType , c.resourceType)
assert.Equal(t, v.expected.Path, c.Path) assert.Equal(t, v.expected.Path, c.Path)
assert.Equal(t, v.expected.parsedURI.Scheme, c.parsedURI.Scheme) assert.Equal(t, v.expected.parsedURI.Scheme, c.parsedURI.Scheme)

View File

@ -16,7 +16,7 @@ import (
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
_ "gopkg.in/yaml.v3" _ "gopkg.in/yaml.v3"
"log/slog" "log/slog"
"net/url" "net/url"
_ "os" _ "os"
@ -28,6 +28,7 @@ _ "os/exec"
"gitea.rosskeen.house/rosskeen.house/machine" "gitea.rosskeen.house/rosskeen.house/machine"
"decl/internal/codec" "decl/internal/codec"
"decl/internal/data" "decl/internal/data"
"decl/internal/folio"
) )
const ( const (
@ -50,7 +51,6 @@ type Container struct {
stater machine.Stater `yaml:"-" json:"-"` stater machine.Stater `yaml:"-" json:"-"`
Id string `json:"ID,omitempty" yaml:"ID,omitempty"` Id string `json:"ID,omitempty" yaml:"ID,omitempty"`
Name string `json:"name" yaml:"name"` Name string `json:"name" yaml:"name"`
// Path string `json:"path" yaml:"path"`
Cmd []string `json:"cmd,omitempty" yaml:"cmd,omitempty"` Cmd []string `json:"cmd,omitempty" yaml:"cmd,omitempty"`
Entrypoint strslice.StrSlice `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"` Entrypoint strslice.StrSlice `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"`
Args []string `json:"args,omitempty" yaml:"args,omitempty"` Args []string `json:"args,omitempty" yaml:"args,omitempty"`
@ -81,21 +81,19 @@ type Container struct {
NetworkSettings *NetworkSettings NetworkSettings *NetworkSettings
*/ */
// State string `yaml:"state,omitempty" json:"state,omitempty"`
// config ConfigurationValueGetter
apiClient ContainerClient apiClient ContainerClient
// Resources data.ResourceMapper `json:"-" yaml:"-"` Resources data.ResourceMapper `json:"-" yaml:"-"`
} }
func init() { func init() {
ResourceTypes.Register([]string{"container"}, func(u *url.URL) data.Resource { ResourceTypes.Register([]string{"container"}, func(u *url.URL) (c data.Resource) {
c := NewContainer(nil) c = NewContainer(nil)
c.Name = filepath.Join(u.Hostname(), u.Path) if u != nil {
if err := c.Common.SetParsedURI(u); err == nil { if err := folio.CastParsedURI(u).ConstructResource(c); err != nil {
return c panic(err)
}
} }
return nil return
}) })
} }
@ -114,6 +112,22 @@ func NewContainer(containerClientApi ContainerClient) *Container {
} }
} }
func (c *Container) Init(u data.URIParser) error {
if u == nil {
u = folio.URI(c.URI()).Parse()
}
uri := u.URL()
c.Name = filepath.Join(uri.Hostname(), uri.Path)
return c.SetParsedURI(u)
}
func (c *Container) SetParsedURI(u data.URIParser) (err error) {
if err = c.Common.SetParsedURI(u); err == nil {
c.Name = filepath.Join(c.Common.parsedURI.Hostname(), c.Common.parsedURI.Path)
}
return
}
func (c *Container) SetResourceMapper(resources data.ResourceMapper) { func (c *Container) SetResourceMapper(resources data.ResourceMapper) {
c.Resources = resources c.Resources = resources
} }
@ -122,7 +136,7 @@ func (c *Container) Clone() data.Resource {
return &Container { return &Container {
Id: c.Id, Id: c.Id,
Name: c.Name, Name: c.Name,
Common: c.Common, Common: c.Common.Clone(),
Cmd: c.Cmd, Cmd: c.Cmd,
Entrypoint: c.Entrypoint, Entrypoint: c.Entrypoint,
Args: c.Args, Args: c.Args,
@ -146,7 +160,6 @@ func (c *Container) Clone() data.Resource {
SizeRw: c.SizeRw, SizeRw: c.SizeRw,
SizeRootFs: c.SizeRootFs, SizeRootFs: c.SizeRootFs,
Networks: c.Networks, Networks: c.Networks,
// State: c.State,
apiClient: c.apiClient, apiClient: c.apiClient,
} }
} }
@ -158,10 +171,6 @@ func (c *Container) StateMachine() machine.Stater {
return c.stater return c.stater
} }
func (c *Container) UseConfig(config data.ConfigurationValueGetter) {
c.config = config
}
func (c *Container) JSON() ([]byte, error) { func (c *Container) JSON() ([]byte, error) {
return json.Marshal(c) return json.Marshal(c)
} }
@ -429,7 +438,7 @@ func (c *Container) Type() string { return "container" }
func (c *Container) ResolveId(ctx context.Context) string { func (c *Container) ResolveId(ctx context.Context) string {
var err error var err error
if err = c.Common.SetURI(c.URI()); err != nil { if err = c.Common.SetParsedURI(folio.URI(c.URI()).Parse()); err != nil {
triggerErr := c.StateMachine().Trigger("notexists") triggerErr := c.StateMachine().Trigger("notexists")
panic(fmt.Errorf("%w: %s %s, %w", err, c.Type(), c.Name, triggerErr)) panic(fmt.Errorf("%w: %s %s, %w", err, c.Type(), c.Name, triggerErr))
} }

View File

@ -101,11 +101,14 @@ type ContainerImage struct {
} }
func init() { func init() {
folio.DocumentRegistry.ResourceTypes.Register([]string{"container-image"}, func(u *url.URL) data.Resource { folio.DocumentRegistry.ResourceTypes.Register([]string{"container-image"}, func(u *url.URL) (c data.Resource) {
c := NewContainerImage(nil) c = NewContainerImage(nil)
c.Name = ContainerImageNameFromURI(u) if u != nil {
slog.Info("NewContainerImage", "container", c) if err := folio.CastParsedURI(u).ConstructResource(c); err != nil {
return c panic(err)
}
}
return
}) })
} }
@ -129,6 +132,15 @@ func NewContainerImage(containerClientApi ContainerImageClient) *ContainerImage
return c return c
} }
func (c *ContainerImage) Init(u data.URIParser) (err error) {
if u == nil {
u = folio.URI(c.URI()).Parse()
}
err = c.SetParsedURI(u)
c.Name = ContainerImageNameFromURI(u.URL())
return
}
func (c *ContainerImage) RegistryAuthConfig() (authConfig registry.AuthConfig, err error) { func (c *ContainerImage) RegistryAuthConfig() (authConfig registry.AuthConfig, err error) {
if c.Common.config != nil { if c.Common.config != nil {
var configValue any var configValue any
@ -255,24 +267,6 @@ func (c *ContainerImage) URI() string {
return URIFromContainerImageName(c.Name) return URIFromContainerImageName(c.Name)
} }
/*
func (c *ContainerImage) SetURI(uri string) error {
resourceUri, e := url.Parse(uri)
if e == nil {
if resourceUri.Scheme == c.Type() {
c.Name = strings.Join([]string{resourceUri.Hostname(), resourceUri.RequestURI()}, ":")
} else {
e = fmt.Errorf("%w: %s is not a %s", ErrInvalidResourceURI, uri, c.Type())
}
}
return e
}
func (c *ContainerImage) UseConfig(config data.ConfigurationValueGetter) {
c.config = config
}
*/
func (c *ContainerImage) JSON() ([]byte, error) { func (c *ContainerImage) JSON() ([]byte, error) {
return json.Marshal(c) return json.Marshal(c)
} }
@ -795,12 +789,6 @@ func (c *ContainerImage) Delete(ctx context.Context) error {
_, err := c.apiClient.ImageRemove(ctx, c.Id, options) _, err := c.apiClient.ImageRemove(ctx, c.Id, options)
return err return err
/*
for _, img := range deletedImages {
fmt.Printf("Deleted image: %s\n", img.Deleted)
fmt.Printf("Untagged image: %s\n", img.Untagged)
}
*/
} }
func (c *ContainerImage) Type() string { return "container-image" } func (c *ContainerImage) Type() string { return "container-image" }
@ -827,5 +815,3 @@ func (c *ContainerImage) ResolveId(ctx context.Context) string {
} }
return c.Id return c.Id
} }

View File

@ -15,7 +15,6 @@ _ "log/slog"
"net/url" "net/url"
_ "os" _ "os"
_ "os/exec" _ "os/exec"
"path/filepath"
_ "strings" _ "strings"
"encoding/json" "encoding/json"
"io" "io"
@ -39,26 +38,29 @@ type ContainerNetworkClient interface {
} }
type ContainerNetwork struct { type ContainerNetwork struct {
*Common `json:",inline" yaml:",inline"` *Common `json:",inline" yaml:",inline"`
stater machine.Stater `json:"-" yaml:"-"` stater machine.Stater `json:"-" yaml:"-"`
Id string `json:"ID,omitempty" yaml:"ID,omitempty"` Id string `json:"ID,omitempty" yaml:"ID,omitempty"`
Name string `json:"name" yaml:"name"` Name string `json:"name" yaml:"name"`
Driver string `json:"driver,omitempty" yaml:"driver,omitempty"` Driver string `json:"driver,omitempty" yaml:"driver,omitempty"`
EnableIPv6 bool `json:"enableipv6,omitempty" yaml:"enableipv6,omitempty"` EnableIPv6 bool `json:"enableipv6,omitempty" yaml:"enableipv6,omitempty"`
Internal bool `json:"internal,omitempty" yaml:"internal,omitempty"` Internal bool `json:"internal,omitempty" yaml:"internal,omitempty"`
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
Created time.Time `json:"created" yaml:"created"` Created time.Time `json:"created" yaml:"created"`
//State string `yaml:"state"`
apiClient ContainerNetworkClient apiClient ContainerNetworkClient
Resources data.ResourceMapper `json:"-" yaml:"-"` Resources data.ResourceMapper `json:"-" yaml:"-"`
} }
func init() { func init() {
folio.DocumentRegistry.ResourceTypes.Register([]string{"container-network"}, func(u *url.URL) data.Resource { folio.DocumentRegistry.ResourceTypes.Register([]string{"container-network"}, func(u *url.URL) (n data.Resource) {
n := NewContainerNetwork(nil) n = NewContainerNetwork(nil)
n.Name = filepath.Join(u.Hostname(), u.Path) if u != nil {
return n if err := folio.CastParsedURI(u).ConstructResource(n); err != nil {
panic(err)
}
}
return
}) })
} }
@ -79,6 +81,13 @@ func NewContainerNetwork(containerClientApi ContainerNetworkClient) (cn *Contain
return cn return cn
} }
func (n *ContainerNetwork) Init(u data.URIParser) error {
if u == nil {
u = folio.URI(n.URI()).Parse()
}
return n.SetParsedURI(u)
}
func (n *ContainerNetwork) NormalizePath() error { func (n *ContainerNetwork) NormalizePath() error {
return nil return nil
} }
@ -92,7 +101,6 @@ func (n *ContainerNetwork) Clone() data.Resource {
Common: n.Common, Common: n.Common,
Id: n.Id, Id: n.Id,
Name: n.Name, Name: n.Name,
//State: n.State,
apiClient: n.apiClient, apiClient: n.apiClient,
} }
} }
@ -154,24 +162,6 @@ func (n *ContainerNetwork) URI() string {
return fmt.Sprintf("container-network://%s", n.Name) return fmt.Sprintf("container-network://%s", n.Name)
} }
/*
func (n *ContainerNetwork) SetURI(uri string) error {
resourceUri, e := url.Parse(uri)
if e == nil {
if resourceUri.Scheme == n.Type() {
n.Name, e = filepath.Abs(filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI()))
} else {
e = fmt.Errorf("%w: %s is not a %s", ErrInvalidResourceURI, uri, n.Type())
}
}
return e
}
func (n *ContainerNetwork) UseConfig(config data.ConfigurationValueGetter) {
n.config = config
}
*/
func (n *ContainerNetwork) JSON() ([]byte, error) { func (n *ContainerNetwork) JSON() ([]byte, error) {
return json.Marshal(n) return json.Marshal(n)
} }

View File

@ -39,9 +39,22 @@ type Exec struct {
} }
func init() { func init() {
folio.DocumentRegistry.ResourceTypes.Register([]string{"exec"}, func(u *url.URL) data.Resource { folio.DocumentRegistry.ResourceTypes.Register([]string{"exec"}, func(u *url.URL) (res data.Resource) {
x := NewExec() x := NewExec()
return x res = x
if u != nil {
uri := folio.CastParsedURI(u)
if ri, ok := res.(data.ResourceInitializer); ok {
if err := ri.Init(uri); err != nil {
panic(err)
}
} else {
if err := x.SetParsedURI(uri); err != nil {
panic(err)
}
}
}
return
}) })
} }
@ -56,7 +69,7 @@ func (x *Exec) SetResourceMapper(resources data.ResourceMapper) {
func (x *Exec) Clone() data.Resource { func (x *Exec) Clone() data.Resource {
return &Exec { return &Exec {
Common: x.Common, Common: x.Common.Clone(),
Id: x.Id, Id: x.Id,
CreateTemplate: x.CreateTemplate, CreateTemplate: x.CreateTemplate,
ReadTemplate: x.ReadTemplate, ReadTemplate: x.ReadTemplate,
@ -76,13 +89,16 @@ func (x *Exec) URI() string {
return fmt.Sprintf("exec://%s", x.Id) return fmt.Sprintf("exec://%s", x.Id)
} }
func (x *Exec) SetURI(uri string) (err error) { func (x *Exec) Init(u data.URIParser) (err error) {
err = x.Common.SetURI(uri) if u == nil {
u = folio.URI(x.URI()).Parse()
}
err = x.SetParsedURI(u)
x.Id = x.Common.Path x.Id = x.Common.Path
return return
} }
func (x *Exec) SetParsedURI(uri *url.URL) (err error) { func (x *Exec) SetParsedURI(uri data.URIParser) (err error) {
err = x.Common.SetParsedURI(uri) err = x.Common.SetParsedURI(uri)
x.Id = x.Common.Path x.Id = x.Common.Path
return return

View File

@ -4,20 +4,21 @@
package resource package resource
import ( import (
_ "context" _ "context"
_ "encoding/json" _ "encoding/json"
_ "fmt" _ "fmt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
_ "gopkg.in/yaml.v3" _ "gopkg.in/yaml.v3"
_ "io" _ "io"
_ "log" _ "log"
_ "net/http" _ "net/http"
_ "net/http/httptest" _ "net/http/httptest"
_ "net/url" _ "net/url"
_ "os" _ "os"
_ "strings" _ "strings"
"testing" "testing"
"decl/internal/command" "decl/internal/command"
"decl/internal/folio"
) )
func TestNewExecResource(t *testing.T) { func TestNewExecResource(t *testing.T) {
@ -55,9 +56,10 @@ func TestCreateExec(t *testing.T) {
} }
func TestExecSetURI(t *testing.T) { func TestExecSetURI(t *testing.T) {
var uri folio.URI = "exec://12345_key"
x := NewExec() x := NewExec()
assert.NotNil(t, x) assert.NotNil(t, x)
e := x.SetURI("exec://" + "12345_key") e := x.SetParsedURI(uri.Parse())
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, "exec", x.Type()) assert.Equal(t, "exec", x.Type())
assert.Equal(t, "12345_key", x.Path) assert.Equal(t, "12345_key", x.Path)

View File

@ -14,7 +14,6 @@ import (
"net/url" "net/url"
"os" "os"
"os/user" "os/user"
"path/filepath"
"strconv" "strconv"
"syscall" "syscall"
"time" "time"
@ -56,19 +55,14 @@ var ErrInvalidFileGroup error = errors.New("Unknown Group")
type FileMode string type FileMode string
func init() { func init() {
folio.DocumentRegistry.ResourceTypes.Register([]string{"file"}, func(u *url.URL) data.Resource { folio.DocumentRegistry.ResourceTypes.Register([]string{"file"}, func(u *url.URL) (res data.Resource) {
f := NewFile() f := NewFile()
f.parsedURI = u slog.Info("FileFactory", "uri", u)
//f.Uri.SetURL(u) if u != nil {
f.Path = filepath.Join(u.Hostname(), u.Path) if err := folio.CastParsedURI(u).ConstructResource(f); err != nil {
f.exttype, f.fileext = f.Uri.Extension() panic(err)
}
slog.Info("folio.DocumentRegistry.ResourceTypes.Register()()", "url", u, "file", f)
/*
if absPath, err := filepath.Abs(f.Path); err == nil {
f.Filesystem = os.DirFS(filepath.Dir(absPath))
} }
*/
return f return f
}) })
} }
@ -83,18 +77,12 @@ The `SerializeContent` the flag allows forcing the content to be serialized in t
*/ */
type File struct { type File struct {
Uri folio.URI `json:"uri,omitempty" yaml:"uri,omitempty"` *Common `json:",inline" yaml:",inline"`
parsedURI *url.URL `json:"-" yaml:"-"`
Filesystem fs.FS `json:"-" yaml:"-"` Filesystem fs.FS `json:"-" yaml:"-"`
exttype string `json:"-" yaml:"-"`
fileext string `json:"-" yaml:"-"`
stater machine.Stater `json:"-" yaml:"-"` stater machine.Stater `json:"-" yaml:"-"`
normalizePath bool `json:"-" yaml:"-"`
absPath string `json:"-" yaml:"-"`
basePath int `json:"-" yaml:"-"` basePath int `json:"-" yaml:"-"`
Path string `json:"path" yaml:"path"`
Owner string `json:"owner" yaml:"owner"` Owner string `json:"owner" yaml:"owner"`
Group string `json:"group" yaml:"group"` Group string `json:"group" yaml:"group"`
Mode FileMode `json:"mode" yaml:"mode"` Mode FileMode `json:"mode" yaml:"mode"`
@ -109,10 +97,8 @@ type File struct {
Size int64 `json:"size,omitempty" yaml:"size,omitempty"` Size int64 `json:"size,omitempty" yaml:"size,omitempty"`
Target string `json:"target,omitempty" yaml:"target,omitempty"` Target string `json:"target,omitempty" yaml:"target,omitempty"`
FileType FileType `json:"filetype" yaml:"filetype"` FileType FileType `json:"filetype" yaml:"filetype"`
State string `json:"state,omitempty" yaml:"state,omitempty"`
SerializeContent bool `json:"serializecontent,omitempty" yaml:"serializecontent,omitempty"` SerializeContent bool `json:"serializecontent,omitempty" yaml:"serializecontent,omitempty"`
GzipContent bool `json:"gzipcontent,omitempty" yaml:"gzipcontent,omitempty"` GzipContent bool `json:"gzipcontent,omitempty" yaml:"gzipcontent,omitempty"`
config data.ConfigurationValueGetter
Resources data.ResourceMapper `json:"-" yaml:"-"` Resources data.ResourceMapper `json:"-" yaml:"-"`
} }
@ -123,29 +109,48 @@ type ResourceFileInfo struct {
func NewFile() *File { func NewFile() *File {
currentUser, _ := user.Current() currentUser, _ := user.Current()
group, _ := user.LookupGroupId(currentUser.Gid) group, _ := user.LookupGroupId(currentUser.Gid)
f := &File{ normalizePath: false, Owner: currentUser.Username, Group: group.Name, Mode: "0644", FileType: RegularFile, SerializeContent: false } f := &File{
Common: NewCommon(FileTypeName, true),
Owner: currentUser.Username,
Group: group.Name,
Mode: "0644",
FileType: RegularFile,
SerializeContent: false,
}
f.PathNormalization(false)
slog.Info("NewFile()", "file", f) slog.Info("NewFile()", "file", f)
return f return f
} }
func NewNormalizedFile() *File { func NewNormalizedFile() *File {
f := NewFile() f := NewFile()
f.normalizePath = true f.PathNormalization(true)
return f return f
} }
func (f *File) Init(u data.URIParser) error {
if u == nil {
u = folio.URI(f.URI()).Parse()
}
return f.SetParsedURI(u)
}
func (f *File) NormalizePath() error {
return f.Common.NormalizePath()
}
func (f *File) ContentType() string { func (f *File) ContentType() string {
var ext strings.Builder var ext strings.Builder
if f.parsedURI.Scheme != "file" { if f.parsedURI.Scheme != "file" {
return f.parsedURI.Scheme return f.parsedURI.Scheme
} }
if f.fileext == "" { if f.fileext == "" {
return f.exttype return f.exttype
} }
ext.WriteString(f.exttype) ext.WriteString(f.exttype)
ext.WriteRune('.') ext.WriteRune('.')
ext.WriteString(f.fileext) ext.WriteString(f.fileext)
return ext.String() return ext.String()
} }
func (f *File) SetResourceMapper(resources data.ResourceMapper) { func (f *File) SetResourceMapper(resources data.ResourceMapper) {
@ -154,13 +159,7 @@ func (f *File) SetResourceMapper(resources data.ResourceMapper) {
func (f *File) Clone() data.Resource { func (f *File) Clone() data.Resource {
return &File { return &File {
Uri: f.Uri, Common: f.Common.Clone(),
parsedURI: f.parsedURI,
exttype: f.exttype,
fileext: f.fileext,
normalizePath: f.normalizePath,
absPath: f.absPath,
Path: f.Path,
Owner: f.Owner, Owner: f.Owner,
Group: f.Group, Group: f.Group,
Mode: f.Mode, Mode: f.Mode,
@ -172,7 +171,6 @@ func (f *File) Clone() data.Resource {
Size: f.Size, Size: f.Size,
Target: f.Target, Target: f.Target,
FileType: f.FileType, FileType: f.FileType,
State: f.State,
} }
} }
@ -189,26 +187,26 @@ func (f *File) Notify(m *machine.EventMessage) {
switch m.On { switch m.On {
case machine.ENTERSTATEEVENT: case machine.ENTERSTATEEVENT:
switch m.Dest { switch m.Dest {
case "start_stat": case "start_stat":
if statErr := f.ReadStat(); statErr == nil { if statErr := f.ReadStat(); statErr == nil {
if triggerErr := f.StateMachine().Trigger("exists"); triggerErr == nil { if triggerErr := f.StateMachine().Trigger("exists"); triggerErr == nil {
return return
}
} else {
if triggerErr := f.StateMachine().Trigger("notexists"); triggerErr == nil {
return
}
} }
case "start_read":
if _,readErr := f.Read(ctx); readErr == nil {
if triggerErr := f.StateMachine().Trigger("state_read"); triggerErr == nil {
return
} else {
f.State = "absent"
panic(triggerErr)
}
} else { } else {
f.State = "absent" if triggerErr := f.StateMachine().Trigger("notexists"); triggerErr == nil {
return
}
}
case "start_read":
if _,readErr := f.Read(ctx); readErr == nil {
if triggerErr := f.StateMachine().Trigger("state_read"); triggerErr == nil {
return
} else {
f.Common.State = "absent"
panic(triggerErr)
}
} else {
f.Common.State = "absent"
if ! errors.Is(readErr, ErrResourceStateAbsent) { if ! errors.Is(readErr, ErrResourceStateAbsent) {
panic(readErr) panic(readErr)
} }
@ -219,7 +217,7 @@ func (f *File) Notify(m *machine.EventMessage) {
return return
} }
} else { } else {
f.State = "absent" f.Common.State = "absent"
panic(e) panic(e)
} }
case "start_update": case "start_update":
@ -227,10 +225,10 @@ func (f *File) Notify(m *machine.EventMessage) {
if triggerErr := f.stater.Trigger("updated"); triggerErr == nil { if triggerErr := f.stater.Trigger("updated"); triggerErr == nil {
return return
} else { } else {
f.State = "absent" f.Common.State = "absent"
} }
} else { } else {
f.State = "absent" f.Common.State = "absent"
panic(updateErr) panic(updateErr)
} }
case "start_delete": case "start_delete":
@ -238,17 +236,17 @@ func (f *File) Notify(m *machine.EventMessage) {
if triggerErr := f.StateMachine().Trigger("deleted"); triggerErr == nil { if triggerErr := f.StateMachine().Trigger("deleted"); triggerErr == nil {
return return
} else { } else {
f.State = "present" f.Common.State = "present"
panic(triggerErr) panic(triggerErr)
} }
} else { } else {
f.State = "present" f.Common.State = "present"
panic(deleteErr) panic(deleteErr)
} }
case "absent": case "absent":
f.State = "absent" f.Common.State = "absent"
case "present", "created", "read": case "present", "created", "read":
f.State = "present" f.Common.State = "present"
} }
case machine.EXITSTATEEVENT: case machine.EXITSTATEEVENT:
} }
@ -258,12 +256,8 @@ func (f *File) SetGzipContent(flag bool) {
f.GzipContent = flag f.GzipContent = flag
} }
func (f *File) PathNormalization(flag bool) {
f.normalizePath = flag
}
func (f *File) FilePath() string { func (f *File) FilePath() string {
return f.Path return f.Common.Path
} }
func (f *File) SetFS(fsys fs.FS) { func (f *File) SetFS(fsys fs.FS) {
@ -271,63 +265,23 @@ func (f *File) SetFS(fsys fs.FS) {
} }
func (f *File) URI() string { func (f *File) URI() string {
return fmt.Sprintf("file://%s", f.Path) return fmt.Sprintf("file://%s", f.Common.Path)
} }
func (f *File) RelativePath() string { func (f *File) RelativePath() string {
return f.Path[f.basePath:] return f.Common.Path[f.basePath:]
} }
func (f *File) SetBasePath(index int) { func (f *File) SetBasePath(index int) {
if index < len(f.Path) { if index < len(f.Common.Path) {
f.basePath = index f.basePath = index
} }
} }
func (f *File) SetURI(uri string) (err error) {
slog.Info("File.SetURI()", "uri", uri, "file", f, "parsed", f.parsedURI)
f.SetURIFromString(uri)
err = f.SetParsedURI(f.Uri.Parse())
return
}
func (f *File) DetectGzip() bool { func (f *File) DetectGzip() bool {
return (f.parsedURI.Query().Get("gzip") == "true" || f.fileext == "gz" || f.exttype == "tgz" || f.exttype == "gz" || f.fileext == "tgz" ) return (f.parsedURI.Query().Get("gzip") == "true" || f.fileext == "gz" || f.exttype == "tgz" || f.exttype == "gz" || f.fileext == "tgz" )
} }
func (f *File) SetURIFromString(uri string) {
f.Uri = folio.URI(uri)
f.exttype, f.fileext = f.Uri.Extension()
}
func (f *File) SetParsedURI(u *url.URL) (err error) {
if u != nil {
if u.Scheme == "" {
u.Scheme = "file"
f.Uri = ""
}
if f.Uri.IsEmpty() {
f.SetURIFromString(u.String())
}
slog.Info("File.SetParsedURI()", "parsed", u, "path", f.Path)
f.parsedURI = u
if f.parsedURI.Scheme == "file" {
f.Path = filepath.Join(f.parsedURI.Hostname(), f.parsedURI.Path)
slog.Info("File.SetParsedURI()", "path", f.Path)
if err = f.NormalizePath(); err != nil {
return
}
return
}
}
err = fmt.Errorf("%w: %s is not a file", ErrInvalidResourceURI, f.Uri)
return
}
func (f *File) UseConfig(config data.ConfigurationValueGetter) {
f.config = config
}
func (f *File) JSON() ([]byte, error) { func (f *File) JSON() ([]byte, error) {
return json.Marshal(f) return json.Marshal(f)
} }
@ -343,7 +297,7 @@ func (f *File) Validate() (err error) {
func (f *File) Apply() error { func (f *File) Apply() error {
ctx := context.Background() ctx := context.Background()
switch f.State { switch f.Common.State {
case "absent": case "absent":
return f.Delete(ctx) return f.Delete(ctx)
case "present": case "present":
@ -385,9 +339,10 @@ func (f *File) ResolveId(ctx context.Context) string {
if e := f.NormalizePath(); e != nil { if e := f.NormalizePath(); e != nil {
panic(e) panic(e)
} }
return f.Path return f.Common.Path
} }
/*
func (f *File) NormalizePath() (err error) { func (f *File) NormalizePath() (err error) {
if f.config != nil { if f.config != nil {
if prefixPath, configErr := f.config.GetValue("prefix"); configErr == nil { if prefixPath, configErr := f.config.GetValue("prefix"); configErr == nil {
@ -399,6 +354,7 @@ func (f *File) NormalizePath() (err error) {
} }
return return
} }
*/
func (f *File) GetContentSourceRef() string { func (f *File) GetContentSourceRef() string {
return string(f.ContentSourceRef) return string(f.ContentSourceRef)
@ -411,7 +367,7 @@ func (f *File) SetContentSourceRef(uri string) {
func (f *File) Stat() (info fs.FileInfo, err error) { func (f *File) Stat() (info fs.FileInfo, err error) {
if _, ok := f.Filesystem.(embed.FS); ok { if _, ok := f.Filesystem.(embed.FS); ok {
info, err = fs.Stat(f.Filesystem, f.Path) info, err = fs.Stat(f.Filesystem, f.Common.Path)
} else { } else {
info, err = os.Lstat(f.absPath) info, err = os.Lstat(f.absPath)
} }
@ -471,12 +427,12 @@ func (f *File) Create(ctx context.Context) error {
switch f.FileType { switch f.FileType {
case SymbolicLinkFile: case SymbolicLinkFile:
linkErr := os.Symlink(f.Target, f.Path) linkErr := os.Symlink(f.Target, f.Common.Path)
if linkErr != nil { if linkErr != nil {
return linkErr return linkErr
} }
case DirectoryFile: case DirectoryFile:
if mkdirErr := os.MkdirAll(f.Path, mode); mkdirErr != nil { if mkdirErr := os.MkdirAll(f.Common.Path, mode); mkdirErr != nil {
return mkdirErr return mkdirErr
} }
default: default:
@ -504,7 +460,8 @@ func (f *File) Create(ctx context.Context) error {
}) })
var createdFileWriter io.WriteCloser var createdFileWriter io.WriteCloser
createdFile, fileErr := os.Create(f.Path) createdFile, fileErr := os.Create(f.Common.Path)
slog.Info("File.Create(): os.Create()", "path", f.Common.Path, "error", fileErr)
if fileErr != nil { if fileErr != nil {
return fileErr return fileErr
} }
@ -518,6 +475,7 @@ func (f *File) Create(ctx context.Context) error {
defer createdFile.Close() defer createdFile.Close()
slog.Info("File.Create(): Chmod()", "path", f.Common.Path, "mode", mode)
if chmodErr := createdFile.Chmod(mode); chmodErr != nil { if chmodErr := createdFile.Chmod(mode); chmodErr != nil {
return chmodErr return chmodErr
} }
@ -529,16 +487,20 @@ func (f *File) Create(ctx context.Context) error {
f.Sha256 = fmt.Sprintf("%x", hash.Sum(nil)) f.Sha256 = fmt.Sprintf("%x", hash.Sum(nil))
if !f.Mtime.IsZero() && !f.Atime.IsZero() { if !f.Mtime.IsZero() && !f.Atime.IsZero() {
if chtimesErr := os.Chtimes(f.Path, f.Atime, f.Mtime); chtimesErr != nil { slog.Info("File.Create(): Chtimes()", "path", f.Common.Path, "atime", f.Atime, "mtime", f.Mtime)
if chtimesErr := os.Chtimes(f.Common.Path, f.Atime, f.Mtime); chtimesErr != nil {
return chtimesErr return chtimesErr
} }
} else {
slog.Info("File.Create(): Chtimes() SKIPPED", "path", f.Common.Path, "atime", f.Atime, "mtime", f.Mtime)
} }
} }
slog.Info("File.Create(): Chown()", "path", f.Common.Path, "uid", uid, "gid", gid)
if chownErr := os.Chown(f.Path, uid, gid); chownErr != nil { if chownErr := os.Chown(f.Common.Path, uid, gid); chownErr != nil {
return chownErr return chownErr
} }
f.State = "present" f.Common.State = "present"
return nil return nil
} }
@ -547,7 +509,7 @@ func (f *File) Update(ctx context.Context) error {
} }
func (f *File) Delete(ctx context.Context) error { func (f *File) Delete(ctx context.Context) error {
return os.Remove(f.Path) return os.Remove(f.Common.Path)
} }
func (f *File) UpdateContentAttributes() { func (f *File) UpdateContentAttributes() {
@ -603,11 +565,11 @@ func (f *File) ContentSourceRefStat() (info fs.FileInfo) {
func (f *File) ReadStat() (err error) { func (f *File) ReadStat() (err error) {
var info fs.FileInfo var info fs.FileInfo
slog.Info("ReadStat()", "filesystem", f.Filesystem, "path", f.Path) slog.Info("ReadStat()", "filesystem", f.Filesystem, "path", f.Common.Path)
info, err = f.Stat() info, err = f.Stat()
slog.Info("ReadStat()", "filesystem", f.Filesystem, "path", f.Path, "info", info, "error", err) slog.Info("ReadStat()", "filesystem", f.Filesystem, "path", f.Common.Path, "info", info, "error", err)
if err == nil { if err == nil {
_ = f.SetFileInfo(info) _ = f.SetFileInfo(info)
@ -621,9 +583,9 @@ func (f *File) ReadStat() (err error) {
} }
slog.Info("ReadStat()", "stat", info, "path", f.Path) slog.Info("ReadStat()", "stat", info, "path", f.Common.Path)
if err != nil { if err != nil {
f.State = "absent" f.Common.State = "absent"
return return
} }
@ -631,27 +593,29 @@ func (f *File) ReadStat() (err error) {
} }
func (f *File) open() (file io.ReadCloser, err error) { func (f *File) open() (file io.ReadCloser, err error) {
slog.Info("open()", "file", f.Path, "fs", f.Filesystem) slog.Info("open()", "file", f.Common.Path, "fs", f.Filesystem)
if _, ok := f.Filesystem.(embed.FS); ok { if _, ok := f.Filesystem.(embed.FS); ok {
file, err = f.Filesystem.Open(f.Path) file, err = f.Filesystem.Open(f.Common.Path)
} else { } else {
file, err = os.Open(f.Path) file, err = os.Open(f.Common.Path)
} }
if f.GzipContent && f.DetectGzip() { if f.GzipContent && f.DetectGzip() {
file, err = gzip.NewReader(file) file, err = gzip.NewReader(file)
} }
slog.Info("open()", "file", f.Path, "error", err) slog.Info("open()", "file", f.Common.Path, "error", err)
return return
} }
func (f *File) Read(ctx context.Context) ([]byte, error) { func (f *File) Read(ctx context.Context) ([]byte, error) {
/*
if normalizePathErr := f.NormalizePath(); normalizePathErr != nil { if normalizePathErr := f.NormalizePath(); normalizePathErr != nil {
return nil, normalizePathErr return nil, normalizePathErr
} }
*/
statErr := f.ReadStat() statErr := f.ReadStat()
if statErr != nil { if statErr != nil {
return nil, fmt.Errorf("%w - %w", ErrResourceStateAbsent, statErr) return nil, fmt.Errorf("%w - %w: %s", ErrResourceStateAbsent, statErr, f.Path)
} }
switch f.FileType { switch f.FileType {
@ -671,13 +635,13 @@ func (f *File) Read(ctx context.Context) ([]byte, error) {
f.Sha256 = fmt.Sprintf("%x", sha256.Sum256(fileContent)) f.Sha256 = fmt.Sprintf("%x", sha256.Sum256(fileContent))
} }
case SymbolicLinkFile: case SymbolicLinkFile:
linkTarget, pathErr := os.Readlink(f.Path) linkTarget, pathErr := os.Readlink(f.Common.Path)
if pathErr != nil { if pathErr != nil {
return nil, pathErr return nil, pathErr
} }
f.Target = linkTarget f.Target = linkTarget
} }
f.State = "present" f.Common.State = "present"
return yaml.Marshal(f) return yaml.Marshal(f)
} }

View File

@ -4,15 +4,15 @@ package resource
import ( import (
"context" "context"
_ "encoding/json" _ "encoding/json"
"fmt" "fmt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"io" "io"
_ "log" _ "log"
_ "net/http" _ "net/http"
_ "net/http/httptest" _ "net/http/httptest"
_ "net/url" _ "net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -23,6 +23,7 @@ import (
"io/fs" "io/fs"
"decl/internal/codec" "decl/internal/codec"
"decl/internal/data" "decl/internal/data"
"decl/internal/folio"
"log/slog" "log/slog"
) )
@ -37,7 +38,8 @@ func TestNewFileNormalized(t *testing.T) {
f := NewNormalizedFile() f := NewNormalizedFile()
assert.NotNil(t, f) assert.NotNil(t, f)
assert.Nil(t, f.SetURI("file://" + indirectFile)) f.Path = indirectFile
assert.Nil(t, f.Init(nil))
assert.NotEqual(t, indirectFile, f.Path) assert.NotEqual(t, indirectFile, f.Path)
assert.Equal(t, absFilePath, f.Path) assert.Equal(t, absFilePath, f.Path)
@ -84,13 +86,21 @@ func TestReadFile(t *testing.T) {
testFile := NewFile() testFile := NewFile()
e := testFile.LoadDecl(decl) e := testFile.LoadDecl(decl)
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, "present", testFile.Common.State)
assert.Equal(t, file, testFile.Common.Path)
applyErr := testFile.Apply() applyErr := testFile.Apply()
assert.Nil(t, applyErr) assert.Nil(t, applyErr)
assert.FileExists(t, file)
f := NewFile() f := NewFile()
assert.NotEqual(t, nil, f) assert.NotNil(t, f)
f.Path = file f.Path = file
assert.Nil(t, f.Init(nil))
r, e := f.Read(ctx) r, e := f.Read(ctx)
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, ProcessTestUserName, f.Owner) assert.Equal(t, ProcessTestUserName, f.Owner)
@ -118,7 +128,8 @@ func TestUseConfig(t *testing.T) {
return nil, data.ErrUnknownConfigurationKey return nil, data.ErrUnknownConfigurationKey
})) }))
assert.Nil(t, f.SetURI(fmt.Sprintf("file://%s", file))) uri := folio.URI(fmt.Sprintf("file://%s", file))
assert.Nil(t, f.Init(uri.Parse()))
assert.Equal(t, filepath.Join("/tmp", file), f.FilePath()) assert.Equal(t, filepath.Join("/tmp", file), f.FilePath())
} }
@ -263,8 +274,8 @@ func TestFileSetURI(t *testing.T) {
file, _ := filepath.Abs(TempDir.FilePath("testuri.txt")) file, _ := filepath.Abs(TempDir.FilePath("testuri.txt"))
f := NewFile() f := NewFile()
assert.NotNil(t, f) assert.NotNil(t, f)
e := f.SetURI("file://" + file) uri := folio.URI("file://" + file).Parse()
assert.Nil(t, e) assert.Nil(t, f.Init(uri))
assert.Equal(t, "file", f.Type()) assert.Equal(t, "file", f.Type())
assert.Equal(t, file, f.Path) assert.Equal(t, file, f.Path)
} }
@ -302,6 +313,7 @@ func TestFileUpdateAttributesFromFileInfo(t *testing.T) {
func TestFileReadStat(t *testing.T) { func TestFileReadStat(t *testing.T) {
ctx := context.Background() ctx := context.Background()
link := TempDir.FilePath("link.txt") link := TempDir.FilePath("link.txt")
linkTargetFile := TempDir.FilePath("testuri.txt") linkTargetFile := TempDir.FilePath("testuri.txt")
@ -309,8 +321,8 @@ func TestFileReadStat(t *testing.T) {
assert.NotNil(t, f) assert.NotNil(t, f)
f.Path = linkTargetFile f.Path = linkTargetFile
e := f.NormalizePath() f.PathNormalization(true)
assert.Nil(t, e) assert.Nil(t, f.Init(nil))
statErr := f.ReadStat() statErr := f.ReadStat()
assert.Error(t, statErr) assert.Error(t, statErr)
@ -324,8 +336,9 @@ func TestFileReadStat(t *testing.T) {
l := NewFile() l := NewFile()
assert.NotNil(t, l) assert.NotNil(t, l)
l.PathNormalization(true)
assert.Nil(t, l.Init(nil))
assert.Nil(t, l.NormalizePath())
l.FileType = SymbolicLinkFile l.FileType = SymbolicLinkFile
l.Path = link l.Path = link
l.Target = linkTargetFile l.Target = linkTargetFile
@ -340,6 +353,9 @@ func TestFileReadStat(t *testing.T) {
testRead := NewFile() testRead := NewFile()
testRead.Path = link testRead.Path = link
assert.Nil(t, testRead.Init(nil))
_,testReadErr := testRead.Read(ctx) _,testReadErr := testRead.Read(ctx)
assert.Nil(t, testReadErr) assert.Nil(t, testReadErr)
assert.Equal(t, linkTargetFile, testRead.Target) assert.Equal(t, linkTargetFile, testRead.Target)
@ -355,6 +371,8 @@ func TestFileResourceFileInfo(t *testing.T) {
f.Mode = "0600" f.Mode = "0600"
f.Content = "some test data" f.Content = "some test data"
f.State = "present" f.State = "present"
assert.Nil(t, f.Init(nil))
assert.Nil(t, f.Apply()) assert.Nil(t, f.Apply())
_, readErr := f.Read(context.Background()) _, readErr := f.Read(context.Background())
@ -378,20 +396,45 @@ func TestFileClone(t *testing.T) {
assert.NotNil(t, f) assert.NotNil(t, f)
f.Path = testFile f.Path = testFile
assert.Nil(t, f.Init(nil))
f.Mode = "0600" f.Mode = "0600"
f.State = "present" f.State = "present"
assert.Nil(t, f.Apply()) assert.Nil(t, f.Apply())
origin := time.Now()
_,readErr := f.Read(ctx) _,readErr := f.Read(ctx)
assert.Nil(t, readErr) assert.Nil(t, readErr)
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
assert.Greater(t, origin, f.Mtime)
clone := f.Clone().(*File) clone := f.Clone().(*File)
assert.Equal(t, f, clone) assert.Equal(t, f.Common.Path, clone.Common.Path)
clone.Mtime = time.Time{} assert.Equal(t, f.Common.absPath, clone.Common.absPath)
assert.Equal(t, f.Common.parsedURI, clone.Common.parsedURI)
assert.Equal(t, f.Common.exttype, clone.Common.exttype)
assert.Equal(t, f.Common.fileext, clone.Common.fileext)
assert.Equal(t, f.Common.State, clone.Common.State)
assert.Equal(t, f.Size, clone.Size)
assert.Equal(t, f.Owner, clone.Owner)
assert.Equal(t, f.Group, clone.Group)
assert.Equal(t, f.Mode, clone.Mode)
assert.Equal(t, f.Atime, clone.Atime)
assert.Equal(t, f.Mtime, clone.Mtime)
assert.Equal(t, f.Ctime, clone.Ctime)
assert.Equal(t, f.Content, clone.Content)
assert.Equal(t, f.Sha256, clone.Sha256)
clone.Mtime = time.Now()
clone.Path = testCloneFile clone.Path = testCloneFile
assert.Nil(t, clone.Init(nil))
assert.NotEqual(t, f.absPath, clone.absPath)
slog.Info("TestFileClone", "clone", clone)
assert.Nil(t, clone.Apply()) assert.Nil(t, clone.Apply())
slog.Info("TestFileClone - applied mtime change", "clone", clone)
_,updateReadErr := f.Read(ctx) _,updateReadErr := f.Read(ctx)
assert.Nil(t, updateReadErr) assert.Nil(t, updateReadErr)
@ -399,7 +442,8 @@ func TestFileClone(t *testing.T) {
_, cloneReadErr := clone.Read(ctx) _, cloneReadErr := clone.Read(ctx)
assert.Nil(t, cloneReadErr) assert.Nil(t, cloneReadErr)
fmt.Printf("file %#v\nclone %#v\n", f, clone) slog.Info("TestFileClone - read mtime change", "orig", f.Mtime, "clone", clone.Mtime)
fmt.Printf("file %#v\n %#v\nclone %#v\n %#v\n", f, f.Common, clone, clone.Common)
assert.NotEqual(t, f.Mtime, clone.Mtime) assert.NotEqual(t, f.Mtime, clone.Mtime)
} }
@ -412,12 +456,16 @@ func TestFileErrors(t *testing.T) {
stater := f.StateMachine() stater := f.StateMachine()
f.Path = testFile f.Path = testFile
assert.Nil(t, f.Init(nil))
f.Mode = "631" f.Mode = "631"
assert.Nil(t, stater.Trigger("create")) assert.Nil(t, stater.Trigger("create"))
assert.FileExists(t, f.Path)
read := NewFile() read := NewFile()
readStater := read.StateMachine() readStater := read.StateMachine()
read.Path = testFile read.Path = testFile
assert.Nil(t, read.Init(nil))
assert.Nil(t, readStater.Trigger("read")) assert.Nil(t, readStater.Trigger("read"))
assert.Equal(t, FileMode("0631"), read.Mode) assert.Equal(t, FileMode("0631"), read.Mode)
@ -534,7 +582,8 @@ func TestFilePathURI(t *testing.T) {
e := f.LoadDecl(decl) e := f.LoadDecl(decl)
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, "", f.FilePath()) assert.Equal(t, "", f.FilePath())
assert.ErrorContains(t, f.Validate(), "path: String length must be greater than or equal to 1") // assert.ErrorContains(t, f.Validate(), "path: String length must be greater than or equal to 1")
assert.ErrorContains(t, f.Validate(), "path is required")
} }
func TestFileAbsent(t *testing.T) { func TestFileAbsent(t *testing.T) {
@ -593,8 +642,8 @@ func TestFileSetURIError(t *testing.T) {
file := TempDir.FilePath("fooread.txt") file := TempDir.FilePath("fooread.txt")
f := NewFile() f := NewFile()
assert.NotNil(t, f) assert.NotNil(t, f)
e := f.SetURI("foo://" + file) uri := folio.URI("foo://" + file).Parse()
assert.NotNil(t, e) e := f.Init(uri)
assert.ErrorIs(t, e, ErrInvalidResourceURI) assert.ErrorIs(t, e, ErrInvalidResourceURI)
} }
@ -602,8 +651,8 @@ func TestFileContentType(t *testing.T) {
file := TempDir.FilePath("fooread.txt") file := TempDir.FilePath("fooread.txt")
f := NewFile() f := NewFile()
assert.NotNil(t, f) assert.NotNil(t, f)
e := f.SetURI("file://" + file) uri := folio.URI("file://" + file).Parse()
assert.Nil(t, e) assert.Nil(t, f.Init(uri))
assert.Equal(t, "txt", f.ContentType()) assert.Equal(t, "txt", f.ContentType())
} }

View File

@ -48,7 +48,6 @@ type Group struct {
ReadCommand *command.Command `json:"-" yaml:"-"` ReadCommand *command.Command `json:"-" yaml:"-"`
UpdateCommand *command.Command `json:"-" yaml:"-"` UpdateCommand *command.Command `json:"-" yaml:"-"`
DeleteCommand *command.Command `json:"-" yaml:"-"` DeleteCommand *command.Command `json:"-" yaml:"-"`
//State string `json:"state,omitempty" yaml:"state,omitempty"`
config data.ConfigurationValueGetter config data.ConfigurationValueGetter
Resources data.ResourceMapper `json:"-" yaml:"-"` Resources data.ResourceMapper `json:"-" yaml:"-"`
groupStatus *user.Group `json:"-" yaml:"-"` groupStatus *user.Group `json:"-" yaml:"-"`
@ -62,18 +61,14 @@ func NewGroup() (g *Group) {
} }
func init() { func init() {
folio.DocumentRegistry.ResourceTypes.Register([]string{"group"}, func(u *url.URL) data.Resource { folio.DocumentRegistry.ResourceTypes.Register([]string{"group"}, func(u *url.URL) (group data.Resource) {
group := NewGroup() group = NewGroup()
group.Name = u.Hostname() if u != nil {
group.GID = LookupGIDString(u.Hostname()) if err := folio.CastParsedURI(u).ConstructResource(group); err != nil {
if _, addGroupPathErr := exec.LookPath("addgroup"); addGroupPathErr == nil { panic(err)
group.GroupType = GroupTypeAddGroup }
} }
if _, pathErr := exec.LookPath("groupadd"); pathErr == nil { return
group.GroupType = GroupTypeGroupAdd
}
group.CreateCommand, group.ReadCommand, group.UpdateCommand, group.DeleteCommand = group.GroupType.NewCRUD()
return group
}) })
} }
@ -87,6 +82,25 @@ func FindSystemGroupType() GroupType {
return GroupTypeAddGroup return GroupTypeAddGroup
} }
func (g *Group) Init(u data.URIParser) error {
var initializeURI folio.URI
if u == nil {
u = folio.URI(g.URI()).Parse()
}
uri := u.URL()
g.Name = uri.Hostname()
g.GID = LookupGIDString(uri.Hostname())
if _, addGroupPathErr := exec.LookPath("addgroup"); addGroupPathErr == nil {
g.GroupType = GroupTypeAddGroup
}
if _, pathErr := exec.LookPath("groupadd"); pathErr == nil {
g.GroupType = GroupTypeGroupAdd
}
g.CreateCommand, g.ReadCommand, g.UpdateCommand, g.DeleteCommand = g.GroupType.NewCRUD()
initializeURI.SetURL(uri)
return g.SetURI(string(initializeURI))
}
func (g *Group) NormalizePath() error { func (g *Group) NormalizePath() error {
return nil return nil
} }
@ -170,26 +184,10 @@ func (g *Group) Notify(m *machine.EventMessage) {
} }
} }
func (g *Group) SetURI(uri string) error {
resourceUri, e := url.Parse(uri)
if e == nil {
if resourceUri.Scheme == "group" {
g.Name = resourceUri.Hostname()
} else {
e = fmt.Errorf("%w: %s is not a group", ErrInvalidResourceURI, uri)
}
}
return e
}
func (g *Group) URI() string { func (g *Group) URI() string {
return fmt.Sprintf("group://%s", g.Name) return fmt.Sprintf("group://%s", g.Name)
} }
func (g *Group) UseConfig(config data.ConfigurationValueGetter) {
g.config = config
}
func (g *Group) ResolveId(ctx context.Context) string { func (g *Group) ResolveId(ctx context.Context) string {
return LookupUIDString(g.Name) return LookupUIDString(g.Name)
} }

View File

@ -60,16 +60,11 @@ func init() {
} }
func HTTPFactory(u *url.URL) data.Resource { func HTTPFactory(u *url.URL) data.Resource {
var err error
h := NewHTTP() h := NewHTTP()
if u != nil {
slog.Info("HTTP.Factory", "http", h, "url", u) if err := folio.CastParsedURI(u).ConstructResource(h); err != nil {
if err = h.SetParsedURI(u); err != nil { panic(err)
panic(err) }
}
if err = h.Open(); err != nil {
panic(err)
} }
return h return h
} }
@ -118,6 +113,16 @@ func (h *HTTP) SchemeCheck(scheme string) bool {
return false return false
} }
func (h *HTTP) Init(u data.URIParser) (err error) {
if u == nil {
u = folio.URI(h.URI()).Parse()
}
if err = h.SetParsedURI(u); err == nil {
err = h.Open()
}
return
}
func (h *HTTP) NormalizePath() error { func (h *HTTP) NormalizePath() error {
return nil return nil
} }
@ -228,16 +233,9 @@ func (h *HTTP) URI() string {
return h.Endpoint.String() return h.Endpoint.String()
} }
func (h *HTTP) SetURI(uri string) (err error) { func (h *HTTP) SetParsedURI(u data.URIParser) (err error) {
if err = h.Common.SetURI(uri); err == nil {
h.Endpoint = h.Common.Uri
}
return
}
func (h *HTTP) SetParsedURI(u *url.URL) (err error) {
if err = h.Common.SetParsedURI(u); err == nil { if err = h.Common.SetParsedURI(u); err == nil {
h.Endpoint = h.Common.Uri h.Endpoint = h.Common.URI()
} }
return return
} }
@ -343,14 +341,14 @@ func (h *HTTP) Apply() error {
func (h *HTTP) Load(docData []byte, f codec.Format) (err error) { func (h *HTTP) Load(docData []byte, f codec.Format) (err error) {
if err = f.StringDecoder(string(docData)).Decode(h); err == nil { if err = f.StringDecoder(string(docData)).Decode(h); err == nil {
err = h.Common.SetURI(string(h.Endpoint)) err = h.Common.SetParsedURI(folio.URI(h.Endpoint).Parse())
} }
return return
} }
func (h *HTTP) LoadReader(r io.ReadCloser, f codec.Format) (err error) { func (h *HTTP) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
if err = f.Decoder(r).Decode(h); err == nil { if err = f.Decoder(r).Decode(h); err == nil {
err = h.Common.SetURI(string(h.Endpoint)) err = h.Common.SetParsedURI(folio.URI(h.Endpoint).Parse())
//err = h.setParsedURI(h.Endpoint) //err = h.setParsedURI(h.Endpoint)
} }
return return
@ -358,7 +356,7 @@ func (h *HTTP) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
func (h *HTTP) LoadString(docData string, f codec.Format) (err error) { func (h *HTTP) LoadString(docData string, f codec.Format) (err error) {
if err = f.StringDecoder(docData).Decode(h); err == nil { if err = f.StringDecoder(docData).Decode(h); err == nil {
err = h.Common.SetURI(string(h.Endpoint)) err = h.Common.SetParsedURI(folio.URI(h.Endpoint).Parse())
//err = h.setParsedURI(h.Endpoint) //err = h.setParsedURI(h.Endpoint)
} }
return return
@ -369,7 +367,7 @@ func (h *HTTP) LoadDecl(yamlResourceDeclaration string) error {
} }
func (h *HTTP) ResolveId(ctx context.Context) string { func (h *HTTP) ResolveId(ctx context.Context) string {
_ = h.Common.SetURI(h.Endpoint.String()) _ = h.Common.SetParsedURI(folio.URI(h.Endpoint).Parse())
slog.Info("HTTP.ResolveId()", "uri", h.Endpoint.String()) slog.Info("HTTP.ResolveId()", "uri", h.Endpoint.String())
return h.Endpoint.String() return h.Endpoint.String()
} }

View File

@ -30,20 +30,11 @@ const (
func init() { func init() {
folio.DocumentRegistry.ResourceTypes.Register([]string{"iptable"}, func(u *url.URL) data.Resource { folio.DocumentRegistry.ResourceTypes.Register([]string{"iptable"}, func(u *url.URL) data.Resource {
i := NewIptable() i := NewIptable()
i.Table = IptableName(u.Hostname()) if u != nil {
if len(u.Path) > 0 { if err := folio.CastParsedURI(u).ConstructResource(i); err != nil {
fields := strings.FieldsFunc(u.Path, func(c rune) bool { return c == '/' }) panic(err)
slog.Info("iptables factory", "iptable", i, "uri", u, "fields", fields, "number_fields", len(fields))
if len(fields) > 0 {
i.Chain = IptableChain(fields[0])
if len(fields) < 3 {
i.ResourceType = IptableTypeChain
} else {
i.ResourceType = IptableTypeRule
id, _ := strconv.ParseUint(fields[1], 10, 32)
i.SetId(uint(id))
}
} }
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD() i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD()
} }
return i return i
@ -168,6 +159,32 @@ func NewIptable() (i *Iptable) {
return return
} }
func (i *Iptable) Init(u data.URIParser) (err error) {
if u == nil {
u = folio.URI(i.URI()).Parse()
}
uri := u.URL()
err = i.SetParsedURI(u)
i.Table = IptableName(uri.Hostname())
if len(uri.Path) > 0 {
fields := strings.FieldsFunc(uri.Path, func(c rune) bool { return c == '/' })
slog.Info("iptables factory", "iptable", i, "uri", uri, "fields", fields, "number_fields", len(fields))
if len(fields) > 0 {
i.Chain = IptableChain(fields[0])
if len(fields) < 3 {
i.ResourceType = IptableTypeChain
} else {
i.ResourceType = IptableTypeRule
id, _ := strconv.ParseUint(fields[1], 10, 32)
i.SetId(uint(id))
}
}
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD()
}
return
}
func (i *Iptable) SetResourceMapper(resources data.ResourceMapper) { func (i *Iptable) SetResourceMapper(resources data.ResourceMapper) {
i.Resources = resources i.Resources = resources
} }
@ -284,7 +301,7 @@ func (i *Iptable) URI() string {
} }
func (i *Iptable) SetParsedURI(uri *url.URL) (err error) { func (i *Iptable) SetParsedURI(uri data.URIParser) (err error) {
if err = i.Common.SetParsedURI(uri); err == nil { if err = i.Common.SetParsedURI(uri); err == nil {
err = i.setFieldsFromPath() err = i.setFieldsFromPath()
} }
@ -312,14 +329,7 @@ func (i *Iptable) setFieldsFromPath() (err error) {
i.Id = uint(id) i.Id = uint(id)
} }
} else { } else {
err = fmt.Errorf("%w: %s is not an iptable rule", ErrInvalidResourceURI, i.Common.Uri) err = fmt.Errorf("%w: %s is not an iptable rule", ErrInvalidResourceURI, i.Common.URI())
}
return
}
func (i *Iptable) SetURI(uri string) (err error) {
if err = i.Common.SetURI(uri); err == nil {
err = i.setFieldsFromPath()
} }
return return
} }

View File

@ -26,52 +26,17 @@ const (
) )
func init() { func init() {
folio.DocumentRegistry.ResourceTypes.Register([]string{"route"}, func(u *url.URL) data.Resource { folio.DocumentRegistry.ResourceTypes.Register([]string{"route"}, func(u *url.URL) (n data.Resource) {
n := NewNetworkRoute() n = NewNetworkRoute()
if u != nil {
if err := folio.CastParsedURI(u).ConstructResource(n); err != nil {
panic(err)
}
}
return n return n
}) })
} }
/*
ROUTE := NODE_SPEC [ INFO_SPEC ]
NODE_SPEC := [ TYPE ] PREFIX [ tos TOS ]
[ table TABLE_ID ] [ proto RTPROTO ]
[ scope SCOPE ] [ metric METRIC ]
[ ttl-propagate { enabled | disabled } ]
INFO_SPEC := { NH | nhid ID } OPTIONS FLAGS [ nexthop NH ]...
NH := [ encap ENCAPTYPE ENCAPHDR ] [ via [ FAMILY ] ADDRESS ]
[ dev STRING ] [ weight NUMBER ] NHFLAGS
FAMILY := [ inet | inet6 | mpls | bridge | link ]
OPTIONS := FLAGS [ mtu NUMBER ] [ advmss NUMBER ] [ as [ to ] ADDRESS ]
[ rtt TIME ] [ rttvar TIME ] [ reordering NUMBER ]
[ window NUMBER ] [ cwnd NUMBER ] [ initcwnd NUMBER ]
[ ssthresh NUMBER ] [ realms REALM ] [ src ADDRESS ]
[ rto_min TIME ] [ hoplimit NUMBER ] [ initrwnd NUMBER ]
[ features FEATURES ] [ quickack BOOL ] [ congctl NAME ]
[ pref PREF ] [ expires TIME ] [ fastopen_no_cookie BOOL ]
NHFLAGS := [ onlink | pervasive ]
PREF := [ low | medium | high ]
TIME := NUMBER[s|ms]
BOOL := [1|0]
FEATURES := ecn
ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | rpl | ioam6 ]
ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL | IOAM6HDR ]
SEG6HDR := [ mode SEGMODE ] segs ADDR1,ADDRi,ADDRn [hmac HMACKEYID] [cleanup]
SEGMODE := [ encap | inline ]
SEG6LOCAL := action ACTION [ OPTIONS ] [ count ]
ACTION := { End | End.X | End.T | End.DX2 | End.DX6 | End.DX4 |
End.DT6 | End.DT4 | End.DT46 | End.B6 | End.B6.Encaps |
End.BM | End.S | End.AS | End.AM | End.BPF }
OPTIONS := OPTION [ OPTIONS ]
OPTION := { srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV |
table TABLEID | vrftable TABLEID | endpoint PROGNAME }
IOAM6HDR := trace prealloc type IOAM6_TRACE_TYPE ns IOAM6_NAMESPACE size IOAM6_TRACE_SIZE
ROUTE_GET_FLAGS := [ fibmatch ]
*/
type NetworkRouteType string type NetworkRouteType string
const ( const (
@ -115,25 +80,24 @@ const (
// Manage the state of network routes // Manage the state of network routes
type NetworkRoute struct { type NetworkRoute struct {
*Common `json:",inline" yaml:",inline"` *Common `json:",inline" yaml:",inline"`
stater machine.Stater `json:"-" yaml:"-"` stater machine.Stater `json:"-" yaml:"-"`
Id string Id string
To string `json:"to" yaml:"to"` To string `json:"to" yaml:"to"`
Interface string `json:"interface" yaml:"interface"` Interface string `json:"interface" yaml:"interface"`
Gateway string `json:"gateway" yaml:"gateway"` Gateway string `json:"gateway" yaml:"gateway"`
Metric uint `json:"metric" yaml:"metric"` Metric uint `json:"metric" yaml:"metric"`
Rtid NetworkRouteTableId `json:"rtid" yaml:"rtid"` Rtid NetworkRouteTableId `json:"rtid" yaml:"rtid"`
RouteType NetworkRouteType `json:"routetype" yaml:"routetype"` RouteType NetworkRouteType `json:"routetype" yaml:"routetype"`
Scope NetworkRouteScope `json:"scope" yaml:"scope"` Scope NetworkRouteScope `json:"scope" yaml:"scope"`
Proto NetworkRouteProto `json:"proto" yaml:"proto"` Proto NetworkRouteProto `json:"proto" yaml:"proto"`
CreateCommand *command.Command `yaml:"-" json:"-"` CreateCommand *command.Command `yaml:"-" json:"-"`
ReadCommand *command.Command `yaml:"-" json:"-"` ReadCommand *command.Command `yaml:"-" json:"-"`
UpdateCommand *command.Command `yaml:"-" json:"-"` UpdateCommand *command.Command `yaml:"-" json:"-"`
DeleteCommand *command.Command `yaml:"-" json:"-"` DeleteCommand *command.Command `yaml:"-" json:"-"`
config data.ConfigurationValueGetter Resources data.ResourceMapper `json:"-" yaml:"-"`
Resources data.ResourceMapper `json:"-" yaml:"-"`
} }
func NewNetworkRoute() *NetworkRoute { func NewNetworkRoute() *NetworkRoute {
@ -144,6 +108,13 @@ func NewNetworkRoute() *NetworkRoute {
return n return n
} }
func (n *NetworkRoute) Init(u data.URIParser) (err error) {
if u == nil {
u = folio.URI(n.URI()).Parse()
}
return n.SetParsedURI(u)
}
func (n *NetworkRoute) NormalizePath() error { func (n *NetworkRoute) NormalizePath() error {
return nil return nil
} }
@ -217,14 +188,6 @@ func (n *NetworkRoute) URI() string {
return fmt.Sprintf("route://%s", n.Id) return fmt.Sprintf("route://%s", n.Id)
} }
func (n *NetworkRoute) SetURI(uri string) error {
return nil
}
func (n *NetworkRoute) UseConfig(config data.ConfigurationValueGetter) {
n.config = config
}
func (n *NetworkRoute) Validate() error { func (n *NetworkRoute) Validate() error {
return fmt.Errorf("failed") return fmt.Errorf("failed")
} }

View File

@ -23,6 +23,9 @@ import (
"decl/internal/tempdir" "decl/internal/tempdir"
) )
const (
PackageTypeName TypeName = "package"
)
var ( var (
PackageTempDir tempdir.Path = "jx_package_resource" PackageTempDir tempdir.Path = "jx_package_resource"
) )
@ -50,7 +53,8 @@ var SupportedPackageTypes []PackageType = []PackageType{PackageTypeApk, PackageT
var SystemPackageType PackageType = FindSystemPackageType() var SystemPackageType PackageType = FindSystemPackageType()
type Package struct { type Package struct {
stater machine.Stater `yaml:"-" json:"-"` *Common `yaml:",inline" json:",inline"`
stater machine.Stater `yaml:"-" json:"-"`
Source string `yaml:"source,omitempty" json:"source,omitempty"` Source string `yaml:"source,omitempty" json:"source,omitempty"`
Name string `yaml:"name" json:"name"` Name string `yaml:"name" json:"name"`
Required string `json:"required,omitempty" yaml:"required,omitempty"` Required string `json:"required,omitempty" yaml:"required,omitempty"`
@ -62,18 +66,12 @@ type Package struct {
ReadCommand *command.Command `yaml:"-" json:"-"` ReadCommand *command.Command `yaml:"-" json:"-"`
UpdateCommand *command.Command `yaml:"-" json:"-"` UpdateCommand *command.Command `yaml:"-" json:"-"`
DeleteCommand *command.Command `yaml:"-" json:"-"` DeleteCommand *command.Command `yaml:"-" json:"-"`
// state attributes
State string `yaml:"state,omitempty" json:"state,omitempty"`
config data.ConfigurationValueGetter
Resources data.ResourceMapper `yaml:"-" json:"-"` Resources data.ResourceMapper `yaml:"-" json:"-"`
} }
func init() { func init() {
folio.DocumentRegistry.ResourceTypes.Register([]string{"package", string(PackageTypeApk), string(PackageTypeApt), string(PackageTypeDeb), string(PackageTypeDnf), string(PackageTypeRpm), string(PackageTypePip), string(PackageTypeYum)}, func(u *url.URL) data.Resource { folio.DocumentRegistry.ResourceTypes.Register([]string{"package", string(PackageTypeApk), string(PackageTypeApt), string(PackageTypeDeb), string(PackageTypeDnf), string(PackageTypeRpm), string(PackageTypePip), string(PackageTypeYum)}, func(u *url.URL) data.Resource {
p := NewPackage() return ConstructNewPackage(u)
e := p.SetParsedURI(u)
slog.Info("PackageFactory SetParsedURI()", "error", e)
return p
}) })
} }
@ -88,7 +86,41 @@ func FindSystemPackageType() PackageType {
} }
func NewPackage() *Package { func NewPackage() *Package {
return &Package{ PackageType: SystemPackageType } return &Package{
Common: NewCommon(PackageTypeName, true),
PackageType: SystemPackageType,
}
}
func ConstructNewPackage(uri *url.URL) (p *Package) {
p = NewPackage()
if uri != nil {
if err := folio.CastParsedURI(uri).ConstructResource(p); err != nil {
panic(err)
}
}
return
}
func (p *Package) Init(u data.URIParser) error {
if u == nil {
u = folio.URI(p.URI()).Parse()
}
uri := u.URL()
p.Name = filepath.Join(uri.Hostname(), uri.Path)
p.Version = uri.Query().Get("version")
if p.Version == "" {
p.Version = "latest"
}
indicatedPackageType := PackageType(uri.Query().Get("type"))
if indicatedPackageType.Validate() != nil {
p.PackageType = SystemPackageType
}
p.CreateCommand, p.ReadCommand, p.UpdateCommand, p.DeleteCommand = p.PackageType.NewCRUD()
return p.SetParsedURI(u)
} }
func (p *Package) SetResourceMapper(resources data.ResourceMapper) { func (p *Package) SetResourceMapper(resources data.ResourceMapper) {
@ -97,11 +129,11 @@ func (p *Package) SetResourceMapper(resources data.ResourceMapper) {
func (p *Package) Clone() data.Resource { func (p *Package) Clone() data.Resource {
newp := &Package { newp := &Package {
Common: p.Common.Clone(),
Name: p.Name, Name: p.Name,
Required: p.Required, Required: p.Required,
Version: p.Version, Version: p.Version,
PackageType: p.PackageType, PackageType: p.PackageType,
State: p.State,
} }
newp.CreateCommand, newp.ReadCommand, newp.UpdateCommand, newp.DeleteCommand = newp.PackageType.NewCRUD() newp.CreateCommand, newp.ReadCommand, newp.UpdateCommand, newp.DeleteCommand = newp.PackageType.NewCRUD()
return newp return newp
@ -135,11 +167,11 @@ func (p *Package) Notify(m *machine.EventMessage) {
if triggerErr := p.StateMachine().Trigger("state_read"); triggerErr == nil { if triggerErr := p.StateMachine().Trigger("state_read"); triggerErr == nil {
return return
} else { } else {
p.State = "absent" p.Common.State = "absent"
panic(triggerErr) panic(triggerErr)
} }
} else { } else {
p.State = "absent" p.Common.State = "absent"
if ! errors.Is(readErr, ErrResourceStateAbsent) { if ! errors.Is(readErr, ErrResourceStateAbsent) {
panic(readErr) panic(readErr)
} }
@ -164,17 +196,17 @@ func (p *Package) Notify(m *machine.EventMessage) {
if triggerErr := p.StateMachine().Trigger("deleted"); triggerErr == nil { if triggerErr := p.StateMachine().Trigger("deleted"); triggerErr == nil {
return return
} else { } else {
p.State = "present" p.Common.State = "present"
panic(triggerErr) panic(triggerErr)
} }
} else { } else {
p.State = "present" p.Common.State = "present"
panic(deleteErr) panic(deleteErr)
} }
case "absent": case "absent":
p.State = "absent" p.Common.State = "absent"
case "present", "created", "updated", "read": case "present", "created", "updated", "read":
p.State = "present" p.Common.State = "present"
} }
case machine.EXITSTATEEVENT: case machine.EXITSTATEEVENT:
} }
@ -192,36 +224,25 @@ func (p *Package) URI() string {
} }
func (p *Package) SetURI(uri string) error { func (p *Package) SetParsedURI(uri data.URIParser) (err error) {
resourceUri, e := url.Parse(uri) u := uri.URL()
if e == nil { if u.Scheme == "package" {
e = p.SetParsedURI(resourceUri) p.Name = filepath.Join(u.Hostname(), u.Path)
} p.Version = u.Query().Get("version")
return e
}
func (p *Package) SetParsedURI(uri *url.URL) (err error) {
if uri.Scheme == "package" {
p.Name = filepath.Join(uri.Hostname(), uri.Path)
p.Version = uri.Query().Get("version")
if p.Version == "" { if p.Version == "" {
p.Version = "latest" p.Version = "latest"
} }
indicatedPackageType := PackageType(uri.Query().Get("type")) indicatedPackageType := PackageType(u.Query().Get("type"))
if indicatedPackageType.Validate() != nil { if indicatedPackageType.Validate() != nil {
p.PackageType = SystemPackageType p.PackageType = SystemPackageType
} }
} else { } else {
err = fmt.Errorf("%w: %s is not a package resource ", ErrInvalidResourceURI, uri.String()) err = fmt.Errorf("%w: %s is not a package resource ", ErrInvalidResourceURI, u.String())
} }
p.CreateCommand, p.ReadCommand, p.UpdateCommand, p.DeleteCommand = p.PackageType.NewCRUD() p.CreateCommand, p.ReadCommand, p.UpdateCommand, p.DeleteCommand = p.PackageType.NewCRUD()
return return
} }
func (p *Package) UseConfig(config data.ConfigurationValueGetter) {
p.config = config
}
func (p *Package) JSON() ([]byte, error) { func (p *Package) JSON() ([]byte, error) {
return json.Marshal(p) return json.Marshal(p)
} }
@ -331,6 +352,9 @@ func (p *Package) LoadDecl(yamlResourceDeclaration string) error {
func (p *Package) Type() string { return "package" } func (p *Package) Type() string { return "package" }
func (p *Package) Read(ctx context.Context) (resourceYaml []byte, err error) { func (p *Package) Read(ctx context.Context) (resourceYaml []byte, err error) {
if p.Version == "latest" {
p.Version = ""
}
if p.ReadCommand.Exists() { if p.ReadCommand.Exists() {
var out []byte var out []byte
out, err = p.ReadCommand.Execute(p) out, err = p.ReadCommand.Execute(p)
@ -495,10 +519,10 @@ func NewApkReadCommand() *command.Command {
if packageName == p.Name { if packageName == p.Name {
p.Name = packageName p.Name = packageName
p.Version = packageVersion p.Version = packageVersion
p.State = "present" p.Common.State = "present"
} else { } else {
slog.Info("NewApkReadCommand().Extrctor() mismatch", "name", p.Name, "parsed", packageName) slog.Info("NewApkReadCommand().Extrctor() mismatch", "name", p.Name, "parsed", packageName)
p.State = "absent" p.Common.State = "absent"
} }
return nil return nil
} }
@ -554,7 +578,7 @@ func NewApkReadPackagesCommand() *command.Command {
packageName := strings.Join(packageFields[0:numberOfFields - 2], "-") packageName := strings.Join(packageFields[0:numberOfFields - 2], "-")
packageVersion := strings.Join(packageFields[numberOfFields - 2:numberOfFields - 1], "-") packageVersion := strings.Join(packageFields[numberOfFields - 2:numberOfFields - 1], "-")
p.Name = packageName p.Name = packageName
p.State = "present" p.Common.State = "present"
p.Version = packageVersion p.Version = packageVersion
} }
} }
@ -595,16 +619,16 @@ func NewAptReadCommand() *command.Command {
switch key { switch key {
case "Package": case "Package":
if value != p.Name { if value != p.Name {
p.State = "absent" p.Common.State = "absent"
return nil return nil
} }
case "Status": case "Status":
statusFields := strings.SplitN(value, " ", 3) statusFields := strings.SplitN(value, " ", 3)
if len(statusFields) > 1 { if len(statusFields) > 1 {
if statusFields[2] == "installed" { if statusFields[2] == "installed" {
p.State = "present" p.Common.State = "present"
} else { } else {
p.State = "absent" p.Common.State = "absent"
} }
} }
case "Version": case "Version":
@ -671,7 +695,7 @@ func NewAptReadPackagesCommand() *command.Command {
packageName := packageFields[0] packageName := packageFields[0]
packageVersion := installedPackage[1] packageVersion := installedPackage[1]
p.Name = packageName p.Name = packageName
p.State = "present" p.Common.State = "present"
p.Version = packageVersion p.Version = packageVersion
p.PackageType = PackageTypeApt p.PackageType = PackageTypeApt
} }
@ -711,16 +735,16 @@ func NewDebReadCommand() *command.Command {
switch key { switch key {
case "Package": case "Package":
if value != p.Name { if value != p.Name {
p.State = "absent" p.Common.State = "absent"
return nil return nil
} }
case "Status": case "Status":
statusFields := strings.SplitN(value, " ", 3) statusFields := strings.SplitN(value, " ", 3)
if len(statusFields) > 1 { if len(statusFields) > 1 {
if statusFields[2] == "installed" { if statusFields[2] == "installed" {
p.State = "present" p.Common.State = "present"
} else { } else {
p.State = "absent" p.Common.State = "absent"
} }
} }
case "Version": case "Version":
@ -787,7 +811,7 @@ func NewDebReadPackagesCommand() *command.Command {
p.Version = packageVersionFields[0] p.Version = packageVersionFields[0]
} }
p.Name = packageName p.Name = packageName
p.State = "present" p.Common.State = "present"
p.PackageType = PackageTypeDeb p.PackageType = PackageTypeDeb
lineIndex++ lineIndex++
} }
@ -833,7 +857,7 @@ func NewDnfReadCommand() *command.Command {
packageName := strings.TrimSpace(strings.Join(packageNameField[0:lenName - 1], ".")) packageName := strings.TrimSpace(strings.Join(packageNameField[0:lenName - 1], "."))
if packageName == p.Name { if packageName == p.Name {
p.State = "present" p.Common.State = "present"
packageVersionField := strings.Split(fields[1], ":") packageVersionField := strings.Split(fields[1], ":")
if len(packageVersionField) > 1 { if len(packageVersionField) > 1 {
//packageEpoch := strings.TrimSpace(packageVersionField[0]) //packageEpoch := strings.TrimSpace(packageVersionField[0])
@ -846,7 +870,7 @@ func NewDnfReadCommand() *command.Command {
slog.Info("DnfReadCommaond.Extract()", "package", packageName, "package", p) slog.Info("DnfReadCommaond.Extract()", "package", packageName, "package", p)
} }
} }
p.State = "absent" p.Common.State = "absent"
slog.Info("Extract()", "package", p) slog.Info("Extract()", "package", p)
return nil return nil
} }
@ -907,7 +931,7 @@ func NewDnfReadPackagesCommand() *command.Command {
p.Version = packageVersionFields[0] p.Version = packageVersionFields[0]
} }
p.Name = packageName p.Name = packageName
p.State = "present" p.Common.State = "present"
p.PackageType = PackageTypeDnf p.PackageType = PackageTypeDnf
lineIndex++ lineIndex++
} }
@ -949,13 +973,13 @@ func NewRpmReadCommand() *command.Command {
packageVersion := strings.Join(packageFields[numberOfFields - 2:numberOfFields - 1], "-") packageVersion := strings.Join(packageFields[numberOfFields - 2:numberOfFields - 1], "-")
slog.Info("Package[RPM].Extract()", "name", packageName, "version", packageVersion, "package", p) slog.Info("Package[RPM].Extract()", "name", packageName, "version", packageVersion, "package", p)
if packageName == p.Name { if packageName == p.Name {
p.State = "present" p.Common.State = "present"
p.Version = packageVersion p.Version = packageVersion
return nil return nil
} }
} }
} }
p.State = "absent" p.Common.State = "absent"
slog.Info("Extract()", "package", p) slog.Info("Extract()", "package", p)
return nil return nil
} }
@ -1013,13 +1037,13 @@ func NewPipReadCommand() *command.Command {
packageName := packageFields[0] packageName := packageFields[0]
packageVersion := packageFields[1] packageVersion := packageFields[1]
if packageName == p.Name { if packageName == p.Name {
p.State = "present" p.Common.State = "present"
p.Version = packageVersion p.Version = packageVersion
return nil return nil
} }
} }
} }
p.State = "absent" p.Common.State = "absent"
slog.Info("Extract()", "package", p) slog.Info("Extract()", "package", p)
return nil return nil
} }
@ -1083,7 +1107,7 @@ func NewYumReadCommand() *command.Command {
//packageArch := strings.TrimSpace(packageNameField[1]) //packageArch := strings.TrimSpace(packageNameField[1])
if packageName == p.Name { if packageName == p.Name {
p.State = "present" p.Common.State = "present"
packageVersionField := strings.Split(fields[1], ":") packageVersionField := strings.Split(fields[1], ":")
//packageEpoch := strings.TrimSpace(packageVersionField[0]) //packageEpoch := strings.TrimSpace(packageVersionField[0])
packageVersion := strings.TrimSpace(packageVersionField[1]) packageVersion := strings.TrimSpace(packageVersionField[1])
@ -1091,7 +1115,7 @@ func NewYumReadCommand() *command.Command {
return nil return nil
} }
} }
p.State = "absent" p.Common.State = "absent"
slog.Info("Extract()", "package", p) slog.Info("Extract()", "package", p)
return nil return nil
} }

View File

@ -4,19 +4,12 @@ package resource
import ( import (
"context" "context"
_ "encoding/json"
"fmt" "fmt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
_ "gopkg.in/yaml.v3"
_ "io"
"log/slog" "log/slog"
_ "net/http"
_ "net/http/httptest"
_ "net/url"
_ "os"
_ "strings"
"testing" "testing"
"decl/internal/command" "decl/internal/command"
"decl/internal/folio"
) )
func TestNewPackageResource(t *testing.T) { func TestNewPackageResource(t *testing.T) {
@ -354,6 +347,8 @@ type: %s
assert.NotNil(t, p) assert.NotNil(t, p)
loadErr := p.LoadDecl(decl) loadErr := p.LoadDecl(decl)
assert.Nil(t, loadErr) assert.Nil(t, loadErr)
assert.Nil(t, p.Init(nil))
p.ReadCommand = SystemPackageType.NewReadCommand() p.ReadCommand = SystemPackageType.NewReadCommand()
/* /*
p.ReadCommand.Executor = func(value any) ([]byte, error) { p.ReadCommand.Executor = func(value any) ([]byte, error) {
@ -363,6 +358,7 @@ type: %s
p.ResolveId(ctx) p.ResolveId(ctx)
yaml, readErr := p.Read(ctx) yaml, readErr := p.Read(ctx)
assert.ErrorIs(t, readErr, ErrResourceStateAbsent) assert.ErrorIs(t, readErr, ErrResourceStateAbsent)
slog.Info("TestReadPackageError", "package", p, "common", p.Common, "yaml", yaml)
assert.YAMLEq(t, expected, string(yaml)) assert.YAMLEq(t, expected, string(yaml))
slog.Info("read()", "yaml", yaml) slog.Info("read()", "yaml", yaml)
assert.Equal(t, "", p.Version) assert.Equal(t, "", p.Version)
@ -375,7 +371,9 @@ func TestCreatePackage(t *testing.T) {
func TestPackageSetURI(t *testing.T) { func TestPackageSetURI(t *testing.T) {
p := NewPackage() p := NewPackage()
assert.NotNil(t, p) assert.NotNil(t, p)
e := p.SetURI("package://" + "12345_key?type=apk")
uri := folio.URI("package://" + "12345_key?type=apk").Parse()
e := p.Init(uri)
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, "package", p.Type()) assert.Equal(t, "package", p.Type())
assert.Equal(t, "12345_key", p.Name) assert.Equal(t, "12345_key", p.Name)

View File

@ -44,11 +44,16 @@ var ErrPKIInvalidEncodingType error = errors.New("Invalid EncodingType")
var ErrPKIFailedDecodingPemBlock error = errors.New("Failed decoding pem block") var ErrPKIFailedDecodingPemBlock error = errors.New("Failed decoding pem block")
func init() { func init() {
ResourceTypes.Register([]string{"pki"}, func(u *url.URL) data.Resource { ResourceTypes.Register([]string{"pki"}, func(u *url.URL) (data.Resource) {
k := NewPKI() k := NewPKI()
ref := folio.ResourceReference(filepath.Join(u.Hostname(), u.Path)) if u != nil {
if len(ref) > 0 { if err := folio.CastParsedURI(u).ConstructResource(k); err != nil {
k.PrivateKeyRef = ref panic(err)
}
ref := folio.ResourceReference(filepath.Join(u.Hostname(), u.Path))
if len(ref) > 0 {
k.PrivateKeyRef = ref
}
} }
return k return k
}) })
@ -83,8 +88,6 @@ type PKI struct {
Bits int `json:"bits" yaml:"bits"` Bits int `json:"bits" yaml:"bits"`
EncodingType EncodingType `json:"type" yaml:"type"` EncodingType EncodingType `json:"type" yaml:"type"`
//State string `json:"state,omitempty" yaml:"state,omitempty"`
config data.ConfigurationValueGetter
Resources data.ResourceMapper `json:"-" yaml:"-"` Resources data.ResourceMapper `json:"-" yaml:"-"`
} }
@ -102,7 +105,6 @@ func (k *PKI) Clone() data.Resource {
return &PKI { return &PKI {
Common: k.Common.Clone(), Common: k.Common.Clone(),
EncodingType: k.EncodingType, EncodingType: k.EncodingType,
//State: k.State,
} }
} }
@ -185,22 +187,6 @@ func (k *PKI) URI() string {
return fmt.Sprintf("pki://%s", filepath.Join(u.Hostname(), u.RequestURI())) return fmt.Sprintf("pki://%s", filepath.Join(u.Hostname(), u.RequestURI()))
} }
func (k *PKI) SetURI(uri string) error {
resourceUri, e := url.Parse(uri)
if e == nil {
if resourceUri.Scheme == "pki" {
k.PrivateKeyRef = folio.ResourceReference(fmt.Sprintf("pki://%s", filepath.Join(resourceUri.Hostname(), resourceUri.Path)))
} else {
e = fmt.Errorf("%w: %s is not a cert", ErrInvalidResourceURI, uri)
}
}
return e
}
func (k *PKI) UseConfig(config data.ConfigurationValueGetter) {
k.config = config
}
func (k *PKI) Validate() error { func (k *PKI) Validate() error {
return fmt.Errorf("failed") return fmt.Errorf("failed")
} }
@ -494,62 +480,6 @@ func (k *PKI) Update(ctx context.Context) (err error) {
func (k *PKI) Type() string { return "pki" } func (k *PKI) Type() string { return "pki" }
/*
func (k *PKI) UnmarshalValue(value *PKIContent) error {
d.Type = value.Type
d.Transition = value.Transition
d.Config = value.Config
newResource, resourceErr := ResourceTypes.New(fmt.Sprintf("%s://", value.Type))
if resourceErr != nil {
slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr)
return resourceErr
}
d.Attributes = newResource
return nil
}
func (k *PKI) UnmarshalYAML(value *yaml.Node) error {
t := &DeclarationType{}
if unmarshalResourceTypeErr := value.Decode(t); unmarshalResourceTypeErr != nil {
return unmarshalResourceTypeErr
}
if err := d.UnmarshalValue(t); err != nil {
return err
}
resourceAttrs := struct {
Attributes yaml.Node `json:"attributes"`
}{}
if unmarshalAttributesErr := value.Decode(&resourceAttrs); unmarshalAttributesErr != nil {
return unmarshalAttributesErr
}
if unmarshalResourceErr := resourceAttrs.Attributes.Decode(d.Attributes); unmarshalResourceErr != nil {
return unmarshalResourceErr
}
return nil
}
func (d *Declaration) UnmarshalJSON(data []byte) error {
t := &DeclarationType{}
if unmarshalResourceTypeErr := json.Unmarshal(data, t); unmarshalResourceTypeErr != nil {
return unmarshalResourceTypeErr
}
if err := d.UnmarshalValue(t); err != nil {
return err
}
resourceAttrs := struct {
Attributes Resource `json:"attributes"`
}{Attributes: d.Attributes}
if unmarshalAttributesErr := json.Unmarshal(data, &resourceAttrs); unmarshalAttributesErr != nil {
return unmarshalAttributesErr
}
return nil
}
*/
func (t *EncodingType) UnmarshalValue(value string) error { func (t *EncodingType) UnmarshalValue(value string) error {
switch value { switch value {
case string(EncodingTypePem): case string(EncodingTypePem):

View File

@ -4,12 +4,12 @@ package resource
import ( import (
"context" "context"
_ "encoding/json" _ "encoding/json"
"fmt" "fmt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
_ "gopkg.in/yaml.v3" _ "gopkg.in/yaml.v3"
"io" "io"
_ "log" _ "log"
_ "os" _ "os"
"decl/internal/transport" "decl/internal/transport"
"decl/internal/ext" "decl/internal/ext"
@ -19,7 +19,6 @@ _ "os"
"strings" "strings"
"testing" "testing"
"path/filepath" "path/filepath"
"net/url"
) )
type TestResourceMapper func(key string) (data.Declaration, bool) type TestResourceMapper func(key string) (data.Declaration, bool)
@ -59,7 +58,7 @@ func (s StringContentReadWriter) LoadString(docData string, f codec.Format) (err
func (s StringContentReadWriter) LoadDecl(yamlResourceDeclaration string) error { return nil } func (s StringContentReadWriter) LoadDecl(yamlResourceDeclaration string) error { return nil }
func (s StringContentReadWriter) ResolveId(ctx context.Context) (string) { return "" } func (s StringContentReadWriter) ResolveId(ctx context.Context) (string) { return "" }
func (s StringContentReadWriter) SetURI(uri string) (error) { return nil } func (s StringContentReadWriter) SetURI(uri string) (error) { return nil }
func (s StringContentReadWriter) SetParsedURI(uri *url.URL) (error) { return nil } func (s StringContentReadWriter) SetParsedURI(uri data.URIParser) (error) { return nil }
func (s StringContentReadWriter) URI() (string) { return "" } func (s StringContentReadWriter) URI() (string) { return "" }
func (s StringContentReadWriter) Validate() (error) { return nil } func (s StringContentReadWriter) Validate() (error) { return nil }
func (s StringContentReadWriter) ResourceType() data.TypeName { return "" } func (s StringContentReadWriter) ResourceType() data.TypeName { return "" }

View File

@ -20,67 +20,6 @@ var (
) )
type ResourceReference string type ResourceReference string
/*
type ResourceSelector func(r *Declaration) bool
type Resource interface {
Type() string
StateMachine() machine.Stater
URI() string
SetURI(string) error
UseConfig(config ConfigurationValueGetter)
ResolveId(context.Context) string
ResourceLoader
StateTransformer
ResourceReader
ResourceValidator
Clone() Resource
SetResourceMapper(resources ResourceMapper)
}
type ContentReader interface {
ContentReaderStream() (*transport.Reader, error)
}
type ContentWriter interface {
ContentWriterStream() (*transport.Writer, error)
}
type ContentReadWriter interface {
ContentReader
ContentWriter
}
type ResourceValidator interface {
Validate() error
}
type ResourceCreator interface {
Create(context.Context) error
}
type ResourceReader interface {
Read(context.Context) ([]byte, error)
}
type ResourceUpdater interface {
Update(context.Context) error
}
type ResourceDeleter interface {
Delete(context.Context) error
}
type ResourceDecoder struct {
}
type ResourceCrudder struct {
ResourceCreator
ResourceReader
ResourceUpdater
ResourceDeleter
}
*/
func NewResource(uri string) data.Resource { func NewResource(uri string) data.Resource {
r, e := ResourceTypes.New(uri) r, e := ResourceTypes.New(uri)
@ -90,50 +29,17 @@ func NewResource(uri string) data.Resource {
return nil return nil
} }
/* func ResourceConstructor(res data.Resource, uri data.URIParser) (err error) {
if uri != nil {
// Return a Content ReadWriter for the resource referred to. return uri.ConstructResource(res)
func (r ResourceReference) Lookup(look data.ResourceMapper) data.ContentReadWriter { } else {
slog.Info("ResourceReference.Lookup()", "resourcereference", r, "resourcemapper", look) if ri, ok := res.(data.ResourceInitializer); ok {
if look != nil { return ri.Init(uri)
if v,ok := look.Get(string(r)); ok {
return v.(data.ContentReadWriter)
} }
} }
return r return
} }
func (r ResourceReference) Dereference(look data.ResourceMapper) data.Resource {
slog.Info("ResourceReference.Dereference()", "resourcereference", r, "resourcemapper", look)
if look != nil {
if v,ok := look.Get(string(r)); ok {
return v.(*Declaration).Attributes
}
}
return nil
}
func (r ResourceReference) Parse() *url.URL {
u, e := url.Parse(string(r))
if e == nil {
return u
}
return nil
}
func (r ResourceReference) Exists() bool {
return transport.ExistsURI(string(r))
}
func (r ResourceReference) ContentReaderStream() (*transport.Reader, error) {
return transport.NewReaderURI(string(r))
}
func (r ResourceReference) ContentWriterStream() (*transport.Writer, error) {
return transport.NewWriterURI(string(r))
}
*/
func StorageMachine(sub machine.Subscriber) machine.Stater { func StorageMachine(sub machine.Subscriber) machine.Stater {
// start_destroy -> absent -> start_create -> present -> start_destroy // start_destroy -> absent -> start_create -> present -> start_destroy
stater := machine.New("unknown") stater := machine.New("unknown")

View File

@ -58,6 +58,8 @@ func TestSchemaValidateJSON(t *testing.T) {
testFile := NewFile() testFile := NewFile()
e := testFile.LoadDecl(decl) e := testFile.LoadDecl(decl)
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, file, testFile.ResolveId(ctx))
fileApplyErr := testFile.Apply() fileApplyErr := testFile.Apply()
assert.Nil(t, fileApplyErr) assert.Nil(t, fileApplyErr)
@ -71,8 +73,10 @@ func TestSchemaValidateJSON(t *testing.T) {
assert.NotNil(t, f) assert.NotNil(t, f)
f.Path = file f.Path = file
assert.Nil(t, f.Init(nil))
r, e := f.Read(ctx) r, e := f.Read(ctx)
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, ProcessTestUserName, f.Owner) assert.Equal(t, ProcessTestUserName, f.Owner)
info, statErr := os.Stat(file) info, statErr := os.Stat(file)
@ -86,8 +90,8 @@ func TestSchemaValidateJSON(t *testing.T) {
} }
func TestSchemaValidateSchema(t *testing.T) { func TestSchemaValidateSchema(t *testing.T) {
s := NewSchema("document") s := NewSchema("document")
assert.NotNil(t, s) assert.NotNil(t, s)
assert.Nil(t, s.ValidateSchema()) assert.Nil(t, s.ValidateSchema())
} }

View File

@ -15,6 +15,7 @@ import (
"decl/internal/codec" "decl/internal/codec"
"decl/internal/data" "decl/internal/data"
"decl/internal/command" "decl/internal/command"
"decl/internal/folio"
"encoding/json" "encoding/json"
"strings" "strings"
"errors" "errors"
@ -64,8 +65,9 @@ type Service struct {
func init() { func init() {
ResourceTypes.Register([]string{"service"}, func(u *url.URL) data.Resource { ResourceTypes.Register([]string{"service"}, func(u *url.URL) data.Resource {
s := NewService() s := NewService()
s.Name = filepath.Join(u.Hostname(), u.Path) if err := folio.CastParsedURI(u).ConstructResource(s); err != nil {
s.CreateCommand, s.ReadCommand, s.UpdateCommand, s.DeleteCommand = s.ServiceManagerType.NewCRUD() panic(err)
}
return s return s
}) })
} }
@ -87,6 +89,17 @@ func NewService() (s *Service) {
return return
} }
func (s *Service) Init(u data.URIParser) (err error) {
if u == nil {
u = folio.URI(s.URI()).Parse()
}
uri := u.URL()
err = s.SetParsedURI(u)
s.Name = filepath.Join(uri.Hostname(), uri.Path)
s.CreateCommand, s.ReadCommand, s.UpdateCommand, s.DeleteCommand = s.ServiceManagerType.NewCRUD()
return
}
func (s *Service) NormalizePath() error { func (s *Service) NormalizePath() error {
return nil return nil
} }
@ -147,7 +160,7 @@ func (s *Service) URI() string {
return fmt.Sprintf("service://%s", s.Name) return fmt.Sprintf("service://%s", s.Name)
} }
func (s *Service) SetParsedURI(uri *url.URL) (err error) { func (s *Service) SetParsedURI(uri data.URIParser) (err error) {
if err = s.Common.SetParsedURI(uri); err == nil { if err = s.Common.SetParsedURI(uri); err == nil {
err = s.setFieldsFromPath() err = s.setFieldsFromPath()
} }
@ -158,14 +171,7 @@ func (s *Service) setFieldsFromPath() (err error) {
if len(s.Common.Path) > 0 { if len(s.Common.Path) > 0 {
s.Name = s.Common.Path s.Name = s.Common.Path
} else { } else {
err = fmt.Errorf("%w: %s is not an iptable rule", ErrInvalidResourceURI, s.Common.Uri) err = fmt.Errorf("%w: %s is not an iptable rule", ErrInvalidResourceURI, s.Common.URI())
}
return
}
func (s *Service) SetURI(uri string) (err error) {
if err = s.Common.SetURI(uri); err == nil {
err = s.setFieldsFromPath()
} }
return return
} }

View File

@ -4,11 +4,10 @@ package resource
import ( import (
"context" "context"
_ "decl/tests/mocks"
"decl/internal/command" "decl/internal/command"
_ "fmt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
"decl/internal/folio"
) )
func TestNewServiceResource(t *testing.T) { func TestNewServiceResource(t *testing.T) {
@ -18,7 +17,9 @@ func TestNewServiceResource(t *testing.T) {
func TestUriServiceResource(t *testing.T) { func TestUriServiceResource(t *testing.T) {
c := NewService() c := NewService()
assert.Nil(t, c.SetURI("service://ssh")) uri := folio.URI("service://ssh").Parse()
assert.Nil(t, c.Init(uri))
assert.Equal(t, "ssh", c.Name) assert.Equal(t, "ssh", c.Name)
} }

View File

@ -72,12 +72,14 @@ func NewUser() *User {
} }
func init() { func init() {
folio.DocumentRegistry.ResourceTypes.Register([]string{"user"}, func(u *url.URL) data.Resource { folio.DocumentRegistry.ResourceTypes.Register([]string{"user"}, func(u *url.URL) (user data.Resource) {
user := NewUser() user = NewUser()
user.Name = u.Hostname() if u != nil {
user.UID = LookupUIDString(u.Hostname()) if err := folio.CastParsedURI(u).ConstructResource(user); err != nil {
user.CreateCommand, user.ReadCommand, user.UpdateCommand, user.DeleteCommand = user.UserType.NewCRUD() panic(err)
return user }
}
return
}) })
} }
@ -91,6 +93,18 @@ func FindSystemUserType() UserType {
return UserTypeShadow return UserTypeShadow
} }
func (u *User) Init(uri data.URIParser) error {
var initializeURI folio.URI
if uri == nil {
uri = folio.URI(u.URI()).Parse()
}
initializeURI.SetURL(uri.URL())
u.Name = uri.URL().Hostname()
u.UID = LookupUIDString(uri.URL().Hostname())
u.CreateCommand, u.ReadCommand, u.UpdateCommand, u.DeleteCommand = u.UserType.NewCRUD()
return u.SetURI(string(initializeURI))
}
func (u *User) NormalizePath() error { func (u *User) NormalizePath() error {
return nil return nil
} }
@ -189,26 +203,10 @@ func (u *User) Notify(m *machine.EventMessage) {
} }
} }
func (u *User) SetURI(uri string) error {
resourceUri, e := url.Parse(uri)
if e == nil {
if resourceUri.Scheme == "user" {
u.Name = resourceUri.Hostname()
} else {
e = fmt.Errorf("%w: %s is not a user", ErrInvalidResourceURI, uri)
}
}
return e
}
func (u *User) URI() string { func (u *User) URI() string {
return fmt.Sprintf("user://%s", u.Name) return fmt.Sprintf("user://%s", u.Name)
} }
func (u *User) UseConfig(config data.ConfigurationValueGetter) {
u.config = config
}
func (u *User) ResolveId(ctx context.Context) string { func (u *User) ResolveId(ctx context.Context) string {
if u.config != nil { if u.config != nil {
if configUser, configUserErr := u.config.GetValue("user"); configUserErr == nil && u.Name == "self" { if configUser, configUserErr := u.config.GetValue("user"); configUserErr == nil && u.Name == "self" {

View File

@ -105,6 +105,19 @@ func (t *Types[Product]) NewFromParsedURI(u *url.URL) (result Product, err error
return return
} }
// Returns an uninitialized resource for the given type
func (t *Types[Product]) NewFromType(typename string) (result Product, err error) {
if r, ok := t.registry[typename]; ok {
if result = r(nil); result != any(nil) {
return result, nil
} else {
return result, fmt.Errorf("%w: factory failed creating %s", ErrInvalidProduct, typename)
}
}
err = fmt.Errorf("%w: %s", ErrUnknownType, typename)
return
}
func (t *Types[Product]) Has(typename string) bool { func (t *Types[Product]) Has(typename string) bool {
if _, ok := t.registry[typename]; ok { if _, ok := t.registry[typename]; ok {
return true return true

View File

@ -61,3 +61,17 @@ func TestTypeName(t *testing.T) {
assert.Equal(t, "file", string(fTypeName.Name)) assert.Equal(t, "file", string(fTypeName.Name))
} }
*/ */
func TestNewFromType(t *testing.T) {
testTypes := New[string]()
assert.NotNil(t, testTypes)
testTypes.Register([]string{"foo"}, func(*url.URL) string { return "test" })
r, e := testTypes.NewFromType("foo")
assert.Nil(t, e)
assert.Equal(t, "test", r)
failr, faile := testTypes.NewFromType("bar")
assert.ErrorIs(t, faile, ErrUnknownType)
assert.Equal(t, "", failr)
}