Fix replication bugs (#7470)

1. Only return the event based trigger for local Harbor
2. Valid the trigger pattern and cron string when creating/updating policies
3. Set the schema as "http" if it isn't specified when creating/updating registries

Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
Wenkai Yin 2019-04-23 19:34:29 +08:00 committed by Wang Yan
parent 823d9c04a9
commit d8310cc708
7 changed files with 131 additions and 18 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings"
common_http "github.com/goharbor/harbor/src/common/http" common_http "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/common/utils"
@ -206,6 +207,10 @@ func (t *RegistryAPI) Post() {
t.SendConflictError(fmt.Errorf("name '%s' is already used", r.Name)) t.SendConflictError(fmt.Errorf("name '%s' is already used", r.Name))
return return
} }
i := strings.Index(r.URL, "://")
if i == -1 {
r.URL = fmt.Sprintf("http://%s", r.URL)
}
status, err := registry.CheckHealthStatus(r) status, err := registry.CheckHealthStatus(r)
if err != nil { if err != nil {
@ -411,6 +416,10 @@ func (t *RegistryAPI) GetInfo() {
t.SendInternalServerError(fmt.Errorf("failed to get registry info %d: %v", id, err)) t.SendInternalServerError(fmt.Errorf("failed to get registry info %d: %v", id, err))
return return
} }
// currently, only the local Harbor registry supports the event based trigger, append it here
if id == 0 {
info.SupportedTriggers = append(info.SupportedTriggers, model.TriggerTypeEventBased)
}
t.WriteJSONData(process(info)) t.WriteJSONData(process(info))
} }

View File

@ -113,7 +113,6 @@ func (a *adapter) Info() (*model.RegistryInfo, error) {
SupportedTriggers: []model.TriggerType{ SupportedTriggers: []model.TriggerType{
model.TriggerTypeManual, model.TriggerTypeManual,
model.TriggerTypeScheduled, model.TriggerTypeScheduled,
model.TriggerTypeEventBased,
}, },
} }

View File

@ -43,7 +43,7 @@ func TestInfo(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, model.RegistryTypeHarbor, info.Type) assert.Equal(t, model.RegistryTypeHarbor, info.Type)
assert.Equal(t, 2, len(info.SupportedResourceFilters)) assert.Equal(t, 2, len(info.SupportedResourceFilters))
assert.Equal(t, 3, len(info.SupportedTriggers)) assert.Equal(t, 2, len(info.SupportedTriggers))
assert.Equal(t, 2, len(info.SupportedResourceTypes)) assert.Equal(t, 2, len(info.SupportedResourceTypes))
assert.Equal(t, model.ResourceTypeImage, info.SupportedResourceTypes[0]) assert.Equal(t, model.ResourceTypeImage, info.SupportedResourceTypes[0])
assert.Equal(t, model.ResourceTypeChart, info.SupportedResourceTypes[1]) assert.Equal(t, model.ResourceTypeChart, info.SupportedResourceTypes[1])
@ -67,7 +67,7 @@ func TestInfo(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, model.RegistryTypeHarbor, info.Type) assert.Equal(t, model.RegistryTypeHarbor, info.Type)
assert.Equal(t, 2, len(info.SupportedResourceFilters)) assert.Equal(t, 2, len(info.SupportedResourceFilters))
assert.Equal(t, 3, len(info.SupportedTriggers)) assert.Equal(t, 2, len(info.SupportedTriggers))
assert.Equal(t, 1, len(info.SupportedResourceTypes)) assert.Equal(t, 1, len(info.SupportedResourceTypes))
assert.Equal(t, model.ResourceTypeImage, info.SupportedResourceTypes[0]) assert.Equal(t, model.ResourceTypeImage, info.SupportedResourceTypes[0])
server.Close() server.Close()

View File

@ -20,6 +20,7 @@ import (
"github.com/astaxie/beego/validation" "github.com/astaxie/beego/validation"
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
"github.com/robfig/cron"
) )
// const definition // const definition
@ -88,10 +89,20 @@ func (p *Policy) Valid(v *validation.Validation) {
// valid the filters // valid the filters
for _, filter := range p.Filters { for _, filter := range p.Filters {
if filter.Type != FilterTypeResource && value, ok := filter.Value.(string)
filter.Type != FilterTypeName && if !ok {
filter.Type != FilterTypeTag && v.SetError("filters", "the type of filter value isn't string")
filter.Type != FilterTypeLabel { break
}
switch filter.Type {
case FilterTypeResource:
rt := ResourceType(value)
if !(rt == ResourceTypeImage || rt == ResourceTypeChart) {
v.SetError("filters", fmt.Sprintf("invalid resource filter: %s", value))
break
}
case FilterTypeName, FilterTypeTag, FilterTypeLabel:
default:
v.SetError("filters", "invalid filter type") v.SetError("filters", "invalid filter type")
break break
} }
@ -99,14 +110,19 @@ func (p *Policy) Valid(v *validation.Validation) {
// valid trigger // valid trigger
if p.Trigger != nil { if p.Trigger != nil {
if p.Trigger.Type != TriggerTypeManual && switch p.Trigger.Type {
p.Trigger.Type != TriggerTypeScheduled && case TriggerTypeManual, TriggerTypeEventBased:
p.Trigger.Type != TriggerTypeEventBased { case TriggerTypeScheduled:
v.SetError("trigger", "invalid trigger type") if p.Trigger.Settings == nil || len(p.Trigger.Settings.Cron) == 0 {
}
if p.Trigger.Type == TriggerTypeScheduled &&
(p.Trigger.Settings == nil || len(p.Trigger.Settings.Cron) == 0) {
v.SetError("trigger", fmt.Sprintf("the cron string cannot be empty when the trigger type is %s", TriggerTypeScheduled)) v.SetError("trigger", fmt.Sprintf("the cron string cannot be empty when the trigger type is %s", TriggerTypeScheduled))
} else {
_, err := cron.Parse(p.Trigger.Settings.Cron)
if err != nil {
v.SetError("trigger", fmt.Sprintf("invalid cron string for scheduled trigger: %s", p.Trigger.Settings.Cron))
}
}
default:
v.SetError("trigger", "invalid trigger type")
} }
} }
} }

View File

@ -15,6 +15,7 @@
package model package model
import ( import (
"fmt"
"testing" "testing"
"github.com/astaxie/beego/validation" "github.com/astaxie/beego/validation"
@ -69,6 +70,48 @@ func TestValidOfPolicy(t *testing.T) {
}, },
pass: false, pass: false,
}, },
// invalid filter
{
policy: &Policy{
Name: "policy01",
SrcRegistry: &Registry{
ID: 0,
},
DestRegistry: &Registry{
ID: 1,
},
Filters: []*Filter{
{
Type: FilterTypeResource,
Value: "invalid_resource_type",
},
},
},
pass: false,
},
// invalid filter
{
policy: &Policy{
Name: "policy01",
SrcRegistry: &Registry{
ID: 0,
},
DestRegistry: &Registry{
ID: 1,
},
Filters: []*Filter{
{
Type: FilterTypeResource,
Value: ResourceTypeImage,
},
{
Type: FilterTypeName,
Value: "a[",
},
},
},
pass: false,
},
// invalid trigger // invalid trigger
{ {
policy: &Policy{ policy: &Policy{
@ -113,6 +156,35 @@ func TestValidOfPolicy(t *testing.T) {
}, },
pass: false, pass: false,
}, },
// invalid cron
{
policy: &Policy{
Name: "policy01",
SrcRegistry: &Registry{
ID: 0,
},
DestRegistry: &Registry{
ID: 1,
},
Filters: []*Filter{
{
Type: FilterTypeResource,
Value: "image",
},
{
Type: FilterTypeName,
Value: "library/**",
},
},
Trigger: &Trigger{
Type: TriggerTypeScheduled,
Settings: &TriggerSettings{
Cron: "* * *",
},
},
},
pass: false,
},
// pass // pass
{ {
policy: &Policy{ policy: &Policy{
@ -124,15 +196,19 @@ func TestValidOfPolicy(t *testing.T) {
ID: 1, ID: 1,
}, },
Filters: []*Filter{ Filters: []*Filter{
{
Type: FilterTypeResource,
Value: "image",
},
{ {
Type: FilterTypeName, Type: FilterTypeName,
Value: "library", Value: "library/**",
}, },
}, },
Trigger: &Trigger{ Trigger: &Trigger{
Type: TriggerTypeScheduled, Type: TriggerTypeScheduled,
Settings: &TriggerSettings{ Settings: &TriggerSettings{
Cron: "* * *", Cron: "* * * * * *",
}, },
}, },
}, },
@ -140,7 +216,8 @@ func TestValidOfPolicy(t *testing.T) {
}, },
} }
for _, c := range cases { for i, c := range cases {
fmt.Printf("running case %d ...\n", i)
v := &validation.Validation{} v := &validation.Validation{}
c.policy.Valid(v) c.policy.Valid(v)
assert.Equal(t, c.pass, len(v.Errors) == 0) assert.Equal(t, c.pass, len(v.Errors) == 0)

View File

@ -18,6 +18,8 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/bmatcuk/doublestar" "github.com/bmatcuk/doublestar"
"github.com/goharbor/harbor/src/common/utils/registry" "github.com/goharbor/harbor/src/common/utils/registry"
) )
@ -27,7 +29,12 @@ func Match(pattern, str string) (bool, error) {
if len(pattern) == 0 { if len(pattern) == 0 {
return true, nil return true, nil
} }
return doublestar.Match(pattern, str) match, err := doublestar.Match(pattern, str)
if err == doublestar.ErrBadPattern {
log.Warningf("failed to match the string %s against pattern %s: %v", str, pattern, err)
return false, nil
}
return match, err
} }
// IsSpecificRepositoryName if the name not contains any char of "*?[{\\]}^,", // IsSpecificRepositoryName if the name not contains any char of "*?[{\\]}^,",

View File

@ -67,6 +67,11 @@ func TestMatch(t *testing.T) {
str: "1.01", str: "1.01",
match: false, match: false,
}, },
{
pattern: "a[",
str: "aaa",
match: false,
},
} }
for _, c := range cases { for _, c := range cases {
match, err := Match(c.pattern, c.str) match, err := Match(c.pattern, c.str)