mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-22 15:41:26 +01:00
Implement label management API
This commit is contained in:
parent
685140cda8
commit
379f113452
@ -1639,6 +1639,164 @@ paths:
|
|||||||
project and target.
|
project and target.
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
|
/labels:
|
||||||
|
get:
|
||||||
|
summary: List labels according to the query strings.
|
||||||
|
description: >
|
||||||
|
This endpoint let user list labels by name, scope and project_id
|
||||||
|
parameters:
|
||||||
|
- name: name
|
||||||
|
in: query
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
description: The label name.
|
||||||
|
- name: scope
|
||||||
|
in: query
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: The label scope. Valid values are g and p. g for global labels and p for project labels.
|
||||||
|
- name: project_id
|
||||||
|
in: query
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: false
|
||||||
|
description: Relevant project ID, required when scope is p.
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
required: false
|
||||||
|
description: The page nubmer.
|
||||||
|
- name: page_size
|
||||||
|
in: query
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
required: false
|
||||||
|
description: The size of per page.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Get successfully.
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Label'
|
||||||
|
'400':
|
||||||
|
description: Invalid parameters.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
post:
|
||||||
|
summary: Post creates a label
|
||||||
|
description: >
|
||||||
|
This endpoint let user creates a label.
|
||||||
|
parameters:
|
||||||
|
- name: label
|
||||||
|
in: body
|
||||||
|
description: The json object of label.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Label'
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Create successfully.
|
||||||
|
'400':
|
||||||
|
description: Invalid parameters.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'409':
|
||||||
|
description: >-
|
||||||
|
Label with the same name and same scope already exists.
|
||||||
|
'415':
|
||||||
|
$ref: '#/responses/UnsupportedMediaType'
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
'/labels/{id}':
|
||||||
|
get:
|
||||||
|
summary: Get the label specified by ID.
|
||||||
|
description: |
|
||||||
|
This endpoint let user get the label by specific ID.
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Label ID
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Get successfully.
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Label'
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'404':
|
||||||
|
description: The resource does not exist.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
put:
|
||||||
|
summary: Update the label properties.
|
||||||
|
description: >
|
||||||
|
This endpoint let user update label properties.
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Label ID
|
||||||
|
- name: label
|
||||||
|
in: body
|
||||||
|
description: The updated label json object.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Label'
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Update successfully.
|
||||||
|
'400':
|
||||||
|
description: Invalid parameters.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'404':
|
||||||
|
description: The resource does not exist.
|
||||||
|
'409':
|
||||||
|
description: >-
|
||||||
|
The label with the same name already exists.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
delete:
|
||||||
|
summary: Delete the label specified by ID.
|
||||||
|
description: >
|
||||||
|
Delete the label specified by ID.
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Label ID
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Delete successfully.
|
||||||
|
'400':
|
||||||
|
description: Invalid parameters.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'404':
|
||||||
|
description: The resource does not exist.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
/replications:
|
/replications:
|
||||||
post:
|
post:
|
||||||
summary: Trigger the replication according to the specified policy.
|
summary: Trigger the replication according to the specified policy.
|
||||||
@ -3056,3 +3214,30 @@ definitions:
|
|||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
description: The status of jobs. The only valid value is stop for now.
|
description: The status of jobs. The only valid value is stop for now.
|
||||||
|
Label:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
description: The ID of label.
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: The name of label.
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: The description of label.
|
||||||
|
color:
|
||||||
|
type: string
|
||||||
|
description: The color of label.
|
||||||
|
scope:
|
||||||
|
type: integer
|
||||||
|
description: The scope of label, g for global labels and p for project labels.
|
||||||
|
project_id:
|
||||||
|
type: integer
|
||||||
|
description: The project ID if the label is a project label.
|
||||||
|
creation_time:
|
||||||
|
type: string
|
||||||
|
description: The creation time of label.
|
||||||
|
update_time:
|
||||||
|
type: string
|
||||||
|
description: The update time of label.
|
||||||
|
@ -254,6 +254,24 @@ create table properties (
|
|||||||
UNIQUE (k)
|
UNIQUE (k)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table harbor_label (
|
||||||
|
id int NOT NULL AUTO_INCREMENT,
|
||||||
|
name varchar(128) NOT NULL,
|
||||||
|
description text,
|
||||||
|
color varchar(16),
|
||||||
|
# 's' for system level labels
|
||||||
|
# 'u' for user level labels
|
||||||
|
level char(1) NOT NULL,
|
||||||
|
# 'g' for global labels
|
||||||
|
# 'p' for project labels
|
||||||
|
scope char(1) NOT NULL,
|
||||||
|
project_id int,
|
||||||
|
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||||
|
update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY(id),
|
||||||
|
CONSTRAINT unique_name_and_scope UNIQUE (name,scope)
|
||||||
|
);
|
||||||
|
|
||||||
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;
|
||||||
|
@ -111,7 +111,7 @@ create table project_metadata (
|
|||||||
creation_time timestamp,
|
creation_time timestamp,
|
||||||
update_time timestamp,
|
update_time timestamp,
|
||||||
deleted tinyint (1) DEFAULT 0 NOT NULL,
|
deleted tinyint (1) DEFAULT 0 NOT NULL,
|
||||||
UNIQUE(project_id, name) ON CONFLICT REPLACE,
|
UNIQUE(project_id, name),
|
||||||
FOREIGN KEY (project_id) REFERENCES project(project_id)
|
FOREIGN KEY (project_id) REFERENCES project(project_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -240,6 +240,27 @@ create table properties (
|
|||||||
UNIQUE(k)
|
UNIQUE(k)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table harbor_label (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name varchar(128) NOT NULL,
|
||||||
|
description text,
|
||||||
|
color varchar(16),
|
||||||
|
/*
|
||||||
|
's' for system level labels
|
||||||
|
'u' for user level labels
|
||||||
|
*/
|
||||||
|
level char(1) NOT NULL,
|
||||||
|
/*
|
||||||
|
'g' for global labels
|
||||||
|
'p' for project labels
|
||||||
|
*/
|
||||||
|
scope char(1) NOT NULL,
|
||||||
|
project_id int,
|
||||||
|
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||||
|
update_time timestamp default CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE(name, scope)
|
||||||
|
);
|
||||||
|
|
||||||
create table alembic_version (
|
create table alembic_version (
|
||||||
version_num varchar(32) NOT NULL
|
version_num varchar(32) NOT NULL
|
||||||
);
|
);
|
||||||
|
@ -29,6 +29,15 @@ const (
|
|||||||
RoleDeveloper = 2
|
RoleDeveloper = 2
|
||||||
RoleGuest = 3
|
RoleGuest = 3
|
||||||
|
|
||||||
|
LabelLevelSystem = "s"
|
||||||
|
LabelLevelUser = "u"
|
||||||
|
LabelScopeGlobal = "g"
|
||||||
|
LabelScopeProject = "p"
|
||||||
|
|
||||||
|
ResourceTypeProject = "p"
|
||||||
|
ResourceTypeRepository = "r"
|
||||||
|
ResourceTypeImage = "i"
|
||||||
|
|
||||||
ExtEndpoint = "ext_endpoint"
|
ExtEndpoint = "ext_endpoint"
|
||||||
AUTHMode = "auth_mode"
|
AUTHMode = "auth_mode"
|
||||||
DatabaseType = "database_type"
|
DatabaseType = "database_type"
|
||||||
|
99
src/common/dao/label.go
Normal file
99
src/common/dao/label.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddLabel creates a label
|
||||||
|
func AddLabel(label *models.Label) (int64, error) {
|
||||||
|
now := time.Now()
|
||||||
|
label.CreationTime = now
|
||||||
|
label.UpdateTime = now
|
||||||
|
return GetOrmer().Insert(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabel specified by ID
|
||||||
|
func GetLabel(id int64) (*models.Label, error) {
|
||||||
|
label := &models.Label{
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
if err := GetOrmer().Read(label); err != nil {
|
||||||
|
if err == orm.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return label, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTotalOfLabels returns the total count of labels
|
||||||
|
func GetTotalOfLabels(query *models.LabelQuery) (int64, error) {
|
||||||
|
qs := getLabelQuerySetter(query)
|
||||||
|
return qs.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLabels list labels according to the query conditions
|
||||||
|
func ListLabels(query *models.LabelQuery) ([]*models.Label, error) {
|
||||||
|
qs := getLabelQuerySetter(query)
|
||||||
|
if query.Size > 0 {
|
||||||
|
qs = qs.Limit(query.Size)
|
||||||
|
if query.Page > 0 {
|
||||||
|
qs = qs.Offset((query.Page - 1) * query.Size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qs = qs.OrderBy("Name")
|
||||||
|
|
||||||
|
labels := []*models.Label{}
|
||||||
|
_, err := qs.All(&labels)
|
||||||
|
return labels, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLabelQuerySetter(query *models.LabelQuery) orm.QuerySeter {
|
||||||
|
qs := GetOrmer().QueryTable(&models.Label{})
|
||||||
|
if len(query.Name) > 0 {
|
||||||
|
qs = qs.Filter("Name", query.Name)
|
||||||
|
}
|
||||||
|
if len(query.Level) > 0 {
|
||||||
|
qs = qs.Filter("Level", query.Level)
|
||||||
|
}
|
||||||
|
if len(query.Scope) > 0 {
|
||||||
|
qs = qs.Filter("Scope", query.Scope)
|
||||||
|
}
|
||||||
|
if query.ProjectID != 0 {
|
||||||
|
qs = qs.Filter("ProjectID", query.ProjectID)
|
||||||
|
}
|
||||||
|
return qs
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLabel ...
|
||||||
|
func UpdateLabel(label *models.Label) error {
|
||||||
|
label.UpdateTime = time.Now()
|
||||||
|
_, err := GetOrmer().Update(label)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLabel ...
|
||||||
|
func DeleteLabel(id int64) error {
|
||||||
|
_, err := GetOrmer().Delete(&models.Label{
|
||||||
|
ID: id,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
91
src/common/dao/label_test.go
Normal file
91
src/common/dao/label_test.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// 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/vmware/harbor/src/common"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMethodsOfLabel(t *testing.T) {
|
||||||
|
label := &models.Label{
|
||||||
|
Name: "test",
|
||||||
|
Level: common.LabelLevelUser,
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// add
|
||||||
|
id, err := AddLabel(label)
|
||||||
|
require.Nil(t, err)
|
||||||
|
label.ID = id
|
||||||
|
|
||||||
|
// get
|
||||||
|
l, err := GetLabel(id)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, label.ID, l.ID)
|
||||||
|
assert.Equal(t, label.Name, l.Name)
|
||||||
|
assert.Equal(t, label.Scope, l.Scope)
|
||||||
|
assert.Equal(t, label.ProjectID, l.ProjectID)
|
||||||
|
|
||||||
|
// get total count
|
||||||
|
total, err := GetTotalOfLabels(&models.LabelQuery{
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 1,
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(1), total)
|
||||||
|
|
||||||
|
// list
|
||||||
|
labels, err := ListLabels(&models.LabelQuery{
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 1,
|
||||||
|
Name: label.Name,
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, 1, len(labels))
|
||||||
|
|
||||||
|
// list
|
||||||
|
labels, err = ListLabels(&models.LabelQuery{
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 1,
|
||||||
|
Name: "not_exist_label",
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, 0, len(labels))
|
||||||
|
|
||||||
|
// update
|
||||||
|
newName := "dev"
|
||||||
|
label.Name = newName
|
||||||
|
err = UpdateLabel(label)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
l, err = GetLabel(id)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, newName, l.Name)
|
||||||
|
|
||||||
|
// delete
|
||||||
|
err = DeleteLabel(id)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
l, err = GetLabel(id)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Nil(t, l)
|
||||||
|
}
|
@ -32,5 +32,6 @@ func init() {
|
|||||||
new(ClairVulnTimestamp),
|
new(ClairVulnTimestamp),
|
||||||
new(WatchItem),
|
new(WatchItem),
|
||||||
new(ProjectMetadata),
|
new(ProjectMetadata),
|
||||||
new(ConfigEntry))
|
new(ConfigEntry),
|
||||||
|
new(Label))
|
||||||
}
|
}
|
||||||
|
94
src/common/models/label.go
Normal file
94
src/common/models/label.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// 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 models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/validation"
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Label holds information used for a label
|
||||||
|
type Label struct {
|
||||||
|
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
||||||
|
Name string `orm:"column(name)" json:"name"`
|
||||||
|
Description string `orm:"column(description)" json:"description"`
|
||||||
|
Color string `orm:"column(color)" json:"color"`
|
||||||
|
Level string `orm:"column(level)" json:"-"`
|
||||||
|
Scope string `orm:"column(scope)" json:"scope"`
|
||||||
|
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||||
|
CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"`
|
||||||
|
UpdateTime time.Time `orm:"column(update_time)" json:"update_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//TableName ...
|
||||||
|
func (l *Label) TableName() string {
|
||||||
|
return "harbor_label"
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabelQuery : query parameters for labels
|
||||||
|
type LabelQuery struct {
|
||||||
|
Name string
|
||||||
|
Level string
|
||||||
|
Scope string
|
||||||
|
ProjectID int64
|
||||||
|
Pagination
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid ...
|
||||||
|
func (l *Label) Valid(v *validation.Validation) {
|
||||||
|
if len(l.Name) == 0 {
|
||||||
|
v.SetError("name", "cannot be empty")
|
||||||
|
}
|
||||||
|
if len(l.Name) > 128 {
|
||||||
|
v.SetError("name", "max length is 128")
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.Scope != common.LabelScopeGlobal && l.Scope != common.LabelScopeProject {
|
||||||
|
v.SetError("scope", fmt.Sprintf("invalid: %s", l.Scope))
|
||||||
|
} else if l.Scope == common.LabelScopeProject && l.ProjectID <= 0 {
|
||||||
|
v.SetError("project_id", fmt.Sprintf("invalid: %d", l.ProjectID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
type ResourceLabel struct {
|
||||||
|
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
||||||
|
LabelID int64 `orm:"column(label_id)" json:"label_id"`
|
||||||
|
ResourceID string `orm:"column(resource_id)" json:"resource_id"`
|
||||||
|
ResourceType rune `orm:"column(resource_type)" json:"resource_type"`
|
||||||
|
CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"`
|
||||||
|
UpdateTime time.Time `orm:"column(update_time)" json:"update_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Valid ...
|
||||||
|
func (r *ResourceLabel) Valid(v *validation.Validation) {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
86
src/common/models/label_test.go
Normal file
86
src/common/models/label_test.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// 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 models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/validation"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidOfLabel(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
label *Label
|
||||||
|
hasError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
label: &Label{
|
||||||
|
Name: "",
|
||||||
|
},
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: &Label{
|
||||||
|
Name: "test",
|
||||||
|
Scope: "",
|
||||||
|
},
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: &Label{
|
||||||
|
Name: "test",
|
||||||
|
Scope: "invalid_scope",
|
||||||
|
},
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: &Label{
|
||||||
|
Name: "test",
|
||||||
|
Scope: "g",
|
||||||
|
},
|
||||||
|
hasError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: &Label{
|
||||||
|
Name: "test",
|
||||||
|
Scope: "p",
|
||||||
|
},
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: &Label{
|
||||||
|
Name: "test",
|
||||||
|
Scope: "p",
|
||||||
|
ProjectID: -1,
|
||||||
|
},
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: &Label{
|
||||||
|
Name: "test",
|
||||||
|
Scope: "p",
|
||||||
|
ProjectID: 1,
|
||||||
|
},
|
||||||
|
hasError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
v := &validation.Validation{}
|
||||||
|
c.label.Valid(v)
|
||||||
|
assert.Equal(t, c.hasError, v.HasErrors())
|
||||||
|
}
|
||||||
|
}
|
@ -132,6 +132,8 @@ func init() {
|
|||||||
beego.Router("/api/configurations/reset", &ConfigAPI{}, "post:Reset")
|
beego.Router("/api/configurations/reset", &ConfigAPI{}, "post:Reset")
|
||||||
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")
|
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")
|
||||||
beego.Router("/api/replications", &ReplicationAPI{})
|
beego.Router("/api/replications", &ReplicationAPI{})
|
||||||
|
beego.Router("/api/labels", &LabelAPI{}, "post:Post;get:List")
|
||||||
|
beego.Router("/api/labels/:id([0-9]+", &LabelAPI{}, "get:Get;put:Put;delete:Delete")
|
||||||
|
|
||||||
_ = updateInitPassword(1, "Harbor12345")
|
_ = updateInitPassword(1, "Harbor12345")
|
||||||
|
|
||||||
|
263
src/ui/api/label.go
Normal file
263
src/ui/api/label.go
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LabelAPI handles requests for label management
|
||||||
|
type LabelAPI struct {
|
||||||
|
label *models.Label
|
||||||
|
BaseController
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare ...
|
||||||
|
func (l *LabelAPI) Prepare() {
|
||||||
|
l.BaseController.Prepare()
|
||||||
|
method := l.Ctx.Request.Method
|
||||||
|
if method == http.MethodGet {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST, PUT, DELETE need login first
|
||||||
|
if !l.SecurityCtx.IsAuthenticated() {
|
||||||
|
l.HandleUnauthorized()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if method == http.MethodPut || method == http.MethodDelete {
|
||||||
|
id, err := l.GetInt64FromPath(":id")
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
l.HandleBadRequest("invalid label ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
label, err := dao.GetLabel(id)
|
||||||
|
if err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("failed to get label %d: %v", id, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if label == nil {
|
||||||
|
l.HandleNotFound(fmt.Sprintf("label %d not found", id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if label.Scope == common.LabelScopeGlobal && !l.SecurityCtx.IsSysAdmin() ||
|
||||||
|
label.Scope == common.LabelScopeProject && !l.SecurityCtx.HasAllPerm(label.ProjectID) {
|
||||||
|
l.HandleForbidden(l.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.label = label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post creates a label
|
||||||
|
func (l *LabelAPI) Post() {
|
||||||
|
label := &models.Label{}
|
||||||
|
l.DecodeJSONReqAndValidate(label)
|
||||||
|
label.Level = common.LabelLevelUser
|
||||||
|
|
||||||
|
switch label.Scope {
|
||||||
|
case common.LabelScopeGlobal:
|
||||||
|
if !l.SecurityCtx.IsSysAdmin() {
|
||||||
|
l.HandleForbidden(l.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
label.ProjectID = 0
|
||||||
|
case common.LabelScopeProject:
|
||||||
|
exist, err := l.ProjectMgr.Exists(label.ProjectID)
|
||||||
|
if err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("failed to check the existence of project %d: %v",
|
||||||
|
label.ProjectID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
l.HandleNotFound(fmt.Sprintf("project %d not found", label.ProjectID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !l.SecurityCtx.HasAllPerm(label.ProjectID) {
|
||||||
|
l.HandleForbidden(l.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
labels, err := dao.ListLabels(&models.LabelQuery{
|
||||||
|
Name: label.Name,
|
||||||
|
Level: label.Level,
|
||||||
|
Scope: label.Scope,
|
||||||
|
ProjectID: label.ProjectID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("failed to list labels: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(labels) > 0 {
|
||||||
|
l.HandleConflict()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := dao.AddLabel(label)
|
||||||
|
if err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("failed to create label: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Redirect(http.StatusCreated, strconv.FormatInt(id, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the label specified by ID
|
||||||
|
func (l *LabelAPI) Get() {
|
||||||
|
id, err := l.GetInt64FromPath(":id")
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
l.HandleBadRequest(fmt.Sprintf("invalid label ID: %s", l.GetStringFromPath(":id")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
label, err := dao.GetLabel(id)
|
||||||
|
if err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("failed to get label %d: %v", id, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if label == nil {
|
||||||
|
l.HandleNotFound(fmt.Sprintf("label %d not found", id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if label.Scope == common.LabelScopeProject {
|
||||||
|
if !l.SecurityCtx.HasReadPerm(label.ProjectID) {
|
||||||
|
if !l.SecurityCtx.IsAuthenticated() {
|
||||||
|
l.HandleUnauthorized()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.HandleForbidden(l.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Data["json"] = label
|
||||||
|
l.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// List labels according to the query strings
|
||||||
|
func (l *LabelAPI) List() {
|
||||||
|
query := &models.LabelQuery{
|
||||||
|
Name: l.GetString("name"),
|
||||||
|
Level: common.LabelLevelUser,
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := l.GetString("scope")
|
||||||
|
if scope != common.LabelScopeGlobal && scope != common.LabelScopeProject {
|
||||||
|
l.HandleBadRequest(fmt.Sprintf("invalid scope: %s", scope))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
query.Scope = scope
|
||||||
|
|
||||||
|
if scope == common.LabelScopeProject {
|
||||||
|
projectIDStr := l.GetString("project_id")
|
||||||
|
if len(projectIDStr) == 0 {
|
||||||
|
l.HandleBadRequest("project_id is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
projectID, err := strconv.ParseInt(projectIDStr, 10, 64)
|
||||||
|
if err != nil || projectID <= 0 {
|
||||||
|
l.HandleBadRequest(fmt.Sprintf("invalid project_id: %s", projectIDStr))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !l.SecurityCtx.HasReadPerm(projectID) {
|
||||||
|
if !l.SecurityCtx.IsAuthenticated() {
|
||||||
|
l.HandleUnauthorized()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.HandleForbidden(l.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
query.ProjectID = projectID
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := dao.GetTotalOfLabels(query)
|
||||||
|
if err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("failed to get total count of labels: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query.Page, query.Size = l.GetPaginationParams()
|
||||||
|
|
||||||
|
labels, err := dao.ListLabels(query)
|
||||||
|
if err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("failed to list labels: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.SetPaginationHeader(total, query.Page, query.Size)
|
||||||
|
l.Data["json"] = labels
|
||||||
|
l.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put updates the label
|
||||||
|
func (l *LabelAPI) Put() {
|
||||||
|
label := &models.Label{}
|
||||||
|
l.DecodeJSONReq(label)
|
||||||
|
|
||||||
|
oldName := l.label.Name
|
||||||
|
|
||||||
|
// only name, description and color can be changed
|
||||||
|
l.label.Name = label.Name
|
||||||
|
l.label.Description = label.Description
|
||||||
|
l.label.Color = label.Color
|
||||||
|
|
||||||
|
l.Validate(l.label)
|
||||||
|
|
||||||
|
if l.label.Name != oldName {
|
||||||
|
labels, err := dao.ListLabels(&models.LabelQuery{
|
||||||
|
Name: l.label.Name,
|
||||||
|
Level: l.label.Level,
|
||||||
|
Scope: l.label.Scope,
|
||||||
|
ProjectID: l.label.ProjectID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("failed to list labels: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(labels) > 0 {
|
||||||
|
l.HandleConflict()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dao.UpdateLabel(l.label); err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("failed to update label %d: %v", l.label.ID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the label
|
||||||
|
func (l *LabelAPI) Delete() {
|
||||||
|
id := l.label.ID
|
||||||
|
if err := dao.DeleteLabel(id); err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("failed to delete label %d: %v", id, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
435
src/ui/api/label_test.go
Normal file
435
src/ui/api/label_test.go
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
// 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"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
labelAPIBasePath = "/api/labels"
|
||||||
|
labelID int64
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLabelAPIPost(t *testing.T) {
|
||||||
|
postFunc := func(resp *httptest.ResponseRecorder) error {
|
||||||
|
id, err := parseResourceID(resp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
labelID = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
// 401
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 400
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
bodyJSON: &models.Label{},
|
||||||
|
credential: nonSysAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 403 non-sysadmin try to create global label
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
bodyJSON: &models.Label{
|
||||||
|
Name: "test",
|
||||||
|
Scope: common.LabelScopeGlobal,
|
||||||
|
},
|
||||||
|
credential: nonSysAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 403 non-member user try to create project label
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
bodyJSON: &models.Label{
|
||||||
|
Name: "test",
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 1,
|
||||||
|
},
|
||||||
|
credential: nonSysAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 403 developer try to create project label
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
bodyJSON: &models.Label{
|
||||||
|
Name: "test",
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 1,
|
||||||
|
},
|
||||||
|
credential: projDeveloper,
|
||||||
|
},
|
||||||
|
code: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 404 non-exist project
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
bodyJSON: &models.Label{
|
||||||
|
Name: "test",
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 10000,
|
||||||
|
},
|
||||||
|
credential: projAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 200
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
bodyJSON: &models.Label{
|
||||||
|
Name: "test",
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 1,
|
||||||
|
},
|
||||||
|
credential: projAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusCreated,
|
||||||
|
postFunc: postFunc,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 409
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
bodyJSON: &models.Label{
|
||||||
|
Name: "test",
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 1,
|
||||||
|
},
|
||||||
|
credential: projAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusConflict,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLabelAPIGet(t *testing.T) {
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
// 400
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, 0),
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 404
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, 1000),
|
||||||
|
},
|
||||||
|
code: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 200
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLabelAPIList(t *testing.T) {
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
// 400 no scope query string
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 400 invalid scope
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
queryStruct: struct {
|
||||||
|
Scope string `url:"scope"`
|
||||||
|
}{
|
||||||
|
Scope: "invalid_scope",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 400 invalid project_id
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
queryStruct: struct {
|
||||||
|
Scope string `url:"scope"`
|
||||||
|
ProjectID int64 `url:"project_id"`
|
||||||
|
}{
|
||||||
|
Scope: "p",
|
||||||
|
ProjectID: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
|
||||||
|
// 200
|
||||||
|
labels := []*models.Label{}
|
||||||
|
err := handleAndParse(&testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
queryStruct: struct {
|
||||||
|
Scope string `url:"scope"`
|
||||||
|
ProjectID int64 `url:"project_id"`
|
||||||
|
Name string `url:"name"`
|
||||||
|
}{
|
||||||
|
Scope: "p",
|
||||||
|
ProjectID: 1,
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
}, &labels)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, 1, len(labels))
|
||||||
|
|
||||||
|
err = handleAndParse(&testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: labelAPIBasePath,
|
||||||
|
queryStruct: struct {
|
||||||
|
Scope string `url:"scope"`
|
||||||
|
ProjectID int64 `url:"project_id"`
|
||||||
|
Name string `url:"name"`
|
||||||
|
}{
|
||||||
|
Scope: "p",
|
||||||
|
ProjectID: 1,
|
||||||
|
Name: "dev",
|
||||||
|
},
|
||||||
|
}, &labels)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, 0, len(labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLabelAPIPut(t *testing.T) {
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
// 401
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 400
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, 0),
|
||||||
|
credential: nonSysAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 404
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, 10000),
|
||||||
|
credential: nonSysAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 403 non-member user
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
|
||||||
|
credential: nonSysAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 403 developer
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
|
||||||
|
credential: projDeveloper,
|
||||||
|
},
|
||||||
|
code: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 400
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
|
||||||
|
bodyJSON: &models.Label{
|
||||||
|
Name: "",
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 1,
|
||||||
|
},
|
||||||
|
credential: projAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 200
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
|
||||||
|
bodyJSON: &models.Label{
|
||||||
|
Name: "product",
|
||||||
|
Scope: common.LabelScopeProject,
|
||||||
|
ProjectID: 1,
|
||||||
|
},
|
||||||
|
credential: projAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
|
||||||
|
label := &models.Label{}
|
||||||
|
err := handleAndParse(&testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
|
||||||
|
}, label)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, "product", label.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLabelAPIDelete(t *testing.T) {
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
// 401
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodDelete,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 400
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodDelete,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, 0),
|
||||||
|
credential: nonSysAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 404
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodDelete,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, 10000),
|
||||||
|
credential: nonSysAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 403 non-member user
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodDelete,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
|
||||||
|
credential: nonSysAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 403 developer
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodDelete,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
|
||||||
|
credential: projDeveloper,
|
||||||
|
},
|
||||||
|
code: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 200
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodDelete,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
|
||||||
|
credential: projAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 404
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodDelete,
|
||||||
|
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
|
||||||
|
credential: projAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
@ -94,6 +94,8 @@ func initRouters() {
|
|||||||
beego.Router("/api/configurations/reset", &api.ConfigAPI{}, "post:Reset")
|
beego.Router("/api/configurations/reset", &api.ConfigAPI{}, "post:Reset")
|
||||||
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/: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")
|
||||||
|
@ -65,3 +65,7 @@ Changelog for harbor database schema
|
|||||||
- add pk `id` to table `properties`
|
- add pk `id` to table `properties`
|
||||||
- remove pk index from column 'k' of table `properties`
|
- remove pk index from column 'k' of table `properties`
|
||||||
- alter `name` length from 41 to 256 of table `project`
|
- alter `name` length from 41 to 256 of table `project`
|
||||||
|
|
||||||
|
## 1.5.0
|
||||||
|
|
||||||
|
- create table `harbor_label`
|
Loading…
Reference in New Issue
Block a user