add accessory dao service (#16045)

Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
Wang Yan 2021-12-03 14:34:02 +08:00 committed by GitHub
parent 5fc4449450
commit 742e7ded00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1281 additions and 0 deletions

View File

@ -0,0 +1,22 @@
/* create table of accessory */
CREATE TABLE IF NOT EXISTS artifact_accessory (
id SERIAL PRIMARY KEY NOT NULL,
/*
the artifact id of the accessory itself.
*/
artifact_id bigint,
/*
the subject artifact id of the accessory.
*/
subject_artifact_id bigint,
/*
the type of the accessory, like signature.cosign.
*/
type varchar(256),
size bigint,
digest varchar(1024),
creation_time timestamp default CURRENT_TIMESTAMP,
FOREIGN KEY (artifact_id) REFERENCES artifact(id),
FOREIGN KEY (subject_artifact_id) REFERENCES artifact(id),
CONSTRAINT unique_artifact_accessory UNIQUE (artifact_id, subject_artifact_id)
);

View File

@ -6,4 +6,8 @@ const (
DigestOfIconChart = "sha256:61cf3a178ff0f75bf08a25d96b75cf7355dc197749a9f128ed3ef34b0df05518" DigestOfIconChart = "sha256:61cf3a178ff0f75bf08a25d96b75cf7355dc197749a9f128ed3ef34b0df05518"
DigestOfIconCNAB = "sha256:089bdda265c14d8686111402c8ad629e8177a1ceb7dcd0f7f39b6480f623b3bd" DigestOfIconCNAB = "sha256:089bdda265c14d8686111402c8ad629e8177a1ceb7dcd0f7f39b6480f623b3bd"
DigestOfIconDefault = "sha256:da834479c923584f4cbcdecc0dac61f32bef1d51e8aae598cf16bd154efab49f" DigestOfIconDefault = "sha256:da834479c923584f4cbcdecc0dac61f32bef1d51e8aae598cf16bd154efab49f"
// ToDo add the accessories images
DigestOfIconAccDefault = ""
DigestOfIconAccCosign = ""
) )

View File

@ -0,0 +1,130 @@
// 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 dao
import (
"context"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
)
// DAO is the data access object for accessory
type DAO interface {
// Count returns the total count of accessory according to the query
Count(ctx context.Context, query *q.Query) (total int64, err error)
// List accessory according to the query
List(ctx context.Context, query *q.Query) (accs []*Accessory, err error)
// Get the accessory specified by ID
Get(ctx context.Context, id int64) (accessory *Accessory, err error)
// Create the accessory
Create(ctx context.Context, accessory *Accessory) (id int64, err error)
// Delete the accessory specified by ID
Delete(ctx context.Context, id int64) (err error)
// DeleteOfArtifact deletes all accessory attached to the artifact
DeleteOfArtifact(ctx context.Context, subArtifactID int64) (err error)
}
// New returns an instance of the default DAO
func New() DAO {
return &dao{}
}
type dao struct{}
func (d *dao) Count(ctx context.Context, query *q.Query) (int64, error) {
qs, err := orm.QuerySetterForCount(ctx, &Accessory{}, query)
if err != nil {
return 0, err
}
return qs.Count()
}
func (d *dao) List(ctx context.Context, query *q.Query) ([]*Accessory, error) {
accs := []*Accessory{}
qs, err := orm.QuerySetter(ctx, &Accessory{}, query)
if err != nil {
return nil, err
}
if _, err = qs.All(&accs); err != nil {
return nil, err
}
return accs, nil
}
func (d *dao) Get(ctx context.Context, id int64) (*Accessory, error) {
acc := &Accessory{
ID: id,
}
ormer, err := orm.FromContext(ctx)
if err != nil {
return nil, err
}
if err := ormer.Read(acc); err != nil {
if e := orm.AsNotFoundError(err, "accessory %d not found", id); e != nil {
err = e
}
return nil, err
}
return acc, nil
}
func (d *dao) Create(ctx context.Context, acc *Accessory) (int64, error) {
ormer, err := orm.FromContext(ctx)
if err != nil {
return 0, err
}
id, err := ormer.Insert(acc)
if err != nil {
if e := orm.AsConflictError(err, "accessory %s already exists under the artifact %d",
acc.Digest, acc.SubjectArtifactID); e != nil {
err = e
} else if e := orm.AsForeignKeyError(err, "the accessory %s tries to attach to a non existing artifact %d",
acc.Digest, acc.SubjectArtifactID); e != nil {
err = e
}
}
return id, err
}
func (d *dao) Delete(ctx context.Context, id int64) error {
ormer, err := orm.FromContext(ctx)
if err != nil {
return err
}
n, err := ormer.Delete(&Accessory{
ID: id,
})
if err != nil {
return err
}
if n == 0 {
return errors.NotFoundError(nil).WithMessage("accessory %d not found", id)
}
return nil
}
func (d *dao) DeleteOfArtifact(ctx context.Context, subArtifactID int64) error {
qs, err := orm.QuerySetter(ctx, &Accessory{}, &q.Query{
Keywords: map[string]interface{}{
"SubjectArtifactID": subArtifactID,
},
})
if err != nil {
return err
}
_, err = qs.Delete()
return err
}

View File

@ -0,0 +1,263 @@
// 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 dao
import (
"context"
"fmt"
beegoorm "github.com/astaxie/beego/orm"
common_dao "github.com/goharbor/harbor/src/common/dao"
errors "github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
artdao "github.com/goharbor/harbor/src/pkg/artifact/dao"
htesting "github.com/goharbor/harbor/src/testing"
"github.com/stretchr/testify/suite"
"testing"
)
type daoTestSuite struct {
htesting.Suite
dao DAO
artDAO artdao.DAO
artifactID int64
subArtifactID int64
accID int64
ctx context.Context
}
func (d *daoTestSuite) SetupSuite() {
d.dao = New()
common_dao.PrepareTestForPostgresSQL()
d.ctx = orm.NewContext(nil, beegoorm.NewOrm())
d.ClearTables = []string{"artifact", "artifact_accessory"}
d.artDAO = artdao.New()
artifactID, err := d.artDAO.Create(d.ctx, &artdao.Artifact{
Type: "IMAGE",
MediaType: "application/vnd.oci.image.config.v1+json",
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
ProjectID: 1,
RepositoryID: 1000,
Digest: d.DigestString(),
})
d.Require().Nil(err)
d.subArtifactID = artifactID
d.artDAO = artdao.New()
artifactID, err = d.artDAO.Create(d.ctx, &artdao.Artifact{
Type: "Signature",
MediaType: "application/vnd.oci.image.config.v1+json",
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
ProjectID: 1,
RepositoryID: 1000,
Digest: d.DigestString(),
})
d.Require().Nil(err)
d.artifactID = artifactID
accID, err := d.dao.Create(d.ctx, &Accessory{
ArtifactID: d.artifactID,
SubjectArtifactID: d.subArtifactID,
Digest: d.DigestString(),
Size: 1234,
Type: "cosign.signature",
})
d.Require().Nil(err)
d.accID = accID
}
func (d *daoTestSuite) TearDownSuite() {
err := d.dao.Delete(d.ctx, d.accID)
d.Require().Nil(err)
err = d.artDAO.Delete(d.ctx, d.artifactID)
d.Require().Nil(err)
err = d.artDAO.Delete(d.ctx, d.subArtifactID)
d.Require().Nil(err)
}
func (d *daoTestSuite) SetupTest() {
}
func (d *daoTestSuite) TearDownTest() {
}
func (d *daoTestSuite) TestCount() {
// nil query
total, err := d.dao.Count(d.ctx, nil)
d.Require().Nil(err)
d.True(total > 0)
total, err = d.dao.Count(d.ctx, &q.Query{
Keywords: map[string]interface{}{
"SubjectArtifactID": d.subArtifactID,
},
})
d.Require().Nil(err)
d.Equal(int64(1), total)
}
func (d *daoTestSuite) TestList() {
// nil query
accs, err := d.dao.List(d.ctx, nil)
d.Require().Nil(err)
found := false
for _, acc := range accs {
if acc.Type == "cosign.signature" {
found = true
break
}
}
d.True(found)
accs, err = d.dao.List(d.ctx, &q.Query{
Keywords: map[string]interface{}{
"SubjectArtifactID": d.subArtifactID,
},
})
d.Require().Nil(err)
d.Require().Equal(1, len(accs))
d.Equal(d.accID, accs[0].ID)
}
func (d *daoTestSuite) TestGet() {
_, err := d.dao.Get(d.ctx, 10000)
d.Require().NotNil(err)
d.True(errors.IsErr(err, errors.NotFoundCode))
acc, err := d.dao.Get(d.ctx, d.accID)
d.Require().Nil(err)
d.Require().NotNil(acc)
d.Equal(d.accID, acc.ID)
}
func (d *daoTestSuite) TestCreate() {
// the happy pass case is covered in Setup
// conflict
acc := &Accessory{
ArtifactID: d.artifactID,
SubjectArtifactID: d.subArtifactID,
Digest: d.DigestString(),
Size: 1234,
Type: "cosign.signature",
}
_, err := d.dao.Create(d.ctx, acc)
d.Require().NotNil(err)
d.True(errors.IsErr(err, errors.ConflictCode))
// violating foreign key constraint: the artifact that the tag tries to attach doesn't exist
acc = &Accessory{
ArtifactID: 999,
SubjectArtifactID: 998,
Digest: d.DigestString(),
Size: 1234,
Type: "cosign.signature",
}
_, err = d.dao.Create(d.ctx, acc)
d.Require().NotNil(err)
d.True(errors.IsErr(err, errors.ViolateForeignKeyConstraintCode))
}
func (d *daoTestSuite) TestDelete() {
// happy pass is covered in TearDown
// not exist
err := d.dao.Delete(d.ctx, 10000)
d.Require().NotNil(err)
var e *errors.Error
d.Require().True(errors.As(err, &e))
d.Equal(errors.NotFoundCode, e.Code)
}
func (d *daoTestSuite) TestDeleteOfArtifact() {
subArtID, err := d.artDAO.Create(d.ctx, &artdao.Artifact{
Type: "IMAGE",
MediaType: "application/vnd.oci.image.config.v1+json",
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
ProjectID: 1,
RepositoryID: 1000,
Digest: d.DigestString(),
})
d.Require().Nil(err)
defer d.artDAO.Delete(d.ctx, subArtID)
artID1, err := d.artDAO.Create(d.ctx, &artdao.Artifact{
Type: "Signature",
MediaType: "application/vnd.oci.image.config.v1+json",
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
ProjectID: 1,
RepositoryID: 1000,
Digest: d.DigestString(),
})
d.Require().Nil(err)
defer d.artDAO.Delete(d.ctx, artID1)
artID2, err := d.artDAO.Create(d.ctx, &artdao.Artifact{
Type: "Signature",
MediaType: "application/vnd.oci.image.config.v1+json",
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
ProjectID: 1,
RepositoryID: 1000,
Digest: d.DigestString(),
})
d.Require().Nil(err)
defer d.artDAO.Delete(d.ctx, artID2)
acc1 := &Accessory{
ArtifactID: artID1,
SubjectArtifactID: subArtID,
Digest: d.DigestString(),
Size: 1234,
Type: "cosign.signature",
}
_, err = d.dao.Create(d.ctx, acc1)
d.Require().Nil(err)
acc2 := &Accessory{
ArtifactID: artID2,
SubjectArtifactID: subArtID,
Digest: d.DigestString(),
Size: 1234,
Type: "cosign.signature",
}
_, err = d.dao.Create(d.ctx, acc2)
d.Require().Nil(err)
accs, err := d.dao.List(d.ctx, &q.Query{
Keywords: map[string]interface{}{
"SubjectArtifactID": subArtID,
},
})
for _, acc := range accs {
fmt.Println(acc.ID)
}
d.Require().Nil(err)
d.Require().Len(accs, 2)
err = d.dao.DeleteOfArtifact(d.ctx, subArtID)
d.Require().Nil(err)
accs, err = d.dao.List(d.ctx, &q.Query{
Keywords: map[string]interface{}{
"SubjectArtifactID": subArtID,
},
})
d.Require().Nil(err)
d.Require().Len(accs, 0)
}
func TestDaoTestSuite(t *testing.T) {
suite.Run(t, &daoTestSuite{})
}

View File

@ -0,0 +1,40 @@
// 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 dao
import (
"github.com/astaxie/beego/orm"
"time"
)
func init() {
orm.RegisterModel(&Accessory{})
}
// Accessory model in database
type Accessory struct {
ID int64 `orm:"pk;auto;column(id)" json:"id"`
ArtifactID int64 `orm:"column(artifact_id)" json:"artifact_id"`
SubjectArtifactID int64 `orm:"column(subject_artifact_id)" json:"subject_artifact_id"`
Type string `orm:"column(type)" json:"type"`
Size int64 `orm:"column(size)" json:"size"`
Digest string `orm:"column(digest)" json:"digest"`
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
}
// TableName for artifact reference
func (a *Accessory) TableName() string {
return "artifact_accessory"
}

View File

@ -0,0 +1,121 @@
// 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 accessory
import (
"context"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/accessory/dao"
"github.com/goharbor/harbor/src/pkg/accessory/model"
_ "github.com/goharbor/harbor/src/pkg/accessory/model/base"
_ "github.com/goharbor/harbor/src/pkg/accessory/model/cosign"
)
var (
// Mgr is a global artifact manager instance
Mgr = NewManager()
)
// Manager is the only interface of artifact module to provide the management functions for artifacts
type Manager interface {
// Get the artifact specified by the ID
Get(ctx context.Context, id int64) (accessory model.Accessory, err error)
// Count returns the total count of tags according to the query.
Count(ctx context.Context, query *q.Query) (total int64, err error)
// List tags according to the query
List(ctx context.Context, query *q.Query) (accs []model.Accessory, err error)
// Create the tag and returns the ID
Create(ctx context.Context, accessory model.AccessoryData) (id int64, err error)
// Delete the tag specified by ID
Delete(ctx context.Context, id int64) (err error)
// DeleteOfArtifact deletes all tags attached to the artifact
DeleteOfArtifact(ctx context.Context, artifactID int64) (err error)
}
// NewManager 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) Get(ctx context.Context, id int64) (model.Accessory, error) {
acc, err := m.dao.Get(ctx, id)
if err != nil {
return nil, err
}
return model.New(acc.Type, model.AccessoryData{
ID: acc.ID,
ArtifactID: acc.ArtifactID,
SubArtifactID: acc.SubjectArtifactID,
Size: acc.Size,
Digest: acc.Digest,
CreatTime: acc.CreationTime,
})
}
func (m *manager) Count(ctx context.Context, query *q.Query) (int64, error) {
return m.dao.Count(ctx, query)
}
func (m *manager) List(ctx context.Context, query *q.Query) ([]model.Accessory, error) {
accsDao, err := m.dao.List(ctx, query)
if err != nil {
return nil, err
}
var accs []model.Accessory
for _, accD := range accsDao {
acc, err := model.New(accD.Type, model.AccessoryData{
ID: accD.ID,
ArtifactID: accD.ArtifactID,
SubArtifactID: accD.SubjectArtifactID,
Size: accD.Size,
Digest: accD.Digest,
CreatTime: accD.CreationTime,
})
if err != nil {
return nil, errors.New(err).WithCode(errors.BadRequestCode)
}
accs = append(accs, acc)
}
return accs, nil
}
func (m *manager) Create(ctx context.Context, accessory model.AccessoryData) (int64, error) {
acc := &dao.Accessory{
ArtifactID: accessory.ArtifactID,
SubjectArtifactID: accessory.SubArtifactID,
Size: accessory.Size,
Digest: accessory.Digest,
Type: accessory.Type,
}
return m.dao.Create(ctx, acc)
}
func (m *manager) Delete(ctx context.Context, id int64) error {
return m.dao.Delete(ctx, id)
}
func (m *manager) DeleteOfArtifact(ctx context.Context, artifactID int64) error {
return m.dao.DeleteOfArtifact(ctx, artifactID)
}

View File

@ -0,0 +1,101 @@
// 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 accessory
import (
"github.com/goharbor/harbor/src/pkg/accessory/dao"
"github.com/goharbor/harbor/src/pkg/accessory/model"
"github.com/goharbor/harbor/src/testing/mock"
testingdao "github.com/goharbor/harbor/src/testing/pkg/accessory/dao"
"github.com/stretchr/testify/suite"
"testing"
)
type managerTestSuite struct {
suite.Suite
mgr *manager
dao *testingdao.DAO
}
func (m *managerTestSuite) SetupTest() {
m.dao = &testingdao.DAO{}
m.mgr = &manager{
dao: m.dao,
}
}
func (m *managerTestSuite) TestList() {
acc := &dao.Accessory{
ID: 1,
Type: model.TypeCosignSignature,
}
mock.OnAnything(m.dao, "List").Return([]*dao.Accessory{
acc,
}, nil)
accs, err := m.mgr.List(nil, nil)
m.Require().Nil(err)
m.Require().Equal(1, len(accs))
m.Equal(int64(1), accs[0].GetData().ID)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestGet() {
acc := &dao.Accessory{
ID: 1,
Type: model.TypeCosignSignature,
}
mock.OnAnything(m.dao, "Get").Return(acc, nil)
accessory, err := m.mgr.Get(nil, 1)
m.Require().Nil(err)
m.Equal(int64(1), accessory.GetData().ID)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestCreate() {
mock.OnAnything(m.dao, "Create").Return(int64(1), nil)
_, err := m.mgr.Create(nil, model.AccessoryData{
ArtifactID: 1,
Size: 1,
Type: model.TypeCosignSignature,
})
m.Require().Nil(err)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestDelete() {
mock.OnAnything(m.dao, "Delete").Return(nil)
err := m.mgr.Delete(nil, 1)
m.Require().Nil(err)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestCount() {
mock.OnAnything(m.dao, "Count").Return(int64(1), nil)
n, err := m.mgr.Count(nil, nil)
m.Require().Nil(err)
m.Equal(int64(1), n)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestDeleteOfArtifact() {
mock.OnAnything(m.dao, "DeleteOfArtifact").Return(nil)
err := m.mgr.DeleteOfArtifact(nil, 1)
m.Require().Nil(err)
m.dao.AssertExpectations(m.T())
}
func TestManager(t *testing.T) {
suite.Run(t, &managerTestSuite{})
}

View File

@ -0,0 +1,110 @@
// 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 (
"fmt"
"sync"
"time"
)
const (
// RefNone identifies base reference
RefNone = "base"
// RefSoft identifies soft reference
RefSoft = "soft"
// RefHard identifies hard reference
RefHard = "hard"
)
type RefProvider interface {
// Kind returns reference Kind.
Kind() string
}
/*
Soft reference: The accessory is not tied to the subject manifest.
Hard reference: The accessory is tied to the subject manifest.
Deletion
1. Soft Reference: If the linkage is Soft Reference, when the subject artifact is removed, the linkage will be removed as well, the accessory artifact becomes an individual artifact.
2. Hard Reference: If the linkage is Hard Reference, the accessory artifact will be removed together with the subject artifact.
Garbage Collection
1. Soft Reference: If the linkage is Soft Reference, Harbor treats the accessory as normal artifact and will not set it as the GC candidate.
2. Hard Reference: If the linkage is Hard Reference, Harbor treats the accessory as an extra stuff of the subject artifact. It means, it being tied to the subject artifact and will be GCed whenever the subject artifact is marked and deleted.
*/
type RefIdentifier interface {
// IsSoft indicates that the linkage of artifact and its accessory is soft reference.
IsSoft() bool
// IsHard indicates that the linkage of artifact and its accessory is hard reference.
IsHard() bool
}
const (
// TypeNone
TypeNone = "base"
// TypeCosignSignature ...
TypeCosignSignature = "signature.cosign"
)
// AccessoryData ...
type AccessoryData struct {
ID int64
ArtifactID int64
SubArtifactID int64
Type string
Size int64
Digest string
CreatTime time.Time
}
// Accessory: Independent, but linked to an existing subject artifact, which enabling the extendibility of an OCI artifact.
type Accessory interface {
RefProvider
RefIdentifier
GetData() AccessoryData
}
// NewAccessoryFunc takes data to return a Accessory.
type NewAccessoryFunc func(data AccessoryData) Accessory
var (
factories = map[string]NewAccessoryFunc{}
lock sync.RWMutex
)
// Register register accessory factory for type
func Register(typ string, factory NewAccessoryFunc) {
lock.Lock()
defer lock.Unlock()
factories[typ] = factory
}
// New returns accessory instance
func New(typ string, data AccessoryData) (Accessory, error) {
lock.Lock()
defer lock.Unlock()
factory, ok := factories[typ]
if !ok {
return nil, fmt.Errorf("accessory type %s not support", typ)
}
data.Type = typ
return factory(data), nil
}

View File

@ -0,0 +1,55 @@
// 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 (
"testing"
"github.com/stretchr/testify/suite"
)
type AccessoryTestSuite struct {
suite.Suite
}
func (suite *AccessoryTestSuite) SetupSuite() {
Register("mock", func(data AccessoryData) Accessory {
return nil
})
}
func (suite *AccessoryTestSuite) TestNew() {
{
c, err := New("", AccessoryData{})
suite.Nil(c)
suite.Error(err)
}
{
c, err := New("mocks", AccessoryData{})
suite.Nil(c)
suite.Error(err)
}
{
c, err := New("mock", AccessoryData{})
suite.Nil(c)
suite.Nil(err)
}
}
func TestAccessoryTestSuite(t *testing.T) {
suite.Run(t, new(AccessoryTestSuite))
}

View File

@ -0,0 +1,55 @@
// 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 base
import (
"github.com/goharbor/harbor/src/pkg/accessory/model"
)
var _ model.Accessory = (*Default)(nil)
// Default default model with TypeNone and RefNone
type Default struct {
Data model.AccessoryData
}
// Kind ...
func (a *Default) Kind() string {
return model.RefNone
}
// IsSoft ...
func (a *Default) IsSoft() bool {
return false
}
// IsHard ...
func (a *Default) IsHard() bool {
return false
}
// GetData ...
func (a *Default) GetData() model.AccessoryData {
return a.Data
}
// New returns base
func New(data model.AccessoryData) model.Accessory {
return &Default{Data: data}
}
func init() {
model.Register(model.TypeNone, New)
}

View File

@ -0,0 +1,65 @@
package base
import (
"github.com/goharbor/harbor/src/pkg/accessory/model"
htesting "github.com/goharbor/harbor/src/testing"
"github.com/stretchr/testify/suite"
"testing"
)
type BaseTestSuite struct {
htesting.Suite
accessory model.Accessory
digest string
}
func (suite *BaseTestSuite) SetupSuite() {
suite.digest = suite.DigestString()
suite.accessory, _ = model.New(model.TypeNone,
model.AccessoryData{
ArtifactID: 1,
SubArtifactID: 2,
Size: 1234,
Digest: suite.digest,
})
}
func (suite *BaseTestSuite) TestGetID() {
suite.Equal(int64(0), suite.accessory.GetData().ID)
}
func (suite *BaseTestSuite) TestGetArtID() {
suite.Equal(int64(1), suite.accessory.GetData().ArtifactID)
}
func (suite *BaseTestSuite) TestSubGetArtID() {
suite.Equal(int64(2), suite.accessory.GetData().SubArtifactID)
}
func (suite *BaseTestSuite) TestSubGetSize() {
suite.Equal(int64(1234), suite.accessory.GetData().Size)
}
func (suite *BaseTestSuite) TestSubGetDigest() {
suite.Equal(suite.digest, suite.accessory.GetData().Digest)
}
func (suite *BaseTestSuite) TestSubGetType() {
suite.Equal(model.TypeNone, suite.accessory.GetData().Type)
}
func (suite *BaseTestSuite) TestSubGetRefType() {
suite.Equal(model.RefNone, suite.accessory.Kind())
}
func (suite *BaseTestSuite) TestIsSoft() {
suite.False(suite.accessory.IsSoft())
}
func (suite *BaseTestSuite) TestIsHard() {
suite.False(suite.accessory.IsHard())
}
func TestCacheTestSuite(t *testing.T) {
suite.Run(t, new(BaseTestSuite))
}

View File

@ -0,0 +1,46 @@
// 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 cosign
import (
"github.com/goharbor/harbor/src/pkg/accessory/model"
"github.com/goharbor/harbor/src/pkg/accessory/model/base"
)
// Signature signature model
type Signature struct {
base.Default
}
// Kind gives the reference type of cosign signature.
func (c *Signature) Kind() string {
return model.RefHard
}
// IsHard ...
func (c *Signature) IsHard() bool {
return true
}
// New returns cosign signature
func New(data model.AccessoryData) model.Accessory {
return &Signature{base.Default{
Data: data,
}}
}
func init() {
model.Register(model.TypeCosignSignature, New)
}

View File

@ -0,0 +1,65 @@
package cosign
import (
"github.com/goharbor/harbor/src/pkg/accessory/model"
htesting "github.com/goharbor/harbor/src/testing"
"github.com/stretchr/testify/suite"
"testing"
)
type CosignTestSuite struct {
htesting.Suite
accessory model.Accessory
digest string
}
func (suite *CosignTestSuite) SetupSuite() {
suite.digest = suite.DigestString()
suite.accessory, _ = model.New(model.TypeCosignSignature,
model.AccessoryData{
ArtifactID: 1,
SubArtifactID: 2,
Size: 4321,
Digest: suite.digest,
})
}
func (suite *CosignTestSuite) TestGetID() {
suite.Equal(int64(0), suite.accessory.GetData().ID)
}
func (suite *CosignTestSuite) TestGetArtID() {
suite.Equal(int64(1), suite.accessory.GetData().ArtifactID)
}
func (suite *CosignTestSuite) TestSubGetArtID() {
suite.Equal(int64(2), suite.accessory.GetData().SubArtifactID)
}
func (suite *CosignTestSuite) TestSubGetSize() {
suite.Equal(int64(4321), suite.accessory.GetData().Size)
}
func (suite *CosignTestSuite) TestSubGetDigest() {
suite.Equal(suite.digest, suite.accessory.GetData().Digest)
}
func (suite *CosignTestSuite) TestSubGetType() {
suite.Equal(model.TypeCosignSignature, suite.accessory.GetData().Type)
}
func (suite *CosignTestSuite) TestSubGetRefType() {
suite.Equal(model.RefHard, suite.accessory.Kind())
}
func (suite *CosignTestSuite) TestIsSoft() {
suite.False(suite.accessory.IsSoft())
}
func (suite *CosignTestSuite) TestIsHard() {
suite.True(suite.accessory.IsHard())
}
func TestCacheTestSuite(t *testing.T) {
suite.Run(t, new(CosignTestSuite))
}

View File

@ -0,0 +1,133 @@
// Code generated by mockery v2.1.0. DO NOT EDIT.
package dao
import (
context "context"
dao "github.com/goharbor/harbor/src/pkg/accessory/dao"
mock "github.com/stretchr/testify/mock"
q "github.com/goharbor/harbor/src/lib/q"
)
// DAO is an autogenerated mock type for the DAO type
type DAO struct {
mock.Mock
}
// Count provides a mock function with given fields: ctx, query
func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) {
ret := _m.Called(ctx, query)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) int64); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Get(0).(int64)
}
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
}
// Create provides a mock function with given fields: ctx, accessory
func (_m *DAO) Create(ctx context.Context, accessory *dao.Accessory) (int64, error) {
ret := _m.Called(ctx, accessory)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *dao.Accessory) int64); ok {
r0 = rf(ctx, accessory)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *dao.Accessory) error); ok {
r1 = rf(ctx, accessory)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Delete provides a mock function with given fields: ctx, id
func (_m *DAO) Delete(ctx context.Context, id int64) error {
ret := _m.Called(ctx, id)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteOfArtifact provides a mock function with given fields: ctx, subArtifactID
func (_m *DAO) DeleteOfArtifact(ctx context.Context, subArtifactID int64) error {
ret := _m.Called(ctx, subArtifactID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, subArtifactID)
} else {
r0 = ret.Error(0)
}
return r0
}
// Get provides a mock function with given fields: ctx, id
func (_m *DAO) Get(ctx context.Context, id int64) (*dao.Accessory, error) {
ret := _m.Called(ctx, id)
var r0 *dao.Accessory
if rf, ok := ret.Get(0).(func(context.Context, int64) *dao.Accessory); ok {
r0 = rf(ctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*dao.Accessory)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, id)
} 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) ([]*dao.Accessory, error) {
ret := _m.Called(ctx, query)
var r0 []*dao.Accessory
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*dao.Accessory); ok {
r0 = rf(ctx, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*dao.Accessory)
}
}
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
}

View File

@ -0,0 +1,69 @@
// Code generated by mockery v2.1.0. DO NOT EDIT.
package model
import (
model "github.com/goharbor/harbor/src/pkg/accessory/model"
mock "github.com/stretchr/testify/mock"
)
// Accessory is an autogenerated mock type for the Accessory type
type Accessory struct {
mock.Mock
}
// GetData provides a mock function with given fields:
func (_m *Accessory) GetData() model.AccessoryData {
ret := _m.Called()
var r0 model.AccessoryData
if rf, ok := ret.Get(0).(func() model.AccessoryData); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(model.AccessoryData)
}
return r0
}
// IsHard provides a mock function with given fields:
func (_m *Accessory) IsHard() bool {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// IsSoft provides a mock function with given fields:
func (_m *Accessory) IsSoft() bool {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// Kind provides a mock function with given fields:
func (_m *Accessory) Kind() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}

View File

@ -53,3 +53,5 @@ package pkg
//go:generate mockery --case snake --dir ../../pkg/label/dao --name DAO --output ./label/dao --outpkg dao //go:generate mockery --case snake --dir ../../pkg/label/dao --name DAO --output ./label/dao --outpkg dao
//go:generate mockery --case snake --dir ../../pkg/joblog --name Manager --output ./joblog --outpkg joblog //go:generate mockery --case snake --dir ../../pkg/joblog --name Manager --output ./joblog --outpkg joblog
//go:generate mockery --case snake --dir ../../pkg/joblog/dao --name DAO --output ./joblog/dao --outpkg dao //go:generate mockery --case snake --dir ../../pkg/joblog/dao --name DAO --output ./joblog/dao --outpkg dao
//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