diff --git a/cmd/flux/build_kustomization.go b/cmd/flux/build_kustomization.go index d4d8a8a082..9262355cdd 100644 --- a/cmd/flux/build_kustomization.go +++ b/cmd/flux/build_kustomization.go @@ -30,22 +30,23 @@ var buildKsCmd = &cobra.Command{ Use: "kustomization", Aliases: []string{"ks"}, Short: "Build Kustomization", - Long: `The build command queries the Kubernetes API and fetches the specified Flux Kustomization, -then it uses the specified files or path to build the overlay to write the resulting multi-doc YAML to stdout.`, + Long: `The build command queries the Kubernetes API and fetches the specified Flux Kustomization. +It then uses the fetched in cluster flux kustomization to perform needed transformation on the local kustomization.yaml +pointed at by --path. The local kustomization.yaml is generated if it does not exist. Finally it builds the overlays using the local kustomization.yaml, and write the resulting multi-doc YAML to stdout.`, Example: `# Create a new overlay. -flux build kustomization my-app --resources ./path/to/local/manifests`, +flux build kustomization my-app --path ./path/to/local/manifests`, ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), RunE: buildKsCmdRun, } type buildKsFlags struct { - resources string + path string } var buildKsArgs buildKsFlags func init() { - buildKsCmd.Flags().StringVar(&buildKsArgs.resources, "resources", "", "Name of a file containing a file to add to the kustomization file.)") + buildKsCmd.Flags().StringVar(&buildKsArgs.path, "path", "", "Path to the manifests location.)") buildCmd.AddCommand(buildKsCmd) } @@ -55,15 +56,15 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) error { } name := args[0] - if buildKsArgs.resources == "" { - return fmt.Errorf("invalid resource path %q", buildKsArgs.resources) + if buildKsArgs.path == "" { + return fmt.Errorf("invalid resource path %q", buildKsArgs.path) } - if fs, err := os.Stat(buildKsArgs.resources); err != nil || !fs.IsDir() { - return fmt.Errorf("invalid resource path %q", buildKsArgs.resources) + if fs, err := os.Stat(buildKsArgs.path); err != nil || !fs.IsDir() { + return fmt.Errorf("invalid resource path %q", buildKsArgs.path) } - builder, err := kustomization.NewBuilder(rootArgs.kubeconfig, rootArgs.kubecontext, rootArgs.namespace, name, buildKsArgs.resources, kustomization.WithTimeout(rootArgs.timeout)) + builder, err := kustomization.NewBuilder(rootArgs.kubeconfig, rootArgs.kubecontext, rootArgs.namespace, name, buildKsArgs.path, kustomization.WithTimeout(rootArgs.timeout)) if err != nil { return err } diff --git a/cmd/flux/diff.go b/cmd/flux/diff.go new file mode 100644 index 0000000000..d4ee4804e8 --- /dev/null +++ b/cmd/flux/diff.go @@ -0,0 +1,31 @@ +/* +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" +) + +var diffCmd = &cobra.Command{ + Use: "diff", + Short: "Diff a flux resource", + Long: "The diff command is used to do a server-side dry-run on flux resources, then output the diff.", +} + +func init() { + rootCmd.AddCommand(diffCmd) +} diff --git a/cmd/flux/diff_kustomization.go b/cmd/flux/diff_kustomization.go new file mode 100644 index 0000000000..bfc5a19538 --- /dev/null +++ b/cmd/flux/diff_kustomization.go @@ -0,0 +1,77 @@ +/* +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/internal/kustomization" + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" +) + +var diffKsCmd = &cobra.Command{ + Use: "kustomization", + Aliases: []string{"ks"}, + Short: "Diff Kustomization", + Long: `The diff command does a build, then it performs a server-side dry-run and output the diff.`, + Example: `# Create a new overlay. +flux diff kustomization my-app --path ./path/to/local/manifests`, + ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), + RunE: diffKsCmdRun, +} + +type diffKsFlags struct { + path string +} + +var diffKsArgs diffKsFlags + +func init() { + diffKsCmd.Flags().StringVar(&diffKsArgs.path, "path", "", "Name of a file containing a file to add to the kustomization file.)") + diffCmd.AddCommand(diffKsCmd) +} + +func diffKsCmdRun(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("%s name is required", kustomizationType.humanKind) + } + name := args[0] + + if diffKsArgs.path == "" { + return fmt.Errorf("invalid resource path %q", diffKsArgs.path) + } + + if fs, err := os.Stat(diffKsArgs.path); err != nil || !fs.IsDir() { + return fmt.Errorf("invalid resource path %q", diffKsArgs.path) + } + + builder, err := kustomization.NewBuilder(rootArgs.kubeconfig, rootArgs.kubecontext, rootArgs.namespace, name, diffKsArgs.path, kustomization.WithTimeout(rootArgs.timeout)) + if err != nil { + return err + } + + err = builder.Diff() + if err != nil { + return err + } + + return nil + +} diff --git a/go.mod b/go.mod index 86cb588342..6ea59ef94f 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/fluxcd/image-reflector-controller/api v0.15.0 github.com/fluxcd/kustomize-controller/api v0.19.1 github.com/fluxcd/notification-controller/api v0.20.1 + github.com/fluxcd/pkg/apis/kustomize v0.3.1 github.com/fluxcd/pkg/apis/meta v0.10.2 github.com/fluxcd/pkg/runtime v0.12.3 github.com/fluxcd/pkg/ssa v0.10.0 @@ -20,9 +21,13 @@ require ( github.com/fluxcd/pkg/version v0.0.1 github.com/fluxcd/source-controller/api v0.20.1 github.com/go-git/go-git/v5 v5.4.2 + github.com/gonvenience/bunt v1.3.2 + github.com/gonvenience/ytbx v1.4.2 github.com/google/go-cmp v0.5.6 github.com/google/go-containerregistry v0.2.0 github.com/hashicorp/go-retryablehttp v0.7.0 // indirect + github.com/homeport/dyff v1.4.6 + github.com/lucasb-eyer/go-colorful v1.2.0 github.com/manifoldco/promptui v0.9.0 github.com/mattn/go-shellwords v1.0.12 github.com/olekukonko/tablewriter v0.0.4 @@ -42,6 +47,11 @@ require ( sigs.k8s.io/yaml v1.3.0 ) +require ( + github.com/drone/envsubst v1.0.3 + sigs.k8s.io/kustomize/kyaml v0.13.0 +) + require ( cloud.google.com/go v0.81.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect @@ -51,6 +61,7 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/BurntSushi/toml v0.4.1 // indirect github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect github.com/Microsoft/go-winio v0.4.16 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect @@ -64,7 +75,6 @@ require ( github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fluxcd/pkg/apis/acl v0.0.3 // indirect - github.com/fluxcd/pkg/apis/kustomize v0.3.1 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/fvbommel/sortorder v1.0.1 // indirect github.com/go-errors/errors v1.0.1 // indirect @@ -76,6 +86,10 @@ require ( github.com/go-openapi/swag v0.19.14 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/gonvenience/neat v1.3.7 // indirect + github.com/gonvenience/term v1.0.1 // indirect + github.com/gonvenience/text v1.0.6 // indirect + github.com/gonvenience/wrap v1.1.0 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/go-github/v41 v41.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect @@ -87,7 +101,6 @@ require ( github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.6.8 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -96,9 +109,13 @@ require ( github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/hashstructure v1.1.0 // indirect github.com/moby/spdystream v0.2.0 // indirect github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -109,15 +126,18 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect - github.com/sergi/go-diff v1.1.0 // indirect + github.com/sergi/go-diff v1.2.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/stretchr/testify v1.7.0 // indirect + github.com/texttheater/golang-levenshtein v1.0.1 // indirect + github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect github.com/xanzy/go-gitlab v0.54.3 // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect @@ -132,7 +152,6 @@ require ( k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect k8s.io/utils v0.0.0-20211208161948-7d6a63dca704 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect - sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.0 // indirect ) diff --git a/go.sum b/go.sum index dd909e609b..adaf1efb6f 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,8 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= @@ -192,8 +194,8 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/drone/envsubst v1.0.3-0.20200804185402-58bc65f69603 h1:PMzPM0wCHDrXlO7TlEq5lqlhGKHEgSnUR4YMSEVKrQ0= -github.com/drone/envsubst v1.0.3-0.20200804185402-58bc65f69603/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= +github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= +github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= @@ -361,6 +363,22 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= +github.com/gonvenience/bunt v1.1.3/go.mod h1:hZ898ZprNWgaVlq4s1ivsJu3AY+3wrlJadF5Gp7Yq2c= +github.com/gonvenience/bunt v1.3.1/go.mod h1:G+d3dJBxxOqV2oca96psgAnPABVC9QptEifjVqPu+mo= +github.com/gonvenience/bunt v1.3.2 h1:gDiyTDfPf87fCtIbFzvENrmlnDZYbENdhhRW2kun/tw= +github.com/gonvenience/bunt v1.3.2/go.mod h1:oTOZqb1TVL1KqZm57zUCJCiwUliNfY8+d3QndOVqxpg= +github.com/gonvenience/neat v1.3.6/go.mod h1:wv0eXmvwFfpuS5bpf2fIofXQvf8W7HTdSzKfGCYbIe8= +github.com/gonvenience/neat v1.3.7 h1:k4shy3sgSBfUk9LTN51naxVIkB6BlGaxH0ReERO92zw= +github.com/gonvenience/neat v1.3.7/go.mod h1:Y4eeQH3GEBvjmLoMiu4oWGyOopGDaI2/y2jwcVfvlvs= +github.com/gonvenience/term v1.0.0/go.mod h1:wohD4Iqso9Eol7qc2VnNhSFFhZxok5PvO7pZhdrAn4E= +github.com/gonvenience/term v1.0.1 h1:8bg2O0ox0Ss64nnUxW5AXlSHhllc8dTOSKuKu6uoGpw= +github.com/gonvenience/term v1.0.1/go.mod h1:TrQEhxBNE/ng5kTV+S0OvQowTDJSfhlBeZbcOmTR6qI= +github.com/gonvenience/text v1.0.6 h1:9JH9fz0BL0NX4uKmjLuVcsBKiniPa+XLpf8KH9so44U= +github.com/gonvenience/text v1.0.6/go.mod h1:9U5WbkT/5wR5+aNMR4HucamY+HgVMEn+UbF78XHmUio= +github.com/gonvenience/wrap v1.1.0 h1:d8gEZrXS/zg4BC1q0U4nHpPIh5k6muKpQ1+rQFBwpYc= +github.com/gonvenience/wrap v1.1.0/go.mod h1:L47Cm1sK1G8QmFAYQfkHcF/sQ1IBJUa0u4sjqiLqPdM= +github.com/gonvenience/ytbx v1.4.2 h1:fpgOpsQ+gwTPqiatki0aY7q3BEjt7EcwiI5b+D0Qjvg= +github.com/gonvenience/ytbx v1.4.2/go.mod h1:GkUMPGH5qZSg1S8L6u9XNI9hJ4L1yKSQFIA4J8vaPdY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= @@ -424,6 +442,7 @@ github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97Dwqy github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -468,6 +487,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/homeport/dyff v1.4.6 h1:ReC8Hi1I6SkmPmNOqGl9EUlZctx+6AloCzSulqwHge8= +github.com/homeport/dyff v1.4.6/go.mod h1:DBCaTwJUIQLNQxNOKTce/OgRxCwwa8erBdN88bBfb9Y= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -526,6 +547,9 @@ github.com/ktrysmt/go-bitbucket v0.9.34/go.mod h1:FWxy2UK7GlK5b0NSJGc5hPqnssVlkN github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -539,9 +563,15 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 h1:BXxTozrOU8zgC5dkpn3J6NTRdoP+hjok/e+ACr4Hibk= +github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3/go.mod h1:x1uk6vxTiVuNt6S5R2UYgdhpj3oKojXvOXauHZ7dEnI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -556,10 +586,15 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= +github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -600,6 +635,11 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/ginkgo v1.15.1/go.mod h1:Dd6YFfwBW84ETqqtL0CPyPXillHgY6XhQH3uuCCTr/o= +github.com/onsi/ginkgo v1.15.2/go.mod h1:Dd6YFfwBW84ETqqtL0CPyPXillHgY6XhQH3uuCCTr/o= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= @@ -609,6 +649,10 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= +github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= +github.com/onsi/gomega v1.12.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= @@ -670,8 +714,9 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -720,12 +765,16 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= +github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/vdemeester/k8s-pkg-credentialprovider v1.18.1-0.20201019120933-f1d16962a4db/go.mod h1:grWy0bkr1XO6hqbaaCKaPXqkBVlMGHYG6PGykktwbJc= +github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= +github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xanzy/go-gitlab v0.54.3 h1:fPfZ3Jcu5dPc3xyIYtAALZsEgoyKNFNuULD+TdJ7Zvk= github.com/xanzy/go-gitlab v0.54.3/go.mod h1:F0QEXwmqiBUxCgJm8fE9S+1veX4XC9Z4cfaAbqwk4YM= @@ -865,6 +914,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -880,6 +930,7 @@ golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210917163549-3c21e5b27794/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -912,6 +963,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -925,6 +977,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -948,6 +1001,7 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -990,11 +1044,14 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 h1:M69LAlWZCshgp0QSzyDcSsSIejIEeuaCVpmwcKwyLMk= golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/kustomization/build.go b/internal/kustomization/build.go index 4b5279a4a4..b2704c80dd 100644 --- a/internal/kustomization/build.go +++ b/internal/kustomization/build.go @@ -27,8 +27,10 @@ import ( "github.com/fluxcd/flux2/internal/utils" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/resource" @@ -44,6 +46,7 @@ var defaultTimeout = 80 * time.Second // and overlays the manifests with the resources specified in the resourcesPath type Builder struct { client client.WithWatch + restMapper meta.RESTMapper name string namespace string resourcesPath string @@ -68,8 +71,18 @@ func NewBuilder(kubeconfig string, kubecontext string, namespace, name, resource return nil, err } + cfg, err := utils.KubeConfig(kubeconfig, kubecontext) + if err != nil { + return nil, err + } + restMapper, err := apiutil.NewDynamicRESTMapper(cfg) + if err != nil { + return nil, err + } + b := &Builder{ client: kubeClient, + restMapper: restMapper, name: name, namespace: namespace, resourcesPath: resources, @@ -154,8 +167,8 @@ func (b *Builder) build() (resmap.ResMap, error) { // store the kustomization object b.kustomization = k - // restore the kustomization.yaml - err = restore(saved, b.resourcesPath) + // overwrite the kustomization.yaml to make sure it's clean + err = overwrite(saved, b.resourcesPath) if err != nil { return nil, fmt.Errorf("failed to restore kustomization.yaml: %w", err) } @@ -205,24 +218,27 @@ func trimSopsData(res *resource.Resource) error { for k, v := range dataMap { data, err := base64.StdEncoding.DecodeString(v) if err != nil { - fmt.Println(fmt.Errorf("failed to decode secret data: %w", err)) + if _, ok := err.(base64.CorruptInputError); ok { + return fmt.Errorf("failed to decode secret data: %w", err) + } } - if bytes.Contains(data, []byte("sops")) { + if bytes.Contains(data, []byte("sops")) && bytes.Contains(data, []byte("ENC[")) { dataMap[k] = sopsMess } } + res.SetDataMap(dataMap) } return nil } -func restore(saved []byte, dirPath string) error { +func overwrite(saved []byte, dirPath string) error { kfile := filepath.Join(dirPath, konfig.DefaultKustomizationFileName()) err := os.WriteFile(kfile, saved, 0644) if err != nil { - return fmt.Errorf("failed to restore kustomization.yaml: %w", err) + return fmt.Errorf("failed to overwrite kustomization.yaml: %w", err) } return nil } diff --git a/internal/kustomization/build_test.go b/internal/kustomization/build_test.go index 7834e108c6..f39b22aab4 100644 --- a/internal/kustomization/build_test.go +++ b/internal/kustomization/build_test.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/kustomize/kyaml/yaml" ) -func TestSanitizeResources(t *testing.T) { +func TestTrimSopsData(t *testing.T) { testCases := []struct { name string yamlStr string @@ -75,18 +75,18 @@ type: Opaque { name: "secret with basic auth", yamlStr: `apiVersion: v1 +data: + password: cGFzc3dvcmQK + username: YWRtaW4K kind: Secret metadata: name: secret-basic-auth type: kubernetes.io/basic-auth -data: - username: admin - password: password `, expected: `apiVersion: v1 data: - password: password - username: admin + password: cGFzc3dvcmQK + username: YWRtaW4K kind: Secret metadata: name: secret-basic-auth diff --git a/internal/kustomization/diff.go b/internal/kustomization/diff.go new file mode 100644 index 0000000000..4bf2f9820c --- /dev/null +++ b/internal/kustomization/diff.go @@ -0,0 +1,287 @@ +package kustomization + +import ( + "bytes" + "context" + "encoding/base64" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" + "github.com/fluxcd/pkg/ssa" + "github.com/gonvenience/bunt" + "github.com/gonvenience/ytbx" + "github.com/google/go-cmp/cmp" + "github.com/homeport/dyff/pkg/dyff" + "github.com/lucasb-eyer/go-colorful" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling" + "sigs.k8s.io/cli-utils/pkg/object" + "sigs.k8s.io/yaml" +) + +const ( + controllerName = "kustomize-controller" + controllerGroup = "kustomize.toolkit.fluxcd.io" +) + +func (b *Builder) manager() (*ssa.ResourceManager, error) { + statusPoller := polling.NewStatusPoller(b.client, b.restMapper) + owner := ssa.Owner{ + Field: controllerName, + Group: controllerGroup, + } + + return ssa.NewResourceManager(b.client, statusPoller, owner), nil +} + +func (b *Builder) Diff() error { + res, err := b.Build() + if err != nil { + return err + } + // convert the build result into Kubernetes unstructured objects + objects, err := ssa.ReadObjects(bytes.NewReader(res)) + if err != nil { + return err + } + + resourceManager, err := b.manager() + if err != nil { + return err + } + + resourceManager.SetOwnerLabels(objects, b.kustomization.GetName(), b.kustomization.GetNamespace()) + + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + + if err := ssa.SetNativeKindsDefaults(objects); err != nil { + return err + } + + // create an inventory of objects to be reconciled + newInventory := newInventory() + for _, obj := range objects { + change, liveObject, mergedObject, err := resourceManager.Diff(ctx, obj) + if err != nil { + if b.kustomization.Spec.Force && strings.Contains(err.Error(), "immutable") { + writeString(fmt.Sprintf("► %s created", obj.GetName()), bunt.Green) + } else { + writeString(fmt.Sprint(`✗`, err), bunt.Red) + } + continue + } + + // if the object is a sops secret, we need to + // make sure we diff only if the keys are different + if obj.GetKind() == "Secret" && change.Action == string(ssa.ConfiguredAction) { + diffSopsSecret(obj, liveObject, mergedObject, change) + } + + if change.Action == string(ssa.CreatedAction) { + writeString(fmt.Sprintf("► %s created", change.Subject), bunt.Green) + } + + if change.Action == string(ssa.ConfiguredAction) { + writeString(fmt.Sprintf("► %s drifted", change.Subject), bunt.WhiteSmoke) + liveFile, mergedFile, tmpDir, err := writeYamls(liveObject, mergedObject) + if err != nil { + return err + } + defer cleanupDir(tmpDir) + + err = diff(liveFile, mergedFile) + if err != nil { + return err + } + } + + addObjectsToInventory(newInventory, change) + } + + if b.kustomization.Spec.Prune { + oldStatus := b.kustomization.Status.DeepCopy() + if oldStatus.Inventory != nil { + diffObjects, err := diffInventory(oldStatus.Inventory, newInventory) + if err != nil { + return err + } + for _, object := range diffObjects { + writeString(fmt.Sprintf("► %s deleted", ssa.FmtUnstructured(object)), bunt.OrangeRed) + } + } + } + + return nil +} + +func writeYamls(liveObject, mergedObject *unstructured.Unstructured) (string, string, string, error) { + tmpDir, err := os.MkdirTemp("", "") + if err != nil { + return "", "", "", err + } + + liveYAML, _ := yaml.Marshal(liveObject) + liveFile := filepath.Join(tmpDir, "live.yaml") + if err := os.WriteFile(liveFile, liveYAML, 0644); err != nil { + return "", "", "", err + } + + mergedYAML, _ := yaml.Marshal(mergedObject) + mergedFile := filepath.Join(tmpDir, "merged.yaml") + if err := os.WriteFile(mergedFile, mergedYAML, 0644); err != nil { + return "", "", "", err + } + + return liveFile, mergedFile, tmpDir, nil +} + +func writeString(t string, color colorful.Color) { + fmt.Println(bunt.Style( + t, + bunt.EachLine(), + bunt.Foreground(color), + )) +} + +func cleanupDir(dir string) error { + return os.RemoveAll(dir) +} + +func diff(liveFile, mergedFile string) error { + from, to, err := ytbx.LoadFiles(liveFile, mergedFile) + if err != nil { + return fmt.Errorf("failed to load input files: %w", err) + } + + report, err := dyff.CompareInputFiles(from, to, + dyff.IgnoreOrderChanges(false), + dyff.KubernetesEntityDetection(true), + ) + if err != nil { + return fmt.Errorf("failed to compare input files: %w", err) + } + + reportWriter := &dyff.HumanReport{ + Report: report, + OmitHeader: true, + } + + if err := reportWriter.WriteReport(os.Stdout); err != nil { + return fmt.Errorf("failed to print report: %w", err) + } + + return nil +} + +func diffSopsSecret(obj, liveObject, mergedObject *unstructured.Unstructured, change *ssa.ChangeSetEntry) { + data := obj.Object["data"] + for _, v := range data.(map[string]interface{}) { + v, err := base64.StdEncoding.DecodeString(v.(string)) + if err != nil { + fmt.Println(err) + } + if bytes.Contains(v, []byte(mask)) { + if liveObject != nil && mergedObject != nil { + change.Action = string(ssa.UnchangedAction) + dataLive := liveObject.Object["data"].(map[string]interface{}) + dataMerged := mergedObject.Object["data"].(map[string]interface{}) + if cmp.Diff(keys(dataLive), keys(dataMerged)) != "" { + change.Action = string(ssa.ConfiguredAction) + } + } + } + } +} + +func keys(m map[string]interface{}) []string { + keys := make([]string, len(m)) + i := 0 + for k := range m { + keys[i] = k + i++ + } + return keys +} + +// diffInventory returns the slice of objects that do not exist in the target inventory. +func diffInventory(inv *kustomizev1.ResourceInventory, target *kustomizev1.ResourceInventory) ([]*unstructured.Unstructured, error) { + versionOf := func(i *kustomizev1.ResourceInventory, objMetadata object.ObjMetadata) string { + for _, entry := range i.Entries { + if entry.ID == objMetadata.String() { + return entry.Version + } + } + return "" + } + + objects := make([]*unstructured.Unstructured, 0) + aList, err := listMetaInInventory(inv) + if err != nil { + return nil, err + } + + bList, err := listMetaInInventory(target) + if err != nil { + return nil, err + } + + list := aList.Diff(bList) + if len(list) == 0 { + return objects, nil + } + + for _, metadata := range list { + u := &unstructured.Unstructured{} + u.SetGroupVersionKind(schema.GroupVersionKind{ + Group: metadata.GroupKind.Group, + Kind: metadata.GroupKind.Kind, + Version: versionOf(inv, metadata), + }) + u.SetName(metadata.Name) + u.SetNamespace(metadata.Namespace) + objects = append(objects, u) + } + + sort.Sort(ssa.SortableUnstructureds(objects)) + return objects, nil +} + +// listMetaInInventory returns the inventory entries as object.ObjMetadata objects. +func listMetaInInventory(inv *kustomizev1.ResourceInventory) (object.ObjMetadataSet, error) { + var metas []object.ObjMetadata + for _, e := range inv.Entries { + m, err := object.ParseObjMetadata(e.ID) + if err != nil { + return metas, err + } + metas = append(metas, m) + } + + return metas, nil +} + +func newInventory() *kustomizev1.ResourceInventory { + return &kustomizev1.ResourceInventory{ + Entries: []kustomizev1.ResourceRef{}, + } +} + +// addObjectsToInventory extracts the metadata from the given objects and adds it to the inventory. +func addObjectsToInventory(inv *kustomizev1.ResourceInventory, entry *ssa.ChangeSetEntry) error { + if entry == nil { + return nil + } + + inv.Entries = append(inv.Entries, kustomizev1.ResourceRef{ + ID: entry.ObjMetadata.String(), + Version: entry.GroupVersion, + }) + + return nil +}