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/schema1"
|
||||
"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"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -54,7 +59,7 @@ type fetcher struct{}
|
||||
// TODO re-implement it based on OCI registry driver
|
||||
func (f *fetcher) FetchManifest(repository, digest string) (string, []byte, error) {
|
||||
// TODO read from cache first
|
||||
client, err := coreutils.NewRepositoryClientForLocal("admin", repository)
|
||||
client, err := newRepositoryClient(repository)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
@ -76,3 +81,19 @@ func (f *fetcher) FetchLayer(repository, digest string) ([]byte, error) {
|
||||
defer reader.Close()
|
||||
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"
|
||||
"fmt"
|
||||
"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"
|
||||
// register chart resolver
|
||||
_ "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)
|
||||
// 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 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(ctx context.Context, id int64) (err error)
|
||||
// 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
|
||||
}
|
||||
|
||||
func (c *controller) Delete(ctx context.Context, id int64) error {
|
||||
// delete artifact first in case the artifact is referenced by other artifact
|
||||
if err := c.artMgr.Delete(ctx, id); err != nil {
|
||||
return err
|
||||
func (c *controller) GetByReference(ctx context.Context, repository, reference string, option *Option) (*Artifact, error) {
|
||||
// the reference is tag
|
||||
if _, err := digest.Parse(reference); err != nil {
|
||||
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
|
||||
_, tags, err := c.tagMgr.List(ctx, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
@ -227,6 +277,11 @@ func (c *controller) Delete(ctx context.Context, id int64) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.artMgr.Delete(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO fire delete artifact event
|
||||
return nil
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ package artifact
|
||||
import (
|
||||
"context"
|
||||
"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/q"
|
||||
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
|
||||
@ -250,6 +251,126 @@ func (c *controllerTestSuite) TestGet() {
|
||||
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() {
|
||||
c.artMgr.On("Delete").Return(nil)
|
||||
c.tagMgr.On("List").Return(0, []*tag.Tag{
|
||||
|
@ -17,38 +17,42 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
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/repository"
|
||||
)
|
||||
|
||||
var (
|
||||
// Ctl is a global repository controller instance
|
||||
Ctl = NewController(repository.Mgr)
|
||||
Ctl = NewController()
|
||||
)
|
||||
|
||||
// Controller defines the operations related with repositories
|
||||
type Controller interface {
|
||||
// Ensure the repository specified by the "name" exists under the project,
|
||||
// creates it if it doesn't exist. The "name" should contain the namespace part.
|
||||
// The "created" will be set as true when the repository is created
|
||||
Ensure(ctx context.Context, projectID int64, name string) (created bool, id int64, err error)
|
||||
// Ensure the repository specified by the "name" exists, creates it if it doesn't exist.
|
||||
// The "name" should contain the namespace part. The "created" will be set as true
|
||||
// when the repository is created
|
||||
Ensure(ctx context.Context, name string) (created bool, id int64, err error)
|
||||
// Get the repository specified by ID
|
||||
Get(ctx context.Context, id int64) (repository *models.RepoRecord, err error)
|
||||
}
|
||||
|
||||
// NewController creates an instance of the default repository controller
|
||||
func NewController(repoMgr repository.Manager) Controller {
|
||||
func NewController() Controller {
|
||||
return &controller{
|
||||
repoMgr: repoMgr,
|
||||
proMgr: project.Mgr,
|
||||
repoMgr: repository.Mgr,
|
||||
}
|
||||
}
|
||||
|
||||
type controller struct {
|
||||
proMgr project.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{
|
||||
Keywords: map[string]interface{}{
|
||||
"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
|
||||
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{
|
||||
ProjectID: projectID,
|
||||
ProjectID: project.ProjectID,
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -24,12 +24,15 @@ import (
|
||||
type controllerTestSuite struct {
|
||||
suite.Suite
|
||||
ctl *controller
|
||||
proMgr *htesting.FakeProjectManager
|
||||
repoMgr *htesting.FakeRepositoryManager
|
||||
}
|
||||
|
||||
func (c *controllerTestSuite) SetupTest() {
|
||||
c.proMgr = &htesting.FakeProjectManager{}
|
||||
c.repoMgr = &htesting.FakeRepositoryManager{}
|
||||
c.ctl = &controller{
|
||||
proMgr: c.proMgr,
|
||||
repoMgr: c.repoMgr,
|
||||
}
|
||||
}
|
||||
@ -43,7 +46,7 @@ func (c *controllerTestSuite) TestEnsure() {
|
||||
Name: "library/hello-world",
|
||||
},
|
||||
}, 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.repoMgr.AssertExpectations(c.T())
|
||||
c.False(created)
|
||||
@ -54,10 +57,14 @@ func (c *controllerTestSuite) TestEnsure() {
|
||||
|
||||
// doesn't exist
|
||||
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)
|
||||
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.repoMgr.AssertExpectations(c.T())
|
||||
c.proMgr.AssertExpectations(c.T())
|
||||
c.True(created)
|
||||
c.Equal(int64(1), id)
|
||||
}
|
||||
|
@ -16,11 +16,8 @@ package artifact
|
||||
|
||||
import (
|
||||
"context"
|
||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/q"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -99,24 +96,6 @@ func (m *manager) Create(ctx context.Context, artifact *Artifact) (int64, error)
|
||||
return id, nil
|
||||
}
|
||||
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
|
||||
if err := m.dao.DeleteReferences(ctx, id); err != nil {
|
||||
return err
|
||||
|
@ -16,7 +16,6 @@ package artifact
|
||||
|
||||
import (
|
||||
"context"
|
||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||
"github.com/goharbor/harbor/src/pkg/artifact/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/q"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@ -180,28 +179,9 @@ func (m *managerTestSuite) TestCreate() {
|
||||
}
|
||||
|
||||
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("DeleteReferences").Return(nil)
|
||||
err = m.mgr.Delete(nil, 1)
|
||||
err := m.mgr.Delete(nil, 1)
|
||||
m.Require().Nil(err)
|
||||
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
|
||||
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)
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write([]byte(payload))
|
||||
|
@ -15,11 +15,6 @@
|
||||
package registry
|
||||
|
||||
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_tag "github.com/goharbor/harbor/src/pkg/tag"
|
||||
"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/tag"
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// New return the registry instance to handle the registry APIs
|
||||
@ -53,9 +51,9 @@ func New(url *url.URL) http.Handler {
|
||||
// handle manifest
|
||||
// TODO maybe we should split it into several sub routers based on the method
|
||||
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.MethodHead).Handler(manifest.NewHandler(project.Mgr, proxy))
|
||||
manifestRouter.NewRoute().Methods(http.MethodDelete).Handler(middleware.WithMiddlewares(manifest.NewHandler(project.Mgr, proxy), readonly.Middleware(), manifestinfo.Middleware(), immutable.MiddlewareDelete()))
|
||||
manifestRouter.NewRoute().Methods(http.MethodGet).Handler(middleware.WithMiddlewares(manifest.NewHandler(proxy), manifestinfo.Middleware(), regtoken.Middleware()))
|
||||
manifestRouter.NewRoute().Methods(http.MethodHead).Handler(manifest.NewHandler(proxy))
|
||||
manifestRouter.NewRoute().Methods(http.MethodDelete).Handler(middleware.WithMiddlewares(manifest.NewHandler(proxy), readonly.Middleware(), manifestinfo.Middleware(), immutable.MiddlewareDelete()))
|
||||
|
||||
// handle blob
|
||||
// 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 (
|
||||
"github.com/goharbor/harbor/src/api/artifact"
|
||||
"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/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/router"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewHandler returns the handler to handler manifest requests
|
||||
// TODO the parameters aren't required, use the global variables instead
|
||||
func NewHandler(proMgr project.Manager, proxy *httputil.ReverseProxy) http.Handler {
|
||||
func NewHandler(proxy *httputil.ReverseProxy) http.Handler {
|
||||
return &handler{
|
||||
proMgr: proMgr,
|
||||
proxy: proxy,
|
||||
repoCtl: repository.Ctl,
|
||||
artCtl: artifact.Ctl,
|
||||
proxy: proxy,
|
||||
}
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
proMgr project.Manager
|
||||
proxy *httputil.ReverseProxy
|
||||
repoCtl repository.Controller
|
||||
artCtl artifact.Controller
|
||||
proxy *httputil.ReverseProxy
|
||||
}
|
||||
|
||||
func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
switch req.Method {
|
||||
case http.MethodHead:
|
||||
h.head(w, req)
|
||||
case http.MethodGet:
|
||||
case http.MethodHead, http.MethodGet:
|
||||
h.get(w, req)
|
||||
case http.MethodDelete:
|
||||
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
|
||||
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)
|
||||
|
||||
// TODO fire event(only for GET method), add access log in the event handler
|
||||
}
|
||||
|
||||
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) {
|
||||
repositoryName, err := router.Param(req.Context(), ":splat")
|
||||
repository, err := router.Param(req.Context(), ":splat")
|
||||
if err != nil {
|
||||
error.Handle(w, req, err)
|
||||
return
|
||||
}
|
||||
projectName, _ := utils.ParseRepository(repositoryName)
|
||||
project, err := h.proMgr.Get(projectName)
|
||||
reference, err := router.Param(req.Context(), ":reference")
|
||||
if err != nil {
|
||||
error.Handle(w, req, err)
|
||||
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
|
||||
_, repositoryID, err := repository.Ctl.Ensure(req.Context(), project.ProjectID, repositoryName)
|
||||
_, repositoryID, err := h.repoCtl.Ensure(req.Context(), repository)
|
||||
if err != nil {
|
||||
error.Handle(w, req, err)
|
||||
return
|
||||
@ -112,23 +127,14 @@ func (h *handler) put(w http.ResponseWriter, req *http.Request) {
|
||||
// https://github.com/docker/distribution/issues/2625
|
||||
|
||||
var tags []string
|
||||
var dgt string
|
||||
reference, err := router.Param(req.Context(), ":reference")
|
||||
if 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 := reference
|
||||
// the reference is tag, get the digest from the response header
|
||||
if _, err = digest.Parse(reference); err != nil {
|
||||
dgt = buffer.Header().Get("Docker-Content-Digest")
|
||||
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 {
|
||||
error.Handle(w, req, err)
|
||||
return
|
||||
|
@ -16,7 +16,6 @@ package registry
|
||||
|
||||
import (
|
||||
"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/manifestinfo"
|
||||
"github.com/goharbor/harbor/src/server/middleware/readonly"
|
||||
@ -41,5 +40,5 @@ func RegisterRoutes() {
|
||||
Middleware(readonly.Middleware()).
|
||||
Middleware(manifestinfo.Middleware()).
|
||||
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