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:
Wenkai Yin 2020-02-15 21:48:24 +08:00
parent 599075d5b4
commit 9d2f1d4d66
15 changed files with 345 additions and 66 deletions

View File

@ -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
}

View File

@ -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() {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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{})
}

View File

@ -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,
}

View File

@ -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
}

View File

@ -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{})
}

View File

@ -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)
}

View File

@ -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{})
}

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -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)
}