From 8e598f69c0021fc6278db1c639a38b432452b890 Mon Sep 17 00:00:00 2001 From: Wantong Jiang Date: Mon, 6 Jan 2025 23:12:03 +0000 Subject: [PATCH 1/4] improve clusterStageUpdateRun UI and examples --- apis/placement/v1alpha1/stagedupdate_types.go | 11 ++++ apis/placement/v1beta1/stageupdate_types.go | 11 ++++ ...etes-fleet.io_clusterapprovalrequests.yaml | 30 ++++++++++- ...etes-fleet.io_clusterstagedupdateruns.yaml | 50 ++++++++++++++++++- examples/stagedupdaterun/approvalRequest.yaml | 4 +- 5 files changed, 100 insertions(+), 6 deletions(-) diff --git a/apis/placement/v1alpha1/stagedupdate_types.go b/apis/placement/v1alpha1/stagedupdate_types.go index 867ba3840..95a1a3577 100644 --- a/apis/placement/v1alpha1/stagedupdate_types.go +++ b/apis/placement/v1alpha1/stagedupdate_types.go @@ -17,6 +17,13 @@ import ( // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster,categories={fleet,fleet-placement},shortName=crsur // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:printcolumn:JSONPath=`.spec.placementName`,name="Placement",type=string +// +kubebuilder:printcolumn:JSONPath=`.spec.resourceSnapshotIndex`,name="ResourceSnapshot",type=string +// +kubebuilder:printcolumn:JSONPath=`.spec.stagedRolloutStrategyName`,name="Strategy",priority=1,type=string +// +kubebuilder:printcolumn:JSONPath=`.status.policySnapshotIndexUsed`,name="PolicySnapshot",type=string +// +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Initialized")].status`,name="Initialized",type=string +// +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Succeeded")].status`,name="Succeeded",type=string +// +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date // ClusterStagedUpdateRun represents a stage by stage update process that applies ClusterResourcePlacement // selected resources to specified clusters. @@ -397,6 +404,10 @@ type ClusterStagedUpdateRunList struct { // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster,categories={fleet,fleet-placement},shortName=careq // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:printcolumn:JSONPath=`.spec.parentStageRollout`,name="UpdateRun",type=string +// +kubebuilder:printcolumn:JSONPath=`.spec.targetStage`,name="Stage",type=string +// +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Approved")].status`,name="Approved",type=string +// +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date // ClusterApprovalRequest defines a request for user approval for cluster staged update run. // The request object MUST have the following labels: diff --git a/apis/placement/v1beta1/stageupdate_types.go b/apis/placement/v1beta1/stageupdate_types.go index 6b2362804..c508cc8c1 100644 --- a/apis/placement/v1beta1/stageupdate_types.go +++ b/apis/placement/v1beta1/stageupdate_types.go @@ -16,6 +16,13 @@ import ( // +kubebuilder:resource:scope=Cluster,categories={fleet,fleet-placement},shortName=crsur // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:storageversion +// +kubebuilder:printcolumn:JSONPath=`.spec.placementName`,name="Placement",type=string +// +kubebuilder:printcolumn:JSONPath=`.spec.resourceSnapshotIndex`,name="ResourceSnapshot",type=string +// +kubebuilder:printcolumn:JSONPath=`.spec.stagedRolloutStrategyName`,name="Strategy",priority=1,type=string +// +kubebuilder:printcolumn:JSONPath=`.status.policySnapshotIndexUsed`,name="PolicySnapshot",type=string +// +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Initialized")].status`,name="Initialized",type=string +// +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Succeeded")].status`,name="Succeeded",type=string +// +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date // ClusterStagedUpdateRun represents a stage by stage update process that applies ClusterResourcePlacement // selected resources to specified clusters. @@ -398,6 +405,10 @@ type ClusterStagedUpdateRunList struct { // +kubebuilder:resource:scope=Cluster,categories={fleet,fleet-placement},shortName=careq // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:storageversion +// +kubebuilder:printcolumn:JSONPath=`.spec.parentStageRollout`,name="UpdateRun",type=string +// +kubebuilder:printcolumn:JSONPath=`.spec.targetStage`,name="Stage",type=string +// +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Approved")].status`,name="Approved",type=string +// +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date // ClusterApprovalRequest defines a request for user approval for cluster staged update run. // The request object MUST have the following labels: diff --git a/config/crd/bases/placement.kubernetes-fleet.io_clusterapprovalrequests.yaml b/config/crd/bases/placement.kubernetes-fleet.io_clusterapprovalrequests.yaml index b98c8f2e5..efcd4e941 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_clusterapprovalrequests.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_clusterapprovalrequests.yaml @@ -19,7 +19,20 @@ spec: singular: clusterapprovalrequest scope: Cluster versions: - - name: v1alpha1 + - additionalPrinterColumns: + - jsonPath: .spec.parentStageRollout + name: UpdateRun + type: string + - jsonPath: .spec.targetStage + name: Stage + type: string + - jsonPath: .status.conditions[?(@.type=="Approved")].status + name: Approved + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 schema: openAPIV3Schema: description: |- @@ -150,7 +163,20 @@ spec: storage: false subresources: status: {} - - name: v1beta1 + - additionalPrinterColumns: + - jsonPath: .spec.parentStageRollout + name: UpdateRun + type: string + - jsonPath: .spec.targetStage + name: Stage + type: string + - jsonPath: .status.conditions[?(@.type=="Approved")].status + name: Approved + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 schema: openAPIV3Schema: description: |- diff --git a/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml b/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml index bc6b6bb44..415ae899b 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml @@ -19,7 +19,30 @@ spec: singular: clusterstagedupdaterun scope: Cluster versions: - - name: v1alpha1 + - additionalPrinterColumns: + - jsonPath: .spec.placementName + name: Placement + type: string + - jsonPath: .spec.resourceSnapshotIndex + name: ResourceSnapshot + type: string + - jsonPath: .spec.stagedRolloutStrategyName + name: Strategy + priority: 1 + type: string + - jsonPath: .status.policySnapshotIndexUsed + name: PolicySnapshot + type: string + - jsonPath: .status.conditions[?(@.type=="Initialized")].status + name: Initialized + type: string + - jsonPath: .status.conditions[?(@.type=="Succeeded")].status + name: Succeeded + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 schema: openAPIV3Schema: description: |- @@ -1213,7 +1236,30 @@ spec: storage: false subresources: status: {} - - name: v1beta1 + - additionalPrinterColumns: + - jsonPath: .spec.placementName + name: Placement + type: string + - jsonPath: .spec.resourceSnapshotIndex + name: ResourceSnapshot + type: string + - jsonPath: .spec.stagedRolloutStrategyName + name: Strategy + priority: 1 + type: string + - jsonPath: .status.policySnapshotIndexUsed + name: PolicySnapshot + type: string + - jsonPath: .status.conditions[?(@.type=="Initialized")].status + name: Initialized + type: string + - jsonPath: .status.conditions[?(@.type=="Succeeded")].status + name: Succeeded + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 schema: openAPIV3Schema: description: |- diff --git a/examples/stagedupdaterun/approvalRequest.yaml b/examples/stagedupdaterun/approvalRequest.yaml index 5bc22e184..5e7bfc5ec 100644 --- a/examples/stagedupdaterun/approvalRequest.yaml +++ b/examples/stagedupdaterun/approvalRequest.yaml @@ -11,5 +11,5 @@ spec: targetStage: canary status: conditions: - - type: Approved - status: "True" + - type: Approved + status: "True" From cd11f6b2bc8ed69a6efec2f9d6999cbc24b747ba Mon Sep 17 00:00:00 2001 From: Wantong Jiang Date: Thu, 9 Jan 2025 22:12:00 +0000 Subject: [PATCH 2/4] fix comments --- apis/placement/v1alpha1/stagedupdate_types.go | 6 +++--- apis/placement/v1beta1/stageupdate_types.go | 6 +++--- ...ement.kubernetes-fleet.io_clusterapprovalrequests.yaml | 4 ++-- ...ement.kubernetes-fleet.io_clusterstagedupdateruns.yaml | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apis/placement/v1alpha1/stagedupdate_types.go b/apis/placement/v1alpha1/stagedupdate_types.go index 95a1a3577..1f2c97ca6 100644 --- a/apis/placement/v1alpha1/stagedupdate_types.go +++ b/apis/placement/v1alpha1/stagedupdate_types.go @@ -18,9 +18,9 @@ import ( // +kubebuilder:resource:scope=Cluster,categories={fleet,fleet-placement},shortName=crsur // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:printcolumn:JSONPath=`.spec.placementName`,name="Placement",type=string -// +kubebuilder:printcolumn:JSONPath=`.spec.resourceSnapshotIndex`,name="ResourceSnapshot",type=string +// +kubebuilder:printcolumn:JSONPath=`.spec.resourceSnapshotIndex`,name="Resource-Snapshot",type=string // +kubebuilder:printcolumn:JSONPath=`.spec.stagedRolloutStrategyName`,name="Strategy",priority=1,type=string -// +kubebuilder:printcolumn:JSONPath=`.status.policySnapshotIndexUsed`,name="PolicySnapshot",type=string +// +kubebuilder:printcolumn:JSONPath=`.status.policySnapshotIndexUsed`,name="Policy-Snapshot",type=string // +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Initialized")].status`,name="Initialized",type=string // +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Succeeded")].status`,name="Succeeded",type=string // +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date @@ -404,7 +404,7 @@ type ClusterStagedUpdateRunList struct { // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster,categories={fleet,fleet-placement},shortName=careq // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +kubebuilder:printcolumn:JSONPath=`.spec.parentStageRollout`,name="UpdateRun",type=string +// +kubebuilder:printcolumn:JSONPath=`.spec.parentStageRollout`,name="Update-Run",type=string // +kubebuilder:printcolumn:JSONPath=`.spec.targetStage`,name="Stage",type=string // +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Approved")].status`,name="Approved",type=string // +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date diff --git a/apis/placement/v1beta1/stageupdate_types.go b/apis/placement/v1beta1/stageupdate_types.go index c508cc8c1..1d3986f3e 100644 --- a/apis/placement/v1beta1/stageupdate_types.go +++ b/apis/placement/v1beta1/stageupdate_types.go @@ -17,9 +17,9 @@ import ( // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:storageversion // +kubebuilder:printcolumn:JSONPath=`.spec.placementName`,name="Placement",type=string -// +kubebuilder:printcolumn:JSONPath=`.spec.resourceSnapshotIndex`,name="ResourceSnapshot",type=string +// +kubebuilder:printcolumn:JSONPath=`.spec.resourceSnapshotIndex`,name="Resource-Snapshot",type=string // +kubebuilder:printcolumn:JSONPath=`.spec.stagedRolloutStrategyName`,name="Strategy",priority=1,type=string -// +kubebuilder:printcolumn:JSONPath=`.status.policySnapshotIndexUsed`,name="PolicySnapshot",type=string +// +kubebuilder:printcolumn:JSONPath=`.status.policySnapshotIndexUsed`,name="Policy-Snapshot",type=string // +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Initialized")].status`,name="Initialized",type=string // +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Succeeded")].status`,name="Succeeded",type=string // +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date @@ -405,7 +405,7 @@ type ClusterStagedUpdateRunList struct { // +kubebuilder:resource:scope=Cluster,categories={fleet,fleet-placement},shortName=careq // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:storageversion -// +kubebuilder:printcolumn:JSONPath=`.spec.parentStageRollout`,name="UpdateRun",type=string +// +kubebuilder:printcolumn:JSONPath=`.spec.parentStageRollout`,name="Update-Run",type=string // +kubebuilder:printcolumn:JSONPath=`.spec.targetStage`,name="Stage",type=string // +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Approved")].status`,name="Approved",type=string // +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date diff --git a/config/crd/bases/placement.kubernetes-fleet.io_clusterapprovalrequests.yaml b/config/crd/bases/placement.kubernetes-fleet.io_clusterapprovalrequests.yaml index efcd4e941..a85381b23 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_clusterapprovalrequests.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_clusterapprovalrequests.yaml @@ -21,7 +21,7 @@ spec: versions: - additionalPrinterColumns: - jsonPath: .spec.parentStageRollout - name: UpdateRun + name: Update-Run type: string - jsonPath: .spec.targetStage name: Stage @@ -165,7 +165,7 @@ spec: status: {} - additionalPrinterColumns: - jsonPath: .spec.parentStageRollout - name: UpdateRun + name: Update-Run type: string - jsonPath: .spec.targetStage name: Stage diff --git a/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml b/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml index 415ae899b..b7a4f6d83 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml @@ -24,14 +24,14 @@ spec: name: Placement type: string - jsonPath: .spec.resourceSnapshotIndex - name: ResourceSnapshot + name: Resource-Snapshot type: string - jsonPath: .spec.stagedRolloutStrategyName name: Strategy priority: 1 type: string - jsonPath: .status.policySnapshotIndexUsed - name: PolicySnapshot + name: Policy-Snapshot type: string - jsonPath: .status.conditions[?(@.type=="Initialized")].status name: Initialized @@ -1241,14 +1241,14 @@ spec: name: Placement type: string - jsonPath: .spec.resourceSnapshotIndex - name: ResourceSnapshot + name: Resource-Snapshot type: string - jsonPath: .spec.stagedRolloutStrategyName name: Strategy priority: 1 type: string - jsonPath: .status.policySnapshotIndexUsed - name: PolicySnapshot + name: Policy-Snapshot type: string - jsonPath: .status.conditions[?(@.type=="Initialized")].status name: Initialized From ea53057372ab3676aaa8549ea69248359461651d Mon Sep 17 00:00:00 2001 From: Wantong Jiang Date: Tue, 14 Jan 2025 08:05:46 +0000 Subject: [PATCH 3/4] update updateRun validations to ensure ApprovalRequest name uniqueness --- apis/placement/v1alpha1/stagedupdate_types.go | 5 ++-- apis/placement/v1beta1/stageupdate_types.go | 5 ++-- ...etes-fleet.io_clusterstagedupdateruns.yaml | 26 ++++++++++++------- ...leet.io_clusterstagedupdatestrategies.yaml | 4 +-- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/apis/placement/v1alpha1/stagedupdate_types.go b/apis/placement/v1alpha1/stagedupdate_types.go index 1f2c97ca6..b2065bc01 100644 --- a/apis/placement/v1alpha1/stagedupdate_types.go +++ b/apis/placement/v1alpha1/stagedupdate_types.go @@ -19,11 +19,12 @@ import ( // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:printcolumn:JSONPath=`.spec.placementName`,name="Placement",type=string // +kubebuilder:printcolumn:JSONPath=`.spec.resourceSnapshotIndex`,name="Resource-Snapshot",type=string -// +kubebuilder:printcolumn:JSONPath=`.spec.stagedRolloutStrategyName`,name="Strategy",priority=1,type=string // +kubebuilder:printcolumn:JSONPath=`.status.policySnapshotIndexUsed`,name="Policy-Snapshot",type=string // +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Initialized")].status`,name="Initialized",type=string // +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Succeeded")].status`,name="Succeeded",type=string // +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date +// +kubebuilder:printcolumn:JSONPath=`.spec.stagedRolloutStrategyName`,name="Strategy",priority=1,type=string +// +kubebuilder:validation:XValidation:rule="size(self.metadata.name) < 128",message="metadata.name max length is 127" // ClusterStagedUpdateRun represents a stage by stage update process that applies ClusterResourcePlacement // selected resources to specified clusters. @@ -109,7 +110,7 @@ type ClusterStagedUpdateStrategyList struct { type StageConfig struct { // The name of the stage. This MUST be unique within the same StagedUpdateStrategy. // +kubebuilder:validation:MaxLength=63 - // +kubebuilder:validation:Pattern="[A-Za-z0-9]+$" + // +kubebuilder:validation:Pattern="^[a-z0-9]+$" // +kubebuilder:validation:Required Name string `json:"name"` diff --git a/apis/placement/v1beta1/stageupdate_types.go b/apis/placement/v1beta1/stageupdate_types.go index 1d3986f3e..616106fc2 100644 --- a/apis/placement/v1beta1/stageupdate_types.go +++ b/apis/placement/v1beta1/stageupdate_types.go @@ -18,11 +18,12 @@ import ( // +kubebuilder:storageversion // +kubebuilder:printcolumn:JSONPath=`.spec.placementName`,name="Placement",type=string // +kubebuilder:printcolumn:JSONPath=`.spec.resourceSnapshotIndex`,name="Resource-Snapshot",type=string -// +kubebuilder:printcolumn:JSONPath=`.spec.stagedRolloutStrategyName`,name="Strategy",priority=1,type=string // +kubebuilder:printcolumn:JSONPath=`.status.policySnapshotIndexUsed`,name="Policy-Snapshot",type=string // +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Initialized")].status`,name="Initialized",type=string // +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Succeeded")].status`,name="Succeeded",type=string // +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date +// +kubebuilder:printcolumn:JSONPath=`.spec.stagedRolloutStrategyName`,name="Strategy",priority=1,type=string +// +kubebuilder:validation:XValidation:rule="size(self.metadata.name) < 128",message="metadata.name max length is 127" // ClusterStagedUpdateRun represents a stage by stage update process that applies ClusterResourcePlacement // selected resources to specified clusters. @@ -109,7 +110,7 @@ type ClusterStagedUpdateStrategyList struct { type StageConfig struct { // The name of the stage. This MUST be unique within the same StagedUpdateStrategy. // +kubebuilder:validation:MaxLength=63 - // +kubebuilder:validation:Pattern="[A-Za-z0-9]+$" + // +kubebuilder:validation:Pattern="^[a-z0-9]+$" // +kubebuilder:validation:Required Name string `json:"name"` diff --git a/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml b/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml index b7a4f6d83..2bc676558 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml @@ -26,10 +26,6 @@ spec: - jsonPath: .spec.resourceSnapshotIndex name: Resource-Snapshot type: string - - jsonPath: .spec.stagedRolloutStrategyName - name: Strategy - priority: 1 - type: string - jsonPath: .status.policySnapshotIndexUsed name: Policy-Snapshot type: string @@ -42,6 +38,10 @@ spec: - jsonPath: .metadata.creationTimestamp name: Age type: date + - jsonPath: .spec.stagedRolloutStrategyName + name: Strategy + priority: 1 + type: string name: v1alpha1 schema: openAPIV3Schema: @@ -884,7 +884,7 @@ spec: description: The name of the stage. This MUST be unique within the same StagedUpdateStrategy. maxLength: 63 - pattern: '[A-Za-z0-9]+$' + pattern: ^[a-z0-9]+$ type: string sortingLabelKey: description: |- @@ -1232,6 +1232,9 @@ spec: required: - spec type: object + x-kubernetes-validations: + - message: metadata.name max length is 127 + rule: size(self.metadata.name) < 128 served: true storage: false subresources: @@ -1243,10 +1246,6 @@ spec: - jsonPath: .spec.resourceSnapshotIndex name: Resource-Snapshot type: string - - jsonPath: .spec.stagedRolloutStrategyName - name: Strategy - priority: 1 - type: string - jsonPath: .status.policySnapshotIndexUsed name: Policy-Snapshot type: string @@ -1259,6 +1258,10 @@ spec: - jsonPath: .metadata.creationTimestamp name: Age type: date + - jsonPath: .spec.stagedRolloutStrategyName + name: Strategy + priority: 1 + type: string name: v1beta1 schema: openAPIV3Schema: @@ -2101,7 +2104,7 @@ spec: description: The name of the stage. This MUST be unique within the same StagedUpdateStrategy. maxLength: 63 - pattern: '[A-Za-z0-9]+$' + pattern: ^[a-z0-9]+$ type: string sortingLabelKey: description: |- @@ -2449,6 +2452,9 @@ spec: required: - spec type: object + x-kubernetes-validations: + - message: metadata.name max length is 127 + rule: size(self.metadata.name) < 128 served: true storage: true subresources: diff --git a/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdatestrategies.yaml b/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdatestrategies.yaml index a47543fa4..29bc3b6e4 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdatestrategies.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdatestrategies.yaml @@ -134,7 +134,7 @@ spec: description: The name of the stage. This MUST be unique within the same StagedUpdateStrategy. maxLength: 63 - pattern: '[A-Za-z0-9]+$' + pattern: ^[a-z0-9]+$ type: string sortingLabelKey: description: |- @@ -273,7 +273,7 @@ spec: description: The name of the stage. This MUST be unique within the same StagedUpdateStrategy. maxLength: 63 - pattern: '[A-Za-z0-9]+$' + pattern: ^[a-z0-9]+$ type: string sortingLabelKey: description: |- From 71a9475705e3122f5a4af5f5734830ff62848b7d Mon Sep 17 00:00:00 2001 From: Wantong Jiang Date: Thu, 16 Jan 2025 00:38:22 +0000 Subject: [PATCH 4/4] add test for updateRun API --- .../api_validation_integration_test.go | 210 +++++++++++++++++- 1 file changed, 208 insertions(+), 2 deletions(-) diff --git a/test/apis/placement/v1beta1/api_validation_integration_test.go b/test/apis/placement/v1beta1/api_validation_integration_test.go index 372c5a7f6..99850f4c1 100644 --- a/test/apis/placement/v1beta1/api_validation_integration_test.go +++ b/test/apis/placement/v1beta1/api_validation_integration_test.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "reflect" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -20,10 +21,16 @@ import ( ) const ( - crpdbNameTemplate = "test-crpdb-%d" + crpdbNameTemplate = "test-crpdb-%d" + validupdateRunNameTemplate = "test-update-run-%d" + invalidupdateRunNameTemplate = "test-update-run-with-invalid-length-0123456789-0123456789-0123456789-0123456789-0123456789-0123456789-0123456789-0123456789-0123456789-%d" + updateRunStrategyNameTemplate = "test-update-run-strategy-%d" + updateRunStageNameTemplate = "stage%d%d" + invalidupdateRunStageNameTemplate = "stage012345678901234567890123456789012345678901234567890123456789%d%d" + approveRequestNameTemplate = "test-approve-request-%d" ) -var _ = Describe("Test placement v1alpha1 API validation", func() { +var _ = Describe("Test placement v1beta1 API validation", func() { Context("Test ClusterPlacementDisruptionBudget API validation - valid cases", func() { It("should allow creation of ClusterPlacementDisruptionBudget with valid maxUnavailable - int", func() { crpdb := placementv1beta1.ClusterResourcePlacementDisruptionBudget{ @@ -292,4 +299,203 @@ var _ = Describe("Test placement v1alpha1 API validation", func() { Expect(statusErr.ErrStatus.Message).Should(MatchRegexp("invalid: spec.minAvailable")) }) }) + + Context("Test ClusterStagedUpdateRun API validation - valid cases", func() { + It("Should allow creation of ClusterStagedUpdateRun with valid name length", func() { + updateRun := placementv1beta1.ClusterStagedUpdateRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(validupdateRunNameTemplate, GinkgoParallelProcess()), + }, + } + Expect(hubClient.Create(ctx, &updateRun)).Should(Succeed()) + Expect(hubClient.Delete(ctx, &updateRun)).Should(Succeed()) + }) + }) + + Context("Test ClusterStagedUpdateRun API validation - invalid cases", func() { + It("Should deny creation of ClusterStagedUpdateRun with name length > 127", func() { + updateRun := placementv1beta1.ClusterStagedUpdateRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(invalidupdateRunNameTemplate, GinkgoParallelProcess()), + }, + } + err := hubClient.Create(ctx, &updateRun) + var statusErr *k8sErrors.StatusError + Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Create updateRun call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8sErrors.StatusError{}))) + Expect(statusErr.ErrStatus.Message).Should(MatchRegexp("metadata.name max length is 127")) + }) + + It("Should deny update of ClusterStagedUpdateRun spec", func() { + updateRun := placementv1beta1.ClusterStagedUpdateRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(validupdateRunNameTemplate, GinkgoParallelProcess()), + }, + Spec: placementv1beta1.StagedUpdateRunSpec{ + PlacementName: "test-placement", + }, + } + Expect(hubClient.Create(ctx, &updateRun)).Should(Succeed()) + + updateRun.Spec.PlacementName = "test-placement-2" + err := hubClient.Update(ctx, &updateRun) + var statusErr *k8sErrors.StatusError + Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Update updateRun call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8sErrors.StatusError{}))) + Expect(statusErr.ErrStatus.Message).Should(MatchRegexp("The spec field is immutable")) + Expect(hubClient.Delete(ctx, &updateRun)).Should(Succeed()) + }) + }) + + Context("Test ClusterStagedUpdateStrategy API validation - valid cases", func() { + It("Should allow creation of ClusterStagedUpdateStrategy with valid stage config", func() { + strategy := placementv1beta1.ClusterStagedUpdateStrategy{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(updateRunStrategyNameTemplate, GinkgoParallelProcess()), + }, + Spec: placementv1beta1.StagedUpdateStrategySpec{ + Stages: []placementv1beta1.StageConfig{ + { + Name: fmt.Sprintf(updateRunStageNameTemplate, GinkgoParallelProcess(), 1), + AfterStageTasks: []placementv1beta1.AfterStageTask{ + { + Type: placementv1beta1.AfterStageTaskTypeApproval, + }, + { + Type: placementv1beta1.AfterStageTaskTypeTimedWait, + WaitTime: metav1.Duration{Duration: time.Second * 10}, + }, + }, + }, + }, + }, + } + Expect(hubClient.Create(ctx, &strategy)).Should(Succeed()) + Expect(hubClient.Delete(ctx, &strategy)).Should(Succeed()) + }) + }) + + Context("Test ClusterStagedUpdateStrategy API validation - invalid cases", func() { + It("Should deny creation of ClusterStagedUpdateStrategy with more than allowed staged", func() { + strategy := placementv1beta1.ClusterStagedUpdateStrategy{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(updateRunStrategyNameTemplate, GinkgoParallelProcess()), + }, + } + for i := 0; i < 32; i++ { + strategy.Spec.Stages = append(strategy.Spec.Stages, placementv1beta1.StageConfig{ + Name: fmt.Sprintf(updateRunStageNameTemplate, GinkgoParallelProcess(), i), + }) + } + err := hubClient.Create(ctx, &strategy) + var statusErr *k8sErrors.StatusError + Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Create updateRunStrategy call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8sErrors.StatusError{}))) + Expect(statusErr.ErrStatus.Message).Should(MatchRegexp("spec.stages: Too many: 32: must have at most 31 items")) + }) + + It("Should deny creation of ClusterStagedUpdateStrategy with invalid stage config - too long stage name", func() { + strategy := placementv1beta1.ClusterStagedUpdateStrategy{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(updateRunStrategyNameTemplate, GinkgoParallelProcess()), + }, + Spec: placementv1beta1.StagedUpdateStrategySpec{ + Stages: []placementv1beta1.StageConfig{ + { + Name: fmt.Sprintf(invalidupdateRunStageNameTemplate, GinkgoParallelProcess(), 1), + }, + }, + }, + } + err := hubClient.Create(ctx, &strategy) + var statusErr *k8sErrors.StatusError + Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Create updateRunStrategy call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8sErrors.StatusError{}))) + Expect(statusErr.ErrStatus.Message).Should(MatchRegexp("Too long: may not be longer than 63")) + }) + + It("Should deny creation of ClusterStagedUpdateStrategy with invalid stage config - stage name with invalid characters", func() { + strategy := placementv1beta1.ClusterStagedUpdateStrategy{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(updateRunStrategyNameTemplate, GinkgoParallelProcess()), + }, + Spec: placementv1beta1.StagedUpdateStrategySpec{ + Stages: []placementv1beta1.StageConfig{ + { + Name: fmt.Sprintf(updateRunStageNameTemplate, GinkgoParallelProcess(), 1) + "-A", + }, + }, + }, + } + err := hubClient.Create(ctx, &strategy) + var statusErr *k8sErrors.StatusError + Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Create updateRunStrategy call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8sErrors.StatusError{}))) + Expect(statusErr.ErrStatus.Message).Should(MatchRegexp("in body should match.*a-z0-9")) + }) + + It("Should deny creation of ClusterStagedUpdateStrategy with invalid stage config - more than 2 AfterStageTasks", func() { + strategy := placementv1beta1.ClusterStagedUpdateStrategy{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(updateRunStrategyNameTemplate, GinkgoParallelProcess()), + }, + Spec: placementv1beta1.StagedUpdateStrategySpec{ + Stages: []placementv1beta1.StageConfig{ + { + Name: fmt.Sprintf(updateRunStageNameTemplate, GinkgoParallelProcess(), 1), + AfterStageTasks: []placementv1beta1.AfterStageTask{ + { + Type: placementv1beta1.AfterStageTaskTypeApproval, + }, + { + Type: placementv1beta1.AfterStageTaskTypeApproval, + }, + { + Type: placementv1beta1.AfterStageTaskTypeTimedWait, + WaitTime: metav1.Duration{Duration: time.Second * 10}, + }, + }, + }, + }, + }, + } + err := hubClient.Create(ctx, &strategy) + var statusErr *k8sErrors.StatusError + Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Create updateRunStrategy call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8sErrors.StatusError{}))) + Expect(statusErr.ErrStatus.Message).Should(MatchRegexp("Too many: 3: must have at most 2 items")) + }) + }) + + Context("Test ClusterApprovalRequest API validation - valid cases", func() { + It("Should allow creation of ClusterApprovalRequest with valid configurations", func() { + appReq := placementv1beta1.ClusterApprovalRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(approveRequestNameTemplate, GinkgoParallelProcess()), + }, + Spec: placementv1beta1.ApprovalRequestSpec{ + TargetUpdateRun: fmt.Sprintf(validupdateRunNameTemplate, GinkgoParallelProcess()), + TargetStage: fmt.Sprintf(updateRunStageNameTemplate, GinkgoParallelProcess(), 1), + }, + } + Expect(hubClient.Create(ctx, &appReq)).Should(Succeed()) + Expect(hubClient.Delete(ctx, &appReq)).Should(Succeed()) + }) + }) + + Context("Test ClusterApprovalRequest API validation - invalid cases", func() { + It("Should deny update of ClusterApprovalRequest spec", func() { + appReq := placementv1beta1.ClusterApprovalRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(approveRequestNameTemplate, GinkgoParallelProcess()), + }, + Spec: placementv1beta1.ApprovalRequestSpec{ + TargetUpdateRun: fmt.Sprintf(validupdateRunNameTemplate, GinkgoParallelProcess()), + TargetStage: fmt.Sprintf(updateRunStageNameTemplate, GinkgoParallelProcess(), 1), + }, + } + Expect(hubClient.Create(ctx, &appReq)).Should(Succeed()) + + appReq.Spec.TargetUpdateRun += "1" + err := hubClient.Update(ctx, &appReq) + var statusErr *k8sErrors.StatusError + Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Update clusterApprovalRequest call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8sErrors.StatusError{}))) + Expect(statusErr.ErrStatus.Message).Should(MatchRegexp("The spec field is immutable")) + Expect(hubClient.Delete(ctx, &appReq)).Should(Succeed()) + }) + }) })