diff --git a/context/envvar.go b/context/envvar.go index 37d82dfb..a64d7897 100644 --- a/context/envvar.go +++ b/context/envvar.go @@ -1,11 +1,17 @@ package context import ( - "context" + "bytes" + "compress/gzip" + "encoding/base64" + "encoding/json" "fmt" "strings" "time" + "github.com/RaveNoX/go-jsonmerge" + "github.com/ohler55/ojg/jp" + "github.com/flanksource/duty/types" "github.com/patrickmn/go-cache" authenticationv1 "k8s.io/api/authentication/v1" @@ -16,6 +22,8 @@ import ( // purges expired items every 10 minutes var envCache = cache.New(5*time.Minute, 10*time.Minute) +const helmSecretType = "helm.sh/release.v1" + func GetEnvValueFromCache(ctx Context, input types.EnvVar, namespace string) (string, error) { if namespace == "" { namespace = ctx.GetNamespace() @@ -24,16 +32,16 @@ func GetEnvValueFromCache(ctx Context, input types.EnvVar, namespace string) (st return input.ValueStatic, nil } if input.ValueFrom.SecretKeyRef != nil { - value, err := GetSecretFromCache(ctx, namespace, input.ValueFrom.SecretKeyRef.Name, input.ValueFrom.SecretKeyRef.Key) - return value, err + return GetSecretFromCache(ctx, namespace, input.ValueFrom.SecretKeyRef.Name, input.ValueFrom.SecretKeyRef.Key) } if input.ValueFrom.ConfigMapKeyRef != nil { - value, err := GetConfigMapFromCache(ctx, namespace, input.ValueFrom.ConfigMapKeyRef.Name, input.ValueFrom.ConfigMapKeyRef.Key) - return value, err + return GetConfigMapFromCache(ctx, namespace, input.ValueFrom.ConfigMapKeyRef.Name, input.ValueFrom.ConfigMapKeyRef.Key) + } + if input.ValueFrom.HelmRef != nil { + return GetHelmValueFromCache(ctx, namespace, input.ValueFrom.HelmRef.Name, input.ValueFrom.HelmRef.Key) } if input.ValueFrom.ServiceAccount != nil { - value, err := GetServiceAccountTokenFromCache(ctx, namespace, *input.ValueFrom.ServiceAccount) - return value, err + return GetServiceAccountTokenFromCache(ctx, namespace, *input.ValueFrom.ServiceAccount) } return "", nil @@ -47,13 +55,81 @@ func GetEnvStringFromCache(ctx Context, env string, namespace string) (string, e return GetEnvValueFromCache(ctx, envvar, namespace) } +func GetHelmValueFromCache(ctx Context, namespace, releaseName, key string) (string, error) { + id := fmt.Sprintf("helm/%s/%s/%s", namespace, releaseName, key) + if value, found := envCache.Get(id); found { + return value.(string), nil + } + + keyJPExpr, err := jp.ParseString(key) + if err != nil { + return "", fmt.Errorf("could not parse key:%s. must be a valid jsonpath expression. %w", key, err) + } + + secretList, err := ctx.Kubernetes().CoreV1().Secrets(namespace).List(ctx, metav1.ListOptions{ + FieldSelector: fmt.Sprintf("type=%s", helmSecretType), + LabelSelector: fmt.Sprintf("status=deployed,name=%s", releaseName), + Limit: 1, + }) + if err != nil { + return "", fmt.Errorf("could not get secrets in namespace: %s: %w", namespace, err) + } + + if len(secretList.Items) == 0 { + return "", fmt.Errorf("a deployed helm secret was not found %s/%s", namespace, releaseName) + } + secret := secretList.Items[0] + + if secret.Name == "" { + return "", fmt.Errorf("could not find helm secret %s/%s", namespace, releaseName) + } + + release, err := base64.StdEncoding.DecodeString(string(secret.Data["release"])) + if err != nil { + return "", fmt.Errorf("could not base64 decode helm secret %s/%s: %w", namespace, secret.Name, err) + } + + gzipReader, err := gzip.NewReader(bytes.NewReader(release)) + if err != nil { + return "", fmt.Errorf("could not unzip helm secret %s/%s: %w", namespace, secret.Name, err) + } + + var rawJson map[string]any + if err := json.NewDecoder(gzipReader).Decode(&rawJson); err != nil { + return "", fmt.Errorf("could not decode unzipped helm secret %s/%s: %w", namespace, secret.Name, err) + } + + var chartValues any = map[string]any{} + if chart, ok := rawJson["chart"].(map[string]any); ok { + chartValues = chart["values"] + } + + merged, info := jsonmerge.Merge(rawJson["config"], chartValues) + if len(info.Errors) != 0 { + return "", fmt.Errorf("could not merge helm config and values of helm secret %s/%s: %v", namespace, secret.Name, info.Errors) + } + + results := keyJPExpr.Get(merged) + if len(results) == 0 { + return "", fmt.Errorf("could not find key %s in merged helm secret %s/%s: %w", key, namespace, secret.Name, err) + } + + output, err := json.Marshal(results[0]) + if err != nil { + return "", fmt.Errorf("could not marshal merged helm secret %s/%s: %w", namespace, secret.Name, err) + } + + envCache.Set(id, string(output), 5*time.Minute) + return string(output), nil +} + func GetSecretFromCache(ctx Context, namespace, name, key string) (string, error) { id := fmt.Sprintf("secret/%s/%s/%s", namespace, name, key) if value, found := envCache.Get(id); found { return value.(string), nil } - secret, err := ctx.Kubernetes().CoreV1().Secrets(namespace).Get(context.Background(), name, metav1.GetOptions{}) + secret, err := ctx.Kubernetes().CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) if secret == nil { return "", fmt.Errorf("could not get contents of secret %s/%s: %w", namespace, name, err) } @@ -76,7 +152,7 @@ func GetConfigMapFromCache(ctx Context, namespace, name, key string) (string, er if value, found := envCache.Get(id); found { return value.(string), nil } - configMap, err := ctx.Kubernetes().CoreV1().ConfigMaps(namespace).Get(context.Background(), name, metav1.GetOptions{}) + configMap, err := ctx.Kubernetes().CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{}) if configMap == nil { return "", fmt.Errorf("could not get contents of configmap %s/%s: %w", namespace, name, err) } diff --git a/go.mod b/go.mod index befee4eb..1c8bf05d 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,13 @@ go 1.20 require ( ariga.io/atlas v0.14.2 cloud.google.com/go/cloudsqlconn v1.5.1 + github.com/RaveNoX/go-jsonmerge v1.0.0 github.com/fergusstrange/embedded-postgres v1.21.0 github.com/flanksource/commons v1.17.1 github.com/flanksource/gomplate/v3 v3.20.16 github.com/flanksource/kommons v0.31.4 github.com/flanksource/postq v0.1.1 + github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.4.0 github.com/hashicorp/hcl/v2 v2.18.1 github.com/hexops/gotextdiff v1.0.3 @@ -18,6 +20,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/liamylian/jsontime/v2 v2.0.0 github.com/lib/pq v1.10.9 + github.com/ohler55/ojg v1.20.3 github.com/onsi/ginkgo/v2 v2.9.4 github.com/onsi/gomega v1.27.6 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -75,7 +78,6 @@ require ( github.com/google/btree v1.0.1 // indirect github.com/google/cel-go v0.18.1 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect github.com/google/s2a-go v0.1.7 // indirect diff --git a/go.sum b/go.sum index 4738ae28..7a1e9f5c 100644 --- a/go.sum +++ b/go.sum @@ -631,6 +631,9 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMo github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/RaveNoX/go-jsonmerge v1.0.0 h1:2e0nqnadoGUP8rAvcA0hkQelZreVO5X3BHomT2XMrAk= +github.com/RaveNoX/go-jsonmerge v1.0.0/go.mod h1:qYM/NA77LhO4h51JJM7Z+xBU3ovqrNIACZe+SkSNVFo= github.com/TomOnTime/utfutil v0.0.0-20210710122150-437f72b26edf h1:+GdVyvpzTy3UFAS1+hbTqm9Mk0U1Xrocm28s/E2GWz0= github.com/TomOnTime/utfutil v0.0.0-20210710122150-437f72b26edf/go.mod h1:FiuynIwe98RFhWI8nZ0dnsldPVsBy9rHH1hn2WYwme4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= @@ -675,6 +678,7 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -1072,6 +1076,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -1169,6 +1174,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/ohler55/ojg v1.20.3 h1:Z+fnElsA/GbI5oiT726qJaG4Ca9q5l7UO68Qd0PtkD4= +github.com/ohler55/ojg v1.20.3/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1286,6 +1293,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn 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/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= diff --git a/hack/migrate/go.mod b/hack/migrate/go.mod index 8daea174..c85e57b7 100644 --- a/hack/migrate/go.mod +++ b/hack/migrate/go.mod @@ -19,6 +19,7 @@ require ( github.com/AlekSi/pointer v1.1.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/RaveNoX/go-jsonmerge v1.0.0 // indirect github.com/TomOnTime/utfutil v0.0.0-20210710122150-437f72b26edf // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect @@ -94,6 +95,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/ohler55/ojg v1.20.3 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/hack/migrate/go.sum b/hack/migrate/go.sum index 6714b0d4..e7c9e601 100644 --- a/hack/migrate/go.sum +++ b/hack/migrate/go.sum @@ -631,6 +631,9 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMo github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/RaveNoX/go-jsonmerge v1.0.0 h1:2e0nqnadoGUP8rAvcA0hkQelZreVO5X3BHomT2XMrAk= +github.com/RaveNoX/go-jsonmerge v1.0.0/go.mod h1:qYM/NA77LhO4h51JJM7Z+xBU3ovqrNIACZe+SkSNVFo= github.com/TomOnTime/utfutil v0.0.0-20210710122150-437f72b26edf h1:+GdVyvpzTy3UFAS1+hbTqm9Mk0U1Xrocm28s/E2GWz0= github.com/TomOnTime/utfutil v0.0.0-20210710122150-437f72b26edf/go.mod h1:FiuynIwe98RFhWI8nZ0dnsldPVsBy9rHH1hn2WYwme4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= @@ -675,6 +678,7 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -1063,6 +1067,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -1159,6 +1164,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/ohler55/ojg v1.20.3 h1:Z+fnElsA/GbI5oiT726qJaG4Ca9q5l7UO68Qd0PtkD4= +github.com/ohler55/ojg v1.20.3/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1275,6 +1282,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn 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/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= diff --git a/types/envvar.go b/types/envvar.go index ec1f83ba..54b4231a 100644 --- a/types/envvar.go +++ b/types/envvar.go @@ -57,8 +57,9 @@ func (e EnvVar) IsEmpty() bool { type EnvVarSource struct { // ServiceAccount specifies the service account whose token should be fetched ServiceAccount *string `json:"serviceAccount,omitempty" yaml:"serviceAccount,omitempty" protobuf:"bytes,1,opt,name=serviceAccount"` - ConfigMapKeyRef *ConfigMapKeySelector `json:"configMapKeyRef,omitempty" yaml:"configMapKeyRef,omitempty" protobuf:"bytes,1,opt,name=configMapKeyRef"` - SecretKeyRef *SecretKeySelector `json:"secretKeyRef,omitempty" yaml:"secretKeyRef,omitempty" protobuf:"bytes,2,opt,name=secretKeyRef"` + HelmRef *HelmRefKeySelector `json:"helmRef,omitempty" yaml:"helmRef,omitempty" protobuf:"bytes,2,opt,name=helmRef"` + ConfigMapKeyRef *ConfigMapKeySelector `json:"configMapKeyRef,omitempty" yaml:"configMapKeyRef,omitempty" protobuf:"bytes,3,opt,name=configMapKeyRef"` + SecretKeyRef *SecretKeySelector `json:"secretKeyRef,omitempty" yaml:"secretKeyRef,omitempty" protobuf:"bytes,4,opt,name=secretKeyRef"` } func (e EnvVarSource) String() string { @@ -71,9 +72,23 @@ func (e EnvVarSource) String() string { if e.ServiceAccount != nil { return "serviceaccount://" + *e.ServiceAccount } + if e.HelmRef != nil { + return "helm://" + e.HelmRef.String() + } return "" } +// +kubebuilder:object:generate=true +type HelmRefKeySelector struct { + LocalObjectReference `json:",inline" yaml:",inline" protobuf:"bytes,1,opt,name=localObjectReference"` + // Key is a JSONPath expression used to fetch the key from the merged JSON. + Key string `json:"key" yaml:"key" protobuf:"bytes,2,opt,name=key"` +} + +func (c HelmRefKeySelector) String() string { + return c.Name + "/" + c.Key +} + // +kubebuilder:object:generate=true type ConfigMapKeySelector struct { LocalObjectReference `json:",inline" yaml:",inline" protobuf:"bytes,1,opt,name=localObjectReference"` @@ -162,6 +177,22 @@ func (e *EnvVar) Scan(value any) error { return nil } + if strings.HasPrefix(v, "helm://") { + if len(strings.Split(v, "/")) != 4 { + return fmt.Errorf("invalid helm reference: %s", v) + } + *e = EnvVar{ + ValueFrom: &EnvVarSource{ + HelmRef: &HelmRefKeySelector{ + LocalObjectReference: LocalObjectReference{ + Name: strings.Split(v, "/")[2], + }, + Key: strings.Split(v, "/")[3], + }, + }} + return nil + } + if strings.HasPrefix(v, "serviceaccount://") { segments := strings.Split(v, "/") if len(segments) != 3 || segments[2] == "" { diff --git a/types/envvar_test.go b/types/envvar_test.go index ac1f0130..4ce9f776 100644 --- a/types/envvar_test.go +++ b/types/envvar_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/flanksource/commons/utils" + "github.com/google/go-cmp/cmp" ) // test EnvVar implements the sql.Scanner interface correctly @@ -79,6 +80,27 @@ func TestEnvVar_Scan(t *testing.T) { expected: nil, errorExpected: true, }, + { + name: "valid helm reference", + input: "helm://canary-checker/the-key", + expected: &EnvVar{ + ValueFrom: &EnvVarSource{ + HelmRef: &HelmRefKeySelector{ + LocalObjectReference: LocalObjectReference{ + Name: "canary-checker", + }, + Key: "the-key", + }, + }, + }, + errorExpected: false, + }, + { + name: "invalid helm reference", + input: "helm:///canary-checker/the-key", + expected: nil, + errorExpected: true, + }, } for _, tc := range tests { @@ -98,12 +120,8 @@ func TestEnvVar_Scan(t *testing.T) { return } - if e.ValueStatic != "" { - t.Errorf("Expected service account reference, but got static value: %s", e.ValueStatic) - } - - if e.ValueFrom == nil || e.ValueFrom.ServiceAccount == nil || *e.ValueFrom.ServiceAccount != *tc.expected.ValueFrom.ServiceAccount { - t.Errorf("Expected service account reference: %v, got: %v", tc.expected.ValueFrom.ServiceAccount, e.ValueFrom.ServiceAccount) + if diff := cmp.Diff(&e, tc.expected); diff != "" { + t.Errorf("EnvVar mismatch (-want +got):\n%s", diff) } }) } diff --git a/types/types.go b/types/types.go index 0afdbc97..63374e3d 100644 --- a/types/types.go +++ b/types/types.go @@ -357,7 +357,7 @@ func GenericStructScan[T any](t *T, val any) error { case string: ba = []byte(v) default: - return fmt.Errorf("Failed to unmarshal JSONB value: %v", val) + return fmt.Errorf("failed to unmarshal JSONB value: %v", val) } err := json.Unmarshal(ba, &t) return err diff --git a/types/zz_generated.deepcopy.go b/types/zz_generated.deepcopy.go index 0c1d63e8..9c7afe50 100644 --- a/types/zz_generated.deepcopy.go +++ b/types/zz_generated.deepcopy.go @@ -78,6 +78,11 @@ func (in *EnvVarSource) DeepCopyInto(out *EnvVarSource) { *out = new(string) **out = **in } + if in.HelmRef != nil { + in, out := &in.HelmRef, &out.HelmRef + *out = new(HelmRefKeySelector) + **out = **in + } if in.ConfigMapKeyRef != nil { in, out := &in.ConfigMapKeyRef, &out.ConfigMapKeyRef *out = new(ConfigMapKeySelector) @@ -100,6 +105,22 @@ func (in *EnvVarSource) DeepCopy() *EnvVarSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmRefKeySelector) DeepCopyInto(out *HelmRefKeySelector) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmRefKeySelector. +func (in *HelmRefKeySelector) DeepCopy() *HelmRefKeySelector { + if in == nil { + return nil + } + out := new(HelmRefKeySelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) { *out = *in