diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c6de6a1c1..9120fc964 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1484,7 +1484,7 @@ paths: description: Create new policy. required: true schema: - $ref: '#/definitions/RepPolicyPost' + $ref: '#/definitions/RepPolicy' tags: - Products responses: @@ -1539,10 +1539,10 @@ paths: description: policy ID - name: policyupdate in: body - description: 'Update policy name, description, target and enablement.' + description: 'Updated properties of the replication policy.' required: true schema: - $ref: '#/definitions/RepPolicyUpdate' + $ref: '#/definitions/RepPolicy' tags: - Products responses: @@ -1560,35 +1560,27 @@ paths: project and target. '500': description: Unexpected internal errors. - /policies/replication/{id}/enablement: - put: - summary: Put modifies enablement of the policy. + /replications: + post: + summary: Trigger the replication according to the specified policy. description: | - This endpoint let user update policy enablement flag. + This endpoint is used to trigger a replication. parameters: - - name: id - in: path - type: integer - format: int64 - required: true - description: policy ID - - name: enabledflag + - name: policy ID in: body - description: The policy enablement flag. + description: The ID of replication policy. required: true schema: - $ref: '#/definitions/RepPolicyEnablementReq' + $ref: '#/definitions/Replication' tags: - Products responses: '200': - description: Update job policy enablement successfully. - '400': - description: Invalid enabled value. + description: Trigger the replication successfully. '401': description: User need to log in first. '404': - description: The specific repository ID's policy does not exist. + description: The policy does not exist. '500': description: Unexpected internal errors. /targets: @@ -2376,27 +2368,22 @@ definitions: type: integer format: int64 description: The policy ID. - project_id: - type: integer - format: int64 - description: The project ID. - project_name: - type: string - description: The project name. - target_id: - type: integer - format: int64 - description: The target ID. name: type: string description: The policy name. - enabled: - type: integer - format: int - description: The policy's enabled status. description: type: string description: The description of the policy. + projects: + type: object + description: The project list that the policy applys to. + items: + $ref: '#/definitions/Project' + targets: + type: object + description: The target list. + items: + $ref: '#/definitions/RepTarget' trigger: type: object description: The trigger for schedule job. @@ -2408,14 +2395,11 @@ definitions: items: $ref: '#/definitions/RepFilter' replicate_existing_image_now: - type: string + type: boolean description: Whether to replicate the existing images now. replicate_deletion: - type: string + type: boolean description: Whether to replicate the deletion operation. - start_time: - type: string - description: The start time of the policy. creation_time: type: string description: The create time of the policy. @@ -2425,100 +2409,27 @@ definitions: error_job_count: format: int description: The error job count number for the policy. - deleted: - type: integer - RepPolicyPost: - type: object - properties: - project_id: - type: integer - format: int64 - description: The project ID. - target_id: - type: integer - format: int64 - description: The target ID. - name: - type: string - description: The policy name. - trigger: - type: object - description: The trigger for schedule job. - items: - $ref: '#/definitions/RepTrigger' - filters: - type: array - description: The replication policy filter array. - items: - $ref: '#/definitions/RepFilter' - replicate_existing_image_now: - type: string - description: Whether to replicate the existing images now. - replicate_deletion: - type: string - description: Whether replication deletion operation. - enabled: - type: integer - format: int - description: '1-enable, 0-disable' - RepPolicyUpdate: - type: object - properties: - target_id: - type: integer - format: int64 - description: The target ID. - name: - type: string - description: The policy name. - enabled: - type: integer - format: int - description: The policy's enabled status. - description: - type: string - description: The description of the policy. - trigger: - type: object - description: The trigger for schedule job. - items: - $ref: '#/definitions/RepTrigger' - filters: - type: array - description: The replication policy filter array. - items: - $ref: '#/definitions/RepFilter' - replicate_existing_image_now: - type: string - description: Whether to replicate the existing images now. - replicate_deletion: - type: string - description: Whether replication deletion operation. RepTrigger: type: object properties: - type: + kind: type: string - description: The replication policy trigger type. - params: - type: object - description: The map is the replication policy trigger parameters. + description: The replication policy trigger kind. + param: + type: string + description: The replication policy trigger parameters. RepFilter: type: object properties: - type: + kind: type: string - description: The replication policy filter type. + description: The replication policy filter kind. value: type: string description: The replication policy filter value. - RepPolicyEnablementReq: - type: object - properties: - enabled: - type: integer - format: int - description: The policy enablement flag. + metadata: + type: object + description: This map object is the replication policy filter metadata. RepTarget: type: object properties: @@ -3001,6 +2912,12 @@ definitions: type: integer description: The offest in seconds of UTC 0 o'clock, only valid when the policy type is "daily" description: The parameters of the policy, the values are dependant on the type of the policy. - + Replication: + type: object + properties: + policy_id: + type: integer + description: The ID of replication policy + diff --git a/src/ui/api/harborapi_test.go b/src/ui/api/harborapi_test.go index 10a54b096..a62e11816 100644 --- a/src/ui/api/harborapi_test.go +++ b/src/ui/api/harborapi_test.go @@ -129,6 +129,7 @@ func init() { beego.Router("/api/configurations", &ConfigAPI{}) beego.Router("/api/configurations/reset", &ConfigAPI{}, "post:Reset") beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping") + beego.Router("/api/replications", &ReplicationAPI{}) _ = updateInitPassword(1, "Harbor12345") diff --git a/src/ui/api/models/replication.go b/src/ui/api/models/replication.go new file mode 100644 index 000000000..c19584ea2 --- /dev/null +++ b/src/ui/api/models/replication.go @@ -0,0 +1,31 @@ +// 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" +) + +// Replication defines the properties of model used in replication API +type Replication struct { + PolicyID int64 `json:"policy_id"` +} + +// Valid ... +func (r *Replication) Valid(v *validation.Validation) { + if r.PolicyID <= 0 { + v.SetError("policy_id", "invalid value") + } +} diff --git a/src/ui/api/replication.go b/src/ui/api/replication.go new file mode 100644 index 000000000..49e1b80dc --- /dev/null +++ b/src/ui/api/replication.go @@ -0,0 +1,63 @@ +// Copyright (c) 2017 VMware, Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "fmt" + + "github.com/vmware/harbor/src/replication/core" + "github.com/vmware/harbor/src/ui/api/models" +) + +// ReplicationAPI handles API calls for replication +type ReplicationAPI struct { + BaseController +} + +// Prepare does authentication and authorization works +func (r *ReplicationAPI) Prepare() { + r.BaseController.Prepare() + if !r.SecurityCtx.IsAuthenticated() { + r.HandleUnauthorized() + return + } + + if !r.SecurityCtx.IsSysAdmin() { + r.HandleForbidden(r.SecurityCtx.GetUsername()) + return + } +} + +// Post trigger a replication according to the specified policy +func (r *ReplicationAPI) Post() { + replication := &models.Replication{} + r.DecodeJSONReqAndValidate(replication) + + policy, err := core.DefaultController.GetPolicy(replication.PolicyID) + if err != nil { + r.HandleInternalServerError(fmt.Sprintf("failed to get replication policy %d: %v", replication.PolicyID, err)) + return + } + + if policy.ID == 0 { + r.HandleNotFound(fmt.Sprintf("replication policy %d not found", replication.PolicyID)) + return + } + + if err = core.DefaultController.Replicate(replication.PolicyID); err != nil { + r.HandleInternalServerError(fmt.Sprintf("failed to trigger the replication policy %d: %v", replication.PolicyID, err)) + return + } +} diff --git a/src/ui/api/replication_test.go b/src/ui/api/replication_test.go new file mode 100644 index 000000000..ce9330f44 --- /dev/null +++ b/src/ui/api/replication_test.go @@ -0,0 +1,92 @@ +// Copyright (c) 2017 VMware, Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package api + +import ( + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "github.com/vmware/harbor/src/common/dao" + "github.com/vmware/harbor/src/common/models" + "github.com/vmware/harbor/src/replication" + api_models "github.com/vmware/harbor/src/ui/api/models" +) + +const ( + replicationAPIBaseURL = "/api/replications" +) + +func TestReplicationAPIPost(t *testing.T) { + targetID, err := dao.AddRepTarget( + models.RepTarget{ + Name: "test_replication_target", + URL: "127.0.0.1", + Username: "username", + Password: "password", + }) + require.Nil(t, err) + defer dao.DeleteRepTarget(targetID) + + policyID, err := dao.AddRepPolicy( + models.RepPolicy{ + Name: "test_replication_policy", + ProjectID: 1, + TargetID: targetID, + Trigger: fmt.Sprintf("{\"kind\":\"%s\"}", replication.TriggerKindManual), + }) + require.Nil(t, err) + defer dao.DeleteRepPolicy(policyID) + + cases := []*codeCheckingCase{ + // 401 + &codeCheckingCase{ + request: &testingRequest{ + method: http.MethodPost, + url: replicationAPIBaseURL, + bodyJSON: &api_models.Replication{ + PolicyID: policyID, + }, + }, + code: http.StatusUnauthorized, + }, + // 404 + &codeCheckingCase{ + request: &testingRequest{ + method: http.MethodPost, + url: replicationAPIBaseURL, + bodyJSON: &api_models.Replication{ + PolicyID: 10000, + }, + credential: admin, + }, + code: http.StatusNotFound, + }, + // 200 + &codeCheckingCase{ + request: &testingRequest{ + method: http.MethodPost, + url: replicationAPIBaseURL, + bodyJSON: &api_models.Replication{ + PolicyID: policyID, + }, + credential: admin, + }, + code: http.StatusOK, + }, + } + + runCodeCheckingCases(t, cases...) +} diff --git a/src/ui/router.go b/src/ui/router.go index aaa46f5ae..fc243afcf 100644 --- a/src/ui/router.go +++ b/src/ui/router.go @@ -118,6 +118,7 @@ func initRouters() { beego.Router("/api/configurations", &api.ConfigAPI{}) beego.Router("/api/configurations/reset", &api.ConfigAPI{}, "post:Reset") beego.Router("/api/statistics", &api.StatisticAPI{}) + beego.Router("/api/replications", &api.ReplicationAPI{}) beego.Router("/api/systeminfo", &api.SystemInfoAPI{}, "get:GetGeneralInfo") beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")