mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 12:15:20 +01:00
Implement artifact/tag related API
Implement APIs: 1. Get artifact 2. Delete artifact 3. Create tag 4. Delete tag Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
parent
e75220ab38
commit
793b23a444
@ -18,80 +18,6 @@ securityDefinitions:
|
||||
security:
|
||||
- basicAuth: []
|
||||
paths:
|
||||
/projects/{project_name}/repositories/{repository_name}/artifacts/{digest}:
|
||||
get:
|
||||
summary: Get the specific artifact
|
||||
description: Get the artifact specified by the digest under the project and repository
|
||||
tags:
|
||||
- artifact
|
||||
operationId: getArtifact
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/projectName'
|
||||
- $ref: '#/parameters/repositoryName'
|
||||
- $ref: '#/parameters/digest'
|
||||
- name: with_tag
|
||||
in: query
|
||||
description: Specify whether the tags are inclued inside the returning artifacts
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
- name: with_label
|
||||
in: query
|
||||
description: Specify whether the labels are inclued inside the returning artifacts
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
- name: with_scan_overview
|
||||
in: query
|
||||
description: Specify whether the scan overview is inclued inside the returning artifacts
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
# should be in tag level
|
||||
- name: with_signatrue
|
||||
in: query
|
||||
description: Specify whether the signature is inclued inside the returning artifacts
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
- name: with_immutable_status
|
||||
in: query
|
||||
description: Specify whether the immutable status is inclued inside the tags of the returning artifacts. Only works when setting "with_tag=true"
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
schema:
|
||||
$ref: '#/definitions/Artifact'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
delete:
|
||||
summary: Delete the specific artifact
|
||||
description: Delete the artifact specified by the digest under the project and repository
|
||||
tags:
|
||||
- artifact
|
||||
operationId: deleteArtifact
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/projectName'
|
||||
- $ref: '#/parameters/repositoryName'
|
||||
- $ref: '#/parameters/digest'
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/projects/{project_name}/repositories/{repository_name}/artifacts:
|
||||
get:
|
||||
summary: List artifacts
|
||||
@ -129,7 +55,7 @@ paths:
|
||||
required: false
|
||||
default: false
|
||||
# should be in tag level
|
||||
- name: with_signatrue
|
||||
- name: with_signature
|
||||
in: query
|
||||
description: Specify whether the signature is inclued inside the returning artifacts
|
||||
type: boolean
|
||||
@ -156,10 +82,151 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Artifact'
|
||||
'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/{reference}:
|
||||
get:
|
||||
summary: Get the specific artifact
|
||||
description: Get the artifact specified by the reference under the project and repository. The reference can be digest or tag.
|
||||
tags:
|
||||
- artifact
|
||||
operationId: getArtifact
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/projectName'
|
||||
- $ref: '#/parameters/repositoryName'
|
||||
- $ref: '#/parameters/reference'
|
||||
- name: with_tag
|
||||
in: query
|
||||
description: Specify whether the tags are inclued inside the returning artifacts
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
- name: with_label
|
||||
in: query
|
||||
description: Specify whether the labels are inclued inside the returning artifacts
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
- name: with_scan_overview
|
||||
in: query
|
||||
description: Specify whether the scan overview is inclued inside the returning artifacts
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
# should be in tag level
|
||||
- name: with_signature
|
||||
in: query
|
||||
description: Specify whether the signature is inclued inside the returning artifacts
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
- name: with_immutable_status
|
||||
in: query
|
||||
description: Specify whether the immutable status is inclued inside the tags of the returning artifacts. Only works when setting "with_tag=true"
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
schema:
|
||||
$ref: '#/definitions/Artifact'
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
delete:
|
||||
summary: Delete the specific artifact
|
||||
description: Delete the artifact specified by the reference under the project and repository. The reference can be digest or tag
|
||||
tags:
|
||||
- artifact
|
||||
operationId: deleteArtifact
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/projectName'
|
||||
- $ref: '#/parameters/repositoryName'
|
||||
- $ref: '#/parameters/reference'
|
||||
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}/tags:
|
||||
post:
|
||||
summary: Create tag
|
||||
description: Create a tag for the specified artifact
|
||||
tags:
|
||||
- artifact
|
||||
operationId: createTag
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/projectName'
|
||||
- $ref: '#/parameters/repositoryName'
|
||||
- $ref: '#/parameters/reference'
|
||||
- name: tag
|
||||
in: body
|
||||
description: The JSON object of tag.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Tag'
|
||||
responses:
|
||||
'201':
|
||||
$ref: '#/responses/201'
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'409':
|
||||
$ref: '#/responses/409'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/tags/{tag_name}:
|
||||
delete:
|
||||
summary: Delete tag
|
||||
description: Delete the tag of the specified artifact
|
||||
tags:
|
||||
- artifact
|
||||
operationId: deleteTag
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/projectName'
|
||||
- $ref: '#/parameters/repositoryName'
|
||||
- $ref: '#/parameters/reference'
|
||||
- $ref: '#/parameters/tagName'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/responses/200'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
parameters:
|
||||
@ -182,10 +249,16 @@ parameters:
|
||||
description: The name of the repository
|
||||
required: true
|
||||
type: string
|
||||
digest:
|
||||
name: digest
|
||||
reference:
|
||||
name: reference
|
||||
in: path
|
||||
description: The digest of the artifact
|
||||
description: The reference of the artifact, can be digest or tag
|
||||
required: true
|
||||
type: string
|
||||
tagName:
|
||||
name: tag_name
|
||||
in: path
|
||||
description: The name of the tag
|
||||
required: true
|
||||
type: string
|
||||
page:
|
||||
@ -205,6 +278,26 @@ parameters:
|
||||
description: The size of per page
|
||||
default: 10
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
headers:
|
||||
X-Request-Id:
|
||||
description: The ID of the corresponding request for the response
|
||||
type: string
|
||||
'201':
|
||||
description: Created
|
||||
headers:
|
||||
X-Request-Id:
|
||||
description: The ID of the corresponding request for the response
|
||||
type: string
|
||||
'400':
|
||||
description: Bad request
|
||||
headers:
|
||||
X-Request-Id:
|
||||
description: The ID of the corresponding request for the response
|
||||
type: string
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
headers:
|
||||
@ -221,6 +314,22 @@ responses:
|
||||
type: string
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
'404':
|
||||
description: Not found
|
||||
headers:
|
||||
X-Request-Id:
|
||||
description: The ID of the corresponding request for the response
|
||||
type: string
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
'409':
|
||||
description: Conflict
|
||||
headers:
|
||||
X-Request-Id:
|
||||
description: The ID of the corresponding request for the response
|
||||
type: string
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
'500':
|
||||
description: Internal server error
|
||||
headers:
|
||||
|
@ -54,8 +54,10 @@ type Controller interface {
|
||||
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
|
||||
Tags(ctx context.Context, query *q.Query, option *TagOption) (total int64, tags []*Tag, err error)
|
||||
// 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)
|
||||
// CreateTag creates a tag
|
||||
CreateTag(ctx context.Context, tag *Tag) (id int64, err error)
|
||||
// DeleteTag deletes the tag specified by tagID
|
||||
DeleteTag(ctx context.Context, tagID int64) (err error)
|
||||
// UpdatePullTime updates the pull time for the artifact. If the tagID is provides, update the pull
|
||||
@ -285,7 +287,11 @@ func (c *controller) Delete(ctx context.Context, id int64) error {
|
||||
// TODO fire delete artifact event
|
||||
return nil
|
||||
}
|
||||
func (c *controller) Tags(ctx context.Context, query *q.Query, option *TagOption) (int64, []*Tag, error) {
|
||||
|
||||
func (c *controller) CreateTag(ctx context.Context, tag *Tag) (int64, error) {
|
||||
return c.tagMgr.Create(ctx, &(tag.Tag))
|
||||
}
|
||||
func (c *controller) ListTags(ctx context.Context, query *q.Query, option *TagOption) (int64, []*Tag, error) {
|
||||
total, tgs, err := c.tagMgr.List(ctx, query)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
|
@ -385,7 +385,7 @@ func (c *controllerTestSuite) TestDelete() {
|
||||
c.tagMgr.AssertExpectations(c.T())
|
||||
}
|
||||
|
||||
func (c *controllerTestSuite) TestTags() {
|
||||
func (c *controllerTestSuite) TestListTags() {
|
||||
c.tagMgr.On("List").Return(1, []*tag.Tag{
|
||||
{
|
||||
ID: 1,
|
||||
@ -394,7 +394,7 @@ func (c *controllerTestSuite) TestTags() {
|
||||
Name: "latest",
|
||||
},
|
||||
}, nil)
|
||||
total, tags, err := c.ctl.Tags(nil, nil, nil)
|
||||
total, tags, err := c.ctl.ListTags(nil, nil, nil)
|
||||
c.Require().Nil(err)
|
||||
c.Equal(int64(1), total)
|
||||
c.Len(tags, 1)
|
||||
@ -402,6 +402,13 @@ func (c *controllerTestSuite) TestTags() {
|
||||
// TODO check other properties: label, etc
|
||||
}
|
||||
|
||||
func (c *controllerTestSuite) TestCreateTag() {
|
||||
c.tagMgr.On("Create").Return(1, nil)
|
||||
id, err := c.ctl.CreateTag(nil, &Tag{})
|
||||
c.Require().Nil(err)
|
||||
c.Equal(int64(1), id)
|
||||
}
|
||||
|
||||
func (c *controllerTestSuite) TestDeleteTag() {
|
||||
c.tagMgr.On("Delete").Return(nil)
|
||||
err := c.ctl.DeleteTag(nil, 1)
|
||||
|
@ -76,6 +76,7 @@ require (
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/theupdateframework/notary v0.6.1
|
||||
golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 // indirect
|
||||
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
|
||||
|
@ -19,11 +19,13 @@ import (
|
||||
"fmt"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/goharbor/harbor/src/api/artifact"
|
||||
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"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/artifact"
|
||||
"time"
|
||||
)
|
||||
|
||||
func newArtifactAPI() *artifactAPI {
|
||||
@ -64,28 +66,8 @@ func (a *artifactAPI) ListArtifacts(ctx context.Context, params operation.ListAr
|
||||
query.Keywords["RepositoryID"] = repository.RepositoryID
|
||||
|
||||
// set option
|
||||
option := &artifact.Option{
|
||||
WithTag: true, // return the tag by default
|
||||
}
|
||||
if params.WithTag != nil {
|
||||
option.WithTag = *(params.WithTag)
|
||||
}
|
||||
if option.WithTag {
|
||||
if params.WithImmutableStatus != nil {
|
||||
option.TagOption = &artifact.TagOption{
|
||||
WithImmutableStatus: *(params.WithImmutableStatus),
|
||||
}
|
||||
}
|
||||
}
|
||||
if params.WithLabel != nil {
|
||||
option.WithLabel = *(params.WithLabel)
|
||||
}
|
||||
if params.WithScanOverview != nil {
|
||||
option.WithScanOverview = *(params.WithScanOverview)
|
||||
}
|
||||
if params.WithSignatrue != nil {
|
||||
option.WithSignature = *(params.WithSignatrue)
|
||||
}
|
||||
option := option(params.WithTag, params.WithImmutableStatus,
|
||||
params.WithLabel, params.WithScanOverview, params.WithSignature)
|
||||
|
||||
// list artifacts according to the query and option
|
||||
total, arts, err := a.artCtl.List(ctx, query, option)
|
||||
@ -103,11 +85,98 @@ func (a *artifactAPI) ListArtifacts(ctx context.Context, params operation.ListAr
|
||||
}
|
||||
|
||||
func (a *artifactAPI) GetArtifact(ctx context.Context, params operation.GetArtifactParams) middleware.Responder {
|
||||
// TODO implement
|
||||
return operation.NewGetArtifactOK()
|
||||
// set option
|
||||
option := option(params.WithTag, params.WithImmutableStatus,
|
||||
params.WithLabel, params.WithScanOverview, params.WithSignature)
|
||||
|
||||
// get the artifact
|
||||
artifact, err := a.artCtl.GetByReference(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName), params.Reference, option)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
return operation.NewGetArtifactOK().WithPayload(artifact.ToSwagger())
|
||||
}
|
||||
|
||||
func (a *artifactAPI) DeleteArtifact(ctx context.Context, params operation.DeleteArtifactParams) middleware.Responder {
|
||||
// TODO implement
|
||||
artifact, err := a.artCtl.GetByReference(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName), params.Reference, nil)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
if err = a.artCtl.Delete(ctx, artifact.ID); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
return operation.NewDeleteArtifactOK()
|
||||
}
|
||||
|
||||
func (a *artifactAPI) CreateTag(ctx context.Context, params operation.CreateTagParams) middleware.Responder {
|
||||
art, err := a.artCtl.GetByReference(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName),
|
||||
params.Reference, &artifact.Option{
|
||||
WithTag: true,
|
||||
})
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
tag := &artifact.Tag{}
|
||||
tag.RepositoryID = art.RepositoryID
|
||||
tag.ArtifactID = art.ID
|
||||
tag.Name = params.Tag.Name
|
||||
tag.PushTime = time.Now()
|
||||
if _, err = a.artCtl.CreateTag(ctx, tag); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
// TODO set location header?
|
||||
return operation.NewCreateTagCreated()
|
||||
}
|
||||
|
||||
func (a *artifactAPI) DeleteTag(ctx context.Context, params operation.DeleteTagParams) middleware.Responder {
|
||||
artifact, err := a.artCtl.GetByReference(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName),
|
||||
params.Reference, &artifact.Option{
|
||||
WithTag: true,
|
||||
})
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
var id int64
|
||||
for _, tag := range artifact.Tags {
|
||||
if tag.Name == params.TagName {
|
||||
id = tag.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
// the tag not found
|
||||
if id == 0 {
|
||||
err = ierror.New(nil).WithCode(ierror.NotFoundCode).WithMessage(
|
||||
"tag %s attached to artifact %d not found", params.TagName, artifact.ID)
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
if err = a.artCtl.DeleteTag(ctx, id); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
return operation.NewDeleteTagOK()
|
||||
}
|
||||
|
||||
func option(withTag, withImmutableStatus, withLabel, withScanOverview, withSignature *bool) *artifact.Option {
|
||||
option := &artifact.Option{
|
||||
WithTag: true, // return the tag by default
|
||||
}
|
||||
if withTag != nil {
|
||||
option.WithTag = *(withTag)
|
||||
}
|
||||
if option.WithTag {
|
||||
if withImmutableStatus != nil {
|
||||
option.TagOption = &artifact.TagOption{
|
||||
WithImmutableStatus: *(withImmutableStatus),
|
||||
}
|
||||
}
|
||||
}
|
||||
if withLabel != nil {
|
||||
option.WithLabel = *(withLabel)
|
||||
}
|
||||
if withScanOverview != nil {
|
||||
option.WithScanOverview = *(withScanOverview)
|
||||
}
|
||||
if withSignature != nil {
|
||||
option.WithSignature = *(withSignature)
|
||||
}
|
||||
return option
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user