From 04cbacb6e038e726ee47767e34906891c5fbd584 Mon Sep 17 00:00:00 2001 From: stefanprodan Date: Wed, 24 Jul 2019 12:09:39 +0300 Subject: [PATCH] Implement confirm rollout gate and hook The confirm-rollout hooks are executed before the pre-rollout hooks. Flagger will halt the canary rollout until the confirm webhook returns HTTP status 200. --- pkg/apis/flagger/v1alpha3/types.go | 2 ++ pkg/controller/scheduler.go | 38 +++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/pkg/apis/flagger/v1alpha3/types.go b/pkg/apis/flagger/v1alpha3/types.go index 1f995e02..9cd7cea4 100755 --- a/pkg/apis/flagger/v1alpha3/types.go +++ b/pkg/apis/flagger/v1alpha3/types.go @@ -137,6 +137,8 @@ const ( PreRolloutHook HookType = "pre-rollout" // PreRolloutHook execute webhook after the canary analysis PostRolloutHook HookType = "post-rollout" + // ConfirmRolloutHook halt canary analysis until webhook returns HTTP 200 + ConfirmRolloutHook HookType = "confirm-rollout" ) // CanaryWebhook holds the reference to external checks used for canary analysis diff --git a/pkg/controller/scheduler.go b/pkg/controller/scheduler.go index de02d079..f08ef22e 100644 --- a/pkg/controller/scheduler.go +++ b/pkg/controller/scheduler.go @@ -134,6 +134,12 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh return } + isApproved := c.runConfirmRolloutHooks(cd) + + if !isApproved { + return + } + // set max weight default value to 100% maxWeight := 100 if cd.Spec.CanaryAnalysis.MaxWeight > 0 { @@ -454,7 +460,10 @@ func (c *Controller) shouldSkipAnalysis(cd *flaggerv1.Canary, meshRouter router. } func (c *Controller) shouldAdvance(cd *flaggerv1.Canary) (bool, error) { - if cd.Status.LastAppliedSpec == "" || cd.Status.Phase == flaggerv1.CanaryPhaseInitializing || cd.Status.Phase == flaggerv1.CanaryPhaseProgressing { + if cd.Status.LastAppliedSpec == "" || + cd.Status.Phase == flaggerv1.CanaryPhaseInitializing || + cd.Status.Phase == flaggerv1.CanaryPhaseProgressing || + cd.Status.Phase == flaggerv1.CanaryPhaseWaiting { return true, nil } @@ -523,6 +532,33 @@ func (c *Controller) hasCanaryRevisionChanged(cd *flaggerv1.Canary) bool { return false } +func (c *Controller) runConfirmRolloutHooks(canary *flaggerv1.Canary) bool { + for _, webhook := range canary.Spec.CanaryAnalysis.Webhooks { + if webhook.Type == flaggerv1.ConfirmRolloutHook { + err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook) + if err != nil { + if canary.Status.Phase != flaggerv1.CanaryPhaseWaiting { + c.recordEventWarningf(canary, "Halt %s.%s advancement waiting for approval %s", + canary.Name, canary.Namespace, webhook.Name) + if err := c.deployer.SetStatusPhase(canary, flaggerv1.CanaryPhaseWaiting); err != nil { + c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).Errorf("%v", err) + } + } + return false + } else { + if canary.Status.Phase == flaggerv1.CanaryPhaseWaiting { + if err := c.deployer.SetStatusPhase(canary, flaggerv1.CanaryPhaseProgressing); err != nil { + c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).Errorf("%v", err) + return false + } + c.recordEventInfof(canary, "Confirm-rollout check %s passed", webhook.Name) + } + } + } + } + return true +} + func (c *Controller) runPreRolloutHooks(canary *flaggerv1.Canary) bool { for _, webhook := range canary.Spec.CanaryAnalysis.Webhooks { if webhook.Type == flaggerv1.PreRolloutHook {