Skip to content
Draft
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: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,5 @@ require (
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
)

replace github.com/openshift/api => github.com/vr4manta/api v0.0.0-20260205131406-2fc32cc56240
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,6 @@ 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/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 Expand Up @@ -616,6 +614,8 @@ github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U=
github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg=
github.com/vmware/govmomi v0.52.0 h1:JyxQ1IQdllrY7PJbv2am9mRsv3p9xWlIQ66bv+XnyLw=
github.com/vmware/govmomi v0.52.0/go.mod h1:Yuc9xjznU3BH0rr6g7MNS1QGvxnJlE1vOvTJ7Lx7dqI=
github.com/vr4manta/api v0.0.0-20260205131406-2fc32cc56240 h1:KdEP6WoTD5JLagASkubKu5tqKqQ8t1Q/D8i/aAOYOYQ=
github.com/vr4manta/api v0.0.0-20260205131406-2fc32cc56240/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU=
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
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