diff --git a/cli_test.go b/cli_test.go index 89f7636..7a24759 100644 --- a/cli_test.go +++ b/cli_test.go @@ -197,7 +197,7 @@ attributes: gecos: "foo user" `) - rw.Write(userdecl) + assert.Nil(t, rw.Write(userdecl)) })) defer server.Close() diff --git a/internal/folio/events.go b/internal/folio/events.go new file mode 100644 index 0000000..db232b5 --- /dev/null +++ b/internal/folio/events.go @@ -0,0 +1,31 @@ +// Copyright 2024 Matthew Rich . All rights reserved. + +package folio + +import ( + "errors" +_ "gitea.rosskeen.house/pylon/luaruntime" +_ "fmt" +) + +type EventHandler string + +type Events map[EventType]EventHandler + +var ( + ErrInvalidHandler error = errors.New("Invalid event handler") +) + +func NewEvents() *Events { + e := make(Events) + return &e +} + +func (e *Events) Set(t EventType, h EventHandler) (err error) { + if err = t.Validate(); err == nil { + (*e)[t] = h + } + return +} + + diff --git a/internal/folio/events_test.go b/internal/folio/events_test.go new file mode 100644 index 0000000..c7cb3ff --- /dev/null +++ b/internal/folio/events_test.go @@ -0,0 +1,22 @@ +// Copyright 2024 Matthew Rich . All rights reserved. + + +package folio + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + + +func TestNewEvent(t *testing.T) { + var et EventType + events := NewEvents() + assert.NotNil(t, events) + + et.Set(EventTypeLoad) + assert.Nil(t, events.Set(et, EventHandler(` +print('hello world') +`))) + +} diff --git a/internal/folio/eventtype.go b/internal/folio/eventtype.go new file mode 100644 index 0000000..71d187b --- /dev/null +++ b/internal/folio/eventtype.go @@ -0,0 +1,39 @@ +// Copyright 2024 Matthew Rich . All rights reserved. + +package folio + +import ( + "errors" + "fmt" +) + +type EventType string + +const ( + EventTypeLoad EventType = "load" + EventTypeCreate EventType = "create" + EventTypeRead EventType = "read" + EventTypeUpdate EventType = "update" + EventTypeDelete EventType = "delete" + EventTypeError EventType = "error" +) + +var ( + ErrUnknownEventType error = errors.New("Unknown EventType") +) + +func (e EventType) Validate() (err error) { + switch e { + case EventTypeLoad, EventTypeCreate, EventTypeRead, EventTypeUpdate, EventTypeDelete, EventTypeError: + default: + return fmt.Errorf("%w: %s", ErrUnknownEventType, e) + } + return +} + +func (e *EventType) Set(v EventType) (err error) { + if err = v.Validate(); err == nil { + (*e) = v + } + return +} diff --git a/internal/folio/eventtype_test.go b/internal/folio/eventtype_test.go new file mode 100644 index 0000000..0d6809e --- /dev/null +++ b/internal/folio/eventtype_test.go @@ -0,0 +1,29 @@ +// Copyright 2024 Matthew Rich . All rights reserved. + + +package folio + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + + +func TestEventType(t *testing.T) { + for _, v := range []struct{ et EventType; expected error } { + { et: EventType("load"), expected: nil }, + { et: EventType("create"), expected: nil }, + { et: EventType("read"), expected: nil }, + { et: EventType("update"), expected: nil }, + { et: EventType("delete"), expected: nil }, + { et: EventType("error"), expected: nil }, + { et: EventType("foo"), expected: ErrUnknownEventType }, + } { + assert.ErrorIs(t, v.et.Validate(), v.expected) + } +} + +func TestEventTypeSet(t *testing.T) { + var et EventType + assert.Nil(t, et.Set(EventTypeLoad)) +} diff --git a/internal/resource/command.go b/internal/resource/command.go deleted file mode 100644 index deea835..0000000 --- a/internal/resource/command.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2024 Matthew Rich . All rights reserved. - -package resource - -import ( -_ "context" - "encoding/json" - "fmt" - "gopkg.in/yaml.v3" - "io" - "log/slog" -_ "net/url" -_ "os" - "os/exec" - "strings" - "text/template" - "decl/internal/codec" -) - -type CommandExecutor func(value any) ([]byte, error) -type CommandExtractAttributes func(output []byte, target any) error - -type CommandArg string - -type Command struct { - Path string `json:"path" yaml:"path"` - Args []CommandArg `json:"args" yaml:"args"` - Split bool `json:"split" yaml:"split"` - Executor CommandExecutor `json:"-" yaml:"-"` - Extractor CommandExtractAttributes `json:"-" yaml:"-"` -} - -func NewCommand() *Command { - c := &Command{ Split: true } - c.Executor = func(value any) ([]byte, error) { - args, err := c.Template(value) - if err != nil { - return nil, err - } - cmd := exec.Command(c.Path, args...) - - slog.Info("execute() - cmd", "path", c.Path, "args", args) - output, stdoutPipeErr := cmd.StdoutPipe() - if stdoutPipeErr != nil { - return nil, stdoutPipeErr - } - - stderr, pipeErr := cmd.StderrPipe() - if pipeErr != nil { - return nil, pipeErr - } - - if startErr := cmd.Start(); startErr != nil { - return nil, startErr - } - - slog.Info("execute() - start", "cmd", cmd) - stdOutOutput, _ := io.ReadAll(output) - stdErrOutput, _ := io.ReadAll(stderr) - - slog.Info("execute() - io", "stdout", string(stdOutOutput), "stderr", string(stdErrOutput)) - waitErr := cmd.Wait() - - slog.Info("execute()", "path", c.Path, "args", args, "output", string(stdOutOutput), "error", string(stdErrOutput)) - - if len(stdErrOutput) > 0 { - return stdOutOutput, fmt.Errorf(string(stdErrOutput)) - } - return stdOutOutput, waitErr - } - return c -} - -func (c *Command) Load(r io.Reader) error { - return codec.NewYAMLDecoder(r).Decode(c) -} - -func (c *Command) LoadDecl(yamlResourceDeclaration string) error { - return codec.NewYAMLStringDecoder(yamlResourceDeclaration).Decode(c) -} - -func (c *Command) Template(value any) ([]string, error) { - var args []string = make([]string, 0, len(c.Args) * 2) - for i, arg := range c.Args { - var commandLineArg strings.Builder - err := template.Must(template.New(fmt.Sprintf("arg%d", i)).Parse(string(arg))).Execute(&commandLineArg, value) - if err != nil { - return nil, err - } - if commandLineArg.Len() > 0 { - var splitArg []string - if c.Split { - splitArg = strings.Split(commandLineArg.String(), " ") - } else { - splitArg = []string{commandLineArg.String()} - } - slog.Info("Template()", "split", splitArg, "len", len(splitArg)) - args = append(args, splitArg...) - } - } - - slog.Info("Template()", "Args", c.Args, "lencargs", len(c.Args), "args", args, "lenargs", len(args), "value", value) - return args, nil -} - -func (c *Command) Execute(value any) ([]byte, error) { - return c.Executor(value) -} - -func (c *CommandArg) UnmarshalValue(value string) error { - *c = CommandArg(value) - return nil -} - -func (c *CommandArg) UnmarshalJSON(data []byte) error { - var s string - if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil { - return unmarshalRouteTypeErr - } - return c.UnmarshalValue(s) -} - -func (c *CommandArg) UnmarshalYAML(value *yaml.Node) error { - var s string - if err := value.Decode(&s); err != nil { - return err - } - return c.UnmarshalValue(s) -} diff --git a/internal/resource/command_test.go b/internal/resource/command_test.go deleted file mode 100644 index 3ac36b5..0000000 --- a/internal/resource/command_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2024 Matthew Rich . All rights reserved. - - -package resource - -import ( -_ "fmt" - "github.com/stretchr/testify/assert" -_ "os" -_ "strings" - "testing" -) - -func TestNewCommand(t *testing.T) { - c := NewCommand() - assert.NotNil(t, c) -} - -func TestCommandLoad(t *testing.T) { - c := NewCommand() - assert.NotNil(t, c) - - decl := ` -path: find -args: -- "{{ .Path }}" -` - - assert.Nil(t, c.LoadDecl(decl)) - assert.Equal(t, "find", c.Path) -} - -func TestCommandTemplate(t *testing.T) { - c := NewCommand() - assert.NotNil(t, c) - - decl := ` -path: find -args: -- "{{ .Path }}" -` - - assert.Nil(t, c.LoadDecl(decl)) - assert.Equal(t, "find", c.Path) - assert.Equal(t, 1, len(c.Args)) - - f := NewFile() - f.Path = "./" - args, templateErr := c.Template(f) - assert.Nil(t, templateErr) - assert.Equal(t, 1, len(args)) - - assert.Equal(t, "./", string(args[0])) - - out, err := c.Execute(f) - assert.Nil(t, err) - assert.Greater(t, len(out), 0) -} diff --git a/internal/resource/iptables.go b/internal/resource/iptables.go index 06b9369..a8ecfba 100644 --- a/internal/resource/iptables.go +++ b/internal/resource/iptables.go @@ -146,7 +146,6 @@ type Iptable struct { UpdateCommand *command.Command `yaml:"-" json:"-"` DeleteCommand *command.Command `yaml:"-" json:"-"` - config data.ConfigurationValueGetter Resources data.ResourceMapper `yaml:"-" json:"-"` } @@ -287,7 +286,7 @@ func (i *Iptable) URI() string { func (i *Iptable) SetParsedURI(uri *url.URL) (err error) { if err = i.Common.SetParsedURI(uri); err == nil { - i.setFieldsFromPath() + err = i.setFieldsFromPath() } return } diff --git a/internal/resource/network_route.go b/internal/resource/network_route.go index f105e6e..ca4f017 100644 --- a/internal/resource/network_route.go +++ b/internal/resource/network_route.go @@ -18,6 +18,7 @@ _ "strconv" "decl/internal/codec" "decl/internal/data" "decl/internal/folio" + "decl/internal/command" ) const ( @@ -126,10 +127,10 @@ type NetworkRoute struct { Scope NetworkRouteScope `json:"scope" yaml:"scope"` Proto NetworkRouteProto `json:"proto" yaml:"proto"` - CreateCommand *Command `yaml:"-" json:"-"` - ReadCommand *Command `yaml:"-" json:"-"` - UpdateCommand *Command `yaml:"-" json:"-"` - DeleteCommand *Command `yaml:"-" json:"-"` + CreateCommand *command.Command `yaml:"-" json:"-"` + ReadCommand *command.Command `yaml:"-" json:"-"` + UpdateCommand *command.Command `yaml:"-" json:"-"` + DeleteCommand *command.Command `yaml:"-" json:"-"` config data.ConfigurationValueGetter Resources data.ResourceMapper `json:"-" yaml:"-"` @@ -478,41 +479,41 @@ func (n *NetworkRoute) UnmarshalJSON(data []byte) error { } -func (n *NetworkRoute) NewCRUD() (create *Command, read *Command, update *Command, del *Command) { +func (n *NetworkRoute) NewCRUD() (create *command.Command, read *command.Command, update *command.Command, del *command.Command) { return NewNetworkRouteCreateCommand(), NewNetworkRouteReadCommand(), NewNetworkRouteUpdateCommand(), NewNetworkRouteDeleteCommand() } -func NewNetworkRouteCreateCommand() *Command { - c := NewCommand() +func NewNetworkRouteCreateCommand() *command.Command { + c := command.NewCommand() c.Path = "ip" - c.Args = []CommandArg{ - CommandArg("route"), - CommandArg("add"), - CommandArg("{{ if .To }}to {{ .To }}{{ end }}"), - CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"), - CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"), - CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"), - CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"), - CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"), - CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"), - CommandArg("{{ if .Metric }}metric {{ .Metric }}{{ end }}"), + c.Args = []command.CommandArg{ + command.CommandArg("route"), + command.CommandArg("add"), + command.CommandArg("{{ if .To }}to {{ .To }}{{ end }}"), + command.CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"), + command.CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"), + command.CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"), + command.CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"), + command.CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"), + command.CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"), + command.CommandArg("{{ if .Metric }}metric {{ .Metric }}{{ end }}"), } return c } -func NewNetworkRouteReadCommand() *Command { - c := NewCommand() +func NewNetworkRouteReadCommand() *command.Command { + c := command.NewCommand() c.Path = "ip" - c.Args = []CommandArg{ - CommandArg("route"), - CommandArg("show"), - CommandArg("{{ if .To }}to {{ .To }}{{ end }}"), - CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"), - CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"), - CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"), - CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"), - CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"), - CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"), + c.Args = []command.CommandArg{ + command.CommandArg("route"), + command.CommandArg("show"), + command.CommandArg("{{ if .To }}to {{ .To }}{{ end }}"), + command.CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"), + command.CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"), + command.CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"), + command.CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"), + command.CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"), + command.CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"), } c.Extractor = func(out []byte, target any) error { n := target.(*NetworkRoute) @@ -535,16 +536,16 @@ func NewNetworkRouteReadCommand() *Command { return c } -func NewNetworkRouteUpdateCommand() *Command { - c := NewCommand() +func NewNetworkRouteUpdateCommand() *command.Command { + c := command.NewCommand() c.Path = "ip" - c.Args = []CommandArg{ - CommandArg("del"), - CommandArg("{{ .Name }}"), + c.Args = []command.CommandArg{ + command.CommandArg("del"), + command.CommandArg("{{ .Name }}"), } return c } -func NewNetworkRouteDeleteCommand() *Command { +func NewNetworkRouteDeleteCommand() *command.Command { return nil } diff --git a/internal/resource/user_test.go b/internal/resource/user_test.go index f120c06..52da42d 100644 --- a/internal/resource/user_test.go +++ b/internal/resource/user_test.go @@ -3,15 +3,15 @@ package resource import ( "context" - _ "encoding/json" +_ "encoding/json" "fmt" "github.com/stretchr/testify/assert" - _ "io" - _ "net/http" - _ "net/http/httptest" - _ "net/url" - _ "os" - _ "strings" +_ "io" +_ "net/http" +_ "net/http/httptest" +_ "net/url" +_ "os" +_ "strings" "testing" "decl/internal/data" ) @@ -107,7 +107,7 @@ func TestUserSecondaryGroups(t *testing.T) { "root", "wheel", } - u.ReadGroups() + assert.Nil(t, u.ReadGroups()) for _, groupName := range u.Groups { groupCounts[groupName]++