From e1ee4b3a4b30f2ac2bc2f938f7943800901cd746 Mon Sep 17 00:00:00 2001 From: Patrick Zhao Date: Sat, 8 Feb 2025 14:25:15 +0800 Subject: [PATCH] add ms teams notification Signed-off-by: Patrick Zhao --- go.mod | 3 +- go.sum | 8 +- .../repository/models/wokflow_task_v4.go | 1 + .../core/common/repository/models/workflow.go | 3 + .../common/repository/models/workflow_v4.go | 6 ++ .../common/service/instantmessage/msteams.go | 96 +++++++++++++++++++ .../service/instantmessage/workflow_task.go | 40 ++++---- .../jobcontroller/job_notification.go | 65 +++++++++++++ .../service/workflow/job/job_jenkins.go | 2 +- .../service/workflow/job/job_notification.go | 2 + pkg/setting/consts.go | 1 + 11 files changed, 202 insertions(+), 25 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/service/instantmessage/msteams.go diff --git a/go.mod b/go.mod index dc02acce8b..88af79856c 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/andygrunwald/go-gerrit v0.0.0-20220906192238-4fc99996c860 github.com/andygrunwald/go-jira v1.16.0 github.com/antihax/optional v1.0.0 + github.com/atc0005/go-teams-notify/v2 v2.13.0 github.com/aws/aws-sdk-go v1.44.99 github.com/blang/semver/v4 v4.0.0 github.com/bradleyfalzon/ghinstallation v1.1.1 @@ -78,7 +79,6 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/samber/lo v1.37.0 github.com/sashabaranov/go-openai v1.24.0 - github.com/segmentio/encoding v0.4.1 github.com/shirou/gopsutil v3.21.11+incompatible github.com/shirou/gopsutil/v3 v3.22.8 github.com/spf13/cobra v1.8.0 @@ -287,7 +287,6 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rubenv/sql-migrate v1.3.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/segmentio/asm v1.1.3 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/afero v1.9.2 // indirect diff --git a/go.sum b/go.sum index bafba7d808..efd1af5126 100644 --- a/go.sum +++ b/go.sum @@ -180,6 +180,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/atc0005/go-teams-notify/v2 v2.13.0 h1:nbDeHy89NjYlF/PEfLVF6lsserY9O5SnN1iOIw3AxXw= +github.com/atc0005/go-teams-notify/v2 v2.13.0/go.mod h1:WSv9moolRsBcpZbwEf6gZxj7h0uJlJskJq5zkEWKO8Y= github.com/aws/aws-sdk-go v1.44.99 h1:ITZ9q/fmH+Ksaz2TbyMU2d19vOOWs/hAlt8NbXAieHw= github.com/aws/aws-sdk-go v1.44.99/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= @@ -1013,10 +1015,6 @@ github.com/sashabaranov/go-openai v1.24.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adO github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= -github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= -github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= -github.com/segmentio/encoding v0.4.1 h1:KLGaLSW0jrmhB58Nn4+98spfvPvmo4Ci1P/WIQ9wn7w= -github.com/segmentio/encoding v0.4.1/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= @@ -1086,6 +1084,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index 3580e9d977..4d8db38709 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -640,6 +640,7 @@ type JobTaskNotificationSpec struct { LarkPersonNotificationConfig *LarkPersonNotificationConfig `bson:"lark_person_notification_config,omitempty" yaml:"lark_person_notification_config,omitempty" json:"lark_person_notification_config,omitempty"` WechatNotificationConfig *WechatNotificationConfig `bson:"wechat_notification_config,omitempty" yaml:"wechat_notification_config,omitempty" json:"wechat_notification_config,omitempty"` DingDingNotificationConfig *DingDingNotificationConfig `bson:"dingding_notification_config,omitempty" yaml:"dingding_notification_config,omitempty" json:"dingding_notification_config,omitempty"` + MSTeamsNotificationConfig *MSTeamsNotificationConfig `bson:"msteams_notification_config,omitempty" yaml:"msteams_notification_config,omitempty" json:"msteams_notification_config,omitempty"` MailNotificationConfig *MailNotificationConfig `bson:"mail_notification_config,omitempty" yaml:"mail_notification_config,omitempty" json:"mail_notification_config,omitempty"` WebhookNotificationConfig *WebhookNotificationConfig `bson:"webhook_notification_config,omitempty" yaml:"webhook_notification_config,omitempty" json:"webhook_notification_config,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index c6d047a5de..c97873e845 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -337,6 +337,7 @@ type NotifyCtl struct { LarkHookNotificationConfig *LarkHookNotificationConfig `bson:"lark_hook_notification_config,omitempty" yaml:"lark_hook_notification_config,omitempty" json:"lark_hook_notification_config,omitempty"` WechatNotificationConfig *WechatNotificationConfig `bson:"wechat_notification_config,omitempty" yaml:"wechat_notification_config,omitempty" json:"wechat_notification_config,omitempty"` DingDingNotificationConfig *DingDingNotificationConfig `bson:"dingding_notification_config,omitempty" yaml:"dingding_notification_config,omitempty" json:"dingding_notification_config,omitempty"` + MSTeamsNotificationConfig *MSTeamsNotificationConfig `bson:"msteams_notification_config,omitempty" yaml:"msteams_notification_config,omitempty" json:"msteams_notification_config,omitempty"` MailNotificationConfig *MailNotificationConfig `bson:"mail_notification_config,omitempty" yaml:"mail_notification_config,omitempty" json:"mail_notification_config,omitempty"` WebhookNotificationConfig *WebhookNotificationConfig `bson:"webhook_notification_config,omitempty" yaml:"webhook_notification_config,omitempty" json:"webhook_notification_config,omitempty"` @@ -436,6 +437,8 @@ func (n *NotifyCtl) GenerateNewNotifyConfigWithOldData() error { IsAtAll: n.IsAtAll, } } + case setting.NotifyWebHookTypeMSTeam: + break default: return fmt.Errorf("unsupported notification type: %s", n.WebHookType) } diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index 6614fb6b75..dab68f416c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -968,6 +968,7 @@ type NotificationJobSpec struct { //LarkHookNotificationConfig *LarkHookNotificationConfig `bson:"lark_hook_notification_config,omitempty" yaml:"lark_hook_notification_config,omitempty" json:"lark_hook_notification_config,omitempty"` WechatNotificationConfig *WechatNotificationConfig `bson:"wechat_notification_config,omitempty" yaml:"wechat_notification_config,omitempty" json:"wechat_notification_config,omitempty"` DingDingNotificationConfig *DingDingNotificationConfig `bson:"dingding_notification_config,omitempty" yaml:"dingding_notification_config,omitempty" json:"dingding_notification_config,omitempty"` + MSTeamsNotificationConfig *MSTeamsNotificationConfig `bson:"msteams_notification_config,omitempty" yaml:"msteams_notification_config,omitempty" json:"msteams_notification_config,omitempty"` MailNotificationConfig *MailNotificationConfig `bson:"mail_notification_config,omitempty" yaml:"mail_notification_config,omitempty" json:"mail_notification_config,omitempty"` WebhookNotificationConfig *WebhookNotificationConfig `bson:"webhook_notification_config,omitempty" yaml:"webhook_notification_config,omitempty" json:"webhook_notification_config,omitempty"` @@ -1092,6 +1093,11 @@ type DingDingNotificationConfig struct { IsAtAll bool `bson:"is_at_all" json:"is_at_all" yaml:"is_at_all"` } +type MSTeamsNotificationConfig struct { + HookAddress string `bson:"hook_address" json:"hook_address" yaml:"hook_address"` + AtEmails []string `bson:"at_emails" json:"at_emails" yaml:"at_emails"` +} + type MailNotificationConfig struct { TargetUsers []*User `bson:"target_users" json:"target_users" yaml:"target_users"` } diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/msteams.go b/pkg/microservice/aslan/core/common/service/instantmessage/msteams.go new file mode 100644 index 0000000000..8dcd6ee667 --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/instantmessage/msteams.go @@ -0,0 +1,96 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package instantmessage + +import ( + "fmt" + "strings" + + goteamsnotify "github.com/atc0005/go-teams-notify/v2" + "github.com/atc0005/go-teams-notify/v2/adaptivecard" + + "github.com/koderover/zadig/v2/pkg/microservice/aslan/config" +) + +func (w *Service) sendMSTeamsMessage(uri, title, content, actionURL string, atEmails []string, taskStatus config.Status) error { + card := adaptivecard.NewCard() + + _, content, found := strings.Cut(content, "\n") + if !found { + return fmt.Errorf("failed to cut content") + } + + titleElement := adaptivecard.NewTitleTextBlock(title, false) + + if taskStatus == config.StatusPassed || taskStatus == config.StatusCreated { + titleElement.Color = adaptivecard.ColorGood + } else if taskStatus == config.StatusFailed { + titleElement.Color = adaptivecard.ColorAttention + } else { + titleElement.Color = adaptivecard.ColorWarning + } + + err := card.AddElement(false, titleElement) + if err != nil { + return fmt.Errorf("failed to add title element to card: %v", err) + } + + bodyElement := adaptivecard.NewTextBlock(content, true) + err = card.AddElement(false, bodyElement) + if err != nil { + return fmt.Errorf("failed to add body element to card: %v", err) + } + + userMentions := make([]adaptivecard.Mention, 0, len(atEmails)) + for _, email := range atEmails { + userMention, err := adaptivecard.NewMention(email, email) + if err != nil { + return fmt.Errorf("failed to create mention: %v", err) + } + userMentions = append(userMentions, userMention) + } + + if len(userMentions) > 0 { + if err := card.AddMention(false, userMentions...); err != nil { + return fmt.Errorf("failed to add mention to card: %v", err) + } + } + + actionURLDesc := "点击查看更多信息" + urlAction, err := adaptivecard.NewActionOpenURL(actionURL, actionURLDesc) + if err != nil { + return fmt.Errorf("failed to create action open url: %v", err) + } + + err = card.AddAction(false, urlAction) + if err != nil { + return fmt.Errorf("failed to add action to card: %v", err) + } + + msg, err := adaptivecard.NewMessageFromCard(card) + if err != nil { + return fmt.Errorf("failed to create message from card: %v", err) + } + + mstClient := goteamsnotify.NewTeamsClient() + err = mstClient.Send(uri, msg) + if err != nil { + return fmt.Errorf("failed to send message to MSTeams: %v", err) + } + + return nil +} diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go b/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go index 6ad4316358..db6902c975 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go @@ -146,7 +146,7 @@ func (w *Service) SendWorkflowTaskApproveNotifications(workflowName string, task } } - if err := w.sendNotification(title, content, notify, larkCard, webhookNotify); err != nil { + if err := w.sendNotification(title, content, notify, larkCard, webhookNotify, task.Status); err != nil { log.Errorf("failed to send notification, err: %s", err) } } @@ -253,7 +253,7 @@ func (w *Service) SendWorkflowTaskNotifications(task *models.WorkflowTask) error } } - if err := w.sendNotification(title, content, notify, larkCard, webhookNotify); err != nil { + if err := w.sendNotification(title, content, notify, larkCard, webhookNotify, task.Status); err != nil { log.Errorf("failed to send notification, err: %s", err) } } @@ -296,11 +296,11 @@ func (w *Service) getApproveNotificationContent(notify *models.NotifyCtl, task * tplTitle := "{{if and (ne .WebHookType \"feishu\") (ne .WebHookType \"feishu_app\") (ne .WebHookType \"feishu_person\")}}### {{end}}{{if eq .WebHookType \"dingding\"}}**{{end}}{{getIcon .Task.Status }}工作流 {{.Task.WorkflowDisplayName}} #{{.Task.TaskID}} 等待审批{{if eq .WebHookType \"dingding\"}}**{{end}} \n" mailTplTitle := "{{getIcon .Task.Status }}工作流 {{.Task.WorkflowDisplayName}} #{{.Task.TaskID}} 等待审批\n" - tplBaseInfo := []string{"{{if eq .WebHookType \"dingding\"}}##### {{end}}**执行用户**:{{.Task.TaskCreator}} \n", - "{{if eq .WebHookType \"dingding\"}}##### {{end}}**项目名称**:{{.ProjectDisplayName}} \n", - "{{if eq .WebHookType \"dingding\"}}##### {{end}}**开始时间**:{{ getStartTime .Task.StartTime}} \n", - "{{if eq .WebHookType \"dingding\"}}##### {{end}}**持续时间**:{{ getDuration .TotalTime}} \n", - "{{if eq .WebHookType \"dingding\"}}##### {{end}}**备注**:{{.Task.Remark}} \n", + tplBaseInfo := []string{"{{if eq .WebHookType \"dingding\"}}##### {{end}}**执行用户**:{{.Task.TaskCreator}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**项目名称**:{{.ProjectDisplayName}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**开始时间**:{{ getStartTime .Task.StartTime}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**持续时间**:{{ getDuration .TotalTime}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**备注**:{{.Task.Remark}} \n", } mailTplBaseInfo := []string{"执行用户:{{.Task.TaskCreator}} \n", "项目名称:{{.ProjectDisplayName}} \n", @@ -427,10 +427,10 @@ func (w *Service) getNotificationContent(notify *models.NotifyCtl, task *models. tplTitle := "{{if and (ne .WebHookType \"feishu\") (ne .WebHookType \"feishu_app\") (ne .WebHookType \"feishu_person\")}}### {{end}}{{if eq .WebHookType \"dingding\"}}{{end}}{{getIcon .Task.Status }}{{getTaskType .Task.Type}} {{.Task.WorkflowDisplayName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{if eq .WebHookType \"dingding\"}}{{end}} \n" mailTplTitle := "{{getIcon .Task.Status }} {{getTaskType .Task.Type}} {{.Task.WorkflowDisplayName}}#{{.Task.TaskID}} {{ taskStatus .Task.Status }}" - tplBaseInfo := []string{"{{if eq .WebHookType \"dingding\"}}##### {{end}}**执行用户**:{{.Task.TaskCreator}} \n", - "{{if eq .WebHookType \"dingding\"}}##### {{end}}**项目名称**:{{.ProjectDisplayName}} \n", - "{{if eq .WebHookType \"dingding\"}}##### {{end}}**开始时间**:{{ getStartTime .Task.StartTime}} \n", - "{{if eq .WebHookType \"dingding\"}}##### {{end}}**持续时间**:{{ getDuration .TotalTime}} \n", + tplBaseInfo := []string{"{{if eq .WebHookType \"dingding\"}}##### {{end}}**执行用户**:{{.Task.TaskCreator}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**项目名称**:{{.ProjectDisplayName}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**开始时间**:{{ getStartTime .Task.StartTime}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**持续时间**:{{ getDuration .TotalTime}} \n", "{{if eq .WebHookType \"dingding\"}}##### {{end}}**备注**:{{.Task.Remark}} \n", } mailTplBaseInfo := []string{"执行用户:{{.Task.TaskCreator}} \n", @@ -462,7 +462,7 @@ func (w *Service) getNotificationContent(notify *models.NotifyCtl, task *models. Error: job.Error, } - jobTplcontent := "{{if and (ne .WebHookType \"feishu\") (ne .WebHookType \"feishu_app\") (ne .WebHookType \"feishu_person\")}}\n\n{{end}}{{if eq .WebHookType \"dingding\"}}---\n\n##### {{end}}**{{jobType .Job.JobType }}**: {{.Job.DisplayName}} **状态**: {{taskStatus .Job.Status }} \n" + jobTplcontent := "{{if and (ne .WebHookType \"feishu\") (ne .WebHookType \"feishu_app\") (ne .WebHookType \"feishu_person\")}}\n\n{{end}}{{if eq .WebHookType \"dingding\"}}---\n\n##### {{end}}**{{jobType .Job.JobType }}**: {{.Job.DisplayName}} **状态**: {{taskStatus .Job.Status }} \n" mailJobTplcontent := "{{jobType .Job.JobType }}:{{.Job.DisplayName}} 状态:{{taskStatus .Job.Status }} \n" switch job.JobType { case string(config.JobZadigBuild): @@ -554,7 +554,7 @@ func (w *Service) getNotificationContent(notify *models.NotifyCtl, task *models. } } if len(commitID) > 0 { - jobTplcontent += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**代码信息**:%s %s[%s](%s) \n", branchTag, prInfo, commitID, gitCommitURL) + jobTplcontent += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**代码信息**:%s %s[%s](%s) \n", branchTag, prInfo, commitID, gitCommitURL) jobTplcontent += "{{if eq .WebHookType \"dingding\"}}##### {{end}}**提交信息**:" mailJobTplcontent += fmt.Sprintf("代码信息:%s %s[%s]( %s )\n", branchTag, prInfo, commitID, gitCommitURL) if len(commitMsgs) == 1 { @@ -567,7 +567,7 @@ func (w *Service) getNotificationContent(notify *models.NotifyCtl, task *models. } } if image != "" && !strings.HasPrefix(image, "{{.") && !strings.Contains(image, "}}") { - jobTplcontent += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**镜像信息**:%s \n", image) + jobTplcontent += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**镜像信息**:%s \n", image) mailJobTplcontent += fmt.Sprintf("镜像信息:%s \n", image) workflowNotifyJobTaskSpec.Image = image } @@ -576,7 +576,7 @@ func (w *Service) getNotificationContent(notify *models.NotifyCtl, task *models. case string(config.JobZadigDeploy): jobSpec := &models.JobTaskDeploySpec{} models.IToi(job.Spec, jobSpec) - jobTplcontent += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**环境**:%s \n", jobSpec.Env) + jobTplcontent += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**环境**:%s \n", jobSpec.Env) mailJobTplcontent += fmt.Sprintf("环境:%s \n", jobSpec.Env) serviceModules := []*webhooknotify.WorkflowNotifyDeployServiceModule{} @@ -597,7 +597,7 @@ func (w *Service) getNotificationContent(notify *models.NotifyCtl, task *models. case string(config.JobZadigHelmDeploy): jobSpec := &models.JobTaskHelmDeploySpec{} models.IToi(job.Spec, jobSpec) - jobTplcontent += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**环境**:%s \n", jobSpec.Env) + jobTplcontent += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**环境**:%s \n", jobSpec.Env) mailJobTplcontent += fmt.Sprintf("环境:%s \n", jobSpec.Env) serviceModules := []*webhooknotify.WorkflowNotifyDeployServiceModule{} @@ -906,9 +906,9 @@ func getJobTaskTplExec(tplcontent string, args *jobTaskNotification) (string, er return buffer.String(), nil } -func (w *Service) sendNotification(title, content string, notify *models.NotifyCtl, card *LarkCard, webhookNotify *webhooknotify.WorkflowNotify) error { +func (w *Service) sendNotification(title, content string, notify *models.NotifyCtl, card *LarkCard, webhookNotify *webhooknotify.WorkflowNotify, taskStatus config.Status) error { link := "" - if notify.WebHookType == setting.NotifyWebHookTypeDingDing || notify.WebHookType == setting.NotifyWebHookTypeWechatWork { + if notify.WebHookType == setting.NotifyWebHookTypeDingDing || notify.WebHookType == setting.NotifyWebHookTypeWechatWork || notify.WebHookType == setting.NotifyWebHookTypeMSTeam { switch webhookNotify.TaskType { case config.WorkflowTaskTypeWorkflow: link = fmt.Sprintf("%s/v1/projects/detail/%s/pipelines/custom/%s/%d?display_name=%s", configbase.SystemAddress(), webhookNotify.ProjectName, webhookNotify.WorkflowName, webhookNotify.TaskID, url.PathEscape(webhookNotify.WorkflowDisplayName)) @@ -923,6 +923,10 @@ func (w *Service) sendNotification(title, content string, notify *models.NotifyC } switch notify.WebHookType { + case setting.NotifyWebHookTypeMSTeam: + if err := w.sendMSTeamsMessage(notify.MSTeamsNotificationConfig.HookAddress, title, content, link, notify.MSTeamsNotificationConfig.AtEmails, taskStatus); err != nil { + return err + } case setting.NotifyWebHookTypeDingDing: if err := w.sendDingDingMessage(notify.DingDingNotificationConfig.HookAddress, title, content, link, notify.DingDingNotificationConfig.AtMobiles, notify.DingDingNotificationConfig.IsAtAll); err != nil { return err diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_notification.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_notification.go index 0a6aad8acc..4377363ea0 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_notification.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_notification.go @@ -26,6 +26,8 @@ import ( "github.com/hashicorp/go-multierror" "github.com/samber/lo" "go.uber.org/zap" + goteamsnotify "github.com/atc0005/go-teams-notify/v2" + "github.com/atc0005/go-teams-notify/v2/adaptivecard" configbase "github.com/koderover/zadig/v2/pkg/config" "github.com/koderover/zadig/v2/pkg/microservice/aslan/config" @@ -107,6 +109,15 @@ func (c *NotificationJobCtl) Run(ctx context.Context) { c.ack() return } + } else if c.jobTaskSpec.WebHookType == setting.NotifyWebHookTypeMSTeam { + err := sendMSTeamsMessage(c.workflowCtx.ProjectName, c.workflowCtx.WorkflowName, c.workflowCtx.WorkflowDisplayName, c.workflowCtx.TaskID, c.jobTaskSpec.MSTeamsNotificationConfig.HookAddress, c.jobTaskSpec.Title, c.jobTaskSpec.Content, c.jobTaskSpec.MSTeamsNotificationConfig.AtEmails) + if err != nil { + c.logger.Error(err) + c.job.Status = config.StatusFailed + c.job.Error = err.Error() + c.ack() + return + } } else if c.jobTaskSpec.WebHookType == setting.NotifyWebHookTypeWechatWork { err := sendWorkWxMessage(c.workflowCtx.ProjectName, c.workflowCtx.WorkflowName, c.workflowCtx.WorkflowDisplayName, c.workflowCtx.TaskID, c.jobTaskSpec.WechatNotificationConfig.HookAddress, c.jobTaskSpec.Title, c.jobTaskSpec.Content, c.jobTaskSpec.WechatNotificationConfig.AtUsers, c.jobTaskSpec.WechatNotificationConfig.IsAtAll) if err != nil { @@ -358,6 +369,60 @@ func sendWorkWxMessage(productName, workflowName, workflowDisplayName string, ta return nil } +func sendMSTeamsMessage(productName, workflowName, workflowDisplayName string, taskID int64, uri, title, message string, emailList []string) error { + card, err := adaptivecard.NewTextBlockCard(message, title, true) + if err != nil { + return fmt.Errorf("failed to create text block card: %v", err) + } + + userMentions := make([]adaptivecard.Mention, 0, len(emailList)) + for _, email := range emailList { + userMention, err := adaptivecard.NewMention(email, email) + if err != nil { + return fmt.Errorf("failed to create mention: %v", err) + } + userMentions = append(userMentions, userMention) + } + + if len(userMentions) > 0 { + if err := card.AddMention(false, userMentions...); err != nil { + return fmt.Errorf("failed to add mention to card: %v", err) + } + } + + actionURL := fmt.Sprintf("%s/v1/projects/detail/%s/pipelines/custom/%s/%d?display_name=%s", + configbase.SystemAddress(), + productName, + workflowName, + taskID, + url.PathEscape(workflowDisplayName), + ) + actionURLDesc := "点击查看更多信息" + + urlAction, err := adaptivecard.NewActionOpenURL(actionURL, actionURLDesc) + if err != nil { + return fmt.Errorf("failed to create action open url: %v", err) + } + + err = card.AddAction(false, urlAction) + if err != nil { + return fmt.Errorf("failed to add action to card: %v", err) + } + + msg, err := adaptivecard.NewMessageFromCard(card) + if err != nil { + return fmt.Errorf("failed to create message from card: %v", err) + } + + mstClient := goteamsnotify.NewTeamsClient() + err = mstClient.Send(uri, msg) + if err != nil { + return fmt.Errorf("failed to send message to MSTeams: %v", err) + } + + return nil +} + func sendMailMessage(title, message string, users []*commonmodels.User, callerID string) error { if len(users) == 0 { return nil diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_jenkins.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_jenkins.go index a70cee2515..e8d840e584 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_jenkins.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_jenkins.go @@ -171,7 +171,7 @@ func (j *JenkinsJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { resp = append(resp, &commonmodels.JobTask{ Name: GenJobName(j.workflow, j.job.Name, 0), Key: genJobKey(j.job.Name, job.JobName), - DisplayName: genJobDisplayName(j.job.Name), + DisplayName: genJobDisplayName(j.job.Name, job.JobName), OriginName: j.job.Name, JobInfo: map[string]string{ JobNameKey: j.job.Name, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_notification.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_notification.go index 1690abb478..f7fcaefd8b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_notification.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_notification.go @@ -111,6 +111,7 @@ func (j *NotificationJob) UpdateWithLatestSetting() error { j.spec.LarkPersonNotificationConfig = latestSpec.LarkPersonNotificationConfig j.spec.WechatNotificationConfig = latestSpec.WechatNotificationConfig j.spec.DingDingNotificationConfig = latestSpec.DingDingNotificationConfig + j.spec.MSTeamsNotificationConfig = latestSpec.MSTeamsNotificationConfig j.spec.MailNotificationConfig = latestSpec.MailNotificationConfig j.spec.WebhookNotificationConfig = latestSpec.WebhookNotificationConfig @@ -175,6 +176,7 @@ func generateNotificationJobSpec(spec *commonmodels.NotificationJobSpec) (*commo resp.LarkPersonNotificationConfig = spec.LarkPersonNotificationConfig resp.LarkGroupNotificationConfig = spec.LarkGroupNotificationConfig resp.DingDingNotificationConfig = spec.DingDingNotificationConfig + resp.MSTeamsNotificationConfig = spec.MSTeamsNotificationConfig resp.WebhookNotificationConfig = spec.WebhookNotificationConfig return resp, nil diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 7e7e6b3bb4..bc3a953563 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -946,6 +946,7 @@ const ( NotifyWebHookTypeFeishuPerson NotifyWebHookType = "feishu_person" NotifyWebhookTypeFeishuApp NotifyWebHookType = "feishu_app" NotifyWebHookTypeWechatWork NotifyWebHookType = "wechat" + NotifyWebHookTypeMSTeam NotifyWebHookType = "msteams" NotifyWebHookTypeMail NotifyWebHookType = "mail" NotifyWebHookTypeWebook NotifyWebHookType = "webhook" )