From 03d5157eafb69efaa10c399831805e4ccdd18be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BE=B7?= Date: Tue, 18 Sep 2018 14:05:35 +0800 Subject: [PATCH] Updae retag api spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 陈德 --- docs/swagger.yaml | 62 ++++++++++++++------------ src/common/models/retag.go | 5 ++- src/common/models/retag_test.go | 9 ++++ src/core/api/repository.go | 76 +++++++++++++++++++++++++++++++ src/core/router.go | 3 +- src/ui/api/retag.go | 79 --------------------------------- 6 files changed, 122 insertions(+), 112 deletions(-) delete mode 100644 src/ui/api/retag.go diff --git a/docs/swagger.yaml b/docs/swagger.yaml index d9a2a75e92..0ca3f5c8cc 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -41,32 +41,6 @@ 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 @@ -1157,6 +1131,33 @@ paths: $ref: '#/definitions/DetailedTag' '500': description: Unexpected internal errors. + post: + summary: Retag an image + description: > + This endpoint tags an existing image with another tag in this repo, source images + can be in different repos or projects. + parameters: + - name: request + in: body + description: reqeust to given source image and target tag + 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. + '409': + description: Target tag already exists. + '500': + description: Unexpected internal errors. '/repositories/{repo_name}/tags/{tag}/labels': get: summary: Get labels of an image. @@ -2995,12 +2996,15 @@ definitions: RetagReq: type: object properties: + tag: + description: new tag to be created + type: string 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 + override: + description: If target tag already exists, whether to override it + type: boolean SearchRepository: type: object properties: diff --git a/src/common/models/retag.go b/src/common/models/retag.go index 5ab9763f17..7f29514359 100644 --- a/src/common/models/retag.go +++ b/src/common/models/retag.go @@ -21,8 +21,9 @@ import ( // RetagRequest gives the source image and target image of retag type RetagRequest struct { - SrcImage string `json:"src_image"` - DestImage string `json:"dest_image"` + Tag string `json:"tag"` // The new tag + SrcImage string `json:"src_image"` // Source images in format /: + Override bool `json:"override"` // If target tag exists, whether override it } // Image holds each part (project, repo, tag) of an image name diff --git a/src/common/models/retag_test.go b/src/common/models/retag_test.go index f30d82ae27..721d0a3405 100644 --- a/src/common/models/retag_test.go +++ b/src/common/models/retag_test.go @@ -45,6 +45,15 @@ func TestParseImage(t *testing.T) { }, Valid: true, }, + { + Input: "library/busybox:sha256:9e2c9d5f44efbb6ee83aecd17a120c513047d289d142ec5738c9f02f9b24ad07", + Expected: &Image{ + Project: "library", + Repo: "busybox", + Tag: "sha256:9e2c9d5f44efbb6ee83aecd17a120c513047d289d142ec5738c9f02f9b24ad07", + }, + Valid: true, + }, { Input: "busybox/v1.0", Valid: false, diff --git a/src/core/api/repository.go b/src/core/api/repository.go index 6f1b46d3b6..780e4d9bfe 100644 --- a/src/core/api/repository.go +++ b/src/core/api/repository.go @@ -424,6 +424,82 @@ func (ra *RepositoryAPI) GetTag() { ra.ServeJSON() } +// Retag tags an existing image to another tag in this repo, the source image is specified by request body. +func (ra *RepositoryAPI) Retag() { + if !ra.SecurityCtx.IsAuthenticated() { + ra.HandleUnauthorized() + return + } + + repoName := ra.GetString(":splat") + request := models.RetagRequest{} + ra.DecodeJSONReq(&request) + srcImage, err := models.ParseImage(request.SrcImage) + if err != nil { + ra.HandleBadRequest(fmt.Sprintf("invalid src image string '%s', should in format '/:'", request.SrcImage)) + return + } + + // Check whether source image exists + exist, _, err := ra.checkExistence(fmt.Sprintf("%s/%s", srcImage.Project, srcImage.Repo), srcImage.Tag) + if err != nil { + ra.HandleInternalServerError(fmt.Sprintf("check existence of %s error: %v", request.SrcImage, err)) + return + } + if !exist { + ra.HandleNotFound(fmt.Sprintf("image %s not exist", request.SrcImage)) + return + } + + // Check whether target project exists + project, repo := utils.ParseRepository(repoName) + exist, err = ra.ProjectMgr.Exists(project) + if err != nil { + ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", project), err) + return + } + if !exist { + ra.HandleNotFound(fmt.Sprintf("project %s not found", project)) + return + } + + // If override not allowed, check whether target tag already exists + if !request.Override { + exist, _, err := ra.checkExistence(repoName, request.Tag) + if err != nil { + ra.HandleInternalServerError(fmt.Sprintf("check existence of %s:%s error: %v", repoName, request.Tag, err)) + return + } + if exist { + ra.HandleConflict(fmt.Sprintf("tag '%s' already existed for '%s'", request.Tag, repoName)) + return + } + } + + // Check whether use has read permission to source project + if !ra.SecurityCtx.HasReadPerm(srcImage.Project) { + log.Errorf("user has no read permission to project '%s'", srcImage.Project) + ra.HandleUnauthorized() + return + } + + // Check whether user has write permission to target project + if !ra.SecurityCtx.HasWritePerm(project) { + log.Errorf("user has no write permission to project '%s'", project) + ra.HandleUnauthorized() + return + } + + // Retag the image + if err = uiutils.Retag(srcImage, &models.Image{ + Project: project, + Repo: repo, + Tag: request.Tag, + }); err != nil { + ra.HandleInternalServerError(fmt.Sprintf("%v", err)) + } +} + // GetTags returns tags of a repository func (ra *RepositoryAPI) GetTags() { repoName := ra.GetString(":splat") diff --git a/src/core/router.go b/src/core/router.go index c19693c9a6..78734f4496 100644 --- a/src/core/router.go +++ b/src/core/router.go @@ -58,7 +58,6 @@ func initRouters() { // API beego.Router("/api/ping", &api.SystemInfoAPI{}, "get:Ping") beego.Router("/api/search", &api.SearchAPI{}) - beego.Router("api/retag", &api.RetagAPI{}, "post:Retag") beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List;post:Post") beego.Router("/api/projects/:id([0-9]+)/logs", &api.ProjectAPI{}, "get:Logs") beego.Router("/api/projects/:id([0-9]+)/_deletable", &api.ProjectAPI{}, "get:Deletable") @@ -73,7 +72,7 @@ func initRouters() { beego.Router("/api/repositories/*/tags/:tag", &api.RepositoryAPI{}, "delete:Delete;get:GetTag") beego.Router("/api/repositories/*/tags/:tag/labels", &api.RepositoryLabelAPI{}, "get:GetOfImage;post:AddToImage") beego.Router("/api/repositories/*/tags/:tag/labels/:id([0-9]+)", &api.RepositoryLabelAPI{}, "delete:RemoveFromImage") - beego.Router("/api/repositories/*/tags", &api.RepositoryAPI{}, "get:GetTags") + beego.Router("/api/repositories/*/tags", &api.RepositoryAPI{}, "get:GetTags;post:Retag") beego.Router("/api/repositories/*/tags/:tag/scan", &api.RepositoryAPI{}, "post:ScanImage") beego.Router("/api/repositories/*/tags/:tag/vulnerability/details", &api.RepositoryAPI{}, "Get:VulnerabilityDetails") beego.Router("/api/repositories/*/tags/:tag/manifest", &api.RepositoryAPI{}, "get:GetManifests") diff --git a/src/ui/api/retag.go b/src/ui/api/retag.go deleted file mode 100644 index 8334dda378..0000000000 --- a/src/ui/api/retag.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2017 VMware, Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package api - -import ( - "fmt" - - "github.com/goharbor/harbor/src/common/dao" - "github.com/goharbor/harbor/src/common/models" - "github.com/goharbor/harbor/src/common/utils/log" - "github.com/goharbor/harbor/src/ui/utils" -) - -// RetagAPI retag an image -type RetagAPI struct { - BaseController -} - -// Retag tags an image to another -func (r *RetagAPI) Retag() { - if !r.SecurityCtx.IsAuthenticated() { - r.HandleUnauthorized() - return - } - - request := models.RetagRequest{} - r.DecodeJSONReq(&request) - - srcImage, err := models.ParseImage(request.SrcImage) - if err != nil { - r.HandleBadRequest(fmt.Sprintf("invalid src image string '%s', should in format '/:'", request.SrcImage)) - return - } - destImage, err := models.ParseImage(request.DestImage) - if err != nil { - r.HandleBadRequest(fmt.Sprintf("invalid dest image string '%s', should in format '/:'", request.DestImage)) - return - } - - if !dao.RepositoryExists(fmt.Sprintf("%s/%s", srcImage.Project, srcImage.Repo)) { - log.Errorf("source repository '%s/%s' not exist", srcImage.Project, srcImage.Repo) - r.HandleNotFound(fmt.Sprintf("repository '%s/%s' not found", srcImage.Project, srcImage.Repo)) - return - } - - if !dao.ProjectExistsByName(destImage.Project) { - log.Errorf("destination project '%s' not exist", destImage.Project) - r.HandleNotFound(fmt.Sprintf("project '%s' not found", destImage.Project)) - return - } - - if !r.SecurityCtx.HasReadPerm(srcImage.Project) { - log.Errorf("user has no read permission to project '%s'", srcImage.Project) - r.HandleUnauthorized() - return - } - - if !r.SecurityCtx.HasWritePerm(destImage.Project) { - log.Errorf("user has no write permission to project '%s'", destImage.Project) - r.HandleUnauthorized() - return - } - - if err = utils.Retag(srcImage, destImage); err != nil { - r.HandleInternalServerError(fmt.Sprintf("%v", err)) - } -}