mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-20 07:37:38 +01:00
Merge pull request #4396 from ywk253100/180309_label_resource
Implement adding/removing labels to/from repositories and images API
This commit is contained in:
commit
0efd8e3c54
@ -995,6 +995,86 @@ paths:
|
|||||||
description: Forbidden.
|
description: Forbidden.
|
||||||
'404':
|
'404':
|
||||||
description: Repository not found.
|
description: Repository not found.
|
||||||
|
'/repositories/{repo_name}/labels':
|
||||||
|
get:
|
||||||
|
summary: Get labels of a repository.
|
||||||
|
description: |
|
||||||
|
Get labels of a repository specified by the repo_name.
|
||||||
|
parameters:
|
||||||
|
- name: repo_name
|
||||||
|
in: path
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: The name of repository.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successfully.
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Label'
|
||||||
|
'401':
|
||||||
|
description: Unauthorized.
|
||||||
|
'403':
|
||||||
|
description: Forbidden. User should have read permisson for the repository to perform the action.
|
||||||
|
'404':
|
||||||
|
description: Repository not found.
|
||||||
|
post:
|
||||||
|
summary: Add a label to the repository.
|
||||||
|
description: |
|
||||||
|
Add a label to the repository.
|
||||||
|
parameters:
|
||||||
|
- name: repo_name
|
||||||
|
in: path
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: The name of repository.
|
||||||
|
- name: label
|
||||||
|
in: body
|
||||||
|
description: Only the ID property is required.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Label'
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successfully.
|
||||||
|
'401':
|
||||||
|
description: Unauthorized.
|
||||||
|
'403':
|
||||||
|
description: Forbidden. User should have write permisson for the repository to perform the action.
|
||||||
|
'404':
|
||||||
|
description: Resource not found.
|
||||||
|
'/repositories/{repo_name}/labels/{label_id}':
|
||||||
|
delete:
|
||||||
|
summary: Delete label from the repository.
|
||||||
|
description: |
|
||||||
|
Delete the label from the repository specified by the repo_name.
|
||||||
|
parameters:
|
||||||
|
- name: repo_name
|
||||||
|
in: path
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: The name of repository.
|
||||||
|
- name: label_id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
description: The ID of label.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successfully.
|
||||||
|
'401':
|
||||||
|
description: Unauthorized.
|
||||||
|
'403':
|
||||||
|
description: Forbidden. User should have write permisson for the repository to perform the action.
|
||||||
|
'404':
|
||||||
|
description: Resource not found.
|
||||||
'/repositories/{repo_name}/tags/{tag}':
|
'/repositories/{repo_name}/tags/{tag}':
|
||||||
get:
|
get:
|
||||||
summary: Get the tag of the repository.
|
summary: Get the tag of the repository.
|
||||||
@ -1075,6 +1155,101 @@ paths:
|
|||||||
$ref: '#/definitions/DetailedTag'
|
$ref: '#/definitions/DetailedTag'
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
|
'/repositories/{repo_name}/tags/{tag}/labels':
|
||||||
|
get:
|
||||||
|
summary: Get labels of an image.
|
||||||
|
description: |
|
||||||
|
Get labels of an image specified by the repo_name and tag.
|
||||||
|
parameters:
|
||||||
|
- name: repo_name
|
||||||
|
in: path
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: The name of repository.
|
||||||
|
- name: tag
|
||||||
|
in: path
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: The tag of the image.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successfully.
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Label'
|
||||||
|
'401':
|
||||||
|
description: Unauthorized.
|
||||||
|
'403':
|
||||||
|
description: Forbidden. User should have read permisson for the image to perform the action.
|
||||||
|
'404':
|
||||||
|
description: Resource not found.
|
||||||
|
post:
|
||||||
|
summary: Add a label to image.
|
||||||
|
description: |
|
||||||
|
Add a label to the image.
|
||||||
|
parameters:
|
||||||
|
- name: repo_name
|
||||||
|
in: path
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: The name of repository.
|
||||||
|
- name: tag
|
||||||
|
in: path
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: The tag of the image.
|
||||||
|
- name: label
|
||||||
|
in: body
|
||||||
|
description: Only the ID property is required.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Label'
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successfully.
|
||||||
|
'401':
|
||||||
|
description: Unauthorized.
|
||||||
|
'403':
|
||||||
|
description: Forbidden. User should have write permisson for the image to perform the action.
|
||||||
|
'404':
|
||||||
|
description: Resource not found.
|
||||||
|
'/repositories/{repo_name}/tags/{tag}/labels/{label_id}':
|
||||||
|
delete:
|
||||||
|
summary: Delete label from the image.
|
||||||
|
description: |
|
||||||
|
Delete the label from the image specified by the repo_name and tag.
|
||||||
|
parameters:
|
||||||
|
- name: repo_name
|
||||||
|
in: path
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: The name of repository.
|
||||||
|
- name: tag
|
||||||
|
in: path
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: The tag of the image.
|
||||||
|
- name: label_id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
description: The ID of label.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successfully.
|
||||||
|
'401':
|
||||||
|
description: Unauthorized.
|
||||||
|
'403':
|
||||||
|
description: Forbidden. User should have write permisson for the image to perform the action.
|
||||||
|
'404':
|
||||||
|
description: Resource not found.
|
||||||
'/repositories/{repo_name}/tags/{tag}/manifest':
|
'/repositories/{repo_name}/tags/{tag}/manifest':
|
||||||
get:
|
get:
|
||||||
summary: Get manifests of a relevant repository.
|
summary: Get manifests of a relevant repository.
|
||||||
@ -3039,6 +3214,11 @@ definitions:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/ComponentOverviewEntry'
|
$ref: '#/definitions/ComponentOverviewEntry'
|
||||||
|
labels:
|
||||||
|
type: array
|
||||||
|
description: The label list.
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Label'
|
||||||
ComponentOverviewEntry:
|
ComponentOverviewEntry:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -3072,6 +3252,11 @@ definitions:
|
|||||||
tags_count:
|
tags_count:
|
||||||
type: integer
|
type: integer
|
||||||
description: The tags count of repository.
|
description: The tags count of repository.
|
||||||
|
labels:
|
||||||
|
type: array
|
||||||
|
description: The label list.
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Label'
|
||||||
creation_time:
|
creation_time:
|
||||||
type: string
|
type: string
|
||||||
description: The creation time of repository.
|
description: The creation time of repository.
|
||||||
|
@ -272,6 +272,23 @@ create table harbor_label (
|
|||||||
CONSTRAINT unique_name_and_scope UNIQUE (name,scope)
|
CONSTRAINT unique_name_and_scope UNIQUE (name,scope)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table harbor_resource_label (
|
||||||
|
id int NOT NULL AUTO_INCREMENT,
|
||||||
|
label_id int NOT NULL,
|
||||||
|
# the resource_id is the ID of project when the resource_type is p
|
||||||
|
# the resource_id is the ID of repository when the resource_type is r
|
||||||
|
# the resource_id is the name of image when the resource_type is i
|
||||||
|
resource_id varchar(256) NOT NULL,
|
||||||
|
# 'p' for project
|
||||||
|
# 'r' for repository
|
||||||
|
# 'i' for image
|
||||||
|
resource_type char(1) NOT NULL,
|
||||||
|
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||||
|
update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY(id),
|
||||||
|
CONSTRAINT unique_label_resource UNIQUE (label_id,resource_id, resource_type)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `alembic_version` (
|
CREATE TABLE IF NOT EXISTS `alembic_version` (
|
||||||
`version_num` varchar(32) NOT NULL
|
`version_num` varchar(32) NOT NULL
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
@ -261,6 +261,26 @@ create table harbor_label (
|
|||||||
UNIQUE(name, scope)
|
UNIQUE(name, scope)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table harbor_resource_label (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
label_id int NOT NULL,
|
||||||
|
/*
|
||||||
|
the resource_id is the ID of project when the resource_type is p
|
||||||
|
the resource_id is the ID of repository when the resource_type is r
|
||||||
|
the resource_id is the name of image when the resource_type is i
|
||||||
|
*/
|
||||||
|
resource_id varchar(256) NOT NULL,
|
||||||
|
/*
|
||||||
|
'p' for project
|
||||||
|
'r' for repository
|
||||||
|
'i' for image
|
||||||
|
*/
|
||||||
|
resource_type char(1) NOT NULL,
|
||||||
|
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||||
|
update_time timestamp default CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE (label_id,resource_id, resource_type)
|
||||||
|
);
|
||||||
|
|
||||||
create table alembic_version (
|
create table alembic_version (
|
||||||
version_num varchar(32) NOT NULL
|
version_num varchar(32) NOT NULL
|
||||||
);
|
);
|
||||||
|
74
src/common/dao/resource_label.go
Normal file
74
src/common/dao/resource_label.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// 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 dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/orm"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddResourceLabel add a label to a resource
|
||||||
|
func AddResourceLabel(rl *models.ResourceLabel) (int64, error) {
|
||||||
|
now := time.Now()
|
||||||
|
rl.CreationTime = now
|
||||||
|
rl.UpdateTime = now
|
||||||
|
return GetOrmer().Insert(rl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResourceLabel specified by ID
|
||||||
|
func GetResourceLabel(rType, rID string, labelID int64) (*models.ResourceLabel, error) {
|
||||||
|
rl := &models.ResourceLabel{
|
||||||
|
ResourceType: rType,
|
||||||
|
ResourceID: rID,
|
||||||
|
LabelID: labelID,
|
||||||
|
}
|
||||||
|
if err := GetOrmer().Read(rl, "ResourceType", "ResourceID", "LabelID"); err != nil {
|
||||||
|
if err == orm.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabelsOfResource returns the label list of the resource
|
||||||
|
func GetLabelsOfResource(rType, rID string) ([]*models.Label, error) {
|
||||||
|
sql := `select l.id, l.name, l.description, l.color, l.scope, l.project_id, l.creation_time, l.update_time
|
||||||
|
from harbor_resource_label rl
|
||||||
|
join harbor_label l on rl.label_id=l.id
|
||||||
|
where rl.resource_type = ? and rl.resource_id = ?`
|
||||||
|
labels := []*models.Label{}
|
||||||
|
_, err := GetOrmer().Raw(sql, rType, rID).QueryRows(&labels)
|
||||||
|
return labels, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteResourceLabel ...
|
||||||
|
func DeleteResourceLabel(id int64) error {
|
||||||
|
_, err := GetOrmer().Delete(&models.ResourceLabel{
|
||||||
|
ID: id,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLabelsOfResource removes all labels of resource specified by rType and rID
|
||||||
|
func DeleteLabelsOfResource(rType, rID string) error {
|
||||||
|
_, err := GetOrmer().QueryTable(&models.ResourceLabel{}).
|
||||||
|
Filter("ResourceType", rType).
|
||||||
|
Filter("ResourceID", rID).Delete()
|
||||||
|
return err
|
||||||
|
}
|
74
src/common/dao/resource_label_test.go
Normal file
74
src/common/dao/resource_label_test.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// 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 dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMethodsOfResourceLabel(t *testing.T) {
|
||||||
|
labelID, err := AddLabel(&models.Label{
|
||||||
|
Name: "test_label",
|
||||||
|
Level: common.LabelLevelUser,
|
||||||
|
Scope: common.LabelScopeGlobal,
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
defer DeleteLabel(labelID)
|
||||||
|
|
||||||
|
resourceID := "1"
|
||||||
|
resourceType := common.ResourceTypeRepository
|
||||||
|
|
||||||
|
// add
|
||||||
|
rl := &models.ResourceLabel{
|
||||||
|
LabelID: labelID,
|
||||||
|
ResourceType: resourceType,
|
||||||
|
ResourceID: resourceID,
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := AddResourceLabel(rl)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// get
|
||||||
|
r, err := GetResourceLabel(resourceType, resourceID, labelID)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, id, r.ID)
|
||||||
|
|
||||||
|
// get by resource
|
||||||
|
labels, err := GetLabelsOfResource(resourceType, resourceID)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, 1, len(labels))
|
||||||
|
assert.Equal(t, id, r.ID)
|
||||||
|
|
||||||
|
// delete
|
||||||
|
err = DeleteResourceLabel(id)
|
||||||
|
require.Nil(t, err)
|
||||||
|
labels, err = GetLabelsOfResource(resourceType, resourceID)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, 0, len(labels))
|
||||||
|
|
||||||
|
// delete by resource
|
||||||
|
id, err = AddResourceLabel(rl)
|
||||||
|
require.Nil(t, err)
|
||||||
|
err = DeleteLabelsOfResource(resourceType, resourceID)
|
||||||
|
require.Nil(t, err)
|
||||||
|
labels, err = GetLabelsOfResource(resourceType, resourceID)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, 0, len(labels))
|
||||||
|
}
|
@ -33,5 +33,6 @@ func init() {
|
|||||||
new(WatchItem),
|
new(WatchItem),
|
||||||
new(ProjectMetadata),
|
new(ProjectMetadata),
|
||||||
new(ConfigEntry),
|
new(ConfigEntry),
|
||||||
new(Label))
|
new(Label),
|
||||||
|
new(ResourceLabel))
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ type Label struct {
|
|||||||
UpdateTime time.Time `orm:"column(update_time)" json:"update_time"`
|
UpdateTime time.Time `orm:"column(update_time)" json:"update_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//TableName ...
|
// TableName ...
|
||||||
func (l *Label) TableName() string {
|
func (l *Label) TableName() string {
|
||||||
return "harbor_label"
|
return "harbor_label"
|
||||||
}
|
}
|
||||||
@ -65,30 +65,17 @@ func (l *Label) Valid(v *validation.Validation) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// ResourceLabel records the relationship between resource and label
|
||||||
type ResourceLabel struct {
|
type ResourceLabel struct {
|
||||||
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
ID int64 `orm:"pk;auto;column(id)"`
|
||||||
LabelID int64 `orm:"column(label_id)" json:"label_id"`
|
LabelID int64 `orm:"column(label_id)"`
|
||||||
ResourceID string `orm:"column(resource_id)" json:"resource_id"`
|
ResourceID string `orm:"column(resource_id)"`
|
||||||
ResourceType rune `orm:"column(resource_type)" json:"resource_type"`
|
ResourceType string `orm:"column(resource_type)"`
|
||||||
CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"`
|
CreationTime time.Time `orm:"column(creation_time)"`
|
||||||
UpdateTime time.Time `orm:"column(update_time)" json:"update_time"`
|
UpdateTime time.Time `orm:"column(update_time)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TableName ...
|
||||||
// Valid ...
|
func (r *ResourceLabel) TableName() string {
|
||||||
func (r *ResourceLabel) Valid(v *validation.Validation) {
|
return "harbor_resource_label"
|
||||||
if r.LabelID <= 0 {
|
|
||||||
v.SetError("label_id", fmt.Sprintf("invalid: %d", r.LabelID))
|
|
||||||
}
|
|
||||||
// TODO
|
|
||||||
//if r.ResourceID <= 0 {
|
|
||||||
// v.SetError("resource_id", fmt.Sprintf("invalid: %v", r.ResourceID))
|
|
||||||
//}
|
|
||||||
if r.ResourceType != common.ResourceTypeProject &&
|
|
||||||
r.ResourceType != common.ResourceTypeRepository &&
|
|
||||||
r.ResourceType != common.ResourceTypeImage {
|
|
||||||
v.SetError("resource_type", fmt.Sprintf("invalid: %d", r.ResourceType))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
@ -111,6 +111,10 @@ func init() {
|
|||||||
beego.Router("/api/users/?:id", &UserAPI{})
|
beego.Router("/api/users/?:id", &UserAPI{})
|
||||||
beego.Router("/api/logs", &LogAPI{})
|
beego.Router("/api/logs", &LogAPI{})
|
||||||
beego.Router("/api/repositories/*", &RepositoryAPI{}, "put:Put")
|
beego.Router("/api/repositories/*", &RepositoryAPI{}, "put:Put")
|
||||||
|
beego.Router("/api/repositories/*/labels", &RepositoryLabelAPI{}, "get:GetOfRepository;post:AddToRepository")
|
||||||
|
beego.Router("/api/repositories/*/labels/:id([0-9]+", &RepositoryLabelAPI{}, "delete:RemoveFromRepository")
|
||||||
|
beego.Router("/api/repositories/*/tags/:tag/labels", &RepositoryLabelAPI{}, "get:GetOfImage;post:AddToImage")
|
||||||
|
beego.Router("/api/repositories/*/tags/:tag/labels/:id([0-9]+", &RepositoryLabelAPI{}, "delete:RemoveFromImage")
|
||||||
beego.Router("/api/repositories/*/tags/:tag", &RepositoryAPI{}, "delete:Delete;get:GetTag")
|
beego.Router("/api/repositories/*/tags/:tag", &RepositoryAPI{}, "delete:Delete;get:GetTag")
|
||||||
beego.Router("/api/repositories/*/tags", &RepositoryAPI{}, "get:GetTags")
|
beego.Router("/api/repositories/*/tags", &RepositoryAPI{}, "get:GetTags")
|
||||||
beego.Router("/api/repositories/*/tags/:tag/manifest", &RepositoryAPI{}, "get:GetManifests")
|
beego.Router("/api/repositories/*/tags/:tag/manifest", &RepositoryAPI{}, "get:GetManifests")
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/manifest/schema2"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/notifier"
|
"github.com/vmware/harbor/src/common/notifier"
|
||||||
@ -54,6 +55,7 @@ type repoResp struct {
|
|||||||
PullCount int64 `json:"pull_count"`
|
PullCount int64 `json:"pull_count"`
|
||||||
StarCount int64 `json:"star_count"`
|
StarCount int64 `json:"star_count"`
|
||||||
TagsCount int64 `json:"tags_count"`
|
TagsCount int64 `json:"tags_count"`
|
||||||
|
Labels []*models.Label `json:"labels"`
|
||||||
CreationTime time.Time `json:"creation_time"`
|
CreationTime time.Time `json:"creation_time"`
|
||||||
UpdateTime time.Time `json:"update_time"`
|
UpdateTime time.Time `json:"update_time"`
|
||||||
}
|
}
|
||||||
@ -78,6 +80,7 @@ type tagResp struct {
|
|||||||
tagDetail
|
tagDetail
|
||||||
Signature *notary.Target `json:"signature"`
|
Signature *notary.Target `json:"signature"`
|
||||||
ScanOverview *models.ImgScanOverview `json:"scan_overview,omitempty"`
|
ScanOverview *models.ImgScanOverview `json:"scan_overview,omitempty"`
|
||||||
|
Labels []*models.Label `json:"labels"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type manifestResp struct {
|
type manifestResp struct {
|
||||||
@ -145,10 +148,10 @@ func getRepositories(projectID int64, keyword string,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return populateTagsCount(repositories)
|
return assembleRepos(repositories)
|
||||||
}
|
}
|
||||||
|
|
||||||
func populateTagsCount(repositories []*models.RepoRecord) ([]*repoResp, error) {
|
func assembleRepos(repositories []*models.RepoRecord) ([]*repoResp, error) {
|
||||||
result := []*repoResp{}
|
result := []*repoResp{}
|
||||||
for _, repository := range repositories {
|
for _, repository := range repositories {
|
||||||
repo := &repoResp{
|
repo := &repoResp{
|
||||||
@ -167,6 +170,15 @@ func populateTagsCount(repositories []*models.RepoRecord) ([]*repoResp, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
repo.TagsCount = int64(len(tags))
|
repo.TagsCount = int64(len(tags))
|
||||||
|
|
||||||
|
labels, err := dao.GetLabelsOfResource(common.ResourceTypeRepository,
|
||||||
|
strconv.FormatInt(repository.RepositoryID, 10))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get labels of repository %s: %v", repository.Name, err)
|
||||||
|
} else {
|
||||||
|
repo.Labels = labels
|
||||||
|
}
|
||||||
|
|
||||||
result = append(result, repo)
|
result = append(result, repo)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
@ -252,6 +264,11 @@ func (ra *RepositoryAPI) Delete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range tags {
|
for _, t := range tags {
|
||||||
|
image := fmt.Sprintf("%s:%s", repoName, t)
|
||||||
|
if err = dao.DeleteLabelsOfResource(common.ResourceTypeImage, image); err != nil {
|
||||||
|
ra.HandleInternalServerError(fmt.Sprintf("failed to delete labels of image %s: %v", image, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
if err = rc.DeleteTag(t); err != nil {
|
if err = rc.DeleteTag(t); err != nil {
|
||||||
if regErr, ok := err.(*registry_error.HTTPError); ok {
|
if regErr, ok := err.(*registry_error.HTTPError); ok {
|
||||||
if regErr.StatusCode == http.StatusNotFound {
|
if regErr.StatusCode == http.StatusNotFound {
|
||||||
@ -296,6 +313,22 @@ func (ra *RepositoryAPI) Delete() {
|
|||||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||||
}
|
}
|
||||||
if !exist {
|
if !exist {
|
||||||
|
repository, err := dao.GetRepositoryByName(repoName)
|
||||||
|
if err != nil {
|
||||||
|
ra.HandleInternalServerError(fmt.Sprintf("failed to get repository %s: %v", repoName, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if repository == nil {
|
||||||
|
ra.HandleNotFound(fmt.Sprintf("repository %s not found", repoName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = dao.DeleteLabelsOfResource(common.ResourceTypeRepository,
|
||||||
|
strconv.FormatInt(repository.RepositoryID, 10)); err != nil {
|
||||||
|
ra.HandleInternalServerError(fmt.Sprintf("failed to delete labels of repository %s: %v",
|
||||||
|
repoName, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
if err = dao.DeleteRepository(repoName); err != nil {
|
if err = dao.DeleteRepository(repoName); err != nil {
|
||||||
log.Errorf("failed to delete repository %s: %v", repoName, err)
|
log.Errorf("failed to delete repository %s: %v", repoName, err)
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||||
@ -343,7 +376,7 @@ func (ra *RepositoryAPI) GetTag() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result := assemble(client, repository, []string{tag},
|
result := assembleTags(client, repository, []string{tag},
|
||||||
ra.SecurityCtx.GetUsername())
|
ra.SecurityCtx.GetUsername())
|
||||||
ra.Data["json"] = result[0]
|
ra.Data["json"] = result[0]
|
||||||
ra.ServeJSON()
|
ra.ServeJSON()
|
||||||
@ -387,13 +420,13 @@ func (ra *RepositoryAPI) GetTags() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ra.Data["json"] = assemble(client, repoName, tags, ra.SecurityCtx.GetUsername())
|
ra.Data["json"] = assembleTags(client, repoName, tags, ra.SecurityCtx.GetUsername())
|
||||||
ra.ServeJSON()
|
ra.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// get config, signature and scan overview and assemble them into one
|
// get config, signature and scan overview and assemble them into one
|
||||||
// struct for each tag in tags
|
// struct for each tag in tags
|
||||||
func assemble(client *registry.Repository, repository string,
|
func assembleTags(client *registry.Repository, repository string,
|
||||||
tags []string, username string) []*tagResp {
|
tags []string, username string) []*tagResp {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -435,6 +468,15 @@ func assemble(client *registry.Repository, repository string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// labels
|
||||||
|
image := fmt.Sprintf("%s:%s", repository, t)
|
||||||
|
labels, err := dao.GetLabelsOfResource(common.ResourceTypeImage, image)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get labels of image %s: %v", image, err)
|
||||||
|
} else {
|
||||||
|
item.Labels = labels
|
||||||
|
}
|
||||||
|
|
||||||
result = append(result, item)
|
result = append(result, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -633,7 +675,7 @@ func (ra *RepositoryAPI) GetTopRepos() {
|
|||||||
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := populateTagsCount(repos)
|
result, err := assembleRepos(repos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to popultate tags count to repositories: %v", err)
|
log.Errorf("failed to popultate tags count to repositories: %v", err)
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
||||||
|
248
src/ui/api/repository_label.go
Normal file
248
src/ui/api/repository_label.go
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
// 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"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
|
uiutils "github.com/vmware/harbor/src/ui/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RepositoryLabelAPI handles requests for adding/removing label to/from repositories and images
|
||||||
|
type RepositoryLabelAPI struct {
|
||||||
|
BaseController
|
||||||
|
repository *models.RepoRecord
|
||||||
|
tag string
|
||||||
|
label *models.Label
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare ...
|
||||||
|
func (r *RepositoryLabelAPI) Prepare() {
|
||||||
|
r.BaseController.Prepare()
|
||||||
|
if !r.SecurityCtx.IsAuthenticated() {
|
||||||
|
r.HandleUnauthorized()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
repository := r.GetString(":splat")
|
||||||
|
project, _ := utils.ParseRepository(repository)
|
||||||
|
if !r.SecurityCtx.HasWritePerm(project) {
|
||||||
|
r.HandleForbidden(r.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := dao.GetRepositoryByName(repository)
|
||||||
|
if err != nil {
|
||||||
|
r.HandleInternalServerError(fmt.Sprintf("failed to get repository %s: %v",
|
||||||
|
repository, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if repo == nil {
|
||||||
|
r.HandleNotFound(fmt.Sprintf("repository %s not found", repository))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.repository = repo
|
||||||
|
|
||||||
|
tag := r.GetString(":tag")
|
||||||
|
if len(tag) > 0 {
|
||||||
|
exist, err := imageExist(r.SecurityCtx.GetUsername(), repository, tag)
|
||||||
|
if err != nil {
|
||||||
|
r.HandleInternalServerError(fmt.Sprintf("failed to check the existence of image %s:%s: %v",
|
||||||
|
repository, tag, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
r.HandleNotFound(fmt.Sprintf("image %s:%s not found", repository, tag))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.tag = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Ctx.Request.Method == http.MethodPost {
|
||||||
|
l := &models.Label{}
|
||||||
|
r.DecodeJSONReq(l)
|
||||||
|
|
||||||
|
label, err := dao.GetLabel(l.ID)
|
||||||
|
if err != nil {
|
||||||
|
r.HandleInternalServerError(fmt.Sprintf("failed to get label %d: %v", l.ID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if label == nil {
|
||||||
|
r.HandleNotFound(fmt.Sprintf("label %d not found", l.ID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if label.Level != common.LabelLevelUser {
|
||||||
|
r.HandleBadRequest("only user level labels can be used")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if label.Scope == common.LabelScopeProject {
|
||||||
|
p, err := r.ProjectMgr.Get(project)
|
||||||
|
if err != nil {
|
||||||
|
r.HandleInternalServerError(fmt.Sprintf("failed to get project %s: %v", project, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ProjectID != label.ProjectID {
|
||||||
|
r.HandleBadRequest("can not add labels which don't belong to the project to the resources under the project")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.label = label
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Ctx.Request.Method == http.MethodDelete {
|
||||||
|
labelID, err := r.GetInt64FromPath(":id")
|
||||||
|
if err != nil {
|
||||||
|
r.HandleInternalServerError(fmt.Sprintf("failed to get ID parameter from path: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
label, err := dao.GetLabel(labelID)
|
||||||
|
if err != nil {
|
||||||
|
r.HandleInternalServerError(fmt.Sprintf("failed to get label %d: %v", labelID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if label == nil {
|
||||||
|
r.HandleNotFound(fmt.Sprintf("label %d not found", labelID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.label = label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOfImage returns labels of an image
|
||||||
|
func (r *RepositoryLabelAPI) GetOfImage() {
|
||||||
|
r.getLabels(common.ResourceTypeImage, fmt.Sprintf("%s:%s", r.repository.Name, r.tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToImage adds the label to an image
|
||||||
|
func (r *RepositoryLabelAPI) AddToImage() {
|
||||||
|
rl := &models.ResourceLabel{
|
||||||
|
LabelID: r.label.ID,
|
||||||
|
ResourceType: common.ResourceTypeImage,
|
||||||
|
ResourceID: fmt.Sprintf("%s:%s", r.repository.Name, r.tag),
|
||||||
|
}
|
||||||
|
r.addLabel(rl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFromImage removes the label from an image
|
||||||
|
func (r *RepositoryLabelAPI) RemoveFromImage() {
|
||||||
|
rl := &models.ResourceLabel{
|
||||||
|
LabelID: r.label.ID,
|
||||||
|
ResourceType: common.ResourceTypeImage,
|
||||||
|
ResourceID: fmt.Sprintf("%s:%s", r.repository.Name, r.tag),
|
||||||
|
}
|
||||||
|
r.removeLabel(rl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOfRepository returns labels of a repository
|
||||||
|
func (r *RepositoryLabelAPI) GetOfRepository() {
|
||||||
|
r.getLabels(common.ResourceTypeRepository, strconv.FormatInt(r.repository.RepositoryID, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToRepository adds the label to a repository
|
||||||
|
func (r *RepositoryLabelAPI) AddToRepository() {
|
||||||
|
rl := &models.ResourceLabel{
|
||||||
|
LabelID: r.label.ID,
|
||||||
|
ResourceType: common.ResourceTypeRepository,
|
||||||
|
ResourceID: strconv.FormatInt(r.repository.RepositoryID, 10),
|
||||||
|
}
|
||||||
|
r.addLabel(rl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFromRepository removes the label from a repository
|
||||||
|
func (r *RepositoryLabelAPI) RemoveFromRepository() {
|
||||||
|
rl := &models.ResourceLabel{
|
||||||
|
LabelID: r.label.ID,
|
||||||
|
ResourceType: common.ResourceTypeRepository,
|
||||||
|
ResourceID: strconv.FormatInt(r.repository.RepositoryID, 10),
|
||||||
|
}
|
||||||
|
r.removeLabel(rl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepositoryLabelAPI) getLabels(rType, rID string) {
|
||||||
|
labels, err := dao.GetLabelsOfResource(rType, rID)
|
||||||
|
if err != nil {
|
||||||
|
r.HandleInternalServerError(fmt.Sprintf("failed to get labels of resource %s %s: %v",
|
||||||
|
rType, rID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.Data["json"] = labels
|
||||||
|
r.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepositoryLabelAPI) addLabel(rl *models.ResourceLabel) {
|
||||||
|
rlabel, err := dao.GetResourceLabel(rl.ResourceType, rl.ResourceID, rl.LabelID)
|
||||||
|
if err != nil {
|
||||||
|
r.HandleInternalServerError(fmt.Sprintf("failed to check the existence of label %d for resource %s %s: %v",
|
||||||
|
rl.LabelID, rl.ResourceType, rl.ResourceID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if rlabel != nil {
|
||||||
|
r.HandleConflict()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := dao.AddResourceLabel(rl); err != nil {
|
||||||
|
r.HandleInternalServerError(fmt.Sprintf("failed to add label %d to resource %s %s: %v",
|
||||||
|
rl.LabelID, rl.ResourceType, rl.ResourceID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the ID of label and return status code 200 rather than 201 as the label is not created
|
||||||
|
r.Redirect(http.StatusOK, strconv.FormatInt(rl.LabelID, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepositoryLabelAPI) removeLabel(rl *models.ResourceLabel) {
|
||||||
|
rlabel, err := dao.GetResourceLabel(rl.ResourceType, rl.ResourceID, rl.LabelID)
|
||||||
|
if err != nil {
|
||||||
|
r.HandleInternalServerError(fmt.Sprintf("failed to check the existence of label %d for resource %s %s: %v",
|
||||||
|
rl.LabelID, rl.ResourceType, rl.ResourceID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if rlabel == nil {
|
||||||
|
r.HandleNotFound(fmt.Sprintf("label %d of resource %s %s not found",
|
||||||
|
rl.LabelID, rl.ResourceType, rl.ResourceID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = dao.DeleteResourceLabel(rlabel.ID); err != nil {
|
||||||
|
r.HandleInternalServerError(fmt.Sprintf("failed to delete resource label record %d: %v",
|
||||||
|
rlabel.ID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageExist(username, repository, tag string) (bool, error) {
|
||||||
|
client, err := uiutils.NewRepositoryClientForUI(username, repository)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exist, err := client.ManifestExist(tag)
|
||||||
|
return exist, err
|
||||||
|
}
|
253
src/ui/api/repository_label_test.go
Normal file
253
src/ui/api/repository_label_test.go
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
// 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"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
resourceLabelAPIBasePath = "/api/repositories"
|
||||||
|
repository = "library/hello-world"
|
||||||
|
tag = "latest"
|
||||||
|
proLibraryLabelID int64
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddToImage(t *testing.T) {
|
||||||
|
sysLevelLabelID, err := dao.AddLabel(&models.Label{
|
||||||
|
Name: "sys_level_label",
|
||||||
|
Level: common.LabelLevelSystem,
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
defer dao.DeleteLabel(sysLevelLabelID)
|
||||||
|
|
||||||
|
proTestLabelID, err := dao.AddLabel(&models.Label{
|
||||||
|
Name: "pro_test_label",
|
||||||
|
Level: common.LabelLevelUser,
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 100,
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
defer dao.DeleteLabel(proTestLabelID)
|
||||||
|
|
||||||
|
proLibraryLabelID, err = dao.AddLabel(&models.Label{
|
||||||
|
Name: "pro_library_label",
|
||||||
|
Level: common.LabelLevelUser,
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 1,
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
// 401
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/tags/%s/labels", resourceLabelAPIBasePath,
|
||||||
|
repository, tag),
|
||||||
|
method: http.MethodPost,
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
// 403
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/tags/%s/labels", resourceLabelAPIBasePath,
|
||||||
|
repository, tag),
|
||||||
|
method: http.MethodPost,
|
||||||
|
credential: projGuest,
|
||||||
|
},
|
||||||
|
code: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
// 404 repository doesn't exist
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/library/non-exist-repo/tags/%s/labels", resourceLabelAPIBasePath, tag),
|
||||||
|
method: http.MethodPost,
|
||||||
|
credential: projDeveloper,
|
||||||
|
},
|
||||||
|
code: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
// 404 image doesn't exist
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/tags/non-exist-tag/labels", resourceLabelAPIBasePath, repository),
|
||||||
|
method: http.MethodPost,
|
||||||
|
credential: projDeveloper,
|
||||||
|
},
|
||||||
|
code: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
// 400
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/tags/%s/labels", resourceLabelAPIBasePath, repository, tag),
|
||||||
|
method: http.MethodPost,
|
||||||
|
credential: projDeveloper,
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
// 404 label doesn't exist
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/tags/%s/labels", resourceLabelAPIBasePath,
|
||||||
|
repository, tag),
|
||||||
|
method: http.MethodPost,
|
||||||
|
credential: projDeveloper,
|
||||||
|
bodyJSON: struct {
|
||||||
|
ID int64
|
||||||
|
}{
|
||||||
|
ID: 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
code: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
// 400 system level label
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/tags/%s/labels", resourceLabelAPIBasePath,
|
||||||
|
repository, tag),
|
||||||
|
method: http.MethodPost,
|
||||||
|
credential: projDeveloper,
|
||||||
|
bodyJSON: struct {
|
||||||
|
ID int64
|
||||||
|
}{
|
||||||
|
ID: sysLevelLabelID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
// 400 try to add the label of project1 to the image under project2
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/tags/%s/labels", resourceLabelAPIBasePath,
|
||||||
|
repository, tag),
|
||||||
|
method: http.MethodPost,
|
||||||
|
credential: projDeveloper,
|
||||||
|
bodyJSON: struct {
|
||||||
|
ID int64
|
||||||
|
}{
|
||||||
|
ID: proTestLabelID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
// 200
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/tags/%s/labels", resourceLabelAPIBasePath,
|
||||||
|
repository, tag),
|
||||||
|
method: http.MethodPost,
|
||||||
|
credential: projDeveloper,
|
||||||
|
bodyJSON: struct {
|
||||||
|
ID int64
|
||||||
|
}{
|
||||||
|
ID: proLibraryLabelID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetOfImage(t *testing.T) {
|
||||||
|
labels := []*models.Label{}
|
||||||
|
err := handleAndParse(&testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/tags/%s/labels", resourceLabelAPIBasePath, repository, tag),
|
||||||
|
method: http.MethodGet,
|
||||||
|
credential: projDeveloper,
|
||||||
|
}, &labels)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, 1, len(labels))
|
||||||
|
assert.Equal(t, proLibraryLabelID, labels[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveFromImage(t *testing.T) {
|
||||||
|
runCodeCheckingCases(t, &codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/tags/%s/labels/%d", resourceLabelAPIBasePath,
|
||||||
|
repository, tag, proLibraryLabelID),
|
||||||
|
method: http.MethodDelete,
|
||||||
|
credential: projDeveloper,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
})
|
||||||
|
|
||||||
|
labels := []*models.Label{}
|
||||||
|
err := handleAndParse(&testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/tags/%s/labels", resourceLabelAPIBasePath,
|
||||||
|
repository, tag),
|
||||||
|
method: http.MethodGet,
|
||||||
|
credential: projDeveloper,
|
||||||
|
}, &labels)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, 0, len(labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddToRepository(t *testing.T) {
|
||||||
|
runCodeCheckingCases(t, &codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/labels", resourceLabelAPIBasePath, repository),
|
||||||
|
method: http.MethodPost,
|
||||||
|
bodyJSON: struct {
|
||||||
|
ID int64
|
||||||
|
}{
|
||||||
|
ID: proLibraryLabelID,
|
||||||
|
},
|
||||||
|
credential: projDeveloper,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetOfRepository(t *testing.T) {
|
||||||
|
labels := []*models.Label{}
|
||||||
|
err := handleAndParse(&testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/labels", resourceLabelAPIBasePath, repository),
|
||||||
|
method: http.MethodGet,
|
||||||
|
credential: projDeveloper,
|
||||||
|
}, &labels)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, 1, len(labels))
|
||||||
|
assert.Equal(t, proLibraryLabelID, labels[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveFromRepository(t *testing.T) {
|
||||||
|
runCodeCheckingCases(t, &codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/labels/%d", resourceLabelAPIBasePath,
|
||||||
|
repository, proLibraryLabelID),
|
||||||
|
method: http.MethodDelete,
|
||||||
|
credential: projDeveloper,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
})
|
||||||
|
|
||||||
|
labels := []*models.Label{}
|
||||||
|
err := handleAndParse(&testingRequest{
|
||||||
|
url: fmt.Sprintf("%s/%s/labels", resourceLabelAPIBasePath, repository),
|
||||||
|
method: http.MethodGet,
|
||||||
|
credential: projDeveloper,
|
||||||
|
}, &labels)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, 0, len(labels))
|
||||||
|
}
|
@ -70,7 +70,11 @@ func initRouters() {
|
|||||||
beego.Router("/api/repositories", &api.RepositoryAPI{}, "get:Get")
|
beego.Router("/api/repositories", &api.RepositoryAPI{}, "get:Get")
|
||||||
beego.Router("/api/repositories/scanAll", &api.RepositoryAPI{}, "post:ScanAll")
|
beego.Router("/api/repositories/scanAll", &api.RepositoryAPI{}, "post:ScanAll")
|
||||||
beego.Router("/api/repositories/*", &api.RepositoryAPI{}, "delete:Delete;put:Put")
|
beego.Router("/api/repositories/*", &api.RepositoryAPI{}, "delete:Delete;put:Put")
|
||||||
|
beego.Router("/api/repositories/*/labels", &api.RepositoryLabelAPI{}, "get:GetOfRepository;post:AddToRepository")
|
||||||
|
beego.Router("/api/repositories/*/labels/:id([0-9]+)", &api.RepositoryLabelAPI{}, "delete:RemoveFromRepository")
|
||||||
beego.Router("/api/repositories/*/tags/:tag", &api.RepositoryAPI{}, "delete:Delete;get:GetTag")
|
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")
|
||||||
beego.Router("/api/repositories/*/tags/:tag/scan", &api.RepositoryAPI{}, "post:ScanImage")
|
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/vulnerability/details", &api.RepositoryAPI{}, "Get:VulnerabilityDetails")
|
||||||
@ -95,7 +99,7 @@ func initRouters() {
|
|||||||
beego.Router("/api/statistics", &api.StatisticAPI{})
|
beego.Router("/api/statistics", &api.StatisticAPI{})
|
||||||
beego.Router("/api/replications", &api.ReplicationAPI{})
|
beego.Router("/api/replications", &api.ReplicationAPI{})
|
||||||
beego.Router("/api/labels", &api.LabelAPI{}, "post:Post;get:List")
|
beego.Router("/api/labels", &api.LabelAPI{}, "post:Post;get:List")
|
||||||
beego.Router("/api/labels/:id([0-9]+", &api.LabelAPI{}, "get:Get;put:Put;delete:Delete")
|
beego.Router("/api/labels/:id([0-9]+)", &api.LabelAPI{}, "get:Get;put:Put;delete:Delete")
|
||||||
|
|
||||||
beego.Router("/api/systeminfo", &api.SystemInfoAPI{}, "get:GetGeneralInfo")
|
beego.Router("/api/systeminfo", &api.SystemInfoAPI{}, "get:GetGeneralInfo")
|
||||||
beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
|
beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
|
||||||
|
@ -69,3 +69,4 @@ Changelog for harbor database schema
|
|||||||
## 1.5.0
|
## 1.5.0
|
||||||
|
|
||||||
- create table `harbor_label`
|
- create table `harbor_label`
|
||||||
|
- create table `harbor_resource_label`
|
Loading…
Reference in New Issue
Block a user