// Copyright 2024 Matthew Rich . All rights reserved. package main import ( "context" "io" "os" "flag" "log" "log/slog" _ "errors" "fmt" _ "gopkg.in/yaml.v3" "decl/internal/resource" "decl/internal/source" "decl/internal/target" ) const ( FormatYaml = "yaml" FormatJson = "json" ) var ( version string commit string date string ) var GlobalOformat *string var GlobalOutput string var GlobalQuiet *bool var ImportMerge *bool var ImportResource *string 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 VersionUsage() { fmt.Println("jx") fmt.Printf("version: %s\n", version) fmt.Printf("commit: %s\n", commit) fmt.Printf("date: %s\n", date) } 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) } } 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) } return extractDocuments } return []*resource.Document{ resource.NewDocument() } } func ImportSubCommand(cmd *flag.FlagSet, output io.Writer) (err error) { ImportResource = cmd.String("resource", "", "(uri) Add a resource to the document.") ImportMerge = cmd.Bool("merge", false, "Merge resources into a single document.") e := cmd.Parse(os.Args[2:]) if e != nil { // returns ErrHelp return e } merged := resource.NewDocument() documents := make([]*resource.Document, 0, 100) for _,source := range cmd.Args() { loaded := LoadSourceURI(source) if loaded != nil { documents = append(documents, loaded...) } } /* switch *GlobalOformat { case FormatYaml: encoder = resource.NewYAMLEncoder(output) case FormatJson: encoder = resource.NewJSONEncoder(output) } */ slog.Info("main.ImportResource", "args", os.Args, "output", GlobalOutput) outputTarget, err := target.TargetTypes.New(GlobalOutput) if err != nil { log.Fatal(err) } defer outputTarget.Close() if len(documents) == 0 { documents = append(documents, resource.NewDocument()) } for _,d := range documents { if d != nil { if *ImportResource != "" { slog.Info("ImportResource", "resource", ImportResource) if addResourceErr := d.AddResource(*ImportResource); addResourceErr != nil { log.Fatal(addResourceErr) } } if *GlobalQuiet { for _, dr := range d.Resources() { if _,e := output.Write([]byte(dr.Resource().URI())); e != nil { return e } } } else { if *ImportMerge { merged.ResourceDecls = append(merged.ResourceDecls, d.ResourceDecls...) } else { slog.Info("main.ImportResource", "outputTarget", outputTarget, "type", outputTarget.Type()) if outputErr := outputTarget.EmitResources([]*resource.Document{d}, nil); outputErr != nil { return outputErr } } } } } if *ImportMerge { if outputErr := outputTarget.EmitResources([]*resource.Document{merged}, nil); outputErr != nil { return outputErr } } return err } func ApplySubCommand(cmd *flag.FlagSet, output io.Writer) (err error) { if e := cmd.Parse(os.Args[2:]); e != nil { return e } var encoder resource.Encoder documents := make([]*resource.Document, 0, 100) for _,source := range cmd.Args() { loaded := LoadSourceURI(source) if loaded != nil { documents = append(documents, loaded...) } } slog.Info("main.Apply()", "documents", documents) for _,d := range documents { slog.Info("main.Appl()", "doc", d) 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() { if _,e := output.Write([]byte(dr.Resource().URI())); e != nil { return e } } } else { if documentGenerateErr := encoder.Encode(d); documentGenerateErr != nil { return documentGenerateErr } } } return err } func DiffSubCommand(cmd *flag.FlagSet, output io.Writer) (err error) { if e := cmd.Parse(os.Args[2:]); e != nil { return e } 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) if rightSource == "" { rightDocuments = append(rightDocuments, LoadSourceURI(leftSource)...) slog.Info("jx diff clone", "docs", rightDocuments) for i, doc := range rightDocuments { if doc != nil { leftDocuments = append(leftDocuments, doc.Clone()) for _,resourceDeclaration := range leftDocuments[i].Resources() { if _, e := resourceDeclaration.Resource().Read(ctx); e != nil { slog.Info("jx diff ", "err", e) //return e } } } } } else { leftDocuments = append(leftDocuments, LoadSourceURI(leftSource)...) rightDocuments = append(rightDocuments, LoadSourceURI(rightSource)...) } slog.Info("jx diff ", "right", rightDocuments, "left", leftDocuments) 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 } index++ continue } 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++ } return err } 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") cmdFlagSet.StringVar(&GlobalOutput, "output", "-", "Output target (default stdout)") cmdFlagSet.StringVar(&GlobalOutput, "o", "-", "Output target (default stdout)") GlobalQuiet = cmdFlagSet.Bool("quiet", false, "Generate terse output.") switch subCmd.Name { case "diff": cmdFlagSet.Usage = func() { fmt.Println("jx diff source [source2]") cmdFlagSet.PrintDefaults() VersionUsage() } case "apply": cmdFlagSet.Usage = func() { fmt.Println("jx diff source [source2]") cmdFlagSet.PrintDefaults() VersionUsage() } case "import": cmdFlagSet.Usage = func() { fmt.Println("jx import source...") cmdFlagSet.PrintDefaults() VersionUsage() } } if os.Args[1] == subCmd.Name { if e := subCmd.Run(cmdFlagSet, os.Stdout); e != nil { log.Fatal(e) } return } } flag.PrintDefaults() VersionUsage() os.Exit(1) }