mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-20 23:57:42 +01:00
Merge pull request #12428 from mmpei/official-master-p2p-200708
Add P2P trigger event and handler
This commit is contained in:
commit
5d7f757b7b
@ -45,6 +45,7 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||||
"github.com/goharbor/harbor/src/pkg/label"
|
"github.com/goharbor/harbor/src/pkg/label"
|
||||||
"github.com/goharbor/harbor/src/pkg/notification"
|
"github.com/goharbor/harbor/src/pkg/notification"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||||
"github.com/goharbor/harbor/src/pkg/registry"
|
"github.com/goharbor/harbor/src/pkg/registry"
|
||||||
"github.com/goharbor/harbor/src/pkg/repository"
|
"github.com/goharbor/harbor/src/pkg/repository"
|
||||||
"github.com/goharbor/harbor/src/pkg/signature"
|
"github.com/goharbor/harbor/src/pkg/signature"
|
||||||
@ -483,8 +484,27 @@ func (c *controller) GetAddition(ctx context.Context, artifactID int64, addition
|
|||||||
return processor.Get(artifact.MediaType).AbstractAddition(ctx, artifact, addition)
|
return processor.Get(artifact.MediaType).AbstractAddition(ctx, artifact, addition)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) AddLabel(ctx context.Context, artifactID int64, labelID int64) error {
|
func (c *controller) AddLabel(ctx context.Context, artifactID int64, labelID int64) (err error) {
|
||||||
return c.labelMgr.AddTo(ctx, labelID, artifactID)
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
// trigger label artifact event
|
||||||
|
e := &event.Event{}
|
||||||
|
metaData := &metadata.ArtifactLabeledMetadata{
|
||||||
|
ArtifactID: artifactID,
|
||||||
|
LabelID: labelID,
|
||||||
|
Ctx: ctx,
|
||||||
|
}
|
||||||
|
if err := e.Build(metaData); err == nil {
|
||||||
|
if err := e.Publish(); err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "mark label to resource handler: event publish"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error(errors.Wrap(err, "mark label to resource handler: event build"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
err = c.labelMgr.AddTo(ctx, labelID, artifactID)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) RemoveLabel(ctx context.Context, artifactID int64, labelID int64) error {
|
func (c *controller) RemoveLabel(ctx context.Context, artifactID int64, labelID int64) error {
|
||||||
|
@ -477,7 +477,7 @@ func (c *controllerTestSuite) TestGetAddition() {
|
|||||||
|
|
||||||
func (c *controllerTestSuite) TestAddTo() {
|
func (c *controllerTestSuite) TestAddTo() {
|
||||||
c.labelMgr.On("AddTo").Return(nil)
|
c.labelMgr.On("AddTo").Return(nil)
|
||||||
err := c.ctl.AddLabel(nil, 1, 1)
|
err := c.ctl.AddLabel(context.Background(), 1, 1)
|
||||||
c.Require().Nil(err)
|
c.Require().Nil(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/controller/event"
|
"github.com/goharbor/harbor/src/controller/event"
|
||||||
"github.com/goharbor/harbor/src/controller/event/handler/auditlog"
|
"github.com/goharbor/harbor/src/controller/event/handler/auditlog"
|
||||||
"github.com/goharbor/harbor/src/controller/event/handler/internal"
|
"github.com/goharbor/harbor/src/controller/event/handler/internal"
|
||||||
|
"github.com/goharbor/harbor/src/controller/event/handler/p2p"
|
||||||
"github.com/goharbor/harbor/src/controller/event/handler/replication"
|
"github.com/goharbor/harbor/src/controller/event/handler/replication"
|
||||||
"github.com/goharbor/harbor/src/controller/event/handler/webhook/artifact"
|
"github.com/goharbor/harbor/src/controller/event/handler/webhook/artifact"
|
||||||
"github.com/goharbor/harbor/src/controller/event/handler/webhook/chart"
|
"github.com/goharbor/harbor/src/controller/event/handler/webhook/chart"
|
||||||
@ -34,6 +35,11 @@ func init() {
|
|||||||
notifier.Subscribe(event.TopicCreateTag, &replication.Handler{})
|
notifier.Subscribe(event.TopicCreateTag, &replication.Handler{})
|
||||||
notifier.Subscribe(event.TopicDeleteTag, &replication.Handler{})
|
notifier.Subscribe(event.TopicDeleteTag, &replication.Handler{})
|
||||||
|
|
||||||
|
// p2p preheat
|
||||||
|
notifier.Subscribe(event.TopicPushArtifact, &p2p.Handler{Context: orm.Context})
|
||||||
|
notifier.Subscribe(event.TopicScanningCompleted, &p2p.Handler{Context: orm.Context})
|
||||||
|
notifier.Subscribe(event.TopicArtifactLabeled, &p2p.Handler{Context: orm.Context})
|
||||||
|
|
||||||
// audit logs
|
// audit logs
|
||||||
notifier.Subscribe(event.TopicPushArtifact, &auditlog.Handler{})
|
notifier.Subscribe(event.TopicPushArtifact, &auditlog.Handler{})
|
||||||
notifier.Subscribe(event.TopicPullArtifact, &auditlog.Handler{})
|
notifier.Subscribe(event.TopicPullArtifact, &auditlog.Handler{})
|
||||||
|
97
src/controller/event/handler/p2p/preheat.go
Normal file
97
src/controller/event/handler/p2p/preheat.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// 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 p2p
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/goharbor/harbor/src/controller/artifact"
|
||||||
|
"github.com/goharbor/harbor/src/controller/artifact/processor/image"
|
||||||
|
"github.com/goharbor/harbor/src/controller/event"
|
||||||
|
"github.com/goharbor/harbor/src/controller/p2p/preheat"
|
||||||
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler ...
|
||||||
|
type Handler struct {
|
||||||
|
Context func() context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ...
|
||||||
|
func (p *Handler) Handle(value interface{}) error {
|
||||||
|
switch value.(type) {
|
||||||
|
case *event.PushArtifactEvent:
|
||||||
|
pushArtEvent, _ := value.(*event.PushArtifactEvent)
|
||||||
|
return p.handlePushArtifact(pushArtEvent)
|
||||||
|
case *event.ScanImageEvent:
|
||||||
|
scanImageEvent, _ := value.(*event.ScanImageEvent)
|
||||||
|
return p.handleImageScanned(scanImageEvent)
|
||||||
|
case *event.ArtifactLabeledEvent:
|
||||||
|
artifactLabeledEvent, _ := value.(*event.ArtifactLabeledEvent)
|
||||||
|
return p.handleArtifactLabeled(artifactLabeledEvent)
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStateful ...
|
||||||
|
func (p *Handler) IsStateful() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Handler) handlePushArtifact(event *event.PushArtifactEvent) error {
|
||||||
|
if event.Artifact.Type != image.ArtifactTypeImage {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Debugf("preheat artifact event %s:%s", event.Artifact.RepositoryName, event.Artifact.Digest)
|
||||||
|
|
||||||
|
art, err := artifact.Ctl.Get(p.Context(), event.Artifact.ID, &artifact.Option{
|
||||||
|
WithTag: true,
|
||||||
|
WithLabel: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = preheat.Enf.PreheatArtifact(p.Context(), art)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Handler) handleImageScanned(event *event.ScanImageEvent) error {
|
||||||
|
log.Debugf("preheat image scanned %s:%s", event.Artifact.Repository, event.Artifact.Tag)
|
||||||
|
art, err := artifact.Ctl.GetByReference(p.Context(), event.Artifact.Repository, event.Artifact.Digest,
|
||||||
|
&artifact.Option{
|
||||||
|
WithTag: true,
|
||||||
|
WithLabel: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = preheat.Enf.PreheatArtifact(p.Context(), art)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Handler) handleArtifactLabeled(event *event.ArtifactLabeledEvent) error {
|
||||||
|
art, err := artifact.Ctl.Get(p.Context(), event.ArtifactID, &artifact.Option{
|
||||||
|
WithTag: true,
|
||||||
|
WithLabel: true,
|
||||||
|
})
|
||||||
|
if art.Type != image.ArtifactTypeImage {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Debugf("preheat artifact labeled %s:%s", art.Artifact.RepositoryName, art.Artifact.Digest)
|
||||||
|
|
||||||
|
_, err = preheat.Enf.PreheatArtifact(p.Context(), art)
|
||||||
|
return err
|
||||||
|
}
|
144
src/controller/event/handler/p2p/preheat_test.go
Normal file
144
src/controller/event/handler/p2p/preheat_test.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package p2p
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/goharbor/harbor/src/controller/artifact"
|
||||||
|
"github.com/goharbor/harbor/src/controller/event"
|
||||||
|
"github.com/goharbor/harbor/src/controller/p2p/preheat"
|
||||||
|
pkg_artifact "github.com/goharbor/harbor/src/pkg/artifact"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||||
|
test_artifact "github.com/goharbor/harbor/src/testing/controller/artifact"
|
||||||
|
test_preheat "github.com/goharbor/harbor/src/testing/controller/p2p/preheat"
|
||||||
|
"github.com/goharbor/harbor/src/testing/mock"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PreheatTestSuite is a test suite of testing preheat handler
|
||||||
|
type PreheatTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
artifactCtl artifact.Controller
|
||||||
|
preheatEnf preheat.Enforcer
|
||||||
|
|
||||||
|
handler *Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPreheat is an entry method of running PreheatTestSuite
|
||||||
|
func TestPreheat(t *testing.T) {
|
||||||
|
suite.Run(t, &PreheatTestSuite{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupSuite prepares env for running PreheatTestSuite
|
||||||
|
func (suite *PreheatTestSuite) SetupSuite() {
|
||||||
|
fakeArtifactCtl := &test_artifact.Controller{}
|
||||||
|
fakeArtifactCtl.On("GetByReference",
|
||||||
|
context.TODO(),
|
||||||
|
mock.AnythingOfType("string"),
|
||||||
|
mock.AnythingOfType("string"),
|
||||||
|
mock.AnythingOfType("*artifact.Option"),
|
||||||
|
).Return(&artifact.Artifact{}, nil)
|
||||||
|
fakeArtifactCtl.On("Get",
|
||||||
|
context.TODO(),
|
||||||
|
mock.AnythingOfType("int64"),
|
||||||
|
mock.AnythingOfType("*artifact.Option"),
|
||||||
|
).Return(&artifact.Artifact{
|
||||||
|
Artifact: pkg_artifact.Artifact{
|
||||||
|
Type: "IMAGE",
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
fakeEnforcer := &test_preheat.FakeEnforcer{}
|
||||||
|
fakeEnforcer.On("PreheatArtifact",
|
||||||
|
context.TODO(),
|
||||||
|
mock.AnythingOfType("*artifact.Artifact"),
|
||||||
|
).Return(nil, nil)
|
||||||
|
|
||||||
|
suite.artifactCtl = artifact.Ctl
|
||||||
|
artifact.Ctl = fakeArtifactCtl
|
||||||
|
suite.preheatEnf = preheat.Enf
|
||||||
|
preheat.Enf = fakeEnforcer
|
||||||
|
suite.handler = &Handler{
|
||||||
|
Context: context.TODO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TearDownSuite cleans the testing env
|
||||||
|
func (suite *PreheatTestSuite) TearDownSuite() {
|
||||||
|
artifact.Ctl = suite.artifactCtl
|
||||||
|
preheat.Enf = suite.preheatEnf
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIsStateful ...
|
||||||
|
func (suite *PreheatTestSuite) TestIsStateful() {
|
||||||
|
b := suite.handler.IsStateful()
|
||||||
|
suite.False(b, "handler is stateful")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHandle ...
|
||||||
|
func (suite *PreheatTestSuite) TestHandle() {
|
||||||
|
type args struct {
|
||||||
|
data interface{}
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "PreheatHandler String Error",
|
||||||
|
args: args{
|
||||||
|
data: "",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PreheatHandler 1",
|
||||||
|
args: args{
|
||||||
|
data: &event.PushArtifactEvent{
|
||||||
|
ArtifactEvent: &event.ArtifactEvent{
|
||||||
|
Artifact: &pkg_artifact.Artifact{
|
||||||
|
Type: "IMAGE",
|
||||||
|
ID: 11,
|
||||||
|
RepositoryID: 23,
|
||||||
|
},
|
||||||
|
Tags: []string{"v1.1", "v1.2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PreheatHandler 2",
|
||||||
|
args: args{
|
||||||
|
data: &event.ScanImageEvent{
|
||||||
|
OccurAt: time.Now(),
|
||||||
|
Artifact: &v1.Artifact{
|
||||||
|
Repository: "library",
|
||||||
|
Digest: "sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a7",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PreheatHandler 3",
|
||||||
|
args: args{
|
||||||
|
data: &event.ArtifactLabeledEvent{
|
||||||
|
OccurAt: time.Now(),
|
||||||
|
ArtifactID: 1,
|
||||||
|
LabelID: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
err := suite.handler.Handle(tt.args.data)
|
||||||
|
if tt.wantErr {
|
||||||
|
suite.Error(err, tt.name)
|
||||||
|
} else {
|
||||||
|
suite.NoError(err, tt.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
src/controller/event/metadata/label.go
Normal file
47
src/controller/event/metadata/label.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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 (
|
||||||
|
"context"
|
||||||
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
|
event2 "github.com/goharbor/harbor/src/controller/event"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ArtifactLabeledMetadata is the metadata from which the artifact labeled event can be resolved
|
||||||
|
type ArtifactLabeledMetadata struct {
|
||||||
|
Ctx context.Context
|
||||||
|
ArtifactID int64
|
||||||
|
LabelID int64
|
||||||
|
Operator string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve to the event from the metadata
|
||||||
|
func (al *ArtifactLabeledMetadata) Resolve(event *event.Event) error {
|
||||||
|
data := &event2.ArtifactLabeledEvent{
|
||||||
|
ArtifactID: al.ArtifactID,
|
||||||
|
LabelID: al.LabelID,
|
||||||
|
OccurAt: time.Now(),
|
||||||
|
}
|
||||||
|
ctx, exist := security.FromContext(al.Ctx)
|
||||||
|
if exist {
|
||||||
|
data.Operator = ctx.GetUsername()
|
||||||
|
}
|
||||||
|
event.Topic = event2.TopicArtifactLabeled
|
||||||
|
event.Data = data
|
||||||
|
return nil
|
||||||
|
}
|
@ -39,12 +39,13 @@ const (
|
|||||||
TopicScanningFailed = "SCANNING_FAILED"
|
TopicScanningFailed = "SCANNING_FAILED"
|
||||||
TopicScanningCompleted = "SCANNING_COMPLETED"
|
TopicScanningCompleted = "SCANNING_COMPLETED"
|
||||||
// QuotaExceedTopic is topic for quota warning event, the usage reaches the warning bar of limitation, like 85%
|
// QuotaExceedTopic is topic for quota warning event, the usage reaches the warning bar of limitation, like 85%
|
||||||
TopicQuotaWarning = "QUOTA_WARNING"
|
TopicQuotaWarning = "QUOTA_WARNING"
|
||||||
TopicQuotaExceed = "QUOTA_EXCEED"
|
TopicQuotaExceed = "QUOTA_EXCEED"
|
||||||
TopicUploadChart = "UPLOAD_CHART"
|
TopicUploadChart = "UPLOAD_CHART"
|
||||||
TopicDownloadChart = "DOWNLOAD_CHART"
|
TopicDownloadChart = "DOWNLOAD_CHART"
|
||||||
TopicDeleteChart = "DELETE_CHART"
|
TopicDeleteChart = "DELETE_CHART"
|
||||||
TopicReplication = "REPLICATION"
|
TopicReplication = "REPLICATION"
|
||||||
|
TopicArtifactLabeled = "ARTIFACT_LABELED"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateProjectEvent is the creating project event
|
// CreateProjectEvent is the creating project event
|
||||||
@ -280,3 +281,11 @@ type ReplicationEvent struct {
|
|||||||
OccurAt time.Time
|
OccurAt time.Time
|
||||||
Status string
|
Status string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ArtifactLabeledEvent is event data of artifact labeled
|
||||||
|
type ArtifactLabeledEvent struct {
|
||||||
|
ArtifactID int64
|
||||||
|
LabelID int64
|
||||||
|
OccurAt time.Time
|
||||||
|
Operator string
|
||||||
|
}
|
||||||
|
@ -100,6 +100,11 @@ type extURLGetter func(c *selector.Candidate) (string, error)
|
|||||||
// The purpose of defining such a func template is decoupling code
|
// The purpose of defining such a func template is decoupling code
|
||||||
type accessCredMaker func(c *selector.Candidate) (string, error)
|
type accessCredMaker func(c *selector.Candidate) (string, error)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Enf default enforcer
|
||||||
|
Enf = NewEnforcer()
|
||||||
|
)
|
||||||
|
|
||||||
// defaultEnforcer is default implementation of Enforcer.
|
// defaultEnforcer is default implementation of Enforcer.
|
||||||
type defaultEnforcer struct {
|
type defaultEnforcer struct {
|
||||||
// for policy management
|
// for policy management
|
||||||
@ -121,7 +126,7 @@ type defaultEnforcer struct {
|
|||||||
credMaker accessCredMaker
|
credMaker accessCredMaker
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEnforcer creat a new enforcer
|
// NewEnforcer create a new enforcer
|
||||||
func NewEnforcer() Enforcer {
|
func NewEnforcer() Enforcer {
|
||||||
return &defaultEnforcer{
|
return &defaultEnforcer{
|
||||||
policyMgr: policy.New(),
|
policyMgr: policy.New(),
|
||||||
|
Loading…
Reference in New Issue
Block a user