Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ require (
github.com/onsi/ginkgo/v2 v2.27.2
github.com/onsi/gomega v1.38.2
github.com/openshift-eng/openshift-tests-extension v0.0.0-20251105193959-75a0be5d9bd7
github.com/openshift/api v0.0.0-20260114133223-6ab113cb7368
github.com/openshift/api v0.0.0-20260213204242-d34f11c515b3
github.com/openshift/client-go v0.0.0-20251202151200-fb4471581cf8
github.com/openshift/cluster-api-actuator-pkg/testutils v0.0.0-20250910145856-21d03d30056d
github.com/openshift/cluster-control-plane-machine-set-operator v0.0.0-20251029084908-344babe6a957
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,8 @@ github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jD
github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/openshift-eng/openshift-tests-extension v0.0.0-20251105193959-75a0be5d9bd7 h1:Z1swlS6b3Adm6RPhjqefs3DWnNFLDxRX+WC8GMXhja4=
github.com/openshift-eng/openshift-tests-extension v0.0.0-20251105193959-75a0be5d9bd7/go.mod h1:6gkP5f2HL0meusT0Aim8icAspcD1cG055xxBZ9yC68M=
github.com/openshift/api v0.0.0-20260114133223-6ab113cb7368 h1:kSr3DOlq0NCrHd65HB2o/pBsks7AfRm+fkpf9RLUPoc=
github.com/openshift/api v0.0.0-20260114133223-6ab113cb7368/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/openshift/api v0.0.0-20260213204242-d34f11c515b3 h1:SZ8+jxtkMvpb4HDTjSAbaOyhFsw5PiWhjBog+XLY7jc=
github.com/openshift/api v0.0.0-20260213204242-d34f11c515b3/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/openshift/client-go v0.0.0-20251202151200-fb4471581cf8 h1:97rgISdT4IOmXlmEUV5Wr6d8BzzjPclzAjCARLbSlT0=
github.com/openshift/client-go v0.0.0-20251202151200-fb4471581cf8/go.mod h1:WVJnsrbSO1J8x8KceOmv1d5CpoN34Uzsaz1O4MIOKJI=
github.com/openshift/cluster-api-actuator-pkg/testutils v0.0.0-20250910145856-21d03d30056d h1:+sqUThLi/lmgT5/scmmjnS6+RZFtbdxRAscNfCPyLPI=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ spec:
statementEntries:
- effect: Allow
action:
- ec2:AllocateHosts
- ec2:CreateTags
- ec2:DescribeAvailabilityZones
- ec2:DescribeDhcpOptions
Expand All @@ -32,6 +33,7 @@ spec:
- ec2:DescribeRegions
- ec2:DescribeSubnets
- ec2:DescribeVpcs
- ec2:ReleaseHosts
- ec2:RunInstances
- ec2:TerminateInstances
- elasticloadbalancing:DescribeLoadBalancers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,16 @@ spec:
serialized/deserialized from this field.
type: object
x-kubernetes-preserve-unknown-fields: true
synchronizedAPI:
description: |-
synchronizedAPI holds the last stable value of authoritativeAPI.
It is used to detect migration cancellation requests and to restore the resource to its previous state.
Valid values are "MachineAPI" and "ClusterAPI".
When omitted, the resource has not yet been reconciled by the migration controller.
enum:
- MachineAPI
- ClusterAPI
type: string
synchronizedGeneration:
description: |-
synchronizedGeneration is the generation of the authoritative resource that the non-authoritative resource is synchronised with.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,16 @@ spec:
serialized/deserialized from this field.
type: object
x-kubernetes-preserve-unknown-fields: true
synchronizedAPI:
description: |-
synchronizedAPI holds the last stable value of authoritativeAPI.
It is used to detect migration cancellation requests and to restore the resource to its previous state.
Valid values are "MachineAPI" and "ClusterAPI".
When omitted, the resource has not yet been reconciled by the migration controller.
enum:
- MachineAPI
- ClusterAPI
type: string
synchronizedGeneration:
description: |-
synchronizedGeneration is the generation of the authoritative resource that the non-authoritative resource is synchronised with.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,16 @@ spec:
serialized/deserialized from this field.
type: object
x-kubernetes-preserve-unknown-fields: true
synchronizedAPI:
description: |-
synchronizedAPI holds the last stable value of authoritativeAPI.
It is used to detect migration cancellation requests and to restore the resource to its previous state.
Valid values are "MachineAPI" and "ClusterAPI".
When omitted, the resource has not yet been reconciled by the migration controller.
enum:
- MachineAPI
- ClusterAPI
type: string
synchronizedGeneration:
description: |-
synchronizedGeneration is the generation of the authoritative resource that the non-authoritative resource is synchronised with.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,16 @@ spec:
description: replicas is the most recently observed number of replicas.
format: int32
type: integer
synchronizedAPI:
description: |-
synchronizedAPI holds the last stable value of authoritativeAPI.
It is used to detect migration cancellation requests and to restore the resource to its previous state.
Valid values are "MachineAPI" and "ClusterAPI".
When omitted, the resource has not yet been reconciled by the migration controller.
enum:
- MachineAPI
- ClusterAPI
type: string
synchronizedGeneration:
description: |-
synchronizedGeneration is the generation of the authoritative resource that the non-authoritative resource is synchronised with.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,16 @@ spec:
description: replicas is the most recently observed number of replicas.
format: int32
type: integer
synchronizedAPI:
description: |-
synchronizedAPI holds the last stable value of authoritativeAPI.
It is used to detect migration cancellation requests and to restore the resource to its previous state.
Valid values are "MachineAPI" and "ClusterAPI".
When omitted, the resource has not yet been reconciled by the migration controller.
enum:
- MachineAPI
- ClusterAPI
type: string
synchronizedGeneration:
description: |-
synchronizedGeneration is the generation of the authoritative resource that the non-authoritative resource is synchronised with.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,16 @@ spec:
description: replicas is the most recently observed number of replicas.
format: int32
type: integer
synchronizedAPI:
description: |-
synchronizedAPI holds the last stable value of authoritativeAPI.
It is used to detect migration cancellation requests and to restore the resource to its previous state.
Valid values are "MachineAPI" and "ClusterAPI".
When omitted, the resource has not yet been reconciled by the migration controller.
enum:
- MachineAPI
- ClusterAPI
type: string
synchronizedGeneration:
description: |-
synchronizedGeneration is the generation of the authoritative resource that the non-authoritative resource is synchronised with.
Expand Down
80 changes: 72 additions & 8 deletions pkg/webhooks/machine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -949,20 +949,15 @@ func processAWSPlacementTenancy(placement machinev1beta1.Placement) field.ErrorL
switch *placement.Host.Affinity {
case machinev1beta1.HostAffinityAnyAvailable:
// DedicatedHost is optional. If it is set, make sure it follows conventions
if placement.Host.DedicatedHost != nil && !awsDedicatedHostNamePattern.MatchString(placement.Host.DedicatedHost.ID) {
errs = append(errs, field.Invalid(field.NewPath("spec.placement.host.dedicatedHost.id"), placement.Host.DedicatedHost.ID, "id must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)"))
if placement.Host.DedicatedHost != nil {
errs = append(errs, validateDedicatedHost(placement.Host.DedicatedHost)...)
}
case machinev1beta1.HostAffinityDedicatedHost:
// We need to make sure DedicatedHost is set with an ID
if placement.Host.DedicatedHost == nil {
errs = append(errs, field.Required(field.NewPath("spec.placement.host.dedicatedHost"), "dedicatedHost is required when hostAffinity is DedicatedHost, and optional otherwise"))
} else {
// If not set, return required error. If it does not match pattern, return pattern failure message.
if placement.Host.DedicatedHost.ID == "" {
errs = append(errs, field.Required(field.NewPath("spec.placement.host.dedicatedHost.id"), "id is required and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)"))
} else if !awsDedicatedHostNamePattern.MatchString(placement.Host.DedicatedHost.ID) {
errs = append(errs, field.Invalid(field.NewPath("spec.placement.host.dedicatedHost.id"), placement.Host.DedicatedHost.ID, "id must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)"))
}
errs = append(errs, validateDedicatedHost(placement.Host.DedicatedHost)...)
}
default:
errs = append(errs, field.Invalid(field.NewPath("spec.placement.host.affinity"), placement.Host.Affinity, "hostAffinity must be either AnyAvailable or DedicatedHost"))
Expand All @@ -983,6 +978,75 @@ func processAWSPlacementTenancy(placement machinev1beta1.Placement) field.ErrorL
return errs
}

// validateDedicatedHost validates that all fields in the DedicatedHost are configured correctly.
func validateDedicatedHost(host *machinev1beta1.DedicatedHost) field.ErrorList {
var errs field.ErrorList

// If host is nil, then nothing to validate
if host == nil {
return errs
}

strategy := machinev1beta1.AllocationStrategyUserProvided
if host.AllocationStrategy != nil {
strategy = *host.AllocationStrategy
}

switch strategy {
// Empty string is for backward compatability in case an existing config exists with the allocation strategy not set.
// Default is User Provided.
case machinev1beta1.AllocationStrategyUserProvided, "":
// User Provided requires the ID being set of the host to use
if host.ID == "" {
errs = append(errs, field.Required(field.NewPath("spec.placement.host.dedicatedHost.id"), "id is required when allocationStrategy is UserProvided and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)"))
} else if !awsDedicatedHostNamePattern.MatchString(host.ID) {
errs = append(errs, field.Invalid(field.NewPath("spec.placement.host.dedicatedHost.id"), host.ID, "id must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)"))
}

// DynamicHostAllocation is not allowed if user provided
if host.DynamicHostAllocation != nil {
errs = append(errs, field.Invalid(field.NewPath("spec.placement.host.dedicatedHost.dynamicHostAllocation"), host.ID, "dynamicHostAllocation is only allowed when allocationStrategy is Dynamic"))
}
case machinev1beta1.AllocationStrategyDynamic:
// ID must not be set
if host.ID != "" {
errs = append(errs, field.Forbidden(field.NewPath("spec.placement.host.dedicatedHost.id"), "id is only allowed when allocationStrategy is Provided"))
}

// Validate DynamicHostAllocation if present
if host.DynamicHostAllocation != nil {
// MinProperties=1: At least one property must be set
// Currently only Tags exists, so if Tags is nil, the struct is empty
if host.DynamicHostAllocation.Tags == nil {
errs = append(errs, field.Required(field.NewPath("spec.placement.host.dedicatedHost.dynamicHostAllocation.tags"), "at least one property must be specified in dynamicHostAllocation"))
} else {
tags := *host.DynamicHostAllocation.Tags

// MinItems=1: At least 1 tag must be specified
if len(tags) < 1 {
errs = append(errs, field.Invalid(field.NewPath("spec.placement.host.dedicatedHost.dynamicHostAllocation.tags"), len(tags), "at least 1 tag must be specified"))
}

// MaxItems=50: Maximum 50 tags can be specified
if len(tags) > 50 {
errs = append(errs, field.Invalid(field.NewPath("spec.placement.host.dedicatedHost.dynamicHostAllocation.tags"), len(tags), "maximum 50 tags can be specified"))
}
}
}
default:
errs = append(
errs,
field.Invalid(
field.NewPath("spec.placement.host.dedicatedHost.allocationStrategy"),
host.AllocationStrategy,
fmt.Sprintf("Invalid allocationStrategy, the only allowed options are: %s, %s", machinev1beta1.AllocationStrategyUserProvided, machinev1beta1.AllocationStrategyDynamic),
),
)
}

return errs
}

// getDuplicatedTags iterates through the AWS TagSpecifications
// to determine if any tag Name is duplicated within the list.
// A list of duplicated names will be returned.
Expand Down
Loading