Skip to content

Commit

Permalink
helm-operator: support go text/template evaluation in override values (
Browse files Browse the repository at this point in the history
…operator-framework#5105)

* support go text/template evaluation in override values
* add documentation for templating in helm override values

Signed-off-by: Joe Lanford <joe.lanford@gmail.com>
Signed-off-by: Thierry Wasylczenko <thierry.wasylczenko@gmail.com>
  • Loading branch information
joelanford authored and twasyl committed Sep 3, 2021
1 parent d239791 commit c5c9434
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 13 deletions.
6 changes: 6 additions & 0 deletions changelog/fragments/helm-overrides-template.yaml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
26 changes: 21 additions & 5 deletions internal/helm/watches/watches.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand Down Expand Up @@ -112,21 +115,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 {
Expand Down
44 changes: 43 additions & 1 deletion internal/helm/watches/watches_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: `---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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:

Expand Down

0 comments on commit c5c9434

Please sign in to comment.