mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-25 19:56:09 +01:00
add artifact trash manager
1, move the deleted artifact into trash 2, disable GC to delete the untagged manifest Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
4ad02de348
commit
f160505686
@ -43,6 +43,17 @@ CREATE TABLE artifact_reference
|
|||||||
CONSTRAINT unique_reference UNIQUE (parent_id, child_id)
|
CONSTRAINT unique_reference UNIQUE (parent_id, child_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* artifact_trash records deleted artifact */
|
||||||
|
CREATE TABLE artifact_trash
|
||||||
|
(
|
||||||
|
id SERIAL PRIMARY KEY NOT NULL,
|
||||||
|
media_type varchar(255) NOT NULL,
|
||||||
|
manifest_media_type varchar(255) NOT NULL,
|
||||||
|
repository_name varchar(255) NOT NULL,
|
||||||
|
digest varchar(255) NOT NULL,
|
||||||
|
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT unique_artifact_trash UNIQUE (repository_name, digest)
|
||||||
|
);
|
||||||
|
|
||||||
/* TODO upgrade: how about keep the table "harbor_resource_label" only for helm v2 chart and use the new table for artifact label reference? */
|
/* TODO upgrade: how about keep the table "harbor_resource_label" only for helm v2 chart and use the new table for artifact label reference? */
|
||||||
/* label_reference records the labels added to the artifact */
|
/* label_reference records the labels added to the artifact */
|
||||||
@ -56,4 +67,5 @@ CREATE TABLE label_reference (
|
|||||||
/* TODO replace artifact_2 after finishing the upgrade work */
|
/* TODO replace artifact_2 after finishing the upgrade work */
|
||||||
FOREIGN KEY (artifact_id) REFERENCES artifact_2(id),
|
FOREIGN KEY (artifact_id) REFERENCES artifact_2(id),
|
||||||
CONSTRAINT unique_label_reference UNIQUE (label_id,artifact_id)
|
CONSTRAINT unique_label_reference UNIQUE (label_id,artifact_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/utils"
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
"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/art"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/artifactrash"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/artifactrash/model"
|
||||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match"
|
"github.com/goharbor/harbor/src/pkg/immutabletag/match"
|
||||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||||
"github.com/goharbor/harbor/src/pkg/label"
|
"github.com/goharbor/harbor/src/pkg/label"
|
||||||
@ -89,6 +91,7 @@ func NewController() Controller {
|
|||||||
return &controller{
|
return &controller{
|
||||||
repoMgr: repository.Mgr,
|
repoMgr: repository.Mgr,
|
||||||
artMgr: artifact.Mgr,
|
artMgr: artifact.Mgr,
|
||||||
|
artrashMgr: artifactrash.Mgr,
|
||||||
tagMgr: tag.Mgr,
|
tagMgr: tag.Mgr,
|
||||||
sigMgr: signature.GetManager(),
|
sigMgr: signature.GetManager(),
|
||||||
labelMgr: label.Mgr,
|
labelMgr: label.Mgr,
|
||||||
@ -102,6 +105,7 @@ func NewController() Controller {
|
|||||||
type controller struct {
|
type controller struct {
|
||||||
repoMgr repository.Manager
|
repoMgr repository.Manager
|
||||||
artMgr artifact.Manager
|
artMgr artifact.Manager
|
||||||
|
artrashMgr artifactrash.Manager
|
||||||
tagMgr tag.Manager
|
tagMgr tag.Manager
|
||||||
sigMgr signature.Manager
|
sigMgr signature.Manager
|
||||||
labelMgr label.Manager
|
labelMgr label.Manager
|
||||||
@ -288,6 +292,7 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot bool) er
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// the child artifact is referenced by some tags, skip
|
// the child artifact is referenced by some tags, skip
|
||||||
if !isRoot && len(art.Tags) > 0 {
|
if !isRoot && len(art.Tags) > 0 {
|
||||||
return nil
|
return nil
|
||||||
@ -342,6 +347,19 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot bool) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repo, err := c.repoMgr.Get(ctx, art.RepositoryID)
|
||||||
|
if err != nil && !ierror.IsErr(err, ierror.NotFoundCode) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = c.artrashMgr.Create(ctx, &model.ArtifactTrash{
|
||||||
|
MediaType: art.MediaType,
|
||||||
|
ManifestMediaType: art.ManifestMediaType,
|
||||||
|
RepositoryName: repo.Name,
|
||||||
|
Digest: art.Digest,
|
||||||
|
})
|
||||||
|
if err != nil && !ierror.IsErr(err, ierror.ConflictCode) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// TODO fire delete artifact event
|
// TODO fire delete artifact event
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/pkg/q"
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
|
"github.com/goharbor/harbor/src/pkg/tag/model/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"
|
||||||
immutesting "github.com/goharbor/harbor/src/testing/pkg/immutabletag"
|
immutesting "github.com/goharbor/harbor/src/testing/pkg/immutabletag"
|
||||||
"github.com/goharbor/harbor/src/testing/pkg/label"
|
"github.com/goharbor/harbor/src/testing/pkg/label"
|
||||||
repotesting "github.com/goharbor/harbor/src/testing/pkg/repository"
|
repotesting "github.com/goharbor/harbor/src/testing/pkg/repository"
|
||||||
@ -69,6 +70,7 @@ type controllerTestSuite struct {
|
|||||||
ctl *controller
|
ctl *controller
|
||||||
repoMgr *repotesting.FakeManager
|
repoMgr *repotesting.FakeManager
|
||||||
artMgr *arttesting.FakeManager
|
artMgr *arttesting.FakeManager
|
||||||
|
artrashMgr *artrashtesting.FakeManager
|
||||||
tagMgr *tagtesting.FakeManager
|
tagMgr *tagtesting.FakeManager
|
||||||
labelMgr *label.FakeManager
|
labelMgr *label.FakeManager
|
||||||
abstractor *fakeAbstractor
|
abstractor *fakeAbstractor
|
||||||
@ -78,6 +80,7 @@ type controllerTestSuite struct {
|
|||||||
func (c *controllerTestSuite) SetupTest() {
|
func (c *controllerTestSuite) SetupTest() {
|
||||||
c.repoMgr = &repotesting.FakeManager{}
|
c.repoMgr = &repotesting.FakeManager{}
|
||||||
c.artMgr = &arttesting.FakeManager{}
|
c.artMgr = &arttesting.FakeManager{}
|
||||||
|
c.artrashMgr = &artrashtesting.FakeManager{}
|
||||||
c.tagMgr = &tagtesting.FakeManager{}
|
c.tagMgr = &tagtesting.FakeManager{}
|
||||||
c.labelMgr = &label.FakeManager{}
|
c.labelMgr = &label.FakeManager{}
|
||||||
c.abstractor = &fakeAbstractor{}
|
c.abstractor = &fakeAbstractor{}
|
||||||
@ -85,6 +88,7 @@ func (c *controllerTestSuite) SetupTest() {
|
|||||||
c.ctl = &controller{
|
c.ctl = &controller{
|
||||||
repoMgr: c.repoMgr,
|
repoMgr: c.repoMgr,
|
||||||
artMgr: c.artMgr,
|
artMgr: c.artMgr,
|
||||||
|
artrashMgr: c.artrashMgr,
|
||||||
tagMgr: c.tagMgr,
|
tagMgr: c.tagMgr,
|
||||||
labelMgr: c.labelMgr,
|
labelMgr: c.labelMgr,
|
||||||
abstractor: c.abstractor,
|
abstractor: c.abstractor,
|
||||||
@ -424,12 +428,14 @@ 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.tagMgr.On("List").Return(0, []*tag.Tag{
|
c.tagMgr.On("List").Return(0, []*tag.Tag{
|
||||||
{
|
{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
|
||||||
|
c.artrashMgr.On("Create").Return(0, nil)
|
||||||
err = c.ctl.deleteDeeply(nil, 1, false)
|
err = c.ctl.deleteDeeply(nil, 1, false)
|
||||||
c.Require().Nil(err)
|
c.Require().Nil(err)
|
||||||
|
|
||||||
@ -474,6 +480,7 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
|
|||||||
c.tagMgr.On("DeleteOfArtifact").Return(nil)
|
c.tagMgr.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)
|
||||||
err = c.ctl.deleteDeeply(nil, 1, true)
|
err = c.ctl.deleteDeeply(nil, 1, true)
|
||||||
c.Require().Nil(err)
|
c.Require().Nil(err)
|
||||||
}
|
}
|
||||||
|
@ -171,6 +171,7 @@ func (d *dao) Delete(ctx context.Context, id int64) error {
|
|||||||
if n == 0 {
|
if n == 0 {
|
||||||
return ierror.NotFoundError(nil).WithMessage("artifact %d not found", id)
|
return ierror.NotFoundError(nil).WithMessage("artifact %d not found", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (d *dao) Update(ctx context.Context, artifact *Artifact, props ...string) error {
|
func (d *dao) Update(ctx context.Context, artifact *Artifact, props ...string) error {
|
||||||
|
78
src/pkg/artifactrash/dao/dao.go
Normal file
78
src/pkg/artifactrash/dao/dao.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||||
|
"github.com/goharbor/harbor/src/internal/orm"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/artifactrash/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DAO is the data access object interface for artifact trash
|
||||||
|
type DAO interface {
|
||||||
|
// Create the artifact trash
|
||||||
|
Create(ctx context.Context, artifactrsh *model.ArtifactTrash) (id int64, err error)
|
||||||
|
// Delete the artifact trash specified by ID
|
||||||
|
Delete(ctx context.Context, id int64) (err error)
|
||||||
|
// Filter lists the artifact that needs to be cleaned
|
||||||
|
Filter(ctx context.Context) (arts []model.ArtifactTrash, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns an instance of the default DAO
|
||||||
|
func New() DAO {
|
||||||
|
return &dao{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type dao struct{}
|
||||||
|
|
||||||
|
// Create ...
|
||||||
|
func (d *dao) Create(ctx context.Context, artifactrsh *model.ArtifactTrash) (id int64, err error) {
|
||||||
|
ormer, err := orm.FromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
artifactrsh.CreationTime = time.Now()
|
||||||
|
id, err = ormer.Insert(artifactrsh)
|
||||||
|
if err != nil {
|
||||||
|
if e := orm.AsConflictError(err, "artifact trash %s already exists under the repository %s",
|
||||||
|
artifactrsh.Digest, artifactrsh.RepositoryName); e != nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete ...
|
||||||
|
func (d *dao) Delete(ctx context.Context, id int64) (err error) {
|
||||||
|
ormer, err := orm.FromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n, err := ormer.Delete(&model.ArtifactTrash{
|
||||||
|
ID: id,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return ierror.NotFoundError(nil).WithMessage("artifact trash %d not found", id)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter ...
|
||||||
|
// ToDo replace artifact_2 with artifact
|
||||||
|
func (d *dao) Filter(ctx context.Context) (arts []model.ArtifactTrash, err error) {
|
||||||
|
var deletedAfs []model.ArtifactTrash
|
||||||
|
ormer, err := orm.FromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return deletedAfs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sql := `SELECT * FROM artifact_trash where artifact_trash.digest NOT IN (select digest from artifact_2)`
|
||||||
|
|
||||||
|
if err := ormer.Raw(sql).QueryRow(&deletedAfs); err != nil {
|
||||||
|
return deletedAfs, err
|
||||||
|
}
|
||||||
|
return deletedAfs, nil
|
||||||
|
}
|
91
src/pkg/artifactrash/dao/dao_test.go
Normal file
91
src/pkg/artifactrash/dao/dao_test.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
beegoorm "github.com/astaxie/beego/orm"
|
||||||
|
common_dao "github.com/goharbor/harbor/src/common/dao"
|
||||||
|
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||||
|
"github.com/goharbor/harbor/src/internal/orm"
|
||||||
|
artdao "github.com/goharbor/harbor/src/pkg/artifact/dao"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/artifactrash/model"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type daoTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
dao DAO
|
||||||
|
afDao artdao.DAO
|
||||||
|
id int64
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *daoTestSuite) SetupSuite() {
|
||||||
|
d.dao = New()
|
||||||
|
common_dao.PrepareTestForPostgresSQL()
|
||||||
|
d.ctx = orm.NewContext(nil, beegoorm.NewOrm())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *daoTestSuite) SetupTest() {
|
||||||
|
art1 := &artdao.Artifact{
|
||||||
|
Type: "image",
|
||||||
|
ManifestMediaType: v1.MediaTypeImageManifest,
|
||||||
|
ProjectID: 10,
|
||||||
|
RepositoryID: 10,
|
||||||
|
Digest: "1234",
|
||||||
|
}
|
||||||
|
id, err := d.afDao.Create(d.ctx, art1)
|
||||||
|
d.Require().Nil(err)
|
||||||
|
err = d.afDao.Delete(d.ctx, id)
|
||||||
|
d.Require().Nil(err)
|
||||||
|
art2 := &artdao.Artifact{
|
||||||
|
Type: "image",
|
||||||
|
ManifestMediaType: v1.MediaTypeImageManifest,
|
||||||
|
ProjectID: 10,
|
||||||
|
RepositoryID: 10,
|
||||||
|
Digest: "5678",
|
||||||
|
}
|
||||||
|
_, err = d.afDao.Create(d.ctx, art2)
|
||||||
|
d.Require().Nil(err)
|
||||||
|
|
||||||
|
aft := &model.ArtifactTrash{
|
||||||
|
ManifestMediaType: v1.MediaTypeImageManifest,
|
||||||
|
RepositoryName: "test/hello-world",
|
||||||
|
Digest: "1234",
|
||||||
|
}
|
||||||
|
id, err = d.dao.Create(d.ctx, aft)
|
||||||
|
d.Require().Nil(err)
|
||||||
|
d.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *daoTestSuite) TearDownTest() {
|
||||||
|
err := d.dao.Delete(d.ctx, d.id)
|
||||||
|
d.Require().Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *daoTestSuite) TestCreate() {
|
||||||
|
// conflict
|
||||||
|
aft := &model.ArtifactTrash{
|
||||||
|
ManifestMediaType: v1.MediaTypeImageManifest,
|
||||||
|
RepositoryName: "test/hello-world",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := d.dao.Create(d.ctx, aft)
|
||||||
|
d.Require().NotNil(err)
|
||||||
|
d.True(ierror.IsErr(err, ierror.ConflictCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *daoTestSuite) TestDelete() {
|
||||||
|
err := d.dao.Delete(d.ctx, 100021)
|
||||||
|
d.Require().NotNil(err)
|
||||||
|
var e *ierror.Error
|
||||||
|
d.Require().True(errors.As(err, &e))
|
||||||
|
d.Equal(ierror.NotFoundCode, e.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *daoTestSuite) TestFilter() {
|
||||||
|
afs, err := d.dao.Filter(d.ctx)
|
||||||
|
d.Require().NotNil(err)
|
||||||
|
d.Require().Equal(afs[0].Digest, "1234")
|
||||||
|
}
|
59
src/pkg/artifactrash/manager.go
Normal file
59
src/pkg/artifactrash/manager.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package artifactrash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/artifactrash/dao"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/artifactrash/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Mgr is a global artifact trash manager instance
|
||||||
|
Mgr = NewManager()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager is the only interface of artifact module to provide the management functions for artifacts
|
||||||
|
type Manager interface {
|
||||||
|
// Create ...
|
||||||
|
Create(ctx context.Context, artifactrsh *model.ArtifactTrash) (id int64, err error)
|
||||||
|
// Delete ...
|
||||||
|
Delete(ctx context.Context, id int64) (err error)
|
||||||
|
// Filter ...
|
||||||
|
Filter(ctx context.Context) (arts []model.ArtifactTrash, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager returns an instance of the default manager
|
||||||
|
func NewManager() Manager {
|
||||||
|
return &manager{
|
||||||
|
dao.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &manager{}
|
||||||
|
|
||||||
|
type manager struct {
|
||||||
|
dao dao.DAO
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) Create(ctx context.Context, artifactrsh *model.ArtifactTrash) (id int64, err error) {
|
||||||
|
return m.dao.Create(ctx, artifactrsh)
|
||||||
|
}
|
||||||
|
func (m *manager) Delete(ctx context.Context, id int64) error {
|
||||||
|
return m.dao.Delete(ctx, id)
|
||||||
|
}
|
||||||
|
func (m *manager) Filter(ctx context.Context) (arts []model.ArtifactTrash, err error) {
|
||||||
|
return m.dao.Filter(ctx)
|
||||||
|
}
|
71
src/pkg/artifactrash/manager_test.go
Normal file
71
src/pkg/artifactrash/manager_test.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package artifactrash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/artifactrash/model"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeDao struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeDao) Create(ctx context.Context, artifactrsh *model.ArtifactTrash) (id int64, err error) {
|
||||||
|
args := f.Called()
|
||||||
|
return int64(args.Int(0)), args.Error(1)
|
||||||
|
}
|
||||||
|
func (f *fakeDao) Delete(ctx context.Context, id int64) (err error) {
|
||||||
|
args := f.Called()
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
func (f *fakeDao) Filter(ctx context.Context) (arts []model.ArtifactTrash, err error) {
|
||||||
|
args := f.Called()
|
||||||
|
return args.Get(0).([]model.ArtifactTrash), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type managerTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
mgr *manager
|
||||||
|
dao *fakeDao
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managerTestSuite) SetupTest() {
|
||||||
|
m.dao = &fakeDao{}
|
||||||
|
m.mgr = &manager{
|
||||||
|
dao: m.dao,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managerTestSuite) TestCreate() {
|
||||||
|
m.dao.On("Create", mock.Anything).Return(1, nil)
|
||||||
|
id, err := m.mgr.Create(nil, &model.ArtifactTrash{
|
||||||
|
ManifestMediaType: v1.MediaTypeImageManifest,
|
||||||
|
RepositoryName: "test/hello-world",
|
||||||
|
Digest: "5678",
|
||||||
|
})
|
||||||
|
m.Require().Nil(err)
|
||||||
|
m.dao.AssertExpectations(m.T())
|
||||||
|
m.Equal(int64(1), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managerTestSuite) TestDelete() {
|
||||||
|
m.dao.On("Delete", mock.Anything).Return(nil)
|
||||||
|
err := m.mgr.Delete(nil, 1)
|
||||||
|
m.Require().Nil(err)
|
||||||
|
m.dao.AssertExpectations(m.T())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managerTestSuite) TestFilter() {
|
||||||
|
m.dao.On("Filter", mock.Anything).Return([]model.ArtifactTrash{
|
||||||
|
{
|
||||||
|
ManifestMediaType: v1.MediaTypeImageManifest,
|
||||||
|
RepositoryName: "test/hello-world",
|
||||||
|
Digest: "5678",
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
arts, err := m.mgr.Filter(nil)
|
||||||
|
m.Require().Nil(err)
|
||||||
|
m.Equal(len(arts), 1)
|
||||||
|
}
|
39
src/pkg/artifactrash/model/model.go
Normal file
39
src/pkg/artifactrash/model/model.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/orm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
orm.RegisterModel(&ArtifactTrash{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtifactTrash records the deleted artifact
|
||||||
|
type ArtifactTrash struct {
|
||||||
|
ID int64 `orm:"pk;auto;column(id)"`
|
||||||
|
MediaType string `orm:"column(media_type)"`
|
||||||
|
ManifestMediaType string `orm:"column(manifest_media_type)"`
|
||||||
|
RepositoryName string `orm:"column(repository_name)"`
|
||||||
|
Digest string `orm:"column(digest)"`
|
||||||
|
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName for artifact trash
|
||||||
|
func (at *ArtifactTrash) TableName() string {
|
||||||
|
return "artifact_trash"
|
||||||
|
}
|
@ -38,7 +38,7 @@ type GCResult struct {
|
|||||||
|
|
||||||
// StartGC ...
|
// StartGC ...
|
||||||
func StartGC(w http.ResponseWriter, r *http.Request) {
|
func StartGC(w http.ResponseWriter, r *http.Request) {
|
||||||
cmd := exec.Command("/bin/bash", "-c", "registry garbage-collect --delete-untagged=true "+regConf)
|
cmd := exec.Command("/bin/bash", "-c", "registry garbage-collect --delete-untagged=false "+regConf)
|
||||||
var outBuf, errBuf bytes.Buffer
|
var outBuf, errBuf bytes.Buffer
|
||||||
cmd.Stdout = &outBuf
|
cmd.Stdout = &outBuf
|
||||||
cmd.Stderr = &errBuf
|
cmd.Stderr = &errBuf
|
||||||
|
47
src/testing/pkg/artifactrash/manager.go
Normal file
47
src/testing/pkg/artifactrash/manager.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package artifactrash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/goharbor/harbor/src/pkg/artifactrash/model"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FakeManager is a fake tag manager that implement the src/pkg/tag.Manager interface
|
||||||
|
type FakeManager struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create ...
|
||||||
|
func (f *FakeManager) Create(ctx context.Context, artifactrsh *model.ArtifactTrash) (id int64, err error) {
|
||||||
|
args := f.Called()
|
||||||
|
return int64(args.Int(0)), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete ...
|
||||||
|
func (f *FakeManager) Delete(ctx context.Context, id int64) error {
|
||||||
|
args := f.Called()
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter ...
|
||||||
|
func (f *FakeManager) Filter(ctx context.Context) (arts []model.ArtifactTrash, err error) {
|
||||||
|
args := f.Called()
|
||||||
|
return args.Get(0).([]model.ArtifactTrash), args.Error(1)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user