mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-02 04:51:22 +01:00
add support slack in webhook
Signed-off-by: peimingming <peimingming@corp.netease.com>
This commit is contained in:
parent
8452100148
commit
3a6d1d75d0
@ -45,7 +45,7 @@ func Init() {
|
||||
model.EventTypeScanningCompleted, model.EventTypeScanningFailed, model.EventTypeProjectQuota,
|
||||
)
|
||||
|
||||
initSupportedNotifyType(model.NotifyTypeHTTP)
|
||||
initSupportedNotifyType(model.NotifyTypeHTTP, model.NotifyTypeSlack)
|
||||
|
||||
log.Info("notification initialization completed")
|
||||
}
|
||||
|
129
src/pkg/notifier/handler/notification/slack_handler.go
Normal file
129
src/pkg/notifier/handler/notification/slack_handler.go
Normal file
@ -0,0 +1,129 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/goharbor/harbor/src/common/job/models"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const (
|
||||
// SlackBodyTemplate defines Slack request body template
|
||||
SlackBodyTemplate = `{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*Harbor webhook events*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*event_type:* {{.Type}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*occur_at:* <!date^{{.OccurAt}}^{date} at {time}|February 18th, 2014 at 6:39 AM PST>"
|
||||
}
|
||||
},
|
||||
{ "type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*operator:* {{.Operator}}"
|
||||
}
|
||||
},
|
||||
{ "type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*event_data:*"
|
||||
}
|
||||
},
|
||||
{ "type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "{{.EventData}}"
|
||||
}
|
||||
}
|
||||
]}`
|
||||
)
|
||||
|
||||
// SlackHandler preprocess event data to slack and start the hook processing
|
||||
type SlackHandler struct {
|
||||
}
|
||||
|
||||
// Handle handles event to slack
|
||||
func (s *SlackHandler) Handle(value interface{}) error {
|
||||
if value == nil {
|
||||
return errors.New("SlackHandler cannot handle nil value")
|
||||
}
|
||||
|
||||
event, ok := value.(*model.HookEvent)
|
||||
if !ok || event == nil {
|
||||
return errors.New("invalid notification slack event")
|
||||
}
|
||||
|
||||
return s.process(event)
|
||||
}
|
||||
|
||||
// IsStateful ...
|
||||
func (s *SlackHandler) IsStateful() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *SlackHandler) process(event *model.HookEvent) error {
|
||||
j := &models.JobData{
|
||||
Metadata: &models.JobMetadata{
|
||||
JobKind: job.KindGeneric,
|
||||
},
|
||||
}
|
||||
// Create a webhookJob to send message to slack
|
||||
j.Name = job.WebhookJob
|
||||
|
||||
// Convert payload to slack format
|
||||
payload, err := s.convert(event.Payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert payload to slack body failed: %v", err)
|
||||
}
|
||||
|
||||
j.Parameters = map[string]interface{}{
|
||||
"payload": payload,
|
||||
"address": event.Target.Address,
|
||||
// Users can define a auth header in http statement in notification(webhook) policy.
|
||||
// So it will be sent in header in http request.
|
||||
"auth_header": event.Target.AuthHeader,
|
||||
"skip_cert_verify": event.Target.SkipCertVerify,
|
||||
}
|
||||
return notification.HookManager.StartHook(event, j)
|
||||
}
|
||||
|
||||
func (s *SlackHandler) convert(payLoad *model.Payload) (string, error) {
|
||||
data := make(map[string]interface{})
|
||||
data["Type"] = payLoad.Type
|
||||
data["OccurAt"] = payLoad.OccurAt
|
||||
data["Operator"] = payLoad.Operator
|
||||
eventData, err := json.MarshalIndent(payLoad.EventData, "", "\t")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshal from eventData %v failed: %v", payLoad.EventData, err)
|
||||
}
|
||||
data["EventData"] = "```" + strings.Replace(string(eventData), `"`, `\"`, -1) + "```"
|
||||
|
||||
st, _ := template.New("slack").Parse(SlackBodyTemplate)
|
||||
var slackBuf bytes.Buffer
|
||||
if err := st.Execute(&slackBuf, data); err != nil {
|
||||
return "", fmt.Errorf("%v", err)
|
||||
}
|
||||
return slackBuf.String(), nil
|
||||
}
|
101
src/pkg/notifier/handler/notification/slack_handler_test.go
Normal file
101
src/pkg/notifier/handler/notification/slack_handler_test.go
Normal file
@ -0,0 +1,101 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
cModels "github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSlackHandler_Handle(t *testing.T) {
|
||||
hookMgr := notification.HookManager
|
||||
defer func() {
|
||||
notification.HookManager = hookMgr
|
||||
}()
|
||||
notification.HookManager = &fakedHookManager{}
|
||||
|
||||
handler := &SlackHandler{}
|
||||
|
||||
type args struct {
|
||||
event *event.Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "SlackHandler_Handle Want Error 1",
|
||||
args: args{
|
||||
event: &event.Event{
|
||||
Topic: "slack",
|
||||
Data: nil,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "SlackHandler_Handle Want Error 2",
|
||||
args: args{
|
||||
event: &event.Event{
|
||||
Topic: "slack",
|
||||
Data: &model.ImageEvent{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "SlackHandler_Handle 1",
|
||||
args: args{
|
||||
event: &event.Event{
|
||||
Topic: "slack",
|
||||
Data: &model.HookEvent{
|
||||
PolicyID: 1,
|
||||
EventType: "pushImage",
|
||||
Target: &cModels.EventTarget{
|
||||
Type: "slack",
|
||||
Address: "http://127.0.0.1:8080",
|
||||
},
|
||||
Payload: &model.Payload{
|
||||
OccurAt: time.Now().Unix(),
|
||||
Type: "pushImage",
|
||||
Operator: "admin",
|
||||
EventData: &model.EventData{
|
||||
Resources: []*model.Resource{
|
||||
{
|
||||
Tag: "v9.0",
|
||||
},
|
||||
},
|
||||
Repository: &model.Repository{
|
||||
Name: "library/debian",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := handler.Handle(tt.args.event.Data)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "Error: %s", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlackHandler_IsStateful(t *testing.T) {
|
||||
handler := &SlackHandler{}
|
||||
assert.False(t, handler.IsStateful())
|
||||
}
|
@ -13,5 +13,6 @@ const (
|
||||
EventTypeTestEndpoint = "testEndpoint"
|
||||
EventTypeProjectQuota = "projectQuota"
|
||||
|
||||
NotifyTypeHTTP = "http"
|
||||
NotifyTypeHTTP = "http"
|
||||
NotifyTypeSlack = "slack"
|
||||
)
|
||||
|
@ -42,6 +42,8 @@ const (
|
||||
|
||||
// WebhookTopic is topic for sending webhook payload
|
||||
WebhookTopic = "http"
|
||||
// SlackTopic is topic for sending slack payload
|
||||
SlackTopic = "slack"
|
||||
// EmailTopic is topic for sending email payload
|
||||
EmailTopic = "email"
|
||||
)
|
||||
|
@ -31,6 +31,7 @@ func init() {
|
||||
model.ScanningCompletedTopic: {¬ification.ScanImagePreprocessHandler{}},
|
||||
model.ScanningFailedTopic: {¬ification.ScanImagePreprocessHandler{}},
|
||||
model.QuotaExceedTopic: {¬ification.QuotaPreprocessHandler{}},
|
||||
model.SlackTopic: {¬ification.SlackHandler{}},
|
||||
}
|
||||
|
||||
for t, handlers := range handlersMap {
|
||||
|
Loading…
Reference in New Issue
Block a user