Skip to content

Commit 7acee07

Browse files
stuggiclaude
andcommitted
Normalize ServicesOverride to avoid CEL immutability check failure
On k8s 1.33 (OCP 4.20), the API server normalizes nil and empty slices differently during admission, causing the CEL validation rule `self == oldSelf` on OpenStackDataPlaneDeployment Spec to fail when updating a deployment (even just adding an annotation). Move the immutability check from CEL to the webhook ValidateUpdate where both old and new specs are normalized via Default() before comparison, making nil vs empty slice differences transparent. Also default ServicesOverride to []string{} in the webhook defaulter for consistent serialization across k8s versions. This is a backportable fix — affects any operator version running on OCP 4.20 regardless of the operator's build-time dependencies. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
1 parent b243f21 commit 7acee07

6 files changed

Lines changed: 33 additions & 22 deletions

api/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,6 @@ spec:
120120
- deploymentRequeueTime
121121
- nodeSets
122122
type: object
123-
x-kubernetes-validations:
124-
- message: OpenStackDataPlaneDeployment Spec is immutable
125-
rule: self == oldSelf
126123
status:
127124
description: OpenStackDataPlaneDeploymentStatus defines the observed state
128125
of OpenStackDataPlaneDeployment

api/dataplane/v1beta1/openstackdataplanedeployment_types.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ type OpenStackDataPlaneDeployment struct {
152152
metav1.TypeMeta `json:",inline"`
153153
metav1.ObjectMeta `json:"metadata,omitempty"`
154154

155-
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="OpenStackDataPlaneDeployment Spec is immutable"
156155
Spec OpenStackDataPlaneDeploymentSpec `json:"spec,omitempty"`
157156
Status OpenStackDataPlaneDeploymentStatus `json:"status,omitempty"`
158157
}

api/dataplane/v1beta1/openstackdataplanedeployment_webhook.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"fmt"
21+
"reflect"
22+
2023
apierrors "k8s.io/apimachinery/pkg/api/errors"
2124
"k8s.io/apimachinery/pkg/runtime"
2225
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -49,7 +52,9 @@ func (r *OpenStackDataPlaneDeployment) Default() {
4952

5053
// Default - set defaults for this OpenStackDataPlaneDeployment
5154
func (spec *OpenStackDataPlaneDeploymentSpec) Default() {
52-
55+
if spec.ServicesOverride == nil {
56+
spec.ServicesOverride = []string{}
57+
}
5358
}
5459

5560
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
@@ -86,7 +91,14 @@ func (spec *OpenStackDataPlaneDeploymentSpec) ValidateCreate() field.ErrorList {
8691
func (r *OpenStackDataPlaneDeployment) ValidateUpdate(original runtime.Object) (admission.Warnings, error) {
8792
openstackdataplanedeploymentlog.Info("validate update", "name", r.Name)
8893

89-
errors := r.Spec.ValidateUpdate()
94+
oldDeployment, ok := original.(*OpenStackDataPlaneDeployment)
95+
if !ok {
96+
return nil, apierrors.NewInternalError(field.InternalError(
97+
field.NewPath("spec"),
98+
fmt.Errorf("expected OpenStackDataPlaneDeployment, got %T", original)))
99+
}
100+
101+
errors := r.Spec.ValidateUpdate(oldDeployment.Spec)
90102

91103
if len(errors) != 0 {
92104
openstackdataplanedeploymentlog.Info("validation failed", "name", r.Name)
@@ -101,8 +113,20 @@ func (r *OpenStackDataPlaneDeployment) ValidateUpdate(original runtime.Object) (
101113
}
102114

103115
// ValidateUpdate validates the OpenStackDataPlaneDeploymentSpec on update
104-
func (spec *OpenStackDataPlaneDeploymentSpec) ValidateUpdate() field.ErrorList {
105-
// TODO(user): fill in your validation logic upon object update.
116+
func (spec *OpenStackDataPlaneDeploymentSpec) ValidateUpdate(old OpenStackDataPlaneDeploymentSpec) field.ErrorList {
117+
newCopy := *spec
118+
newCopy.Default()
119+
old.Default()
120+
121+
if !reflect.DeepEqual(newCopy, old) {
122+
return field.ErrorList{
123+
field.Invalid(
124+
field.NewPath("spec"),
125+
"object",
126+
"OpenStackDataPlaneDeployment Spec is immutable",
127+
),
128+
}
129+
}
106130

107131
return field.ErrorList{}
108132
}

bindata/crds/crds.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20889,9 +20889,6 @@ spec:
2088920889
- deploymentRequeueTime
2089020890
- nodeSets
2089120891
type: object
20892-
x-kubernetes-validations:
20893-
- message: OpenStackDataPlaneDeployment Spec is immutable
20894-
rule: self == oldSelf
2089520892
status:
2089620893
description: OpenStackDataPlaneDeploymentStatus defines the observed state
2089720894
of OpenStackDataPlaneDeployment

config/crd/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,6 @@ spec:
120120
- deploymentRequeueTime
121121
- nodeSets
122122
type: object
123-
x-kubernetes-validations:
124-
- message: OpenStackDataPlaneDeployment Spec is immutable
125-
rule: self == oldSelf
126123
status:
127124
description: OpenStackDataPlaneDeploymentStatus defines the observed state
128125
of OpenStackDataPlaneDeployment

test/functional/dataplane/openstackdataplanedeployment_controller_test.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ var _ = Describe("Dataplane Deployment Test", func() {
163163
BackoffLimit: &DefaultBackoffLimit,
164164
PreserveJobs: true,
165165
DeploymentRequeueTime: 15,
166-
ServicesOverride: nil,
166+
ServicesOverride: []string{},
167167
AnsibleEEEnvConfigMapName: "openstack-aee-default-env",
168168
}
169169
Expect(dataplaneDeploymentInstance.Spec).Should(Equal(expectedSpec))
@@ -360,7 +360,6 @@ var _ = Describe("Dataplane Deployment Test", func() {
360360
BackoffLimit: &DefaultBackoffLimit,
361361
PreserveJobs: true,
362362
DeploymentRequeueTime: 15,
363-
ServicesOverride: nil,
364363
AnsibleEEEnvConfigMapName: "openstack-aee-default-env",
365364
}
366365
Expect(dataplaneDeploymentInstance.Spec).Should(Equal(expectedSpec))
@@ -571,7 +570,6 @@ var _ = Describe("Dataplane Deployment Test", func() {
571570
BackoffLimit: &DefaultBackoffLimit,
572571
PreserveJobs: true,
573572
DeploymentRequeueTime: 15,
574-
ServicesOverride: nil,
575573
AnsibleEEEnvConfigMapName: "openstack-aee-default-env",
576574
}
577575
Expect(dataplaneDeploymentInstance.Spec).Should(Equal(expectedSpec))
@@ -686,7 +684,7 @@ var _ = Describe("Dataplane Deployment Test", func() {
686684
BackoffLimit: &DefaultBackoffLimit,
687685
PreserveJobs: true,
688686
DeploymentRequeueTime: 15,
689-
ServicesOverride: nil,
687+
ServicesOverride: []string{},
690688
AnsibleEEEnvConfigMapName: "openstack-aee-default-env",
691689
}
692690
Expect(dataplaneDeploymentInstance.Spec).Should(Equal(expectedSpec))
@@ -839,7 +837,6 @@ var _ = Describe("Dataplane Deployment Test", func() {
839837
BackoffLimit: &DefaultBackoffLimit,
840838
PreserveJobs: true,
841839
DeploymentRequeueTime: 15,
842-
ServicesOverride: nil,
843840
AnsibleEEEnvConfigMapName: "openstack-aee-default-env",
844841
}
845842
Expect(dataplaneDeploymentInstance.Spec).Should(Equal(expectedSpec))
@@ -1539,7 +1536,7 @@ var _ = Describe("Dataplane Deployment Test", func() {
15391536
BackoffLimit: &DefaultBackoffLimit,
15401537
PreserveJobs: true,
15411538
DeploymentRequeueTime: 15,
1542-
ServicesOverride: nil,
1539+
ServicesOverride: []string{},
15431540
AnsibleEEEnvConfigMapName: "openstack-aee-default-env",
15441541
}
15451542
Expect(dataplaneDeploymentInstance.Spec).Should(Equal(expectedSpec))
@@ -1640,7 +1637,7 @@ var _ = Describe("Dataplane Deployment Test", func() {
16401637
AnsibleLimit: "",
16411638
AnsibleSkipTags: "",
16421639
DeploymentRequeueTime: 15,
1643-
ServicesOverride: nil,
1640+
ServicesOverride: []string{},
16441641
BackoffLimit: ptr.To(int32(6)),
16451642
PreserveJobs: true,
16461643
AnsibleEEEnvConfigMapName: "openstack-aee-default-env",
@@ -1742,7 +1739,7 @@ var _ = Describe("Dataplane Deployment Test", func() {
17421739
AnsibleLimit: "",
17431740
AnsibleSkipTags: "",
17441741
DeploymentRequeueTime: 15,
1745-
ServicesOverride: nil,
1742+
ServicesOverride: []string{},
17461743
BackoffLimit: ptr.To(int32(6)),
17471744
PreserveJobs: true,
17481745
AnsibleEEEnvConfigMapName: "openstack-aee-default-env",

0 commit comments

Comments
 (0)