Skip to content

Commit

Permalink
Send notification about stage executions (#5440)
Browse files Browse the repository at this point in the history
* Add notification for stage

Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]>

* Add notifications for stage execution events in scheduler

Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]>

* Add notifications for executing rollback stage

Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]>

* Add slack notification handling for stage events

Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]>

* Remove Started At from stage event noti

Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]>

* Fix build error

Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]>

* Fix the test

Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]>

* Refactor stage notification handling

Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]>

---------

Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]>
  • Loading branch information
Warashi authored Jan 24, 2025
1 parent cc28c03 commit e842421
Show file tree
Hide file tree
Showing 11 changed files with 2,918 additions and 71 deletions.
57 changes: 57 additions & 0 deletions pkg/app/piped/controller/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,14 @@ func (s *scheduler) Run(ctx context.Context) error {
))
defer span.End()

s.notifyStageStartEvent(ps)

result = s.executeStage(sig, *ps, func(in executor.Input) (executor.Executor, bool) {
return s.executorRegistry.Executor(model.Stage(ps.Name), in)
})

s.notifyStageEndEvent(ps, result)

switch result {
case model.StageStatus_STAGE_SUCCESS:
span.SetStatus(codes.Ok, statusReason)
Expand Down Expand Up @@ -450,10 +454,14 @@ func (s *scheduler) Run(ctx context.Context) error {
))
defer span.End()

s.notifyStageStartEvent(&rbs)

result := s.executeStage(sig, rbs, func(in executor.Input) (executor.Executor, bool) {
return s.executorRegistry.RollbackExecutor(s.deployment.Kind, in)
})

s.notifyStageEndEvent(&rbs, result)

switch result {
case model.StageStatus_STAGE_SUCCESS:
span.SetStatus(codes.Ok, statusReason)
Expand Down Expand Up @@ -850,3 +858,52 @@ func (a appAnalysisResultStore) GetLatestAnalysisResult(ctx context.Context) (*m
func (a appAnalysisResultStore) PutLatestAnalysisResult(ctx context.Context, analysisResult *model.AnalysisResult) error {
return a.store.PutLatestAnalysisResult(ctx, a.applicationID, analysisResult)
}

// notifyStageStartEvent sends notification evnet STAGE_STARTED
func (s *scheduler) notifyStageStartEvent(stage *model.PipelineStage) {
s.notifier.Notify(model.NotificationEvent{
Type: model.NotificationEventType_EVENT_STAGE_STARTED,
Metadata: &model.NotificationEventStageStarted{
Deployment: s.deployment,
Stage: stage,
},
})
}

// notifyStageEndEvent sends notification event based on the stage result.
func (s *scheduler) notifyStageEndEvent(stage *model.PipelineStage, result model.StageStatus) {
switch result {
case model.StageStatus_STAGE_SUCCESS, model.StageStatus_STAGE_EXITED: // Exit stage is treated as success.
s.notifier.Notify(model.NotificationEvent{
Type: model.NotificationEventType_EVENT_STAGE_SUCCEEDED,
Metadata: &model.NotificationEventStageSucceeded{
Deployment: s.deployment,
Stage: stage,
},
})
case model.StageStatus_STAGE_FAILURE:
s.notifier.Notify(model.NotificationEvent{
Type: model.NotificationEventType_EVENT_STAGE_FAILED,
Metadata: &model.NotificationEventStageFailed{
Deployment: s.deployment,
Stage: stage,
},
})
case model.StageStatus_STAGE_CANCELLED:
s.notifier.Notify(model.NotificationEvent{
Type: model.NotificationEventType_EVENT_STAGE_CANCELLED,
Metadata: &model.NotificationEventStageCancelled{
Deployment: s.deployment,
Stage: stage,
},
})
case model.StageStatus_STAGE_SKIPPED:
s.notifier.Notify(model.NotificationEvent{
Type: model.NotificationEventType_EVENT_STAGE_SKIPPED,
Metadata: &model.NotificationEventStageSkipped{
Deployment: s.deployment,
Stage: stage,
},
})
}
}
45 changes: 45 additions & 0 deletions pkg/app/piped/notifier/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,22 @@ func (s *slack) buildSlackMessage(event model.NotificationEvent, webURL string)
}
}

generateStageEventData := func(d *model.Deployment, s *model.PipelineStage, accounts []string, groups []string) {
accountsStr := getAccountsAsString(accounts)
groupsStr := getGroupsAsString(groups)
link = fmt.Sprintf("%s/deployments/%s?project=%s", webURL, d.Id, d.ProjectId)
fields = []slackField{
{"Project", truncateText(d.ProjectId, 8), true},
{"Application", makeSlackLink(d.ApplicationName, fmt.Sprintf("%s/applications/%s?project=%s", webURL, d.ApplicationId, d.ProjectId)), true},
{"Kind", strings.ToLower(d.Kind.String()), true},
{"Deployment", makeSlackLink(truncateText(d.Id, 8), link), true},
{"Stage", s.Name, true},
{"Triggered By", d.TriggeredBy(), true},
{"Mention To Users", accountsStr, true},
{"Mention To Groups", groupsStr, true},
}
}

switch event.Type {
case model.NotificationEventType_EVENT_DEPLOYMENT_TRIGGERED:
md := event.Metadata.(*model.NotificationEventDeploymentTriggered)
Expand Down Expand Up @@ -337,6 +353,35 @@ func (s *slack) buildSlackMessage(event model.NotificationEvent, webURL string)
title = "A piped has been stopped"
generatePipedEventData(md.Id, md.Name, md.Version, md.ProjectId, s.config.MentionedAccounts, s.config.MentionedGroups)

case model.NotificationEventType_EVENT_STAGE_STARTED:
md := event.Metadata.(*model.NotificationEventStageStarted)
title = fmt.Sprintf("Stage %q was started", md.Stage.Name)
generateStageEventData(md.Deployment, md.Stage, s.config.MentionedAccounts, s.config.MentionedGroups)

case model.NotificationEventType_EVENT_STAGE_SKIPPED:
md := event.Metadata.(*model.NotificationEventStageSkipped)
title = fmt.Sprintf("Stage %q was skipped", md.Stage.Name)
generateStageEventData(md.Deployment, md.Stage, s.config.MentionedAccounts, s.config.MentionedGroups)

case model.NotificationEventType_EVENT_STAGE_SUCCEEDED:
md := event.Metadata.(*model.NotificationEventStageSucceeded)
title = fmt.Sprintf("Stage %q was completed successfully", md.Stage.Name)
color = slackSuccessColor
generateStageEventData(md.Deployment, md.Stage, s.config.MentionedAccounts, s.config.MentionedGroups)

case model.NotificationEventType_EVENT_STAGE_FAILED:
md := event.Metadata.(*model.NotificationEventStageFailed)
title = fmt.Sprintf("Stage %q was failed", md.Stage.Name)
text = md.Stage.StatusReason
color = slackErrorColor
generateStageEventData(md.Deployment, md.Stage, s.config.MentionedAccounts, s.config.MentionedGroups)

case model.NotificationEventType_EVENT_STAGE_CANCELLED:
md := event.Metadata.(*model.NotificationEventStageCancelled)
title = fmt.Sprintf("Stage %q was cancelled", md.Stage.Name)
color = slackWarnColor
generateStageEventData(md.Deployment, md.Stage, s.config.MentionedAccounts, s.config.MentionedGroups)

// TODO: Support application type of notification event.
default:
return slackMessage{}, false
Expand Down
57 changes: 57 additions & 0 deletions pkg/app/pipedv1/controller/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,12 @@ func (s *scheduler) Run(ctx context.Context) error {
))
defer span.End()

s.notifyStageStartEvent(ps)

result = s.executeStage(sig, ps)

s.notifyStageEndEvent(ps, result)

switch result {
case model.StageStatus_STAGE_SUCCESS:
span.SetStatus(codes.Ok, statusReason)
Expand Down Expand Up @@ -420,8 +424,12 @@ func (s *scheduler) Run(ctx context.Context) error {
))
defer span.End()

s.notifyStageStartEvent(rbs)

result := s.executeStage(sig, rbs)

s.notifyStageEndEvent(rbs, result)

switch result {
case model.StageStatus_STAGE_SUCCESS:
span.SetStatus(codes.Ok, statusReason)
Expand Down Expand Up @@ -756,3 +764,52 @@ func (s *scheduler) reportMostRecentlySuccessfulDeployment(ctx context.Context)

return err
}

// notifyStageStartEvent sends notification evnet STAGE_STARTED
func (s *scheduler) notifyStageStartEvent(stage *model.PipelineStage) {
s.notifier.Notify(model.NotificationEvent{
Type: model.NotificationEventType_EVENT_STAGE_STARTED,
Metadata: &model.NotificationEventStageStarted{
Deployment: s.deployment,
Stage: stage,
},
})
}

// notifyStageEndEvent sends notification event based on the stage result.
func (s *scheduler) notifyStageEndEvent(stage *model.PipelineStage, result model.StageStatus) {
switch result {
case model.StageStatus_STAGE_SUCCESS, model.StageStatus_STAGE_EXITED: // Exit stage is treated as success.
s.notifier.Notify(model.NotificationEvent{
Type: model.NotificationEventType_EVENT_STAGE_SUCCEEDED,
Metadata: &model.NotificationEventStageSucceeded{
Deployment: s.deployment,
Stage: stage,
},
})
case model.StageStatus_STAGE_FAILURE:
s.notifier.Notify(model.NotificationEvent{
Type: model.NotificationEventType_EVENT_STAGE_FAILED,
Metadata: &model.NotificationEventStageFailed{
Deployment: s.deployment,
Stage: stage,
},
})
case model.StageStatus_STAGE_CANCELLED:
s.notifier.Notify(model.NotificationEvent{
Type: model.NotificationEventType_EVENT_STAGE_CANCELLED,
Metadata: &model.NotificationEventStageCancelled{
Deployment: s.deployment,
Stage: stage,
},
})
case model.StageStatus_STAGE_SKIPPED:
s.notifier.Notify(model.NotificationEvent{
Type: model.NotificationEventType_EVENT_STAGE_SKIPPED,
Metadata: &model.NotificationEventStageSkipped{
Deployment: s.deployment,
Stage: stage,
},
})
}
}
45 changes: 45 additions & 0 deletions pkg/app/pipedv1/notifier/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,22 @@ func (s *slack) buildSlackMessage(event model.NotificationEvent, webURL string)
}
}

generateStageEventData := func(d *model.Deployment, s *model.PipelineStage, accounts []string, groups []string) {
accountsStr := getAccountsAsString(accounts)
groupsStr := getGroupsAsString(groups)
link = fmt.Sprintf("%s/deployments/%s?project=%s", webURL, d.Id, d.ProjectId)
fields = []slackField{
{"Project", truncateText(d.ProjectId, 8), true},
{"Application", makeSlackLink(d.ApplicationName, fmt.Sprintf("%s/applications/%s?project=%s", webURL, d.ApplicationId, d.ProjectId)), true},
{"Kind", strings.ToLower(d.Kind.String()), true},
{"Deployment", makeSlackLink(truncateText(d.Id, 8), link), true},
{"Stage", s.Name, true},
{"Triggered By", d.TriggeredBy(), true},
{"Mention To Users", accountsStr, true},
{"Mention To Groups", groupsStr, true},
}
}

switch event.Type {
case model.NotificationEventType_EVENT_DEPLOYMENT_TRIGGERED:
md := event.Metadata.(*model.NotificationEventDeploymentTriggered)
Expand Down Expand Up @@ -337,6 +353,35 @@ func (s *slack) buildSlackMessage(event model.NotificationEvent, webURL string)
title = "A piped has been stopped"
generatePipedEventData(md.Id, md.Name, md.Version, md.ProjectId, s.config.MentionedAccounts, s.config.MentionedGroups)

case model.NotificationEventType_EVENT_STAGE_STARTED:
md := event.Metadata.(*model.NotificationEventStageStarted)
title = fmt.Sprintf("Stage %q was started", md.Stage.Name)
generateStageEventData(md.Deployment, md.Stage, s.config.MentionedAccounts, s.config.MentionedGroups)

case model.NotificationEventType_EVENT_STAGE_SKIPPED:
md := event.Metadata.(*model.NotificationEventStageSkipped)
title = fmt.Sprintf("Stage %q was skipped", md.Stage.Name)
generateStageEventData(md.Deployment, md.Stage, s.config.MentionedAccounts, s.config.MentionedGroups)

case model.NotificationEventType_EVENT_STAGE_SUCCEEDED:
md := event.Metadata.(*model.NotificationEventStageSucceeded)
title = fmt.Sprintf("Stage %q was completed successfully", md.Stage.Name)
color = slackSuccessColor
generateStageEventData(md.Deployment, md.Stage, s.config.MentionedAccounts, s.config.MentionedGroups)

case model.NotificationEventType_EVENT_STAGE_FAILED:
md := event.Metadata.(*model.NotificationEventStageFailed)
title = fmt.Sprintf("Stage %q was failed", md.Stage.Name)
text = md.Stage.StatusReason
color = slackErrorColor
generateStageEventData(md.Deployment, md.Stage, s.config.MentionedAccounts, s.config.MentionedGroups)

case model.NotificationEventType_EVENT_STAGE_CANCELLED:
md := event.Metadata.(*model.NotificationEventStageCancelled)
title = fmt.Sprintf("Stage %q was cancelled", md.Stage.Name)
color = slackWarnColor
generateStageEventData(md.Deployment, md.Stage, s.config.MentionedAccounts, s.config.MentionedGroups)

// TODO: Support application type of notification event.
default:
return slackMessage{}, false
Expand Down
2 changes: 2 additions & 0 deletions pkg/model/notificationevent.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ func (e NotificationEvent) Group() NotificationEventGroup {
return NotificationEventGroup_EVENT_APPLICATION_HEALTH
case e.Type < 400:
return NotificationEventGroup_EVENT_PIPED
case e.Type < 500:
return NotificationEventGroup_EVENT_STAGE
default:
return NotificationEventGroup_EVENT_NONE
}
Expand Down
Loading

0 comments on commit e842421

Please sign in to comment.