From 40f59fb63c451c4e4848029d120ea6d9ff7e90cb Mon Sep 17 00:00:00 2001 From: Martin Simka Date: Thu, 16 Jul 2026 14:57:53 +0200 Subject: [PATCH 1/2] UPSTREAM: 3265: allow unconditional providerID updates in OpenStackMachine Remove conditional immutability check that only allowed providerID and instanceID to be set when the old value was nil. This created a race condition where controller reconciliation retries would be rejected by the webhook, causing IPI installation failures. The fix aligns CAPO with upstream CAPI patterns used by AWS, GCP, and VSphere providers, which all allow providerID to be updated unconditionally. This prevents webhook rejections during normal controller operation while maintaining the same practical behavior since the controller only sets these fields once. | Provider | File | Restriction | |----------|------|-------------| | **AWS (CAPA)** | [awsmachine_webhook.go](https://github.com/kubernetes-sigs/cluster-api-provider-aws/blob/af0e5ca481bd15349b3d6e3336238152467086a4/webhooks/awsmachine_webhook.go#L128) | Unconditional | | **GCP (CAPG)** | [gcpmachine_webhook.go](https://github.com/kubernetes-sigs/cluster-api-provider-gcp/blob/930755247ae023047b1d4b62b656c3112ecdaecc/webhooks/gcpmachine_webhook.go#L88) | Unconditional | | **VSphere (CAPV)** | [vspheremachine.go](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/blob/dadbe25b1117ace60df90d3dd9e4c943d39ac4a0/internal/webhooks/vspheremachine.go#L109) | Allowlist | | **Azure (CAPZ)** | [azuremachine_webhook.go](https://github.com/kubernetes-sigs/cluster-api-provider-azure/blob/cd72698e45a63a84a8dbf2f385837e42a5735278/internal/webhooks/azuremachine_webhook.go#L75) | No restriction | --- pkg/webhooks/openstackmachine_webhook.go | 16 ++++++---------- .../apivalidations/openstackmachine_test.go | 4 ++-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/pkg/webhooks/openstackmachine_webhook.go b/pkg/webhooks/openstackmachine_webhook.go index 601fef6473..cef0e24eb7 100644 --- a/pkg/webhooks/openstackmachine_webhook.go +++ b/pkg/webhooks/openstackmachine_webhook.go @@ -97,17 +97,13 @@ func (*openStackMachineWebhook) ValidateUpdate(_ context.Context, oldObjRaw, new newOpenStackMachineSpec := newOpenStackMachine["spec"].(map[string]interface{}) oldOpenStackMachineSpec := oldOpenStackMachine["spec"].(map[string]interface{}) - // allow changes to providerID once - if oldOpenStackMachineSpec["providerID"] == nil { - delete(oldOpenStackMachineSpec, "providerID") - delete(newOpenStackMachineSpec, "providerID") - } + // allow changes to providerID (matches AWS, GCP, VSphere providers) + delete(oldOpenStackMachineSpec, "providerID") + delete(newOpenStackMachineSpec, "providerID") - // allow changes to instanceID once - if oldOpenStackMachineSpec["instanceID"] == nil { - delete(oldOpenStackMachineSpec, "instanceID") - delete(newOpenStackMachineSpec, "instanceID") - } + // allow changes to instanceID (matches AWS, GCP, VSphere providers) + delete(oldOpenStackMachineSpec, "instanceID") + delete(newOpenStackMachineSpec, "instanceID") // allow changes to identifyRef delete(oldOpenStackMachineSpec, "identityRef") diff --git a/test/e2e/suites/apivalidations/openstackmachine_test.go b/test/e2e/suites/apivalidations/openstackmachine_test.go index 28af1cf80f..d2db5dab4a 100644 --- a/test/e2e/suites/apivalidations/openstackmachine_test.go +++ b/test/e2e/suites/apivalidations/openstackmachine_test.go @@ -60,7 +60,7 @@ var _ = Describe("OpenStackMachine API validations", func() { Expect(k8sClient.Create(ctx, machine)).To(Succeed(), "OpenStackMachine creation with spec.identityRef should succeed") }) - It("should only allow the providerID to be set once", func() { + It("should allow the providerID to be updated", func() { machine := defaultMachine() By("Creating a bare machine") @@ -72,7 +72,7 @@ var _ = Describe("OpenStackMachine API validations", func() { By("Modifying the providerID") machine.Spec.ProviderID = ptr.To("bar") - Expect(k8sClient.Update(ctx, machine)).NotTo(Succeed(), "Updating providerID should fail") + Expect(k8sClient.Update(ctx, machine)).To(Succeed(), "Updating providerID should succeed") }) It("should allow the identityRef to be set several times", func() { From b8be9b429eb5867ceeffe1b3159fd4c0be8c51bb Mon Sep 17 00:00:00 2001 From: Martin Simka Date: Mon, 27 Jul 2026 16:24:51 +0200 Subject: [PATCH 2/2] UPSTREAM: : vendor --- .../pkg/webhooks/openstackmachine_webhook.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/hack/tools/vendor/sigs.k8s.io/cluster-api-provider-openstack/pkg/webhooks/openstackmachine_webhook.go b/hack/tools/vendor/sigs.k8s.io/cluster-api-provider-openstack/pkg/webhooks/openstackmachine_webhook.go index 601fef6473..cef0e24eb7 100644 --- a/hack/tools/vendor/sigs.k8s.io/cluster-api-provider-openstack/pkg/webhooks/openstackmachine_webhook.go +++ b/hack/tools/vendor/sigs.k8s.io/cluster-api-provider-openstack/pkg/webhooks/openstackmachine_webhook.go @@ -97,17 +97,13 @@ func (*openStackMachineWebhook) ValidateUpdate(_ context.Context, oldObjRaw, new newOpenStackMachineSpec := newOpenStackMachine["spec"].(map[string]interface{}) oldOpenStackMachineSpec := oldOpenStackMachine["spec"].(map[string]interface{}) - // allow changes to providerID once - if oldOpenStackMachineSpec["providerID"] == nil { - delete(oldOpenStackMachineSpec, "providerID") - delete(newOpenStackMachineSpec, "providerID") - } + // allow changes to providerID (matches AWS, GCP, VSphere providers) + delete(oldOpenStackMachineSpec, "providerID") + delete(newOpenStackMachineSpec, "providerID") - // allow changes to instanceID once - if oldOpenStackMachineSpec["instanceID"] == nil { - delete(oldOpenStackMachineSpec, "instanceID") - delete(newOpenStackMachineSpec, "instanceID") - } + // allow changes to instanceID (matches AWS, GCP, VSphere providers) + delete(oldOpenStackMachineSpec, "instanceID") + delete(newOpenStackMachineSpec, "instanceID") // allow changes to identifyRef delete(oldOpenStackMachineSpec, "identityRef")