mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-17 04:11:24 +01:00
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:
parent
a83c78c1a5
commit
fbb3226e85
@ -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,
|
||||
|
66
src/api/event/handler/auditlog/auditlog.go
Normal file
66
src/api/event/handler/auditlog/auditlog.go
Normal 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
|
||||
}
|
@ -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)
|
42
src/api/event/handler/init.go
Normal file
42
src/api/event/handler/init.go
Normal 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, "a.Handler{})
|
||||
notifier.Subscribe(event.TopicQuotaWarning, "a.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{})
|
||||
}
|
@ -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 {
|
66
src/api/event/handler/util/util.go
Normal file
66
src/api/event/handler/util/util.go
Normal 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
|
||||
}
|
142
src/api/event/handler/webhook/artifact/artifact.go
Normal file
142
src/api/event/handler/webhook/artifact/artifact.go
Normal 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 := ¬ifyModel.Payload{
|
||||
Type: event.EventType,
|
||||
OccurAt: event.OccurAt.Unix(),
|
||||
EventData: ¬ifyModel.EventData{
|
||||
Repository: ¬ifyModel.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 := ¬ifyModel.Resource{
|
||||
Tag: reference,
|
||||
Digest: event.Artifact.Digest,
|
||||
ResourceURL: resURL,
|
||||
}
|
||||
payload.EventData.Resources = append(payload.EventData.Resources, resource)
|
||||
|
||||
return payload, nil
|
||||
}
|
@ -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
|
@ -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
|
@ -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())
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
78
src/api/event/handler/webhook/scan/delete.go
Normal file
78
src/api/event/handler/webhook/scan/delete.go
Normal 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
|
||||
}
|
@ -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
|
@ -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 := ¬ificationtesting.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)
|
@ -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
|
||||
}
|
@ -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)
|
75
src/api/event/metadata/chart.go
Normal file
75
src/api/event/metadata/chart.go
Normal 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
|
||||
}
|
71
src/api/event/metadata/chart_test.go
Normal file
71
src/api/event/metadata/chart_test.go
Normal 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{})
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
51
src/api/event/metadata/quota.go
Normal file
51
src/api/event/metadata/quota.go
Normal 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
|
||||
}
|
48
src/api/event/metadata/quota_test.go
Normal file
48
src/api/event/metadata/quota_test.go
Normal 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{})
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
48
src/api/event/metadata/scan.go
Normal file
48
src/api/event/metadata/scan.go
Normal 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
|
||||
}
|
52
src/api/event/metadata/scan_test.go
Normal file
52
src/api/event/metadata/scan_test.go
Normal 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{})
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
@ -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
|
||||
}
|
||||
|
@ -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{}
|
||||
|
@ -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},
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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",
|
||||
|
@ -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(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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{}{}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
@ -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())
|
||||
}
|
@ -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 := ¬ifyModel.Payload{
|
||||
Type: event.EventType,
|
||||
OccurAt: event.OccurAt.Unix(),
|
||||
EventData: ¬ifyModel.EventData{
|
||||
Repository: ¬ifyModel.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 := ¬ifyModel.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
|
||||
}
|
@ -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,
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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"`
|
||||
|
@ -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
|
||||
|
@ -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: {¬ification.HTTPHandler{}},
|
||||
model.UploadChartTopic: {¬ification.ChartPreprocessHandler{}},
|
||||
model.DownloadChartTopic: {¬ification.ChartPreprocessHandler{}},
|
||||
model.DeleteChartTopic: {¬ification.ChartPreprocessHandler{}},
|
||||
model.ScanningCompletedTopic: {¬ification.ScanImagePreprocessHandler{}},
|
||||
model.ScanningFailedTopic: {¬ification.ScanImagePreprocessHandler{}},
|
||||
model.QuotaExceedTopic: {¬ification.QuotaPreprocessHandler{}},
|
||||
model.SlackTopic: {¬ification.SlackHandler{}},
|
||||
model.WebhookTopic: {¬ification.HTTPHandler{}},
|
||||
model.SlackTopic: {¬ification.SlackHandler{}},
|
||||
}
|
||||
|
||||
for t, handlers := range handlersMap {
|
||||
|
@ -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
|
||||
}
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
})
|
||||
|
62
src/testing/pkg/notification/manager.go
Normal file
62
src/testing/pkg/notification/manager.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user