mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-02 04:51:22 +01:00
Fire event when create/delete resources (#11010)
1. Create/delete project 2. Create/delete repository 3. Push/pull/delete artifact 4. Create/delete tag Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
parent
ba6ef67993
commit
b02cab434f
@ -19,6 +19,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/api/event"
|
||||
evt "github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -139,6 +141,15 @@ func (c *controller) Ensure(ctx context.Context, repository, digest string, tags
|
||||
return false, 0, err
|
||||
}
|
||||
}
|
||||
// fire event
|
||||
e := &event.PushArtifactEventMetadata{
|
||||
Ctx: ctx,
|
||||
Artifact: artifact,
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
e.Tag = tags[0]
|
||||
}
|
||||
evt.BuildAndPublish(e)
|
||||
return created, artifact.ID, nil
|
||||
}
|
||||
|
||||
@ -363,7 +374,18 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot bool) er
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO fire delete artifact event
|
||||
// only fire event for the root parent artifact
|
||||
if isRoot {
|
||||
var tags []string
|
||||
for _, tag := range art.Tags {
|
||||
tags = append(tags, tag.Name)
|
||||
}
|
||||
evt.BuildAndPublish(&event.DeleteArtifactEventMetadata{
|
||||
Ctx: ctx,
|
||||
Artifact: &art.Artifact,
|
||||
Tags: tags,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -428,7 +450,6 @@ func (c *controller) copyDeeply(ctx context.Context, srcRepo, reference, dstRepo
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// TODO fire event
|
||||
return id, nil
|
||||
}
|
||||
|
||||
|
95
src/api/event/artifact.go
Normal file
95
src/api/event/artifact.go
Normal file
@ -0,0 +1,95 @@
|
||||
// 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"
|
||||
"github.com/goharbor/harbor/src/common/security"
|
||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PushArtifactEventMetadata is the metadata from which the push artifact event can be resolved
|
||||
type PushArtifactEventMetadata struct {
|
||||
Ctx context.Context
|
||||
Artifact *artifact.Artifact
|
||||
Tag string
|
||||
}
|
||||
|
||||
// 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(),
|
||||
}
|
||||
ctx, exist := security.FromContext(p.Ctx)
|
||||
if exist {
|
||||
data.Operator = ctx.GetUsername()
|
||||
}
|
||||
event.Topic = TopicPushArtifact
|
||||
event.Data = data
|
||||
return nil
|
||||
}
|
||||
|
||||
// PullArtifactEventMetadata is the metadata from which the pull artifact event can be resolved
|
||||
type PullArtifactEventMetadata struct {
|
||||
Ctx context.Context
|
||||
Artifact *artifact.Artifact
|
||||
Tag string
|
||||
}
|
||||
|
||||
// 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(),
|
||||
}
|
||||
ctx, exist := security.FromContext(p.Ctx)
|
||||
if exist {
|
||||
data.Operator = ctx.GetUsername()
|
||||
}
|
||||
event.Topic = TopicPullArtifact
|
||||
event.Data = data
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteArtifactEventMetadata is the metadata from which the delete artifact event can be resolved
|
||||
type DeleteArtifactEventMetadata struct {
|
||||
Ctx context.Context
|
||||
Artifact *artifact.Artifact
|
||||
Tags []string
|
||||
}
|
||||
|
||||
// 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(),
|
||||
}
|
||||
ctx, exist := security.FromContext(d.Ctx)
|
||||
if exist {
|
||||
data.Operator = ctx.GetUsername()
|
||||
}
|
||||
event.Topic = TopicDeleteArtifact
|
||||
event.Data = data
|
||||
return nil
|
||||
}
|
83
src/api/event/artifact_test.go
Normal file
83
src/api/event/artifact_test.go
Normal file
@ -0,0 +1,83 @@
|
||||
// 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"
|
||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type artifactEventTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (a *artifactEventTestSuite) TestResolveOfPushArtifactEventMetadata() {
|
||||
e := &event.Event{}
|
||||
metadata := &PushArtifactEventMetadata{
|
||||
Ctx: context.Background(),
|
||||
Artifact: &artifact.Artifact{ID: 1},
|
||||
Tag: "latest",
|
||||
}
|
||||
err := metadata.Resolve(e)
|
||||
a.Require().Nil(err)
|
||||
a.Equal(TopicPushArtifact, e.Topic)
|
||||
a.Require().NotNil(e.Data)
|
||||
data, ok := e.Data.(*PushArtifactEvent)
|
||||
a.Require().True(ok)
|
||||
a.Equal(int64(1), data.Artifact.ID)
|
||||
a.Equal("latest", data.Tag)
|
||||
}
|
||||
|
||||
func (a *artifactEventTestSuite) TestResolveOfPullArtifactEventMetadata() {
|
||||
e := &event.Event{}
|
||||
metadata := &PullArtifactEventMetadata{
|
||||
Ctx: context.Background(),
|
||||
Artifact: &artifact.Artifact{ID: 1},
|
||||
Tag: "latest",
|
||||
}
|
||||
err := metadata.Resolve(e)
|
||||
a.Require().Nil(err)
|
||||
a.Equal(TopicPullArtifact, e.Topic)
|
||||
a.Require().NotNil(e.Data)
|
||||
data, ok := e.Data.(*PullArtifactEvent)
|
||||
a.Require().True(ok)
|
||||
a.Equal(int64(1), data.Artifact.ID)
|
||||
a.Equal("latest", data.Tag)
|
||||
}
|
||||
|
||||
func (a *artifactEventTestSuite) TestResolveOfDeleteArtifactEventMetadata() {
|
||||
e := &event.Event{}
|
||||
metadata := &DeleteArtifactEventMetadata{
|
||||
Ctx: context.Background(),
|
||||
Artifact: &artifact.Artifact{ID: 1},
|
||||
Tags: []string{"latest"},
|
||||
}
|
||||
err := metadata.Resolve(e)
|
||||
a.Require().Nil(err)
|
||||
a.Equal(TopicDeleteArtifact, e.Topic)
|
||||
a.Require().NotNil(e.Data)
|
||||
data, ok := e.Data.(*DeleteArtifactEvent)
|
||||
a.Require().True(ok)
|
||||
a.Equal(int64(1), data.Artifact.ID)
|
||||
a.Require().Len(data.Tags, 1)
|
||||
a.Equal("latest", data.Tags[0])
|
||||
}
|
||||
|
||||
func TestArtifactEventTestSuite(t *testing.T) {
|
||||
suite.Run(t, &artifactEventTestSuite{})
|
||||
}
|
149
src/api/event/handler/replication.go
Normal file
149
src/api/event/handler/replication.go
Normal file
@ -0,0 +1,149 @@
|
||||
//
|
||||
// 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 handler
|
||||
|
||||
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"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func init() {
|
||||
handler := &replicationHandler{
|
||||
proMgr: project.Mgr,
|
||||
}
|
||||
notifier.Subscribe(event.TopicPushArtifact, handler)
|
||||
notifier.Subscribe(event.TopicDeleteArtifact, handler)
|
||||
notifier.Subscribe(event.TopicCreateTag, handler)
|
||||
}
|
||||
|
||||
type replicationHandler struct {
|
||||
proMgr project.Manager
|
||||
}
|
||||
|
||||
func (r *replicationHandler) Handle(value interface{}) error {
|
||||
pushArtEvent, ok := value.(*event.PushArtifactEvent)
|
||||
if ok {
|
||||
return r.handlePushArtifact(pushArtEvent)
|
||||
}
|
||||
deleteArtEvent, ok := value.(*event.DeleteArtifactEvent)
|
||||
if ok {
|
||||
return r.handleDeleteArtifact(deleteArtEvent)
|
||||
}
|
||||
createTagEvent, ok := value.(*event.CreateTagEvent)
|
||||
if ok {
|
||||
return r.handleCreateTag(createTagEvent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *replicationHandler) IsStateful() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO handle create tag
|
||||
|
||||
func (r *replicationHandler) handlePushArtifact(event *event.PushArtifactEvent) error {
|
||||
art := event.Artifact
|
||||
public := false
|
||||
project, err := r.proMgr.Get(art.ProjectID)
|
||||
if err == nil && project != nil {
|
||||
public = project.IsPublic()
|
||||
} else {
|
||||
log.Error(err)
|
||||
}
|
||||
project.IsPublic()
|
||||
e := &repevent.Event{
|
||||
Type: repevent.EventTypeArtifactPush,
|
||||
Resource: &model.Resource{
|
||||
Type: model.ResourceTypeArtifact,
|
||||
Metadata: &model.ResourceMetadata{
|
||||
Repository: &model.Repository{
|
||||
Name: event.Repository,
|
||||
Metadata: map[string]interface{}{
|
||||
"public": strconv.FormatBool(public),
|
||||
},
|
||||
},
|
||||
Artifacts: []*model.Artifact{
|
||||
{
|
||||
Type: art.Type,
|
||||
Digest: art.Digest,
|
||||
Tags: []string{event.Tag},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
return replication.EventHandler.Handle(e)
|
||||
}
|
||||
|
||||
func (r *replicationHandler) handleDeleteArtifact(event *event.DeleteArtifactEvent) error {
|
||||
art := event.Artifact
|
||||
e := &repevent.Event{
|
||||
Type: repevent.EventTypeArtifactDelete,
|
||||
Resource: &model.Resource{
|
||||
Type: model.ResourceTypeArtifact,
|
||||
Metadata: &model.ResourceMetadata{
|
||||
Repository: &model.Repository{
|
||||
Name: event.Repository,
|
||||
},
|
||||
Artifacts: []*model.Artifact{
|
||||
{
|
||||
Type: art.Type,
|
||||
Digest: art.Digest,
|
||||
Tags: event.Tags,
|
||||
}},
|
||||
},
|
||||
Deleted: true,
|
||||
},
|
||||
}
|
||||
return replication.EventHandler.Handle(e)
|
||||
}
|
||||
|
||||
func (r *replicationHandler) handleCreateTag(event *event.CreateTagEvent) error {
|
||||
art := event.AttachedArtifact
|
||||
public := false
|
||||
project, err := r.proMgr.Get(art.ProjectID)
|
||||
if err == nil && project != nil {
|
||||
public = project.IsPublic()
|
||||
} else {
|
||||
log.Error(err)
|
||||
}
|
||||
project.IsPublic()
|
||||
e := &repevent.Event{
|
||||
Type: repevent.EventTypeArtifactPush,
|
||||
Resource: &model.Resource{
|
||||
Type: model.ResourceTypeArtifact,
|
||||
Metadata: &model.ResourceMetadata{
|
||||
Repository: &model.Repository{
|
||||
Name: event.Repository,
|
||||
Metadata: map[string]interface{}{
|
||||
"public": strconv.FormatBool(public),
|
||||
},
|
||||
},
|
||||
Artifacts: []*model.Artifact{
|
||||
{
|
||||
Type: art.Type,
|
||||
Digest: art.Digest,
|
||||
Tags: []string{event.Tag},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
return replication.EventHandler.Handle(e)
|
||||
}
|
54
src/api/event/project.go
Normal file
54
src/api/event/project.go
Normal file
@ -0,0 +1,54 @@
|
||||
// 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 (
|
||||
"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
|
||||
}
|
||||
|
||||
// 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(),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteProjectEventMetadata is the metadata from which the delete project event can be resolved
|
||||
type DeleteProjectEventMetadata struct {
|
||||
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(),
|
||||
}
|
||||
return nil
|
||||
}
|
61
src/api/event/project_test.go
Normal file
61
src/api/event/project_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
// 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 (
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type projectEventTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (p *projectEventTestSuite) TestResolveOfCreateProjectEventMetadata() {
|
||||
e := &event.Event{}
|
||||
metadata := &CreateProjectEventMetadata{
|
||||
Project: "library",
|
||||
Operator: "admin",
|
||||
}
|
||||
err := metadata.Resolve(e)
|
||||
p.Require().Nil(err)
|
||||
p.Equal(TopicCreateProject, e.Topic)
|
||||
p.Require().NotNil(e.Data)
|
||||
data, ok := e.Data.(*CreateProjectEvent)
|
||||
p.Require().True(ok)
|
||||
p.Equal("library", data.Project)
|
||||
p.Equal("admin", data.Operator)
|
||||
}
|
||||
|
||||
func (p *projectEventTestSuite) TestResolveOfDeleteProjectEventMetadata() {
|
||||
e := &event.Event{}
|
||||
metadata := &DeleteProjectEventMetadata{
|
||||
Project: "library",
|
||||
Operator: "admin",
|
||||
}
|
||||
err := metadata.Resolve(e)
|
||||
p.Require().Nil(err)
|
||||
p.Equal(TopicDeleteProject, e.Topic)
|
||||
p.Require().NotNil(e.Data)
|
||||
data, ok := e.Data.(*DeleteProjectEvent)
|
||||
p.Require().True(ok)
|
||||
p.Equal("library", data.Project)
|
||||
p.Equal("admin", data.Operator)
|
||||
}
|
||||
|
||||
func TestProjectEventTestSuite(t *testing.T) {
|
||||
suite.Run(t, &projectEventTestSuite{})
|
||||
}
|
43
src/api/event/repository.go
Normal file
43
src/api/event/repository.go
Normal file
@ -0,0 +1,43 @@
|
||||
// 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"
|
||||
"github.com/goharbor/harbor/src/common/security"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DeleteRepositoryEventMetadata is the metadata from which the delete repository event can be resolved
|
||||
type DeleteRepositoryEventMetadata struct {
|
||||
Ctx context.Context
|
||||
Repository string
|
||||
}
|
||||
|
||||
// Resolve to the event from the metadata
|
||||
func (d *DeleteRepositoryEventMetadata) Resolve(event *event.Event) error {
|
||||
data := &DeleteRepositoryEvent{
|
||||
Repository: d.Repository,
|
||||
OccurAt: time.Now(),
|
||||
}
|
||||
cx, exist := security.FromContext(d.Ctx)
|
||||
if exist {
|
||||
data.Operator = cx.GetUsername()
|
||||
}
|
||||
event.Topic = TopicDeleteRepository
|
||||
event.Data = data
|
||||
return nil
|
||||
}
|
45
src/api/event/repository_test.go
Normal file
45
src/api/event/repository_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
// 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"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type repositoryEventTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (r *repositoryEventTestSuite) TestResolveOfDeleteRepositoryEventMetadata() {
|
||||
e := &event.Event{}
|
||||
metadata := &DeleteRepositoryEventMetadata{
|
||||
Ctx: context.Background(),
|
||||
Repository: "library/hello-world",
|
||||
}
|
||||
err := metadata.Resolve(e)
|
||||
r.Require().Nil(err)
|
||||
r.Equal(TopicDeleteRepository, e.Topic)
|
||||
r.Require().NotNil(e.Data)
|
||||
data, ok := e.Data.(*DeleteRepositoryEvent)
|
||||
r.Require().True(ok)
|
||||
r.Equal("library/hello-world", data.Repository)
|
||||
}
|
||||
|
||||
func TestRepositoryEventTestSuite(t *testing.T) {
|
||||
suite.Run(t, &repositoryEventTestSuite{})
|
||||
}
|
71
src/api/event/tag.go
Normal file
71
src/api/event/tag.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 event
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/common/security"
|
||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CreateTagEventMetadata is the metadata from which the create tag event can be resolved
|
||||
type CreateTagEventMetadata struct {
|
||||
Ctx context.Context
|
||||
Tag string
|
||||
AttachedArtifact *artifact.Artifact
|
||||
}
|
||||
|
||||
// Resolve to the event from the metadata
|
||||
func (c *CreateTagEventMetadata) Resolve(event *event.Event) error {
|
||||
data := &CreateTagEvent{
|
||||
Repository: c.AttachedArtifact.RepositoryName,
|
||||
Tag: c.Tag,
|
||||
AttachedArtifact: c.AttachedArtifact,
|
||||
OccurAt: time.Now(),
|
||||
}
|
||||
cx, exist := security.FromContext(c.Ctx)
|
||||
if exist {
|
||||
data.Operator = cx.GetUsername()
|
||||
}
|
||||
event.Topic = TopicCreateTag
|
||||
event.Data = data
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteTagEventMetadata is the metadata from which the delete tag event can be resolved
|
||||
type DeleteTagEventMetadata struct {
|
||||
Ctx context.Context
|
||||
Tag string
|
||||
AttachedArtifact *artifact.Artifact
|
||||
}
|
||||
|
||||
// Resolve to the event from the metadata
|
||||
func (d *DeleteTagEventMetadata) Resolve(event *event.Event) error {
|
||||
data := &DeleteTagEvent{
|
||||
Repository: d.AttachedArtifact.RepositoryName,
|
||||
Tag: d.Tag,
|
||||
AttachedArtifact: d.AttachedArtifact,
|
||||
OccurAt: time.Now(),
|
||||
}
|
||||
ctx, exist := security.FromContext(d.Ctx)
|
||||
if exist {
|
||||
data.Operator = ctx.GetUsername()
|
||||
}
|
||||
event.Topic = TopicDeleteTag
|
||||
event.Data = data
|
||||
return nil
|
||||
}
|
65
src/api/event/tag_test.go
Normal file
65
src/api/event/tag_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
// 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"
|
||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type tagEventTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (t *tagEventTestSuite) TestResolveOfCreateTagEventMetadata() {
|
||||
e := &event.Event{}
|
||||
metadata := &CreateTagEventMetadata{
|
||||
Ctx: context.Background(),
|
||||
Tag: "latest",
|
||||
AttachedArtifact: &artifact.Artifact{ID: 1},
|
||||
}
|
||||
err := metadata.Resolve(e)
|
||||
t.Require().Nil(err)
|
||||
t.Equal(TopicCreateTag, e.Topic)
|
||||
t.Require().NotNil(e.Data)
|
||||
data, ok := e.Data.(*CreateTagEvent)
|
||||
t.Require().True(ok)
|
||||
t.Equal(int64(1), data.AttachedArtifact.ID)
|
||||
t.Equal("latest", data.Tag)
|
||||
}
|
||||
|
||||
func (t *tagEventTestSuite) TestResolveOfDeleteTagEventMetadata() {
|
||||
e := &event.Event{}
|
||||
metadata := &DeleteTagEventMetadata{
|
||||
Ctx: context.Background(),
|
||||
Tag: "latest",
|
||||
AttachedArtifact: &artifact.Artifact{ID: 1},
|
||||
}
|
||||
err := metadata.Resolve(e)
|
||||
t.Require().Nil(err)
|
||||
t.Equal(TopicDeleteTag, e.Topic)
|
||||
t.Require().NotNil(e.Data)
|
||||
data, ok := e.Data.(*DeleteTagEvent)
|
||||
t.Require().True(ok)
|
||||
t.Equal(int64(1), data.AttachedArtifact.ID)
|
||||
t.Equal("latest", data.Tag)
|
||||
}
|
||||
|
||||
func TestTagEventTestSuite(t *testing.T) {
|
||||
suite.Run(t, &tagEventTestSuite{})
|
||||
}
|
100
src/api/event/topic.go
Normal file
100
src/api/event/topic.go
Normal file
@ -0,0 +1,100 @@
|
||||
// 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 (
|
||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||
"time"
|
||||
)
|
||||
|
||||
// the event consumers can refer to this file to find all topics and the corresponding event structures
|
||||
|
||||
// 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"
|
||||
)
|
||||
|
||||
// CreateProjectEvent is the creating project event
|
||||
type CreateProjectEvent struct {
|
||||
Project string
|
||||
Operator string
|
||||
OccurAt time.Time
|
||||
}
|
||||
|
||||
// DeleteProjectEvent is the deleting project event
|
||||
type DeleteProjectEvent struct {
|
||||
Project string
|
||||
Operator string
|
||||
OccurAt time.Time
|
||||
}
|
||||
|
||||
// DeleteRepositoryEvent is the deleting repository event
|
||||
type DeleteRepositoryEvent struct {
|
||||
Repository string
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// CreateTagEvent is the creating tag event
|
||||
type CreateTagEvent struct {
|
||||
Repository string
|
||||
Tag string
|
||||
AttachedArtifact *artifact.Artifact
|
||||
Operator string
|
||||
OccurAt time.Time
|
||||
}
|
||||
|
||||
// DeleteTagEvent is the deleting tag event
|
||||
type DeleteTagEvent struct {
|
||||
Repository string
|
||||
Tag string
|
||||
AttachedArtifact *artifact.Artifact
|
||||
Operator string
|
||||
OccurAt time.Time
|
||||
}
|
@ -16,13 +16,7 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/api/event"
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/dao/project"
|
||||
@ -33,9 +27,15 @@ import (
|
||||
errutil "github.com/goharbor/harbor/src/common/utils/error"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
evt "github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
"github.com/goharbor/harbor/src/pkg/types"
|
||||
"github.com/pkg/errors"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type deletableResp struct {
|
||||
@ -211,19 +211,11 @@ func (p *ProjectAPI) Post() {
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err = dao.AddAccessLog(
|
||||
models.AccessLog{
|
||||
Username: p.SecurityCtx.GetUsername(),
|
||||
ProjectID: projectID,
|
||||
RepoName: pro.Name + "/",
|
||||
RepoTag: "N/A",
|
||||
Operation: "create",
|
||||
OpTime: time.Now(),
|
||||
}); err != nil {
|
||||
log.Errorf("failed to add access log: %v", err)
|
||||
}
|
||||
}()
|
||||
// fire event
|
||||
evt.BuildAndPublish(&event.CreateProjectEventMetadata{
|
||||
Project: pro.Name,
|
||||
Operator: owner,
|
||||
})
|
||||
|
||||
p.Redirect(http.StatusCreated, strconv.FormatInt(projectID, 10))
|
||||
}
|
||||
@ -301,18 +293,11 @@ func (p *ProjectAPI) Delete() {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := dao.AddAccessLog(models.AccessLog{
|
||||
Username: p.SecurityCtx.GetUsername(),
|
||||
ProjectID: p.project.ProjectID,
|
||||
RepoName: p.project.Name + "/",
|
||||
RepoTag: "N/A",
|
||||
Operation: "delete",
|
||||
OpTime: time.Now(),
|
||||
}); err != nil {
|
||||
log.Errorf("failed to add access log: %v", err)
|
||||
}
|
||||
}()
|
||||
// fire event
|
||||
evt.BuildAndPublish(&event.DeleteProjectEventMetadata{
|
||||
Project: p.project.Name,
|
||||
Operator: p.SecurityCtx.GetUsername(),
|
||||
})
|
||||
}
|
||||
|
||||
// Deletable ...
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
_ "github.com/astaxie/beego/session/redis"
|
||||
_ "github.com/goharbor/harbor/src/api/event/handler"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/job"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
|
53
src/internal/response_recorder.go
Normal file
53
src/internal/response_recorder.go
Normal file
@ -0,0 +1,53 @@
|
||||
// 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 internal
|
||||
|
||||
import "net/http"
|
||||
|
||||
// NewResponseRecorder creates a response recorder
|
||||
func NewResponseRecorder(w http.ResponseWriter) *ResponseRecorder {
|
||||
recorder := &ResponseRecorder{}
|
||||
recorder.ResponseWriter = w
|
||||
return recorder
|
||||
}
|
||||
|
||||
// ResponseRecorder is a wrapper for the http.ResponseWriter to record the response status code
|
||||
type ResponseRecorder struct {
|
||||
StatusCode int
|
||||
wroteHeader bool
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
// Write records the status code before writing data to the underlying writer
|
||||
func (r *ResponseRecorder) Write(data []byte) (int, error) {
|
||||
if !r.wroteHeader {
|
||||
r.WriteHeader(http.StatusOK)
|
||||
}
|
||||
return r.ResponseWriter.Write(data)
|
||||
}
|
||||
|
||||
// WriteHeader records the status code before writing the code to the underlying writer
|
||||
func (r *ResponseRecorder) WriteHeader(statusCode int) {
|
||||
if !r.wroteHeader {
|
||||
r.wroteHeader = true
|
||||
r.StatusCode = statusCode
|
||||
r.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// Success checks whether the status code is >= 200 & <= 399
|
||||
func (r *ResponseRecorder) Success() bool {
|
||||
return r.StatusCode >= http.StatusOK && r.StatusCode < http.StatusBadRequest
|
||||
}
|
56
src/internal/response_recorder_test.go
Normal file
56
src/internal/response_recorder_test.go
Normal file
@ -0,0 +1,56 @@
|
||||
// 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 internal
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type responseRecorderTestSuite struct {
|
||||
suite.Suite
|
||||
recorder *ResponseRecorder
|
||||
}
|
||||
|
||||
func (r *responseRecorderTestSuite) SetupTest() {
|
||||
r.recorder = NewResponseRecorder(httptest.NewRecorder())
|
||||
}
|
||||
|
||||
func (r *responseRecorderTestSuite) TestWriteHeader() {
|
||||
// write once
|
||||
r.recorder.WriteHeader(http.StatusInternalServerError)
|
||||
r.Equal(http.StatusInternalServerError, r.recorder.StatusCode)
|
||||
|
||||
// write again
|
||||
r.recorder.WriteHeader(http.StatusNotFound)
|
||||
r.Equal(http.StatusInternalServerError, r.recorder.StatusCode)
|
||||
}
|
||||
|
||||
func (r *responseRecorderTestSuite) TestWrite() {
|
||||
_, err := r.recorder.Write([]byte{'a'})
|
||||
r.Require().Nil(err)
|
||||
r.Equal(http.StatusOK, r.recorder.StatusCode)
|
||||
}
|
||||
|
||||
func (r *responseRecorderTestSuite) TestSuccess() {
|
||||
r.recorder.WriteHeader(http.StatusInternalServerError)
|
||||
r.False(r.recorder.Success())
|
||||
}
|
||||
|
||||
func TestResponseRecorder(t *testing.T) {
|
||||
suite.Run(t, &responseRecorderTestSuite{})
|
||||
}
|
@ -39,26 +39,6 @@ func (e *Event) WithTopicEvent(topicEvent TopicEvent) *Event {
|
||||
return e
|
||||
}
|
||||
|
||||
// Build an event by metadata
|
||||
func (e *Event) Build(metadata ...Metadata) error {
|
||||
for _, md := range metadata {
|
||||
if err := md.Resolve(e); err != nil {
|
||||
log.Debugf("failed to resolve event metadata: %v", md)
|
||||
return errors.Wrap(err, "failed to resolve event metadata")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Publish an event
|
||||
func (e *Event) Publish() error {
|
||||
if err := notifier.Publish(e.Topic, e.Data); err != nil {
|
||||
log.Debugf("failed to publish topic %s with event: %v", e.Topic, e.Data)
|
||||
return errors.Wrap(err, "failed to publish event")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Metadata is the event raw data to be processed
|
||||
type Metadata interface {
|
||||
Resolve(event *Event) error
|
||||
@ -322,3 +302,40 @@ func (h *HookMetaData) Resolve(evt *Event) error {
|
||||
evt.Data = data
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build an event by metadata
|
||||
func (e *Event) Build(metadata ...Metadata) error {
|
||||
for _, md := range metadata {
|
||||
if err := md.Resolve(e); err != nil {
|
||||
log.Debugf("failed to resolve event metadata: %v", md)
|
||||
return errors.Wrap(err, "failed to resolve event metadata")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Publish an event
|
||||
func (e *Event) Publish() error {
|
||||
if err := notifier.Publish(e.Topic, e.Data); err != nil {
|
||||
log.Debugf("failed to publish topic %s with event: %v", e.Topic, e.Data)
|
||||
return errors.Wrap(err, "failed to publish event")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildAndPublish builds the event according to the metadata and publish the event
|
||||
// The process is done in a separated goroutine
|
||||
func BuildAndPublish(metadata ...Metadata) {
|
||||
go func() {
|
||||
event := &Event{}
|
||||
if err := event.Build(metadata...); err != nil {
|
||||
log.Errorf("failed to build the event from metadata: %v", err)
|
||||
return
|
||||
}
|
||||
if err := event.Publish(); err != nil {
|
||||
log.Errorf("failed to publish the event %s: %v", event.Topic, err)
|
||||
return
|
||||
}
|
||||
log.Debugf("event %s published", event.Topic)
|
||||
}()
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ func (a *adapter) FetchImages(filters []*model.Filter) ([]*model.Resource, error
|
||||
}
|
||||
|
||||
rawResources[index] = &model.Resource{
|
||||
Type: model.ResourceTypeImage,
|
||||
Type: model.ResourceTypeArtifact,
|
||||
Registry: a.registry,
|
||||
Metadata: &model.ResourceMetadata{
|
||||
Repository: &model.Repository{
|
||||
|
@ -83,7 +83,7 @@ func TestFetchImages(t *testing.T) {
|
||||
resources, err := adapter.FetchImages(nil)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resources))
|
||||
assert.Equal(t, model.ResourceTypeImage, resources[0].Type)
|
||||
assert.Equal(t, model.ResourceTypeArtifact, resources[0].Type)
|
||||
assert.Equal(t, "library/hello-world", resources[0].Metadata.Repository.Name)
|
||||
assert.Equal(t, 2, len(resources[0].Metadata.Artifacts))
|
||||
assert.Equal(t, "1.0", resources[0].Metadata.Artifacts[0].Tags[0])
|
||||
@ -102,7 +102,7 @@ func TestFetchImages(t *testing.T) {
|
||||
resources, err = adapter.FetchImages(filters)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resources))
|
||||
assert.Equal(t, model.ResourceTypeImage, resources[0].Type)
|
||||
assert.Equal(t, model.ResourceTypeArtifact, resources[0].Type)
|
||||
assert.Equal(t, "library/hello-world", resources[0].Metadata.Repository.Name)
|
||||
assert.Equal(t, 1, len(resources[0].Metadata.Artifacts))
|
||||
assert.Equal(t, "1.0", resources[0].Metadata.Artifacts[0].Tags[0])
|
||||
|
@ -18,10 +18,10 @@ import "github.com/goharbor/harbor/src/replication/model"
|
||||
|
||||
// const definitions
|
||||
const (
|
||||
EventTypeImagePush = "image_push"
|
||||
EventTypeImageDelete = "image_delete"
|
||||
EventTypeChartUpload = "chart_upload"
|
||||
EventTypeChartDelete = "chart_delete"
|
||||
EventTypeArtifactPush = "artifact_push"
|
||||
EventTypeArtifactDelete = "artifact_delete"
|
||||
EventTypeChartUpload = "chart_upload"
|
||||
EventTypeChartDelete = "chart_delete"
|
||||
)
|
||||
|
||||
// Event is the model that defines the image/chart pull/push event
|
||||
|
@ -57,8 +57,8 @@ func (h *handler) Handle(event *Event) error {
|
||||
var policies []*model.Policy
|
||||
var err error
|
||||
switch event.Type {
|
||||
case EventTypeImagePush, EventTypeChartUpload,
|
||||
EventTypeImageDelete, EventTypeChartDelete:
|
||||
case EventTypeArtifactPush, EventTypeChartUpload,
|
||||
EventTypeArtifactDelete, EventTypeChartDelete:
|
||||
policies, err = h.getRelatedPolicies(event.Resource)
|
||||
default:
|
||||
return fmt.Errorf("unsupported event type %s", event.Type)
|
||||
|
@ -253,7 +253,7 @@ func TestHandle(t *testing.T) {
|
||||
Vtags: []string{},
|
||||
},
|
||||
},
|
||||
Type: EventTypeImagePush,
|
||||
Type: EventTypeArtifactPush,
|
||||
})
|
||||
require.NotNil(t, err)
|
||||
|
||||
@ -285,7 +285,7 @@ func TestHandle(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: EventTypeImagePush,
|
||||
Type: EventTypeArtifactPush,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
@ -303,7 +303,7 @@ func TestHandle(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: EventTypeImageDelete,
|
||||
Type: EventTypeArtifactDelete,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
@ -33,6 +33,9 @@ func init() {
|
||||
if err := trans.RegisterFactory(model.ResourceTypeImage, factory); err != nil {
|
||||
log.Errorf("failed to register transfer factory: %v", err)
|
||||
}
|
||||
if err := trans.RegisterFactory(model.ResourceTypeArtifact, factory); err != nil {
|
||||
log.Errorf("failed to register transfer factory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type repository struct {
|
||||
|
@ -16,10 +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/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"
|
||||
serror "github.com/goharbor/harbor/src/server/error"
|
||||
"github.com/goharbor/harbor/src/server/router"
|
||||
"github.com/opencontainers/go-digest"
|
||||
@ -43,9 +45,23 @@ func getManifest(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL.Path = strings.TrimSuffix(req.URL.Path, reference) + artifact.Digest
|
||||
req.URL.RawPath = req.URL.EscapedPath()
|
||||
}
|
||||
proxy.ServeHTTP(w, req)
|
||||
|
||||
// TODO fire event(only for GET method), add access log in the event handler
|
||||
recorder := internal.NewResponseRecorder(w)
|
||||
proxy.ServeHTTP(recorder, req)
|
||||
// fire event
|
||||
if recorder.Success() {
|
||||
// TODO don't fire event for the pulling from replication
|
||||
e := &event.PullArtifactEventMetadata{
|
||||
Ctx: req.Context(),
|
||||
Artifact: &artifact.Artifact,
|
||||
}
|
||||
// TODO provide a util function to determine whether the reference is tag or not
|
||||
// the reference is tag
|
||||
if _, err = digest.Parse(reference); err != nil {
|
||||
e.Tag = reference
|
||||
}
|
||||
evt.BuildAndPublish(e)
|
||||
}
|
||||
}
|
||||
|
||||
// just delete the artifact from database
|
||||
@ -74,8 +90,6 @@ func deleteManifest(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
|
||||
// TODO fire event, add access log in the event handler
|
||||
}
|
||||
|
||||
func putManifest(w http.ResponseWriter, req *http.Request) {
|
||||
@ -121,6 +135,4 @@ func putManifest(w http.ResponseWriter, req *http.Request) {
|
||||
if _, err := buffer.Flush(); err != nil {
|
||||
log.Errorf("failed to flush: %v", err)
|
||||
}
|
||||
|
||||
// TODO fire event, add access log in the event handler
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ package handler
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/api/event"
|
||||
evt "github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@ -238,6 +240,14 @@ func (a *artifactAPI) CreateTag(ctx context.Context, params operation.CreateTagP
|
||||
if _, err = a.tagCtl.Create(ctx, tag); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
// fire event
|
||||
evt.BuildAndPublish(&event.CreateTagEventMetadata{
|
||||
Ctx: ctx,
|
||||
Tag: tag.Name,
|
||||
AttachedArtifact: &art.Artifact,
|
||||
})
|
||||
|
||||
// TODO as we provide no API for get the single tag, ignore setting the location header here
|
||||
return operation.NewCreateTagCreated()
|
||||
}
|
||||
@ -269,6 +279,14 @@ func (a *artifactAPI) DeleteTag(ctx context.Context, params operation.DeleteTagP
|
||||
if err = a.tagCtl.Delete(ctx, id); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
// fire event
|
||||
evt.BuildAndPublish(&event.DeleteTagEventMetadata{
|
||||
Ctx: ctx,
|
||||
Tag: params.TagName,
|
||||
AttachedArtifact: &artifact.Artifact,
|
||||
})
|
||||
|
||||
return operation.NewDeleteTagOK()
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,13 @@ import (
|
||||
|
||||
"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"
|
||||
@ -141,5 +143,12 @@ func (r *repositoryAPI) DeleteRepository(ctx context.Context, params operation.D
|
||||
if err := r.repoCtl.Delete(ctx, repository.RepositoryID); err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
}
|
||||
|
||||
// fire event
|
||||
evt.BuildAndPublish(&event.DeleteRepositoryEventMetadata{
|
||||
Ctx: ctx,
|
||||
Repository: repository.Name,
|
||||
})
|
||||
|
||||
return operation.NewDeleteRepositoryOK()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user