mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-18 00:05:12 +01:00
Merge pull request #10561 from ywk253100/200120_artifact_controller
Implement the get/delete handler for registry API
This commit is contained in:
commit
dee70f7deb
@ -18,9 +18,14 @@ import (
|
|||||||
"github.com/docker/distribution/manifest/manifestlist"
|
"github.com/docker/distribution/manifest/manifestlist"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/manifest/schema2"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils/registry"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils/registry/auth"
|
||||||
|
"github.com/goharbor/harbor/src/core/config"
|
||||||
|
"github.com/goharbor/harbor/src/core/service/token"
|
||||||
coreutils "github.com/goharbor/harbor/src/core/utils"
|
coreutils "github.com/goharbor/harbor/src/core/utils"
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -54,7 +59,7 @@ type fetcher struct{}
|
|||||||
// TODO re-implement it based on OCI registry driver
|
// TODO re-implement it based on OCI registry driver
|
||||||
func (f *fetcher) FetchManifest(repository, digest string) (string, []byte, error) {
|
func (f *fetcher) FetchManifest(repository, digest string) (string, []byte, error) {
|
||||||
// TODO read from cache first
|
// TODO read from cache first
|
||||||
client, err := coreutils.NewRepositoryClientForLocal("admin", repository)
|
client, err := newRepositoryClient(repository)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@ -76,3 +81,19 @@ func (f *fetcher) FetchLayer(repository, digest string) ([]byte, error) {
|
|||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
return ioutil.ReadAll(reader)
|
return ioutil.ReadAll(reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRepositoryClient(repository string) (*registry.Repository, error) {
|
||||||
|
uam := &auth.UserAgentModifier{
|
||||||
|
UserAgent: "harbor-registry-client",
|
||||||
|
}
|
||||||
|
authorizer := auth.NewRawTokenAuthorizer("admin", token.Registry)
|
||||||
|
transport := registry.NewTransport(http.DefaultTransport, authorizer, uam)
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
}
|
||||||
|
endpoint, err := config.RegistryURL()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return registry.NewRepository(repository, endpoint, client)
|
||||||
|
}
|
||||||
|
@ -18,7 +18,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/goharbor/harbor/src/api/artifact/abstractor"
|
"github.com/goharbor/harbor/src/api/artifact/abstractor"
|
||||||
// register image resolvers
|
"github.com/opencontainers/go-digest"
|
||||||
|
// 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
|
||||||
_ "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver/chart"
|
_ "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver/chart"
|
||||||
@ -48,6 +49,9 @@ type Controller interface {
|
|||||||
List(ctx context.Context, query *q.Query, option *Option) (total int64, artifacts []*Artifact, err error)
|
List(ctx context.Context, query *q.Query, option *Option) (total int64, artifacts []*Artifact, err error)
|
||||||
// Get the artifact specified by ID, specify the properties returned with option
|
// Get the artifact specified by ID, specify the properties returned with option
|
||||||
Get(ctx context.Context, id int64, option *Option) (artifact *Artifact, err error)
|
Get(ctx context.Context, id int64, option *Option) (artifact *Artifact, err error)
|
||||||
|
// Get the artifact specified by repository name and reference, the reference can be tag or digest,
|
||||||
|
// specify the properties returned with option
|
||||||
|
GetByReference(ctx context.Context, repository, reference string, option *Option) (artifact *Artifact, err error)
|
||||||
// Delete the artifact specified by ID. All tags attached to the artifact are deleted as well
|
// Delete the artifact specified by ID. All tags attached to the artifact are deleted as well
|
||||||
Delete(ctx context.Context, id int64) (err error)
|
Delete(ctx context.Context, id int64) (err error)
|
||||||
// Tags returns the tags according to the query, specify the properties returned with option
|
// Tags returns the tags according to the query, specify the properties returned with option
|
||||||
@ -207,12 +211,58 @@ func (c *controller) Get(ctx context.Context, id int64, option *Option) (*Artifa
|
|||||||
return c.assembleArtifact(ctx, art, option), nil
|
return c.assembleArtifact(ctx, art, option), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) Delete(ctx context.Context, id int64) error {
|
func (c *controller) GetByReference(ctx context.Context, repository, reference string, option *Option) (*Artifact, error) {
|
||||||
// delete artifact first in case the artifact is referenced by other artifact
|
// the reference is tag
|
||||||
if err := c.artMgr.Delete(ctx, id); err != nil {
|
if _, err := digest.Parse(reference); err != nil {
|
||||||
return err
|
return c.getByTag(ctx, repository, reference, option)
|
||||||
}
|
}
|
||||||
|
// the reference is digest
|
||||||
|
return c.getByDigest(ctx, repository, reference, option)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) getByDigest(ctx context.Context, repository, digest string, option *Option) (*Artifact, error) {
|
||||||
|
repo, err := c.repoMgr.GetByName(ctx, repository)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, artifacts, err := c.List(ctx, &q.Query{
|
||||||
|
Keywords: map[string]interface{}{
|
||||||
|
"RepositoryID": repo.RepositoryID,
|
||||||
|
"Digest": digest,
|
||||||
|
},
|
||||||
|
}, option)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(artifacts) == 0 {
|
||||||
|
return nil, ierror.New(nil).WithCode(ierror.NotFoundCode).
|
||||||
|
WithMessage("artifact %s@%s not found", repository, digest)
|
||||||
|
}
|
||||||
|
return artifacts[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) getByTag(ctx context.Context, repository, tag string, option *Option) (*Artifact, error) {
|
||||||
|
repo, err := c.repoMgr.GetByName(ctx, repository)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, tags, err := c.tagMgr.List(ctx, &q.Query{
|
||||||
|
Keywords: map[string]interface{}{
|
||||||
|
"RepositoryID": repo.RepositoryID,
|
||||||
|
"Name": tag,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(tags) == 0 {
|
||||||
|
return nil, ierror.New(nil).WithCode(ierror.NotFoundCode).
|
||||||
|
WithMessage("artifact %s:%s not found", repository, tag)
|
||||||
|
}
|
||||||
|
return c.Get(ctx, tags[0].ArtifactID, option)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) Delete(ctx context.Context, id int64) error {
|
||||||
// delete all tags that attached to the artifact
|
// delete all tags that attached to the artifact
|
||||||
_, tags, err := c.tagMgr.List(ctx, &q.Query{
|
_, tags, err := c.tagMgr.List(ctx, &q.Query{
|
||||||
Keywords: map[string]interface{}{
|
Keywords: map[string]interface{}{
|
||||||
@ -227,6 +277,11 @@ func (c *controller) Delete(ctx context.Context, id int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.artMgr.Delete(ctx, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// TODO fire delete artifact event
|
// TODO fire delete artifact event
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package artifact
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
|
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||||
"github.com/goharbor/harbor/src/pkg/q"
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
|
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
|
||||||
@ -250,6 +251,126 @@ func (c *controllerTestSuite) TestGet() {
|
|||||||
c.Equal(int64(1), art.ID)
|
c.Equal(int64(1), art.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *controllerTestSuite) TestGetByDigest() {
|
||||||
|
// not found
|
||||||
|
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
|
||||||
|
RepositoryID: 1,
|
||||||
|
}, nil)
|
||||||
|
c.artMgr.On("List").Return(0, nil, nil)
|
||||||
|
art, err := c.ctl.getByDigest(nil, "library/hello-world",
|
||||||
|
"sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180", nil)
|
||||||
|
c.Require().NotNil(err)
|
||||||
|
c.repoMgr.AssertExpectations(c.T())
|
||||||
|
c.artMgr.AssertExpectations(c.T())
|
||||||
|
c.True(ierror.IsErr(err, ierror.NotFoundCode))
|
||||||
|
|
||||||
|
// reset the mock
|
||||||
|
c.SetupTest()
|
||||||
|
|
||||||
|
// success
|
||||||
|
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
|
||||||
|
RepositoryID: 1,
|
||||||
|
}, nil)
|
||||||
|
c.artMgr.On("List").Return(1, []*artifact.Artifact{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
RepositoryID: 1,
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
art, err = c.ctl.getByDigest(nil, "library/hello-world",
|
||||||
|
"sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180", nil)
|
||||||
|
c.Require().Nil(err)
|
||||||
|
c.repoMgr.AssertExpectations(c.T())
|
||||||
|
c.artMgr.AssertExpectations(c.T())
|
||||||
|
c.Require().NotNil(art)
|
||||||
|
c.Equal(int64(1), art.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controllerTestSuite) TestGetByTag() {
|
||||||
|
// not found
|
||||||
|
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
|
||||||
|
RepositoryID: 1,
|
||||||
|
}, nil)
|
||||||
|
c.tagMgr.On("List").Return(0, nil, nil)
|
||||||
|
art, err := c.ctl.getByTag(nil, "library/hello-world", "latest", nil)
|
||||||
|
c.Require().NotNil(err)
|
||||||
|
c.repoMgr.AssertExpectations(c.T())
|
||||||
|
c.tagMgr.AssertExpectations(c.T())
|
||||||
|
c.True(ierror.IsErr(err, ierror.NotFoundCode))
|
||||||
|
|
||||||
|
// reset the mock
|
||||||
|
c.SetupTest()
|
||||||
|
|
||||||
|
// success
|
||||||
|
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
|
||||||
|
RepositoryID: 1,
|
||||||
|
}, nil)
|
||||||
|
c.tagMgr.On("List").Return(1, []*tag.Tag{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
RepositoryID: 1,
|
||||||
|
Name: "latest",
|
||||||
|
ArtifactID: 1,
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
c.artMgr.On("Get").Return(&artifact.Artifact{
|
||||||
|
ID: 1,
|
||||||
|
}, nil)
|
||||||
|
art, err = c.ctl.getByTag(nil, "library/hello-world", "latest", nil)
|
||||||
|
c.Require().Nil(err)
|
||||||
|
c.repoMgr.AssertExpectations(c.T())
|
||||||
|
c.tagMgr.AssertExpectations(c.T())
|
||||||
|
c.artMgr.AssertExpectations(c.T())
|
||||||
|
c.Require().NotNil(art)
|
||||||
|
c.Equal(int64(1), art.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controllerTestSuite) TestGetByReference() {
|
||||||
|
// reference is digest
|
||||||
|
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
|
||||||
|
RepositoryID: 1,
|
||||||
|
}, nil)
|
||||||
|
c.artMgr.On("List").Return(1, []*artifact.Artifact{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
RepositoryID: 1,
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
art, err := c.ctl.GetByReference(nil, "library/hello-world",
|
||||||
|
"sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180", nil)
|
||||||
|
c.Require().Nil(err)
|
||||||
|
c.repoMgr.AssertExpectations(c.T())
|
||||||
|
c.artMgr.AssertExpectations(c.T())
|
||||||
|
c.Require().NotNil(art)
|
||||||
|
c.Equal(int64(1), art.ID)
|
||||||
|
|
||||||
|
// reset the mock
|
||||||
|
c.SetupTest()
|
||||||
|
|
||||||
|
// reference is tag
|
||||||
|
c.repoMgr.On("GetByName").Return(&models.RepoRecord{
|
||||||
|
RepositoryID: 1,
|
||||||
|
}, nil)
|
||||||
|
c.tagMgr.On("List").Return(1, []*tag.Tag{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
RepositoryID: 1,
|
||||||
|
Name: "latest",
|
||||||
|
ArtifactID: 1,
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
c.artMgr.On("Get").Return(&artifact.Artifact{
|
||||||
|
ID: 1,
|
||||||
|
}, nil)
|
||||||
|
art, err = c.ctl.GetByReference(nil, "library/hello-world", "latest", nil)
|
||||||
|
c.Require().Nil(err)
|
||||||
|
c.repoMgr.AssertExpectations(c.T())
|
||||||
|
c.tagMgr.AssertExpectations(c.T())
|
||||||
|
c.artMgr.AssertExpectations(c.T())
|
||||||
|
c.Require().NotNil(art)
|
||||||
|
c.Equal(int64(1), art.ID)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *controllerTestSuite) TestDelete() {
|
func (c *controllerTestSuite) TestDelete() {
|
||||||
c.artMgr.On("Delete").Return(nil)
|
c.artMgr.On("Delete").Return(nil)
|
||||||
c.tagMgr.On("List").Return(0, []*tag.Tag{
|
c.tagMgr.On("List").Return(0, []*tag.Tag{
|
||||||
|
@ -17,38 +17,42 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/project"
|
||||||
"github.com/goharbor/harbor/src/pkg/q"
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
"github.com/goharbor/harbor/src/pkg/repository"
|
"github.com/goharbor/harbor/src/pkg/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Ctl is a global repository controller instance
|
// Ctl is a global repository controller instance
|
||||||
Ctl = NewController(repository.Mgr)
|
Ctl = NewController()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Controller defines the operations related with repositories
|
// Controller defines the operations related with repositories
|
||||||
type Controller interface {
|
type Controller interface {
|
||||||
// Ensure the repository specified by the "name" exists under the project,
|
// Ensure the repository specified by the "name" exists, creates it if it doesn't exist.
|
||||||
// creates it if it doesn't exist. The "name" should contain the namespace part.
|
// The "name" should contain the namespace part. The "created" will be set as true
|
||||||
// The "created" will be set as true when the repository is created
|
// when the repository is created
|
||||||
Ensure(ctx context.Context, projectID int64, name string) (created bool, id int64, err error)
|
Ensure(ctx context.Context, name string) (created bool, id int64, err error)
|
||||||
// Get the repository specified by ID
|
// Get the repository specified by ID
|
||||||
Get(ctx context.Context, id int64) (repository *models.RepoRecord, err error)
|
Get(ctx context.Context, id int64) (repository *models.RepoRecord, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewController creates an instance of the default repository controller
|
// NewController creates an instance of the default repository controller
|
||||||
func NewController(repoMgr repository.Manager) Controller {
|
func NewController() Controller {
|
||||||
return &controller{
|
return &controller{
|
||||||
repoMgr: repoMgr,
|
proMgr: project.Mgr,
|
||||||
|
repoMgr: repository.Mgr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type controller struct {
|
type controller struct {
|
||||||
|
proMgr project.Manager
|
||||||
repoMgr repository.Manager
|
repoMgr repository.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) Ensure(ctx context.Context, projectID int64, name string) (bool, int64, error) {
|
func (c *controller) Ensure(ctx context.Context, name string) (bool, int64, error) {
|
||||||
query := &q.Query{
|
query := &q.Query{
|
||||||
Keywords: map[string]interface{}{
|
Keywords: map[string]interface{}{
|
||||||
"name": name,
|
"name": name,
|
||||||
@ -64,8 +68,13 @@ func (c *controller) Ensure(ctx context.Context, projectID int64, name string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the repository doesn't exist, create it first
|
// the repository doesn't exist, create it first
|
||||||
|
projectName, _ := utils.ParseRepository(name)
|
||||||
|
project, err := c.proMgr.Get(projectName)
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
id, err := c.repoMgr.Create(ctx, &models.RepoRecord{
|
id, err := c.repoMgr.Create(ctx, &models.RepoRecord{
|
||||||
ProjectID: projectID,
|
ProjectID: project.ProjectID,
|
||||||
Name: name,
|
Name: name,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -24,12 +24,15 @@ import (
|
|||||||
type controllerTestSuite struct {
|
type controllerTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
ctl *controller
|
ctl *controller
|
||||||
|
proMgr *htesting.FakeProjectManager
|
||||||
repoMgr *htesting.FakeRepositoryManager
|
repoMgr *htesting.FakeRepositoryManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controllerTestSuite) SetupTest() {
|
func (c *controllerTestSuite) SetupTest() {
|
||||||
|
c.proMgr = &htesting.FakeProjectManager{}
|
||||||
c.repoMgr = &htesting.FakeRepositoryManager{}
|
c.repoMgr = &htesting.FakeRepositoryManager{}
|
||||||
c.ctl = &controller{
|
c.ctl = &controller{
|
||||||
|
proMgr: c.proMgr,
|
||||||
repoMgr: c.repoMgr,
|
repoMgr: c.repoMgr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,7 +46,7 @@ func (c *controllerTestSuite) TestEnsure() {
|
|||||||
Name: "library/hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
created, id, err := c.ctl.Ensure(nil, 1, "library/hello-world")
|
created, id, err := c.ctl.Ensure(nil, "library/hello-world")
|
||||||
c.Require().Nil(err)
|
c.Require().Nil(err)
|
||||||
c.repoMgr.AssertExpectations(c.T())
|
c.repoMgr.AssertExpectations(c.T())
|
||||||
c.False(created)
|
c.False(created)
|
||||||
@ -54,10 +57,14 @@ func (c *controllerTestSuite) TestEnsure() {
|
|||||||
|
|
||||||
// doesn't exist
|
// doesn't exist
|
||||||
c.repoMgr.On("List").Return(0, []*models.RepoRecord{}, nil)
|
c.repoMgr.On("List").Return(0, []*models.RepoRecord{}, nil)
|
||||||
|
c.proMgr.On("Get").Return(&models.Project{
|
||||||
|
ProjectID: 1,
|
||||||
|
}, nil)
|
||||||
c.repoMgr.On("Create").Return(1, nil)
|
c.repoMgr.On("Create").Return(1, nil)
|
||||||
created, id, err = c.ctl.Ensure(nil, 1, "library/hello-world")
|
created, id, err = c.ctl.Ensure(nil, "library/hello-world")
|
||||||
c.Require().Nil(err)
|
c.Require().Nil(err)
|
||||||
c.repoMgr.AssertExpectations(c.T())
|
c.repoMgr.AssertExpectations(c.T())
|
||||||
|
c.proMgr.AssertExpectations(c.T())
|
||||||
c.True(created)
|
c.True(created)
|
||||||
c.Equal(int64(1), id)
|
c.Equal(int64(1), id)
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,8 @@ package artifact
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
||||||
"github.com/goharbor/harbor/src/pkg/q"
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -99,24 +96,6 @@ func (m *manager) Create(ctx context.Context, artifact *Artifact) (int64, error)
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
func (m *manager) Delete(ctx context.Context, id int64) error {
|
func (m *manager) Delete(ctx context.Context, id int64) error {
|
||||||
// check whether the artifact is referenced by other artifacts
|
|
||||||
references, err := m.dao.ListReferences(ctx, &q.Query{
|
|
||||||
Keywords: map[string]interface{}{
|
|
||||||
"child_id": id,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(references) > 0 {
|
|
||||||
var ids []string
|
|
||||||
for _, reference := range references {
|
|
||||||
ids = append(ids, strconv.FormatInt(reference.ParentID, 10))
|
|
||||||
}
|
|
||||||
return ierror.PreconditionFailedError(nil).
|
|
||||||
WithMessage("artifact %d is referenced by other artifacts: %s", id, strings.Join(ids, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete references
|
// delete references
|
||||||
if err := m.dao.DeleteReferences(ctx, id); err != nil {
|
if err := m.dao.DeleteReferences(ctx, id); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -16,7 +16,6 @@ package artifact
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
||||||
"github.com/goharbor/harbor/src/pkg/q"
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
@ -180,28 +179,9 @@ func (m *managerTestSuite) TestCreate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerTestSuite) TestDelete() {
|
func (m *managerTestSuite) TestDelete() {
|
||||||
// referenced by other artifacts, delete failed
|
|
||||||
m.dao.On("ListReferences").Return([]*dao.ArtifactReference{
|
|
||||||
{
|
|
||||||
ParentID: 1,
|
|
||||||
ChildID: 1,
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
err := m.mgr.Delete(nil, 1)
|
|
||||||
m.Require().NotNil(err)
|
|
||||||
m.dao.AssertExpectations(m.T())
|
|
||||||
e, ok := err.(*ierror.Error)
|
|
||||||
m.Require().True(ok)
|
|
||||||
m.Equal(ierror.PreconditionCode, e.Code)
|
|
||||||
|
|
||||||
// reset the mock
|
|
||||||
m.SetupTest()
|
|
||||||
|
|
||||||
// // referenced by no artifacts
|
|
||||||
m.dao.On("ListReferences").Return([]*dao.ArtifactReference{}, nil)
|
|
||||||
m.dao.On("Delete", mock.Anything).Return(nil)
|
m.dao.On("Delete", mock.Anything).Return(nil)
|
||||||
m.dao.On("DeleteReferences").Return(nil)
|
m.dao.On("DeleteReferences").Return(nil)
|
||||||
err = m.mgr.Delete(nil, 1)
|
err := m.mgr.Delete(nil, 1)
|
||||||
m.Require().Nil(err)
|
m.Require().Nil(err)
|
||||||
m.dao.AssertExpectations(m.T())
|
m.dao.AssertExpectations(m.T())
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
|
|
||||||
// Handle generates the HTTP status code and error payload and writes them to the response
|
// Handle generates the HTTP status code and error payload and writes them to the response
|
||||||
func Handle(w http.ResponseWriter, req *http.Request, err error) {
|
func Handle(w http.ResponseWriter, req *http.Request, err error) {
|
||||||
log.Errorf("failed to handle the request %s: %v", req.URL, err)
|
log.Errorf("failed to handle the request %s %s: %v", req.Method, req.URL, err)
|
||||||
statusCode, payload := serror.APIError(err)
|
statusCode, payload := serror.APIError(err)
|
||||||
w.WriteHeader(statusCode)
|
w.WriteHeader(statusCode)
|
||||||
w.Write([]byte(payload))
|
w.Write([]byte(payload))
|
||||||
|
@ -15,11 +15,6 @@
|
|||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/pkg/project"
|
|
||||||
pkg_repo "github.com/goharbor/harbor/src/pkg/repository"
|
pkg_repo "github.com/goharbor/harbor/src/pkg/repository"
|
||||||
pkg_tag "github.com/goharbor/harbor/src/pkg/tag"
|
pkg_tag "github.com/goharbor/harbor/src/pkg/tag"
|
||||||
"github.com/goharbor/harbor/src/server/middleware"
|
"github.com/goharbor/harbor/src/server/middleware"
|
||||||
@ -32,6 +27,9 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/server/registry/manifest"
|
"github.com/goharbor/harbor/src/server/registry/manifest"
|
||||||
"github.com/goharbor/harbor/src/server/registry/tag"
|
"github.com/goharbor/harbor/src/server/registry/tag"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New return the registry instance to handle the registry APIs
|
// New return the registry instance to handle the registry APIs
|
||||||
@ -53,9 +51,9 @@ func New(url *url.URL) http.Handler {
|
|||||||
// handle manifest
|
// handle manifest
|
||||||
// TODO maybe we should split it into several sub routers based on the method
|
// TODO maybe we should split it into several sub routers based on the method
|
||||||
manifestRouter := rootRouter.Path("/v2/{name:.*}/manifests/{reference}").Subrouter()
|
manifestRouter := rootRouter.Path("/v2/{name:.*}/manifests/{reference}").Subrouter()
|
||||||
manifestRouter.NewRoute().Methods(http.MethodGet).Handler(middleware.WithMiddlewares(manifest.NewHandler(project.Mgr, proxy), manifestinfo.Middleware(), regtoken.Middleware()))
|
manifestRouter.NewRoute().Methods(http.MethodGet).Handler(middleware.WithMiddlewares(manifest.NewHandler(proxy), manifestinfo.Middleware(), regtoken.Middleware()))
|
||||||
manifestRouter.NewRoute().Methods(http.MethodHead).Handler(manifest.NewHandler(project.Mgr, proxy))
|
manifestRouter.NewRoute().Methods(http.MethodHead).Handler(manifest.NewHandler(proxy))
|
||||||
manifestRouter.NewRoute().Methods(http.MethodDelete).Handler(middleware.WithMiddlewares(manifest.NewHandler(project.Mgr, proxy), readonly.Middleware(), manifestinfo.Middleware(), immutable.MiddlewareDelete()))
|
manifestRouter.NewRoute().Methods(http.MethodDelete).Handler(middleware.WithMiddlewares(manifest.NewHandler(proxy), readonly.Middleware(), manifestinfo.Middleware(), immutable.MiddlewareDelete()))
|
||||||
|
|
||||||
// handle blob
|
// handle blob
|
||||||
// as we need to apply middleware to the blob requests, so create a sub router to handle the blob APIs
|
// as we need to apply middleware to the blob requests, so create a sub router to handle the blob APIs
|
||||||
|
@ -17,37 +17,35 @@ package manifest
|
|||||||
import (
|
import (
|
||||||
"github.com/goharbor/harbor/src/api/artifact"
|
"github.com/goharbor/harbor/src/api/artifact"
|
||||||
"github.com/goharbor/harbor/src/api/repository"
|
"github.com/goharbor/harbor/src/api/repository"
|
||||||
"github.com/goharbor/harbor/src/common/utils"
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
"github.com/goharbor/harbor/src/internal"
|
"github.com/goharbor/harbor/src/internal"
|
||||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/project"
|
|
||||||
"github.com/goharbor/harbor/src/server/registry/error"
|
"github.com/goharbor/harbor/src/server/registry/error"
|
||||||
"github.com/goharbor/harbor/src/server/router"
|
"github.com/goharbor/harbor/src/server/router"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewHandler returns the handler to handler manifest requests
|
// NewHandler returns the handler to handler manifest requests
|
||||||
// TODO the parameters aren't required, use the global variables instead
|
func NewHandler(proxy *httputil.ReverseProxy) http.Handler {
|
||||||
func NewHandler(proMgr project.Manager, proxy *httputil.ReverseProxy) http.Handler {
|
|
||||||
return &handler{
|
return &handler{
|
||||||
proMgr: proMgr,
|
repoCtl: repository.Ctl,
|
||||||
proxy: proxy,
|
artCtl: artifact.Ctl,
|
||||||
|
proxy: proxy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
proMgr project.Manager
|
repoCtl repository.Controller
|
||||||
proxy *httputil.ReverseProxy
|
artCtl artifact.Controller
|
||||||
|
proxy *httputil.ReverseProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case http.MethodHead:
|
case http.MethodHead, http.MethodGet:
|
||||||
h.head(w, req)
|
|
||||||
case http.MethodGet:
|
|
||||||
h.get(w, req)
|
h.get(w, req)
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
h.delete(w, req)
|
h.delete(w, req)
|
||||||
@ -56,42 +54,59 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the artifact exist before proxying the request to the backend registry
|
|
||||||
func (h *handler) head(w http.ResponseWriter, req *http.Request) {
|
|
||||||
// TODO check the existence
|
|
||||||
h.proxy.ServeHTTP(w, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the artifact exist before proxying the request to the backend registry
|
// make sure the artifact exist before proxying the request to the backend registry
|
||||||
func (h *handler) get(w http.ResponseWriter, req *http.Request) {
|
func (h *handler) get(w http.ResponseWriter, req *http.Request) {
|
||||||
// TODO check the existence
|
// check the existence in the database first
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
reference := vars["reference"]
|
||||||
|
artifact, err := h.artCtl.GetByReference(req.Context(), vars["name"], reference, nil)
|
||||||
|
if err != nil {
|
||||||
|
error.Handle(w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// the reference is tag, replace it with digest
|
||||||
|
if _, err = digest.Parse(reference); err != nil {
|
||||||
|
req = req.Clone(req.Context())
|
||||||
|
req.URL.Path = strings.TrimSuffix(req.URL.Path, reference) + artifact.Digest
|
||||||
|
req.URL.RawPath = ""
|
||||||
|
req.URL.RawPath = req.URL.EscapedPath()
|
||||||
|
}
|
||||||
h.proxy.ServeHTTP(w, req)
|
h.proxy.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
// TODO fire event(only for GET method), add access log in the event handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) delete(w http.ResponseWriter, req *http.Request) {
|
func (h *handler) delete(w http.ResponseWriter, req *http.Request) {
|
||||||
// TODO implement, just delete from database
|
// just delete the artifact from database
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
artifact, err := h.artCtl.GetByReference(req.Context(), vars["name"], vars["reference"], nil)
|
||||||
|
if err != nil {
|
||||||
|
error.Handle(w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = h.artCtl.Delete(req.Context(), artifact.ID); err != nil {
|
||||||
|
error.Handle(w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO fire event, add access log in the event handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) put(w http.ResponseWriter, req *http.Request) {
|
func (h *handler) put(w http.ResponseWriter, req *http.Request) {
|
||||||
repositoryName, err := router.Param(req.Context(), ":splat")
|
repository, err := router.Param(req.Context(), ":splat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
error.Handle(w, req, err)
|
error.Handle(w, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
projectName, _ := utils.ParseRepository(repositoryName)
|
reference, err := router.Param(req.Context(), ":reference")
|
||||||
project, err := h.proMgr.Get(projectName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
error.Handle(w, req, err)
|
error.Handle(w, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if project == nil {
|
|
||||||
error.Handle(w, req,
|
|
||||||
ierror.NotFoundError(nil).WithMessage("project %s not found", projectName))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the repository exist before pushing the manifest
|
// make sure the repository exist before pushing the manifest
|
||||||
_, repositoryID, err := repository.Ctl.Ensure(req.Context(), project.ProjectID, repositoryName)
|
_, repositoryID, err := h.repoCtl.Ensure(req.Context(), repository)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
error.Handle(w, req, err)
|
error.Handle(w, req, err)
|
||||||
return
|
return
|
||||||
@ -112,23 +127,14 @@ func (h *handler) put(w http.ResponseWriter, req *http.Request) {
|
|||||||
// https://github.com/docker/distribution/issues/2625
|
// https://github.com/docker/distribution/issues/2625
|
||||||
|
|
||||||
var tags []string
|
var tags []string
|
||||||
var dgt string
|
dgt := reference
|
||||||
reference, err := router.Param(req.Context(), ":reference")
|
// the reference is tag, get the digest from the response header
|
||||||
if err != nil {
|
if _, err = digest.Parse(reference); err != nil {
|
||||||
error.Handle(w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dg, err := digest.Parse(reference)
|
|
||||||
if err == nil {
|
|
||||||
// the reference is digest
|
|
||||||
dgt = dg.String()
|
|
||||||
} else {
|
|
||||||
// the reference is tag, get the digest from the response header
|
|
||||||
dgt = buffer.Header().Get("Docker-Content-Digest")
|
dgt = buffer.Header().Get("Docker-Content-Digest")
|
||||||
tags = append(tags, reference)
|
tags = append(tags, reference)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = artifact.Ctl.Ensure(req.Context(), repositoryID, dgt, tags...)
|
_, _, err = h.artCtl.Ensure(req.Context(), repositoryID, dgt, tags...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
error.Handle(w, req, err)
|
error.Handle(w, req, err)
|
||||||
return
|
return
|
||||||
|
@ -16,7 +16,6 @@ package registry
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
"github.com/goharbor/harbor/src/core/config"
|
||||||
"github.com/goharbor/harbor/src/pkg/project"
|
|
||||||
"github.com/goharbor/harbor/src/server/middleware/immutable"
|
"github.com/goharbor/harbor/src/server/middleware/immutable"
|
||||||
"github.com/goharbor/harbor/src/server/middleware/manifestinfo"
|
"github.com/goharbor/harbor/src/server/middleware/manifestinfo"
|
||||||
"github.com/goharbor/harbor/src/server/middleware/readonly"
|
"github.com/goharbor/harbor/src/server/middleware/readonly"
|
||||||
@ -41,5 +40,5 @@ func RegisterRoutes() {
|
|||||||
Middleware(readonly.Middleware()).
|
Middleware(readonly.Middleware()).
|
||||||
Middleware(manifestinfo.Middleware()).
|
Middleware(manifestinfo.Middleware()).
|
||||||
Middleware(immutable.MiddlewarePush()).
|
Middleware(immutable.MiddlewarePush()).
|
||||||
Handler(manifest.NewHandler(project.Mgr, proxy))
|
Handler(manifest.NewHandler(proxy))
|
||||||
}
|
}
|
||||||
|
45
src/testing/project_manager.go
Normal file
45
src/testing/project_manager.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// 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 testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FakeProjectManager is a fake project manager that implement src/pkg/project.Manager interface
|
||||||
|
type FakeProjectManager struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// List ...
|
||||||
|
func (f *FakeProjectManager) List(query ...*models.ProjectQueryParam) ([]*models.Project, error) {
|
||||||
|
args := f.Called()
|
||||||
|
var projects []*models.Project
|
||||||
|
if args.Get(0) != nil {
|
||||||
|
projects = args.Get(0).([]*models.Project)
|
||||||
|
}
|
||||||
|
return projects, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ...
|
||||||
|
func (f *FakeProjectManager) Get(interface{}) (*models.Project, error) {
|
||||||
|
args := f.Called()
|
||||||
|
var project *models.Project
|
||||||
|
if args.Get(0) != nil {
|
||||||
|
project = args.Get(0).(*models.Project)
|
||||||
|
}
|
||||||
|
return project, args.Error(1)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user