Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,14 @@ spec:
with the IAM role for the instance. The instance profile contains the IAM
role.
type: string
id:
description: |-
ID is the ID of an existing launch template (e.g. lt-xxxx). When set, CAPA will
not create or manage the launch template and will use the referenced one directly.
type: string
x-kubernetes-validations:
- message: ID is immutable
rule: self == oldSelf
imageLookupBaseOS:
description: |-
ImageLookupBaseOS is the name of the base operating system to use for
Expand Down Expand Up @@ -784,6 +792,9 @@ spec:
name:
description: The name of the launch template.
type: string
x-kubernetes-validations:
- message: Name is immutable once set
rule: self == oldSelf
nonRootVolumes:
description: Configuration options for the non root storage volumes.
items:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,8 @@ spec:
AWSLaunchTemplate specifies the launch template to use to create the managed node group.
If AWSLaunchTemplate is specified, certain node group configuraions outside of launch template
are prohibited (https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html).
When AWSLaunchTemplate.ID is set, CAPA treats the template as BYO and does not create or
delete the launch template.
properties:
additionalSecurityGroups:
description: |-
Expand Down Expand Up @@ -677,6 +679,14 @@ spec:
with the IAM role for the instance. The instance profile contains the IAM
role.
type: string
id:
description: |-
ID is the ID of an existing launch template (e.g. lt-xxxx). When set, CAPA will
not create or manage the launch template and will use the referenced one directly.
type: string
x-kubernetes-validations:
- message: ID is immutable
rule: self == oldSelf
imageLookupBaseOS:
description: |-
ImageLookupBaseOS is the name of the base operating system to use for
Expand Down Expand Up @@ -793,6 +803,9 @@ spec:
name:
description: The name of the launch template.
type: string
x-kubernetes-validations:
- message: Name is immutable once set
rule: self == oldSelf
nonRootVolumes:
description: Configuration options for the non root storage volumes.
items:
Expand Down
10 changes: 10 additions & 0 deletions exp/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ func (src *AWSMachinePool) ConvertTo(dstRaw conversion.Hub) error {
if restored.Spec.AWSLaunchTemplate.InstanceMetadataOptions != nil {
dst.Spec.AWSLaunchTemplate.InstanceMetadataOptions = restored.Spec.AWSLaunchTemplate.InstanceMetadataOptions
}
// ID is a v1beta2-only field; restore it from the annotation.
if restored.Spec.AWSLaunchTemplate.ID != nil {
dst.Spec.AWSLaunchTemplate.ID = restored.Spec.AWSLaunchTemplate.ID
}
if restored.Spec.AvailabilityZoneSubnetType != nil {
dst.Spec.AvailabilityZoneSubnetType = restored.Spec.AvailabilityZoneSubnetType
}
Expand Down Expand Up @@ -118,6 +122,10 @@ func (src *AWSManagedMachinePool) ConvertTo(dstRaw conversion.Hub) error {
if dst.Spec.AWSLaunchTemplate == nil {
dst.Spec.AWSLaunchTemplate = restored.Spec.AWSLaunchTemplate
}
// ID is a v1beta2-only field (BYO launch template); restore it from the annotation.
if restored.Spec.AWSLaunchTemplate.ID != nil {
dst.Spec.AWSLaunchTemplate.ID = restored.Spec.AWSLaunchTemplate.ID
}
dst.Spec.AWSLaunchTemplate.InstanceMetadataOptions = restored.Spec.AWSLaunchTemplate.InstanceMetadataOptions
dst.Spec.AWSLaunchTemplate.NonRootVolumes = restored.Spec.AWSLaunchTemplate.NonRootVolumes

Expand Down Expand Up @@ -233,6 +241,8 @@ func (r *AWSFargateProfileList) ConvertFrom(srcRaw conversion.Hub) error {
}

// Convert_v1beta2_AWSLaunchTemplate_To_v1beta1_AWSLaunchTemplate converts the v1beta2 AWSLaunchTemplate receiver to a v1beta1 AWSLaunchTemplate.
// AWSLaunchTemplate.ID (BYO launch template) is a v1beta2-only field and is intentionally dropped on downgrade;
// v1beta1 is deprecated and will not gain new fields.
func Convert_v1beta2_AWSLaunchTemplate_To_v1beta1_AWSLaunchTemplate(in *expinfrav1.AWSLaunchTemplate, out *AWSLaunchTemplate, s apiconversion.Scope) error {
return autoConvert_v1beta2_AWSLaunchTemplate_To_v1beta1_AWSLaunchTemplate(in, out, s)
}
Expand Down
1 change: 1 addition & 0 deletions exp/api/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions exp/api/v1beta2/awsmanagedmachinepool_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ type AWSManagedMachinePoolSpec struct {
// AWSLaunchTemplate specifies the launch template to use to create the managed node group.
// If AWSLaunchTemplate is specified, certain node group configuraions outside of launch template
// are prohibited (https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html).
// When AWSLaunchTemplate.ID is set, CAPA treats the template as BYO and does not create or
// delete the launch template.
// +optional
AWSLaunchTemplate *AWSLaunchTemplate `json:"awsLaunchTemplate,omitempty"`

Expand Down
9 changes: 9 additions & 0 deletions exp/api/v1beta2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,16 @@ type BlockDeviceMapping struct {

// AWSLaunchTemplate defines the desired state of AWSLaunchTemplate.
type AWSLaunchTemplate struct {
// ID is the ID of an existing launch template (e.g. lt-xxxx). When set, CAPA will
// not create or manage the launch template and will use the referenced one directly.
// +optional
// +immutable
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ID is immutable"
ID *string `json:"id,omitempty"`
Comment thread
AmitSahastra marked this conversation as resolved.

// The name of the launch template.
// +immutable
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Name is immutable once set"
Name string `json:"name,omitempty"`

// The name or the Amazon Resource Name (ARN) of the instance profile associated
Expand Down
5 changes: 5 additions & 0 deletions exp/api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 12 additions & 2 deletions exp/controllers/awsmanagedmachinepool_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package controllers

import (
"context"
"strconv"
"time"

autoscalingtypes "github.com/aws/aws-sdk-go-v2/service/autoscaling/types"
Expand Down Expand Up @@ -212,7 +213,15 @@ func (r *AWSManagedMachinePoolReconciler) reconcileNormal(
ec2svc := r.getEC2Service(ec2Scope)
reconSvc := r.getReconcileService(ec2Scope)

if machinePoolScope.ManagedMachinePool.Spec.AWSLaunchTemplate != nil {
// BYO launch template: use existing LT; do not create, update, or delete it.
if machinePoolScope.IsBYOLaunchTemplate() {
lt := machinePoolScope.ManagedMachinePool.Spec.AWSLaunchTemplate
machinePoolScope.SetLaunchTemplateIDStatus(*lt.ID)
if lt.VersionNumber != nil {
machinePoolScope.SetLaunchTemplateLatestVersionStatus(strconv.FormatInt(*lt.VersionNumber, 10))
}
v1beta1conditions.MarkTrue(machinePoolScope.ManagedMachinePool, expinfrav1.LaunchTemplateReadyCondition)
} else if machinePoolScope.ManagedMachinePool.Spec.AWSLaunchTemplate != nil {
canStartInstanceRefresh := func() (bool, *autoscalingtypes.InstanceRefreshStatus, error) {
return true, nil, nil
}
Expand Down Expand Up @@ -268,7 +277,8 @@ func (r *AWSManagedMachinePoolReconciler) reconcileDelete(
return errors.Wrapf(err, "failed to reconcile machine pool deletion for AWSManagedMachinePool %s/%s", machinePoolScope.ManagedMachinePool.Namespace, machinePoolScope.ManagedMachinePool.Name)
}

if machinePoolScope.ManagedMachinePool.Spec.AWSLaunchTemplate != nil {
// Only delete the launch template when it is CAPA-managed (not BYO).
if machinePoolScope.ManagedMachinePool.Spec.AWSLaunchTemplate != nil && !machinePoolScope.IsBYOLaunchTemplate() {
launchTemplateID := machinePoolScope.ManagedMachinePool.Status.LaunchTemplateID
launchTemplate, _, _, _, err := ec2Svc.GetLaunchTemplate(machinePoolScope.LaunchTemplateName())
if err != nil {
Expand Down
85 changes: 78 additions & 7 deletions exp/webhooks/awsmanagedmachinepool_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,63 @@ func (w *AWSManagedMachinePool) validateLaunchTemplate(r *expinfrav1.AWSManagedM
return allErrs
}

if r.Spec.InstanceType != nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "InstanceType"), r.Spec.InstanceType, "InstanceType cannot be specified when LaunchTemplate is specified"))
lt := r.Spec.AWSLaunchTemplate
ltPath := field.NewPath("spec", "awsLaunchTemplate")
isBYO := lt.ID != nil && *lt.ID != ""

// For CAPA-managed LTs (no id), spec.instanceType is forbidden because the instance type
// must be configured inside the launch template itself. For BYO LTs (id is set), the AWS
// CreateNodegroup API allows InstanceTypes to be specified alongside the launch template
// when the launch template itself does not specify an instance type.
if r.Spec.InstanceType != nil && !isBYO {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "instanceType"), r.Spec.InstanceType, "instanceType cannot be specified with a CAPA-managed launch template; set spec.awsLaunchTemplate.instanceType instead"))
}
if r.Spec.DiskSize != nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "DiskSize"), r.Spec.DiskSize, "DiskSize cannot be specified when LaunchTemplate is specified"))
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "diskSize"), r.Spec.DiskSize, "diskSize cannot be specified when LaunchTemplate is specified"))
}

if lt.IamInstanceProfile != "" {
allErrs = append(allErrs, field.Invalid(ltPath.Child("iamInstanceProfile"), lt.IamInstanceProfile, "IAM instance profile in launch template is prohibited in EKS managed node group"))
}

if r.Spec.AWSLaunchTemplate.IamInstanceProfile != "" {
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"))
// When using a BYO launch template (ID is set), versionNumber is required and
// CAPA-managed fields must not be specified.
if isBYO {
if lt.VersionNumber == nil {
allErrs = append(allErrs, field.Required(ltPath.Child("versionNumber"), "versionNumber is required when using a BYO launch template (id is set)"))
}
if lt.AMI.ID != nil || lt.AMI.EKSOptimizedLookupType != nil {
allErrs = append(allErrs, field.Forbidden(ltPath.Child("ami"), "ami cannot be specified with a BYO launch template (id is set)"))
}
if lt.InstanceType != "" {
allErrs = append(allErrs, field.Forbidden(ltPath.Child("instanceType"), "instanceType cannot be specified with a BYO launch template (id is set)"))
}
Comment thread
AmitSahastra marked this conversation as resolved.
if lt.RootVolume != nil {
allErrs = append(allErrs, field.Forbidden(ltPath.Child("rootVolume"), "rootVolume cannot be specified with a BYO launch template (id is set)"))
}
if len(lt.NonRootVolumes) > 0 {
allErrs = append(allErrs, field.Forbidden(ltPath.Child("nonRootVolumes"), "nonRootVolumes cannot be specified with a BYO launch template (id is set)"))
}
if lt.SSHKeyName != nil {
allErrs = append(allErrs, field.Forbidden(ltPath.Child("sshKeyName"), "sshKeyName cannot be specified with a BYO launch template (id is set)"))
}
if lt.ImageLookupFormat != "" {
allErrs = append(allErrs, field.Forbidden(ltPath.Child("imageLookupFormat"), "imageLookupFormat cannot be specified with a BYO launch template (id is set)"))
}
if lt.ImageLookupOrg != "" {
allErrs = append(allErrs, field.Forbidden(ltPath.Child("imageLookupOrg"), "imageLookupOrg cannot be specified with a BYO launch template (id is set)"))
}
if lt.ImageLookupBaseOS != "" {
allErrs = append(allErrs, field.Forbidden(ltPath.Child("imageLookupBaseOS"), "imageLookupBaseOS cannot be specified with a BYO launch template (id is set)"))
}
if len(lt.AdditionalSecurityGroups) > 0 {
allErrs = append(allErrs, field.Forbidden(ltPath.Child("additionalSecurityGroups"), "additionalSecurityGroups cannot be specified with a BYO launch template (id is set)"))
}
// amiType is silently ignored at the service layer for BYO LTs; the AMI type is
// determined entirely by the referenced launch template.
if r.Spec.AMIType != nil {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "amiType"), "amiType cannot be specified with a BYO launch template; the AMI type is determined by the launch template"))
}
Comment thread
AmitSahastra marked this conversation as resolved.
Outdated
}

return allErrs
Expand Down Expand Up @@ -278,8 +326,31 @@ func (w *AWSManagedMachinePool) validateImmutable(r *expinfrav1.AWSManagedMachin
field.Invalid(field.NewPath("spec", "AWSLaunchTemplate"), old.Spec.AWSLaunchTemplate, "field is immutable"),
)
}
if old.Spec.AWSLaunchTemplate != nil && r.Spec.AWSLaunchTemplate != nil {
appendErrorIfMutated(old.Spec.AWSLaunchTemplate.Name, r.Spec.AWSLaunchTemplate.Name, "awsLaunchTemplate.name")
allErrs = append(allErrs, w.validateLaunchTemplateImmutability(r, old)...)

return allErrs
}

// validateLaunchTemplateImmutability ensures that immutable fields within AWSLaunchTemplate
// (ID and Name) are not modified after creation. VersionNumber is intentionally excluded
// as it may be updated to roll out a new launch template version to the nodegroup.
func (w *AWSManagedMachinePool) validateLaunchTemplateImmutability(r *expinfrav1.AWSManagedMachinePool, old *expinfrav1.AWSManagedMachinePool) field.ErrorList {
var allErrs field.ErrorList

oldLT := old.Spec.AWSLaunchTemplate
newLT := r.Spec.AWSLaunchTemplate

if oldLT == nil || newLT == nil {
return allErrs
}

ltPath := field.NewPath("spec", "awsLaunchTemplate")

if !reflect.DeepEqual(oldLT.ID, newLT.ID) {
allErrs = append(allErrs, field.Forbidden(ltPath.Child("id"), "id is immutable"))
}
if !reflect.DeepEqual(oldLT.Name, newLT.Name) {
allErrs = append(allErrs, field.Forbidden(ltPath.Child("name"), "name is immutable"))
}

return allErrs
Expand Down
Loading
Loading