From a6ea2e8c8c4fa5afb05a0a3ae6e2fca96a50540d Mon Sep 17 00:00:00 2001 From: Matthew Rich Date: Fri, 19 Apr 2024 00:52:10 -0700 Subject: [PATCH] add cli sub commands --- cli_test.go | 36 +++- cmd/cli/main.go | 292 ++++++++++++++++++++++------- go.mod | 29 ++- go.sum | 79 ++++++-- internal/resource/container.go | 32 ++++ internal/resource/declaration.go | 7 + internal/resource/document.go | 49 ++++- internal/resource/exec.go | 11 ++ internal/resource/file.go | 75 +++++--- internal/resource/file_test.go | 11 ++ internal/resource/http.go | 10 + internal/resource/network_route.go | 15 ++ internal/resource/package.go | 26 ++- internal/resource/resource.go | 3 +- internal/resource/user.go | 26 +++ internal/signature/ident_test.go | 14 ++ internal/source/docsource_test.go | 18 ++ internal/source/http.go | 10 +- internal/source/types_test.go | 8 +- 19 files changed, 612 insertions(+), 139 deletions(-) create mode 100644 internal/signature/ident_test.go diff --git a/cli_test.go b/cli_test.go index 95de1b7..3c5da76 100644 --- a/cli_test.go +++ b/cli_test.go @@ -8,13 +8,47 @@ import ( "os/exec" "testing" "errors" + "log/slog" + "net/http" + "net/http/httptest" + "fmt" ) func TestCli(t *testing.T) { if _, e := os.Stat("./decl"); errors.Is(e, os.ErrNotExist) { t.Skip("cli not built") } - yaml, cliErr := exec.Command("./decl", "-import-resource", "file://decl").Output() + yaml, cliErr := exec.Command("./decl", "-import-resource", "file://COPYRIGHT").Output() + slog.Info("TestCli", "err", cliErr) assert.Nil(t, cliErr) + assert.NotEqual(t, "", string(yaml)) + assert.Greater(t, len(yaml), 0) +} + +func TestCliHTTPSource(t *testing.T) { + if _, e := os.Stat("./decl"); errors.Is(e, os.ErrNotExist) { + t.Skip("cli not built") + } + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, ` +resources: +- type: file + attributes: + path: foo.txt + owner: nobody + group: nobody + mode: 0644 + content: | + test file + content + state: present +`) + })) + defer ts.Close() + + yaml, cliErr := exec.Command("./decl", "-resource-file", ts.URL, "-apply=false").Output() + slog.Info("TestCliHTTPSource", "err", cliErr) + assert.Nil(t, cliErr) + assert.NotEqual(t, "", string(yaml)) assert.Greater(t, len(yaml), 0) } diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 03bf693..169626c 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -3,97 +3,245 @@ package main import ( + "context" "io" "os" "flag" "log" "log/slog" - "errors" -_ "fmt" +_ "errors" + "fmt" _ "gopkg.in/yaml.v3" "decl/internal/resource" + "decl/internal/source" ) +const ( + FormatYaml = "yaml" + FormatJson = "json" +) + +var GlobalOformat *string +var GlobalQuiet *bool + +var ImportMerge *bool + + +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) + } +} + +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) { + 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() { + documents = append(documents, LoadSourceURI(source)...) + } + + switch *GlobalOformat { + case FormatYaml: + encoder = resource.NewYAMLEncoder(output) + case FormatJson: + encoder = resource.NewJSONEncoder(output) + } + + for _,d := range documents { + if d != nil { + 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 + } + } + } + } + } + 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() { + documents = append(documents, LoadSourceURI(source)...) + } + + for _,d := range documents { + 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 + } + } + } + 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 + } + } + } + } + } 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 + } + 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() { - 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) - } + LoggerConfig() - file := flag.String("resource-file", "", "Resource file path") - resourceUri := flag.String("import-resource", "", "Add an existing resource") - - flag.Parse() + if len(os.Args) < 2 { + fmt.Println("expected subcommands: diff, apply, import") + os.Exit(1) + } - var resourceFile *os.File - var inputFileErr error + 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.") - slog.Info("args", "resource-file", *file, "import-resource", *resourceUri) - if *file == "-" { - if stdinInfo, stdinErr := os.Stdin.Stat(); stdinErr == nil { - if (stdinInfo.Mode() & os.ModeCharDevice) == 0 { - resourceFile = os.Stdin - } - } else { - inputFileErr = stdinErr - } - } else if *file != "" { - resourceFile,inputFileErr = os.Open(*file) - } - - if inputFileErr != nil { - log.Fatal(inputFileErr) - } - - documents := make([]*resource.Document, 0, 100) - decoder := resource.NewYAMLDecoder(resourceFile) - encoder := resource.NewYAMLEncoder(os.Stdout) - index := 0 - - slog.Info("loading resource document", "file", resourceFile) - - documents = append(documents, resource.NewDocument()) - if resourceFile != nil { - for { - d := documents[index] - e := decoder.Decode(d) - if errors.Is(e, io.EOF) { - if len(documents) > 1 { - documents[index] = nil - } - break + switch subCmd.Name { + case "diff": + cmdFlagSet.Usage = func() { + fmt.Println("jx diff source [source2]") + cmdFlagSet.PrintDefaults() } - if e != nil { - log.Fatal(e) + case "apply": + cmdFlagSet.Usage = func() { + fmt.Println("jx diff source [source2]") + cmdFlagSet.PrintDefaults() } - if validationErr := d.Validate(); validationErr != nil { - log.Fatal(validationErr) + case "import": + cmdFlagSet.Usage = func() { + fmt.Println("jx import source [source2]") + cmdFlagSet.PrintDefaults() } - if applyErr := d.Apply(); applyErr != nil { - log.Fatal(applyErr) - } - documents = append(documents, resource.NewDocument()) - index++ + } + slog.Info("command", "command", subCmd) + if os.Args[1] == subCmd.Name { + subCmd.Run(cmdFlagSet, os.Stdout) + return } } - for _,document := range documents { - if document != nil { - if *resourceUri != "" { - slog.Info("importing resource", "resource", *resourceUri) - if addResourceErr := document.AddResource(*resourceUri); addResourceErr != nil { - log.Fatal(addResourceErr) - } - } - if documentGenerateErr := encoder.Encode(document); documentGenerateErr != nil { - log.Fatal(documentGenerateErr) - } - } - } } diff --git a/go.mod b/go.mod index 5e96dcf..0bcf966 100644 --- a/go.mod +++ b/go.mod @@ -2,13 +2,20 @@ module decl go 1.21.1 -require github.com/stretchr/testify v1.9.0 +require ( + github.com/docker/docker v25.0.5+incompatible + github.com/opencontainers/image-spec v1.1.0 + github.com/sters/yaml-diff v1.3.2 + github.com/stretchr/testify v1.9.0 + github.com/xeipuuv/gojsonschema v1.2.0 + gopkg.in/yaml.v3 v3.0.1 +) require ( github.com/Microsoft/go-winio v0.4.14 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/fatih/color v1.16.0 // indirect @@ -19,19 +26,21 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sters/yaml-diff v1.3.2 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/sys v0.17.0 // indirect + go.opentelemetry.io/otel v1.25.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 // indirect + go.opentelemetry.io/otel/metric v1.25.0 // indirect + go.opentelemetry.io/otel/sdk v1.25.0 // indirect + go.opentelemetry.io/otel/trace v1.25.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/time v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index 7bf3367..23a606b 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,16 @@ +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ= -github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -22,37 +26,56 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sters/yaml-diff v1.3.2 h1:99Ke50QYFQYZjKMOiePxwyuQ+WeCvNy6cRooqdLs/ZE= github.com/sters/yaml-diff v1.3.2/go.mod h1:86usbNZiUqke5wYjMxDVEjmvGjmY2FkMwOwe0A5zf68= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= @@ -65,21 +88,33 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= +go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 h1:dT33yIHtmsqpixFsSQPwNeY5drM9wTcoL8h0FWF4oGM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0/go.mod h1:h95q0LBGh7hlAC08X2DhSeyIG02YQ0UyioTCVAqRPmc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 h1:Mbi5PKN7u322woPa85d7ebZ+SOvEoPvoiBu+ryHWgfA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0/go.mod h1:e7ciERRhZaOZXVjx5MiL8TK5+Xv7G5Gv5PA2ZDEJdL8= +go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= +go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= +go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= +go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= +go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= +go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -89,13 +124,15 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -106,8 +143,18 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= +google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/internal/resource/container.go b/internal/resource/container.go index 48c5977..1d597ac 100644 --- a/internal/resource/container.go +++ b/internal/resource/container.go @@ -95,6 +95,38 @@ func NewContainer(containerClientApi ContainerClient) *Container { } } +func (c *Container) Clone() Resource { + return &Container { + Id: c.Id, + Name: c.Name, + Path: c.Path, + Cmd: c.Cmd, + Entrypoint: c.Entrypoint, + Args: c.Args, + Environment: c.Environment, + Image: c.Image, + ResolvConfPath: c.ResolvConfPath, + HostnamePath: c.HostnamePath, + HostsPath: c.HostsPath, + LogPath: c.LogPath, + Created: c.Created, + ContainerState: c.ContainerState, + RestartCount: c.RestartCount, + Driver: c.Driver, + Platform: c.Platform, + MountLabel: c.MountLabel, + ProcessLabel: c.ProcessLabel, + AppArmorProfile: c.AppArmorProfile, + ExecIDs: c.ExecIDs, + HostConfig: c.HostConfig, + GraphDriver: c.GraphDriver, + SizeRw: c.SizeRw, + SizeRootFs: c.SizeRootFs, + State: c.State, + apiClient: c.apiClient, + } +} + func (c *Container) URI() string { return fmt.Sprintf("container://%s", c.Id) } diff --git a/internal/resource/declaration.go b/internal/resource/declaration.go index a3b7da3..83cdfd9 100644 --- a/internal/resource/declaration.go +++ b/internal/resource/declaration.go @@ -41,6 +41,13 @@ func NewDeclaration() *Declaration { return &Declaration{} } +func (d *Declaration) Clone() *Declaration { + return &Declaration { + Type: d.Type, + Attributes: d.Attributes.Clone(), + } +} + func (d *Declaration) Load(r io.Reader) error { c := NewYAMLDecoder(r) return c.Decode(d) diff --git a/internal/resource/document.go b/internal/resource/document.go index ce9f0e5..817d1e3 100644 --- a/internal/resource/document.go +++ b/internal/resource/document.go @@ -4,11 +4,13 @@ package resource import ( "encoding/json" -_ "fmt" + "fmt" "gopkg.in/yaml.v3" "io" "log/slog" _ "net/url" + "github.com/sters/yaml-diff/yamldiff" + "strings" ) type Document struct { @@ -19,6 +21,15 @@ func NewDocument() *Document { return &Document{} } +func (d *Document) Clone() *Document { + clone := NewDocument() + clone.ResourceDecls = make([]Declaration, len(d.ResourceDecls)) + for i, res := range d.ResourceDecls { + clone.ResourceDecls[i] = *res.Clone() + } + return clone +} + func (d *Document) Load(r io.Reader) error { c := NewYAMLDecoder(r) return c.Decode(d); @@ -94,3 +105,39 @@ func (d *Document) JSON() ([]byte, error) { func (d *Document) YAML() ([]byte, error) { return yaml.Marshal(d) } + +func (d *Document) Diff(with *Document, output io.Writer) (string, error) { + 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 _,diff := range yamldiff.Do(yamlDiff, withDiff, opts...) { + slog.Info("Diff()", "diff", diff) + fmt.Printf("yaml %s with %s\n", yamlDiff, withDiff) + output.Write([]byte(diff.Dump())) + } + slog.Info("Document.Diff() ", "document.yaml", ydata, "with.yaml", wdata) + if stringOutput, ok := output.(*strings.Builder); ok { + return stringOutput.String(), nil + } + return "", nil +} diff --git a/internal/resource/exec.go b/internal/resource/exec.go index ec0bfa9..d6ad242 100644 --- a/internal/resource/exec.go +++ b/internal/resource/exec.go @@ -37,6 +37,17 @@ func NewExec() *Exec { return &Exec{loader: YamlLoadDecl} } +func (x *Exec) Clone() Resource { + return &Exec { + Id: x.Id, + CreateTemplate: x.CreateTemplate, + ReadTemplate: x.ReadTemplate, + UpdateTemplate: x.UpdateTemplate, + DeleteTemplate: x.DeleteTemplate, + State: x.State, + } +} + func (x *Exec) URI() string { return fmt.Sprintf("exec://%s", x.Id) } diff --git a/internal/resource/file.go b/internal/resource/file.go index 3f1d5be..feb51ee 100644 --- a/internal/resource/file.go +++ b/internal/resource/file.go @@ -31,6 +31,7 @@ const ( ) var ErrInvalidResourceURI error = errors.New("Invalid resource URI") +var ErrInvalidFileInfo error = errors.New("Invalid FileInfo") func init() { ResourceTypes.Register("file", func(u *url.URL) Resource { @@ -65,6 +66,22 @@ func NewFile() *File { return f } +func (f *File) Clone() Resource { + return &File { + Path: f.Path, + Owner: f.Owner, + Group: f.Group, + Mode: f.Mode, + Atime: f.Atime, + Ctime: f.Ctime, + Mtime: f.Mtime, + Content: f.Content, + Target: f.Target, + FileType: f.FileType, + State: f.State, + } +} + func (f *File) URI() string { return fmt.Sprintf("file://%s", f.Path) } @@ -176,6 +193,37 @@ func (f *File) NormalizePath() error { return fileAbsErr } +func (f *File) UpdateAttributesFromFileInfo(info os.FileInfo) error { + if info != nil { + f.Mtime = info.ModTime() + if stat, ok := info.Sys().(*syscall.Stat_t); ok { + f.Atime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) + f.Ctime = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)) + + userId := strconv.Itoa(int(stat.Uid)) + groupId := strconv.Itoa(int(stat.Gid)) + fileUser, userErr := user.LookupId(userId) + if userErr != nil { //UnknownUserIdError + //panic(userErr) + f.Owner = userId + } else { + f.Owner = fileUser.Username + } + fileGroup, groupErr := user.LookupGroupId(groupId) + if groupErr != nil { + //panic(groupErr) + f.Group = groupId + } else { + f.Group = fileGroup.Name + } + } + f.Mode = fmt.Sprintf("%04o", info.Mode().Perm()) + f.FileType.SetMode(info.Mode()) + return nil + } + return ErrInvalidFileInfo +} + func (f *File) ReadStat() error { info, e := os.Lstat(f.Path) if e != nil { @@ -183,32 +231,7 @@ func (f *File) ReadStat() error { return e } - f.Mtime = info.ModTime() - - if stat, ok := info.Sys().(*syscall.Stat_t); ok { - f.Atime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) - f.Ctime = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)) - - userId := strconv.Itoa(int(stat.Uid)) - groupId := strconv.Itoa(int(stat.Gid)) - fileUser, userErr := user.LookupId(userId) - if userErr != nil { //UnknownUserIdError - //panic(userErr) - f.Owner = userId - } else { - f.Owner = fileUser.Username - } - fileGroup, groupErr := user.LookupGroupId(groupId) - if groupErr != nil { - //panic(groupErr) - f.Group = groupId - } else { - f.Group = fileGroup.Name - } - } - f.Mode = fmt.Sprintf("%04o", info.Mode().Perm()) - f.FileType.SetMode(info.Mode()) - return nil + return f.UpdateAttributesFromFileInfo(info) } func (f *File) Read(ctx context.Context) ([]byte, error) { diff --git a/internal/resource/file_test.go b/internal/resource/file_test.go index d4ae3c0..9920070 100644 --- a/internal/resource/file_test.go +++ b/internal/resource/file_test.go @@ -207,6 +207,17 @@ func TestFileNormalizePath(t *testing.T) { assert.Equal(t, absFile, f.Path) } +func TestFileUpdateAttributesFromFileInfo(t *testing.T) { + f := NewFile() + assert.NotNil(t, f) + + info, e := os.Lstat(TempDir) + assert.Nil(t, e) + + f.UpdateAttributesFromFileInfo(info) + assert.Equal(t, DirectoryFile, f.FileType) +} + func TestFileReadStat(t *testing.T) { ctx := context.Background() link := filepath.Join(TempDir, "link.txt") diff --git a/internal/resource/http.go b/internal/resource/http.go index cefb599..8d56a69 100644 --- a/internal/resource/http.go +++ b/internal/resource/http.go @@ -45,6 +45,16 @@ func NewHTTP() *HTTP { return &HTTP{ client: &http.Client{} } } +func (h *HTTP) Clone() Resource { + return &HTTP { + client: h.client, + Endpoint: h.Endpoint, + Headers: h.Headers, + Body: h.Body, + State: h.State, + } +} + func (h *HTTP) URI() string { return h.Endpoint } diff --git a/internal/resource/network_route.go b/internal/resource/network_route.go index dc019cd..6e3a209 100644 --- a/internal/resource/network_route.go +++ b/internal/resource/network_route.go @@ -123,6 +123,21 @@ func NewNetworkRoute() *NetworkRoute { return &NetworkRoute{Rtid: NetworkRouteTableMain} } +func (n *NetworkRoute) Clone() Resource { + return &NetworkRoute { + Id: n.Id, + To: n.To, + Interface: n.Interface, + Gateway: n.Gateway, + Metric: n.Metric, + Rtid: n.Rtid, + RouteType: n.RouteType, + Scope: n.Scope, + Proto: n.Proto, + State: n.State, + } +} + func (n *NetworkRoute) URI() string { return fmt.Sprintf("route://%s", n.Id) } diff --git a/internal/resource/package.go b/internal/resource/package.go index 5b6c7e9..68db7eb 100644 --- a/internal/resource/package.go +++ b/internal/resource/package.go @@ -82,6 +82,18 @@ func NewPackage() *Package { return &Package{} } +func (p *Package) Clone() Resource { + newp := &Package { + Name: p.Name, + Required: p.Required, + Version: p.Version, + PackageType: p.PackageType, + State: p.State, + } + newp.CreateCommand, newp.ReadCommand, newp.UpdateCommand, newp.DeleteCommand = newp.PackageType.NewCRUD() + return newp +} + func (p *Package) URI() string { return fmt.Sprintf("package://%s?version=%s&type=%s", p.Name, p.Version, p.PackageType) } @@ -301,18 +313,26 @@ func NewAptReadCommand() *Command { value := strings.TrimSpace(fieldKeyValue[1]) switch key { case "Package": - if value == p.Name { - p.State = "present" - } else { + if value != p.Name { p.State = "absent" return nil } + case "Status": + statusFields := strings.SplitN(value, " ", 3) + if len(statusFields) > 1 { + if statusFields[2] == "installed" { + p.State = "present" + } else { + p.State = "absent" + } + } case "Version": p.Version = value } } } } + slog.Info("Extract()", "package", p) return nil } return c diff --git a/internal/resource/resource.go b/internal/resource/resource.go index 8b4c425..2bb803f 100644 --- a/internal/resource/resource.go +++ b/internal/resource/resource.go @@ -14,12 +14,13 @@ import ( type Resource interface { Type() string URI() string - //SetURI(string) error + SetURI(string) error ResolveId(context.Context) string ResourceLoader StateTransformer ResourceReader ResourceValidator + Clone() Resource } type ResourceValidator interface { diff --git a/internal/resource/user.go b/internal/resource/user.go index 088a58e..332f261 100644 --- a/internal/resource/user.go +++ b/internal/resource/user.go @@ -42,6 +42,32 @@ func init() { }) } +func (u *User) Clone() Resource { + return &User { + Name: u.Name, + UID: u.UID, + Group: u.Group, + Groups: u.Groups, + Gecos: u.Gecos, + Home: u.Home, + CreateHome: u.CreateHome, + Shell: u.Shell, + State: u.State, + } +} + +func (u *User) SetURI(uri string) error { + resourceUri, e := url.Parse(uri) + if e == nil { + if resourceUri.Scheme == "user" { + u.Name = resourceUri.Hostname() + } else { + e = fmt.Errorf("%w: %s is not a user", ErrInvalidResourceURI, uri) + } + } + return e +} + func (u *User) URI() string { return fmt.Sprintf("user://%s", u.Name) } diff --git a/internal/signature/ident_test.go b/internal/signature/ident_test.go new file mode 100644 index 0000000..ab8f101 --- /dev/null +++ b/internal/signature/ident_test.go @@ -0,0 +1,14 @@ +// Copyright 2024 Matthew Rich . All rights reserved. + +package signature + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewIdent(t *testing.T) { + i := NewIdent() + assert.NotNil(t, i) +} + diff --git a/internal/source/docsource_test.go b/internal/source/docsource_test.go index 20083ef..f75b0da 100644 --- a/internal/source/docsource_test.go +++ b/internal/source/docsource_test.go @@ -8,8 +8,26 @@ _ "fmt" "github.com/stretchr/testify/assert" _ "log" "testing" + "os" + "log" ) + +var TempDir string + +func TestMain(m *testing.M) { + var err error + TempDir, err = os.MkdirTemp("", "testdocsourcefile") + if err != nil || TempDir == "" { + log.Fatal(err) + } + + rc := m.Run() + + os.RemoveAll(TempDir) + os.Exit(rc) +} + func TestNewDocSource(t *testing.T) { resourceUri := "tar://foo" testFile := NewDocSource(resourceUri) diff --git a/internal/source/http.go b/internal/source/http.go index a899c45..c4d7f7b 100644 --- a/internal/source/http.go +++ b/internal/source/http.go @@ -46,9 +46,9 @@ func (h *HTTP) ExtractResources(filter ResourceSelector) ([]*resource.Document, return documents, err } defer resp.Body.Close() - signature := resp.Header.Get("Signature") + documentSignature := resp.Header.Get("Signature") hash := sha256.New() - sumReadData := iofilter.New(resp.Body, func(p []byte, readn int, readerr error) (n int, err error) { + sumReadData := iofilter.NewReader(resp.Body, func(p []byte, readn int, readerr error) (n int, err error) { hash.Write(p) return }) @@ -79,9 +79,9 @@ func (h *HTTP) ExtractResources(filter ResourceSelector) ([]*resource.Document, index++ } - if signature != "" { - sig := signature.&Ident{} - sig.VerifySum(hash.Sum(nil), signature) + if documentSignature != "" { + sig := &signature.Ident{} + sig.VerifySum(hash.Sum(nil), []byte(documentSignature)) } return documents, nil diff --git a/internal/source/types_test.go b/internal/source/types_test.go index d5b5727..f735bae 100644 --- a/internal/source/types_test.go +++ b/internal/source/types_test.go @@ -13,23 +13,23 @@ _ "context" type MockDocSource struct { InjectType func() string - InjectExtractResources func(uri string, filter ResourceSelector) ([]*resource.Document, error) + InjectExtractResources func(filter ResourceSelector) ([]*resource.Document, error) } func (m *MockDocSource) Type() string { return m.InjectType() } -func (m *MockDocSource) ExtractResources(uri string, filter ResourceSelector) ([]*resource.Document, error) { return m.InjectExtractResources(uri, filter) } +func (m *MockDocSource) ExtractResources(filter ResourceSelector) ([]*resource.Document, error) { return m.InjectExtractResources(filter) } func NewFooDocSource() DocSource { return &MockDocSource{ InjectType: func() string { return "foo" }, - InjectExtractResources: func(uri string, filter ResourceSelector) ([]*resource.Document, error) { return nil,nil }, + InjectExtractResources: func(filter ResourceSelector) ([]*resource.Document, error) { return nil,nil }, } } func NewFileDocSource() DocSource { return &MockDocSource{ InjectType: func() string { return "file" }, - InjectExtractResources: func(uri string, filter ResourceSelector) ([]*resource.Document, error) { return nil,nil }, + InjectExtractResources: func(filter ResourceSelector) ([]*resource.Document, error) { return nil,nil }, } }