Skip to content

Commit d60de14

Browse files
committed
Add AWSManagedPoolTemplate
Signed-off-by: Dinar Valeev <k0da@opensuse.org>
1 parent bb846f0 commit d60de14

File tree

6 files changed

+1013
-55
lines changed

6 files changed

+1013
-55
lines changed

config/crd/bases/infrastructure.cluster.x-k8s.io_awsmanagedmachinepooltemplates.yaml

Lines changed: 654 additions & 0 deletions
Large diffs are not rendered by default.

exp/api/v1beta2/awsmanagedmachinepool_webhook.go

Lines changed: 59 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"fmt"
2222
"reflect"
2323

24+
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/eks"
25+
2426
"github.com/google/go-cmp/cmp"
2527
"github.com/pkg/errors"
2628
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -31,8 +33,6 @@ import (
3133
ctrl "sigs.k8s.io/controller-runtime"
3234
"sigs.k8s.io/controller-runtime/pkg/webhook"
3335
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
34-
35-
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/eks"
3636
)
3737

3838
const (
@@ -60,13 +60,13 @@ type awsManagedMachinePoolWebhook struct{}
6060
var _ webhook.CustomDefaulter = &awsManagedMachinePoolWebhook{}
6161
var _ webhook.CustomValidator = &awsManagedMachinePoolWebhook{}
6262

63-
func (r *AWSManagedMachinePool) validateScaling() field.ErrorList {
63+
func validateScaling(r *AWSManagedMachinePoolSpec) field.ErrorList {
6464
var allErrs field.ErrorList
65-
if r.Spec.Scaling != nil { //nolint:nestif
65+
if r.Scaling != nil { //nolint:nestif
6666
minField := field.NewPath("spec", "scaling", "minSize")
6767
maxField := field.NewPath("spec", "scaling", "maxSize")
68-
minSize := r.Spec.Scaling.MinSize
69-
maxSize := r.Spec.Scaling.MaxSize
68+
minSize := r.Scaling.MinSize
69+
maxSize := r.Scaling.MaxSize
7070
if minSize != nil {
7171
if *minSize < 0 {
7272
allErrs = append(allErrs, field.Invalid(minField, *minSize, "must be greater or equal zero"))
@@ -85,18 +85,18 @@ func (r *AWSManagedMachinePool) validateScaling() field.ErrorList {
8585
return allErrs
8686
}
8787

88-
func (r *AWSManagedMachinePool) validateNodegroupUpdateConfig() field.ErrorList {
88+
func validateNodegroupUpdateConfig(r *AWSManagedMachinePoolSpec) field.ErrorList {
8989
var allErrs field.ErrorList
9090

91-
if r.Spec.UpdateConfig != nil {
91+
if r.UpdateConfig != nil {
9292
nodegroupUpdateConfigField := field.NewPath("spec", "updateConfig")
9393

94-
if r.Spec.UpdateConfig.MaxUnavailable == nil && r.Spec.UpdateConfig.MaxUnavailablePercentage == nil {
95-
allErrs = append(allErrs, field.Invalid(nodegroupUpdateConfigField, r.Spec.UpdateConfig, "must specify one of maxUnavailable or maxUnavailablePercentage when using nodegroup updateconfig"))
94+
if r.UpdateConfig.MaxUnavailable == nil && r.UpdateConfig.MaxUnavailablePercentage == nil {
95+
allErrs = append(allErrs, field.Invalid(nodegroupUpdateConfigField, r.UpdateConfig, "must specify one of maxUnavailable or maxUnavailablePercentage when using nodegroup updateconfig"))
9696
}
9797

98-
if r.Spec.UpdateConfig.MaxUnavailable != nil && r.Spec.UpdateConfig.MaxUnavailablePercentage != nil {
99-
allErrs = append(allErrs, field.Invalid(nodegroupUpdateConfigField, r.Spec.UpdateConfig, "cannot specify both maxUnavailable and maxUnavailablePercentage"))
98+
if r.UpdateConfig.MaxUnavailable != nil && r.UpdateConfig.MaxUnavailablePercentage != nil {
99+
allErrs = append(allErrs, field.Invalid(nodegroupUpdateConfigField, r.UpdateConfig, "cannot specify both maxUnavailable and maxUnavailablePercentage"))
100100
}
101101
}
102102

@@ -106,15 +106,15 @@ func (r *AWSManagedMachinePool) validateNodegroupUpdateConfig() field.ErrorList
106106
return allErrs
107107
}
108108

109-
func (r *AWSManagedMachinePool) validateRemoteAccess() field.ErrorList {
109+
func validateRemoteAccess(r *AWSManagedMachinePoolSpec) field.ErrorList {
110110
var allErrs field.ErrorList
111-
if r.Spec.RemoteAccess == nil {
111+
if r.RemoteAccess == nil {
112112
return allErrs
113113
}
114114
remoteAccessPath := field.NewPath("spec", "remoteAccess")
115-
sourceSecurityGroups := r.Spec.RemoteAccess.SourceSecurityGroups
115+
sourceSecurityGroups := r.RemoteAccess.SourceSecurityGroups
116116

117-
if public := r.Spec.RemoteAccess.Public; public && len(sourceSecurityGroups) > 0 {
117+
if public := r.RemoteAccess.Public; public && len(sourceSecurityGroups) > 0 {
118118
allErrs = append(
119119
allErrs,
120120
field.Invalid(remoteAccessPath.Child("sourceSecurityGroups"), sourceSecurityGroups, "must be empty if public is set"),
@@ -124,28 +124,28 @@ func (r *AWSManagedMachinePool) validateRemoteAccess() field.ErrorList {
124124
return allErrs
125125
}
126126

127-
func (r *AWSManagedMachinePool) validateLaunchTemplate() field.ErrorList {
127+
func validateLaunchTemplate(r *AWSManagedMachinePoolSpec) field.ErrorList {
128128
var allErrs field.ErrorList
129-
if r.Spec.AWSLaunchTemplate == nil {
129+
if r.AWSLaunchTemplate == nil {
130130
return allErrs
131131
}
132132

133-
if r.Spec.InstanceType != nil {
134-
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "InstanceType"), r.Spec.InstanceType, "InstanceType cannot be specified when LaunchTemplate is specified"))
133+
if r.InstanceType != nil {
134+
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "InstanceType"), r.InstanceType, "InstanceType cannot be specified when LaunchTemplate is specified"))
135135
}
136-
if r.Spec.DiskSize != nil {
137-
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "DiskSize"), r.Spec.DiskSize, "DiskSize cannot be specified when LaunchTemplate is specified"))
136+
if r.DiskSize != nil {
137+
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "DiskSize"), r.DiskSize, "DiskSize cannot be specified when LaunchTemplate is specified"))
138138
}
139139

140-
if r.Spec.AWSLaunchTemplate.IamInstanceProfile != "" {
141-
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "AWSLaunchTemplate", "IamInstanceProfile"), r.Spec.AWSLaunchTemplate.IamInstanceProfile, "IAM instance profile in launch template is prohibited in EKS managed node group"))
140+
if r.AWSLaunchTemplate.IamInstanceProfile != "" {
141+
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "AWSLaunchTemplate", "IamInstanceProfile"), r.AWSLaunchTemplate.IamInstanceProfile, "IAM instance profile in launch template is prohibited in EKS managed node group"))
142142
}
143143

144144
return allErrs
145145
}
146146

147-
func (r *AWSManagedMachinePool) validateLifecycleHooks() field.ErrorList {
148-
return validateLifecycleHooks(r.Spec.AWSLifecycleHooks)
147+
func (r *AWSManagedMachinePoolSpec) validateLifecycleHooks() field.ErrorList {
148+
return validateLifecycleHooks(r.AWSLifecycleHooks)
149149
}
150150

151151
// ValidateCreate will do any extra validation when creating a AWSManagedMachinePool.
@@ -162,19 +162,19 @@ func (*awsManagedMachinePoolWebhook) ValidateCreate(_ context.Context, obj runti
162162
if r.Spec.EKSNodegroupName == "" {
163163
allErrs = append(allErrs, field.Required(field.NewPath("spec.eksNodegroupName"), "eksNodegroupName is required"))
164164
}
165-
if errs := r.validateScaling(); errs != nil || len(errs) == 0 {
165+
if errs := validateScaling(&r.Spec); errs != nil || len(errs) == 0 {
166166
allErrs = append(allErrs, errs...)
167167
}
168-
if errs := r.validateRemoteAccess(); len(errs) > 0 {
168+
if errs := validateRemoteAccess(&r.Spec); len(errs) > 0 {
169169
allErrs = append(allErrs, errs...)
170170
}
171-
if errs := r.validateNodegroupUpdateConfig(); len(errs) > 0 {
171+
if errs := validateNodegroupUpdateConfig(&r.Spec); len(errs) > 0 {
172172
allErrs = append(allErrs, errs...)
173173
}
174-
if errs := r.validateLaunchTemplate(); len(errs) > 0 {
174+
if errs := validateLaunchTemplate(&r.Spec); len(errs) > 0 {
175175
allErrs = append(allErrs, errs...)
176176
}
177-
if errs := r.validateLifecycleHooks(); len(errs) > 0 {
177+
if errs := r.Spec.validateLifecycleHooks(); len(errs) > 0 {
178178
allErrs = append(allErrs, errs...)
179179
}
180180

@@ -207,19 +207,19 @@ func (*awsManagedMachinePoolWebhook) ValidateUpdate(_ context.Context, oldObj, n
207207
}
208208

209209
var allErrs field.ErrorList
210-
allErrs = append(allErrs, r.validateImmutable(oldPool)...)
210+
allErrs = append(allErrs, validateAMPImmutable(&oldPool.Spec, &r.Spec)...)
211211
allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
212212

213-
if errs := r.validateScaling(); errs != nil || len(errs) == 0 {
213+
if errs := validateScaling(&r.Spec); errs != nil || len(errs) == 0 {
214214
allErrs = append(allErrs, errs...)
215215
}
216-
if errs := r.validateNodegroupUpdateConfig(); len(errs) > 0 {
216+
if errs := validateNodegroupUpdateConfig(&r.Spec); len(errs) > 0 {
217217
allErrs = append(allErrs, errs...)
218218
}
219-
if errs := r.validateLaunchTemplate(); len(errs) > 0 {
219+
if errs := validateLaunchTemplate(&r.Spec); len(errs) > 0 {
220220
allErrs = append(allErrs, errs...)
221221
}
222-
if errs := r.validateLifecycleHooks(); len(errs) > 0 {
222+
if errs := r.Spec.validateLifecycleHooks(); len(errs) > 0 {
223223
allErrs = append(allErrs, errs...)
224224
}
225225

@@ -239,7 +239,7 @@ func (*awsManagedMachinePoolWebhook) ValidateDelete(_ context.Context, _ runtime
239239
return nil, nil
240240
}
241241

242-
func (r *AWSManagedMachinePool) validateImmutable(old *AWSManagedMachinePool) field.ErrorList {
242+
func validateAMPImmutable(old *AWSManagedMachinePoolSpec, current *AWSManagedMachinePoolSpec) field.ErrorList {
243243
var allErrs field.ErrorList
244244

245245
appendErrorIfMutated := func(old, update interface{}, name string) {
@@ -259,26 +259,26 @@ func (r *AWSManagedMachinePool) validateImmutable(old *AWSManagedMachinePool) fi
259259
}
260260
}
261261

262-
if old.Spec.EKSNodegroupName != "" {
263-
appendErrorIfMutated(old.Spec.EKSNodegroupName, r.Spec.EKSNodegroupName, "eksNodegroupName")
262+
if old.EKSNodegroupName != "" {
263+
appendErrorIfMutated(old.EKSNodegroupName, current.EKSNodegroupName, "eksNodegroupName")
264264
}
265-
appendErrorIfMutated(old.Spec.SubnetIDs, r.Spec.SubnetIDs, "subnetIDs")
266-
appendErrorIfSetAndMutated(old.Spec.RoleName, r.Spec.RoleName, "roleName")
267-
appendErrorIfMutated(old.Spec.DiskSize, r.Spec.DiskSize, "diskSize")
268-
appendErrorIfMutated(old.Spec.AMIType, r.Spec.AMIType, "amiType")
269-
appendErrorIfMutated(old.Spec.RemoteAccess, r.Spec.RemoteAccess, "remoteAccess")
270-
appendErrorIfSetAndMutated(old.Spec.CapacityType, r.Spec.CapacityType, "capacityType")
271-
appendErrorIfMutated(old.Spec.AvailabilityZones, r.Spec.AvailabilityZones, "availabilityZones")
272-
appendErrorIfMutated(old.Spec.AvailabilityZoneSubnetType, r.Spec.AvailabilityZoneSubnetType, "availabilityZoneSubnetType")
273-
if (old.Spec.AWSLaunchTemplate != nil && r.Spec.AWSLaunchTemplate == nil) ||
274-
(old.Spec.AWSLaunchTemplate == nil && r.Spec.AWSLaunchTemplate != nil) {
265+
appendErrorIfMutated(old.SubnetIDs, current.SubnetIDs, "subnetIDs")
266+
appendErrorIfSetAndMutated(old.RoleName, current.RoleName, "roleName")
267+
appendErrorIfMutated(old.DiskSize, current.DiskSize, "diskSize")
268+
appendErrorIfMutated(old.AMIType, current.AMIType, "amiType")
269+
appendErrorIfMutated(old.RemoteAccess, current.RemoteAccess, "remoteAccess")
270+
appendErrorIfSetAndMutated(old.CapacityType, current.CapacityType, "capacityType")
271+
appendErrorIfMutated(old.AvailabilityZones, current.AvailabilityZones, "availabilityZones")
272+
appendErrorIfMutated(old.AvailabilityZoneSubnetType, current.AvailabilityZoneSubnetType, "availabilityZoneSubnetType")
273+
if (old.AWSLaunchTemplate != nil && current.AWSLaunchTemplate == nil) ||
274+
(old.AWSLaunchTemplate == nil && current.AWSLaunchTemplate != nil) {
275275
allErrs = append(
276276
allErrs,
277-
field.Invalid(field.NewPath("spec", "AWSLaunchTemplate"), old.Spec.AWSLaunchTemplate, "field is immutable"),
277+
field.Invalid(field.NewPath("spec", "AWSLaunchTemplate"), old.AWSLaunchTemplate, "field is immutable"),
278278
)
279279
}
280-
if old.Spec.AWSLaunchTemplate != nil && r.Spec.AWSLaunchTemplate != nil {
281-
appendErrorIfMutated(old.Spec.AWSLaunchTemplate.Name, r.Spec.AWSLaunchTemplate.Name, "awsLaunchTemplate.name")
280+
if old.AWSLaunchTemplate != nil && current.AWSLaunchTemplate != nil {
281+
appendErrorIfMutated(old.AWSLaunchTemplate.Name, current.AWSLaunchTemplate.Name, "awsLaunchTemplate.name")
282282
}
283283

284284
return allErrs
@@ -306,9 +306,13 @@ func (*awsManagedMachinePoolWebhook) Default(_ context.Context, obj runtime.Obje
306306
}
307307

308308
if r.Spec.UpdateConfig == nil {
309-
r.Spec.UpdateConfig = &UpdateConfig{
310-
MaxUnavailable: ptr.To[int](1),
311-
}
309+
r.Spec.UpdateConfig = defaultManagedMachinePoolUpdateConfig()
312310
}
313311
return nil
314312
}
313+
314+
func defaultManagedMachinePoolUpdateConfig() *UpdateConfig {
315+
return &UpdateConfig{
316+
MaxUnavailable: ptr.To[int](1),
317+
}
318+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1beta2
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
// +kubebuilder:object:root=true
24+
// +kubebuilder:storageversion
25+
// +kubebuilder:resource:path=awsmanagedmachinepooltemplates,scope=Namespaced,categories=cluster-api,shortName=awsmmpt
26+
27+
// AWSManagedMachinePoolTemplate is the Schema for the awsmanagedmachinepooltemplates API.
28+
type AWSManagedMachinePoolTemplate struct {
29+
metav1.TypeMeta `json:",inline"`
30+
metav1.ObjectMeta `json:"metadata,omitempty"`
31+
32+
Spec AWSManagedMachinePoolTemplateSpec `json:"spec,omitempty"`
33+
}
34+
35+
// AWSManagedMachinePoolTemplateResource wraps AWSManagedMachinePoolSpec
36+
type AWSManagedMachinePoolTemplateResource struct {
37+
Spec AWSManagedMachinePoolSpec `json:"spec"`
38+
}
39+
40+
// AWSManagedMachinePoolTemplateSpec defines the desired state of AWSManagedMachinePoolTemplate.
41+
type AWSManagedMachinePoolTemplateSpec struct {
42+
Template *AWSManagedMachinePoolTemplateResource `json:"template"`
43+
}
44+
45+
// +kubebuilder:object:root=true
46+
47+
// AWSManagedMachinePoolTemplateList contains a list of AWSManagedMachinePoolTemplates.
48+
type AWSManagedMachinePoolTemplateList struct {
49+
metav1.TypeMeta `json:",inline"`
50+
metav1.ListMeta `json:"metadata,omitempty"`
51+
Items []AWSManagedMachinePoolTemplate `json:"items"`
52+
}
53+
54+
func init() {
55+
SchemeBuilder.Register(&AWSManagedMachinePoolTemplate{}, &AWSManagedMachinePoolTemplateList{})
56+
}

0 commit comments

Comments
 (0)