diff --git a/rollouts/api/v1alpha1/rollout_types.go b/rollouts/api/v1alpha1/rollout_types.go index 520a96ab6f..ff2c12a4a7 100644 --- a/rollouts/api/v1alpha1/rollout_types.go +++ b/rollouts/api/v1alpha1/rollout_types.go @@ -115,9 +115,19 @@ type StrategyRollingUpdate struct { // StrategyProgressive defines the progressive rollout strategy to use. type StrategyProgressive struct { - // Reference of ProgressiveRolloutStrategy to use. - Name string `json:"name"` + // Name of the ProgressiveRolloutStrategy to use. + Name string `json:"name"` + + // Namespace of the ProgressiveRolloutStrategy to use. Namespace string `json:"namespace"` + + // PauseAfterWave represents the highest wave the strategy will deploy. + PauseAfterWave PauseAfterWave `json:"pauseAfterWave,omitempty"` +} + +type PauseAfterWave struct { + // WaveName represents name of the wave defined in the ProgressiveRolloutStrategy. + WaveName string `json:"waveName"` } type RolloutStrategy struct { diff --git a/rollouts/api/v1alpha1/zz_generated.deepcopy.go b/rollouts/api/v1alpha1/zz_generated.deepcopy.go index 06b0910918..09bc6b1992 100644 --- a/rollouts/api/v1alpha1/zz_generated.deepcopy.go +++ b/rollouts/api/v1alpha1/zz_generated.deepcopy.go @@ -172,6 +172,21 @@ func (in *PackagesConfig) DeepCopy() *PackagesConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PauseAfterWave) DeepCopyInto(out *PauseAfterWave) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PauseAfterWave. +func (in *PauseAfterWave) DeepCopy() *PauseAfterWave { + if in == nil { + return nil + } + out := new(PauseAfterWave) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProgressiveRolloutStrategy) DeepCopyInto(out *ProgressiveRolloutStrategy) { *out = *in @@ -578,6 +593,7 @@ func (in *StrategyAllAtOnce) DeepCopy() *StrategyAllAtOnce { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StrategyProgressive) DeepCopyInto(out *StrategyProgressive) { *out = *in + out.PauseAfterWave = in.PauseAfterWave } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StrategyProgressive. diff --git a/rollouts/config/crd/bases/gitops.kpt.dev_rollouts.yaml b/rollouts/config/crd/bases/gitops.kpt.dev_rollouts.yaml index 4a9ca6ed3e..57872720ee 100644 --- a/rollouts/config/crd/bases/gitops.kpt.dev_rollouts.yaml +++ b/rollouts/config/crd/bases/gitops.kpt.dev_rollouts.yaml @@ -119,10 +119,23 @@ spec: strategy to use. properties: name: - description: Reference of ProgressiveRolloutStrategy to use. + description: Name of the ProgressiveRolloutStrategy to use. type: string namespace: + description: Namespace of the ProgressiveRolloutStrategy to + use. type: string + pauseAfterWave: + description: PauseAfterWave represents the highest wave the + strategy will deploy. + properties: + waveName: + description: WaveName represents name of the wave defined + in the ProgressiveRolloutStrategy. + type: string + required: + - waveName + type: object required: - name - namespace diff --git a/rollouts/config/samples/gitops_rollout_cert_manager_progressive.yaml b/rollouts/config/samples/gitops_rollout_cert_manager_progressive.yaml index 396a8b067d..7f16e254b7 100644 --- a/rollouts/config/samples/gitops_rollout_cert_manager_progressive.yaml +++ b/rollouts/config/samples/gitops_rollout_cert_manager_progressive.yaml @@ -37,3 +37,5 @@ spec: progressive: name: cluster-addons-default-rollout namespace: default + pauseAfterWave: + waveName: dev clusters diff --git a/rollouts/controllers/rollout_controller.go b/rollouts/controllers/rollout_controller.go index 1585efc20a..91c349f008 100644 --- a/rollouts/controllers/rollout_controller.go +++ b/rollouts/controllers/rollout_controller.go @@ -192,43 +192,56 @@ func (r *RolloutReconciler) validateProgressiveRolloutStrategy(ctx context.Conte return err } - clusterWaveMap := make(map[string]int) + clusterWaveMap := make(map[string]string) for _, cluster := range allClusters.Items { - clusterWaveMap[cluster.Name] = -1 + clusterWaveMap[cluster.Name] = "" } - for waveIdx, wave := range strategy.Spec.Waves { + pauseAfterWaveName := "" + pauseWaveNameFound := false + + if rollout.Spec.Strategy.Progressive != nil { + pauseAfterWaveName = rollout.Spec.Strategy.Progressive.PauseAfterWave.WaveName + } + + for _, wave := range strategy.Spec.Waves { waveClusters, err := r.store.ListClusters(ctx, rollout.Spec.Targets.Selector, wave.Targets.Selector) if err != nil { return err } if len(waveClusters.Items) == 0 { - return fmt.Errorf("wave %d does not target any clusters", waveIdx) + return fmt.Errorf("wave %q does not target any clusters", wave.Name) } for _, cluster := range waveClusters.Items { currentClusterWave, found := clusterWaveMap[cluster.Name] if !found { // this should never happen - return fmt.Errorf("wave %d references cluster %s not selected by the rollout", waveIdx, cluster.Name) + return fmt.Errorf("wave %q references cluster %s not selected by the rollout", wave.Name, cluster.Name) } - if currentClusterWave > -1 { - return fmt.Errorf("a cluster cannot be selected by more than one wave - cluster %s is selected by waves %d and %d", cluster.Name, currentClusterWave, waveIdx) + if currentClusterWave != "" { + return fmt.Errorf("a cluster cannot be selected by more than one wave - cluster %s is selected by waves %q and %q", cluster.Name, currentClusterWave, wave.Name) } - clusterWaveMap[cluster.Name] = waveIdx + clusterWaveMap[cluster.Name] = wave.Name } + + pauseWaveNameFound = pauseWaveNameFound || pauseAfterWaveName == wave.Name } for _, cluster := range allClusters.Items { wave, _ := clusterWaveMap[cluster.Name] - if wave == -1 { + if wave == "" { return fmt.Errorf("waves should cover all clusters selected by the rollout - cluster %s is not covered by any waves", cluster.Name) } } + if pauseAfterWaveName != "" && !pauseWaveNameFound { + return fmt.Errorf("%q pause wave not found in progressive rollout strategy", pauseAfterWaveName) + } + return nil } @@ -257,7 +270,12 @@ func (r *RolloutReconciler) reconcileRollout(ctx context.Context, rollout *gitop allClusterStatuses := []gitopsv1alpha1.ClusterStatus{} - waveInProgress := false + pauseFutureWaves := false + pauseAfterWaveName := "" + + if rollout.Spec.Strategy.Progressive != nil { + pauseAfterWaveName = rollout.Spec.Strategy.Progressive.PauseAfterWave.WaveName + } for _, wave := range strategy.Spec.Waves { waveClusters, err := r.store.ListClusters(ctx, rollout.Spec.Targets.Selector, wave.Targets.Selector) @@ -282,13 +300,13 @@ func (r *RolloutReconciler) reconcileRollout(ctx context.Context, rollout *gitop return err } - thisWaveInProgress, clusterStatuses, err := r.rolloutTargets(ctx, rollout, &wave, targets, waveInProgress) + thisWaveInProgress, clusterStatuses, err := r.rolloutTargets(ctx, rollout, &wave, targets, pauseFutureWaves) if err != nil { return err } - if thisWaveInProgress { - waveInProgress = true + if thisWaveInProgress || wave.Name == pauseAfterWaveName { + pauseFutureWaves = true } allClusterStatuses = append(allClusterStatuses, clusterStatuses...) @@ -382,14 +400,14 @@ func (r *RolloutReconciler) computeTargets(ctx context.Context, return targets, nil } -func (r *RolloutReconciler) rolloutTargets(ctx context.Context, rollout *gitopsv1alpha1.Rollout, wave *gitopsv1alpha1.Wave, targets *Targets, previousWaveAlreadyInProgress bool) (bool, []gitopsv1alpha1.ClusterStatus, error) { +func (r *RolloutReconciler) rolloutTargets(ctx context.Context, rollout *gitopsv1alpha1.Rollout, wave *gitopsv1alpha1.Wave, targets *Targets, pauseWave bool) (bool, []gitopsv1alpha1.ClusterStatus, error) { clusterStatuses := []gitopsv1alpha1.ClusterStatus{} concurrentUpdates := 0 maxConcurrent := int(wave.MaxConcurrent) waiting := "Waiting" - if previousWaveAlreadyInProgress { + if pauseWave { maxConcurrent = 0 waiting = "Waiting (Upcoming Wave)" }