From d0ac70d26cf743f5f88e0b36d529c0d38525f20a Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Tue, 21 Jan 2020 14:11:56 +0800 Subject: [PATCH] Implement the get/delete handler for registry API Implement the get/delete handler for registry API Signed-off-by: Wenkai Yin --- src/api/artifact/abstractor/blob/fetcher.go | 23 +++- src/api/artifact/controller.go | 65 ++++++++++- src/api/artifact/controller_test.go | 121 ++++++++++++++++++++ src/api/repository/controller.go | 27 +++-- src/api/repository/controller_test.go | 11 +- src/pkg/artifact/manager.go | 21 ---- src/pkg/artifact/manager_test.go | 22 +--- src/server/registry/error/error.go | 2 +- src/server/registry/handler.go | 14 +-- src/server/registry/manifest/manifest.go | 90 ++++++++------- src/server/registry/route.go | 3 +- src/testing/project_manager.go | 45 ++++++++ 12 files changed, 332 insertions(+), 112 deletions(-) create mode 100644 src/testing/project_manager.go diff --git a/src/api/artifact/abstractor/blob/fetcher.go b/src/api/artifact/abstractor/blob/fetcher.go index 3cb938534..58cb03a14 100644 --- a/src/api/artifact/abstractor/blob/fetcher.go +++ b/src/api/artifact/abstractor/blob/fetcher.go @@ -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) +} diff --git a/src/api/artifact/controller.go b/src/api/artifact/controller.go index 47a0ccca7..00a4fc63f 100644 --- a/src/api/artifact/controller.go +++ b/src/api/artifact/controller.go @@ -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 } diff --git a/src/api/artifact/controller_test.go b/src/api/artifact/controller_test.go index 8a53dd77a..8148b51fd 100644 --- a/src/api/artifact/controller_test.go +++ b/src/api/artifact/controller_test.go @@ -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{ diff --git a/src/api/repository/controller.go b/src/api/repository/controller.go index e8229fcfc..8c8c82a36 100644 --- a/src/api/repository/controller.go +++ b/src/api/repository/controller.go @@ -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 { diff --git a/src/api/repository/controller_test.go b/src/api/repository/controller_test.go index 7d9b21810..b18aea370 100644 --- a/src/api/repository/controller_test.go +++ b/src/api/repository/controller_test.go @@ -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) } diff --git a/src/pkg/artifact/manager.go b/src/pkg/artifact/manager.go index 5de8236a6..13788aa82 100644 --- a/src/pkg/artifact/manager.go +++ b/src/pkg/artifact/manager.go @@ -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 diff --git a/src/pkg/artifact/manager_test.go b/src/pkg/artifact/manager_test.go index c8a161432..d0ac5f9e2 100644 --- a/src/pkg/artifact/manager_test.go +++ b/src/pkg/artifact/manager_test.go @@ -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()) } diff --git a/src/server/registry/error/error.go b/src/server/registry/error/error.go index a4d38a53a..969b1390c 100644 --- a/src/server/registry/error/error.go +++ b/src/server/registry/error/error.go @@ -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)) diff --git a/src/server/registry/handler.go b/src/server/registry/handler.go index e43d2e725..d323f1b10 100644 --- a/src/server/registry/handler.go +++ b/src/server/registry/handler.go @@ -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 diff --git a/src/server/registry/manifest/manifest.go b/src/server/registry/manifest/manifest.go index 9052f7c3e..23069e69a 100644 --- a/src/server/registry/manifest/manifest.go +++ b/src/server/registry/manifest/manifest.go @@ -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 diff --git a/src/server/registry/route.go b/src/server/registry/route.go index ecb75a02e..2cd738c3b 100644 --- a/src/server/registry/route.go +++ b/src/server/registry/route.go @@ -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)) } diff --git a/src/testing/project_manager.go b/src/testing/project_manager.go new file mode 100644 index 000000000..92dd42ab0 --- /dev/null +++ b/src/testing/project_manager.go @@ -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) +}