// Copyright 2024 Matthew Rich . All rights reserved. package resource import ( "encoding/json" "fmt" "gopkg.in/yaml.v3" "io" "log/slog" _ "net/url" "github.com/sters/yaml-diff/yamldiff" "strings" "decl/internal/codec" "decl/internal/types" "decl/internal/config" "context" ) type ResourceMap[Value any] map[string]Value func (rm ResourceMap[Value]) Get(key string) (any, bool) { v, ok := rm[key] return v, ok } type ResourceMapper interface { Get(key string) (any, bool) } type Document struct { uris ResourceMap[*Declaration] ResourceDecls []Declaration `json:"resources" yaml:"resources"` config *config.Document } func NewDocument() *Document { return &Document{ uris: make(ResourceMap[*Declaration]) } } func (d *Document) Types() *types.Types[Resource] { return ResourceTypes } func (d *Document) Filter(filter ResourceSelector) []*Declaration { resources := make([]*Declaration, 0, len(d.ResourceDecls)) for i := range d.ResourceDecls { filterResource := &d.ResourceDecls[i] if filter == nil || filter(filterResource) { resources = append(resources, &d.ResourceDecls[i]) } } return resources } func (d *Document) GetResource(uri string) *Declaration { if decl, ok := d.uris[uri]; ok { return decl } return nil } func (d *Document) Clone() *Document { clone := NewDocument() clone.config = d.config clone.ResourceDecls = make([]Declaration, len(d.ResourceDecls)) for i, res := range d.ResourceDecls { clone.ResourceDecls[i] = *res.Clone() clone.ResourceDecls[i].SetDocument(clone) clone.ResourceDecls[i].SetConfig(d.config) } return clone } func (d *Document) Load(r io.Reader) (err error) { c := codec.NewYAMLDecoder(r) err = c.Decode(d) slog.Info("Document.Load()", "error", err) if err == nil { for i := range d.ResourceDecls { d.ResourceDecls[i].SetDocument(d) } } return } func (d *Document) Validate() error { jsonDocument, jsonErr := d.JSON() slog.Info("document.Validate() json", "json", jsonDocument, "err", jsonErr) if jsonErr == nil { s := NewSchema("document") err := s.Validate(string(jsonDocument)) if err != nil { return err } /* for i := range d.ResourceDecls { if e := d.ResourceDecls[i].Resource().Validate(); e != nil { return fmt.Errorf("failed to validate resource %s; %w", d.ResourceDecls[i].Resource().URI(), e) } } */ } return nil } func (d *Document) SetConfig(config *config.Document) { d.config = config } func (d *Document) ConfigDoc() *config.Document { return d.config } func (d *Document) Resources() []Declaration { return d.ResourceDecls } func (d *Document) ResolveIds(ctx context.Context) { for i := range d.ResourceDecls { d.ResourceDecls[i].ResolveId(ctx) } } func (d *Document) Apply(state string) error { if d == nil { panic("Undefined Document") } slog.Info("Document.Apply()", "declarations", d, "override", state) var start, i int = 0, 0 if state == "delete" { start = len(d.ResourceDecls) - 1 } for { idx := i - start if idx < 0 { idx = - idx } slog.Info("Document.Apply() applying resource", "index", idx, "uri", d.ResourceDecls[idx].Resource().URI(), "resource", d.ResourceDecls[idx].Resource()) if state != "" { d.ResourceDecls[idx].Transition = state } d.ResourceDecls[idx].SetConfig(d.config) if e := d.ResourceDecls[idx].Apply(); e != nil { slog.Error("Document.Apply() error applying resource", "index", idx, "uri", d.ResourceDecls[idx].Resource().URI(), "resource", d.ResourceDecls[idx].Resource(), "error", e) return e } if i >= len(d.ResourceDecls) - 1 { break } i++ } return nil } func (d *Document) Generate(w io.Writer) error { e := codec.NewYAMLEncoder(w) err := e.Encode(d); if err == nil { return e.Close() } e.Close() return err } func (d *Document) MapResourceURI(uri string, declaration *Declaration) { d.uris[uri] = declaration } func (d *Document) AddResourceDeclaration(resourceType string, resourceDeclaration Resource) { slog.Info("Document.AddResourceDeclaration()", "type", resourceType, "resource", resourceDeclaration) decl := NewDeclarationFromDocument(d) decl.Type = TypeName(resourceType) decl.Attributes = resourceDeclaration d.ResourceDecls = append(d.ResourceDecls, *decl) d.MapResourceURI(decl.Attributes.URI(), decl) decl.SetDocument(d) } func (d *Document) AddResource(uri string) error { decl := NewDeclarationFromDocument(d) if e := decl.SetURI(uri); e != nil { return e } d.ResourceDecls = append(d.ResourceDecls, *decl) d.MapResourceURI(decl.Attributes.URI(), decl) decl.SetDocument(d) return nil } func (d *Document) JSON() ([]byte, error) { return json.Marshal(d) } func (d *Document) YAML() ([]byte, error) { return yaml.Marshal(d) } func (d *Document) Diff(with *Document, output io.Writer) (returnOutput string, diffErr error) { defer func() { if r := recover(); r != nil { returnOutput = "" diffErr = fmt.Errorf("%s", r) } }() slog.Info("Document.Diff()") opts := []yamldiff.DoOptionFunc{} if output == nil { output = &strings.Builder{} } ydata, yerr := d.YAML() if yerr != nil { return "", yerr } yamlDiff,yamlDiffErr := yamldiff.Load(string(ydata)) if yamlDiffErr != nil { return "", yamlDiffErr } wdata,werr := with.YAML() if werr != nil { return "", werr } withDiff,withDiffErr := yamldiff.Load(string(wdata)) if withDiffErr != nil { return "", withDiffErr } for _,docDiffResults := range yamldiff.Do(yamlDiff, withDiff, opts...) { slog.Info("Diff()", "diff", docDiffResults, "dump", docDiffResults.Dump()) _,e := output.Write([]byte(docDiffResults.Dump())) if e != nil { return "", e } } slog.Info("Document.Diff() ", "document.yaml", ydata, "with.yaml", wdata) if stringOutput, ok := output.(*strings.Builder); ok { return stringOutput.String(), nil } return "", nil } func (d *Document) UnmarshalYAML(value *yaml.Node) error { type decodeDocument Document t := (*decodeDocument)(d) if unmarshalDocumentErr := value.Decode(t); unmarshalDocumentErr != nil { return unmarshalDocumentErr } for i := range d.ResourceDecls { d.ResourceDecls[i].SetDocument(d) d.MapResourceURI(d.ResourceDecls[i].Attributes.URI(), &d.ResourceDecls[i]) } return nil } func (d *Document) UnmarshalJSON(data []byte) error { type decodeDocument Document t := (*decodeDocument)(d) if unmarshalDocumentErr := json.Unmarshal(data, t); unmarshalDocumentErr != nil { return unmarshalDocumentErr } for i := range d.ResourceDecls { d.ResourceDecls[i].SetDocument(d) d.MapResourceURI(d.ResourceDecls[i].Attributes.URI(), &d.ResourceDecls[i]) } return nil }