mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-29 13:57:33 +02:00
Merge branch 'master' into feature/pluggable_scanner_s3_merge
This commit is contained in:
commit
a86afd6ebc
@ -959,13 +959,13 @@ paths:
|
|||||||
description: User ID does not exist.
|
description: User ID does not exist.
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
'/users/{user_id}/gen_cli_secret':
|
'/users/{user_id}/cli_secret':
|
||||||
post:
|
put:
|
||||||
summary: Generate new CLI secret for a user.
|
summary: Set CLI secret for a user.
|
||||||
description: |
|
description: |
|
||||||
This endpoint let user generate a new CLI secret for himself. This API only works when auth mode is set to 'OIDC'.
|
This endpoint let user generate a new CLI secret for himself. This API only works when auth mode is set to 'OIDC'.
|
||||||
Once this API returns with successful status, the old secret will be invalid, as there will be only one CLI secret
|
Once this API returns with successful status, the old secret will be invalid, as there will be only one CLI secret
|
||||||
for a user. The new secret will be returned in the response.
|
for a user.
|
||||||
parameters:
|
parameters:
|
||||||
- name: user_id
|
- name: user_id
|
||||||
in: path
|
in: path
|
||||||
@ -973,19 +973,23 @@ paths:
|
|||||||
format: int
|
format: int
|
||||||
required: true
|
required: true
|
||||||
description: User ID
|
description: User ID
|
||||||
tags:
|
- name: input_secret
|
||||||
- Products
|
in: body
|
||||||
responses:
|
description: JSON object that includes the new secret
|
||||||
'200':
|
required: true
|
||||||
description: The secret is successfully generated.
|
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
description: The new secret
|
description: The new secret
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: The secret is successfully updated
|
||||||
'400':
|
'400':
|
||||||
description: Invalid user ID. Or user is not onboarded via OIDC authentication.
|
description: Invalid user ID. Or user is not onboarded via OIDC authentication. Or the secret does not meet the standard.
|
||||||
'401':
|
'401':
|
||||||
description: User need to log in first.
|
description: User need to log in first.
|
||||||
'403':
|
'403':
|
||||||
|
@ -906,7 +906,7 @@ For example, you have following tags, listed according to their push time, and a
|
|||||||
|
|
||||||
You configure a retention policy to retain the two latest tags that match `harbor-*`, so that `harbor-rc` and `harbor-latest` are deleted. However, since all tags refer to the same SHA digest, this policy would also delete the tags `harbor-1.8` and `harbor-release`, so all tags are retained.
|
You configure a retention policy to retain the two latest tags that match `harbor-*`, so that `harbor-rc` and `harbor-latest` are deleted. However, since all tags refer to the same SHA digest, this policy would also delete the tags `harbor-1.8` and `harbor-release`, so all tags are retained.
|
||||||
|
|
||||||
### Combining Rules on a Respository
|
### Combining Rules on a Repository
|
||||||
|
|
||||||
You can define up to 15 rules per project. You can apply multiple rules to a repository or set of repositories. When you apply multiple rules to a repository, they are applied with `OR` logic rather than with `AND` logic. In this way, there is no prioritization of application of the rules on a given repository. Rules run concurrently in the background, and the resulting sets from each rule are combined at the end of the run.
|
You can define up to 15 rules per project. You can apply multiple rules to a repository or set of repositories. When you apply multiple rules to a repository, they are applied with `OR` logic rather than with `AND` logic. In this way, there is no prioritization of application of the rules on a given repository. Rules run concurrently in the background, and the resulting sets from each rule are combined at the end of the run.
|
||||||
|
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
// Copyright Project Harbor Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package dao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/astaxie/beego/orm"
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddRobot ...
|
|
||||||
func AddRobot(robot *models.Robot) (int64, error) {
|
|
||||||
now := time.Now()
|
|
||||||
robot.CreationTime = now
|
|
||||||
robot.UpdateTime = now
|
|
||||||
id, err := GetOrmer().Insert(robot)
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
|
|
||||||
return 0, ErrDupRows
|
|
||||||
}
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRobotByID ...
|
|
||||||
func GetRobotByID(id int64) (*models.Robot, error) {
|
|
||||||
robot := &models.Robot{
|
|
||||||
ID: id,
|
|
||||||
}
|
|
||||||
if err := GetOrmer().Read(robot); err != nil {
|
|
||||||
if err == orm.ErrNoRows {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return robot, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRobots list robots according to the query conditions
|
|
||||||
func ListRobots(query *models.RobotQuery) ([]*models.Robot, error) {
|
|
||||||
qs := getRobotQuerySetter(query).OrderBy("Name")
|
|
||||||
if query != nil {
|
|
||||||
if query.Size > 0 {
|
|
||||||
qs = qs.Limit(query.Size)
|
|
||||||
if query.Page > 0 {
|
|
||||||
qs = qs.Offset((query.Page - 1) * query.Size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
robots := []*models.Robot{}
|
|
||||||
_, err := qs.All(&robots)
|
|
||||||
return robots, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRobotQuerySetter(query *models.RobotQuery) orm.QuerySeter {
|
|
||||||
qs := GetOrmer().QueryTable(&models.Robot{})
|
|
||||||
|
|
||||||
if query == nil {
|
|
||||||
return qs
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(query.Name) > 0 {
|
|
||||||
if query.FuzzyMatchName {
|
|
||||||
qs = qs.Filter("Name__icontains", query.Name)
|
|
||||||
} else {
|
|
||||||
qs = qs.Filter("Name", query.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if query.ProjectID != 0 {
|
|
||||||
qs = qs.Filter("ProjectID", query.ProjectID)
|
|
||||||
}
|
|
||||||
return qs
|
|
||||||
}
|
|
||||||
|
|
||||||
// CountRobot ...
|
|
||||||
func CountRobot(query *models.RobotQuery) (int64, error) {
|
|
||||||
return getRobotQuerySetter(query).Count()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRobot ...
|
|
||||||
func UpdateRobot(robot *models.Robot) error {
|
|
||||||
robot.UpdateTime = time.Now()
|
|
||||||
_, err := GetOrmer().Update(robot)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRobot ...
|
|
||||||
func DeleteRobot(id int64) error {
|
|
||||||
_, err := GetOrmer().QueryTable(&models.Robot{}).Filter("ID", id).Delete()
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,159 +0,0 @@
|
|||||||
// Copyright Project Harbor Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package dao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAddRobot(t *testing.T) {
|
|
||||||
robotName := "test1"
|
|
||||||
robot := &models.Robot{
|
|
||||||
Name: robotName,
|
|
||||||
Description: "test1 description",
|
|
||||||
ProjectID: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
// add
|
|
||||||
id, err := AddRobot(robot)
|
|
||||||
require.Nil(t, err)
|
|
||||||
robot.ID = id
|
|
||||||
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.NotNil(t, id)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetRobot(t *testing.T) {
|
|
||||||
robotName := "test2"
|
|
||||||
robot := &models.Robot{
|
|
||||||
Name: robotName,
|
|
||||||
Description: "test2 description",
|
|
||||||
ProjectID: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
// add
|
|
||||||
id, err := AddRobot(robot)
|
|
||||||
require.Nil(t, err)
|
|
||||||
robot.ID = id
|
|
||||||
|
|
||||||
robot, err = GetRobotByID(id)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, robotName, robot.Name)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListRobots(t *testing.T) {
|
|
||||||
robotName := "test3"
|
|
||||||
robot := &models.Robot{
|
|
||||||
Name: robotName,
|
|
||||||
Description: "test3 description",
|
|
||||||
ProjectID: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := AddRobot(robot)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
robots, err := ListRobots(&models.RobotQuery{
|
|
||||||
ProjectID: 1,
|
|
||||||
})
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, 3, len(robots))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDisableRobot(t *testing.T) {
|
|
||||||
robotName := "test4"
|
|
||||||
robot := &models.Robot{
|
|
||||||
Name: robotName,
|
|
||||||
Description: "test4 description",
|
|
||||||
ProjectID: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
// add
|
|
||||||
id, err := AddRobot(robot)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// Disable
|
|
||||||
robot.Disabled = true
|
|
||||||
err = UpdateRobot(robot)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// Get
|
|
||||||
robot, err = GetRobotByID(id)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, true, robot.Disabled)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnableRobot(t *testing.T) {
|
|
||||||
robotName := "test5"
|
|
||||||
robot := &models.Robot{
|
|
||||||
Name: robotName,
|
|
||||||
Description: "test5 description",
|
|
||||||
Disabled: true,
|
|
||||||
ProjectID: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
// add
|
|
||||||
id, err := AddRobot(robot)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// Disable
|
|
||||||
robot.Disabled = false
|
|
||||||
err = UpdateRobot(robot)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// Get
|
|
||||||
robot, err = GetRobotByID(id)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, false, robot.Disabled)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteRobot(t *testing.T) {
|
|
||||||
robotName := "test6"
|
|
||||||
robot := &models.Robot{
|
|
||||||
Name: robotName,
|
|
||||||
Description: "test6 description",
|
|
||||||
ProjectID: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
// add
|
|
||||||
id, err := AddRobot(robot)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// Disable
|
|
||||||
err = DeleteRobot(id)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// Get
|
|
||||||
robot, err = GetRobotByID(id)
|
|
||||||
assert.Nil(t, robot)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListAllRobot(t *testing.T) {
|
|
||||||
|
|
||||||
robots, err := ListRobots(nil)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, 5, len(robots))
|
|
||||||
|
|
||||||
}
|
|
@ -35,7 +35,6 @@ func init() {
|
|||||||
new(UserGroup),
|
new(UserGroup),
|
||||||
new(AdminJob),
|
new(AdminJob),
|
||||||
new(JobLog),
|
new(JobLog),
|
||||||
new(Robot),
|
|
||||||
new(OIDCUser),
|
new(OIDCUser),
|
||||||
new(NotificationPolicy),
|
new(NotificationPolicy),
|
||||||
new(NotificationJob),
|
new(NotificationJob),
|
||||||
|
@ -18,17 +18,18 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
"github.com/goharbor/harbor/src/core/promgr"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SecurityContext implements security.Context interface based on database
|
// SecurityContext implements security.Context interface based on database
|
||||||
type SecurityContext struct {
|
type SecurityContext struct {
|
||||||
robot *models.Robot
|
robot *model.Robot
|
||||||
pm promgr.ProjectManager
|
pm promgr.ProjectManager
|
||||||
policy []*rbac.Policy
|
policy []*rbac.Policy
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSecurityContext ...
|
// NewSecurityContext ...
|
||||||
func NewSecurityContext(robot *models.Robot, pm promgr.ProjectManager, policy []*rbac.Policy) *SecurityContext {
|
func NewSecurityContext(robot *model.Robot, pm promgr.ProjectManager, policy []*rbac.Policy) *SecurityContext {
|
||||||
return &SecurityContext{
|
return &SecurityContext{
|
||||||
robot: robot,
|
robot: robot,
|
||||||
pm: pm,
|
pm: pm,
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
"github.com/goharbor/harbor/src/core/promgr"
|
||||||
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/local"
|
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/local"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -96,7 +97,7 @@ func TestIsAuthenticated(t *testing.T) {
|
|||||||
assert.False(t, ctx.IsAuthenticated())
|
assert.False(t, ctx.IsAuthenticated())
|
||||||
|
|
||||||
// authenticated
|
// authenticated
|
||||||
ctx = NewSecurityContext(&models.Robot{
|
ctx = NewSecurityContext(&model.Robot{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
@ -109,7 +110,7 @@ func TestGetUsername(t *testing.T) {
|
|||||||
assert.Equal(t, "", ctx.GetUsername())
|
assert.Equal(t, "", ctx.GetUsername())
|
||||||
|
|
||||||
// authenticated
|
// authenticated
|
||||||
ctx = NewSecurityContext(&models.Robot{
|
ctx = NewSecurityContext(&model.Robot{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
@ -122,7 +123,7 @@ func TestIsSysAdmin(t *testing.T) {
|
|||||||
assert.False(t, ctx.IsSysAdmin())
|
assert.False(t, ctx.IsSysAdmin())
|
||||||
|
|
||||||
// authenticated, non admin
|
// authenticated, non admin
|
||||||
ctx = NewSecurityContext(&models.Robot{
|
ctx = NewSecurityContext(&model.Robot{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
@ -141,7 +142,7 @@ func TestHasPullPerm(t *testing.T) {
|
|||||||
Action: rbac.ActionPull,
|
Action: rbac.ActionPull,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
robot := &models.Robot{
|
robot := &model.Robot{
|
||||||
Name: "test_robot_1",
|
Name: "test_robot_1",
|
||||||
Description: "desc",
|
Description: "desc",
|
||||||
}
|
}
|
||||||
@ -158,7 +159,7 @@ func TestHasPushPerm(t *testing.T) {
|
|||||||
Action: rbac.ActionPush,
|
Action: rbac.ActionPush,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
robot := &models.Robot{
|
robot := &model.Robot{
|
||||||
Name: "test_robot_2",
|
Name: "test_robot_2",
|
||||||
Description: "desc",
|
Description: "desc",
|
||||||
}
|
}
|
||||||
@ -179,7 +180,7 @@ func TestHasPushPullPerm(t *testing.T) {
|
|||||||
Action: rbac.ActionPull,
|
Action: rbac.ActionPull,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
robot := &models.Robot{
|
robot := &model.Robot{
|
||||||
Name: "test_robot_3",
|
Name: "test_robot_3",
|
||||||
Description: "desc",
|
Description: "desc",
|
||||||
}
|
}
|
||||||
|
@ -97,9 +97,10 @@ func TestImmutableTagRuleAPI_List(t *testing.T) {
|
|||||||
func TestImmutableTagRuleAPI_Post(t *testing.T) {
|
func TestImmutableTagRuleAPI_Post(t *testing.T) {
|
||||||
|
|
||||||
// body := `{
|
// body := `{
|
||||||
// "id":0,
|
// "projectID":1,
|
||||||
// "projectID":1,
|
|
||||||
// "priority":0,
|
// "priority":0,
|
||||||
|
// "template": "immutable_template",
|
||||||
|
// "action": "immutable",
|
||||||
// "disabled":false,
|
// "disabled":false,
|
||||||
// "action":"immutable",
|
// "action":"immutable",
|
||||||
// "template":"immutable_template",
|
// "template":"immutable_template",
|
||||||
|
@ -15,30 +15,29 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common"
|
|
||||||
"github.com/goharbor/harbor/src/common/dao"
|
"github.com/goharbor/harbor/src/common/dao"
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/common/rbac/project"
|
"github.com/goharbor/harbor/src/common/rbac/project"
|
||||||
"github.com/goharbor/harbor/src/common/token"
|
"github.com/goharbor/harbor/src/pkg/robot"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RobotAPI ...
|
// RobotAPI ...
|
||||||
type RobotAPI struct {
|
type RobotAPI struct {
|
||||||
BaseController
|
BaseController
|
||||||
project *models.Project
|
project *models.Project
|
||||||
robot *models.Robot
|
ctr robot.Controller
|
||||||
|
robot *model.Robot
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare ...
|
// Prepare ...
|
||||||
func (r *RobotAPI) Prepare() {
|
func (r *RobotAPI) Prepare() {
|
||||||
|
|
||||||
r.BaseController.Prepare()
|
r.BaseController.Prepare()
|
||||||
method := r.Ctx.Request.Method
|
method := r.Ctx.Request.Method
|
||||||
|
|
||||||
@ -68,6 +67,7 @@ func (r *RobotAPI) Prepare() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.project = project
|
r.project = project
|
||||||
|
r.ctr = robot.RobotCtr
|
||||||
|
|
||||||
if method == http.MethodPut || method == http.MethodDelete {
|
if method == http.MethodPut || method == http.MethodDelete {
|
||||||
id, err := r.GetInt64FromPath(":id")
|
id, err := r.GetInt64FromPath(":id")
|
||||||
@ -75,8 +75,7 @@ func (r *RobotAPI) Prepare() {
|
|||||||
r.SendBadRequestError(errors.New("invalid robot ID"))
|
r.SendBadRequestError(errors.New("invalid robot ID"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
robot, err := r.ctr.GetRobotAccount(id)
|
||||||
robot, err := dao.GetRobotByID(id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.SendInternalServerError(fmt.Errorf("failed to get robot %d: %v", id, err))
|
r.SendInternalServerError(fmt.Errorf("failed to get robot %d: %v", id, err))
|
||||||
return
|
return
|
||||||
@ -101,7 +100,7 @@ func (r *RobotAPI) Post() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var robotReq models.RobotReq
|
var robotReq model.RobotCreate
|
||||||
isValid, err := r.DecodeJSONReqAndValidate(&robotReq)
|
isValid, err := r.DecodeJSONReqAndValidate(&robotReq)
|
||||||
if !isValid {
|
if !isValid {
|
||||||
r.SendBadRequestError(err)
|
r.SendBadRequestError(err)
|
||||||
@ -113,59 +112,25 @@ func (r *RobotAPI) Post() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token duration in minutes
|
robot, err := r.ctr.CreateRobotAccount(&robotReq)
|
||||||
tokenDuration := time.Duration(config.RobotTokenDuration()) * time.Minute
|
|
||||||
expiresAt := time.Now().UTC().Add(tokenDuration).Unix()
|
|
||||||
createdName := common.RobotPrefix + robotReq.Name
|
|
||||||
|
|
||||||
// first to add a robot account, and get its id.
|
|
||||||
robot := models.Robot{
|
|
||||||
Name: createdName,
|
|
||||||
Description: robotReq.Description,
|
|
||||||
ProjectID: r.project.ProjectID,
|
|
||||||
ExpiresAt: expiresAt,
|
|
||||||
}
|
|
||||||
id, err := dao.AddRobot(&robot)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == dao.ErrDupRows {
|
if err == dao.ErrDupRows {
|
||||||
r.SendConflictError(errors.New("conflict robot account"))
|
r.SendConflictError(errors.New("conflict robot account"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.SendInternalServerError(fmt.Errorf("failed to create robot account: %v", err))
|
r.SendInternalServerError(errors.Wrap(err, "robot API: post"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the token, and return it with response data.
|
|
||||||
// token is not stored in the database.
|
|
||||||
jwtToken, err := token.New(id, r.project.ProjectID, expiresAt, robotReq.Access)
|
|
||||||
if err != nil {
|
|
||||||
r.SendInternalServerError(fmt.Errorf("failed to valid parameters to generate token for robot account, %v", err))
|
|
||||||
err := dao.DeleteRobot(id)
|
|
||||||
if err != nil {
|
|
||||||
r.SendInternalServerError(fmt.Errorf("failed to delete the robot account: %d, %v", id, err))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rawTk, err := jwtToken.Raw()
|
|
||||||
if err != nil {
|
|
||||||
r.SendInternalServerError(fmt.Errorf("failed to sign token for robot account, %v", err))
|
|
||||||
err := dao.DeleteRobot(id)
|
|
||||||
if err != nil {
|
|
||||||
r.SendInternalServerError(fmt.Errorf("failed to delete the robot account: %d, %v", id, err))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
robotRep := models.RobotRep{
|
|
||||||
Name: robot.Name,
|
|
||||||
Token: rawTk,
|
|
||||||
}
|
|
||||||
|
|
||||||
w := r.Ctx.ResponseWriter
|
w := r.Ctx.ResponseWriter
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
r.Redirect(http.StatusCreated, strconv.FormatInt(id, 10))
|
robotRep := model.RobotRep{
|
||||||
|
Name: robot.Name,
|
||||||
|
Token: robot.Token,
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Redirect(http.StatusCreated, strconv.FormatInt(robot.ID, 10))
|
||||||
r.Data["json"] = robotRep
|
r.Data["json"] = robotRep
|
||||||
r.ServeJSON()
|
r.ServeJSON()
|
||||||
}
|
}
|
||||||
@ -176,28 +141,19 @@ func (r *RobotAPI) List() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
query := models.RobotQuery{
|
robots, err := r.ctr.ListRobotAccount(r.project.ProjectID)
|
||||||
ProjectID: r.project.ProjectID,
|
|
||||||
}
|
|
||||||
|
|
||||||
count, err := dao.CountRobot(&query)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.SendInternalServerError(fmt.Errorf("failed to list robots on project: %d, %v", r.project.ProjectID, err))
|
r.SendInternalServerError(errors.Wrap(err, "robot API: list"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
query.Page, query.Size, err = r.GetPaginationParams()
|
count := len(robots)
|
||||||
|
page, size, err := r.GetPaginationParams()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.SendBadRequestError(err)
|
r.SendBadRequestError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
robots, err := dao.ListRobots(&query)
|
r.SetPaginationHeader(int64(count), page, size)
|
||||||
if err != nil {
|
|
||||||
r.SendInternalServerError(fmt.Errorf("failed to get robots %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SetPaginationHeader(count, query.Page, query.Size)
|
|
||||||
r.Data["json"] = robots
|
r.Data["json"] = robots
|
||||||
r.ServeJSON()
|
r.ServeJSON()
|
||||||
}
|
}
|
||||||
@ -214,13 +170,13 @@ func (r *RobotAPI) Get() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
robot, err := dao.GetRobotByID(id)
|
robot, err := r.ctr.GetRobotAccount(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.SendInternalServerError(fmt.Errorf("failed to get robot %d: %v", id, err))
|
r.SendInternalServerError(errors.Wrap(err, "robot API: get robot"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if robot == nil {
|
if robot == nil {
|
||||||
r.SendNotFoundError(fmt.Errorf("robot %d not found", id))
|
r.SendNotFoundError(fmt.Errorf("robot API: robot %d not found", id))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,7 +190,7 @@ func (r *RobotAPI) Put() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var robotReq models.RobotReq
|
var robotReq model.RobotCreate
|
||||||
if err := r.DecodeJSONReq(&robotReq); err != nil {
|
if err := r.DecodeJSONReq(&robotReq); err != nil {
|
||||||
r.SendBadRequestError(err)
|
r.SendBadRequestError(err)
|
||||||
return
|
return
|
||||||
@ -242,8 +198,8 @@ func (r *RobotAPI) Put() {
|
|||||||
|
|
||||||
r.robot.Disabled = robotReq.Disabled
|
r.robot.Disabled = robotReq.Disabled
|
||||||
|
|
||||||
if err := dao.UpdateRobot(r.robot); err != nil {
|
if err := r.ctr.UpdateRobotAccount(r.robot); err != nil {
|
||||||
r.SendInternalServerError(fmt.Errorf("failed to update robot %d: %v", r.robot.ID, err))
|
r.SendInternalServerError(errors.Wrap(err, "robot API: update"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,13 +211,13 @@ func (r *RobotAPI) Delete() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dao.DeleteRobot(r.robot.ID); err != nil {
|
if err := r.ctr.DeleteRobotAccount(r.robot.ID); err != nil {
|
||||||
r.SendInternalServerError(fmt.Errorf("failed to delete robot %d: %v", r.robot.ID, err))
|
r.SendInternalServerError(errors.Wrap(err, "robot API: delete"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRobotReq(p *models.Project, robotReq *models.RobotReq) error {
|
func validateRobotReq(p *models.Project, robotReq *model.RobotCreate) error {
|
||||||
if len(robotReq.Access) == 0 {
|
if len(robotReq.Access) == 0 {
|
||||||
return errors.New("access required")
|
return errors.New("access required")
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -53,7 +53,7 @@ func TestRobotAPIPost(t *testing.T) {
|
|||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
url: robotPath,
|
url: robotPath,
|
||||||
bodyJSON: &models.RobotReq{},
|
bodyJSON: &model.RobotCreate{},
|
||||||
credential: nonSysAdmin,
|
credential: nonSysAdmin,
|
||||||
},
|
},
|
||||||
code: http.StatusForbidden,
|
code: http.StatusForbidden,
|
||||||
@ -63,7 +63,7 @@ func TestRobotAPIPost(t *testing.T) {
|
|||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
url: robotPath,
|
url: robotPath,
|
||||||
bodyJSON: &models.RobotReq{
|
bodyJSON: &model.RobotCreate{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Description: "test desc",
|
Description: "test desc",
|
||||||
Access: policies,
|
Access: policies,
|
||||||
@ -77,7 +77,7 @@ func TestRobotAPIPost(t *testing.T) {
|
|||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
url: robotPath,
|
url: robotPath,
|
||||||
bodyJSON: &models.RobotReq{
|
bodyJSON: &model.RobotCreate{
|
||||||
Name: "testIllgel#",
|
Name: "testIllgel#",
|
||||||
Description: "test desc",
|
Description: "test desc",
|
||||||
},
|
},
|
||||||
@ -89,7 +89,7 @@ func TestRobotAPIPost(t *testing.T) {
|
|||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
url: robotPath,
|
url: robotPath,
|
||||||
bodyJSON: &models.RobotReq{
|
bodyJSON: &model.RobotCreate{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Description: "resource not exist",
|
Description: "resource not exist",
|
||||||
Access: []*rbac.Policy{
|
Access: []*rbac.Policy{
|
||||||
@ -104,7 +104,7 @@ func TestRobotAPIPost(t *testing.T) {
|
|||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
url: robotPath,
|
url: robotPath,
|
||||||
bodyJSON: &models.RobotReq{
|
bodyJSON: &model.RobotCreate{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Description: "action not exist",
|
Description: "action not exist",
|
||||||
Access: []*rbac.Policy{
|
Access: []*rbac.Policy{
|
||||||
@ -119,7 +119,7 @@ func TestRobotAPIPost(t *testing.T) {
|
|||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
url: robotPath,
|
url: robotPath,
|
||||||
bodyJSON: &models.RobotReq{
|
bodyJSON: &model.RobotCreate{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Description: "policy not exit",
|
Description: "policy not exit",
|
||||||
Access: []*rbac.Policy{
|
Access: []*rbac.Policy{
|
||||||
@ -135,7 +135,7 @@ func TestRobotAPIPost(t *testing.T) {
|
|||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
url: robotPath,
|
url: robotPath,
|
||||||
bodyJSON: &models.RobotReq{
|
bodyJSON: &model.RobotCreate{
|
||||||
Name: "test2",
|
Name: "test2",
|
||||||
Description: "test2 desc",
|
Description: "test2 desc",
|
||||||
},
|
},
|
||||||
@ -149,7 +149,7 @@ func TestRobotAPIPost(t *testing.T) {
|
|||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
url: robotPath,
|
url: robotPath,
|
||||||
bodyJSON: &models.RobotReq{
|
bodyJSON: &model.RobotCreate{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Description: "test desc",
|
Description: "test desc",
|
||||||
Access: policies,
|
Access: policies,
|
||||||
@ -306,7 +306,7 @@ func TestRobotAPIPut(t *testing.T) {
|
|||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodPut,
|
method: http.MethodPut,
|
||||||
url: fmt.Sprintf("%s/%d", robotPath, 1),
|
url: fmt.Sprintf("%s/%d", robotPath, 1),
|
||||||
bodyJSON: &models.Robot{
|
bodyJSON: &model.Robot{
|
||||||
Disabled: true,
|
Disabled: true,
|
||||||
},
|
},
|
||||||
credential: projAdmin4Robot,
|
credential: projAdmin4Robot,
|
||||||
|
@ -52,7 +52,7 @@ type userSearch struct {
|
|||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type secretResp struct {
|
type secretReq struct {
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,8 +405,8 @@ func (ua *UserAPI) ChangePassword() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req.NewPassword) == 0 {
|
if err := validateSecret(req.NewPassword); err != nil {
|
||||||
ua.SendBadRequestError(errors.New("empty new_password"))
|
ua.SendBadRequestError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,8 +512,8 @@ func (ua *UserAPI) ListUserPermissions() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenCLISecret generates a new CLI secret and replace the old one
|
// SetCLISecret handles request PUT /api/users/:id/cli_secret to update the CLI secret of the user
|
||||||
func (ua *UserAPI) GenCLISecret() {
|
func (ua *UserAPI) SetCLISecret() {
|
||||||
if ua.AuthMode != common.OIDCAuth {
|
if ua.AuthMode != common.OIDCAuth {
|
||||||
ua.SendPreconditionFailedError(errors.New("the auth mode has to be oidc auth"))
|
ua.SendPreconditionFailedError(errors.New("the auth mode has to be oidc auth"))
|
||||||
return
|
return
|
||||||
@ -534,8 +534,17 @@ func (ua *UserAPI) GenCLISecret() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sec := utils.GenerateRandomString()
|
s := &secretReq{}
|
||||||
encSec, err := utils.ReversibleEncrypt(sec, ua.secretKey)
|
if err := ua.DecodeJSONReq(s); err != nil {
|
||||||
|
ua.SendBadRequestError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := validateSecret(s.Secret); err != nil {
|
||||||
|
ua.SendBadRequestError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encSec, err := utils.ReversibleEncrypt(s.Secret, ua.secretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to encrypt secret, error: %v", err)
|
log.Errorf("Failed to encrypt secret, error: %v", err)
|
||||||
ua.SendInternalServerError(errors.New("failed to encrypt secret"))
|
ua.SendInternalServerError(errors.New("failed to encrypt secret"))
|
||||||
@ -548,8 +557,6 @@ func (ua *UserAPI) GenCLISecret() {
|
|||||||
ua.SendInternalServerError(errors.New("failed to update secret in DB"))
|
ua.SendInternalServerError(errors.New("failed to update secret in DB"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ua.Data["json"] = secretResp{sec}
|
|
||||||
ua.ServeJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ua *UserAPI) getOIDCUserInfo() (*models.OIDCUser, error) {
|
func (ua *UserAPI) getOIDCUserInfo() (*models.OIDCUser, error) {
|
||||||
@ -588,12 +595,24 @@ func validate(user models.User) error {
|
|||||||
if utils.IsContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
|
if utils.IsContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
|
||||||
return fmt.Errorf("username contains illegal characters")
|
return fmt.Errorf("username contains illegal characters")
|
||||||
}
|
}
|
||||||
if utils.IsIllegalLength(user.Password, 8, 20) {
|
|
||||||
return fmt.Errorf("password with illegal length")
|
if err := validateSecret(user.Password); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return commonValidate(user)
|
return commonValidate(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateSecret(in string) error {
|
||||||
|
hasLower := regexp.MustCompile(`[a-z]`)
|
||||||
|
hasUpper := regexp.MustCompile(`[A-Z]`)
|
||||||
|
hasNumber := regexp.MustCompile(`[0-9]`)
|
||||||
|
if len(in) >= 8 && hasLower.MatchString(in) && hasUpper.MatchString(in) && hasNumber.MatchString(in) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("the password or secret must longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number")
|
||||||
|
}
|
||||||
|
|
||||||
// commonValidate validates email, realname, comment information when user register or change their profile
|
// commonValidate validates email, realname, comment information when user register or change their profile
|
||||||
func commonValidate(user models.User) error {
|
func commonValidate(user models.User) error {
|
||||||
|
|
||||||
|
@ -380,8 +380,8 @@ func buildChangeUserPasswordURL(id int) string {
|
|||||||
|
|
||||||
func TestUsersUpdatePassword(t *testing.T) {
|
func TestUsersUpdatePassword(t *testing.T) {
|
||||||
fmt.Println("Testing Update User Password")
|
fmt.Println("Testing Update User Password")
|
||||||
oldPassword := "old_password"
|
oldPassword := "old_Passw0rd"
|
||||||
newPassword := "new_password"
|
newPassword := "new_Passw0rd"
|
||||||
|
|
||||||
user01 := models.User{
|
user01 := models.User{
|
||||||
Username: "user01_for_testing_change_password",
|
Username: "user01_for_testing_change_password",
|
||||||
@ -515,7 +515,7 @@ func TestUsersUpdatePassword(t *testing.T) {
|
|||||||
method: http.MethodPut,
|
method: http.MethodPut,
|
||||||
url: buildChangeUserPasswordURL(user01.UserID),
|
url: buildChangeUserPasswordURL(user01.UserID),
|
||||||
bodyJSON: &passwordReq{
|
bodyJSON: &passwordReq{
|
||||||
NewPassword: "another_new_password",
|
NewPassword: "another_new_Passw0rd",
|
||||||
},
|
},
|
||||||
credential: admin,
|
credential: admin,
|
||||||
},
|
},
|
||||||
@ -642,3 +642,13 @@ func TestUsersCurrentPermissions(t *testing.T) {
|
|||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
assert.Equal(int(403), httpStatusCode, "httpStatusCode should be 403")
|
assert.Equal(int(403), httpStatusCode, "httpStatusCode should be 403")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateSecret(t *testing.T) {
|
||||||
|
assert.NotNil(t, validateSecret(""))
|
||||||
|
assert.NotNil(t, validateSecret("12345678"))
|
||||||
|
assert.NotNil(t, validateSecret("passw0rd"))
|
||||||
|
assert.NotNil(t, validateSecret("PASSW0RD"))
|
||||||
|
assert.NotNil(t, validateSecret("Sh0rt"))
|
||||||
|
assert.Nil(t, validateSecret("Passw0rd"))
|
||||||
|
assert.Nil(t, validateSecret("Thisis1Valid_password"))
|
||||||
|
}
|
||||||
|
@ -43,6 +43,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/pkg/authproxy"
|
"github.com/goharbor/harbor/src/pkg/authproxy"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/robot"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContextValueKey for content value
|
// ContextValueKey for content value
|
||||||
@ -194,7 +195,8 @@ func (r *robotAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// Do authn for robot account, as Harbor only stores the token ID, just validate the ID and disable.
|
// Do authn for robot account, as Harbor only stores the token ID, just validate the ID and disable.
|
||||||
robot, err := dao.GetRobotByID(htk.Claims.(*token.RobotClaims).TokenID)
|
ctr := robot.RobotCtr
|
||||||
|
robot, err := ctr.GetRobotAccount(htk.Claims.(*token.RobotClaims).TokenID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get robot %s: %v", robotName, err)
|
log.Errorf("failed to get robot %s: %v", robotName, err)
|
||||||
return false
|
return false
|
||||||
|
@ -85,17 +85,22 @@ func updateInitPassword(userID int, password string) error {
|
|||||||
|
|
||||||
// Quota migration
|
// Quota migration
|
||||||
func quotaSync() error {
|
func quotaSync() error {
|
||||||
usages, err := dao.ListQuotaUsages()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("list quota usage error, %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
projects, err := dao.GetProjects(nil)
|
projects, err := dao.GetProjects(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("list project error, %v", err)
|
log.Errorf("list project error, %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pids []string
|
||||||
|
for _, project := range projects {
|
||||||
|
pids = append(pids, strconv.FormatInt(project.ProjectID, 10))
|
||||||
|
}
|
||||||
|
usages, err := dao.ListQuotaUsages(&models.QuotaUsageQuery{Reference: "project", ReferenceIDs: pids})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("list quota usage error, %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// The condition handles these two cases:
|
// The condition handles these two cases:
|
||||||
// 1, len(project) > 1 && len(usages) == 1. existing projects without usage, as we do always has 'library' usage in DB.
|
// 1, len(project) > 1 && len(usages) == 1. existing projects without usage, as we do always has 'library' usage in DB.
|
||||||
// 2, migration fails at the phase of inserting usage into DB, and parts of them are inserted successfully.
|
// 2, migration fails at the phase of inserting usage into DB, and parts of them are inserted successfully.
|
||||||
|
@ -52,7 +52,7 @@ func initRouters() {
|
|||||||
beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
|
beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
|
||||||
beego.Router("/api/users/:id/permissions", &api.UserAPI{}, "get:ListUserPermissions")
|
beego.Router("/api/users/:id/permissions", &api.UserAPI{}, "get:ListUserPermissions")
|
||||||
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
||||||
beego.Router("/api/users/:id/gen_cli_secret", &api.UserAPI{}, "post:GenCLISecret")
|
beego.Router("/api/users/:id/cli_secret", &api.UserAPI{}, "put:SetCLISecret")
|
||||||
beego.Router("/api/usergroups/?:ugid([0-9]+)", &api.UserGroupAPI{})
|
beego.Router("/api/usergroups/?:ugid([0-9]+)", &api.UserGroupAPI{})
|
||||||
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
|
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
|
||||||
beego.Router("/api/ldap/users/search", &api.LdapAPI{}, "get:Search")
|
beego.Router("/api/ldap/users/search", &api.LdapAPI{}, "get:Search")
|
||||||
|
115
src/pkg/robot/controller.go
Normal file
115
src/pkg/robot/controller.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package robot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/goharbor/harbor/src/common"
|
||||||
|
"github.com/goharbor/harbor/src/common/token"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
|
"github.com/goharbor/harbor/src/core/config"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// RobotCtr is a global variable for the default robot account controller implementation
|
||||||
|
RobotCtr = NewController(NewDefaultRobotAccountManager())
|
||||||
|
)
|
||||||
|
|
||||||
|
// Controller to handle the requests related with robot account
|
||||||
|
type Controller interface {
|
||||||
|
// GetRobotAccount ...
|
||||||
|
GetRobotAccount(id int64) (*model.Robot, error)
|
||||||
|
|
||||||
|
// CreateRobotAccount ...
|
||||||
|
CreateRobotAccount(robotReq *model.RobotCreate) (*model.Robot, error)
|
||||||
|
|
||||||
|
// DeleteRobotAccount ...
|
||||||
|
DeleteRobotAccount(id int64) error
|
||||||
|
|
||||||
|
// UpdateRobotAccount ...
|
||||||
|
UpdateRobotAccount(r *model.Robot) error
|
||||||
|
|
||||||
|
// ListRobotAccount ...
|
||||||
|
ListRobotAccount(pid int64) ([]*model.Robot, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultAPIController ...
|
||||||
|
type DefaultAPIController struct {
|
||||||
|
manager Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewController ...
|
||||||
|
func NewController(robotMgr Manager) Controller {
|
||||||
|
return &DefaultAPIController{
|
||||||
|
manager: robotMgr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRobotAccount ...
|
||||||
|
func (d *DefaultAPIController) GetRobotAccount(id int64) (*model.Robot, error) {
|
||||||
|
return d.manager.GetRobotAccount(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRobotAccount ...
|
||||||
|
func (d *DefaultAPIController) CreateRobotAccount(robotReq *model.RobotCreate) (*model.Robot, error) {
|
||||||
|
|
||||||
|
var deferDel error
|
||||||
|
// Token duration in minutes
|
||||||
|
tokenDuration := time.Duration(config.RobotTokenDuration()) * time.Minute
|
||||||
|
expiresAt := time.Now().UTC().Add(tokenDuration).Unix()
|
||||||
|
createdName := common.RobotPrefix + robotReq.Name
|
||||||
|
|
||||||
|
// first to add a robot account, and get its id.
|
||||||
|
robot := &model.Robot{
|
||||||
|
Name: createdName,
|
||||||
|
Description: robotReq.Description,
|
||||||
|
ProjectID: robotReq.ProjectID,
|
||||||
|
ExpiresAt: expiresAt,
|
||||||
|
}
|
||||||
|
id, err := d.manager.CreateRobotAccount(robot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the token, and return it with response data.
|
||||||
|
// token is not stored in the database.
|
||||||
|
jwtToken, err := token.New(id, robotReq.ProjectID, expiresAt, robotReq.Access)
|
||||||
|
if err != nil {
|
||||||
|
deferDel = err
|
||||||
|
return nil, fmt.Errorf("failed to valid parameters to generate token for robot account, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawTk, err := jwtToken.Raw()
|
||||||
|
if err != nil {
|
||||||
|
deferDel = err
|
||||||
|
return nil, fmt.Errorf("failed to sign token for robot account, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func(deferDel error) {
|
||||||
|
if deferDel != nil {
|
||||||
|
if err := d.manager.DeleteRobotAccount(id); err != nil {
|
||||||
|
log.Error(errors.Wrap(err, fmt.Sprintf("failed to delete the robot account: %d", id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(deferDel)
|
||||||
|
|
||||||
|
robot.Token = rawTk
|
||||||
|
robot.ID = id
|
||||||
|
return robot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRobotAccount ...
|
||||||
|
func (d *DefaultAPIController) DeleteRobotAccount(id int64) error {
|
||||||
|
return d.manager.DeleteRobotAccount(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRobotAccount ...
|
||||||
|
func (d *DefaultAPIController) UpdateRobotAccount(r *model.Robot) error {
|
||||||
|
return d.manager.UpdateRobotAccount(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRobotAccount ...
|
||||||
|
func (d *DefaultAPIController) ListRobotAccount(pid int64) ([]*model.Robot, error) {
|
||||||
|
return d.manager.ListRobotAccount(pid)
|
||||||
|
}
|
103
src/pkg/robot/controller_test.go
Normal file
103
src/pkg/robot/controller_test.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package robot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/goharbor/harbor/src/common"
|
||||||
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils/test"
|
||||||
|
core_cfg "github.com/goharbor/harbor/src/core/config"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ControllerTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
ctr Controller
|
||||||
|
t *testing.T
|
||||||
|
assert *assert.Assertions
|
||||||
|
require *require.Assertions
|
||||||
|
|
||||||
|
robotID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupSuite ...
|
||||||
|
func (s *ControllerTestSuite) SetupSuite() {
|
||||||
|
test.InitDatabaseFromEnv()
|
||||||
|
conf := map[string]interface{}{
|
||||||
|
common.RobotTokenDuration: "30",
|
||||||
|
}
|
||||||
|
core_cfg.InitWithSettings(conf)
|
||||||
|
s.t = s.T()
|
||||||
|
s.assert = assert.New(s.t)
|
||||||
|
s.require = require.New(s.t)
|
||||||
|
s.ctr = RobotCtr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ControllerTestSuite) TestRobotAccount() {
|
||||||
|
|
||||||
|
res := rbac.Resource("/project/1")
|
||||||
|
|
||||||
|
rbacPolicy := &rbac.Policy{
|
||||||
|
Resource: res.Subresource(rbac.ResourceRepository),
|
||||||
|
Action: "pull",
|
||||||
|
}
|
||||||
|
policies := []*rbac.Policy{}
|
||||||
|
policies = append(policies, rbacPolicy)
|
||||||
|
|
||||||
|
robot1 := &model.RobotCreate{
|
||||||
|
Name: "robot1",
|
||||||
|
Description: "TestCreateRobotAccount",
|
||||||
|
ProjectID: int64(1),
|
||||||
|
Access: policies,
|
||||||
|
}
|
||||||
|
|
||||||
|
robot, err := s.ctr.CreateRobotAccount(robot1)
|
||||||
|
s.require.Nil(err)
|
||||||
|
s.require.Equal(robot.ProjectID, int64(1))
|
||||||
|
s.require.Equal(robot.Description, "TestCreateRobotAccount")
|
||||||
|
s.require.NotEmpty(robot.Token)
|
||||||
|
s.require.Equal(robot.Name, common.RobotPrefix+"robot1")
|
||||||
|
|
||||||
|
robotGet, err := s.ctr.GetRobotAccount(robot.ID)
|
||||||
|
s.require.Nil(err)
|
||||||
|
s.require.Equal(robotGet.ProjectID, int64(1))
|
||||||
|
s.require.Equal(robotGet.Description, "TestCreateRobotAccount")
|
||||||
|
|
||||||
|
robot.Disabled = true
|
||||||
|
err = s.ctr.UpdateRobotAccount(robot)
|
||||||
|
s.require.Nil(err)
|
||||||
|
s.require.Equal(robot.Disabled, true)
|
||||||
|
|
||||||
|
robot2 := &model.RobotCreate{
|
||||||
|
Name: "robot2",
|
||||||
|
Description: "TestCreateRobotAccount",
|
||||||
|
ProjectID: int64(1),
|
||||||
|
Access: policies,
|
||||||
|
}
|
||||||
|
r2, _ := s.ctr.CreateRobotAccount(robot2)
|
||||||
|
s.robotID = r2.ID
|
||||||
|
|
||||||
|
robots, err := s.ctr.ListRobotAccount(int64(1))
|
||||||
|
s.require.Nil(err)
|
||||||
|
s.require.Equal(len(robots), 2)
|
||||||
|
s.require.Equal(robots[1].Name, common.RobotPrefix+"robot2")
|
||||||
|
|
||||||
|
err = s.ctr.DeleteRobotAccount(robot.ID)
|
||||||
|
s.require.Nil(err)
|
||||||
|
|
||||||
|
robots, err = s.ctr.ListRobotAccount(int64(1))
|
||||||
|
s.require.Equal(len(robots), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TearDownSuite clears env for test suite
|
||||||
|
func (s *ControllerTestSuite) TearDownSuite() {
|
||||||
|
err := s.ctr.DeleteRobotAccount(s.robotID)
|
||||||
|
require.NoError(s.T(), err, "delete robot")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestController ...
|
||||||
|
func TestController(t *testing.T) {
|
||||||
|
suite.Run(t, new(ControllerTestSuite))
|
||||||
|
}
|
121
src/pkg/robot/dao/robot.go
Normal file
121
src/pkg/robot/dao/robot.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/astaxie/beego/orm"
|
||||||
|
"github.com/goharbor/harbor/src/common/dao"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RobotAccountDao defines the interface to access the ImmutableRule data model
|
||||||
|
type RobotAccountDao interface {
|
||||||
|
// CreateRobotAccount ...
|
||||||
|
CreateRobotAccount(robot *model.Robot) (int64, error)
|
||||||
|
|
||||||
|
// UpdateRobotAccount ...
|
||||||
|
UpdateRobotAccount(robot *model.Robot) error
|
||||||
|
|
||||||
|
// GetRobotAccount ...
|
||||||
|
GetRobotAccount(id int64) (*model.Robot, error)
|
||||||
|
|
||||||
|
// ListRobotAccounts ...
|
||||||
|
ListRobotAccounts(query *q.Query) ([]*model.Robot, error)
|
||||||
|
|
||||||
|
// DeleteRobotAccount ...
|
||||||
|
DeleteRobotAccount(id int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a default implementation for RobotAccountDao
|
||||||
|
func New() RobotAccountDao {
|
||||||
|
return &robotAccountDao{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type robotAccountDao struct{}
|
||||||
|
|
||||||
|
// CreateRobotAccount ...
|
||||||
|
func (r *robotAccountDao) CreateRobotAccount(robot *model.Robot) (int64, error) {
|
||||||
|
now := time.Now()
|
||||||
|
robot.CreationTime = now
|
||||||
|
robot.UpdateTime = now
|
||||||
|
id, err := dao.GetOrmer().Insert(robot)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
|
||||||
|
return 0, dao.ErrDupRows
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRobotAccount ...
|
||||||
|
func (r *robotAccountDao) GetRobotAccount(id int64) (*model.Robot, error) {
|
||||||
|
robot := &model.Robot{
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
if err := dao.GetOrmer().Read(robot); err != nil {
|
||||||
|
if err == orm.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return robot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRobotAccounts ...
|
||||||
|
func (r *robotAccountDao) ListRobotAccounts(query *q.Query) ([]*model.Robot, error) {
|
||||||
|
o := dao.GetOrmer()
|
||||||
|
qt := o.QueryTable(new(model.Robot))
|
||||||
|
|
||||||
|
if query != nil {
|
||||||
|
if len(query.Keywords) > 0 {
|
||||||
|
for k, v := range query.Keywords {
|
||||||
|
qt = qt.Filter(fmt.Sprintf("%s__icontains", k), v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.PageNumber > 0 && query.PageSize > 0 {
|
||||||
|
qt = qt.Limit(query.PageSize, (query.PageNumber-1)*query.PageSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
robots := make([]*model.Robot, 0)
|
||||||
|
_, err := qt.All(&robots)
|
||||||
|
return robots, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRobotAccount ...
|
||||||
|
func (r *robotAccountDao) UpdateRobotAccount(robot *model.Robot) error {
|
||||||
|
robot.UpdateTime = time.Now()
|
||||||
|
_, err := dao.GetOrmer().Update(robot)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRobotAccount ...
|
||||||
|
func (r *robotAccountDao) DeleteRobotAccount(id int64) error {
|
||||||
|
_, err := dao.GetOrmer().QueryTable(&model.Robot{}).Filter("ID", id).Delete()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRobotQuerySetter(query *model.RobotQuery) orm.QuerySeter {
|
||||||
|
qs := dao.GetOrmer().QueryTable(&model.Robot{})
|
||||||
|
|
||||||
|
if query == nil {
|
||||||
|
return qs
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(query.Name) > 0 {
|
||||||
|
if query.FuzzyMatchName {
|
||||||
|
qs = qs.Filter("Name__icontains", query.Name)
|
||||||
|
} else {
|
||||||
|
qs = qs.Filter("Name", query.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if query.ProjectID != 0 {
|
||||||
|
qs = qs.Filter("ProjectID", query.ProjectID)
|
||||||
|
}
|
||||||
|
return qs
|
||||||
|
}
|
140
src/pkg/robot/dao/robot_test.go
Normal file
140
src/pkg/robot/dao/robot_test.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/goharbor/harbor/src/common/dao"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type robotAccountDaoTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
require *require.Assertions
|
||||||
|
assert *assert.Assertions
|
||||||
|
dao RobotAccountDao
|
||||||
|
id1 int64
|
||||||
|
id2 int64
|
||||||
|
id3 int64
|
||||||
|
id4 int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *robotAccountDaoTestSuite) SetupSuite() {
|
||||||
|
t.require = require.New(t.T())
|
||||||
|
t.assert = assert.New(t.T())
|
||||||
|
dao.PrepareTestForPostgresSQL()
|
||||||
|
t.dao = New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *robotAccountDaoTestSuite) TestCreateRobotAccount() {
|
||||||
|
robotName := "test1"
|
||||||
|
robot := &model.Robot{
|
||||||
|
Name: robotName,
|
||||||
|
Description: "test1 description",
|
||||||
|
ProjectID: 1,
|
||||||
|
}
|
||||||
|
id, err := t.dao.CreateRobotAccount(robot)
|
||||||
|
t.require.Nil(err)
|
||||||
|
t.id1 = id
|
||||||
|
t.require.Nil(err)
|
||||||
|
t.require.NotNil(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *robotAccountDaoTestSuite) TestGetRobotAccount() {
|
||||||
|
robotName := "test2"
|
||||||
|
robot := &model.Robot{
|
||||||
|
Name: robotName,
|
||||||
|
Description: "test2 description",
|
||||||
|
ProjectID: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// add
|
||||||
|
id, err := t.dao.CreateRobotAccount(robot)
|
||||||
|
t.require.Nil(err)
|
||||||
|
t.id2 = id
|
||||||
|
|
||||||
|
robot, err = t.dao.GetRobotAccount(id)
|
||||||
|
t.require.Nil(err)
|
||||||
|
t.require.Equal(robotName, robot.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *robotAccountDaoTestSuite) TestListRobotAccounts() {
|
||||||
|
robotName := "test3"
|
||||||
|
robot := &model.Robot{
|
||||||
|
Name: robotName,
|
||||||
|
Description: "test3 description",
|
||||||
|
ProjectID: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := t.dao.CreateRobotAccount(robot)
|
||||||
|
t.require.Nil(err)
|
||||||
|
t.id3 = id
|
||||||
|
|
||||||
|
keywords := make(map[string]interface{})
|
||||||
|
keywords["ProjectID"] = 1
|
||||||
|
robots, err := t.dao.ListRobotAccounts(&q.Query{
|
||||||
|
Keywords: keywords,
|
||||||
|
})
|
||||||
|
t.require.Nil(err)
|
||||||
|
t.require.Equal(3, len(robots))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *robotAccountDaoTestSuite) TestUpdateRobotAccount() {
|
||||||
|
robotName := "test4"
|
||||||
|
robot := &model.Robot{
|
||||||
|
Name: robotName,
|
||||||
|
Description: "test4 description",
|
||||||
|
ProjectID: 1,
|
||||||
|
}
|
||||||
|
// add
|
||||||
|
id, err := t.dao.CreateRobotAccount(robot)
|
||||||
|
t.require.Nil(err)
|
||||||
|
t.id4 = id
|
||||||
|
// Disable
|
||||||
|
robot.Disabled = true
|
||||||
|
err = t.dao.UpdateRobotAccount(robot)
|
||||||
|
t.require.Nil(err)
|
||||||
|
// Get
|
||||||
|
robot, err = t.dao.GetRobotAccount(id)
|
||||||
|
t.require.Nil(err)
|
||||||
|
t.require.Equal(true, robot.Disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *robotAccountDaoTestSuite) TestDeleteRobotAccount() {
|
||||||
|
robotName := "test5"
|
||||||
|
robot := &model.Robot{
|
||||||
|
Name: robotName,
|
||||||
|
Description: "test5 description",
|
||||||
|
ProjectID: 1,
|
||||||
|
}
|
||||||
|
// add
|
||||||
|
id, err := t.dao.CreateRobotAccount(robot)
|
||||||
|
t.require.Nil(err)
|
||||||
|
// Disable
|
||||||
|
err = t.dao.DeleteRobotAccount(id)
|
||||||
|
t.require.Nil(err)
|
||||||
|
// Get
|
||||||
|
robot, err = t.dao.GetRobotAccount(id)
|
||||||
|
t.require.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TearDownSuite clears env for test suite
|
||||||
|
func (t *robotAccountDaoTestSuite) TearDownSuite() {
|
||||||
|
err := t.dao.DeleteRobotAccount(t.id1)
|
||||||
|
require.NoError(t.T(), err, "delete robot 1")
|
||||||
|
|
||||||
|
err = t.dao.DeleteRobotAccount(t.id2)
|
||||||
|
require.NoError(t.T(), err, "delete robot 2")
|
||||||
|
|
||||||
|
err = t.dao.DeleteRobotAccount(t.id3)
|
||||||
|
require.NoError(t.T(), err, "delete robot 3")
|
||||||
|
|
||||||
|
err = t.dao.DeleteRobotAccount(t.id4)
|
||||||
|
require.NoError(t.T(), err, "delete robot 4")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRobotAccountDaoTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &robotAccountDaoTestSuite{})
|
||||||
|
}
|
71
src/pkg/robot/manager.go
Normal file
71
src/pkg/robot/manager.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package robot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/robot/dao"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Mgr is a global variable for the default robot account manager implementation
|
||||||
|
Mgr = NewDefaultRobotAccountManager()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager ...
|
||||||
|
type Manager interface {
|
||||||
|
// GetRobotAccount ...
|
||||||
|
GetRobotAccount(id int64) (*model.Robot, error)
|
||||||
|
|
||||||
|
// CreateRobotAccount ...
|
||||||
|
CreateRobotAccount(m *model.Robot) (int64, error)
|
||||||
|
|
||||||
|
// DeleteRobotAccount ...
|
||||||
|
DeleteRobotAccount(id int64) error
|
||||||
|
|
||||||
|
// UpdateRobotAccount ...
|
||||||
|
UpdateRobotAccount(m *model.Robot) error
|
||||||
|
|
||||||
|
// ListRobotAccount ...
|
||||||
|
ListRobotAccount(pid int64) ([]*model.Robot, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultRobotManager struct {
|
||||||
|
dao dao.RobotAccountDao
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultRobotAccountManager return a new instance of defaultRobotManager
|
||||||
|
func NewDefaultRobotAccountManager() Manager {
|
||||||
|
return &defaultRobotManager{
|
||||||
|
dao: dao.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRobotAccount ...
|
||||||
|
func (drm *defaultRobotManager) GetRobotAccount(id int64) (*model.Robot, error) {
|
||||||
|
return drm.dao.GetRobotAccount(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRobotAccount ...
|
||||||
|
func (drm *defaultRobotManager) CreateRobotAccount(r *model.Robot) (int64, error) {
|
||||||
|
return drm.dao.CreateRobotAccount(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRobotAccount ...
|
||||||
|
func (drm *defaultRobotManager) DeleteRobotAccount(id int64) error {
|
||||||
|
return drm.dao.DeleteRobotAccount(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRobotAccount ...
|
||||||
|
func (drm *defaultRobotManager) UpdateRobotAccount(r *model.Robot) error {
|
||||||
|
return drm.dao.UpdateRobotAccount(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRobotAccount ...
|
||||||
|
func (drm *defaultRobotManager) ListRobotAccount(pid int64) ([]*model.Robot, error) {
|
||||||
|
keywords := make(map[string]interface{})
|
||||||
|
keywords["ProjectID"] = pid
|
||||||
|
query := q.Query{
|
||||||
|
Keywords: keywords,
|
||||||
|
}
|
||||||
|
return drm.dao.ListRobotAccounts(&query)
|
||||||
|
}
|
142
src/pkg/robot/manager_test.go
Normal file
142
src/pkg/robot/manager_test.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package robot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockRobotDao struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRobotDao) CreateRobotAccount(r *model.Robot) (int64, error) {
|
||||||
|
args := m.Called(r)
|
||||||
|
return int64(args.Int(0)), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRobotDao) UpdateRobotAccount(r *model.Robot) error {
|
||||||
|
args := m.Called(r)
|
||||||
|
return args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRobotDao) DeleteRobotAccount(id int64) error {
|
||||||
|
args := m.Called(id)
|
||||||
|
return args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRobotDao) GetRobotAccount(id int64) (*model.Robot, error) {
|
||||||
|
args := m.Called(id)
|
||||||
|
var r *model.Robot
|
||||||
|
if args.Get(0) != nil {
|
||||||
|
r = args.Get(0).(*model.Robot)
|
||||||
|
}
|
||||||
|
return r, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRobotDao) ListRobotAccounts(query *q.Query) ([]*model.Robot, error) {
|
||||||
|
args := m.Called()
|
||||||
|
var rs []*model.Robot
|
||||||
|
if args.Get(0) != nil {
|
||||||
|
rs = args.Get(0).([]*model.Robot)
|
||||||
|
}
|
||||||
|
return rs, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type managerTestingSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
t *testing.T
|
||||||
|
assert *assert.Assertions
|
||||||
|
require *require.Assertions
|
||||||
|
mockRobotDao *mockRobotDao
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managerTestingSuite) SetupSuite() {
|
||||||
|
m.t = m.T()
|
||||||
|
m.assert = assert.New(m.t)
|
||||||
|
m.require = require.New(m.t)
|
||||||
|
|
||||||
|
err := os.Setenv("RUN_MODE", "TEST")
|
||||||
|
m.require.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managerTestingSuite) TearDownSuite() {
|
||||||
|
err := os.Unsetenv("RUN_MODE")
|
||||||
|
m.require.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managerTestingSuite) SetupTest() {
|
||||||
|
m.mockRobotDao = &mockRobotDao{}
|
||||||
|
Mgr = &defaultRobotManager{
|
||||||
|
dao: m.mockRobotDao,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagerTestingSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &managerTestingSuite{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managerTestingSuite) TestCreateRobotAccount() {
|
||||||
|
m.mockRobotDao.On("CreateRobotAccount", mock.Anything).Return(1, nil)
|
||||||
|
id, err := Mgr.CreateRobotAccount(&model.Robot{})
|
||||||
|
m.mockRobotDao.AssertCalled(m.t, "CreateRobotAccount", mock.Anything)
|
||||||
|
m.require.Nil(err)
|
||||||
|
m.assert.Equal(int64(1), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managerTestingSuite) TestUpdateRobotAccount() {
|
||||||
|
m.mockRobotDao.On("UpdateRobotAccount", mock.Anything).Return(1, nil)
|
||||||
|
err := Mgr.UpdateRobotAccount(&model.Robot{})
|
||||||
|
m.mockRobotDao.AssertCalled(m.t, "UpdateRobotAccount", mock.Anything)
|
||||||
|
m.require.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managerTestingSuite) TestDeleteRobotAccount() {
|
||||||
|
m.mockRobotDao.On("DeleteRobotAccount", mock.Anything).Return(1, nil)
|
||||||
|
err := Mgr.DeleteRobotAccount(int64(1))
|
||||||
|
m.mockRobotDao.AssertCalled(m.t, "DeleteRobotAccount", mock.Anything)
|
||||||
|
m.require.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managerTestingSuite) TestGetRobotAccount() {
|
||||||
|
m.mockRobotDao.On("GetRobotAccount", mock.Anything).Return(&model.Robot{
|
||||||
|
ID: 1,
|
||||||
|
ProjectID: 1,
|
||||||
|
Disabled: true,
|
||||||
|
ExpiresAt: 150000,
|
||||||
|
}, nil)
|
||||||
|
ir, err := Mgr.GetRobotAccount(1)
|
||||||
|
m.mockRobotDao.AssertCalled(m.t, "GetRobotAccount", mock.Anything)
|
||||||
|
m.require.Nil(err)
|
||||||
|
m.require.NotNil(ir)
|
||||||
|
m.assert.Equal(int64(1), ir.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managerTestingSuite) ListRobotAccount() {
|
||||||
|
m.mockRobotDao.On("ListRobotAccount", mock.Anything).Return([]model.Robot{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
ProjectID: 1,
|
||||||
|
Disabled: false,
|
||||||
|
ExpiresAt: 12345,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
ProjectID: 1,
|
||||||
|
Disabled: false,
|
||||||
|
ExpiresAt: 54321,
|
||||||
|
}}, nil)
|
||||||
|
|
||||||
|
rs, err := Mgr.ListRobotAccount(int64(1))
|
||||||
|
m.mockRobotDao.AssertCalled(m.t, "ListRobotAccount", mock.Anything)
|
||||||
|
m.require.Nil(err)
|
||||||
|
m.assert.Equal(len(rs), 2)
|
||||||
|
m.assert.Equal(rs[0].Disabled, false)
|
||||||
|
m.assert.Equal(rs[1].ExpiresAt, 54321)
|
||||||
|
|
||||||
|
}
|
@ -1,20 +1,7 @@
|
|||||||
// Copyright Project Harbor Authors
|
package model
|
||||||
//
|
|
||||||
// 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 (
|
import (
|
||||||
|
"github.com/astaxie/beego/orm"
|
||||||
"github.com/astaxie/beego/validation"
|
"github.com/astaxie/beego/validation"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/common/utils"
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
@ -24,10 +11,15 @@ import (
|
|||||||
// RobotTable is the name of table in DB that holds the robot object
|
// RobotTable is the name of table in DB that holds the robot object
|
||||||
const RobotTable = "robot"
|
const RobotTable = "robot"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
orm.RegisterModel(&Robot{})
|
||||||
|
}
|
||||||
|
|
||||||
// Robot holds the details of a robot.
|
// Robot holds the details of a robot.
|
||||||
type Robot struct {
|
type Robot struct {
|
||||||
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
||||||
Name string `orm:"column(name)" json:"name"`
|
Name string `orm:"column(name)" json:"name"`
|
||||||
|
Token string `orm:"-" json:"token"`
|
||||||
Description string `orm:"column(description)" json:"description"`
|
Description string `orm:"column(description)" json:"description"`
|
||||||
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||||
ExpiresAt int64 `orm:"column(expiresat)" json:"expires_at"`
|
ExpiresAt int64 `orm:"column(expiresat)" json:"expires_at"`
|
||||||
@ -36,6 +28,11 @@ type Robot struct {
|
|||||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TableName ...
|
||||||
|
func (r *Robot) TableName() string {
|
||||||
|
return RobotTable
|
||||||
|
}
|
||||||
|
|
||||||
// RobotQuery ...
|
// RobotQuery ...
|
||||||
type RobotQuery struct {
|
type RobotQuery struct {
|
||||||
Name string
|
Name string
|
||||||
@ -45,16 +42,23 @@ type RobotQuery struct {
|
|||||||
Pagination
|
Pagination
|
||||||
}
|
}
|
||||||
|
|
||||||
// RobotReq ...
|
// RobotCreate ...
|
||||||
type RobotReq struct {
|
type RobotCreate struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
ProjectID int64 `json:"pid"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
Access []*rbac.Policy `json:"access"`
|
Access []*rbac.Policy `json:"access"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pagination ...
|
||||||
|
type Pagination struct {
|
||||||
|
Page int64
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
|
|
||||||
// Valid ...
|
// Valid ...
|
||||||
func (rq *RobotReq) Valid(v *validation.Validation) {
|
func (rq *RobotCreate) Valid(v *validation.Validation) {
|
||||||
if utils.IsIllegalLength(rq.Name, 1, 255) {
|
if utils.IsIllegalLength(rq.Name, 1, 255) {
|
||||||
v.SetError("name", "robot name with illegal length")
|
v.SetError("name", "robot name with illegal length")
|
||||||
}
|
}
|
||||||
@ -68,8 +72,3 @@ type RobotRep struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName ...
|
|
||||||
func (r *Robot) TableName() string {
|
|
||||||
return RobotTable
|
|
||||||
}
|
|
@ -61,6 +61,10 @@ Switch To Replication
|
|||||||
Retry Element Click xpath=${project_replication_xpath}
|
Retry Element Click xpath=${project_replication_xpath}
|
||||||
Sleep 1
|
Sleep 1
|
||||||
|
|
||||||
|
Switch To Project Configuration
|
||||||
|
Retry Element Click ${project_config_tabsheet}
|
||||||
|
Sleep 1
|
||||||
|
|
||||||
Navigate To Projects
|
Navigate To Projects
|
||||||
Retry Element Click xpath=${projects_xpath}
|
Retry Element Click xpath=${projects_xpath}
|
||||||
Sleep 2
|
Sleep 2
|
||||||
@ -82,7 +86,7 @@ Search Private Projects
|
|||||||
Make Project Private
|
Make Project Private
|
||||||
[Arguments] ${projectname}
|
[Arguments] ${projectname}
|
||||||
Go Into Project ${project name}
|
Go Into Project ${project name}
|
||||||
Retry Element Click ${project_config_tabsheet}
|
Switch To Project Configuration
|
||||||
Retry Checkbox Should Be Selected ${project_config_public_checkbox}
|
Retry Checkbox Should Be Selected ${project_config_public_checkbox}
|
||||||
Retry Double Keywords When Error Retry Element Click ${project_config_public_checkbox_label} Retry Checkbox Should Not Be Selected ${project_config_public_checkbox}
|
Retry Double Keywords When Error Retry Element Click ${project_config_public_checkbox_label} Retry Checkbox Should Not Be Selected ${project_config_public_checkbox}
|
||||||
Retry Element Click //button[contains(.,'SAVE')]
|
Retry Element Click //button[contains(.,'SAVE')]
|
||||||
@ -91,7 +95,7 @@ Make Project Private
|
|||||||
Make Project Public
|
Make Project Public
|
||||||
[Arguments] ${projectname}
|
[Arguments] ${projectname}
|
||||||
Go Into Project ${project name}
|
Go Into Project ${project name}
|
||||||
Retry Element Click ${project_config_tabsheet}
|
Switch To Project Configuration
|
||||||
Retry Checkbox Should Not Be Selected ${project_config_public_checkbox}
|
Retry Checkbox Should Not Be Selected ${project_config_public_checkbox}
|
||||||
Retry Double Keywords When Error Retry Element Click ${project_config_public_checkbox_label} Retry Checkbox Should Be Selected ${project_config_public_checkbox}
|
Retry Double Keywords When Error Retry Element Click ${project_config_public_checkbox_label} Retry Checkbox Should Be Selected ${project_config_public_checkbox}
|
||||||
Retry Element Click //button[contains(.,'SAVE')]
|
Retry Element Click //button[contains(.,'SAVE')]
|
||||||
|
@ -50,6 +50,10 @@ ${tag_images_btn} xpath=//hbr-repository//button[contains(.,'Images')]
|
|||||||
${project_member_action_xpath} xpath=//*[@id='member-action']
|
${project_member_action_xpath} xpath=//*[@id='member-action']
|
||||||
${project_member_set_role_xpath} xpath=//clr-dropdown-menu//label[contains(.,'SET ROLE')]
|
${project_member_set_role_xpath} xpath=//clr-dropdown-menu//label[contains(.,'SET ROLE')]
|
||||||
${project_config_public_checkbox} xpath=//input[@name='public']
|
${project_config_public_checkbox} xpath=//input[@name='public']
|
||||||
|
${project_config_content_trust_checkbox} xpath=//input[@name='content-trust']
|
||||||
|
${project_config_scan_images_on_push_checkbox} xpath=//input[@name='scan-image-on-push']
|
||||||
|
${project_config_prevent_vulnerable_images_from_running_checkbox} xpath=//input[@name='prevent-vulenrability-image-input']
|
||||||
|
${project_config_severity_select} xpath=//select[@id='severity']
|
||||||
${project_config_public_checkbox_label} xpath=//*[@id="clr-wrapper-public"]/div/clr-checkbox-wrapper/label
|
${project_config_public_checkbox_label} xpath=//*[@id="clr-wrapper-public"]/div/clr-checkbox-wrapper/label
|
||||||
${project_config_prevent_vulenrability_checkbox_label} xpath=//*[@id='prevent-vulenrability-image']//clr-checkbox-wrapper//label
|
${project_config_prevent_vulenrability_checkbox_label} xpath=//*[@id='prevent-vulenrability-image']//clr-checkbox-wrapper//label
|
||||||
${project_config_system_wl_radio_input} xpath=//clr-radio-wrapper//label[contains(.,'System whitelist')]
|
${project_config_system_wl_radio_input} xpath=//clr-radio-wrapper//label[contains(.,'System whitelist')]
|
||||||
|
@ -27,8 +27,7 @@ Verify Project
|
|||||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||||
:FOR ${project} IN @{project}
|
:FOR ${project} IN @{project}
|
||||||
\ Page Should Contain ${project}
|
\ Page Should Contain ${project}
|
||||||
#TO_DO:
|
Verify Project Metadata ${json}
|
||||||
#Verify project metadata.
|
|
||||||
Close Browser
|
Close Browser
|
||||||
|
|
||||||
Verify Image Tag
|
Verify Image Tag
|
||||||
@ -40,11 +39,38 @@ Verify Image Tag
|
|||||||
\ @{out_has_image}= Get Value From Json ${json} $.projects[?(@.name=${project})].has_image
|
\ @{out_has_image}= Get Value From Json ${json} $.projects[?(@.name=${project})].has_image
|
||||||
\ ${has_image} Set Variable If @{out_has_image}[0] == ${true} ${true} ${false}
|
\ ${has_image} Set Variable If @{out_has_image}[0] == ${true} ${true} ${false}
|
||||||
\ Go Into Project ${project} has_image=${has_image}
|
\ Go Into Project ${project} has_image=${has_image}
|
||||||
\ @{repo}= Get Value From Json ${json} $.projects[?(@name=${project})]..repo..name
|
\ @{repo}= Get Value From Json ${json} $.projects[?(@.name=${project})]..repo..name
|
||||||
\ Loop Image Repo @{repo}
|
\ Run Keyword If ${has_image} == ${true} Loop Image Repo @{repo}
|
||||||
\ Navigate To Projects
|
\ Navigate To Projects
|
||||||
Close Browser
|
Close Browser
|
||||||
|
|
||||||
|
Verify Project Metadata
|
||||||
|
[Arguments] ${json}
|
||||||
|
@{project}= Get Value From Json ${json} $.projects.[*].name
|
||||||
|
Init Chrome Driver
|
||||||
|
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||||
|
:FOR ${project} IN @{project}
|
||||||
|
\ @{out_has_image}= Get Value From Json ${json} $.projects[?(@.name=${project})].has_image
|
||||||
|
\ ${has_image} Set Variable If @{out_has_image}[0] == ${true} ${true} ${false}
|
||||||
|
\ Go Into Project ${project} has_image=${has_image}
|
||||||
|
\ Switch To Project Configuration
|
||||||
|
\ Verify Checkbox ${json} $.projects[?(@.name=${project})].configuration.public ${project_config_public_checkbox}
|
||||||
|
\ Verify Checkbox ${json} $.projects[?(@.name=${project})].configuration.enable_content_trust ${project_config_content_trust_checkbox}
|
||||||
|
\ Verify Checkbox ${json} $.projects[?(@.name=${project})].configuration.automatically_scan_images_on_push ${project_config_scan_images_on_push_checkbox}
|
||||||
|
\ Verify Checkbox ${json} $.projects[?(@.name=${project})].configuration.prevent_vulnerable_images_from_running ${project_config_prevent_vulnerable_images_from_running_checkbox}
|
||||||
|
\ ${ret} Get Selected List Value ${project_config_severity_select}
|
||||||
|
\ @{severity}= Get Value From Json ${json} $.projects[?(@.name=${project})].configuration.prevent_vlunerable_images_from_running_severity
|
||||||
|
\ Should Contain ${ret} @{severity}[0]
|
||||||
|
\ Navigate To Projects
|
||||||
|
Close Browser
|
||||||
|
|
||||||
|
Verify Checkbox
|
||||||
|
[Arguments] ${json} ${key} ${checkbox}
|
||||||
|
@{out}= Get Value From Json ${json} ${key}
|
||||||
|
Run Keyword If '@{out}[0]'=='true' Checkbox Should Be Selected ${checkbox}
|
||||||
|
... ELSE Checkbox Should Not Be Selected ${checkbox}
|
||||||
|
|
||||||
|
|
||||||
Loop Image Repo
|
Loop Image Repo
|
||||||
[Arguments] @{repo}
|
[Arguments] @{repo}
|
||||||
:For ${repo} In @{repo}
|
:For ${repo} In @{repo}
|
||||||
@ -60,7 +86,7 @@ Verify Member Exist
|
|||||||
\ ${has_image} Set Variable If @{out_has_image}[0] == ${true} ${true} ${false}
|
\ ${has_image} Set Variable If @{out_has_image}[0] == ${true} ${true} ${false}
|
||||||
\ Go Into Project ${project} has_image=${has_image}
|
\ Go Into Project ${project} has_image=${has_image}
|
||||||
\ Switch To Member
|
\ Switch To Member
|
||||||
\ @{members}= Get Value From Json ${json} $.projects[?(@name=${project})].member..name
|
\ @{members}= Get Value From Json ${json} $.projects[?(@.name=${project})].member..name
|
||||||
\ Loop Member @{members}
|
\ Loop Member @{members}
|
||||||
\ Navigate To Projects
|
\ Navigate To Projects
|
||||||
Close Browser
|
Close Browser
|
||||||
|
@ -99,12 +99,12 @@
|
|||||||
{
|
{
|
||||||
"name":"busybox",
|
"name":"busybox",
|
||||||
"tag":"latest",
|
"tag":"latest",
|
||||||
"signed":"False"
|
"signed":"false"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"alpine",
|
"name":"alpine",
|
||||||
"tag":"latest",
|
"tag":"latest",
|
||||||
"signed":"True"
|
"signed":"true"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"member":[
|
"member":[
|
||||||
@ -144,10 +144,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configuration":{
|
"configuration":{
|
||||||
|
"public":"true",
|
||||||
"enable_content_trust":"true",
|
"enable_content_trust":"true",
|
||||||
"automatically_scan_images_on_push":"true",
|
"automatically_scan_images_on_push":"true",
|
||||||
"prevent_vulnerable_images_from_running":"true",
|
"prevent_vulnerable_images_from_running":"true",
|
||||||
"prevent_vlunerable_images_from_running_severity":"High"
|
"prevent_vlunerable_images_from_running_severity":"high"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -159,12 +160,12 @@
|
|||||||
{
|
{
|
||||||
"name":"busybox",
|
"name":"busybox",
|
||||||
"tag":"latest",
|
"tag":"latest",
|
||||||
"signed":"False"
|
"signed":"false"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"alpine",
|
"name":"alpine",
|
||||||
"tag":"latest",
|
"tag":"latest",
|
||||||
"signed":"True"
|
"signed":"true"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"member":[
|
"member":[
|
||||||
@ -204,10 +205,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configuration":{
|
"configuration":{
|
||||||
"enable_content_trust":"True",
|
"public":"false",
|
||||||
"automatically_scan_images_on_push":"True",
|
"enable_content_trust":"false",
|
||||||
"prevent_vulnerable_images_from_running":"True",
|
"automatically_scan_images_on_push":"false",
|
||||||
"prevent_vlunerable_images_from_running_severity":"High"
|
"prevent_vulnerable_images_from_running":"true",
|
||||||
|
"prevent_vlunerable_images_from_running_severity":"medium"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -68,13 +68,13 @@ class HarborAPI:
|
|||||||
body=dict(body=payload)
|
body=dict(body=payload)
|
||||||
request(url+"replication/policies", 'post', **body)
|
request(url+"replication/policies", 'post', **body)
|
||||||
|
|
||||||
def update_project_setting(self, project, contenttrust, preventrunning, preventseverity, scanonpush):
|
def update_project_setting(self, project, public, contenttrust, preventrunning, preventseverity, scanonpush):
|
||||||
r = request(url+"projects?name="+project+"", 'get')
|
r = request(url+"projects?name="+project+"", 'get')
|
||||||
projectid = str(r.json()[0]['project_id'])
|
projectid = str(r.json()[0]['project_id'])
|
||||||
payload = {
|
payload = {
|
||||||
"project_name": ""+project+"",
|
"project_name": ""+project+"",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"public": "True",
|
"public": public,
|
||||||
"enable_content_trust": contenttrust,
|
"enable_content_trust": contenttrust,
|
||||||
"prevent_vulnerable_images_from_running": preventrunning,
|
"prevent_vulnerable_images_from_running": preventrunning,
|
||||||
"prevent_vulnerable_images_from_running_severity": preventseverity,
|
"prevent_vulnerable_images_from_running_severity": preventseverity,
|
||||||
@ -188,6 +188,7 @@ def do_data_creation():
|
|||||||
replicationrule["rulename"])
|
replicationrule["rulename"])
|
||||||
for project in data["projects"]:
|
for project in data["projects"]:
|
||||||
harborAPI.update_project_setting(project["name"],
|
harborAPI.update_project_setting(project["name"],
|
||||||
|
project["configuration"]["public"],
|
||||||
project["configuration"]["enable_content_trust"],
|
project["configuration"]["enable_content_trust"],
|
||||||
project["configuration"]["prevent_vulnerable_images_from_running"],
|
project["configuration"]["prevent_vulnerable_images_from_running"],
|
||||||
project["configuration"]["prevent_vlunerable_images_from_running_severity"],
|
project["configuration"]["prevent_vlunerable_images_from_running_severity"],
|
||||||
|
@ -58,19 +58,29 @@ class HarborAPI:
|
|||||||
body=dict(body=payload)
|
body=dict(body=payload)
|
||||||
request(url+"policies/replication", 'post', **body)
|
request(url+"policies/replication", 'post', **body)
|
||||||
|
|
||||||
def update_project_setting(self, project, contenttrust, preventrunning, preventseverity, scanonpush):
|
def update_project_setting(self, project, public, contenttrust, preventrunning, preventseverity, scanonpush):
|
||||||
r = request(url+"projects?name="+project+"", 'get')
|
r = request(url+"projects?name="+project+"", 'get')
|
||||||
projectid = str(r.json()[0]['project_id'])
|
projectid = str(r.json()[0]['project_id'])
|
||||||
payload = {
|
if args.version == "1.6":
|
||||||
"project_name": ""+project+"",
|
payload = {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"public": "True",
|
"public": public,
|
||||||
"enable_content_trust": contenttrust,
|
"enable_content_trust": contenttrust,
|
||||||
"prevent_vulnerable_images_from_running": preventrunning,
|
"prevent_vulnerable_images_from_running": preventrunning,
|
||||||
"prevent_vulnerable_images_from_running_severity": preventseverity,
|
"prevent_vulnerable_images_from_running_severity": preventseverity,
|
||||||
"automatically_scan_images_on_push": scanonpush
|
"automatically_scan_images_on_push": scanonpush
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
payload = {
|
||||||
|
"metadata": {
|
||||||
|
"public": public,
|
||||||
|
"enable_content_trust": contenttrust,
|
||||||
|
"prevent_vul": preventrunning,
|
||||||
|
"severity": preventseverity,
|
||||||
|
"auto_scan": scanonpush
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
body=dict(body=payload)
|
body=dict(body=payload)
|
||||||
request(url+"projects/"+projectid+"", 'put', **body)
|
request(url+"projects/"+projectid+"", 'put', **body)
|
||||||
|
|
||||||
@ -178,6 +188,7 @@ def do_data_creation():
|
|||||||
replicationrule["rulename"])
|
replicationrule["rulename"])
|
||||||
for project in data["projects"]:
|
for project in data["projects"]:
|
||||||
harborAPI.update_project_setting(project["name"],
|
harborAPI.update_project_setting(project["name"],
|
||||||
|
project["configuration"]["public"],
|
||||||
project["configuration"]["enable_content_trust"],
|
project["configuration"]["enable_content_trust"],
|
||||||
project["configuration"]["prevent_vulnerable_images_from_running"],
|
project["configuration"]["prevent_vulnerable_images_from_running"],
|
||||||
project["configuration"]["prevent_vlunerable_images_from_running_severity"],
|
project["configuration"]["prevent_vlunerable_images_from_running_severity"],
|
||||||
|
Loading…
Reference in New Issue
Block a user