Merge pull request #10756 from ywk253100/200218_repo_api

Implement delete/update repository API
This commit is contained in:
Wenkai Yin(尹文开) 2020-02-20 09:06:37 +08:00 committed by GitHub
commit beddef6873
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 451 additions and 155 deletions

View File

@ -16,6 +16,100 @@ securityDefinitions:
basicAuth: basicAuth:
type: basic type: basic
paths: paths:
/projects/{project_name}/repositories:
get:
summary: List repositories
description: List repositories of the specified project
tags:
- repository
operationId: listRepositories
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/projectName'
- $ref: '#/parameters/page'
- $ref: '#/parameters/pageSize'
- name: name
in: query
description: Query the repositories by name
type: string
required: false
responses:
'200':
description: Success
headers:
X-Total-Count:
description: The total count of repositories
type: integer
Link:
description: Link refers to the previous page and next page
type: string
schema:
type: array
items:
$ref: '#/definitions/Repository'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
/projects/{project_name}/repositories/{repository_name}:
put:
summary: Update repository
description: Update the repository specified by name
tags:
- repository
operationId: updateRepository
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/projectName'
- $ref: '#/parameters/repositoryName'
- name: repository
in: body
description: The JSON object of repository.
required: true
schema:
$ref: '#/definitions/Repository'
responses:
'200':
$ref: '#/responses/200'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
delete:
summary: Delete repository
description: Delete the repository specified by name
tags:
- repository
operationId: deleteRepository
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/projectName'
- $ref: '#/parameters/repositoryName'
responses:
'200':
$ref: '#/responses/200'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
/projects/{project_name}/repositories/{repository_name}/artifacts: /projects/{project_name}/repositories/{repository_name}/artifacts:
get: get:
summary: List artifacts summary: List artifacts
@ -57,10 +151,9 @@ paths:
type: boolean type: boolean
required: false required: false
default: false default: false
# should be in tag level
- name: with_signature - name: with_signature
in: query in: query
description: Specify whether the signature is inclued inside the returning artifacts description: Specify whether the signature is inclued inside the tags of the returning artifacts. Only works when setting "with_tag=true"
type: boolean type: boolean
required: false required: false
default: false default: false
@ -70,7 +163,6 @@ paths:
type: boolean type: boolean
required: false required: false
default: false default: false
# TODO add other query string: type, ....
responses: responses:
'200': '200':
description: Success description: Success
@ -234,28 +326,6 @@ paths:
$ref: '#/responses/404' $ref: '#/responses/404'
'500': '500':
$ref: '#/responses/500' $ref: '#/responses/500'
/projects/{project_name}/repositories/{repository_name}:
delete:
summary: Delete repository
description: Delete the repository specified by name
tags:
- repository
operationId: deleteRepository
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/projectName'
- $ref: '#/parameters/repositoryName'
responses:
'200':
$ref: '#/responses/200'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
/projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/additions/{addition}: /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/additions/{addition}:
get: get:
summary: Get the addition of the specific artifact summary: Get the addition of the specific artifact
@ -476,6 +546,39 @@ definitions:
message: message:
type: string type: string
description: The error message description: The error message
Repository:
type: object
properties:
id:
type: integer
format: int64
description: The ID of the repository
project_id:
type: integer
format: int64
description: The ID of the project that the repository belongs to
name:
type: string
description: The name of the repository
description:
type: string
description: The description of the repository
artifact_count:
type: integer
format: int64
description: The count of the artifacts inside the repository
pull_count:
type: integer
format: int64
description: The count that the artifact inside the repository pulled
creation_time:
type: string
format: date-time
description: The creation time of the repository
update_time:
type: string
format: date-time
description: The update time of the repository
Artifact: Artifact:
type: object type: object
x-go-type: x-go-type:

View File

@ -58,8 +58,12 @@ type Controller interface {
// and are attached to the artifact. If the tags don't exist, create them first. // and are attached to the artifact. If the tags don't exist, create them first.
// The "created" will be set as true when the artifact is created // The "created" will be set as true when the artifact is created
Ensure(ctx context.Context, repositoryID int64, digest string, tags ...string) (created bool, id int64, err error) Ensure(ctx context.Context, repositoryID int64, digest string, tags ...string) (created bool, id int64, err error)
// Count returns the total count of artifacts according to the query.
// The artifacts that referenced by others and without tags are not counted
Count(ctx context.Context, query *q.Query) (total int64, err error)
// List artifacts according to the query, specify the properties returned with option // List artifacts according to the query, specify the properties returned with option
List(ctx context.Context, query *q.Query, option *Option) (total int64, artifacts []*Artifact, err error) // The artifacts that referenced by others and without tags are not returned
List(ctx context.Context, query *q.Query, option *Option) (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, // Get the artifact specified by repository name and reference, the reference can be tag or digest,
@ -68,7 +72,7 @@ type Controller interface {
// 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)
// ListTags lists the tags according to the query, specify the properties returned with option // ListTags lists the tags according to the query, specify the properties returned with option
ListTags(ctx context.Context, query *q.Query, option *TagOption) (total int64, tags []*Tag, err error) ListTags(ctx context.Context, query *q.Query, option *TagOption) (tags []*Tag, err error)
// CreateTag creates a tag // CreateTag creates a tag
CreateTag(ctx context.Context, tag *Tag) (id int64, err error) CreateTag(ctx context.Context, tag *Tag) (id int64, err error)
// DeleteTag deletes the tag specified by tagID // DeleteTag deletes the tag specified by tagID
@ -185,7 +189,7 @@ func (c *controller) ensureTag(ctx context.Context, repositoryID, artifactID int
"name": name, "name": name,
}, },
} }
_, tags, err := c.tagMgr.List(ctx, query) tags, err := c.tagMgr.List(ctx, query)
if err != nil { if err != nil {
return err return err
} }
@ -216,17 +220,22 @@ func (c *controller) ensureTag(ctx context.Context, repositoryID, artifactID int
return err return err
} }
func (c *controller) List(ctx context.Context, query *q.Query, option *Option) (int64, []*Artifact, error) { func (c *controller) Count(ctx context.Context, query *q.Query) (int64, error) {
total, arts, err := c.artMgr.List(ctx, query) return c.artMgr.Count(ctx, query)
}
func (c *controller) List(ctx context.Context, query *q.Query, option *Option) ([]*Artifact, error) {
arts, err := c.artMgr.List(ctx, query)
if err != nil { if err != nil {
return 0, nil, err return nil, err
} }
var artifacts []*Artifact var artifacts []*Artifact
for _, art := range arts { for _, art := range arts {
artifacts = append(artifacts, c.assembleArtifact(ctx, art, option)) artifacts = append(artifacts, c.assembleArtifact(ctx, art, option))
} }
return total, artifacts, nil return artifacts, nil
} }
func (c *controller) Get(ctx context.Context, id int64, option *Option) (*Artifact, error) { func (c *controller) Get(ctx context.Context, id int64, option *Option) (*Artifact, error) {
art, err := c.artMgr.Get(ctx, id) art, err := c.artMgr.Get(ctx, id)
if err != nil { if err != nil {
@ -261,7 +270,7 @@ func (c *controller) getByTag(ctx context.Context, repository, tag string, optio
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, tags, err := c.tagMgr.List(ctx, &q.Query{ tags, err := c.tagMgr.List(ctx, &q.Query{
Keywords: map[string]interface{}{ Keywords: map[string]interface{}{
"RepositoryID": repo.RepositoryID, "RepositoryID": repo.RepositoryID,
"Name": tag, "Name": tag,
@ -369,16 +378,16 @@ func (c *controller) CreateTag(ctx context.Context, tag *Tag) (int64, error) {
// TODO fire event // TODO fire event
return c.tagMgr.Create(ctx, &(tag.Tag)) return c.tagMgr.Create(ctx, &(tag.Tag))
} }
func (c *controller) ListTags(ctx context.Context, query *q.Query, option *TagOption) (int64, []*Tag, error) { func (c *controller) ListTags(ctx context.Context, query *q.Query, option *TagOption) ([]*Tag, error) {
total, tgs, err := c.tagMgr.List(ctx, query) tgs, err := c.tagMgr.List(ctx, query)
if err != nil { if err != nil {
return 0, nil, err return nil, err
} }
var tags []*Tag var tags []*Tag
for _, tg := range tgs { for _, tg := range tgs {
tags = append(tags, c.assembleTag(ctx, tg, option)) tags = append(tags, c.assembleTag(ctx, tg, option))
} }
return total, tags, nil return tags, nil
} }
func (c *controller) DeleteTag(ctx context.Context, tagID int64) error { func (c *controller) DeleteTag(ctx context.Context, tagID int64) error {
@ -442,7 +451,7 @@ func (c *controller) assembleArtifact(ctx context.Context, art *artifact.Artifac
} }
func (c *controller) populateTags(ctx context.Context, art *Artifact, option *TagOption) { func (c *controller) populateTags(ctx context.Context, art *Artifact, option *TagOption) {
_, tags, err := c.tagMgr.List(ctx, &q.Query{ tags, err := c.tagMgr.List(ctx, &q.Query{
Keywords: map[string]interface{}{ Keywords: map[string]interface{}{
"artifact_id": art.ID, "artifact_id": art.ID,
}, },

View File

@ -143,7 +143,7 @@ func (c *controllerTestSuite) TestAssembleArtifact() {
PushTime: time.Now(), PushTime: time.Now(),
PullTime: time.Now(), PullTime: time.Now(),
} }
c.tagMgr.On("List").Return(1, []*tag.Tag{tg}, nil) c.tagMgr.On("List").Return([]*tag.Tag{tg}, nil)
c.repoMgr.On("Get").Return(&models.RepoRecord{ c.repoMgr.On("Get").Return(&models.RepoRecord{
Name: "library/hello-world", Name: "library/hello-world",
}, nil) }, nil)
@ -198,7 +198,7 @@ func (c *controllerTestSuite) TestEnsureArtifact() {
func (c *controllerTestSuite) TestEnsureTag() { func (c *controllerTestSuite) TestEnsureTag() {
// the tag already exists under the repository and is attached to the artifact // the tag already exists under the repository and is attached to the artifact
c.tagMgr.On("List").Return(1, []*tag.Tag{ c.tagMgr.On("List").Return([]*tag.Tag{
{ {
ID: 1, ID: 1,
RepositoryID: 1, RepositoryID: 1,
@ -214,7 +214,7 @@ func (c *controllerTestSuite) TestEnsureTag() {
c.SetupTest() c.SetupTest()
// the tag exists under the repository, but it is attached to other artifact // the tag exists under the repository, but it is attached to other artifact
c.tagMgr.On("List").Return(1, []*tag.Tag{ c.tagMgr.On("List").Return([]*tag.Tag{
{ {
ID: 1, ID: 1,
RepositoryID: 1, RepositoryID: 1,
@ -231,7 +231,7 @@ func (c *controllerTestSuite) TestEnsureTag() {
c.SetupTest() c.SetupTest()
// the tag doesn't exist under the repository, create it // the tag doesn't exist under the repository, create it
c.tagMgr.On("List").Return(1, []*tag.Tag{}, nil) c.tagMgr.On("List").Return([]*tag.Tag{}, nil)
c.tagMgr.On("Create").Return(1, nil) c.tagMgr.On("Create").Return(1, nil)
err = c.ctl.ensureTag(nil, 1, 1, "latest") err = c.ctl.ensureTag(nil, 1, 1, "latest")
c.Require().Nil(err) c.Require().Nil(err)
@ -247,7 +247,7 @@ func (c *controllerTestSuite) TestEnsure() {
}, nil) }, nil)
c.artMgr.On("GetByDigest").Return(nil, ierror.NotFoundError(nil)) c.artMgr.On("GetByDigest").Return(nil, ierror.NotFoundError(nil))
c.artMgr.On("Create").Return(1, nil) c.artMgr.On("Create").Return(1, nil)
c.tagMgr.On("List").Return(1, []*tag.Tag{}, nil) c.tagMgr.On("List").Return([]*tag.Tag{}, nil)
c.tagMgr.On("Create").Return(1, nil) c.tagMgr.On("Create").Return(1, nil)
c.abstractor.On("AbstractMetadata").Return(nil) c.abstractor.On("AbstractMetadata").Return(nil)
_, id, err := c.ctl.Ensure(nil, 1, digest, "latest") _, id, err := c.ctl.Ensure(nil, 1, digest, "latest")
@ -259,18 +259,25 @@ func (c *controllerTestSuite) TestEnsure() {
c.Equal(int64(1), id) c.Equal(int64(1), id)
} }
func (c *controllerTestSuite) TestCount() {
c.artMgr.On("Count").Return(1, nil)
total, err := c.ctl.Count(nil, nil)
c.Require().Nil(err)
c.Equal(int64(1), total)
}
func (c *controllerTestSuite) TestList() { func (c *controllerTestSuite) TestList() {
query := &q.Query{} query := &q.Query{}
option := &Option{ option := &Option{
WithTag: true, WithTag: true,
} }
c.artMgr.On("List").Return(1, []*artifact.Artifact{ c.artMgr.On("List").Return([]*artifact.Artifact{
{ {
ID: 1, ID: 1,
RepositoryID: 1, RepositoryID: 1,
}, },
}, nil) }, nil)
c.tagMgr.On("List").Return(1, []*tag.Tag{ c.tagMgr.On("List").Return([]*tag.Tag{
{ {
ID: 1, ID: 1,
RepositoryID: 1, RepositoryID: 1,
@ -281,9 +288,8 @@ func (c *controllerTestSuite) TestList() {
c.repoMgr.On("Get").Return(&models.RepoRecord{ c.repoMgr.On("Get").Return(&models.RepoRecord{
Name: "library/hello-world", Name: "library/hello-world",
}, nil) }, nil)
total, artifacts, err := c.ctl.List(nil, query, option) artifacts, err := c.ctl.List(nil, query, option)
c.Require().Nil(err) c.Require().Nil(err)
c.Equal(int64(1), total)
c.Require().Len(artifacts, 1) c.Require().Len(artifacts, 1)
c.Equal(int64(1), artifacts[0].ID) c.Equal(int64(1), artifacts[0].ID)
c.Require().Len(artifacts[0].Tags, 1) c.Require().Len(artifacts[0].Tags, 1)
@ -337,7 +343,7 @@ func (c *controllerTestSuite) TestGetByTag() {
c.repoMgr.On("GetByName").Return(&models.RepoRecord{ c.repoMgr.On("GetByName").Return(&models.RepoRecord{
RepositoryID: 1, RepositoryID: 1,
}, nil) }, nil)
c.tagMgr.On("List").Return(0, nil, nil) c.tagMgr.On("List").Return(nil, nil)
art, err := c.ctl.getByTag(nil, "library/hello-world", "latest", nil) art, err := c.ctl.getByTag(nil, "library/hello-world", "latest", nil)
c.Require().NotNil(err) c.Require().NotNil(err)
c.True(ierror.IsErr(err, ierror.NotFoundCode)) c.True(ierror.IsErr(err, ierror.NotFoundCode))
@ -349,7 +355,7 @@ func (c *controllerTestSuite) TestGetByTag() {
c.repoMgr.On("GetByName").Return(&models.RepoRecord{ c.repoMgr.On("GetByName").Return(&models.RepoRecord{
RepositoryID: 1, RepositoryID: 1,
}, nil) }, nil)
c.tagMgr.On("List").Return(1, []*tag.Tag{ c.tagMgr.On("List").Return([]*tag.Tag{
{ {
ID: 1, ID: 1,
RepositoryID: 1, RepositoryID: 1,
@ -390,7 +396,7 @@ func (c *controllerTestSuite) TestGetByReference() {
c.repoMgr.On("GetByName").Return(&models.RepoRecord{ c.repoMgr.On("GetByName").Return(&models.RepoRecord{
RepositoryID: 1, RepositoryID: 1,
}, nil) }, nil)
c.tagMgr.On("List").Return(1, []*tag.Tag{ c.tagMgr.On("List").Return([]*tag.Tag{
{ {
ID: 1, ID: 1,
RepositoryID: 1, RepositoryID: 1,
@ -429,7 +435,7 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
// child artifact and contains tags // child artifact and contains tags
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil) c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
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([]*tag.Tag{
{ {
ID: 1, ID: 1,
}, },
@ -444,7 +450,7 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
// root artifact is referenced by other artifacts // root artifact is referenced by other artifacts
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil) c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
c.tagMgr.On("List").Return(0, nil, nil) c.tagMgr.On("List").Return(nil, nil)
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil) c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
c.artMgr.On("ListReferences").Return([]*artifact.Reference{ c.artMgr.On("ListReferences").Return([]*artifact.Reference{
{ {
@ -459,7 +465,7 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
// child artifact contains no tag but referenced by other artifacts // child artifact contains no tag but referenced by other artifacts
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil) c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
c.tagMgr.On("List").Return(0, nil, nil) c.tagMgr.On("List").Return(nil, nil)
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil) c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
c.artMgr.On("ListReferences").Return([]*artifact.Reference{ c.artMgr.On("ListReferences").Return([]*artifact.Reference{
{ {
@ -474,7 +480,7 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
// root artifact is referenced by other artifacts // root artifact is referenced by other artifacts
c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil) c.artMgr.On("Get").Return(&artifact.Artifact{ID: 1}, nil)
c.tagMgr.On("List").Return(0, nil, nil) c.tagMgr.On("List").Return(nil, nil)
c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil) c.repoMgr.On("Get").Return(&models.RepoRecord{}, nil)
c.artMgr.On("ListReferences").Return(nil, nil) c.artMgr.On("ListReferences").Return(nil, nil)
c.tagMgr.On("DeleteOfArtifact").Return(nil) c.tagMgr.On("DeleteOfArtifact").Return(nil)
@ -486,7 +492,7 @@ func (c *controllerTestSuite) TestDeleteDeeply() {
} }
func (c *controllerTestSuite) TestListTags() { func (c *controllerTestSuite) TestListTags() {
c.tagMgr.On("List").Return(1, []*tag.Tag{ c.tagMgr.On("List").Return([]*tag.Tag{
{ {
ID: 1, ID: 1,
RepositoryID: 1, RepositoryID: 1,
@ -494,9 +500,8 @@ func (c *controllerTestSuite) TestListTags() {
ArtifactID: 1, ArtifactID: 1,
}, },
}, nil) }, nil)
total, tags, err := c.ctl.ListTags(nil, nil, nil) tags, err := c.ctl.ListTags(nil, nil, nil)
c.Require().Nil(err) c.Require().Nil(err)
c.Equal(int64(1), total)
c.Len(tags, 1) c.Len(tags, 1)
c.tagMgr.AssertExpectations(c.T()) c.tagMgr.AssertExpectations(c.T())
c.Equal(tags[0].Immutable, false) c.Equal(tags[0].Immutable, false)

View File

@ -36,14 +36,18 @@ type Controller interface {
// The "name" should contain the namespace part. The "created" will be set as true // The "name" should contain the namespace part. The "created" will be set as true
// when the repository is created // when the repository is created
Ensure(ctx context.Context, name string) (created bool, id int64, err error) Ensure(ctx context.Context, name string) (created bool, id int64, err error)
// Count returns the total count of repositories according to the query
Count(ctx context.Context, query *q.Query) (total int64, err error)
// List repositories according to the query // List repositories according to the query
List(ctx context.Context, query *q.Query) (total int64, repositories []*models.RepoRecord, err error) List(ctx context.Context, query *q.Query) (repositories []*models.RepoRecord, 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)
// GetByName gets the repository specified by name // GetByName gets the repository specified by name
GetByName(ctx context.Context, name string) (repository *models.RepoRecord, err error) GetByName(ctx context.Context, name string) (repository *models.RepoRecord, err error)
// Delete the repository specified by ID // Delete the repository specified by ID
Delete(ctx context.Context, id int64) (err error) Delete(ctx context.Context, id int64) (err error)
// Update the repository. Specify the properties or all properties will be updated
Update(ctx context.Context, repository *models.RepoRecord, properties ...string) (err error)
} }
// NewController creates an instance of the default repository controller // NewController creates an instance of the default repository controller
@ -67,7 +71,7 @@ func (c *controller) Ensure(ctx context.Context, name string) (bool, int64, erro
"name": name, "name": name,
}, },
} }
_, repositories, err := c.repoMgr.List(ctx, query) repositories, err := c.repoMgr.List(ctx, query)
if err != nil { if err != nil {
return false, 0, err return false, 0, err
} }
@ -89,7 +93,7 @@ func (c *controller) Ensure(ctx context.Context, name string) (bool, int64, erro
if err != nil { if err != nil {
// if got conflict error, try to get again // if got conflict error, try to get again
if ierror.IsConflictErr(err) { if ierror.IsConflictErr(err) {
_, repositories, err = c.repoMgr.List(ctx, query) repositories, err = c.repoMgr.List(ctx, query)
if err != nil { if err != nil {
return false, 0, err return false, 0, err
} }
@ -102,7 +106,11 @@ func (c *controller) Ensure(ctx context.Context, name string) (bool, int64, erro
return true, id, nil return true, id, nil
} }
func (c *controller) List(ctx context.Context, query *q.Query) (int64, []*models.RepoRecord, error) { func (c *controller) Count(ctx context.Context, query *q.Query) (int64, error) {
return c.repoMgr.Count(ctx, query)
}
func (c *controller) List(ctx context.Context, query *q.Query) ([]*models.RepoRecord, error) {
return c.repoMgr.List(ctx, query) return c.repoMgr.List(ctx, query)
} }
@ -118,7 +126,7 @@ func (c *controller) Delete(ctx context.Context, id int64) error {
// TODO auth // TODO auth
// TODO how to make sure the logic included by middlewares(immutable, readonly, quota, etc) // TODO how to make sure the logic included by middlewares(immutable, readonly, quota, etc)
// TODO is covered when deleting the artifacts of the repository // TODO is covered when deleting the artifacts of the repository
_, artifacts, err := c.artCtl.List(ctx, &q.Query{ artifacts, err := c.artCtl.List(ctx, &q.Query{
Keywords: map[string]interface{}{ Keywords: map[string]interface{}{
"RepositoryID": id, "RepositoryID": id,
}, },
@ -135,3 +143,7 @@ func (c *controller) Delete(ctx context.Context, id int64) error {
// TODO fire event // TODO fire event
} }
func (c *controller) Update(ctx context.Context, repository *models.RepoRecord, properties ...string) error {
return c.repoMgr.Update(ctx, repository, properties...)
}

View File

@ -45,7 +45,7 @@ func (c *controllerTestSuite) SetupTest() {
func (c *controllerTestSuite) TestEnsure() { func (c *controllerTestSuite) TestEnsure() {
// already exists // already exists
c.repoMgr.On("List").Return(0, []*models.RepoRecord{ c.repoMgr.On("List").Return([]*models.RepoRecord{
{ {
RepositoryID: 1, RepositoryID: 1,
ProjectID: 1, ProjectID: 1,
@ -62,7 +62,7 @@ func (c *controllerTestSuite) TestEnsure() {
c.SetupTest() c.SetupTest()
// doesn't exist // doesn't exist
c.repoMgr.On("List").Return(0, []*models.RepoRecord{}, nil) c.repoMgr.On("List").Return([]*models.RepoRecord{}, nil)
c.proMgr.On("Get").Return(&models.Project{ c.proMgr.On("Get").Return(&models.Project{
ProjectID: 1, ProjectID: 1,
}, nil) }, nil)
@ -75,16 +75,21 @@ func (c *controllerTestSuite) TestEnsure() {
c.Equal(int64(1), id) c.Equal(int64(1), id)
} }
func (c *controllerTestSuite) TestCount() {
c.repoMgr.On("Count").Return(1, nil)
total, err := c.ctl.Count(nil, nil)
c.Require().Nil(err)
c.Equal(int64(1), total)
}
func (c *controllerTestSuite) TestList() { func (c *controllerTestSuite) TestList() {
c.repoMgr.On("List").Return(1, []*models.RepoRecord{ c.repoMgr.On("List").Return([]*models.RepoRecord{
{ {
RepositoryID: 1, RepositoryID: 1,
}, },
}, nil) }, nil)
total, repositories, err := c.ctl.List(nil, nil) repositories, err := c.ctl.List(nil, nil)
c.Require().Nil(err) c.Require().Nil(err)
c.repoMgr.AssertExpectations(c.T())
c.Equal(int64(1), total)
c.Require().Len(repositories, 1) c.Require().Len(repositories, 1)
c.Equal(int64(1), repositories[0].RepositoryID) c.Equal(int64(1), repositories[0].RepositoryID)
} }
@ -112,13 +117,22 @@ func (c *controllerTestSuite) TestGetByName() {
func (c *controllerTestSuite) TestDelete() { func (c *controllerTestSuite) TestDelete() {
art := &artifact.Artifact{} art := &artifact.Artifact{}
art.ID = 1 art.ID = 1
c.artCtl.On("List").Return(1, []*artifact.Artifact{art}, nil) c.artCtl.On("List").Return([]*artifact.Artifact{art}, nil)
c.artCtl.On("Delete").Return(nil) c.artCtl.On("Delete").Return(nil)
c.repoMgr.On("Delete").Return(nil) c.repoMgr.On("Delete").Return(nil)
err := c.ctl.Delete(nil, 1) err := c.ctl.Delete(nil, 1)
c.Require().Nil(err) c.Require().Nil(err)
} }
func (c *controllerTestSuite) TestUpdate() {
c.repoMgr.On("Update").Return(nil)
err := c.ctl.Update(nil, &models.RepoRecord{
RepositoryID: 1,
Description: "description",
}, "Description")
c.Require().Nil(err)
}
func TestControllerTestSuite(t *testing.T) { func TestControllerTestSuite(t *testing.T) {
suite.Run(t, &controllerTestSuite{}) suite.Run(t, &controllerTestSuite{})
} }

View File

@ -67,7 +67,7 @@ func fetchArtifacts(ctx context.Context, repositoryID int64, chunkSize int) <-ch
} }
for { for {
_, artifacts, err := artifact.Ctl.List(ctx, query, &artifact.Option{WithTag: true}) artifacts, err := artifact.Ctl.List(ctx, query, &artifact.Option{WithTag: true})
if err != nil { if err != nil {
log.Errorf("[scan all]: list artifacts failed, error: %v", err) log.Errorf("[scan all]: list artifacts failed, error: %v", err)
return return
@ -100,7 +100,7 @@ func fetchRepositories(ctx context.Context, chunkSize int) <-chan *models.RepoRe
} }
for { for {
_, repositories, err := repository.Ctl.List(ctx, query) repositories, err := repository.Ctl.List(ctx, query)
if err != nil { if err != nil {
log.Warningf("[scan all]: list repositories failed, error: %v", err) log.Warningf("[scan all]: list repositories failed, error: %v", err)
break break

View File

@ -15,6 +15,8 @@
package models package models
import ( import (
"github.com/go-openapi/strfmt"
"github.com/goharbor/harbor/src/server/v2.0/models"
"time" "time"
"github.com/goharbor/harbor/src/pkg/signature/notary/model" "github.com/goharbor/harbor/src/pkg/signature/notary/model"
@ -24,6 +26,8 @@ import (
// RepoTable is the table name for repository // RepoTable is the table name for repository
const RepoTable = "repository" const RepoTable = "repository"
// TODO move the model into pkg/repository
// RepoRecord holds the record of an repository in DB, all the infors are from the registry notification event. // RepoRecord holds the record of an repository in DB, all the infors are from the registry notification event.
type RepoRecord struct { type RepoRecord struct {
RepositoryID int64 `orm:"pk;auto;column(repository_id)" json:"repository_id"` RepositoryID int64 `orm:"pk;auto;column(repository_id)" json:"repository_id"`
@ -36,8 +40,21 @@ type RepoRecord struct {
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"` UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
} }
// ToSwagger converts the repository into the swagger model
func (r *RepoRecord) ToSwagger() *models.Repository {
return &models.Repository{
CreationTime: strfmt.DateTime(r.CreationTime),
Description: r.Description,
ID: r.RepositoryID,
Name: r.Name,
ProjectID: r.ProjectID,
PullCount: r.PullCount,
UpdateTime: strfmt.DateTime(r.UpdateTime),
}
}
// TableName is required by by beego orm to map RepoRecord to table repository // TableName is required by by beego orm to map RepoRecord to table repository
func (rp *RepoRecord) TableName() string { func (r *RepoRecord) TableName() string {
return RepoTable return RepoTable
} }

View File

@ -77,6 +77,7 @@ require (
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
github.com/theupdateframework/notary v0.6.1 github.com/theupdateframework/notary v0.6.1
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 // indirect gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 // indirect
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect

View File

@ -29,9 +29,12 @@ var (
// Manager is the only interface of artifact module to provide the management functions for artifacts // Manager is the only interface of artifact module to provide the management functions for artifacts
type Manager interface { type Manager interface {
// Count returns the total count of artifacts according to the query.
// The artifacts that referenced by others and without tags are not counted
Count(ctx context.Context, query *q.Query) (total int64, err error)
// List artifacts according to the query. The artifacts that referenced by others and // List artifacts according to the query. The artifacts that referenced by others and
// without tags are not returned // without tags are not returned
List(ctx context.Context, query *q.Query) (total int64, artifacts []*Artifact, err error) List(ctx context.Context, query *q.Query) (artifacts []*Artifact, err error)
// Get the artifact specified by the ID // Get the artifact specified by the ID
Get(ctx context.Context, id int64) (artifact *Artifact, err error) Get(ctx context.Context, id int64) (artifact *Artifact, err error)
// GetByDigest returns the artifact specified by repository ID and digest // GetByDigest returns the artifact specified by repository ID and digest
@ -63,24 +66,24 @@ type manager struct {
dao dao.DAO dao dao.DAO
} }
func (m *manager) List(ctx context.Context, query *q.Query) (int64, []*Artifact, error) { func (m *manager) Count(ctx context.Context, query *q.Query) (int64, error) {
total, err := m.dao.Count(ctx, query) return m.dao.Count(ctx, query)
if err != nil {
return 0, nil, err
} }
func (m *manager) List(ctx context.Context, query *q.Query) ([]*Artifact, error) {
arts, err := m.dao.List(ctx, query) arts, err := m.dao.List(ctx, query)
if err != nil { if err != nil {
return 0, nil, err return nil, err
} }
var artifacts []*Artifact var artifacts []*Artifact
for _, art := range arts { for _, art := range arts {
artifact, err := m.assemble(ctx, art) artifact, err := m.assemble(ctx, art)
if err != nil { if err != nil {
return 0, nil, err return nil, err
} }
artifacts = append(artifacts, artifact) artifacts = append(artifacts, artifact)
} }
return total, artifacts, nil return artifacts, nil
} }
func (m *manager) Get(ctx context.Context, id int64) (*Artifact, error) { func (m *manager) Get(ctx context.Context, id int64) (*Artifact, error) {

View File

@ -86,6 +86,13 @@ func (m *managerTestSuite) SetupTest() {
} }
} }
func (m *managerTestSuite) TestCount() {
m.dao.On("Count", mock.Anything).Return(1, nil)
total, err := m.mgr.Count(nil, nil)
m.Require().Nil(err)
m.Equal(int64(1), total)
}
func (m *managerTestSuite) TestAssemble() { func (m *managerTestSuite) TestAssemble() {
art := &dao.Artifact{ art := &dao.Artifact{
ID: 1, ID: 1,
@ -139,13 +146,10 @@ func (m *managerTestSuite) TestList() {
ExtraAttrs: `{"attr1":"value1"}`, ExtraAttrs: `{"attr1":"value1"}`,
Annotations: `{"anno1":"value1"}`, Annotations: `{"anno1":"value1"}`,
} }
m.dao.On("Count", mock.Anything).Return(1, nil)
m.dao.On("List", mock.Anything).Return([]*dao.Artifact{art}, nil) m.dao.On("List", mock.Anything).Return([]*dao.Artifact{art}, nil)
m.dao.On("ListReferences").Return([]*dao.ArtifactReference{}, nil) m.dao.On("ListReferences").Return([]*dao.ArtifactReference{}, nil)
total, artifacts, err := m.mgr.List(nil, nil) artifacts, err := m.mgr.List(nil, nil)
m.Require().Nil(err) m.Require().Nil(err)
m.dao.AssertExpectations(m.T())
m.Equal(int64(1), total)
m.Equal(1, len(artifacts)) m.Equal(1, len(artifacts))
m.Equal(art.ID, artifacts[0].ID) m.Equal(art.ID, artifacts[0].ID)
} }

View File

@ -27,8 +27,10 @@ var Mgr = New()
// Manager is used for repository management // Manager is used for repository management
type Manager interface { type Manager interface {
// Count returns the total count of repositories according to the query
Count(ctx context.Context, query *q.Query) (total int64, err error)
// List repositories according to the query // List repositories according to the query
List(ctx context.Context, query *q.Query) (total int64, repositories []*models.RepoRecord, err error) List(ctx context.Context, query *q.Query) (repositories []*models.RepoRecord, 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)
// GetByName gets the repository specified by name // GetByName gets the repository specified by name
@ -52,16 +54,16 @@ type manager struct {
dao dao.DAO dao dao.DAO
} }
func (m *manager) List(ctx context.Context, query *q.Query) (int64, []*models.RepoRecord, error) { func (m *manager) Count(ctx context.Context, query *q.Query) (int64, error) {
total, err := m.dao.Count(ctx, query) return m.dao.Count(ctx, query)
if err != nil {
return 0, nil, err
} }
func (m *manager) List(ctx context.Context, query *q.Query) ([]*models.RepoRecord, error) {
repositories, err := m.dao.List(ctx, query) repositories, err := m.dao.List(ctx, query)
if err != nil { if err != nil {
return 0, nil, err return nil, err
} }
return total, repositories, nil return repositories, nil
} }
func (m *manager) Get(ctx context.Context, id int64) (*models.RepoRecord, error) { func (m *manager) Get(ctx context.Context, id int64) (*models.RepoRecord, error) {
@ -69,7 +71,7 @@ func (m *manager) Get(ctx context.Context, id int64) (*models.RepoRecord, error)
} }
func (m *manager) GetByName(ctx context.Context, name string) (repository *models.RepoRecord, err error) { func (m *manager) GetByName(ctx context.Context, name string) (repository *models.RepoRecord, err error) {
_, repositories, err := m.List(ctx, &q.Query{ repositories, err := m.List(ctx, &q.Query{
Keywords: map[string]interface{}{ Keywords: map[string]interface{}{
"Name": name, "Name": name,
}, },
@ -92,5 +94,5 @@ func (m *manager) Delete(ctx context.Context, id int64) error {
return m.dao.Delete(ctx, id) return m.dao.Delete(ctx, id)
} }
func (m *manager) Update(ctx context.Context, repository *models.RepoRecord, props ...string) error { func (m *manager) Update(ctx context.Context, repository *models.RepoRecord, props ...string) error {
return m.dao.Update(ctx, repository) return m.dao.Update(ctx, repository, props...)
} }

View File

@ -65,18 +65,22 @@ func (m *managerTestSuite) SetupTest() {
} }
} }
func (m *managerTestSuite) TestCount() {
m.dao.On("Count", mock.Anything).Return(1, nil)
total, err := m.mgr.Count(nil, nil)
m.Require().Nil(err)
m.Equal(int64(1), total)
}
func (m *managerTestSuite) TestList() { func (m *managerTestSuite) TestList() {
repository := &models.RepoRecord{ repository := &models.RepoRecord{
RepositoryID: 1, RepositoryID: 1,
ProjectID: 1, ProjectID: 1,
Name: "library/hello-world", Name: "library/hello-world",
} }
m.dao.On("Count", mock.Anything).Return(1, nil)
m.dao.On("List", mock.Anything).Return([]*models.RepoRecord{repository}, nil) m.dao.On("List", mock.Anything).Return([]*models.RepoRecord{repository}, nil)
total, repositories, err := m.mgr.List(nil, nil) repositories, err := m.mgr.List(nil, nil)
m.Require().Nil(err) m.Require().Nil(err)
m.dao.AssertExpectations(m.T())
m.Equal(int64(1), total)
m.Equal(1, len(repositories)) m.Equal(1, len(repositories))
m.Equal(repository.RepositoryID, repositories[0].RepositoryID) m.Equal(repository.RepositoryID, repositories[0].RepositoryID)
} }
@ -101,7 +105,6 @@ func (m *managerTestSuite) TestGetByName() {
ProjectID: 1, ProjectID: 1,
Name: "library/hello-world", Name: "library/hello-world",
} }
m.dao.On("Count", mock.Anything).Return(1, nil)
m.dao.On("List", mock.Anything).Return([]*models.RepoRecord{repository}, nil) m.dao.On("List", mock.Anything).Return([]*models.RepoRecord{repository}, nil)
repo, err := m.mgr.GetByName(nil, "library/hello-world") repo, err := m.mgr.GetByName(nil, "library/hello-world")
m.Require().Nil(err) m.Require().Nil(err)

View File

@ -350,7 +350,7 @@ func getRepositories(projectMgr project.Manager, repositoryMgr repository.Manage
*/ */
// get image repositories // get image repositories
// TODO set the context which contains the ORM // TODO set the context which contains the ORM
_, imageRepositories, err := repositoryMgr.List(orm.NewContext(nil, beegoorm.NewOrm()), &pq.Query{ imageRepositories, err := repositoryMgr.List(orm.NewContext(nil, beegoorm.NewOrm()), &pq.Query{
Keywords: map[string]interface{}{ Keywords: map[string]interface{}{
"ProjectID": projectID, "ProjectID": projectID,
}, },

View File

@ -165,7 +165,7 @@ func (l *launchTestSuite) TestGetProjects() {
} }
func (l *launchTestSuite) TestGetRepositories() { func (l *launchTestSuite) TestGetRepositories() {
l.repositoryMgr.On("List").Return(1, []*models.RepoRecord{ l.repositoryMgr.On("List").Return([]*models.RepoRecord{
{ {
RepositoryID: 1, RepositoryID: 1,
ProjectID: 1, ProjectID: 1,
@ -211,7 +211,7 @@ func (l *launchTestSuite) TestLaunch() {
require.NotNil(l.T(), err) require.NotNil(l.T(), err)
// system scope // system scope
l.repositoryMgr.On("List").Return(2, []*models.RepoRecord{ l.repositoryMgr.On("List").Return([]*models.RepoRecord{
{ {
RepositoryID: 1, RepositoryID: 1,
ProjectID: 1, ProjectID: 1,

View File

@ -28,8 +28,10 @@ var (
// Manager manages the tags // Manager manages the tags
type Manager interface { type Manager interface {
// Count returns the total count of tags according to the query.
Count(ctx context.Context, query *q.Query) (total int64, err error)
// List tags according to the query // List tags according to the query
List(ctx context.Context, query *q.Query) (total int64, tags []*tag.Tag, err error) List(ctx context.Context, query *q.Query) (tags []*tag.Tag, err error)
// Get the tag specified by ID // Get the tag specified by ID
Get(ctx context.Context, id int64) (tag *tag.Tag, err error) Get(ctx context.Context, id int64) (tag *tag.Tag, err error)
// Create the tag and returns the ID // Create the tag and returns the ID
@ -53,16 +55,12 @@ type manager struct {
dao dao.DAO dao dao.DAO
} }
func (m *manager) List(ctx context.Context, query *q.Query) (int64, []*tag.Tag, error) { func (m *manager) Count(ctx context.Context, query *q.Query) (int64, error) {
total, err := m.dao.Count(ctx, query) return m.dao.Count(ctx, query)
if err != nil {
return 0, nil, err
} }
tags, err := m.dao.List(ctx, query)
if err != nil { func (m *manager) List(ctx context.Context, query *q.Query) ([]*tag.Tag, error) {
return 0, nil, err return m.dao.List(ctx, query)
}
return total, tags, nil
} }
func (m *manager) Get(ctx context.Context, id int64) (*tag.Tag, error) { func (m *manager) Get(ctx context.Context, id int64) (*tag.Tag, error) {

View File

@ -70,6 +70,13 @@ func (m *managerTestSuite) SetupTest() {
} }
} }
func (m *managerTestSuite) TestCount() {
m.dao.On("Count", mock.Anything).Return(1, nil)
total, err := m.mgr.Count(nil, nil)
m.Require().Nil(err)
m.Equal(int64(1), total)
}
func (m *managerTestSuite) TestList() { func (m *managerTestSuite) TestList() {
tg := &tag.Tag{ tg := &tag.Tag{
ID: 1, ID: 1,
@ -79,12 +86,9 @@ func (m *managerTestSuite) TestList() {
PushTime: time.Now(), PushTime: time.Now(),
PullTime: time.Now(), PullTime: time.Now(),
} }
m.dao.On("Count", mock.Anything).Return(1, nil)
m.dao.On("List", mock.Anything).Return([]*tag.Tag{tg}, nil) m.dao.On("List", mock.Anything).Return([]*tag.Tag{tg}, nil)
total, tags, err := m.mgr.List(nil, nil) tags, err := m.mgr.List(nil, nil)
m.Require().Nil(err) m.Require().Nil(err)
m.dao.AssertExpectations(m.T())
m.Equal(int64(1), total)
m.Equal(1, len(tags)) m.Equal(1, len(tags))
m.Equal(tg.ID, tags[0].ID) m.Equal(tg.ID, tags[0].ID)
} }

View File

@ -55,12 +55,12 @@ func (r *repositoryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request)
var repoNames []string var repoNames []string
// get all repositories // get all repositories
// ToDo filter out the untagged repos // ToDo filter out the untagged repos
total, repoRecords, err := r.repoCtl.List(req.Context(), nil) repoRecords, err := r.repoCtl.List(req.Context(), nil)
if err != nil { if err != nil {
serror.SendError(w, err) serror.SendError(w, err)
return return
} }
if total <= 0 { if len(repoRecords) <= 0 {
r.sendResponse(w, req, repoNames) r.sendResponse(w, req, repoNames)
return return
} }

View File

@ -51,7 +51,7 @@ func (c *catalogTestSuite) TestCatalog() {
c.SetupTest() c.SetupTest()
req := httptest.NewRequest(http.MethodGet, "/v2/_catalog", nil) req := httptest.NewRequest(http.MethodGet, "/v2/_catalog", nil)
var w *httptest.ResponseRecorder var w *httptest.ResponseRecorder
c.repoCtl.On("List").Return(2, []*models.RepoRecord{ c.repoCtl.On("List").Return([]*models.RepoRecord{
{ {
RepositoryID: 1, RepositoryID: 1,
Name: "hello-world", Name: "hello-world",
@ -77,7 +77,7 @@ func (c *catalogTestSuite) TestCatalogPaginationN1() {
c.SetupTest() c.SetupTest()
req := httptest.NewRequest(http.MethodGet, "/v2/_catalog?n=1", nil) req := httptest.NewRequest(http.MethodGet, "/v2/_catalog?n=1", nil)
var w *httptest.ResponseRecorder var w *httptest.ResponseRecorder
c.repoCtl.On("List").Return(2, []*models.RepoRecord{ c.repoCtl.On("List").Return([]*models.RepoRecord{
{ {
RepositoryID: 1, RepositoryID: 1,
Name: "hello-world", Name: "hello-world",
@ -104,7 +104,7 @@ func (c *catalogTestSuite) TestCatalogPaginationN2() {
c.SetupTest() c.SetupTest()
req := httptest.NewRequest(http.MethodGet, "/v2/_catalog?n=3", nil) req := httptest.NewRequest(http.MethodGet, "/v2/_catalog?n=3", nil)
var w *httptest.ResponseRecorder var w *httptest.ResponseRecorder
c.repoCtl.On("List").Return(2, []*models.RepoRecord{ c.repoCtl.On("List").Return([]*models.RepoRecord{
{ {
RepositoryID: 1, RepositoryID: 1,
Name: "hello-world", Name: "hello-world",
@ -131,7 +131,7 @@ func (c *catalogTestSuite) TestCatalogPaginationN3() {
c.SetupTest() c.SetupTest()
req := httptest.NewRequest(http.MethodGet, "/v2/_catalog?last=busybox&n=1", nil) req := httptest.NewRequest(http.MethodGet, "/v2/_catalog?last=busybox&n=1", nil)
var w *httptest.ResponseRecorder var w *httptest.ResponseRecorder
c.repoCtl.On("List").Return(2, []*models.RepoRecord{ c.repoCtl.On("List").Return([]*models.RepoRecord{
{ {
RepositoryID: 1, RepositoryID: 1,
Name: "hello-world", Name: "hello-world",

View File

@ -80,7 +80,7 @@ func (t *tagHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
// get tags ... // get tags ...
total, tags, err := t.artCtl.ListTags(req.Context(), &q.Query{ tags, err := t.artCtl.ListTags(req.Context(), &q.Query{
Keywords: map[string]interface{}{ Keywords: map[string]interface{}{
"RepositoryID": repository.RepositoryID, "RepositoryID": repository.RepositoryID,
}}, nil) }}, nil)
@ -88,7 +88,7 @@ func (t *tagHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
serror.SendError(w, err) serror.SendError(w, err)
return return
} }
if total == 0 { if len(tags) == 0 {
t.sendResponse(w, req, tagNames) t.sendResponse(w, req, tagNames)
return return
} }

View File

@ -64,7 +64,7 @@ func (c *tagTestSuite) TestListTag() {
RepositoryID: 1, RepositoryID: 1,
Name: "library/hello-world", Name: "library/hello-world",
}, nil) }, nil)
c.artCtl.On("ListTags").Return(2, []*artifact.Tag{ c.artCtl.On("ListTags").Return([]*artifact.Tag{
{ {
Tag: tag.Tag{ Tag: tag.Tag{
RepositoryID: 1, RepositoryID: 1,
@ -99,7 +99,7 @@ func (c *tagTestSuite) TestListTagPagination1() {
RepositoryID: 1, RepositoryID: 1,
Name: "hello-world", Name: "hello-world",
}, nil) }, nil)
c.artCtl.On("ListTags").Return(2, []*artifact.Tag{ c.artCtl.On("ListTags").Return([]*artifact.Tag{
{ {
Tag: tag.Tag{ Tag: tag.Tag{
RepositoryID: 1, RepositoryID: 1,
@ -135,7 +135,7 @@ func (c *tagTestSuite) TestListTagPagination2() {
RepositoryID: 1, RepositoryID: 1,
Name: "hello-world", Name: "hello-world",
}, nil) }, nil)
c.artCtl.On("ListTags").Return(2, []*artifact.Tag{ c.artCtl.On("ListTags").Return([]*artifact.Tag{
{ {
Tag: tag.Tag{ Tag: tag.Tag{
RepositoryID: 1, RepositoryID: 1,
@ -171,7 +171,7 @@ func (c *tagTestSuite) TestListTagPagination3() {
RepositoryID: 1, RepositoryID: 1,
Name: "hello-world", Name: "hello-world",
}, nil) }, nil)
c.artCtl.On("ListTags").Return(2, []*artifact.Tag{ c.artCtl.On("ListTags").Return([]*artifact.Tag{
{ {
Tag: tag.Tag{ Tag: tag.Tag{
RepositoryID: 1, RepositoryID: 1,

View File

@ -77,8 +77,13 @@ func (a *artifactAPI) ListArtifacts(ctx context.Context, params operation.ListAr
option := option(params.WithTag, params.WithImmutableStatus, option := option(params.WithTag, params.WithImmutableStatus,
params.WithLabel, params.WithSignature) params.WithLabel, params.WithSignature)
// get the total count of artifacts
total, err := a.artCtl.Count(ctx, query)
if err != nil {
return a.SendError(ctx, err)
}
// list artifacts according to the query and option // list artifacts according to the query and option
total, arts, err := a.artCtl.List(ctx, query, option) arts, err := a.artCtl.List(ctx, query, option)
if err != nil { if err != nil {
return a.SendError(ctx, err) return a.SendError(ctx, err)
} }

View File

@ -18,20 +18,99 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/repository" "github.com/goharbor/harbor/src/api/repository"
cmodels "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac" "github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/project"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/goharbor/harbor/src/server/v2.0/models"
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/repository" operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/repository"
) )
func newRepositoryAPI() *repositoryAPI { func newRepositoryAPI() *repositoryAPI {
return &repositoryAPI{ return &repositoryAPI{
proMgr: project.Mgr,
repoCtl: repository.Ctl, repoCtl: repository.Ctl,
artCtl: artifact.Ctl,
} }
} }
type repositoryAPI struct { type repositoryAPI struct {
BaseAPI BaseAPI
// TODO replace proMgr with proCtl
proMgr project.Manager
repoCtl repository.Controller repoCtl repository.Controller
artCtl artifact.Controller
}
func (r *repositoryAPI) ListRepositories(ctx context.Context, params operation.ListRepositoriesParams) middleware.Responder {
if err := r.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionList, rbac.ResourceRepository); err != nil {
return r.SendError(ctx, err)
}
project, err := r.proMgr.Get(params.ProjectName)
if err != nil {
return r.SendError(ctx, err)
}
// set query
query := &q.Query{
Keywords: map[string]interface{}{
"ProjectID": project.ProjectID,
},
}
// TODO support fuzzy match
if params.Name != nil {
query.Keywords["Name"] = *(params.Name)
}
if params.Page != nil {
query.PageNumber = *(params.Page)
}
if params.PageSize != nil {
query.PageSize = *(params.PageSize)
}
total, err := r.repoCtl.Count(ctx, query)
if err != nil {
return r.SendError(ctx, err)
}
repositories, err := r.repoCtl.List(ctx, query)
if err != nil {
return r.SendError(ctx, err)
}
var repos []*models.Repository
for _, repository := range repositories {
repo := repository.ToSwagger()
total, err := r.artCtl.Count(ctx, &q.Query{
Keywords: map[string]interface{}{
"RepositoryID": repo.ID,
},
})
if err != nil {
log.Errorf("failed to get the count of artifacts under the repository %s: %v",
repo.Name, err)
}
repo.ArtifactCount = total
repos = append(repos, repo)
}
// TODO add link header
return operation.NewListRepositoriesOK().WithXTotalCount(total).WithLink("").WithPayload(repos)
}
func (r *repositoryAPI) UpdateRepository(ctx context.Context, params operation.UpdateRepositoryParams) middleware.Responder {
if err := r.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionUpdate, rbac.ResourceRepository); err != nil {
return r.SendError(ctx, err)
}
repository, err := r.repoCtl.GetByName(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName))
if err != nil {
return r.SendError(ctx, err)
}
if err := r.repoCtl.Update(ctx, &cmodels.RepoRecord{
RepositoryID: repository.RepositoryID,
Description: params.Repository.Description,
}, "Description"); err != nil {
return r.SendError(ctx, err)
}
return operation.NewDeleteRepositoryOK()
} }
func (r *repositoryAPI) DeleteRepository(ctx context.Context, params operation.DeleteRepositoryParams) middleware.Responder { func (r *repositoryAPI) DeleteRepository(ctx context.Context, params operation.DeleteRepositoryParams) middleware.Responder {

View File

@ -34,14 +34,20 @@ func (f *FakeController) Ensure(ctx context.Context, repositoryID int64, digest
return args.Bool(0), int64(args.Int(1)), args.Error(2) return args.Bool(0), int64(args.Int(1)), args.Error(2)
} }
// Count ...
func (f *FakeController) Count(ctx context.Context, query *q.Query) (int64, error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}
// List ... // List ...
func (f *FakeController) List(ctx context.Context, query *q.Query, option *artifact.Option) (int64, []*artifact.Artifact, error) { func (f *FakeController) List(ctx context.Context, query *q.Query, option *artifact.Option) ([]*artifact.Artifact, error) {
args := f.Called() args := f.Called()
var artifacts []*artifact.Artifact var artifacts []*artifact.Artifact
if args.Get(1) != nil { if args.Get(0) != nil {
artifacts = args.Get(1).([]*artifact.Artifact) artifacts = args.Get(0).([]*artifact.Artifact)
} }
return int64(args.Int(0)), artifacts, args.Error(2) return artifacts, args.Error(1)
} }
// Get ... // Get ...
@ -71,13 +77,13 @@ func (f *FakeController) Delete(ctx context.Context, id int64) (err error) {
} }
// ListTags ... // ListTags ...
func (f *FakeController) ListTags(ctx context.Context, query *q.Query, option *artifact.TagOption) (int64, []*artifact.Tag, error) { func (f *FakeController) ListTags(ctx context.Context, query *q.Query, option *artifact.TagOption) ([]*artifact.Tag, error) {
args := f.Called() args := f.Called()
var tags []*artifact.Tag var tags []*artifact.Tag
if args.Get(1) != nil { if args.Get(0) != nil {
tags = args.Get(1).([]*artifact.Tag) tags = args.Get(0).([]*artifact.Tag)
} }
return int64(args.Int(0)), tags, args.Error(2) return tags, args.Error(1)
} }
// CreateTag ... // CreateTag ...

View File

@ -32,14 +32,21 @@ func (f *FakeController) Ensure(ctx context.Context, name string) (bool, int64,
return args.Bool(0), int64(args.Int(1)), args.Error(2) return args.Bool(0), int64(args.Int(1)), args.Error(2)
} }
// Count ...
func (f *FakeController) Count(ctx context.Context, query *q.Query) (int64, error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}
// List ... // List ...
func (f *FakeController) List(ctx context.Context, query *q.Query) (int64, []*models.RepoRecord, error) { func (f *FakeController) List(ctx context.Context, query *q.Query) ([]*models.RepoRecord, error) {
args := f.Called() args := f.Called()
var repositories []*models.RepoRecord var repositories []*models.RepoRecord
if args.Get(1) != nil { if args.Get(0) != nil {
repositories = args.Get(1).([]*models.RepoRecord) repositories = args.Get(0).([]*models.RepoRecord)
} }
return int64(args.Int(0)), repositories, args.Error(2) return repositories, args.Error(1)
} }
@ -68,3 +75,9 @@ func (f *FakeController) Delete(ctx context.Context, id int64) error {
args := f.Called() args := f.Called()
return args.Error(0) return args.Error(0)
} }
// Update ...
func (f *FakeController) Update(ctx context.Context, repository *models.RepoRecord, properties ...string) error {
args := f.Called()
return args.Error(0)
}

View File

@ -27,14 +27,20 @@ type FakeManager struct {
mock.Mock mock.Mock
} }
// Count ...
func (f *FakeManager) Count(ctx context.Context, query *q.Query) (int64, error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}
// List ... // List ...
func (f *FakeManager) List(ctx context.Context, query *q.Query) (int64, []*artifact.Artifact, error) { func (f *FakeManager) List(ctx context.Context, query *q.Query) ([]*artifact.Artifact, error) {
args := f.Called() args := f.Called()
var artifacts []*artifact.Artifact var artifacts []*artifact.Artifact
if args.Get(1) != nil { if args.Get(0) != nil {
artifacts = args.Get(1).([]*artifact.Artifact) artifacts = args.Get(0).([]*artifact.Artifact)
} }
return int64(args.Int(0)), artifacts, args.Error(2) return artifacts, args.Error(1)
} }
// Get ... // Get ...

View File

@ -26,14 +26,20 @@ type FakeManager struct {
mock.Mock mock.Mock
} }
// Count ...
func (f *FakeManager) Count(ctx context.Context, query *q.Query) (int64, error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}
// List ... // List ...
func (f *FakeManager) List(ctx context.Context, query *q.Query) (int64, []*models.RepoRecord, error) { func (f *FakeManager) List(ctx context.Context, query *q.Query) ([]*models.RepoRecord, error) {
args := f.Called() args := f.Called()
var repositories []*models.RepoRecord var repositories []*models.RepoRecord
if args.Get(1) != nil { if args.Get(0) != nil {
repositories = args.Get(1).([]*models.RepoRecord) repositories = args.Get(0).([]*models.RepoRecord)
} }
return int64(args.Int(0)), repositories, args.Error(2) return repositories, args.Error(1)
} }
// Get ... // Get ...

View File

@ -26,14 +26,20 @@ type FakeManager struct {
mock.Mock mock.Mock
} }
// Count ...
func (f *FakeManager) Count(ctx context.Context, query *q.Query) (int64, error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}
// List ... // List ...
func (f *FakeManager) List(ctx context.Context, query *q.Query) (int64, []*tag.Tag, error) { func (f *FakeManager) List(ctx context.Context, query *q.Query) ([]*tag.Tag, error) {
args := f.Called() args := f.Called()
var tags []*tag.Tag var tags []*tag.Tag
if args.Get(1) != nil { if args.Get(0) != nil {
tags = args.Get(1).([]*tag.Tag) tags = args.Get(0).([]*tag.Tag)
} }
return int64(args.Int(0)), tags, args.Error(2) return tags, args.Error(1)
} }
// Get ... // Get ...