mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-28 05:18:01 +02:00
Implement the listing artifact API
Implement the listing artifact API Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
parent
8b3313a1ce
commit
19f4bad042
@ -18,21 +18,52 @@ securityDefinitions:
|
|||||||
security:
|
security:
|
||||||
- basicAuth: []
|
- basicAuth: []
|
||||||
paths:
|
paths:
|
||||||
/projects/{project_id}/repositories/{repository_id}/artifacts/{artifact_id}:
|
/projects/{project_name}/repositories/{repository_name}/artifacts/{digest}:
|
||||||
get:
|
get:
|
||||||
summary: Read artifact by id
|
summary: Get the specific artifact
|
||||||
description: endpoint returns artifact by id
|
description: Get the artifact specified by the digest under the project and repository
|
||||||
tags:
|
tags:
|
||||||
- artifact
|
- artifact
|
||||||
operationId: readArtifact
|
operationId: getArtifact
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/parameters/requestId'
|
- $ref: '#/parameters/requestId'
|
||||||
- $ref: '#/parameters/projectId'
|
- $ref: '#/parameters/projectName'
|
||||||
- $ref: '#/parameters/repositoryId'
|
- $ref: '#/parameters/repositoryName'
|
||||||
- $ref: '#/parameters/artifactId'
|
- $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:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: OK
|
description: Success
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Artifact'
|
$ref: '#/definitions/Artifact'
|
||||||
'401':
|
'401':
|
||||||
@ -42,59 +73,78 @@ paths:
|
|||||||
'500':
|
'500':
|
||||||
$ref: '#/responses/500'
|
$ref: '#/responses/500'
|
||||||
delete:
|
delete:
|
||||||
summary: Delete artifact by id
|
summary: Delete the specific artifact
|
||||||
description: endpoint to delete the artifact by id
|
description: Delete the artifact specified by the digest under the project and repository
|
||||||
tags:
|
tags:
|
||||||
- artifact
|
- artifact
|
||||||
operationId: deleteArtifact
|
operationId: deleteArtifact
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/parameters/requestId'
|
- $ref: '#/parameters/requestId'
|
||||||
- $ref: '#/parameters/projectId'
|
- $ref: '#/parameters/projectName'
|
||||||
- $ref: '#/parameters/repositoryId'
|
- $ref: '#/parameters/repositoryName'
|
||||||
- $ref: '#/parameters/artifactId'
|
- $ref: '#/parameters/digest'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Artifact is deleted successfully.
|
description: Success
|
||||||
'401':
|
'401':
|
||||||
$ref: '#/responses/401'
|
$ref: '#/responses/401'
|
||||||
'403':
|
'403':
|
||||||
$ref: '#/responses/403'
|
$ref: '#/responses/403'
|
||||||
'500':
|
'500':
|
||||||
$ref: '#/responses/500'
|
$ref: '#/responses/500'
|
||||||
/projects/{project_id}/repositories/{repository_id}/artifacts:
|
/projects/{project_name}/repositories/{repository_name}/artifacts:
|
||||||
get:
|
get:
|
||||||
summary: List artifacts of the repository
|
summary: List artifacts
|
||||||
description: endpoint returns all artifacts of the repository.
|
description: List artifacts under the specific project and repository.
|
||||||
tags:
|
tags:
|
||||||
- artifact
|
- artifact
|
||||||
operationId: listArtifacts
|
operationId: listArtifacts
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/parameters/requestId'
|
- $ref: '#/parameters/requestId'
|
||||||
- $ref: '#/parameters/projectId'
|
- $ref: '#/parameters/projectName'
|
||||||
- $ref: '#/parameters/repositoryId'
|
- $ref: '#/parameters/repositoryName'
|
||||||
- $ref: '#/parameters/page'
|
- $ref: '#/parameters/page'
|
||||||
- $ref: '#/parameters/pageSize'
|
- $ref: '#/parameters/pageSize'
|
||||||
- name: label
|
- name: type
|
||||||
in: query
|
in: query
|
||||||
description: Response for artifact include label info when it's true
|
description: Query the artifacts by type. Valid values can be "IMAGE", "CHART", etc.
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
- 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
|
type: boolean
|
||||||
required: false
|
required: false
|
||||||
default: false
|
default: false
|
||||||
- name: signature
|
- name: with_scan_overview
|
||||||
in: query
|
in: query
|
||||||
description: Response for artifact include signature info when it's true
|
description: Specify whether the scan overview is inclued inside the returning artifacts
|
||||||
type: boolean
|
type: boolean
|
||||||
required: false
|
required: false
|
||||||
default: false
|
default: false
|
||||||
- name: vulnerability
|
# should be in tag level
|
||||||
|
- name: with_signatrue
|
||||||
in: query
|
in: query
|
||||||
description: Response for artifact include vulnerability info when it's true
|
description: Specify whether the signature is inclued inside the returning artifacts
|
||||||
type: boolean
|
type: boolean
|
||||||
required: false
|
required: false
|
||||||
default: 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
|
||||||
|
# TODO add other query string: type, ....
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: OK
|
description: Success
|
||||||
headers:
|
headers:
|
||||||
X-Total-Count:
|
X-Total-Count:
|
||||||
description: The total count of artifacts
|
description: The total count of artifacts
|
||||||
@ -115,70 +165,67 @@ paths:
|
|||||||
parameters:
|
parameters:
|
||||||
requestId:
|
requestId:
|
||||||
name: X-Request-Id
|
name: X-Request-Id
|
||||||
description: A unique id for the request
|
description: An unique ID for the request
|
||||||
in: header
|
in: header
|
||||||
type: string
|
type: string
|
||||||
required: false
|
required: false
|
||||||
minLength: 1
|
minLength: 1
|
||||||
projectId:
|
projectName:
|
||||||
name: project_id
|
name: project_name
|
||||||
in: path
|
in: path
|
||||||
description: The id of the project
|
description: The name of the project
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: string
|
||||||
format: int64
|
repositoryName:
|
||||||
repositoryId:
|
name: repository_name
|
||||||
name: repository_id
|
|
||||||
in: path
|
in: path
|
||||||
description: The id of the repository
|
description: The name of the repository
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: string
|
||||||
format: int64
|
digest:
|
||||||
artifactId:
|
name: digest
|
||||||
name: artifact_id
|
|
||||||
in: path
|
in: path
|
||||||
description: The id of the artifact
|
description: The digest of the artifact
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: string
|
||||||
format: int64
|
|
||||||
page:
|
page:
|
||||||
name: page
|
name: page
|
||||||
in: query
|
in: query
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int64
|
||||||
required: false
|
required: false
|
||||||
description: 'The page number, default is 1.'
|
description: The page number
|
||||||
default: 1
|
default: 1
|
||||||
pageSize:
|
pageSize:
|
||||||
name: page_size
|
name: page_size
|
||||||
in: query
|
in: query
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int64
|
||||||
required: false
|
required: false
|
||||||
description: 'The size of per page, default is 10, maximum is 100.'
|
description: The size of per page
|
||||||
default: 10
|
default: 10
|
||||||
responses:
|
responses:
|
||||||
'401':
|
'401':
|
||||||
description: UnauthorizedError
|
description: Unauthorized
|
||||||
headers:
|
headers:
|
||||||
X-Request-Id:
|
X-Request-Id:
|
||||||
description: The request id this is a response to
|
description: The ID of the corresponding request for the response
|
||||||
type: string
|
type: string
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
'403':
|
'403':
|
||||||
description: ForbiddenError
|
description: Forbidden
|
||||||
headers:
|
headers:
|
||||||
X-Request-Id:
|
X-Request-Id:
|
||||||
description: The request id this is a response to
|
description: The ID of the corresponding request for the response
|
||||||
type: string
|
type: string
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
'500':
|
'500':
|
||||||
description: InternalServerError
|
description: Internal server error
|
||||||
headers:
|
headers:
|
||||||
X-Request-Id:
|
X-Request-Id:
|
||||||
description: The request id this is a response to
|
description: The ID of the corresponding request for the response
|
||||||
type: string
|
type: string
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
@ -204,40 +251,132 @@ definitions:
|
|||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
description: The id of the artifact
|
description: The ID of the artifact
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
description: The type of the artifact, eg image, chart, etc
|
description: The type of the artifact, e.g. image, chart, etc
|
||||||
repository:
|
|
||||||
$ref: '#/definitions/Repository'
|
|
||||||
description: The repository of the artifact
|
|
||||||
tags:
|
|
||||||
description: The list of tags that attached to the artifact
|
|
||||||
media_type:
|
media_type:
|
||||||
type: string
|
type: string
|
||||||
description: The specific media type for the artifact
|
description: The media type of the artifact
|
||||||
|
manifest_media_type:
|
||||||
|
type: string
|
||||||
|
description: The manifest media type of the artifact
|
||||||
|
project_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The ID of the project that the artifact belongs to
|
||||||
|
repository_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The ID of the repository that the artifact belongs to
|
||||||
digest:
|
digest:
|
||||||
type: string
|
type: string
|
||||||
description: The digest of the artifact
|
description: The digest of the artifact
|
||||||
size:
|
size:
|
||||||
type: string
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
description: The size of the artifact
|
description: The size of the artifact
|
||||||
upload_time:
|
push_time:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
description: The upload time for the artifact
|
description: The push time of the artifact
|
||||||
labels:
|
pull_time:
|
||||||
description: The list of labels that attached to the artifact
|
type: string
|
||||||
signature:
|
format: date-time
|
||||||
description: The signature attached to the artifact
|
description: The latest pull time of the artifact
|
||||||
Repository:
|
extra_attrs:
|
||||||
|
$ref: '#/definitions/ExtraAttrs'
|
||||||
|
annotations:
|
||||||
|
$ref: '#/definitions/Annotations'
|
||||||
|
references:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Reference'
|
||||||
|
tags:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Tag'
|
||||||
|
sub_resource_links:
|
||||||
|
$ref: '#/definitions/SubResourceLinks'
|
||||||
|
Tag:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
description: The id of the repository
|
description: The ID of the tag
|
||||||
|
repository_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The ID of the repository that the tag belongs to
|
||||||
|
artifact_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The ID of the artifact that the tag attached to
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: The name of repository.
|
description: The name of the tag
|
||||||
|
push_time:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: The push time of the tag
|
||||||
|
pull_time:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: The latest pull time of the tag
|
||||||
|
ExtraAttrs:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: object
|
||||||
|
Annotations:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
SubResourceLinks:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/ResourceLink'
|
||||||
|
ResourceLink:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
href:
|
||||||
|
type: string
|
||||||
|
description: The link of the resource
|
||||||
|
absolute:
|
||||||
|
type: boolean
|
||||||
|
description: Determine whether the link is an absolute URL or not
|
||||||
|
Reference:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
parent_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The parent ID of the reference
|
||||||
|
child_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The child ID of the reference
|
||||||
|
platform:
|
||||||
|
$ref: '#/definitions/Platform'
|
||||||
|
Platform:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
architecture:
|
||||||
|
type: string
|
||||||
|
description: The architecture that the artifact applys to
|
||||||
|
os:
|
||||||
|
type: string
|
||||||
|
description: The OS that the artifact applys to
|
||||||
|
os.version:
|
||||||
|
type: string
|
||||||
|
description: The version of the OS that the artifact applys to
|
||||||
|
os.features:
|
||||||
|
type: array
|
||||||
|
description: The features of the OS that the artifact applys to
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
variant:
|
||||||
|
type: string
|
||||||
|
description: The variant of the CPU
|
||||||
|
@ -296,7 +296,13 @@ func (c *controller) assembleArtifact(ctx context.Context, art *artifact.Artifac
|
|||||||
log.Errorf("failed to list tag of artifact %d: %v", artifact.ID, err)
|
log.Errorf("failed to list tag of artifact %d: %v", artifact.ID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO populate other properties: scan, signature etc.
|
if option.WithLabel {
|
||||||
|
// TODO populate label
|
||||||
|
}
|
||||||
|
if option.WithScanOverview {
|
||||||
|
// TODO populate scan overview
|
||||||
|
}
|
||||||
|
// TODO populate signature on artifact or label level?
|
||||||
return artifact
|
return artifact
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,6 +314,9 @@ func (c *controller) assembleTag(ctx context.Context, tag *tm.Tag, option *TagOp
|
|||||||
if option == nil {
|
if option == nil {
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
// TODO populate label, signature, immutable status for tag
|
if option.WithImmutableStatus {
|
||||||
|
// TODO populate immutable status
|
||||||
|
}
|
||||||
|
// TODO populate signature on tag level?
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,6 @@ func (c *controllerTestSuite) TestAssembleTag() {
|
|||||||
PullTime: time.Now(),
|
PullTime: time.Now(),
|
||||||
}
|
}
|
||||||
option := &TagOption{
|
option := &TagOption{
|
||||||
WithLabel: true,
|
|
||||||
WithImmutableStatus: true,
|
WithImmutableStatus: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,10 +84,11 @@ func (c *controllerTestSuite) TestAssembleArtifact() {
|
|||||||
option := &Option{
|
option := &Option{
|
||||||
WithTag: true,
|
WithTag: true,
|
||||||
TagOption: &TagOption{
|
TagOption: &TagOption{
|
||||||
WithLabel: false,
|
|
||||||
WithImmutableStatus: false,
|
WithImmutableStatus: false,
|
||||||
},
|
},
|
||||||
WithScanResult: true,
|
WithLabel: false,
|
||||||
|
WithScanOverview: true,
|
||||||
WithSignature: true,
|
WithSignature: true,
|
||||||
}
|
}
|
||||||
tg := &tag.Tag{
|
tg := &tag.Tag{
|
||||||
@ -210,7 +210,7 @@ func (c *controllerTestSuite) TestList() {
|
|||||||
query := &q.Query{}
|
query := &q.Query{}
|
||||||
option := &Option{
|
option := &Option{
|
||||||
WithTag: true,
|
WithTag: true,
|
||||||
WithScanResult: true,
|
WithScanOverview: true,
|
||||||
WithSignature: true,
|
WithSignature: true,
|
||||||
}
|
}
|
||||||
c.artMgr.On("List").Return(1, []*artifact.Artifact{
|
c.artMgr.On("List").Return(1, []*artifact.Artifact{
|
||||||
|
@ -15,8 +15,10 @@
|
|||||||
package artifact
|
package artifact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||||
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
|
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
|
||||||
|
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Artifact is the overall view of artifact
|
// Artifact is the overall view of artifact
|
||||||
@ -28,6 +30,62 @@ type Artifact struct {
|
|||||||
// TODO add other attrs: signature, scan result, etc
|
// TODO add other attrs: signature, scan result, etc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToSwagger converts the artifact to the swagger model
|
||||||
|
func (a *Artifact) ToSwagger() *models.Artifact {
|
||||||
|
art := &models.Artifact{
|
||||||
|
ID: a.ID,
|
||||||
|
Type: a.Type,
|
||||||
|
MediaType: a.MediaType,
|
||||||
|
ManifestMediaType: a.ManifestMediaType,
|
||||||
|
ProjectID: a.ProjectID,
|
||||||
|
RepositoryID: a.RepositoryID,
|
||||||
|
Digest: a.Digest,
|
||||||
|
Size: a.Size,
|
||||||
|
PullTime: strfmt.DateTime(a.PullTime),
|
||||||
|
PushTime: strfmt.DateTime(a.PushTime),
|
||||||
|
ExtraAttrs: a.ExtraAttrs,
|
||||||
|
Annotations: a.Annotations,
|
||||||
|
}
|
||||||
|
for _, reference := range a.References {
|
||||||
|
ref := &models.Reference{
|
||||||
|
ChildID: reference.ChildID,
|
||||||
|
ParentID: reference.ParentID,
|
||||||
|
}
|
||||||
|
if reference.Platform != nil {
|
||||||
|
ref.Platform = &models.Platform{
|
||||||
|
Architecture: reference.Platform.Architecture,
|
||||||
|
Os: reference.Platform.OS,
|
||||||
|
OsFeatures: reference.Platform.OSFeatures,
|
||||||
|
OsVersion: reference.Platform.OSVersion,
|
||||||
|
Variant: reference.Platform.Variant,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
art.References = append(art.References, ref)
|
||||||
|
}
|
||||||
|
for _, tag := range a.Tags {
|
||||||
|
art.Tags = append(art.Tags, &models.Tag{
|
||||||
|
ArtifactID: tag.ArtifactID,
|
||||||
|
ID: tag.ID,
|
||||||
|
Name: tag.Name,
|
||||||
|
PullTime: strfmt.DateTime(tag.PullTime),
|
||||||
|
PushTime: strfmt.DateTime(tag.PushTime),
|
||||||
|
RepositoryID: tag.RepositoryID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for resource, links := range a.SubResourceLinks {
|
||||||
|
for _, link := range links {
|
||||||
|
art.SubResourceLinks[resource] = []models.ResourceLink{}
|
||||||
|
if link != nil {
|
||||||
|
art.SubResourceLinks[resource] = append(art.SubResourceLinks[resource], models.ResourceLink{
|
||||||
|
Absolute: link.Absolute,
|
||||||
|
Href: link.HREF,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return art
|
||||||
|
}
|
||||||
|
|
||||||
// Tag is the overall view of tag
|
// Tag is the overall view of tag
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
tag.Tag
|
tag.Tag
|
||||||
@ -50,13 +108,14 @@ type ResourceLink struct {
|
|||||||
type Option struct {
|
type Option struct {
|
||||||
WithTag bool
|
WithTag bool
|
||||||
TagOption *TagOption // only works when WithTag is set to true
|
TagOption *TagOption // only works when WithTag is set to true
|
||||||
WithScanResult bool
|
WithLabel bool
|
||||||
WithSignature bool // TODO move it to TagOption?
|
WithScanOverview bool
|
||||||
|
// TODO move it to TagOption?
|
||||||
|
WithSignature bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TagOption is used to specify the properties returned when listing/getting tags
|
// TagOption is used to specify the properties returned when listing/getting tags
|
||||||
type TagOption struct {
|
type TagOption struct {
|
||||||
WithLabel bool
|
|
||||||
WithImmutableStatus bool
|
WithImmutableStatus bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +111,8 @@ func (r *Reference) From(ref *dao.ArtifactReference) {
|
|||||||
r.ParentID = ref.ParentID
|
r.ParentID = ref.ParentID
|
||||||
r.ChildID = ref.ChildID
|
r.ChildID = ref.ChildID
|
||||||
if len(ref.Platform) > 0 {
|
if len(ref.Platform) > 0 {
|
||||||
if err := json.Unmarshal([]byte(ref.Platform), r); err != nil {
|
r.Platform = &v1.Platform{}
|
||||||
|
if err := json.Unmarshal([]byte(ref.Platform), r.Platform); err != nil {
|
||||||
log.Errorf("failed to unmarshal the platform of reference: %v", err)
|
log.Errorf("failed to unmarshal the platform of reference: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
|
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||||
"github.com/goharbor/harbor/src/pkg/q"
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
"github.com/goharbor/harbor/src/pkg/repository/dao"
|
"github.com/goharbor/harbor/src/pkg/repository/dao"
|
||||||
)
|
)
|
||||||
@ -30,6 +31,8 @@ type Manager interface {
|
|||||||
List(ctx context.Context, query *q.Query) (total int64, repositories []*models.RepoRecord, err error)
|
List(ctx context.Context, query *q.Query) (total int64, 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(ctx context.Context, name string) (repository *models.RepoRecord, err error)
|
||||||
// Create a repository
|
// Create a repository
|
||||||
Create(ctx context.Context, repository *models.RepoRecord) (id int64, err error)
|
Create(ctx context.Context, repository *models.RepoRecord) (id int64, err error)
|
||||||
// Delete the repository specified by ID
|
// Delete the repository specified by ID
|
||||||
@ -65,6 +68,22 @@ func (m *manager) Get(ctx context.Context, id int64) (*models.RepoRecord, error)
|
|||||||
return m.dao.Get(ctx, id)
|
return m.dao.Get(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *manager) GetByName(ctx context.Context, name string) (repository *models.RepoRecord, err error) {
|
||||||
|
_, repositories, err := m.List(ctx, &q.Query{
|
||||||
|
Keywords: map[string]interface{}{
|
||||||
|
"Name": name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(repositories) == 0 {
|
||||||
|
return nil, ierror.New(nil).WithCode(ierror.NotFoundCode).
|
||||||
|
WithMessage("repository %s not found", name)
|
||||||
|
}
|
||||||
|
return repositories[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *manager) Create(ctx context.Context, repository *models.RepoRecord) (int64, error) {
|
func (m *manager) Create(ctx context.Context, repository *models.RepoRecord) (int64, error) {
|
||||||
return m.dao.Create(ctx, repository)
|
return m.dao.Create(ctx, repository)
|
||||||
}
|
}
|
||||||
|
@ -95,6 +95,21 @@ func (m *managerTestSuite) TestGet() {
|
|||||||
m.Equal(repository.RepositoryID, repo.RepositoryID)
|
m.Equal(repository.RepositoryID, repo.RepositoryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *managerTestSuite) TestGetByName() {
|
||||||
|
repository := &models.RepoRecord{
|
||||||
|
RepositoryID: 1,
|
||||||
|
ProjectID: 1,
|
||||||
|
Name: "library/hello-world",
|
||||||
|
}
|
||||||
|
m.dao.On("Count", mock.Anything).Return(1, nil)
|
||||||
|
m.dao.On("List", mock.Anything).Return([]*models.RepoRecord{repository}, nil)
|
||||||
|
repo, err := m.mgr.GetByName(nil, "library/hello-world")
|
||||||
|
m.Require().Nil(err)
|
||||||
|
m.dao.AssertExpectations(m.T())
|
||||||
|
m.Require().NotNil(repo)
|
||||||
|
m.Equal(repository.RepositoryID, repo.RepositoryID)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *managerTestSuite) TestCreate() {
|
func (m *managerTestSuite) TestCreate() {
|
||||||
m.dao.On("Create", mock.Anything).Return(1, nil)
|
m.dao.On("Create", mock.Anything).Return(1, nil)
|
||||||
id, err := m.mgr.Create(nil, &models.RepoRecord{
|
id, err := m.mgr.Create(nil, &models.RepoRecord{
|
||||||
|
@ -16,32 +16,98 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"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/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"
|
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/artifact"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ArtifactAPI the api implemention of artifacts
|
func newArtifactAPI() *artifactAPI {
|
||||||
type ArtifactAPI struct {
|
return &artifactAPI{
|
||||||
BaseAPI
|
artCtl: artifact.Ctl,
|
||||||
|
proMgr: project.Mgr,
|
||||||
|
repoMgr: repository.Mgr,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteArtifact ...
|
type artifactAPI struct {
|
||||||
func (api *ArtifactAPI) DeleteArtifact(ctx context.Context, params operation.DeleteArtifactParams) middleware.Responder {
|
BaseAPI
|
||||||
|
artCtl artifact.Controller
|
||||||
|
proMgr project.Manager
|
||||||
|
repoMgr repository.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO do auth in a separate middleware
|
||||||
|
|
||||||
|
func (a *artifactAPI) ListArtifacts(ctx context.Context, params operation.ListArtifactsParams) middleware.Responder {
|
||||||
|
// set query
|
||||||
|
query := &q.Query{
|
||||||
|
Keywords: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
if params.Type != nil {
|
||||||
|
query.Keywords["Type"] = *(params.Type)
|
||||||
|
}
|
||||||
|
if params.Page != nil {
|
||||||
|
query.PageNumber = *(params.Page)
|
||||||
|
}
|
||||||
|
if params.PageSize != nil {
|
||||||
|
query.PageSize = *(params.PageSize)
|
||||||
|
}
|
||||||
|
repository, err := a.repoMgr.GetByName(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName))
|
||||||
|
if err != nil {
|
||||||
|
return a.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// list artifacts according to the query and option
|
||||||
|
total, arts, err := a.artCtl.List(ctx, query, option)
|
||||||
|
if err != nil {
|
||||||
|
return a.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var artifacts []*models.Artifact
|
||||||
|
for _, art := range arts {
|
||||||
|
artifacts = append(artifacts, art.ToSwagger())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add link header
|
||||||
|
return operation.NewListArtifactsOK().WithXTotalCount(total).WithLink("").WithPayload(artifacts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *artifactAPI) GetArtifact(ctx context.Context, params operation.GetArtifactParams) middleware.Responder {
|
||||||
|
// TODO implement
|
||||||
|
return operation.NewGetArtifactOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *artifactAPI) DeleteArtifact(ctx context.Context, params operation.DeleteArtifactParams) middleware.Responder {
|
||||||
|
// TODO implement
|
||||||
return operation.NewDeleteArtifactOK()
|
return operation.NewDeleteArtifactOK()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListArtifacts ...
|
|
||||||
func (api *ArtifactAPI) ListArtifacts(ctx context.Context, params operation.ListArtifactsParams) middleware.Responder {
|
|
||||||
return operation.NewListArtifactsOK()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadArtifact ...
|
|
||||||
func (api *ArtifactAPI) ReadArtifact(ctx context.Context, params operation.ReadArtifactParams) middleware.Responder {
|
|
||||||
return operation.NewReadArtifactOK()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArtifactAPI returns API of artifacts
|
|
||||||
func NewArtifactAPI() *ArtifactAPI {
|
|
||||||
return &ArtifactAPI{}
|
|
||||||
}
|
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
|
// TODO move this file out of v2.0 folder as this is common for all versions of API
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ import (
|
|||||||
// New returns http handler for API V2.0
|
// New returns http handler for API V2.0
|
||||||
func New() http.Handler {
|
func New() http.Handler {
|
||||||
h, api, err := restapi.HandlerAPI(restapi.Config{
|
h, api, err := restapi.HandlerAPI(restapi.Config{
|
||||||
ArtifactAPI: NewArtifactAPI(),
|
ArtifactAPI: newArtifactAPI(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -30,13 +30,21 @@ type FakeArtifactManager struct {
|
|||||||
// List ...
|
// List ...
|
||||||
func (f *FakeArtifactManager) List(ctx context.Context, query *q.Query) (int64, []*artifact.Artifact, error) {
|
func (f *FakeArtifactManager) List(ctx context.Context, query *q.Query) (int64, []*artifact.Artifact, error) {
|
||||||
args := f.Called()
|
args := f.Called()
|
||||||
return int64(args.Int(0)), args.Get(1).([]*artifact.Artifact), args.Error(2)
|
var artifacts []*artifact.Artifact
|
||||||
|
if args.Get(1) != nil {
|
||||||
|
artifacts = args.Get(1).([]*artifact.Artifact)
|
||||||
|
}
|
||||||
|
return int64(args.Int(0)), artifacts, args.Error(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get ...
|
// Get ...
|
||||||
func (f *FakeArtifactManager) Get(ctx context.Context, id int64) (*artifact.Artifact, error) {
|
func (f *FakeArtifactManager) Get(ctx context.Context, id int64) (*artifact.Artifact, error) {
|
||||||
args := f.Called()
|
args := f.Called()
|
||||||
return args.Get(0).(*artifact.Artifact), args.Error(1)
|
var art *artifact.Artifact
|
||||||
|
if args.Get(0) != nil {
|
||||||
|
art = args.Get(0).(*artifact.Artifact)
|
||||||
|
}
|
||||||
|
return art, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create ...
|
// Create ...
|
||||||
|
@ -29,13 +29,31 @@ type FakeRepositoryManager struct {
|
|||||||
// List ...
|
// List ...
|
||||||
func (f *FakeRepositoryManager) List(ctx context.Context, query *q.Query) (int64, []*models.RepoRecord, error) {
|
func (f *FakeRepositoryManager) List(ctx context.Context, query *q.Query) (int64, []*models.RepoRecord, error) {
|
||||||
args := f.Called()
|
args := f.Called()
|
||||||
return int64(args.Int(0)), args.Get(1).([]*models.RepoRecord), args.Error(2)
|
var repositories []*models.RepoRecord
|
||||||
|
if args.Get(1) != nil {
|
||||||
|
repositories = args.Get(1).([]*models.RepoRecord)
|
||||||
|
}
|
||||||
|
return int64(args.Int(0)), repositories, args.Error(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get ...
|
// Get ...
|
||||||
func (f *FakeRepositoryManager) Get(ctx context.Context, id int64) (*models.RepoRecord, error) {
|
func (f *FakeRepositoryManager) Get(ctx context.Context, id int64) (*models.RepoRecord, error) {
|
||||||
args := f.Called()
|
args := f.Called()
|
||||||
return args.Get(0).(*models.RepoRecord), args.Error(1)
|
var repository *models.RepoRecord
|
||||||
|
if args.Get(0) != nil {
|
||||||
|
repository = args.Get(0).(*models.RepoRecord)
|
||||||
|
}
|
||||||
|
return repository, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByName ...
|
||||||
|
func (f *FakeRepositoryManager) GetByName(ctx context.Context, name string) (*models.RepoRecord, error) {
|
||||||
|
args := f.Called()
|
||||||
|
var repository *models.RepoRecord
|
||||||
|
if args.Get(0) != nil {
|
||||||
|
repository = args.Get(0).(*models.RepoRecord)
|
||||||
|
}
|
||||||
|
return repository, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete ...
|
// Delete ...
|
||||||
|
@ -29,13 +29,21 @@ type FakeTagManager struct {
|
|||||||
// List ...
|
// List ...
|
||||||
func (f *FakeTagManager) List(ctx context.Context, query *q.Query) (int64, []*tag.Tag, error) {
|
func (f *FakeTagManager) List(ctx context.Context, query *q.Query) (int64, []*tag.Tag, error) {
|
||||||
args := f.Called()
|
args := f.Called()
|
||||||
return int64(args.Int(0)), args.Get(1).([]*tag.Tag), args.Error(2)
|
var tags []*tag.Tag
|
||||||
|
if args.Get(1) != nil {
|
||||||
|
tags = args.Get(1).([]*tag.Tag)
|
||||||
|
}
|
||||||
|
return int64(args.Int(0)), tags, args.Error(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get ...
|
// Get ...
|
||||||
func (f *FakeTagManager) Get(ctx context.Context, id int64) (*tag.Tag, error) {
|
func (f *FakeTagManager) Get(ctx context.Context, id int64) (*tag.Tag, error) {
|
||||||
args := f.Called()
|
args := f.Called()
|
||||||
return args.Get(0).(*tag.Tag), args.Error(1)
|
var tg *tag.Tag
|
||||||
|
if args.Get(0) != nil {
|
||||||
|
tg = args.Get(0).(*tag.Tag)
|
||||||
|
}
|
||||||
|
return tg, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create ...
|
// Create ...
|
||||||
|
Loading…
Reference in New Issue
Block a user