mirror of
https://github.com/goharbor/harbor.git
synced 2024-10-01 23:07:39 +02:00
Merge pull request #11030 from mmpei/webhook-dev-slack
add support slack in webhook
This commit is contained in:
commit
c2826d0368
@ -45,7 +45,7 @@ func Init() {
|
|||||||
model.EventTypeScanningCompleted, model.EventTypeScanningFailed, model.EventTypeProjectQuota,
|
model.EventTypeScanningCompleted, model.EventTypeScanningFailed, model.EventTypeProjectQuota,
|
||||||
)
|
)
|
||||||
|
|
||||||
initSupportedNotifyType(model.NotifyTypeHTTP)
|
initSupportedNotifyType(model.NotifyTypeHTTP, model.NotifyTypeSlack)
|
||||||
|
|
||||||
log.Info("notification initialization completed")
|
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())
|
||||||
|
}
|
@ -14,4 +14,5 @@ const (
|
|||||||
EventTypeProjectQuota = "projectQuota"
|
EventTypeProjectQuota = "projectQuota"
|
||||||
|
|
||||||
NotifyTypeHTTP = "http"
|
NotifyTypeHTTP = "http"
|
||||||
|
NotifyTypeSlack = "slack"
|
||||||
)
|
)
|
||||||
|
@ -42,6 +42,8 @@ const (
|
|||||||
|
|
||||||
// WebhookTopic is topic for sending webhook payload
|
// WebhookTopic is topic for sending webhook payload
|
||||||
WebhookTopic = "http"
|
WebhookTopic = "http"
|
||||||
|
// SlackTopic is topic for sending slack payload
|
||||||
|
SlackTopic = "slack"
|
||||||
// EmailTopic is topic for sending email payload
|
// EmailTopic is topic for sending email payload
|
||||||
EmailTopic = "email"
|
EmailTopic = "email"
|
||||||
)
|
)
|
||||||
|
@ -31,6 +31,7 @@ func init() {
|
|||||||
model.ScanningCompletedTopic: {¬ification.ScanImagePreprocessHandler{}},
|
model.ScanningCompletedTopic: {¬ification.ScanImagePreprocessHandler{}},
|
||||||
model.ScanningFailedTopic: {¬ification.ScanImagePreprocessHandler{}},
|
model.ScanningFailedTopic: {¬ification.ScanImagePreprocessHandler{}},
|
||||||
model.QuotaExceedTopic: {¬ification.QuotaPreprocessHandler{}},
|
model.QuotaExceedTopic: {¬ification.QuotaPreprocessHandler{}},
|
||||||
|
model.SlackTopic: {¬ification.SlackHandler{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for t, handlers := range handlersMap {
|
for t, handlers := range handlersMap {
|
||||||
|
Loading…
Reference in New Issue
Block a user