From 42ca1ba5d16fae1ea52e86e1fcd56fba0b78b73e Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 8 Apr 2024 13:04:01 +0300 Subject: [PATCH] kustomize: Implement envsubst strict mode Signed-off-by: Stefan Prodan --- kustomize/go.mod | 5 ++- kustomize/go.sum | 14 +++---- kustomize/kustomize_varsub.go | 37 +++++++++++-------- kustomize/kustomize_varsub_test.go | 15 ++++++-- kustomize/testdata/resources/deployment.yaml | 2 +- kustomize/testdata/varsubstrict/config.yaml | 10 +++++ .../testdata/varsubstrict/kustomization.yml | 5 +++ 7 files changed, 59 insertions(+), 29 deletions(-) create mode 100644 kustomize/testdata/varsubstrict/config.yaml create mode 100644 kustomize/testdata/varsubstrict/kustomization.yml diff --git a/kustomize/go.mod b/kustomize/go.mod index 51c4172fe..362a1dc75 100644 --- a/kustomize/go.mod +++ b/kustomize/go.mod @@ -4,14 +4,15 @@ go 1.22 replace ( github.com/fluxcd/pkg/apis/kustomize => ../apis/kustomize + github.com/fluxcd/pkg/envsubst => ../envsubst github.com/fluxcd/pkg/sourceignore => ../sourceignore ) require ( - github.com/drone/envsubst v1.0.3 github.com/fluxcd/pkg/apis/kustomize v1.4.0 + github.com/fluxcd/pkg/envsubst v1.0.0 github.com/fluxcd/pkg/sourceignore v0.6.0 - github.com/go-git/go-git/v5 v5.11.0 + github.com/go-git/go-git/v5 v5.12.0 github.com/onsi/gomega v1.32.0 github.com/otiai10/copy v1.14.0 k8s.io/api v0.29.3 diff --git a/kustomize/go.sum b/kustomize/go.sum index 336e02d4c..75838e666 100644 --- a/kustomize/go.sum +++ b/kustomize/go.sum @@ -16,8 +16,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= -github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -34,8 +32,8 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -142,8 +140,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -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/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -155,8 +153,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -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/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/kustomize/kustomize_varsub.go b/kustomize/kustomize_varsub.go index 4b20b5382..bf0dfe86b 100644 --- a/kustomize/kustomize_varsub.go +++ b/kustomize/kustomize_varsub.go @@ -23,7 +23,6 @@ import ( "regexp" "strings" - "github.com/drone/envsubst" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -32,6 +31,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/yaml" + + "github.com/fluxcd/pkg/envsubst" ) const ( @@ -51,13 +52,16 @@ const ( // SubstituteVariables replaces the vars with their values in the specified resource. // If a resource is labeled or annotated with // 'kustomize.toolkit.fluxcd.io/substitute: disabled' the substitution is skipped. -// if dryRun is true, this means we should not attempt to talk to the cluster. +// If dryRun is true, this means we should not attempt to talk to the cluster. +// If strict is true, the substitution will fail if a var without a default value +// is declared in files but is missing from the input vars. func SubstituteVariables( ctx context.Context, kubeClient client.Client, kustomization unstructured.Unstructured, res *resource.Resource, - dryRun bool) (*resource.Resource, error) { + dryRun bool, + strict bool) (*resource.Resource, error) { resData, err := res.AsYAML() if err != nil { return nil, err @@ -94,9 +98,9 @@ func SubstituteVariables( // run bash variable substitutions if len(vars) > 0 { - jsonData, err := varSubstitution(resData, vars) + jsonData, err := varSubstitution(resData, vars, strict) if err != nil { - return nil, fmt.Errorf("YAMLToJSON: %w", err) + return nil, fmt.Errorf("envsubst error: %w", err) } err = res.UnmarshalJSON(jsonData) if err != nil { @@ -118,25 +122,25 @@ func loadVars(ctx context.Context, kubeClient client.Client, kustomization unstr namespacedName := types.NamespacedName{Namespace: kustomization.GetNamespace(), Name: reference.Name} switch reference.Kind { case "ConfigMap": - resource := &corev1.ConfigMap{} - if err := kubeClient.Get(ctx, namespacedName, resource); err != nil { + cm := &corev1.ConfigMap{} + if err := kubeClient.Get(ctx, namespacedName, cm); err != nil { if reference.Optional && apierrors.IsNotFound(err) { continue } return nil, fmt.Errorf("substitute from 'ConfigMap/%s' error: %w", reference.Name, err) } - for k, v := range resource.Data { + for k, v := range cm.Data { vars[k] = strings.ReplaceAll(v, "\n", "") } case "Secret": - resource := &corev1.Secret{} - if err := kubeClient.Get(ctx, namespacedName, resource); err != nil { + secret := &corev1.Secret{} + if err := kubeClient.Get(ctx, namespacedName, secret); err != nil { if reference.Optional && apierrors.IsNotFound(err) { continue } return nil, fmt.Errorf("substitute from 'Secret/%s' error: %w", reference.Name, err) } - for k, v := range resource.Data { + for k, v := range secret.Data { vars[k] = strings.ReplaceAll(string(v), "\n", "") } } @@ -145,7 +149,7 @@ func loadVars(ctx context.Context, kubeClient client.Client, kustomization unstr return vars, nil } -func varSubstitution(data []byte, vars map[string]string) ([]byte, error) { +func varSubstitution(data []byte, vars map[string]string, strict bool) ([]byte, error) { r, _ := regexp.Compile(varsubRegex) for v := range vars { if !r.MatchString(v) { @@ -153,8 +157,12 @@ func varSubstitution(data []byte, vars map[string]string) ([]byte, error) { } } - output, err := envsubst.Eval(string(data), func(s string) string { - return vars[s] + output, err := envsubst.Eval(string(data), func(s string) (string, bool) { + if strict { + v, exists := vars[s] + return v, exists + } + return vars[s], true }) if err != nil { return nil, fmt.Errorf("variable substitution failed: %w", err) @@ -194,5 +202,4 @@ func getSubstituteFrom(kustomization unstructured.Unstructured) ([]SubstituteRef } return nil, resultErr - } diff --git a/kustomize/kustomize_varsub_test.go b/kustomize/kustomize_varsub_test.go index 270a0dc81..2534e420c 100644 --- a/kustomize/kustomize_varsub_test.go +++ b/kustomize/kustomize_varsub_test.go @@ -22,9 +22,10 @@ import ( "strings" "testing" - "github.com/fluxcd/pkg/kustomize" . "github.com/onsi/gomega" "sigs.k8s.io/kustomize/kyaml/filesys" + + "github.com/fluxcd/pkg/kustomize" ) func TestKustomizationVarsub(t *testing.T) { @@ -50,8 +51,8 @@ func TestKustomizationVarsub(t *testing.T) { resMap, err := kustomize.Build(fs, "./testdata/resources/") g.Expect(err).NotTo(HaveOccurred()) for _, res := range resMap.Resources() { - outRes, err := kustomize.SubstituteVariables(context.Background(), kubeClient, clientObjects[0], res, false) - + outRes, err := kustomize.SubstituteVariables(context.Background(), + kubeClient, clientObjects[0], res, false, false) g.Expect(err).NotTo(HaveOccurred()) if outRes != nil { @@ -69,4 +70,12 @@ func TestKustomizationVarsub(t *testing.T) { g.Expect(err).NotTo(HaveOccurred()) g.Expect(string(resources)).To(Equal(string(expected))) + + // Test with strict mode + strictMapRes, err := kustomize.Build(fs, "./testdata/varsubstrict/") + g.Expect(err).NotTo(HaveOccurred()) + _, err = kustomize.SubstituteVariables(context.Background(), + kubeClient, clientObjects[0], strictMapRes.Resources()[0], false, true) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("variable not set")) } diff --git a/kustomize/testdata/resources/deployment.yaml b/kustomize/testdata/resources/deployment.yaml index ad1441585..473bf3192 100644 --- a/kustomize/testdata/resources/deployment.yaml +++ b/kustomize/testdata/resources/deployment.yaml @@ -13,7 +13,7 @@ spec: template: metadata: annotations: - prometheus.io/scrape: ${prometheus_scrape:=false} + prometheus.io/scrape: ${prometheus_scrape:=false}${missing} prometheus.io/port: ${prometheus_port} labels: app: app diff --git a/kustomize/testdata/varsubstrict/config.yaml b/kustomize/testdata/varsubstrict/config.yaml new file mode 100644 index 000000000..60ad01ce4 --- /dev/null +++ b/kustomize/testdata/varsubstrict/config.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + environment: ${cluster_env:=dev} + region: ${cluster_region} + name: app-vars-strict + namespace: apps +data: + missing: ${missing} diff --git a/kustomize/testdata/varsubstrict/kustomization.yml b/kustomize/testdata/varsubstrict/kustomization.yml new file mode 100644 index 000000000..633d12c30 --- /dev/null +++ b/kustomize/testdata/varsubstrict/kustomization.yml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: apps +resources: +- ./config.yaml