From 98759642b78ac8e813b85cd3181589b0ced977b7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?=
 <yinw@vmware.com>
Date: Sun, 29 Mar 2020 12:19:54 +0800
Subject: [PATCH] Add API to list tags under the specific repository (#11336)

Add API to list tags under the specific repository

Signed-off-by: Wenkai Yin <yinw@vmware.com>
---
 api/v2.0/swagger.yaml                | 50 +++++++++++++++
 src/common/rbac/const.go             | 55 +++++++---------
 src/common/rbac/project_rbac_role.go | 89 +++-----------------------
 src/common/rbac/project_rbac_util.go | 13 +---
 src/server/v2.0/handler/handler.go   |  1 +
 src/server/v2.0/handler/tag.go       | 93 ++++++++++++++++++++++++++++
 6 files changed, 175 insertions(+), 126 deletions(-)
 create mode 100644 src/server/v2.0/handler/tag.go

diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml
index 46fa15561..b0d5e04c5 100644
--- a/api/v2.0/swagger.yaml
+++ b/api/v2.0/swagger.yaml
@@ -529,6 +529,56 @@ paths:
           $ref: '#/responses/409'
         '500':
           $ref: '#/responses/500'
+  /projects/{project_name}/repositories/{repository_name}/tags:
+    get:
+      summary: List tags
+      description: List tags under the specific project and repository.
+      tags:
+        - tag
+      operationId: listTags
+      parameters:
+        - $ref: '#/parameters/requestId'
+        - $ref: '#/parameters/projectName'
+        - $ref: '#/parameters/repositoryName'
+        - $ref: '#/parameters/query'
+        - $ref: '#/parameters/page'
+        - $ref: '#/parameters/pageSize'
+        - name: with_signature
+          in: query
+          description: Specify whether the signature is included inside the returning tags
+          type: boolean
+          required: false
+          default: false
+        - name: with_immutable_status
+          in: query
+          description: Specify whether the immutable status is included inside the returning tags
+          type: boolean
+          required: false
+          default: false
+      responses:
+        '200':
+          description: Success
+          headers:
+            X-Total-Count:
+              description: The total count of tags
+              type: integer
+            Link:
+              description: Link refers to the previous page and next page
+              type: string
+          schema:
+            type: array
+            items:
+              $ref: '#/definitions/Tag'
+        '400':
+          $ref: '#/responses/400'
+        '401':
+          $ref: '#/responses/401'
+        '403':
+          $ref: '#/responses/403'
+        '404':
+          $ref: '#/responses/404'
+        '500':
+          $ref: '#/responses/500'
   /audit-logs:
     get:
       summary: Get recent logs of the projects which the user is a member of
diff --git a/src/common/rbac/const.go b/src/common/rbac/const.go
index f3c7edd67..f71332b7d 100755
--- a/src/common/rbac/const.go
+++ b/src/common/rbac/const.go
@@ -34,37 +34,26 @@ const (
 
 // const resource variables
 const (
-	ResourceAll                        = Resource("*")             // resource match any other resources
-	ResourceConfiguration              = Resource("configuration") // project configuration compatible for portal only
-	ResourceHelmChart                  = Resource("helm-chart")
-	ResourceHelmChartVersion           = Resource("helm-chart-version")
-	ResourceHelmChartVersionLabel      = Resource("helm-chart-version-label")
-	ResourceLabel                      = Resource("label")
-	ResourceLabelResource              = Resource("label-resource")
-	ResourceLog                        = Resource("log")
-	ResourceMember                     = Resource("member")
-	ResourceMetadata                   = Resource("metadata")
-	ResourceQuota                      = Resource("quota")
-	ResourceReplication                = Resource("replication")     // TODO remove
-	ResourceReplicationJob             = Resource("replication-job") // TODO remove
-	ResourceReplicationExecution       = Resource("replication-execution")
-	ResourceReplicationTask            = Resource("replication-task")
-	ResourceRepository                 = Resource("repository")
-	ResourceTagRetention               = Resource("tag-retention")
-	ResourceImmutableTag               = Resource("immutable-tag")
-	ResourceRepositoryLabel            = Resource("repository-label")     // TODO remove
-	ResourceRepositoryTag              = Resource("repository-tag")       // TODO remove
-	ResourceRepositoryTagLabel         = Resource("repository-tag-label") // TODO remove
-	ResourceRepositoryTagManifest      = Resource("repository-tag-manifest")
-	ResourceRepositoryTagScanJob       = Resource("repository-tag-scan-job")      // TODO: remove
-	ResourceRepositoryTagVulnerability = Resource("repository-tag-vulnerability") // TODO: remove
-	ResourceRobot                      = Resource("robot")
-	ResourceNotificationPolicy         = Resource("notification-policy")
-	ResourceScan                       = Resource("scan")
-	ResourceScanner                    = Resource("scanner")
-	ResourceArtifact                   = Resource("artifact")
-	ResourceTag                        = Resource("tag")
-	ResourceArtifactAddition           = Resource("artifact-addition")
-	ResourceArtifactLabel              = Resource("artifact-label")
-	ResourceSelf                       = Resource("") // subresource for self
+	ResourceAll                   = Resource("*")             // resource match any other resources
+	ResourceConfiguration         = Resource("configuration") // project configuration compatible for portal only
+	ResourceHelmChart             = Resource("helm-chart")
+	ResourceHelmChartVersion      = Resource("helm-chart-version")
+	ResourceHelmChartVersionLabel = Resource("helm-chart-version-label")
+	ResourceLabel                 = Resource("label")
+	ResourceLog                   = Resource("log")
+	ResourceMember                = Resource("member")
+	ResourceMetadata              = Resource("metadata")
+	ResourceQuota                 = Resource("quota")
+	ResourceRepository            = Resource("repository")
+	ResourceTagRetention          = Resource("tag-retention")
+	ResourceImmutableTag          = Resource("immutable-tag")
+	ResourceRobot                 = Resource("robot")
+	ResourceNotificationPolicy    = Resource("notification-policy")
+	ResourceScan                  = Resource("scan")
+	ResourceScanner               = Resource("scanner")
+	ResourceArtifact              = Resource("artifact")
+	ResourceTag                   = Resource("tag")
+	ResourceArtifactAddition      = Resource("artifact-addition")
+	ResourceArtifactLabel         = Resource("artifact-label")
+	ResourceSelf                  = Resource("") // subresource for self
 )
diff --git a/src/common/rbac/project_rbac_role.go b/src/common/rbac/project_rbac_role.go
index 17d8b3ec2..87ce9e9f6 100644
--- a/src/common/rbac/project_rbac_role.go
+++ b/src/common/rbac/project_rbac_role.go
@@ -39,20 +39,12 @@ var (
 
 			{Resource: ResourceLog, Action: ActionList},
 
-			{Resource: ResourceReplication, Action: ActionRead},
-			{Resource: ResourceReplication, Action: ActionList},
-
-			{Resource: ResourceReplicationJob, Action: ActionRead},
-			{Resource: ResourceReplicationJob, Action: ActionList},
-
 			{Resource: ResourceLabel, Action: ActionCreate},
 			{Resource: ResourceLabel, Action: ActionRead},
 			{Resource: ResourceLabel, Action: ActionUpdate},
 			{Resource: ResourceLabel, Action: ActionDelete},
 			{Resource: ResourceLabel, Action: ActionList},
 
-			{Resource: ResourceLabelResource, Action: ActionList},
-
 			{Resource: ResourceQuota, Action: ActionRead},
 
 			{Resource: ResourceRepository, Action: ActionCreate},
@@ -75,25 +67,6 @@ var (
 			{Resource: ResourceImmutableTag, Action: ActionDelete},
 			{Resource: ResourceImmutableTag, Action: ActionList},
 
-			{Resource: ResourceRepositoryLabel, Action: ActionCreate},
-			{Resource: ResourceRepositoryLabel, Action: ActionDelete},
-			{Resource: ResourceRepositoryLabel, Action: ActionList},
-
-			{Resource: ResourceRepositoryTag, Action: ActionRead},
-			{Resource: ResourceRepositoryTag, Action: ActionDelete},
-			{Resource: ResourceRepositoryTag, Action: ActionList},
-
-			{Resource: ResourceRepositoryTagScanJob, Action: ActionCreate},
-			{Resource: ResourceRepositoryTagScanJob, Action: ActionRead},
-
-			{Resource: ResourceRepositoryTagVulnerability, Action: ActionList},
-
-			{Resource: ResourceRepositoryTagManifest, Action: ActionRead},
-
-			{Resource: ResourceRepositoryTagLabel, Action: ActionCreate},
-			{Resource: ResourceRepositoryTagLabel, Action: ActionDelete},
-			{Resource: ResourceRepositoryTagLabel, Action: ActionList},
-
 			{Resource: ResourceHelmChart, Action: ActionCreate}, // upload helm chart
 			{Resource: ResourceHelmChart, Action: ActionRead},   // download helm chart
 			{Resource: ResourceHelmChart, Action: ActionDelete},
@@ -134,6 +107,7 @@ var (
 			{Resource: ResourceArtifact, Action: ActionList},
 			{Resource: ResourceArtifactAddition, Action: ActionRead},
 
+			{Resource: ResourceTag, Action: ActionList},
 			{Resource: ResourceTag, Action: ActionCreate},
 			{Resource: ResourceTag, Action: ActionDelete},
 
@@ -156,9 +130,6 @@ var (
 
 			{Resource: ResourceQuota, Action: ActionRead},
 
-			{Resource: ResourceReplication, Action: ActionRead},
-			{Resource: ResourceReplication, Action: ActionList},
-
 			{Resource: ResourceLabel, Action: ActionCreate},
 			{Resource: ResourceLabel, Action: ActionRead},
 			{Resource: ResourceLabel, Action: ActionUpdate},
@@ -185,25 +156,6 @@ var (
 			{Resource: ResourceImmutableTag, Action: ActionDelete},
 			{Resource: ResourceImmutableTag, Action: ActionList},
 
-			{Resource: ResourceRepositoryLabel, Action: ActionCreate},
-			{Resource: ResourceRepositoryLabel, Action: ActionDelete},
-			{Resource: ResourceRepositoryLabel, Action: ActionList},
-
-			{Resource: ResourceRepositoryTag, Action: ActionRead},
-			{Resource: ResourceRepositoryTag, Action: ActionDelete},
-			{Resource: ResourceRepositoryTag, Action: ActionList},
-
-			{Resource: ResourceRepositoryTagScanJob, Action: ActionCreate},
-			{Resource: ResourceRepositoryTagScanJob, Action: ActionRead},
-
-			{Resource: ResourceRepositoryTagVulnerability, Action: ActionList},
-
-			{Resource: ResourceRepositoryTagManifest, Action: ActionRead},
-
-			{Resource: ResourceRepositoryTagLabel, Action: ActionCreate},
-			{Resource: ResourceRepositoryTagLabel, Action: ActionDelete},
-			{Resource: ResourceRepositoryTagLabel, Action: ActionList},
-
 			{Resource: ResourceHelmChart, Action: ActionCreate},
 			{Resource: ResourceHelmChart, Action: ActionRead},
 			{Resource: ResourceHelmChart, Action: ActionDelete},
@@ -235,6 +187,7 @@ var (
 			{Resource: ResourceArtifact, Action: ActionList},
 			{Resource: ResourceArtifactAddition, Action: ActionRead},
 
+			{Resource: ResourceTag, Action: ActionList},
 			{Resource: ResourceTag, Action: ActionCreate},
 			{Resource: ResourceTag, Action: ActionDelete},
 
@@ -262,21 +215,6 @@ var (
 			{Resource: ResourceRepository, Action: ActionPush},
 			{Resource: ResourceRepository, Action: ActionPull},
 
-			{Resource: ResourceRepositoryLabel, Action: ActionCreate},
-			{Resource: ResourceRepositoryLabel, Action: ActionDelete},
-			{Resource: ResourceRepositoryLabel, Action: ActionList},
-
-			{Resource: ResourceRepositoryTag, Action: ActionRead},
-			{Resource: ResourceRepositoryTag, Action: ActionList},
-
-			{Resource: ResourceRepositoryTagVulnerability, Action: ActionList},
-
-			{Resource: ResourceRepositoryTagManifest, Action: ActionRead},
-
-			{Resource: ResourceRepositoryTagLabel, Action: ActionCreate},
-			{Resource: ResourceRepositoryTagLabel, Action: ActionDelete},
-			{Resource: ResourceRepositoryTagLabel, Action: ActionList},
-
 			{Resource: ResourceHelmChart, Action: ActionCreate},
 			{Resource: ResourceHelmChart, Action: ActionRead},
 			{Resource: ResourceHelmChart, Action: ActionList},
@@ -302,6 +240,7 @@ var (
 			{Resource: ResourceArtifact, Action: ActionList},
 			{Resource: ResourceArtifactAddition, Action: ActionRead},
 
+			{Resource: ResourceTag, Action: ActionList},
 			{Resource: ResourceTag, Action: ActionCreate},
 
 			{Resource: ResourceArtifactLabel, Action: ActionCreate},
@@ -325,17 +264,6 @@ var (
 			{Resource: ResourceRepository, Action: ActionList},
 			{Resource: ResourceRepository, Action: ActionPull},
 
-			{Resource: ResourceRepositoryLabel, Action: ActionList},
-
-			{Resource: ResourceRepositoryTag, Action: ActionRead},
-			{Resource: ResourceRepositoryTag, Action: ActionList},
-
-			{Resource: ResourceRepositoryTagLabel, Action: ActionList},
-
-			{Resource: ResourceRepositoryTagVulnerability, Action: ActionList},
-
-			{Resource: ResourceRepositoryTagManifest, Action: ActionRead},
-
 			{Resource: ResourceHelmChart, Action: ActionRead},
 			{Resource: ResourceHelmChart, Action: ActionList},
 
@@ -351,6 +279,8 @@ var (
 
 			{Resource: ResourceScanner, Action: ActionRead},
 
+			{Resource: ResourceTag, Action: ActionList},
+
 			{Resource: ResourceArtifact, Action: ActionRead},
 			{Resource: ResourceArtifact, Action: ActionList},
 			{Resource: ResourceArtifactAddition, Action: ActionRead},
@@ -364,13 +294,6 @@ var (
 			{Resource: ResourceRepository, Action: ActionList},
 			{Resource: ResourceRepository, Action: ActionPull},
 
-			{Resource: ResourceRepositoryTag, Action: ActionRead},
-			{Resource: ResourceRepositoryTag, Action: ActionList},
-
-			{Resource: ResourceRepositoryTagVulnerability, Action: ActionList},
-
-			{Resource: ResourceRepositoryTagManifest, Action: ActionRead},
-
 			{Resource: ResourceHelmChart, Action: ActionRead},
 			{Resource: ResourceHelmChart, Action: ActionList},
 
@@ -383,6 +306,8 @@ var (
 
 			{Resource: ResourceScanner, Action: ActionRead},
 
+			{Resource: ResourceTag, Action: ActionList},
+
 			{Resource: ResourceArtifact, Action: ActionRead},
 			{Resource: ResourceArtifact, Action: ActionList},
 			{Resource: ResourceArtifactAddition, Action: ActionRead},
diff --git a/src/common/rbac/project_rbac_util.go b/src/common/rbac/project_rbac_util.go
index 8bcf1ab58..ea7e2198d 100644
--- a/src/common/rbac/project_rbac_util.go
+++ b/src/common/rbac/project_rbac_util.go
@@ -29,17 +29,6 @@ var (
 		{Resource: ResourceRepository, Action: ActionList},
 		{Resource: ResourceRepository, Action: ActionPull},
 
-		{Resource: ResourceRepositoryLabel, Action: ActionList},
-
-		{Resource: ResourceRepositoryTag, Action: ActionRead},
-		{Resource: ResourceRepositoryTag, Action: ActionList},
-
-		{Resource: ResourceRepositoryTagLabel, Action: ActionList},
-
-		{Resource: ResourceRepositoryTagVulnerability, Action: ActionList},
-
-		{Resource: ResourceRepositoryTagManifest, Action: ActionRead},
-
 		{Resource: ResourceHelmChart, Action: ActionRead},
 		{Resource: ResourceHelmChart, Action: ActionList},
 
@@ -49,6 +38,8 @@ var (
 		{Resource: ResourceScan, Action: ActionRead},
 		{Resource: ResourceScanner, Action: ActionRead},
 
+		{Resource: ResourceTag, Action: ActionList},
+
 		{Resource: ResourceArtifact, Action: ActionRead},
 		{Resource: ResourceArtifact, Action: ActionList},
 		{Resource: ResourceArtifactAddition, Action: ActionRead},
diff --git a/src/server/v2.0/handler/handler.go b/src/server/v2.0/handler/handler.go
index e35cb1fc0..09a9b0aa1 100644
--- a/src/server/v2.0/handler/handler.go
+++ b/src/server/v2.0/handler/handler.go
@@ -34,6 +34,7 @@ func New() http.Handler {
 		AuditlogAPI:   newAuditLogAPI(),
 		ScanAPI:       newScanAPI(),
 		ProjectAPI:    newProjectAPI(),
+		TagAPI:        newTagAPI(),
 	})
 	if err != nil {
 		log.Fatal(err)
diff --git a/src/server/v2.0/handler/tag.go b/src/server/v2.0/handler/tag.go
new file mode 100644
index 000000000..d907221f5
--- /dev/null
+++ b/src/server/v2.0/handler/tag.go
@@ -0,0 +1,93 @@
+// Copyright Project Harbor Authors
+//
+// 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 handler
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/go-openapi/runtime/middleware"
+	"github.com/goharbor/harbor/src/common/rbac"
+	"github.com/goharbor/harbor/src/controller/repository"
+	"github.com/goharbor/harbor/src/controller/tag"
+	"github.com/goharbor/harbor/src/server/v2.0/models"
+	operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/tag"
+)
+
+func newTagAPI() *tagAPI {
+	return &tagAPI{
+		repoCtl: repository.Ctl,
+		tagCtl:  tag.Ctl,
+	}
+}
+
+type tagAPI struct {
+	BaseAPI
+	repoCtl repository.Controller
+	tagCtl  tag.Controller
+}
+
+func (t *tagAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder {
+	if err := unescapePathParams(params, "RepositoryName"); err != nil {
+		t.SendError(ctx, err)
+	}
+	return nil
+}
+
+func (t *tagAPI) ListTags(ctx context.Context, params operation.ListTagsParams) middleware.Responder {
+	if err := t.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionList, rbac.ResourceTag); err != nil {
+		return t.SendError(ctx, err)
+	}
+	// set query
+	query, err := t.BuildQuery(ctx, params.Q, params.Page, params.PageSize)
+	if err != nil {
+		return t.SendError(ctx, err)
+	}
+
+	repository, err := t.repoCtl.GetByName(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName))
+	if err != nil {
+		return t.SendError(ctx, err)
+	}
+	query.Keywords["RepositoryID"] = repository.RepositoryID
+
+	// get the total count of tags
+	total, err := t.tagCtl.Count(ctx, query)
+	if err != nil {
+		return t.SendError(ctx, err)
+	}
+
+	// set option
+	option := &tag.Option{}
+	if params.WithSignature != nil {
+		option.WithSignature = *params.WithSignature
+	}
+	if params.WithImmutableStatus != nil {
+		option.WithImmutableStatus = *params.WithImmutableStatus
+	}
+	// list tags according to the query and option
+	tags, err := t.tagCtl.List(ctx, query, option)
+	if err != nil {
+		return t.SendError(ctx, err)
+	}
+
+	var ts []*models.Tag
+	for _, tag := range tags {
+		ts = append(ts, tag.ToSwagger())
+	}
+	return operation.NewListTagsOK().
+		WithXTotalCount(total).
+		WithLink(t.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
+		WithPayload(ts)
+}