fix catalog api issue (#11666)

The v2 catalog API needs to filter out the empty repository and the repository which artifacts are all with no tags.

1,In v2.0.0, Harbor does not delete repository even there is no artifact, it's different with v1.10.0
2, Compares with docker distribution, it doesn't return the respository with untagged images.

Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
Wang Yan 2020-04-20 23:37:16 +08:00 committed by GitHub
parent b747fdb68d
commit ff2a7e61c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 183 additions and 3 deletions

View File

@ -15,10 +15,13 @@
package registry
import (
"context"
"encoding/json"
"fmt"
"github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/controller/repository"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/q"
serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/registry/util"
"net/http"
@ -29,11 +32,13 @@ import (
func newRepositoryHandler() http.Handler {
return &repositoryHandler{
repoCtl: repository.Ctl,
artCtl: artifact.Ctl,
}
}
type repositoryHandler struct {
repoCtl repository.Controller
artCtl artifact.Controller
}
func (r *repositoryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
@ -54,7 +59,6 @@ func (r *repositoryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request)
repoNames := make([]string, 0)
// get all repositories
// ToDo filter out the untagged repos
repoRecords, err := r.repoCtl.List(req.Context(), nil)
if err != nil {
serror.SendError(w, err)
@ -64,8 +68,15 @@ func (r *repositoryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request)
r.sendResponse(w, req, repoNames)
return
}
for _, r := range repoRecords {
repoNames = append(repoNames, r.Name)
for _, repo := range repoRecords {
valid, err := r.validateRepo(req.Context(), repo.RepositoryID)
if err != nil {
serror.SendError(w, err)
return
}
if valid {
repoNames = append(repoNames, repo.Name)
}
}
sort.Strings(repoNames)
if !withN {
@ -129,6 +140,34 @@ func (r *repositoryHandler) sendResponse(w http.ResponseWriter, req *http.Reques
}
}
// empty repo and all of artifacts are untagged should be filtered out.
func (r *repositoryHandler) validateRepo(ctx context.Context, repositoryID int64) (bool, error) {
arts, err := r.artCtl.List(ctx, &q.Query{
Keywords: map[string]interface{}{
"RepositoryID": repositoryID,
},
}, &artifact.Option{
WithTag: true,
})
if err != nil {
return false, err
}
// empty repo
if len(arts) == 0 {
return false, nil
}
for _, art := range arts {
if len(art.Tags) != 0 {
return true, nil
}
}
// if all of artifact are untagged, filter out
return false, nil
}
type catalogAPIResponse struct {
Repositories []string `json:"repositories"`
}

View File

@ -17,8 +17,14 @@ package registry
import (
"encoding/json"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/controller/repository"
"github.com/goharbor/harbor/src/controller/tag"
pkg_art "github.com/goharbor/harbor/src/pkg/artifact"
model_tag "github.com/goharbor/harbor/src/pkg/tag/model/tag"
artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact"
repotesting "github.com/goharbor/harbor/src/testing/controller/repository"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/stretchr/testify/suite"
"net/http"
"net/http/httptest"
@ -28,16 +34,21 @@ import (
type catalogTestSuite struct {
suite.Suite
originalRepoCtl repository.Controller
originalArtCtl artifact.Controller
repoCtl *repotesting.FakeController
artCtl *artifacttesting.Controller
}
func (c *catalogTestSuite) SetupSuite() {
c.originalRepoCtl = repository.Ctl
c.originalArtCtl = artifact.Ctl
}
func (c *catalogTestSuite) SetupTest() {
c.repoCtl = &repotesting.FakeController{}
repository.Ctl = c.repoCtl
c.artCtl = &artifacttesting.Controller{}
artifact.Ctl = c.artCtl
}
func (c *catalogTestSuite) TearDownTest() {
@ -45,6 +56,7 @@ func (c *catalogTestSuite) TearDownTest() {
func (c *catalogTestSuite) TearDownSuite() {
repository.Ctl = c.originalRepoCtl
artifact.Ctl = c.originalArtCtl
}
func (c *catalogTestSuite) TestCatalog() {
@ -61,6 +73,22 @@ func (c *catalogTestSuite) TestCatalog() {
Name: "busybox",
},
}, nil)
mock.OnAnything(c.artCtl, "List").Return([]*artifact.Artifact{
{
Artifact: pkg_art.Artifact{
ProjectID: 1,
RepositoryID: 1,
},
Tags: []*tag.Tag{
{
Tag: model_tag.Tag{
RepositoryID: 1,
ArtifactID: 1,
},
},
},
},
}, nil)
w = httptest.NewRecorder()
newRepositoryHandler().ServeHTTP(w, req)
c.Equal(http.StatusOK, w.Code)
@ -87,6 +115,22 @@ func (c *catalogTestSuite) TestCatalogPaginationN1() {
Name: "busybox",
},
}, nil)
mock.OnAnything(c.artCtl, "List").Return([]*artifact.Artifact{
{
Artifact: pkg_art.Artifact{
ProjectID: 1,
RepositoryID: 1,
},
Tags: []*tag.Tag{
{
Tag: model_tag.Tag{
RepositoryID: 1,
ArtifactID: 1,
},
},
},
},
}, nil)
w = httptest.NewRecorder()
newRepositoryHandler().ServeHTTP(w, req)
c.Equal(http.StatusOK, w.Code)
@ -114,6 +158,22 @@ func (c *catalogTestSuite) TestCatalogPaginationN2() {
Name: "busybox",
},
}, nil)
mock.OnAnything(c.artCtl, "List").Return([]*artifact.Artifact{
{
Artifact: pkg_art.Artifact{
ProjectID: 1,
RepositoryID: 1,
},
Tags: []*tag.Tag{
{
Tag: model_tag.Tag{
RepositoryID: 1,
ArtifactID: 1,
},
},
},
},
}, nil)
w = httptest.NewRecorder()
newRepositoryHandler().ServeHTTP(w, req)
c.Equal(http.StatusOK, w.Code)
@ -141,6 +201,22 @@ func (c *catalogTestSuite) TestCatalogPaginationN3() {
Name: "busybox",
},
}, nil)
mock.OnAnything(c.artCtl, "List").Return([]*artifact.Artifact{
{
Artifact: pkg_art.Artifact{
ProjectID: 1,
RepositoryID: 1,
},
Tags: []*tag.Tag{
{
Tag: model_tag.Tag{
RepositoryID: 1,
ArtifactID: 1,
},
},
},
},
}, nil)
w = httptest.NewRecorder()
newRepositoryHandler().ServeHTTP(w, req)
c.Equal(http.StatusOK, w.Code)
@ -154,6 +230,71 @@ func (c *catalogTestSuite) TestCatalogPaginationN3() {
c.Equal("hello-world", ctlg.Repositories[0])
}
func (c *catalogTestSuite) TestCatalogUntaggedArtifact() {
c.SetupTest()
req := httptest.NewRequest(http.MethodGet, "/v2/_catalog", nil)
var w *httptest.ResponseRecorder
c.repoCtl.On("List").Return([]*models.RepoRecord{
{
RepositoryID: 1,
Name: "hello-world",
},
{
RepositoryID: 2,
Name: "busybox",
},
}, nil)
// untagged artifact
mock.OnAnything(c.artCtl, "List").Return([]*artifact.Artifact{
{
Artifact: pkg_art.Artifact{
ProjectID: 1,
RepositoryID: 1,
},
},
}, nil)
w = httptest.NewRecorder()
newRepositoryHandler().ServeHTTP(w, req)
c.Equal(http.StatusOK, w.Code)
var ctlg struct {
Repositories []string `json:"repositories"`
}
decoder := json.NewDecoder(w.Body)
err := decoder.Decode(&ctlg)
c.Nil(err)
c.Equal(0, len(ctlg.Repositories))
}
func (c *catalogTestSuite) TestCatalogEmptyRepo() {
c.SetupTest()
req := httptest.NewRequest(http.MethodGet, "/v2/_catalog", nil)
var w *httptest.ResponseRecorder
c.repoCtl.On("List").Return([]*models.RepoRecord{
{
RepositoryID: 1,
Name: "hello-world",
},
{
RepositoryID: 2,
Name: "busybox",
},
}, nil)
// empty repository
mock.OnAnything(c.artCtl, "List").Return([]*artifact.Artifact{
{},
}, nil)
w = httptest.NewRecorder()
newRepositoryHandler().ServeHTTP(w, req)
c.Equal(http.StatusOK, w.Code)
var ctlg struct {
Repositories []string `json:"repositories"`
}
decoder := json.NewDecoder(w.Body)
err := decoder.Decode(&ctlg)
c.Nil(err)
c.Equal(0, len(ctlg.Repositories))
}
func TestCatalogTestSuite(t *testing.T) {
suite.Run(t, &catalogTestSuite{})
}