From 228f75471c776ad1671b4ee1ffe046d94ae296d8 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Wed, 21 Aug 2024 22:37:49 -0700 Subject: [PATCH] update definition --- apis/cluster/v1beta1/zz_generated.deepcopy.go | 2 +- apis/placement/v1alpha1/stagedupdate_types.go | 382 ++++++++++++++++++ apis/placement/v1alpha1/stagerollout_types.go | 344 ---------------- .../v1alpha1/zz_generated.deepcopy.go | 266 +++++++----- .../v1beta1/clusterresourceplacement_types.go | 13 +- .../v1beta1/zz_generated.deepcopy.go | 2 +- apis/v1alpha1/zz_generated.deepcopy.go | 2 +- ...es-fleet.io_clusterresourceplacements.yaml | 5 +- ...etes-fleet.io_rolloutapprovalrequests.yaml | 4 +- ...etes-fleet.io_stagedrolloutstrategies.yaml | 11 +- ...fleet.io_stagedupdateapprovalrequests.yaml | 158 ++++++++ ....kubernetes-fleet.io_stagedupdateruns.yaml | 304 ++++++++++++++ ...netes-fleet.io_stagedupdatestrategies.yaml | 174 ++++++++ .../validator/clusterresourceplacement.go | 6 +- .../clusterresourceplacement_test.go | 19 +- test/apis/v1alpha1/zz_generated.deepcopy.go | 2 +- test/e2e/README.md | 4 +- 17 files changed, 1228 insertions(+), 470 deletions(-) create mode 100644 apis/placement/v1alpha1/stagedupdate_types.go delete mode 100644 apis/placement/v1alpha1/stagerollout_types.go create mode 100644 config/crd/bases/placement.kubernetes-fleet.io_stagedupdateapprovalrequests.yaml create mode 100644 config/crd/bases/placement.kubernetes-fleet.io_stagedupdateruns.yaml create mode 100644 config/crd/bases/placement.kubernetes-fleet.io_stagedupdatestrategies.yaml diff --git a/apis/cluster/v1beta1/zz_generated.deepcopy.go b/apis/cluster/v1beta1/zz_generated.deepcopy.go index 6d06cb15b..6c25f0189 100644 --- a/apis/cluster/v1beta1/zz_generated.deepcopy.go +++ b/apis/cluster/v1beta1/zz_generated.deepcopy.go @@ -10,7 +10,7 @@ Licensed under the MIT license. package v1beta1 import ( - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/apis/placement/v1alpha1/stagedupdate_types.go b/apis/placement/v1alpha1/stagedupdate_types.go new file mode 100644 index 000000000..8191a4603 --- /dev/null +++ b/apis/placement/v1alpha1/stagedupdate_types.go @@ -0,0 +1,382 @@ +/* +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +genclient:namespaced +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope="Namespaced",categories={fleet,fleet-placement} +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// StagedUpdateRun defines a stage by stage update run that applies the selected resources by the +// corresponding ClusterResourcePlacement to its selected clusters. We remove the resources from the clusters that are +// unselected after all the stages explicitly defined in the updateStrategy complete. +// Each StagedUpdateRun object corresponds to a single "release" of a certain version of the resources. +// The release is abandoned if the StagedUpdateRun object is deleted or the scheduling decision changes. +type StagedUpdateRun struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // The desired state of StagedUpdateRun. + // +required + Spec StagedUpdateRunSpec `json:"spec"` + + // The observed status of StagedUpdateRun. + // +optional + Status StagedUpdateRunStatus `json:"status,omitempty"` +} + +// StagedUpdateRunSpec defines the desired the update strategy and the snapshot indices +// on which the update is based. +type StagedUpdateRunSpec struct { + // The name of the CRP that this update run is applied to. + // There can be multiple active update runs for each ClusterResourcePlacement but + // it's up to the devOps to make sure they don't conflict with each other. + // +required + ParentCRPName string `json:"parentCRPName"` + + // The resource snapshot index that this update run is based on. + // +required + ResourceSnapShotIndex string `json:"resourceSnapShotIndex"` + + // The override snapshot index that this update run is based on. + // +required + OverrideSnapShotIndex string `json:"overrideSnapShotIndex"` + + // The reference to the update strategy that specifies the stages and the sequence + // in which the application will be updated on the member clusters. + // +required + StagedUpdateStrategyRef StagedUpdateStrategyReference `json:"stagedRolloutStrategyRef"` +} + +// StagedUpdateStrategyReference is a reference to a StagedUpdateStrategy. +type StagedUpdateStrategyReference struct { + // Name is the name of the referenced StagedUpdateStrategy. + Name string `json:"name"` + // Namespace is the namespace where the referenced StagedUpdateStrategy is located. + // +optional + Namespace string `json:"namespace,omitempty"` +} + +// +genclient +// +genclient:namespaced +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope="Namespaced",categories={fleet,fleet-placement} +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// StagedUpdateStrategy defines a reusable strategy that specifies the stages and the sequence +// in which the application will be updated on the member clusters. +type StagedUpdateStrategy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // The desired state of StagedUpdateStrategy. + // +required + Spec StagedUpdateStrategySpec `json:"spec"` + + // The observed status of StagedUpdateStrategy. + // +optional + Status StagedUpdateStrategyStatus `json:"status,omitempty"` +} + +// StagedUpdateStrategySpec defines the desired the update strategy. +type StagedUpdateStrategySpec struct { + // Staged update configurations for each update stage. + // +required + Stages []StagedUpdateConfig `json:"stages"` + + // TODO: Add alerting configuration. +} + +// StagedUpdateStrategyStatus defines the observed state of the StagedUpdateStrategy. +type StagedUpdateStrategyStatus struct { + // ComputedStages list the name of the clusters in each stage. + // The clusters in each stage are ordered following its order to be rolled out. + // +required + ComputedStages [][]string `json:"currentStages"` +} + +// StagedUpdateStrategyList contains a list of StagedUpdateStrategy. +// +kubebuilder:resource:scope="Namespaced" +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type StagedUpdateStrategyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []StagedUpdateStrategy `json:"items"` +} + +// StagedUpdateConfig describes a single update stage group configuration. +// The clusters in each stage are updated sequentially for now. +// We will stop the update if any of the updates fail. +type StagedUpdateConfig struct { + // The name of the stage. This MUST be unique within the same StagedUpdateRun. + // +required + Name string `json:"name"` + + // LabelSelector is a label query over all the joined member clusters. Clusters matching the query are selected. + // We don't check if there are overlap between clusters selected by different stages. + LabelSelector *metav1.LabelSelector `json:"labelSelector"` + + // The label key used to sort the selected clusters. + // The clusters within the stage are updated sequentially following the rule below: + // - primary: Ascending order based on the value of the label key, interpreted as integers if present. + // - secondary: Ascending order based on the name of the cluster if the label key is absent. + // +optional + SortingLabelKey *string `json:"sortingLabelKey,omitempty"` + + // The collection of tasks that each stage needs to complete successfully before moving to the next stage. + // Each task is executed in parallel. + // +optional + PostStageTasks []PostStageTask `json:"postRolloutTasks,omitempty"` +} + +// PostStageTask is the collection of post stage tasks that ALL need to be completed before we can move to the next stage. +type PostStageTask struct { + // The type of the post stage task. + // +kubebuilder:validation:Enum=TimedWait;ManualApproval + // +required + Type PostStageTaskType `json:"type"` + + // The name of the StagedUpdateApprovalRequest we need to create to get the approval for the next stage. + // We will generate a name with a prefix of the StagedUpdateRun name if it's empty. + // +optional + ApprovalRequestName string `json:"approvalTask,omitempty"` + + // The time to wait after all the clusters in the current stage complete the update before we move to the next stage. + // +kubebuilder:default:="1h" + // +kubebuilder:validation:Pattern="^0|([0-9]+(\\.[0-9]+)?(s|m|h))+$" + // +kubebuilder:validation:Type:=string + // +optional + WaitTime metav1.Duration `json:"waitTime,omitempty"` +} + +// StagedUpdateRunStatus defines the observed state of the StagedUpdateRun. +type StagedUpdateRunStatus struct { + // ComputedStages list the name of the clusters in each stage when the updateRun started. + // The clusters in each stage are ordered following its order to be rolled out. + // The updateRun will stop if the cluster stages change during the update. + // +required + ComputedStages [][]string `json:"currentStages"` + + // CurrentStageIndex is the index of the stage that is in the middle of the update. + // The index starts from 0. The index is -1 if the update is not started. + // The index is the size of the Stages if the update is completed. + // +required + CurrentStageIndex int `json:"currentStageIndex"` + + // The applied strategy of the CRP that we are currently using. + // It is the same as the strategy in the CRP when we first start the update. + // We will NOT update the strategy in the update even if the appliedStrategy changes in the CRP. + // +required + CurrentAppliedStrategy string `json:"currentAppliedStrategy"` + + // The collection of clusters on which we are going to remove the application from. + // We restart the update if CRP changes the selected clusters during our update. + // Therefore, we need to keep track of the clusters that we have applied the resources to but no long is selected. + // We remove the application from these clusters after all the stages are completed and remove them from this list. + // +required + ToBeRevertedClusters []string `json:"toBeRevertedClusters"` + + // the clusters that are actively updating. It will contain the clusters that we are removing after all + // the stages are completed. It can be empty if the update is stopped. + // We currently only support a single cluster update at a time but we may support multiple clusters in the future. + // +optional + CurrentUpdatingClusters []string `json:"currentUpdatingClusters,omitempty"` + + // The status of the post update tasks that are associated with current stage. + // +optional + CurrentPostStageTaskStatus []PostStageTaskStatus `json:"currentPostStageTaskStatus,omitempty"` + + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + // + // Conditions is an array of current observed conditions for StagedUpdateRun. + // Known conditions are "RollingOut". + // +optional + Conditions []metav1.Condition `json:"conditions"` +} + +type PostStageTaskStatus struct { + // The type of the post update task. + // +required + Type PostStageTaskType `json:"type"` + + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + // + // Conditions is an array of current observed conditions for the specific type of post update task. + // Known conditions are "ApprovalRequestCreated", "WaitTimeElapsed", and "ApprovalRequestApproved". + // +optional + Conditions []metav1.Condition `json:"conditions"` +} + +// PostStageTaskType identifies a specific type of the PostStageTask. +// +enum +type PostStageTaskType string + +const ( + // PostStageTaskSTypeTimedWait indicates the post stage task is a timed wait. + PostStageTaskSTypeTimedWait PostStageTaskType = "TimedWait" + + // PostStageTaskSTypeManualApproval indicates the post stage task is a manual approval. + PostStageTaskSTypeManualApproval PostStageTaskType = "ManualApproval" +) + +// PostStageTaskConditionType identifies a specific condition of the PostStageTask. +// +enum +type PostStageTaskConditionType string + +const ( + // PostStageTaskConditionApprovalRequestCreated indicates whether the approval request is created. + // Its condition status can be one of the following: + // - "True" means the approval request is created. + // - "False" means the approval request is not created. + PostStageTaskConditionApprovalRequestCreated PostStageTaskConditionType = "ApprovalRequestCreated" + + // PostStageTaskConditionApprovalRequestApproved indicates whether the approval request is approved. + // Its condition status can be one of the following: + // - "True" means the approval request is approved. + // - "False" means the approval request is not approved. + PostStageTaskConditionApprovalRequestApproved PostStageTaskConditionType = "ApprovalRequestApproved" + + // PostStageTaskConditionApprovalWaitTimeElapsed indicates whether the wait time after each stage is elapsed. + // We will fill the message of the condition of the remaining wait time if the status is "False". + // Its condition status can be one of the following: + // - "True" means the wait time is elapsed. + // - "False" means the wait time is not elapsed. + PostStageTaskConditionApprovalWaitTimeElapsed PostStageTaskConditionType = "WaitTimeElapsed" +) + +// StagedUpdateRunConditionType identifies a specific condition of the StagedUpdateRun. +// +enum +type StagedUpdateRunConditionType string + +const ( + // StagedUpdateRunConditionRunning indicates whether the staged update run is proceeding normally. + // Its condition status can be one of the following: + // - "True" means the staged update run is proceeding normally. + // - "False" means the staged update run is not proceeding normally. + // - "Unknown" means it is unknown. + StagedUpdateRunConditionRunning StagedUpdateRunConditionType = "Running" + + // StagedUpdateRunConditionWaitingBetweenStage indicates whether the staged update run is waiting between stages. + // Its condition status can be one of the following: + // - "True" means the staged update run is waiting between stages. + // - "False" means the staged update run is not waiting between stages. + // - "Unknown" means it is unknown. + StagedUpdateRunConditionWaitingBetweenStage StagedUpdateRunConditionType = "WaitingBetweenStage" + // TODO: add alerting condition +) + +// StagedUpdateRunList contains a list of StagedUpdateRun. +// +kubebuilder:resource:scope="Namespaced" +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type StagedUpdateRunList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []StagedUpdateRun `json:"items"` +} + +// +genclient +// +genclient:nonNamespaced +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope="Namespaced",categories={fleet,fleet-placement} +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// StagedUpdateApprovalRequest defines a stage by stage update policy that is applied to the ClusterResourcePlacement of the same name. +// Each snapshot MUST have the following labels: +// - `ParentStagedUpdateRun` which points to its owner stage update. +// - `TargetStage` which is the name of the stage that this approval request is for. +// - `IsLatestUpdateRunApproval` which indicates whether this approval request is the latest one related to this update run. +type StagedUpdateApprovalRequest struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // The desired state of StagedUpdateApprovalRequest. + // +required + Spec StagedUpdateApprovalRequestSpec `json:"spec"` + + // The desired state of StagedUpdateApprovalRequest. + // +required + Status StagedUpdateApprovalRequestStatus `json:"status"` +} + +// StagedUpdateApprovalRequestSpec defines the desired the update approval request. +// The entire spec is immutable. +type StagedUpdateApprovalRequestSpec struct { + // The name of the staged update run that this approval request is for. + // +required + ParentStagedUpdateRun string `json:"parentStageRollout"` + + // The index of the update stage that this approval request is for. + // +required + TargetStage int `json:"targetStage"` + + // The resource snapshot index that this approval request is based on. + // +required + ResourceSnapshotIndex string `json:"resourceSnapshotIndex"` + + // The override snapshot index that this approval request is based on. + // +required + OverrideSnapShotIndex string `json:"overrideSnapShotIndex"` +} + +// StagedUpdateApprovalRequestStatus defines the observed state of the StagedUpdateApprovalRequest. +type StagedUpdateApprovalRequestStatus struct { + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + // + // Conditions is an array of current observed conditions for the specific type of post update task. + // Known conditions are "Approved", and "ApprovalAccepted". + // +optional + Conditions []metav1.Condition `json:"conditions"` +} + +// StagedUpdateApprovalRequestConditionType identifies a specific condition of the StagedUpdateApprovalRequest. +type StagedUpdateApprovalRequestConditionType string + +const ( + // StagedUpdateApprovalRequestConditionApproved indicates if the approval request was approved. + // Its condition status can be one of the following: + // - "True" means the request is approved. + // - "False" means the request not approved. + StagedUpdateApprovalRequestConditionApproved StagedUpdateApprovalRequestConditionType = "Approved" + + // StagedUpdateApprovalRequestConditionApprovalAccepted indicates whether the approval request is accepted by the update process. + // Its condition status can be one of the following: + // - "True" means the approval request is accepted. + // - "False" means the approval request is not accepted. + // - "Unknown" means it is not approved yet. + StagedUpdateApprovalRequestConditionApprovalAccepted StagedUpdateApprovalRequestConditionType = "ApprovalAccepted" +) + +// StagedUpdateApprovalRequestList contains a list of StagedUpdateApprovalRequest. +// +kubebuilder:resource:scope="Namespaced" +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type StagedUpdateApprovalRequestList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []StagedUpdateApprovalRequest `json:"items"` +} + +func init() { + SchemeBuilder.Register( + &StagedUpdateRun{}, &StagedUpdateRunList{}, &StagedUpdateStrategy{}, &StagedUpdateStrategyList{}, &StagedUpdateApprovalRequest{}, &StagedUpdateApprovalRequestList{}, + ) +} diff --git a/apis/placement/v1alpha1/stagerollout_types.go b/apis/placement/v1alpha1/stagerollout_types.go deleted file mode 100644 index 356406c15..000000000 --- a/apis/placement/v1alpha1/stagerollout_types.go +++ /dev/null @@ -1,344 +0,0 @@ -/* -Copyright (c) Microsoft Corporation. -Licensed under the MIT license. -*/ - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +genclient -// +genclient:namespaced -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:resource:scope="Namespaced",categories={fleet,fleet-placement} -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// StagedRolloutRun defines a stage by stage rollout run that is applied to -// the clusters that the corresponding ClusterResourcePlacement selects. -type StagedRolloutRun struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // The desired state of StagedRolloutRun. - // +required - Spec StagedRolloutRunSpec `json:"spec"` - - // The observed status of StagedRolloutRun. - // +optional - Status StagedRolloutRunStatus `json:"status,omitempty"` -} - -// StagedRolloutRunSpec defines the desired the rollout strategy and the snapshot indices -// on which the rollout is based. It removes the resources from the clusters that are -// unselected clusters after all the stages complete. -type StagedRolloutRunSpec struct { - // The name of the CRP that this rollout is applied to. - // There can be multiple active rollouts for each ClusterResourcePlacement but - // it's up to the devOps to make sure they don't conflict with each other. - // +required - ParentCRPName string `json:"parentCRPName"` - - // The resource snapshot index that this rollout is based on. - // +required - ResourceSnapShotIndex string `json:"resourceSnapShotIndex"` - - // The override snapshot index that this rollout is based on. - // +required - OverrideSnapShotIndex string `json:"overrideSnapShotIndex"` - - // The name of the rollout strategy that specifies the stages and the sequence in which - // the application will be updated on the member clusters. - // +required - StagedRolloutStrategy string `json:"stagedRolloutStrategy"` -} - -// +genclient -// +genclient:nonNamespaced -// +kubebuilder:object:root=true -// +kubebuilder:resource:scope="Cluster",categories={fleet,fleet-placement} -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// StagedRolloutStrategy defines a reusable strategy that specifies the stages and the sequence -// in which the application will be updated on the member clusters. -type StagedRolloutStrategy struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // The desired state of StagedRolloutStrategy. - // +required - Spec StagedRolloutStrategySpec `json:"spec"` - - // The observed status of StagedRolloutStrategy. - // +optional - Status StagedRolloutStrategyStatus `json:"status,omitempty"` -} - -// StagedRolloutStrategySpec defines the desired the rollout strategy. -type StagedRolloutStrategySpec struct { - // Stage rollout configurations for each rollout stage. - // +required - Stages []StagedRolloutConfig `json:"stages"` - - // TODO: Add alerting configuration. -} - -// StagedRolloutConfig describes a single rollout stage group configuration. -// The clusters in each stage are updated sequentially for now. -// We will stop the rollout if any of the updates fail. -type StagedRolloutConfig struct { - // The name of the stage. This MUST be unique within the same StagedRolloutRun. - // +required - Name string `json:"name"` - - // LabelSelector is a label query over all the joined member clusters. Clusters matching the query are selected. - // We don't check if there are overlap between clusters selected by different stages. - LabelSelector *metav1.LabelSelector `json:"labelSelector"` - - // The label key used to sort the selected clusters. - // The clusters within the stage are updated sequentially following the rule below: - // - primary: Ascending order based on the value of the label key, interpreted as integers if present. - // - secondary: Ascending order based on the name of the cluster if the label key is absent. - // +optional - SortingLabelKey *string `json:"sortingLabelKey,omitempty"` - - // The collection of tasks that each stage needs to complete successfully before moving to the next stage. - // Each task is executed in parallel. - // +optional - PostRolloutTasks []PostRolloutTask `json:"postRolloutTasks,omitempty"` -} - -// PostRolloutTask is the collection of post rollout tasks that ALL need to be completed before we can move to the next stage. -type PostRolloutTask struct { - // The type of the post rollout task. - Type PostRolloutTaskType `json:"type"` - - // The name of the RolloutApprovalRequest we need to create to get the approval for the next stage. - // We will generate a name with a prefix of the StagedRolloutRun name if it's empty. - // +optional - ApprovalRequestName string `json:"approvalTask,omitempty"` - - // The time to wait after all the clusters in the current stage complete the update before we move to the next stage. - // +optional - WaitTime *metav1.Duration `json:"waitTime,omitempty"` -} - -// StagedRolloutStrategyStatus defines the observed state of the StagedRolloutStrategy. -type StagedRolloutStrategyStatus struct { - // ComputedStages list the name of the clusters in each stage. - // The clusters in each stage are ordered following its order to be rolled out. - // +required - ComputedStages [][]string `json:"currentStages"` -} - -// StagedRolloutRunStatus defines the observed state of the StagedRolloutRun. -type StagedRolloutRunStatus struct { - // CurrentStageIndex is the index of the stage that is in the middle of the rollout. - // The index starts from 0. The index is -1 if the rollout is not started. - // The index is the size of the Stages if the rollout is completed. - // +required - CurrentStageIndex int `json:"currentStageIndex"` - - // The applied strategy of the CRP that we are currently using. - // It is the same as the strategy in the CRP when we first start the rollout. - // We will NOT update the strategy in the rollout even if the appliedStrategy changes in the CRP. - // +required - CurrentAppliedStrategy string `json:"currentAppliedStrategy"` - - // The collection of clusters on which we are going to remove the application from. - // We restart the rollout if CRP changes the selected clusters during our rollout. - // Therefore, we need to keep track of the clusters that we have applied the resources to but no long is selected. - // We remove the application from these clusters after all the stages are completed and remove them from this list. - // +required - ToBeRevertedClusters []string `json:"toBeRevertedClusters"` - - // the clusters that are actively updating. It will contain the clusters that we are removing after all - // the stages are completed. It can be empty if the rollout is stopped. - // We currently only support a single cluster rollout at a time but we may support multiple clusters in the future. - // +optional - CurrentUpdatingClusters []string `json:"currentUpdatingClusters,omitempty"` - - // The status of the post rollout tasks that are associated with current stage. - // +optional - CurrentPostRolloutTaskStatus []PostRolloutTaskStatus `json:"currentPostRolloutTaskStatus,omitempty"` - - // +patchMergeKey=type - // +patchStrategy=merge - // +listType=map - // +listMapKey=type - // - // Conditions is an array of current observed conditions for StagedRolloutRun. - // Known conditions are "RollingOut". - // +optional - Conditions []metav1.Condition `json:"conditions"` -} - -type PostRolloutTaskStatus struct { - // The type of the post rollout task. - // +required - Type PostRolloutTaskType `json:"type"` - - // +patchMergeKey=type - // +patchStrategy=merge - // +listType=map - // +listMapKey=type - // - // Conditions is an array of current observed conditions for the specific type of post rollout task. - // Known conditions are "ApprovalRequestCreated", "WaitTimeElapsed", and "ApprovalRequestApproved". - // +optional - Conditions []metav1.Condition `json:"conditions"` -} - -// PostRolloutTaskType identifies a specific type of the PostRolloutTask. -// +enum -type PostRolloutTaskType string - -const ( - // PostRolloutTaskSTypeTimedWait indicates the post rollout task is a timed wait. - PostRolloutTaskSTypeTimedWait PostRolloutTaskType = "TimedWait" - - // PostRolloutTaskSTypeManualApproval indicates the post rollout task is a manual approval. - PostRolloutTaskSTypeManualApproval PostRolloutTaskType = "ManualApproval" -) - -// PostRolloutTaskConditionType identifies a specific condition of the PostRolloutTask. -// +enum -type PostRolloutTaskConditionType string - -const ( - // PostRolloutTaskConditionApprovalRequestCreated indicates whether the approval request is created. - // Its condition status can be one of the following: - // - "True" means the approval request is created. - // - "False" means the approval request is not created. - PostRolloutTaskConditionApprovalRequestCreated PostRolloutTaskConditionType = "ApprovalRequestCreated" - - // PostRolloutTaskConditionApprovalRequestApproved indicates whether the approval request is approved. - // Its condition status can be one of the following: - // - "True" means the approval request is approved. - // - "False" means the approval request is not approved. - PostRolloutTaskConditionApprovalRequestApproved PostRolloutTaskConditionType = "ApprovalRequestApproved" - - // PostRolloutTaskConditionApprovalWaitTimeElapsed indicates whether the wait time after each stage is elapsed. - // We will fill the message of the condition of the remaining wait time if the status is "False". - // Its condition status can be one of the following: - // - "True" means the wait time is elapsed. - // - "False" means the wait time is not elapsed. - PostRolloutTaskConditionApprovalWaitTimeElapsed PostRolloutTaskConditionType = "WaitTimeElapsed" -) - -// StageRolloutConditionType identifies a specific condition of the StagedRolloutRun. -// +enum -type StageRolloutConditionType string - -const ( - // StageRolloutConditionRollingOut indicates whether the stage is rolling out normally. - // Its condition status can be one of the following: - // - "True" means the stage is rolling out. - // - "False" means the stage rolling out is not progressing. - // - "Unknown" means it is unknown. - StageRolloutConditionRollingOut StageRolloutConditionType = "RollingOut" - - // TODO: add alerting condition -) - -// StageRolloutList contains a list of StagedRolloutRun. -// +kubebuilder:resource:scope="Cluster" -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type StageRolloutList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []StagedRolloutRun `json:"items"` -} - -// +genclient -// +genclient:nonNamespaced -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:resource:scope="Namespaced",categories={fleet,fleet-placement} -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// RolloutApprovalRequest defines a stage by stage rollout policy that is applied to the ClusterResourcePlacement of the same name. -// Each snapshot MUST have the following labels: -// - `ParentStageRollout` which points to its owner stage rollout. -// - `TargetStage` which is the name of the stage that this approval request is for. -// - `IsLatestRolloutApproval` which indicates whether this approval request is the latest one related to this rollout -type RolloutApprovalRequest struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // The desired state of StagedRolloutRun. - // +required - Spec RolloutApprovalRequestSpec `json:"spec"` - - // The desired state of StagedRolloutRun. - // +required - Status RolloutApprovalRequestStatus `json:"status"` -} - -// RolloutApprovalRequestSpec defines the desired the rollout approval request. -// The entire spec is immutable. -type RolloutApprovalRequestSpec struct { - // The name of the rollout stage that this approval request is for. - // +required - ParentStageRollout string `json:"parentStageRollout"` - - // The name of the rollout stage that this approval request is for. - // +required - TargetStage string `json:"targetStage"` - - // The resource snapshot index that this approval request is based on. - // +required - ResourceSnapshotIndex string `json:"resourceSnapshotIndex"` - - // The override snapshot index that this approval request is based on. - // +required - OverrideSnapShotIndex string `json:"overrideSnapShotIndex"` -} - -// RolloutApprovalRequestStatus defines the observed state of the RolloutApprovalRequest. -type RolloutApprovalRequestStatus struct { - // +patchMergeKey=type - // +patchStrategy=merge - // +listType=map - // +listMapKey=type - // - // Conditions is an array of current observed conditions for the specific type of post rollout task. - // Known conditions are "Approved", and "ApprovalAccepted". - // +optional - Conditions []metav1.Condition `json:"conditions"` -} - -// RolloutApprovalConditionType identifies a specific condition of the RolloutApprovalRequest. -type RolloutApprovalConditionType string - -const ( - // RolloutApprovalConditionApproved indicates the request was approved. - // Its condition status can be one of the following: - // - "True" means the request is approved. - // - "False" means the request not approved. - RolloutApprovalConditionApproved RolloutApprovalConditionType = "Approved" - - // RolloutApprovalConditionApprovalAccepted indicates whether the approval request is accepted by the rollout process. - // Its condition status can be one of the following: - // - "True" means the approval request is accepted. - // - "False" means the approval request is not accepted. - // - "Unknown" means it is not approved yet. - RolloutApprovalConditionApprovalAccepted RolloutApprovalConditionType = "ApprovalAccepted" -) - -// RolloutApprovalRequestList contains a list of RolloutApprovalRequest. -// +kubebuilder:resource:scope="Namespaced" -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type RolloutApprovalRequestList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []RolloutApprovalRequest `json:"items"` -} - -func init() { - SchemeBuilder.Register( - &StagedRolloutRun{}, &StageRolloutList{}, &RolloutApprovalRequest{}, &RolloutApprovalRequestList{}, - ) -} diff --git a/apis/placement/v1alpha1/zz_generated.deepcopy.go b/apis/placement/v1alpha1/zz_generated.deepcopy.go index de64790d8..1b6a03803 100644 --- a/apis/placement/v1alpha1/zz_generated.deepcopy.go +++ b/apis/placement/v1alpha1/zz_generated.deepcopy.go @@ -10,9 +10,10 @@ Licensed under the MIT license. package v1alpha1 import ( - "go.goms.io/fleet/apis/placement/v1beta1" - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + + "go.goms.io/fleet/apis/placement/v1beta1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -245,27 +246,23 @@ func (in *OverrideRule) DeepCopy() *OverrideRule { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PostRolloutTask) DeepCopyInto(out *PostRolloutTask) { +func (in *PostStageTask) DeepCopyInto(out *PostStageTask) { *out = *in - if in.WaitTime != nil { - in, out := &in.WaitTime, &out.WaitTime - *out = new(v1.Duration) - **out = **in - } + out.WaitTime = in.WaitTime } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostRolloutTask. -func (in *PostRolloutTask) DeepCopy() *PostRolloutTask { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostStageTask. +func (in *PostStageTask) DeepCopy() *PostStageTask { if in == nil { return nil } - out := new(PostRolloutTask) + out := new(PostStageTask) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PostRolloutTaskStatus) DeepCopyInto(out *PostRolloutTaskStatus) { +func (in *PostStageTaskStatus) DeepCopyInto(out *PostStageTaskStatus) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions @@ -276,12 +273,12 @@ func (in *PostRolloutTaskStatus) DeepCopyInto(out *PostRolloutTaskStatus) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostRolloutTaskStatus. -func (in *PostRolloutTaskStatus) DeepCopy() *PostRolloutTaskStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostStageTaskStatus. +func (in *PostStageTaskStatus) DeepCopy() *PostStageTaskStatus { if in == nil { return nil } - out := new(PostRolloutTaskStatus) + out := new(PostStageTaskStatus) in.DeepCopyInto(out) return out } @@ -464,7 +461,7 @@ func (in *ResourceSelector) DeepCopy() *ResourceSelector { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RolloutApprovalRequest) DeepCopyInto(out *RolloutApprovalRequest) { +func (in *StagedUpdateApprovalRequest) DeepCopyInto(out *StagedUpdateApprovalRequest) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -472,18 +469,18 @@ func (in *RolloutApprovalRequest) DeepCopyInto(out *RolloutApprovalRequest) { in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RolloutApprovalRequest. -func (in *RolloutApprovalRequest) DeepCopy() *RolloutApprovalRequest { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateApprovalRequest. +func (in *StagedUpdateApprovalRequest) DeepCopy() *StagedUpdateApprovalRequest { if in == nil { return nil } - out := new(RolloutApprovalRequest) + out := new(StagedUpdateApprovalRequest) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RolloutApprovalRequest) DeepCopyObject() runtime.Object { +func (in *StagedUpdateApprovalRequest) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -491,31 +488,31 @@ func (in *RolloutApprovalRequest) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RolloutApprovalRequestList) DeepCopyInto(out *RolloutApprovalRequestList) { +func (in *StagedUpdateApprovalRequestList) DeepCopyInto(out *StagedUpdateApprovalRequestList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]RolloutApprovalRequest, len(*in)) + *out = make([]StagedUpdateApprovalRequest, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RolloutApprovalRequestList. -func (in *RolloutApprovalRequestList) DeepCopy() *RolloutApprovalRequestList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateApprovalRequestList. +func (in *StagedUpdateApprovalRequestList) DeepCopy() *StagedUpdateApprovalRequestList { if in == nil { return nil } - out := new(RolloutApprovalRequestList) + out := new(StagedUpdateApprovalRequestList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RolloutApprovalRequestList) DeepCopyObject() runtime.Object { +func (in *StagedUpdateApprovalRequestList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -523,22 +520,22 @@ func (in *RolloutApprovalRequestList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RolloutApprovalRequestSpec) DeepCopyInto(out *RolloutApprovalRequestSpec) { +func (in *StagedUpdateApprovalRequestSpec) DeepCopyInto(out *StagedUpdateApprovalRequestSpec) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RolloutApprovalRequestSpec. -func (in *RolloutApprovalRequestSpec) DeepCopy() *RolloutApprovalRequestSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateApprovalRequestSpec. +func (in *StagedUpdateApprovalRequestSpec) DeepCopy() *StagedUpdateApprovalRequestSpec { if in == nil { return nil } - out := new(RolloutApprovalRequestSpec) + out := new(StagedUpdateApprovalRequestSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RolloutApprovalRequestStatus) DeepCopyInto(out *RolloutApprovalRequestStatus) { +func (in *StagedUpdateApprovalRequestStatus) DeepCopyInto(out *StagedUpdateApprovalRequestStatus) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions @@ -549,50 +546,18 @@ func (in *RolloutApprovalRequestStatus) DeepCopyInto(out *RolloutApprovalRequest } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RolloutApprovalRequestStatus. -func (in *RolloutApprovalRequestStatus) DeepCopy() *RolloutApprovalRequestStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateApprovalRequestStatus. +func (in *StagedUpdateApprovalRequestStatus) DeepCopy() *StagedUpdateApprovalRequestStatus { if in == nil { return nil } - out := new(RolloutApprovalRequestStatus) + out := new(StagedUpdateApprovalRequestStatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StageRolloutList) DeepCopyInto(out *StageRolloutList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]StagedRolloutRun, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StageRolloutList. -func (in *StageRolloutList) DeepCopy() *StageRolloutList { - if in == nil { - return nil - } - out := new(StageRolloutList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *StageRolloutList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StagedRolloutConfig) DeepCopyInto(out *StagedRolloutConfig) { +func (in *StagedUpdateConfig) DeepCopyInto(out *StagedUpdateConfig) { *out = *in if in.LabelSelector != nil { in, out := &in.LabelSelector, &out.LabelSelector @@ -604,27 +569,25 @@ func (in *StagedRolloutConfig) DeepCopyInto(out *StagedRolloutConfig) { *out = new(string) **out = **in } - if in.PostRolloutTasks != nil { - in, out := &in.PostRolloutTasks, &out.PostRolloutTasks - *out = make([]PostRolloutTask, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + if in.PostStageTasks != nil { + in, out := &in.PostStageTasks, &out.PostStageTasks + *out = make([]PostStageTask, len(*in)) + copy(*out, *in) } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedRolloutConfig. -func (in *StagedRolloutConfig) DeepCopy() *StagedRolloutConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateConfig. +func (in *StagedUpdateConfig) DeepCopy() *StagedUpdateConfig { if in == nil { return nil } - out := new(StagedRolloutConfig) + out := new(StagedUpdateConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StagedRolloutRun) DeepCopyInto(out *StagedRolloutRun) { +func (in *StagedUpdateRun) DeepCopyInto(out *StagedUpdateRun) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -632,18 +595,18 @@ func (in *StagedRolloutRun) DeepCopyInto(out *StagedRolloutRun) { in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedRolloutRun. -func (in *StagedRolloutRun) DeepCopy() *StagedRolloutRun { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateRun. +func (in *StagedUpdateRun) DeepCopy() *StagedUpdateRun { if in == nil { return nil } - out := new(StagedRolloutRun) + out := new(StagedUpdateRun) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *StagedRolloutRun) DeepCopyObject() runtime.Object { +func (in *StagedUpdateRun) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -651,23 +614,67 @@ func (in *StagedRolloutRun) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StagedRolloutRunSpec) DeepCopyInto(out *StagedRolloutRunSpec) { +func (in *StagedUpdateRunList) DeepCopyInto(out *StagedUpdateRunList) { *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]StagedUpdateRun, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedRolloutRunSpec. -func (in *StagedRolloutRunSpec) DeepCopy() *StagedRolloutRunSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateRunList. +func (in *StagedUpdateRunList) DeepCopy() *StagedUpdateRunList { if in == nil { return nil } - out := new(StagedRolloutRunSpec) + out := new(StagedUpdateRunList) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *StagedUpdateRunList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StagedRolloutRunStatus) DeepCopyInto(out *StagedRolloutRunStatus) { +func (in *StagedUpdateRunSpec) DeepCopyInto(out *StagedUpdateRunSpec) { *out = *in + out.StagedUpdateStrategyRef = in.StagedUpdateStrategyRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateRunSpec. +func (in *StagedUpdateRunSpec) DeepCopy() *StagedUpdateRunSpec { + if in == nil { + return nil + } + out := new(StagedUpdateRunSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StagedUpdateRunStatus) DeepCopyInto(out *StagedUpdateRunStatus) { + *out = *in + if in.ComputedStages != nil { + in, out := &in.ComputedStages, &out.ComputedStages + *out = make([][]string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = make([]string, len(*in)) + copy(*out, *in) + } + } + } if in.ToBeRevertedClusters != nil { in, out := &in.ToBeRevertedClusters, &out.ToBeRevertedClusters *out = make([]string, len(*in)) @@ -678,9 +685,9 @@ func (in *StagedRolloutRunStatus) DeepCopyInto(out *StagedRolloutRunStatus) { *out = make([]string, len(*in)) copy(*out, *in) } - if in.CurrentPostRolloutTaskStatus != nil { - in, out := &in.CurrentPostRolloutTaskStatus, &out.CurrentPostRolloutTaskStatus - *out = make([]PostRolloutTaskStatus, len(*in)) + if in.CurrentPostStageTaskStatus != nil { + in, out := &in.CurrentPostStageTaskStatus, &out.CurrentPostStageTaskStatus + *out = make([]PostStageTaskStatus, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -694,18 +701,18 @@ func (in *StagedRolloutRunStatus) DeepCopyInto(out *StagedRolloutRunStatus) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedRolloutRunStatus. -func (in *StagedRolloutRunStatus) DeepCopy() *StagedRolloutRunStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateRunStatus. +func (in *StagedUpdateRunStatus) DeepCopy() *StagedUpdateRunStatus { if in == nil { return nil } - out := new(StagedRolloutRunStatus) + out := new(StagedUpdateRunStatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StagedRolloutStrategy) DeepCopyInto(out *StagedRolloutStrategy) { +func (in *StagedUpdateStrategy) DeepCopyInto(out *StagedUpdateStrategy) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -713,18 +720,50 @@ func (in *StagedRolloutStrategy) DeepCopyInto(out *StagedRolloutStrategy) { in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedRolloutStrategy. -func (in *StagedRolloutStrategy) DeepCopy() *StagedRolloutStrategy { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateStrategy. +func (in *StagedUpdateStrategy) DeepCopy() *StagedUpdateStrategy { + if in == nil { + return nil + } + out := new(StagedUpdateStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *StagedUpdateStrategy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StagedUpdateStrategyList) DeepCopyInto(out *StagedUpdateStrategyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]StagedUpdateStrategy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateStrategyList. +func (in *StagedUpdateStrategyList) DeepCopy() *StagedUpdateStrategyList { if in == nil { return nil } - out := new(StagedRolloutStrategy) + out := new(StagedUpdateStrategyList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *StagedRolloutStrategy) DeepCopyObject() runtime.Object { +func (in *StagedUpdateStrategyList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -732,29 +771,44 @@ func (in *StagedRolloutStrategy) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StagedRolloutStrategySpec) DeepCopyInto(out *StagedRolloutStrategySpec) { +func (in *StagedUpdateStrategyReference) DeepCopyInto(out *StagedUpdateStrategyReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateStrategyReference. +func (in *StagedUpdateStrategyReference) DeepCopy() *StagedUpdateStrategyReference { + if in == nil { + return nil + } + out := new(StagedUpdateStrategyReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StagedUpdateStrategySpec) DeepCopyInto(out *StagedUpdateStrategySpec) { *out = *in if in.Stages != nil { in, out := &in.Stages, &out.Stages - *out = make([]StagedRolloutConfig, len(*in)) + *out = make([]StagedUpdateConfig, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedRolloutStrategySpec. -func (in *StagedRolloutStrategySpec) DeepCopy() *StagedRolloutStrategySpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateStrategySpec. +func (in *StagedUpdateStrategySpec) DeepCopy() *StagedUpdateStrategySpec { if in == nil { return nil } - out := new(StagedRolloutStrategySpec) + out := new(StagedUpdateStrategySpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StagedRolloutStrategyStatus) DeepCopyInto(out *StagedRolloutStrategyStatus) { +func (in *StagedUpdateStrategyStatus) DeepCopyInto(out *StagedUpdateStrategyStatus) { *out = *in if in.ComputedStages != nil { in, out := &in.ComputedStages, &out.ComputedStages @@ -769,12 +823,12 @@ func (in *StagedRolloutStrategyStatus) DeepCopyInto(out *StagedRolloutStrategySt } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedRolloutStrategyStatus. -func (in *StagedRolloutStrategyStatus) DeepCopy() *StagedRolloutStrategyStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StagedUpdateStrategyStatus. +func (in *StagedUpdateStrategyStatus) DeepCopy() *StagedUpdateStrategyStatus { if in == nil { return nil } - out := new(StagedRolloutStrategyStatus) + out := new(StagedUpdateStrategyStatus) in.DeepCopyInto(out) return out } diff --git a/apis/placement/v1beta1/clusterresourceplacement_types.go b/apis/placement/v1beta1/clusterresourceplacement_types.go index 6b45cc346..3dcee921d 100644 --- a/apis/placement/v1beta1/clusterresourceplacement_types.go +++ b/apis/placement/v1beta1/clusterresourceplacement_types.go @@ -410,10 +410,11 @@ const ( // RolloutStrategy describes how to roll out a new change in selected resources to target clusters. type RolloutStrategy struct { - // Type of rollout. The only supported type is "RollingUpdate". Default is "RollingUpdate". - // +optional - // +kubebuilder:validation:Enum=RollingUpdate + // Type of rollout. The only supported types are "RollingUpdate" and "StagedUpdate. + // Default is "RollingUpdate". // +kubebuilder:default=RollingUpdate + // +kubebuilder:validation:Enum=RollingUpdate;External + // +optional Type RolloutStrategyType `json:"type,omitempty"` // Rolling update config params. Present only if RolloutStrategyType = RollingUpdate. @@ -489,9 +490,9 @@ const ( // i.e. gradually create the new one while replace the old ones. RollingUpdateRolloutStrategyType RolloutStrategyType = "RollingUpdate" - // StageRollingUpdateRolloutStrategyType replaces the old placed resource stage by stage - // i.e. update the resources in the testing clusters first, then the staging clusters, and finally the production clusters. - StageRollingUpdateRolloutStrategyType RolloutStrategyType = "StageRollingUpdate" + // ExternalRolloutStrategyType means there is an external rollout controller that will + // handle the rollout of the resources. + ExternalRolloutStrategyType RolloutStrategyType = "External" ) // RollingUpdateConfig contains the config to control the desired behavior of rolling update. diff --git a/apis/placement/v1beta1/zz_generated.deepcopy.go b/apis/placement/v1beta1/zz_generated.deepcopy.go index dcfc83ab5..4c48828e9 100644 --- a/apis/placement/v1beta1/zz_generated.deepcopy.go +++ b/apis/placement/v1beta1/zz_generated.deepcopy.go @@ -10,7 +10,7 @@ Licensed under the MIT license. package v1beta1 import ( - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 0d4061551..ac4844274 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -11,7 +11,7 @@ package v1alpha1 import ( corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/config/crd/bases/placement.kubernetes-fleet.io_clusterresourceplacements.yaml b/config/crd/bases/placement.kubernetes-fleet.io_clusterresourceplacements.yaml index fa2b10f7c..9100d36e7 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_clusterresourceplacements.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_clusterresourceplacements.yaml @@ -1845,10 +1845,13 @@ spec: type: object type: default: RollingUpdate - description: Type of rollout. The only supported type is "RollingUpdate". + description: |- + Type of rollout. The only supported types are "RollingUpdate" and "StagedUpdate. Default is "RollingUpdate". + "StagedUpdate" is an alpha-level feature. enum: - RollingUpdate + - External type: string type: object required: diff --git a/config/crd/bases/placement.kubernetes-fleet.io_rolloutapprovalrequests.yaml b/config/crd/bases/placement.kubernetes-fleet.io_rolloutapprovalrequests.yaml index 1593905f1..0e1ded6a1 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_rolloutapprovalrequests.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_rolloutapprovalrequests.yaml @@ -45,7 +45,7 @@ spec: metadata: type: object spec: - description: The desired state of StagedRolloutRun. + description: The desired state of StagedUpdateRun. properties: overrideSnapShotIndex: description: The override snapshot index that this approval request @@ -70,7 +70,7 @@ spec: - targetStage type: object status: - description: The desired state of StagedRolloutRun. + description: The desired state of StagedUpdateRun. properties: conditions: description: |- diff --git a/config/crd/bases/placement.kubernetes-fleet.io_stagedrolloutstrategies.yaml b/config/crd/bases/placement.kubernetes-fleet.io_stagedrolloutstrategies.yaml index 3239821d6..c9a2c0fab 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_stagedrolloutstrategies.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_stagedrolloutstrategies.yaml @@ -15,7 +15,7 @@ spec: listKind: StagedRolloutStrategyList plural: stagedrolloutstrategies singular: stagedrolloutstrategy - scope: Cluster + scope: Namespaced versions: - name: v1alpha1 schema: @@ -102,7 +102,7 @@ spec: x-kubernetes-map-type: atomic name: description: The name of the stage. This MUST be unique within - the same StagedRolloutRun. + the same StagedUpdateRun. type: string postRolloutTasks: description: |- @@ -116,15 +116,20 @@ spec: approvalTask: description: |- The name of the RolloutApprovalRequest we need to create to get the approval for the next stage. - We will generate a name with a prefix of the StagedRolloutRun name if it's empty. + We will generate a name with a prefix of the StagedUpdateRun name if it's empty. type: string type: description: The type of the post rollout task. + enum: + - TimedWait + - ManualApproval type: string waitTime: + default: 1h description: The time to wait after all the clusters in the current stage complete the update before we move to the next stage. + pattern: ^0|([0-9]+(\.[0-9]+)?(s|m|h))+$ type: string required: - type diff --git a/config/crd/bases/placement.kubernetes-fleet.io_stagedupdateapprovalrequests.yaml b/config/crd/bases/placement.kubernetes-fleet.io_stagedupdateapprovalrequests.yaml new file mode 100644 index 000000000..d74f24692 --- /dev/null +++ b/config/crd/bases/placement.kubernetes-fleet.io_stagedupdateapprovalrequests.yaml @@ -0,0 +1,158 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: stagedupdateapprovalrequests.placement.kubernetes-fleet.io +spec: + group: placement.kubernetes-fleet.io + names: + categories: + - fleet + - fleet-placement + kind: StagedUpdateApprovalRequest + listKind: StagedUpdateApprovalRequestList + plural: stagedupdateapprovalrequests + singular: stagedupdateapprovalrequest + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + StagedUpdateApprovalRequest defines a stage by stage update policy that is applied to the ClusterResourcePlacement of the same name. + Each snapshot MUST have the following labels: + - `ParentStagedUpdateRun` which points to its owner stage update. + - `TargetStage` which is the name of the stage that this approval request is for. + - `IsLatestUpdateRunApproval` which indicates whether this approval request is the latest one related to this update run. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: The desired state of StagedUpdateApprovalRequest. + properties: + overrideSnapShotIndex: + description: The override snapshot index that this approval request + is based on. + type: string + parentStageRollout: + description: The name of the staged update run that this approval + request is for. + type: string + resourceSnapshotIndex: + description: The resource snapshot index that this approval request + is based on. + type: string + targetStage: + description: The index of the update stage that this approval request + is for. + type: integer + required: + - overrideSnapShotIndex + - parentStageRollout + - resourceSnapshotIndex + - targetStage + type: object + status: + description: The desired state of StagedUpdateApprovalRequest. + properties: + conditions: + description: |- + Conditions is an array of current observed conditions for the specific type of post update task. + Known conditions are "Approved", and "ApprovalAccepted". + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + - status + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/placement.kubernetes-fleet.io_stagedupdateruns.yaml b/config/crd/bases/placement.kubernetes-fleet.io_stagedupdateruns.yaml new file mode 100644 index 000000000..994b70647 --- /dev/null +++ b/config/crd/bases/placement.kubernetes-fleet.io_stagedupdateruns.yaml @@ -0,0 +1,304 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: stagedupdateruns.placement.kubernetes-fleet.io +spec: + group: placement.kubernetes-fleet.io + names: + categories: + - fleet + - fleet-placement + kind: StagedUpdateRun + listKind: StagedUpdateRunList + plural: stagedupdateruns + singular: stagedupdaterun + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + StagedUpdateRun defines a stage by stage update run that applies the selected resources by the + corresponding ClusterResourcePlacement to its selected clusters. We remove the resources from the clusters that are + unselected after all the stages explicitly defined in the updateStrategy complete. + Each StagedUpdateRun object corresponds to a single "release" of a certain version of the resources. + The release is abandoned if the StagedUpdateRun object is deleted or the scheduling decision changes. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: The desired state of StagedUpdateRun. + properties: + overrideSnapShotIndex: + description: The override snapshot index that this update run is based + on. + type: string + parentCRPName: + description: |- + The name of the CRP that this update run is applied to. + There can be multiple active update runs for each ClusterResourcePlacement but + it's up to the devOps to make sure they don't conflict with each other. + type: string + resourceSnapShotIndex: + description: The resource snapshot index that this update run is based + on. + type: string + stagedRolloutStrategyRef: + description: |- + The reference to the update strategy that specifies the stages and the sequence + in which the application will be updated on the member clusters. + properties: + name: + description: Name is the name of the referenced StagedUpdateStrategy. + type: string + namespace: + description: Namespace is the namespace where the referenced StagedUpdateStrategy + is located. + type: string + required: + - name + type: object + required: + - overrideSnapShotIndex + - parentCRPName + - resourceSnapShotIndex + - stagedRolloutStrategyRef + type: object + status: + description: The observed status of StagedUpdateRun. + properties: + conditions: + description: |- + Conditions is an array of current observed conditions for StagedUpdateRun. + Known conditions are "RollingOut". + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + currentAppliedStrategy: + description: |- + The applied strategy of the CRP that we are currently using. + It is the same as the strategy in the CRP when we first start the update. + We will NOT update the strategy in the update even if the appliedStrategy changes in the CRP. + type: string + currentPostStageTaskStatus: + description: The status of the post update tasks that are associated + with current stage. + items: + properties: + conditions: + description: |- + Conditions is an array of current observed conditions for the specific type of post update task. + Known conditions are "ApprovalRequestCreated", "WaitTimeElapsed", and "ApprovalRequestApproved". + items: + description: "Condition contains details for one aspect of + the current state of this API Resource.\n---\nThis struct + is intended for direct use as an array at the field path + .status.conditions. For example,\n\n\n\ttype FooStatus + struct{\n\t // Represents the observations of a foo's + current state.\n\t // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // + +listType=map\n\t // +listMapKey=type\n\t Conditions + []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" + patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: + description: The type of the post update task. + type: string + required: + - type + type: object + type: array + currentStageIndex: + description: |- + CurrentStageIndex is the index of the stage that is in the middle of the update. + The index starts from 0. The index is -1 if the update is not started. + The index is the size of the Stages if the update is completed. + type: integer + currentStages: + description: |- + ComputedStages list the name of the clusters in each stage when the updateRun started. + The clusters in each stage are ordered following its order to be rolled out. + The updateRun will stop if the cluster stages change during the update. + items: + items: + type: string + type: array + type: array + currentUpdatingClusters: + description: |- + the clusters that are actively updating. It will contain the clusters that we are removing after all + the stages are completed. It can be empty if the update is stopped. + We currently only support a single cluster update at a time but we may support multiple clusters in the future. + items: + type: string + type: array + toBeRevertedClusters: + description: |- + The collection of clusters on which we are going to remove the application from. + We restart the update if CRP changes the selected clusters during our update. + Therefore, we need to keep track of the clusters that we have applied the resources to but no long is selected. + We remove the application from these clusters after all the stages are completed and remove them from this list. + items: + type: string + type: array + required: + - currentAppliedStrategy + - currentStageIndex + - currentStages + - toBeRevertedClusters + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/placement.kubernetes-fleet.io_stagedupdatestrategies.yaml b/config/crd/bases/placement.kubernetes-fleet.io_stagedupdatestrategies.yaml new file mode 100644 index 000000000..dfb55862e --- /dev/null +++ b/config/crd/bases/placement.kubernetes-fleet.io_stagedupdatestrategies.yaml @@ -0,0 +1,174 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: stagedupdatestrategies.placement.kubernetes-fleet.io +spec: + group: placement.kubernetes-fleet.io + names: + categories: + - fleet + - fleet-placement + kind: StagedUpdateStrategy + listKind: StagedUpdateStrategyList + plural: stagedupdatestrategies + singular: stagedupdatestrategy + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + StagedUpdateStrategy defines a reusable strategy that specifies the stages and the sequence + in which the application will be updated on the member clusters. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: The desired state of StagedUpdateStrategy. + properties: + stages: + description: Staged update configurations for each update stage. + items: + description: |- + StagedUpdateConfig describes a single update stage group configuration. + The clusters in each stage are updated sequentially for now. + We will stop the update if any of the updates fail. + properties: + labelSelector: + description: |- + LabelSelector is a label query over all the joined member clusters. Clusters matching the query are selected. + We don't check if there are overlap between clusters selected by different stages. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + name: + description: The name of the stage. This MUST be unique within + the same StagedUpdateRun. + type: string + postRolloutTasks: + description: |- + The collection of tasks that each stage needs to complete successfully before moving to the next stage. + Each task is executed in parallel. + items: + description: PostStageTask is the collection of post stage + tasks that ALL need to be completed before we can move to + the next stage. + properties: + approvalTask: + description: |- + The name of the StagedUpdateApprovalRequest we need to create to get the approval for the next stage. + We will generate a name with a prefix of the StagedUpdateRun name if it's empty. + type: string + type: + description: The type of the post stage task. + enum: + - TimedWait + - ManualApproval + type: string + waitTime: + default: 1h + description: The time to wait after all the clusters in + the current stage complete the update before we move + to the next stage. + pattern: ^0|([0-9]+(\.[0-9]+)?(s|m|h))+$ + type: string + required: + - type + type: object + type: array + sortingLabelKey: + description: |- + The label key used to sort the selected clusters. + The clusters within the stage are updated sequentially following the rule below: + - primary: Ascending order based on the value of the label key, interpreted as integers if present. + - secondary: Ascending order based on the name of the cluster if the label key is absent. + type: string + required: + - labelSelector + - name + type: object + type: array + required: + - stages + type: object + status: + description: The observed status of StagedUpdateStrategy. + properties: + currentStages: + description: |- + ComputedStages list the name of the clusters in each stage. + The clusters in each stage are ordered following its order to be rolled out. + items: + items: + type: string + type: array + type: array + required: + - currentStages + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/pkg/utils/validator/clusterresourceplacement.go b/pkg/utils/validator/clusterresourceplacement.go index 5ba0abefe..55d1f0b39 100644 --- a/pkg/utils/validator/clusterresourceplacement.go +++ b/pkg/utils/validator/clusterresourceplacement.go @@ -371,11 +371,15 @@ func validateLabelSelector(labelSelector *metav1.LabelSelector, parent string) e func validateRolloutStrategy(rolloutStrategy placementv1beta1.RolloutStrategy) error { allErr := make([]error, 0) - if rolloutStrategy.Type != "" && rolloutStrategy.Type != placementv1beta1.RollingUpdateRolloutStrategyType { + if rolloutStrategy.Type != "" && rolloutStrategy.Type != placementv1beta1.RollingUpdateRolloutStrategyType && + rolloutStrategy.Type != placementv1beta1.ExternalRolloutStrategyType { allErr = append(allErr, fmt.Errorf("unsupported rollout strategy type `%s`", rolloutStrategy.Type)) } if rolloutStrategy.RollingUpdate != nil { + if rolloutStrategy.Type == placementv1beta1.ExternalRolloutStrategyType { + allErr = append(allErr, fmt.Errorf("rollingUpdateConifg is not valid for ExternalRollout strategy type")) + } if rolloutStrategy.RollingUpdate.UnavailablePeriodSeconds != nil && *rolloutStrategy.RollingUpdate.UnavailablePeriodSeconds < 0 { allErr = append(allErr, fmt.Errorf("unavailablePeriodSeconds must be greater than or equal to 0, got %d", *rolloutStrategy.RollingUpdate.UnavailablePeriodSeconds)) } diff --git a/pkg/utils/validator/clusterresourceplacement_test.go b/pkg/utils/validator/clusterresourceplacement_test.go index 7a4deeacc..0ad3a9128 100644 --- a/pkg/utils/validator/clusterresourceplacement_test.go +++ b/pkg/utils/validator/clusterresourceplacement_test.go @@ -22,7 +22,6 @@ import ( ) var ( - unavailablePeriodSeconds = -10 positiveNumberOfClusters int32 = 1 negativeNumberOfClusters int32 = -1 resourceSelector = placementv1beta1.ClusterResourceSelector{ @@ -341,6 +340,8 @@ func TestValidateClusterResourcePlacement(t *testing.T) { } func TestValidateClusterResourcePlacement_RolloutStrategy(t *testing.T) { + var unavailablePeriodSeconds = -10 + tests := map[string]struct { strategy placementv1beta1.RolloutStrategy wantErr bool @@ -349,6 +350,22 @@ func TestValidateClusterResourcePlacement_RolloutStrategy(t *testing.T) { "empty rollout strategy": { wantErr: false, }, + "valid rollout strategy - External": { + strategy: placementv1beta1.RolloutStrategy{ + Type: placementv1beta1.ExternalRolloutStrategyType, + }, + wantErr: false, + }, + "invalid rollout strategy - External strategy with rollingUpdate config": { + strategy: placementv1beta1.RolloutStrategy{ + Type: placementv1beta1.ExternalRolloutStrategyType, + RollingUpdate: &placementv1beta1.RollingUpdateConfig{ + UnavailablePeriodSeconds: &unavailablePeriodSeconds, + }, + }, + wantErr: true, + wantErrMsg: "rollingUpdateConifg is not valid for ExternalRollout strategy type", + }, "invalid rollout strategy": { strategy: placementv1beta1.RolloutStrategy{ Type: "random type", diff --git a/test/apis/v1alpha1/zz_generated.deepcopy.go b/test/apis/v1alpha1/zz_generated.deepcopy.go index 0b5d2e30b..ef7e4433a 100644 --- a/test/apis/v1alpha1/zz_generated.deepcopy.go +++ b/test/apis/v1alpha1/zz_generated.deepcopy.go @@ -10,7 +10,7 @@ Licensed under the MIT license. package v1alpha1 import ( - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/test/e2e/README.md b/test/e2e/README.md index 8e8be2484..e33a0f97a 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -28,8 +28,8 @@ test suites, follow the steps below: 3. Run the command below to start running the tests: ```sh - go test . - ``` + ginkgo -v -p . + ``` ## Access the `Kind` clusters