Skip to content

Commit 1cf378b

Browse files
committed
feat: add additionalTags EKS handlers
1 parent 2f00c8e commit 1cf378b

File tree

7 files changed

+472
-0
lines changed

7 files changed

+472
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
+++
2+
title = "EKS Additional Tags"
3+
+++
4+
5+
The EKS additional tags customization allows the user to specify custom tags to be applied to AWS resources created by the EKS cluster.
6+
The customization can be applied at the cluster level and worker node level.
7+
This customization will be available when the
8+
[provider-specific cluster configuration patch]({{< ref "..">}}) is included in the `ClusterClass`.
9+
10+
## Example
11+
12+
To specify additional tags for EKS resources, use the following configuration:
13+
14+
```yaml
15+
apiVersion: cluster.x-k8s.io/v1beta1
16+
kind: Cluster
17+
metadata:
18+
name: <NAME>
19+
spec:
20+
topology:
21+
variables:
22+
- name: clusterConfig
23+
value:
24+
eks:
25+
additionalTags:
26+
Environment: production
27+
Team: platform
28+
CostCenter: "12345"
29+
```
30+
31+
We can further customize individual MachineDeployments by using the overrides field with the following configuration:
32+
33+
```yaml
34+
spec:
35+
topology:
36+
# ...
37+
workers:
38+
machineDeployments:
39+
- class: default-worker
40+
name: md-0
41+
variables:
42+
overrides:
43+
- name: workerConfig
44+
value:
45+
eks:
46+
additionalTags:
47+
NodeType: worker
48+
Workload: database
49+
Environment: production
50+
```
51+
52+
## Tag Precedence
53+
54+
When tags are specified at multiple levels, the following precedence applies (higher precedence overrides lower):
55+
56+
1. **Worker level tags** (highest precedence)
57+
2. **Cluster level tags** (lowest precedence)
58+
59+
This means that if the same tag key is specified at multiple levels, the worker level values will take precedence over the cluster level values.
60+
61+
## Applying this configuration will result in the following values being set
62+
63+
- `AWSManagedControlPlane`:
64+
65+
- ```yaml
66+
spec:
67+
template:
68+
spec:
69+
additionalTags:
70+
Environment: production
71+
Team: platform
72+
CostCenter: "12345"
73+
```
74+
75+
- worker `AWSMachineTemplate`:
76+
77+
- ```yaml
78+
spec:
79+
template:
80+
spec:
81+
additionalTags:
82+
Environment: production
83+
Team: platform
84+
CostCenter: "12345"
85+
NodeType: worker
86+
Workload: general
87+
```

pkg/handlers/eks/mutation/metapatch_handler.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/placementgroup"
1717
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/region"
1818
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/securitygroups"
19+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/tags"
1920
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/volumes"
2021
)
2122

@@ -25,6 +26,7 @@ func MetaPatchHandler(mgr manager.Manager) handlers.Named {
2526
region.NewPatch(),
2627
network.NewPatch(),
2728
identityref.NewPatch(),
29+
tags.NewClusterPatch(),
2830
}
2931
patchHandlers = append(patchHandlers, metaMutators()...)
3032

@@ -44,6 +46,7 @@ func MetaWorkerPatchHandler(mgr manager.Manager) handlers.Named {
4446
securitygroups.NewWorkerPatch(),
4547
volumes.NewWorkerPatch(),
4648
placementgroup.NewWorkerPatch(),
49+
tags.NewWorkerPatch(),
4750
}
4851
patchHandlers = append(patchHandlers, workerMetaMutators()...)
4952

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright 2025 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package tags
5+
6+
import (
7+
"context"
8+
9+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
10+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
12+
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
13+
ctrl "sigs.k8s.io/controller-runtime"
14+
"sigs.k8s.io/controller-runtime/pkg/client"
15+
16+
capav1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
17+
eksv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2"
18+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
19+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation"
20+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches"
21+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables"
22+
)
23+
24+
const (
25+
// VariableName is the external patch variable name.
26+
VariableName = "additionalTags"
27+
)
28+
29+
type eksTagsClusterPatchHandler struct {
30+
metaVariableName string
31+
variableFieldPath []string
32+
patchSelector clusterv1.PatchSelector
33+
}
34+
35+
func newEKSClusterPatchHandler(
36+
metaVariableName string,
37+
variableFieldPath []string,
38+
patchSelector clusterv1.PatchSelector,
39+
) *eksTagsClusterPatchHandler {
40+
return &eksTagsClusterPatchHandler{
41+
metaVariableName: metaVariableName,
42+
variableFieldPath: variableFieldPath,
43+
patchSelector: patchSelector,
44+
}
45+
}
46+
47+
func (h *eksTagsClusterPatchHandler) Mutate(
48+
ctx context.Context,
49+
obj *unstructured.Unstructured,
50+
vars map[string]apiextensionsv1.JSON,
51+
holderRef runtimehooksv1.HolderReference,
52+
_ client.ObjectKey,
53+
_ mutation.ClusterGetter,
54+
) error {
55+
log := ctrl.LoggerFrom(ctx).WithValues(
56+
"holderRef", holderRef,
57+
)
58+
59+
additionalTagsVar, err := variables.Get[capav1.Tags](
60+
vars,
61+
h.metaVariableName,
62+
h.variableFieldPath...,
63+
)
64+
if err != nil {
65+
if variables.IsNotFoundError(err) {
66+
log.V(5).Info("EKS additionalTags variable for control plane not defined")
67+
return nil
68+
}
69+
return err
70+
}
71+
72+
log = log.WithValues(
73+
"variableName",
74+
h.metaVariableName,
75+
"variableFieldPath",
76+
h.variableFieldPath,
77+
"variableValue",
78+
additionalTagsVar,
79+
)
80+
81+
return patches.MutateIfApplicable(
82+
obj,
83+
vars,
84+
&holderRef,
85+
h.patchSelector,
86+
log,
87+
func(obj *eksv1.AWSManagedControlPlaneTemplate) error {
88+
log.WithValues(
89+
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
90+
"patchedObjectName", client.ObjectKeyFromObject(obj),
91+
).Info("setting additionalTags in AWSManagedControlPlaneTemplate spec")
92+
93+
obj.Spec.Template.Spec.AdditionalTags = additionalTagsVar
94+
95+
return nil
96+
},
97+
)
98+
}
99+
100+
func NewClusterPatch() *eksTagsClusterPatchHandler {
101+
return newEKSClusterPatchHandler(
102+
v1alpha1.ClusterConfigVariableName,
103+
[]string{
104+
v1alpha1.EKSVariableName,
105+
VariableName,
106+
},
107+
clusterv1.PatchSelector{
108+
APIVersion: eksv1.GroupVersion.String(),
109+
Kind: "AWSManagedControlPlaneTemplate",
110+
MatchResources: clusterv1.PatchSelectorMatch{
111+
ControlPlane: true,
112+
},
113+
},
114+
)
115+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2025 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package tags
5+
6+
import (
7+
. "github.com/onsi/ginkgo/v2"
8+
"github.com/onsi/gomega"
9+
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
10+
11+
capav1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
12+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
13+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation"
14+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest"
15+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/testutils"
16+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers"
17+
)
18+
19+
var _ = Describe("Generate EKS Tags patches for managed control plane", func() {
20+
patchGenerator := func() mutation.GeneratePatches {
21+
return mutation.NewMetaGeneratePatchesHandler("", helpers.TestEnv.Client, NewClusterPatch()).(mutation.GeneratePatches)
22+
}
23+
24+
testDefs := []capitest.PatchTestDef{
25+
{
26+
Name: "unset variable",
27+
},
28+
{
29+
Name: "additionalTags set for managed control plane",
30+
Vars: []runtimehooksv1.Variable{
31+
capitest.VariableWithValue(
32+
v1alpha1.ClusterConfigVariableName,
33+
capav1.Tags{
34+
"Environment": "production",
35+
"Team": "platform",
36+
},
37+
v1alpha1.EKSVariableName,
38+
VariableName,
39+
),
40+
},
41+
RequestItem: testutils.NewEKSControlPlaneRequestItem("1234"),
42+
ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{
43+
Operation: "add",
44+
Path: "/spec/template/spec/additionalTags",
45+
ValueMatcher: gomega.And(
46+
gomega.HaveKeyWithValue("Environment", "production"),
47+
gomega.HaveKeyWithValue("Team", "platform"),
48+
),
49+
}},
50+
},
51+
{
52+
Name: "empty additionalTags for managed control plane",
53+
Vars: []runtimehooksv1.Variable{
54+
capitest.VariableWithValue(
55+
v1alpha1.ClusterConfigVariableName,
56+
capav1.Tags{},
57+
v1alpha1.EKSVariableName,
58+
VariableName,
59+
),
60+
},
61+
RequestItem: testutils.NewEKSControlPlaneRequestItem("1234"),
62+
ExpectedPatchMatchers: []capitest.JSONPatchMatcher{},
63+
},
64+
{
65+
Name: "additionalTags with special characters for managed control plane",
66+
Vars: []runtimehooksv1.Variable{
67+
capitest.VariableWithValue(
68+
v1alpha1.ClusterConfigVariableName,
69+
capav1.Tags{
70+
"kubernetes.io/cluster/test-cluster": "owned",
71+
"Cost-Center": "12345",
72+
"Environment": "dev-test",
73+
},
74+
v1alpha1.EKSVariableName,
75+
VariableName,
76+
),
77+
},
78+
RequestItem: testutils.NewEKSControlPlaneRequestItem("1234"),
79+
ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{
80+
Operation: "add",
81+
Path: "/spec/template/spec/additionalTags",
82+
ValueMatcher: gomega.And(
83+
gomega.HaveKeyWithValue("kubernetes.io/cluster/test-cluster", "owned"),
84+
gomega.HaveKeyWithValue("Cost-Center", "12345"),
85+
gomega.HaveKeyWithValue("Environment", "dev-test"),
86+
),
87+
}},
88+
},
89+
}
90+
91+
// create test node for each case
92+
for _, tt := range testDefs {
93+
It(tt.Name, func() {
94+
capitest.AssertGeneratePatches(
95+
GinkgoT(),
96+
patchGenerator,
97+
&tt,
98+
)
99+
})
100+
}
101+
})
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2025 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package tags
5+
6+
import (
7+
"testing"
8+
9+
. "github.com/onsi/ginkgo/v2"
10+
"github.com/onsi/gomega"
11+
)
12+
13+
func TestEKSTagsPatch(t *testing.T) {
14+
gomega.RegisterFailHandler(Fail)
15+
RunSpecs(t, "EKS Tags mutator suite")
16+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2025 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package tags
5+
6+
import (
7+
capav1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
8+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
9+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation"
10+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches/selectors"
11+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/tags"
12+
)
13+
14+
func NewWorkerPatch() mutation.MetaMutator {
15+
return tags.NewAWSTagsPatchHandler(
16+
v1alpha1.WorkerConfigVariableName,
17+
[]string{
18+
v1alpha1.EKSVariableName,
19+
tags.VariableName,
20+
},
21+
selectors.InfrastructureWorkerMachineTemplates(
22+
capav1.GroupVersion.Version,
23+
"AWSMachineTemplate",
24+
),
25+
)
26+
}

0 commit comments

Comments
 (0)