Refactor event model (#10876)

Move src/pkg/notification/model/const.go to src/pkg/notifier/model/const.go
Add auditlog handler to log project event, repo event, artifact event and tag event.

Signed-off-by: stonezdj <stonezdj@gmail.com>
This commit is contained in:
stonezdj(Daojun Zhang) 2020-03-11 11:51:28 +08:00 committed by GitHub
parent f49994d81d
commit c7fd3bdfc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 392 additions and 43 deletions

View File

@ -7,7 +7,7 @@ import (
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/notification/model"
"github.com/goharbor/harbor/src/pkg/notifier/model"
)
type fakedNotificationJobMgr struct {

View File

@ -6,7 +6,7 @@ import (
"github.com/pkg/errors"
"github.com/goharbor/harbor/src/pkg/notification/model"
"github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/notification"

View File

@ -12,7 +12,7 @@ func init() {
// AuditLog ...
type AuditLog struct {
ID int64 `orm:"pk;auto;column(id)" json:"id"`
ProjectID int `orm:"column(project_id)" json:"project_id"`
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
Operation string `orm:"column(operation)" json:"operation"`
ResourceType string `orm:"column(resource_type)" json:"resource_type"`
Resource string `orm:"column(resource)" json:"resource"`

View File

@ -5,9 +5,9 @@ import (
"github.com/goharbor/harbor/src/pkg/notification/hook"
"github.com/goharbor/harbor/src/pkg/notification/job"
jobMgr "github.com/goharbor/harbor/src/pkg/notification/job/manager"
"github.com/goharbor/harbor/src/pkg/notification/model"
"github.com/goharbor/harbor/src/pkg/notification/policy"
"github.com/goharbor/harbor/src/pkg/notification/policy/manager"
"github.com/goharbor/harbor/src/pkg/notifier/model"
)
var (

View File

@ -10,7 +10,7 @@ import (
commonhttp "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/notification/model"
"github.com/goharbor/harbor/src/pkg/notifier/model"
notifierModel "github.com/goharbor/harbor/src/pkg/notifier/model"
)

View File

@ -5,9 +5,9 @@ import (
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/log"
notifyModel "github.com/goharbor/harbor/src/pkg/notification/model"
"github.com/goharbor/harbor/src/pkg/notifier"
"github.com/goharbor/harbor/src/pkg/notifier/model"
notifyModel "github.com/goharbor/harbor/src/pkg/notifier/model"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/pkg/errors"
)
@ -22,6 +22,43 @@ type Event struct {
Data interface{}
}
// TopicEvent - Events that contains topic information
type TopicEvent interface {
Topic() string
}
// New ...
func New() *Event {
return &Event{}
}
// WithTopicEvent - builder method
func (e *Event) WithTopicEvent(topicEvent TopicEvent) *Event {
e.Topic = topicEvent.Topic()
e.Data = topicEvent
return e
}
// Build an event by metadata
func (e *Event) Build(metadata ...Metadata) error {
for _, md := range metadata {
if err := md.Resolve(e); err != nil {
log.Debugf("failed to resolve event metadata: %v", md)
return errors.Wrap(err, "failed to resolve event metadata")
}
}
return nil
}
// Publish an event
func (e *Event) Publish() error {
if err := notifier.Publish(e.Topic, e.Data); err != nil {
log.Debugf("failed to publish topic %s with event: %v", e.Topic, e.Data)
return errors.Wrap(err, "failed to publish event")
}
return nil
}
// Metadata is the event raw data to be processed
type Metadata interface {
Resolve(event *Event) error
@ -285,23 +322,3 @@ func (h *HookMetaData) Resolve(evt *Event) error {
evt.Data = data
return nil
}
// Build an event by metadata
func (e *Event) Build(metadata ...Metadata) error {
for _, md := range metadata {
if err := md.Resolve(e); err != nil {
log.Debugf("failed to resolve event metadata: %v", md)
return errors.Wrap(err, "failed to resolve event metadata")
}
}
return nil
}
// Publish an event
func (e *Event) Publish() error {
if err := notifier.Publish(e.Topic, e.Data); err != nil {
log.Debugf("failed to publish topic %s with event: %v", e.Topic, e.Data)
return errors.Wrap(err, "failed to publish event")
}
return nil
}

View File

@ -0,0 +1,50 @@
package auditlog
import (
beegoorm "github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/internal/orm"
"github.com/goharbor/harbor/src/pkg/audit"
am "github.com/goharbor/harbor/src/pkg/audit/model"
"github.com/goharbor/harbor/src/pkg/notifier/model"
)
// Handler - audit log handler
type Handler struct {
AuditLogMgr audit.Manager
}
// AuditResolver - interface to resolve to AuditLog
type AuditResolver interface {
ResolveToAuditLog() (*am.AuditLog, error)
}
// AuditHandler ...
var AuditHandler = Handler{AuditLogMgr: audit.Mgr}
// Handle ...
func (h *Handler) Handle(value interface{}) error {
ctx := orm.NewContext(nil, beegoorm.NewOrm())
var auditLog *am.AuditLog
switch v := value.(type) {
case *model.ProjectEvent, *model.RepositoryEvent, *model.ArtifactEvent, *model.TagEvent:
resolver := value.(AuditResolver)
al, err := resolver.ResolveToAuditLog()
if err != nil {
log.Errorf("failed to handler event %v", err)
return err
}
auditLog = al
default:
log.Errorf("Can not handler this event type! %#v", v)
}
if auditLog != nil {
h.AuditLogMgr.Create(ctx, auditLog)
}
return nil
}
// IsStateful ...
func (h *Handler) IsStateful() bool {
return false
}

View File

@ -0,0 +1,98 @@
package auditlog
import (
"context"
common_dao "github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/audit/model"
"github.com/goharbor/harbor/src/pkg/notifier"
"github.com/goharbor/harbor/src/pkg/notifier/event"
nm "github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"testing"
"time"
)
type MockAuditLogManager struct {
mock.Mock
}
func (m *MockAuditLogManager) Count(ctx context.Context, query *q.Query) (total int64, err error) {
args := m.Called()
return int64(args.Int(0)), args.Error(1)
}
func (m *MockAuditLogManager) Create(ctx context.Context, audit *model.AuditLog) (id int64, err error) {
args := m.Called()
return int64(args.Int(0)), args.Error(1)
}
func (m *MockAuditLogManager) Delete(ctx context.Context, id int64) (err error) {
args := m.Called()
return args.Error(0)
}
func (m *MockAuditLogManager) Get(ctx context.Context, id int64) (audit *model.AuditLog, err error) {
args := m.Called()
return args.Get(0).(*model.AuditLog), args.Error(1)
}
func (m *MockAuditLogManager) List(ctx context.Context, query *q.Query) (audits []*model.AuditLog, err error) {
args := m.Called()
return args.Get(0).([]*model.AuditLog), args.Error(1)
}
type AuditLogHandlerTestSuite struct {
suite.Suite
auditLogHandler *Handler
logMgr *MockAuditLogManager
}
func (suite *AuditLogHandlerTestSuite) SetupSuite() {
common_dao.PrepareTestForPostgresSQL()
suite.logMgr = &MockAuditLogManager{}
suite.auditLogHandler = &Handler{AuditLogMgr: suite.logMgr}
log.SetLevel(log.DebugLevel)
}
func (suite *AuditLogHandlerTestSuite) TestSubscribeTagEvent() {
suite.logMgr.On("Create", mock.Anything).Return(1, nil)
suite.logMgr.On("Count", mock.Anything).Return(1, nil)
// sample code to use the event framework.
notifier.Subscribe(nm.PushTagTopic, suite.auditLogHandler)
// event data should implement the interface TopicEvent
data := &nm.TagEvent{
TargetTopic: nm.PushTagTopic, // Topic is a attribute of event
Project: &models.Project{
ProjectID: 1,
Name: "library",
},
RepoName: "busybox",
Digest: "abcdef",
TagName: "dev",
OccurAt: time.Now(),
Operator: "admin",
Operation: "push", // Use Operation instead of event type.
}
// No EventMetadata anymore and there is no need to call resolve
// The handler receives the TagEvent
// The handler should use switch type interface to get TagEvent
event.New().WithTopicEvent(data).Publish()
cnt, err := suite.logMgr.Count(nil, nil)
suite.Nil(err)
suite.Equal(int64(1), cnt)
}
func TestAuditLogHandlerTestSuite(t *testing.T) {
suite.Run(t, &AuditLogHandlerTestSuite{})
}

View File

@ -6,7 +6,6 @@ import (
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/pkg/notification"
notificationModel "github.com/goharbor/harbor/src/pkg/notification/model"
"github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -48,11 +47,11 @@ func (f *fakedPolicyMgr) GetRelatedPolices(id int64, eventType string) ([]*model
{
ID: 1,
EventTypes: []string{
notificationModel.EventTypeUploadChart,
notificationModel.EventTypeDownloadChart,
notificationModel.EventTypeDeleteChart,
notificationModel.EventTypeScanningCompleted,
notificationModel.EventTypeScanningFailed,
model.EventTypeUploadChart,
model.EventTypeDownloadChart,
model.EventTypeDeleteChart,
model.EventTypeScanningCompleted,
model.EventTypeScanningFailed,
},
Targets: []models.EventTarget{
{

View File

@ -10,7 +10,6 @@ import (
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/pkg/notification"
notificationModel "github.com/goharbor/harbor/src/pkg/notification/model"
"github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
@ -53,8 +52,8 @@ func (f *fakedNotificationPlyMgr) GetRelatedPolices(id int64, eventType string)
{
ID: 1,
EventTypes: []string{
notificationModel.EventTypePullImage,
notificationModel.EventTypePushImage,
model.EventTypePullImage,
model.EventTypePushImage,
},
Targets: []models.EventTarget{
{

View File

@ -140,6 +140,24 @@ func resolveImageEventData(value interface{}) (*notifyModel.ImageEvent, error) {
return imgEvent, nil
}
func resolveTagEventToImageEvent(value interface{}) (*notifyModel.ImageEvent, error) {
tagEvent, ok := value.(*notifyModel.TagEvent)
if !ok || tagEvent == nil {
return nil, errors.New("invalid image event")
}
imageEvent := notifyModel.ImageEvent{
EventType: notifyModel.PushImageTopic,
Project: tagEvent.Project,
RepoName: tagEvent.RepoName,
Resource: []*notifyModel.ImgResource{
{Tag: tagEvent.TagName},
},
OccurAt: tagEvent.OccurAt,
Operator: tagEvent.Operator,
}
return &imageEvent, nil
}
// preprocessAndSendImageHook preprocess image event data and send hook by notification policy target
func preprocessAndSendImageHook(value interface{}) error {
// if global notification configured disabled, return directly

View File

@ -8,7 +8,6 @@ import (
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/pkg/notification"
nm "github.com/goharbor/harbor/src/pkg/notification/model"
"github.com/goharbor/harbor/src/pkg/notification/policy"
"github.com/goharbor/harbor/src/pkg/notifier"
"github.com/goharbor/harbor/src/pkg/notifier/model"
@ -40,7 +39,7 @@ func (suite *QuotaPreprocessHandlerSuite) SetupSuite() {
Tag: "latest",
}
suite.evt = &model.QuotaEvent{
EventType: nm.EventTypeProjectQuota,
EventType: model.EventTypeProjectQuota,
OccurAt: time.Now().UTC(),
RepoName: "hello-world",
Resource: res,

View File

@ -11,13 +11,12 @@ import (
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/pkg/notification"
nm "github.com/goharbor/harbor/src/pkg/notification/model"
"github.com/goharbor/harbor/src/pkg/notification/policy"
"github.com/goharbor/harbor/src/pkg/notifier"
"github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
"github.com/goharbor/harbor/src/pkg/scan/report"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
@ -53,7 +52,7 @@ func (suite *ScanImagePreprocessHandlerSuite) SetupSuite() {
MimeType: v1.MimeTypeDockerArtifact,
}
suite.evt = &model.ScanImageEvent{
EventType: nm.EventTypeScanningCompleted,
EventType: model.EventTypeScanningCompleted,
OccurAt: time.Now().UTC(),
Operator: "admin",
Artifact: a,

View File

@ -3,7 +3,11 @@ package model
import (
"time"
"fmt"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/audit/model"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
)
@ -59,6 +63,144 @@ type HookEvent struct {
Payload *Payload
}
// ProjectEvent info of Project related event
type ProjectEvent struct {
// EventType - create/delete event
TargetTopic string
Project *models.Project
OccurAt time.Time
Operator string
Operation string
}
// ResolveToAuditLog ...
func (p *ProjectEvent) ResolveToAuditLog() (*model.AuditLog, error) {
auditLog := &model.AuditLog{
ProjectID: p.Project.ProjectID,
OpTime: p.OccurAt,
Operation: p.Operation,
Username: p.Operator,
ResourceType: "project",
Resource: fmt.Sprintf("/api/project/%v",
p.Project.ProjectID)}
return auditLog, nil
}
// Topic ...
func (p *ProjectEvent) Topic() string {
return p.TargetTopic
}
// RepositoryEvent info of repository related event
type RepositoryEvent struct {
TargetTopic string
Project *models.Project
RepoName string
OccurAt time.Time
Operator string
Operation string
}
// ResolveToAuditLog ...
func (r *RepositoryEvent) ResolveToAuditLog() (*model.AuditLog, error) {
auditLog := &model.AuditLog{
ProjectID: r.Project.ProjectID,
OpTime: r.OccurAt,
Operation: r.Operation,
Username: r.Operator,
ResourceType: "repository",
Resource: fmt.Sprintf("/api/project/%v/repository/%v",
r.Project.ProjectID, r.RepoName)}
return auditLog, nil
}
// Topic ...
func (r *RepositoryEvent) Topic() string {
return r.TargetTopic
}
// ArtifactEvent info of artifact related event
type ArtifactEvent struct {
TargetTopic string
Project *models.Project
RepoName string
Digest string
OccurAt time.Time
Operator string
Operation string
}
// ResolveToAuditLog ...
func (a *ArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
auditLog := &model.AuditLog{
ProjectID: a.Project.ProjectID,
OpTime: a.OccurAt,
Operation: a.Operation,
Username: a.Operator,
ResourceType: "artifact",
Resource: fmt.Sprintf("/api/project/%v/repository/%v/artifact/%v",
a.Project.ProjectID, a.RepoName, a.Digest)}
return auditLog, nil
}
// Topic ...
func (a *ArtifactEvent) Topic() string {
return a.TargetTopic
}
// TagEvent info of tag related event
type TagEvent struct {
TargetTopic string
Project *models.Project
RepoName string
TagName string
Digest string
OccurAt time.Time
Operation string
Operator string
}
// ResolveToAuditLog ...
func (t *TagEvent) ResolveToAuditLog() (*model.AuditLog, error) {
auditLog := &model.AuditLog{
ProjectID: t.Project.ProjectID,
OpTime: t.OccurAt,
Operation: t.Operation,
Username: t.Operator,
ResourceType: "tag",
Resource: fmt.Sprintf("/api/project/%v/repository/%v/tag/%v",
t.Project.ProjectID, t.RepoName, t.TagName)}
log.Infof("create audit log %+v", auditLog)
return auditLog, nil
}
// Topic ...
func (t *TagEvent) Topic() string {
return t.TargetTopic
}
// ToImageEvent ...
func (t *TagEvent) ToImageEvent() *ImageEvent {
var eventType string
// convert tag operation to previous event type so that webhook can handle it
if t.Operation == "push" {
eventType = EventTypePushImage
} else if t.Operation == "pull" {
eventType = EventTypePullImage
} else if t.Operation == "delete" {
eventType = EventTypeDeleteImage
}
imgEvent := &ImageEvent{
EventType: eventType,
Project: t.Project,
Resource: []*ImgResource{{Tag: t.TagName}},
Operator: t.Operator,
OccurAt: t.OccurAt,
RepoName: t.RepoName,
}
return imgEvent
}
// Payload of notification event
type Payload struct {
Type string `json:"type"`

View File

@ -2,6 +2,23 @@ package model
// Define global topic names
const (
// TagTopic
PushTagTopic = "PushTagTopic"
PullTagTopic = "PullTagTopic"
DeleteTagTopic = "DeleteTagTopic"
// ProjectTopic ...
CreateProjectTopic = "CreateProjectTopic"
DeleteProjectTopic = "DeleteProjectTopic"
// RepositoryTopic ...
CreateRepositoryTopic = "CreateRepositoryTopic"
DeleteRepositoryTopic = "DeleteRepositoryTopic"
// ArtifactTopic
CreateArtifactTopic = "CreateArtifactTopic"
DeleteArtifactTopic = "DeleteArtifactTopic"
// PushImageTopic is topic for push image event
PushImageTopic = "OnPushImage"
// PullImageTopic is topic for pull image event

View File

@ -3,6 +3,7 @@ package topic
import (
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/notifier"
"github.com/goharbor/harbor/src/pkg/notifier/handler/auditlog"
"github.com/goharbor/harbor/src/pkg/notifier/handler/notification"
"github.com/goharbor/harbor/src/pkg/notifier/model"
)
@ -10,9 +11,19 @@ import (
// Subscribe topics
func init() {
handlersMap := map[string][]notifier.NotificationHandler{
model.PushImageTopic: {&notification.ImagePreprocessHandler{}},
model.PullImageTopic: {&notification.ImagePreprocessHandler{}},
model.DeleteImageTopic: {&notification.ImagePreprocessHandler{}},
model.PushTagTopic: {&auditlog.AuditHandler},
model.PullTagTopic: {&auditlog.AuditHandler},
model.DeleteTagTopic: {&auditlog.AuditHandler},
model.CreateProjectTopic: {&auditlog.AuditHandler},
model.DeleteProjectTopic: {&auditlog.AuditHandler},
model.CreateRepositoryTopic: {&auditlog.AuditHandler},
model.DeleteRepositoryTopic: {&auditlog.AuditHandler},
model.CreateArtifactTopic: {&auditlog.AuditHandler},
model.DeleteArtifactTopic: {&auditlog.AuditHandler},
model.WebhookTopic: {&notification.HTTPHandler{}},
model.UploadChartTopic: {&notification.ChartPreprocessHandler{}},
model.DownloadChartTopic: {&notification.ChartPreprocessHandler{}},