diff --git a/apis/placement/v1alpha1/stagedupdate_types.go b/apis/placement/v1alpha1/stagedupdate_types.go new file mode 100644 index 000000000..6c0b572e0 --- /dev/null +++ b/apis/placement/v1alpha1/stagedupdate_types.go @@ -0,0 +1,477 @@ +/* +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "go.goms.io/fleet/apis/placement/v1beta1" +) + +// +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 represents a stage by stage update process that applies selected resources to specified clusters. +// Resources from unselected clusters are removed after all stages in the update strategy are completed. +// Each StagedUpdateRun object corresponds to a single release of a specific resource version. +// The release is abandoned if the StagedUpdateRun object is deleted or the scheduling decision changes. +// The name of the StagedUpdateRun must conform to RFC 1123. +type StagedUpdateRun struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // The desired state of StagedUpdateRun. The spec is immutable. + // +kubebuilder:validation:Required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="The spec field is immutable" + Spec StagedUpdateRunSpec `json:"spec"` + + // The observed status of StagedUpdateRun. + // +kubebuilder:validation:Optional + Status StagedUpdateRunStatus `json:"status,omitempty"` +} + +// StagedUpdateRunSpec defines the desired rollout strategy and the snapshot indices of the resources to be updated. +// It specifies a stage-by-stage update process across selected clusters for the given ResourcePlacement object. +type StagedUpdateRunSpec struct { + // A reference to the placement that this update run is applied to. + // There can be multiple active update runs for each placement, but + // it's up to the DevOps team to ensure they don't conflict with each other. + // +kubebuilder:validation:Required + PlacementRef PlacementReference `json:"placementRef"` + + // The resource snapshot index of the selected resources to be updated across clusters. + // The index represents a group of resource snapshots that includes all the resources a ResourcePlacement selected. + // +kubebuilder:validation:Required + ResourceSnapshotIndex string `json:"resourceSnapshotIndex"` + + // The reference to the update strategy that specifies the stages and the sequence + // in which the selected resources will be updated on the member clusters. The stages + // are computed according to the referenced strategy when the update run starts + // and recorded in the status field. + // +kubebuilder:validation:Required + StagedUpdateStrategyRef v1beta1.NamespacedName `json:"stagedRolloutStrategyRef"` +} + +// PlacementReference is a reference to a placement object. +type PlacementReference struct { + // Name is the name of the referenced placement. + // +kubebuilder:validation:Required + Name string `json:"name"` +} + +// +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 selected resources will be updated on the member clusters. +type StagedUpdateStrategy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // The desired state of StagedUpdateStrategy. + // +kubebuilder:validation:Required + Spec StagedUpdateStrategySpec `json:"spec"` +} + +// StagedUpdateStrategySpec defines the desired state of the StagedUpdateStrategy. +type StagedUpdateStrategySpec struct { + // Stage specifies the configuration for each update stage. + // +kubebuilder:validation:MaxItems=31 + // +kubebuilder:validation:Required + Stages []StageConfig `json:"stages"` +} + +// 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"` +} + +// StageConfig describes a single update stage. +// The clusters in each stage are updated sequentially. +// The update stops if any of the updates fail. +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:Required + Name string `json:"name"` + + // LabelSelector is a label query over all the joined member clusters. Clusters matching the query are selected + // for this stage. There cannot be overlapping clusters between stages when the stagedUpdateRun is created. + // If the label selector is absent, the stage includes all the selected clusters. + // +kubebuilder:validation:Optional + LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"` + + // 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 or the label value is the same. + // +kubebuilder:validation: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 and there cannot be more than one task of the same type. + // +kubebuilder:validation:MaxItems=2 + // +kubebuilder:validation:Optional + AfterStageTasks []AfterStageTask `json:"afterStageTasks,omitempty"` +} + +// AfterStageTask is the collection of post-stage tasks that ALL need to be completed before moving to the next stage. +type AfterStageTask struct { + // The type of the after-stage task. + // +kubebuilder:validation:Enum=TimedWait;Approval + // +kubebuilder:validation:Required + Type AfterStageTaskType `json:"type"` + + // The time to wait after all the clusters in the current stage complete the update before moving to the next stage. + // +kubebuilder:default="1h" + // +kubebuilder:validation:Pattern="^0|([0-9]+(\\.[0-9]+)?(s|m|h))+$" + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Optional + WaitTime metav1.Duration `json:"waitTime,omitempty"` +} + +// StagedUpdateRunStatus defines the observed state of the StagedUpdateRun. +type StagedUpdateRunStatus struct { + // PolicySnapShotIndexUsed records the policy snapshot index of the ClusterResourcePlacement (CRP) that + // the update run is based on. The index represents the latest policy snapshot at the start of the update run. + // If a newer policy snapshot is detected after the run starts, the staged update run is abandoned. + // The scheduler must identify all clusters that meet the current policy before the update run begins. + // All clusters involved in the update run are selected from the list of clusters scheduled by the CRP according + // to the current policy. + // +kubebuilder:validation:Optional + PolicySnapshotIndexUsed string `json:"policySnapshotIndexUsed,omitempty"` + + // ApplyStrategy is the apply strategy that the stagedUpdateRun is using. + // It is the same as the apply strategy in the CRP when the staged update run starts. + // The apply strategy is not updated during the update run even if it changes in the CRP. + // +kubebuilder:validation:Optional + ApplyStrategy v1beta1.ApplyStrategy `json:"appliedStrategy,omitempty"` + + // StagedUpdateStrategySnapshot is the snapshot of the StagedUpdateStrategy used for the update run. + // The snapshot is immutable during the update run. + // The strategy is applied to the list of clusters scheduled by the CRP according to the current policy. + // The update run fails to initialize if the strategy fails to produce a valid list of stages where each selected + // cluster is included in exactly one stage. + // +kubebuilder:validation:Optional + StagedUpdateStrategySnapshot StagedUpdateStrategySpec `json:"stagedUpdateStrategySnapshot,omitempty"` + + // StagesStatus lists the current updating status of each stage. + // The list is empty if the update run is not started or failed to initialize. + // +kubebuilder:validation:Optional + StagesStatus []StageUpdatingStatus `json:"stagesStatus,omitempty"` + + // DeletionStageStatus lists the current status of the deletion stage. The deletion stage + // removes all the resources from the clusters that are not selected by the + // current policy after all the update stages are completed. + // +kubebuilder:validation:Optional + DeletionStageStatus StageUpdatingStatus `json:"deletionStageStatus,omitempty"` + + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + // + // Conditions is an array of current observed conditions for StagedUpdateRun. + // Known conditions are "Initialized", "Progressing", "Succeeded". + // +kubebuilder:validation:Optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// StagedUpdateRunConditionType identifies a specific condition of the StagedUpdateRun. +// +enum +type StagedUpdateRunConditionType string + +const ( + // StagedUpdateRunConditionInitialized indicates whether the staged update run is initialized, meaning it + // has computed all the stages according to the referenced strategy and is ready to start the update. + // Its condition status can be one of the following: + // - "True": The staged update run is initialized. + // - "False": The staged update run encountered an error during initialization. + StagedUpdateRunConditionInitialized StagedUpdateRunConditionType = "Initialized" + + // StagedUpdateRunConditionProgressing indicates whether the staged update run is making progress. + // Its condition status can be one of the following: + // - "True": The staged update run is making progress. + // - "False": The staged update run is waiting/paused. + // - "Unknown" means it is unknown. + StagedUpdateRunConditionProgressing StagedUpdateRunConditionType = "Progressing" + + // StagedUpdateRunConditionSucceeded indicates whether the staged update run is completed successfully. + // Its condition status can be one of the following: + // - "True": The staged update run is completed successfully. + // - "False": The staged update run encountered an error and stopped. + StagedUpdateRunConditionSucceeded StagedUpdateRunConditionType = "Succeeded" +) + +// StageUpdatingStatus defines the status of the update run in a stage. +type StageUpdatingStatus struct { + // The name of the stage. + // +kubebuilder:validation:Required + StageName string `json:"stageName"` + + // The list of each cluster's updating status in this stage. + // +kubebuilder:validation:Required + Clusters []ClusterUpdatingStatus `json:"clusters"` + + // The status of the post-update tasks associated with the current stage. + // Empty if the stage has not finished updating all the clusters. + // +kubebuilder:validation:MaxItems=2 + // +kubebuilder:validation:Optional + AfterStageTaskStatus []AfterStageTaskStatus `json:"afterStageTaskStatus,omitempty"` + + // The time when the update started on the stage. Empty if the stage has not started updating. + // +kubebuilder:validation:Optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Format=date-time + StartTime *metav1.Time `json:"startTime,omitempty"` + + // The time when the update finished on the stage. Empty if the stage has not started updating. + // +kubebuilder:validation:Optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Format=date-time + EndTime *metav1.Time `json:"endTime,omitempty"` + + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + // + // Conditions is an array of current observed updating conditions for the stage. Empty if the stage has not started updating. + // Known conditions are "Progressing", "Succeeded". + // +kubebuilder:validation:Optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// StageUpdatingConditionType identifies a specific condition of the stage that is being updated. +// +enum +type StageUpdatingConditionType string + +const ( + // StageUpdatingConditionProgressing indicates whether the stage updating is making progress. + // Its condition status can be one of the following: + // - "True": The stage updating is making progress. + // - "False": The stage updating is waiting/pausing. + StageUpdatingConditionProgressing StageUpdatingConditionType = "Progressing" + + // ClusterUpdatingStatusConditionSucceeded indicates whether the stage updating is completed successfully. + // Its condition status can be one of the following: + // - "True": The stage updating is completed successfully. + // - "False": The stage updating encountered an error and stopped. + ClusterUpdatingStatusConditionSucceeded StageUpdatingConditionType = "Succeeded" +) + +// ClusterUpdatingStatus defines the status of the update run on a cluster. +type ClusterUpdatingStatus struct { + // The name of the cluster. + // +kubebuilder:validation:Required + ClusterName string `json:"clusterName"` + + // ResourceOverrideSnapshots is a list of ResourceOverride snapshots associated with the cluster. + // The list is computed at the beginning of the update run and not updated during the update run. + // The list is empty if there are no resource overrides associated with the cluster. + // +kubebuilder:validation:Optional + ResourceOverrideSnapshots []v1beta1.NamespacedName `json:"resourceOverrideSnapshots,omitempty"` + + // ClusterResourceOverrides contains a list of applicable ClusterResourceOverride snapshot names + // associated with the cluster. + // The list is computed at the beginning of the update run and not updated during the update run. + // The list is empty if there are no cluster overrides associated with the cluster. + // +kubebuilder:validation:Optional + ClusterResourceOverrideSnapshots []string `json:"clusterResourceOverrideSnapshots,omitempty"` + + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + // + // Conditions is an array of current observed conditions for clusters. Empty if the cluster has not started updating. + // Known conditions are "Started", "Succeeded". + // +kubebuilder:validation:Optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// ClusterUpdatingStatusConditionType identifies a specific condition of the UpdatingStatus of the cluster. +// +enum +type ClusterUpdatingStatusConditionType string + +const ( + // UpdatingStatusConditionTypeStarted indicates whether the cluster updating has started. + // Its condition status can be one of the following: + // - "True": The cluster updating has started. + // - "False": The stage updating has not started. + UpdatingStatusConditionTypeStarted ClusterUpdatingStatusConditionType = "Started" + + // UpdatingStatusConditionTypeSucceeded indicates whether the cluster updating is completed successfully. + // Its condition status can be one of the following: + // - "True": The cluster updating is completed successfully. + // - "False": The cluster updating encountered an error and stopped. + UpdatingStatusConditionTypeSucceeded ClusterUpdatingStatusConditionType = "Succeeded" +) + +type AfterStageTaskStatus struct { + // The type of the post-update task. + // +kubebuilder:validation:Enum=TimedWait;Approval + // +kubebuilder:validation:Required + Type AfterStageTaskType `json:"type"` + + // The name of the approval request object that is created for this stage. + // Only valid if the AfterStageTaskType is Approval. + // +kubebuilder:validation:Optional + ApprovalRequestName string `json:"approvalRequestName,omitempty"` + + // +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". + // +kubebuilder:validation:Optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// AfterStageTaskType identifies a specific type of the AfterStageTask. +// +enum +type AfterStageTaskType string + +const ( + // AfterStageTaskTypeTimedWait indicates the post-stage task is a timed wait. + AfterStageTaskTypeTimedWait AfterStageTaskType = "TimedWait" + + // AfterStageTaskTypeApproval indicates the post-stage task is an approval. + AfterStageTaskTypeApproval AfterStageTaskType = "Approval" +) + +// AfterStageTaskConditionType identifies a specific condition of the AfterStageTask. +// +enum +type AfterStageTaskConditionType string + +const ( + // AfterStageTaskConditionApprovalRequestCreated indicates if the approval request has been created. + // Its condition status can be: + // - "True": The approval request has been created. + // - "False": The approval request has not been created. + AfterStageTaskConditionApprovalRequestCreated AfterStageTaskConditionType = "ApprovalRequestCreated" + + // AfterStageTaskConditionApprovalRequestApproved indicates if the approval request has been approved. + // Its condition status can be: + // - "True": The approval request has been approved. + // - "False": The approval request has not been approved. + AfterStageTaskConditionApprovalRequestApproved AfterStageTaskConditionType = "ApprovalRequestApproved" + + // AfterStageTaskConditionApprovalWaitTimeElapsed indicates if the wait time after each stage has elapsed. + // If the status is "False", the condition message will include the remaining wait time. + // Its condition status can be: + // - "True": The wait time has elapsed. + // - "False": The wait time has not elapsed. + AfterStageTaskConditionApprovalWaitTimeElapsed AfterStageTaskConditionType = "WaitTimeElapsed" +) + +// 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: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 + +// ApprovalRequest defines a request for user approval. +// The request object MUST have the following labels: +// - `TargetUpdateRun`: Points to the update run that this approval request is for. +// - `TargetStage`: The name of the stage that this approval request is for. +// - `IsLatestUpdateRunApproval`: Indicates whether this approval request is the latest one related to this update run. +type ApprovalRequest struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // The desired state of ApprovalRequest. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="The spec field is immutable" + // +kubebuilder:validation:Required + Spec ApprovalRequestSpec `json:"spec"` + + // The observed state of ApprovalRequest. + // +kubebuilder:validation:Optional + Status ApprovalRequestStatus `json:"status,omitempty"` +} + +// ApprovalRequestSpec defines the desired state of the update run approval request. +// The entire spec is immutable. +type ApprovalRequestSpec struct { + // The name of the staged update run that this approval request is for. + // +kubebuilder:validation:Required + TargetUpdateRun string `json:"parentStageRollout"` + + // The name of the update stage that this approval request is for. + // +kubebuilder:validation:Required + TargetStage string `json:"targetStage"` +} + +// ApprovalRequestStatus defines the observed state of the ApprovalRequest. +type ApprovalRequestStatus 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". + // +kubebuilder:validation:Optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// ApprovalRequestConditionType identifies a specific condition of the ApprovalRequest. +type ApprovalRequestConditionType string + +const ( + // ApprovalRequestConditionApproved indicates if the approval request was approved. + // Its condition status can be: + // - "True": The request is approved. + // - "False": The request is not approved. + ApprovalRequestConditionApproved ApprovalRequestConditionType = "Approved" + + // ApprovalRequestConditionApprovalAccepted indicates whether the approval request is accepted by the update process. + // Its condition status can be: + // - "True": The approval request is accepted. + // - "False": The approval request is not accepted. + // - "Unknown": The approval request is not yet approved. + ApprovalRequestConditionApprovalAccepted ApprovalRequestConditionType = "ApprovalAccepted" +) + +// ApprovalRequestList contains a list of ApprovalRequest. +// +kubebuilder:resource:scope="Namespaced" +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ApprovalRequestList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ApprovalRequest `json:"items"` +} + +func init() { + SchemeBuilder.Register( + &StagedUpdateRun{}, &StagedUpdateRunList{}, &StagedUpdateStrategy{}, &StagedUpdateStrategyList{}, &ApprovalRequest{}, &ApprovalRequestList{}, + ) +} diff --git a/apis/placement/v1alpha1/zz_generated.deepcopy.go b/apis/placement/v1alpha1/zz_generated.deepcopy.go index 2850df3d3..f5c9c8c2d 100644 --- a/apis/placement/v1alpha1/zz_generated.deepcopy.go +++ b/apis/placement/v1alpha1/zz_generated.deepcopy.go @@ -11,9 +11,144 @@ package v1alpha1 import ( "go.goms.io/fleet/apis/placement/v1beta1" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AfterStageTask) DeepCopyInto(out *AfterStageTask) { + *out = *in + out.WaitTime = in.WaitTime +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AfterStageTask. +func (in *AfterStageTask) DeepCopy() *AfterStageTask { + if in == nil { + return nil + } + out := new(AfterStageTask) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AfterStageTaskStatus) DeepCopyInto(out *AfterStageTaskStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AfterStageTaskStatus. +func (in *AfterStageTaskStatus) DeepCopy() *AfterStageTaskStatus { + if in == nil { + return nil + } + out := new(AfterStageTaskStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApprovalRequest) DeepCopyInto(out *ApprovalRequest) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApprovalRequest. +func (in *ApprovalRequest) DeepCopy() *ApprovalRequest { + if in == nil { + return nil + } + out := new(ApprovalRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApprovalRequest) 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 *ApprovalRequestList) DeepCopyInto(out *ApprovalRequestList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ApprovalRequest, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApprovalRequestList. +func (in *ApprovalRequestList) DeepCopy() *ApprovalRequestList { + if in == nil { + return nil + } + out := new(ApprovalRequestList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApprovalRequestList) 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 *ApprovalRequestSpec) DeepCopyInto(out *ApprovalRequestSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApprovalRequestSpec. +func (in *ApprovalRequestSpec) DeepCopy() *ApprovalRequestSpec { + if in == nil { + return nil + } + out := new(ApprovalRequestSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApprovalRequestStatus) DeepCopyInto(out *ApprovalRequestStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApprovalRequestStatus. +func (in *ApprovalRequestStatus) DeepCopy() *ApprovalRequestStatus { + if in == nil { + return nil + } + out := new(ApprovalRequestStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterResourceOverride) DeepCopyInto(out *ClusterResourceOverride) { *out = *in @@ -178,6 +313,38 @@ func (in *ClusterResourceOverrideSpec) DeepCopy() *ClusterResourceOverrideSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterUpdatingStatus) DeepCopyInto(out *ClusterUpdatingStatus) { + *out = *in + if in.ResourceOverrideSnapshots != nil { + in, out := &in.ResourceOverrideSnapshots, &out.ResourceOverrideSnapshots + *out = make([]v1beta1.NamespacedName, len(*in)) + copy(*out, *in) + } + if in.ClusterResourceOverrideSnapshots != nil { + in, out := &in.ClusterResourceOverrideSnapshots, &out.ClusterResourceOverrideSnapshots + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterUpdatingStatus. +func (in *ClusterUpdatingStatus) DeepCopy() *ClusterUpdatingStatus { + if in == nil { + return nil + } + out := new(ClusterUpdatingStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JSONPatchOverride) DeepCopyInto(out *JSONPatchOverride) { *out = *in @@ -243,6 +410,21 @@ func (in *OverrideRule) DeepCopy() *OverrideRule { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlacementReference) DeepCopyInto(out *PlacementReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementReference. +func (in *PlacementReference) DeepCopy() *PlacementReference { + if in == nil { + return nil + } + out := new(PlacementReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceOverride) DeepCopyInto(out *ResourceOverride) { *out = *in @@ -419,3 +601,265 @@ func (in *ResourceSelector) DeepCopy() *ResourceSelector { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StageConfig) DeepCopyInto(out *StageConfig) { + *out = *in + if in.LabelSelector != nil { + in, out := &in.LabelSelector, &out.LabelSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.SortingLabelKey != nil { + in, out := &in.SortingLabelKey, &out.SortingLabelKey + *out = new(string) + **out = **in + } + if in.AfterStageTasks != nil { + in, out := &in.AfterStageTasks, &out.AfterStageTasks + *out = make([]AfterStageTask, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StageConfig. +func (in *StageConfig) DeepCopy() *StageConfig { + if in == nil { + return nil + } + out := new(StageConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StageUpdatingStatus) DeepCopyInto(out *StageUpdatingStatus) { + *out = *in + if in.Clusters != nil { + in, out := &in.Clusters, &out.Clusters + *out = make([]ClusterUpdatingStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.AfterStageTaskStatus != nil { + in, out := &in.AfterStageTaskStatus, &out.AfterStageTaskStatus + *out = make([]AfterStageTaskStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.StartTime != nil { + in, out := &in.StartTime, &out.StartTime + *out = (*in).DeepCopy() + } + if in.EndTime != nil { + in, out := &in.EndTime, &out.EndTime + *out = (*in).DeepCopy() + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StageUpdatingStatus. +func (in *StageUpdatingStatus) DeepCopy() *StageUpdatingStatus { + if in == nil { + return nil + } + out := new(StageUpdatingStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StagedUpdateRun) DeepCopyInto(out *StagedUpdateRun) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// 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(StagedUpdateRun) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *StagedUpdateRun) 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 *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 StagedUpdateRunList. +func (in *StagedUpdateRunList) DeepCopy() *StagedUpdateRunList { + if in == nil { + return nil + } + 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 *StagedUpdateRunSpec) DeepCopyInto(out *StagedUpdateRunSpec) { + *out = *in + out.PlacementRef = in.PlacementRef + 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 + in.ApplyStrategy.DeepCopyInto(&out.ApplyStrategy) + in.StagedUpdateStrategySnapshot.DeepCopyInto(&out.StagedUpdateStrategySnapshot) + if in.StagesStatus != nil { + in, out := &in.StagesStatus, &out.StagesStatus + *out = make([]StageUpdatingStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.DeletionStageStatus.DeepCopyInto(&out.DeletionStageStatus) + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// 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(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 *StagedUpdateStrategy) DeepCopyInto(out *StagedUpdateStrategy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// 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(StagedUpdateStrategyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *StagedUpdateStrategyList) 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 *StagedUpdateStrategySpec) DeepCopyInto(out *StagedUpdateStrategySpec) { + *out = *in + if in.Stages != nil { + in, out := &in.Stages, &out.Stages + *out = make([]StageConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// 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(StagedUpdateStrategySpec) + in.DeepCopyInto(out) + return out +} diff --git a/apis/placement/v1beta1/clusterresourceplacement_types.go b/apis/placement/v1beta1/clusterresourceplacement_types.go index 4250eda76..34e6fbf0b 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". + // Type of rollout. The only supported types are "RollingUpdate" and "External". + // Default is "RollingUpdate". // +kubebuilder:validation:Optional - // +kubebuilder:validation:Enum=RollingUpdate // +kubebuilder:default=RollingUpdate + // +kubebuilder:validation:Enum=RollingUpdate;External Type RolloutStrategyType `json:"type,omitempty"` // Rolling update config params. Present only if RolloutStrategyType = RollingUpdate. @@ -515,6 +516,10 @@ const ( // RollingUpdateRolloutStrategyType replaces the old placed resource using rolling update // i.e. gradually create the new one while replace the old ones. RollingUpdateRolloutStrategyType RolloutStrategyType = "RollingUpdate" + + // 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/commons.go b/apis/placement/v1beta1/commons.go index 69635b5f3..dce5bb8e8 100644 --- a/apis/placement/v1beta1/commons.go +++ b/apis/placement/v1beta1/commons.go @@ -82,9 +82,10 @@ const ( // NamespacedName comprises a resource name, with a mandatory namespace. type NamespacedName struct { // Name is the name of the namespaced scope resource. - // +required + // +kubebuilder:validation:Required Name string `json:"name"` + // Namespace is namespace of the namespaced scope resource. - // +required + // +kubebuilder:validation:Required Namespace string `json:"namespace"` } diff --git a/config/crd/bases/placement.kubernetes-fleet.io_approvalrequests.yaml b/config/crd/bases/placement.kubernetes-fleet.io_approvalrequests.yaml new file mode 100644 index 000000000..e258f33a1 --- /dev/null +++ b/config/crd/bases/placement.kubernetes-fleet.io_approvalrequests.yaml @@ -0,0 +1,150 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: approvalrequests.placement.kubernetes-fleet.io +spec: + group: placement.kubernetes-fleet.io + names: + categories: + - fleet + - fleet-placement + kind: ApprovalRequest + listKind: ApprovalRequestList + plural: approvalrequests + singular: approvalrequest + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + ApprovalRequest defines a request for the approval from the user. + The request object MUST have the following labels: + - `TargetUpdateRun` which points to the update run that this approval request is for. + - `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 ApprovalRequest. + properties: + parentStageRollout: + description: The name of the staged update run that this approval + request is for. + type: string + targetStage: + description: The name of the update stage that this approval request + is for. + type: string + required: + - parentStageRollout + - targetStage + type: object + x-kubernetes-validations: + - message: The spec field is immutable + rule: self == oldSelf + status: + description: The desired state of ApprovalRequest. + 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 + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/placement.kubernetes-fleet.io_clusterresourceplacements.yaml b/config/crd/bases/placement.kubernetes-fleet.io_clusterresourceplacements.yaml index b0f2b6fae..39beb40cd 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_clusterresourceplacements.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_clusterresourceplacements.yaml @@ -1859,10 +1859,12 @@ 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 "External". Default is "RollingUpdate". enum: - RollingUpdate + - External type: string type: object required: 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..0e6ad363b --- /dev/null +++ b/config/crd/bases/placement.kubernetes-fleet.io_stagedupdateruns.yaml @@ -0,0 +1,1006 @@ +--- +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 (i.e., the selected clusters) changes. + The name of the StagedUpdateRun needs to conform to [RFC 1123](https://tools.ietf.org/html/rfc1123). + 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. The spec is immutable. + properties: + placementRef: + description: |- + A reference to the placement that this update run is applied to. + There can be multiple active update runs for each placement but + it's up to the devOps to make sure they don't conflict with each other. + properties: + name: + description: Name is the name of the referenced placement. + type: string + required: + - name + type: object + resourceSnapshotIndex: + description: |- + The resource snapshot index of the selected resources to be updated across clusters. + The index represents a group of resourceSnapshots that includes all the resources a ResourcePlacement selected. + type: string + stagedRolloutStrategyRef: + description: |- + The reference to the update strategy that specifies the stages and the sequence + in which the selected resources will be updated on the member clusters. We will compute + the stages according to the referenced strategy when we first start the update run + and record the computed stages in the status field. + properties: + name: + description: Name is the name of the namespaced scope resource. + type: string + namespace: + description: Namespace is namespace of the namespaced scope resource. + type: string + required: + - name + - namespace + type: object + required: + - placementRef + - resourceSnapshotIndex + - stagedRolloutStrategyRef + type: object + x-kubernetes-validations: + - message: The spec field is immutable + rule: self == oldSelf + status: + description: The observed status of StagedUpdateRun. + properties: + appliedStrategy: + description: |- + ApplyStrategy is the apply strategy that the stagedUpdateRun is using. + It is the same as the apply strategy in the CRP when we first start the staged update run. + We will NOT update the apply strategy during the update run even if the apply strategy changes in the CRP. + properties: + actionType: + default: AlwaysApply + description: TakeoverAction describes the action to take when + we place the selected resources on the target cluster the first + time. + enum: + - AlwaysApply + - ApplyIfNoDiff + type: string + allowCoOwnership: + description: |- + AllowCoOwnership defines whether to apply the resource if it already exists in the target cluster and is not + solely owned by fleet (i.e., metadata.ownerReferences contains only fleet custom resources). + If true, apply the resource and add fleet as a co-owner. + If false, leave the resource unchanged and fail the apply. + type: boolean + serverSideApplyConfig: + description: ServerSideApplyConfig defines the configuration for + server side apply. It is honored only when type is ServerSideApply. + properties: + force: + description: |- + Force represents to force apply to succeed when resolving the conflicts + For any conflicting fields, + - If true, use the values from the resource to be applied to overwrite the values of the existing resource in the + target cluster, as well as take over ownership of such fields. + - If false, apply will fail with the reason ApplyConflictWithOtherApplier. + + + For non-conflicting fields, values stay unchanged and ownership are shared between appliers. + type: boolean + type: object + type: + default: ClientSideApply + description: |- + Type defines the type of strategy to use. Default to ClientSideApply. + Server-side apply is more powerful and flexible than client-side apply. + You SHOULD use server-side apply to safely resolve any potential drift between the + original applied resource version and the current resource on the member cluster. + Read more about the differences between server-side apply and client-side apply: + https://kubernetes.io/docs/reference/using-api/server-side-apply/#comparison-with-client-side-apply. + You can also use ReportDiff to only report the difference between the resource on the member cluster + and the resource to be applied from the hub on all the selected clusters. + enum: + - ClientSideApply + - ServerSideApply + - ReportDiff + type: string + type: object + conditions: + description: |- + Conditions is an array of current observed conditions for StagedUpdateRun. + Known conditions are "Initialized", "Progressing", "Succeeded". + 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 + deletionStageStatus: + description: |- + DeletionStageStatus list the current status of the deletion stage. The deletion stage is + the stage that removes all the resources from the clusters that are not selected by the + current policy after all the update stages are completed. + properties: + 'afterStageTaskStatus ': + description: |- + The status of the post update tasks that are associated with current stage. + Empty if the stage has not finished updating all the clusters. + items: + properties: + approvalRequestName: + description: |- + The name of the approval request object that is created for this stage. + Only valid if the AfterStageTaskType is Approval. + type: string + 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. + enum: + - TimedWait + - Approval + type: string + required: + - type + type: object + maxItems: 2 + type: array + clusters: + description: The list of each cluster's updating status in this + stage. + items: + description: ClusterUpdatingStatus defines the status of the + update run on a cluster. + properties: + clusterName: + description: The name of the cluster. + type: string + clusterResourceOverrideSnapshots: + description: |- + ClusterResourceOverrides contains a list of applicable ClusterResourceOverride snapshot names + associated with the cluster. + The list is computed at the beginning of the update run and not updated during the update run. + The list is empty if there are no cluster overrides associated with the cluster. + items: + type: string + type: array + conditions: + description: |- + Conditions is an array of current observed conditions for clusters. Empty if the cluster has not started updating. + Known conditions are "Started,"Succeeded". + 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 + resourceOverrideSnapshots: + description: |- + ResourceOverrideSnapshots is a list of ResourceOverride snapshots associated with the cluster. + The list is computed at the beginning of the update run and not updated during the update run. + The list is empty if there are no resource overrides associated with the cluster. + items: + description: NamespacedName comprises a resource name, + with a mandatory namespace. + properties: + name: + description: Name is the name of the namespaced scope + resource. + type: string + namespace: + description: Namespace is namespace of the namespaced + scope resource. + type: string + required: + - name + - namespace + type: object + type: array + required: + - clusterName + type: object + type: array + conditions: + description: |- + Conditions is an array of current observed updating conditions for the stage. Empty if the stage has not started updating. + Known conditions are "Progressing", "Succeeded". + 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 + endTime: + description: The time when the update finished on the stage. Empty + if the stage has not started updating. + format: date-time + type: string + stageName: + description: The name of the stage. + type: string + startTime: + description: The time when the update started on the stage. Empty + if the stage has not started updating. + format: date-time + type: string + required: + - clusters + - stageName + type: object + policySnapshotIndexUsed: + description: |- + PolicySnapShotIndexUsed records the policy snapshot index of the ClusterResourcePlacement (CRP) that + the update run is based on. The index represents the latest policy snapshot at the start of the update run. + If a newer policy snapshot is detected after the run starts, the staged update run is abandoned. + The scheduler must identify all clusters that meet the current policy before the update run begins. + All clusters involved in the update run are selected from the list of clusters scheduled by the CRP according + to the current policy. + type: string + stagedUpdateStrategySnapshot: + description: |- + StagedUpdateStrategySnapshot is the snapshot of the StagedUpdateStrategy that we are going to use for the update run. + The snapshot is immutable during the update run. + We will apply the strategy to the the list of clusters scheduled by the CRP according to the current policy. + The update run will fail to initialize if the strategy fails to produce a valid list of stages in which each selected + cluster is included in exactly one stage. + properties: + stages: + description: Stage specifies the configuration for each update + stage. + items: + description: |- + StageConfig describes a single update stage. + The clusters in each stage are updated sequentially for now. + We will stop the update if any of the updates fail. + properties: + afterStageTasks: + description: |- + The collection of tasks that each stage needs to complete successfully before moving to the next stage. + Each task is executed in parallel and there can not be more than one task of the same type. + items: + description: AfterStageTask is the collection of post + stage tasks that ALL need to be completed before we + can move to the next stage. + properties: + type: + description: The type of the after stage task. + enum: + - TimedWait + - Approval + 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 + maxItems: 2 + type: array + labelSelector: + description: |- + LabelSelector is a label query over all the joined member clusters. Clusters matching the query are selected + for this stage. There cannot be overlapping clusters between stages when the stagedUpdateRun is created. + If the label selector is absent, the stage includes all the selected clusters. + 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 StagedUpdateStrategy. + maxLength: 63 + pattern: '[A-Za-z0-9]+$' + type: string + 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 or the label value is the same. + type: string + required: + - name + type: object + maxItems: 31 + type: array + required: + - stages + type: object + stagesStatus: + description: |- + StagesStatus list the current updating status of each stage. + The list is empty if the update run is not started or failed to initialize. + items: + description: StageUpdatingStatus defines the status of the update + run in a stage. + properties: + 'afterStageTaskStatus ': + description: |- + The status of the post update tasks that are associated with current stage. + Empty if the stage has not finished updating all the clusters. + items: + properties: + approvalRequestName: + description: |- + The name of the approval request object that is created for this stage. + Only valid if the AfterStageTaskType is Approval. + type: string + 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. + enum: + - TimedWait + - Approval + type: string + required: + - type + type: object + maxItems: 2 + type: array + clusters: + description: The list of each cluster's updating status in this + stage. + items: + description: ClusterUpdatingStatus defines the status of the + update run on a cluster. + properties: + clusterName: + description: The name of the cluster. + type: string + clusterResourceOverrideSnapshots: + description: |- + ClusterResourceOverrides contains a list of applicable ClusterResourceOverride snapshot names + associated with the cluster. + The list is computed at the beginning of the update run and not updated during the update run. + The list is empty if there are no cluster overrides associated with the cluster. + items: + type: string + type: array + conditions: + description: |- + Conditions is an array of current observed conditions for clusters. Empty if the cluster has not started updating. + Known conditions are "Started,"Succeeded". + 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 + resourceOverrideSnapshots: + description: |- + ResourceOverrideSnapshots is a list of ResourceOverride snapshots associated with the cluster. + The list is computed at the beginning of the update run and not updated during the update run. + The list is empty if there are no resource overrides associated with the cluster. + items: + description: NamespacedName comprises a resource name, + with a mandatory namespace. + properties: + name: + description: Name is the name of the namespaced + scope resource. + type: string + namespace: + description: Namespace is namespace of the namespaced + scope resource. + type: string + required: + - name + - namespace + type: object + type: array + required: + - clusterName + type: object + type: array + conditions: + description: |- + Conditions is an array of current observed updating conditions for the stage. Empty if the stage has not started updating. + Known conditions are "Progressing", "Succeeded". + 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 + endTime: + description: The time when the update finished on the stage. + Empty if the stage has not started updating. + format: date-time + type: string + stageName: + description: The name of the stage. + type: string + startTime: + description: The time when the update started on the stage. + Empty if the stage has not started updating. + format: date-time + type: string + required: + - clusters + - stageName + type: object + type: array + 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..80609e893 --- /dev/null +++ b/config/crd/bases/placement.kubernetes-fleet.io_stagedupdatestrategies.yaml @@ -0,0 +1,158 @@ +--- +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 selected resources 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: Stage specifies the configuration for each update stage. + items: + description: |- + StageConfig describes a single update stage. + The clusters in each stage are updated sequentially for now. + We will stop the update if any of the updates fail. + properties: + afterStageTasks: + description: |- + The collection of tasks that each stage needs to complete successfully before moving to the next stage. + Each task is executed in parallel and there can not be more than one task of the same type. + items: + description: AfterStageTask is the collection of post stage + tasks that ALL need to be completed before we can move to + the next stage. + properties: + type: + description: The type of the after stage task. + enum: + - TimedWait + - Approval + 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 + maxItems: 2 + type: array + labelSelector: + description: |- + LabelSelector is a label query over all the joined member clusters. Clusters matching the query are selected + for this stage. There cannot be overlapping clusters between stages when the stagedUpdateRun is created. + If the label selector is absent, the stage includes all the selected clusters. + 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 StagedUpdateStrategy. + maxLength: 63 + pattern: '[A-Za-z0-9]+$' + type: string + 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 or the label value is the same. + type: string + required: + - name + type: object + maxItems: 31 + type: array + required: + - stages + 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/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