// Copyright 2024 Matthew Rich . All rights reserved. package folio import ( _ "errors" "context" "encoding/json" "fmt" "io" "gopkg.in/yaml.v3" "log/slog" _ "gitea.rosskeen.house/rosskeen.house/machine" "gitea.rosskeen.house/pylon/luaruntime" "decl/internal/codec" "decl/internal/data" "decl/internal/schema" "errors" ) var ( ErrUnknownStateTransition error = errors.New("Unknown state transition") ) type ConfigName string type DeclarationType struct { Type TypeName `json:"type" yaml:"type"` Transition string `json:"transition,omitempty" yaml:"transition,omitempty"` Config ConfigName `json:"config,omitempty" yaml:"config,omitempty"` OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"` Error string `json:"error,omitempty" yaml:"error,omitempty"` Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"` On *Events `json:"on,omitempty" yaml:"on,omitempty"` } type Declaration struct { Type TypeName `json:"type" yaml:"type"` Transition string `json:"transition,omitempty" yaml:"transition,omitempty"` Attributes data.Resource `json:"attributes" yaml:"attributes"` Config ConfigName `json:"config,omitempty" yaml:"config,omitempty"` OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"` Error string `json:"error,omitempty" yaml:"error,omitempty"` Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"` On *Events `json:"on,omitempty" yaml:"on,omitempty"` runtime luaruntime.LuaRunner document *Document configBlock data.Block ResourceTypes data.TypesRegistry[data.Resource] `json:"-" yaml:"-"` } func NewDeclaration() *Declaration { return &Declaration{ ResourceTypes: DocumentRegistry.ResourceTypes, runtime: luaruntime.New() } } func NewDeclarationFromDocument(document *Document) *Declaration { return &Declaration{ document: document, ResourceTypes: document.Types() } } func (n *ConfigName) Exists() bool { return DocumentRegistry.ConfigNameMap.Has(string(*n)) } func (n *ConfigName) GetBlock() *Block { if v, ok := DocumentRegistry.ConfigNameMap.Get(string(*n)); ok { return v } return nil } func (d *Declaration) SetDocument(newDocument *Document) { slog.Info("Declaration.SetDocument()", "declaration", d) d.document = newDocument d.SetConfig(d.document.config) d.ResourceTypes = d.document.Types() d.Attributes.SetResourceMapper(d.document.uris) } func (d *Declaration) ResolveId(ctx context.Context) string { defer func() { if r := recover(); r != nil { slog.Info("Declaration.ResolveId() - panic", "recover", r, "state", d.Attributes.StateMachine()) if triggerErr := d.Attributes.StateMachine().Trigger("notexists"); triggerErr != nil { panic(triggerErr) } } }() slog.Info("Declaration.ResolveId()") id := d.Attributes.ResolveId(ctx) return id } func (d *Declaration) Clone() data.Declaration { return &Declaration { Type: d.Type, Transition: d.Transition, Attributes: d.Attributes.Clone(), //runtime: luaruntime.New(), Config: d.Config, Requires: d.Requires, On: d.On, } } func (d *Declaration) Load(docData []byte, f codec.Format) (err error) { err = f.StringDecoder(string(docData)).Decode(d) return } func (d *Declaration) LoadReader(r io.ReadCloser, f codec.Format) (err error) { err = f.Decoder(r).Decode(d) return } func (d *Declaration) LoadString(docData string, f codec.Format) (err error) { err = f.StringDecoder(docData).Decode(d) return } func (d *Declaration) ResourceType() data.TypeName { return data.TypeName(d.Type) } func (d *Declaration) URI() string { return d.Attributes.URI() } func (d *Declaration) JSON() ([]byte, error) { return json.Marshal(d) } func (d *Declaration) Validate() (err error) { var declarationJson []byte if declarationJson, err = d.JSON(); err == nil { s := schema.New(fmt.Sprintf("%s-declaration", d.Type), schemaFiles) err = s.Validate(string(declarationJson)) } return err } func (d *Declaration) NewResourceFromParsedURI(u data.URIParser) (err error) { if u == nil { d.Attributes, err = d.ResourceTypes.NewFromType(string(d.Type)) } else { parsed := u.URL() if d.Attributes, err = d.ResourceTypes.NewFromParsedURI(parsed); err == nil { err = d.Attributes.SetParsedURI(u) } } return } func (d *Declaration) NewResource(uri *string) (err error) { slog.Info("Declaration.NewResource()") if d.ResourceTypes == nil { panic(fmt.Errorf("Undefined type registry: unable to create new resource %s", *uri)) } if uri == nil { d.Attributes, err = d.ResourceTypes.NewFromType(string(d.Type)) } else { slog.Info("Declaration.NewResource()", "uri", *uri) parsedURI := URI(*uri).Parse() d.Attributes, err = d.ResourceTypes.NewFromParsedURI(parsedURI.URL()) } return } func (d *Declaration) Resource() data.Resource { return d.Attributes } func (d *Declaration) Apply(stateTransition string) (result error) { defer func() { if r := recover(); r != nil { slog.Info("Declaration.Apply()", "error", r, "resourceerror", d.Error) if d.Error != "" { result = fmt.Errorf("%s - %s", r, d.Error) } else { result = fmt.Errorf("%s", r) } } if result != nil { d.Error = result.Error() } }() if stateTransition == "" { stateTransition = d.Transition } stater := d.Attributes.StateMachine() slog.Info("Declaration.Apply()", "stateTransition", stateTransition, "machine", stater, "machine.state", stater.CurrentState(), "uri", d.Attributes.URI()) switch stateTransition { case "construct": if doc, ok := DocumentRegistry.DeclarationMap[d]; ok { d.SetDocument(doc) } case "stat": result = stater.Trigger("stat") case "read": result = stater.Trigger("read") case "delete", "absent": if stater.CurrentState() == "present" { result = stater.Trigger("delete") } case "update": if result = stater.Trigger("update"); result != nil { return result } result = stater.Trigger("read") default: return fmt.Errorf("%w: %s on %s", ErrUnknownStateTransition, stateTransition, d.Attributes.URI()) case "create", "present": if stater.CurrentState() == "absent" || stater.CurrentState() == "unknown" { if result = stater.Trigger("create"); result != nil { slog.Info("Declaration.Apply()", "trigger", "create", "state", stater.CurrentState(), "error", result, "declaration", d) return result } } result = stater.Trigger("read") currentState := stater.CurrentState() switch currentState { case "create", "present": default: return fmt.Errorf("Failed to create resource: %s - state: %s, err: %s", d.URI(), currentState, d.Error) } } return result } func (d *Declaration) SetConfig(configDoc data.Document) { slog.Info("Declaration.SetConfig()", "config", configDoc) if configDoc != nil { if configDoc.Has(string(d.Config)) { if v, ok := configDoc.Get(string(d.Config)); ok { d.configBlock = v.(data.Block) d.Attributes.UseConfig(d.configBlock) return } } } if v, ok := DocumentRegistry.ConfigNameMap.Get(string(d.Config)); ok { d.configBlock = v d.Attributes.UseConfig(d.configBlock) return } if d.Config != "" { // XXX panic(fmt.Sprintf("failed setting config: %s", d.Config)) } } func (d *Declaration) SetURI(uri string) (err error) { slog.Info("Declaration.SetURI()", "uri", uri, "declaration", d) if d.Attributes == nil { err = d.NewResource(&uri) } else { err = d.Attributes.SetParsedURI(URI(uri).Parse()) } if err != nil { return err } if d.Attributes == nil { return fmt.Errorf("%w: %s", ErrUnknownResourceType, uri) } d.Type = TypeName(d.Attributes.Type()) _, err = d.Attributes.Read(context.Background()) // fix context slog.Info("Declaration.SetURI() - read", "error", err) return } func (d *Declaration) SetParsedURI(uri data.URIParser) (err error) { slog.Info("Declaration.SetParsedURI()", "uri", uri, "declaration", d) if d.Attributes == nil { err = d.NewResourceFromParsedURI(uri) } else { err = d.Attributes.SetParsedURI(uri) } if err != nil { return err } if d.Attributes == nil { return fmt.Errorf("%w: %s", ErrUnknownResourceType, uri) } d.Type = TypeName(d.Attributes.Type()) _, err = d.Attributes.Read(context.Background()) // fix context slog.Info("Declaration.SetURI() - read", "error", err) return } func (d *Declaration) UnmarshalValue(value *DeclarationType) error { slog.Info("Declaration.UnmarshalValue", "declaration", d, "value", value, "addr", d) if d.ResourceTypes == nil { panic(fmt.Errorf("Undefined type registry: unable to create new resource %s", value.Type)) } d.Type = value.Type d.Transition = value.Transition d.Config = value.Config d.OnError = value.OnError d.Error = value.Error d.Requires = value.Requires 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) if resourceErr != nil { slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr) return resourceErr } d.Attributes = newResource 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 } func (d *Declaration) UnmarshalYAML(value *yaml.Node) (err error) { if d.ResourceTypes == nil { d.ResourceTypes = DocumentRegistry.ResourceTypes } 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 } 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) (err error) { if d.ResourceTypes == nil { d.ResourceTypes = DocumentRegistry.ResourceTypes } t := &DeclarationType{} if unmarshalResourceTypeErr := json.Unmarshal(jsonData, t); unmarshalResourceTypeErr != nil { return unmarshalResourceTypeErr } if err := d.UnmarshalValue(t); err != nil { return err } resourceAttrs := struct { Attributes data.Resource `json:"attributes"` }{Attributes: d.Attributes} if unmarshalAttributesErr := json.Unmarshal(jsonData, &resourceAttrs); unmarshalAttributesErr != nil { return unmarshalAttributesErr } if i, ok := d.Attributes.(data.ResourceInitializer); ok { err = i.Init(nil) } else { err = fmt.Errorf("failed to execute init") } return } /* func (l *LuaWorker) Receive(m message.Envelope) { s := m.Sender() switch b := m.Body().(type) { case *message.Error: // case *worker.Terminated: case *CodeExecute: stackSize := l.runtime.Api().GetTop() if e := l.runtime.LoadScriptFromString(b.Code); e != nil { s.Send(message.New(&message.Error{ E: e }, l)) } returnsCount := l.runtime.Api().GetTop() - stackSize if len(b.Entrypoint) == 0 { if ! l.runtime.Api().IsNil(-1) { if returnsCount == 0 { s.Send(message.New(&CodeResult{ Result: []interface{}{ 0 } }, l)) } else { lr,le := l.runtime.CopyReturnValuesFromCall(int(returnsCount)) if le != nil { s.Send(message.New(&message.Error{ E: le }, l)) } else { s.Send(message.New(&CodeResult{ Result: lr }, l)) } } } } else { r,ce := l.runtime.CallFunction(b.Entrypoint, b.Args) if ce != nil { s.Send(message.New(&message.Error{ E: ce }, l)) } s.Send(message.New(&CodeResult{ Result: r }, l)) } } } */