231 lines
5.4 KiB
Go
231 lines
5.4 KiB
Go
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. 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
|
|
|
|
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)
|
|
if err == nil {
|
|
for i := range d.ResourceDecls {
|
|
d.ResourceDecls[i].SetDocument(d)
|
|
d.ResourceDecls[i].SetConfig(d.config)
|
|
}
|
|
}
|
|
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) {
|
|
decl := NewDeclaration()
|
|
decl.Type = TypeName(resourceType)
|
|
decl.Attributes = resourceDeclaration
|
|
decl.SetDocument(d)
|
|
d.ResourceDecls = append(d.ResourceDecls, *decl)
|
|
d.MapResourceURI(decl.Attributes.URI(), decl)
|
|
}
|
|
|
|
func (d *Document) AddResource(uri string) error {
|
|
decl := NewDeclaration()
|
|
if e := decl.SetURI(uri); e != nil {
|
|
return e
|
|
}
|
|
decl.SetDocument(d)
|
|
d.ResourceDecls = append(d.ResourceDecls, *decl)
|
|
d.MapResourceURI(decl.Attributes.URI(), decl)
|
|
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
|
|
}
|