mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-27 01:02:34 +01:00
System Artifact Manager database schema creation, model definitons, and tests (#16678)
Closes: https://github.com/goharbor/harbor/issues/16540 https://github.com/goharbor/harbor/issues/16541 https://github.com/goharbor/harbor/issues/16542 Signed-off-by: prahaladdarkin <prahaladd@vmware.com>
This commit is contained in:
parent
1f797fafc4
commit
27ec871185
@ -1,2 +1,18 @@
|
||||
/* Correct project_metadata.public value, should only be true or false, other invaild value will be rewrite to false */
|
||||
UPDATE project_metadata SET value='false' WHERE name='public' AND value NOT IN('true', 'false');
|
||||
UPDATE project_metadata SET value='false' WHERE name='public' AND value NOT IN('true', 'false');
|
||||
|
||||
/*
|
||||
System Artifact Manager
|
||||
Github proposal link : https://github.com/goharbor/community/pull/181
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS system_artifact (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
repository varchar(256) NOT NULL,
|
||||
digest varchar(255) NOT NULL DEFAULT '' ,
|
||||
size bigint NOT NULL DEFAULT 0 ,
|
||||
vendor varchar(255) NOT NULL DEFAULT '' ,
|
||||
type varchar(255) NOT NULL DEFAULT '' ,
|
||||
create_time timestamp default CURRENT_TIMESTAMP,
|
||||
extra_attrs text NOT NULL DEFAULT '' ,
|
||||
UNIQUE ("repository", "digest", "vendor")
|
||||
);
|
55
src/pkg/systemartifact/cleanupcriteria.go
Normal file
55
src/pkg/systemartifact/cleanupcriteria.go
Normal file
@ -0,0 +1,55 @@
|
||||
package systemartifact
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/jobservice/logger"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/systemartifact/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/systemartifact/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultCleanupWindowSeconds = 86400
|
||||
)
|
||||
|
||||
// Selector provides an interface that can be implemented
|
||||
// by consumers of the system artifact management framework to
|
||||
// provide a custom clean-up criteria. This allows producers of the
|
||||
// system artifact data to control the lifespan of the generated artifact
|
||||
// records and data.
|
||||
// Every system data artifact produces must register a cleanup criteria.
|
||||
|
||||
type Selector interface {
|
||||
// List all system artifacts created greater than 24 hours.
|
||||
List(ctx context.Context) ([]*model.SystemArtifact, error)
|
||||
// ListWithFilters allows retrieval of system artifact records that match
|
||||
// multiple filter and sort criteria that can be specified by the clients
|
||||
ListWithFilters(ctx context.Context, query *q.Query) ([]*model.SystemArtifact, error)
|
||||
}
|
||||
|
||||
var DefaultSelector = Default()
|
||||
|
||||
func Default() Selector {
|
||||
return &defaultSelector{dao: dao.NewSystemArtifactDao()}
|
||||
}
|
||||
|
||||
// defaultSelector is a default implementation of the Selector which select system artifacts
|
||||
// older than 24 hours for clean-up
|
||||
type defaultSelector struct {
|
||||
dao dao.DAO
|
||||
}
|
||||
|
||||
func (cleanupCriteria *defaultSelector) ListWithFilters(ctx context.Context, query *q.Query) ([]*model.SystemArtifact, error) {
|
||||
return cleanupCriteria.dao.List(ctx, query)
|
||||
}
|
||||
|
||||
func (cleanupCriteria *defaultSelector) List(ctx context.Context) ([]*model.SystemArtifact, error) {
|
||||
|
||||
currentTime := time.Now()
|
||||
duration := time.Duration(DefaultCleanupWindowSeconds) * time.Second
|
||||
timeRange := q.Range{Max: currentTime.Add(-duration).Format(time.RFC3339)}
|
||||
logger.Debugf("Cleaning up system artifacts with range: %v", timeRange)
|
||||
query := q.New(map[string]interface{}{"create_time": &timeRange})
|
||||
return cleanupCriteria.dao.List(ctx, query)
|
||||
}
|
162
src/pkg/systemartifact/cleanupcriteria_test.go
Normal file
162
src/pkg/systemartifact/cleanupcriteria_test.go
Normal file
@ -0,0 +1,162 @@
|
||||
package systemartifact
|
||||
|
||||
import (
|
||||
"context"
|
||||
common_dao "github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/systemartifact/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/systemartifact/model"
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type defaultCleanupCriteriaTestSuite struct {
|
||||
htesting.Suite
|
||||
dao dao.DAO
|
||||
ctx context.Context
|
||||
cleanupCriteria Selector
|
||||
}
|
||||
|
||||
func (suite *defaultCleanupCriteriaTestSuite) SetupSuite() {
|
||||
suite.Suite.SetupSuite()
|
||||
suite.dao = dao.NewSystemArtifactDao()
|
||||
suite.cleanupCriteria = DefaultSelector
|
||||
common_dao.PrepareTestForPostgresSQL()
|
||||
suite.ctx = orm.Context()
|
||||
sa := model.SystemArtifact{}
|
||||
suite.ClearTables = append(suite.ClearTables, sa.TableName())
|
||||
}
|
||||
|
||||
func (suite *defaultCleanupCriteriaTestSuite) TestList() {
|
||||
// insert a normal system artifact
|
||||
currentTime := time.Now()
|
||||
|
||||
{
|
||||
saNow := model.SystemArtifact{
|
||||
Repository: "test_repo1000",
|
||||
Digest: "test_digest1000",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor1000",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: currentTime,
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
oneDayAndElevenMinutesAgo := time.Duration(96500) * time.Second
|
||||
|
||||
sa1 := model.SystemArtifact{
|
||||
Repository: "test_repo2000",
|
||||
Digest: "test_digest2000",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor2000",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: currentTime.Add(-oneDayAndElevenMinutesAgo),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
twoDaysAgo := time.Duration(172800) * time.Second
|
||||
sa2 := model.SystemArtifact{
|
||||
Repository: "test_repo3000",
|
||||
Digest: "test_digest3000",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor3000",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: currentTime.Add(-twoDaysAgo),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
id1, err := suite.dao.Create(suite.ctx, &saNow)
|
||||
id2, err := suite.dao.Create(suite.ctx, &sa1)
|
||||
id3, err := suite.dao.Create(suite.ctx, &sa2)
|
||||
|
||||
suite.NoError(err, "Unexpected error when inserting test record")
|
||||
suite.NotEqual(0, id1, "Expected a valid record identifier but was 0")
|
||||
suite.NotEqual(0, id2, "Expected a valid record identifier but was 0")
|
||||
suite.NotEqual(0, id3, "Expected a valid record identifier but was 0")
|
||||
|
||||
actualSysArtifactIds := make(map[int64]bool)
|
||||
|
||||
sysArtifactList, err := suite.cleanupCriteria.List(suite.ctx)
|
||||
|
||||
for _, sysArtifact := range sysArtifactList {
|
||||
actualSysArtifactIds[sysArtifact.ID] = true
|
||||
}
|
||||
expectedSysArtifactIds := map[int64]bool{id2: true, id3: true}
|
||||
|
||||
for k := range expectedSysArtifactIds {
|
||||
_, ok := actualSysArtifactIds[k]
|
||||
suite.Truef(ok, "Expected system artifact : %v not present in the list", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *defaultCleanupCriteriaTestSuite) TestListWithFilters() {
|
||||
// insert a normal system artifact
|
||||
currentTime := time.Now()
|
||||
|
||||
{
|
||||
saNow := model.SystemArtifact{
|
||||
Repository: "test_repo73000",
|
||||
Digest: "test_digest73000",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor73000",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: currentTime,
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
oneDayAndElevenMinutesAgo := time.Duration(96500) * time.Second
|
||||
|
||||
sa1 := model.SystemArtifact{
|
||||
Repository: "test_repo29000",
|
||||
Digest: "test_digest29000",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor29000",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: currentTime.Add(-oneDayAndElevenMinutesAgo),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
twoDaysAgo := time.Duration(172800) * time.Second
|
||||
sa2 := model.SystemArtifact{
|
||||
Repository: "test_repo37000",
|
||||
Digest: "test_digest37000",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor37000",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: currentTime.Add(-twoDaysAgo),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
id1, err := suite.dao.Create(suite.ctx, &saNow)
|
||||
id2, err := suite.dao.Create(suite.ctx, &sa1)
|
||||
id3, err := suite.dao.Create(suite.ctx, &sa2)
|
||||
|
||||
suite.NoError(err, "Unexpected error when inserting test record")
|
||||
suite.NotEqual(0, id1, "Expected a valid record identifier but was 0")
|
||||
suite.NotEqual(0, id2, "Expected a valid record identifier but was 0")
|
||||
suite.NotEqual(0, id3, "Expected a valid record identifier but was 0")
|
||||
|
||||
actualSysArtifactIds := make(map[int64]bool)
|
||||
|
||||
query := q.Query{Keywords: map[string]interface{}{"vendor": "test_vendor37000", "repository": "test_repo37000"}}
|
||||
sysArtifactList, err := suite.cleanupCriteria.ListWithFilters(suite.ctx, &query)
|
||||
|
||||
for _, sysArtifact := range sysArtifactList {
|
||||
actualSysArtifactIds[sysArtifact.ID] = true
|
||||
}
|
||||
expectedSysArtifactIds := map[int64]bool{id3: true}
|
||||
|
||||
for k := range expectedSysArtifactIds {
|
||||
_, ok := actualSysArtifactIds[k]
|
||||
suite.Truef(ok, "Expected system artifact : %v not present in the list", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanupCriteriaTestSuite(t *testing.T) {
|
||||
suite.Run(t, &defaultCleanupCriteriaTestSuite{})
|
||||
}
|
128
src/pkg/systemartifact/dao/dao.go
Normal file
128
src/pkg/systemartifact/dao/dao.go
Normal file
@ -0,0 +1,128 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/systemartifact/model"
|
||||
)
|
||||
|
||||
const (
|
||||
sizeQuery = "select sum(size) as total_size from system_artifact"
|
||||
totalSizeColumn = "total_size"
|
||||
)
|
||||
|
||||
// DAO defines an data access interface for manging the CRUD and read of system
|
||||
// artifact tracking records
|
||||
type DAO interface {
|
||||
|
||||
// Create a system artifact tracking record.
|
||||
Create(ctx context.Context, systemArtifact *model.SystemArtifact) (int64, error)
|
||||
|
||||
// Get a system artifact tracking record identified by vendor, repository and digest
|
||||
Get(ctx context.Context, vendor, repository, digest string) (*model.SystemArtifact, error)
|
||||
|
||||
// Delete a system artifact tracking record identified by vendor, repository and digest
|
||||
Delete(ctx context.Context, vendor, repository, digest string) error
|
||||
|
||||
// List all the system artifact records that match the criteria specified
|
||||
// within the query.
|
||||
List(ctx context.Context, query *q.Query) ([]*model.SystemArtifact, error)
|
||||
|
||||
// Size returns the sum of all the system artifacts.
|
||||
Size(ctx context.Context) (int64, error)
|
||||
}
|
||||
|
||||
// NewSystemArtifactDao returns an instance of the system artifact dao layer
|
||||
func NewSystemArtifactDao() DAO {
|
||||
return &systemArtifactDAO{}
|
||||
}
|
||||
|
||||
// The default implementation of the system artifact DAO.
|
||||
type systemArtifactDAO struct{}
|
||||
|
||||
func (*systemArtifactDAO) Create(ctx context.Context, systemArtifact *model.SystemArtifact) (int64, error) {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
id, err := ormer.Insert(systemArtifact)
|
||||
if err != nil {
|
||||
if e := orm.AsConflictError(err, "system artifact with repository name %s and digest %s already exists",
|
||||
systemArtifact.Repository, systemArtifact.Digest); e != nil {
|
||||
err = e
|
||||
}
|
||||
return int64(0), err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (*systemArtifactDAO) Get(ctx context.Context, vendor, repository, digest string) (*model.SystemArtifact, error) {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sa := model.SystemArtifact{Repository: repository, Digest: digest, Vendor: vendor}
|
||||
|
||||
err = ormer.Read(&sa, "vendor", "repository", "digest")
|
||||
|
||||
if err != nil {
|
||||
if e := orm.AsNotFoundError(err, "system artifact with repository name %s and digest %s not found",
|
||||
repository, digest); e != nil {
|
||||
err = e
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &sa, nil
|
||||
}
|
||||
|
||||
func (*systemArtifactDAO) Delete(ctx context.Context, vendor, repository, digest string) error {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sa := model.SystemArtifact{
|
||||
Repository: repository,
|
||||
Digest: digest,
|
||||
Vendor: vendor,
|
||||
}
|
||||
|
||||
_, err = ormer.Delete(&sa, "vendor", "repository", "digest")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (*systemArtifactDAO) List(ctx context.Context, query *q.Query) ([]*model.SystemArtifact, error) {
|
||||
qs, err := orm.QuerySetter(ctx, &model.SystemArtifact{}, query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var systemArtifactRecords []*model.SystemArtifact
|
||||
|
||||
_, err = qs.All(&systemArtifactRecords)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return systemArtifactRecords, nil
|
||||
}
|
||||
|
||||
func (d *systemArtifactDAO) Size(ctx context.Context) (int64, error) {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return int64(0), err
|
||||
}
|
||||
var totalSize int64
|
||||
if err := ormer.Raw(sizeQuery).QueryRow(&totalSize); err != nil {
|
||||
return int64(0), err
|
||||
}
|
||||
|
||||
return totalSize, nil
|
||||
}
|
319
src/pkg/systemartifact/dao/dao_test.go
Normal file
319
src/pkg/systemartifact/dao/dao_test.go
Normal file
@ -0,0 +1,319 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
common_dao "github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/systemartifact/model"
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type daoTestSuite struct {
|
||||
htesting.Suite
|
||||
dao DAO
|
||||
ctx context.Context
|
||||
id int64
|
||||
}
|
||||
|
||||
func (suite *daoTestSuite) SetupSuite() {
|
||||
suite.Suite.SetupSuite()
|
||||
suite.dao = &systemArtifactDAO{}
|
||||
common_dao.PrepareTestForPostgresSQL()
|
||||
suite.ctx = orm.Context()
|
||||
sa := model.SystemArtifact{}
|
||||
suite.ClearTables = append(suite.ClearTables, sa.TableName())
|
||||
}
|
||||
|
||||
func (suite *daoTestSuite) SetupTest() {
|
||||
|
||||
}
|
||||
|
||||
func (suite *daoTestSuite) TeardownTest() {
|
||||
suite.ExecSQL("delete from system_artifact")
|
||||
suite.TearDownSuite()
|
||||
}
|
||||
|
||||
func (suite *daoTestSuite) TestCreate() {
|
||||
suite.ExecSQL("delete from system_artifact")
|
||||
|
||||
// insert a normal system artifact
|
||||
{
|
||||
sa := model.SystemArtifact{
|
||||
Repository: "test_repo",
|
||||
Digest: "test_digest",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
id, err := suite.dao.Create(suite.ctx, &sa)
|
||||
suite.NoError(err, "Unexpected error when inserting test record")
|
||||
suite.NotEqual(0, id, "Expected a valid record identifier but was 0")
|
||||
}
|
||||
|
||||
// attempt to create another system artifact with same data and then create a unique constraint violation error
|
||||
{
|
||||
sa := model.SystemArtifact{
|
||||
Repository: "test_repo",
|
||||
Digest: "test_digest",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
id, err := suite.dao.Create(suite.ctx, &sa)
|
||||
|
||||
suite.Equal(int64(0), id, "Expected id to be 0 owing to unique constraint violation")
|
||||
suite.Error(err, "Expected error to be not nil")
|
||||
errWithInfo := err.(*errors.Error)
|
||||
suite.Equalf(errors.ConflictCode, errWithInfo.Code, "Expected conflict code but was %s", errWithInfo.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *daoTestSuite) TestGet() {
|
||||
|
||||
// insert a normal system artifact and attempt to get it
|
||||
{
|
||||
sa := model.SystemArtifact{
|
||||
Repository: "test_repo1",
|
||||
Digest: "test_digest1",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
id, err := suite.dao.Create(suite.ctx, &sa)
|
||||
suite.NoError(err, "Unexpected error when inserting test record")
|
||||
suite.NotEqual(0, id, "Expected a valid record identifier but was 0")
|
||||
|
||||
saRead, err := suite.dao.Get(suite.ctx, "test_vendor", "test_repo1", "test_digest1")
|
||||
suite.NoErrorf(err, "Unexpected error when reading system artifact: %v", err)
|
||||
suite.Equalf(id, saRead.ID, "The ID for inserted system record %d is not equal to the read system record %d", id, saRead.ID)
|
||||
}
|
||||
|
||||
// attempt to retrieve a non-existent system artifact record with incorrect repo name and correct digest
|
||||
{
|
||||
saRead, err := suite.dao.Get(suite.ctx, "test_vendor", "test_repo2", "test_digest1")
|
||||
suite.Errorf(err, "Expected no record found error for provided repository and digest")
|
||||
suite.Nil(saRead, "Expected system artifact record to be nil")
|
||||
|
||||
errWithInfo := err.(*errors.Error)
|
||||
suite.Equalf(errors.NotFoundCode, errWithInfo.Code, "Expected not found code but was %s", errWithInfo.Code)
|
||||
}
|
||||
|
||||
// attempt to retrieve a non-existent system artifact record with correct repo name and incorrect digest
|
||||
{
|
||||
saRead, err := suite.dao.Get(suite.ctx, "test_vendor", "test_repo1", "test_digest2")
|
||||
suite.Errorf(err, "Expected no record found error for provided repository and digest")
|
||||
suite.Nil(saRead, "Expected system artifact record to be nil")
|
||||
|
||||
errWithInfo := err.(*errors.Error)
|
||||
suite.Equalf(errors.NotFoundCode, errWithInfo.Code, "Expected not found code but was %s", errWithInfo.Code)
|
||||
}
|
||||
|
||||
// multiple system artifact records from different vendors.
|
||||
// insert a normal system artifact and attempt to get it
|
||||
{
|
||||
sa_vendor1 := model.SystemArtifact{
|
||||
Repository: "test_repo10",
|
||||
Digest: "test_digest10",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor10",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
sa_vendor2 := model.SystemArtifact{
|
||||
Repository: "test_repo20",
|
||||
Digest: "test_digest20",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor20",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
idVendor1, err := suite.dao.Create(suite.ctx, &sa_vendor1)
|
||||
idVendor2, err := suite.dao.Create(suite.ctx, &sa_vendor2)
|
||||
|
||||
suite.NoError(err, "Unexpected error when inserting test record")
|
||||
suite.NotEqual(0, idVendor1, "Expected a valid record identifier but was 0")
|
||||
suite.NotEqual(0, idVendor2, "Expected a valid record identifier but was 0")
|
||||
|
||||
saRead, err := suite.dao.Get(suite.ctx, "test_vendor10", "test_repo10", "test_digest10")
|
||||
saRead2, err := suite.dao.Get(suite.ctx, "test_vendor20", "test_repo20", "test_digest20")
|
||||
|
||||
suite.NoErrorf(err, "Unexpected error when reading system artifact: %v", err)
|
||||
suite.Equalf(idVendor1, saRead.ID, "The ID for inserted system record %d is not equal to the read system record %d", idVendor1, saRead.ID)
|
||||
suite.Equalf(idVendor2, saRead2.ID, "The ID for inserted system record %d is not equal to the read system record %d", idVendor2, saRead2.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *daoTestSuite) TestDelete() {
|
||||
|
||||
// insert a normal system artifact and attempt to get it
|
||||
{
|
||||
sa := model.SystemArtifact{
|
||||
Repository: "test_repo3",
|
||||
Digest: "test_digest3",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
id, err := suite.dao.Create(suite.ctx, &sa)
|
||||
suite.NoError(err, "Unexpected error when inserting test record")
|
||||
suite.NotEqual(0, id, "Expected a valid record identifier but was 0")
|
||||
|
||||
err = suite.dao.Delete(suite.ctx, "test_vendor", "test_repo3", "test_digest3")
|
||||
suite.NoErrorf(err, "Unexpected error when reading system artifact: %v", err)
|
||||
}
|
||||
|
||||
// attempt to delete a non-existent system artifact record with incorrect repo name and correct digest
|
||||
{
|
||||
err := suite.dao.Delete(suite.ctx, "test_vendor", "test_repo4", "test_digest3")
|
||||
suite.NoErrorf(err, "Attempt to delete a non-existent system artifact should not fail")
|
||||
}
|
||||
|
||||
// attempt to retrieve a non-existent system artifact record with correct repo name and incorrect digest
|
||||
{
|
||||
err := suite.dao.Delete(suite.ctx, "test_vendor", "test_repo3", "test_digest4")
|
||||
suite.NoErrorf(err, "Attempt to delete a non-existent system artifact should not fail")
|
||||
}
|
||||
|
||||
// multiple system artifact records from different vendors.
|
||||
// insert a normal system artifact and attempt to get it
|
||||
{
|
||||
sa_vendor1 := model.SystemArtifact{
|
||||
Repository: "test_repo200",
|
||||
Digest: "test_digest200",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor200",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
sa_vendor2 := model.SystemArtifact{
|
||||
Repository: "test_repo300",
|
||||
Digest: "test_digest300",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor300",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
idVendor1, err := suite.dao.Create(suite.ctx, &sa_vendor1)
|
||||
idVendor2, err := suite.dao.Create(suite.ctx, &sa_vendor2)
|
||||
|
||||
suite.NoError(err, "Unexpected error when inserting test record")
|
||||
suite.NotEqual(0, idVendor1, "Expected a valid record identifier but was 0")
|
||||
suite.NotEqual(0, idVendor2, "Expected a valid record identifier but was 0")
|
||||
|
||||
saRead, err := suite.dao.Get(suite.ctx, "test_vendor200", "test_repo200", "test_digest200")
|
||||
saRead2, err := suite.dao.Get(suite.ctx, "test_vendor300", "test_repo300", "test_digest300")
|
||||
|
||||
suite.NoErrorf(err, "Unexpected error when reading system artifact: %v", err)
|
||||
suite.Equalf(idVendor1, saRead.ID, "The ID for inserted system record %d is not equal to the read system record %d", idVendor1, saRead.ID)
|
||||
suite.Equalf(idVendor2, saRead2.ID, "The ID for inserted system record %d is not equal to the read system record %d", idVendor2, saRead2.ID)
|
||||
|
||||
err = suite.dao.Delete(suite.ctx, "test_vendor200", "test_repo200", "test_digest200")
|
||||
|
||||
suite.NoErrorf(err, "Unexpected error when reading system artifact: %v", err)
|
||||
saRead, err = suite.dao.Get(suite.ctx, "test_vendor200", "test_repo200", "test_digest200")
|
||||
suite.Errorf(err, "Expected no record found error for provided repository and digest")
|
||||
suite.Nil(saRead, "Expected system artifact record to be nil")
|
||||
errWithInfo := err.(*errors.Error)
|
||||
suite.Equalf(errors.NotFoundCode, errWithInfo.Code, "Expected not found code but was %s", errWithInfo.Code)
|
||||
|
||||
saRead3, err := suite.dao.Get(suite.ctx, "test_vendor300", "test_repo300", "test_digest300")
|
||||
suite.Equalf(idVendor2, saRead2.ID, "The ID for inserted system record %d is not equal to the read system record %d", idVendor2, saRead3.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *daoTestSuite) TestList() {
|
||||
expectedSystemArtifactIds := make(map[int64]bool)
|
||||
|
||||
// insert a normal system artifact
|
||||
{
|
||||
sa := model.SystemArtifact{
|
||||
Repository: "test_repo4",
|
||||
Digest: "test_digest4",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
id, err := suite.dao.Create(suite.ctx, &sa)
|
||||
suite.NoError(err, "Unexpected error when inserting test record")
|
||||
suite.NotEqual(0, id, "Expected a valid record identifier but was 0")
|
||||
expectedSystemArtifactIds[id] = true
|
||||
}
|
||||
|
||||
// attempt to read all the system artifact records
|
||||
{
|
||||
query := q.Query{}
|
||||
query.Keywords = map[string]interface{}{"repository": "test_repo4", "digest": "test_digest4"}
|
||||
|
||||
sysArtifacts, err := suite.dao.List(suite.ctx, &query)
|
||||
suite.NotNilf(sysArtifacts, "Expected system artifacts list to be non-nil")
|
||||
suite.NoErrorf(err, "Unexpected error when listing system artifact records : %v", err)
|
||||
suite.Equalf(1, len(sysArtifacts), "Expected system artifacts list of size 1 but was: %d", len(sysArtifacts))
|
||||
|
||||
// iterate through the system artifact and validate that the ids are in the expected list of ids
|
||||
for _, sysArtifact := range sysArtifacts {
|
||||
_, ok := expectedSystemArtifactIds[sysArtifact.ID]
|
||||
suite.Truef(ok, "Expected system artifact id %d to be present but was absent", sysArtifact.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *daoTestSuite) TestSize() {
|
||||
// insert a normal system artifact
|
||||
{
|
||||
sa := model.SystemArtifact{
|
||||
Repository: "test_repo8",
|
||||
Digest: "test_digest8",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
id, err := suite.dao.Create(suite.ctx, &sa)
|
||||
suite.NoError(err, "Unexpected error when inserting test record")
|
||||
suite.NotEqual(0, id, "Expected a valid record identifier but was 0")
|
||||
|
||||
sa1 := model.SystemArtifact{
|
||||
Repository: "test_repo9",
|
||||
Digest: "test_digest9",
|
||||
Size: int64(500),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_repo_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
id, err = suite.dao.Create(suite.ctx, &sa1)
|
||||
suite.NoError(err, "Unexpected error when inserting test record")
|
||||
suite.NotEqual(0, id, "Expected a valid record identifier but was 0")
|
||||
|
||||
size, err := suite.dao.Size(suite.ctx)
|
||||
suite.NoError(err, "Unexpected error when calculating record size")
|
||||
suite.Truef(size > int64(0), "Expected size to be non-zero")
|
||||
}
|
||||
}
|
||||
func TestDaoTestSuite(t *testing.T) {
|
||||
suite.Run(t, &daoTestSuite{})
|
||||
}
|
253
src/pkg/systemartifact/manager.go
Normal file
253
src/pkg/systemartifact/manager.go
Normal file
@ -0,0 +1,253 @@
|
||||
package systemartifact
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/jobservice/logger"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/registry"
|
||||
"github.com/goharbor/harbor/src/pkg/systemartifact/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/systemartifact/model"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
Mgr = NewManager()
|
||||
keyFormat = "%s:%s"
|
||||
)
|
||||
|
||||
const repositoryFormat = "sys_harbor/%s/%s"
|
||||
|
||||
// Manager provides a low-level interface for harbor services
|
||||
// to create registry artifacts containing arbitrary data but which
|
||||
// are not standard OCI artifacts.
|
||||
// By using this framework, harbor components can create artifacts for
|
||||
// cross component data sharing. The framework abstracts out the book-keeping
|
||||
// logic involved in managing and tracking system artifacts.
|
||||
// The Manager ultimately relies on the harbor registry client to perform
|
||||
// the BLOB related operations into the registry.
|
||||
type Manager interface {
|
||||
|
||||
// Create a system artifact described by artifact record.
|
||||
// The reader would be used to read from the underlying data artifact.
|
||||
// Returns a system artifact tracking record id or any errors encountered in the data artifact upload process.
|
||||
// Invoking this API would result in a repository being created with the specified name and digest within the registry.
|
||||
Create(ctx context.Context, artifactRecord *model.SystemArtifact, reader io.Reader) (int64, error)
|
||||
|
||||
// Read a system artifact described by repository name and digest.
|
||||
// The reader is responsible for closing the IO stream after the read completes.
|
||||
Read(ctx context.Context, vendor string, repository string, digest string) (io.ReadCloser, error)
|
||||
|
||||
// Delete deletes a system artifact identified by a repository name and digest.
|
||||
// Also deletes the tracking record from the underlying table.
|
||||
Delete(ctx context.Context, vendor string, repository string, digest string) error
|
||||
|
||||
// Exists checks for the existence of a system artifact identified by repository and digest.
|
||||
// A system artifact is considered as in existence if both the following conditions are true:
|
||||
// 1. There is a system artifact tracking record within the Harbor DB
|
||||
// 2. There is a BLOB corresponding to the repository name and digest obtained from system artifact record.
|
||||
Exists(ctx context.Context, vendor string, repository string, digest string) (bool, error)
|
||||
|
||||
// GetStorageSize returns the total disk space used by the system artifacts stored in the registry.
|
||||
GetStorageSize(ctx context.Context) (int64, error)
|
||||
|
||||
// RegisterCleanupCriteria a clean-up criteria for a specific vendor and artifact type combination.
|
||||
RegisterCleanupCriteria(vendor string, artifactType string, criteria Selector)
|
||||
|
||||
// GetCleanupCriteria returns a clean-up criteria for a specific vendor and artifact type combination.
|
||||
// if no clean-up criteria is found then the default clean-up criteria is returned
|
||||
GetCleanupCriteria(vendor string, artifactType string) Selector
|
||||
|
||||
// Cleanup cleans up the system artifacts (tracking records as well as blobs) based on the
|
||||
// artifact records selected by the Selector registered for each vendor type.
|
||||
// Returns the total number of records deleted, the reclaimed size and any error (if encountered)
|
||||
Cleanup(ctx context.Context) (int64, int64, error)
|
||||
}
|
||||
|
||||
type systemArtifactManager struct {
|
||||
regCli registry.Client
|
||||
dao dao.DAO
|
||||
defaultCleanupCriterion Selector
|
||||
cleanupCriteria map[string]Selector
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewManager() Manager {
|
||||
sysArtifactMgr := &systemArtifactManager{
|
||||
regCli: registry.Cli,
|
||||
dao: dao.NewSystemArtifactDao(),
|
||||
defaultCleanupCriterion: DefaultSelector,
|
||||
cleanupCriteria: make(map[string]Selector),
|
||||
}
|
||||
return sysArtifactMgr
|
||||
}
|
||||
|
||||
func (mgr *systemArtifactManager) Create(ctx context.Context, artifactRecord *model.SystemArtifact, reader io.Reader) (int64, error) {
|
||||
|
||||
var artifactId int64
|
||||
|
||||
// the entire create operation is executed within a transaction to ensure that any failures
|
||||
// during the blob creation or tracking record creation result in a rollback of the transaction
|
||||
createError := orm.WithTransaction(func(ctx context.Context) error {
|
||||
id, err := mgr.dao.Create(ctx, artifactRecord)
|
||||
if err != nil {
|
||||
log.Errorf("Error creating system artifact record for %s/%s/%s: %v", artifactRecord.Vendor, artifactRecord.Repository, artifactRecord.Digest, err)
|
||||
return err
|
||||
}
|
||||
repoName := mgr.getRepositoryName(artifactRecord.Vendor, artifactRecord.Repository)
|
||||
err = mgr.regCli.PushBlob(repoName, artifactRecord.Digest, artifactRecord.Size, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
artifactId = id
|
||||
return nil
|
||||
})(ctx)
|
||||
|
||||
return artifactId, createError
|
||||
}
|
||||
|
||||
func (mgr *systemArtifactManager) Read(ctx context.Context, vendor string, repository string, digest string) (io.ReadCloser, error) {
|
||||
sa, err := mgr.dao.Get(ctx, vendor, repository, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoName := mgr.getRepositoryName(vendor, repository)
|
||||
_, readCloser, err := mgr.regCli.PullBlob(repoName, sa.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return readCloser, nil
|
||||
}
|
||||
|
||||
func (mgr *systemArtifactManager) Delete(ctx context.Context, vendor string, repository string, digest string) error {
|
||||
|
||||
repoName := mgr.getRepositoryName(vendor, repository)
|
||||
if err := mgr.regCli.DeleteBlob(repoName, digest); err != nil {
|
||||
log.Errorf("Error deleting system artifact BLOB : %s. Error: %v", repoName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return mgr.dao.Delete(ctx, vendor, repository, digest)
|
||||
}
|
||||
|
||||
func (mgr *systemArtifactManager) Exists(ctx context.Context, vendor string, repository string, digest string) (bool, error) {
|
||||
_, err := mgr.dao.Get(ctx, vendor, repository, digest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
repoName := mgr.getRepositoryName(vendor, repository)
|
||||
exist, err := mgr.regCli.BlobExist(repoName, digest)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return exist, nil
|
||||
}
|
||||
|
||||
func (mgr *systemArtifactManager) GetStorageSize(ctx context.Context) (int64, error) {
|
||||
return mgr.dao.Size(ctx)
|
||||
}
|
||||
|
||||
func (mgr *systemArtifactManager) RegisterCleanupCriteria(vendor string, artifactType string, criteria Selector) {
|
||||
key := fmt.Sprintf(keyFormat, vendor, artifactType)
|
||||
defer mgr.lock.Unlock()
|
||||
mgr.lock.Lock()
|
||||
mgr.cleanupCriteria[key] = criteria
|
||||
}
|
||||
|
||||
func (mgr *systemArtifactManager) GetCleanupCriteria(vendor string, artifactType string) Selector {
|
||||
key := fmt.Sprintf(keyFormat, vendor, artifactType)
|
||||
defer mgr.lock.Unlock()
|
||||
mgr.lock.Lock()
|
||||
if criteria, ok := mgr.cleanupCriteria[key]; ok {
|
||||
return criteria
|
||||
}
|
||||
return DefaultSelector
|
||||
}
|
||||
|
||||
func (mgr *systemArtifactManager) Cleanup(ctx context.Context) (int64, int64, error) {
|
||||
logger.Info("Starting system artifact cleanup")
|
||||
// clean up artifact records having customized cleanup criteria first
|
||||
totalReclaimedSize := int64(0)
|
||||
totalRecordsDeleted := int64(0)
|
||||
|
||||
// get a copy of the registered cleanup criteria and
|
||||
// iterate through this copy to invoke the cleanup
|
||||
registeredCriteria := make(map[string]Selector, 0)
|
||||
mgr.lock.Lock()
|
||||
for key, val := range mgr.cleanupCriteria {
|
||||
registeredCriteria[key] = val
|
||||
}
|
||||
mgr.lock.Unlock()
|
||||
|
||||
for key, val := range registeredCriteria {
|
||||
logger.Infof("Executing cleanup for 'vendor:artifactType' : %s", key)
|
||||
deleted, size, err := mgr.cleanup(ctx, val)
|
||||
totalRecordsDeleted += deleted
|
||||
totalReclaimedSize += size
|
||||
|
||||
if err != nil {
|
||||
// one vendor error should not impact the clean-up of other vendor types. Hence the cleanup logic would continue
|
||||
// after logging the error
|
||||
logger.Errorf("Error when cleaning up system artifacts for 'vendor:artifactType':%s, %v", key, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
logger.Info("Executing cleanup for default cleanup criteria")
|
||||
// clean up artifact records using the default criteria
|
||||
deleted, size, err := mgr.cleanup(ctx, mgr.defaultCleanupCriterion)
|
||||
if err != nil {
|
||||
// one vendor error should not impact the clean-up of other vendor types. Hence the cleanup logic would continue
|
||||
// after logging the error
|
||||
logger.Errorf("Error when cleaning up system artifacts for 'vendor:artifactType':%s, %v", "DefaultCriteria", err)
|
||||
}
|
||||
totalRecordsDeleted += deleted
|
||||
totalReclaimedSize += size
|
||||
|
||||
return totalRecordsDeleted, totalReclaimedSize, nil
|
||||
}
|
||||
|
||||
func (mgr *systemArtifactManager) cleanup(ctx context.Context, criteria Selector) (int64, int64, error) {
|
||||
// clean up artifact records having customized cleanup criteria first
|
||||
totalReclaimedSize := int64(0)
|
||||
totalRecordsDeleted := int64(0)
|
||||
|
||||
isDefaultSelector := criteria == mgr.defaultCleanupCriterion
|
||||
|
||||
records, err := criteria.List(ctx)
|
||||
|
||||
if err != nil {
|
||||
|
||||
return totalRecordsDeleted, totalReclaimedSize, err
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
// skip vendor artifact types with custom clean-up criteria registered
|
||||
if isDefaultSelector && mgr.isSelectorRegistered(record.Vendor, record.Type) {
|
||||
continue
|
||||
}
|
||||
err = mgr.Delete(ctx, record.Vendor, record.Repository, record.Digest)
|
||||
if err != nil {
|
||||
logger.Errorf("Error cleaning up artifact record for vendor: %s, repository: %s, digest: %s", record.Vendor, record.Repository, record.Digest)
|
||||
return totalRecordsDeleted, totalReclaimedSize, err
|
||||
}
|
||||
totalReclaimedSize += record.Size
|
||||
totalRecordsDeleted += 1
|
||||
}
|
||||
return totalRecordsDeleted, totalReclaimedSize, nil
|
||||
}
|
||||
|
||||
func (mgr *systemArtifactManager) getRepositoryName(vendor string, repository string) string {
|
||||
return fmt.Sprintf(repositoryFormat, vendor, repository)
|
||||
}
|
||||
|
||||
func (mgr *systemArtifactManager) isSelectorRegistered(vendor, artifactType string) bool {
|
||||
key := fmt.Sprintf(keyFormat, vendor, artifactType)
|
||||
_, ok := mgr.cleanupCriteria[key]
|
||||
return ok
|
||||
}
|
492
src/pkg/systemartifact/manager_test.go
Normal file
492
src/pkg/systemartifact/manager_test.go
Normal file
@ -0,0 +1,492 @@
|
||||
package systemartifact
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/systemartifact/model"
|
||||
ormtesting "github.com/goharbor/harbor/src/testing/lib/orm"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
registrytesting "github.com/goharbor/harbor/src/testing/pkg/registry"
|
||||
"github.com/goharbor/harbor/src/testing/pkg/systemartifact/cleanup"
|
||||
sysartifactdaotesting "github.com/goharbor/harbor/src/testing/pkg/systemartifact/dao"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ManagerTestSuite struct {
|
||||
suite.Suite
|
||||
regCli *registrytesting.FakeClient
|
||||
dao *sysartifactdaotesting.DAO
|
||||
mgr *systemArtifactManager
|
||||
cleanupCriteria *cleanup.Selector
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) SetupSuite() {
|
||||
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) SetupTest() {
|
||||
suite.regCli = ®istrytesting.FakeClient{}
|
||||
suite.dao = &sysartifactdaotesting.DAO{}
|
||||
suite.cleanupCriteria = &cleanup.Selector{}
|
||||
suite.mgr = &systemArtifactManager{
|
||||
regCli: suite.regCli,
|
||||
dao: suite.dao,
|
||||
defaultCleanupCriterion: suite.cleanupCriteria,
|
||||
cleanupCriteria: make(map[string]Selector),
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestCreate() {
|
||||
sa := model.SystemArtifact{
|
||||
Repository: "test_repo",
|
||||
Digest: "test_digest",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
suite.dao.On("Create", mock.Anything, &sa, mock.Anything).Return(int64(1), nil).Once()
|
||||
suite.regCli.On("PushBlob", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
reader := strings.NewReader("test data string")
|
||||
id, err := suite.mgr.Create(orm.NewContext(nil, &ormtesting.FakeOrmer{}), &sa, reader)
|
||||
suite.Equalf(int64(1), id, "Expected row to correctly inserted")
|
||||
suite.NoErrorf(err, "Unexpected error when creating artifact: %v", err)
|
||||
suite.regCli.AssertCalled(suite.T(), "PushBlob")
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestCreatePushBlobFails() {
|
||||
sa := model.SystemArtifact{
|
||||
Repository: "test_repo",
|
||||
Digest: "test_digest",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
suite.dao.On("Create", mock.Anything, &sa, mock.Anything).Return(int64(1), nil).Once()
|
||||
suite.dao.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
suite.regCli.On("PushBlob", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(errors.New("error")).Once()
|
||||
reader := strings.NewReader("test data string")
|
||||
id, err := suite.mgr.Create(orm.NewContext(nil, &ormtesting.FakeOrmer{}), &sa, reader)
|
||||
suite.Equalf(int64(0), id, "Expected no row to be inserted")
|
||||
suite.Errorf(err, "Expected error when creating artifact: %v", err)
|
||||
suite.dao.AssertCalled(suite.T(), "Create", mock.Anything, &sa, mock.Anything)
|
||||
suite.regCli.AssertCalled(suite.T(), "PushBlob")
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestCreateArtifactRecordFailure() {
|
||||
sa := model.SystemArtifact{
|
||||
Repository: "test_repo",
|
||||
Digest: "test_digest",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
suite.dao.On("Create", mock.Anything, &sa, mock.Anything).Return(int64(0), errors.New("error")).Once()
|
||||
suite.regCli.On("PushBlob", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
suite.regCli.On("PushBlob", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
suite.regCli.On("DeleteBlob", mock.Anything, mock.Anything).Return(nil).Once()
|
||||
|
||||
reader := strings.NewReader("test data string")
|
||||
id, err := suite.mgr.Create(orm.NewContext(nil, &ormtesting.FakeOrmer{}), &sa, reader)
|
||||
suite.Equalf(int64(0), id, "Expected no row to be inserted")
|
||||
suite.Errorf(err, "Expected error when creating artifact: %v", err)
|
||||
suite.dao.AssertCalled(suite.T(), "Create", mock.Anything, mock.Anything)
|
||||
suite.regCli.AssertNotCalled(suite.T(), "PushBlob")
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestRead() {
|
||||
sa := model.SystemArtifact{
|
||||
Repository: "test_repo",
|
||||
Digest: "test_digest",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
dummyRepoFilepath := fmt.Sprintf("/tmp/sys_art_test.dmp_%v", time.Now())
|
||||
data := []byte("test data")
|
||||
err := ioutil.WriteFile(dummyRepoFilepath, data, os.ModePerm)
|
||||
suite.NoErrorf(err, "Unexpected error when creating test repo file: %v", dummyRepoFilepath)
|
||||
|
||||
repoHandle, err := os.Open(dummyRepoFilepath)
|
||||
suite.NoErrorf(err, "Unexpected error when reading test repo file: %v", dummyRepoFilepath)
|
||||
defer repoHandle.Close()
|
||||
|
||||
suite.dao.On("Get", mock.Anything, "test_vendor", "test_repo", "test_digest").Return(&sa, nil).Once()
|
||||
suite.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(len(data), repoHandle, nil).Once()
|
||||
|
||||
readCloser, err := suite.mgr.Read(context.TODO(), "test_vendor", "test_repo", "test_digest")
|
||||
|
||||
suite.NoErrorf(err, "Unexpected error when reading artifact: %v", err)
|
||||
suite.dao.AssertCalled(suite.T(), "Get", mock.Anything, "test_vendor", "test_repo", "test_digest")
|
||||
suite.regCli.AssertCalled(suite.T(), "PullBlob")
|
||||
suite.NotNilf(readCloser, "Expected valid read closer instance but was nil")
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestReadSystemArtifactRecordNotFound() {
|
||||
|
||||
dummyRepoFilepath := fmt.Sprintf("/tmp/sys_art_test.dmp_%v", time.Now())
|
||||
data := []byte("test data")
|
||||
err := ioutil.WriteFile(dummyRepoFilepath, data, os.ModePerm)
|
||||
suite.NoErrorf(err, "Unexpected error when creating test repo file: %v", dummyRepoFilepath)
|
||||
|
||||
repoHandle, err := os.Open(dummyRepoFilepath)
|
||||
suite.NoErrorf(err, "Unexpected error when reading test repo file: %v", dummyRepoFilepath)
|
||||
defer repoHandle.Close()
|
||||
|
||||
errToRet := orm.ErrNoRows
|
||||
|
||||
suite.dao.On("Get", mock.Anything, "test_vendor", "test_repo", "test_digest").Return(nil, errToRet).Once()
|
||||
suite.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(len(data), repoHandle, nil).Once()
|
||||
|
||||
readCloser, err := suite.mgr.Read(context.TODO(), "test_vendor", "test_repo", "test_digest")
|
||||
|
||||
suite.Errorf(err, "Expected error when reading artifact: %v", errToRet)
|
||||
suite.dao.AssertCalled(suite.T(), "Get", mock.Anything, "test_vendor", "test_repo", "test_digest")
|
||||
suite.regCli.AssertNotCalled(suite.T(), "PullBlob")
|
||||
suite.Nilf(readCloser, "Expected null read closer instance but was valid")
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestDelete() {
|
||||
|
||||
suite.dao.On("Delete", mock.Anything, "test_vendor", "test_repo", "test_digest").Return(nil).Once()
|
||||
suite.regCli.On("DeleteBlob", mock.Anything, mock.Anything).Return(nil).Once()
|
||||
|
||||
err := suite.mgr.Delete(context.TODO(), "test_vendor", "test_repo", "test_digest")
|
||||
|
||||
suite.NoErrorf(err, "Unexpected error when deleting artifact: %v", err)
|
||||
suite.dao.AssertCalled(suite.T(), "Delete", mock.Anything, "test_vendor", "test_repo", "test_digest")
|
||||
suite.regCli.AssertCalled(suite.T(), "DeleteBlob")
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestDeleteSystemArtifactDeleteError() {
|
||||
|
||||
errToRet := orm.ErrNoRows
|
||||
suite.dao.On("Delete", mock.Anything, "test_vendor", "test_repo", "test_digest").Return(errToRet).Once()
|
||||
suite.regCli.On("DeleteBlob", mock.Anything, mock.Anything).Return(nil).Once()
|
||||
|
||||
err := suite.mgr.Delete(context.TODO(), "test_vendor", "test_repo", "test_digest")
|
||||
|
||||
suite.Errorf(err, "Expected error when deleting artifact: %v", err)
|
||||
suite.dao.AssertCalled(suite.T(), "Delete", mock.Anything, "test_vendor", "test_repo", "test_digest")
|
||||
suite.regCli.AssertCalled(suite.T(), "DeleteBlob")
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestDeleteSystemArtifactBlobDeleteError() {
|
||||
|
||||
errToRet := orm.ErrNoRows
|
||||
suite.dao.On("Delete", mock.Anything, "test_vendor", "test_repo", "test_digest").Return(nil).Once()
|
||||
suite.regCli.On("DeleteBlob", mock.Anything, mock.Anything).Return(errToRet).Once()
|
||||
|
||||
err := suite.mgr.Delete(context.TODO(), "test_vendor", "test_repo", "test_digest")
|
||||
|
||||
suite.Errorf(err, "Expected error when deleting artifact: %v", err)
|
||||
suite.dao.AssertNotCalled(suite.T(), "Delete", mock.Anything, "test_vendor", "test_repo", "test_digest")
|
||||
suite.regCli.AssertCalled(suite.T(), "DeleteBlob")
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestExist() {
|
||||
sa := model.SystemArtifact{
|
||||
Repository: "test_repo",
|
||||
Digest: "test_digest",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
suite.dao.On("Get", mock.Anything, "test_vendor", "test_repo", "test_digest").Return(&sa, nil).Once()
|
||||
suite.regCli.On("BlobExist", mock.Anything, mock.Anything).Return(true, nil).Once()
|
||||
|
||||
exists, err := suite.mgr.Exists(context.TODO(), "test_vendor", "test_repo", "test_digest")
|
||||
|
||||
suite.NoErrorf(err, "Unexpected error when checking if artifact exists: %v", err)
|
||||
suite.dao.AssertCalled(suite.T(), "Get", mock.Anything, "test_vendor", "test_repo", "test_digest")
|
||||
suite.regCli.AssertCalled(suite.T(), "BlobExist")
|
||||
suite.True(exists, "Expected exists to be true but was false")
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestExistSystemArtifactRecordReadError() {
|
||||
|
||||
errToReturn := orm.ErrNoRows
|
||||
|
||||
suite.dao.On("Get", mock.Anything, "test_vendor", "test_repo", "test_digest").Return(nil, errToReturn).Once()
|
||||
suite.regCli.On("BlobExist", mock.Anything, mock.Anything).Return(true, nil).Once()
|
||||
|
||||
exists, err := suite.mgr.Exists(context.TODO(), "test_vendor", "test_repo", "test_digest")
|
||||
|
||||
suite.Error(err, "Expected error when checking if artifact exists")
|
||||
suite.dao.AssertCalled(suite.T(), "Get", mock.Anything, "test_vendor", "test_repo", "test_digest")
|
||||
suite.regCli.AssertNotCalled(suite.T(), "BlobExist")
|
||||
suite.False(exists, "Expected exists to be false but was true")
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestExistSystemArtifactBlobReadError() {
|
||||
|
||||
sa := model.SystemArtifact{
|
||||
Repository: "test_repo",
|
||||
Digest: "test_digest",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor",
|
||||
Type: "test_type",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
suite.dao.On("Get", mock.Anything, "test_vendor", "test_repo", "test_digest").Return(&sa, nil).Once()
|
||||
suite.regCli.On("BlobExist", mock.Anything, mock.Anything).Return(false, errors.New("test error")).Once()
|
||||
|
||||
exists, err := suite.mgr.Exists(context.TODO(), "test_vendor", "test_repo", "test_digest")
|
||||
|
||||
suite.Error(err, "Expected error when checking if artifact exists")
|
||||
suite.dao.AssertCalled(suite.T(), "Get", mock.Anything, "test_vendor", "test_repo", "test_digest")
|
||||
suite.regCli.AssertCalled(suite.T(), "BlobExist")
|
||||
suite.False(exists, "Expected exists to be false but was true")
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestGetStorageSize() {
|
||||
|
||||
suite.dao.On("Size", mock.Anything).Return(int64(400), nil).Once()
|
||||
|
||||
size, err := suite.mgr.GetStorageSize(context.TODO())
|
||||
|
||||
suite.NoErrorf(err, "Unexpected error encountered: %v", err)
|
||||
suite.dao.AssertCalled(suite.T(), "Size", mock.Anything)
|
||||
suite.Equalf(int64(400), size, "Expected size to be 400 but was : %v", size)
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestGetStorageSizeError() {
|
||||
|
||||
suite.dao.On("Size", mock.Anything).Return(int64(0), errors.New("test error")).Once()
|
||||
|
||||
size, err := suite.mgr.GetStorageSize(context.TODO())
|
||||
|
||||
suite.Errorf(err, "Expected error encountered: %v", err)
|
||||
suite.dao.AssertCalled(suite.T(), "Size", mock.Anything)
|
||||
suite.Equalf(int64(0), size, "Expected size to be 0 but was : %v", size)
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestCleanupCriteriaRegistration() {
|
||||
vendor := "test_vendor"
|
||||
artifactType := "test_artifact_type"
|
||||
suite.mgr.RegisterCleanupCriteria(vendor, artifactType, suite)
|
||||
|
||||
criteria := suite.mgr.GetCleanupCriteria(vendor, artifactType)
|
||||
suite.Equalf(suite, criteria, "Expected cleanup criteria to be the same as suite")
|
||||
|
||||
criteria = suite.mgr.GetCleanupCriteria("test_vendor1", "test_artifact1")
|
||||
suite.Equalf(DefaultSelector, criteria, "Expected cleanup criteria to be the same as default cleanup criteria")
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestCleanup() {
|
||||
sa1 := model.SystemArtifact{
|
||||
Repository: "test_repo1",
|
||||
Digest: "test_digest1",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor1",
|
||||
Type: "test_type1",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
sa2 := model.SystemArtifact{
|
||||
Repository: "test_repo2",
|
||||
Digest: "test_digest2",
|
||||
Size: int64(300),
|
||||
Vendor: "test_vendor2",
|
||||
Type: "test_type2",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
sa3 := model.SystemArtifact{
|
||||
Repository: "test_repo3",
|
||||
Digest: "test_digest3",
|
||||
Size: int64(300),
|
||||
Vendor: "test_vendor3",
|
||||
Type: "test_type3",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
mockCleaupCriteria1 := cleanup.Selector{}
|
||||
mockCleaupCriteria1.On("List", mock.Anything).Return([]*model.SystemArtifact{&sa1}, nil).Once()
|
||||
|
||||
mockCleaupCriteria2 := cleanup.Selector{}
|
||||
mockCleaupCriteria2.On("List", mock.Anything).Return([]*model.SystemArtifact{&sa2}, nil).Once()
|
||||
|
||||
suite.cleanupCriteria.On("List", mock.Anything).Return([]*model.SystemArtifact{&sa3}, nil).Once()
|
||||
|
||||
suite.mgr.RegisterCleanupCriteria("test_vendor1", "test_type1", &mockCleaupCriteria1)
|
||||
suite.mgr.RegisterCleanupCriteria("test_vendor2", "test_type2", &mockCleaupCriteria2)
|
||||
|
||||
suite.dao.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Times(3)
|
||||
suite.regCli.On("DeleteBlob", mock.Anything, mock.Anything).Return(nil).Times(3)
|
||||
|
||||
totalDeleted, totalSizeReclaimed, err := suite.mgr.Cleanup(context.TODO())
|
||||
suite.Equalf(int64(3), totalDeleted, "System artifacts delete; Expected:%d, Actual:%d", int64(3), totalDeleted)
|
||||
suite.Equalf(int64(700), totalSizeReclaimed, "System artifacts delete; Expected:%d, Actual:%d", int64(700), totalDeleted)
|
||||
suite.NoErrorf(err, "Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestCleanupError() {
|
||||
sa1 := model.SystemArtifact{
|
||||
Repository: "test_repo13000",
|
||||
Digest: "test_digest13000",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor13000",
|
||||
Type: "test_type13000",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
sa3 := model.SystemArtifact{
|
||||
Repository: "test_repo33000",
|
||||
Digest: "test_digest33000",
|
||||
Size: int64(300),
|
||||
Vendor: "test_vendor33000",
|
||||
Type: "test_type33000",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
mockCleaupCriteria1 := cleanup.Selector{}
|
||||
mockCleaupCriteria1.On("List", mock.Anything).Return([]*model.SystemArtifact{&sa1}, nil).Once()
|
||||
|
||||
mockCleaupCriteria2 := cleanup.Selector{}
|
||||
mockCleaupCriteria2.On("List", mock.Anything).Return(nil, errors.New("test error")).Once()
|
||||
|
||||
suite.cleanupCriteria.On("List", mock.Anything).Return([]*model.SystemArtifact{&sa3}, nil)
|
||||
|
||||
suite.mgr.RegisterCleanupCriteria("test_vendor13000", "test_type13000", &mockCleaupCriteria1)
|
||||
suite.mgr.RegisterCleanupCriteria("test_vendor23000", "test_type23000", &mockCleaupCriteria2)
|
||||
|
||||
suite.dao.On("Delete", mock.Anything, "test_vendor13000", "test_repo13000", "test_digest13000").Return(nil)
|
||||
suite.dao.On("Delete", mock.Anything, "test_vendor33000", "test_repo33000", "test_digest33000").Return(nil)
|
||||
suite.dao.On("Delete", mock.Anything, "test_vendor23000", "test_repo23000", mock.Anything).Return(nil)
|
||||
suite.regCli.On("DeleteBlob", mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
totalDeleted, totalSizeReclaimed, err := suite.mgr.Cleanup(context.TODO())
|
||||
suite.Equalf(int64(2), totalDeleted, "System artifacts delete; Expected:%d, Actual:%d", int64(2), totalDeleted)
|
||||
suite.Equalf(int64(400), totalSizeReclaimed, "System artifacts delete; Expected:%d, Actual:%d", int64(400), totalDeleted)
|
||||
suite.NoError(err, "Expected no error but was %v", err)
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestCleanupErrorDefaultCriteria() {
|
||||
sa1 := model.SystemArtifact{
|
||||
Repository: "test_repo1",
|
||||
Digest: "test_digest1",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor1",
|
||||
Type: "test_type1",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
sa2 := model.SystemArtifact{
|
||||
Repository: "test_repo2",
|
||||
Digest: "test_digest2",
|
||||
Size: int64(300),
|
||||
Vendor: "test_vendor2",
|
||||
Type: "test_type2",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
mockCleaupCriteria1 := cleanup.Selector{}
|
||||
mockCleaupCriteria1.On("List", mock.Anything).Return([]*model.SystemArtifact{&sa1}, nil).Once()
|
||||
|
||||
mockCleaupCriteria2 := cleanup.Selector{}
|
||||
mockCleaupCriteria2.On("List", mock.Anything).Return([]*model.SystemArtifact{&sa2}, nil).Once()
|
||||
|
||||
suite.cleanupCriteria.On("List", mock.Anything).Return(nil, errors.New("test error"))
|
||||
|
||||
suite.mgr.RegisterCleanupCriteria("test_vendor1", "test_type1", &mockCleaupCriteria1)
|
||||
suite.mgr.RegisterCleanupCriteria("test_vendor2", "test_type2", &mockCleaupCriteria2)
|
||||
|
||||
suite.dao.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
suite.regCli.On("DeleteBlob", mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
totalDeleted, totalSizeReclaimed, err := suite.mgr.Cleanup(context.TODO())
|
||||
suite.Equalf(int64(2), totalDeleted, "System artifacts delete; Expected:%d, Actual:%d", int64(2), totalDeleted)
|
||||
suite.Equalf(int64(400), totalSizeReclaimed, "System artifacts delete; Expected:%d, Actual:%d", int64(400), totalDeleted)
|
||||
suite.NoErrorf(err, "Expected no error but was %v", err)
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) TestCleanupErrorForVendor() {
|
||||
sa1 := model.SystemArtifact{
|
||||
Repository: "test_repo10000",
|
||||
Digest: "test_digest10000",
|
||||
Size: int64(100),
|
||||
Vendor: "test_vendor10000",
|
||||
Type: "test_type10000",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
sa2 := model.SystemArtifact{
|
||||
Repository: "test_repo20000",
|
||||
Digest: "test_digest20000",
|
||||
Size: int64(300),
|
||||
Vendor: "test_vendor10000",
|
||||
Type: "test_type10000",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
sa3 := model.SystemArtifact{
|
||||
Repository: "test_repo30000",
|
||||
Digest: "test_digest30000",
|
||||
Size: int64(300),
|
||||
Vendor: "test_vendor30000",
|
||||
Type: "test_type30000",
|
||||
CreateTime: time.Now(),
|
||||
ExtraAttrs: "",
|
||||
}
|
||||
|
||||
mockCleaupCriteria1 := cleanup.Selector{}
|
||||
mockCleaupCriteria1.On("List", mock.Anything).Return([]*model.SystemArtifact{&sa1, &sa2}, nil).Times(2)
|
||||
|
||||
suite.cleanupCriteria.On("List", mock.Anything).Return([]*model.SystemArtifact{&sa3}, nil).Times(2)
|
||||
|
||||
suite.mgr.RegisterCleanupCriteria("test_vendor10000", "test_type10000", &mockCleaupCriteria1)
|
||||
|
||||
suite.dao.On("Delete", mock.Anything, "test_vendor10000", "test_repo10000", "test_digest10000").Return(nil).Once()
|
||||
suite.dao.On("Delete", mock.Anything, "test_vendor10000", "test_repo20000", "test_digest20000").Return(errors.New("test error")).Once()
|
||||
suite.dao.On("Delete", mock.Anything, "test_vendor30000", "test_repo30000", "test_digest30000").Return(nil).Once()
|
||||
suite.regCli.On("DeleteBlob", mock.Anything, mock.Anything).Return(nil).Times(3)
|
||||
|
||||
totalDeleted, totalSizeReclaimed, err := suite.mgr.Cleanup(context.TODO())
|
||||
suite.Equalf(int64(2), totalDeleted, "System artifacts delete; Expected:%d, Actual:%d", int64(2), totalDeleted)
|
||||
suite.Equalf(int64(400), totalSizeReclaimed, "System artifacts delete; Expected:%d, Actual:%d", int64(400), totalDeleted)
|
||||
suite.NoErrorf(err, "Expected no error, but was %v", err)
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) List(ctx context.Context) ([]*model.SystemArtifact, error) {
|
||||
return make([]*model.SystemArtifact, 0), nil
|
||||
}
|
||||
|
||||
func (suite *ManagerTestSuite) ListWithFilters(ctx context.Context, query *q.Query) ([]*model.SystemArtifact, error) {
|
||||
return make([]*model.SystemArtifact, 0), nil
|
||||
}
|
||||
|
||||
func TestManagerTestSuite(t *testing.T) {
|
||||
mgr := &ManagerTestSuite{}
|
||||
suite.Run(t, mgr)
|
||||
}
|
43
src/pkg/systemartifact/model/model.go
Normal file
43
src/pkg/systemartifact/model/model.go
Normal file
@ -0,0 +1,43 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
orm.RegisterModel(
|
||||
new(SystemArtifact),
|
||||
)
|
||||
}
|
||||
|
||||
// SystemArtifact represents a tracking record for each system artifact that has been
|
||||
// created within registry using the system artifact manager
|
||||
type SystemArtifact struct {
|
||||
ID int64 `orm:"pk;auto;column(id)"`
|
||||
// the name of repository associated with the artifact
|
||||
Repository string `orm:"column(repository)"`
|
||||
// the SHA-256 digest of the artifact data.
|
||||
Digest string `orm:"column(digest)"`
|
||||
// the size of the artifact data in bytes
|
||||
Size int64 `orm:"column(size)"`
|
||||
// the harbor subsystem that created the artifact
|
||||
Vendor string `orm:"column(vendor)"`
|
||||
// the type of the system artifact.
|
||||
// the type field specifies the type of artifact data and is useful when a harbor component generates more than one
|
||||
// kind of artifact. for e.g. a scan data export job could create a detailed CSV export data file as well
|
||||
// as an summary export file. here type could be set to "CSVDetail" and "ScanSummary"
|
||||
Type string `orm:"column(type)"`
|
||||
// the time of creation of the system artifact
|
||||
CreateTime time.Time `orm:"column(create_time)"`
|
||||
// optional extra attributes for the system artifact
|
||||
ExtraAttrs string `orm:"column(extra_attrs)"`
|
||||
}
|
||||
|
||||
func (sa *SystemArtifact) TableName() string {
|
||||
return "system_artifact"
|
||||
}
|
||||
|
||||
func (sa *SystemArtifact) TableUnique() [][]string {
|
||||
return [][]string{{"vendor", "repository_name", "digest"}}
|
||||
}
|
@ -56,3 +56,6 @@ package pkg
|
||||
//go:generate mockery --case snake --dir ../../pkg/accessory/model --name Accessory --output ./accessory/model --outpkg model
|
||||
//go:generate mockery --case snake --dir ../../pkg/accessory/dao --name DAO --output ./accessory/dao --outpkg dao
|
||||
//go:generate mockery --case snake --dir ../../pkg/accessory --name Manager --output ./accessory --outpkg accessory
|
||||
//go:generate mockery --case snake --dir ../../pkg/systemartifact --name Manager --output ./systemartifact --outpkg systemartifact
|
||||
//go:generate mockery --case snake --dir ../../pkg/systemartifact/ --name Selector --output ./systemartifact/cleanup --outpkg cleanup
|
||||
//go:generate mockery --case snake --dir ../../pkg/systemartifact/dao --name DAO --output ./systemartifact/dao --outpkg dao
|
||||
|
63
src/testing/pkg/systemartifact/cleanup/selector.go
Normal file
63
src/testing/pkg/systemartifact/cleanup/selector.go
Normal file
@ -0,0 +1,63 @@
|
||||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package cleanup
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
model "github.com/goharbor/harbor/src/pkg/systemartifact/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
q "github.com/goharbor/harbor/src/lib/q"
|
||||
)
|
||||
|
||||
// Selector is an autogenerated mock type for the Selector type
|
||||
type Selector struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: ctx
|
||||
func (_m *Selector) List(ctx context.Context) ([]*model.SystemArtifact, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
var r0 []*model.SystemArtifact
|
||||
if rf, ok := ret.Get(0).(func(context.Context) []*model.SystemArtifact); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.SystemArtifact)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ListWithFilters provides a mock function with given fields: ctx, query
|
||||
func (_m *Selector) ListWithFilters(ctx context.Context, query *q.Query) ([]*model.SystemArtifact, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 []*model.SystemArtifact
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*model.SystemArtifact); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.SystemArtifact)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok {
|
||||
r1 = rf(ctx, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
120
src/testing/pkg/systemartifact/dao/dao.go
Normal file
120
src/testing/pkg/systemartifact/dao/dao.go
Normal file
@ -0,0 +1,120 @@
|
||||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
model "github.com/goharbor/harbor/src/pkg/systemartifact/model"
|
||||
|
||||
q "github.com/goharbor/harbor/src/lib/q"
|
||||
)
|
||||
|
||||
// DAO is an autogenerated mock type for the DAO type
|
||||
type DAO struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, systemArtifact
|
||||
func (_m *DAO) Create(ctx context.Context, systemArtifact *model.SystemArtifact) (int64, error) {
|
||||
ret := _m.Called(ctx, systemArtifact)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *model.SystemArtifact) int64); ok {
|
||||
r0 = rf(ctx, systemArtifact)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *model.SystemArtifact) error); ok {
|
||||
r1 = rf(ctx, systemArtifact)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, vendor, repository, digest
|
||||
func (_m *DAO) Delete(ctx context.Context, vendor string, repository string, digest string) error {
|
||||
ret := _m.Called(ctx, vendor, repository, digest)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
|
||||
r0 = rf(ctx, vendor, repository, digest)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, vendor, repository, digest
|
||||
func (_m *DAO) Get(ctx context.Context, vendor string, repository string, digest string) (*model.SystemArtifact, error) {
|
||||
ret := _m.Called(ctx, vendor, repository, digest)
|
||||
|
||||
var r0 *model.SystemArtifact
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) *model.SystemArtifact); ok {
|
||||
r0 = rf(ctx, vendor, repository, digest)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.SystemArtifact)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
|
||||
r1 = rf(ctx, vendor, repository, digest)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: ctx, query
|
||||
func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.SystemArtifact, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 []*model.SystemArtifact
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*model.SystemArtifact); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.SystemArtifact)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok {
|
||||
r1 = rf(ctx, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Size provides a mock function with given fields: ctx
|
||||
func (_m *DAO) Size(ctx context.Context) (int64, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context) int64); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
168
src/testing/pkg/systemartifact/manager.go
Normal file
168
src/testing/pkg/systemartifact/manager.go
Normal file
@ -0,0 +1,168 @@
|
||||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package systemartifact
|
||||
|
||||
import (
|
||||
context "context"
|
||||
io "io"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
model "github.com/goharbor/harbor/src/pkg/systemartifact/model"
|
||||
|
||||
systemartifact "github.com/goharbor/harbor/src/pkg/systemartifact"
|
||||
)
|
||||
|
||||
// Manager is an autogenerated mock type for the Manager type
|
||||
type Manager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Cleanup provides a mock function with given fields: ctx
|
||||
func (_m *Manager) Cleanup(ctx context.Context) (int64, int64, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context) int64); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 int64
|
||||
if rf, ok := ret.Get(1).(func(context.Context) int64); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Get(1).(int64)
|
||||
}
|
||||
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(2).(func(context.Context) error); ok {
|
||||
r2 = rf(ctx)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, artifactRecord, reader
|
||||
func (_m *Manager) Create(ctx context.Context, artifactRecord *model.SystemArtifact, reader io.Reader) (int64, error) {
|
||||
ret := _m.Called(ctx, artifactRecord, reader)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *model.SystemArtifact, io.Reader) int64); ok {
|
||||
r0 = rf(ctx, artifactRecord, reader)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *model.SystemArtifact, io.Reader) error); ok {
|
||||
r1 = rf(ctx, artifactRecord, reader)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, vendor, repository, digest
|
||||
func (_m *Manager) Delete(ctx context.Context, vendor string, repository string, digest string) error {
|
||||
ret := _m.Called(ctx, vendor, repository, digest)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
|
||||
r0 = rf(ctx, vendor, repository, digest)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Exists provides a mock function with given fields: ctx, vendor, repository, digest
|
||||
func (_m *Manager) Exists(ctx context.Context, vendor string, repository string, digest string) (bool, error) {
|
||||
ret := _m.Called(ctx, vendor, repository, digest)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) bool); ok {
|
||||
r0 = rf(ctx, vendor, repository, digest)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
|
||||
r1 = rf(ctx, vendor, repository, digest)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetCleanupCriteria provides a mock function with given fields: vendor, artifactType
|
||||
func (_m *Manager) GetCleanupCriteria(vendor string, artifactType string) systemartifact.Selector {
|
||||
ret := _m.Called(vendor, artifactType)
|
||||
|
||||
var r0 systemartifact.Selector
|
||||
if rf, ok := ret.Get(0).(func(string, string) systemartifact.Selector); ok {
|
||||
r0 = rf(vendor, artifactType)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(systemartifact.Selector)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetStorageSize provides a mock function with given fields: ctx
|
||||
func (_m *Manager) GetStorageSize(ctx context.Context) (int64, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context) int64); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Read provides a mock function with given fields: ctx, vendor, repository, digest
|
||||
func (_m *Manager) Read(ctx context.Context, vendor string, repository string, digest string) (io.ReadCloser, error) {
|
||||
ret := _m.Called(ctx, vendor, repository, digest)
|
||||
|
||||
var r0 io.ReadCloser
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) io.ReadCloser); ok {
|
||||
r0 = rf(ctx, vendor, repository, digest)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(io.ReadCloser)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
|
||||
r1 = rf(ctx, vendor, repository, digest)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RegisterCleanupCriteria provides a mock function with given fields: vendor, artifactType, criteria
|
||||
func (_m *Manager) RegisterCleanupCriteria(vendor string, artifactType string, criteria systemartifact.Selector) {
|
||||
_m.Called(vendor, artifactType, criteria)
|
||||
}
|
Loading…
Reference in New Issue
Block a user