Repository: operator-framework/operator-sdk
Pull request #5105 (2021-08-02 15:42:36)

helm-operator: support go text/template evaluation in override values
<!--

Welcome to the Operator SDK! Before contributing, make sure to:

- Read the contributing guidelines https://github.com/operator-framework/operator-sdk/blob/master/CONTRIBUTING.MD
- Rebase your branch on the latest upstream master
- Link any relevant issues, PR's, or documentation
- When fixing an issue, add "Closes #<ISSUE_NUMBER>"
- Follow the below checklist if making a user-facing change 

-->

**Description of the change:**
Support text/template expansion of helm-operator overrideValues.

**Motivation for the change:**
In some cases, environment variables need to be parsed to be used in helm charts, especially in dependent charts that helm-operator authors have less (or zero) control over.

For example, with this change it is possible to split a fully qualified image repo:tag into separate repo and tag values to pass to helm as values:

Environment:
```sh
export MY_IMAGE="quay.io/operator-framework/helm-operator:latest"
```

Input values:
```yaml
overrideValues:
  repo: '{{ ("$MY_IMAGE" | split ":")._0 }}'
  tag: '{{ ("$MY_IMAGE" | split ":")._1 }}'
```

Expanded values:
```yaml
overrideValues:
  repo: "quay.io/operator-framework/helm-operator"
  tag: "latest"
```

/cc @madorn 

**Checklist**

If the pull request includes user-facing changes, extra documentation is required:
- [ ] Add a new changelog fragment in `changelog/fragments` (see [`changelog/fragments/00-template.yaml`](https://github.com/operator-framework/operator-sdk/tree/master/changelog/fragments/00-template.yaml))
- [ ] Add or update relevant sections of the docs website in [`website/content/en/docs`](https://github.com/operator-framework/operator-sdk/tree/master/website/content/en/docs)


estroz: Can you add a line or two about which text functions can be used to https://master.sdk.operatorframework.io/docs/building-operators/helm/reference/advanced_features/override_values/
openshift-ci[bot]: New changes are detected. LGTM label has been removed.

Patch:
diff --git a/changelog/fragments/helm-overrides-template.yaml b/changelog/fragments/helm-overrides-template.yaml
new file mode 100644
index 00000000..9a2cd4ad
--- /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/changelog/fragments/revert-helm-equality-comparison.yaml b/changelog/fragments/revert-helm-equality-comparison.yaml
new file mode 100644
index 00000000..3827baf0
--- /dev/null
+++ b/changelog/fragments/revert-helm-equality-comparison.yaml
@@ -0,0 +1,20 @@
+# entries is a list of entries to include in
+# release notes and/or the migration guide
+entries:
+  - description: >
+      For helm-based operators, reverted the following PRs which modified helm release
+      equality comparison.
+      - https://github.com/operator-framework/operator-sdk/pull/5042
+      - https://github.com/operator-framework/operator-sdk/pull/4937
+
+    # kind is one of:
+    # - addition
+    # - change
+    # - deprecation
+    # - removal
+    # - bugfix
+    kind: change
+
+    # Is this a breaking change?
+    breaking: false
+
diff --git a/go.mod b/go.mod
index fafd3f49..5a771785 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 5e52c8fc..103a2751 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/release/manager.go b/internal/helm/release/manager.go
index ffeb40b2..07641ccc 100644
--- a/internal/helm/release/manager.go
+++ b/internal/helm/release/manager.go
@@ -20,7 +20,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"reflect"
 	"strings"
 
 	jsonpatch "gomodules.xyz/jsonpatch/v3"
@@ -33,7 +32,6 @@ import (
 	"helm.sh/helm/v3/pkg/storage/driver"
 	apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
-	apiequality "k8s.io/apimachinery/pkg/api/equality"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -127,10 +125,15 @@ func (m *manager) Sync(ctx context.Context) error {
 	m.deployedRelease = deployedRelease
 	m.isInstalled = true
 
-	m.isUpgradeRequired, err = m.isUpgrade(deployedRelease)
+	// Get the next candidate release to determine if an upgrade is necessary.
+	candidateRelease, err := m.getCandidateRelease(m.namespace, m.releaseName, m.chart, m.values)
 	if err != nil {
-		return fmt.Errorf("failed to get upgrade status: %w", err)
+		return fmt.Errorf("failed to get candidate release: %w", err)
 	}
+	if deployedRelease.Manifest != candidateRelease.Manifest {
+		m.isUpgradeRequired = true
+	}
+
 	return nil
 }
 
@@ -138,43 +141,6 @@ func notFoundErr(err error) bool {
 	return err != nil && strings.Contains(err.Error(), "not found")
 }
 
-// This is caused by the different logic of loading from local and loading from secret
-// For example, the Raw field, which has the tag `json:"-"`, causes the Unmarshal to be lost when it into Release
-// We need to make them follow the JSON tag
-// see: https://github.com/helm/helm/blob/cf0c6fed519d48101cd69ce01a355125215ee46f/pkg/storage/driver/util.go#L81
-func equalJSONStruct(a, b interface{}) (bool, error) {
-	if reflect.ValueOf(a).IsNil() || reflect.ValueOf(b).IsNil() {
-		return apiequality.Semantic.DeepEqual(a, b), nil
-	}
-
-	aBuf, bBuf := &bytes.Buffer{}, &bytes.Buffer{}
-	err := json.NewEncoder(aBuf).Encode(a)
-	if err != nil {
-		return false, err
-	}
-	err = json.NewEncoder(bBuf).Encode(b)
-	return aBuf.String() == bBuf.String(), err
-}
-
-func (m manager) isUpgrade(deployedRelease *rpb.Release) (bool, error) {
-	if deployedRelease == nil {
-		return false, nil
-	}
-
-	// Judging whether to skip updates
-	skip := m.namespace == deployedRelease.Namespace
-	skip = skip && m.releaseName == deployedRelease.Name
-
-	ok, err := equalJSONStruct(m.chart, deployedRelease.Chart)
-	if err != nil {
-		return false, err
-	}
-	skip = skip && ok
-
-	ok, err = equalJSONStruct(m.values, deployedRelease.Config)
-	return !(skip && ok), err
-}
-
 func (m manager) getDeployedRelease() (*rpb.Release, error) {
 	deployedRelease, err := m.storageBackend.Deployed(m.releaseName)
 	if err != nil {
@@ -186,6 +152,14 @@ func (m manager) getDeployedRelease() (*rpb.Release, error) {
 	return deployedRelease, nil
 }
 
+func (m manager) getCandidateRelease(namespace, name string, chart *cpb.Chart,
+	values map[string]interface{}) (*rpb.Release, error) {
+	upgrade := action.NewUpgrade(m.actionConfig)
+	upgrade.Namespace = namespace
+	upgrade.DryRun = true
+	return upgrade.Run(name, chart, values)
+}
+
 // InstallRelease performs a Helm release install.
 func (m manager) InstallRelease(ctx context.Context, opts ...InstallOption) (*rpb.Release, error) {
 	install := action.NewInstall(m.actionConfig)
diff --git a/internal/helm/watches/watches.go b/internal/helm/watches/watches.go
index 8aa0e650..e2f43a1d 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"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -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 {
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 cbfce953..16b69d08 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:
 


Test patch:
diff --git a/internal/helm/release/manager_test.go b/internal/helm/release/manager_test.go
index 70c5e5b5..466bb693 100644
--- a/internal/helm/release/manager_test.go
+++ b/internal/helm/release/manager_test.go
@@ -15,21 +15,18 @@
 package release
 
 import (
-	"bytes"
-	"encoding/json"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
-	cpb "helm.sh/helm/v3/pkg/chart"
-	lpb "helm.sh/helm/v3/pkg/chart/loader"
-	rpb "helm.sh/helm/v3/pkg/release"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	apitypes "k8s.io/apimachinery/pkg/types"
+	"k8s.io/cli-runtime/pkg/resource"
+
 	appsv1 "k8s.io/api/apps/v1"
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+
+	"github.com/stretchr/testify/assert"
 	"k8s.io/apimachinery/pkg/runtime"
-	apitypes "k8s.io/apimachinery/pkg/types"
-	"k8s.io/cli-runtime/pkg/resource"
 )
 
 func newTestUnstructured(containers []interface{}) *unstructured.Unstructured {
@@ -216,85 +213,3 @@ func TestManagerGenerateStrategicMergePatch(t *testing.T) {
 		assert.Equal(t, test.patch, string(diff))
 	}
 }
-
-func TestManagerisUpgrade(t *testing.T) {
-	tests := []struct {
-		name            string
-		releaseName     string
-		releaseNs       string
-		values          map[string]interface{}
-		chart           *cpb.Chart
-		deployedRelease *rpb.Release
-		want            bool
-	}{
-		{
-			name:            "ok",
-			releaseName:     "deployed",
-			releaseNs:       "deployed-ns",
-			values:          map[string]interface{}{"key": "value"},
-			chart:           newTestChart(t, "./testdata/simple"),
-			deployedRelease: newTestRelease(newTestChart(t, "./testdata/simple"), map[string]interface{}{"key": "value"}, "deployed", "deployed-ns"),
-			want:            false,
-		},
-		{
-			name:            "different chart",
-			releaseName:     "deployed",
-			releaseNs:       "deployed-ns",
-			values:          map[string]interface{}{"key": "value"},
-			chart:           newTestChart(t, "./testdata/simple"),
-			deployedRelease: newTestRelease(newTestChart(t, "./testdata/simpledf"), map[string]interface{}{"key": "value"}, "deployed", "deployed-ns"),
-			want:            true,
-		},
-		{
-			name:            "different values",
-			releaseName:     "deployed",
-			releaseNs:       "deployed-ns",
-			values:          map[string]interface{}{"key": "1", "int": int32(1)},
-			chart:           newTestChart(t, "./testdata/simple"),
-			deployedRelease: newTestRelease(newTestChart(t, "./testdata/simple"), map[string]interface{}{"key": "", "int": int64(1)}, "deployed", "deployed-ns"),
-			want:            true,
-		},
-		{
-			name:            "nil values",
-			releaseName:     "deployed",
-			releaseNs:       "deployed-ns",
-			values:          nil,
-			chart:           newTestChart(t, "./testdata/simple"),
-			deployedRelease: newTestRelease(newTestChart(t, "./testdata/simple"), map[string]interface{}{}, "deployed", "deployed-ns"),
-			want:            false,
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			m := manager{
-				releaseName: test.releaseName,
-				namespace:   test.releaseNs,
-				values:      test.values,
-				chart:       test.chart,
-			}
-			isUpgrade, err := m.isUpgrade(test.deployedRelease)
-			assert.Equal(t, test.want, isUpgrade)
-			assert.Equal(t, nil, err)
-		})
-	}
-}
-
-func newTestChart(t *testing.T, path string) *cpb.Chart {
-	chart, err := lpb.Load(path)
-	assert.Nil(t, err)
-	return chart
-}
-
-func newTestRelease(chart *cpb.Chart, values map[string]interface{}, name, namespace string) *rpb.Release { // nolint: unparam
-	release := rpb.Mock(&rpb.MockReleaseOptions{
-		Name:      name,
-		Namespace: namespace,
-		Version:   1,
-	})
-
-	buffer := &bytes.Buffer{}
-	_ = json.NewEncoder(buffer).Encode(chart)
-	_ = json.NewDecoder(buffer).Decode(release.Chart)
-	release.Config = values
-	return release
-}
diff --git a/internal/helm/release/testdata/simple/.helmignore b/internal/helm/release/testdata/simple/.helmignore
deleted file mode 100644
index f0c13194..00000000
--- a/internal/helm/release/testdata/simple/.helmignore
+++ /dev/null
@@ -1,21 +0,0 @@
-# Patterns to ignore when building packages.
-# This supports shell glob matching, relative path matching, and
-# negation (prefixed with !). Only one pattern per line.
-.DS_Store
-# Common VCS dirs
-.git/
-.gitignore
-.bzr/
-.bzrignore
-.hg/
-.hgignore
-.svn/
-# Common backup files
-*.swp
-*.bak
-*.tmp
-*~
-# Various IDEs
-.project
-.idea/
-*.tmproj
diff --git a/internal/helm/release/testdata/simple/Chart.yaml b/internal/helm/release/testdata/simple/Chart.yaml
deleted file mode 100644
index 786c7721..00000000
--- a/internal/helm/release/testdata/simple/Chart.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-apiVersion: v1
-description: a simple charts
-name: simple
-keywords:
-  - helm
-  - operator-sdk
-version: 0.0.1+master
-appVersion: "master"
diff --git a/internal/helm/release/testdata/simple/templates/NOTES.txt b/internal/helm/release/testdata/simple/templates/NOTES.txt
deleted file mode 100644
index da930300..00000000
--- a/internal/helm/release/testdata/simple/templates/NOTES.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-A simple charts
\ No newline at end of file
diff --git a/internal/helm/release/testdata/simple/templates/_helpers.tpl b/internal/helm/release/testdata/simple/templates/_helpers.tpl
deleted file mode 100644
index e5fe9272..00000000
--- a/internal/helm/release/testdata/simple/templates/_helpers.tpl
+++ /dev/null
@@ -1,7 +0,0 @@
-{{/* vim: set filetype=mustache: */}}
-{{/*
-Expand the name of the chart.
-*/}}
-{{- define "simple.fullname" -}}
-{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
-{{- end -}}
diff --git a/internal/helm/release/testdata/simple/templates/secrets.yaml b/internal/helm/release/testdata/simple/templates/secrets.yaml
deleted file mode 100644
index 4b61fbb7..00000000
--- a/internal/helm/release/testdata/simple/templates/secrets.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-apiVersion: v1
-kind: Secret
-metadata:
-  name: {{ template "simple.fullname" . }}
-  namespace: {{ .Release.Namespace }}
-type: Opaque
-data:
-  password: {{ randAlphaNum 10 | b64enc | quote }}
\ No newline at end of file
diff --git a/internal/helm/release/testdata/simple/values.yaml b/internal/helm/release/testdata/simple/values.yaml
deleted file mode 100644
index ca762273..00000000
--- a/internal/helm/release/testdata/simple/values.yaml
+++ /dev/null
@@ -1,1 +0,0 @@
-# simple values
\ No newline at end of file
diff --git a/internal/helm/release/testdata/simpledf/.helmignore b/internal/helm/release/testdata/simpledf/.helmignore
deleted file mode 100644
index f0c13194..00000000
--- a/internal/helm/release/testdata/simpledf/.helmignore
+++ /dev/null
@@ -1,21 +0,0 @@
-# Patterns to ignore when building packages.
-# This supports shell glob matching, relative path matching, and
-# negation (prefixed with !). Only one pattern per line.
-.DS_Store
-# Common VCS dirs
-.git/
-.gitignore
-.bzr/
-.bzrignore
-.hg/
-.hgignore
-.svn/
-# Common backup files
-*.swp
-*.bak
-*.tmp
-*~
-# Various IDEs
-.project
-.idea/
-*.tmproj
diff --git a/internal/helm/release/testdata/simpledf/Chart.yaml b/internal/helm/release/testdata/simpledf/Chart.yaml
deleted file mode 100644
index 786c7721..00000000
--- a/internal/helm/release/testdata/simpledf/Chart.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-apiVersion: v1
-description: a simple charts
-name: simple
-keywords:
-  - helm
-  - operator-sdk
-version: 0.0.1+master
-appVersion: "master"
diff --git a/internal/helm/release/testdata/simpledf/templates/NOTES.txt b/internal/helm/release/testdata/simpledf/templates/NOTES.txt
deleted file mode 100644
index da930300..00000000
--- a/internal/helm/release/testdata/simpledf/templates/NOTES.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-A simple charts
\ No newline at end of file
diff --git a/internal/helm/release/testdata/simpledf/templates/_helpers.tpl b/internal/helm/release/testdata/simpledf/templates/_helpers.tpl
deleted file mode 100644
index e5fe9272..00000000
--- a/internal/helm/release/testdata/simpledf/templates/_helpers.tpl
+++ /dev/null
@@ -1,7 +0,0 @@
-{{/* vim: set filetype=mustache: */}}
-{{/*
-Expand the name of the chart.
-*/}}
-{{- define "simple.fullname" -}}
-{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
-{{- end -}}
diff --git a/internal/helm/release/testdata/simpledf/templates/secrets.yaml b/internal/helm/release/testdata/simpledf/templates/secrets.yaml
deleted file mode 100644
index 4b61fbb7..00000000
--- a/internal/helm/release/testdata/simpledf/templates/secrets.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-apiVersion: v1
-kind: Secret
-metadata:
-  name: {{ template "simple.fullname" . }}
-  namespace: {{ .Release.Namespace }}
-type: Opaque
-data:
-  password: {{ randAlphaNum 10 | b64enc | quote }}
\ No newline at end of file
diff --git a/internal/helm/release/testdata/simpledf/values.yaml b/internal/helm/release/testdata/simpledf/values.yaml
deleted file mode 100644
index a99c72dc..00000000
--- a/internal/helm/release/testdata/simpledf/values.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-# simple values
-name: simpledf
\ No newline at end of file
diff --git a/internal/helm/watches/watches_test.go b/internal/helm/watches/watches_test.go
index cdea650c..7f543f39 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: `---