diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 638ce5cbc..d9a2a75e9 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -41,6 +41,32 @@ paths: $ref: '#/definitions/Search' '500': 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: get: summary: List projects @@ -2966,6 +2992,15 @@ definitions: type: array items: $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: type: object properties: diff --git a/src/common/models/retag.go b/src/common/models/retag.go index e0b1ab134..5ab9763f1 100644 --- a/src/common/models/retag.go +++ b/src/common/models/retag.go @@ -19,17 +19,21 @@ import ( "strings" ) +// RetagRequest gives the source image and target image of retag type RetagRequest struct { - SrcImage string `json:"src_image"` - DestImage string `json:"dest_image"` + SrcImage string `json:"src_image"` + DestImage string `json:"dest_image"` } +// Image holds each part (project, repo, tag) of an image name type Image struct { Project string Repo 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) { repo := strings.SplitN(image, "/", 2) if len(repo) < 2 { @@ -38,10 +42,10 @@ func ParseImage(image string) (*Image, error) { i := strings.SplitN(repo[1], ":", 2) res := &Image{ Project: repo[0], - Repo: i[0], + Repo: i[0], } if len(i) == 2 { res.Tag = i[1] } return res, nil -} \ No newline at end of file +} diff --git a/src/common/models/retag_test.go b/src/common/models/retag_test.go index 5728dd859..f30d82ae2 100644 --- a/src/common/models/retag_test.go +++ b/src/common/models/retag_test.go @@ -15,23 +15,24 @@ package models import ( - "testing" "reflect" + "testing" + "github.com/stretchr/testify/assert" ) func TestParseImage(t *testing.T) { cases := []struct { - Input string + Input string Expected *Image - Valid bool - } { + Valid bool + }{ { Input: "library/busybox", Expected: &Image{ Project: "library", - Repo: "busybox", - Tag: "", + Repo: "busybox", + Tag: "", }, Valid: true, }, @@ -39,8 +40,8 @@ func TestParseImage(t *testing.T) { Input: "library/busybox:v1.0", Expected: &Image{ Project: "library", - Repo: "busybox", - Tag: "v1.0", + Repo: "busybox", + Tag: "v1.0", }, Valid: true, }, @@ -62,4 +63,4 @@ func TestParseImage(t *testing.T) { } } } -} \ No newline at end of file +} diff --git a/src/common/utils/registry/repository.go b/src/common/utils/registry/repository.go index 2f56d05dd..7e7a8a27f 100644 --- a/src/common/utils/registry/repository.go +++ b/src/common/utils/registry/repository.go @@ -258,6 +258,7 @@ func (r *Repository) DeleteManifest(digest string) error { } } +// MountBlob ... func (r *Repository) MountBlob(digest, from string) error { req, err := http.NewRequest("POST", buildMountBlobURL(r.Endpoint.String(), r.Name, digest, from), nil) req.Header.Set(http.CanonicalHeaderKey("Content-Length"), "0") diff --git a/src/ui/api/retag.go b/src/ui/api/retag.go index b0259dc9b..8334dda37 100644 --- a/src/ui/api/retag.go +++ b/src/ui/api/retag.go @@ -28,6 +28,7 @@ type RetagAPI struct { BaseController } +// Retag tags an image to another func (r *RetagAPI) Retag() { if !r.SecurityCtx.IsAuthenticated() { r.HandleUnauthorized() diff --git a/src/ui/utils/retag.go b/src/ui/utils/retag.go index 1abf43bb2..0394f0408 100644 --- a/src/ui/utils/retag.go +++ b/src/ui/utils/retag.go @@ -25,13 +25,15 @@ import ( "github.com/docker/distribution/manifest/schema2" ) +// Retag tags an image to another func Retag(srcImage, destImage *models.Image) error { + isSameRepo := getRepoName(srcImage) == getRepoName(destImage) srcClient, err := NewRepositoryClientForUI("harbor-ui", getRepoName(srcImage)) if err != nil { return err } destClient := srcClient - if getRepoName(srcImage) != getRepoName(destImage) { + if !isSameRepo { destClient, err = NewRepositoryClientForUI("harbor-ui", getRepoName(destImage)) if err != nil { return err @@ -70,7 +72,7 @@ func Retag(srcImage, destImage *models.Image) error { return nil } - if getRepoName(srcImage) != getRepoName(destImage) { + if !isSameRepo { for _, descriptor := range manifest.References() { err := destClient.MountBlob(descriptor.Digest.String(), srcClient.Name) if err != nil {