Skip to content

Commit 76ca116

Browse files
committed
feat: add rootvolume handlers
1 parent 5b62278 commit 76ca116

File tree

13 files changed

+661
-0
lines changed

13 files changed

+661
-0
lines changed

pkg/handlers/aws/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/aws/mutation/network"
1717
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/placementgroup"
1818
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/region"
19+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/rootvolume"
1920
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/securitygroups"
2021
genericmutation "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation"
2122
)
@@ -31,6 +32,7 @@ func MetaPatchHandler(mgr manager.Manager) handlers.Named {
3132
instancetype.NewControlPlanePatch(),
3233
ami.NewControlPlanePatch(),
3334
securitygroups.NewControlPlanePatch(),
35+
rootvolume.NewControlPlanePatch(),
3436
placementgroup.NewControlPlanePatch(),
3537
}
3638
patchHandlers = append(patchHandlers, genericmutation.MetaMutators(mgr)...)
@@ -50,6 +52,7 @@ func MetaWorkerPatchHandler(mgr manager.Manager) handlers.Named {
5052
instancetype.NewWorkerPatch(),
5153
ami.NewWorkerPatch(),
5254
securitygroups.NewWorkerPatch(),
55+
rootvolume.NewWorkerPatch(),
5356
placementgroup.NewWorkerPatch(),
5457
}
5558
patchHandlers = append(patchHandlers, genericmutation.WorkerMetaMutators()...)
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2025 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package rootvolume
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+
"k8s.io/utils/ptr"
12+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
13+
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
14+
ctrl "sigs.k8s.io/controller-runtime"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
17+
capav1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/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 = "rootVolume"
27+
)
28+
29+
type awsRootVolumeSpecPatchHandler struct {
30+
metaVariableName string
31+
variableFieldPath []string
32+
patchSelector clusterv1.PatchSelector
33+
}
34+
35+
func NewAWSRootVolumeSpecPatchHandler(
36+
metaVariableName string,
37+
variableFieldPath []string,
38+
patchSelector clusterv1.PatchSelector,
39+
) *awsRootVolumeSpecPatchHandler {
40+
return &awsRootVolumeSpecPatchHandler{
41+
metaVariableName: metaVariableName,
42+
variableFieldPath: variableFieldPath,
43+
patchSelector: patchSelector,
44+
}
45+
}
46+
47+
func (h *awsRootVolumeSpecPatchHandler) 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+
rootVolumeVar, err := variables.Get[v1alpha1.Volume](
59+
vars,
60+
h.metaVariableName,
61+
h.variableFieldPath...,
62+
)
63+
if err != nil {
64+
if variables.IsNotFoundError(err) {
65+
log.V(5).
66+
Info("No root volume configuration provided. Skipping.")
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+
rootVolumeVar,
79+
)
80+
81+
return patches.MutateIfApplicable(
82+
obj,
83+
vars,
84+
&holderRef,
85+
h.patchSelector,
86+
log,
87+
func(obj *capav1.AWSMachineTemplate) error {
88+
log.WithValues(
89+
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
90+
"patchedObjectName", client.ObjectKeyFromObject(obj),
91+
).Info("setting root volume configuration")
92+
93+
// Convert the v1alpha1.Volume to capav1.Volume
94+
rootVolume := capav1.Volume{
95+
DeviceName: rootVolumeVar.DeviceName,
96+
Size: rootVolumeVar.Size,
97+
Type: rootVolumeVar.Type,
98+
IOPS: rootVolumeVar.IOPS,
99+
EncryptionKey: rootVolumeVar.EncryptionKey,
100+
}
101+
if rootVolumeVar.Throughput != 0 {
102+
rootVolume.Throughput = ptr.To(rootVolumeVar.Throughput)
103+
}
104+
if rootVolumeVar.Encrypted {
105+
rootVolume.Encrypted = ptr.To(rootVolumeVar.Encrypted)
106+
}
107+
108+
obj.Spec.Template.Spec.RootVolume = &rootVolume
109+
return nil
110+
},
111+
)
112+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2025 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package rootvolume
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/patches/selectors"
10+
)
11+
12+
func NewControlPlanePatch() *awsRootVolumeSpecPatchHandler {
13+
return NewAWSRootVolumeSpecPatchHandler(
14+
v1alpha1.ClusterConfigVariableName,
15+
[]string{
16+
v1alpha1.ControlPlaneConfigVariableName,
17+
v1alpha1.AWSVariableName,
18+
VariableName,
19+
},
20+
selectors.InfrastructureControlPlaneMachines(
21+
capav1.GroupVersion.Version,
22+
"AWSMachineTemplate",
23+
),
24+
)
25+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2025 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package rootvolume
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/internal/test/request"
16+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers"
17+
)
18+
19+
var _ = Describe("Generate RootVolume patches for ControlPlane", func() {
20+
patchGenerator := func() mutation.GeneratePatches {
21+
return mutation.NewMetaGeneratePatchesHandler(
22+
"",
23+
helpers.TestEnv.Client,
24+
NewControlPlanePatch(),
25+
).(mutation.GeneratePatches)
26+
}
27+
28+
testDefs := []capitest.PatchTestDef{
29+
{
30+
Name: "unset variable",
31+
},
32+
{
33+
Name: "RootVolume for controlplane set",
34+
Vars: []runtimehooksv1.Variable{
35+
capitest.VariableWithValue(
36+
v1alpha1.ClusterConfigVariableName,
37+
v1alpha1.Volume{
38+
DeviceName: "/dev/sda1",
39+
Size: 100,
40+
Type: capav1.VolumeTypeGP3,
41+
IOPS: 3000,
42+
Throughput: 125,
43+
Encrypted: true,
44+
EncryptionKey: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
45+
},
46+
v1alpha1.ControlPlaneConfigVariableName,
47+
v1alpha1.AWSVariableName,
48+
VariableName,
49+
),
50+
},
51+
RequestItem: request.NewCPAWSMachineTemplateRequestItem("1234"),
52+
ExpectedPatchMatchers: []capitest.JSONPatchMatcher{
53+
{
54+
Operation: "add",
55+
Path: "/spec/template/spec/rootVolume",
56+
ValueMatcher: gomega.And(
57+
gomega.HaveKeyWithValue("deviceName", "/dev/sda1"),
58+
gomega.HaveKeyWithValue("size", float64(100)),
59+
gomega.HaveKeyWithValue("type", "gp3"),
60+
gomega.HaveKeyWithValue("iops", float64(3000)),
61+
gomega.HaveKeyWithValue("throughput", float64(125)),
62+
gomega.HaveKeyWithValue("encrypted", true),
63+
gomega.HaveKeyWithValue(
64+
"encryptionKey",
65+
"arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
66+
),
67+
),
68+
},
69+
},
70+
},
71+
{
72+
Name: "RootVolume with minimal configuration",
73+
Vars: []runtimehooksv1.Variable{
74+
capitest.VariableWithValue(
75+
v1alpha1.ClusterConfigVariableName,
76+
v1alpha1.Volume{
77+
Size: 50,
78+
Type: capav1.VolumeTypeGP2,
79+
},
80+
v1alpha1.ControlPlaneConfigVariableName,
81+
v1alpha1.AWSVariableName,
82+
VariableName,
83+
),
84+
},
85+
RequestItem: request.NewCPAWSMachineTemplateRequestItem("1234"),
86+
ExpectedPatchMatchers: []capitest.JSONPatchMatcher{
87+
{
88+
Operation: "add",
89+
Path: "/spec/template/spec/rootVolume",
90+
ValueMatcher: gomega.And(
91+
gomega.HaveKeyWithValue("size", float64(50)),
92+
gomega.HaveKeyWithValue("type", "gp2"),
93+
),
94+
},
95+
},
96+
},
97+
}
98+
99+
// create test node for each case
100+
for _, tt := range testDefs {
101+
It(tt.Name, func() {
102+
capitest.AssertGeneratePatches(
103+
GinkgoT(),
104+
patchGenerator,
105+
&tt,
106+
)
107+
})
108+
}
109+
})
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 rootvolume
5+
6+
import (
7+
"testing"
8+
9+
. "github.com/onsi/ginkgo/v2"
10+
. "github.com/onsi/gomega"
11+
)
12+
13+
func TestRootVolumePatch(t *testing.T) {
14+
RegisterFailHandler(Fail)
15+
RunSpecs(t, "AWS root volume patches for ControlPlane and Workers suite")
16+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2025 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package rootvolume
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/patches/selectors"
10+
)
11+
12+
func NewWorkerPatch() *awsRootVolumeSpecPatchHandler {
13+
return NewAWSRootVolumeSpecPatchHandler(
14+
v1alpha1.WorkerConfigVariableName,
15+
[]string{
16+
v1alpha1.AWSVariableName,
17+
VariableName,
18+
},
19+
selectors.InfrastructureWorkerMachineTemplates(
20+
capav1.GroupVersion.Version,
21+
"AWSMachineTemplate",
22+
),
23+
)
24+
}

0 commit comments

Comments
 (0)