Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Checksum field to the Webhook payload to distinguish canary runs #1521

Merged
merged 1 commit into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions docs/gitbook/usage/webhooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,19 @@ Webhook payload (HTTP POST):

```javascript
{
"name": "podinfo",
"namespace": "test",
"phase": "Progressing",
"metadata": {
"test": "all",
"token": "16688eb5e9f289f1991c"
}
"name": "podinfo",
"namespace": "test",
"phase": "Progressing",
"checksum": "85d557f47b",
"metadata": {
"test": "all",
"token": "16688eb5e9f289f1991c"
}
}
```

The checksum field is hashed from the TrackedConfigs and LastAppliedSpec of the Canary, it can be used to identify a Canary for a specific configuration of the deployed resources.

Response status codes:

* 200-202 - advance canary by increasing the traffic weight
Expand All @@ -107,6 +110,7 @@ Event payload (HTTP POST):
"name": "string (canary name)",
"namespace": "string (canary namespace)",
"phase": "string (canary phase)",
"checksum": "string (canary checksum"),
"metadata": {
"eventMessage": "string (canary event message)",
"eventType": "string (canary event type)",
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/flagger/v1beta1/canary.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,11 @@ type CanaryWebhookPayload struct {
// Phase of the canary analysis
Phase CanaryPhase `json:"phase"`

// Hash from the TrackedConfigs and LastAppliedSpec of the Canary.
// Can be used to identify a Canary for a specific configuration of the
// deployed resources.
Checksum string `json:"checksum"`
stefanprodan marked this conversation as resolved.
Show resolved Hide resolved

// Metadata (key-value pairs) for this webhook
Metadata map[string]string `json:"metadata,omitempty"`
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/canary/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func hasSpecChanged(cd *flaggerv1.Canary, spec interface{}) (bool, error) {
return true, nil
}

newHash := computeHash(spec)
newHash := ComputeHash(spec)

// do not trigger a canary deployment on manual rollback
if cd.Status.LastPromotedSpec == newHash {
Expand All @@ -48,10 +48,10 @@ func hasSpecChanged(cd *flaggerv1.Canary, spec interface{}) (bool, error) {
return false, nil
}

// computeHash returns a hash value calculated from a spec using the spew library
// ComputeHash returns a hash value calculated from a spec using the spew library
// which follows pointers and prints actual values of the nested objects
// ensuring the hash does not change when a pointer changes.
func computeHash(spec interface{}) string {
func ComputeHash(spec interface{}) string {
hasher := fnv.New32a()
printer := spew.ConfigState{
Indent: " ",
Expand Down
2 changes: 1 addition & 1 deletion pkg/canary/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
)

func syncCanaryStatus(flaggerClient clientset.Interface, cd *flaggerv1.Canary, status flaggerv1.CanaryStatus, canaryResource interface{}, setAll func(cdCopy *flaggerv1.Canary)) error {
hash := computeHash(canaryResource)
hash := ComputeHash(canaryResource)

firstTry := true
name, ns := cd.GetName(), cd.GetNamespace()
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ func (c *Controller) runAnalysis(canary *flaggerv1.Canary) bool {
// run external checks
for _, webhook := range canary.GetAnalysis().Webhooks {
if webhook.Type == "" || webhook.Type == flaggerv1.RolloutHook {
err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
err := CallWebhook(*canary, flaggerv1.CanaryPhaseProgressing, webhook)
if err != nil {
c.recordEventWarningf(canary, "Halt %s.%s advancement external check %s failed %v",
canary.Name, canary.Namespace, webhook.Name, err)
Expand Down
12 changes: 6 additions & 6 deletions pkg/controller/scheduler_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
func (c *Controller) runConfirmTrafficIncreaseHooks(canary *flaggerv1.Canary) bool {
for _, webhook := range canary.GetAnalysis().Webhooks {
if webhook.Type == flaggerv1.ConfirmTrafficIncreaseHook {
err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
err := CallWebhook(*canary, flaggerv1.CanaryPhaseProgressing, webhook)
if err != nil {
c.recordEventWarningf(canary, "Halt %s.%s advancement waiting for traffic increase approval %s",
canary.Name, canary.Namespace, webhook.Name)
Expand All @@ -44,7 +44,7 @@ func (c *Controller) runConfirmTrafficIncreaseHooks(canary *flaggerv1.Canary) bo
func (c *Controller) runConfirmRolloutHooks(canary *flaggerv1.Canary, canaryController canary.Controller) bool {
for _, webhook := range canary.GetAnalysis().Webhooks {
if webhook.Type == flaggerv1.ConfirmRolloutHook {
err := CallWebhook(canary.Name, canary.Namespace, canary.Status.Phase, webhook)
err := CallWebhook(*canary, canary.Status.Phase, webhook)
if err != nil {
if canary.Status.Phase != flaggerv1.CanaryPhaseWaiting {
if err := canaryController.SetStatusPhase(canary, flaggerv1.CanaryPhaseWaiting); err != nil {
Expand All @@ -67,7 +67,7 @@ func (c *Controller) runConfirmRolloutHooks(canary *flaggerv1.Canary, canaryCont
func (c *Controller) runConfirmPromotionHooks(canary *flaggerv1.Canary, canaryController canary.Controller) bool {
for _, webhook := range canary.GetAnalysis().Webhooks {
if webhook.Type == flaggerv1.ConfirmPromotionHook {
err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
err := CallWebhook(*canary, flaggerv1.CanaryPhaseProgressing, webhook)
if err != nil {
if canary.Status.Phase != flaggerv1.CanaryPhaseWaitingPromotion {
if err := canaryController.SetStatusPhase(canary, flaggerv1.CanaryPhaseWaitingPromotion); err != nil {
Expand Down Expand Up @@ -95,7 +95,7 @@ func (c *Controller) runConfirmPromotionHooks(canary *flaggerv1.Canary, canaryCo
func (c *Controller) runPreRolloutHooks(canary *flaggerv1.Canary) bool {
for _, webhook := range canary.GetAnalysis().Webhooks {
if webhook.Type == flaggerv1.PreRolloutHook {
err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
err := CallWebhook(*canary, flaggerv1.CanaryPhaseProgressing, webhook)
if err != nil {
c.recordEventWarningf(canary, "Halt %s.%s advancement pre-rollout check %s failed %v",
canary.Name, canary.Namespace, webhook.Name, err)
Expand All @@ -111,7 +111,7 @@ func (c *Controller) runPreRolloutHooks(canary *flaggerv1.Canary) bool {
func (c *Controller) runPostRolloutHooks(canary *flaggerv1.Canary, phase flaggerv1.CanaryPhase) bool {
for _, webhook := range canary.GetAnalysis().Webhooks {
if webhook.Type == flaggerv1.PostRolloutHook {
err := CallWebhook(canary.Name, canary.Namespace, phase, webhook)
err := CallWebhook(*canary, phase, webhook)
if err != nil {
c.recordEventWarningf(canary, "Post-rollout hook %s failed %v", webhook.Name, err)
return false
Expand All @@ -126,7 +126,7 @@ func (c *Controller) runPostRolloutHooks(canary *flaggerv1.Canary, phase flagger
func (c *Controller) runRollbackHooks(canary *flaggerv1.Canary, phase flaggerv1.CanaryPhase) bool {
for _, webhook := range canary.GetAnalysis().Webhooks {
if webhook.Type == flaggerv1.RollbackHook {
err := CallWebhook(canary.Name, canary.Namespace, phase, webhook)
err := CallWebhook(*canary, phase, webhook)
if err != nil {
c.recordEventInfof(canary, "Rollback hook %s not signaling a rollback", webhook.Name)
} else {
Expand Down
21 changes: 18 additions & 3 deletions pkg/controller/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"time"

flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
"github.com/fluxcd/flagger/pkg/canary"
)

func callWebhook(webhook string, payload interface{}, timeout string) error {
Expand Down Expand Up @@ -81,11 +82,12 @@ func callWebhook(webhook string, payload interface{}, timeout string) error {

// CallWebhook does a HTTP POST to an external service and
// returns an error if the response status code is non-2xx
func CallWebhook(name string, namespace string, phase flaggerv1.CanaryPhase, w flaggerv1.CanaryWebhook) error {
func CallWebhook(canary flaggerv1.Canary, phase flaggerv1.CanaryPhase, w flaggerv1.CanaryWebhook) error {
payload := flaggerv1.CanaryWebhookPayload{
Name: name,
Namespace: namespace,
Name: canary.Name,
Namespace: canary.Namespace,
Phase: phase,
Checksum: canaryChecksum(canary),
}

if w.Metadata != nil {
Expand All @@ -106,6 +108,7 @@ func CallEventWebhook(r *flaggerv1.Canary, w flaggerv1.CanaryWebhook, message, e
Name: r.Name,
Namespace: r.Namespace,
Phase: r.Status.Phase,
Checksum: canaryChecksum(*r),
Metadata: map[string]string{
"eventMessage": message,
"eventType": eventtype,
Expand All @@ -123,3 +126,15 @@ func CallEventWebhook(r *flaggerv1.Canary, w flaggerv1.CanaryWebhook, message, e
}
return callWebhook(w.URL, payload, "5s")
}

func canaryChecksum(c flaggerv1.Canary) string {
canaryFields := struct {
TrackedConfigs *map[string]string
LastAppliedSpec string
}{
c.Status.TrackedConfigs,
c.Status.LastAppliedSpec,
}

return canary.ComputeHash(canaryFields)
}
Loading
Loading