Skip to content
49 changes: 44 additions & 5 deletions apis/placement/v1beta1/stageupdate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ type UpdateRunObjList interface {
// +kubebuilder:printcolumn:JSONPath=`.spec.resourceSnapshotIndex`,name="Resource-Snapshot-Index",type=string
// +kubebuilder:printcolumn:JSONPath=`.status.policySnapshotIndexUsed`,name="Policy-Snapshot-Index",type=string
// +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Initialized")].status`,name="Initialized",type=string
// +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Progressing")].status`,name="Progressing",type=string
// +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Succeeded")].status`,name="Succeeded",type=string
// +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date
// +kubebuilder:printcolumn:JSONPath=`.spec.stagedRolloutStrategyName`,name="Strategy",priority=1,type=string
Expand All @@ -107,9 +108,8 @@ type ClusterStagedUpdateRun struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// The desired state of ClusterStagedUpdateRun. The spec is immutable.
// The desired state of ClusterStagedUpdateRun.
// +kubebuilder:validation:Required
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="The spec field is immutable"
Spec UpdateRunSpec `json:"spec"`

// The observed status of ClusterStagedUpdateRun.
Expand Down Expand Up @@ -147,27 +147,66 @@ func (c *ClusterStagedUpdateRun) SetUpdateRunStatus(status UpdateRunStatus) {
c.Status = status
}

// State represents the desired state of an update run.
// +enum
type State string

const (
// StateNotStarted describes user intent to initialize but not execute the update run.
// This is the default state when an update run is created.
StateNotStarted State = "NotStarted"

// StateStarted describes user intent to execute (or resume execution if paused).
// Users can subsequently set the state to Stopped or Abandoned.
StateStarted State = "Started"

// StateStopped describes user intent to pause the update run.
// Users can subsequently set the state to Started or Abandoned.
StateStopped State = "Stopped"

// StateAbandoned describes user intent to abandon the update run.
// This is a terminal state; once set, it cannot be changed.
StateAbandoned State = "Abandoned"
)

// UpdateRunSpec 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.
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.state) || oldSelf.state != 'NotStarted' || self.state != 'Stopped'",message="invalid state transition: cannot transition from NotStarted to Stopped"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would the logic blow more straightforward to read?

rule="!(has(oldSelf.state) && oldSelf.state == 'NotStarted' && self.state == 'Stopped')"

// +kubebuilder:validation:XValidation:rule="!has(oldSelf.state) || oldSelf.state != 'Started' || self.state != 'NotStarted'",message="invalid state transition: cannot transition from Started to NotStarted"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rule="!(has(oldSelf.state) && (oldSelf.state == 'Started' || oldSelf.state == 'Stopped' ) && self.state == 'Notstarted')"

// +kubebuilder:validation:XValidation:rule="!has(oldSelf.state) || oldSelf.state != 'Stopped' || self.state != 'NotStarted'",message="invalid state transition: cannot transition from Stopped to NotStarted"
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.state) || oldSelf.state != 'Abandoned' || self.state == 'Abandoned'",message="invalid state transition: Abandoned is a terminal state and cannot transition to any other state"
type UpdateRunSpec struct {
// PlacementName is the name of 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
// +kubebuilder:validation:MaxLength=255
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="placementName is immutable"
PlacementName string `json:"placementName"`

// 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
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="resourceSnapshotIndex is immutable"
ResourceSnapshotIndex string `json:"resourceSnapshotIndex"`

// The name of 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
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="stagedRolloutStrategyName is immutable"
StagedUpdateStrategyName string `json:"stagedRolloutStrategyName"`

// State indicates the desired state of the update run.
// NotStarted: The update run is initialized but execution has not started (default).
// Started: The update run should execute or resume execution.
// Stopped: The update run should pause execution.
// Abandoned: The update run should be abandoned and terminated.
// +kubebuilder:validation:Optional
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why the validation is optional?

Copy link
Collaborator Author

@Arvindthiru Arvindthiru Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users allowed to not specify state, so we can apply the default state

// +kubebuilder:default=NotStarted
// +kubebuilder:validation:Enum=NotStarted;Started;Stopped;Abandoned
State State `json:"state,omitempty"`
}

// UpdateStrategySpecGetterSetter offers the functionality to work with UpdateStrategySpec.
Expand Down Expand Up @@ -387,7 +426,7 @@ const (
// 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.
// - "False": The staged update run is waiting/paused/abandoned.
// - "Unknown" means it is unknown.
StagedUpdateRunConditionProgressing StagedUpdateRunConditionType = "Progressing"

Expand Down Expand Up @@ -746,6 +785,7 @@ func (c *ClusterApprovalRequestList) GetApprovalRequestObjs() []ApprovalRequestO
// +kubebuilder:printcolumn:JSONPath=`.spec.resourceSnapshotIndex`,name="Resource-Snapshot-Index",type=string
// +kubebuilder:printcolumn:JSONPath=`.status.policySnapshotIndexUsed`,name="Policy-Snapshot-Index",type=string
// +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Initialized")].status`,name="Initialized",type=string
// +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Progressing")].status`,name="Progressing",type=string
// +kubebuilder:printcolumn:JSONPath=`.status.conditions[?(@.type=="Succeeded")].status`,name="Succeeded",type=string
// +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date
// +kubebuilder:printcolumn:JSONPath=`.spec.stagedRolloutStrategyName`,name="Strategy",priority=1,type=string
Expand All @@ -761,9 +801,8 @@ type StagedUpdateRun struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// The desired state of StagedUpdateRun. The spec is immutable.
// The desired state of StagedUpdateRun.
// +kubebuilder:validation:Required
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="The spec field is immutable"
Spec UpdateRunSpec `json:"spec"`

// The observed status of StagedUpdateRun.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,9 @@ spec:
- jsonPath: .status.conditions[?(@.type=="Initialized")].status
name: Initialized
type: string
- jsonPath: .status.conditions[?(@.type=="Progressing")].status
name: Progressing
type: string
- jsonPath: .status.conditions[?(@.type=="Succeeded")].status
name: Succeeded
type: string
Expand Down Expand Up @@ -1151,8 +1154,7 @@ spec:
metadata:
type: object
spec:
description: The desired state of ClusterStagedUpdateRun. The spec is
immutable.
description: The desired state of ClusterStagedUpdateRun.
properties:
placementName:
description: |-
Expand All @@ -1161,26 +1163,63 @@ spec:
it's up to the DevOps team to ensure they don't conflict with each other.
maxLength: 255
type: string
x-kubernetes-validations:
- message: placementName is immutable
rule: self == oldSelf
resourceSnapshotIndex:
description: |-
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.
type: string
x-kubernetes-validations:
- message: resourceSnapshotIndex is immutable
rule: self == oldSelf
stagedRolloutStrategyName:
description: |-
The name of 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.
type: string
x-kubernetes-validations:
- message: stagedRolloutStrategyName is immutable
rule: self == oldSelf
state:
default: NotStarted
description: |-
State indicates the desired state of the update run.
NotStarted: The update run is initialized but execution has not started (default).
Started: The update run should execute or resume execution.
Stopped: The update run should pause execution.
Abandoned: The update run should be abandoned and terminated.
enum:
- NotStarted
- Started
- Stopped
- Abandoned
type: string
required:
- placementName
- resourceSnapshotIndex
- stagedRolloutStrategyName
type: object
x-kubernetes-validations:
- message: The spec field is immutable
rule: self == oldSelf
- message: 'invalid state transition: cannot transition from NotStarted
to Stopped'
rule: '!has(oldSelf.state) || oldSelf.state != ''NotStarted'' || self.state
!= ''Stopped'''
- message: 'invalid state transition: cannot transition from Started to
NotStarted'
rule: '!has(oldSelf.state) || oldSelf.state != ''Started'' || self.state
!= ''NotStarted'''
- message: 'invalid state transition: cannot transition from Stopped to
NotStarted'
rule: '!has(oldSelf.state) || oldSelf.state != ''Stopped'' || self.state
!= ''NotStarted'''
- message: 'invalid state transition: Abandoned is a terminal state and
cannot transition to any other state'
rule: '!has(oldSelf.state) || oldSelf.state != ''Abandoned'' || self.state
== ''Abandoned'''
status:
description: The observed status of ClusterStagedUpdateRun.
properties:
Expand Down Expand Up @@ -1998,7 +2037,7 @@ spec:
description: |-
MaxConcurrency specifies the maximum number of clusters that can be updated concurrently within this stage.
Value can be an absolute number (ex: 5) or a percentage of the total clusters in the stage (ex: 50%).
Absolute number is calculated from percentage by rounding up.
Fractional results are rounded down. A minimum of 1 update is enforced.
If not specified, all clusters in the stage are updated sequentially (effectively maxConcurrency = 1).
Defaults to 1.
pattern: ^((100|[0-9]{1,2})%|[0-9]+)$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ spec:
description: |-
MaxConcurrency specifies the maximum number of clusters that can be updated concurrently within this stage.
Value can be an absolute number (ex: 5) or a percentage of the total clusters in the stage (ex: 50%).
Absolute number is calculated from percentage by rounding up.
Fractional results are rounded down. A minimum of 1 update is enforced.
If not specified, all clusters in the stage are updated sequentially (effectively maxConcurrency = 1).
Defaults to 1.
pattern: ^((100|[0-9]{1,2})%|[0-9]+)$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ spec:
- jsonPath: .status.conditions[?(@.type=="Initialized")].status
name: Initialized
type: string
- jsonPath: .status.conditions[?(@.type=="Progressing")].status
name: Progressing
type: string
- jsonPath: .status.conditions[?(@.type=="Succeeded")].status
name: Succeeded
type: string
Expand Down Expand Up @@ -71,7 +74,7 @@ spec:
metadata:
type: object
spec:
description: The desired state of StagedUpdateRun. The spec is immutable.
description: The desired state of StagedUpdateRun.
properties:
placementName:
description: |-
Expand All @@ -80,26 +83,63 @@ spec:
it's up to the DevOps team to ensure they don't conflict with each other.
maxLength: 255
type: string
x-kubernetes-validations:
- message: placementName is immutable
rule: self == oldSelf
resourceSnapshotIndex:
description: |-
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.
type: string
x-kubernetes-validations:
- message: resourceSnapshotIndex is immutable
rule: self == oldSelf
stagedRolloutStrategyName:
description: |-
The name of 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.
type: string
x-kubernetes-validations:
- message: stagedRolloutStrategyName is immutable
rule: self == oldSelf
state:
default: NotStarted
description: |-
State indicates the desired state of the update run.
NotStarted: The update run is initialized but execution has not started (default).
Started: The update run should execute or resume execution.
Stopped: The update run should pause execution.
Abandoned: The update run should be abandoned and terminated.
enum:
- NotStarted
- Started
- Stopped
- Abandoned
type: string
required:
- placementName
- resourceSnapshotIndex
- stagedRolloutStrategyName
type: object
x-kubernetes-validations:
- message: The spec field is immutable
rule: self == oldSelf
- message: 'invalid state transition: cannot transition from NotStarted
to Stopped'
rule: '!has(oldSelf.state) || oldSelf.state != ''NotStarted'' || self.state
!= ''Stopped'''
- message: 'invalid state transition: cannot transition from Started to
NotStarted'
rule: '!has(oldSelf.state) || oldSelf.state != ''Started'' || self.state
!= ''NotStarted'''
- message: 'invalid state transition: cannot transition from Stopped to
NotStarted'
rule: '!has(oldSelf.state) || oldSelf.state != ''Stopped'' || self.state
!= ''NotStarted'''
- message: 'invalid state transition: Abandoned is a terminal state and
cannot transition to any other state'
rule: '!has(oldSelf.state) || oldSelf.state != ''Abandoned'' || self.state
== ''Abandoned'''
status:
description: The observed status of StagedUpdateRun.
properties:
Expand Down Expand Up @@ -917,7 +957,7 @@ spec:
description: |-
MaxConcurrency specifies the maximum number of clusters that can be updated concurrently within this stage.
Value can be an absolute number (ex: 5) or a percentage of the total clusters in the stage (ex: 50%).
Absolute number is calculated from percentage by rounding up.
Fractional results are rounded down. A minimum of 1 update is enforced.
If not specified, all clusters in the stage are updated sequentially (effectively maxConcurrency = 1).
Defaults to 1.
pattern: ^((100|[0-9]{1,2})%|[0-9]+)$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ spec:
description: |-
MaxConcurrency specifies the maximum number of clusters that can be updated concurrently within this stage.
Value can be an absolute number (ex: 5) or a percentage of the total clusters in the stage (ex: 50%).
Absolute number is calculated from percentage by rounding up.
Fractional results are rounded down. A minimum of 1 update is enforced.
If not specified, all clusters in the stage are updated sequentially (effectively maxConcurrency = 1).
Defaults to 1.
pattern: ^((100|[0-9]{1,2})%|[0-9]+)$
Expand Down
Loading
Loading