mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-26 10:38:00 +01:00
Merge pull request #3871 from ywk253100/171225_policy_rbac
Assign read-only privilege of replication policy to project admin and add stopping replication jobs API.
This commit is contained in:
commit
95339d5488
@ -1391,6 +1391,32 @@ paths:
|
|||||||
description: User need to login first.
|
description: User need to login first.
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
|
put:
|
||||||
|
summary: Update status of jobs. Only stop is supported for now.
|
||||||
|
description: >
|
||||||
|
The endpoint is used to stop the replication jobs of a policy.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
parameters:
|
||||||
|
- name: policyinfo
|
||||||
|
in: body
|
||||||
|
description: The policy ID and status.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/UpdateJobs'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Update the status successfully.
|
||||||
|
'400':
|
||||||
|
description: Bad request because of invalid parameters.
|
||||||
|
'401':
|
||||||
|
description: User need to login first.
|
||||||
|
'403':
|
||||||
|
description: User has no privilege for the operation.
|
||||||
|
'404':
|
||||||
|
description: Resource requested does not exist.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
/jobs/replication/{id}:
|
/jobs/replication/{id}:
|
||||||
delete:
|
delete:
|
||||||
summary: Delete specific ID job.
|
summary: Delete specific ID job.
|
||||||
@ -2414,9 +2440,22 @@ definitions:
|
|||||||
kind:
|
kind:
|
||||||
type: string
|
type: string
|
||||||
description: The replication policy trigger kind. The valid values are manual, immediate and schedule.
|
description: The replication policy trigger kind. The valid values are manual, immediate and schedule.
|
||||||
param:
|
schedule_param:
|
||||||
|
$ref: '#/definitions/ScheduleParam'
|
||||||
|
ScheduleParam:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
type: string
|
type: string
|
||||||
description: The replication policy trigger parameters.
|
description: The schedule type. The valid values are daily and weekly.
|
||||||
|
weekday:
|
||||||
|
type: integer
|
||||||
|
format: int8
|
||||||
|
description: Optional, only used when the type is weedly. The valid values are 1-7.
|
||||||
|
offtime:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The time offset with the UTC 00:00 in seconds.
|
||||||
RepFilter:
|
RepFilter:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -2927,5 +2966,12 @@ definitions:
|
|||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
description: The description of the repository.
|
description: The description of the repository.
|
||||||
|
UpdateJobs:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
policy_id:
|
||||||
|
type: integer
|
||||||
|
description: The ID of replication policy
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
description: The status of jobs. The only valid value is stop for now.
|
@ -17,12 +17,20 @@ package client
|
|||||||
import (
|
import (
|
||||||
"github.com/vmware/harbor/src/common/http"
|
"github.com/vmware/harbor/src/common/http"
|
||||||
"github.com/vmware/harbor/src/common/http/modifier/auth"
|
"github.com/vmware/harbor/src/common/http/modifier/auth"
|
||||||
"github.com/vmware/harbor/src/jobservice/api"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Replication holds information for submiting a replication job
|
||||||
|
type Replication struct {
|
||||||
|
PolicyID int64 `json:"policy_id"`
|
||||||
|
Repository string `json:"repository"`
|
||||||
|
Operation string `json:"operation"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
// Client defines the methods that a jobservice client should implement
|
// Client defines the methods that a jobservice client should implement
|
||||||
type Client interface {
|
type Client interface {
|
||||||
SubmitReplicationJob(*api.ReplicationReq) error
|
SubmitReplicationJob(*Replication) error
|
||||||
|
StopReplicationJobs(policyID int64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultClient provides a default implement for the interface Client
|
// DefaultClient provides a default implement for the interface Client
|
||||||
@ -50,7 +58,19 @@ func NewDefaultClient(endpoint string, cfg *Config) *DefaultClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SubmitReplicationJob submits a replication job to the jobservice
|
// SubmitReplicationJob submits a replication job to the jobservice
|
||||||
func (d *DefaultClient) SubmitReplicationJob(replication *api.ReplicationReq) error {
|
func (d *DefaultClient) SubmitReplicationJob(replication *Replication) error {
|
||||||
url := d.endpoint + "/api/jobs/replication"
|
url := d.endpoint + "/api/jobs/replication"
|
||||||
return d.client.Post(url, replication)
|
return d.client.Post(url, replication)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopReplicationJobs stop replication jobs of the policy specified by the policy ID
|
||||||
|
func (d *DefaultClient) StopReplicationJobs(policyID int64) error {
|
||||||
|
url := d.endpoint + "/api/jobs/replication/actions"
|
||||||
|
return d.client.Post(url, &struct {
|
||||||
|
PolicyID int64 `json:"policy_id"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
}{
|
||||||
|
PolicyID: policyID,
|
||||||
|
Action: "stop",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -22,18 +22,37 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/vmware/harbor/src/common/utils/test"
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
"github.com/vmware/harbor/src/jobservice/api"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var url string
|
var url string
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
requestMapping := []*test.RequestHandlerMapping{
|
requestMapping := []*test.RequestHandlerMapping{
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Pattern: "/api/jobs/replication/actions",
|
||||||
|
Handler: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
action := &struct {
|
||||||
|
PolicyID int64 `json:"policy_id"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
}{}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(action); err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if action.PolicyID != 1 {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
&test.RequestHandlerMapping{
|
&test.RequestHandlerMapping{
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Pattern: "/api/jobs/replication",
|
Pattern: "/api/jobs/replication",
|
||||||
Handler: func(w http.ResponseWriter, r *http.Request) {
|
Handler: func(w http.ResponseWriter, r *http.Request) {
|
||||||
replication := &api.ReplicationReq{}
|
replication := &Replication{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(replication); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(replication); err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
@ -50,6 +69,18 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
func TestSubmitReplicationJob(t *testing.T) {
|
func TestSubmitReplicationJob(t *testing.T) {
|
||||||
client := NewDefaultClient(url, &Config{})
|
client := NewDefaultClient(url, &Config{})
|
||||||
err := client.SubmitReplicationJob(&api.ReplicationReq{})
|
err := client.SubmitReplicationJob(&Replication{})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStopReplicationJobs(t *testing.T) {
|
||||||
|
client := NewDefaultClient(url, &Config{})
|
||||||
|
|
||||||
|
// 404
|
||||||
|
err := client.StopReplicationJobs(2)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
// 200
|
||||||
|
err = client.StopReplicationJobs(1)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
|
|
||||||
common_models "github.com/vmware/harbor/src/common/models"
|
common_models "github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/jobservice/api"
|
|
||||||
"github.com/vmware/harbor/src/jobservice/client"
|
"github.com/vmware/harbor/src/jobservice/client"
|
||||||
"github.com/vmware/harbor/src/replication"
|
"github.com/vmware/harbor/src/replication"
|
||||||
"github.com/vmware/harbor/src/replication/models"
|
"github.com/vmware/harbor/src/replication/models"
|
||||||
@ -63,7 +62,7 @@ type DefaultController struct {
|
|||||||
|
|
||||||
//Keep controller as singleton instance
|
//Keep controller as singleton instance
|
||||||
var (
|
var (
|
||||||
GlobalController Controller = NewDefaultController(ControllerConfig{}) //Use default data
|
GlobalController Controller
|
||||||
)
|
)
|
||||||
|
|
||||||
//ControllerConfig includes related configurations required by the controller
|
//ControllerConfig includes related configurations required by the controller
|
||||||
@ -82,16 +81,17 @@ func NewDefaultController(cfg ControllerConfig) *DefaultController {
|
|||||||
triggerManager: trigger.NewManager(cfg.CacheCapacity),
|
triggerManager: trigger.NewManager(cfg.CacheCapacity),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO read from configuration
|
ctl.replicator = replicator.NewDefaultReplicator(config.GlobalJobserviceClient)
|
||||||
endpoint := "http://jobservice:8080"
|
|
||||||
ctl.replicator = replicator.NewDefaultReplicator(endpoint,
|
|
||||||
&client.Config{
|
|
||||||
Secret: config.UISecret(),
|
|
||||||
})
|
|
||||||
|
|
||||||
return ctl
|
return ctl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init creates the GlobalController and inits it
|
||||||
|
func Init() error {
|
||||||
|
GlobalController = NewDefaultController(ControllerConfig{}) //Use default data
|
||||||
|
return GlobalController.Init()
|
||||||
|
}
|
||||||
|
|
||||||
//Init will initialize the controller and the sub components
|
//Init will initialize the controller and the sub components
|
||||||
func (ctl *DefaultController) Init() error {
|
func (ctl *DefaultController) Init() error {
|
||||||
if ctl.initialized {
|
if ctl.initialized {
|
||||||
@ -308,11 +308,11 @@ func replicate(replicator replicator.Replicator, policyID int64, candidates []mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
for repository, tags := range repositories {
|
for repository, tags := range repositories {
|
||||||
replication := &api.ReplicationReq{
|
replication := &client.Replication{
|
||||||
PolicyID: policyID,
|
PolicyID: policyID,
|
||||||
Repo: repository,
|
Repository: repository,
|
||||||
Operation: operation,
|
Operation: operation,
|
||||||
TagList: tags,
|
Tags: tags,
|
||||||
}
|
}
|
||||||
log.Debugf("submiting replication job to jobservice: %v", replication)
|
log.Debugf("submiting replication job to jobservice: %v", replication)
|
||||||
if err := replicator.Replicate(replication); err != nil {
|
if err := replicator.Replicate(replication); err != nil {
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
GlobalController = NewDefaultController(ControllerConfig{})
|
||||||
// set the policy manager used by GlobalController with a fake policy manager
|
// set the policy manager used by GlobalController with a fake policy manager
|
||||||
controller := GlobalController.(*DefaultController)
|
controller := GlobalController.(*DefaultController)
|
||||||
controller.policyManager = &test.FakePolicyManager{}
|
controller.policyManager = &test.FakePolicyManager{}
|
||||||
|
@ -15,13 +15,12 @@
|
|||||||
package replicator
|
package replicator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/vmware/harbor/src/jobservice/api"
|
|
||||||
"github.com/vmware/harbor/src/jobservice/client"
|
"github.com/vmware/harbor/src/jobservice/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Replicator submits the replication work to the jobservice
|
// Replicator submits the replication work to the jobservice
|
||||||
type Replicator interface {
|
type Replicator interface {
|
||||||
Replicate(*api.ReplicationReq) error
|
Replicate(*client.Replication) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultReplicator provides a default implement for Replicator
|
// DefaultReplicator provides a default implement for Replicator
|
||||||
@ -30,13 +29,13 @@ type DefaultReplicator struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultReplicator returns an instance of DefaultReplicator
|
// NewDefaultReplicator returns an instance of DefaultReplicator
|
||||||
func NewDefaultReplicator(endpoint string, cfg *client.Config) *DefaultReplicator {
|
func NewDefaultReplicator(client client.Client) *DefaultReplicator {
|
||||||
return &DefaultReplicator{
|
return &DefaultReplicator{
|
||||||
client: client.NewDefaultClient(endpoint, cfg),
|
client: client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replicate ...
|
// Replicate ...
|
||||||
func (d *DefaultReplicator) Replicate(replication *api.ReplicationReq) error {
|
func (d *DefaultReplicator) Replicate(replication *client.Replication) error {
|
||||||
return d.client.SubmitReplicationJob(replication)
|
return d.client.SubmitReplicationJob(replication)
|
||||||
}
|
}
|
||||||
|
@ -18,18 +18,20 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/vmware/harbor/src/jobservice/api"
|
|
||||||
"github.com/vmware/harbor/src/jobservice/client"
|
"github.com/vmware/harbor/src/jobservice/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeJobserviceClient struct{}
|
type fakeJobserviceClient struct{}
|
||||||
|
|
||||||
func (f *fakeJobserviceClient) SubmitReplicationJob(replication *api.ReplicationReq) error {
|
func (f *fakeJobserviceClient) SubmitReplicationJob(replication *client.Replication) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeJobserviceClient) StopReplicationJobs(policyID int64) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplicate(t *testing.T) {
|
func TestReplicate(t *testing.T) {
|
||||||
replicator := NewDefaultReplicator("http://jobservice", &client.Config{})
|
replicator := NewDefaultReplicator(&fakeJobserviceClient{})
|
||||||
replicator.client = &fakeJobserviceClient{}
|
assert.Nil(t, replicator.Replicate(&client.Replication{}))
|
||||||
assert.Nil(t, replicator.Replicate(&api.ReplicationReq{}))
|
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ func init() {
|
|||||||
|
|
||||||
_ = updateInitPassword(1, "Harbor12345")
|
_ = updateInitPassword(1, "Harbor12345")
|
||||||
|
|
||||||
if err := core.GlobalController.Init(); err != nil {
|
if err := core.Init(); err != nil {
|
||||||
log.Fatalf("failed to initialize GlobalController: %v", err)
|
log.Fatalf("failed to initialize GlobalController: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
src/ui/api/models/replication_job.go
Normal file
35
src/ui/api/models/replication_job.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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 (
|
||||||
|
"github.com/astaxie/beego/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StopJobsReq holds information needed to stop the jobs for a replication rule
|
||||||
|
type StopJobsReq struct {
|
||||||
|
PolicyID int64 `json:"policy_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid ...
|
||||||
|
func (s *StopJobsReq) Valid(v *validation.Validation) {
|
||||||
|
if s.PolicyID <= 0 {
|
||||||
|
v.SetError("policy_id", "invalid value")
|
||||||
|
}
|
||||||
|
if s.Status != "stop" {
|
||||||
|
v.SetError("status", "invalid status, valid values: [stop]")
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,9 @@ import (
|
|||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/replication/core"
|
||||||
|
api_models "github.com/vmware/harbor/src/ui/api/models"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
"github.com/vmware/harbor/src/ui/utils"
|
"github.com/vmware/harbor/src/ui/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,7 +43,7 @@ func (ra *RepJobAPI) Prepare() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ra.SecurityCtx.IsSysAdmin() {
|
if !(ra.Ctx.Request.Method == http.MethodGet || ra.SecurityCtx.IsSysAdmin()) {
|
||||||
ra.HandleForbidden(ra.SecurityCtx.GetUsername())
|
ra.HandleForbidden(ra.SecurityCtx.GetUsername())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -63,16 +66,21 @@ func (ra *RepJobAPI) List() {
|
|||||||
ra.CustomAbort(http.StatusBadRequest, "invalid policy_id")
|
ra.CustomAbort(http.StatusBadRequest, "invalid policy_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
policy, err := dao.GetRepPolicy(policyID)
|
policy, err := core.GlobalController.GetPolicy(policyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get policy %d: %v", policyID, err)
|
log.Errorf("failed to get policy %d: %v", policyID, err)
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
if policy == nil {
|
if policy.ID == 0 {
|
||||||
ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("policy %d not found", policyID))
|
ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("policy %d not found", policyID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ra.SecurityCtx.HasAllPerm(policy.ProjectIDs[0]) {
|
||||||
|
ra.HandleForbidden(ra.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
repository := ra.GetString("repository")
|
repository := ra.GetString("repository")
|
||||||
status := ra.GetString("status")
|
status := ra.GetString("status")
|
||||||
|
|
||||||
@ -145,12 +153,56 @@ func (ra *RepJobAPI) GetLog() {
|
|||||||
if ra.jobID == 0 {
|
if ra.jobID == 0 {
|
||||||
ra.CustomAbort(http.StatusBadRequest, "id is nil")
|
ra.CustomAbort(http.StatusBadRequest, "id is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
job, err := dao.GetRepJob(ra.jobID)
|
||||||
|
if err != nil {
|
||||||
|
ra.HandleInternalServerError(fmt.Sprintf("failed to get replication job %d: %v", ra.jobID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if job == nil {
|
||||||
|
ra.HandleNotFound(fmt.Sprintf("replication job %d not found", ra.jobID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, err := core.GlobalController.GetPolicy(job.PolicyID)
|
||||||
|
if err != nil {
|
||||||
|
ra.HandleInternalServerError(fmt.Sprintf("failed to get policy %d: %v", job.PolicyID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ra.SecurityCtx.HasAllPerm(policy.ProjectIDs[0]) {
|
||||||
|
ra.HandleForbidden(ra.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
url := buildJobLogURL(strconv.FormatInt(ra.jobID, 10), ReplicationJobType)
|
url := buildJobLogURL(strconv.FormatInt(ra.jobID, 10), ReplicationJobType)
|
||||||
err := utils.RequestAsUI(http.MethodGet, url, nil, utils.NewJobLogRespHandler(&ra.BaseAPI))
|
err = utils.RequestAsUI(http.MethodGet, url, nil, utils.NewJobLogRespHandler(&ra.BaseAPI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ra.RenderError(http.StatusInternalServerError, err.Error())
|
ra.RenderError(http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopJobs stop replication jobs for the policy
|
||||||
|
func (ra *RepJobAPI) StopJobs() {
|
||||||
|
req := &api_models.StopJobsReq{}
|
||||||
|
ra.DecodeJSONReqAndValidate(req)
|
||||||
|
|
||||||
|
policy, err := core.GlobalController.GetPolicy(req.PolicyID)
|
||||||
|
if err != nil {
|
||||||
|
ra.HandleInternalServerError(fmt.Sprintf("failed to get policy %d: %v", req.PolicyID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if policy.ID == 0 {
|
||||||
|
ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("policy %d not found", req.PolicyID))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = config.GlobalJobserviceClient.StopReplicationJobs(req.PolicyID); err != nil {
|
||||||
|
ra.HandleInternalServerError(fmt.Sprintf("failed to stop replication jobs of policy %d: %v", req.PolicyID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//TODO:add Post handler to call job service API to submit jobs by policy
|
//TODO:add Post handler to call job service API to submit jobs by policy
|
||||||
|
@ -42,7 +42,7 @@ func (pa *RepPolicyAPI) Prepare() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !pa.SecurityCtx.IsSysAdmin() {
|
if !(pa.Ctx.Request.Method == http.MethodGet || pa.SecurityCtx.IsSysAdmin()) {
|
||||||
pa.HandleForbidden(pa.SecurityCtx.GetUsername())
|
pa.HandleForbidden(pa.SecurityCtx.GetUsername())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -61,6 +61,11 @@ func (pa *RepPolicyAPI) Get() {
|
|||||||
pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !pa.SecurityCtx.HasAllPerm(policy.ProjectIDs[0]) {
|
||||||
|
pa.HandleForbidden(pa.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ply, err := convertFromRepPolicy(pa.ProjectMgr, policy)
|
ply, err := convertFromRepPolicy(pa.ProjectMgr, policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pa.ParseAndHandleError(fmt.Sprintf("failed to convert from replication policy"), err)
|
pa.ParseAndHandleError(fmt.Sprintf("failed to convert from replication policy"), err)
|
||||||
@ -94,6 +99,9 @@ func (pa *RepPolicyAPI) List() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, policy := range policies {
|
for _, policy := range policies {
|
||||||
|
if !pa.SecurityCtx.HasAllPerm(policy.ProjectIDs[0]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
ply, err := convertFromRepPolicy(pa.ProjectMgr, policy)
|
ply, err := convertFromRepPolicy(pa.ProjectMgr, policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pa.ParseAndHandleError(fmt.Sprintf("failed to convert from replication policy"), err)
|
pa.ParseAndHandleError(fmt.Sprintf("failed to convert from replication policy"), err)
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/replication"
|
"github.com/vmware/harbor/src/replication"
|
||||||
rep_models "github.com/vmware/harbor/src/replication/models"
|
rep_models "github.com/vmware/harbor/src/replication/models"
|
||||||
@ -265,15 +266,28 @@ func TestRepPolicyAPIPost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRepPolicyAPIGet(t *testing.T) {
|
func TestRepPolicyAPIGet(t *testing.T) {
|
||||||
|
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
// 404
|
// 404
|
||||||
runCodeCheckingCases(t, &codeCheckingCase{
|
&codeCheckingCase{
|
||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
url: fmt.Sprintf("%s/%d", repPolicyAPIBasePath, 10000),
|
url: fmt.Sprintf("%s/%d", repPolicyAPIBasePath, 10000),
|
||||||
credential: sysAdmin,
|
credential: sysAdmin,
|
||||||
},
|
},
|
||||||
code: http.StatusNotFound,
|
code: http.StatusNotFound,
|
||||||
})
|
},
|
||||||
|
// 401
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: fmt.Sprintf("%s/%d", repPolicyAPIBasePath, policyID),
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
|
||||||
// 200
|
// 200
|
||||||
policy := &api_models.ReplicationPolicy{}
|
policy := &api_models.ReplicationPolicy{}
|
||||||
@ -290,6 +304,39 @@ func TestRepPolicyAPIGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRepPolicyAPIList(t *testing.T) {
|
func TestRepPolicyAPIList(t *testing.T) {
|
||||||
|
projectAdmin := models.User{
|
||||||
|
Username: "project_admin",
|
||||||
|
Password: "ProjectAdmin",
|
||||||
|
Email: "project_admin@test.com",
|
||||||
|
}
|
||||||
|
projectDev := models.User{
|
||||||
|
Username: "project_dev",
|
||||||
|
Password: "ProjectDev",
|
||||||
|
Email: "project_dev@test.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
proAdminID, err := dao.Register(projectAdmin)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer dao.DeleteUser(int(proAdminID))
|
||||||
|
|
||||||
|
if err = dao.AddProjectMember(1, int(proAdminID), models.PROJECTADMIN); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer dao.DeleteProjectMember(1, int(proAdminID))
|
||||||
|
|
||||||
|
proDevID, err := dao.Register(projectDev)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer dao.DeleteUser(int(proDevID))
|
||||||
|
|
||||||
|
if err = dao.AddProjectMember(1, int(proDevID), models.DEVELOPER); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer dao.DeleteProjectMember(1, int(proDevID))
|
||||||
|
|
||||||
// 400: invalid project ID
|
// 400: invalid project ID
|
||||||
runCodeCheckingCases(t, &codeCheckingCase{
|
runCodeCheckingCases(t, &codeCheckingCase{
|
||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
@ -305,7 +352,7 @@ func TestRepPolicyAPIList(t *testing.T) {
|
|||||||
code: http.StatusBadRequest,
|
code: http.StatusBadRequest,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 200
|
// 200 system admin
|
||||||
policies := []*api_models.ReplicationPolicy{}
|
policies := []*api_models.ReplicationPolicy{}
|
||||||
resp, err := handleAndParse(
|
resp, err := handleAndParse(
|
||||||
&testingRequest{
|
&testingRequest{
|
||||||
@ -326,6 +373,52 @@ func TestRepPolicyAPIList(t *testing.T) {
|
|||||||
assert.Equal(t, policyID, policies[0].ID)
|
assert.Equal(t, policyID, policies[0].ID)
|
||||||
assert.Equal(t, policyName, policies[0].Name)
|
assert.Equal(t, policyName, policies[0].Name)
|
||||||
|
|
||||||
|
// 200 project admin
|
||||||
|
policies = []*api_models.ReplicationPolicy{}
|
||||||
|
resp, err = handleAndParse(
|
||||||
|
&testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: repPolicyAPIBasePath,
|
||||||
|
queryStruct: struct {
|
||||||
|
ProjectID int64 `url:"project_id"`
|
||||||
|
Name string `url:"name"`
|
||||||
|
}{
|
||||||
|
ProjectID: projectID,
|
||||||
|
Name: policyName,
|
||||||
|
},
|
||||||
|
credential: &usrInfo{
|
||||||
|
Name: projectAdmin.Username,
|
||||||
|
Passwd: projectAdmin.Password,
|
||||||
|
},
|
||||||
|
}, &policies)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Code)
|
||||||
|
require.Equal(t, 1, len(policies))
|
||||||
|
assert.Equal(t, policyID, policies[0].ID)
|
||||||
|
assert.Equal(t, policyName, policies[0].Name)
|
||||||
|
|
||||||
|
// 200 project developer
|
||||||
|
policies = []*api_models.ReplicationPolicy{}
|
||||||
|
resp, err = handleAndParse(
|
||||||
|
&testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: repPolicyAPIBasePath,
|
||||||
|
queryStruct: struct {
|
||||||
|
ProjectID int64 `url:"project_id"`
|
||||||
|
Name string `url:"name"`
|
||||||
|
}{
|
||||||
|
ProjectID: projectID,
|
||||||
|
Name: policyName,
|
||||||
|
},
|
||||||
|
credential: &usrInfo{
|
||||||
|
Name: projectDev.Username,
|
||||||
|
Passwd: projectDev.Password,
|
||||||
|
},
|
||||||
|
}, &policies)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Code)
|
||||||
|
require.Equal(t, 0, len(policies))
|
||||||
|
|
||||||
// 200
|
// 200
|
||||||
policies = []*api_models.ReplicationPolicy{}
|
policies = []*api_models.ReplicationPolicy{}
|
||||||
resp, err = handleAndParse(
|
resp, err = handleAndParse(
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/secret"
|
"github.com/vmware/harbor/src/common/secret"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
jobservice_client "github.com/vmware/harbor/src/jobservice/client"
|
||||||
"github.com/vmware/harbor/src/ui/promgr"
|
"github.com/vmware/harbor/src/ui/promgr"
|
||||||
"github.com/vmware/harbor/src/ui/promgr/pmsdriver"
|
"github.com/vmware/harbor/src/ui/promgr/pmsdriver"
|
||||||
"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
|
"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
|
||||||
@ -54,6 +55,8 @@ var (
|
|||||||
AdmiralClient *http.Client
|
AdmiralClient *http.Client
|
||||||
// TokenReader is used in integration mode to read token
|
// TokenReader is used in integration mode to read token
|
||||||
TokenReader admiral.TokenReader
|
TokenReader admiral.TokenReader
|
||||||
|
// GlobalJobserviceClient is a global client for jobservice
|
||||||
|
GlobalJobserviceClient jobservice_client.Client
|
||||||
)
|
)
|
||||||
|
|
||||||
// Init configurations
|
// Init configurations
|
||||||
@ -92,6 +95,11 @@ func InitByURL(adminServerURL string) error {
|
|||||||
// init project manager based on deploy mode
|
// init project manager based on deploy mode
|
||||||
initProjectManager()
|
initProjectManager()
|
||||||
|
|
||||||
|
GlobalJobserviceClient = jobservice_client.NewDefaultClient(InternalJobServiceURL(),
|
||||||
|
&jobservice_client.Config{
|
||||||
|
Secret: UISecret(),
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,6 +268,10 @@ func InternalJobServiceURL() string {
|
|||||||
|
|
||||||
return "http://jobservice"
|
return "http://jobservice"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg[common.JobServiceURL] == nil {
|
||||||
|
return "http://jobservice"
|
||||||
|
}
|
||||||
return strings.TrimSuffix(cfg[common.JobServiceURL].(string), "/")
|
return strings.TrimSuffix(cfg[common.JobServiceURL].(string), "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ func main() {
|
|||||||
notifier.Publish(notifier.ScanAllPolicyTopic, notifier.ScanPolicyNotification{Type: scanAllPolicy.Type, DailyTime: (int64)(dailyTime)})
|
notifier.Publish(notifier.ScanAllPolicyTopic, notifier.ScanPolicyNotification{Type: scanAllPolicy.Type, DailyTime: (int64)(dailyTime)})
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := core.GlobalController.Init(); err != nil {
|
if err := core.Init(); err != nil {
|
||||||
log.Errorf("failed to initialize the replication controller: %v", err)
|
log.Errorf("failed to initialize the replication controller: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ func initRouters() {
|
|||||||
beego.Router("/api/repositories/*/tags/:tag/manifest", &api.RepositoryAPI{}, "get:GetManifests")
|
beego.Router("/api/repositories/*/tags/:tag/manifest", &api.RepositoryAPI{}, "get:GetManifests")
|
||||||
beego.Router("/api/repositories/*/signatures", &api.RepositoryAPI{}, "get:GetSignatures")
|
beego.Router("/api/repositories/*/signatures", &api.RepositoryAPI{}, "get:GetSignatures")
|
||||||
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
||||||
beego.Router("/api/jobs/replication/", &api.RepJobAPI{}, "get:List")
|
beego.Router("/api/jobs/replication/", &api.RepJobAPI{}, "get:List;put:StopJobs")
|
||||||
beego.Router("/api/jobs/replication/:id([0-9]+)", &api.RepJobAPI{})
|
beego.Router("/api/jobs/replication/:id([0-9]+)", &api.RepJobAPI{})
|
||||||
beego.Router("/api/jobs/replication/:id([0-9]+)/log", &api.RepJobAPI{}, "get:GetLog")
|
beego.Router("/api/jobs/replication/:id([0-9]+)/log", &api.RepJobAPI{}, "get:GetLog")
|
||||||
beego.Router("/api/jobs/scan/:id([0-9]+)/log", &api.ScanJobAPI{}, "get:GetLog")
|
beego.Router("/api/jobs/scan/:id([0-9]+)/log", &api.ScanJobAPI{}, "get:GetLog")
|
||||||
|
Loading…
Reference in New Issue
Block a user