add resource event handler types
Some checks failed
Lint / golangci-lint (push) Failing after 9m55s
Declarative Tests / test (push) Failing after 17s

This commit is contained in:
Matthew Rich 2024-10-09 23:03:52 +00:00
parent d88b67ea2f
commit a73acb8b93
10 changed files with 168 additions and 234 deletions

View File

@ -197,7 +197,7 @@ attributes:
gecos: "foo user" gecos: "foo user"
`) `)
rw.Write(userdecl) assert.Nil(t, rw.Write(userdecl))
})) }))
defer server.Close() defer server.Close()

31
internal/folio/events.go Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. 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
}

View File

@ -0,0 +1,22 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. 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')
`)))
}

View File

@ -0,0 +1,39 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. 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
}

View File

@ -0,0 +1,29 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. 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))
}

View File

@ -1,129 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. 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)
}

View File

@ -1,58 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. 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)
}

View File

@ -146,7 +146,6 @@ type Iptable struct {
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 `yaml:"-" json:"-"` Resources data.ResourceMapper `yaml:"-" json:"-"`
} }
@ -287,7 +286,7 @@ func (i *Iptable) URI() string {
func (i *Iptable) SetParsedURI(uri *url.URL) (err error) { func (i *Iptable) SetParsedURI(uri *url.URL) (err error) {
if err = i.Common.SetParsedURI(uri); err == nil { if err = i.Common.SetParsedURI(uri); err == nil {
i.setFieldsFromPath() err = i.setFieldsFromPath()
} }
return return
} }

View File

@ -18,6 +18,7 @@ _ "strconv"
"decl/internal/codec" "decl/internal/codec"
"decl/internal/data" "decl/internal/data"
"decl/internal/folio" "decl/internal/folio"
"decl/internal/command"
) )
const ( const (
@ -126,10 +127,10 @@ type NetworkRoute struct {
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 `yaml:"-" json:"-"` CreateCommand *command.Command `yaml:"-" json:"-"`
ReadCommand *Command `yaml:"-" json:"-"` ReadCommand *command.Command `yaml:"-" json:"-"`
UpdateCommand *Command `yaml:"-" json:"-"` UpdateCommand *command.Command `yaml:"-" json:"-"`
DeleteCommand *Command `yaml:"-" json:"-"` DeleteCommand *command.Command `yaml:"-" json:"-"`
config data.ConfigurationValueGetter config data.ConfigurationValueGetter
Resources data.ResourceMapper `json:"-" yaml:"-"` 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() return NewNetworkRouteCreateCommand(), NewNetworkRouteReadCommand(), NewNetworkRouteUpdateCommand(), NewNetworkRouteDeleteCommand()
} }
func NewNetworkRouteCreateCommand() *Command { func NewNetworkRouteCreateCommand() *command.Command {
c := NewCommand() c := command.NewCommand()
c.Path = "ip" c.Path = "ip"
c.Args = []CommandArg{ c.Args = []command.CommandArg{
CommandArg("route"), command.CommandArg("route"),
CommandArg("add"), command.CommandArg("add"),
CommandArg("{{ if .To }}to {{ .To }}{{ end }}"), command.CommandArg("{{ if .To }}to {{ .To }}{{ end }}"),
CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"), command.CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"),
CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"), command.CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"),
CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"), command.CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"),
CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"), command.CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"),
CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"), command.CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"),
CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"), command.CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"),
CommandArg("{{ if .Metric }}metric {{ .Metric }}{{ end }}"), command.CommandArg("{{ if .Metric }}metric {{ .Metric }}{{ end }}"),
} }
return c return c
} }
func NewNetworkRouteReadCommand() *Command { func NewNetworkRouteReadCommand() *command.Command {
c := NewCommand() c := command.NewCommand()
c.Path = "ip" c.Path = "ip"
c.Args = []CommandArg{ c.Args = []command.CommandArg{
CommandArg("route"), command.CommandArg("route"),
CommandArg("show"), command.CommandArg("show"),
CommandArg("{{ if .To }}to {{ .To }}{{ end }}"), command.CommandArg("{{ if .To }}to {{ .To }}{{ end }}"),
CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"), command.CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"),
CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"), command.CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"),
CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"), command.CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"),
CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"), command.CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"),
CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"), command.CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"),
CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"), command.CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"),
} }
c.Extractor = func(out []byte, target any) error { c.Extractor = func(out []byte, target any) error {
n := target.(*NetworkRoute) n := target.(*NetworkRoute)
@ -535,16 +536,16 @@ func NewNetworkRouteReadCommand() *Command {
return c return c
} }
func NewNetworkRouteUpdateCommand() *Command { func NewNetworkRouteUpdateCommand() *command.Command {
c := NewCommand() c := command.NewCommand()
c.Path = "ip" c.Path = "ip"
c.Args = []CommandArg{ c.Args = []command.CommandArg{
CommandArg("del"), command.CommandArg("del"),
CommandArg("{{ .Name }}"), command.CommandArg("{{ .Name }}"),
} }
return c return c
} }
func NewNetworkRouteDeleteCommand() *Command { func NewNetworkRouteDeleteCommand() *command.Command {
return nil return nil
} }

View File

@ -107,7 +107,7 @@ func TestUserSecondaryGroups(t *testing.T) {
"root", "root",
"wheel", "wheel",
} }
u.ReadGroups() assert.Nil(t, u.ReadGroups())
for _, groupName := range u.Groups { for _, groupName := range u.Groups {
groupCounts[groupName]++ groupCounts[groupName]++