mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-11 18:38:14 +01:00
Merge pull request #10882 from wy65701436/tag-controller
add tag controller
This commit is contained in:
commit
3d336bfac3
src
api
pkg/retention/dep
server
testing/api
tests/apitests/python
@ -20,9 +20,8 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/api/artifact/abstractor"
|
"github.com/goharbor/harbor/src/api/artifact/abstractor"
|
||||||
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
|
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
|
||||||
"github.com/goharbor/harbor/src/api/artifact/descriptor"
|
"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/internal"
|
||||||
"github.com/goharbor/harbor/src/pkg/art"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/artifactrash"
|
"github.com/goharbor/harbor/src/pkg/artifactrash"
|
||||||
"github.com/goharbor/harbor/src/pkg/artifactrash/model"
|
"github.com/goharbor/harbor/src/pkg/artifactrash/model"
|
||||||
"github.com/goharbor/harbor/src/pkg/blob"
|
"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/artifact"
|
||||||
"github.com/goharbor/harbor/src/pkg/q"
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
"github.com/goharbor/harbor/src/pkg/repository"
|
"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 (
|
var (
|
||||||
@ -72,16 +69,10 @@ type Controller interface {
|
|||||||
// Get the artifact specified by repository name and reference, the reference can be tag or digest,
|
// Get the artifact specified by repository name and reference, the reference can be tag or digest,
|
||||||
// specify the properties returned with option
|
// specify the properties returned with option
|
||||||
GetByReference(ctx context.Context, repository, reference string, option *Option) (artifact *Artifact, err error)
|
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)
|
Delete(ctx context.Context, id int64) (err error)
|
||||||
// Copy the artifact specified by "srcRepo" and "reference" into the repository specified by "dstRepo"
|
// 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)
|
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)
|
|
||||||
// CreateTag creates a tag
|
|
||||||
CreateTag(ctx context.Context, 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
|
// UpdatePullTime updates the pull time for the artifact. If the tagID is provides, update the pull
|
||||||
// time of the tag as well
|
// time of the tag as well
|
||||||
UpdatePullTime(ctx context.Context, artifactID int64, tagID int64, time time.Time) (err error)
|
UpdatePullTime(ctx context.Context, artifactID int64, tagID int64, time time.Time) (err error)
|
||||||
@ -98,11 +89,11 @@ type Controller interface {
|
|||||||
// NewController creates an instance of the default artifact controller
|
// NewController creates an instance of the default artifact controller
|
||||||
func NewController() Controller {
|
func NewController() Controller {
|
||||||
return &controller{
|
return &controller{
|
||||||
|
tagCtl: tag.Ctl,
|
||||||
repoMgr: repository.Mgr,
|
repoMgr: repository.Mgr,
|
||||||
artMgr: artifact.Mgr,
|
artMgr: artifact.Mgr,
|
||||||
artrashMgr: artifactrash.Mgr,
|
artrashMgr: artifactrash.Mgr,
|
||||||
blobMgr: blob.Mgr,
|
blobMgr: blob.Mgr,
|
||||||
tagMgr: tag.Mgr,
|
|
||||||
sigMgr: signature.GetManager(),
|
sigMgr: signature.GetManager(),
|
||||||
labelMgr: label.Mgr,
|
labelMgr: label.Mgr,
|
||||||
abstractor: abstractor.NewAbstractor(),
|
abstractor: abstractor.NewAbstractor(),
|
||||||
@ -114,11 +105,11 @@ func NewController() Controller {
|
|||||||
// TODO concurrency summary
|
// TODO concurrency summary
|
||||||
|
|
||||||
type controller struct {
|
type controller struct {
|
||||||
|
tagCtl tag.Controller
|
||||||
repoMgr repository.Manager
|
repoMgr repository.Manager
|
||||||
artMgr artifact.Manager
|
artMgr artifact.Manager
|
||||||
artrashMgr artifactrash.Manager
|
artrashMgr artifactrash.Manager
|
||||||
blobMgr blob.Manager
|
blobMgr blob.Manager
|
||||||
tagMgr tag.Manager
|
|
||||||
sigMgr signature.Manager
|
sigMgr signature.Manager
|
||||||
labelMgr label.Manager
|
labelMgr label.Manager
|
||||||
abstractor abstractor.Abstractor
|
abstractor abstractor.Abstractor
|
||||||
@ -132,7 +123,7 @@ func (c *controller) Ensure(ctx context.Context, repository, digest string, tags
|
|||||||
return false, 0, err
|
return false, 0, err
|
||||||
}
|
}
|
||||||
for _, tag := range tags {
|
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
|
return false, 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,44 +180,6 @@ func (c *controller) ensureArtifact(ctx context.Context, repository, digest stri
|
|||||||
return true, artifact, nil
|
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) {
|
func (c *controller) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||||
return c.artMgr.Count(ctx, query)
|
return c.artMgr.Count(ctx, query)
|
||||||
}
|
}
|
||||||
@ -274,12 +227,12 @@ func (c *controller) getByTag(ctx context.Context, repository, tag string, optio
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tags, err := c.tagMgr.List(ctx, &q.Query{
|
tags, err := c.tagCtl.List(ctx, &q.Query{
|
||||||
Keywords: map[string]interface{}{
|
Keywords: map[string]interface{}{
|
||||||
"RepositoryID": repo.RepositoryID,
|
"RepositoryID": repo.RepositoryID,
|
||||||
"Name": tag,
|
"Name": tag,
|
||||||
},
|
},
|
||||||
})
|
}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -341,7 +294,11 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot bool) er
|
|||||||
|
|
||||||
// delete all tags that attached to the root artifact
|
// delete all tags that attached to the root artifact
|
||||||
if isRoot {
|
if isRoot {
|
||||||
if err = c.tagMgr.DeleteOfArtifact(ctx, id); err != nil {
|
var ids []int64
|
||||||
|
for _, tag := range art.Tags {
|
||||||
|
ids = append(ids, tag.ID)
|
||||||
|
}
|
||||||
|
if err = c.tagCtl.DeleteTags(ctx, ids); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -448,35 +405,8 @@ func (c *controller) copyDeeply(ctx context.Context, srcRepo, reference, dstRepo
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) CreateTag(ctx context.Context, tag *Tag) (int64, error) {
|
|
||||||
// TODO fire event
|
|
||||||
return c.tagMgr.Create(ctx, &(tag.Tag))
|
|
||||||
}
|
|
||||||
func (c *controller) ListTags(ctx context.Context, query *q.Query, option *TagOption) ([]*Tag, error) {
|
|
||||||
tgs, err := c.tagMgr.List(ctx, query)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *controller) UpdatePullTime(ctx context.Context, artifactID int64, tagID int64, time time.Time) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -486,9 +416,7 @@ func (c *controller) UpdatePullTime(ctx context.Context, artifactID int64, tagID
|
|||||||
if err := c.artMgr.UpdatePullTime(ctx, artifactID, time); err != nil {
|
if err := c.artMgr.UpdatePullTime(ctx, artifactID, time); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.tagMgr.Update(ctx, &tm.Tag{
|
return c.tagCtl.Update(ctx, tag, "PullTime")
|
||||||
ID: tagID,
|
|
||||||
}, "PullTime")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) GetAddition(ctx context.Context, artifactID int64, addition string) (*resolver.Addition, error) {
|
func (c *controller) GetAddition(ctx context.Context, artifactID int64, addition string) (*resolver.Addition, error) {
|
||||||
@ -527,62 +455,17 @@ func (c *controller) assembleArtifact(ctx context.Context, art *artifact.Artifac
|
|||||||
return artifact
|
return artifact
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) populateTags(ctx context.Context, art *Artifact, option *TagOption) {
|
func (c *controller) populateTags(ctx context.Context, art *Artifact, option *tag.Option) {
|
||||||
tags, err := c.tagMgr.List(ctx, &q.Query{
|
tags, err := c.tagCtl.List(ctx, &q.Query{
|
||||||
Keywords: map[string]interface{}{
|
Keywords: map[string]interface{}{
|
||||||
"artifact_id": art.ID,
|
"artifact_id": art.ID,
|
||||||
},
|
},
|
||||||
})
|
}, option)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to list tag of artifact %d: %v", art.ID, err)
|
log.Errorf("failed to list tag of artifact %d: %v", art.ID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, tag := range tags {
|
art.Tags = 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) populateLabels(ctx context.Context, art *Artifact) {
|
func (c *controller) populateLabels(ctx context.Context, art *Artifact) {
|
||||||
|
@ -16,17 +16,16 @@ package artifact
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
|
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
|
||||||
"github.com/goharbor/harbor/src/api/artifact/descriptor"
|
"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/common/models"
|
||||||
"github.com/goharbor/harbor/src/internal"
|
"github.com/goharbor/harbor/src/internal"
|
||||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||||
"github.com/goharbor/harbor/src/pkg/q"
|
"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"
|
arttesting "github.com/goharbor/harbor/src/testing/pkg/artifact"
|
||||||
artrashtesting "github.com/goharbor/harbor/src/testing/pkg/artifactrash"
|
artrashtesting "github.com/goharbor/harbor/src/testing/pkg/artifactrash"
|
||||||
"github.com/goharbor/harbor/src/testing/pkg/blob"
|
"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/label"
|
||||||
"github.com/goharbor/harbor/src/testing/pkg/registry"
|
"github.com/goharbor/harbor/src/testing/pkg/registry"
|
||||||
repotesting "github.com/goharbor/harbor/src/testing/pkg/repository"
|
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/mock"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO find another way to test artifact controller, it's hard to maintain currently
|
// TODO find another way to test artifact controller, it's hard to maintain currently
|
||||||
@ -77,7 +77,7 @@ type controllerTestSuite struct {
|
|||||||
artMgr *arttesting.FakeManager
|
artMgr *arttesting.FakeManager
|
||||||
artrashMgr *artrashtesting.FakeManager
|
artrashMgr *artrashtesting.FakeManager
|
||||||
blobMgr *blob.Manager
|
blobMgr *blob.Manager
|
||||||
tagMgr *tagtesting.FakeManager
|
tagCtl *tagtesting.FakeController
|
||||||
labelMgr *label.FakeManager
|
labelMgr *label.FakeManager
|
||||||
abstractor *fakeAbstractor
|
abstractor *fakeAbstractor
|
||||||
immutableMtr *immutesting.FakeMatcher
|
immutableMtr *immutesting.FakeMatcher
|
||||||
@ -89,7 +89,7 @@ func (c *controllerTestSuite) SetupTest() {
|
|||||||
c.artMgr = &arttesting.FakeManager{}
|
c.artMgr = &arttesting.FakeManager{}
|
||||||
c.artrashMgr = &artrashtesting.FakeManager{}
|
c.artrashMgr = &artrashtesting.FakeManager{}
|
||||||
c.blobMgr = &blob.Manager{}
|
c.blobMgr = &blob.Manager{}
|
||||||
c.tagMgr = &tagtesting.FakeManager{}
|
c.tagCtl = &tagtesting.FakeController{}
|
||||||
c.labelMgr = &label.FakeManager{}
|
c.labelMgr = &label.FakeManager{}
|
||||||
c.abstractor = &fakeAbstractor{}
|
c.abstractor = &fakeAbstractor{}
|
||||||
c.immutableMtr = &immutesting.FakeMatcher{}
|
c.immutableMtr = &immutesting.FakeMatcher{}
|
||||||
@ -99,7 +99,7 @@ func (c *controllerTestSuite) SetupTest() {
|
|||||||
artMgr: c.artMgr,
|
artMgr: c.artMgr,
|
||||||
artrashMgr: c.artrashMgr,
|
artrashMgr: c.artrashMgr,
|
||||||
blobMgr: c.blobMgr,
|
blobMgr: c.blobMgr,
|
||||||
tagMgr: c.tagMgr,
|
tagCtl: c.tagCtl,
|
||||||
labelMgr: c.labelMgr,
|
labelMgr: c.labelMgr,
|
||||||
abstractor: c.abstractor,
|
abstractor: c.abstractor,
|
||||||
immutableMtr: c.immutableMtr,
|
immutableMtr: c.immutableMtr,
|
||||||
@ -108,34 +108,6 @@ func (c *controllerTestSuite) SetupTest() {
|
|||||||
descriptor.Register(&fakeDescriptor{}, "")
|
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() {
|
func (c *controllerTestSuite) TestAssembleArtifact() {
|
||||||
art := &artifact.Artifact{
|
art := &artifact.Artifact{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
@ -144,20 +116,22 @@ func (c *controllerTestSuite) TestAssembleArtifact() {
|
|||||||
}
|
}
|
||||||
option := &Option{
|
option := &Option{
|
||||||
WithTag: true,
|
WithTag: true,
|
||||||
TagOption: &TagOption{
|
TagOption: &tag.Option{
|
||||||
WithImmutableStatus: false,
|
WithImmutableStatus: false,
|
||||||
},
|
},
|
||||||
WithLabel: true,
|
WithLabel: true,
|
||||||
}
|
}
|
||||||
tg := &tag.Tag{
|
tg := &tag.Tag{
|
||||||
ID: 1,
|
Tag: model_tag.Tag{
|
||||||
RepositoryID: 1,
|
ID: 1,
|
||||||
ArtifactID: 1,
|
RepositoryID: 1,
|
||||||
Name: "latest",
|
ArtifactID: 1,
|
||||||
PushTime: time.Now(),
|
Name: "latest",
|
||||||
PullTime: time.Now(),
|
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")
|
ctx := internal.SetAPIVersion(nil, "2.0")
|
||||||
lb := &models.Label{
|
lb := &models.Label{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
@ -169,7 +143,7 @@ func (c *controllerTestSuite) TestAssembleArtifact() {
|
|||||||
artifact := c.ctl.assembleArtifact(ctx, art, option)
|
artifact := c.ctl.assembleArtifact(ctx, art, option)
|
||||||
c.Require().NotNil(artifact)
|
c.Require().NotNil(artifact)
|
||||||
c.Equal(art.ID, artifact.ID)
|
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)
|
||||||
c.Require().NotNil(artifact.AdditionLinks["build_history"])
|
c.Require().NotNil(artifact.AdditionLinks["build_history"])
|
||||||
c.False(artifact.AdditionLinks["build_history"].Absolute)
|
c.False(artifact.AdditionLinks["build_history"].Absolute)
|
||||||
@ -207,48 +181,6 @@ func (c *controllerTestSuite) TestEnsureArtifact() {
|
|||||||
c.Equal(int64(1), art.ID)
|
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() {
|
func (c *controllerTestSuite) TestEnsure() {
|
||||||
digest := "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180"
|
digest := "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180"
|
||||||
|
|
||||||
@ -258,14 +190,13 @@ func (c *controllerTestSuite) TestEnsure() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
c.artMgr.On("GetByDigest").Return(nil, ierror.NotFoundError(nil))
|
c.artMgr.On("GetByDigest").Return(nil, ierror.NotFoundError(nil))
|
||||||
c.artMgr.On("Create").Return(1, 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.abstractor.On("AbstractMetadata").Return(nil)
|
||||||
|
c.tagCtl.On("Ensure").Return(nil)
|
||||||
_, id, err := c.ctl.Ensure(nil, "library/hello-world", digest, "latest")
|
_, id, err := c.ctl.Ensure(nil, "library/hello-world", digest, "latest")
|
||||||
c.Require().Nil(err)
|
c.Require().Nil(err)
|
||||||
c.repoMgr.AssertExpectations(c.T())
|
c.repoMgr.AssertExpectations(c.T())
|
||||||
c.artMgr.AssertExpectations(c.T())
|
c.artMgr.AssertExpectations(c.T())
|
||||||
c.tagMgr.AssertExpectations(c.T())
|
c.tagCtl.AssertExpectations(c.T())
|
||||||
c.abstractor.AssertExpectations(c.T())
|
c.abstractor.AssertExpectations(c.T())
|
||||||
c.Equal(int64(1), id)
|
c.Equal(int64(1), id)
|
||||||
}
|
}
|
||||||
@ -288,12 +219,14 @@ func (c *controllerTestSuite) TestList() {
|
|||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
c.tagMgr.On("List").Return([]*tag.Tag{
|
c.tagCtl.On("List").Return([]*tag.Tag{
|
||||||
{
|
{
|
||||||
ID: 1,
|
Tag: model_tag.Tag{
|
||||||
RepositoryID: 1,
|
ID: 1,
|
||||||
ArtifactID: 1,
|
RepositoryID: 1,
|
||||||
Name: "latest",
|
ArtifactID: 1,
|
||||||
|
Name: "latest",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
c.repoMgr.On("Get").Return(&models.RepoRecord{
|
c.repoMgr.On("Get").Return(&models.RepoRecord{
|
||||||
@ -357,7 +290,7 @@ func (c *controllerTestSuite) TestGetByTag() {
|
|||||||
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
|
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
}, nil)
|
}, 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)
|
art, err := c.ctl.getByTag(nil, "library/hello-world", "latest", nil)
|
||||||
c.Require().NotNil(err)
|
c.Require().NotNil(err)
|
||||||
c.True(ierror.IsErr(err, ierror.NotFoundCode))
|
c.True(ierror.IsErr(err, ierror.NotFoundCode))
|
||||||
@ -369,12 +302,14 @@ func (c *controllerTestSuite) TestGetByTag() {
|
|||||||
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
|
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
}, nil)
|
}, nil)
|
||||||
c.tagMgr.On("List").Return([]*tag.Tag{
|
c.tagCtl.On("List").Return([]*tag.Tag{
|
||||||
{
|
{
|
||||||
ID: 1,
|
Tag: model_tag.Tag{
|
||||||
RepositoryID: 1,
|
ID: 1,
|
||||||
Name: "latest",
|
RepositoryID: 1,
|
||||||
ArtifactID: 1,
|
Name: "latest",
|
||||||
|
ArtifactID: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
c.artMgr.On("Get").Return(&artifact.Artifact{
|
c.artMgr.On("Get").Return(&artifact.Artifact{
|
||||||
@ -410,12 +345,14 @@ func (c *controllerTestSuite) TestGetByReference() {
|
|||||||
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
|
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
}, nil)
|
}, nil)
|
||||||
c.tagMgr.On("List").Return([]*tag.Tag{
|
c.tagCtl.On("List").Return([]*tag.Tag{
|
||||||
{
|
{
|
||||||
ID: 1,
|
Tag: model_tag.Tag{
|
||||||
RepositoryID: 1,
|
ID: 1,
|
||||||
Name: "latest",
|
RepositoryID: 1,
|
||||||
ArtifactID: 1,
|
Name: "latest",
|
||||||
|
ArtifactID: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
c.artMgr.On("Get").Return(&artifact.Artifact{
|
c.artMgr.On("Get").Return(&artifact.Artifact{
|
||||||
@ -449,9 +386,11 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
|
|||||||
// child artifact and contains tags
|
// child artifact and contains tags
|
||||||
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
|
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
|
||||||
c.artMgr.On("Delete").Return(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)
|
}, nil)
|
||||||
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||||
@ -464,7 +403,7 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
|
|||||||
|
|
||||||
// root artifact is referenced by other artifacts
|
// root artifact is referenced by other artifacts
|
||||||
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
|
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.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||||
c.artMgr.On("ListReferences").Return([]*artifact.Reference{
|
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
|
// child artifact contains no tag but referenced by other artifacts
|
||||||
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
|
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.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||||
c.artMgr.On("ListReferences").Return([]*artifact.Reference{
|
c.artMgr.On("ListReferences").Return([]*artifact.Reference{
|
||||||
{
|
{
|
||||||
@ -494,15 +433,16 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
|
|||||||
|
|
||||||
// root artifact is referenced by other artifacts
|
// root artifact is referenced by other artifacts
|
||||||
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
|
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("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.blobMgr.On("CleanupAssociationsForProject", nil, int64(0), mock.AnythingOfType("[]*models.Blob")).Return(nil).Once()
|
||||||
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||||
c.artMgr.On("ListReferences").Return(nil, 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.artMgr.On("Delete").Return(nil)
|
||||||
c.labelMgr.On("RemoveAllFrom").Return(nil)
|
c.labelMgr.On("RemoveAllFrom").Return(nil)
|
||||||
c.artrashMgr.On("Create").Return(0, nil)
|
c.artrashMgr.On("Create").Return(0, nil)
|
||||||
|
c.tagCtl.On("DeleteTags").Return(nil)
|
||||||
err = c.ctl.deleteDeeply(nil, 1, true)
|
err = c.ctl.deleteDeeply(nil, 1, true)
|
||||||
c.Require().Nil(err)
|
c.Require().Nil(err)
|
||||||
}
|
}
|
||||||
@ -517,13 +457,15 @@ func (c *controllerTestSuite) TestCopy() {
|
|||||||
Name: "library/hello-world",
|
Name: "library/hello-world",
|
||||||
}, nil)
|
}, nil)
|
||||||
c.artMgr.On("GetByDigest").Return(nil, ierror.NotFoundError(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,
|
Tag: model_tag.Tag{
|
||||||
Name: "latest",
|
ID: 1,
|
||||||
|
Name: "latest",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
c.tagMgr.On("Update").Return(nil)
|
c.tagCtl.On("Update").Return(nil)
|
||||||
c.repoMgr.On("Get").Return(&models.RepoRecord{
|
c.repoMgr.On("Get").Return(&models.RepoRecord{
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "library/hello-world",
|
Name: "library/hello-world",
|
||||||
@ -531,66 +473,39 @@ func (c *controllerTestSuite) TestCopy() {
|
|||||||
c.abstractor.On("AbstractMetadata").Return(nil)
|
c.abstractor.On("AbstractMetadata").Return(nil)
|
||||||
c.artMgr.On("Create").Return(1, nil)
|
c.artMgr.On("Create").Return(1, nil)
|
||||||
c.regCli.On("Copy").Return(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")
|
_, err := c.ctl.Copy(nil, "library/hello-world", "latest", "library/hello-world2")
|
||||||
c.Require().Nil(err)
|
c.Require().Nil(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controllerTestSuite) TestListTags() {
|
|
||||||
c.tagMgr.On("List").Return([]*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.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.Require().Nil(err)
|
|
||||||
c.Equal(int64(1), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *controllerTestSuite) TestDeleteTag() {
|
|
||||||
c.tagMgr.On("Delete").Return(nil)
|
|
||||||
err := c.ctl.DeleteTag(nil, 1)
|
|
||||||
c.Require().Nil(err)
|
|
||||||
c.tagMgr.AssertExpectations(c.T())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *controllerTestSuite) TestUpdatePullTime() {
|
func (c *controllerTestSuite) TestUpdatePullTime() {
|
||||||
// artifact ID and tag ID matches
|
// artifact ID and tag ID matches
|
||||||
c.tagMgr.On("Get").Return(&tag.Tag{
|
c.tagCtl.On("Get").Return(&tag.Tag{
|
||||||
ID: 1,
|
Tag: model_tag.Tag{
|
||||||
ArtifactID: 1,
|
ID: 1,
|
||||||
|
ArtifactID: 1,
|
||||||
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
c.artMgr.On("UpdatePullTime").Return(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())
|
err := c.ctl.UpdatePullTime(nil, 1, 1, time.Now())
|
||||||
c.Require().Nil(err)
|
c.Require().Nil(err)
|
||||||
c.artMgr.AssertExpectations(c.T())
|
c.artMgr.AssertExpectations(c.T())
|
||||||
c.tagMgr.AssertExpectations(c.T())
|
c.tagCtl.AssertExpectations(c.T())
|
||||||
|
|
||||||
// reset the mock
|
// reset the mock
|
||||||
c.SetupTest()
|
c.SetupTest()
|
||||||
|
|
||||||
// artifact ID and tag ID doesn't match
|
// artifact ID and tag ID doesn't match
|
||||||
c.tagMgr.On("Get").Return(&tag.Tag{
|
c.tagCtl.On("Get").Return(&tag.Tag{
|
||||||
ID: 1,
|
Tag: model_tag.Tag{
|
||||||
ArtifactID: 2,
|
ID: 1,
|
||||||
|
ArtifactID: 2,
|
||||||
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
err = c.ctl.UpdatePullTime(nil, 1, 1, time.Now())
|
err = c.ctl.UpdatePullTime(nil, 1, 1, time.Now())
|
||||||
c.Require().NotNil(err)
|
c.Require().NotNil(err)
|
||||||
c.tagMgr.AssertExpectations(c.T())
|
c.tagCtl.AssertExpectations(c.T())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,17 +17,16 @@ package artifact
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/api/tag"
|
||||||
cmodels "github.com/goharbor/harbor/src/common/models"
|
cmodels "github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/utils"
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
"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
|
// Artifact is the overall view of artifact
|
||||||
type Artifact struct {
|
type Artifact struct {
|
||||||
artifact.Artifact
|
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
|
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"`
|
Labels []*cmodels.Label `json:"labels"`
|
||||||
}
|
}
|
||||||
@ -44,13 +43,6 @@ func (artifact *Artifact) SetAdditionLink(addition, version string) {
|
|||||||
artifact.AdditionLinks[addition] = &AdditionLink{HREF: href, Absolute: false}
|
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
|
// AdditionLink is a link via that the addition can be fetched
|
||||||
type AdditionLink struct {
|
type AdditionLink struct {
|
||||||
HREF string `json:"href"`
|
HREF string `json:"href"`
|
||||||
@ -60,13 +52,6 @@ type AdditionLink struct {
|
|||||||
// Option is used to specify the properties returned when listing/getting artifacts
|
// Option is used to specify the properties returned when listing/getting artifacts
|
||||||
type Option struct {
|
type Option struct {
|
||||||
WithTag bool
|
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
|
WithLabel bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TagOption is used to specify the properties returned when listing/getting tags
|
|
||||||
type TagOption struct {
|
|
||||||
WithImmutableStatus bool
|
|
||||||
WithSignature bool
|
|
||||||
SignatureChecker *signature.Checker
|
|
||||||
}
|
|
||||||
|
229
src/api/tag/controller.go
Normal file
229
src/api/tag/controller.go
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
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, ids []int64) (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,
|
||||||
|
})
|
||||||
|
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
|
||||||
|
}
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.assembleTag(ctx, &tag.Tag, option), 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, ids []int64) (err error) {
|
||||||
|
// in order to leverage the signature and immutable status check
|
||||||
|
for _, id := range ids {
|
||||||
|
if err := c.Delete(ctx, 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)
|
||||||
|
}
|
218
src/api/tag/controller_test.go
Normal file
218
src/api/tag/controller_test.go
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
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)
|
||||||
|
ids := []int64{1, 2, 3, 4}
|
||||||
|
err := c.ctl.DeleteTags(nil, ids)
|
||||||
|
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
20
src/api/tag/model.go
Normal 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
|
||||||
|
}
|
@ -16,7 +16,8 @@ package dep
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
modelsv2 "github.com/goharbor/harbor/src/api/artifact"
|
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"
|
"testing"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/chartserver"
|
"github.com/goharbor/harbor/src/chartserver"
|
||||||
@ -38,9 +39,9 @@ type fakeCoreClient struct {
|
|||||||
func (f *fakeCoreClient) ListAllArtifacts(project, repository string) ([]*modelsv2.Artifact, error) {
|
func (f *fakeCoreClient) ListAllArtifacts(project, repository string) ([]*modelsv2.Artifact, error) {
|
||||||
image := &modelsv2.Artifact{}
|
image := &modelsv2.Artifact{}
|
||||||
image.Digest = "sha256:123456"
|
image.Digest = "sha256:123456"
|
||||||
image.Tags = []*modelsv2.Tag{
|
image.Tags = []*tag.Tag{
|
||||||
{
|
{
|
||||||
Tag: tag.Tag{
|
Tag: model_tag.Tag{
|
||||||
Name: "latest",
|
Name: "latest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/goharbor/harbor/src/api/artifact"
|
"github.com/goharbor/harbor/src/api/artifact"
|
||||||
|
"github.com/goharbor/harbor/src/api/tag"
|
||||||
common_util "github.com/goharbor/harbor/src/common/utils"
|
common_util "github.com/goharbor/harbor/src/common/utils"
|
||||||
internal_errors "github.com/goharbor/harbor/src/internal/error"
|
internal_errors "github.com/goharbor/harbor/src/internal/error"
|
||||||
serror "github.com/goharbor/harbor/src/server/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{
|
af, err := artifact.Ctl.GetByReference(req.Context(), art.Repository, art.Digest, &artifact.Option{
|
||||||
WithTag: true,
|
WithTag: true,
|
||||||
TagOption: &artifact.TagOption{WithImmutableStatus: true},
|
TagOption: &tag.Option{WithImmutableStatus: true},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if internal_errors.IsErr(err, internal_errors.NotFoundCode) {
|
if internal_errors.IsErr(err, internal_errors.NotFoundCode) {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/goharbor/harbor/src/api/artifact"
|
"github.com/goharbor/harbor/src/api/artifact"
|
||||||
|
"github.com/goharbor/harbor/src/api/tag"
|
||||||
common_util "github.com/goharbor/harbor/src/common/utils"
|
common_util "github.com/goharbor/harbor/src/common/utils"
|
||||||
internal_errors "github.com/goharbor/harbor/src/internal/error"
|
internal_errors "github.com/goharbor/harbor/src/internal/error"
|
||||||
serror "github.com/goharbor/harbor/src/server/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{
|
af, err := artifact.Ctl.GetByReference(req.Context(), art.Repository, art.Tag, &artifact.Option{
|
||||||
WithTag: true,
|
WithTag: true,
|
||||||
TagOption: &artifact.TagOption{WithImmutableStatus: true},
|
TagOption: &tag.Option{WithImmutableStatus: true},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if internal_errors.IsErr(err, internal_errors.NotFoundCode) {
|
if internal_errors.IsErr(err, internal_errors.NotFoundCode) {
|
||||||
|
@ -17,8 +17,8 @@ package registry
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/goharbor/harbor/src/api/artifact"
|
|
||||||
"github.com/goharbor/harbor/src/api/repository"
|
"github.com/goharbor/harbor/src/api/repository"
|
||||||
|
"github.com/goharbor/harbor/src/api/tag"
|
||||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||||
"github.com/goharbor/harbor/src/pkg/q"
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
serror "github.com/goharbor/harbor/src/server/error"
|
serror "github.com/goharbor/harbor/src/server/error"
|
||||||
@ -32,13 +32,13 @@ import (
|
|||||||
func newTagHandler() http.Handler {
|
func newTagHandler() http.Handler {
|
||||||
return &tagHandler{
|
return &tagHandler{
|
||||||
repoCtl: repository.Ctl,
|
repoCtl: repository.Ctl,
|
||||||
artCtl: artifact.Ctl,
|
tagCtl: tag.Ctl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type tagHandler struct {
|
type tagHandler struct {
|
||||||
repoCtl repository.Controller
|
repoCtl repository.Controller
|
||||||
artCtl artifact.Controller
|
tagCtl tag.Controller
|
||||||
repositoryName string
|
repositoryName string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ func (t *tagHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get tags ...
|
// get tags ...
|
||||||
tags, err := t.artCtl.ListTags(req.Context(), &q.Query{
|
tags, err := t.tagCtl.List(req.Context(), &q.Query{
|
||||||
Keywords: map[string]interface{}{
|
Keywords: map[string]interface{}{
|
||||||
"RepositoryID": repository.RepositoryID,
|
"RepositoryID": repository.RepositoryID,
|
||||||
}}, nil)
|
}}, nil)
|
||||||
|
@ -16,12 +16,12 @@ package registry
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/goharbor/harbor/src/api/artifact"
|
|
||||||
"github.com/goharbor/harbor/src/api/repository"
|
"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/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"
|
repotesting "github.com/goharbor/harbor/src/testing/api/repository"
|
||||||
|
tagtesting "github.com/goharbor/harbor/src/testing/api/tag"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -32,20 +32,20 @@ type tagTestSuite struct {
|
|||||||
suite.Suite
|
suite.Suite
|
||||||
originalRepoCtl repository.Controller
|
originalRepoCtl repository.Controller
|
||||||
repoCtl *repotesting.FakeController
|
repoCtl *repotesting.FakeController
|
||||||
originalArtCtl artifact.Controller
|
originalTagCtl tag.Controller
|
||||||
artCtl *arttesting.FakeController
|
tagCtl *tagtesting.FakeController
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tagTestSuite) SetupSuite() {
|
func (c *tagTestSuite) SetupSuite() {
|
||||||
c.originalArtCtl = artifact.Ctl
|
|
||||||
c.originalRepoCtl = repository.Ctl
|
c.originalRepoCtl = repository.Ctl
|
||||||
|
c.originalTagCtl = tag.Ctl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tagTestSuite) SetupTest() {
|
func (c *tagTestSuite) SetupTest() {
|
||||||
c.artCtl = &arttesting.FakeController{}
|
|
||||||
artifact.Ctl = c.artCtl
|
|
||||||
c.repoCtl = &repotesting.FakeController{}
|
c.repoCtl = &repotesting.FakeController{}
|
||||||
repository.Ctl = c.repoCtl
|
repository.Ctl = c.repoCtl
|
||||||
|
c.tagCtl = &tagtesting.FakeController{}
|
||||||
|
tag.Ctl = c.tagCtl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tagTestSuite) TearDownTest() {
|
func (c *tagTestSuite) TearDownTest() {
|
||||||
@ -53,7 +53,7 @@ func (c *tagTestSuite) TearDownTest() {
|
|||||||
|
|
||||||
func (c *tagTestSuite) TearDownSuite() {
|
func (c *tagTestSuite) TearDownSuite() {
|
||||||
repository.Ctl = c.originalRepoCtl
|
repository.Ctl = c.originalRepoCtl
|
||||||
artifact.Ctl = c.originalArtCtl
|
tag.Ctl = c.originalTagCtl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tagTestSuite) TestListTag() {
|
func (c *tagTestSuite) TestListTag() {
|
||||||
@ -64,15 +64,15 @@ func (c *tagTestSuite) TestListTag() {
|
|||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "library/hello-world",
|
Name: "library/hello-world",
|
||||||
}, nil)
|
}, nil)
|
||||||
c.artCtl.On("ListTags").Return([]*artifact.Tag{
|
c.tagCtl.On("List").Return([]*tag.Tag{
|
||||||
{
|
{
|
||||||
Tag: tag.Tag{
|
Tag: model_tag.Tag{
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "v1",
|
Name: "v1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Tag: tag.Tag{
|
Tag: model_tag.Tag{
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "v2",
|
Name: "v2",
|
||||||
},
|
},
|
||||||
@ -99,15 +99,15 @@ func (c *tagTestSuite) TestListTagPagination1() {
|
|||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "hello-world",
|
Name: "hello-world",
|
||||||
}, nil)
|
}, nil)
|
||||||
c.artCtl.On("ListTags").Return([]*artifact.Tag{
|
c.tagCtl.On("List").Return([]*tag.Tag{
|
||||||
{
|
{
|
||||||
Tag: tag.Tag{
|
Tag: model_tag.Tag{
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "v1",
|
Name: "v1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Tag: tag.Tag{
|
Tag: model_tag.Tag{
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "v2",
|
Name: "v2",
|
||||||
},
|
},
|
||||||
@ -135,15 +135,15 @@ func (c *tagTestSuite) TestListTagPagination2() {
|
|||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "hello-world",
|
Name: "hello-world",
|
||||||
}, nil)
|
}, nil)
|
||||||
c.artCtl.On("ListTags").Return([]*artifact.Tag{
|
c.tagCtl.On("List").Return([]*tag.Tag{
|
||||||
{
|
{
|
||||||
Tag: tag.Tag{
|
Tag: model_tag.Tag{
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "v1",
|
Name: "v1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Tag: tag.Tag{
|
Tag: model_tag.Tag{
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "v2",
|
Name: "v2",
|
||||||
},
|
},
|
||||||
@ -171,15 +171,15 @@ func (c *tagTestSuite) TestListTagPagination3() {
|
|||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "hello-world",
|
Name: "hello-world",
|
||||||
}, nil)
|
}, nil)
|
||||||
c.artCtl.On("ListTags").Return([]*artifact.Tag{
|
c.tagCtl.On("List").Return([]*tag.Tag{
|
||||||
{
|
{
|
||||||
Tag: tag.Tag{
|
Tag: model_tag.Tag{
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "v1",
|
Name: "v1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Tag: tag.Tag{
|
Tag: model_tag.Tag{
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Name: "v2",
|
Name: "v2",
|
||||||
},
|
},
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
|
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
|
||||||
"github.com/goharbor/harbor/src/api/repository"
|
"github.com/goharbor/harbor/src/api/repository"
|
||||||
"github.com/goharbor/harbor/src/api/scan"
|
"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/rbac"
|
||||||
"github.com/goharbor/harbor/src/common/utils"
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||||
@ -50,6 +51,7 @@ func newArtifactAPI() *artifactAPI {
|
|||||||
proMgr: project.Mgr,
|
proMgr: project.Mgr,
|
||||||
repoCtl: repository.Ctl,
|
repoCtl: repository.Ctl,
|
||||||
scanCtl: scan.DefaultController,
|
scanCtl: scan.DefaultController,
|
||||||
|
tagCtl: tag.Ctl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +61,7 @@ type artifactAPI struct {
|
|||||||
proMgr project.Manager
|
proMgr project.Manager
|
||||||
repoCtl repository.Controller
|
repoCtl repository.Controller
|
||||||
scanCtl scan.Controller
|
scanCtl scan.Controller
|
||||||
|
tagCtl tag.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *artifactAPI) ListArtifacts(ctx context.Context, params operation.ListArtifactsParams) middleware.Responder {
|
func (a *artifactAPI) ListArtifacts(ctx context.Context, params operation.ListArtifactsParams) middleware.Responder {
|
||||||
@ -232,12 +235,12 @@ func (a *artifactAPI) CreateTag(ctx context.Context, params operation.CreateTagP
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return a.SendError(ctx, err)
|
return a.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
tag := &artifact.Tag{}
|
tag := &tag.Tag{}
|
||||||
tag.RepositoryID = art.RepositoryID
|
tag.RepositoryID = art.RepositoryID
|
||||||
tag.ArtifactID = art.ID
|
tag.ArtifactID = art.ID
|
||||||
tag.Name = params.Tag.Name
|
tag.Name = params.Tag.Name
|
||||||
tag.PushTime = time.Now()
|
tag.PushTime = time.Now()
|
||||||
if _, err = a.artCtl.CreateTag(ctx, tag); err != nil {
|
if _, err = a.tagCtl.Create(ctx, tag); err != nil {
|
||||||
return a.SendError(ctx, err)
|
return a.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
// TODO set location header?
|
// TODO set location header?
|
||||||
@ -268,7 +271,7 @@ func (a *artifactAPI) DeleteTag(ctx context.Context, params operation.DeleteTagP
|
|||||||
"tag %s attached to artifact %d not found", params.TagName, artifact.ID)
|
"tag %s attached to artifact %d not found", params.TagName, artifact.ID)
|
||||||
return a.SendError(ctx, err)
|
return a.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
if err = a.artCtl.DeleteTag(ctx, id); err != nil {
|
if err = a.tagCtl.Delete(ctx, id); err != nil {
|
||||||
return a.SendError(ctx, err)
|
return a.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
return operation.NewDeleteTagOK()
|
return operation.NewDeleteTagOK()
|
||||||
@ -340,7 +343,7 @@ func option(withTag, withImmutableStatus, withLabel, withSignature *bool) *artif
|
|||||||
}
|
}
|
||||||
|
|
||||||
if option.WithTag {
|
if option.WithTag {
|
||||||
option.TagOption = &artifact.TagOption{
|
option.TagOption = &tag.Option{
|
||||||
WithImmutableStatus: boolValue(withImmutableStatus),
|
WithImmutableStatus: boolValue(withImmutableStatus),
|
||||||
WithSignature: boolValue(withSignature),
|
WithSignature: boolValue(withSignature),
|
||||||
}
|
}
|
||||||
|
@ -82,28 +82,6 @@ func (f *FakeController) Copy(ctx context.Context, srcRepo, ref, dstRepo string)
|
|||||||
return int64(args.Int(0)), args.Error(1)
|
return int64(args.Int(0)), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTags ...
|
|
||||||
func (f *FakeController) ListTags(ctx context.Context, query *q.Query, option *artifact.TagOption) ([]*artifact.Tag, error) {
|
|
||||||
args := f.Called()
|
|
||||||
var tags []*artifact.Tag
|
|
||||||
if args.Get(0) != nil {
|
|
||||||
tags = args.Get(0).([]*artifact.Tag)
|
|
||||||
}
|
|
||||||
return tags, args.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTag ...
|
|
||||||
func (f *FakeController) CreateTag(ctx context.Context, tag *artifact.Tag) (int64, error) {
|
|
||||||
args := f.Called()
|
|
||||||
return int64(args.Int(0)), args.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteTag ...
|
|
||||||
func (f *FakeController) DeleteTag(ctx context.Context, tagID int64) (err error) {
|
|
||||||
args := f.Called()
|
|
||||||
return args.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdatePullTime ...
|
// UpdatePullTime ...
|
||||||
func (f *FakeController) UpdatePullTime(ctx context.Context, artifactID int64, tagID int64, time time.Time) error {
|
func (f *FakeController) UpdatePullTime(ctx context.Context, artifactID int64, tagID int64, time time.Time) error {
|
||||||
args := f.Called()
|
args := f.Called()
|
||||||
|
69
src/testing/api/tag/controller.go
Normal file
69
src/testing/api/tag/controller.go
Normal 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, ids []int64) (err error) {
|
||||||
|
args := f.Called()
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
@ -25,11 +25,12 @@ class TestProjects(unittest.TestCase):
|
|||||||
|
|
||||||
@unittest.skipIf(TEARDOWN == False, "Test data won't be erased.")
|
@unittest.skipIf(TEARDOWN == False, "Test data won't be erased.")
|
||||||
def test_ClearData(self):
|
def test_ClearData(self):
|
||||||
|
# remove the deletion as the signed image cannot be deleted.
|
||||||
#1. Delete repository(RA) by user(UA);
|
#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);
|
#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);
|
#3. Delete user(UA);
|
||||||
self.user.delete_user(TestProjects.user_sign_image_id, **ADMIN_CLIENT)
|
self.user.delete_user(TestProjects.user_sign_image_id, **ADMIN_CLIENT)
|
||||||
|
Loading…
Reference in New Issue
Block a user