Skip to content

Commit 2f00c8e

Browse files
committed
feat: add additionalTags AWS handlers
1 parent a4ca329 commit 2f00c8e

File tree

11 files changed

+890
-0
lines changed

11 files changed

+890
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
+++
2+
title = "AWS Additional Tags"
3+
+++
4+
5+
The AWS additional tags customization allows the user to specify custom tags to be applied to AWS resources created by the cluster.
6+
The customization can be applied at the cluster level, control plane 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 all AWS 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+
aws:
25+
additionalTags:
26+
Environment: production
27+
Team: platform
28+
CostCenter: "12345"
29+
controlPlane:
30+
aws:
31+
additionalTags:
32+
NodeType: control-plane
33+
- name: workerConfig
34+
value:
35+
aws:
36+
additionalTags:
37+
NodeType: worker
38+
Workload: general
39+
```
40+
41+
We can further customize individual MachineDeployments by using the overrides field with the following configuration:
42+
43+
```yaml
44+
spec:
45+
topology:
46+
# ...
47+
workers:
48+
machineDeployments:
49+
- class: default-worker
50+
name: md-0
51+
variables:
52+
overrides:
53+
- name: workerConfig
54+
value:
55+
aws:
56+
additionalTags:
57+
NodeType: worker
58+
Workload: database
59+
Environment: production
60+
```
61+
62+
## Tag Precedence
63+
64+
When tags are specified at multiple levels, the following precedence applies (higher precedence overrides lower):
65+
66+
1. **Worker level tags** and **Control plane level tags** (highest precedence)
67+
1. **Cluster level tags** (lowest precedence)
68+
69+
This means that if the same tag key is specified at multiple levels, the worker and contorl-plane level values will take precedence over the cluster level values.
70+
71+
## Applying this configuration will result in the following values being set
72+
73+
- `AWSCluster`:
74+
75+
- ```yaml
76+
spec:
77+
template:
78+
spec:
79+
additionalTags:
80+
Environment: production
81+
Team: platform
82+
CostCenter: "12345"
83+
```
84+
85+
- control-plane `AWSMachineTemplate`:
86+
87+
- ```yaml
88+
spec:
89+
template:
90+
spec:
91+
additionalTags:
92+
Environment: production
93+
Team: platform
94+
CostCenter: "12345"
95+
NodeType: control-plane
96+
```
97+
98+
- worker `AWSMachineTemplate`:
99+
100+
- ```yaml
101+
spec:
102+
template:
103+
spec:
104+
additionalTags:
105+
Environment: production
106+
Team: platform
107+
CostCenter: "12345"
108+
NodeType: worker
109+
Workload: general
110+
```

pkg/handlers/aws/mutation/metapatch_handler.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/placementgroup"
1919
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/region"
2020
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/securitygroups"
21+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/tags"
2122
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/volumes"
2223
genericmutation "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation"
2324
)
@@ -26,9 +27,11 @@ import (
2627
func MetaPatchHandler(mgr manager.Manager) handlers.Named {
2728
patchHandlers := []mutation.MetaMutator{
2829
calico.NewPatch(),
30+
tags.NewClusterPatch(),
2931
region.NewPatch(),
3032
network.NewPatch(),
3133
controlplaneloadbalancer.NewPatch(),
34+
tags.NewControlPlanePatch(),
3235
identityref.NewPatch(),
3336
iaminstanceprofile.NewControlPlanePatch(),
3437
instancetype.NewControlPlanePatch(),
@@ -50,6 +53,7 @@ func MetaPatchHandler(mgr manager.Manager) handlers.Named {
5053
// MetaWorkerPatchHandler returns a meta patch handler for mutating CAPA workers.
5154
func MetaWorkerPatchHandler(mgr manager.Manager) handlers.Named {
5255
patchHandlers := []mutation.MetaMutator{
56+
tags.NewWorkerPatch(),
5357
iaminstanceprofile.NewWorkerPatch(),
5458
instancetype.NewWorkerPatch(),
5559
ami.NewWorkerPatch(),
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation"
18+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches"
19+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables"
20+
)
21+
22+
const (
23+
// VariableName is the external patch variable name.
24+
VariableName = "additionalTags"
25+
)
26+
27+
type awsTagsPatchHandler struct {
28+
metaVariableName string
29+
variableFieldPath []string
30+
patchSelector clusterv1.PatchSelector
31+
}
32+
33+
func NewAWSTagsPatchHandler(
34+
metaVariableName string,
35+
variableFieldPath []string,
36+
patchSelector clusterv1.PatchSelector,
37+
) *awsTagsPatchHandler {
38+
return &awsTagsPatchHandler{
39+
metaVariableName: metaVariableName,
40+
variableFieldPath: variableFieldPath,
41+
patchSelector: patchSelector,
42+
}
43+
}
44+
45+
func (h *awsTagsPatchHandler) Mutate(
46+
ctx context.Context,
47+
obj *unstructured.Unstructured,
48+
vars map[string]apiextensionsv1.JSON,
49+
holderRef runtimehooksv1.HolderReference,
50+
_ client.ObjectKey,
51+
_ mutation.ClusterGetter,
52+
) error {
53+
log := ctrl.LoggerFrom(ctx).WithValues(
54+
"holderRef", holderRef,
55+
)
56+
57+
additionalTagsVar, err := variables.Get[capav1.Tags](
58+
vars,
59+
h.metaVariableName,
60+
h.variableFieldPath...,
61+
)
62+
if err != nil {
63+
if variables.IsNotFoundError(err) {
64+
log.V(5).Info("AWS additionalTags variable not defined")
65+
return nil
66+
}
67+
return err
68+
}
69+
70+
log = log.WithValues(
71+
"variableName",
72+
h.metaVariableName,
73+
"variableFieldPath",
74+
h.variableFieldPath,
75+
"variableValue",
76+
additionalTagsVar,
77+
)
78+
79+
return patches.MutateIfApplicable(
80+
obj,
81+
vars,
82+
&holderRef,
83+
h.patchSelector,
84+
log,
85+
func(obj *capav1.AWSMachineTemplate) error {
86+
log.WithValues(
87+
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
88+
"patchedObjectName", client.ObjectKeyFromObject(obj),
89+
).Info("setting additionalTags in AWSMachineTemplate spec")
90+
91+
obj.Spec.Template.Spec.AdditionalTags = additionalTagsVar
92+
93+
return nil
94+
},
95+
)
96+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
18+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation"
19+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches"
20+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches/selectors"
21+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables"
22+
)
23+
24+
type awsTagsClusterPatchHandler struct {
25+
metaVariableName string
26+
variableFieldPath []string
27+
patchSelector clusterv1.PatchSelector
28+
}
29+
30+
func NewAWSTagsClusterPatchHandler(
31+
metaVariableName string,
32+
variableFieldPath []string,
33+
patchSelector clusterv1.PatchSelector,
34+
) *awsTagsClusterPatchHandler {
35+
return &awsTagsClusterPatchHandler{
36+
metaVariableName: metaVariableName,
37+
variableFieldPath: variableFieldPath,
38+
patchSelector: patchSelector,
39+
}
40+
}
41+
42+
func (h *awsTagsClusterPatchHandler) Mutate(
43+
ctx context.Context,
44+
obj *unstructured.Unstructured,
45+
vars map[string]apiextensionsv1.JSON,
46+
holderRef runtimehooksv1.HolderReference,
47+
_ client.ObjectKey,
48+
_ mutation.ClusterGetter,
49+
) error {
50+
log := ctrl.LoggerFrom(ctx).WithValues(
51+
"holderRef", holderRef,
52+
)
53+
54+
additionalTagsVar, err := variables.Get[capav1.Tags](
55+
vars,
56+
h.metaVariableName,
57+
h.variableFieldPath...,
58+
)
59+
if err != nil {
60+
if variables.IsNotFoundError(err) {
61+
log.V(5).Info("AWS additionalTags variable for cluster not defined")
62+
return nil
63+
}
64+
return err
65+
}
66+
67+
log = log.WithValues(
68+
"variableName",
69+
h.metaVariableName,
70+
"variableFieldPath",
71+
h.variableFieldPath,
72+
"variableValue",
73+
additionalTagsVar,
74+
)
75+
76+
return patches.MutateIfApplicable(
77+
obj,
78+
vars,
79+
&holderRef,
80+
h.patchSelector,
81+
log,
82+
func(obj *capav1.AWSClusterTemplate) error {
83+
log.WithValues(
84+
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
85+
"patchedObjectName", client.ObjectKeyFromObject(obj),
86+
).Info("setting additionalTags in AWSClusterTemplate spec")
87+
88+
obj.Spec.Template.Spec.AdditionalTags = additionalTagsVar
89+
90+
return nil
91+
},
92+
)
93+
}
94+
95+
func NewClusterPatch() *awsTagsClusterPatchHandler {
96+
return NewAWSTagsClusterPatchHandler(
97+
v1alpha1.ClusterConfigVariableName,
98+
[]string{
99+
v1alpha1.AWSVariableName,
100+
VariableName,
101+
},
102+
selectors.InfrastructureCluster(capav1.GroupVersion.Version, "AWSClusterTemplate"),
103+
)
104+
}

0 commit comments

Comments
 (0)