Skip to content
Open
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 @@ -41,7 +41,7 @@ require (
github.com/onsi/gomega v1.39.1
github.com/opencontainers/go-digest v1.0.0
github.com/openshift-eng/openshift-tests-extension v0.0.0-20260707142426-572a3e9deb7a
github.com/openshift/api v0.0.0-20260810132456-8f52beb625b5
github.com/openshift/api v0.0.0-20260901194050-81278704edb0
github.com/openshift/client-go v0.0.0-20260810202730-ddca5e0b7146
github.com/openshift/imagebuilder v1.2.21
github.com/openshift/library-go v0.0.0-20260720123941-85336565c3c7
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -651,8 +651,8 @@ github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22
github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg=
github.com/openshift-eng/openshift-tests-extension v0.0.0-20260707142426-572a3e9deb7a h1:ulT0JZ/x6S4hYhyjUJ9T49YAxDLl1i5idFOMm9RHBkY=
github.com/openshift-eng/openshift-tests-extension v0.0.0-20260707142426-572a3e9deb7a/go.mod h1:pHOS9c6BjZv91OkkHyIHAOWnYhxwcxWQkyYGEvPyUCE=
github.com/openshift/api v0.0.0-20260810132456-8f52beb625b5 h1:/UAIG4kF4dXdamTuN9rL5kSjbQZ9wDES9O2q/wS8Bsk=
github.com/openshift/api v0.0.0-20260810132456-8f52beb625b5/go.mod h1:k6qH5QOVa5GDln2VVm8Jz4NV3Z7R2SATHFLwGS6Wh3M=
github.com/openshift/api v0.0.0-20260901194050-81278704edb0 h1:9PTDE/0weDetokFkqYdHtZQDcULZhJSm5gNF3GceH4A=
github.com/openshift/api v0.0.0-20260901194050-81278704edb0/go.mod h1:k6qH5QOVa5GDln2VVm8Jz4NV3Z7R2SATHFLwGS6Wh3M=
github.com/openshift/apiserver-library-go v0.0.0-20260715200723-42e5e402ca43 h1:V9hWaBi9cnohNk1F0Ph6wpI0otMWqMHleJ3oj5603Bc=
github.com/openshift/apiserver-library-go v0.0.0-20260715200723-42e5e402ca43/go.mod h1:ZuzfEq1ccZpHNx05xEUKlm2TcMHt2iXVutb79kAuTfM=
github.com/openshift/client-go v0.0.0-20260810202730-ddca5e0b7146 h1:fX/gaOPiS2vrYGSAFSeqqoWjj32H+NbMmB5RDjwhCnU=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ spec:
validations:
- expression: "!has(object.spec.managedBootImages) || (has(object.spec.managedBootImages) && params.status.platformStatus.type in ['GCP','AWS','VSphere','Azure'])"
message: "This feature is only supported on these platforms: GCP, AWS, VSphere, Azure"
- expression: "!has(object.spec.managedBootImages) || !has(object.spec.managedBootImages.machineManagers) || !object.spec.managedBootImages.machineManagers.exists(m, m.apiGroup == 'cluster.x-k8s.io' && m.resource == 'machinesets') || params.status.platformStatus.type == 'AWS'"
message: "The CAPI MachineSet boot image update feature is only supported on AWS"
- expression: "!has(object.spec.managedBootImages) || !has(object.spec.managedBootImages.machineManagers) || !object.spec.managedBootImages.machineManagers.exists(m, m.apiGroup == 'cluster.x-k8s.io' && m.resource == 'machinedeployments') || params.status.platformStatus.type == 'AWS'"
message: "The CAPI MachineDeployment boot image update feature is only supported on AWS"
45 changes: 31 additions & 14 deletions pkg/apihelpers/apihelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -571,36 +571,53 @@ func CheckNodeDisruptionActionsForTargetActions(actions []opv1.NodeDisruptionPol

// HasMAPIMachineSetManager checks if a MachineManager entry for a target resource exists.
func HasMAPIMachineSetManager(machineManagers []opv1.MachineManager, resource opv1.MachineManagerMachineSetsResourceType) bool {
for _, manager := range machineManagers {
if manager.Resource == resource {
return true
}
}
return false
return HasMachineManager(machineManagers, resource, opv1.MachineAPI)
}

// GetMAPIMachineSetManager returns a target machine resource's machine manager. This should ideally
// only be called if the machine manager exists in the list, returns None if not found.
func GetMAPIMachineSetManager(machineManagers []opv1.MachineManager, resource opv1.MachineManagerMachineSetsResourceType) opv1.MachineManager {
for _, manager := range machineManagers {
if manager.Resource == resource {
return manager
}
}
// Return a None manager if no match is found
return opv1.MachineManager{Resource: resource, APIGroup: opv1.MachineAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.None}}
return GetMachineManager(machineManagers, resource, opv1.MachineAPI)
}

// HasMAPIMachineSetManagerWithMode checks if a MachineManager entry for a target resource exists with the specified mode.
func HasMAPIMachineSetManagerWithMode(machineManagers []opv1.MachineManager, resource opv1.MachineManagerMachineSetsResourceType, mode opv1.MachineManagerSelectorMode) bool {
return HasMachineManagerWithMode(machineManagers, resource, opv1.MachineAPI, mode)
}

// HasMachineManagerWithMode checks if a MachineManager entry exists for a given resource, API group, and mode.
func HasMachineManagerWithMode(machineManagers []opv1.MachineManager, resource opv1.MachineManagerMachineSetsResourceType, apiGroup opv1.MachineManagerMachineSetsAPIGroupType, mode opv1.MachineManagerSelectorMode) bool {
for _, manager := range machineManagers {
if manager.Resource == resource {
if manager.Resource == resource && manager.APIGroup == apiGroup {
return manager.Selection.Mode == mode
}
}
return false
}

// HasMachineManager checks if a MachineManager entry exists for a given resource and API group.
// Use this instead of HasMAPIMachineSetManager when the API group must also match (e.g. to
// distinguish CAPI MachineSets from MAPI MachineSets).
func HasMachineManager(machineManagers []opv1.MachineManager, resource opv1.MachineManagerMachineSetsResourceType, apiGroup opv1.MachineManagerMachineSetsAPIGroupType) bool {
for _, manager := range machineManagers {
if manager.Resource == resource && manager.APIGroup == apiGroup {
return true
}
}
return false
}

// GetMachineManager returns the MachineManager for the given resource and API group.
// Returns a Mode=None manager if not found.
func GetMachineManager(machineManagers []opv1.MachineManager, resource opv1.MachineManagerMachineSetsResourceType, apiGroup opv1.MachineManagerMachineSetsAPIGroupType) opv1.MachineManager {
for _, manager := range machineManagers {
if manager.Resource == resource && manager.APIGroup == apiGroup {
return manager
}
}
return opv1.MachineManager{Resource: resource, APIGroup: apiGroup, Selection: opv1.MachineManagerSelector{Mode: opv1.None}}
}

// MergeMachineManager updates or adds a MachineManager entry for the target MachineSets resource
// with the specified manager, preserving other MachineManager entries.
func MergeMachineManager(status *opv1.MachineConfigurationStatus, manager opv1.MachineManager) {
Expand Down
31 changes: 31 additions & 0 deletions pkg/operator/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -2496,6 +2496,37 @@ func (optr *Operator) syncManagedBootImagesStatus(mcop *opv1.MachineConfiguratio
apihelpers.MergeMachineManager(status, cpmsManager)
}

// Populate/Reflect opinion for CAPI MachineSets and MachineDeployments, if the AWS CAPI feature gate is enabled
if optr.fgHandler.Enabled(features.FeatureGateManagedBootImagesAWSCAPI) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// CAPI MachineSets — mirrors MAPI MachineSet opt-in logic
capiMachineSetManager := opv1.MachineManager{Resource: opv1.MachineSets, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.None}}
if mcop.Spec.ManagedBootImages.MachineManagers != nil && apihelpers.HasMachineManager(mcop.Spec.ManagedBootImages.MachineManagers, opv1.MachineSets, opv1.ClusterAPI) {
// An admin-defined opinion for CAPI MachineSets exists, so reflect that to the status
capiMachineSetManager = apihelpers.GetMachineManager(mcop.Spec.ManagedBootImages.MachineManagers, opv1.MachineSets, opv1.ClusterAPI)
} else if isDefaultOnPlatform {
// No admin opinion: auto opt-in on supported platforms (install or upgrade from None)
defaultOptInEvent = defaultOptInEvent ||
(mcop.Status.ManagedBootImagesStatus.MachineManagers == nil) ||
apihelpers.HasMachineManagerWithMode(mcop.Status.ManagedBootImagesStatus.MachineManagers, opv1.MachineSets, opv1.ClusterAPI, opv1.None)
capiMachineSetManager.Selection.Mode = opv1.All
}
apihelpers.MergeMachineManager(status, capiMachineSetManager)

// CAPI MachineDeployments — mirrors MAPI MachineSet opt-in logic
capiMachineDeploymentManager := opv1.MachineManager{Resource: opv1.MachineDeployments, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.None}}
if mcop.Spec.ManagedBootImages.MachineManagers != nil && apihelpers.HasMachineManager(mcop.Spec.ManagedBootImages.MachineManagers, opv1.MachineDeployments, opv1.ClusterAPI) {
// An admin-defined opinion for CAPI MachineDeployments exists, so reflect that to the status
capiMachineDeploymentManager = apihelpers.GetMachineManager(mcop.Spec.ManagedBootImages.MachineManagers, opv1.MachineDeployments, opv1.ClusterAPI)
} else if isDefaultOnPlatform {
// No admin opinion: auto opt-in on supported platforms (install or upgrade from None)
defaultOptInEvent = defaultOptInEvent ||
(mcop.Status.ManagedBootImagesStatus.MachineManagers == nil) ||
apihelpers.HasMachineManagerWithMode(mcop.Status.ManagedBootImagesStatus.MachineManagers, opv1.MachineDeployments, opv1.ClusterAPI, opv1.None)
capiMachineDeploymentManager.Selection.Mode = opv1.All
}
apihelpers.MergeMachineManager(status, capiMachineDeploymentManager)
}

return defaultOptInEvent
}

Expand Down
144 changes: 144 additions & 0 deletions pkg/operator/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ func TestSyncMachineConfiguration(t *testing.T) {
expectedSkewEnforcementStatus opv1.BootImageSkewEnforcementStatus
annotationExpected bool
enableCPMSFeatureGate bool
enableCAPIFeatureGate bool
provisioningCRPresent bool
provisioningOSDownloadURL string
}{
Expand Down Expand Up @@ -761,6 +762,119 @@ func TestSyncMachineConfiguration(t *testing.T) {
},
expectedSkewEnforcementStatus: apihelpers.GetSkewEnforcementStatusAutomaticWithOCPVersion("4.18.0"),
},
// CAPI test cases - feature gate enabled
{
name: "AWS platform, CAPI gate enabled, no admin opinion, CAPI MS and MD auto opt-in to All",
infra: buildInfra(withPlatformType(configv1.AWSPlatformType)),
mcop: buildMachineConfigurationWithNoBootImageConfiguration(),
clusterVersion: buildClusterVersion("4.18.0"),
annotationExpected: true,
enableCAPIFeatureGate: true,
expectedManagedBootImagesStatus: opv1.ManagedBootImages{
MachineManagers: []opv1.MachineManager{
{Resource: opv1.MachineSets, APIGroup: opv1.MachineAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
{Resource: opv1.MachineSets, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
{Resource: opv1.MachineDeployments, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
},
},
expectedSkewEnforcementStatus: apihelpers.GetSkewEnforcementStatusAutomaticWithOCPVersion("4.18.0"),
},
{
name: "GCP platform, CAPI gate enabled, no admin opinion, CAPI MS and MD auto opt-in to All",
infra: buildInfra(withPlatformType(configv1.GCPPlatformType)),
mcop: buildMachineConfigurationWithNoBootImageConfiguration(),
clusterVersion: buildClusterVersion("4.18.0"),
annotationExpected: true,
enableCAPIFeatureGate: true,
expectedManagedBootImagesStatus: opv1.ManagedBootImages{
MachineManagers: []opv1.MachineManager{
{Resource: opv1.MachineSets, APIGroup: opv1.MachineAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
{Resource: opv1.MachineSets, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
{Resource: opv1.MachineDeployments, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
},
},
expectedSkewEnforcementStatus: apihelpers.GetSkewEnforcementStatusAutomaticWithOCPVersion("4.18.0"),
},
{
name: "Azure platform, CAPI gate enabled, no admin opinion, CAPI MS and MD auto opt-in to All",
infra: buildInfra(withPlatformType(configv1.AzurePlatformType)),
mcop: buildMachineConfigurationWithNoBootImageConfiguration(),
clusterVersion: buildClusterVersion("4.18.0"),
annotationExpected: true,
enableCAPIFeatureGate: true,
expectedManagedBootImagesStatus: opv1.ManagedBootImages{
MachineManagers: []opv1.MachineManager{
{Resource: opv1.MachineSets, APIGroup: opv1.MachineAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
{Resource: opv1.MachineSets, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
{Resource: opv1.MachineDeployments, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
},
},
expectedSkewEnforcementStatus: apihelpers.GetSkewEnforcementStatusAutomaticWithOCPVersion("4.18.0"),
},
{
name: "vSphere platform, CAPI gate enabled, no admin opinion, CAPI MS and MD auto opt-in to All",
infra: buildInfra(withPlatformType(configv1.VSpherePlatformType)),
mcop: buildMachineConfigurationWithNoBootImageConfiguration(),
clusterVersion: buildClusterVersion("4.18.0"),
annotationExpected: true,
enableCAPIFeatureGate: true,
expectedManagedBootImagesStatus: opv1.ManagedBootImages{
MachineManagers: []opv1.MachineManager{
{Resource: opv1.MachineSets, APIGroup: opv1.MachineAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
{Resource: opv1.MachineSets, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
{Resource: opv1.MachineDeployments, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
},
},
expectedSkewEnforcementStatus: apihelpers.GetSkewEnforcementStatusAutomaticWithOCPVersion("4.18.0"),
},
{
// MAPI MachineSets have no spec entry so they auto opt-in; CAPI entries come from spec.
name: "AWS platform, CAPI gate enabled, admin opinion for both CAPI MS and MD, MAPI still auto opts in",
infra: buildInfra(withPlatformType(configv1.AWSPlatformType)),
mcop: buildMachineConfigurationWithCAPIMachineSetsAndDeploymentsEnabled(),
clusterVersion: buildClusterVersion("4.18.0"),
annotationExpected: true,
enableCAPIFeatureGate: true,
expectedManagedBootImagesStatus: opv1.ManagedBootImages{
MachineManagers: []opv1.MachineManager{
{Resource: opv1.MachineSets, APIGroup: opv1.MachineAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
{Resource: opv1.MachineSets, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
{Resource: opv1.MachineDeployments, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
},
},
expectedSkewEnforcementStatus: apihelpers.GetSkewEnforcementStatusAutomaticWithOCPVersion("4.18.0"),
},
{
// Admin disabled CAPI MS; MD has no spec opinion so it auto opts in; MAPI MS also auto opts in.
name: "AWS platform, CAPI gate enabled, CAPI MS disabled in spec, CAPI MD and MAPI MS auto opt-in",
infra: buildInfra(withPlatformType(configv1.AWSPlatformType)),
mcop: buildMachineConfigurationWithCAPIMachineSetsDisabled(),
clusterVersion: buildClusterVersion("4.18.0"),
annotationExpected: true,
enableCAPIFeatureGate: true,
expectedManagedBootImagesStatus: opv1.ManagedBootImages{
MachineManagers: []opv1.MachineManager{
{Resource: opv1.MachineSets, APIGroup: opv1.MachineAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
{Resource: opv1.MachineSets, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.None}},
{Resource: opv1.MachineDeployments, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
},
},
expectedSkewEnforcementStatus: apihelpers.GetSkewEnforcementStatusAutomaticWithOCPVersion("4.18.0"),
},
{
name: "AWS platform, CAPI gate disabled, no CAPI managers in status",
infra: buildInfra(withPlatformType(configv1.AWSPlatformType)),
mcop: buildMachineConfigurationWithNoBootImageConfiguration(),
clusterVersion: buildClusterVersion("4.18.0"),
annotationExpected: true,
enableCAPIFeatureGate: false,
expectedManagedBootImagesStatus: opv1.ManagedBootImages{
MachineManagers: []opv1.MachineManager{
{Resource: opv1.MachineSets, APIGroup: opv1.MachineAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
},
},
expectedSkewEnforcementStatus: apihelpers.GetSkewEnforcementStatusAutomaticWithOCPVersion("4.18.0"),
},
{
name: "SNO cluster, no skew enforcement spec, skew enforcement defaults to None",
infra: buildInfra(withPlatformType(configv1.AWSPlatformType), withControlPlaneTopology(configv1.SingleReplicaTopologyMode)),
Expand Down Expand Up @@ -805,6 +919,9 @@ func TestSyncMachineConfiguration(t *testing.T) {
if tc.enableCPMSFeatureGate {
enabledFeatureGates = append(enabledFeatureGates, features.FeatureGateManagedBootImagesCPMS)
}
if tc.enableCAPIFeatureGate {
enabledFeatureGates = append(enabledFeatureGates, features.FeatureGateManagedBootImagesAWSCAPI)
}

provisioningGVR := schema.GroupVersionResource{Group: "metal3.io", Version: "v1alpha1", Resource: "provisionings"}
provisioningIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
Expand Down Expand Up @@ -1052,3 +1169,30 @@ func buildMachineConfigurationWithBootImageDisabledAndNoSkewEnforcement() *opv1.
},
}
}

func buildMachineConfigurationWithCAPIMachineSetsAndDeploymentsEnabled() *opv1.MachineConfiguration {
return &opv1.MachineConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
Spec: opv1.MachineConfigurationSpec{
ManagedBootImages: opv1.ManagedBootImages{
MachineManagers: []opv1.MachineManager{
{Resource: opv1.MachineSets, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
{Resource: opv1.MachineDeployments, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.All}},
},
},
},
}
}

func buildMachineConfigurationWithCAPIMachineSetsDisabled() *opv1.MachineConfiguration {
return &opv1.MachineConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
Spec: opv1.MachineConfigurationSpec{
ManagedBootImages: opv1.ManagedBootImages{
MachineManagers: []opv1.MachineManager{
{Resource: opv1.MachineSets, APIGroup: opv1.ClusterAPI, Selection: opv1.MachineManagerSelector{Mode: opv1.None}},
},
},
},
}
}
2 changes: 1 addition & 1 deletion vendor/github.com/openshift/api/.golangci.yaml

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

6 changes: 5 additions & 1 deletion vendor/github.com/openshift/api/config/v1/types.go

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

Loading