diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks/test/integ.eks-helm-asset.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-eks/test/integ.eks-helm-asset.ts index b79d8b3629fd4..eb41c582510a1 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-eks/test/integ.eks-helm-asset.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks/test/integ.eks-helm-asset.ts @@ -28,7 +28,7 @@ class EksClusterStack extends Stack { vpc: this.vpc, mastersRole, defaultCapacity: 2, - ...getClusterVersionConfig(this), + ...getClusterVersionConfig(this, eks.KubernetesVersion.V1_31), tags: { foo: 'bar', }, @@ -108,6 +108,21 @@ class EksClusterStack extends Stack { values: { aws: { region: this.region } }, }); + // testing installation with --take-ownership flag set to true + // https://gallery.ecr.aws/aws-controllers-k8s/sns-chart + this.cluster.addHelmChart('test-take-ownership-installation', { + chart: 'sns-chart', + release: 'sns-chart-release', + repository: 'oci://public.ecr.aws/aws-controllers-k8s/sns-chart', + version: '1.1.2', + namespace: 'ask-system', + createNamespace: true, + skipCrds: true, + atomic: true, + takeOwnership: true, + values: { aws: { region: this.region } }, + }); + // https://github.com/orgs/grafana-operator/packages/container/package/helm-charts%2Fgrafana-operator this.cluster.addHelmChart('test-non-ecr-oci-chart', { chart: 'grafana-operator', diff --git a/packages/@aws-cdk/aws-eks-v2-alpha/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks-v2-alpha/lib/helm-chart.ts index fffdba99f81dd..af3cf709f4c8c 100644 --- a/packages/@aws-cdk/aws-eks-v2-alpha/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks-v2-alpha/lib/helm-chart.ts @@ -91,6 +91,13 @@ export interface HelmChartOptions { * @default - CRDs are installed if not already present */ readonly skipCrds?: boolean; + + /** + * if set, this will use the --take-ownership flag provided since helm >=v3.17.0. Helm will not throw an error if any manifest + * that it renders if already on the cluster and will instead overwrite and annotate it with ownership from the rendered manifest + * @default false + */ + readonly takeOwnership?: boolean; } /** @@ -158,6 +165,8 @@ export class HelmChart extends Construct { const skipCrds = props.skipCrds ?? false; // default to set atomic as false const atomic = props.atomic ?? false; + // default to not take ownership since helm only only added this as of 3.17.0 + const takeOwnership = props.takeOwnership ?? false; this.chartAsset?.grantRead(provider.handlerRole); @@ -179,6 +188,7 @@ export class HelmChart extends Construct { CreateNamespace: createNamespace || undefined, SkipCrds: skipCrds || undefined, Atomic: atomic || undefined, // props are stringified so we encode “false” as undefined + TakeOwnership: takeOwnership || undefined, }, }); } diff --git a/packages/@aws-cdk/aws-eks-v2-alpha/lib/kubectl-handler/helm/__init__.py b/packages/@aws-cdk/aws-eks-v2-alpha/lib/kubectl-handler/helm/__init__.py index 49b684851420e..c9f59b1c8c8e5 100644 --- a/packages/@aws-cdk/aws-eks-v2-alpha/lib/kubectl-handler/helm/__init__.py +++ b/packages/@aws-cdk/aws-eks-v2-alpha/lib/kubectl-handler/helm/__init__.py @@ -53,6 +53,7 @@ def helm_handler(event, context): repository = props.get('Repository', None) values_text = props.get('Values', None) skip_crds = props.get('SkipCrds', False) + take_ownership = props.get('TakeOwnership', False) # "log in" to the cluster subprocess.check_call([ 'aws', 'eks', 'update-kubeconfig', @@ -91,7 +92,7 @@ def helm_handler(event, context): chart_dir = get_chart_from_oci(tmpdir.name, repository, version) chart = chart_dir - helm('upgrade', release, chart, repository, values_file, namespace, version, wait, timeout, create_namespace, atomic=atomic) + helm('upgrade', release, chart, repository, values_file, namespace, version, wait, timeout, create_namespace, atomic=atomic, take_ownership=take_ownership) elif request_type == "Delete": try: helm('uninstall', release, namespace=namespace, wait=wait, timeout=timeout) @@ -158,7 +159,7 @@ def get_chart_from_oci(tmpdir, repository = None, version = None): raise Exception(f'Operation failed after {maxAttempts} attempts: {output}') -def helm(verb, release, chart = None, repo = None, file = None, namespace = None, version = None, wait = False, timeout = None, create_namespace = None, skip_crds = False, atomic = False): +def helm(verb, release, chart = None, repo = None, file = None, namespace = None, version = None, wait = False, timeout = None, create_namespace = None, skip_crds = False, atomic = False, take_ownership = False): import subprocess cmnd = ['helm', verb, release] @@ -183,7 +184,9 @@ def helm(verb, release, chart = None, repo = None, file = None, namespace = None if not timeout is None: cmnd.extend(['--timeout', timeout]) if atomic: - cmnd.append('--atomic') + cmnd.append('--atomic') + if take_ownership: + cmnd.append('--take-ownership') cmnd.extend(['--kubeconfig', kubeconfig]) maxAttempts = 3 diff --git a/packages/@aws-cdk/custom-resource-handlers/lib/aws-eks/kubectl-handler/helm/__init__.py b/packages/@aws-cdk/custom-resource-handlers/lib/aws-eks/kubectl-handler/helm/__init__.py index 49b684851420e..daebce3fd572f 100644 --- a/packages/@aws-cdk/custom-resource-handlers/lib/aws-eks/kubectl-handler/helm/__init__.py +++ b/packages/@aws-cdk/custom-resource-handlers/lib/aws-eks/kubectl-handler/helm/__init__.py @@ -53,6 +53,7 @@ def helm_handler(event, context): repository = props.get('Repository', None) values_text = props.get('Values', None) skip_crds = props.get('SkipCrds', False) + take_ownership = props.get('TakeOwnership', False) # "log in" to the cluster subprocess.check_call([ 'aws', 'eks', 'update-kubeconfig', @@ -91,7 +92,7 @@ def helm_handler(event, context): chart_dir = get_chart_from_oci(tmpdir.name, repository, version) chart = chart_dir - helm('upgrade', release, chart, repository, values_file, namespace, version, wait, timeout, create_namespace, atomic=atomic) + helm('upgrade', release, chart, repository, values_file, namespace, version, wait, timeout, create_namespace, atomic=atomic, take_ownership=take_ownership) elif request_type == "Delete": try: helm('uninstall', release, namespace=namespace, wait=wait, timeout=timeout) @@ -158,7 +159,7 @@ def get_chart_from_oci(tmpdir, repository = None, version = None): raise Exception(f'Operation failed after {maxAttempts} attempts: {output}') -def helm(verb, release, chart = None, repo = None, file = None, namespace = None, version = None, wait = False, timeout = None, create_namespace = None, skip_crds = False, atomic = False): +def helm(verb, release, chart = None, repo = None, file = None, namespace = None, version = None, wait = False, timeout = None, create_namespace = None, skip_crds = False, atomic = False, take_ownership = False): import subprocess cmnd = ['helm', verb, release] @@ -183,7 +184,9 @@ def helm(verb, release, chart = None, repo = None, file = None, namespace = None if not timeout is None: cmnd.extend(['--timeout', timeout]) if atomic: - cmnd.append('--atomic') + cmnd.append('--atomic') + if take_ownership: + cmnd.append('--take-ownership') cmnd.extend(['--kubeconfig', kubeconfig]) maxAttempts = 3 diff --git a/packages/aws-cdk-lib/aws-eks/README.md b/packages/aws-cdk-lib/aws-eks/README.md index a32f94c651bb4..f9d6926235712 100644 --- a/packages/aws-cdk-lib/aws-eks/README.md +++ b/packages/aws-cdk-lib/aws-eks/README.md @@ -36,7 +36,8 @@ In addition, the library also supports defining Kubernetes resource manifests wi - [Permissions and Security](#permissions-and-security) - [AWS IAM Mapping](#aws-iam-mapping) - [Access Config](#access-config) - - [Access Entry](#access-mapping) + - [Access Entry](#access-entry) + - [Migrating from ConfigMap to Access Entry](#migrating-from-configmap-to-access-entry) - [Cluster Security Group](#cluster-security-group) - [Node SSH Access](#node-ssh-access) - [Service Accounts](#service-accounts) @@ -1622,6 +1623,22 @@ new eks.HelmChart(this, 'NginxIngress', { }); ``` +Helm chart can also execute upgrades with the `--take-ownership` flag as long as the cluster's kubectl layer has `helm >=3.17.0`. +This option is useful if you are using a tool or development pattern where manifests might be added separately via kubectl during +something like a migration. Without this option set, your helm chart construct will fail to deploy by if it cannot detect the helm-applied annotations on existing manifests. + +```ts +declare const cluster: eks.Cluster; +// option 1: use a construct +new eks.HelmChart(this, 'NginxIngress', { + cluster, + chart: 'nginx-ingress', + repository: 'https://helm.nginx.com/stable', + namespace: 'kube-system', + takeOwnership: true, +}); +``` + ### OCI Charts OCI charts are also supported. diff --git a/packages/aws-cdk-lib/aws-eks/lib/helm-chart.ts b/packages/aws-cdk-lib/aws-eks/lib/helm-chart.ts index ddf7e3a30bbc9..ce0afa982653f 100644 --- a/packages/aws-cdk-lib/aws-eks/lib/helm-chart.ts +++ b/packages/aws-cdk-lib/aws-eks/lib/helm-chart.ts @@ -91,6 +91,14 @@ export interface HelmChartOptions { * @default - CRDs are installed if not already present */ readonly skipCrds?: boolean; + + /** + * if set, this will use the --take-ownership flag provided since helm >=v3.17.0. Helm will not throw an error if any manifest + * that it renders if already on the cluster and will instead overwrite and annotate it with ownership from the rendered manifest + * + * @default false + */ + readonly takeOwnership?: boolean; } /** @@ -158,6 +166,8 @@ export class HelmChart extends Construct { const skipCrds = props.skipCrds ?? false; // default to set atomic as false const atomic = props.atomic ?? false; + // default to not take ownership since helm only only added this as of 3.17.0 + const takeOwnership = props.takeOwnership ?? false; this.chartAsset?.grantRead(provider.handlerRole); @@ -179,6 +189,7 @@ export class HelmChart extends Construct { CreateNamespace: createNamespace || undefined, SkipCrds: skipCrds || undefined, Atomic: atomic || undefined, // props are stringified so we encode “false” as undefined + TakeOwnership: takeOwnership || undefined, }, }); } diff --git a/packages/aws-cdk-lib/aws-eks/test/helm-chart.test.ts b/packages/aws-cdk-lib/aws-eks/test/helm-chart.test.ts index 1f2d6633624a5..85b429a9fb075 100644 --- a/packages/aws-cdk-lib/aws-eks/test/helm-chart.test.ts +++ b/packages/aws-cdk-lib/aws-eks/test/helm-chart.test.ts @@ -236,6 +236,29 @@ describe('helm chart', () => { }); + test('should enable takeOwnership operations when specified', () => { + //GIVEN + const { stack, cluster } = testFixtureCluster(); + + //WHEN + new eks.HelmChart(stack, 'MyAtomicChart', { cluster, chart: 'chart', takeOwnership: true }); + + //THEN + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { TakeOwnership: true }); + }); + + test('should disable takeOwnership operations by default', () => { + //GIVEN + const { stack, cluster } = testFixtureCluster(); + + //WHEN + new eks.HelmChart(stack, 'MyAtomicChart', { cluster, chart: 'chart' }); + + //THEN + const charts = Template.fromStack(stack).findResources(eks.HelmChart.RESOURCE_TYPE, { Atomic: true }); + expect(Object.keys(charts).length).toEqual(0); + }); + test('should timeout only after 10 minutes', () => { // GIVEN const { stack, cluster } = testFixtureCluster();