2024-03-25 20:27:30 +00:00
|
|
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
|
|
|
|
|
|
package resource
|
|
|
|
|
|
|
|
import (
|
2024-03-25 20:31:06 +00:00
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"gopkg.in/yaml.v3"
|
2024-07-22 22:03:22 +00:00
|
|
|
"encoding/json"
|
2024-03-25 20:31:06 +00:00
|
|
|
_ "log"
|
|
|
|
"net/url"
|
|
|
|
_ "os"
|
|
|
|
_ "os/exec"
|
2024-04-21 06:13:17 +00:00
|
|
|
_ "strings"
|
|
|
|
"io"
|
2024-05-06 00:48:54 +00:00
|
|
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
2024-05-14 18:26:05 +00:00
|
|
|
"decl/internal/codec"
|
2024-07-22 22:03:22 +00:00
|
|
|
"decl/internal/command"
|
2024-09-19 08:05:29 +00:00
|
|
|
"decl/internal/data"
|
|
|
|
"decl/internal/folio"
|
|
|
|
"errors"
|
|
|
|
"log/slog"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
ExecTypeName TypeName = "exec"
|
2024-03-25 20:27:30 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Exec struct {
|
2024-09-19 08:05:29 +00:00
|
|
|
*Common `yaml:",inline" json:",inline"`
|
2024-05-24 05:11:51 +00:00
|
|
|
stater machine.Stater `yaml:"-" json:"-"`
|
2024-07-22 22:03:22 +00:00
|
|
|
Id string `yaml:"id,omitempty" json:"id,omitempty"`
|
|
|
|
CreateTemplate *command.Command `yaml:"create,omitempty" json:"create,omitempty"`
|
|
|
|
ReadTemplate *command.Command `yaml:"read,omitempty" json:"read,omitempty"`
|
|
|
|
UpdateTemplate *command.Command `yaml:"update,omitempty" json:"update,omitempty"`
|
|
|
|
DeleteTemplate *command.Command `yaml:"delete,omitempty" json:"delete,omitempty"`
|
2024-03-25 20:27:30 +00:00
|
|
|
|
2024-09-19 08:05:29 +00:00
|
|
|
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
2024-03-25 20:27:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
2024-10-16 17:26:42 +00:00
|
|
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"exec"}, func(u *url.URL) (res data.Resource) {
|
2024-03-25 20:31:06 +00:00
|
|
|
x := NewExec()
|
2024-10-16 17:26:42 +00:00
|
|
|
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
|
2024-03-25 20:31:06 +00:00
|
|
|
})
|
2024-03-25 20:27:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewExec() *Exec {
|
2024-09-28 05:04:15 +00:00
|
|
|
e := &Exec{ Common: NewCommon(ExecTypeName, true) }
|
|
|
|
return e
|
2024-03-25 20:27:30 +00:00
|
|
|
}
|
|
|
|
|
2024-09-19 08:05:29 +00:00
|
|
|
func (x *Exec) SetResourceMapper(resources data.ResourceMapper) {
|
2024-07-17 08:34:57 +00:00
|
|
|
x.Resources = resources
|
|
|
|
}
|
|
|
|
|
2024-09-19 08:05:29 +00:00
|
|
|
func (x *Exec) Clone() data.Resource {
|
2024-04-19 07:52:10 +00:00
|
|
|
return &Exec {
|
2024-10-16 17:26:42 +00:00
|
|
|
Common: x.Common.Clone(),
|
2024-04-19 07:52:10 +00:00
|
|
|
Id: x.Id,
|
|
|
|
CreateTemplate: x.CreateTemplate,
|
|
|
|
ReadTemplate: x.ReadTemplate,
|
|
|
|
UpdateTemplate: x.UpdateTemplate,
|
|
|
|
DeleteTemplate: x.DeleteTemplate,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-06 00:48:54 +00:00
|
|
|
func (x *Exec) StateMachine() machine.Stater {
|
2024-05-24 05:11:51 +00:00
|
|
|
if x.stater == nil {
|
|
|
|
x.stater = ProcessMachine(x)
|
|
|
|
}
|
|
|
|
return x.stater
|
2024-05-06 00:48:54 +00:00
|
|
|
}
|
|
|
|
|
2024-03-25 20:27:30 +00:00
|
|
|
func (x *Exec) URI() string {
|
2024-03-25 20:31:06 +00:00
|
|
|
return fmt.Sprintf("exec://%s", x.Id)
|
2024-03-25 20:27:30 +00:00
|
|
|
}
|
|
|
|
|
2024-10-16 17:26:42 +00:00
|
|
|
func (x *Exec) Init(u data.URIParser) (err error) {
|
|
|
|
if u == nil {
|
|
|
|
u = folio.URI(x.URI()).Parse()
|
|
|
|
}
|
|
|
|
err = x.SetParsedURI(u)
|
2024-09-19 08:05:29 +00:00
|
|
|
x.Id = x.Common.Path
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-10-16 17:26:42 +00:00
|
|
|
func (x *Exec) SetParsedURI(uri data.URIParser) (err error) {
|
2024-09-19 08:05:29 +00:00
|
|
|
err = x.Common.SetParsedURI(uri)
|
|
|
|
x.Id = x.Common.Path
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-07-22 22:03:22 +00:00
|
|
|
func (x *Exec) Validate() (err error) {
|
|
|
|
var execJson []byte
|
|
|
|
if execJson, err = x.JSON(); err == nil {
|
|
|
|
s := NewSchema(x.Type())
|
|
|
|
err = s.Validate(string(execJson))
|
|
|
|
}
|
|
|
|
return err
|
2024-04-09 19:30:05 +00:00
|
|
|
}
|
|
|
|
|
2024-03-25 20:27:30 +00:00
|
|
|
func (x *Exec) Apply() error {
|
2024-03-25 20:31:06 +00:00
|
|
|
return nil
|
2024-03-25 20:27:30 +00:00
|
|
|
}
|
|
|
|
|
2024-05-24 05:11:51 +00:00
|
|
|
func (x *Exec) Notify(m *machine.EventMessage) {
|
|
|
|
ctx := context.Background()
|
2024-09-19 08:05:29 +00:00
|
|
|
slog.Info("Notify()", "exec", x, "m", m)
|
2024-05-24 05:11:51 +00:00
|
|
|
switch m.On {
|
|
|
|
case machine.ENTERSTATEEVENT:
|
|
|
|
switch m.Dest {
|
2024-09-19 08:05:29 +00:00
|
|
|
case "start_read":
|
|
|
|
if _, readErr := x.Read(ctx); readErr == nil {
|
|
|
|
if triggerErr := x.StateMachine().Trigger("state_read"); triggerErr == nil {
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
x.State = "absent"
|
|
|
|
panic(triggerErr)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
x.State = "absent"
|
|
|
|
if ! errors.Is(readErr, ErrResourceStateAbsent) {
|
|
|
|
panic(readErr)
|
|
|
|
}
|
|
|
|
}
|
2024-05-24 05:11:51 +00:00
|
|
|
case "start_create":
|
|
|
|
if e := x.Create(ctx); e == nil {
|
|
|
|
if triggerErr := x.stater.Trigger("created"); triggerErr == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2024-09-19 08:05:29 +00:00
|
|
|
case "start_delete":
|
|
|
|
if deleteErr := x.Delete(ctx); deleteErr == nil {
|
|
|
|
if triggerErr := x.StateMachine().Trigger("deleted"); triggerErr == nil {
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
x.State = "present"
|
|
|
|
panic(triggerErr)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
x.State = "present"
|
|
|
|
panic(deleteErr)
|
|
|
|
}
|
|
|
|
case "absent":
|
|
|
|
x.State = "absent"
|
|
|
|
case "present", "created", "read":
|
|
|
|
x.State = "present"
|
2024-05-24 05:11:51 +00:00
|
|
|
}
|
|
|
|
case machine.EXITSTATEEVENT:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-19 08:05:29 +00:00
|
|
|
func (x *Exec) Load(docData []byte, f codec.Format) (err error) {
|
|
|
|
err = f.StringDecoder(string(docData)).Decode(x)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (x *Exec) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
|
|
|
|
err = f.Decoder(r).Decode(x)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (x *Exec) LoadString(docData string, f codec.Format) (err error) {
|
|
|
|
err = f.StringDecoder(docData).Decode(x)
|
|
|
|
return
|
2024-04-21 06:13:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (x *Exec) LoadDecl(yamlResourceDeclaration string) error {
|
2024-09-19 08:05:29 +00:00
|
|
|
return x.LoadString(yamlResourceDeclaration, codec.FormatYaml)
|
2024-03-25 20:27:30 +00:00
|
|
|
}
|
|
|
|
|
2024-07-22 22:03:22 +00:00
|
|
|
func (x *Exec) JSON() ([]byte, error) {
|
|
|
|
return json.Marshal(x)
|
|
|
|
}
|
|
|
|
|
2024-03-25 20:27:30 +00:00
|
|
|
func (x *Exec) Type() string { return "exec" }
|
|
|
|
|
2024-07-22 22:03:22 +00:00
|
|
|
func (x *Exec) Create(ctx context.Context) (err error) {
|
|
|
|
x.CreateTemplate.Defaults()
|
2024-07-22 22:26:09 +00:00
|
|
|
_, err = x.CreateTemplate.Execute(x)
|
2024-09-19 08:05:29 +00:00
|
|
|
slog.Info("Exec.Create()", "resource", x, "error", err)
|
2024-07-22 22:03:22 +00:00
|
|
|
return err
|
2024-05-24 05:11:51 +00:00
|
|
|
}
|
|
|
|
|
2024-03-25 20:27:30 +00:00
|
|
|
func (x *Exec) Read(ctx context.Context) ([]byte, error) {
|
2024-09-19 08:05:29 +00:00
|
|
|
if x.ReadTemplate != nil {
|
|
|
|
x.ReadTemplate.Defaults()
|
|
|
|
}
|
2024-03-25 20:31:06 +00:00
|
|
|
return yaml.Marshal(x)
|
2024-03-25 20:27:30 +00:00
|
|
|
}
|
2024-09-19 08:05:29 +00:00
|
|
|
func (x *Exec) Update(ctx context.Context) (err error) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (x *Exec) Delete(ctx context.Context) (err error) {
|
|
|
|
return
|
|
|
|
}
|