diff --git a/docs/swagger.yaml b/docs/swagger.yaml index a1cb91e7e..2ca87344a 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1968,6 +1968,33 @@ paths: description: The resource does not exist. '500': description: Unexpected internal errors. + '/labels/{id}/resources': + get: + summary: Get the resources that the label is referenced by. + description: | + This endpoint let user get the resources that the label is referenced by. Only the replication policies are returned for now. + parameters: + - name: id + in: path + type: integer + format: int64 + required: true + description: Label ID + tags: + - Products + responses: + '200': + description: Get successfully. + schema: + $ref: '#/definitions/Resource' + '401': + description: User need to log in first. + '403': + description: Forbidden. + '404': + description: The resource does not exist. + '500': + description: Unexpected internal errors. /replications: post: summary: Trigger the replication according to the specified policy. @@ -3624,5 +3651,13 @@ definitions: ldap_group_dn: type: string description: The DN of the LDAP group if group type is 1 (LDAP group). + Resource: + type: object + properties: + replication_policies: + type: array + description: The replication policy list. + items: + $ref: '#/definitions/RepPolicy' diff --git a/src/ui/api/harborapi_test.go b/src/ui/api/harborapi_test.go index 5c9469d48..7ac4e4f1b 100644 --- a/src/ui/api/harborapi_test.go +++ b/src/ui/api/harborapi_test.go @@ -141,6 +141,7 @@ func init() { beego.Router("/api/replications", &ReplicationAPI{}) beego.Router("/api/labels", &LabelAPI{}, "post:Post;get:List") beego.Router("/api/labels/:id([0-9]+", &LabelAPI{}, "get:Get;put:Put;delete:Delete") + beego.Router("/api/labels/:id([0-9]+)/resources", &LabelAPI{}, "get:ListResources") beego.Router("/api/ping", &SystemInfoAPI{}, "get:Ping") _ = updateInitPassword(1, "Harbor12345") diff --git a/src/ui/api/label.go b/src/ui/api/label.go index 4f5221ca3..a314650de 100644 --- a/src/ui/api/label.go +++ b/src/ui/api/label.go @@ -22,6 +22,9 @@ import ( "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" + "github.com/vmware/harbor/src/replication" + "github.com/vmware/harbor/src/replication/core" + rep_models "github.com/vmware/harbor/src/replication/models" ) // LabelAPI handles requests for label management @@ -266,3 +269,57 @@ func (l *LabelAPI) Delete() { return } } + +// ListResources lists the resources that the label is referenced by +func (l *LabelAPI) ListResources() { + if !l.SecurityCtx.IsAuthenticated() { + l.HandleUnauthorized() + return + } + + id, err := l.GetInt64FromPath(":id") + if err != nil || id <= 0 { + l.HandleBadRequest("invalid label ID") + return + } + + label, err := dao.GetLabel(id) + if err != nil { + l.HandleInternalServerError(fmt.Sprintf("failed to get label %d: %v", id, err)) + return + } + + if label == nil || label.Deleted { + l.HandleNotFound(fmt.Sprintf("label %d not found", id)) + return + } + + if label.Scope == common.LabelScopeGlobal && !l.SecurityCtx.IsSysAdmin() || + label.Scope == common.LabelScopeProject && !l.SecurityCtx.HasAllPerm(label.ProjectID) { + l.HandleForbidden(l.SecurityCtx.GetUsername()) + return + } + + result, err := core.GlobalController.GetPolicies(rep_models.QueryParameter{}) + if err != nil { + l.HandleInternalServerError(fmt.Sprintf("failed to get policies: %v", err)) + return + } + policies := []*rep_models.ReplicationPolicy{} + if result != nil { + for _, policy := range result.Policies { + for _, filter := range policy.Filters { + if filter.Kind != replication.FilterItemKindLabel { + continue + } + if filter.Value.(int64) == label.ID { + policies = append(policies, policy) + } + } + } + } + resources := map[string]interface{}{} + resources["replication_policies"] = policies + l.Data["json"] = resources + l.ServeJSON() +} diff --git a/src/ui/api/label_test.go b/src/ui/api/label_test.go index 410f4534d..84a31a72a 100644 --- a/src/ui/api/label_test.go +++ b/src/ui/api/label_test.go @@ -23,7 +23,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" + "github.com/vmware/harbor/src/replication" + rep_models "github.com/vmware/harbor/src/replication/models" ) var ( @@ -433,3 +436,105 @@ func TestLabelAPIDelete(t *testing.T) { runCodeCheckingCases(t, cases...) } + +func TestListResources(t *testing.T) { + // global level label + globalLabelID, err := dao.AddLabel(&models.Label{ + Name: "globel_level_label", + Scope: common.LabelScopeGlobal, + }) + require.Nil(t, err) + defer dao.DeleteLabel(globalLabelID) + + // project level label + projectLabelID, err := dao.AddLabel(&models.Label{ + Name: "project_level_label", + Scope: common.LabelScopeProject, + ProjectID: 1, + }) + require.Nil(t, err) + defer dao.DeleteLabel(projectLabelID) + + targetID, err := dao.AddRepTarget(models.RepTarget{ + Name: "target_for_testing_label_resource", + URL: "https://192.168.0.1", + }) + require.Nil(t, err) + defer dao.DeleteRepTarget(targetID) + + // create a policy references both global and project labels + policyID, err := dao.AddRepPolicy(models.RepPolicy{ + Name: "policy_for_testing_label_resource", + ProjectID: 1, + TargetID: targetID, + Trigger: fmt.Sprintf(`{"kind":"%s"}`, replication.TriggerKindManual), + Filters: fmt.Sprintf(`[{"kind":"%s","value":%d}, {"kind":"%s","value":%d}]`, + replication.FilterItemKindLabel, globalLabelID, + replication.FilterItemKindLabel, projectLabelID), + }) + require.Nil(t, err) + defer dao.DeleteRepPolicy(policyID) + + cases := []*codeCheckingCase{ + // 401 + &codeCheckingCase{ + request: &testingRequest{ + method: http.MethodGet, + url: fmt.Sprintf("%s/%d/resources", labelAPIBasePath, globalLabelID), + }, + code: http.StatusUnauthorized, + }, + // 404 + &codeCheckingCase{ + request: &testingRequest{ + method: http.MethodGet, + url: fmt.Sprintf("%s/%d/resources", labelAPIBasePath, 10000), + credential: sysAdmin, + }, + code: http.StatusNotFound, + }, + // 403: global level label + &codeCheckingCase{ + request: &testingRequest{ + method: http.MethodGet, + url: fmt.Sprintf("%s/%d/resources", labelAPIBasePath, globalLabelID), + credential: projAdmin, + }, + code: http.StatusForbidden, + }, + // 403: project level label + &codeCheckingCase{ + request: &testingRequest{ + method: http.MethodGet, + url: fmt.Sprintf("%s/%d/resources", labelAPIBasePath, projectLabelID), + credential: projDeveloper, + }, + code: http.StatusForbidden, + }, + } + runCodeCheckingCases(t, cases...) + + // 200: global level label + resources := map[string][]rep_models.ReplicationPolicy{} + err = handleAndParse(&testingRequest{ + method: http.MethodGet, + url: fmt.Sprintf("%s/%d/resources", labelAPIBasePath, globalLabelID), + credential: sysAdmin, + }, &resources) + require.Nil(t, err) + policies := resources["replication_policies"] + require.Equal(t, 1, len(policies)) + assert.Equal(t, policyID, policies[0].ID) + + // 200: project level label + resources = map[string][]rep_models.ReplicationPolicy{} + err = handleAndParse(&testingRequest{ + method: http.MethodGet, + url: fmt.Sprintf("%s/%d/resources", labelAPIBasePath, projectLabelID), + credential: projAdmin, + }, &resources) + require.Nil(t, err) + policies = resources["replication_policies"] + require.Equal(t, 1, len(policies)) + assert.Equal(t, policyID, policies[0].ID) +} diff --git a/src/ui/router.go b/src/ui/router.go index 9b297b553..a080a659a 100644 --- a/src/ui/router.go +++ b/src/ui/router.go @@ -104,6 +104,7 @@ func initRouters() { beego.Router("/api/replications", &api.ReplicationAPI{}) beego.Router("/api/labels", &api.LabelAPI{}, "post:Post;get:List") beego.Router("/api/labels/:id([0-9]+)", &api.LabelAPI{}, "get:Get;put:Put;delete:Delete") + beego.Router("/api/labels/:id([0-9]+)/resources", &api.LabelAPI{}, "get:ListResources") beego.Router("/api/systeminfo", &api.SystemInfoAPI{}, "get:GetGeneralInfo") beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")