Skip to content

Commit 3200ae8

Browse files
authored
Add back eks defaultAddonsToRemove property (#5716)
Adding back the patch for `defaultAddonsToRemove` by popular demand. fixes #5715 re #4755
1 parent 7497e77 commit 3200ae8

File tree

17 files changed

+591
-0
lines changed

17 files changed

+591
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
name: eks-remove-addons
2+
runtime: nodejs
3+
description: A simple example of using the defaultAddonsToRemove flag
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# examples/eks-remove-addons
2+
3+
An example for how you can use the `bootstrapSelfManagedAddons` flag to disable add-ons.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright 2016-2018, Pulumi Corporation.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import * as aws from "@pulumi/aws";
16+
17+
const clusterRole = new aws.iam.Role('test-cluster-role', {
18+
assumeRolePolicy: {
19+
Version: '2012-10-17',
20+
Statement: [{
21+
Principal: {
22+
Service: 'eks.amazonaws.com',
23+
},
24+
Effect: 'Allow',
25+
Action: 'sts:AssumeRole',
26+
}]
27+
}
28+
});
29+
30+
new aws.iam.RolePolicyAttachment('attachment', {
31+
policyArn: aws.iam.ManagedPolicy.AmazonEKSClusterPolicy,
32+
role: clusterRole.name,
33+
});
34+
35+
const vpc = new aws.ec2.Vpc('test-vpc', {
36+
cidrBlock: '10.192.0.0/16'
37+
});
38+
39+
const azs = aws.getAvailabilityZonesOutput({
40+
state: 'available',
41+
allAvailabilityZones: false,
42+
filters: [
43+
{
44+
name: 'opt-in-status',
45+
values: ['opt-in-not-required'],
46+
},
47+
],
48+
});
49+
50+
const subnet1 = new aws.ec2.Subnet('test-subnet1', {
51+
vpcId: vpc.id,
52+
cidrBlock: '10.192.20.0/24',
53+
availabilityZone: azs.apply(az => az.names[0]),
54+
mapPublicIpOnLaunch: false,
55+
});
56+
57+
const subnet2 = new aws.ec2.Subnet('test-subnet2', {
58+
vpcId: vpc.id,
59+
availabilityZone: azs.apply(az => az.names[1]),
60+
cidrBlock: '10.192.21.0/24',
61+
mapPublicIpOnLaunch: false,
62+
});
63+
64+
const cluster = new aws.eks.Cluster('test-cluster', {
65+
roleArn: clusterRole.arn,
66+
vpcConfig: {
67+
subnetIds: [subnet1.id, subnet2.id],
68+
},
69+
bootstrapSelfManagedAddons: false,
70+
});
71+
72+
// TODO: example needs node group
73+
// const corednsVersion = aws.eks.getAddonVersionOutput({
74+
// addonName: "coredns",
75+
// kubernetesVersion: cluster.version,
76+
// mostRecent: true,
77+
// });
78+
//
79+
// new aws.eks.Addon("coredns", {
80+
// clusterName: cluster.name,
81+
// addonName: "coredns",
82+
// addonVersion: corednsVersion.version,
83+
// resolveConflictsOnUpdate: "PRESERVE",
84+
// configurationValues: JSON.stringify({
85+
// replicaCount: 4,
86+
// resources: {
87+
// limits: {
88+
// cpu: "100m",
89+
// memory: "150Mi",
90+
// },
91+
// requests: {
92+
// cpu: "100m",
93+
// memory: "150Mi",
94+
// },
95+
// },
96+
// }),
97+
// });
98+
99+
const cluster2 = new aws.eks.Cluster('test-cluster2', {
100+
roleArn: clusterRole.arn,
101+
vpcConfig: {
102+
subnetIds: [subnet1.id, subnet2.id],
103+
},
104+
defaultAddonsToRemove: ['vpc-cni', 'kube-proxy'],
105+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "bucket",
3+
"version": "0.0.1",
4+
"license": "Apache-2.0",
5+
"scripts": {
6+
"build": "tsc"
7+
},
8+
"dependencies": {
9+
"@pulumi/aws": "^7.0.0",
10+
"@pulumi/pulumi": "^3.0.0"
11+
},
12+
"devDependencies": {
13+
"@types/node": "^8.0.0"
14+
}
15+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"compilerOptions": {
3+
"strict": true,
4+
"outDir": "bin",
5+
"target": "es2020",
6+
"module": "commonjs",
7+
"moduleResolution": "node",
8+
"sourceMap": true,
9+
"experimentalDecorators": true,
10+
"pretty": true,
11+
"noFallthroughCasesInSwitch": true,
12+
"noImplicitReturns": true,
13+
"forceConsistentCasingInFileNames": true
14+
},
15+
"files": [
16+
"index.ts"
17+
]
18+
}

examples/examples_nodejs_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,7 @@ func TestUpstreamWarningsPropagated(t *testing.T) {
13461346
}
13471347

13481348
func TestAccProviderRoleChaining(t *testing.T) {
1349+
skipIfShort(t)
13491350
region := getEnvRegion(t)
13501351
test := integration.ProgramTestOptions{
13511352
Dir: filepath.Join(getCwd(t), "provider-role-chaining"),
@@ -1361,6 +1362,22 @@ func TestAccProviderRoleChaining(t *testing.T) {
13611362
integration.ProgramTest(t, &test)
13621363
}
13631364

1365+
func TestAccConfigureClusterAddons(t *testing.T) {
1366+
skipIfShort(t)
1367+
region := getEnvRegion(t)
1368+
test := integration.ProgramTestOptions{
1369+
Dir: filepath.Join(getCwd(t), "eks-remove-addons"),
1370+
Dependencies: []string{
1371+
"@pulumi/aws",
1372+
},
1373+
Config: map[string]string{
1374+
"aws:region": region,
1375+
},
1376+
}
1377+
skipRefresh(&test)
1378+
integration.ProgramTest(t, &test)
1379+
}
1380+
13641381
// also test the release verification test so it will also fail in CI
13651382
func TestReleaseVerification(t *testing.T) {
13661383
region := getEnvRegion(t)
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: corymhall <[email protected]>
3+
Date: Fri, 1 Aug 2025 14:29:38 -0400
4+
Subject: [PATCH] aws_eks_cluster: implement default_addons_to_remove
5+
6+
add back this feature and set it to deprecated. Users should instead
7+
set `bootstrapSelfManagedAddons: false` and selectively add the addons
8+
they want with the `aws.eks.Addon` resource.
9+
10+
diff --git a/internal/service/eks/cluster.go b/internal/service/eks/cluster.go
11+
index 8a7d28be91..a62c86208f 100644
12+
--- a/internal/service/eks/cluster.go
13+
+++ b/internal/service/eks/cluster.go
14+
@@ -168,6 +168,14 @@ func resourceCluster() *schema.Resource {
15+
Type: schema.TypeString,
16+
Computed: true,
17+
},
18+
+ "default_addons_to_remove": {
19+
+ Type: schema.TypeList,
20+
+ Deprecated: "Configure bootstrap_self_managed_addons instead. This attribute will be removed in the next major version of the provider",
21+
+ Optional: true,
22+
+ Elem: &schema.Schema{
23+
+ Type: schema.TypeString,
24+
+ },
25+
+ },
26+
"enabled_cluster_log_types": {
27+
Type: schema.TypeSet,
28+
Optional: true,
29+
@@ -600,6 +608,10 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta any
30+
return sdkdiag.AppendErrorf(diags, "waiting for EKS Cluster (%s) create: %s", d.Id(), err)
31+
}
32+
33+
+ if err := removeAddons(d, conn); err != nil {
34+
+ return sdkdiag.AppendErrorf(diags, "removing addons (%s): %s", d.Id(), err)
35+
+ }
36+
+
37+
return append(diags, resourceClusterRead(ctx, d, meta)...)
38+
}
39+
40+
diff --git a/internal/service/eks/cluster_addon_removal.go b/internal/service/eks/cluster_addon_removal.go
41+
new file mode 100644
42+
index 0000000000..bd53bad5c1
43+
--- /dev/null
44+
+++ b/internal/service/eks/cluster_addon_removal.go
45+
@@ -0,0 +1,139 @@
46+
+package eks
47+
+
48+
+import (
49+
+ "context"
50+
+ "fmt"
51+
+ "log"
52+
+ "sync"
53+
+ "time"
54+
+
55+
+ "github.com/aws/aws-sdk-go-v2/aws"
56+
+ "github.com/aws/aws-sdk-go-v2/service/eks"
57+
+ "github.com/aws/aws-sdk-go-v2/service/eks/types"
58+
+
59+
+ "github.com/hashicorp/go-multierror"
60+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id"
61+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
62+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
63+
+ "github.com/hashicorp/terraform-provider-aws/internal/errs"
64+
+
65+
+ "github.com/hashicorp/terraform-provider-aws/internal/flex"
66+
+ "github.com/hashicorp/terraform-provider-aws/internal/tfresource"
67+
+)
68+
+
69+
+const (
70+
+ addonCreatedTimeout = 20 * time.Minute
71+
+ addonUpdatedTimeout = 20 * time.Minute
72+
+ addonDeletedTimeout = 40 * time.Minute
73+
+)
74+
+
75+
+func removeAddons(d *schema.ResourceData, conn *eks.Client) error {
76+
+ if v, ok := d.GetOk("default_addons_to_remove"); ok && len(v.([]interface{})) > 0 {
77+
+ ctx := context.Background()
78+
+ var wg sync.WaitGroup
79+
+ var removalErrors *multierror.Error
80+
+
81+
+ for _, addon := range flex.ExpandStringList(v.([]interface{})) {
82+
+ if addon == nil {
83+
+ return fmt.Errorf("addonName cannot be dereferenced")
84+
+ }
85+
+ addonName := *addon
86+
+ wg.Add(1)
87+
+
88+
+ go func() {
89+
+ defer wg.Done()
90+
+ removalErrors = multierror.Append(removalErrors, removeAddon(d, conn, addonName, ctx))
91+
+ }()
92+
+ }
93+
+ wg.Wait()
94+
+ return removalErrors.ErrorOrNil()
95+
+ }
96+
+ return nil
97+
+}
98+
+
99+
+func removeAddon(d *schema.ResourceData, conn *eks.Client, addonName string, ctx context.Context) error {
100+
+ log.Printf("[DEBUG] Creating EKS Add-On: %s", addonName)
101+
+ createAddonInput := &eks.CreateAddonInput{
102+
+ AddonName: aws.String(addonName),
103+
+ ClientRequestToken: aws.String(id.UniqueId()),
104+
+ ClusterName: aws.String(d.Id()),
105+
+ ResolveConflicts: types.ResolveConflictsOverwrite,
106+
+ }
107+
+
108+
+ err := retry.RetryContext(ctx, propagationTimeout, func() *retry.RetryError {
109+
+ _, err := conn.CreateAddon(ctx, createAddonInput)
110+
+
111+
+ if errs.IsAErrorMessageContains[*types.InvalidParameterException](err, "CREATE_FAILED") {
112+
+ return retry.RetryableError(err)
113+
+ }
114+
+
115+
+ if errs.IsAErrorMessageContains[*types.InvalidParameterException](err, "does not exist") {
116+
+ return retry.RetryableError(err)
117+
+ }
118+
+
119+
+ if err != nil {
120+
+ return retry.NonRetryableError(err)
121+
+ }
122+
+
123+
+ return nil
124+
+ })
125+
+
126+
+ if tfresource.TimedOut(err) {
127+
+ _, err = conn.CreateAddon(ctx, createAddonInput)
128+
+ }
129+
+
130+
+ if err != nil {
131+
+ return fmt.Errorf("error creating EKS Add-On (%s): %w", addonName, err)
132+
+ }
133+
+
134+
+ _, err = waitAddonCreatedAllowDegraded(ctx, conn, d.Id(), addonName)
135+
+
136+
+ if err != nil {
137+
+ return fmt.Errorf("unexpected EKS Add-On (%s) state returned during creation: %w", addonName, err)
138+
+ }
139+
+ log.Printf("[DEBUG] Created EKS Add-On: %s", addonName)
140+
+
141+
+ deleteAddonInput := &eks.DeleteAddonInput{
142+
+ AddonName: aws.String(addonName),
143+
+ ClusterName: aws.String(d.Id()),
144+
+ }
145+
+
146+
+ log.Printf("[DEBUG] Deleting EKS Add-On: %s", addonName)
147+
+ _, err = conn.DeleteAddon(ctx, deleteAddonInput)
148+
+
149+
+ if err != nil {
150+
+ return fmt.Errorf("error deleting EKS Add-On (%s): %w", addonName, err)
151+
+ }
152+
+
153+
+ _, err = waitAddonDeleted(ctx, conn, d.Id(), addonName, addonDeletedTimeout)
154+
+
155+
+ if err != nil {
156+
+ return fmt.Errorf("error waiting for EKS Add-On (%s) to delete: %w", addonName, err)
157+
+ }
158+
+ log.Printf("[DEBUG] Deleted EKS Add-On: %s", addonName)
159+
+ return nil
160+
+}
161+
+
162+
+func waitAddonCreatedAllowDegraded(ctx context.Context, conn *eks.Client, clusterName, addonName string) (*types.Addon, error) {
163+
+ // We do not care about the addons actually being created successfully here. We only want them to be adopted by
164+
+ // Terraform to be able to fully remove them afterwards again.
165+
+
166+
+ stateConf := retry.StateChangeConf{
167+
+ Pending: []string{string(types.AddonStatusCreating)},
168+
+ Target: []string{string(types.AddonStatusActive), string(types.AddonStatusDegraded)},
169+
+ Refresh: statusAddon(ctx, conn, clusterName, addonName),
170+
+ Timeout: addonCreatedTimeout,
171+
+ }
172+
+
173+
+ outputRaw, err := stateConf.WaitForStateContext(ctx)
174+
+
175+
+ if output, ok := outputRaw.(*types.Addon); ok {
176+
+ if status, health := output.Status, output.Health; status == types.AddonStatusCreateFailed && health != nil {
177+
+ tfresource.SetLastError(err, addonIssuesError(health.Issues))
178+
+ }
179+
+
180+
+ return output, err
181+
+ }
182+
+
183+
+ return nil, err
184+
+}

provider/cmd/pulumi-resource-aws/bridge-metadata.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12776,6 +12776,9 @@
1277612776
}
1277712777
}
1277812778
},
12779+
"default_addons_to_remove": {
12780+
"maxItemsOne": false
12781+
},
1277912782
"enabled_cluster_log_types": {
1278012783
"maxItemsOne": false
1278112784
},

0 commit comments

Comments
 (0)