add resource event handler types
This commit is contained in:
parent
d88b67ea2f
commit
a73acb8b93
@ -197,7 +197,7 @@ attributes:
|
||||
gecos: "foo user"
|
||||
`)
|
||||
|
||||
rw.Write(userdecl)
|
||||
assert.Nil(t, rw.Write(userdecl))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
31
internal/folio/events.go
Normal file
31
internal/folio/events.go
Normal 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
|
||||
}
|
||||
|
||||
|
22
internal/folio/events_test.go
Normal file
22
internal/folio/events_test.go
Normal 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')
|
||||
`)))
|
||||
|
||||
}
|
39
internal/folio/eventtype.go
Normal file
39
internal/folio/eventtype.go
Normal 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
|
||||
}
|
29
internal/folio/eventtype_test.go
Normal file
29
internal/folio/eventtype_test.go
Normal 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))
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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]++
|
||||
|
Loading…
Reference in New Issue
Block a user