diff --git a/docs/resources/release.md b/docs/resources/release.md index 1f7242e93d..3bcbd169bc 100644 --- a/docs/resources/release.md +++ b/docs/resources/release.md @@ -55,6 +55,7 @@ A Chart is a Helm package. It contains all of the resource definitions necessary - `set_list` (Block List) Custom list values to be merged with the values. (see [below for nested schema](#nestedblock--set_list)) - `set_sensitive` (Block Set) Custom sensitive values to be merged with the values. (see [below for nested schema](#nestedblock--set_sensitive)) - `skip_crds` (Boolean) If set, no CRDs will be installed. By default, CRDs are installed if not already present. Defaults to `false`. +- `take_ownership` (Boolean) If set, ignore checks for Helm annotations and take ownership of the resources. - `timeout` (Number) Time in seconds to wait for any individual kubernetes operation. Defaults to 300 seconds. - `upgrade_install` (Boolean) If true, the provider will install the release at the specified version even if a release not controlled by the provider is present: this is equivalent to running 'helm upgrade --install' with the Helm CLI. WARNING: this may not be suitable for production use -- see the 'Upgrade Mode' note in the provider documentation. Defaults to `false`. - `values` (List of String) List of values in raw yaml format to pass to helm. diff --git a/helm/provider_test.go b/helm/provider_test.go index d9157f4a25..435615688f 100644 --- a/helm/provider_test.go +++ b/helm/provider_test.go @@ -329,6 +329,39 @@ func deleteNamespace(t *testing.T, namespace string) { } } +func createRandomConfigMap(t *testing.T, namespace string) string { + if !accTest { + t.Skip("TF_ACC=1 not set") + return "" + } + + name := fmt.Sprintf("%s-%s", testNamespacePrefix, acctest.RandString(10)) + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + _, err := client.CoreV1().ConfigMaps(namespace).Create(context.TODO(), cm, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("Could not create test configmap %q in namespace %q: %s", name, namespace, err) + } + return name +} + +func getConfigMap(t *testing.T, namespace, name string) *v1.ConfigMap { + if !accTest { + t.Skip("TF_ACC=1 not set") + return nil + } + + cm, err := client.CoreV1().ConfigMaps(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + t.Fatalf("Could not fetch test configmap %q from namespace %q: %s", name, namespace, err) + } + return cm +} + func randName(prefix string) string { return fmt.Sprintf("%s-%s", prefix, acctest.RandString(10)) } diff --git a/helm/resource_helm_release.go b/helm/resource_helm_release.go index ec9feb7255..db758d60ce 100644 --- a/helm/resource_helm_release.go +++ b/helm/resource_helm_release.go @@ -99,6 +99,7 @@ type HelmReleaseModel struct { SetSensitive types.List `tfsdk:"set_sensitive"` SkipCrds types.Bool `tfsdk:"skip_crds"` Status types.String `tfsdk:"status"` + TakeOwnership types.Bool `tfsdk:"take_ownership"` Timeout types.Int64 `tfsdk:"timeout"` Values types.List `tfsdk:"values"` Verify types.Bool `tfsdk:"verify"` @@ -125,6 +126,7 @@ var defaultAttributes = map[string]interface{}{ "reset_values": false, "reuse_values": false, "skip_crds": false, + "take_ownership": false, "timeout": int64(300), "verify": false, "wait": true, @@ -478,6 +480,12 @@ func (r *HelmRelease) Schema(ctx context.Context, req resource.SchemaRequest, re Computed: true, Description: "Status of the release", }, + "take_ownership": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(defaultAttributes["take_ownership"].(bool)), + Description: "Ignore check for Helm annotations and take ownership of the resources.", + }, "timeout": schema.Int64Attribute{ Optional: true, Computed: true, @@ -774,6 +782,7 @@ func (r *HelmRelease) Create(ctx context.Context, req resource.CreateRequest, re client.Replace = state.Replace.ValueBool() client.Description = state.Description.ValueString() client.CreateNamespace = state.CreateNamespace.ValueBool() + client.TakeOwnership = state.TakeOwnership.ValueBool() if state.PostRender != nil { binaryPath := state.PostRender.BinaryPath.ValueString() @@ -1818,6 +1827,8 @@ func (r *HelmRelease) ModifyPlan(ctx context.Context, req resource.ModifyPlanReq install.Replace = plan.Replace.ValueBool() install.Description = plan.Description.ValueString() install.CreateNamespace = plan.CreateNamespace.ValueBool() + install.TakeOwnership = plan.TakeOwnership.ValueBool() + install.PostRenderer = client.PostRenderer values, diags := getValues(ctx, &plan) diff --git a/helm/resource_helm_release_test.go b/helm/resource_helm_release_test.go index ca256589d3..7ac93f0403 100644 --- a/helm/resource_helm_release_test.go +++ b/helm/resource_helm_release_test.go @@ -2354,3 +2354,60 @@ func testAccHelmReleaseRecomputeMetadataSet(resource, ns, name string) string { } `, resource, name, ns, resource) } + +func testAccHelmReleaseConfigTakeOwnership(resource, ns, name, version, configmap string) string { + return fmt.Sprintf(` + resource "helm_release" "%s" { + name = %q + namespace = %q + description = "Test" + repository = %q + chart = "take-ownership" + version = %q + + take_ownership = true + + set = [ + { + name = "configMap" + value = %q + }, + { + name = "configMapNamespace" + value = %q + }, + ] + } + `, resource, name, ns, testRepositoryURL, version, configmap, ns) +} + +func testAccCheckConfigMapUpdated(t *testing.T, namespace, name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + cm := getConfigMap(t, namespace, name) + + if d, ok := cm.Data["ownership"]; !ok || d != "taken" { + return fmt.Errorf("configmap contents were not updated") + } + return nil + } +} + +func TestAccResourceRelease_takeOwnership(t *testing.T) { + name := randName("take-ownership") + namespace := createRandomNamespace(t) + configmap := createRandomConfigMap(t, namespace) + defer deleteNamespace(t, namespace) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: testAccHelmReleaseConfigTakeOwnership(testResourceName, namespace, name, "1.0.0", configmap), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(fmt.Sprintf("helm_release.%s", testResourceName), "status", release.StatusDeployed.String()), + testAccCheckConfigMapUpdated(t, namespace, configmap), + ), + }, + }, + }) +} diff --git a/helm/testdata/charts/take-ownership/.helmignore b/helm/testdata/charts/take-ownership/.helmignore new file mode 100644 index 0000000000..0e8a0eb36f --- /dev/null +++ b/helm/testdata/charts/take-ownership/.helmignore @@ -0,0 +1,23 @@ +# 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 +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/testdata/charts/take-ownership/Chart.yaml b/helm/testdata/charts/take-ownership/Chart.yaml new file mode 100644 index 0000000000..5745c8bcc0 --- /dev/null +++ b/helm/testdata/charts/take-ownership/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +name: take-ownership +description: This is a simple chart to use as a test fixture, using the take ownership feature + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 1.0.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +appVersion: 1.0.0 diff --git a/helm/testdata/charts/take-ownership/templates/_helpers.tpl b/helm/testdata/charts/take-ownership/templates/_helpers.tpl new file mode 100644 index 0000000000..15fb193b34 --- /dev/null +++ b/helm/testdata/charts/take-ownership/templates/_helpers.tpl @@ -0,0 +1,43 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "take-ownership.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "take-ownership.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "take-ownership.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "take-ownership.labels" -}} +helm.sh/chart: {{ include "take-ownership.chart" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + diff --git a/helm/testdata/charts/take-ownership/templates/configmap.yaml b/helm/testdata/charts/take-ownership/templates/configmap.yaml new file mode 100644 index 0000000000..4fd90d6b9f --- /dev/null +++ b/helm/testdata/charts/take-ownership/templates/configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ .Values.configMap }}" + namespace: "{{ .Values.configMapNamespace }}" + labels: + {{- include "take-ownership.labels" . | nindent 4 }} +data: + ownership: "taken" diff --git a/helm/testdata/charts/take-ownership/values.yaml b/helm/testdata/charts/take-ownership/values.yaml new file mode 100644 index 0000000000..55f66721c8 --- /dev/null +++ b/helm/testdata/charts/take-ownership/values.yaml @@ -0,0 +1,8 @@ +# Default values for test-chart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +configMap: "" +configMapNamespace: "" +nameOverride: "" +fullnameOverride: "" \ No newline at end of file