// 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" "net/url" "runtime/debug" ) 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"` } 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"` // runtime luaruntime.LuaRunner document *Document configBlock data.Block ResourceTypes data.TypesRegistry[data.Resource] `json:"-" yaml:"-"` } func NewDeclaration() *Declaration { return &Declaration{ ResourceTypes: DocumentRegistry.ResourceTypes } } func NewDeclarationFromDocument(document *Document) *Declaration { return &Declaration{ document: document, ResourceTypes: document.Types() } } 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, } } 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 *url.URL) (err error) { if u == nil { d.Attributes, err = d.ResourceTypes.NewFromParsedURI(&url.URL{ Scheme: string(d.Type) }) } else { if d.Attributes, err = d.ResourceTypes.NewFromParsedURI(u); err == nil { err = d.Attributes.SetParsedURI(u) } } return } func (d *Declaration) NewResource(uri *string) (err error) { 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.New(fmt.Sprintf("%s://", d.Type)) } else { if d.Attributes, err = d.ResourceTypes.New(*uri); err == nil { err = d.Attributes.SetURI(*uri) } } 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, "stacktrace", string(debug.Stack())) 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()", "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 "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: fallthrough 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, result) } } 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) } } 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.SetURI(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) SetParsedURI(uri *url.URL) (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 newResource, resourceErr := d.ResourceTypes.New(fmt.Sprintf("%s://", value.Type)) if resourceErr != nil { slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr) return resourceErr } d.Attributes = newResource return nil } func (d *Declaration) UnmarshalYAML(value *yaml.Node) 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 } return nil } func (d *Declaration) UnmarshalJSON(jsonData []byte) 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 } return nil } /* func (d *Declaration) MarshalJSON() ([]byte, error) { buf := new(bytes.Buffer) buf.WriteByte('"') buf.WriteString("value")) buf.WriteByte('"') return buf.Bytes(), nil } */ /* func (d *Declaration) MarshalYAML() (any, error) { return d, nil } */ /* 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)) } } } */