jx/internal/client/client.go

306 lines
7.2 KiB
Go
Raw Normal View History

2024-09-24 19:26:40 +00:00
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package client
import (
"decl/internal/data"
"decl/internal/folio"
_ "decl/internal/fan"
_ "decl/internal/config"
_ "decl/internal/resource"
"decl/internal/fs"
_ "errors"
"fmt"
"context"
"log/slog"
"os"
)
var (
)
type App struct {
Target folio.URI
ImportedMap map[folio.URI]data.Document
Documents []data.Document
emitter data.Converter
merged data.Document
Config data.Document
}
func NewClient() *App {
a := &App{ ImportedMap: make(map[folio.URI]data.Document), Documents: make([]data.Document, 0, 100) }
return a
}
// Load config documents from default system config path. Ignore if missing.
func (a *App) SystemConfiguration(configPath string) (err error) {
var extractor data.Converter
var sourceResource data.Resource
if a.Config == nil {
a.Config = folio.DocumentRegistry.NewDocument("file:///etc/jx/runtimeconfig.jx.yaml")
}
if configPath != "" {
//configURI := folio.URI(configPath)
var loaded []data.Document
docFs := fs.NewWalkDir(os.DirFS(configPath), configPath, func(fsys fs.FS, path string, file fs.DirEntry) (loadErr error) {
u := folio.URI(fmt.Sprintf("file://%s", path))
if ! file.IsDir() {
if extractor, loadErr = folio.DocumentRegistry.ConverterTypes.New(string(u)); loadErr == nil {
if sourceResource, loadErr = u.NewResource(nil); loadErr == nil {
if loaded, loadErr = extractor.(data.ManyExtractor).ExtractMany(sourceResource, nil); loadErr == nil {
a.Config.AppendConfigurations(loaded)
}
}
}
}
return
})
err = docFs.Walk(nil)
}
return
}
func (a *App) MergeDocuments() {
a.merged = folio.DocumentRegistry.NewDocument("file://-")
for _, d := range a.Documents {
for _, declaration := range d.(*folio.Document).ResourceDeclarations {
a.merged.AddDeclaration((data.Declaration)(declaration))
}
}
}
func (a *App) SetOutput(uri string) (err error) {
if uri == "-" {
uri = "jx://-"
}
a.Target = folio.URI(uri)
if a.emitter, err = folio.DocumentRegistry.ConverterTypes.New(uri); err != nil {
return fmt.Errorf("Failed opening target: %s, %w", uri, err)
}
return
}
// Each document has an `imports` keyword which can be used to load dependencies
func (a *App) LoadDocumentImports() error {
for _, d := range a.Documents {
for _, importedDocument := range d.ImportedDocuments() {
docURI := folio.URI(importedDocument.GetURI())
if _, ok := a.ImportedMap[docURI]; !ok {
a.ImportedMap[docURI] = importedDocument
if _, outputErr := a.emitter.Emit(importedDocument, nil); outputErr != nil {
return outputErr
}
}
}
}
return nil
}
func (a *App) ImportResource(ctx context.Context, uri string) (err error) {
if len(a.Documents) < 1 {
a.Documents = append(a.Documents, folio.DocumentRegistry.NewDocument(""))
}
resourceURI := folio.URI(uri)
u := resourceURI.Parse()
if u == nil {
return fmt.Errorf("Failed adding resource: %s", uri)
}
if u.Scheme == "" {
u.Scheme = "file"
}
for _, d := range a.Documents {
if newResource, newResourceErr := d.NewResource(uri); newResourceErr == nil {
if _, err = newResource.Read(ctx); err != nil {
return
}
} else {
return newResourceErr
}
}
return
}
func (a *App) ImportSource(uri string) (loadedDocuments []data.Document, err error) {
if loadedDocuments, err = folio.DocumentRegistry.Load(folio.URI(uri)); err == nil && loadedDocuments != nil {
a.Documents = append(a.Documents, loadedDocuments...)
} else {
return
}
return
}
func (a *App) Import(docs []string) (err error) {
for _, source := range docs {
if _, err = a.ImportSource(source); err != nil {
return
}
}
return
}
func (a *App) Apply(ctx context.Context, deleteResources bool) (err error) {
for _, d := range a.Documents {
d.SetConfig(a.Config)
var overrideState string = ""
if deleteResources {
overrideState = "delete"
}
d.ResolveIds(ctx)
if ! d.CheckConstraints() {
slog.Info("Client.Apply() document constrains failed", "requires", d)
continue
}
if e := d.(*folio.Document).Apply(overrideState); e != nil {
slog.Info("Client.Apply() error", "error", e)
return e
}
if d.Failures() > 0 {
err = fmt.Errorf("Failed resources: %d, %w", d.Failures(), err)
}
}
return
}
func (a *App) ImportCmd(ctx context.Context, docs []string, resourceURI string, quiet bool, merge bool) (err error) {
if err = a.Import(docs); err != nil {
return
}
if err = a.LoadDocumentImports(); err != nil {
return
}
if len(resourceURI) > 0 {
if err = a.ImportResource(ctx, resourceURI); err != nil {
return
}
}
if quiet {
err = a.Quiet()
} else {
if merge {
a.MergeDocuments()
}
err = a.Emit()
if err != nil {
return
}
}
return
}
func (a *App) ApplyCmd(ctx context.Context, docs []string, quiet bool, deleteResources bool) (err error) {
if err = a.Import(docs); err != nil {
return
}
if err = a.LoadDocumentImports(); err != nil {
return
}
if err = a.Apply(ctx, deleteResources); err != nil {
return
}
if quiet {
err = a.Quiet()
} else {
err = a.Emit()
if err != nil {
return
}
}
return
}
func (a *App) Diff(left []data.Document, right []data.Document) (err error) {
output := os.Stdout
slog.Info("jx diff ", "right", right, "left", left)
index := 0
for {
if index >= len(right) && index >= len(left) {
break
}
if index >= len(right) {
if _, err = left[index].Diff(folio.DocumentRegistry.NewDocument(""), output); err != nil {
return
}
index++
continue
}
if index >= len(left) {
if _, err = folio.DocumentRegistry.NewDocument("").Diff(right[index], output); err != nil {
return
}
index++
continue
}
if _, err = left[index].Diff(right[index], output); err != nil {
return
}
index++
}
return
}
func (a *App) DiffCmd(docs []string) (err error) {
output := os.Stdout
var leftDocuments, rightDocuments []data.Document
//leftSource := folio.URI(docs[0])
rightSource := folio.URI(docs[1])
if leftDocuments, err = a.ImportSource(docs[0]); err == nil {
if rightSource.IsEmpty() {
for _, doc := range leftDocuments {
_, err = doc.DiffState(output)
}
} else {
rightDocuments, err = a.ImportSource(docs[1])
err = a.Diff(leftDocuments, rightDocuments)
}
}
return err
}
func (a *App) Quiet() (err error) {
output := os.Stdout
for _, d := range a.Documents {
for _, dr := range d.Declarations() {
if _, err = output.Write([]byte(fmt.Sprintf("%s\n", dr.Resource().URI()))); err != nil {
return
}
}
}
return
}
func (a *App) Emit() (err error) {
if a.merged == nil {
for _, d := range a.Documents {
if _, err = a.emitter.Emit(d, nil); err != nil {
return
}
}
} else {
if _, err = a.emitter.Emit(a.merged, nil); err != nil {
return
}
}
return
}
func (a *App) Close() (err error) {
return a.emitter.Close()
}