mirror of
https://github.com/goharbor/harbor.git
synced 2024-10-01 23:07:39 +02:00
Merge pull request #5003 from ywk253100/180517_label_filter_api
Add label filter in replication policy API
This commit is contained in:
commit
fb33f83b9a
@ -2959,9 +2959,14 @@ definitions:
|
|||||||
description: >-
|
description: >-
|
||||||
The replication policy filter kind. The valid values are project,
|
The replication policy filter kind. The valid values are project,
|
||||||
repository and tag.
|
repository and tag.
|
||||||
|
value:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- integer
|
||||||
|
description: The value of replication policy filter. When creating repository and tag filter, filling it with the pattern as string. When creating label filter, filling it with label ID as integer.
|
||||||
pattern:
|
pattern:
|
||||||
type: string
|
type: string
|
||||||
description: The replication policy filter pattern.
|
description: Depraceted, use value instead. The replication policy filter pattern.
|
||||||
metadata:
|
metadata:
|
||||||
type: object
|
type: object
|
||||||
description: This map object is the replication policy filter metadata.
|
description: This map object is the replication policy filter metadata.
|
||||||
@ -3541,7 +3546,7 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
description: The color of label.
|
description: The color of label.
|
||||||
scope:
|
scope:
|
||||||
type: integer
|
type: string
|
||||||
description: The scope of label, g for global labels and p for project labels.
|
description: The scope of label, g for global labels and p for project labels.
|
||||||
project_id:
|
project_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
@ -30,13 +30,44 @@ type Filter struct {
|
|||||||
|
|
||||||
// Valid ...
|
// Valid ...
|
||||||
func (f *Filter) Valid(v *validation.Validation) {
|
func (f *Filter) Valid(v *validation.Validation) {
|
||||||
if !(f.Kind == replication.FilterItemKindProject ||
|
switch f.Kind {
|
||||||
f.Kind == replication.FilterItemKindRepository ||
|
case replication.FilterItemKindProject,
|
||||||
f.Kind == replication.FilterItemKindTag) {
|
replication.FilterItemKindRepository,
|
||||||
|
replication.FilterItemKindTag:
|
||||||
|
if f.Value == nil {
|
||||||
|
// check the Filter.Pattern if the Filter.Value is nil for compatibility
|
||||||
|
if len(f.Pattern) == 0 {
|
||||||
|
v.SetError("value", "the value can not be empty")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pattern, ok := f.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
v.SetError("value", "the type of value should be string for project, repository and image filter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(pattern) == 0 {
|
||||||
|
v.SetError("value", "the value can not be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case replication.FilterItemKindLabel:
|
||||||
|
if f.Value == nil {
|
||||||
|
v.SetError("value", "the value can not be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
labelID, ok := f.Value.(float64)
|
||||||
|
i := int64(labelID)
|
||||||
|
if !ok || float64(i) != labelID {
|
||||||
|
v.SetError("value", "the type of value should be integer for label filter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i <= 0 {
|
||||||
|
v.SetError("value", fmt.Sprintf("invalid label ID: %d", i))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.Value = i
|
||||||
|
default:
|
||||||
v.SetError("kind", fmt.Sprintf("invalid filter kind: %s", f.Kind))
|
v.SetError("kind", fmt.Sprintf("invalid filter kind: %s", f.Kind))
|
||||||
}
|
return
|
||||||
|
|
||||||
if len(f.Pattern) == 0 {
|
|
||||||
v.SetError("pattern", "filter pattern can not be empty")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,29 @@ func TestValid(t *testing.T) {
|
|||||||
Kind: replication.FilterItemKindRepository,
|
Kind: replication.FilterItemKindRepository,
|
||||||
Pattern: "*",
|
Pattern: "*",
|
||||||
}: false,
|
}: false,
|
||||||
|
&Filter{
|
||||||
|
Kind: replication.FilterItemKindRepository,
|
||||||
|
Value: "*",
|
||||||
|
}: false,
|
||||||
|
&Filter{
|
||||||
|
Kind: replication.FilterItemKindLabel,
|
||||||
|
}: true,
|
||||||
|
&Filter{
|
||||||
|
Kind: replication.FilterItemKindLabel,
|
||||||
|
Value: "",
|
||||||
|
}: true,
|
||||||
|
&Filter{
|
||||||
|
Kind: replication.FilterItemKindLabel,
|
||||||
|
Value: 1.2,
|
||||||
|
}: true,
|
||||||
|
&Filter{
|
||||||
|
Kind: replication.FilterItemKindLabel,
|
||||||
|
Value: -1,
|
||||||
|
}: true,
|
||||||
|
&Filter{
|
||||||
|
Kind: replication.FilterItemKindLabel,
|
||||||
|
Value: 1,
|
||||||
|
}: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
for filter, hasError := range cases {
|
for filter, hasError := range cases {
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
persist_models "github.com/vmware/harbor/src/common/models"
|
persist_models "github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/replication"
|
||||||
"github.com/vmware/harbor/src/replication/models"
|
"github.com/vmware/harbor/src/replication/models"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
@ -110,6 +111,11 @@ func convertFromPersistModel(policy *persist_models.RepPolicy) (models.Replicati
|
|||||||
if filters[i].Value == nil && len(filters[i].Pattern) > 0 {
|
if filters[i].Value == nil && len(filters[i].Pattern) > 0 {
|
||||||
filters[i].Value = filters[i].Pattern
|
filters[i].Value = filters[i].Pattern
|
||||||
}
|
}
|
||||||
|
// convert the type of Value to int64 as the default type of
|
||||||
|
// json Unmarshal for number is float64
|
||||||
|
if filters[i].Kind == replication.FilterItemKindLabel {
|
||||||
|
filters[i].Value = int64(filters[i].Value.(float64))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ply.Filters = filters
|
ply.Filters = filters
|
||||||
}
|
}
|
||||||
|
@ -56,11 +56,13 @@ func (r *ReplicationPolicy) Valid(v *validation.Validation) {
|
|||||||
v.SetError("targets", "can not be empty")
|
v.SetError("targets", "can not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, filter := range r.Filters {
|
for i := range r.Filters {
|
||||||
filter.Valid(v)
|
r.Filters[i].Valid(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Trigger != nil {
|
if r.Trigger == nil {
|
||||||
|
v.SetError("trigger", "can not be empty")
|
||||||
|
} else {
|
||||||
r.Trigger.Valid(v)
|
r.Trigger.Valid(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ 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"
|
||||||
"github.com/vmware/harbor/src/replication/core"
|
"github.com/vmware/harbor/src/replication/core"
|
||||||
rep_models "github.com/vmware/harbor/src/replication/models"
|
rep_models "github.com/vmware/harbor/src/replication/models"
|
||||||
api_models "github.com/vmware/harbor/src/ui/api/models"
|
api_models "github.com/vmware/harbor/src/ui/api/models"
|
||||||
@ -34,6 +35,14 @@ type RepPolicyAPI struct {
|
|||||||
BaseController
|
BaseController
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// labelWrapper wraps models.Label by adding the property "inactive"
|
||||||
|
type labelWrapper struct {
|
||||||
|
models.Label
|
||||||
|
// if the label referenced by label filter is deleted,
|
||||||
|
// inactive will set be true
|
||||||
|
Inactive bool `json:"inactive"`
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare validates whether the user has system admin role
|
// Prepare validates whether the user has system admin role
|
||||||
func (pa *RepPolicyAPI) Prepare() {
|
func (pa *RepPolicyAPI) Prepare() {
|
||||||
pa.BaseController.Prepare()
|
pa.BaseController.Prepare()
|
||||||
@ -153,6 +162,22 @@ func (pa *RepPolicyAPI) Post() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the existence of labels
|
||||||
|
for _, filter := range policy.Filters {
|
||||||
|
if filter.Kind == replication.FilterItemKindLabel {
|
||||||
|
labelID := filter.Value.(int64)
|
||||||
|
label, err := dao.GetLabel(labelID)
|
||||||
|
if err != nil {
|
||||||
|
pa.HandleInternalServerError(fmt.Sprintf("failed to get label %d: %v", labelID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if label == nil {
|
||||||
|
pa.HandleNotFound(fmt.Sprintf("label %d not found", labelID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
id, err := core.GlobalController.CreatePolicy(convertToRepPolicy(policy))
|
id, err := core.GlobalController.CreatePolicy(convertToRepPolicy(policy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pa.HandleInternalServerError(fmt.Sprintf("failed to create policy: %v", err))
|
pa.HandleInternalServerError(fmt.Sprintf("failed to create policy: %v", err))
|
||||||
@ -219,6 +244,22 @@ func (pa *RepPolicyAPI) Put() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the existence of labels
|
||||||
|
for _, filter := range policy.Filters {
|
||||||
|
if filter.Kind == replication.FilterItemKindLabel {
|
||||||
|
labelID := filter.Value.(int64)
|
||||||
|
label, err := dao.GetLabel(labelID)
|
||||||
|
if err != nil {
|
||||||
|
pa.HandleInternalServerError(fmt.Sprintf("failed to get label %d: %v", labelID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if label == nil {
|
||||||
|
pa.HandleNotFound(fmt.Sprintf("label %d not found", labelID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err = core.GlobalController.UpdatePolicy(convertToRepPolicy(policy)); err != nil {
|
if err = core.GlobalController.UpdatePolicy(convertToRepPolicy(policy)); err != nil {
|
||||||
pa.HandleInternalServerError(fmt.Sprintf("failed to update policy %d: %v", id, err))
|
pa.HandleInternalServerError(fmt.Sprintf("failed to update policy %d: %v", id, err))
|
||||||
return
|
return
|
||||||
@ -279,7 +320,6 @@ func convertFromRepPolicy(projectMgr promgr.ProjectManager, policy rep_models.Re
|
|||||||
ID: policy.ID,
|
ID: policy.ID,
|
||||||
Name: policy.Name,
|
Name: policy.Name,
|
||||||
Description: policy.Description,
|
Description: policy.Description,
|
||||||
Filters: policy.Filters,
|
|
||||||
ReplicateDeletion: policy.ReplicateDeletion,
|
ReplicateDeletion: policy.ReplicateDeletion,
|
||||||
Trigger: policy.Trigger,
|
Trigger: policy.Trigger,
|
||||||
CreationTime: policy.CreationTime,
|
CreationTime: policy.CreationTime,
|
||||||
@ -306,6 +346,28 @@ func convertFromRepPolicy(projectMgr promgr.ProjectManager, policy rep_models.Re
|
|||||||
ply.Targets = append(ply.Targets, target)
|
ply.Targets = append(ply.Targets, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// populate label used in label filter
|
||||||
|
for _, filter := range policy.Filters {
|
||||||
|
if filter.Kind == replication.FilterItemKindLabel {
|
||||||
|
labelID := filter.Value.(int64)
|
||||||
|
label, err := dao.GetLabel(labelID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lw := &labelWrapper{}
|
||||||
|
// if the label is not found, set inactive to true
|
||||||
|
if label == nil {
|
||||||
|
lw.ID = labelID
|
||||||
|
lw.Name = "unknown"
|
||||||
|
lw.Inactive = true
|
||||||
|
} else {
|
||||||
|
lw.Label = *label
|
||||||
|
}
|
||||||
|
filter.Value = lw
|
||||||
|
}
|
||||||
|
ply.Filters = append(ply.Filters, filter)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO call the method from replication controller
|
// TODO call the method from replication controller
|
||||||
errJobCount, err := dao.GetTotalCountOfRepJobs(&models.RepJobQuery{
|
errJobCount, err := dao.GetTotalCountOfRepJobs(&models.RepJobQuery{
|
||||||
PolicyID: policy.ID,
|
PolicyID: policy.ID,
|
||||||
|
@ -37,6 +37,7 @@ var (
|
|||||||
projectID int64 = 1
|
projectID int64 = 1
|
||||||
targetID int64
|
targetID int64
|
||||||
policyID int64
|
policyID int64
|
||||||
|
labelID2 int64
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRepPolicyAPIPost(t *testing.T) {
|
func TestRepPolicyAPIPost(t *testing.T) {
|
||||||
@ -52,6 +53,14 @@ func TestRepPolicyAPIPost(t *testing.T) {
|
|||||||
CommonAddTarget()
|
CommonAddTarget()
|
||||||
targetID = int64(CommonGetTarget())
|
targetID = int64(CommonGetTarget())
|
||||||
|
|
||||||
|
var err error
|
||||||
|
labelID2, err = dao.AddLabel(&models.Label{
|
||||||
|
Name: "label_for_replication_filter",
|
||||||
|
Scope: "g",
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
defer dao.DeleteLabel(labelID2)
|
||||||
|
|
||||||
cases := []*codeCheckingCase{
|
cases := []*codeCheckingCase{
|
||||||
// 401
|
// 401
|
||||||
&codeCheckingCase{
|
&codeCheckingCase{
|
||||||
@ -231,6 +240,41 @@ func TestRepPolicyAPIPost(t *testing.T) {
|
|||||||
},
|
},
|
||||||
code: http.StatusNotFound,
|
code: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
// 404, label not found
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: repPolicyAPIBasePath,
|
||||||
|
bodyJSON: &api_models.ReplicationPolicy{
|
||||||
|
Name: policyName,
|
||||||
|
Projects: []*models.Project{
|
||||||
|
&models.Project{
|
||||||
|
ProjectID: projectID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []*models.RepTarget{
|
||||||
|
&models.RepTarget{
|
||||||
|
ID: targetID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Filters: []rep_models.Filter{
|
||||||
|
rep_models.Filter{
|
||||||
|
Kind: replication.FilterItemKindRepository,
|
||||||
|
Pattern: "*",
|
||||||
|
},
|
||||||
|
rep_models.Filter{
|
||||||
|
Kind: replication.FilterItemKindLabel,
|
||||||
|
Value: 10000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trigger: &rep_models.Trigger{
|
||||||
|
Kind: replication.TriggerKindManual,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
credential: sysAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusNotFound,
|
||||||
|
},
|
||||||
// 201
|
// 201
|
||||||
&codeCheckingCase{
|
&codeCheckingCase{
|
||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
@ -253,6 +297,10 @@ func TestRepPolicyAPIPost(t *testing.T) {
|
|||||||
Kind: replication.FilterItemKindRepository,
|
Kind: replication.FilterItemKindRepository,
|
||||||
Pattern: "*",
|
Pattern: "*",
|
||||||
},
|
},
|
||||||
|
rep_models.Filter{
|
||||||
|
Kind: replication.FilterItemKindLabel,
|
||||||
|
Value: labelID2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Trigger: &rep_models.Trigger{
|
Trigger: &rep_models.Trigger{
|
||||||
Kind: replication.TriggerKindManual,
|
Kind: replication.TriggerKindManual,
|
||||||
@ -303,6 +351,21 @@ func TestRepPolicyAPIGet(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, policyID, policy.ID)
|
assert.Equal(t, policyID, policy.ID)
|
||||||
assert.Equal(t, policyName, policy.Name)
|
assert.Equal(t, policyName, policy.Name)
|
||||||
|
assert.Equal(t, 2, len(policy.Filters))
|
||||||
|
found := false
|
||||||
|
for _, filter := range policy.Filters {
|
||||||
|
if filter.Kind == replication.FilterItemKindLabel {
|
||||||
|
found = true
|
||||||
|
label, ok := filter.Value.(map[string]interface{})
|
||||||
|
if assert.True(t, ok) {
|
||||||
|
id := int64(label["id"].(float64))
|
||||||
|
inactive := label["inactive"].(bool)
|
||||||
|
assert.Equal(t, labelID2, id)
|
||||||
|
assert.True(t, inactive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, found)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepPolicyAPIList(t *testing.T) {
|
func TestRepPolicyAPIList(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user