diff --git a/changelog/fragments/helm-overrides-template.yaml b/changelog/fragments/helm-overrides-template.yaml new file mode 100644 index 00000000000..9a2cd4adee0 --- /dev/null +++ b/changelog/fragments/helm-overrides-template.yaml @@ -0,0 +1,6 @@ +# entries is a list of entries to include in +# release notes and/or the migration guide +entries: + - description: > + For helm-based operators, support go `text/template` expansion of override values + kind: addition diff --git a/go.mod b/go.mod index fafd3f49965..5a771785064 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/fatih/structtag v1.1.0 github.com/go-logr/logr v0.4.0 + github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 github.com/kr/text v0.2.0 github.com/markbates/inflect v1.0.4 diff --git a/go.sum b/go.sum index 5e52c8fc55d..103a2751a6d 100644 --- a/go.sum +++ b/go.sum @@ -409,6 +409,7 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= diff --git a/internal/helm/watches/watches.go b/internal/helm/watches/watches.go index c02413abb32..5f2b7d617cc 100644 --- a/internal/helm/watches/watches.go +++ b/internal/helm/watches/watches.go @@ -15,12 +15,15 @@ package watches import ( + "bytes" "errors" "fmt" "io" "io/ioutil" "os" + "text/template" + sprig "github.com/go-task/slim-sprig" "helm.sh/helm/v3/pkg/chartutil" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/yaml" @@ -110,21 +113,34 @@ func LoadReader(reader io.Reader) ([]Watch, error) { trueVal := true w.WatchDependentResources = &trueVal } - w.OverrideValues = expandOverrideEnvs(w.OverrideValues) + w.OverrideValues, err = expandOverrideValues(w.OverrideValues) + if err != nil { + return nil, fmt.Errorf("failed to expand override values: %v", err) + } watches[i] = w } return watches, nil } -func expandOverrideEnvs(in map[string]string) map[string]string { +func expandOverrideValues(in map[string]string) (map[string]string, error) { if in == nil { - return nil + return nil, nil } out := make(map[string]string) for k, v := range in { - out[k] = os.ExpandEnv(v) + envV := os.ExpandEnv(v) + + v := &bytes.Buffer{} + tmplV, err := template.New(k).Funcs(sprig.TxtFuncMap()).Parse(envV) + if err != nil { + return nil, fmt.Errorf("invalid template string %q: %v", envV, err) + } + if err := tmplV.Execute(v, nil); err != nil { + return nil, fmt.Errorf("failed to execute template %q: %v", envV, err) + } + out[k] = v.String() } - return out + return out, nil } func verifyGVK(gvk schema.GroupVersionKind) error { diff --git a/internal/helm/watches/watches_test.go b/internal/helm/watches/watches_test.go index cdea650c4ee..7f543f39105 100644 --- a/internal/helm/watches/watches_test.go +++ b/internal/helm/watches/watches_test.go @@ -55,7 +55,7 @@ func TestLoadReader(t *testing.T) { expectErr: false, }, { - name: "valid with override expansion", + name: "valid with override env expansion", data: `--- - group: mygroup version: v1alpha1 @@ -76,6 +76,48 @@ func TestLoadReader(t *testing.T) { }, expectErr: false, }, + { + name: "valid with override template expansion", + data: `--- +- group: mygroup + version: v1alpha1 + kind: MyKind + chart: ../../../internal/plugins/helm/v1/chartutil/testdata/test-chart + watchDependentResources: false + overrideValues: + repo: '{{ ("$MY_IMAGE" | split ":")._0 }}' + tag: '{{ ("$MY_IMAGE" | split ":")._1 }}' +`, + env: map[string]string{"MY_IMAGE": "quay.io/operator-framework/helm-operator:latest"}, + expectWatches: []Watch{ + { + GroupVersionKind: schema.GroupVersionKind{Group: "mygroup", Version: "v1alpha1", Kind: "MyKind"}, + ChartDir: "../../../internal/plugins/helm/v1/chartutil/testdata/test-chart", + WatchDependentResources: &falseVal, + OverrideValues: map[string]string{ + "repo": "quay.io/operator-framework/helm-operator", + "tag": "latest", + }, + }, + }, + expectErr: false, + }, + + { + name: "invalid with override template expansion", + data: `--- +- group: mygroup + version: v1alpha1 + kind: MyKind + chart: ../../../internal/plugins/helm/v1/chartutil/testdata/test-chart + watchDependentResources: false + overrideValues: + repo: '{{ ("$MY_IMAGE" | split ":")._0 }}' + tag: '{{ ("$MY_IMAGE" | split ":")._1' +`, + env: map[string]string{"MY_IMAGE": "quay.io/operator-framework/helm-operator:latest"}, + expectErr: true, + }, { name: "multiple gvk", data: `--- diff --git a/website/content/en/docs/building-operators/helm/reference/advanced_features/override_values.md b/website/content/en/docs/building-operators/helm/reference/advanced_features/override_values.md index cbfce953f88..16b69d08a2a 100644 --- a/website/content/en/docs/building-operators/helm/reference/advanced_features/override_values.md +++ b/website/content/en/docs/building-operators/helm/reference/advanced_features/override_values.md @@ -14,8 +14,8 @@ or the helm binary (helm v3) for security reasons. With the helm Operator this becomes possible by override values. This enforces that certain template values provided by the chart's default `values.yaml` or by a CR spec are always set when rendering the chart. If the value is set by a CR it gets overridden by the global override value. -The override value can be static but can also refer to an environment variable. To pass down environment -variables to the chart override values is currently the only way. +The override value can be static but can also refer to an environment variable and use go templates. +To pass down environment variables to the chart override values is currently the only way. An example use case of this is when your helm chart references container images by chart variables, which is a good practice. @@ -27,6 +27,8 @@ versus individually per CR / chart release. > and then also associate these with an environment variable of your Operator like shown below. > This allows your Operator to be mirrored for offline usage when packaged for OLM. +## Basic usage + To configure your operator with override values, add an `overrideValues` map to your `watches.yaml` file for the GVK and chart you need to override. For example, to change the repository used by the nginx chart, you would update your `watches.yaml` to the @@ -39,14 +41,16 @@ following: kind: Nginx chart: helm-charts/nginx overrideValues: - image.repository: quay.io/mycustomrepo + image.repository: quay.io/mycustomrepo/myimage ``` -By setting `image.repository` to `quay.io/mycustomrepo` you are ensuring that -`quay.io/mycustomrepo` will always be used instead of the chart's default repository +By setting `image.repository` to `quay.io/mycustomrepo/myimage` you are ensuring that +`quay.io/mycustomrepo/myimage` will always be used instead of the chart's default repository (`nginx`). If the CR attempts to set this value, it will be ignored. -It is now possible to reference environment variables in the `overrideValues` section: +## Using environment variables + +It is also possible to reference environment variables in the `overrideValues` section: ```yaml overrideValues: @@ -61,7 +65,7 @@ following snippet to the container spec: ```yaml env: - name: IMAGE_REPOSITORY - value: quay.io/mycustomrepo + value: quay.io/mycustomrepo/myimage ``` If an environment variable reference is listed in `overrideValues`, but is not present @@ -70,6 +74,32 @@ override all other values. Therefore, these environment variables should _always set. It is suggested to update the Dockerfile to set these environment variables to the same defaults that are defined by the chart. +## Using Go templates + +Lastly, you can use Go `text/template` strings along with +[slim-sprig](https://go-task.github.io/slim-sprig/) functions to provide even more +flexibility when building override values. + +For example, consider a situation where your operator has an environment variable, +`$IMAGE`, set to `quay.io/mycustomrepo/myimage:latest`. You can use sprig template +functions to split that environment variable into its repo and tag: + +```yaml + overrideValues: + image.repository: '{{ ("$IMAGE" | split ":")._0 }}' + image.tag: '{{ ("$IMAGE" | split ":")._1 }}' +``` + +The resulting override values sent to the helm installation would look like: + +```yaml + overrideValues: + image.repository: quay.io/mycustomrepo/myimage + image.tag: latest +``` + +## Event generation + To warn users that their CR settings may be ignored, the Helm operator creates events on the CR that include the name and value of each overridden value. For example: