mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-23 00:57:44 +01:00
Refactor the logic of deleting artifact
Delete the child artifacts along with the parent when deleting an artifact Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
parent
599075d5b4
commit
9d2f1d4d66
@ -274,30 +274,76 @@ func (c *controller) getByTag(ctx context.Context, repository, tag string, optio
|
||||
}
|
||||
|
||||
func (c *controller) Delete(ctx context.Context, id int64) error {
|
||||
// remove labels added to the artifact
|
||||
if err := c.labelMgr.RemoveAllFrom(ctx, id); err != nil {
|
||||
return c.deleteDeeply(ctx, id, true)
|
||||
}
|
||||
|
||||
// "isRoot" is used to specify whether the artifact is the root parent artifact
|
||||
// the error handling logic for the root parent artifact and others is different
|
||||
func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot bool) error {
|
||||
art, err := c.Get(ctx, id, &Option{WithTag: true})
|
||||
if err != nil {
|
||||
// return nil if the nonexistent artifact isn't the root parent
|
||||
if !isRoot && ierror.IsErr(err, ierror.NotFoundCode) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
// delete all tags that attached to the artifact
|
||||
_, tags, err := c.tagMgr.List(ctx, &q.Query{
|
||||
// the child artifact is referenced by some tags, skip
|
||||
if !isRoot && len(art.Tags) > 0 {
|
||||
return nil
|
||||
}
|
||||
parents, err := c.artMgr.ListReferences(ctx, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"artifact_id": id,
|
||||
"ChildID": id,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, tag := range tags {
|
||||
if err = c.DeleteTag(ctx, tag.ID); err != nil {
|
||||
if len(parents) > 0 {
|
||||
// the root artifact is referenced by other artifacts
|
||||
if isRoot {
|
||||
return ierror.New(nil).WithCode(ierror.ViolateForeignKeyConstraintCode).
|
||||
WithMessage("the deleting artifact is referenced by others")
|
||||
}
|
||||
// the child artifact is referenced by other artifacts, skip
|
||||
return nil
|
||||
}
|
||||
// delete child artifacts if contains any
|
||||
for _, reference := range art.References {
|
||||
// delete reference
|
||||
if err = c.artMgr.DeleteReference(ctx, reference.ID); err != nil &&
|
||||
!ierror.IsErr(err, ierror.NotFoundCode) {
|
||||
return err
|
||||
}
|
||||
if err = c.deleteDeeply(ctx, reference.ChildID, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.artMgr.Delete(ctx, id); err != nil {
|
||||
// delete all tags that attached to the root artifact
|
||||
if isRoot {
|
||||
if err = c.tagMgr.DeleteOfArtifact(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// remove labels added to the artifact
|
||||
if err := c.labelMgr.RemoveAllFrom(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete the artifact itself
|
||||
if err = c.artMgr.Delete(ctx, art.ID); err != nil {
|
||||
// the child artifact doesn't exist, skip
|
||||
if !isRoot && ierror.IsErr(err, ierror.NotFoundCode) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO fire delete artifact event
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -361,6 +407,8 @@ func (c *controller) assembleArtifact(ctx context.Context, art *artifact.Artifac
|
||||
artifact := &Artifact{
|
||||
Artifact: *art,
|
||||
}
|
||||
// populate addition links
|
||||
c.populateAdditionLinks(ctx, artifact)
|
||||
if option == nil {
|
||||
return artifact
|
||||
}
|
||||
|
@ -277,7 +277,6 @@ func (c *controllerTestSuite) TestList() {
|
||||
c.repoMgr.On("Get").Return(&models.RepoRecord{
|
||||
Name: "library/hello-world",
|
||||
}, nil)
|
||||
c.abstractor.On("ListSupportedAdditions").Return([]string{"BUILD_HISTORY"})
|
||||
total, artifacts, err := c.ctl.List(nil, query, option)
|
||||
c.Require().Nil(err)
|
||||
c.Equal(int64(1), total)
|
||||
@ -292,7 +291,7 @@ func (c *controllerTestSuite) TestGet() {
|
||||
ID: 1,
|
||||
RepositoryID: 1,
|
||||
}, nil)
|
||||
c.abstractor.On("ListSupportedAdditions").Return([]string{"BUILD_HISTORY"})
|
||||
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||
art, err := c.ctl.Get(nil, 1, nil)
|
||||
c.Require().Nil(err)
|
||||
c.Require().NotNil(art)
|
||||
@ -305,7 +304,6 @@ func (c *controllerTestSuite) TestGetByDigest() {
|
||||
RepositoryID: 1,
|
||||
}, nil)
|
||||
c.artMgr.On("GetByDigest").Return(nil, ierror.NotFoundError(nil))
|
||||
c.abstractor.On("ListSupportedAdditions").Return([]string{"BUILD_HISTORY"})
|
||||
art, err := c.ctl.getByDigest(nil, "library/hello-world",
|
||||
"sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180", nil)
|
||||
c.Require().NotNil(err)
|
||||
@ -322,7 +320,7 @@ func (c *controllerTestSuite) TestGetByDigest() {
|
||||
ID: 1,
|
||||
RepositoryID: 1,
|
||||
}, nil)
|
||||
c.abstractor.On("ListSupportedAdditions").Return([]string{"BUILD_HISTORY"})
|
||||
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||
art, err = c.ctl.getByDigest(nil, "library/hello-world",
|
||||
"sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180", nil)
|
||||
c.Require().Nil(err)
|
||||
@ -336,7 +334,6 @@ func (c *controllerTestSuite) TestGetByTag() {
|
||||
RepositoryID: 1,
|
||||
}, nil)
|
||||
c.tagMgr.On("List").Return(0, nil, nil)
|
||||
c.abstractor.On("ListSupportedAdditions").Return([]string{"BUILD_HISTORY"})
|
||||
art, err := c.ctl.getByTag(nil, "library/hello-world", "latest", nil)
|
||||
c.Require().NotNil(err)
|
||||
c.True(ierror.IsErr(err, ierror.NotFoundCode))
|
||||
@ -359,7 +356,7 @@ func (c *controllerTestSuite) TestGetByTag() {
|
||||
c.artMgr.On("Get").Return(&artifact.Artifact{
|
||||
ID: 1,
|
||||
}, nil)
|
||||
c.abstractor.On("ListSupportedAdditions").Return([]string{"BUILD_HISTORY"})
|
||||
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||
art, err = c.ctl.getByTag(nil, "library/hello-world", "latest", nil)
|
||||
c.Require().Nil(err)
|
||||
c.Require().NotNil(art)
|
||||
@ -375,7 +372,7 @@ func (c *controllerTestSuite) TestGetByReference() {
|
||||
ID: 1,
|
||||
RepositoryID: 1,
|
||||
}, nil)
|
||||
c.abstractor.On("ListSupportedAdditions").Return([]string{"BUILD_HISTORY"})
|
||||
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||
art, err := c.ctl.GetByReference(nil, "library/hello-world",
|
||||
"sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180", nil)
|
||||
c.Require().Nil(err)
|
||||
@ -400,26 +397,85 @@ func (c *controllerTestSuite) TestGetByReference() {
|
||||
c.artMgr.On("Get").Return(&artifact.Artifact{
|
||||
ID: 1,
|
||||
}, nil)
|
||||
c.abstractor.On("ListSupportedAdditions").Return([]string{"BUILD_HISTORY"})
|
||||
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||
art, err = c.ctl.GetByReference(nil, "library/hello-world", "latest", nil)
|
||||
c.Require().Nil(err)
|
||||
c.Require().NotNil(art)
|
||||
c.Equal(int64(1), art.ID)
|
||||
}
|
||||
|
||||
func (c *controllerTestSuite) TestDelete() {
|
||||
c.artMgr.On("Delete").Return(nil)
|
||||
func (c *controllerTestSuite) TestDeleteDeeply() {
|
||||
// root artifact and doesn't exist
|
||||
c.artMgr.On("Get").Return(nil, ierror.NotFoundError(nil))
|
||||
err := c.ctl.deleteDeeply(nil, 1, true)
|
||||
c.Require().NotNil(err)
|
||||
c.Assert().True(ierror.IsErr(err, ierror.NotFoundCode))
|
||||
|
||||
// reset the mock
|
||||
c.SetupTest()
|
||||
|
||||
// child artifact and doesn't exist
|
||||
c.artMgr.On("Get").Return(nil, ierror.NotFoundError(nil))
|
||||
err = c.ctl.deleteDeeply(nil, 1, false)
|
||||
c.Require().Nil(err)
|
||||
|
||||
// reset the mock
|
||||
c.SetupTest()
|
||||
|
||||
// child artifact and contains tags
|
||||
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
|
||||
c.tagMgr.On("List").Return(0, []*tag.Tag{
|
||||
{
|
||||
ID: 1,
|
||||
},
|
||||
}, nil)
|
||||
c.tagMgr.On("Delete").Return(nil)
|
||||
c.labelMgr.On("RemoveAllFrom").Return(nil)
|
||||
err := c.ctl.Delete(nil, 1)
|
||||
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||
err = c.ctl.deleteDeeply(nil, 1, false)
|
||||
c.Require().Nil(err)
|
||||
|
||||
// reset the mock
|
||||
c.SetupTest()
|
||||
|
||||
// root artifact is referenced by other artifacts
|
||||
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
|
||||
c.tagMgr.On("List").Return(0, nil, nil)
|
||||
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||
c.artMgr.On("ListReferences").Return([]*artifact.Reference{
|
||||
{
|
||||
ID: 1,
|
||||
},
|
||||
}, nil)
|
||||
err = c.ctl.deleteDeeply(nil, 1, true)
|
||||
c.Require().NotNil(err)
|
||||
|
||||
// reset the mock
|
||||
c.SetupTest()
|
||||
|
||||
// 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(0, nil, nil)
|
||||
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||
c.artMgr.On("ListReferences").Return([]*artifact.Reference{
|
||||
{
|
||||
ID: 1,
|
||||
},
|
||||
}, nil)
|
||||
err = c.ctl.deleteDeeply(nil, 1, false)
|
||||
c.Require().Nil(err)
|
||||
|
||||
// reset the mock
|
||||
c.SetupTest()
|
||||
|
||||
// root artifact is referenced by other artifacts
|
||||
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
|
||||
c.tagMgr.On("List").Return(0, nil, nil)
|
||||
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||
c.artMgr.On("ListReferences").Return(nil, nil)
|
||||
c.tagMgr.On("DeleteOfArtifact").Return(nil)
|
||||
c.artMgr.On("Delete").Return(nil)
|
||||
c.labelMgr.On("RemoveAllFrom").Return(nil)
|
||||
err = c.ctl.deleteDeeply(nil, 1, true)
|
||||
c.Require().Nil(err)
|
||||
c.artMgr.AssertExpectations(c.T())
|
||||
c.tagMgr.AssertExpectations(c.T())
|
||||
}
|
||||
|
||||
func (c *controllerTestSuite) TestListTags() {
|
||||
|
@ -24,22 +24,22 @@ import (
|
||||
// Artifact is the overall view of artifact
|
||||
type Artifact struct {
|
||||
artifact.Artifact
|
||||
Tags []*Tag // the list of tags that attached to the artifact
|
||||
AdditionLinks map[string]*AdditionLink // the resource link for build history(image), values.yaml(chart), dependency(chart), etc
|
||||
Labels []*cmodels.Label
|
||||
Tags []*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"`
|
||||
}
|
||||
|
||||
// Tag is the overall view of tag
|
||||
type Tag struct {
|
||||
tag.Tag
|
||||
Immutable bool
|
||||
Signed bool
|
||||
Immutable bool `json:"immutable"`
|
||||
Signed bool `json:"signed"`
|
||||
}
|
||||
|
||||
// AdditionLink is a link via that the addition can be fetched
|
||||
type AdditionLink struct {
|
||||
HREF string
|
||||
Absolute bool // specify the href is an absolute URL or not
|
||||
HREF string `json:"href"`
|
||||
Absolute bool `json:"absolute"` // specify the href is an absolute URL or not
|
||||
}
|
||||
|
||||
// Option is used to specify the properties returned when listing/getting artifacts
|
||||
|
@ -43,6 +43,8 @@ type DAO interface {
|
||||
CreateReference(ctx context.Context, reference *ArtifactReference) (id int64, err error)
|
||||
// ListReferences lists the artifact references according to the query
|
||||
ListReferences(ctx context.Context, query *q.Query) (references []*ArtifactReference, err error)
|
||||
// DeleteReference specified by ID
|
||||
DeleteReference(ctx context.Context, id int64) (err error)
|
||||
// DeleteReferences deletes the references referenced by the artifact specified by parent ID
|
||||
DeleteReferences(ctx context.Context, parentID int64) (err error)
|
||||
}
|
||||
@ -213,6 +215,22 @@ func (d *dao) ListReferences(ctx context.Context, query *q.Query) ([]*ArtifactRe
|
||||
}
|
||||
return references, nil
|
||||
}
|
||||
|
||||
func (d *dao) DeleteReference(ctx context.Context, id int64) error {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := ormer.Delete(&ArtifactReference{ID: id})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return ierror.NotFoundError(nil).WithMessage("artifact reference %d not found", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dao) DeleteReferences(ctx context.Context, parentID int64) error {
|
||||
// make sure the parent artifact exist
|
||||
_, err := d.Get(ctx, parentID)
|
||||
|
@ -425,6 +425,13 @@ func (d *daoTestSuite) TestListReferences() {
|
||||
d.Equal(d.reference01ID, references[0].ID)
|
||||
}
|
||||
|
||||
func (d *daoTestSuite) TestDeleteReference() {
|
||||
// not exist
|
||||
err := d.dao.DeleteReference(d.ctx, 10000)
|
||||
d.Require().NotNil(err)
|
||||
d.True(ierror.IsErr(err, ierror.NotFoundCode))
|
||||
}
|
||||
|
||||
func (d *daoTestSuite) TestDeleteReferences() {
|
||||
// happy pass is covered in TearDownTest
|
||||
|
||||
|
@ -16,9 +16,10 @@ package artifact
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/q"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -43,6 +44,10 @@ type Manager interface {
|
||||
Delete(ctx context.Context, id int64) (err error)
|
||||
// UpdatePullTime updates the pull time of the artifact
|
||||
UpdatePullTime(ctx context.Context, artifactID int64, time time.Time) (err error)
|
||||
// ListReferences according to the query
|
||||
ListReferences(ctx context.Context, query *q.Query) (references []*Reference, err error)
|
||||
// DeleteReference specified by ID
|
||||
DeleteReference(ctx context.Context, id int64) (err error)
|
||||
}
|
||||
|
||||
// NewManager returns an instance of the default manager
|
||||
@ -122,29 +127,43 @@ func (m *manager) UpdatePullTime(ctx context.Context, artifactID int64, time tim
|
||||
}, "PullTime")
|
||||
}
|
||||
|
||||
func (m *manager) ListReferences(ctx context.Context, query *q.Query) ([]*Reference, error) {
|
||||
references, err := m.dao.ListReferences(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var refs []*Reference
|
||||
for _, reference := range references {
|
||||
ref := &Reference{}
|
||||
ref.From(reference)
|
||||
art, err := m.dao.Get(ctx, reference.ChildID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ref.ChildDigest = art.Digest
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
func (m *manager) DeleteReference(ctx context.Context, id int64) error {
|
||||
return m.dao.DeleteReference(ctx, id)
|
||||
}
|
||||
|
||||
// assemble the artifact with references populated
|
||||
func (m *manager) assemble(ctx context.Context, art *dao.Artifact) (*Artifact, error) {
|
||||
artifact := &Artifact{}
|
||||
// convert from database object
|
||||
artifact.From(art)
|
||||
// populate the references
|
||||
refs, err := m.dao.ListReferences(ctx, &q.Query{
|
||||
references, err := m.ListReferences(ctx, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"parent_id": artifact.ID,
|
||||
"ParentID": artifact.ID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ref := range refs {
|
||||
reference := &Reference{}
|
||||
reference.From(ref)
|
||||
art, err := m.dao.Get(ctx, reference.ChildID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reference.ChildDigest = art.Digest
|
||||
artifact.References = append(artifact.References, reference)
|
||||
}
|
||||
artifact.References = references
|
||||
return artifact, nil
|
||||
}
|
||||
|
@ -64,6 +64,10 @@ func (f *fakeDao) ListReferences(ctx context.Context, query *q.Query) ([]*dao.Ar
|
||||
args := f.Called()
|
||||
return args.Get(0).([]*dao.ArtifactReference), args.Error(1)
|
||||
}
|
||||
func (f *fakeDao) DeleteReference(ctx context.Context, id int64) error {
|
||||
args := f.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
func (f *fakeDao) DeleteReferences(ctx context.Context, parentID int64) error {
|
||||
args := f.Called()
|
||||
return args.Error(0)
|
||||
@ -223,6 +227,31 @@ func (m *managerTestSuite) TestUpdatePullTime() {
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestListReferences() {
|
||||
m.dao.On("ListReferences").Return([]*dao.ArtifactReference{
|
||||
{
|
||||
ID: 1,
|
||||
ParentID: 1,
|
||||
ChildID: 2,
|
||||
},
|
||||
}, nil)
|
||||
m.dao.On("Get").Return(&dao.Artifact{
|
||||
ID: 1,
|
||||
Digest: "digest",
|
||||
}, nil)
|
||||
references, err := m.mgr.ListReferences(nil, nil)
|
||||
m.Require().Nil(err)
|
||||
m.Require().Len(references, 1)
|
||||
m.Equal(int64(1), references[0].ID)
|
||||
m.Equal("digest", references[0].ChildDigest)
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestDeleteReference() {
|
||||
m.dao.On("DeleteReference").Return(nil)
|
||||
err := m.mgr.DeleteReference(nil, 1)
|
||||
m.Require().Nil(err)
|
||||
}
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
suite.Run(t, &managerTestSuite{})
|
||||
}
|
||||
|
@ -27,19 +27,19 @@ import (
|
||||
// underlying concrete detail and provides an unified artifact view
|
||||
// for all users.
|
||||
type Artifact struct {
|
||||
ID int64
|
||||
Type string // image, chart, etc
|
||||
MediaType string // the media type of artifact. Mostly, it's the value of `manifest.config.mediatype`
|
||||
ManifestMediaType string // the media type of manifest/index
|
||||
ProjectID int64
|
||||
RepositoryID int64
|
||||
Digest string
|
||||
Size int64
|
||||
PushTime time.Time
|
||||
PullTime time.Time
|
||||
ExtraAttrs map[string]interface{} // only contains the simple attributes specific for the different artifact type, most of them should come from the config layer
|
||||
Annotations map[string]string
|
||||
References []*Reference // child artifacts referenced by the parent artifact if the artifact is an index
|
||||
ID int64 `json:"id"`
|
||||
Type string `json:"type"` // image, chart, etc
|
||||
MediaType string `json:"media_type"` // the media type of artifact. Mostly, it's the value of `manifest.config.mediatype`
|
||||
ManifestMediaType string `json:"manifest_media_type"` // the media type of manifest/index
|
||||
ProjectID int64 `json:"project_id"`
|
||||
RepositoryID int64 `json:"repository_id"`
|
||||
Digest string `json:"digest"`
|
||||
Size int64 `json:"size"`
|
||||
PushTime time.Time `json:"push_time"`
|
||||
PullTime time.Time `json:"pull_time"`
|
||||
ExtraAttrs map[string]interface{} `json:"extra_attrs"` // only contains the simple attributes specific for the different artifact type, most of them should come from the config layer
|
||||
Annotations map[string]string `json:"annotations"`
|
||||
References []*Reference `json:"references"` // child artifacts referenced by the parent artifact if the artifact is an index
|
||||
}
|
||||
|
||||
// From converts the database level artifact to the business level object
|
||||
@ -101,14 +101,16 @@ func (a *Artifact) To() *dao.Artifact {
|
||||
|
||||
// Reference records the child artifact referenced by parent artifact
|
||||
type Reference struct {
|
||||
ParentID int64
|
||||
ChildID int64
|
||||
ChildDigest string // As we only provide the API based on digest rather than ID, the digest of child artifact is needed
|
||||
ID int64 `json:"id"`
|
||||
ParentID int64 `json:"parent_id"`
|
||||
ChildID int64 `json:"child_id"`
|
||||
ChildDigest string `json:"child_digest"` // As we only provide the API based on digest rather than ID, the digest of child artifact is needed
|
||||
Platform *v1.Platform
|
||||
}
|
||||
|
||||
// From converts the data level reference to business level
|
||||
func (r *Reference) From(ref *dao.ArtifactReference) {
|
||||
r.ID = ref.ID
|
||||
r.ParentID = ref.ParentID
|
||||
r.ChildID = ref.ChildID
|
||||
if len(ref.Platform) > 0 {
|
||||
@ -122,6 +124,7 @@ func (r *Reference) From(ref *dao.ArtifactReference) {
|
||||
// To converts the reference to data level object
|
||||
func (r *Reference) To() *dao.ArtifactReference {
|
||||
ref := &dao.ArtifactReference{
|
||||
ID: r.ID,
|
||||
ParentID: r.ParentID,
|
||||
ChildID: r.ChildID,
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ type DAO interface {
|
||||
Update(ctx context.Context, tag *tag.Tag, props ...string) (err error)
|
||||
// Delete the tag specified by ID
|
||||
Delete(ctx context.Context, id int64) (err error)
|
||||
// DeleteOfArtifact deletes all tags attached to the artifact
|
||||
DeleteOfArtifact(ctx context.Context, artifactID int64) (err error)
|
||||
}
|
||||
|
||||
// New returns an instance of the default DAO
|
||||
@ -141,3 +143,16 @@ func (d *dao) Delete(ctx context.Context, id int64) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dao) DeleteOfArtifact(ctx context.Context, artifactID int64) error {
|
||||
qs, err := orm.QuerySetter(ctx, &tag.Tag{}, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"ArtifactID": artifactID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = qs.Delete()
|
||||
return err
|
||||
}
|
||||
|
@ -61,7 +61,6 @@ func (d *daoTestSuite) TearDownSuite() {
|
||||
}
|
||||
|
||||
func (d *daoTestSuite) SetupTest() {
|
||||
|
||||
tag := &tag.Tag{
|
||||
RepositoryID: 1000,
|
||||
ArtifactID: d.artifactID,
|
||||
@ -223,6 +222,53 @@ func (d *daoTestSuite) TestUpdate() {
|
||||
d.Equal(ierror.NotFoundCode, e.Code)
|
||||
}
|
||||
|
||||
func (d *daoTestSuite) TestDeleteOfArtifact() {
|
||||
artifactID, err := d.artDAO.Create(d.ctx, &artdao.Artifact{
|
||||
Type: "IMAGE",
|
||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
ProjectID: 1,
|
||||
RepositoryID: 1000,
|
||||
Digest: "sha256:digest02",
|
||||
})
|
||||
d.Require().Nil(err)
|
||||
defer d.artDAO.Delete(d.ctx, artifactID)
|
||||
|
||||
tag1 := &tag.Tag{
|
||||
RepositoryID: 1000,
|
||||
ArtifactID: artifactID,
|
||||
Name: "tag1",
|
||||
}
|
||||
_, err = d.dao.Create(d.ctx, tag1)
|
||||
d.Require().Nil(err)
|
||||
tag2 := &tag.Tag{
|
||||
RepositoryID: 1000,
|
||||
ArtifactID: artifactID,
|
||||
Name: "tag2",
|
||||
}
|
||||
_, err = d.dao.Create(d.ctx, tag2)
|
||||
d.Require().Nil(err)
|
||||
|
||||
tags, err := d.dao.List(d.ctx, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"ArtifactID": artifactID,
|
||||
},
|
||||
})
|
||||
d.Require().Nil(err)
|
||||
d.Require().Len(tags, 2)
|
||||
|
||||
err = d.dao.DeleteOfArtifact(d.ctx, artifactID)
|
||||
d.Require().Nil(err)
|
||||
|
||||
tags, err = d.dao.List(d.ctx, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"ArtifactID": artifactID,
|
||||
},
|
||||
})
|
||||
d.Require().Nil(err)
|
||||
d.Require().Len(tags, 0)
|
||||
}
|
||||
|
||||
func TestDaoTestSuite(t *testing.T) {
|
||||
suite.Run(t, &daoTestSuite{})
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ type Manager interface {
|
||||
Update(ctx context.Context, tag *tag.Tag, props ...string) (err error)
|
||||
// Delete the tag specified by ID
|
||||
Delete(ctx context.Context, id int64) (err error)
|
||||
// DeleteOfArtifact deletes all tags attached to the artifact
|
||||
DeleteOfArtifact(ctx context.Context, artifactID int64) (err error)
|
||||
}
|
||||
|
||||
// NewManager creates an instance of the default tag manager
|
||||
@ -78,3 +80,7 @@ func (m *manager) Update(ctx context.Context, tag *tag.Tag, props ...string) err
|
||||
func (m *manager) Delete(ctx context.Context, id int64) error {
|
||||
return m.dao.Delete(ctx, id)
|
||||
}
|
||||
|
||||
func (m *manager) DeleteOfArtifact(ctx context.Context, artifactID int64) error {
|
||||
return m.dao.DeleteOfArtifact(ctx, artifactID)
|
||||
}
|
||||
|
@ -52,6 +52,10 @@ func (f *fakeDao) Delete(ctx context.Context, id int64) error {
|
||||
args := f.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
func (f *fakeDao) DeleteOfArtifact(ctx context.Context, artifactID int64) error {
|
||||
args := f.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
type managerTestSuite struct {
|
||||
suite.Suite
|
||||
@ -113,6 +117,12 @@ func (m *managerTestSuite) TestDelete() {
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestDeleteOfArtifact() {
|
||||
m.dao.On("DeleteOfArtifact", mock.Anything).Return(nil)
|
||||
err := m.mgr.DeleteOfArtifact(nil, 1)
|
||||
m.Require().Nil(err)
|
||||
}
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
suite.Run(t, &managerTestSuite{})
|
||||
}
|
||||
|
@ -20,10 +20,10 @@ import (
|
||||
|
||||
// Tag model in database
|
||||
type Tag struct {
|
||||
ID int64 `orm:"pk;auto;column(id)"`
|
||||
RepositoryID int64 `orm:"column(repository_id)"` // tags are the resources of repository, one repository only contains one same name tag
|
||||
ArtifactID int64 `orm:"column(artifact_id)"` // the artifact ID that the tag attaches to, it changes when pushing a same name but different digest artifact
|
||||
Name string `orm:"column(name)"`
|
||||
PushTime time.Time `orm:"column(push_time)"`
|
||||
PullTime time.Time `orm:"column(pull_time)"`
|
||||
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
||||
RepositoryID int64 `orm:"column(repository_id)" json:"repository_id"` // tags are the resources of repository, one repository only contains one same name tag
|
||||
ArtifactID int64 `orm:"column(artifact_id)" json:"artifact_id"` // the artifact ID that the tag attaches to, it changes when pushing a same name but different digest artifact
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
PushTime time.Time `orm:"column(push_time)" json:"push_time"`
|
||||
PullTime time.Time `orm:"column(pull_time)" json:"pull_time"`
|
||||
}
|
||||
|
@ -74,3 +74,19 @@ func (f *FakeManager) UpdatePullTime(ctx context.Context, artifactID int64, time
|
||||
args := f.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// ListReferences ...
|
||||
func (f *FakeManager) ListReferences(ctx context.Context, query *q.Query) ([]*artifact.Reference, error) {
|
||||
args := f.Called()
|
||||
var references []*artifact.Reference
|
||||
if args.Get(0) != nil {
|
||||
references = args.Get(0).([]*artifact.Reference)
|
||||
}
|
||||
return references, args.Error(1)
|
||||
}
|
||||
|
||||
// DeleteReference ...
|
||||
func (f *FakeManager) DeleteReference(ctx context.Context, id int64) error {
|
||||
args := f.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
@ -63,3 +63,9 @@ func (f *FakeManager) Delete(ctx context.Context, id int64) error {
|
||||
args := f.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// DeleteOfArtifact ...
|
||||
func (f *FakeManager) DeleteOfArtifact(ctx context.Context, artifactID int64) error {
|
||||
args := f.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user