mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-11 18:38:14 +01:00
modify artifact copy api to support cosign (#16194)
Signed-off-by: Wang Yan <wangyan@vmware.com>
This commit is contained in:
parent
8f77567589
commit
01c6f6084b
@ -19,6 +19,7 @@ import (
|
||||
"context"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
accessorymodel "github.com/goharbor/harbor/src/pkg/accessory/model"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -78,7 +79,7 @@ type Controller interface {
|
||||
// creates it if it doesn't exist. If tags are provided, ensure they exist
|
||||
// and are attached to the artifact. If the tags don't exist, create them first.
|
||||
// The "created" will be set as true when the artifact is created
|
||||
Ensure(ctx context.Context, repository, digest string, tags ...string) (created bool, id int64, err error)
|
||||
Ensure(ctx context.Context, repository, digest string, option *ArtOption) (created bool, id int64, err error)
|
||||
// Count returns the total count of artifacts according to the query.
|
||||
// The artifacts that referenced by others and without tags are not counted
|
||||
Count(ctx context.Context, query *q.Query) (total int64, err error)
|
||||
@ -140,14 +141,26 @@ type controller struct {
|
||||
accessoryMgr accessory.Manager
|
||||
}
|
||||
|
||||
func (c *controller) Ensure(ctx context.Context, repository, digest string, tags ...string) (bool, int64, error) {
|
||||
type ArtOption struct {
|
||||
Tags []string
|
||||
Accs []accessorymodel.AccessoryData
|
||||
}
|
||||
|
||||
func (c *controller) Ensure(ctx context.Context, repository, digest string, option *ArtOption) (bool, int64, error) {
|
||||
created, artifact, err := c.ensureArtifact(ctx, repository, digest)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
for _, tag := range tags {
|
||||
if err = c.tagCtl.Ensure(ctx, artifact.RepositoryID, artifact.ID, tag); err != nil {
|
||||
return false, 0, err
|
||||
if option != nil {
|
||||
for _, tag := range option.Tags {
|
||||
if err = c.tagCtl.Ensure(ctx, artifact.RepositoryID, artifact.ID, tag); err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
}
|
||||
for _, acc := range option.Accs {
|
||||
if err = c.accessoryMgr.Ensure(ctx, artifact.ID, acc.ArtifactID, acc.Size, acc.Digest, acc.Type); err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
// fire event
|
||||
@ -155,8 +168,9 @@ func (c *controller) Ensure(ctx context.Context, repository, digest string, tags
|
||||
Ctx: ctx,
|
||||
Artifact: artifact,
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
e.Tag = tags[0]
|
||||
|
||||
if option != nil && len(option.Tags) > 0 {
|
||||
e.Tag = option.Tags[0]
|
||||
}
|
||||
notification.AddEvent(ctx, e)
|
||||
return created, artifact.ID, nil
|
||||
@ -429,18 +443,19 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot, isAcces
|
||||
}
|
||||
|
||||
func (c *controller) Copy(ctx context.Context, srcRepo, reference, dstRepo string) (int64, error) {
|
||||
return c.copyDeeply(ctx, srcRepo, reference, dstRepo, true)
|
||||
dstAccs := make([]accessorymodel.AccessoryData, 0)
|
||||
return c.copyDeeply(ctx, srcRepo, reference, dstRepo, true, &dstAccs)
|
||||
}
|
||||
|
||||
// as we call the docker registry APIs in the registry client directly,
|
||||
// this bypass our own logic(ensure, fire event, etc.) inside the registry handlers,
|
||||
// these logic must be covered explicitly here.
|
||||
// "copyDeeply" iterates the child artifacts and copy them first
|
||||
func (c *controller) copyDeeply(ctx context.Context, srcRepo, reference, dstRepo string, isRoot bool) (int64, error) {
|
||||
func (c *controller) copyDeeply(ctx context.Context, srcRepo, reference, dstRepo string, isRoot bool, dstAccs *[]accessorymodel.AccessoryData) (int64, error) {
|
||||
var option *Option
|
||||
// only get the tags of the root parent
|
||||
if isRoot {
|
||||
option = &Option{WithTag: true}
|
||||
option = &Option{WithTag: true, WithAccessory: true}
|
||||
}
|
||||
|
||||
srcArt, err := c.GetByReference(ctx, srcRepo, reference, option)
|
||||
@ -457,17 +472,32 @@ func (c *controller) copyDeeply(ctx context.Context, srcRepo, reference, dstRepo
|
||||
if !isRoot {
|
||||
return dstArt.ID, nil
|
||||
}
|
||||
// the root parent already exists, goto next step to copy tags
|
||||
goto tags
|
||||
// the root parent already exists, goto next step to ensure artifact: create artifact & references, copy tags & accessories.
|
||||
goto ensureArt
|
||||
}
|
||||
if !errors.IsErr(err, errors.NotFoundCode) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// copy accessory if contains any
|
||||
for _, acc := range srcArt.Accessories {
|
||||
id, err := c.copyDeeply(ctx, srcRepo, acc.GetData().Digest, dstRepo, false, dstAccs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dstAcc := accessorymodel.AccessoryData{
|
||||
ArtifactID: id,
|
||||
Digest: acc.GetData().Digest,
|
||||
Type: acc.GetData().Type,
|
||||
Size: acc.GetData().Size,
|
||||
}
|
||||
*dstAccs = append(*dstAccs, dstAcc)
|
||||
}
|
||||
|
||||
// the artifact doesn't exist under the destination repository, continue to copy
|
||||
// copy child artifacts if contains any
|
||||
for _, reference := range srcArt.References {
|
||||
if _, err = c.copyDeeply(ctx, srcRepo, reference.ChildDigest, dstRepo, false); err != nil {
|
||||
if _, err = c.copyDeeply(ctx, srcRepo, reference.ChildDigest, dstRepo, false, dstAccs); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
@ -477,14 +507,18 @@ func (c *controller) copyDeeply(ctx context.Context, srcRepo, reference, dstRepo
|
||||
return 0, err
|
||||
}
|
||||
|
||||
tags:
|
||||
ensureArt:
|
||||
// only copy the tags of outermost artifact
|
||||
var tags []string
|
||||
for _, tag := range srcArt.Tags {
|
||||
tags = append(tags, tag.Name)
|
||||
}
|
||||
// ensure the parent artifact exist in the database
|
||||
_, id, err := c.Ensure(ctx, dstRepo, digest, tags...)
|
||||
artopt := &ArtOption{
|
||||
Tags: tags,
|
||||
Accs: *dstAccs,
|
||||
}
|
||||
_, id, err := c.Ensure(ctx, dstRepo, digest, artopt)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -586,6 +620,24 @@ func (c *controller) Walk(ctx context.Context, root *Artifact, walkFn func(*Arti
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(artifact.Accessories) > 0 {
|
||||
var ids []int64
|
||||
for _, acc := range artifact.Accessories {
|
||||
ids = append(ids, acc.GetData().ArtifactID)
|
||||
}
|
||||
|
||||
children, err := c.List(ctx, q.New(q.KeyWords{"id__in": ids, "base": "*"}), option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, child := range children {
|
||||
if !walked[child.Digest] {
|
||||
queue.PushBack(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -250,7 +250,10 @@ func (c *controllerTestSuite) TestEnsure() {
|
||||
c.artMgr.On("Create", mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
c.abstractor.On("AbstractMetadata").Return(nil)
|
||||
c.tagCtl.On("Ensure").Return(nil)
|
||||
_, id, err := c.ctl.Ensure(orm.NewContext(nil, &ormtesting.FakeOrmer{}), "library/hello-world", digest, "latest")
|
||||
c.accMgr.On("Ensure").Return(nil)
|
||||
_, id, err := c.ctl.Ensure(orm.NewContext(nil, &ormtesting.FakeOrmer{}), "library/hello-world", digest, &ArtOption{
|
||||
Tags: []string{"latest"},
|
||||
})
|
||||
c.Require().Nil(err)
|
||||
c.repoMgr.AssertExpectations(c.T())
|
||||
c.artMgr.AssertExpectations(c.T())
|
||||
@ -540,6 +543,17 @@ func (c *controllerTestSuite) TestCopy() {
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
acc := &basemodel.Default{
|
||||
Data: accessorymodel.AccessoryData{
|
||||
ID: 1,
|
||||
ArtifactID: 2,
|
||||
SubArtifactID: 1,
|
||||
Type: accessorymodel.TypeCosignSignature,
|
||||
},
|
||||
}
|
||||
c.accMgr.On("List", mock.Anything, mock.Anything).Return([]accessorymodel.Accessory{
|
||||
acc,
|
||||
}, nil)
|
||||
c.tagCtl.On("Update").Return(nil)
|
||||
c.repoMgr.On("Get", mock.Anything, mock.Anything).Return(&repomodel.RepoRecord{
|
||||
RepositoryID: 1,
|
||||
@ -549,6 +563,7 @@ func (c *controllerTestSuite) TestCopy() {
|
||||
c.artMgr.On("Create", mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
c.regCli.On("Copy").Return(nil)
|
||||
c.tagCtl.On("Ensure").Return(nil)
|
||||
c.accMgr.On("Ensure", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
_, err := c.ctl.Copy(orm.NewContext(nil, &ormtesting.FakeOrmer{}), "library/hello-world", "latest", "library/hello-world2")
|
||||
c.Require().Nil(err)
|
||||
}
|
||||
|
@ -32,15 +32,17 @@ var (
|
||||
|
||||
// Manager is the only interface of artifact module to provide the management functions for artifacts
|
||||
type Manager interface {
|
||||
// Ensure ...
|
||||
Ensure(ctx context.Context, subArtID, artifactID, size int64, digest, accType string) error
|
||||
// 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 returns the total count of accessory according to the query.
|
||||
Count(ctx context.Context, query *q.Query) (total int64, err error)
|
||||
// List tags according to the query
|
||||
// List accessory according to the query
|
||||
List(ctx context.Context, query *q.Query) (accs []model.Accessory, err error)
|
||||
// Create the tag and returns the ID
|
||||
// Create the accessory and returns the ID
|
||||
Create(ctx context.Context, accessory model.AccessoryData) (id int64, err error)
|
||||
// Delete the tag specified by ID
|
||||
// Delete the accessory specified by ID
|
||||
Delete(ctx context.Context, id int64) (err error)
|
||||
// DeleteAccessories deletes accessories according to the query
|
||||
DeleteAccessories(ctx context.Context, q *q.Query) (err error)
|
||||
@ -59,6 +61,26 @@ type manager struct {
|
||||
dao dao.DAO
|
||||
}
|
||||
|
||||
func (m *manager) Ensure(ctx context.Context, subArtID, artifactID, size int64, digest, accType string) error {
|
||||
accs, err := m.dao.List(ctx, q.New(q.KeyWords{"ArtifactID": artifactID, "Digest": digest}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(accs) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
acc := model.AccessoryData{
|
||||
ArtifactID: artifactID,
|
||||
SubArtifactID: subArtID,
|
||||
Digest: digest,
|
||||
Size: size,
|
||||
Type: accType,
|
||||
}
|
||||
_, err = m.Create(ctx, acc)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *manager) Get(ctx context.Context, id int64) (model.Accessory, error) {
|
||||
acc, err := m.dao.Get(ctx, id)
|
||||
if err != nil {
|
||||
|
@ -37,6 +37,13 @@ func (m *managerTestSuite) SetupTest() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestEnsure() {
|
||||
mock.OnAnything(m.dao, "List").Return([]*dao.Accessory{}, nil)
|
||||
mock.OnAnything(m.dao, "Create").Return(int64(1), nil)
|
||||
err := m.mgr.Ensure(nil, int64(1), int64(1), int64(1), "sha256:1234", model.TypeCosignSignature)
|
||||
m.Require().Nil(err)
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestList() {
|
||||
acc := &dao.Accessory{
|
||||
ID: 1,
|
||||
|
@ -125,7 +125,9 @@ func putManifest(w http.ResponseWriter, req *http.Request) {
|
||||
tags = append(tags, reference)
|
||||
}
|
||||
|
||||
_, _, err = artifact.Ctl.Ensure(req.Context(), repo, dgt, tags...)
|
||||
_, _, err = artifact.Ctl.Ensure(req.Context(), repo, dgt, &artifact.ArtOption{
|
||||
Tags: tags,
|
||||
})
|
||||
if err != nil {
|
||||
lib_http.SendError(w, err)
|
||||
return
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@ -170,6 +171,18 @@ func (a *artifactAPI) CopyArtifact(ctx context.Context, params operation.CopyArt
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
srcArt, err := a.artCtl.GetByReference(ctx, srcRepo, ref, nil)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
accs, err := a.accMgr.List(ctx, q.New(q.KeyWords{"ArtifactID": srcArt.ID, "Digest": srcArt.Digest}))
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
if len(accs) >= 1 && accs[0].IsHard() {
|
||||
return a.SendError(ctx, errors.New(nil).WithCode(errors.DENIED).WithMessage("the operation isn't supported for an artifact accessory"))
|
||||
}
|
||||
|
||||
dstRepo := fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName)
|
||||
_, _, err = a.repoCtl.Ensure(ctx, dstRepo)
|
||||
if err != nil {
|
||||
|
@ -91,34 +91,27 @@ func (_m *Controller) Delete(ctx context.Context, id int64) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Ensure provides a mock function with given fields: ctx, repository, digest, tags
|
||||
func (_m *Controller) Ensure(ctx context.Context, repository string, digest string, tags ...string) (bool, int64, error) {
|
||||
_va := make([]interface{}, len(tags))
|
||||
for _i := range tags {
|
||||
_va[_i] = tags[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, repository, digest)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
// Ensure provides a mock function with given fields: ctx, repository, digest, option
|
||||
func (_m *Controller) Ensure(ctx context.Context, repository string, digest string, option *artifact.ArtOption) (bool, int64, error) {
|
||||
ret := _m.Called(ctx, repository, digest, option)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, ...string) bool); ok {
|
||||
r0 = rf(ctx, repository, digest, tags...)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, *artifact.ArtOption) bool); ok {
|
||||
r0 = rf(ctx, repository, digest, option)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 int64
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, ...string) int64); ok {
|
||||
r1 = rf(ctx, repository, digest, tags...)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, *artifact.ArtOption) int64); ok {
|
||||
r1 = rf(ctx, repository, digest, option)
|
||||
} else {
|
||||
r1 = ret.Get(1).(int64)
|
||||
}
|
||||
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(2).(func(context.Context, string, string, ...string) error); ok {
|
||||
r2 = rf(ctx, repository, digest, tags...)
|
||||
if rf, ok := ret.Get(2).(func(context.Context, string, string, *artifact.ArtOption) error); ok {
|
||||
r2 = rf(ctx, repository, digest, option)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
@ -86,6 +86,20 @@ func (_m *Manager) DeleteAccessories(ctx context.Context, _a1 *q.Query) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Ensure provides a mock function with given fields: ctx, subArtID, artifactID, size, digest, accType
|
||||
func (_m *Manager) Ensure(ctx context.Context, subArtID int64, artifactID int64, size int64, digest string, accType string) error {
|
||||
ret := _m.Called(ctx, subArtID, artifactID, size, digest, accType)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, int64, int64, string, string) error); ok {
|
||||
r0 = rf(ctx, subArtID, artifactID, size, digest, accType)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, id
|
||||
func (_m *Manager) Get(ctx context.Context, id int64) (model.Accessory, error) {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
Loading…
Reference in New Issue
Block a user