move notification handles and events metadata into api (#11085)

1, enable audit logs for notifications
2, move the handler and meatadata into API
3, use the notification middleware to send out notification

Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
Wang Yan 2020-03-16 16:56:34 +08:00 committed by GitHub
parent a83c78c1a5
commit fbb3226e85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1358 additions and 1574 deletions

View File

@ -20,7 +20,7 @@ import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/event/metadata"
"github.com/goharbor/harbor/src/api/tag"
"github.com/goharbor/harbor/src/internal"
"github.com/goharbor/harbor/src/internal/orm"
@ -30,7 +30,7 @@ import (
"github.com/goharbor/harbor/src/pkg/immutabletag/match"
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
"github.com/goharbor/harbor/src/pkg/label"
evt "github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/registry"
"github.com/goharbor/harbor/src/pkg/signature"
"github.com/opencontainers/go-digest"
@ -142,14 +142,14 @@ func (c *controller) Ensure(ctx context.Context, repository, digest string, tags
}
}
// fire event
e := &event.PushArtifactEventMetadata{
e := &metadata.PushArtifactEventMetadata{
Ctx: ctx,
Artifact: artifact,
}
if len(tags) > 0 {
e.Tag = tags[0]
}
evt.BuildAndPublish(e)
notification.AddEvent(ctx, e)
return created, artifact.ID, nil
}
@ -380,7 +380,7 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot bool) er
for _, tag := range art.Tags {
tags = append(tags, tag.Name)
}
evt.BuildAndPublish(&event.DeleteArtifactEventMetadata{
notification.AddEvent(ctx, &metadata.DeleteArtifactEventMetadata{
Ctx: ctx,
Artifact: &art.Artifact,
Tags: tags,

View File

@ -0,0 +1,66 @@
// Copyright Project Harbor 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 auditlog
import (
"context"
beegorm "github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/api/event"
"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"
)
// Handler - audit log handler
type Handler struct {
}
// AuditResolver - interface to resolve to AuditLog
type AuditResolver interface {
ResolveToAuditLog() (*am.AuditLog, error)
}
// Handle ...
func (h *Handler) Handle(value interface{}) error {
ctx := orm.NewContext(context.Background(), beegorm.NewOrm())
var auditLog *am.AuditLog
switch v := value.(type) {
case *event.PushArtifactEvent, *event.PullArtifactEvent, *event.DeleteArtifactEvent,
*event.DeleteRepositoryEvent, *event.CreateProjectEvent, *event.DeleteProjectEvent,
*event.DeleteTagEvent, *event.CreateTagEvent:
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 {
_, err := audit.Mgr.Create(ctx, auditLog)
if err != nil {
log.Debugf("add audit log err: %v", err)
}
}
return nil
}
// IsStateful ...
func (h *Handler) IsStateful() bool {
return false
}

View File

@ -1,20 +1,33 @@
// Copyright Project Harbor 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 auditlog
import (
"context"
"github.com/goharbor/harbor/src/api/event/metadata"
"github.com/goharbor/harbor/src/api/event"
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"
ne "github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"testing"
"time"
)
type MockAuditLogManager struct {
@ -55,7 +68,7 @@ type AuditLogHandlerTestSuite struct {
func (suite *AuditLogHandlerTestSuite) SetupSuite() {
common_dao.PrepareTestForPostgresSQL()
suite.logMgr = &MockAuditLogManager{}
suite.auditLogHandler = &Handler{AuditLogMgr: suite.logMgr}
suite.auditLogHandler = &Handler{}
log.SetLevel(log.DebugLevel)
}
@ -66,26 +79,13 @@ func (suite *AuditLogHandlerTestSuite) TestSubscribeTagEvent() {
// sample code to use the event framework.
notifier.Subscribe(nm.PushTagTopic, suite.auditLogHandler)
notifier.Subscribe(event.TopicCreateProject, 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(),
ne.BuildAndPublish(&metadata.CreateProjectEventMetadata{
ProjectID: 1,
Project: "test",
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)

View File

@ -0,0 +1,42 @@
package handler
import (
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/event/handler/auditlog"
"github.com/goharbor/harbor/src/api/event/handler/replication"
"github.com/goharbor/harbor/src/api/event/handler/webhook/artifact"
"github.com/goharbor/harbor/src/api/event/handler/webhook/chart"
"github.com/goharbor/harbor/src/api/event/handler/webhook/quota"
"github.com/goharbor/harbor/src/api/event/handler/webhook/scan"
"github.com/goharbor/harbor/src/pkg/notifier"
)
func init() {
// notification
notifier.Subscribe(event.TopicPushArtifact, &artifact.Handler{})
notifier.Subscribe(event.TopicPullArtifact, &artifact.Handler{})
notifier.Subscribe(event.TopicDeleteArtifact, &artifact.Handler{})
notifier.Subscribe(event.TopicUploadChart, &chart.Handler{})
notifier.Subscribe(event.TopicDeleteChart, &chart.Handler{})
notifier.Subscribe(event.TopicDownloadChart, &chart.Handler{})
notifier.Subscribe(event.TopicQuotaExceed, &quota.Handler{})
notifier.Subscribe(event.TopicQuotaWarning, &quota.Handler{})
notifier.Subscribe(event.TopicScanningFailed, &scan.Handler{})
notifier.Subscribe(event.TopicScanningCompleted, &scan.Handler{})
notifier.Subscribe(event.TopicDeleteArtifact, &scan.DelArtHandler{})
// replication
notifier.Subscribe(event.TopicPushArtifact, &replication.Handler{})
notifier.Subscribe(event.TopicDeleteArtifact, &replication.Handler{})
notifier.Subscribe(event.TopicCreateTag, &replication.Handler{})
// audit logs
notifier.Subscribe(event.TopicPushArtifact, &auditlog.Handler{})
notifier.Subscribe(event.TopicPullArtifact, &auditlog.Handler{})
notifier.Subscribe(event.TopicDeleteArtifact, &auditlog.Handler{})
notifier.Subscribe(event.TopicCreateProject, &auditlog.Handler{})
notifier.Subscribe(event.TopicDeleteProject, &auditlog.Handler{})
notifier.Subscribe(event.TopicDeleteRepository, &auditlog.Handler{})
notifier.Subscribe(event.TopicCreateTag, &auditlog.Handler{})
notifier.Subscribe(event.TopicDeleteTag, &auditlog.Handler{})
}

View File

@ -1,3 +1,4 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -11,12 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package handler
package replication
import (
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/notifier"
"github.com/goharbor/harbor/src/pkg/project"
"github.com/goharbor/harbor/src/replication"
repevent "github.com/goharbor/harbor/src/replication/event"
@ -24,20 +24,12 @@ import (
"strconv"
)
func init() {
handler := &replicationHandler{
proMgr: project.Mgr,
}
notifier.Subscribe(event.TopicPushArtifact, handler)
notifier.Subscribe(event.TopicDeleteArtifact, handler)
notifier.Subscribe(event.TopicCreateTag, handler)
// Handler ...
type Handler struct {
}
type replicationHandler struct {
proMgr project.Manager
}
func (r *replicationHandler) Handle(value interface{}) error {
// Handle ...
func (r *Handler) Handle(value interface{}) error {
pushArtEvent, ok := value.(*event.PushArtifactEvent)
if ok {
return r.handlePushArtifact(pushArtEvent)
@ -53,16 +45,17 @@ func (r *replicationHandler) Handle(value interface{}) error {
return nil
}
func (r *replicationHandler) IsStateful() bool {
// IsStateful ...
func (r *Handler) IsStateful() bool {
return false
}
// TODO handle create tag
func (r *replicationHandler) handlePushArtifact(event *event.PushArtifactEvent) error {
func (r *Handler) handlePushArtifact(event *event.PushArtifactEvent) error {
art := event.Artifact
public := false
project, err := r.proMgr.Get(art.ProjectID)
project, err := project.Mgr.Get(art.ProjectID)
if err == nil && project != nil {
public = project.IsPublic()
} else {
@ -84,7 +77,7 @@ func (r *replicationHandler) handlePushArtifact(event *event.PushArtifactEvent)
{
Type: art.Type,
Digest: art.Digest,
Tags: []string{event.Tag},
Tags: event.Tags,
}},
},
},
@ -92,7 +85,7 @@ func (r *replicationHandler) handlePushArtifact(event *event.PushArtifactEvent)
return replication.EventHandler.Handle(e)
}
func (r *replicationHandler) handleDeleteArtifact(event *event.DeleteArtifactEvent) error {
func (r *Handler) handleDeleteArtifact(event *event.DeleteArtifactEvent) error {
art := event.Artifact
e := &repevent.Event{
Type: repevent.EventTypeArtifactDelete,
@ -115,10 +108,10 @@ func (r *replicationHandler) handleDeleteArtifact(event *event.DeleteArtifactEve
return replication.EventHandler.Handle(e)
}
func (r *replicationHandler) handleCreateTag(event *event.CreateTagEvent) error {
func (r *Handler) handleCreateTag(event *event.CreateTagEvent) error {
art := event.AttachedArtifact
public := false
project, err := r.proMgr.Get(art.ProjectID)
project, err := project.Mgr.Get(art.ProjectID)
if err == nil && project != nil {
public = project.IsPublic()
} else {

View File

@ -0,0 +1,66 @@
package util
import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/pkg/notifier/event"
notifyModel "github.com/goharbor/harbor/src/pkg/notifier/model"
"strings"
)
// SendHookWithPolicies send hook by publishing topic of specified target type(notify type)
func SendHookWithPolicies(policies []*models.NotificationPolicy, payload *notifyModel.Payload, eventType string) error {
// if global notification configured disabled, return directly
if !config.NotificationEnable() {
log.Debug("notification feature is not enabled")
return nil
}
errRet := false
for _, ply := range policies {
targets := ply.Targets
for _, target := range targets {
evt := &event.Event{}
hookMetadata := &event.HookMetaData{
EventType: eventType,
PolicyID: ply.ID,
Payload: payload,
Target: &target,
}
// It should never affect evaluating other policies when one is failed, but error should return
if err := evt.Build(hookMetadata); err == nil {
if err := evt.Publish(); err != nil {
errRet = true
log.Errorf("failed to publish hook notify event: %v", err)
}
} else {
errRet = true
log.Errorf("failed to build hook notify event metadata: %v", err)
}
log.Debugf("published image event %s by topic %s", payload.Type, target.Type)
}
}
if errRet {
return errors.New("failed to trigger some of the events")
}
return nil
}
// GetNameFromImgRepoFullName gets image name from repo full name with format `repoName/imageName`
func GetNameFromImgRepoFullName(repo string) string {
idx := strings.Index(repo, "/")
return repo[idx+1:]
}
// BuildImageResourceURL ...
func BuildImageResourceURL(repoName, tag string) (string, error) {
extURL, err := config.ExtURL()
if err != nil {
return "", fmt.Errorf("get external endpoint failed: %v", err)
}
resURL := fmt.Sprintf("%s/%s:%s", extURL, repoName, tag)
return resURL, nil
}

View File

@ -0,0 +1,142 @@
// Copyright Project Harbor 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 artifact
import (
"context"
"fmt"
beegorm "github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/event/handler/util"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/internal/orm"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/notifier/model"
notifyModel "github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/goharbor/harbor/src/pkg/project"
"github.com/goharbor/harbor/src/pkg/repository"
)
// Handler preprocess artifact event data
type Handler struct {
project *models.Project
}
// Handle preprocess artifact event data and then publish hook event
func (a *Handler) Handle(value interface{}) error {
switch v := value.(type) {
case *event.PushArtifactEvent:
return a.handle(v.ArtifactEvent)
case *event.PullArtifactEvent:
return a.handle(v.ArtifactEvent)
case *event.DeleteArtifactEvent:
return a.handle(v.ArtifactEvent)
default:
log.Errorf("Can not handler this event type! %#v", v)
}
return nil
}
// IsStateful ...
func (a *Handler) IsStateful() bool {
return false
}
func (a *Handler) handle(event *event.ArtifactEvent) error {
var err error
a.project, err = project.Mgr.Get(event.Artifact.ProjectID)
if err != nil {
log.Errorf("failed to get project:%d, error: %v", event.Artifact.ProjectID, err)
return err
}
policies, err := notification.PolicyMgr.GetRelatedPolices(a.project.ProjectID, event.EventType)
if err != nil {
log.Errorf("failed to find policy for %s event: %v", event.EventType, err)
return err
}
if len(policies) == 0 {
log.Debugf("cannot find policy for %s event: %v", event.EventType, event)
return nil
}
payload, err := a.constructArtifactPayload(event)
if err != nil {
return err
}
err = util.SendHookWithPolicies(policies, payload, event.EventType)
if err != nil {
return err
}
return nil
}
func (a *Handler) constructArtifactPayload(event *event.ArtifactEvent) (*model.Payload, error) {
repoName := event.Repository
if repoName == "" {
return nil, fmt.Errorf("invalid %s event with empty repo name", event.EventType)
}
repoType := models.ProjectPrivate
if a.project.IsPublic() {
repoType = models.ProjectPublic
}
imageName := util.GetNameFromImgRepoFullName(repoName)
payload := &notifyModel.Payload{
Type: event.EventType,
OccurAt: event.OccurAt.Unix(),
EventData: &notifyModel.EventData{
Repository: &notifyModel.Repository{
Name: imageName,
Namespace: a.project.Name,
RepoFullName: repoName,
RepoType: repoType,
},
},
Operator: event.Operator,
}
ctx := orm.NewContext(context.Background(), beegorm.NewOrm())
repoRecord, err := repository.Mgr.GetByName(ctx, repoName)
if err != nil {
log.Errorf("failed to get repository with name %s: %v", repoName, err)
return nil, err
}
payload.EventData.Repository.DateCreated = repoRecord.CreationTime.Unix()
var reference string
if len(event.Tags) == 0 {
reference = event.Artifact.Digest
} else {
reference = event.Tags[0]
}
resURL, err := util.BuildImageResourceURL(repoName, reference)
if err != nil {
log.Errorf("get resource URL failed: %v", err)
return nil, err
}
resource := &notifyModel.Resource{
Tag: reference,
Digest: event.Artifact.Digest,
ResourceURL: resURL,
}
payload.EventData.Resources = append(payload.EventData.Resources, resource)
return payload, nil
}

View File

@ -12,21 +12,4 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package event
import (
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/notifier"
"github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/pkg/errors"
)
// Init the events for scan
func Init() {
log.Debugf("Subscribe topic %s for cascade deletion of scan reports", model.DeleteImageTopic)
err := notifier.Subscribe(model.DeleteImageTopic, NewOnDelImageHandler())
if err != nil {
log.Error(errors.Wrap(err, "register on delete image handler: init: scan"))
}
}
package artifact

View File

@ -1,28 +1,39 @@
package notification
// Copyright Project Harbor 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 chart
import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/event/handler/util"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/goharbor/harbor/src/pkg/project"
)
// ChartPreprocessHandler preprocess chart event data
type ChartPreprocessHandler struct {
// Handler preprocess chart event data
type Handler struct {
}
// Handle preprocess chart event data and then publish hook event
func (cph *ChartPreprocessHandler) Handle(value interface{}) error {
// if global notification configured disabled, return directly
if !config.NotificationEnable() {
log.Debug("notification feature is not enabled")
return nil
}
chartEvent, ok := value.(*model.ChartEvent)
func (cph *Handler) Handle(value interface{}) error {
chartEvent, ok := value.(*event.ChartEvent)
if !ok {
return errors.New("invalid chart event type")
}
@ -31,7 +42,7 @@ func (cph *ChartPreprocessHandler) Handle(value interface{}) error {
return fmt.Errorf("data miss in chart event: %v", chartEvent)
}
project, err := config.GlobalProjectMgr.Get(chartEvent.ProjectName)
project, err := project.Mgr.Get(chartEvent.ProjectName)
if err != nil {
log.Errorf("failed to find project[%s] for chart event: %v", chartEvent.ProjectName, err)
return err
@ -55,7 +66,7 @@ func (cph *ChartPreprocessHandler) Handle(value interface{}) error {
return err
}
err = sendHookWithPolicies(policies, payload, chartEvent.EventType)
err = util.SendHookWithPolicies(policies, payload, chartEvent.EventType)
if err != nil {
return err
}
@ -64,11 +75,11 @@ func (cph *ChartPreprocessHandler) Handle(value interface{}) error {
}
// IsStateful ...
func (cph *ChartPreprocessHandler) IsStateful() bool {
func (cph *Handler) IsStateful() bool {
return false
}
func constructChartPayload(event *model.ChartEvent, project *models.Project) (*model.Payload, error) {
func constructChartPayload(event *event.ChartEvent, project *models.Project) (*model.Payload, error) {
repoType := models.ProjectPrivate
if project.IsPublic() {
repoType = models.ProjectPublic

View File

@ -1,76 +1,41 @@
package notification
// Copyright Project Harbor 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 chart
import (
"github.com/goharbor/harbor/src/api/event"
common_dao "github.com/goharbor/harbor/src/common/dao"
"testing"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/notifier/model"
testingnotification "github.com/goharbor/harbor/src/testing/pkg/notification"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type fakedPolicyMgr struct {
}
func (f *fakedPolicyMgr) Create(*models.NotificationPolicy) (int64, error) {
return 0, nil
}
func (f *fakedPolicyMgr) List(id int64) ([]*models.NotificationPolicy, error) {
return nil, nil
}
func (f *fakedPolicyMgr) Get(id int64) (*models.NotificationPolicy, error) {
return nil, nil
}
func (f *fakedPolicyMgr) GetByNameAndProjectID(string, int64) (*models.NotificationPolicy, error) {
return nil, nil
}
func (f *fakedPolicyMgr) Update(*models.NotificationPolicy) error {
return nil
}
func (f *fakedPolicyMgr) Delete(int64) error {
return nil
}
func (f *fakedPolicyMgr) Test(*models.NotificationPolicy) error {
return nil
}
func (f *fakedPolicyMgr) GetRelatedPolices(id int64, eventType string) ([]*models.NotificationPolicy, error) {
return []*models.NotificationPolicy{
{
ID: 1,
EventTypes: []string{
model.EventTypeUploadChart,
model.EventTypeDownloadChart,
model.EventTypeDeleteChart,
model.EventTypeScanningCompleted,
model.EventTypeScanningFailed,
},
Targets: []models.EventTarget{
{
Type: "http",
Address: "http://127.0.0.1:8080",
},
},
},
}, nil
}
func TestChartPreprocessHandler_Handle(t *testing.T) {
common_dao.PrepareTestForPostgresSQL()
PolicyMgr := notification.PolicyMgr
defer func() {
notification.PolicyMgr = PolicyMgr
}()
notification.PolicyMgr = &fakedPolicyMgr{}
notification.PolicyMgr = &testingnotification.FakedPolicyMgr{}
handler := &ChartPreprocessHandler{}
handler := &Handler{}
config.Init()
name := "project_for_test_chart_event_preprocess"
@ -99,23 +64,23 @@ func TestChartPreprocessHandler_Handle(t *testing.T) {
wantErr bool
}{
{
name: "ChartPreprocessHandler Want Error 1",
name: "Handler Want Error 1",
args: args{
data: nil,
},
wantErr: true,
},
{
name: "ChartPreprocessHandler Want Error 2",
name: "Handler Want Error 2",
args: args{
data: &model.ChartEvent{},
data: &event.ChartEvent{},
},
wantErr: true,
},
{
name: "ChartPreprocessHandler Want Error 3",
name: "Handler Want Error 3",
args: args{
data: &model.ChartEvent{
data: &event.ChartEvent{
Versions: []string{
"v1.2.1",
},
@ -125,9 +90,9 @@ func TestChartPreprocessHandler_Handle(t *testing.T) {
wantErr: true,
},
{
name: "ChartPreprocessHandler Want Error 4",
name: "Handler Want Error 4",
args: args{
data: &model.ChartEvent{
data: &event.ChartEvent{
Versions: []string{
"v1.2.1",
},
@ -138,9 +103,9 @@ func TestChartPreprocessHandler_Handle(t *testing.T) {
wantErr: true,
},
{
name: "ChartPreprocessHandler Want Error 5",
name: "Handler Want Error 5",
args: args{
data: &model.ChartEvent{
data: &event.ChartEvent{
Versions: []string{
"v1.2.1",
},
@ -166,6 +131,6 @@ func TestChartPreprocessHandler_Handle(t *testing.T) {
}
func TestChartPreprocessHandler_IsStateful(t *testing.T) {
handler := &ChartPreprocessHandler{}
handler := &Handler{}
assert.False(t, handler.IsStateful())
}

View File

@ -1,28 +1,39 @@
package notification
// Copyright Project Harbor 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 quota
import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/event/handler/util"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/notifier/model"
notifyModel "github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/goharbor/harbor/src/pkg/project"
)
// QuotaPreprocessHandler preprocess image event data
type QuotaPreprocessHandler struct {
// Handler preprocess image event data
type Handler struct {
}
// Handle ...
func (qp *QuotaPreprocessHandler) Handle(value interface{}) error {
if !config.NotificationEnable() {
log.Debug("notification feature is not enabled")
return nil
}
quotaEvent, ok := value.(*model.QuotaEvent)
func (qp *Handler) Handle(value interface{}) error {
quotaEvent, ok := value.(*event.QuotaEvent)
if !ok {
return errors.New("invalid quota event type")
}
@ -30,14 +41,11 @@ func (qp *QuotaPreprocessHandler) Handle(value interface{}) error {
return fmt.Errorf("nil quota event")
}
project, err := config.GlobalProjectMgr.Get(quotaEvent.Project.Name)
project, err := project.Mgr.Get(quotaEvent.Project.Name)
if err != nil {
log.Errorf("failed to get project:%s, error: %v", quotaEvent.Project.Name, err)
return err
}
if project == nil {
return fmt.Errorf("project not found of quota event: %s", quotaEvent.Project.Name)
}
policies, err := notification.PolicyMgr.GetRelatedPolices(project.ProjectID, quotaEvent.EventType)
if err != nil {
log.Errorf("failed to find policy for %s event: %v", quotaEvent.EventType, err)
@ -53,7 +61,7 @@ func (qp *QuotaPreprocessHandler) Handle(value interface{}) error {
return err
}
err = sendHookWithPolicies(policies, payload, quotaEvent.EventType)
err = util.SendHookWithPolicies(policies, payload, quotaEvent.EventType)
if err != nil {
return err
}
@ -61,11 +69,11 @@ func (qp *QuotaPreprocessHandler) Handle(value interface{}) error {
}
// IsStateful ...
func (qp *QuotaPreprocessHandler) IsStateful() bool {
func (qp *Handler) IsStateful() bool {
return false
}
func constructQuotaPayload(event *model.QuotaEvent) (*model.Payload, error) {
func constructQuotaPayload(event *event.QuotaEvent) (*model.Payload, error) {
repoName := event.RepoName
if repoName == "" {
return nil, fmt.Errorf("invalid %s event with empty repo name", event.EventType)
@ -76,7 +84,7 @@ func constructQuotaPayload(event *model.QuotaEvent) (*model.Payload, error) {
repoType = models.ProjectPublic
}
imageName := getNameFromImgRepoFullName(repoName)
imageName := util.GetNameFromImgRepoFullName(repoName)
quotaCustom := make(map[string]string)
quotaCustom["Details"] = event.Msg

View File

@ -1,6 +1,22 @@
package notification
// Copyright Project Harbor 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 quota
import (
"github.com/goharbor/harbor/src/api/event"
common_dao "github.com/goharbor/harbor/src/common/dao"
"testing"
"time"
@ -11,6 +27,7 @@ import (
"github.com/goharbor/harbor/src/pkg/notification/policy"
"github.com/goharbor/harbor/src/pkg/notifier"
"github.com/goharbor/harbor/src/pkg/notifier/model"
testing_notification "github.com/goharbor/harbor/src/testing/pkg/notification"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
@ -19,7 +36,7 @@ import (
type QuotaPreprocessHandlerSuite struct {
suite.Suite
om policy.Manager
evt *model.QuotaEvent
evt *event.QuotaEvent
}
// TestQuotaPreprocessHandler ...
@ -29,17 +46,18 @@ func TestQuotaPreprocessHandler(t *testing.T) {
// SetupSuite prepares env for test suite.
func (suite *QuotaPreprocessHandlerSuite) SetupSuite() {
common_dao.PrepareTestForPostgresSQL()
cfg := map[string]interface{}{
common.NotificationEnable: true,
}
config.InitWithSettings(cfg)
res := &model.ImgResource{
res := &event.ImgResource{
Digest: "sha256:abcd",
Tag: "latest",
}
suite.evt = &model.QuotaEvent{
EventType: model.EventTypeProjectQuota,
suite.evt = &event.QuotaEvent{
EventType: event.TopicQuotaExceed,
OccurAt: time.Now().UTC(),
RepoName: "hello-world",
Resource: res,
@ -51,7 +69,7 @@ func (suite *QuotaPreprocessHandlerSuite) SetupSuite() {
}
suite.om = notification.PolicyMgr
mp := &fakedPolicyMgr{}
mp := &testing_notification.FakedPolicyMgr{}
notification.PolicyMgr = mp
h := &MockHandler{}
@ -67,7 +85,7 @@ func (suite *QuotaPreprocessHandlerSuite) TearDownSuite() {
// TestHandle ...
func (suite *QuotaPreprocessHandlerSuite) TestHandle() {
handler := &QuotaPreprocessHandler{}
handler := &Handler{}
err := handler.Handle(suite.evt)
suite.NoError(err)
}

View File

@ -0,0 +1,78 @@
// Copyright Project Harbor 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 scan
import (
"context"
bo "github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/scan"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/internal/orm"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/pkg/errors"
)
// DelArtHandler is a handler to listen to the internal delete image event.
type DelArtHandler struct {
}
// Handle ...
func (o *DelArtHandler) Handle(value interface{}) error {
if value == nil {
return errors.New("delete image event handler: nil value ")
}
evt, ok := value.(*event.ArtifactEvent)
if !ok {
return errors.New("delete image event handler: malformed image event model")
}
log.Debugf("clear the scan reports as receiving event %s", evt.EventType)
digests := make([]string, 0)
query := &q.Query{
Keywords: make(map[string]interface{}),
}
ctx := orm.NewContext(context.TODO(), bo.NewOrm())
// Check if it is safe to delete the reports.
query.Keywords["digest"] = evt.Artifact.Digest
l, err := artifact.Ctl.List(ctx, query, nil)
if err != nil && len(l) != 0 {
// Just logged
log.Error(errors.Wrap(err, "delete image event handler"))
// Passed for safe consideration
} else {
if len(l) == 0 {
digests = append(digests, evt.Artifact.Digest)
log.Debugf("prepare to remove the scan report linked with artifact: %s", evt.Artifact.Digest)
}
}
if err := scan.DefaultController.DeleteReports(digests...); err != nil {
return errors.Wrap(err, "delete image event handler")
}
return nil
}
// IsStateful ...
func (o *DelArtHandler) IsStateful() bool {
return false
}

View File

@ -1,41 +1,51 @@
package notification
// Copyright Project Harbor 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 scan
import (
"context"
"time"
o "github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/event/handler/util"
"github.com/goharbor/harbor/src/internal/orm"
"github.com/goharbor/harbor/src/pkg/notifier/model"
"time"
"github.com/goharbor/harbor/src/api/scan"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/internal/orm"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/goharbor/harbor/src/pkg/project"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/pkg/errors"
)
// ScanImagePreprocessHandler preprocess chart event data
type ScanImagePreprocessHandler struct {
// Handler preprocess scan artifact event
type Handler struct {
}
// Handle preprocess chart event data and then publish hook event
func (si *ScanImagePreprocessHandler) Handle(value interface{}) error {
// if global notification configured disabled, return directly
if !config.NotificationEnable() {
log.Debug("notification feature is not enabled")
return nil
}
func (si *Handler) Handle(value interface{}) error {
if value == nil {
return errors.New("empty scan image event")
return errors.New("empty scan artifact event")
}
e, ok := value.(*model.ScanImageEvent)
e, ok := value.(*event.ScanImageEvent)
if !ok {
return errors.New("invalid scan image event type")
return errors.New("invalid scan artifact event type")
}
policies, err := notification.PolicyMgr.GetRelatedPolices(e.Artifact.NamespaceID, e.EventType)
@ -50,7 +60,7 @@ func (si *ScanImagePreprocessHandler) Handle(value interface{}) error {
}
// Get project
project, err := config.GlobalProjectMgr.Get(e.Artifact.NamespaceID)
project, err := project.Mgr.Get(e.Artifact.NamespaceID)
if err != nil {
return errors.Wrap(err, "scan preprocess handler")
}
@ -60,7 +70,7 @@ func (si *ScanImagePreprocessHandler) Handle(value interface{}) error {
return errors.Wrap(err, "scan preprocess handler")
}
err = sendHookWithPolicies(policies, payload, e.EventType)
err = util.SendHookWithPolicies(policies, payload, e.EventType)
if err != nil {
return errors.Wrap(err, "scan preprocess handler")
}
@ -69,17 +79,17 @@ func (si *ScanImagePreprocessHandler) Handle(value interface{}) error {
}
// IsStateful ...
func (si *ScanImagePreprocessHandler) IsStateful() bool {
func (si *Handler) IsStateful() bool {
return false
}
func constructScanImagePayload(event *model.ScanImageEvent, project *models.Project) (*model.Payload, error) {
func constructScanImagePayload(event *event.ScanImageEvent, project *models.Project) (*model.Payload, error) {
repoType := models.ProjectPrivate
if project.IsPublic() {
repoType = models.ProjectPublic
}
repoName := getNameFromImgRepoFullName(event.Artifact.Repository)
repoName := util.GetNameFromImgRepoFullName(event.Artifact.Repository)
payload := &model.Payload{
Type: event.EventType,
@ -95,12 +105,7 @@ func constructScanImagePayload(event *model.ScanImageEvent, project *models.Proj
Operator: event.Operator,
}
extURL, err := config.ExtURL()
if err != nil {
return nil, errors.Wrap(err, "construct scan payload")
}
resURL, err := buildImageResourceURL(extURL, event.Artifact.Repository, event.Artifact.Tag)
resURL, err := util.BuildImageResourceURL(event.Artifact.Repository, event.Artifact.Tag)
if err != nil {
return nil, errors.Wrap(err, "construct scan payload")
}
@ -116,7 +121,6 @@ func constructScanImagePayload(event *model.ScanImageEvent, project *models.Proj
if err != nil {
return nil, err
}
// Wait for reasonable time to make sure the report is ready
// Interval=500ms and total time = 5s
// If the report is still not ready in the total time, then failed at then

View File

@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
package scan
import (
"github.com/goharbor/harbor/src/api/event"
common_dao "github.com/goharbor/harbor/src/common/dao"
"testing"
"time"
@ -32,6 +34,7 @@ import (
artifacttesting "github.com/goharbor/harbor/src/testing/api/artifact"
scantesting "github.com/goharbor/harbor/src/testing/api/scan"
"github.com/goharbor/harbor/src/testing/mock"
notificationtesting "github.com/goharbor/harbor/src/testing/pkg/notification"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
@ -42,7 +45,7 @@ type ScanImagePreprocessHandlerSuite struct {
om policy.Manager
pid int64
evt *model.ScanImageEvent
evt *event.ScanImageEvent
c sc.Controller
artifactCtl artifact.Controller
}
@ -54,6 +57,7 @@ func TestScanImagePreprocessHandler(t *testing.T) {
// SetupSuite prepares env for test suite.
func (suite *ScanImagePreprocessHandlerSuite) SetupSuite() {
common_dao.PrepareTestForPostgresSQL()
cfg := map[string]interface{}{
common.NotificationEnable: true,
}
@ -66,8 +70,8 @@ func (suite *ScanImagePreprocessHandlerSuite) SetupSuite() {
Digest: "digest-code",
MimeType: v1.MimeTypeDockerArtifact,
}
suite.evt = &model.ScanImageEvent{
EventType: model.EventTypeScanningCompleted,
suite.evt = &event.ScanImageEvent{
EventType: event.TopicScanningCompleted,
OccurAt: time.Now().UTC(),
Operator: "admin",
Artifact: a,
@ -104,7 +108,7 @@ func (suite *ScanImagePreprocessHandlerSuite) SetupSuite() {
artifact.Ctl = artifactCtl
suite.om = notification.PolicyMgr
mp := &fakedPolicyMgr{}
mp := &notificationtesting.FakedPolicyMgr{}
notification.PolicyMgr = mp
h := &MockHTTPHandler{}
@ -122,7 +126,7 @@ func (suite *ScanImagePreprocessHandlerSuite) TearDownSuite() {
// TestHandle ...
func (suite *ScanImagePreprocessHandlerSuite) TestHandle() {
handler := &ScanImagePreprocessHandler{}
handler := &Handler{}
err := handler.Handle(suite.evt)
suite.NoError(err)

View File

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package event
package metadata
import (
"context"
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/notifier/event"
@ -31,17 +32,20 @@ type PushArtifactEventMetadata struct {
// Resolve to the event from the metadata
func (p *PushArtifactEventMetadata) Resolve(event *event.Event) error {
data := &PushArtifactEvent{
Repository: p.Artifact.RepositoryName,
Artifact: p.Artifact,
Tag: p.Tag,
OccurAt: time.Now(),
data := &event2.PushArtifactEvent{
ArtifactEvent: &event2.ArtifactEvent{
EventType: event2.TopicPushArtifact,
Repository: p.Artifact.RepositoryName,
Artifact: p.Artifact,
Tags: []string{p.Tag},
OccurAt: time.Now(),
},
}
ctx, exist := security.FromContext(p.Ctx)
if exist {
data.Operator = ctx.GetUsername()
}
event.Topic = TopicPushArtifact
event.Topic = event2.TopicPushArtifact
event.Data = data
return nil
}
@ -55,17 +59,20 @@ type PullArtifactEventMetadata struct {
// Resolve to the event from the metadata
func (p *PullArtifactEventMetadata) Resolve(event *event.Event) error {
data := &PullArtifactEvent{
Repository: p.Artifact.RepositoryName,
Artifact: p.Artifact,
Tag: p.Tag,
OccurAt: time.Now(),
data := &event2.PullArtifactEvent{
ArtifactEvent: &event2.ArtifactEvent{
EventType: event2.TopicPullArtifact,
Repository: p.Artifact.RepositoryName,
Artifact: p.Artifact,
Tags: []string{p.Tag},
OccurAt: time.Now(),
},
}
ctx, exist := security.FromContext(p.Ctx)
if exist {
data.Operator = ctx.GetUsername()
}
event.Topic = TopicPullArtifact
event.Topic = event2.TopicPullArtifact
event.Data = data
return nil
}
@ -79,17 +86,20 @@ type DeleteArtifactEventMetadata struct {
// Resolve to the event from the metadata
func (d *DeleteArtifactEventMetadata) Resolve(event *event.Event) error {
data := &DeleteArtifactEvent{
Repository: d.Artifact.RepositoryName,
Artifact: d.Artifact,
Tags: d.Tags,
OccurAt: time.Now(),
data := &event2.DeleteArtifactEvent{
ArtifactEvent: &event2.ArtifactEvent{
EventType: event2.TopicDeleteArtifact,
Repository: d.Artifact.RepositoryName,
Artifact: d.Artifact,
Tags: d.Tags,
OccurAt: time.Now(),
},
}
ctx, exist := security.FromContext(d.Ctx)
if exist {
data.Operator = ctx.GetUsername()
}
event.Topic = TopicDeleteArtifact
event.Topic = event2.TopicDeleteArtifact
event.Data = data
return nil
}

View File

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package event
package metadata
import (
"context"
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/stretchr/testify/suite"
@ -35,12 +36,12 @@ func (a *artifactEventTestSuite) TestResolveOfPushArtifactEventMetadata() {
}
err := metadata.Resolve(e)
a.Require().Nil(err)
a.Equal(TopicPushArtifact, e.Topic)
a.Equal(event2.TopicPushArtifact, e.Topic)
a.Require().NotNil(e.Data)
data, ok := e.Data.(*PushArtifactEvent)
data, ok := e.Data.(*event2.PushArtifactEvent)
a.Require().True(ok)
a.Equal(int64(1), data.Artifact.ID)
a.Equal("latest", data.Tag)
a.Equal("latest", data.Tags[0])
}
func (a *artifactEventTestSuite) TestResolveOfPullArtifactEventMetadata() {
@ -52,12 +53,12 @@ func (a *artifactEventTestSuite) TestResolveOfPullArtifactEventMetadata() {
}
err := metadata.Resolve(e)
a.Require().Nil(err)
a.Equal(TopicPullArtifact, e.Topic)
a.Equal(event2.TopicPullArtifact, e.Topic)
a.Require().NotNil(e.Data)
data, ok := e.Data.(*PullArtifactEvent)
data, ok := e.Data.(*event2.PullArtifactEvent)
a.Require().True(ok)
a.Equal(int64(1), data.Artifact.ID)
a.Equal("latest", data.Tag)
a.Equal("latest", data.Tags[0])
}
func (a *artifactEventTestSuite) TestResolveOfDeleteArtifactEventMetadata() {
@ -69,9 +70,9 @@ func (a *artifactEventTestSuite) TestResolveOfDeleteArtifactEventMetadata() {
}
err := metadata.Resolve(e)
a.Require().Nil(err)
a.Equal(TopicDeleteArtifact, e.Topic)
a.Equal(event2.TopicDeleteArtifact, e.Topic)
a.Require().NotNil(e.Data)
data, ok := e.Data.(*DeleteArtifactEvent)
data, ok := e.Data.(*event2.DeleteArtifactEvent)
a.Require().True(ok)
a.Equal(int64(1), data.Artifact.ID)
a.Require().Len(data.Tags, 1)

View File

@ -0,0 +1,75 @@
package metadata
import (
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"time"
)
// ChartMetaData defines meta data of chart event
type ChartMetaData struct {
ProjectName string
ChartName string
Versions []string
OccurAt time.Time
Operator string
}
func (cmd *ChartMetaData) convert(evt *event2.ChartEvent) {
evt.ProjectName = cmd.ProjectName
evt.OccurAt = cmd.OccurAt
evt.Operator = cmd.Operator
evt.ChartName = cmd.ChartName
evt.Versions = cmd.Versions
}
// ChartUploadMetaData defines meta data of chart upload event
type ChartUploadMetaData struct {
ChartMetaData
}
// Resolve chart uploading metadata into common chart event
func (cu *ChartUploadMetaData) Resolve(event *event.Event) error {
data := &event2.ChartEvent{
EventType: event2.TopicUploadChart,
}
cu.convert(data)
event.Topic = event2.TopicUploadChart
event.Data = data
return nil
}
// ChartDownloadMetaData defines meta data of chart download event
type ChartDownloadMetaData struct {
ChartMetaData
}
// Resolve chart download metadata into common chart event
func (cd *ChartDownloadMetaData) Resolve(evt *event.Event) error {
data := &event2.ChartEvent{
EventType: event2.TopicDownloadChart,
}
cd.convert(data)
evt.Topic = event2.TopicDownloadChart
evt.Data = data
return nil
}
// ChartDeleteMetaData defines meta data of chart delete event
type ChartDeleteMetaData struct {
ChartMetaData
}
// Resolve chart delete metadata into common chart event
func (cd *ChartDeleteMetaData) Resolve(evt *event.Event) error {
data := &event2.ChartEvent{
EventType: event2.TopicDeleteChart,
}
cd.convert(data)
evt.Topic = event2.TopicDeleteChart
evt.Data = data
return nil
}

View File

@ -0,0 +1,71 @@
// Copyright Project Harbor 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 metadata
import (
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/stretchr/testify/suite"
"testing"
"time"
)
type chartEventTestSuite struct {
suite.Suite
}
func (r *chartEventTestSuite) TestResolveOfUploadChartEventMetadata() {
e := &event.Event{}
metadata := &ChartUploadMetaData{
ChartMetaData{
ProjectName: "library",
ChartName: "redis-v2.0",
Versions: nil,
OccurAt: time.Time{},
Operator: "admin",
},
}
err := metadata.Resolve(e)
r.Require().Nil(err)
r.Equal(event2.TopicUploadChart, e.Topic)
r.Require().NotNil(e.Data)
data, ok := e.Data.(*event2.ChartEvent)
r.Require().True(ok)
r.Equal("redis-v2.0", data.ChartName)
}
func (r *chartEventTestSuite) TestResolveOfDownloadChartEventMetadata() {
e := &event.Event{}
metadata := &ChartDownloadMetaData{
ChartMetaData{
ProjectName: "library",
ChartName: "redis-v2.0",
Versions: nil,
OccurAt: time.Time{},
Operator: "admin",
},
}
err := metadata.Resolve(e)
r.Require().Nil(err)
r.Equal(event2.TopicDownloadChart, e.Topic)
r.Require().NotNil(e.Data)
data, ok := e.Data.(*event2.ChartEvent)
r.Require().True(ok)
r.Equal("redis-v2.0", data.ChartName)
}
func TestChartEventTestSuite(t *testing.T) {
suite.Run(t, &chartEventTestSuite{})
}

View File

@ -12,43 +12,50 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package event
package metadata
import (
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"time"
)
// CreateProjectEventMetadata is the metadata from which the create project event can be resolved
type CreateProjectEventMetadata struct {
Project string
Operator string
ProjectID int64
Project string
Operator string
}
// Resolve to the event from the metadata
func (c *CreateProjectEventMetadata) Resolve(event *event.Event) error {
event.Topic = TopicCreateProject
event.Data = &CreateProjectEvent{
Project: c.Project,
Operator: c.Operator,
OccurAt: time.Now(),
event.Topic = event2.TopicCreateProject
event.Data = &event2.CreateProjectEvent{
EventType: event2.TopicCreateProject,
ProjectID: c.ProjectID,
Project: c.Project,
Operator: c.Operator,
OccurAt: time.Now(),
}
return nil
}
// DeleteProjectEventMetadata is the metadata from which the delete project event can be resolved
type DeleteProjectEventMetadata struct {
Project string
Operator string
ProjectID int64
Project string
Operator string
}
// Resolve to the event from the metadata
func (d *DeleteProjectEventMetadata) Resolve(event *event.Event) error {
event.Topic = TopicDeleteProject
event.Data = &DeleteProjectEvent{
Project: d.Project,
Operator: d.Operator,
OccurAt: time.Now(),
event.Topic = event2.TopicDeleteProject
event.Data = &event2.DeleteProjectEvent{
EventType: event2.TopicDeleteProject,
ProjectID: d.ProjectID,
Project: d.Project,
Operator: d.Operator,
OccurAt: time.Now(),
}
return nil
}

View File

@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package event
package metadata
import (
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/stretchr/testify/suite"
"testing"
@ -32,9 +33,9 @@ func (p *projectEventTestSuite) TestResolveOfCreateProjectEventMetadata() {
}
err := metadata.Resolve(e)
p.Require().Nil(err)
p.Equal(TopicCreateProject, e.Topic)
p.Equal(event2.TopicCreateProject, e.Topic)
p.Require().NotNil(e.Data)
data, ok := e.Data.(*CreateProjectEvent)
data, ok := e.Data.(*event2.CreateProjectEvent)
p.Require().True(ok)
p.Equal("library", data.Project)
p.Equal("admin", data.Operator)
@ -48,9 +49,9 @@ func (p *projectEventTestSuite) TestResolveOfDeleteProjectEventMetadata() {
}
err := metadata.Resolve(e)
p.Require().Nil(err)
p.Equal(TopicDeleteProject, e.Topic)
p.Equal(event2.TopicDeleteProject, e.Topic)
p.Require().NotNil(e.Data)
data, ok := e.Data.(*DeleteProjectEvent)
data, ok := e.Data.(*event2.DeleteProjectEvent)
p.Require().True(ok)
p.Equal("library", data.Project)
p.Equal("admin", data.Operator)

View File

@ -0,0 +1,51 @@
package metadata
import (
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/pkg/errors"
"time"
)
// QuotaMetaData defines quota related event data
type QuotaMetaData struct {
Project *models.Project
RepoName string
Tag string
Digest string
// used to define the event topic
Level int
// the msg contains the limitation and current usage of quota
Msg string
OccurAt time.Time
}
// Resolve quota exceed into common image event
func (q *QuotaMetaData) Resolve(evt *event.Event) error {
var topic string
data := &event2.QuotaEvent{
EventType: event2.TopicQuotaExceed,
Project: q.Project,
Resource: &event2.ImgResource{
Tag: q.Tag,
Digest: q.Digest,
},
OccurAt: q.OccurAt,
RepoName: q.RepoName,
Msg: q.Msg,
}
switch q.Level {
case 1:
topic = event2.TopicQuotaExceed
case 2:
topic = event2.TopicQuotaWarning
default:
return errors.New("not supported quota status")
}
evt.Topic = topic
evt.Data = data
return nil
}

View File

@ -0,0 +1,48 @@
// Copyright Project Harbor 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 metadata
import (
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/stretchr/testify/suite"
"testing"
)
type quotaEventTestSuite struct {
suite.Suite
}
func (r *quotaEventTestSuite) TestResolveOfDeleteRepositoryEventMetadata() {
e := &event.Event{}
metadata := &QuotaMetaData{
RepoName: "library/hello-world",
Tag: "latest",
Digest: "sha256:123absd",
Level: 1,
Msg: "quota exceed",
}
err := metadata.Resolve(e)
r.Require().Nil(err)
r.Equal(event2.TopicQuotaExceed, e.Topic)
r.Require().NotNil(e.Data)
data, ok := e.Data.(*event2.QuotaEvent)
r.Require().True(ok)
r.Equal("library/hello-world", data.Resource)
}
func TestQuotaEventTestSuite(t *testing.T) {
suite.Run(t, &repositoryEventTestSuite{})
}

View File

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package event
package metadata
import (
"context"
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"time"
@ -25,19 +26,21 @@ import (
type DeleteRepositoryEventMetadata struct {
Ctx context.Context
Repository string
ProjectID int64
}
// Resolve to the event from the metadata
func (d *DeleteRepositoryEventMetadata) Resolve(event *event.Event) error {
data := &DeleteRepositoryEvent{
data := &event2.DeleteRepositoryEvent{
Repository: d.Repository,
ProjectID: d.ProjectID,
OccurAt: time.Now(),
}
cx, exist := security.FromContext(d.Ctx)
if exist {
data.Operator = cx.GetUsername()
}
event.Topic = TopicDeleteRepository
event.Topic = event2.TopicDeleteRepository
event.Data = data
return nil
}

View File

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package event
package metadata
import (
"context"
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/stretchr/testify/suite"
"testing"
@ -33,9 +34,9 @@ func (r *repositoryEventTestSuite) TestResolveOfDeleteRepositoryEventMetadata()
}
err := metadata.Resolve(e)
r.Require().Nil(err)
r.Equal(TopicDeleteRepository, e.Topic)
r.Equal(event2.TopicDeleteRepository, e.Topic)
r.Require().NotNil(e.Data)
data, ok := e.Data.(*DeleteRepositoryEvent)
data, ok := e.Data.(*event2.DeleteRepositoryEvent)
r.Require().True(ok)
r.Equal("library/hello-world", data.Repository)
}

View File

@ -0,0 +1,48 @@
package metadata
import (
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/notifier/event"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/pkg/errors"
"time"
)
const (
autoTriggeredOperator = "auto"
)
// ScanImageMetaData defines meta data of image scanning event
type ScanImageMetaData struct {
Artifact *v1.Artifact
Status string
}
// Resolve image scanning metadata into common chart event
func (si *ScanImageMetaData) Resolve(evt *event.Event) error {
var eventType string
var topic string
switch si.Status {
case models.JobFinished:
eventType = event2.TopicScanningCompleted
topic = event2.TopicScanningCompleted
case models.JobError, models.JobStopped:
eventType = event2.TopicScanningFailed
topic = event2.TopicScanningFailed
default:
return errors.New("not supported scan hook status")
}
data := &event2.ScanImageEvent{
EventType: eventType,
Artifact: si.Artifact,
OccurAt: time.Now(),
Operator: autoTriggeredOperator,
}
evt.Topic = topic
evt.Data = data
return nil
}

View File

@ -0,0 +1,52 @@
// Copyright Project Harbor 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 metadata
import (
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/pkg/notifier/event"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/stretchr/testify/suite"
"testing"
)
type scanEventTestSuite struct {
suite.Suite
}
func (r *scanEventTestSuite) TestResolveOfScanImageEventMetadata() {
e := &event.Event{}
metadata := &ScanImageMetaData{
Artifact: &v1.Artifact{
NamespaceID: 0,
Repository: "library/hello-world",
Tag: "latest",
Digest: "sha256:absdfd87123",
MimeType: "docker.chart",
},
Status: "finished",
}
err := metadata.Resolve(e)
r.Require().Nil(err)
r.Equal(event2.TopicScanningCompleted, e.Topic)
r.Require().NotNil(e.Data)
data, ok := e.Data.(*event2.ScanImageEvent)
r.Require().True(ok)
r.Equal("library/hello-world", data.Artifact.Repository)
}
func TestScanEventTestSuite(t *testing.T) {
suite.Run(t, &scanEventTestSuite{})
}

View File

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package event
package metadata
import (
"context"
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/notifier/event"
@ -31,7 +32,8 @@ type CreateTagEventMetadata struct {
// Resolve to the event from the metadata
func (c *CreateTagEventMetadata) Resolve(event *event.Event) error {
data := &CreateTagEvent{
data := &event2.CreateTagEvent{
EventType: event2.TopicCreateTag,
Repository: c.AttachedArtifact.RepositoryName,
Tag: c.Tag,
AttachedArtifact: c.AttachedArtifact,
@ -41,7 +43,7 @@ func (c *CreateTagEventMetadata) Resolve(event *event.Event) error {
if exist {
data.Operator = cx.GetUsername()
}
event.Topic = TopicCreateTag
event.Topic = event2.TopicCreateTag
event.Data = data
return nil
}
@ -55,7 +57,8 @@ type DeleteTagEventMetadata struct {
// Resolve to the event from the metadata
func (d *DeleteTagEventMetadata) Resolve(event *event.Event) error {
data := &DeleteTagEvent{
data := &event2.DeleteTagEvent{
EventType: event2.TopicDeleteTag,
Repository: d.AttachedArtifact.RepositoryName,
Tag: d.Tag,
AttachedArtifact: d.AttachedArtifact,
@ -65,7 +68,7 @@ func (d *DeleteTagEventMetadata) Resolve(event *event.Event) error {
if exist {
data.Operator = ctx.GetUsername()
}
event.Topic = TopicDeleteTag
event.Topic = event2.TopicDeleteTag
event.Data = data
return nil
}

View File

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package event
package metadata
import (
"context"
event2 "github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/stretchr/testify/suite"
@ -35,9 +36,9 @@ func (t *tagEventTestSuite) TestResolveOfCreateTagEventMetadata() {
}
err := metadata.Resolve(e)
t.Require().Nil(err)
t.Equal(TopicCreateTag, e.Topic)
t.Equal(event2.TopicCreateTag, e.Topic)
t.Require().NotNil(e.Data)
data, ok := e.Data.(*CreateTagEvent)
data, ok := e.Data.(*event2.CreateTagEvent)
t.Require().True(ok)
t.Equal(int64(1), data.AttachedArtifact.ID)
t.Equal("latest", data.Tag)
@ -52,9 +53,9 @@ func (t *tagEventTestSuite) TestResolveOfDeleteTagEventMetadata() {
}
err := metadata.Resolve(e)
t.Require().Nil(err)
t.Equal(TopicDeleteTag, e.Topic)
t.Equal(event2.TopicDeleteTag, e.Topic)
t.Require().NotNil(e.Data)
data, ok := e.Data.(*DeleteTagEvent)
data, ok := e.Data.(*event2.DeleteTagEvent)
t.Require().True(ok)
t.Equal(int64(1), data.AttachedArtifact.ID)
t.Equal("latest", data.Tag)

View File

@ -15,7 +15,11 @@
package event
import (
"fmt"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/audit/model"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"time"
)
@ -23,66 +27,168 @@ import (
// const definition
const (
TopicCreateProject = "CREATE_PROJECT"
TopicDeleteProject = "DELETE_PROJECT"
TopicDeleteRepository = "DELETE_REPOSITORY"
TopicPushArtifact = "PUSH_ARTIFACT"
TopicPullArtifact = "PULL_ARTIFACT"
TopicDeleteArtifact = "DELETE_ARTIFACT"
TopicCreateTag = "CREATE_TAG"
TopicDeleteTag = "DELETE_TAG"
TopicCreateProject = "CREATE_PROJECT"
TopicDeleteProject = "DELETE_PROJECT"
TopicPushArtifact = "PUSH_ARTIFACT"
TopicPullArtifact = "PULL_ARTIFACT"
TopicDeleteArtifact = "DELETE_ARTIFACT"
TopicDeleteRepository = "DELETE_REPOSITORY"
TopicCreateTag = "CREATE_TAG"
TopicDeleteTag = "DELETE_TAG"
TopicScanningFailed = "SCANNING_FAILED"
TopicScanningCompleted = "SCANNING_COMPLETED"
// QuotaExceedTopic is topic for quota warning event, the usage reaches the warning bar of limitation, like 85%
TopicQuotaWarning = "QUOTA_WARNNING"
TopicQuotaExceed = "QUOTA_EXCEED"
TopicUploadChart = "UPLOAD_CHART"
TopicDownloadChart = "DOWNLOAD_CHART"
TopicDeleteChart = "DELETE_CHART"
)
// CreateProjectEvent is the creating project event
type CreateProjectEvent struct {
Project string
Operator string
OccurAt time.Time
EventType string
ProjectID int64
Project string
Operator string
OccurAt time.Time
}
// ResolveToAuditLog ...
func (c *CreateProjectEvent) ResolveToAuditLog() (*model.AuditLog, error) {
auditLog := &model.AuditLog{
ProjectID: c.ProjectID,
OpTime: c.OccurAt,
Operation: "create",
Username: c.Operator,
ResourceType: "project",
Resource: c.Project}
return auditLog, nil
}
// DeleteProjectEvent is the deleting project event
type DeleteProjectEvent struct {
Project string
Operator string
OccurAt time.Time
EventType string
ProjectID int64
Project string
Operator string
OccurAt time.Time
}
// ResolveToAuditLog ...
func (d *DeleteProjectEvent) ResolveToAuditLog() (*model.AuditLog, error) {
auditLog := &model.AuditLog{
ProjectID: d.ProjectID,
OpTime: d.OccurAt,
Operation: "delete",
Username: d.Operator,
ResourceType: "project",
Resource: d.Project}
return auditLog, nil
}
// DeleteRepositoryEvent is the deleting repository event
type DeleteRepositoryEvent struct {
EventType string
ProjectID int64
Repository string
Operator string
OccurAt time.Time
}
// ResolveToAuditLog ...
func (d *DeleteRepositoryEvent) ResolveToAuditLog() (*model.AuditLog, error) {
auditLog := &model.AuditLog{
ProjectID: d.ProjectID,
OpTime: d.OccurAt,
Operation: "delete",
Username: d.Operator,
ResourceType: "repository",
Resource: d.Repository,
}
return auditLog, nil
}
// ArtifactEvent is the pushing/pulling artifact event
type ArtifactEvent struct {
EventType string
Repository string
Artifact *artifact.Artifact
Tags []string // when the artifact is pushed by digest, the tag here will be null
Operator string
OccurAt time.Time
}
// PushArtifactEvent is the pushing artifact event
type PushArtifactEvent struct {
Repository string
Artifact *artifact.Artifact
Tag string // when the artifact is pushed by digest, the tag here will be null
Operator string
OccurAt time.Time
*ArtifactEvent
}
// ResolveToAuditLog ...
func (p *PushArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
auditLog := &model.AuditLog{
ProjectID: p.Artifact.ProjectID,
OpTime: p.OccurAt,
Operation: "create",
Username: p.Operator,
ResourceType: "artifact"}
if len(p.Tags) == 0 {
auditLog.Resource = fmt.Sprintf("%s:%s",
p.Artifact.RepositoryName, p.Artifact.Digest)
} else {
auditLog.Resource = fmt.Sprintf("%s:%s",
p.Artifact.RepositoryName, p.Tags[0])
}
return auditLog, nil
}
// PullArtifactEvent is the pulling artifact event
type PullArtifactEvent struct {
Repository string
Artifact *artifact.Artifact
Tag string // when the artifact is pulled by digest, the tag here will be null
Operator string
OccurAt time.Time
*ArtifactEvent
}
// ResolveToAuditLog ...
func (p *PullArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
auditLog := &model.AuditLog{
ProjectID: p.Artifact.ProjectID,
OpTime: p.OccurAt,
Operation: "pull",
Username: p.Operator,
ResourceType: "artifact"}
if len(p.Tags) == 0 {
auditLog.Resource = fmt.Sprintf("%s:%s",
p.Artifact.RepositoryName, p.Artifact.Digest)
} else {
auditLog.Resource = fmt.Sprintf("%s:%s",
p.Artifact.RepositoryName, p.Tags[0])
}
return auditLog, nil
}
// DeleteArtifactEvent is the deleting artifact event
type DeleteArtifactEvent struct {
Repository string
Artifact *artifact.Artifact
Tags []string // all the tags that attached to the deleted artifact
Operator string
OccurAt time.Time
*ArtifactEvent
}
// ResolveToAuditLog ...
func (p *DeleteArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
auditLog := &model.AuditLog{
ProjectID: p.Artifact.ProjectID,
OpTime: p.OccurAt,
Operation: "delete",
Username: p.Operator,
ResourceType: "artifact",
Resource: fmt.Sprintf("%s:%s", p.Artifact.RepositoryName, p.Artifact.Digest)}
return auditLog, nil
}
// CreateTagEvent is the creating tag event
type CreateTagEvent struct {
EventType string
Repository string
Tag string
AttachedArtifact *artifact.Artifact
@ -90,11 +196,70 @@ type CreateTagEvent struct {
OccurAt time.Time
}
// ResolveToAuditLog ...
func (c *CreateTagEvent) ResolveToAuditLog() (*model.AuditLog, error) {
auditLog := &model.AuditLog{
ProjectID: c.AttachedArtifact.ProjectID,
OpTime: c.OccurAt,
Operation: "create",
Username: c.Operator,
ResourceType: "tag",
Resource: fmt.Sprintf("%s:%s", c.Repository, c.Tag)}
return auditLog, nil
}
// DeleteTagEvent is the deleting tag event
type DeleteTagEvent struct {
EventType string
Repository string
Tag string
AttachedArtifact *artifact.Artifact
Operator string
OccurAt time.Time
}
// ResolveToAuditLog ...
func (d *DeleteTagEvent) ResolveToAuditLog() (*model.AuditLog, error) {
auditLog := &model.AuditLog{
ProjectID: d.AttachedArtifact.ProjectID,
OpTime: d.OccurAt,
Operation: "delete",
Username: d.Operator,
ResourceType: "tag",
Resource: fmt.Sprintf("%s:%s", d.Repository, d.Tag)}
return auditLog, nil
}
// ScanImageEvent is scanning image related event data to publish
type ScanImageEvent struct {
EventType string
Artifact *v1.Artifact
OccurAt time.Time
Operator string
}
// ChartEvent is chart related event data to publish
type ChartEvent struct {
EventType string
ProjectName string
ChartName string
Versions []string
OccurAt time.Time
Operator string
}
// QuotaEvent is project quota related event data to publish
type QuotaEvent struct {
EventType string
Project *models.Project
Resource *ImgResource
OccurAt time.Time
RepoName string
Msg string
}
// ImgResource include image digest and tag
type ImgResource struct {
Digest string
Tag string
}

View File

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/goharbor/harbor/src/api/event/metadata"
"io/ioutil"
"log"
"net/http"
@ -123,8 +124,8 @@ func modifyResponse(res *http.Response) error {
if e != nil && e.Resource != nil && e.Resource.Metadata != nil && len(e.Resource.Metadata.Vtags) > 0 &&
len(e.Resource.ExtendedInfo) > 0 {
event := &n_event.Event{}
metaData := &n_event.ChartUploadMetaData{
ChartMetaData: n_event.ChartMetaData{
metaData := &metadata.ChartUploadMetaData{
ChartMetaData: metadata.ChartMetaData{
ProjectName: e.Resource.ExtendedInfo["projectName"].(string),
ChartName: e.Resource.ExtendedInfo["chartName"].(string),
Versions: e.Resource.Metadata.Vtags,
@ -146,7 +147,7 @@ func modifyResponse(res *http.Response) error {
// Process downloading chart success webhook event
if res.StatusCode == http.StatusOK {
chartDownloadEvent := res.Request.Context().Value(common.ChartDownloadCtxKey)
eventMetaData, ok := chartDownloadEvent.(*n_event.ChartDownloadMetaData)
eventMetaData, ok := chartDownloadEvent.(*metadata.ChartDownloadMetaData)
if ok && eventMetaData != nil {
// Trigger harbor webhook
event := &n_event.Event{}

View File

@ -5,6 +5,7 @@ import (
"context"
"errors"
"fmt"
"github.com/goharbor/harbor/src/api/event/metadata"
"io"
"io/ioutil"
"mime/multipart"
@ -287,8 +288,8 @@ func (cra *ChartRepositoryAPI) DeleteChartVersion() {
}
event := &n_event.Event{}
metaData := &n_event.ChartDeleteMetaData{
ChartMetaData: n_event.ChartMetaData{
metaData := &metadata.ChartDeleteMetaData{
ChartMetaData: metadata.ChartMetaData{
ProjectName: cra.namespace,
ChartName: chartName,
Versions: []string{version},
@ -395,8 +396,8 @@ func (cra *ChartRepositoryAPI) DeleteChart() {
}
event := &n_event.Event{}
metaData := &n_event.ChartDeleteMetaData{
ChartMetaData: n_event.ChartMetaData{
metaData := &metadata.ChartDeleteMetaData{
ChartMetaData: metadata.ChartMetaData{
ProjectName: cra.namespace,
ChartName: chartName,
Versions: versions,
@ -527,8 +528,8 @@ func (cra *ChartRepositoryAPI) addEventContext(files []formFile, request *http.R
func (cra *ChartRepositoryAPI) addDownloadChartEventContext(fileName, namespace string, request *http.Request) {
chartName, version := parseChartVersionFromFilename(fileName)
event := &n_event.ChartDownloadMetaData{
ChartMetaData: n_event.ChartMetaData{
event := &metadata.ChartDownloadMetaData{
ChartMetaData: metadata.ChartMetaData{
ProjectName: namespace,
ChartName: chartName,
Versions: []string{version},

View File

@ -1,13 +1,13 @@
package api
import (
"github.com/goharbor/harbor/src/api/event"
"net/http"
"testing"
"time"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/notifier/model"
)
type fakedNotificationJobMgr struct {
@ -28,11 +28,11 @@ func (f *fakedNotificationJobMgr) Update(job *models.NotificationJob, props ...s
func (f *fakedNotificationJobMgr) ListJobsGroupByEventType(policyID int64) ([]*models.NotificationJob, error) {
return []*models.NotificationJob{
{
EventType: model.EventTypePullImage,
EventType: event.TopicPullArtifact,
CreationTime: time.Now(),
},
{
EventType: model.EventTypeDeleteImage,
EventType: event.TopicDeleteArtifact,
CreationTime: time.Now(),
},
}, nil

View File

@ -3,6 +3,7 @@ package api
import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/api/event"
"net/http"
"strconv"
"time"
@ -18,7 +19,8 @@ import (
// NotificationPolicyAPI ...
type NotificationPolicyAPI struct {
BaseController
project *models.Project
project *models.Project
supportedEvents map[string]struct{}
}
// notificationPolicyForUI defines the structure of notification policy info display in UI
@ -63,6 +65,7 @@ func (w *NotificationPolicyAPI) Prepare() {
return
}
w.project = project
w.supportedEvents = initSupportedEvents()
}
// Get ...
@ -274,7 +277,7 @@ func (w *NotificationPolicyAPI) GetSupportedEventTypes() {
notificationTypes.NotifyType = append(notificationTypes.NotifyType, key)
}
for key := range notification.SupportedEventTypes {
for key := range w.supportedEvents {
notificationTypes.EventType = append(notificationTypes.EventType, key)
}
w.WriteJSONData(notificationTypes)
@ -345,7 +348,7 @@ func (w *NotificationPolicyAPI) validateEventTypes(policy *models.NotificationPo
}
for _, eventType := range policy.EventTypes {
_, ok := notification.SupportedEventTypes[eventType]
_, ok := w.supportedEvents[eventType]
if !ok {
w.SendBadRequestError(fmt.Errorf("unsupport event type %s", eventType))
return false
@ -369,6 +372,19 @@ func getLastTriggerTimeGroupByEventType(eventType string, policyID int64) (time.
return time.Time{}, nil
}
func initSupportedEvents() map[string]struct{} {
var supportedEventTypes = make(map[string]struct{})
eventTypes := []string{event.TopicPushArtifact, event.TopicPullArtifact,
event.TopicDeleteArtifact, event.TopicUploadChart, event.TopicDeleteChart,
event.TopicDownloadChart, event.TopicQuotaExceed, event.TopicScanningFailed,
event.TopicScanningCompleted}
for _, eventType := range eventTypes {
supportedEventTypes[eventType] = struct{}{}
}
return supportedEventTypes
}
// constructPolicyWithTriggerTime construct notification policy information displayed in UI
// including event type, enabled, creation time, last trigger time
func constructPolicyWithTriggerTime(policies []*models.NotificationPolicy) ([]*notificationPolicyForUI, error) {

View File

@ -1,13 +1,12 @@
package api
import (
"github.com/goharbor/harbor/src/api/event"
"net/http"
"testing"
"github.com/pkg/errors"
"github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/notification"
)
@ -24,8 +23,8 @@ func (f *fakedNotificationPlyMgr) List(id int64) ([]*models.NotificationPolicy,
{
ID: 1,
EventTypes: []string{
model.EventTypePullImage,
model.EventTypePushImage,
event.TopicPullArtifact,
event.TopicPushArtifact,
},
},
}, nil
@ -168,7 +167,7 @@ func TestNotificationPolicyAPI_Post(t *testing.T) {
url: "/api/projects/1/webhook/policies",
credential: sysAdmin,
bodyJSON: &models.NotificationPolicy{
EventTypes: []string{"pullImage", "pushImage", "deleteImage"},
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
Targets: []models.EventTarget{
{
Address: "tcp://127.0.0.1:8080",
@ -184,7 +183,7 @@ func TestNotificationPolicyAPI_Post(t *testing.T) {
url: "/api/projects/1/webhook/policies",
credential: sysAdmin,
bodyJSON: &models.NotificationPolicy{
EventTypes: []string{"pullImage", "pushImage", "deleteImage"},
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
Targets: []models.EventTarget{
{
Type: "smn",
@ -218,7 +217,7 @@ func TestNotificationPolicyAPI_Post(t *testing.T) {
credential: sysAdmin,
bodyJSON: &models.NotificationPolicy{
ID: 111,
EventTypes: []string{"pullImage", "pushImage", "deleteImage"},
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
Targets: []models.EventTarget{
{
Type: "http",
@ -238,7 +237,7 @@ func TestNotificationPolicyAPI_Post(t *testing.T) {
url: "/api/projects/1/webhook/policies",
credential: sysAdmin,
bodyJSON: &models.NotificationPolicy{
EventTypes: []string{"pullImage", "pushImage", "deleteImage"},
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
Targets: []models.EventTarget{
{
Type: "http",
@ -373,7 +372,7 @@ func TestNotificationPolicyAPI_Put(t *testing.T) {
url: "/api/projects/1/webhook/policies/1",
credential: sysAdmin,
bodyJSON: &models.NotificationPolicy{
EventTypes: []string{"pullImage", "pushImage", "deleteImage"},
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
Targets: []models.EventTarget{},
}},
code: http.StatusBadRequest,
@ -385,7 +384,7 @@ func TestNotificationPolicyAPI_Put(t *testing.T) {
url: "/api/projects/1/webhook/policies/1",
credential: sysAdmin,
bodyJSON: &models.NotificationPolicy{
EventTypes: []string{"pullImage", "pushImage", "deleteImage"},
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
Targets: []models.EventTarget{
{
Address: "tcp://127.0.0.1:8080",
@ -401,7 +400,7 @@ func TestNotificationPolicyAPI_Put(t *testing.T) {
url: "/api/projects/1/webhook/policies/1",
credential: sysAdmin,
bodyJSON: &models.NotificationPolicy{
EventTypes: []string{"pullImage", "pushImage", "deleteImage"},
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
Targets: []models.EventTarget{
{
Type: "smn",
@ -435,7 +434,7 @@ func TestNotificationPolicyAPI_Put(t *testing.T) {
credential: sysAdmin,
bodyJSON: &models.NotificationPolicy{
Name: "imagePolicyTest",
EventTypes: []string{"pullImage", "pushImage", "deleteImage"},
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
Targets: []models.EventTarget{
{
Type: "http",

View File

@ -22,7 +22,7 @@ import (
"strings"
"sync"
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/event/metadata"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/dao/project"
@ -213,9 +213,10 @@ func (p *ProjectAPI) Post() {
}
// fire event
evt.BuildAndPublish(&event.CreateProjectEventMetadata{
Project: pro.Name,
Operator: owner,
evt.BuildAndPublish(&metadata.CreateProjectEventMetadata{
ProjectID: projectID,
Project: pro.Name,
Operator: owner,
})
p.Redirect(http.StatusCreated, strconv.FormatInt(projectID, 10))
@ -295,9 +296,10 @@ func (p *ProjectAPI) Delete() {
}
// fire event
evt.BuildAndPublish(&event.DeleteProjectEventMetadata{
Project: p.project.Name,
Operator: p.SecurityCtx.GetUsername(),
evt.BuildAndPublish(&metadata.DeleteProjectEventMetadata{
ProjectID: p.project.ProjectID,
Project: p.project.Name,
Operator: p.SecurityCtx.GetUsername(),
})
}

View File

@ -45,7 +45,6 @@ import (
_ "github.com/goharbor/harbor/src/pkg/notifier/topic"
"github.com/goharbor/harbor/src/pkg/scan"
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
"github.com/goharbor/harbor/src/pkg/scan/event"
"github.com/goharbor/harbor/src/pkg/scheduler"
"github.com/goharbor/harbor/src/pkg/version"
"github.com/goharbor/harbor/src/replication"
@ -155,8 +154,6 @@ func main() {
log.Info("initializing notification...")
notification.Init()
// Initialize the event handlers for handling artifact cascade deletion
event.Init()
filter.Init()
beego.InsertFilter("/api/*", beego.BeforeStatic, filter.SessionCheck)

View File

@ -20,6 +20,7 @@ import (
"github.com/goharbor/harbor/src/core/service/notifications"
"github.com/goharbor/harbor/src/api/event/metadata"
"github.com/goharbor/harbor/src/api/scan"
"github.com/goharbor/harbor/src/common/job"
"github.com/goharbor/harbor/src/common/models"
@ -116,7 +117,7 @@ func (h *Handler) HandleScan() {
log.Debugf("Scan %s for artifact: %#v", h.status, req.Artifact)
e := &event.Event{}
metaData := &event.ScanImageMetaData{
metaData := &metadata.ScanImageMetaData{
Artifact: req.Artifact,
Status: h.status,
}

View File

@ -39,26 +39,12 @@ func Init() {
// init notification job manager
JobMgr = jobMgr.NewDefaultManager()
SupportedEventTypes = make(map[string]struct{})
SupportedNotifyTypes = make(map[string]struct{})
initSupportedEventType(
model.EventTypePushImage, model.EventTypePullImage, model.EventTypeDeleteImage,
model.EventTypeUploadChart, model.EventTypeDeleteChart, model.EventTypeDownloadChart,
model.EventTypeScanningCompleted, model.EventTypeScanningFailed, model.EventTypeProjectQuota,
)
initSupportedNotifyType(model.NotifyTypeHTTP, model.NotifyTypeSlack)
log.Info("notification initialization completed")
}
func initSupportedEventType(eventTypes ...string) {
for _, eventType := range eventTypes {
SupportedEventTypes[eventType] = struct{}{}
}
}
func initSupportedNotifyType(notifyTypes ...string) {
for _, notifyType := range notifyTypes {
SupportedNotifyTypes[notifyType] = struct{}{}

View File

@ -1,7 +1,6 @@
package manager
import (
"encoding/json"
"fmt"
"net/http"
"time"
@ -10,8 +9,6 @@ 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/notifier/model"
notifierModel "github.com/goharbor/harbor/src/pkg/notifier/model"
)
// DefaultManager ...
@ -95,17 +92,10 @@ func (m *DefaultManager) Delete(policyID int64) error {
// Test the specified notification policy, just test for network connection without request body
func (m *DefaultManager) Test(policy *models.NotificationPolicy) error {
p, err := json.Marshal(notifierModel.Payload{
Type: model.EventTypeTestEndpoint,
})
if err != nil {
return err
}
for _, target := range policy.Targets {
switch target.Type {
case "http":
return m.policyHTTPTest(target.Address, target.SkipCertVerify, p)
return m.policyHTTPTest(target.Address, target.SkipCertVerify)
default:
return fmt.Errorf("invalid policy target type: %s", target.Type)
}
@ -113,7 +103,7 @@ func (m *DefaultManager) Test(policy *models.NotificationPolicy) error {
return nil
}
func (m *DefaultManager) policyHTTPTest(address string, skipCertVerify bool, p []byte) error {
func (m *DefaultManager) policyHTTPTest(address string, skipCertVerify bool) error {
req, err := http.NewRequest(http.MethodPost, address, nil)
if err != nil {
return err

View File

@ -1,21 +1,13 @@
package event
import (
"time"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/log"
"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"
)
const (
autoTriggeredOperator = "auto"
)
// Event to publish
type Event struct {
Topic string
@ -44,243 +36,6 @@ type Metadata interface {
Resolve(event *Event) error
}
// ImageDelMetaData defines images deleting related event data
type ImageDelMetaData struct {
Project *models.Project
Tags []string
Digests map[string]string
OccurAt time.Time
Operator string
RepoName string
}
// Resolve image deleting metadata into common image event
func (i *ImageDelMetaData) Resolve(evt *Event) error {
data := &model.ImageEvent{
EventType: notifyModel.EventTypeDeleteImage,
Project: i.Project,
OccurAt: i.OccurAt,
Operator: i.Operator,
RepoName: i.RepoName,
}
for _, t := range i.Tags {
res := &model.ImgResource{
Tag: t,
Digest: i.Digests[t],
}
data.Resource = append(data.Resource, res)
}
evt.Topic = model.DeleteImageTopic
evt.Data = data
return nil
}
// ImagePushMetaData defines images pushing related event data
type ImagePushMetaData struct {
Project *models.Project
Tag string
Digest string
OccurAt time.Time
Operator string
RepoName string
}
// Resolve image pushing metadata into common image event
func (i *ImagePushMetaData) Resolve(evt *Event) error {
data := &model.ImageEvent{
EventType: notifyModel.EventTypePushImage,
Project: i.Project,
OccurAt: i.OccurAt,
Operator: i.Operator,
RepoName: i.RepoName,
Resource: []*model.ImgResource{
{
Tag: i.Tag,
Digest: i.Digest,
},
},
}
evt.Topic = model.PushImageTopic
evt.Data = data
return nil
}
// ImagePullMetaData defines images pulling related event data
type ImagePullMetaData struct {
Project *models.Project
Tag string
Digest string
OccurAt time.Time
Operator string
RepoName string
}
// Resolve image pulling metadata into common image event
func (i *ImagePullMetaData) Resolve(evt *Event) error {
data := &model.ImageEvent{
EventType: notifyModel.EventTypePullImage,
Project: i.Project,
OccurAt: i.OccurAt,
Operator: i.Operator,
RepoName: i.RepoName,
Resource: []*model.ImgResource{
{
Tag: i.Tag,
Digest: i.Digest,
},
},
}
evt.Topic = model.PullImageTopic
evt.Data = data
return nil
}
// ChartMetaData defines meta data of chart event
type ChartMetaData struct {
ProjectName string
ChartName string
Versions []string
OccurAt time.Time
Operator string
}
func (cmd *ChartMetaData) convert(evt *model.ChartEvent) {
evt.ProjectName = cmd.ProjectName
evt.OccurAt = cmd.OccurAt
evt.Operator = cmd.Operator
evt.ChartName = cmd.ChartName
evt.Versions = cmd.Versions
}
// ChartUploadMetaData defines meta data of chart upload event
type ChartUploadMetaData struct {
ChartMetaData
}
// Resolve chart uploading metadata into common chart event
func (cu *ChartUploadMetaData) Resolve(evt *Event) error {
data := &model.ChartEvent{
EventType: notifyModel.EventTypeUploadChart,
}
cu.convert(data)
evt.Topic = model.UploadChartTopic
evt.Data = data
return nil
}
// ChartDownloadMetaData defines meta data of chart download event
type ChartDownloadMetaData struct {
ChartMetaData
}
// Resolve chart download metadata into common chart event
func (cd *ChartDownloadMetaData) Resolve(evt *Event) error {
data := &model.ChartEvent{
EventType: notifyModel.EventTypeDownloadChart,
}
cd.convert(data)
evt.Topic = model.DownloadChartTopic
evt.Data = data
return nil
}
// ChartDeleteMetaData defines meta data of chart delete event
type ChartDeleteMetaData struct {
ChartMetaData
}
// Resolve chart delete metadata into common chart event
func (cd *ChartDeleteMetaData) Resolve(evt *Event) error {
data := &model.ChartEvent{
EventType: notifyModel.EventTypeDeleteChart,
}
cd.convert(data)
evt.Topic = model.DeleteChartTopic
evt.Data = data
return nil
}
// ScanImageMetaData defines meta data of image scanning event
type ScanImageMetaData struct {
Artifact *v1.Artifact
Status string
}
// Resolve image scanning metadata into common chart event
func (si *ScanImageMetaData) Resolve(evt *Event) error {
var eventType string
var topic string
switch si.Status {
case models.JobFinished:
eventType = notifyModel.EventTypeScanningCompleted
topic = model.ScanningCompletedTopic
case models.JobError, models.JobStopped:
eventType = notifyModel.EventTypeScanningFailed
topic = model.ScanningFailedTopic
default:
return errors.New("not supported scan hook status")
}
data := &model.ScanImageEvent{
EventType: eventType,
Artifact: si.Artifact,
OccurAt: time.Now(),
Operator: autoTriggeredOperator,
}
evt.Topic = topic
evt.Data = data
return nil
}
// QuotaMetaData defines quota related event data
type QuotaMetaData struct {
Project *models.Project
RepoName string
Tag string
Digest string
// used to define the event topic
Level int
// the msg contains the limitation and current usage of quota
Msg string
OccurAt time.Time
}
// Resolve quota exceed into common image event
func (q *QuotaMetaData) Resolve(evt *Event) error {
var topic string
data := &model.QuotaEvent{
EventType: notifyModel.EventTypeProjectQuota,
Project: q.Project,
Resource: &model.ImgResource{
Tag: q.Tag,
Digest: q.Digest,
},
OccurAt: q.OccurAt,
RepoName: q.RepoName,
Msg: q.Msg,
}
switch q.Level {
case 1:
topic = model.QuotaExceedTopic
case 2:
topic = model.QuotaWarningTopic
default:
return errors.New("not supported quota status")
}
evt.Topic = topic
evt.Data = data
return nil
}
// HookMetaData defines hook notification related event data
type HookMetaData struct {
PolicyID int64

View File

@ -1,141 +1,13 @@
package event
import (
"testing"
"time"
"github.com/goharbor/harbor/src/common/models"
notifierModel "github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
func TestImagePushEvent_Build(t *testing.T) {
type args struct {
imgPushMetadata *ImagePushMetaData
hookMetadata *HookMetaData
}
tests := []struct {
name string
args args
wantErr bool
want *Event
}{
{
name: "Build Image Push Event",
args: args{
imgPushMetadata: &ImagePushMetaData{
Project: &models.Project{ProjectID: 1, Name: "library"},
Tag: "v1.0",
Digest: "abcd",
OccurAt: time.Now(),
Operator: "admin",
RepoName: "library/alpine",
},
},
want: &Event{
Topic: notifierModel.PushImageTopic,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := &Event{}
err := event.Build(tt.args.imgPushMetadata)
if tt.wantErr {
require.NotNil(t, err, "Error: %s", err)
return
}
assert.Equal(t, tt.want.Topic, event.Topic)
})
}
}
func TestImagePullEvent_Build(t *testing.T) {
type args struct {
imgPullMetadata *ImagePullMetaData
}
tests := []struct {
name string
args args
wantErr bool
want *Event
}{
{
name: "Build Image Pull Event",
args: args{
imgPullMetadata: &ImagePullMetaData{
Project: &models.Project{ProjectID: 1, Name: "library"},
Tag: "v1.0",
Digest: "abcd",
OccurAt: time.Now(),
Operator: "admin",
RepoName: "library/alpine",
},
},
want: &Event{
Topic: notifierModel.PullImageTopic,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := &Event{}
err := event.Build(tt.args.imgPullMetadata)
if tt.wantErr {
require.NotNil(t, err, "Error: %s", err)
return
}
assert.Equal(t, tt.want.Topic, event.Topic)
})
}
}
func TestImageDelEvent_Build(t *testing.T) {
type args struct {
imgDelMetadata *ImageDelMetaData
}
tests := []struct {
name string
args args
wantErr bool
want *Event
}{
{
name: "Build Image Delete Event",
args: args{
imgDelMetadata: &ImageDelMetaData{
Project: &models.Project{ProjectID: 1, Name: "library"},
Tags: []string{"v1.0"},
OccurAt: time.Now(),
Operator: "admin",
RepoName: "library/alpine",
},
},
want: &Event{
Topic: notifierModel.DeleteImageTopic,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := &Event{}
err := event.Build(tt.args.imgDelMetadata)
if tt.wantErr {
require.NotNil(t, err, "Error: %s", err)
return
}
assert.Equal(t, tt.want.Topic, event.Topic)
})
}
}
func TestHookEvent_Build(t *testing.T) {
type args struct {
hookMetadata *HookMetaData

View File

@ -1,50 +0,0 @@
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

@ -53,7 +53,7 @@ func TestHTTPHandler_Handle(t *testing.T) {
args: args{
event: &event.Event{
Topic: "http",
Data: &model.ImageEvent{},
Data: &model.EventData{},
},
},
wantErr: true,

View File

@ -1,18 +0,0 @@
package notification
// ImagePreprocessHandler preprocess image event data
type ImagePreprocessHandler struct {
}
// Handle preprocess image event data and then publish hook event
func (h *ImagePreprocessHandler) Handle(value interface{}) error {
if err := preprocessAndSendImageHook(value); err != nil {
return err
}
return nil
}
// IsStateful ...
func (h *ImagePreprocessHandler) IsStateful() bool {
return false
}

View File

@ -1,192 +0,0 @@
package notification
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
type fakedNotificationPlyMgr struct {
}
func (f *fakedNotificationPlyMgr) Create(*models.NotificationPolicy) (int64, error) {
return 0, nil
}
func (f *fakedNotificationPlyMgr) List(id int64) ([]*models.NotificationPolicy, error) {
return nil, nil
}
func (f *fakedNotificationPlyMgr) Get(id int64) (*models.NotificationPolicy, error) {
return nil, nil
}
func (f *fakedNotificationPlyMgr) GetByNameAndProjectID(string, int64) (*models.NotificationPolicy, error) {
return nil, nil
}
func (f *fakedNotificationPlyMgr) Update(*models.NotificationPolicy) error {
return nil
}
func (f *fakedNotificationPlyMgr) Delete(int64) error {
return nil
}
func (f *fakedNotificationPlyMgr) Test(*models.NotificationPolicy) error {
return nil
}
func (f *fakedNotificationPlyMgr) GetRelatedPolices(id int64, eventType string) ([]*models.NotificationPolicy, error) {
if id == 1 {
return []*models.NotificationPolicy{
{
ID: 1,
EventTypes: []string{
model.EventTypePullImage,
model.EventTypePushImage,
},
Targets: []models.EventTarget{
{
Type: "http",
Address: "http://127.0.0.1:8080",
},
},
},
}, nil
}
if id == 2 {
return nil, nil
}
return nil, errors.New("")
}
func TestMain(m *testing.M) {
dao.PrepareTestForPostgresSQL()
os.Exit(m.Run())
}
func TestImagePreprocessHandler_Handle(t *testing.T) {
PolicyMgr := notification.PolicyMgr
defer func() {
notification.PolicyMgr = PolicyMgr
}()
notification.PolicyMgr = &fakedNotificationPlyMgr{}
handler := &ImagePreprocessHandler{}
config.Init()
type args struct {
data interface{}
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "ImagePreprocessHandler Want Error 1",
args: args{
data: nil,
},
wantErr: true,
},
{
name: "ImagePreprocessHandler Want Error 2",
args: args{
data: &model.ImageEvent{},
},
wantErr: true,
},
{
name: "ImagePreprocessHandler Want Error 3",
args: args{
data: &model.ImageEvent{
Resource: []*model.ImgResource{
{
Tag: "v1.0",
},
},
Project: &models.Project{
ProjectID: 3,
},
},
},
wantErr: true,
},
{
name: "ImagePreprocessHandler Want Error 4",
args: args{
data: &model.ImageEvent{
Resource: []*model.ImgResource{
{
Tag: "v1.0",
},
},
Project: &models.Project{
ProjectID: 1,
},
},
},
wantErr: true,
},
// No handlers registered for handling topic http
{
name: "ImagePreprocessHandler Want Error 5",
args: args{
data: &model.ImageEvent{
RepoName: "test/alpine",
Resource: []*model.ImgResource{
{
Tag: "v1.0",
},
},
Project: &models.Project{
ProjectID: 1,
},
},
},
wantErr: true,
},
{
name: "ImagePreprocessHandler 2",
args: args{
data: &model.ImageEvent{
Resource: []*model.ImgResource{
{
Tag: "v1.0",
},
},
Project: &models.Project{
ProjectID: 2,
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := handler.Handle(tt.args.data)
if tt.wantErr {
require.NotNil(t, err, "Error: %s", err)
return
}
assert.Nil(t, err)
})
}
}
func TestImagePreprocessHandler_IsStateful(t *testing.T) {
handler := &ImagePreprocessHandler{}
assert.False(t, handler.IsStateful())
}

View File

@ -1,196 +0,0 @@
package notification
import (
"errors"
"fmt"
"strings"
"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/core/config"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/notifier/event"
notifyModel "github.com/goharbor/harbor/src/pkg/notifier/model"
)
// getNameFromImgRepoFullName gets image name from repo full name with format `repoName/imageName`
func getNameFromImgRepoFullName(repo string) string {
idx := strings.Index(repo, "/")
return repo[idx+1:]
}
func buildImageResourceURL(extURL, repoName, tag string) (string, error) {
resURL := fmt.Sprintf("%s/%s:%s", extURL, repoName, tag)
return resURL, nil
}
func constructImagePayload(event *notifyModel.ImageEvent) (*notifyModel.Payload, error) {
repoName := event.RepoName
if repoName == "" {
return nil, fmt.Errorf("invalid %s event with empty repo name", event.EventType)
}
repoType := models.ProjectPrivate
if event.Project.IsPublic() {
repoType = models.ProjectPublic
}
imageName := getNameFromImgRepoFullName(repoName)
payload := &notifyModel.Payload{
Type: event.EventType,
OccurAt: event.OccurAt.Unix(),
EventData: &notifyModel.EventData{
Repository: &notifyModel.Repository{
Name: imageName,
Namespace: event.Project.Name,
RepoFullName: repoName,
RepoType: repoType,
},
},
Operator: event.Operator,
}
repoRecord, err := dao.GetRepositoryByName(repoName)
if err != nil {
log.Errorf("failed to get repository with name %s: %v", repoName, err)
return nil, err
}
// once repo has been delete, cannot ensure to get repo record
if repoRecord == nil {
log.Debugf("cannot find repository info with repo %s", repoName)
} else {
payload.EventData.Repository.DateCreated = repoRecord.CreationTime.Unix()
}
extURL, err := config.ExtURL()
if err != nil {
return nil, fmt.Errorf("get external endpoint failed: %v", err)
}
for _, res := range event.Resource {
tag := res.Tag
digest := res.Digest
if tag == "" {
log.Errorf("invalid notification event with empty tag: %v", event)
continue
}
resURL, err := buildImageResourceURL(extURL, event.RepoName, tag)
if err != nil {
log.Errorf("get resource URL failed: %v", err)
continue
}
resource := &notifyModel.Resource{
Tag: tag,
Digest: digest,
ResourceURL: resURL,
}
payload.EventData.Resources = append(payload.EventData.Resources, resource)
}
return payload, nil
}
// send hook by publishing topic of specified target type(notify type)
func sendHookWithPolicies(policies []*models.NotificationPolicy, payload *notifyModel.Payload, eventType string) error {
errRet := false
for _, ply := range policies {
targets := ply.Targets
for _, target := range targets {
evt := &event.Event{}
hookMetadata := &event.HookMetaData{
EventType: eventType,
PolicyID: ply.ID,
Payload: payload,
Target: &target,
}
// It should never affect evaluating other policies when one is failed, but error should return
if err := evt.Build(hookMetadata); err == nil {
if err := evt.Publish(); err != nil {
errRet = true
log.Errorf("failed to publish hook notify event: %v", err)
}
} else {
errRet = true
log.Errorf("failed to build hook notify event metadata: %v", err)
}
log.Debugf("published image event %s by topic %s", payload.Type, target.Type)
}
}
if errRet {
return errors.New("failed to trigger some of the events")
}
return nil
}
func resolveImageEventData(value interface{}) (*notifyModel.ImageEvent, error) {
imgEvent, ok := value.(*notifyModel.ImageEvent)
if !ok || imgEvent == nil {
return nil, errors.New("invalid image event")
}
if len(imgEvent.Resource) == 0 {
return nil, fmt.Errorf("empty event resouece data in image event: %v", imgEvent)
}
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
if !config.NotificationEnable() {
log.Debug("notification feature is not enabled")
return nil
}
imgEvent, err := resolveImageEventData(value)
if err != nil {
return err
}
policies, err := notification.PolicyMgr.GetRelatedPolices(imgEvent.Project.ProjectID, imgEvent.EventType)
if err != nil {
log.Errorf("failed to find policy for %s event: %v", imgEvent.EventType, err)
return err
}
// if cannot find policy including event type in project, return directly
if len(policies) == 0 {
log.Debugf("cannot find policy for %s event: %v", imgEvent.EventType, imgEvent)
return nil
}
payload, err := constructImagePayload(imgEvent)
if err != nil {
return err
}
err = sendHookWithPolicies(policies, payload, imgEvent.EventType)
if err != nil {
return err
}
return nil
}

View File

@ -45,7 +45,7 @@ func TestSlackHandler_Handle(t *testing.T) {
args: args{
event: &event.Event{
Topic: "slack",
Data: &model.ImageEvent{},
Data: &model.EventData{},
},
},
wantErr: true,

View File

@ -2,17 +2,6 @@ package model
// const definitions
const (
EventTypePushImage = "pushImage"
EventTypePullImage = "pullImage"
EventTypeDeleteImage = "deleteImage"
EventTypeUploadChart = "uploadChart"
EventTypeDeleteChart = "deleteChart"
EventTypeDownloadChart = "downloadChart"
EventTypeScanningCompleted = "scanningCompleted"
EventTypeScanningFailed = "scanningFailed"
EventTypeTestEndpoint = "testEndpoint"
EventTypeProjectQuota = "projectQuota"
NotifyTypeHTTP = "http"
NotifyTypeSlack = "slack"
)

View File

@ -1,60 +1,9 @@
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"
)
// ImageEvent is image related event data to publish
type ImageEvent struct {
EventType string
Project *models.Project
Resource []*ImgResource
OccurAt time.Time
Operator string
RepoName string
}
// ImgResource include image digest and tag
type ImgResource struct {
Digest string
Tag string
}
// ChartEvent is chart related event data to publish
type ChartEvent struct {
EventType string
ProjectName string
ChartName string
Versions []string
OccurAt time.Time
Operator string
}
// ScanImageEvent is scanning image related event data to publish
type ScanImageEvent struct {
EventType string
Artifact *v1.Artifact
OccurAt time.Time
Operator string
}
// QuotaEvent is project quota related event data to publish
type QuotaEvent struct {
EventType string
Project *models.Project
Resource *ImgResource
OccurAt time.Time
RepoName string
Msg string
}
// HookEvent is hook related event data to publish
type HookEvent struct {
PolicyID int64
@ -63,144 +12,6 @@ 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,44 +2,6 @@ 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
PullImageTopic = "OnPullImage"
// DeleteImageTopic is topic for delete image event
DeleteImageTopic = "OnDeleteImage"
// UploadChartTopic is topic for upload chart event
UploadChartTopic = "OnUploadChart"
// DownloadChartTopic is topic for download chart event
DownloadChartTopic = "OnDownloadChart"
// DeleteChartTopic is topic for delete chart event
DeleteChartTopic = "OnDeleteChart"
// ScanningFailedTopic is topic for scanning failed event
ScanningFailedTopic = "OnScanningFailed"
// ScanningCompletedTopic is topic for scanning completed event
ScanningCompletedTopic = "OnScanningCompleted"
// QuotaExceedTopic is topic for quota warning event, the usage reaches the warning bar of limitation, like 85%
QuotaWarningTopic = "OnQuotaWarning"
// QuotaExceedTopic is topic for quota exceeded event
QuotaExceedTopic = "OnQuotaExceed"
// WebhookTopic is topic for sending webhook payload
WebhookTopic = "http"
// SlackTopic is topic for sending slack payload

View File

@ -3,7 +3,6 @@ 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"
)
@ -11,27 +10,8 @@ import (
// Subscribe topics
func init() {
handlersMap := map[string][]notifier.NotificationHandler{
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{}},
model.DeleteChartTopic: {&notification.ChartPreprocessHandler{}},
model.ScanningCompletedTopic: {&notification.ScanImagePreprocessHandler{}},
model.ScanningFailedTopic: {&notification.ScanImagePreprocessHandler{}},
model.QuotaExceedTopic: {&notification.QuotaPreprocessHandler{}},
model.SlackTopic: {&notification.SlackHandler{}},
model.WebhookTopic: {&notification.HTTPHandler{}},
model.SlackTopic: {&notification.SlackHandler{}},
}
for t, handlers := range handlersMap {

View File

@ -1,101 +0,0 @@
// Copyright Project Harbor 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 event
import (
"context"
bo "github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/scan"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/internal/orm"
"github.com/goharbor/harbor/src/pkg/notifier"
"github.com/goharbor/harbor/src/pkg/notifier/model"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/pkg/errors"
)
// scanCtlGetter for getting a scan controller reference to avoid package importing order issue.
type scanCtlGetter func() scan.Controller
// artCtlGetter for getting a artifact controller reference to avoid package importing order issue.
type artCtlGetter func() artifact.Controller
// onDelImageHandler is a handler to listen to the internal delete image event.
type onDelImageHandler struct {
// scan controller
scanCtl scanCtlGetter
// artifact controller
artCtl artCtlGetter
}
// NewOnDelImageHandler creates a new handler to handle on del event.
func NewOnDelImageHandler() notifier.NotificationHandler {
return &onDelImageHandler{
scanCtl: func() scan.Controller {
return scan.DefaultController
},
artCtl: func() artifact.Controller {
return artifact.Ctl
},
}
}
func (o *onDelImageHandler) Handle(value interface{}) error {
if value == nil {
return errors.New("delete image event handler: nil value ")
}
evt, ok := value.(*model.ImageEvent)
if !ok {
return errors.New("delete image event handler: malformed image event model")
}
log.Debugf("clear the scan reports as receiving event %s", evt.EventType)
digests := make([]string, 0)
query := &q.Query{
Keywords: make(map[string]interface{}),
}
ctx := orm.NewContext(context.TODO(), bo.NewOrm())
for _, res := range evt.Resource {
// Check if it is safe to delete the reports.
query.Keywords["digest"] = res.Digest
l, err := o.artCtl().List(ctx, query, nil)
if err != nil {
// Just logged
log.Error(errors.Wrap(err, "delete image event handler"))
// Passed for safe consideration
continue
}
if len(l) == 0 {
digests = append(digests, res.Digest)
log.Debugf("prepare to remove the scan report linked with artifact: %s", res.Digest)
}
}
if err := o.scanCtl().DeleteReports(digests...); err != nil {
return errors.Wrap(err, "delete image event handler")
}
return nil
}
func (o *onDelImageHandler) IsStateful() bool {
return false
}

View File

@ -3,7 +3,7 @@ package notification
import (
"context"
"fmt"
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/event/metadata"
pkg_art "github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/stretchr/testify/suite"
@ -20,7 +20,7 @@ func (suite *NotificatoinMiddlewareTestSuite) TestMiddleware() {
next := func() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusAccepted)
notification.AddEvent(r.Context(), &event.DeleteArtifactEventMetadata{
notification.AddEvent(r.Context(), &metadata.DeleteArtifactEventMetadata{
Ctx: context.Background(),
Artifact: &pkg_art.Artifact{
ProjectID: 1,
@ -42,7 +42,7 @@ func (suite *NotificatoinMiddlewareTestSuite) TestMiddlewareMustNotify() {
next := func() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
notification.AddEvent(r.Context(), &event.DeleteArtifactEventMetadata{
notification.AddEvent(r.Context(), &metadata.DeleteArtifactEventMetadata{
Ctx: context.Background(),
Artifact: &pkg_art.Artifact{
ProjectID: 1,

View File

@ -16,12 +16,12 @@ package registry
import (
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/event/metadata"
"github.com/goharbor/harbor/src/api/repository"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/internal"
ierror "github.com/goharbor/harbor/src/internal/error"
evt "github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/pkg/notification"
serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/router"
"github.com/opencontainers/go-digest"
@ -51,7 +51,7 @@ func getManifest(w http.ResponseWriter, req *http.Request) {
// fire event
if recorder.Success() {
// TODO don't fire event for the pulling from replication
e := &event.PullArtifactEventMetadata{
e := &metadata.PullArtifactEventMetadata{
Ctx: req.Context(),
Artifact: &artifact.Artifact,
}
@ -60,7 +60,7 @@ func getManifest(w http.ResponseWriter, req *http.Request) {
if _, err = digest.Parse(reference); err != nil {
e.Tag = reference
}
evt.BuildAndPublish(e)
notification.AddEvent(req.Context(), e)
}
}

View File

@ -17,6 +17,8 @@ package handler
import (
"context"
"fmt"
"github.com/goharbor/harbor/src/api/event/metadata"
"github.com/goharbor/harbor/src/pkg/notification"
"net/http"
"strings"
"time"
@ -26,14 +28,12 @@ import (
"github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/repository"
"github.com/goharbor/harbor/src/api/scan"
"github.com/goharbor/harbor/src/api/tag"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/utils"
ierror "github.com/goharbor/harbor/src/internal/error"
evt "github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/server/v2.0/handler/assembler"
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
"github.com/goharbor/harbor/src/server/v2.0/models"
@ -218,7 +218,7 @@ func (a *artifactAPI) CreateTag(ctx context.Context, params operation.CreateTagP
}
// fire event
evt.BuildAndPublish(&event.CreateTagEventMetadata{
notification.AddEvent(ctx, &metadata.CreateTagEventMetadata{
Ctx: ctx,
Tag: tag.Name,
AttachedArtifact: &art.Artifact,
@ -257,7 +257,7 @@ func (a *artifactAPI) DeleteTag(ctx context.Context, params operation.DeleteTagP
}
// fire event
evt.BuildAndPublish(&event.DeleteTagEventMetadata{
notification.AddEvent(ctx, &metadata.DeleteTagEventMetadata{
Ctx: ctx,
Tag: params.TagName,
AttachedArtifact: &artifact.Artifact,

View File

@ -17,16 +17,16 @@ package handler
import (
"context"
"fmt"
"github.com/goharbor/harbor/src/api/event/metadata"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/project"
"github.com/goharbor/harbor/src/api/repository"
cmodels "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/utils/log"
evt "github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/goharbor/harbor/src/server/v2.0/models"
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/repository"
@ -145,7 +145,7 @@ func (r *repositoryAPI) DeleteRepository(ctx context.Context, params operation.D
}
// fire event
evt.BuildAndPublish(&event.DeleteRepositoryEventMetadata{
notification.AddEvent(ctx, &metadata.DeleteRepositoryEventMetadata{
Ctx: ctx,
Repository: repository.Name,
})

View File

@ -0,0 +1,62 @@
package notification
import (
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/common/models"
)
type FakedPolicyMgr struct {
}
func (f *FakedPolicyMgr) Create(*models.NotificationPolicy) (int64, error) {
return 0, nil
}
func (f *FakedPolicyMgr) List(id int64) ([]*models.NotificationPolicy, error) {
return nil, nil
}
func (f *FakedPolicyMgr) Get(id int64) (*models.NotificationPolicy, error) {
return nil, nil
}
func (f *FakedPolicyMgr) GetByNameAndProjectID(string, int64) (*models.NotificationPolicy, error) {
return nil, nil
}
func (f *FakedPolicyMgr) Update(*models.NotificationPolicy) error {
return nil
}
func (f *FakedPolicyMgr) Delete(int64) error {
return nil
}
func (f *FakedPolicyMgr) Test(*models.NotificationPolicy) error {
return nil
}
func (f *FakedPolicyMgr) GetRelatedPolices(id int64, eventType string) ([]*models.NotificationPolicy, error) {
return []*models.NotificationPolicy{
{
ID: 1,
EventTypes: []string{
event.TopicUploadChart,
event.TopicDownloadChart,
event.TopicDeleteChart,
event.TopicPushArtifact,
event.TopicPullArtifact,
event.TopicDeleteArtifact,
event.TopicScanningFailed,
event.TopicScanningCompleted,
event.TopicQuotaExceed,
},
Targets: []models.EventTarget{
{
Type: "http",
Address: "http://127.0.0.1:8080",
},
},
},
}, nil
}