From 2f173f9487c25edee82ccf3c98316d1015c48ab4 Mon Sep 17 00:00:00 2001 From: Juan Manuel Parrilla Madrid Date: Wed, 8 Apr 2026 15:13:21 +0200 Subject: [PATCH 1/4] feat(CNTRLPLANE-3173): persist lastSuccessfulEtcdBackupURL in HC status Add LastSuccessfulEtcdBackupURL field to HostedClusterStatus so the most recent successful etcd backup snapshot URL survives HCPEtcdBackup CR retention/deletion. The etcdbackup controller writes the URL directly to the HostedCluster status on backup success, resolving the URL from the HCP's HostedClusterAnnotation. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Juan Manuel Parrilla Madrid --- api/hypershift/v1beta1/hostedcluster_types.go | 10 + ...d.hostedclusters.etcdbackup.testsuite.yaml | 266 ++++++++++++++++++ .../controllers/etcdbackup/reconciler.go | 50 +++- .../controllers/etcdbackup/reconciler_test.go | 106 ++++++- 4 files changed, 422 insertions(+), 10 deletions(-) create mode 100644 cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/featuregated.hostedclusters.etcdbackup.testsuite.yaml diff --git a/api/hypershift/v1beta1/hostedcluster_types.go b/api/hypershift/v1beta1/hostedcluster_types.go index b80f13462bab..733e836a3c93 100644 --- a/api/hypershift/v1beta1/hostedcluster_types.go +++ b/api/hypershift/v1beta1/hostedcluster_types.go @@ -2180,6 +2180,16 @@ type HostedClusterStatus struct { // configuration contains the cluster configuration status of the HostedCluster // +optional Configuration *ConfigurationStatus `json:"configuration,omitempty"` + + // lastSuccessfulEtcdBackupURL is the cloud storage URL of the most recent + // successful etcd backup snapshot. Persisted here because HCPEtcdBackup CRs + // are ephemeral and may be deleted by retention policies. + // +openshift:enable:FeatureGate=HCPEtcdBackup + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=2048 + // +kubebuilder:validation:XValidation:rule="self.matches('^(https|s3)://.*')",message="lastSuccessfulEtcdBackupURL must be a valid URL with scheme https or s3" + LastSuccessfulEtcdBackupURL string `json:"lastSuccessfulEtcdBackupURL,omitempty"` } // AutoNodeStatus contains the observed state of the AutoNode provisioner. diff --git a/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/featuregated.hostedclusters.etcdbackup.testsuite.yaml b/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/featuregated.hostedclusters.etcdbackup.testsuite.yaml new file mode 100644 index 000000000000..063199d588a1 --- /dev/null +++ b/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/featuregated.hostedclusters.etcdbackup.testsuite.yaml @@ -0,0 +1,266 @@ +apiVersion: apiextensions.k8s.io/v1 +name: "HostedCluster etcd backup status validation" +crdName: hostedclusters.hypershift.openshift.io +featureGates: + - HCPEtcdBackup +version: v1beta1 +tests: + onUpdate: + - name: When lastSuccessfulEtcdBackupURL has invalid scheme it should fail + initial: | + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + spec: + dns: + baseDomain: example.com + etcd: + managementType: Managed + managed: + storage: + type: PersistentVolume + persistentVolume: + size: 8Gi + platform: + type: AWS + pullSecret: + name: secret + release: + image: quay.io/openshift-release-dev/ocp-release:4.15.11-x86_64 + secretEncryption: + aescbc: + activeKey: + name: key + type: aescbc + services: + - service: APIServer + servicePublishingStrategy: + type: Route + route: {} + - service: OAuthServer + servicePublishingStrategy: + type: Route + route: {} + - service: Konnectivity + servicePublishingStrategy: + type: Route + route: {} + - service: Ignition + servicePublishingStrategy: + type: Route + route: {} + updated: | + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + spec: + dns: + baseDomain: example.com + etcd: + managementType: Managed + managed: + storage: + type: PersistentVolume + persistentVolume: + size: 8Gi + platform: + type: AWS + pullSecret: + name: secret + release: + image: quay.io/openshift-release-dev/ocp-release:4.15.11-x86_64 + secretEncryption: + aescbc: + activeKey: + name: key + type: aescbc + services: + - service: APIServer + servicePublishingStrategy: + type: Route + route: {} + - service: OAuthServer + servicePublishingStrategy: + type: Route + route: {} + - service: Konnectivity + servicePublishingStrategy: + type: Route + route: {} + - service: Ignition + servicePublishingStrategy: + type: Route + route: {} + status: + lastSuccessfulEtcdBackupURL: "http://bucket.example.com/snapshot.db" + expectedStatusError: "lastSuccessfulEtcdBackupURL must be a valid URL with scheme https or s3" + + - name: When lastSuccessfulEtcdBackupURL has https scheme it should pass + initial: | + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + spec: + dns: + baseDomain: example.com + etcd: + managementType: Managed + managed: + storage: + type: PersistentVolume + persistentVolume: + size: 8Gi + platform: + type: AWS + pullSecret: + name: secret + release: + image: quay.io/openshift-release-dev/ocp-release:4.15.11-x86_64 + secretEncryption: + aescbc: + activeKey: + name: key + type: aescbc + services: + - service: APIServer + servicePublishingStrategy: + type: Route + route: {} + - service: OAuthServer + servicePublishingStrategy: + type: Route + route: {} + - service: Konnectivity + servicePublishingStrategy: + type: Route + route: {} + - service: Ignition + servicePublishingStrategy: + type: Route + route: {} + updated: | + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + spec: + dns: + baseDomain: example.com + etcd: + managementType: Managed + managed: + storage: + type: PersistentVolume + persistentVolume: + size: 8Gi + platform: + type: AWS + pullSecret: + name: secret + release: + image: quay.io/openshift-release-dev/ocp-release:4.15.11-x86_64 + secretEncryption: + aescbc: + activeKey: + name: key + type: aescbc + services: + - service: APIServer + servicePublishingStrategy: + type: Route + route: {} + - service: OAuthServer + servicePublishingStrategy: + type: Route + route: {} + - service: Konnectivity + servicePublishingStrategy: + type: Route + route: {} + - service: Ignition + servicePublishingStrategy: + type: Route + route: {} + status: + lastSuccessfulEtcdBackupURL: "https://bucket.s3.amazonaws.com/snapshot.db" + + - name: When lastSuccessfulEtcdBackupURL has s3 scheme it should pass + initial: | + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + spec: + dns: + baseDomain: example.com + etcd: + managementType: Managed + managed: + storage: + type: PersistentVolume + persistentVolume: + size: 8Gi + platform: + type: AWS + pullSecret: + name: secret + release: + image: quay.io/openshift-release-dev/ocp-release:4.15.11-x86_64 + secretEncryption: + aescbc: + activeKey: + name: key + type: aescbc + services: + - service: APIServer + servicePublishingStrategy: + type: Route + route: {} + - service: OAuthServer + servicePublishingStrategy: + type: Route + route: {} + - service: Konnectivity + servicePublishingStrategy: + type: Route + route: {} + - service: Ignition + servicePublishingStrategy: + type: Route + route: {} + updated: | + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + spec: + dns: + baseDomain: example.com + etcd: + managementType: Managed + managed: + storage: + type: PersistentVolume + persistentVolume: + size: 8Gi + platform: + type: AWS + pullSecret: + name: secret + release: + image: quay.io/openshift-release-dev/ocp-release:4.15.11-x86_64 + secretEncryption: + aescbc: + activeKey: + name: key + type: aescbc + services: + - service: APIServer + servicePublishingStrategy: + type: Route + route: {} + - service: OAuthServer + servicePublishingStrategy: + type: Route + route: {} + - service: Konnectivity + servicePublishingStrategy: + type: Route + route: {} + - service: Ignition + servicePublishingStrategy: + type: Route + route: {} + status: + lastSuccessfulEtcdBackupURL: "s3://bucket/snapshot.db" diff --git a/hypershift-operator/controllers/etcdbackup/reconciler.go b/hypershift-operator/controllers/etcdbackup/reconciler.go index 09299d5f5155..89055e4886ff 100644 --- a/hypershift-operator/controllers/etcdbackup/reconciler.go +++ b/hypershift-operator/controllers/etcdbackup/reconciler.go @@ -23,6 +23,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/util/retry" "k8s.io/client-go/util/workqueue" "k8s.io/utils/ptr" @@ -317,6 +318,22 @@ func (r *HCPEtcdBackupReconciler) updateHCPBackupCondition(ctx context.Context, return r.Status().Update(ctx, hcp) } +// updateHostedClusterBackupURL persists the snapshot URL in the HostedCluster +// status so it survives HCPEtcdBackup CR retention/deletion. +// Uses RetryOnConflict because the HC is updated by multiple controllers, +// and a requeue-based retry risks losing the URL if the Pod is cleaned up +// (TTLSecondsAfterFinished) before the next reconcile extracts it. +func (r *HCPEtcdBackupReconciler) updateHostedClusterBackupURL(ctx context.Context, hcp *hyperv1.HostedControlPlane, snapshotURL string) error { + return retry.RetryOnConflict(retry.DefaultBackoff, func() error { + hc, err := hyperutil.HostedClusterFromAnnotation(ctx, r.Client, hcp) + if err != nil { + return err + } + hc.Status.LastSuccessfulEtcdBackupURL = snapshotURL + return r.Status().Update(ctx, hc) + }) +} + // getHostedControlPlane finds the HostedControlPlane in the given namespace. // Returns nil if none found. func (r *HCPEtcdBackupReconciler) getHostedControlPlane(ctx context.Context, namespace string) (*hyperv1.HostedControlPlane, error) { @@ -400,9 +417,27 @@ func (r *HCPEtcdBackupReconciler) handleJobStatus(ctx context.Context, backup *h // Extract snapshotURL from the upload container's termination message. // The etcd-upload command writes the URL to /dev/termination-log. - if url, err := r.getSnapshotURLFromPod(ctx, job); err != nil { + url, err := r.getSnapshotURLFromPod(ctx, job) + if err != nil { logger.Error(err, "failed to read snapshot URL from pod termination message") - } else if url != "" { + url = "" // don't use url on error + } + + // Cleanup temporary RBAC and NetworkPolicy as soon as the Job completes. + // This must happen before any status updates that could fail and cause + // requeue, to avoid leaving security resources exposed indefinitely. + if err := r.cleanupResources(ctx, backup); err != nil { + logger.Error(err, "failed to cleanup resources after successful backup") + } + + // Persist the snapshot URL in the HostedCluster status BEFORE marking + // the backup as terminal so the controller retries on requeue. + // This is idempotent: if it succeeds but the backup status update below + // fails, the next reconcile re-extracts the URL and writes the same value. + if url != "" { + if err := r.updateHostedClusterBackupURL(ctx, hcp, url); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update HostedCluster LastSuccessfulEtcdBackupURL: %w", err) + } backup.Status.SnapshotURL = url } @@ -430,15 +465,17 @@ func (r *HCPEtcdBackupReconciler) handleJobStatus(ctx context.Context, backup *h logger.Error(err, "failed to update HCP backup condition") } - if err := r.cleanupResources(ctx, backup); err != nil { - logger.Error(err, "failed to cleanup resources after successful backup") - } return ctrl.Result{}, nil } if cond.Type == batchv1.JobFailed && cond.Status == corev1.ConditionTrue { logger.Info("backup Job failed", "job", job.Name, "reason", cond.Message) + // Cleanup temporary resources immediately on Job termination. + if err := r.cleanupResources(ctx, backup); err != nil { + logger.Error(err, "failed to cleanup resources after failed backup") + } + r.setCondition(backup, metav1.Condition{ Type: string(hyperv1.BackupCompleted), Status: metav1.ConditionFalse, @@ -460,9 +497,6 @@ func (r *HCPEtcdBackupReconciler) handleJobStatus(ctx context.Context, backup *h logger.Error(err, "failed to update HCP backup condition") } - if err := r.cleanupResources(ctx, backup); err != nil { - logger.Error(err, "failed to cleanup resources after failed backup") - } return ctrl.Result{}, nil } } diff --git a/hypershift-operator/controllers/etcdbackup/reconciler_test.go b/hypershift-operator/controllers/etcdbackup/reconciler_test.go index ce4175145804..fa117e86d8a7 100644 --- a/hypershift-operator/controllers/etcdbackup/reconciler_test.go +++ b/hypershift-operator/controllers/etcdbackup/reconciler_test.go @@ -12,6 +12,7 @@ import ( hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1" "github.com/openshift/hypershift/hypershift-operator/featuregate" "github.com/openshift/hypershift/support/releaseinfo" + hyperutil "github.com/openshift/hypershift/support/util" configv1 "github.com/openshift/api/config/v1" imageapi "github.com/openshift/api/image/v1" @@ -21,15 +22,18 @@ import ( corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" ) func TestMain(m *testing.M) { @@ -42,6 +46,8 @@ const ( testHONamespace = "hypershift" testBackupName = "backup-1" testHCPName = "test-hcp" + testHCNamespace = "clusters" + testHCName = "test" testReleaseImage = "quay.io/openshift-release-dev/ocp-release:4.16.0-x86_64" ) @@ -63,7 +69,7 @@ func newReconciler(objs ...client.Object) *HCPEtcdBackupReconciler { fakeClient := fake.NewClientBuilder(). WithScheme(scheme). WithObjects(clientObjs...). - WithStatusSubresource(&hyperv1.HCPEtcdBackup{}, &hyperv1.HostedControlPlane{}). + WithStatusSubresource(&hyperv1.HCPEtcdBackup{}, &hyperv1.HostedControlPlane{}, &hyperv1.HostedCluster{}). Build() return &HCPEtcdBackupReconciler{ @@ -97,11 +103,23 @@ func newHCPEtcdBackup() *hyperv1.HCPEtcdBackup { } } +func newHostedCluster() *hyperv1.HostedCluster { + return &hyperv1.HostedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: testHCName, + Namespace: testHCNamespace, + }, + } +} + func newHostedControlPlane() *hyperv1.HostedControlPlane { return &hyperv1.HostedControlPlane{ ObjectMeta: metav1.ObjectMeta{ Name: testHCPName, Namespace: testHCPNamespace, + Annotations: map[string]string{ + hyperutil.HostedClusterAnnotation: testHCNamespace + "/" + testHCName, + }, }, Spec: hyperv1.HostedControlPlaneSpec{ ReleaseImage: testReleaseImage, @@ -751,6 +769,7 @@ func TestHandleJobStatus(t *testing.T) { g := NewGomegaWithT(t) backup := newHCPEtcdBackup() hcp := newHostedControlPlane() + hc := newHostedCluster() job := newTestJob(batchv1.JobStatus{ Conditions: []batchv1.JobCondition{ { @@ -786,7 +805,7 @@ func TestHandleJobStatus(t *testing.T) { }, }, } - r := newReconciler(backup, job, hcp, pod) + r := newReconciler(backup, job, hcp, hc, pod) result, err := r.handleJobStatus(context.Background(), backup, job, hcp) g.Expect(err).ToNot(HaveOccurred()) @@ -805,6 +824,11 @@ func TestHandleJobStatus(t *testing.T) { g.Expect(hcpCond).ToNot(BeNil()) g.Expect(hcpCond.Status).To(Equal(metav1.ConditionTrue)) g.Expect(hcpCond.Reason).To(Equal(hyperv1.BackupSucceededReason)) + + // Verify HostedCluster status has the snapshot URL persisted + updatedHC := &hyperv1.HostedCluster{} + g.Expect(r.Get(context.Background(), types.NamespacedName{Name: testHCName, Namespace: testHCNamespace}, updatedHC)).To(Succeed()) + g.Expect(updatedHC.Status.LastSuccessfulEtcdBackupURL).To(Equal("s3://my-bucket/backups/test/snapshot.db")) }) t.Run("When Job fails it should set BackupFailed", func(t *testing.T) { @@ -1396,3 +1420,81 @@ func TestReconcileHappyPath(t *testing.T) { g.Expect(hcpCond.Reason).To(Equal(hyperv1.BackupInProgressReason)) }) } + +func TestUpdateHostedClusterBackupURL(t *testing.T) { + t.Run("When HCP has HostedClusterAnnotation it should update HC status with snapshot URL", func(t *testing.T) { + g := NewGomegaWithT(t) + hcp := newHostedControlPlane() + hc := newHostedCluster() + r := newReconciler(hcp, hc) + + err := r.updateHostedClusterBackupURL(context.Background(), hcp, "s3://bucket/snapshot.db") + g.Expect(err).ToNot(HaveOccurred()) + + updatedHC := &hyperv1.HostedCluster{} + g.Expect(r.Get(context.Background(), types.NamespacedName{Name: testHCName, Namespace: testHCNamespace}, updatedHC)).To(Succeed()) + g.Expect(updatedHC.Status.LastSuccessfulEtcdBackupURL).To(Equal("s3://bucket/snapshot.db")) + }) + + t.Run("When HCP is missing HostedClusterAnnotation it should return an error", func(t *testing.T) { + g := NewGomegaWithT(t) + hcp := newHostedControlPlane() + hcp.Annotations = nil + r := newReconciler(hcp) + + err := r.updateHostedClusterBackupURL(context.Background(), hcp, "s3://bucket/snapshot.db") + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("missing")) + }) + + t.Run("When HostedCluster does not exist it should return an error", func(t *testing.T) { + g := NewGomegaWithT(t) + hcp := newHostedControlPlane() + r := newReconciler(hcp) // no HC in the fake client + + err := r.updateHostedClusterBackupURL(context.Background(), hcp, "s3://bucket/snapshot.db") + g.Expect(err).To(HaveOccurred()) + }) + + t.Run("When HC status update conflicts it should retry and succeed", func(t *testing.T) { + g := NewGomegaWithT(t) + hcp := newHostedControlPlane() + hc := newHostedCluster() + + callCount := 0 + s := newScheme() + fakeClient := fake.NewClientBuilder(). + WithScheme(s). + WithObjects(hcp, hc). + WithStatusSubresource(&hyperv1.HCPEtcdBackup{}, &hyperv1.HostedControlPlane{}, &hyperv1.HostedCluster{}). + WithInterceptorFuncs(interceptor.Funcs{ + SubResourceUpdate: func(ctx context.Context, cl client.Client, subResourceName string, obj client.Object, opts ...client.SubResourceUpdateOption) error { + if _, ok := obj.(*hyperv1.HostedCluster); ok { + callCount++ + if callCount == 1 { + return apierrors.NewConflict( + schema.GroupResource{Group: "hypershift.openshift.io", Resource: "hostedclusters"}, + obj.GetName(), + fmt.Errorf("the object has been modified"), + ) + } + } + return cl.Status().Update(ctx, obj, opts...) + }, + }). + Build() + + r := &HCPEtcdBackupReconciler{ + Client: fakeClient, + OperatorNamespace: testHONamespace, + } + + err := r.updateHostedClusterBackupURL(context.Background(), hcp, "s3://bucket/snapshot.db") + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(callCount).To(Equal(2)) + + updatedHC := &hyperv1.HostedCluster{} + g.Expect(r.Get(context.Background(), types.NamespacedName{Name: testHCName, Namespace: testHCNamespace}, updatedHC)).To(Succeed()) + g.Expect(updatedHC.Status.LastSuccessfulEtcdBackupURL).To(Equal("s3://bucket/snapshot.db")) + }) +} From dac4638dbeb6ffae9c95a90e83d9b1b0200eba5a Mon Sep 17 00:00:00 2001 From: Juan Manuel Parrilla Madrid Date: Wed, 8 Apr 2026 15:13:31 +0200 Subject: [PATCH 2/4] chore(generated): regenerate CRDs, clients, vendor, and docs Regenerate after adding LastSuccessfulEtcdBackupURL to HostedClusterStatus. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Juan Manuel Parrilla Madrid --- .../HCPEtcdBackup.yaml | 12 +++++++ .../hypershift/v1beta1/hostedclusterstatus.go | 35 ++++++++++++------- ...usters-Hypershift-CustomNoUpgrade.crd.yaml | 12 +++++++ ...s-Hypershift-TechPreviewNoUpgrade.crd.yaml | 12 +++++++ docs/content/reference/aggregated-docs.md | 14 ++++++++ docs/content/reference/api.md | 14 ++++++++ .../hypershift/v1beta1/hostedcluster_types.go | 10 ++++++ 7 files changed, 96 insertions(+), 13 deletions(-) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml index 639559bd579a..db1604226816 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml @@ -6688,6 +6688,18 @@ spec: type: string type: object x-kubernetes-map-type: atomic + lastSuccessfulEtcdBackupURL: + description: |- + lastSuccessfulEtcdBackupURL is the cloud storage URL of the most recent + successful etcd backup snapshot. Persisted here because HCPEtcdBackup CRs + are ephemeral and may be deleted by retention policies. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: lastSuccessfulEtcdBackupURL must be a valid URL with scheme + https or s3 + rule: self.matches('^(https|s3)://.*') oauthCallbackURLTemplate: description: |- oauthCallbackURLTemplate contains a template for the URL to use as a callback diff --git a/client/applyconfiguration/hypershift/v1beta1/hostedclusterstatus.go b/client/applyconfiguration/hypershift/v1beta1/hostedclusterstatus.go index 42115bfa1e97..4ca327284e20 100644 --- a/client/applyconfiguration/hypershift/v1beta1/hostedclusterstatus.go +++ b/client/applyconfiguration/hypershift/v1beta1/hostedclusterstatus.go @@ -26,19 +26,20 @@ import ( // HostedClusterStatusApplyConfiguration represents a declarative configuration of the HostedClusterStatus type for use // with apply. type HostedClusterStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ControlPlaneVersion *ControlPlaneVersionStatusApplyConfiguration `json:"controlPlaneVersion,omitempty"` - Version *ClusterVersionStatusApplyConfiguration `json:"version,omitempty"` - KubeConfig *corev1.LocalObjectReference `json:"kubeconfig,omitempty"` - CustomKubeconfig *corev1.LocalObjectReference `json:"customKubeconfig,omitempty"` - KubeadminPassword *corev1.LocalObjectReference `json:"kubeadminPassword,omitempty"` - IgnitionEndpoint *string `json:"ignitionEndpoint,omitempty"` - ControlPlaneEndpoint *APIEndpointApplyConfiguration `json:"controlPlaneEndpoint,omitempty"` - OAuthCallbackURLTemplate *string `json:"oauthCallbackURLTemplate,omitempty"` - PayloadArch *hypershiftv1beta1.PayloadArchType `json:"payloadArch,omitempty"` - Platform *PlatformStatusApplyConfiguration `json:"platform,omitempty"` - AutoNode *AutoNodeStatusApplyConfiguration `json:"autoNode,omitempty"` - Configuration *ConfigurationStatusApplyConfiguration `json:"configuration,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ControlPlaneVersion *ControlPlaneVersionStatusApplyConfiguration `json:"controlPlaneVersion,omitempty"` + Version *ClusterVersionStatusApplyConfiguration `json:"version,omitempty"` + KubeConfig *corev1.LocalObjectReference `json:"kubeconfig,omitempty"` + CustomKubeconfig *corev1.LocalObjectReference `json:"customKubeconfig,omitempty"` + KubeadminPassword *corev1.LocalObjectReference `json:"kubeadminPassword,omitempty"` + IgnitionEndpoint *string `json:"ignitionEndpoint,omitempty"` + ControlPlaneEndpoint *APIEndpointApplyConfiguration `json:"controlPlaneEndpoint,omitempty"` + OAuthCallbackURLTemplate *string `json:"oauthCallbackURLTemplate,omitempty"` + PayloadArch *hypershiftv1beta1.PayloadArchType `json:"payloadArch,omitempty"` + Platform *PlatformStatusApplyConfiguration `json:"platform,omitempty"` + AutoNode *AutoNodeStatusApplyConfiguration `json:"autoNode,omitempty"` + Configuration *ConfigurationStatusApplyConfiguration `json:"configuration,omitempty"` + LastSuccessfulEtcdBackupURL *string `json:"lastSuccessfulEtcdBackupURL,omitempty"` } // HostedClusterStatusApplyConfiguration constructs a declarative configuration of the HostedClusterStatus type for use with @@ -155,3 +156,11 @@ func (b *HostedClusterStatusApplyConfiguration) WithConfiguration(value *Configu b.Configuration = value return b } + +// WithLastSuccessfulEtcdBackupURL sets the LastSuccessfulEtcdBackupURL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSuccessfulEtcdBackupURL field is set to the value of the last call. +func (b *HostedClusterStatusApplyConfiguration) WithLastSuccessfulEtcdBackupURL(value string) *HostedClusterStatusApplyConfiguration { + b.LastSuccessfulEtcdBackupURL = &value + return b +} diff --git a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml index ec0e1156a565..fae6e9b8480a 100644 --- a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml +++ b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml @@ -8718,6 +8718,18 @@ spec: type: string type: object x-kubernetes-map-type: atomic + lastSuccessfulEtcdBackupURL: + description: |- + lastSuccessfulEtcdBackupURL is the cloud storage URL of the most recent + successful etcd backup snapshot. Persisted here because HCPEtcdBackup CRs + are ephemeral and may be deleted by retention policies. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: lastSuccessfulEtcdBackupURL must be a valid URL with scheme + https or s3 + rule: self.matches('^(https|s3)://.*') oauthCallbackURLTemplate: description: |- oauthCallbackURLTemplate contains a template for the URL to use as a callback diff --git a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml index ea538f3de603..284c06f9ea2a 100644 --- a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml +++ b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml @@ -8618,6 +8618,18 @@ spec: type: string type: object x-kubernetes-map-type: atomic + lastSuccessfulEtcdBackupURL: + description: |- + lastSuccessfulEtcdBackupURL is the cloud storage URL of the most recent + successful etcd backup snapshot. Persisted here because HCPEtcdBackup CRs + are ephemeral and may be deleted by retention policies. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: lastSuccessfulEtcdBackupURL must be a valid URL with scheme + https or s3 + rule: self.matches('^(https|s3)://.*') oauthCallbackURLTemplate: description: |- oauthCallbackURLTemplate contains a template for the URL to use as a callback diff --git a/docs/content/reference/aggregated-docs.md b/docs/content/reference/aggregated-docs.md index af071967d6a2..866b7e7edb9d 100644 --- a/docs/content/reference/aggregated-docs.md +++ b/docs/content/reference/aggregated-docs.md @@ -39888,6 +39888,20 @@ ConfigurationStatus

configuration contains the cluster configuration status of the HostedCluster

+ + +lastSuccessfulEtcdBackupURL
+ +string + + + +(Optional) +

lastSuccessfulEtcdBackupURL is the cloud storage URL of the most recent +successful etcd backup snapshot. Persisted here because HCPEtcdBackup CRs +are ephemeral and may be deleted by retention policies.

+ + ###HostedControlPlaneSpec { #hypershift.openshift.io/v1beta1.HostedControlPlaneSpec } diff --git a/docs/content/reference/api.md b/docs/content/reference/api.md index 0b4345101814..1d6c5b273a43 100644 --- a/docs/content/reference/api.md +++ b/docs/content/reference/api.md @@ -9454,6 +9454,20 @@ ConfigurationStatus

configuration contains the cluster configuration status of the HostedCluster

+ + +lastSuccessfulEtcdBackupURL
+ +string + + + +(Optional) +

lastSuccessfulEtcdBackupURL is the cloud storage URL of the most recent +successful etcd backup snapshot. Persisted here because HCPEtcdBackup CRs +are ephemeral and may be deleted by retention policies.

+ + ###HostedControlPlaneSpec { #hypershift.openshift.io/v1beta1.HostedControlPlaneSpec } diff --git a/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go b/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go index b80f13462bab..733e836a3c93 100644 --- a/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go +++ b/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go @@ -2180,6 +2180,16 @@ type HostedClusterStatus struct { // configuration contains the cluster configuration status of the HostedCluster // +optional Configuration *ConfigurationStatus `json:"configuration,omitempty"` + + // lastSuccessfulEtcdBackupURL is the cloud storage URL of the most recent + // successful etcd backup snapshot. Persisted here because HCPEtcdBackup CRs + // are ephemeral and may be deleted by retention policies. + // +openshift:enable:FeatureGate=HCPEtcdBackup + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=2048 + // +kubebuilder:validation:XValidation:rule="self.matches('^(https|s3)://.*')",message="lastSuccessfulEtcdBackupURL must be a valid URL with scheme https or s3" + LastSuccessfulEtcdBackupURL string `json:"lastSuccessfulEtcdBackupURL,omitempty"` } // AutoNodeStatus contains the observed state of the AutoNode provisioner. From 3a68b71976ce78b629f8c2855cf7b16adb38f8ba Mon Sep 17 00:00:00 2001 From: Juan Manuel Parrilla Madrid Date: Thu, 9 Apr 2026 15:23:11 +0200 Subject: [PATCH 3/4] feat(CNTRLPLANE-2678): enforce restoreSnapshotURL immutability via CEL Add CEL validation rule to prevent day-2 modifications of the restoreSnapshotURL field. This ensures the field can only be set at creation time, which aligns with the existing API contract and makes the OADP restoration workflow safe without changing the field semantics. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Juan Manuel Parrilla Madrid --- api/hypershift/v1beta1/hostedcluster_types.go | 3 +- .../stable.hostedclusters.etcd.testsuite.yaml | 136 ++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/api/hypershift/v1beta1/hostedcluster_types.go b/api/hypershift/v1beta1/hostedcluster_types.go index 733e836a3c93..d99f765f090f 100644 --- a/api/hypershift/v1beta1/hostedcluster_types.go +++ b/api/hypershift/v1beta1/hostedcluster_types.go @@ -1953,7 +1953,8 @@ type ManagedEtcdStorageSpec struct { // +kubebuilder:validation:MaxItems=1 // +kubebuilder:validation:items:MaxLength=1024 // +kubebuilder:validation:XValidation:rule="self.size() <= 1", message="RestoreSnapshotURL shouldn't contain more than 1 entry" - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="restoreSnapshotURL is immutable" + // +kubebuilder:validation:XValidation:rule="self == oldSelf", message="restoreSnapshotURL is immutable" + // +kubebuilder:validation:XValidation:rule="self.size() == 0 || self[0].matches('^(https|s3)://.*')", message="restoreSnapshotURL must be a valid URL with scheme https or s3" RestoreSnapshotURL []string `json:"restoreSnapshotURL,omitempty"` } diff --git a/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.etcd.testsuite.yaml b/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.etcd.testsuite.yaml index 4b51a7b0934d..ba471d7f5e15 100644 --- a/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.etcd.testsuite.yaml +++ b/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.etcd.testsuite.yaml @@ -96,6 +96,142 @@ tests: type: Route route: {} + - name: When restoreSnapshotURL has invalid scheme it should fail + initial: | + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + spec: + dns: + baseDomain: example.com + etcd: + managementType: Managed + managed: + storage: + type: PersistentVolume + persistentVolume: + size: 8Gi + restoreSnapshotURL: + - "http://backup.example.com/snapshot.db" + platform: + type: AWS + pullSecret: + name: secret + release: + image: quay.io/openshift-release-dev/ocp-release:4.15.11-x86_64 + secretEncryption: + aescbc: + activeKey: + name: key + type: aescbc + services: + - service: APIServer + servicePublishingStrategy: + type: Route + route: {} + - service: OAuthServer + servicePublishingStrategy: + type: Route + route: {} + - service: Konnectivity + servicePublishingStrategy: + type: Route + route: {} + - service: Ignition + servicePublishingStrategy: + type: Route + route: {} + expectedError: "restoreSnapshotURL must be a valid URL with scheme https or s3" + + - name: When restoreSnapshotURL has https scheme it should pass + initial: | + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + spec: + dns: + baseDomain: example.com + etcd: + managementType: Managed + managed: + storage: + type: PersistentVolume + persistentVolume: + size: 8Gi + restoreSnapshotURL: + - "https://backup.example.com/snapshot.db" + platform: + type: AWS + pullSecret: + name: secret + release: + image: quay.io/openshift-release-dev/ocp-release:4.15.11-x86_64 + secretEncryption: + aescbc: + activeKey: + name: key + type: aescbc + services: + - service: APIServer + servicePublishingStrategy: + type: Route + route: {} + - service: OAuthServer + servicePublishingStrategy: + type: Route + route: {} + - service: Konnectivity + servicePublishingStrategy: + type: Route + route: {} + - service: Ignition + servicePublishingStrategy: + type: Route + route: {} + + - name: When restoreSnapshotURL has s3 scheme it should pass + initial: | + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + spec: + dns: + baseDomain: example.com + etcd: + managementType: Managed + managed: + storage: + type: PersistentVolume + persistentVolume: + size: 8Gi + restoreSnapshotURL: + - "s3://bucket/snapshot.db" + platform: + type: AWS + pullSecret: + name: secret + release: + image: quay.io/openshift-release-dev/ocp-release:4.15.11-x86_64 + secretEncryption: + aescbc: + activeKey: + name: key + type: aescbc + services: + - service: APIServer + servicePublishingStrategy: + type: Route + route: {} + - service: OAuthServer + servicePublishingStrategy: + type: Route + route: {} + - service: Konnectivity + servicePublishingStrategy: + type: Route + route: {} + - service: Ignition + servicePublishingStrategy: + type: Route + route: {} + onUpdate: - name: When restoreSnapshotURL is changed it should fail initial: | From 647381ec66f596ff9e4aedc9e3a48a2a38f3dce8 Mon Sep 17 00:00:00 2001 From: Juan Manuel Parrilla Madrid Date: Thu, 9 Apr 2026 15:23:34 +0200 Subject: [PATCH 4/4] chore(CNTRLPLANE-2678): regenerate CRDs for restoreSnapshotURL immutability Generated CRD manifests reflecting the new CEL validation rule on the restoreSnapshotURL field. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Juan Manuel Parrilla Madrid --- .../hostedclusters.hypershift.openshift.io/AAA_ungated.yaml | 3 +++ .../AutoNodeKarpenter.yaml | 3 +++ .../ClusterUpdateAcceptRisks.yaml | 3 +++ .../ClusterVersionOperatorConfiguration.yaml | 3 +++ .../hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml | 3 +++ .../ExternalOIDCWithUIDAndExtraClaimMappings.yaml | 3 +++ .../ExternalOIDCWithUpstreamParity.yaml | 3 +++ .../hostedclusters.hypershift.openshift.io/GCPPlatform.yaml | 3 +++ .../hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml | 3 +++ .../HyperShiftOnlyDynamicResourceAllocation.yaml | 3 +++ .../ImageStreamImportMode.yaml | 3 +++ .../KMSEncryptionProvider.yaml | 3 +++ .../hostedclusters.hypershift.openshift.io/OpenStack.yaml | 3 +++ .../AAA_ungated.yaml | 3 +++ .../AutoNodeKarpenter.yaml | 3 +++ .../ClusterUpdateAcceptRisks.yaml | 3 +++ .../ClusterVersionOperatorConfiguration.yaml | 3 +++ .../ExternalOIDC.yaml | 3 +++ .../ExternalOIDCWithUIDAndExtraClaimMappings.yaml | 3 +++ .../ExternalOIDCWithUpstreamParity.yaml | 3 +++ .../GCPPlatform.yaml | 3 +++ .../HCPEtcdBackup.yaml | 3 +++ .../HyperShiftOnlyDynamicResourceAllocation.yaml | 3 +++ .../ImageStreamImportMode.yaml | 3 +++ .../KMSEncryptionProvider.yaml | 3 +++ .../hostedcontrolplanes.hypershift.openshift.io/OpenStack.yaml | 3 +++ .../hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml | 3 +++ .../hostedclusters-Hypershift-Default.crd.yaml | 3 +++ .../hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml | 3 +++ .../hostedcontrolplanes-Hypershift-CustomNoUpgrade.crd.yaml | 3 +++ .../hostedcontrolplanes-Hypershift-Default.crd.yaml | 3 +++ ...ostedcontrolplanes-Hypershift-TechPreviewNoUpgrade.crd.yaml | 3 +++ .../hypershift/api/hypershift/v1beta1/hostedcluster_types.go | 3 ++- 33 files changed, 98 insertions(+), 1 deletion(-) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml index 2728d21fb954..b5222bb3375b 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml @@ -2529,6 +2529,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AutoNodeKarpenter.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AutoNodeKarpenter.yaml index 4144f1221233..70641ae031d8 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AutoNodeKarpenter.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AutoNodeKarpenter.yaml @@ -2656,6 +2656,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml index fa2ff2076c00..cc3c053f800a 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml @@ -2520,6 +2520,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml index e420c333520c..de1dc8aa58af 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml @@ -2520,6 +2520,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml index 1e8b6ee91820..6630d27aeb21 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml @@ -2853,6 +2853,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml index 7b69662e9c4d..d4c1bdd7fcf2 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml @@ -2993,6 +2993,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml index eb1675967890..7822b30358e7 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml @@ -2974,6 +2974,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml index 5f7edb99161e..6c6fdc61639e 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml @@ -2520,6 +2520,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml index db1604226816..9f9425548989 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml @@ -2585,6 +2585,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml index 85517eabe63b..45c638c0545a 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml @@ -2542,6 +2542,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml index 359a05a454cb..1dd0ce8473d5 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml @@ -2538,6 +2538,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml index af0021330081..0fbf3783689c 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml @@ -2596,6 +2596,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml index 45f7d4281e33..a9550f13ec18 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml @@ -2520,6 +2520,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AAA_ungated.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AAA_ungated.yaml index 7bbd3b9403d2..9a49d67f9eda 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AAA_ungated.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AAA_ungated.yaml @@ -2446,6 +2446,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AutoNodeKarpenter.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AutoNodeKarpenter.yaml index 090458227c3e..04be6dd42570 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AutoNodeKarpenter.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AutoNodeKarpenter.yaml @@ -2575,6 +2575,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml index db479139d40b..4181eaa306c3 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml @@ -2437,6 +2437,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml index 011843889c3f..e6615973f4b7 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml @@ -2437,6 +2437,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDC.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDC.yaml index 85ecaf3a151b..5fbcb4772f82 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDC.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDC.yaml @@ -2770,6 +2770,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml index 467ef11d489a..84fef65195b8 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml @@ -2910,6 +2910,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml index 61e4fe637380..5a85ccca1e67 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml @@ -2891,6 +2891,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/GCPPlatform.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/GCPPlatform.yaml index 57aebd76bda7..c859a25b2c1d 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/GCPPlatform.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/GCPPlatform.yaml @@ -2437,6 +2437,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HCPEtcdBackup.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HCPEtcdBackup.yaml index e0198f238e5c..1d9fa278fff6 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HCPEtcdBackup.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HCPEtcdBackup.yaml @@ -2502,6 +2502,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml index cbc9011e3bef..d1aea3cc45ae 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml @@ -2459,6 +2459,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ImageStreamImportMode.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ImageStreamImportMode.yaml index 2f88c0df3bf8..4594af147b5a 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ImageStreamImportMode.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ImageStreamImportMode.yaml @@ -2455,6 +2455,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/KMSEncryptionProvider.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/KMSEncryptionProvider.yaml index 084976b65e78..d252df9539c9 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/KMSEncryptionProvider.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/KMSEncryptionProvider.yaml @@ -2513,6 +2513,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/OpenStack.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/OpenStack.yaml index b7a413503c33..c5516bc62f51 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/OpenStack.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/OpenStack.yaml @@ -2437,6 +2437,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml index fae6e9b8480a..a3663b2d3ca9 100644 --- a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml +++ b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml @@ -3438,6 +3438,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-Default.crd.yaml b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-Default.crd.yaml index 1cbee8c3935b..8e955327ae9f 100644 --- a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-Default.crd.yaml +++ b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-Default.crd.yaml @@ -3022,6 +3022,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml index 284c06f9ea2a..72f606f53dc0 100644 --- a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml +++ b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml @@ -3349,6 +3349,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-CustomNoUpgrade.crd.yaml b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-CustomNoUpgrade.crd.yaml index 882f2ddbd142..67a307c95f3a 100644 --- a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-CustomNoUpgrade.crd.yaml +++ b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-CustomNoUpgrade.crd.yaml @@ -3357,6 +3357,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-Default.crd.yaml b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-Default.crd.yaml index ea7aaac0edd6..7207fcca0821 100644 --- a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-Default.crd.yaml +++ b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-Default.crd.yaml @@ -2939,6 +2939,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-TechPreviewNoUpgrade.crd.yaml b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-TechPreviewNoUpgrade.crd.yaml index af5005432821..42063effc660 100644 --- a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-TechPreviewNoUpgrade.crd.yaml +++ b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-TechPreviewNoUpgrade.crd.yaml @@ -3268,6 +3268,9 @@ spec: rule: self.size() <= 1 - message: restoreSnapshotURL is immutable rule: self == oldSelf + - message: restoreSnapshotURL must be a valid URL with + scheme https or s3 + rule: self.size() == 0 || self[0].matches('^(https|s3)://.*') type: description: |- type is the kind of persistent storage implementation to use for etcd. diff --git a/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go b/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go index 733e836a3c93..d99f765f090f 100644 --- a/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go +++ b/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go @@ -1953,7 +1953,8 @@ type ManagedEtcdStorageSpec struct { // +kubebuilder:validation:MaxItems=1 // +kubebuilder:validation:items:MaxLength=1024 // +kubebuilder:validation:XValidation:rule="self.size() <= 1", message="RestoreSnapshotURL shouldn't contain more than 1 entry" - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="restoreSnapshotURL is immutable" + // +kubebuilder:validation:XValidation:rule="self == oldSelf", message="restoreSnapshotURL is immutable" + // +kubebuilder:validation:XValidation:rule="self.size() == 0 || self[0].matches('^(https|s3)://.*')", message="restoreSnapshotURL must be a valid URL with scheme https or s3" RestoreSnapshotURL []string `json:"restoreSnapshotURL,omitempty"` }