mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-18 12:51:27 +01:00
feat(artifact): add Walk method to artifact controller (#10881)
1. Add Walk method to artifact controller. 2. Only query references when artifact is image index. Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
parent
3d336bfac3
commit
0f5a115a65
@ -15,8 +15,12 @@
|
|||||||
package artifact
|
package artifact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"container/list"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/api/artifact/abstractor"
|
"github.com/goharbor/harbor/src/api/artifact/abstractor"
|
||||||
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
|
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
|
||||||
"github.com/goharbor/harbor/src/api/artifact/descriptor"
|
"github.com/goharbor/harbor/src/api/artifact/descriptor"
|
||||||
@ -31,8 +35,7 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/pkg/registry"
|
"github.com/goharbor/harbor/src/pkg/registry"
|
||||||
"github.com/goharbor/harbor/src/pkg/signature"
|
"github.com/goharbor/harbor/src/pkg/signature"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
// registry image resolvers
|
// registry image resolvers
|
||||||
_ "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver/image"
|
_ "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver/image"
|
||||||
// register chart resolver
|
// register chart resolver
|
||||||
@ -84,6 +87,8 @@ type Controller interface {
|
|||||||
AddLabel(ctx context.Context, artifactID int64, labelID int64) (err error)
|
AddLabel(ctx context.Context, artifactID int64, labelID int64) (err error)
|
||||||
// RemoveLabel from the specified artifact
|
// RemoveLabel from the specified artifact
|
||||||
RemoveLabel(ctx context.Context, artifactID int64, labelID int64) (err error)
|
RemoveLabel(ctx context.Context, artifactID int64, labelID int64) (err error)
|
||||||
|
// Walk walks the artifact tree rooted at root, calling walkFn for each artifact in the tree, including root.
|
||||||
|
Walk(ctx context.Context, root *Artifact, walkFn func(*Artifact) error, option *Option) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewController creates an instance of the default artifact controller
|
// NewController creates an instance of the default artifact controller
|
||||||
@ -313,7 +318,7 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot bool) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean associations between blob and project when when the blob is not needed by project
|
// clean associations between blob and project when the blob is not needed by project
|
||||||
if err := c.blobMgr.CleanupAssociationsForProject(ctx, art.ProjectID, blobs); err != nil {
|
if err := c.blobMgr.CleanupAssociationsForProject(ctx, art.ProjectID, blobs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -435,6 +440,39 @@ func (c *controller) RemoveLabel(ctx context.Context, artifactID int64, labelID
|
|||||||
return c.labelMgr.RemoveFrom(ctx, labelID, artifactID)
|
return c.labelMgr.RemoveFrom(ctx, labelID, artifactID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *controller) Walk(ctx context.Context, root *Artifact, walkFn func(*Artifact) error, option *Option) error {
|
||||||
|
queue := list.New()
|
||||||
|
queue.PushBack(root)
|
||||||
|
|
||||||
|
for queue.Len() > 0 {
|
||||||
|
elem := queue.Front()
|
||||||
|
queue.Remove(elem)
|
||||||
|
|
||||||
|
artifact := elem.Value.(*Artifact)
|
||||||
|
if err := walkFn(artifact); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(artifact.References) > 0 {
|
||||||
|
var ids []int64
|
||||||
|
for _, ref := range artifact.References {
|
||||||
|
ids = append(ids, ref.ChildID)
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts, err := c.List(ctx, q.New(q.KeyWords{"id__in": ids}), option)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, artifact := range artifacts {
|
||||||
|
queue.PushBack(artifact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// assemble several part into a single artifact
|
// assemble several part into a single artifact
|
||||||
func (c *controller) assembleArtifact(ctx context.Context, art *artifact.Artifact, option *Option) *Artifact {
|
func (c *controller) assembleArtifact(ctx context.Context, art *artifact.Artifact, option *Option) *Artifact {
|
||||||
artifact := &Artifact{
|
artifact := &Artifact{
|
||||||
|
@ -16,6 +16,9 @@ package artifact
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
|
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
|
||||||
"github.com/goharbor/harbor/src/api/artifact/descriptor"
|
"github.com/goharbor/harbor/src/api/artifact/descriptor"
|
||||||
"github.com/goharbor/harbor/src/api/tag"
|
"github.com/goharbor/harbor/src/api/tag"
|
||||||
@ -33,10 +36,9 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/testing/pkg/label"
|
"github.com/goharbor/harbor/src/testing/pkg/label"
|
||||||
"github.com/goharbor/harbor/src/testing/pkg/registry"
|
"github.com/goharbor/harbor/src/testing/pkg/registry"
|
||||||
repotesting "github.com/goharbor/harbor/src/testing/pkg/repository"
|
repotesting "github.com/goharbor/harbor/src/testing/pkg/repository"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO find another way to test artifact controller, it's hard to maintain currently
|
// TODO find another way to test artifact controller, it's hard to maintain currently
|
||||||
@ -528,6 +530,41 @@ func (c *controllerTestSuite) TestRemoveFrom() {
|
|||||||
c.Require().Nil(err)
|
c.Require().Nil(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *controllerTestSuite) TestWalk() {
|
||||||
|
c.artMgr.On("List").Return([]*artifact.Artifact{
|
||||||
|
{ManifestMediaType: v1.MediaTypeImageManifest},
|
||||||
|
{ManifestMediaType: v1.MediaTypeImageManifest},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
{
|
||||||
|
root := &Artifact{}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
c.ctl.Walk(context.TODO(), root, func(a *Artifact) error {
|
||||||
|
n++
|
||||||
|
return nil
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
c.Equal(1, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
root := &Artifact{}
|
||||||
|
root.References = []*artifact.Reference{
|
||||||
|
{ParentID: 1, ChildID: 2},
|
||||||
|
{ParentID: 1, ChildID: 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
c.ctl.Walk(context.TODO(), root, func(a *Artifact) error {
|
||||||
|
n++
|
||||||
|
return nil
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
c.Equal(3, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestControllerTestSuite(t *testing.T) {
|
func TestControllerTestSuite(t *testing.T) {
|
||||||
suite.Run(t, &controllerTestSuite{})
|
suite.Run(t, &controllerTestSuite{})
|
||||||
}
|
}
|
||||||
|
@ -158,15 +158,15 @@ func (m *manager) assemble(ctx context.Context, art *dao.Artifact) (*Artifact, e
|
|||||||
artifact := &Artifact{}
|
artifact := &Artifact{}
|
||||||
// convert from database object
|
// convert from database object
|
||||||
artifact.From(art)
|
artifact.From(art)
|
||||||
|
|
||||||
// populate the references
|
// populate the references
|
||||||
references, err := m.ListReferences(ctx, &q.Query{
|
if artifact.HasChildren() {
|
||||||
Keywords: map[string]interface{}{
|
references, err := m.ListReferences(ctx, q.New(q.KeyWords{"ParentID": artifact.ID}))
|
||||||
"ParentID": artifact.ID,
|
if err != nil {
|
||||||
},
|
return nil, err
|
||||||
})
|
}
|
||||||
if err != nil {
|
artifact.References = references
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
artifact.References = references
|
|
||||||
return artifact, nil
|
return artifact, nil
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,14 @@ package artifact
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/q"
|
|
||||||
"github.com/stretchr/testify/mock"
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeDao struct {
|
type fakeDao struct {
|
||||||
@ -98,7 +100,7 @@ func (m *managerTestSuite) TestAssemble() {
|
|||||||
ID: 1,
|
ID: 1,
|
||||||
Type: "IMAGE",
|
Type: "IMAGE",
|
||||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
|
ManifestMediaType: v1.MediaTypeImageIndex,
|
||||||
ProjectID: 1,
|
ProjectID: 1,
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Digest: "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180",
|
Digest: "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180",
|
||||||
@ -159,7 +161,7 @@ func (m *managerTestSuite) TestGet() {
|
|||||||
ID: 1,
|
ID: 1,
|
||||||
Type: "IMAGE",
|
Type: "IMAGE",
|
||||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
|
ManifestMediaType: v1.MediaTypeImageIndex,
|
||||||
ProjectID: 1,
|
ProjectID: 1,
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
Digest: "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180",
|
Digest: "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180",
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/manifest/manifestlist"
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
@ -43,6 +44,12 @@ type Artifact struct {
|
|||||||
References []*Reference `json:"references"` // child artifacts referenced by the parent artifact if the artifact is an index
|
References []*Reference `json:"references"` // child artifacts referenced by the parent artifact if the artifact is an index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasChildren returns true when artifact has children artifacts, most times that means the artifact is Image Index.
|
||||||
|
func (a *Artifact) HasChildren() bool {
|
||||||
|
return a.ManifestMediaType == v1.MediaTypeImageIndex ||
|
||||||
|
a.ManifestMediaType == manifestlist.MediaTypeManifestList
|
||||||
|
}
|
||||||
|
|
||||||
// From converts the database level artifact to the business level object
|
// From converts the database level artifact to the business level object
|
||||||
func (a *Artifact) From(art *dao.Artifact) {
|
func (a *Artifact) From(art *dao.Artifact) {
|
||||||
a.ID = art.ID
|
a.ID = art.ID
|
||||||
|
@ -18,7 +18,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/manifest/manifestlist"
|
||||||
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
@ -94,6 +96,17 @@ func (m *modelTestSuite) TestArtifactTo() {
|
|||||||
assert.Equal(t, `{"anno1":"value1"}`, dbArt.Annotations)
|
assert.Equal(t, `{"anno1":"value1"}`, dbArt.Annotations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *modelTestSuite) TestHasChildren() {
|
||||||
|
art1 := Artifact{ManifestMediaType: v1.MediaTypeImageIndex}
|
||||||
|
m.True(art1.HasChildren())
|
||||||
|
|
||||||
|
art2 := Artifact{ManifestMediaType: manifestlist.MediaTypeManifestList}
|
||||||
|
m.True(art2.HasChildren())
|
||||||
|
|
||||||
|
art3 := Artifact{ManifestMediaType: v1.MediaTypeImageManifest}
|
||||||
|
m.False(art3.HasChildren())
|
||||||
|
}
|
||||||
|
|
||||||
func TestModel(t *testing.T) {
|
func TestModel(t *testing.T) {
|
||||||
suite.Run(t, &modelTestSuite{})
|
suite.Run(t, &modelTestSuite{})
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,12 @@ package artifact
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/api/artifact"
|
"github.com/goharbor/harbor/src/api/artifact"
|
||||||
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
|
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
|
||||||
"github.com/goharbor/harbor/src/pkg/q"
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FakeController is a fake artifact controller that implement src/api/artifact.Controller interface
|
// FakeController is a fake artifact controller that implement src/api/artifact.Controller interface
|
||||||
@ -109,3 +110,9 @@ func (f *FakeController) RemoveLabel(ctx context.Context, artifactID int64, labe
|
|||||||
args := f.Called()
|
args := f.Called()
|
||||||
return args.Error(0)
|
return args.Error(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Walk ...
|
||||||
|
func (f *FakeController) Walk(ctx context.Context, root *artifact.Artifact, workFn func(*artifact.Artifact) error, option *artifact.Option) error {
|
||||||
|
args := f.Called()
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user