2024-03-20 19:23:31 +00:00
|
|
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
2024-04-12 16:08:08 +00:00
|
|
|
|
2024-03-20 19:23:31 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-04-19 07:52:10 +00:00
|
|
|
"context"
|
2024-04-12 16:08:08 +00:00
|
|
|
"io"
|
2024-03-20 19:23:31 +00:00
|
|
|
"os"
|
|
|
|
"flag"
|
|
|
|
"log"
|
2024-04-03 18:47:55 +00:00
|
|
|
"log/slog"
|
2024-04-19 07:52:10 +00:00
|
|
|
_ "errors"
|
|
|
|
"fmt"
|
2024-03-20 19:23:31 +00:00
|
|
|
_ "gopkg.in/yaml.v3"
|
|
|
|
"decl/internal/resource"
|
2024-04-19 07:52:10 +00:00
|
|
|
"decl/internal/source"
|
2024-03-20 19:23:31 +00:00
|
|
|
)
|
|
|
|
|
2024-04-19 07:52:10 +00:00
|
|
|
const (
|
|
|
|
FormatYaml = "yaml"
|
|
|
|
FormatJson = "json"
|
|
|
|
)
|
2024-03-20 19:23:31 +00:00
|
|
|
|
2024-04-19 07:52:10 +00:00
|
|
|
var GlobalOformat *string
|
|
|
|
var GlobalQuiet *bool
|
2024-04-12 16:08:08 +00:00
|
|
|
|
2024-04-19 07:52:10 +00:00
|
|
|
var ImportMerge *bool
|
2024-04-21 06:13:17 +00:00
|
|
|
var ImportResource *string
|
2024-04-03 18:47:55 +00:00
|
|
|
|
2024-04-12 16:08:08 +00:00
|
|
|
|
2024-04-19 07:52:10 +00:00
|
|
|
var ctx context.Context = context.Background()
|
|
|
|
|
|
|
|
type RunCommand func(cmd *flag.FlagSet, output io.Writer) error
|
|
|
|
|
|
|
|
type SubCommand struct {
|
|
|
|
Name string
|
|
|
|
Run RunCommand
|
|
|
|
}
|
|
|
|
|
|
|
|
var jxSubCommands = []SubCommand {
|
|
|
|
{
|
|
|
|
Name: "diff",
|
|
|
|
Run: DiffSubCommand,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "apply",
|
|
|
|
Run: ApplySubCommand,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "import",
|
|
|
|
Run: ImportSubCommand,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func LoggerConfig() {
|
|
|
|
var programLevel = new(slog.LevelVar)
|
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel}))
|
|
|
|
slog.SetDefault(logger)
|
|
|
|
if debugLogging,ok := os.LookupEnv("DECL_DEBUG"); ok && debugLogging != "" {
|
|
|
|
programLevel.Set(slog.LevelDebug)
|
|
|
|
} else {
|
|
|
|
programLevel.Set(slog.LevelError)
|
|
|
|
}
|
|
|
|
}
|
2024-04-12 16:08:08 +00:00
|
|
|
|
2024-04-19 07:52:10 +00:00
|
|
|
func LoadSourceURI(uri string) []*resource.Document {
|
|
|
|
slog.Info("loading ", "uri", uri)
|
|
|
|
if uri != "" {
|
|
|
|
ds, err := source.SourceTypes.New(uri)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
extractDocuments, extractErr := ds.ExtractResources(nil)
|
|
|
|
if extractErr != nil {
|
|
|
|
log.Fatal(extractErr)
|
|
|
|
}
|
2024-04-21 06:13:17 +00:00
|
|
|
slog.Info("extract documents", "documents", extractDocuments)
|
2024-04-19 07:52:10 +00:00
|
|
|
return extractDocuments
|
|
|
|
}
|
|
|
|
return []*resource.Document{ resource.NewDocument() }
|
|
|
|
}
|
|
|
|
|
|
|
|
func ImportSubCommand(cmd *flag.FlagSet, output io.Writer) (err error) {
|
2024-04-21 06:13:17 +00:00
|
|
|
ImportResource = cmd.String("resource", "", "(uri) Add a resource to the document.")
|
2024-04-19 07:52:10 +00:00
|
|
|
ImportMerge = cmd.Bool("merge", false, "Merge resources into a single document.")
|
|
|
|
cmd.Parse(os.Args[2:])
|
|
|
|
var encoder resource.Encoder
|
|
|
|
merged := resource.NewDocument()
|
|
|
|
documents := make([]*resource.Document, 0, 100)
|
|
|
|
for _,source := range cmd.Args() {
|
2024-04-21 06:13:17 +00:00
|
|
|
loaded := LoadSourceURI(source)
|
|
|
|
if loaded != nil {
|
|
|
|
documents = append(documents, loaded...)
|
|
|
|
}
|
2024-04-19 07:52:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch *GlobalOformat {
|
|
|
|
case FormatYaml:
|
|
|
|
encoder = resource.NewYAMLEncoder(output)
|
|
|
|
case FormatJson:
|
|
|
|
encoder = resource.NewJSONEncoder(output)
|
|
|
|
}
|
|
|
|
|
2024-04-21 06:13:17 +00:00
|
|
|
if len(documents) == 0 {
|
|
|
|
documents = append(documents, resource.NewDocument())
|
|
|
|
}
|
|
|
|
|
2024-04-19 07:52:10 +00:00
|
|
|
for _,d := range documents {
|
|
|
|
if d != nil {
|
2024-04-21 06:13:17 +00:00
|
|
|
|
|
|
|
if *ImportResource != "" {
|
|
|
|
slog.Info("ImportResource", "resource", ImportResource)
|
|
|
|
if addResourceErr := d.AddResource(*ImportResource); addResourceErr != nil {
|
|
|
|
log.Fatal(addResourceErr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-19 07:52:10 +00:00
|
|
|
if *GlobalQuiet {
|
|
|
|
for _, dr := range d.Resources() {
|
|
|
|
output.Write([]byte(dr.Resource().URI()))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if *ImportMerge {
|
|
|
|
merged.ResourceDecls = append(merged.ResourceDecls, d.ResourceDecls...)
|
|
|
|
slog.Info("merging", "doc", merged.ResourceDecls, "src", d.ResourceDecls)
|
|
|
|
} else {
|
|
|
|
if documentGenerateErr := encoder.Encode(d); documentGenerateErr != nil {
|
|
|
|
return documentGenerateErr
|
|
|
|
}
|
2024-04-12 16:08:08 +00:00
|
|
|
}
|
|
|
|
}
|
2024-04-19 07:52:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if *ImportMerge {
|
|
|
|
if documentGenerateErr := encoder.Encode(merged); documentGenerateErr != nil {
|
|
|
|
return documentGenerateErr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func ApplySubCommand(cmd *flag.FlagSet, output io.Writer) (err error) {
|
|
|
|
cmd.Parse(os.Args[2:])
|
|
|
|
var encoder resource.Encoder
|
|
|
|
documents := make([]*resource.Document, 0, 100)
|
|
|
|
for _,source := range cmd.Args() {
|
2024-04-21 06:13:17 +00:00
|
|
|
loaded := LoadSourceURI(source)
|
|
|
|
if loaded != nil {
|
|
|
|
documents = append(documents, loaded...)
|
|
|
|
}
|
2024-04-19 07:52:10 +00:00
|
|
|
}
|
|
|
|
|
2024-04-21 06:13:17 +00:00
|
|
|
slog.Info("main.Apply()", "documents", documents)
|
2024-04-19 07:52:10 +00:00
|
|
|
for _,d := range documents {
|
2024-04-21 06:13:17 +00:00
|
|
|
slog.Info("main.Appl()", "doc", d)
|
2024-04-19 07:52:10 +00:00
|
|
|
if e := d.Apply(); e != nil {
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
|
|
|
|
switch *GlobalOformat {
|
|
|
|
case FormatYaml:
|
|
|
|
encoder = resource.NewYAMLEncoder(output)
|
|
|
|
case FormatJson:
|
|
|
|
encoder = resource.NewJSONEncoder(output)
|
|
|
|
}
|
|
|
|
if *GlobalQuiet {
|
|
|
|
for _, dr := range d.Resources() {
|
|
|
|
output.Write([]byte(dr.Resource().URI()))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if documentGenerateErr := encoder.Encode(d); documentGenerateErr != nil {
|
|
|
|
return documentGenerateErr
|
2024-04-12 16:08:08 +00:00
|
|
|
}
|
2024-04-19 07:52:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func DiffSubCommand(cmd *flag.FlagSet, output io.Writer) (err error) {
|
|
|
|
cmd.Parse(os.Args[2:])
|
|
|
|
leftSource := cmd.Arg(0)
|
|
|
|
rightSource := cmd.Arg(1)
|
|
|
|
leftDocuments := make([]*resource.Document, 0, 100)
|
|
|
|
rightDocuments := make([]*resource.Document, 0, 100)
|
|
|
|
|
|
|
|
slog.Info("jx diff subcommand", "left", leftSource, "right", rightSource, "flagset", cmd)
|
|
|
|
leftDocuments = append(leftDocuments, LoadSourceURI(leftSource)...)
|
|
|
|
|
|
|
|
if rightSource == "" {
|
|
|
|
slog.Info("jx diff clone", "docs", leftDocuments)
|
|
|
|
for i, doc := range leftDocuments {
|
|
|
|
if doc != nil {
|
|
|
|
rightDocuments = append(rightDocuments, doc.Clone())
|
|
|
|
for _,resourceDeclaration := range leftDocuments[i].Resources() {
|
|
|
|
if _, e := resourceDeclaration.Resource().Read(ctx); e != nil {
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
}
|
2024-04-12 16:08:08 +00:00
|
|
|
}
|
2024-04-19 07:52:10 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
rightDocuments = append(rightDocuments, LoadSourceURI(rightSource)...)
|
|
|
|
}
|
|
|
|
|
|
|
|
index := 0
|
|
|
|
for {
|
|
|
|
if index >= len(rightDocuments) && index >= len(leftDocuments) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if index >= len(rightDocuments) {
|
|
|
|
if _,e := leftDocuments[index].Diff(resource.NewDocument(), output); e != nil {
|
|
|
|
return e
|
2024-04-12 16:08:08 +00:00
|
|
|
}
|
|
|
|
index++
|
2024-04-19 07:52:10 +00:00
|
|
|
continue
|
2024-04-03 18:47:55 +00:00
|
|
|
}
|
2024-04-19 07:52:10 +00:00
|
|
|
if index >= len(leftDocuments) {
|
|
|
|
if _,e := resource.NewDocument().Diff(rightDocuments[index], output); e != nil {
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
index++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if _,e := leftDocuments[index].Diff(rightDocuments[index], output); e != nil {
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
index++
|
2024-04-03 18:47:55 +00:00
|
|
|
}
|
2024-04-19 07:52:10 +00:00
|
|
|
return err
|
|
|
|
}
|
2024-03-20 19:23:31 +00:00
|
|
|
|
2024-04-19 07:52:10 +00:00
|
|
|
func main() {
|
|
|
|
|
|
|
|
LoggerConfig()
|
|
|
|
|
|
|
|
if len(os.Args) < 2 {
|
|
|
|
fmt.Println("expected subcommands: diff, apply, import")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _,subCmd := range jxSubCommands {
|
|
|
|
cmdFlagSet := flag.NewFlagSet(subCmd.Name, flag.ExitOnError)
|
|
|
|
GlobalOformat = cmdFlagSet.String("oformat", "yaml", "Output serialization format")
|
|
|
|
GlobalQuiet = cmdFlagSet.Bool("quiet", false, "Generate terse output.")
|
|
|
|
|
|
|
|
switch subCmd.Name {
|
|
|
|
case "diff":
|
|
|
|
cmdFlagSet.Usage = func() {
|
|
|
|
fmt.Println("jx diff source [source2]")
|
|
|
|
cmdFlagSet.PrintDefaults()
|
2024-04-12 16:08:08 +00:00
|
|
|
}
|
2024-04-19 07:52:10 +00:00
|
|
|
case "apply":
|
|
|
|
cmdFlagSet.Usage = func() {
|
|
|
|
fmt.Println("jx diff source [source2]")
|
|
|
|
cmdFlagSet.PrintDefaults()
|
2024-04-12 16:08:08 +00:00
|
|
|
}
|
2024-04-19 07:52:10 +00:00
|
|
|
case "import":
|
|
|
|
cmdFlagSet.Usage = func() {
|
|
|
|
fmt.Println("jx import source [source2]")
|
|
|
|
cmdFlagSet.PrintDefaults()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
slog.Info("command", "command", subCmd)
|
|
|
|
if os.Args[1] == subCmd.Name {
|
|
|
|
subCmd.Run(cmdFlagSet, os.Stdout)
|
|
|
|
return
|
2024-04-12 16:08:08 +00:00
|
|
|
}
|
2024-04-03 19:27:16 +00:00
|
|
|
}
|
2024-04-19 07:52:10 +00:00
|
|
|
|
2024-03-20 19:23:31 +00:00
|
|
|
}
|