From ca4b06c0c44102967d0821aa3315891f335df37c Mon Sep 17 00:00:00 2001 From: Pablo Rodriguez Nava Date: Fri, 13 Mar 2026 13:10:26 +0100 Subject: [PATCH] MCO-2142: Avoid OSImageStream defaultStream unset This change prevents users from resetting the value of the OSImageStream.spec.defaultStream field once it's set. The main idea motivation behind this limitation is the design choice and agreement that makes all clusters have a default at install-time. Given that the field is not required (and it's not safe to make it required as the field already exist) this validation is the middle-ground we can provide without making the field required. Signed-off-by: Pablo Rodriguez Nava --- .../OSStreams.yaml | 36 +++++++++++++++++++ .../v1alpha1/types_osimagestream.go | 2 ++ ..._machine-config_01_osimagestreams.crd.yaml | 4 +++ .../OSStreams.yaml | 4 +++ .../zz_generated.swagger_doc_generated.go | 2 +- .../generated_openapi/zz_generated.openapi.go | 2 +- ..._machine-config_01_osimagestreams.crd.yaml | 4 +++ 7 files changed, 52 insertions(+), 2 deletions(-) diff --git a/machineconfiguration/v1alpha1/tests/osimagestreams.machineconfiguration.openshift.io/OSStreams.yaml b/machineconfiguration/v1alpha1/tests/osimagestreams.machineconfiguration.openshift.io/OSStreams.yaml index adb55a4401b..c394b6d0a79 100644 --- a/machineconfiguration/v1alpha1/tests/osimagestreams.machineconfiguration.openshift.io/OSStreams.yaml +++ b/machineconfiguration/v1alpha1/tests/osimagestreams.machineconfiguration.openshift.io/OSStreams.yaml @@ -599,6 +599,42 @@ tests: osExtensionsImage: quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e expectedError: "spec.defaultStream must reference an existing stream name from status.availableStreams" + - name: Should reject removing spec.defaultStream once it has been set + initial: | + apiVersion: machineconfiguration.openshift.io/v1alpha1 + kind: OSImageStream + metadata: + name: cluster + spec: + defaultStream: rhel-coreos + updated: | + apiVersion: machineconfiguration.openshift.io/v1alpha1 + kind: OSImageStream + metadata: + name: cluster + spec: {} + expectedError: "spec.defaultStream cannot be removed once set" + + - name: Should allow updating spec without defaultStream when it was never set + initial: | + apiVersion: machineconfiguration.openshift.io/v1alpha1 + kind: OSImageStream + metadata: + name: cluster + spec: {} + updated: | + apiVersion: machineconfiguration.openshift.io/v1alpha1 + kind: OSImageStream + metadata: + name: cluster + spec: {} + expected: | + apiVersion: machineconfiguration.openshift.io/v1alpha1 + kind: OSImageStream + metadata: + name: cluster + spec: {} + - name: Should allow updating stale spec.defaultStream to a valid stream after forced upgrade initial: | apiVersion: machineconfiguration.openshift.io/v1alpha1 diff --git a/machineconfiguration/v1alpha1/types_osimagestream.go b/machineconfiguration/v1alpha1/types_osimagestream.go index 07ba93d0a6f..fccf6da8cbc 100644 --- a/machineconfiguration/v1alpha1/types_osimagestream.go +++ b/machineconfiguration/v1alpha1/types_osimagestream.go @@ -74,6 +74,7 @@ type OSImageStreamStatus struct { } // OSImageStreamSpec defines the desired state of a OSImageStream. +// +kubebuilder:validation:XValidation:rule="!has(oldSelf.defaultStream) || has(self.defaultStream)",message="spec.defaultStream cannot be removed once set" type OSImageStreamSpec struct { // defaultStream is the desired name of the stream that should be used as the // default when no specific stream is requested by a MachineConfigPool. @@ -94,6 +95,7 @@ type OSImageStreamSpec struct { // valid value is accepted. // // When omitted, the operator determines the default stream automatically. + // Once set, this field cannot be removed. // // It must be a valid RFC 1123 subdomain between 1 and 253 characters in length, // consisting of lowercase alphanumeric characters, hyphens ('-'), and periods ('.'). diff --git a/machineconfiguration/v1alpha1/zz_generated.crd-manifests/0000_80_machine-config_01_osimagestreams.crd.yaml b/machineconfiguration/v1alpha1/zz_generated.crd-manifests/0000_80_machine-config_01_osimagestreams.crd.yaml index 68257363c3d..d18a1ecaa3e 100644 --- a/machineconfiguration/v1alpha1/zz_generated.crd-manifests/0000_80_machine-config_01_osimagestreams.crd.yaml +++ b/machineconfiguration/v1alpha1/zz_generated.crd-manifests/0000_80_machine-config_01_osimagestreams.crd.yaml @@ -71,6 +71,7 @@ spec: valid value is accepted. When omitted, the operator determines the default stream automatically. + Once set, this field cannot be removed. It must be a valid RFC 1123 subdomain between 1 and 253 characters in length, consisting of lowercase alphanumeric characters, hyphens ('-'), and periods ('.'). @@ -83,6 +84,9 @@ spec: character. rule: '!format.dns1123Subdomain().validate(self).hasValue()' type: object + x-kubernetes-validations: + - message: spec.defaultStream cannot be removed once set + rule: '!has(oldSelf.defaultStream) || has(self.defaultStream)' status: description: |- status describes the last observed state of this OSImageStream. diff --git a/machineconfiguration/v1alpha1/zz_generated.featuregated-crd-manifests/osimagestreams.machineconfiguration.openshift.io/OSStreams.yaml b/machineconfiguration/v1alpha1/zz_generated.featuregated-crd-manifests/osimagestreams.machineconfiguration.openshift.io/OSStreams.yaml index 9e76484095e..c71faa35747 100644 --- a/machineconfiguration/v1alpha1/zz_generated.featuregated-crd-manifests/osimagestreams.machineconfiguration.openshift.io/OSStreams.yaml +++ b/machineconfiguration/v1alpha1/zz_generated.featuregated-crd-manifests/osimagestreams.machineconfiguration.openshift.io/OSStreams.yaml @@ -71,6 +71,7 @@ spec: valid value is accepted. When omitted, the operator determines the default stream automatically. + Once set, this field cannot be removed. It must be a valid RFC 1123 subdomain between 1 and 253 characters in length, consisting of lowercase alphanumeric characters, hyphens ('-'), and periods ('.'). @@ -83,6 +84,9 @@ spec: character. rule: '!format.dns1123Subdomain().validate(self).hasValue()' type: object + x-kubernetes-validations: + - message: spec.defaultStream cannot be removed once set + rule: '!has(oldSelf.defaultStream) || has(self.defaultStream)' status: description: |- status describes the last observed state of this OSImageStream. diff --git a/machineconfiguration/v1alpha1/zz_generated.swagger_doc_generated.go b/machineconfiguration/v1alpha1/zz_generated.swagger_doc_generated.go index aad6325b4bb..44cb8b0d0c6 100644 --- a/machineconfiguration/v1alpha1/zz_generated.swagger_doc_generated.go +++ b/machineconfiguration/v1alpha1/zz_generated.swagger_doc_generated.go @@ -186,7 +186,7 @@ func (OSImageStreamSet) SwaggerDoc() map[string]string { var map_OSImageStreamSpec = map[string]string{ "": "OSImageStreamSpec defines the desired state of a OSImageStream.", - "defaultStream": "defaultStream is the desired name of the stream that should be used as the default when no specific stream is requested by a MachineConfigPool.\n\nThis field is set by the installer during installation. Users may need to update it if the currently selected stream is no longer available, for example when the stream has reached its End of Life. The MachineConfigOperator uses this value to determine which stream from status.availableStreams to apply as the default for MachineConfigPools that do not specify a stream override.\n\nWhen status.availableStreams has been populated by the operator, updating this field requires that the new value references the name of one of the streams in status.availableStreams. Status-only updates by the operator are not subject to this constraint, allowing the operator to update availableStreams independently of this field. During initial creation, before the operator has populated status, any valid value is accepted.\n\nWhen omitted, the operator determines the default stream automatically.\n\nIt must be a valid RFC 1123 subdomain between 1 and 253 characters in length, consisting of lowercase alphanumeric characters, hyphens ('-'), and periods ('.').", + "defaultStream": "defaultStream is the desired name of the stream that should be used as the default when no specific stream is requested by a MachineConfigPool.\n\nThis field is set by the installer during installation. Users may need to update it if the currently selected stream is no longer available, for example when the stream has reached its End of Life. The MachineConfigOperator uses this value to determine which stream from status.availableStreams to apply as the default for MachineConfigPools that do not specify a stream override.\n\nWhen status.availableStreams has been populated by the operator, updating this field requires that the new value references the name of one of the streams in status.availableStreams. Status-only updates by the operator are not subject to this constraint, allowing the operator to update availableStreams independently of this field. During initial creation, before the operator has populated status, any valid value is accepted.\n\nWhen omitted, the operator determines the default stream automatically. Once set, this field cannot be removed.\n\nIt must be a valid RFC 1123 subdomain between 1 and 253 characters in length, consisting of lowercase alphanumeric characters, hyphens ('-'), and periods ('.').", } func (OSImageStreamSpec) SwaggerDoc() map[string]string { diff --git a/openapi/generated_openapi/zz_generated.openapi.go b/openapi/generated_openapi/zz_generated.openapi.go index 2c2f5c3a6f3..056a1d9a708 100644 --- a/openapi/generated_openapi/zz_generated.openapi.go +++ b/openapi/generated_openapi/zz_generated.openapi.go @@ -46094,7 +46094,7 @@ func schema_openshift_api_machineconfiguration_v1alpha1_OSImageStreamSpec(ref co Properties: map[string]spec.Schema{ "defaultStream": { SchemaProps: spec.SchemaProps{ - Description: "defaultStream is the desired name of the stream that should be used as the default when no specific stream is requested by a MachineConfigPool.\n\nThis field is set by the installer during installation. Users may need to update it if the currently selected stream is no longer available, for example when the stream has reached its End of Life. The MachineConfigOperator uses this value to determine which stream from status.availableStreams to apply as the default for MachineConfigPools that do not specify a stream override.\n\nWhen status.availableStreams has been populated by the operator, updating this field requires that the new value references the name of one of the streams in status.availableStreams. Status-only updates by the operator are not subject to this constraint, allowing the operator to update availableStreams independently of this field. During initial creation, before the operator has populated status, any valid value is accepted.\n\nWhen omitted, the operator determines the default stream automatically.\n\nIt must be a valid RFC 1123 subdomain between 1 and 253 characters in length, consisting of lowercase alphanumeric characters, hyphens ('-'), and periods ('.').", + Description: "defaultStream is the desired name of the stream that should be used as the default when no specific stream is requested by a MachineConfigPool.\n\nThis field is set by the installer during installation. Users may need to update it if the currently selected stream is no longer available, for example when the stream has reached its End of Life. The MachineConfigOperator uses this value to determine which stream from status.availableStreams to apply as the default for MachineConfigPools that do not specify a stream override.\n\nWhen status.availableStreams has been populated by the operator, updating this field requires that the new value references the name of one of the streams in status.availableStreams. Status-only updates by the operator are not subject to this constraint, allowing the operator to update availableStreams independently of this field. During initial creation, before the operator has populated status, any valid value is accepted.\n\nWhen omitted, the operator determines the default stream automatically. Once set, this field cannot be removed.\n\nIt must be a valid RFC 1123 subdomain between 1 and 253 characters in length, consisting of lowercase alphanumeric characters, hyphens ('-'), and periods ('.').", Type: []string{"string"}, Format: "", }, diff --git a/payload-manifests/crds/0000_80_machine-config_01_osimagestreams.crd.yaml b/payload-manifests/crds/0000_80_machine-config_01_osimagestreams.crd.yaml index 68257363c3d..d18a1ecaa3e 100644 --- a/payload-manifests/crds/0000_80_machine-config_01_osimagestreams.crd.yaml +++ b/payload-manifests/crds/0000_80_machine-config_01_osimagestreams.crd.yaml @@ -71,6 +71,7 @@ spec: valid value is accepted. When omitted, the operator determines the default stream automatically. + Once set, this field cannot be removed. It must be a valid RFC 1123 subdomain between 1 and 253 characters in length, consisting of lowercase alphanumeric characters, hyphens ('-'), and periods ('.'). @@ -83,6 +84,9 @@ spec: character. rule: '!format.dns1123Subdomain().validate(self).hasValue()' type: object + x-kubernetes-validations: + - message: spec.defaultStream cannot be removed once set + rule: '!has(oldSelf.defaultStream) || has(self.defaultStream)' status: description: |- status describes the last observed state of this OSImageStream.