add tag controller

use the tag controller to handle CRUD of tags, especially the delete scenario, it could validate
the immutable and signature. And move the code of tag handling from artifact controller to tag controller

Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
wang yan 2020-02-27 17:02:11 +08:00
parent 6709dfb13f
commit 79cf21f82f
14 changed files with 698 additions and 298 deletions

View File

@ -20,9 +20,8 @@ import (
"github.com/goharbor/harbor/src/api/artifact/abstractor"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/api/artifact/descriptor"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/api/tag"
"github.com/goharbor/harbor/src/internal"
"github.com/goharbor/harbor/src/pkg/art"
"github.com/goharbor/harbor/src/pkg/artifactrash"
"github.com/goharbor/harbor/src/pkg/artifactrash/model"
"github.com/goharbor/harbor/src/pkg/blob"
@ -45,8 +44,6 @@ import (
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/goharbor/harbor/src/pkg/repository"
"github.com/goharbor/harbor/src/pkg/tag"
tm "github.com/goharbor/harbor/src/pkg/tag/model/tag"
)
var (
@ -72,14 +69,14 @@ type Controller interface {
// Get the artifact specified by repository name and reference, the reference can be tag or digest,
// specify the properties returned with option
GetByReference(ctx context.Context, repository, reference string, option *Option) (artifact *Artifact, err error)
// Delete the artifact specified by ID. All tags attached to the artifact are deleted as well
// Delete the artifact specified by artifact ID
Delete(ctx context.Context, id int64) (err error)
// Copy the artifact specified by "srcRepo" and "reference" into the repository specified by "dstRepo"
Copy(ctx context.Context, srcRepo, reference, dstRepo string) (id int64, err error)
// ListTags lists the tags according to the query, specify the properties returned with option
ListTags(ctx context.Context, query *q.Query, option *TagOption) (tags []*Tag, err error)
ListTags(ctx context.Context, query *q.Query, option *tag.Option) (tags []*tag.Tag, err error)
// CreateTag creates a tag
CreateTag(ctx context.Context, tag *Tag) (id int64, err error)
CreateTag(ctx context.Context, tag *tag.Tag) (id int64, err error)
// DeleteTag deletes the tag specified by tagID
DeleteTag(ctx context.Context, tagID int64) (err error)
// UpdatePullTime updates the pull time for the artifact. If the tagID is provides, update the pull
@ -98,11 +95,11 @@ type Controller interface {
// NewController creates an instance of the default artifact controller
func NewController() Controller {
return &controller{
tagCtl: tag.Ctl,
repoMgr: repository.Mgr,
artMgr: artifact.Mgr,
artrashMgr: artifactrash.Mgr,
blobMgr: blob.Mgr,
tagMgr: tag.Mgr,
sigMgr: signature.GetManager(),
labelMgr: label.Mgr,
abstractor: abstractor.NewAbstractor(),
@ -114,11 +111,11 @@ func NewController() Controller {
// TODO concurrency summary
type controller struct {
tagCtl tag.Controller
repoMgr repository.Manager
artMgr artifact.Manager
artrashMgr artifactrash.Manager
blobMgr blob.Manager
tagMgr tag.Manager
sigMgr signature.Manager
labelMgr label.Manager
abstractor abstractor.Abstractor
@ -132,7 +129,7 @@ func (c *controller) Ensure(ctx context.Context, repository, digest string, tags
return false, 0, err
}
for _, tag := range tags {
if err = c.ensureTag(ctx, artifact.RepositoryID, artifact.ID, tag); err != nil {
if err = c.tagCtl.Ensure(ctx, artifact.RepositoryID, artifact.ID, tag); err != nil {
return false, 0, err
}
}
@ -189,44 +186,6 @@ func (c *controller) ensureArtifact(ctx context.Context, repository, digest stri
return true, artifact, nil
}
func (c *controller) ensureTag(ctx context.Context, repositoryID, artifactID int64, name string) error {
query := &q.Query{
Keywords: map[string]interface{}{
"repository_id": repositoryID,
"name": name,
},
}
tags, err := c.tagMgr.List(ctx, query)
if err != nil {
return err
}
// the tag already exists under the repository
if len(tags) > 0 {
tag := tags[0]
// the tag already exists under the repository and is attached to the artifact, return directly
if tag.ArtifactID == artifactID {
return nil
}
// the tag exists under the repository, but it is attached to other artifact
// update it to point to the provided artifact
tag.ArtifactID = artifactID
tag.PushTime = time.Now()
return c.tagMgr.Update(ctx, tag, "ArtifactID", "PushTime")
}
// the tag doesn't exist under the repository, create it
_, err = c.tagMgr.Create(ctx, &tm.Tag{
RepositoryID: repositoryID,
ArtifactID: artifactID,
Name: name,
PushTime: time.Now(),
})
// ignore the conflict error
if err != nil && ierror.IsConflictErr(err) {
return nil
}
return err
}
func (c *controller) Count(ctx context.Context, query *q.Query) (int64, error) {
return c.artMgr.Count(ctx, query)
}
@ -274,12 +233,12 @@ func (c *controller) getByTag(ctx context.Context, repository, tag string, optio
if err != nil {
return nil, err
}
tags, err := c.tagMgr.List(ctx, &q.Query{
tags, err := c.tagCtl.List(ctx, &q.Query{
Keywords: map[string]interface{}{
"RepositoryID": repo.RepositoryID,
"Name": tag,
},
})
}, nil)
if err != nil {
return nil, err
}
@ -341,7 +300,7 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot bool) er
// delete all tags that attached to the root artifact
if isRoot {
if err = c.tagMgr.DeleteOfArtifact(ctx, id); err != nil {
if err = c.tagCtl.DeleteTags(ctx, art.Tags); err != nil {
return err
}
}
@ -448,35 +407,25 @@ func (c *controller) copyDeeply(ctx context.Context, srcRepo, reference, dstRepo
return id, nil
}
func (c *controller) CreateTag(ctx context.Context, tag *Tag) (int64, error) {
func (c *controller) CreateTag(ctx context.Context, tag *tag.Tag) (int64, error) {
// TODO fire event
return c.tagMgr.Create(ctx, &(tag.Tag))
return c.tagCtl.Create(ctx, tag)
}
func (c *controller) ListTags(ctx context.Context, query *q.Query, option *TagOption) ([]*Tag, error) {
tgs, err := c.tagMgr.List(ctx, query)
func (c *controller) ListTags(ctx context.Context, query *q.Query, option *tag.Option) ([]*tag.Tag, error) {
tags, err := c.tagCtl.List(ctx, query, option)
if err != nil {
return nil, err
}
var tags []*Tag
for _, tg := range tgs {
art, err := c.artMgr.Get(ctx, tg.ArtifactID)
if err != nil {
return nil, err
}
tags = append(tags, c.assembleTag(ctx, art, tg, option))
}
return tags, nil
}
func (c *controller) DeleteTag(ctx context.Context, tagID int64) error {
// Immutable checking is covered in middleware
// TODO check signature
// TODO fire delete tag event
return c.tagMgr.Delete(ctx, tagID)
return c.tagCtl.Delete(ctx, tagID)
}
func (c *controller) UpdatePullTime(ctx context.Context, artifactID int64, tagID int64, time time.Time) error {
tag, err := c.tagMgr.Get(ctx, tagID)
tag, err := c.tagCtl.Get(ctx, tagID, nil)
if err != nil {
return err
}
@ -486,9 +435,7 @@ func (c *controller) UpdatePullTime(ctx context.Context, artifactID int64, tagID
if err := c.artMgr.UpdatePullTime(ctx, artifactID, time); err != nil {
return err
}
return c.tagMgr.Update(ctx, &tm.Tag{
ID: tagID,
}, "PullTime")
return c.tagCtl.Update(ctx, tag, "PullTime")
}
func (c *controller) GetAddition(ctx context.Context, artifactID int64, addition string) (*resolver.Addition, error) {
@ -527,62 +474,17 @@ func (c *controller) assembleArtifact(ctx context.Context, art *artifact.Artifac
return artifact
}
func (c *controller) populateTags(ctx context.Context, art *Artifact, option *TagOption) {
tags, err := c.tagMgr.List(ctx, &q.Query{
func (c *controller) populateTags(ctx context.Context, art *Artifact, option *tag.Option) {
tags, err := c.tagCtl.List(ctx, &q.Query{
Keywords: map[string]interface{}{
"artifact_id": art.ID,
},
})
}, option)
if err != nil {
log.Errorf("failed to list tag of artifact %d: %v", art.ID, err)
return
}
for _, tag := range tags {
art.Tags = append(art.Tags, c.assembleTag(ctx, &art.Artifact, tag, option))
}
}
// assemble several part into a single tag
func (c *controller) assembleTag(ctx context.Context, art *artifact.Artifact, tag *tm.Tag, option *TagOption) *Tag {
t := &Tag{
Tag: *tag,
}
if option == nil {
return t
}
if option.WithImmutableStatus {
c.populateImmutableStatus(ctx, art, t)
}
if option.WithSignature {
c.populateTagSignature(ctx, art, t, option)
}
return t
}
func (c *controller) populateImmutableStatus(ctx context.Context, artifact *artifact.Artifact, tag *Tag) {
_, repoName := utils.ParseRepository(artifact.RepositoryName)
matched, err := c.immutableMtr.Match(artifact.ProjectID, art.Candidate{
Repository: repoName,
Tags: []string{tag.Name},
NamespaceID: artifact.ProjectID,
})
if err != nil {
log.Error(err)
return
}
tag.Immutable = matched
}
func (c *controller) populateTagSignature(ctx context.Context, artifact *artifact.Artifact, tag *Tag, option *TagOption) {
if option.SignatureChecker == nil {
chk, err := signature.GetManager().GetCheckerByRepo(ctx, artifact.RepositoryName)
if err != nil {
log.Error(err)
return
}
option.SignatureChecker = chk
}
tag.Signed = option.SignatureChecker.IsTagSigned(tag.Name, artifact.Digest)
art.Tags = tags
}
func (c *controller) populateLabels(ctx context.Context, art *Artifact) {

View File

@ -16,17 +16,16 @@ package artifact
import (
"context"
"testing"
"time"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/api/artifact/descriptor"
"github.com/goharbor/harbor/src/api/tag"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/internal"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
model_tag "github.com/goharbor/harbor/src/pkg/tag/model/tag"
tagtesting "github.com/goharbor/harbor/src/testing/api/tag"
arttesting "github.com/goharbor/harbor/src/testing/pkg/artifact"
artrashtesting "github.com/goharbor/harbor/src/testing/pkg/artifactrash"
"github.com/goharbor/harbor/src/testing/pkg/blob"
@ -34,9 +33,10 @@ import (
"github.com/goharbor/harbor/src/testing/pkg/label"
"github.com/goharbor/harbor/src/testing/pkg/registry"
repotesting "github.com/goharbor/harbor/src/testing/pkg/repository"
tagtesting "github.com/goharbor/harbor/src/testing/pkg/tag"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"testing"
"time"
)
// TODO find another way to test artifact controller, it's hard to maintain currently
@ -77,7 +77,7 @@ type controllerTestSuite struct {
artMgr *arttesting.FakeManager
artrashMgr *artrashtesting.FakeManager
blobMgr *blob.Manager
tagMgr *tagtesting.FakeManager
tagCtl *tagtesting.FakeController
labelMgr *label.FakeManager
abstractor *fakeAbstractor
immutableMtr *immutesting.FakeMatcher
@ -89,7 +89,7 @@ func (c *controllerTestSuite) SetupTest() {
c.artMgr = &arttesting.FakeManager{}
c.artrashMgr = &artrashtesting.FakeManager{}
c.blobMgr = &blob.Manager{}
c.tagMgr = &tagtesting.FakeManager{}
c.tagCtl = &tagtesting.FakeController{}
c.labelMgr = &label.FakeManager{}
c.abstractor = &fakeAbstractor{}
c.immutableMtr = &immutesting.FakeMatcher{}
@ -99,7 +99,7 @@ func (c *controllerTestSuite) SetupTest() {
artMgr: c.artMgr,
artrashMgr: c.artrashMgr,
blobMgr: c.blobMgr,
tagMgr: c.tagMgr,
tagCtl: c.tagCtl,
labelMgr: c.labelMgr,
abstractor: c.abstractor,
immutableMtr: c.immutableMtr,
@ -108,34 +108,6 @@ func (c *controllerTestSuite) SetupTest() {
descriptor.Register(&fakeDescriptor{}, "")
}
func (c *controllerTestSuite) TestAssembleTag() {
art := &artifact.Artifact{
ID: 1,
ProjectID: 1,
RepositoryID: 1,
RepositoryName: "library/hello-world",
Digest: "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180",
}
tg := &tag.Tag{
ID: 1,
RepositoryID: 1,
ArtifactID: 1,
Name: "latest",
PushTime: time.Now(),
PullTime: time.Now(),
}
option := &TagOption{
WithImmutableStatus: true,
}
c.immutableMtr.On("Match").Return(true, nil)
tag := c.ctl.assembleTag(nil, art, tg, option)
c.Require().NotNil(tag)
c.Equal(tag.ID, tg.ID)
c.Equal(true, tag.Immutable)
// TODO check other fields of option
}
func (c *controllerTestSuite) TestAssembleArtifact() {
art := &artifact.Artifact{
ID: 1,
@ -144,20 +116,22 @@ func (c *controllerTestSuite) TestAssembleArtifact() {
}
option := &Option{
WithTag: true,
TagOption: &TagOption{
TagOption: &tag.Option{
WithImmutableStatus: false,
},
WithLabel: true,
}
tg := &tag.Tag{
ID: 1,
RepositoryID: 1,
ArtifactID: 1,
Name: "latest",
PushTime: time.Now(),
PullTime: time.Now(),
Tag: model_tag.Tag{
ID: 1,
RepositoryID: 1,
ArtifactID: 1,
Name: "latest",
PushTime: time.Now(),
PullTime: time.Now(),
},
}
c.tagMgr.On("List").Return([]*tag.Tag{tg}, nil)
c.tagCtl.On("List").Return([]*tag.Tag{tg}, nil)
ctx := internal.SetAPIVersion(nil, "2.0")
lb := &models.Label{
ID: 1,
@ -169,7 +143,7 @@ func (c *controllerTestSuite) TestAssembleArtifact() {
artifact := c.ctl.assembleArtifact(ctx, art, option)
c.Require().NotNil(artifact)
c.Equal(art.ID, artifact.ID)
c.Contains(artifact.Tags, &Tag{Tag: *tg})
c.Contains(artifact.Tags, tg)
c.Require().NotNil(artifact.AdditionLinks)
c.Require().NotNil(artifact.AdditionLinks["build_history"])
c.False(artifact.AdditionLinks["build_history"].Absolute)
@ -207,48 +181,6 @@ func (c *controllerTestSuite) TestEnsureArtifact() {
c.Equal(int64(1), art.ID)
}
func (c *controllerTestSuite) TestEnsureTag() {
// the tag already exists under the repository and is attached to the artifact
c.tagMgr.On("List").Return([]*tag.Tag{
{
ID: 1,
RepositoryID: 1,
ArtifactID: 1,
Name: "latest",
},
}, nil)
err := c.ctl.ensureTag(nil, 1, 1, "latest")
c.Require().Nil(err)
c.tagMgr.AssertExpectations(c.T())
// reset the mock
c.SetupTest()
// the tag exists under the repository, but it is attached to other artifact
c.tagMgr.On("List").Return([]*tag.Tag{
{
ID: 1,
RepositoryID: 1,
ArtifactID: 2,
Name: "latest",
},
}, nil)
c.tagMgr.On("Update").Return(nil)
err = c.ctl.ensureTag(nil, 1, 1, "latest")
c.Require().Nil(err)
c.tagMgr.AssertExpectations(c.T())
// reset the mock
c.SetupTest()
// the tag doesn't exist under the repository, create it
c.tagMgr.On("List").Return([]*tag.Tag{}, nil)
c.tagMgr.On("Create").Return(1, nil)
err = c.ctl.ensureTag(nil, 1, 1, "latest")
c.Require().Nil(err)
c.tagMgr.AssertExpectations(c.T())
}
func (c *controllerTestSuite) TestEnsure() {
digest := "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180"
@ -258,14 +190,13 @@ func (c *controllerTestSuite) TestEnsure() {
}, nil)
c.artMgr.On("GetByDigest").Return(nil, ierror.NotFoundError(nil))
c.artMgr.On("Create").Return(1, nil)
c.tagMgr.On("List").Return([]*tag.Tag{}, nil)
c.tagMgr.On("Create").Return(1, nil)
c.abstractor.On("AbstractMetadata").Return(nil)
c.tagCtl.On("Ensure").Return(nil)
_, id, err := c.ctl.Ensure(nil, "library/hello-world", digest, "latest")
c.Require().Nil(err)
c.repoMgr.AssertExpectations(c.T())
c.artMgr.AssertExpectations(c.T())
c.tagMgr.AssertExpectations(c.T())
c.tagCtl.AssertExpectations(c.T())
c.abstractor.AssertExpectations(c.T())
c.Equal(int64(1), id)
}
@ -288,12 +219,14 @@ func (c *controllerTestSuite) TestList() {
RepositoryID: 1,
},
}, nil)
c.tagMgr.On("List").Return([]*tag.Tag{
c.tagCtl.On("List").Return([]*tag.Tag{
{
ID: 1,
RepositoryID: 1,
ArtifactID: 1,
Name: "latest",
Tag: model_tag.Tag{
ID: 1,
RepositoryID: 1,
ArtifactID: 1,
Name: "latest",
},
},
}, nil)
c.repoMgr.On("Get").Return(&models.RepoRecord{
@ -357,7 +290,7 @@ func (c *controllerTestSuite) TestGetByTag() {
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
RepositoryID: 1,
}, nil)
c.tagMgr.On("List").Return(nil, nil)
c.tagCtl.On("List").Return(nil, nil)
art, err := c.ctl.getByTag(nil, "library/hello-world", "latest", nil)
c.Require().NotNil(err)
c.True(ierror.IsErr(err, ierror.NotFoundCode))
@ -369,12 +302,14 @@ func (c *controllerTestSuite) TestGetByTag() {
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
RepositoryID: 1,
}, nil)
c.tagMgr.On("List").Return([]*tag.Tag{
c.tagCtl.On("List").Return([]*tag.Tag{
{
ID: 1,
RepositoryID: 1,
Name: "latest",
ArtifactID: 1,
Tag: model_tag.Tag{
ID: 1,
RepositoryID: 1,
Name: "latest",
ArtifactID: 1,
},
},
}, nil)
c.artMgr.On("Get").Return(&artifact.Artifact{
@ -410,12 +345,14 @@ func (c *controllerTestSuite) TestGetByReference() {
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
RepositoryID: 1,
}, nil)
c.tagMgr.On("List").Return([]*tag.Tag{
c.tagCtl.On("List").Return([]*tag.Tag{
{
ID: 1,
RepositoryID: 1,
Name: "latest",
ArtifactID: 1,
Tag: model_tag.Tag{
ID: 1,
RepositoryID: 1,
Name: "latest",
ArtifactID: 1,
},
},
}, nil)
c.artMgr.On("Get").Return(&artifact.Artifact{
@ -449,9 +386,11 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
// child artifact and contains tags
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
c.artMgr.On("Delete").Return(nil)
c.tagMgr.On("List").Return([]*tag.Tag{
c.tagCtl.On("List").Return([]*tag.Tag{
{
ID: 1,
Tag: model_tag.Tag{
ID: 1,
},
},
}, nil)
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
@ -464,7 +403,7 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
// root artifact is referenced by other artifacts
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
c.tagMgr.On("List").Return(nil, nil)
c.tagCtl.On("List").Return(nil, nil)
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
c.artMgr.On("ListReferences").Return([]*artifact.Reference{
{
@ -479,7 +418,7 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
// child artifact contains no tag but referenced by other artifacts
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
c.tagMgr.On("List").Return(nil, nil)
c.tagCtl.On("List").Return(nil, nil)
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
c.artMgr.On("ListReferences").Return([]*artifact.Reference{
{
@ -494,15 +433,16 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
// root artifact is referenced by other artifacts
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
c.tagMgr.On("List").Return(nil, nil)
c.tagCtl.On("List").Return(nil, nil)
c.blobMgr.On("List", nil, mock.AnythingOfType("models.ListParams")).Return(nil, nil).Once()
c.blobMgr.On("CleanupAssociationsForProject", nil, int64(0), mock.AnythingOfType("[]*models.Blob")).Return(nil).Once()
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
c.artMgr.On("ListReferences").Return(nil, nil)
c.tagMgr.On("DeleteOfArtifact").Return(nil)
c.tagCtl.On("DeleteOfArtifact").Return(nil)
c.artMgr.On("Delete").Return(nil)
c.labelMgr.On("RemoveAllFrom").Return(nil)
c.artrashMgr.On("Create").Return(0, nil)
c.tagCtl.On("DeleteTags").Return(nil)
err = c.ctl.deleteDeeply(nil, 1, true)
c.Require().Nil(err)
}
@ -517,13 +457,15 @@ func (c *controllerTestSuite) TestCopy() {
Name: "library/hello-world",
}, nil)
c.artMgr.On("GetByDigest").Return(nil, ierror.NotFoundError(nil))
c.tagMgr.On("List").Return([]*tag.Tag{
c.tagCtl.On("List").Return([]*tag.Tag{
{
ID: 1,
Name: "latest",
Tag: model_tag.Tag{
ID: 1,
Name: "latest",
},
},
}, nil)
c.tagMgr.On("Update").Return(nil)
c.tagCtl.On("Update").Return(nil)
c.repoMgr.On("Get").Return(&models.RepoRecord{
RepositoryID: 1,
Name: "library/hello-world",
@ -531,66 +473,73 @@ func (c *controllerTestSuite) TestCopy() {
c.abstractor.On("AbstractMetadata").Return(nil)
c.artMgr.On("Create").Return(1, nil)
c.regCli.On("Copy").Return(nil)
c.tagCtl.On("Ensure").Return(nil)
_, err := c.ctl.Copy(nil, "library/hello-world", "latest", "library/hello-world2")
c.Require().Nil(err)
}
func (c *controllerTestSuite) TestListTags() {
c.tagMgr.On("List").Return([]*tag.Tag{
c.tagCtl.On("List").Return([]*tag.Tag{
{
ID: 1,
RepositoryID: 1,
Name: "latest",
ArtifactID: 1,
Tag: model_tag.Tag{
ID: 1,
RepositoryID: 1,
Name: "latest",
ArtifactID: 1,
},
},
}, nil)
c.artMgr.On("Get").Return(&artifact.Artifact{}, nil)
tags, err := c.ctl.ListTags(nil, nil, nil)
c.Require().Nil(err)
c.Len(tags, 1)
c.tagMgr.AssertExpectations(c.T())
c.tagCtl.AssertExpectations(c.T())
c.Equal(tags[0].Immutable, false)
// TODO check other properties: label, etc
}
func (c *controllerTestSuite) TestCreateTag() {
c.tagMgr.On("Create").Return(1, nil)
id, err := c.ctl.CreateTag(nil, &Tag{})
c.tagCtl.On("Create").Return(1, nil)
id, err := c.ctl.CreateTag(nil, &tag.Tag{})
c.Require().Nil(err)
c.Equal(int64(1), id)
}
func (c *controllerTestSuite) TestDeleteTag() {
c.tagMgr.On("Delete").Return(nil)
c.tagCtl.On("Delete").Return(nil)
err := c.ctl.DeleteTag(nil, 1)
c.Require().Nil(err)
c.tagMgr.AssertExpectations(c.T())
c.tagCtl.AssertExpectations(c.T())
}
func (c *controllerTestSuite) TestUpdatePullTime() {
// artifact ID and tag ID matches
c.tagMgr.On("Get").Return(&tag.Tag{
ID: 1,
ArtifactID: 1,
c.tagCtl.On("Get").Return(&tag.Tag{
Tag: model_tag.Tag{
ID: 1,
ArtifactID: 1,
},
}, nil)
c.artMgr.On("UpdatePullTime").Return(nil)
c.tagMgr.On("Update").Return(nil)
c.tagCtl.On("Update").Return(nil)
err := c.ctl.UpdatePullTime(nil, 1, 1, time.Now())
c.Require().Nil(err)
c.artMgr.AssertExpectations(c.T())
c.tagMgr.AssertExpectations(c.T())
c.tagCtl.AssertExpectations(c.T())
// reset the mock
c.SetupTest()
// artifact ID and tag ID doesn't match
c.tagMgr.On("Get").Return(&tag.Tag{
ID: 1,
ArtifactID: 2,
c.tagCtl.On("Get").Return(&tag.Tag{
Tag: model_tag.Tag{
ID: 1,
ArtifactID: 2,
},
}, nil)
err = c.ctl.UpdatePullTime(nil, 1, 1, time.Now())
c.Require().NotNil(err)
c.tagMgr.AssertExpectations(c.T())
c.tagCtl.AssertExpectations(c.T())
}

View File

@ -17,17 +17,16 @@ package artifact
import (
"fmt"
"github.com/goharbor/harbor/src/api/tag"
cmodels "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/signature"
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
)
// Artifact is the overall view of artifact
type Artifact struct {
artifact.Artifact
Tags []*Tag `json:"tags"` // the list of tags that attached to the artifact
Tags []*tag.Tag `json:"tags"` // the list of tags that attached to the artifact
AdditionLinks map[string]*AdditionLink `json:"addition_links"` // the resource link for build history(image), values.yaml(chart), dependency(chart), etc
Labels []*cmodels.Label `json:"labels"`
}
@ -44,13 +43,6 @@ func (artifact *Artifact) SetAdditionLink(addition, version string) {
artifact.AdditionLinks[addition] = &AdditionLink{HREF: href, Absolute: false}
}
// Tag is the overall view of tag
type Tag struct {
tag.Tag
Immutable bool `json:"immutable"`
Signed bool `json:"signed"`
}
// AdditionLink is a link via that the addition can be fetched
type AdditionLink struct {
HREF string `json:"href"`
@ -60,13 +52,6 @@ type AdditionLink struct {
// Option is used to specify the properties returned when listing/getting artifacts
type Option struct {
WithTag bool
TagOption *TagOption // only works when WithTag is set to true
TagOption *tag.Option // only works when WithTag is set to true
WithLabel bool
}
// TagOption is used to specify the properties returned when listing/getting tags
type TagOption struct {
WithImmutableStatus bool
WithSignature bool
SignatureChecker *signature.Checker
}

242
src/api/tag/controller.go Normal file
View File

@ -0,0 +1,242 @@
package tag
import (
"context"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/log"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/art"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/immutabletag/match"
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/goharbor/harbor/src/pkg/signature"
"github.com/goharbor/harbor/src/pkg/tag"
model_tag "github.com/goharbor/harbor/src/pkg/tag/model/tag"
"time"
)
var (
// Ctl is a global tag controller instance
Ctl = NewController()
)
// Controller manages the tags
type Controller interface {
// Ensure
Ensure(ctx context.Context, repositoryID, artifactID int64, name string) error
// Count returns the total count of tags according to the query.
Count(ctx context.Context, query *q.Query) (total int64, err error)
// List tags according to the query
List(ctx context.Context, query *q.Query, option *Option) (tags []*Tag, err error)
// Get the tag specified by ID
Get(ctx context.Context, id int64, option *Option) (tag *Tag, err error)
// Create the tag and returns the ID
Create(ctx context.Context, tag *Tag) (id int64, err error)
// Update the tag. Only the properties specified by "props" will be updated if it is set
Update(ctx context.Context, tag *Tag, props ...string) (err error)
// Delete the tag specified by ID with limitation check
Delete(ctx context.Context, id int64) (err error)
// DeleteTags deletes all tags
DeleteTags(ctx context.Context, tags []*Tag) (err error)
}
// NewController creates an instance of the default repository controller
func NewController() Controller {
return &controller{
tagMgr: tag.Mgr,
artMgr: artifact.Mgr,
immutableMtr: rule.NewRuleMatcher(),
}
}
type controller struct {
tagMgr tag.Manager
artMgr artifact.Manager
immutableMtr match.ImmutableTagMatcher
}
// Ensure ...
func (c *controller) Ensure(ctx context.Context, repositoryID, artifactID int64, name string) error {
query := &q.Query{
Keywords: map[string]interface{}{
"repository_id": repositoryID,
"name": name,
},
}
tags, err := c.List(ctx, query, &Option{
WithImmutableStatus: true,
WithSignature: true,
})
if err != nil {
return err
}
// the tag already exists under the repository
if len(tags) > 0 {
tag := tags[0]
// existing tag must check the immutable status and signature
if tag.Immutable {
return ierror.New(nil).WithCode(ierror.PreconditionCode).
WithMessage("the tag %s configured as immutable, cannot be updated", tag.Name)
}
if tag.Signed {
return ierror.New(nil).WithCode(ierror.PreconditionCode).
WithMessage("the tag %s with signature cannot be updated", tag.Name)
}
// the tag already exists under the repository and is attached to the artifact, return directly
if tag.ArtifactID == artifactID {
return nil
}
// the tag exists under the repository, but it is attached to other artifact
// update it to point to the provided artifact
tag.ArtifactID = artifactID
tag.PushTime = time.Now()
return c.Update(ctx, tag, "ArtifactID", "PushTime")
}
// the tag doesn't exist under the repository, create it
tag := &Tag{}
tag.RepositoryID = repositoryID
tag.ArtifactID = artifactID
tag.Name = name
tag.PushTime = time.Now()
_, err = c.Create(ctx, tag)
// ignore the conflict error
if err != nil && ierror.IsConflictErr(err) {
return nil
}
return err
}
// Count ...
func (c *controller) Count(ctx context.Context, query *q.Query) (total int64, err error) {
return c.tagMgr.Count(ctx, query)
}
// List ...
func (c *controller) List(ctx context.Context, query *q.Query, option *Option) ([]*Tag, error) {
tgs, err := c.tagMgr.List(ctx, query)
if err != nil {
return nil, err
}
var tags []*Tag
for _, tg := range tgs {
tags = append(tags, c.assembleTag(ctx, tg, option))
}
return tags, nil
}
// Get ...
func (c *controller) Get(ctx context.Context, id int64, option *Option) (tag *Tag, err error) {
tag = &Tag{}
daoTag, err := c.tagMgr.Get(ctx, id)
if err != nil {
return nil, err
}
tag.Tag = *daoTag
if option == nil {
return tag, nil
}
if option.WithImmutableStatus {
c.populateImmutableStatus(ctx, tag)
}
if option.WithSignature {
c.populateTagSignature(ctx, tag, option)
}
return tag, nil
}
// Create ...
func (c *controller) Create(ctx context.Context, tag *Tag) (id int64, err error) {
return c.tagMgr.Create(ctx, &(tag.Tag))
}
// Update ...
func (c *controller) Update(ctx context.Context, tag *Tag, props ...string) (err error) {
return c.tagMgr.Update(ctx, &tag.Tag, props...)
}
// Delete needs to check the signature and immutable status
func (c *controller) Delete(ctx context.Context, id int64) (err error) {
option := &Option{
WithImmutableStatus: true,
WithSignature: true,
}
tag, err := c.Get(ctx, id, option)
if err != nil {
return err
}
if tag.Immutable {
return ierror.New(nil).WithCode(ierror.PreconditionCode).
WithMessage("the tag %s configured as immutable, cannot be deleted", tag.Name)
}
if tag.Signed {
return ierror.New(nil).WithCode(ierror.PreconditionCode).
WithMessage("the tag %s with signature cannot be deleted", tag.Name)
}
return c.tagMgr.Delete(ctx, id)
}
// DeleteTags ...
func (c *controller) DeleteTags(ctx context.Context, tags []*Tag) (err error) {
// in order to leverage the signature and immutable status check
for _, tag := range tags {
if err := c.Delete(ctx, tag.ID); err != nil {
return err
}
}
return nil
}
// assemble several part into a single tag
func (c *controller) assembleTag(ctx context.Context, tag *model_tag.Tag, option *Option) *Tag {
t := &Tag{
Tag: *tag,
}
if option == nil {
return t
}
if option.WithImmutableStatus {
c.populateImmutableStatus(ctx, t)
}
if option.WithSignature {
c.populateTagSignature(ctx, t, option)
}
return t
}
func (c *controller) populateImmutableStatus(ctx context.Context, tag *Tag) {
artifact, err := c.artMgr.Get(ctx, tag.ArtifactID)
if err != nil {
return
}
_, repoName := utils.ParseRepository(artifact.RepositoryName)
matched, err := c.immutableMtr.Match(artifact.ProjectID, art.Candidate{
Repository: repoName,
Tags: []string{tag.Name},
NamespaceID: artifact.ProjectID,
})
if err != nil {
return
}
tag.Immutable = matched
}
func (c *controller) populateTagSignature(ctx context.Context, tag *Tag, option *Option) {
artifact, err := c.artMgr.Get(ctx, tag.ArtifactID)
if err != nil {
return
}
if option.SignatureChecker == nil {
chk, err := signature.GetManager().GetCheckerByRepo(ctx, artifact.RepositoryName)
if err != nil {
log.Error(err)
return
}
option.SignatureChecker = chk
}
tag.Signed = option.SignatureChecker.IsTagSigned(tag.Name, artifact.Digest)
}

View File

@ -0,0 +1,226 @@
package tag
import (
"github.com/goharbor/harbor/src/common"
coreConfig "github.com/goharbor/harbor/src/core/config"
ierror "github.com/goharbor/harbor/src/internal/error"
pkg_artifact "github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
"github.com/goharbor/harbor/src/testing/pkg/artifact"
immutesting "github.com/goharbor/harbor/src/testing/pkg/immutabletag"
"github.com/goharbor/harbor/src/testing/pkg/repository"
tagtesting "github.com/goharbor/harbor/src/testing/pkg/tag"
"github.com/stretchr/testify/suite"
"testing"
"time"
)
type controllerTestSuite struct {
suite.Suite
ctl *controller
repoMgr *repository.FakeManager
artMgr *artifact.FakeManager
tagMgr *tagtesting.FakeManager
immutableMtr *immutesting.FakeMatcher
}
func (c *controllerTestSuite) SetupTest() {
c.repoMgr = &repository.FakeManager{}
c.artMgr = &artifact.FakeManager{}
c.tagMgr = &tagtesting.FakeManager{}
c.immutableMtr = &immutesting.FakeMatcher{}
c.ctl = &controller{
tagMgr: c.tagMgr,
artMgr: c.artMgr,
immutableMtr: c.immutableMtr,
}
var tagCtlTestConfig = map[string]interface{}{
common.WithNotary: false,
}
coreConfig.InitWithSettings(tagCtlTestConfig)
}
func (c *controllerTestSuite) TestEnsureTag() {
// the tag already exists under the repository and is attached to the artifact
c.tagMgr.On("List").Return([]*tag.Tag{
{
ID: 1,
RepositoryID: 1,
ArtifactID: 1,
Name: "latest",
},
}, nil)
c.artMgr.On("Get").Return(&pkg_artifact.Artifact{
ID: 1,
}, nil)
c.immutableMtr.On("Match").Return(false, nil)
err := c.ctl.Ensure(nil, 1, 1, "latest")
c.Require().Nil(err)
c.tagMgr.AssertExpectations(c.T())
// reset the mock
c.SetupTest()
// the tag exists under the repository, but it is attached to other artifact
c.tagMgr.On("List").Return([]*tag.Tag{
{
ID: 1,
RepositoryID: 1,
ArtifactID: 2,
Name: "latest",
},
}, nil)
c.tagMgr.On("Update").Return(nil)
c.artMgr.On("Get").Return(&pkg_artifact.Artifact{
ID: 1,
}, nil)
c.immutableMtr.On("Match").Return(false, nil)
err = c.ctl.Ensure(nil, 1, 1, "latest")
c.Require().Nil(err)
c.tagMgr.AssertExpectations(c.T())
// reset the mock
c.SetupTest()
// the tag doesn't exist under the repository, create it
c.tagMgr.On("List").Return([]*tag.Tag{}, nil)
c.tagMgr.On("Create").Return(1, nil)
c.artMgr.On("Get").Return(&pkg_artifact.Artifact{
ID: 1,
}, nil)
c.immutableMtr.On("Match").Return(false, nil)
err = c.ctl.Ensure(nil, 1, 1, "latest")
c.Require().Nil(err)
c.tagMgr.AssertExpectations(c.T())
}
func (c *controllerTestSuite) TestCount() {
c.tagMgr.On("Count").Return(1, nil)
total, err := c.ctl.Count(nil, nil)
c.Require().Nil(err)
c.Equal(int64(1), total)
}
func (c *controllerTestSuite) TestList() {
c.tagMgr.On("List").Return([]*tag.Tag{
{
RepositoryID: 1,
Name: "testlist",
},
}, nil)
tags, err := c.ctl.List(nil, nil, nil)
c.Require().Nil(err)
c.Require().Len(tags, 1)
c.Equal(int64(1), tags[0].RepositoryID)
c.Equal("testlist", tags[0].Name)
}
func (c *controllerTestSuite) TestGet() {
getTest := &tag.Tag{}
getTest.RepositoryID = 1
getTest.Name = "testget"
c.tagMgr.On("Get").Return(getTest, nil)
tag, err := c.ctl.Get(nil, 1, nil)
c.Require().Nil(err)
c.tagMgr.AssertExpectations(c.T())
c.Equal(int64(1), tag.RepositoryID)
c.Equal(false, tag.Immutable)
}
func (c *controllerTestSuite) TestDelete() {
c.tagMgr.On("Get").Return(&tag.Tag{
RepositoryID: 1,
Name: "test",
}, nil)
c.artMgr.On("Get").Return(&pkg_artifact.Artifact{
ID: 1,
}, nil)
c.immutableMtr.On("Match").Return(false, nil)
c.tagMgr.On("Delete").Return(nil)
err := c.ctl.Delete(nil, 1)
c.Require().Nil(err)
}
func (c *controllerTestSuite) TestDeleteImmutable() {
c.tagMgr.On("Get").Return(&tag.Tag{
RepositoryID: 1,
Name: "test",
}, nil)
c.artMgr.On("Get").Return(&pkg_artifact.Artifact{
ID: 1,
}, nil)
c.immutableMtr.On("Match").Return(true, nil)
c.tagMgr.On("Delete").Return(nil)
err := c.ctl.Delete(nil, 1)
c.Require().NotNil(err)
c.True(ierror.IsErr(err, ierror.PreconditionCode))
}
func (c *controllerTestSuite) TestUpdate() {
c.tagMgr.On("Update").Return(nil)
err := c.ctl.Update(nil, &Tag{
Tag: tag.Tag{
RepositoryID: 1,
Name: "test",
},
Immutable: true,
}, "ArtifactID")
c.Require().Nil(err)
}
func (c *controllerTestSuite) TestDeleteTags() {
c.tagMgr.On("Get").Return(&tag.Tag{
RepositoryID: 1,
}, nil)
c.artMgr.On("Get").Return(&pkg_artifact.Artifact{
ID: 1,
}, nil)
c.immutableMtr.On("Match").Return(false, nil)
c.tagMgr.On("Delete").Return(nil)
tags := []*Tag{
{
Tag: tag.Tag{
RepositoryID: 10,
Name: "test2",
},
Signed: true,
},
}
err := c.ctl.DeleteTags(nil, tags)
c.Require().Nil(err)
}
func (c *controllerTestSuite) TestAssembleTag() {
art := &pkg_artifact.Artifact{
ID: 1,
ProjectID: 1,
RepositoryID: 1,
RepositoryName: "library/hello-world",
Digest: "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180",
}
tg := &tag.Tag{
ID: 1,
RepositoryID: 1,
ArtifactID: 1,
Name: "latest",
PushTime: time.Now(),
PullTime: time.Now(),
}
option := &Option{
WithImmutableStatus: true,
}
c.artMgr.On("Get").Return(art, nil)
c.immutableMtr.On("Match").Return(true, nil)
tag := c.ctl.assembleTag(nil, tg, option)
c.Require().NotNil(tag)
c.Equal(tag.ID, tg.ID)
c.Equal(true, tag.Immutable)
// TODO check signature
}
func TestControllerTestSuite(t *testing.T) {
suite.Run(t, &controllerTestSuite{})
}

20
src/api/tag/model.go Normal file
View File

@ -0,0 +1,20 @@
package tag
import (
"github.com/goharbor/harbor/src/pkg/signature"
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
)
// Tag is the overall view of tag
type Tag struct {
tag.Tag
Immutable bool `json:"immutable"`
Signed bool `json:"signed"`
}
// Option is used to specify the properties returned when listing/getting tags
type Option struct {
WithImmutableStatus bool
WithSignature bool
SignatureChecker *signature.Checker
}

View File

@ -16,7 +16,8 @@ package dep
import (
modelsv2 "github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
"github.com/goharbor/harbor/src/api/tag"
model_tag "github.com/goharbor/harbor/src/pkg/tag/model/tag"
"testing"
"github.com/goharbor/harbor/src/chartserver"
@ -38,9 +39,9 @@ type fakeCoreClient struct {
func (f *fakeCoreClient) ListAllArtifacts(project, repository string) ([]*modelsv2.Artifact, error) {
image := &modelsv2.Artifact{}
image.Digest = "sha256:123456"
image.Tags = []*modelsv2.Tag{
image.Tags = []*tag.Tag{
{
Tag: tag.Tag{
Tag: model_tag.Tag{
Name: "latest",
},
},

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/tag"
common_util "github.com/goharbor/harbor/src/common/utils"
internal_errors "github.com/goharbor/harbor/src/internal/error"
serror "github.com/goharbor/harbor/src/server/error"
@ -40,7 +41,7 @@ func handleDelete(req *http.Request) error {
af, err := artifact.Ctl.GetByReference(req.Context(), art.Repository, art.Digest, &artifact.Option{
WithTag: true,
TagOption: &artifact.TagOption{WithImmutableStatus: true},
TagOption: &tag.Option{WithImmutableStatus: true},
})
if err != nil {
if internal_errors.IsErr(err, internal_errors.NotFoundCode) {

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/tag"
common_util "github.com/goharbor/harbor/src/common/utils"
internal_errors "github.com/goharbor/harbor/src/internal/error"
serror "github.com/goharbor/harbor/src/server/error"
@ -42,7 +43,7 @@ func handlePush(req *http.Request) error {
af, err := artifact.Ctl.GetByReference(req.Context(), art.Repository, art.Tag, &artifact.Option{
WithTag: true,
TagOption: &artifact.TagOption{WithImmutableStatus: true},
TagOption: &tag.Option{WithImmutableStatus: true},
})
if err != nil {
if internal_errors.IsErr(err, internal_errors.NotFoundCode) {

View File

@ -18,8 +18,9 @@ import (
"encoding/json"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/repository"
"github.com/goharbor/harbor/src/api/tag"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
model_tag "github.com/goharbor/harbor/src/pkg/tag/model/tag"
arttesting "github.com/goharbor/harbor/src/testing/api/artifact"
repotesting "github.com/goharbor/harbor/src/testing/api/repository"
"github.com/stretchr/testify/suite"
@ -64,15 +65,15 @@ func (c *tagTestSuite) TestListTag() {
RepositoryID: 1,
Name: "library/hello-world",
}, nil)
c.artCtl.On("ListTags").Return([]*artifact.Tag{
c.artCtl.On("ListTags").Return([]*tag.Tag{
{
Tag: tag.Tag{
Tag: model_tag.Tag{
RepositoryID: 1,
Name: "v1",
},
},
{
Tag: tag.Tag{
Tag: model_tag.Tag{
RepositoryID: 1,
Name: "v2",
},
@ -99,15 +100,15 @@ func (c *tagTestSuite) TestListTagPagination1() {
RepositoryID: 1,
Name: "hello-world",
}, nil)
c.artCtl.On("ListTags").Return([]*artifact.Tag{
c.artCtl.On("ListTags").Return([]*tag.Tag{
{
Tag: tag.Tag{
Tag: model_tag.Tag{
RepositoryID: 1,
Name: "v1",
},
},
{
Tag: tag.Tag{
Tag: model_tag.Tag{
RepositoryID: 1,
Name: "v2",
},
@ -135,15 +136,15 @@ func (c *tagTestSuite) TestListTagPagination2() {
RepositoryID: 1,
Name: "hello-world",
}, nil)
c.artCtl.On("ListTags").Return([]*artifact.Tag{
c.artCtl.On("ListTags").Return([]*tag.Tag{
{
Tag: tag.Tag{
Tag: model_tag.Tag{
RepositoryID: 1,
Name: "v1",
},
},
{
Tag: tag.Tag{
Tag: model_tag.Tag{
RepositoryID: 1,
Name: "v2",
},
@ -171,15 +172,15 @@ func (c *tagTestSuite) TestListTagPagination3() {
RepositoryID: 1,
Name: "hello-world",
}, nil)
c.artCtl.On("ListTags").Return([]*artifact.Tag{
c.artCtl.On("ListTags").Return([]*tag.Tag{
{
Tag: tag.Tag{
Tag: model_tag.Tag{
RepositoryID: 1,
Name: "v1",
},
},
{
Tag: tag.Tag{
Tag: model_tag.Tag{
RepositoryID: 1,
Name: "v2",
},

View File

@ -28,6 +28,7 @@ import (
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"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"
@ -232,7 +233,7 @@ func (a *artifactAPI) CreateTag(ctx context.Context, params operation.CreateTagP
if err != nil {
return a.SendError(ctx, err)
}
tag := &artifact.Tag{}
tag := &tag.Tag{}
tag.RepositoryID = art.RepositoryID
tag.ArtifactID = art.ID
tag.Name = params.Tag.Name
@ -340,7 +341,7 @@ func option(withTag, withImmutableStatus, withLabel, withSignature *bool) *artif
}
if option.WithTag {
option.TagOption = &artifact.TagOption{
option.TagOption = &tag.Option{
WithImmutableStatus: boolValue(withImmutableStatus),
WithSignature: boolValue(withSignature),
}

View File

@ -18,6 +18,7 @@ import (
"context"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/api/tag"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/stretchr/testify/mock"
"time"
@ -83,17 +84,17 @@ func (f *FakeController) Copy(ctx context.Context, srcRepo, ref, dstRepo string)
}
// ListTags ...
func (f *FakeController) ListTags(ctx context.Context, query *q.Query, option *artifact.TagOption) ([]*artifact.Tag, error) {
func (f *FakeController) ListTags(ctx context.Context, query *q.Query, option *tag.Option) ([]*tag.Tag, error) {
args := f.Called()
var tags []*artifact.Tag
var tags []*tag.Tag
if args.Get(0) != nil {
tags = args.Get(0).([]*artifact.Tag)
tags = args.Get(0).([]*tag.Tag)
}
return tags, args.Error(1)
}
// CreateTag ...
func (f *FakeController) CreateTag(ctx context.Context, tag *artifact.Tag) (int64, error) {
func (f *FakeController) CreateTag(ctx context.Context, tag *tag.Tag) (int64, error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}

View File

@ -0,0 +1,69 @@
package tag
import (
"context"
"github.com/goharbor/harbor/src/api/tag"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/stretchr/testify/mock"
)
// FakeController is a fake artifact controller that implement src/api/tag.Controller interface
type FakeController struct {
mock.Mock
}
// Ensure ...
func (f *FakeController) Ensure(ctx context.Context, repositoryID, artifactID int64, name string) error {
args := f.Called()
return args.Error(0)
}
// Count ...
func (f *FakeController) Count(ctx context.Context, query *q.Query) (total int64, err error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}
// List ...
func (f *FakeController) List(ctx context.Context, query *q.Query, option *tag.Option) ([]*tag.Tag, error) {
args := f.Called()
var tags []*tag.Tag
if args.Get(0) != nil {
tags = args.Get(0).([]*tag.Tag)
}
return tags, args.Error(1)
}
// Get ...
func (f *FakeController) Get(ctx context.Context, id int64, option *tag.Option) (*tag.Tag, error) {
args := f.Called()
var tg *tag.Tag
if args.Get(0) != nil {
tg = args.Get(0).(*tag.Tag)
}
return tg, args.Error(1)
}
// Create ...
func (f *FakeController) Create(ctx context.Context, tag *tag.Tag) (id int64, err error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}
// Update ...
func (f *FakeController) Update(ctx context.Context, tag *tag.Tag, props ...string) (err error) {
args := f.Called()
return args.Error(0)
}
// Delete ...
func (f *FakeController) Delete(ctx context.Context, id int64) (err error) {
args := f.Called()
return args.Error(0)
}
// DeleteTags ...
func (f *FakeController) DeleteTags(ctx context.Context, tags []*tag.Tag) (err error) {
args := f.Called()
return args.Error(0)
}

View File

@ -25,11 +25,12 @@ class TestProjects(unittest.TestCase):
@unittest.skipIf(TEARDOWN == False, "Test data won't be erased.")
def test_ClearData(self):
# remove the deletion as the signed image cannot be deleted.
#1. Delete repository(RA) by user(UA);
self.repo.delete_repoitory(TestProjects.project_sign_image_name, TestProjects.repo_name.split('/')[1], **TestProjects.USER_sign_image_CLIENT)
#self.repo.delete_repoitory(TestProjects.project_sign_image_name, TestProjects.repo_name.split('/')[1], **TestProjects.USER_sign_image_CLIENT)
#2. Delete project(PA);
self.project.delete_project(TestProjects.project_sign_image_id, **TestProjects.USER_sign_image_CLIENT)
#self.project.delete_project(TestProjects.project_sign_image_id, **TestProjects.USER_sign_image_CLIENT)
#3. Delete user(UA);
self.user.delete_user(TestProjects.user_sign_image_id, **ADMIN_CLIENT)