Update swagger file to add retag API

Signed-off-by: 陈德 <chende@caicloud.io>
This commit is contained in:
陈德 2018-09-01 15:10:28 +08:00
parent 48d2435146
commit 75f1cdb449
6 changed files with 59 additions and 15 deletions

View File

@ -41,6 +41,32 @@ paths:
$ref: '#/definitions/Search' $ref: '#/definitions/Search'
'500': '500':
description: Unexpected internal errors. description: Unexpected internal errors.
/retag:
post:
summary: Retag an image
description: >
This endpoint tags an image with another tag in the same repo, or
to another repo or project.
parameters:
- name: request
in: body
description: reqeust to given source image and target image
required: true
schema:
$ref: '#/definitions/RetagReq'
tags:
- Products
responses:
'200':
description: Image retag successfully.
'400':
description: Invalid image values provided
'401':
description: User has no permission to the source project or destination project.
'404':
description: Project or repository not found.
'500':
description: Unexpected internal errors.
/projects: /projects:
get: get:
summary: List projects summary: List projects
@ -2966,6 +2992,15 @@ definitions:
type: array type: array
items: items:
$ref: '#/definitions/SearchResult' $ref: '#/definitions/SearchResult'
RetagReq:
type: object
properties:
src_image:
description: Source image to be retagged, e.g. 'stage/app:v1.0'
type: string
dest_image:
description: Destination image tag to, e.g. 'product/app:v1.0'
type: string
SearchRepository: SearchRepository:
type: object type: object
properties: properties:

View File

@ -19,17 +19,21 @@ import (
"strings" "strings"
) )
// RetagRequest gives the source image and target image of retag
type RetagRequest struct { type RetagRequest struct {
SrcImage string `json:"src_image"` SrcImage string `json:"src_image"`
DestImage string `json:"dest_image"` DestImage string `json:"dest_image"`
} }
// Image holds each part (project, repo, tag) of an image name
type Image struct { type Image struct {
Project string Project string
Repo string Repo string
Tag string Tag string
} }
// ParseImage parses an image name such as 'library/app:v1.0' to a structure with
// project, repo, and tag fields
func ParseImage(image string) (*Image, error) { func ParseImage(image string) (*Image, error) {
repo := strings.SplitN(image, "/", 2) repo := strings.SplitN(image, "/", 2)
if len(repo) < 2 { if len(repo) < 2 {
@ -38,10 +42,10 @@ func ParseImage(image string) (*Image, error) {
i := strings.SplitN(repo[1], ":", 2) i := strings.SplitN(repo[1], ":", 2)
res := &Image{ res := &Image{
Project: repo[0], Project: repo[0],
Repo: i[0], Repo: i[0],
} }
if len(i) == 2 { if len(i) == 2 {
res.Tag = i[1] res.Tag = i[1]
} }
return res, nil return res, nil
} }

View File

@ -15,23 +15,24 @@
package models package models
import ( import (
"testing"
"reflect" "reflect"
"testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestParseImage(t *testing.T) { func TestParseImage(t *testing.T) {
cases := []struct { cases := []struct {
Input string Input string
Expected *Image Expected *Image
Valid bool Valid bool
} { }{
{ {
Input: "library/busybox", Input: "library/busybox",
Expected: &Image{ Expected: &Image{
Project: "library", Project: "library",
Repo: "busybox", Repo: "busybox",
Tag: "", Tag: "",
}, },
Valid: true, Valid: true,
}, },
@ -39,8 +40,8 @@ func TestParseImage(t *testing.T) {
Input: "library/busybox:v1.0", Input: "library/busybox:v1.0",
Expected: &Image{ Expected: &Image{
Project: "library", Project: "library",
Repo: "busybox", Repo: "busybox",
Tag: "v1.0", Tag: "v1.0",
}, },
Valid: true, Valid: true,
}, },
@ -62,4 +63,4 @@ func TestParseImage(t *testing.T) {
} }
} }
} }
} }

View File

@ -258,6 +258,7 @@ func (r *Repository) DeleteManifest(digest string) error {
} }
} }
// MountBlob ...
func (r *Repository) MountBlob(digest, from string) error { func (r *Repository) MountBlob(digest, from string) error {
req, err := http.NewRequest("POST", buildMountBlobURL(r.Endpoint.String(), r.Name, digest, from), nil) req, err := http.NewRequest("POST", buildMountBlobURL(r.Endpoint.String(), r.Name, digest, from), nil)
req.Header.Set(http.CanonicalHeaderKey("Content-Length"), "0") req.Header.Set(http.CanonicalHeaderKey("Content-Length"), "0")

View File

@ -28,6 +28,7 @@ type RetagAPI struct {
BaseController BaseController
} }
// Retag tags an image to another
func (r *RetagAPI) Retag() { func (r *RetagAPI) Retag() {
if !r.SecurityCtx.IsAuthenticated() { if !r.SecurityCtx.IsAuthenticated() {
r.HandleUnauthorized() r.HandleUnauthorized()

View File

@ -25,13 +25,15 @@ import (
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
) )
// Retag tags an image to another
func Retag(srcImage, destImage *models.Image) error { func Retag(srcImage, destImage *models.Image) error {
isSameRepo := getRepoName(srcImage) == getRepoName(destImage)
srcClient, err := NewRepositoryClientForUI("harbor-ui", getRepoName(srcImage)) srcClient, err := NewRepositoryClientForUI("harbor-ui", getRepoName(srcImage))
if err != nil { if err != nil {
return err return err
} }
destClient := srcClient destClient := srcClient
if getRepoName(srcImage) != getRepoName(destImage) { if !isSameRepo {
destClient, err = NewRepositoryClientForUI("harbor-ui", getRepoName(destImage)) destClient, err = NewRepositoryClientForUI("harbor-ui", getRepoName(destImage))
if err != nil { if err != nil {
return err return err
@ -70,7 +72,7 @@ func Retag(srcImage, destImage *models.Image) error {
return nil return nil
} }
if getRepoName(srcImage) != getRepoName(destImage) { if !isSameRepo {
for _, descriptor := range manifest.References() { for _, descriptor := range manifest.References() {
err := destClient.MountBlob(descriptor.Digest.String(), srcClient.Name) err := destClient.MountBlob(descriptor.Digest.String(), srcClient.Name)
if err != nil { if err != nil {