diff --git a/src/common/notifier/config_watcher.go b/src/common/notifier/config_watcher.go new file mode 100644 index 000000000..bd86ef231 --- /dev/null +++ b/src/common/notifier/config_watcher.go @@ -0,0 +1,39 @@ +package notifier + +import ( + "errors" + "reflect" + + "github.com/vmware/harbor/src/common/models" + "github.com/vmware/harbor/src/common/utils" +) + +//WatchConfigChanges is used to watch the configuration changes. +func WatchConfigChanges(cfg map[string]interface{}) error { + if cfg == nil { + return errors.New("Empty configurations") + } + + //Currently only watch the scan all policy change. + if v, ok := cfg[ScanAllPolicyTopic]; ok { + policyCfg := &models.ScanAllPolicy{} + if err := utils.ConvertMapToStruct(policyCfg, v); err != nil { + return err + } + + policyNotification := ScanPolicyNotification{ + Type: policyCfg.Type, + DailyTime: 0, + } + + if t, yes := policyCfg.Parm["daily_time"]; yes { + if reflect.TypeOf(t).Kind() == reflect.Int { + policyNotification.DailyTime = (int64)(t.(int)) + } + } + + return Publish(ScanAllPolicyTopic, policyNotification) + } + + return nil +} diff --git a/src/common/notifier/config_watcher_test.go b/src/common/notifier/config_watcher_test.go new file mode 100644 index 000000000..1bf22ba93 --- /dev/null +++ b/src/common/notifier/config_watcher_test.go @@ -0,0 +1,57 @@ +package notifier + +import ( + "encoding/json" + "strconv" + "strings" + "testing" + "time" +) + +var jsonText = ` +{ +"scan_all_policy": { + "type": "daily", + "parameter": { + "daily_time": + } + } +} +` + +func TestWatchConfiguration(t *testing.T) { + now := time.Now().UTC() + offset := (now.Hour()+1)*3600 + now.Minute()*60 + jsonT := strings.Replace(jsonText, "", strconv.Itoa(offset), -1) + v := make(map[string]interface{}) + if err := json.Unmarshal([]byte(jsonT), &v); err != nil { + t.Fatal(err) + } + + if err := WatchConfigChanges(v); err != nil { + if !strings.Contains(err.Error(), "No handlers registered") { + t.Fatal(err) + } + } +} + +var jsonText2 = ` +{ +"scan_all_policy": { + "type": "none" + } +} +` + +func TestWatchConfiguration2(t *testing.T) { + v := make(map[string]interface{}) + if err := json.Unmarshal([]byte(jsonText2), &v); err != nil { + t.Fatal(err) + } + + if err := WatchConfigChanges(v); err != nil { + if !strings.Contains(err.Error(), "No handlers registered") { + t.Fatal(err) + } + } +} diff --git a/src/common/notifier/notifier.go b/src/common/notifier/notifier.go index 5b92247ff..f93df9462 100644 --- a/src/common/notifier/notifier.go +++ b/src/common/notifier/notifier.go @@ -183,7 +183,6 @@ func (nw *NotificationWatcher) Notify(notification Notification) error { //Trigger handlers for _, h := range handlers { var handlerChan chan bool - if h.IsStateful() { t := reflect.TypeOf(h).String() handlerChan = nw.handlerChannels[t].channel diff --git a/src/common/notifier/notifier_test.go b/src/common/notifier/notifier_test.go index 4d3499a9d..9f98679ce 100644 --- a/src/common/notifier/notifier_test.go +++ b/src/common/notifier/notifier_test.go @@ -2,11 +2,14 @@ package notifier import ( "reflect" + "sync/atomic" "testing" "time" + + "github.com/vmware/harbor/src/common/scheduler" ) -var statefulData int +var statefulData int32 type fakeStatefulHandler struct { number int @@ -21,7 +24,7 @@ func (fsh *fakeStatefulHandler) Handle(v interface{}) error { if v != nil && reflect.TypeOf(v).Kind() == reflect.Int { increment = v.(int) } - statefulData += increment + atomic.AddInt32(&statefulData, (int32)(increment)) return nil } @@ -126,11 +129,12 @@ func TestPublish(t *testing.T) { //Waiting for async is done <-time.After(1 * time.Second) - if statefulData != 150 { - t.Fatalf("Expect execution result %d, but got %d", 150, statefulData) + finalData := atomic.LoadInt32(&statefulData) + if finalData != 150 { + t.Fatalf("Expect execution result %d, but got %d", 150, finalData) } - err = UnSubscribe("topic1", "*notifier.fakeStatefulHandler") + err = UnSubscribe("topic1", "") if err != nil { t.Fatal(err) } @@ -139,4 +143,81 @@ func TestPublish(t *testing.T) { if err != nil { t.Fatal(err) } + + //Clear stateful data. + atomic.StoreInt32(&statefulData, 0) +} + +func TestConcurrentPublish(t *testing.T) { + err := Subscribe("topic1", &fakeStatefulHandler{0}) + if err != nil { + t.Fatal(err) + } + + if len(notificationWatcher.handlers) != 1 { + t.Fail() + } + + //Publish in a short interval. + for i := 0; i < 10; i++ { + Publish("topic1", 100) + } + + //Waiting for async is done + <-time.After(1 * time.Second) + + finalData := atomic.LoadInt32(&statefulData) + if finalData != 1000 { + t.Fatalf("Expect execution result %d, but got %d", 1000, finalData) + } + + err = UnSubscribe("topic1", "") + if err != nil { + t.Fatal(err) + } + + //Clear stateful data. + atomic.StoreInt32(&statefulData, 0) +} + +func TestConcurrentPublishWithScanPolicyHandler(t *testing.T) { + scheduler.DefaultScheduler.Start() + if !scheduler.DefaultScheduler.IsRunning() { + t.Fatal("Policy scheduler is not started") + } + + if err := Subscribe("testing_topic", &ScanPolicyNotificationHandler{}); err != nil { + t.Fatal(err.Error()) + } + if len(notificationWatcher.handlers) != 1 { + t.Fatal("Handler is not registered") + } + + utcTime := time.Now().UTC().Unix() + notification := ScanPolicyNotification{"daily", utcTime + 3600} + for i := 1; i <= 10; i++ { + notification.DailyTime += (int64)(i) + if err := Publish("testing_topic", notification); err != nil { + t.Fatalf("index=%d, error=%s", i, err.Error()) + } + } + + //Wating for everything is ready. + <-time.After(2 * time.Second) + + if err := UnSubscribe("testing_topic", ""); err != nil { + t.Fatal(err.Error()) + } + + if len(notificationWatcher.handlers) != 0 { + t.Fatal("Handler is not unregistered") + } + + scheduler.DefaultScheduler.Stop() + //Wating for everything is ready. + <-time.After(1 * time.Second) + if scheduler.DefaultScheduler.IsRunning() { + t.Fatal("Policy scheduler is not stopped") + } + } diff --git a/src/common/notifier/scan_policy_notitification_handler.go b/src/common/notifier/scan_policy_notitification_handler.go index 56c1bf885..076019f06 100644 --- a/src/common/notifier/scan_policy_notitification_handler.go +++ b/src/common/notifier/scan_policy_notitification_handler.go @@ -4,6 +4,7 @@ import ( "errors" "reflect" + "fmt" "time" "github.com/vmware/harbor/src/common/scheduler" @@ -15,6 +16,9 @@ const ( //PolicyTypeDaily specify the policy type is "daily" PolicyTypeDaily = "daily" + //PolicyTypeNone specify the policy type is "none" + PolicyTypeNone = "none" + alternatePolicy = "Alternate Policy" ) @@ -53,20 +57,50 @@ func (s *ScanPolicyNotificationHandler) Handle(value interface{}) error { hasScheduled := scheduler.DefaultScheduler.HasScheduled(alternatePolicy) if notification.Type == PolicyTypeDaily { if !hasScheduled { - schedulePolicy := policy.NewAlternatePolicy(&policy.AlternatePolicyConfiguration{ + //Schedule a new policy. + return schedulePolicy(notification) + } + + //To check and compare if the related parameter is changed. + if pl := scheduler.DefaultScheduler.GetPolicy(alternatePolicy); pl != nil { + policyCandidate := policy.NewAlternatePolicy(&policy.AlternatePolicyConfiguration{ Duration: 24 * time.Hour, OffsetTime: notification.DailyTime, }) - attachTask := task.NewScanAllTask() - schedulePolicy.AttachTasks(attachTask) + if !pl.Equal(policyCandidate) { + //Parameter changed. + //Unschedule policy. + if err := scheduler.DefaultScheduler.UnSchedule(alternatePolicy); err != nil { + return err + } - return scheduler.DefaultScheduler.Schedule(schedulePolicy) + //Schedule a new policy. + return schedulePolicy(notification) + } + //Same policy configuration, do nothing + return nil } - } else { + + return errors.New("Inconsistent policy scheduling status") + } else if notification.Type == PolicyTypeNone { if hasScheduled { return scheduler.DefaultScheduler.UnSchedule(alternatePolicy) } + } else { + return fmt.Errorf("Notification type %s is not supported", notification.Type) } return nil } + +//Schedule policy. +func schedulePolicy(notification ScanPolicyNotification) error { + schedulePolicy := policy.NewAlternatePolicy(&policy.AlternatePolicyConfiguration{ + Duration: 24 * time.Hour, + OffsetTime: notification.DailyTime, + }) + attachTask := task.NewScanAllTask() + schedulePolicy.AttachTasks(attachTask) + + return scheduler.DefaultScheduler.Schedule(schedulePolicy) +} diff --git a/src/common/notifier/scan_policy_notitification_handler_test.go b/src/common/notifier/scan_policy_notitification_handler_test.go index 46efffbe5..89f1c651f 100644 --- a/src/common/notifier/scan_policy_notitification_handler_test.go +++ b/src/common/notifier/scan_policy_notitification_handler_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/vmware/harbor/src/common/scheduler" + "github.com/vmware/harbor/src/common/scheduler/policy" ) var testingScheduler = scheduler.DefaultScheduler @@ -27,19 +28,37 @@ func TestScanPolicyNotificationHandler(t *testing.T) { t.Fatal(err) } - //Waiting for everything is ready. - <-time.After(1 * time.Second) if !testingScheduler.HasScheduled("Alternate Policy") { t.Fatal("Handler does not work") } - notification2 := ScanPolicyNotification{"none", 0} + //Policy parameter changed. + notification2 := ScanPolicyNotification{"daily", utcTime + 7200} if err := handler.Handle(notification2); err != nil { t.Fatal(err) } - //Waiting for everything is ready. - <-time.After(1 * time.Second) + if !testingScheduler.HasScheduled("Alternate Policy") { + t.Fatal("Handler does not work [2]") + } + pl := testingScheduler.GetPolicy("Alternate Policy") + if pl == nil { + t.Fail() + } + spl := pl.(*policy.AlternatePolicy) + cfg := spl.GetConfig() + if cfg == nil { + t.Fail() + } + if cfg.OffsetTime != utcTime+7200 { + t.Fatal("Policy is not updated") + } + + notification3 := ScanPolicyNotification{"none", 0} + if err := handler.Handle(notification3); err != nil { + t.Fatal(err) + } + if testingScheduler.HasScheduled("Alternate Policy") { t.Fail() } diff --git a/src/common/scheduler/policy/alternate_policy.go b/src/common/scheduler/policy/alternate_policy.go index 38a20e92f..8d6ac3859 100644 --- a/src/common/scheduler/policy/alternate_policy.go +++ b/src/common/scheduler/policy/alternate_policy.go @@ -44,9 +44,10 @@ type AlternatePolicy struct { //NewAlternatePolicy is constructor of creating AlternatePolicy. func NewAlternatePolicy(config *AlternatePolicyConfiguration) *AlternatePolicy { return &AlternatePolicy{ - tasks: []task.Task{}, - config: config, - isEnabled: false, + tasks: []task.Task{}, + config: config, + isEnabled: false, + terminator: make(chan bool), } } @@ -108,7 +109,6 @@ func (alp *AlternatePolicy) Evaluate() (<-chan bool, error) { } alp.done = make(chan bool) - alp.terminator = make(chan bool) alp.evaluation = make(chan bool) go func() { @@ -152,3 +152,23 @@ func (alp *AlternatePolicy) Evaluate() (<-chan bool, error) { return alp.evaluation, nil } + +//Equal is an implementation of same method in policy interface. +func (alp *AlternatePolicy) Equal(p Policy) bool { + if p == nil { + return false + } + + pl, ok := p.(*AlternatePolicy) + if !ok { + return false + } + + cfg := pl.GetConfig() + cfg2 := alp.GetConfig() + if (cfg == nil && cfg2 != nil) || (cfg != nil && cfg2 == nil) { + return false + } + + return cfg == nil || (cfg.Duration == cfg2.Duration && cfg.OffsetTime == cfg2.OffsetTime) +} diff --git a/src/common/scheduler/policy/policy.go b/src/common/scheduler/policy/policy.go index ad11cf5df..2f50be368 100644 --- a/src/common/scheduler/policy/policy.go +++ b/src/common/scheduler/policy/policy.go @@ -34,6 +34,11 @@ type Policy interface { Evaluate() (<-chan bool, error) //Disable the enabled policy and release all the allocated resources. - //Disable should also send signal to the terminated channel which returned by Done. Disable() error + + //Equal will compare the two policies based on related factors if existing such as confgiuration etc. + //to determine whether the two policies are same ones or not. Please pay attention that, not every policy + //needs to support this method. If no need, please directly return false to indicate each policies are + //different. + Equal(p Policy) bool } diff --git a/src/common/scheduler/scheduler.go b/src/common/scheduler/scheduler.go index 3073f6343..db2f15c5c 100644 --- a/src/common/scheduler/scheduler.go +++ b/src/common/scheduler/scheduler.go @@ -61,9 +61,6 @@ type Scheduler struct { //Store to keep the references of scheduled policies. policies Store - //Queue for accepting the scheduling polices. - scheduleQueue chan policy.Policy - //Queue for receiving policy unschedule request or complete signal. unscheduleQueue chan string @@ -90,7 +87,6 @@ func NewScheduler(config *Configuration) *Scheduler { qSize = config.QueueSize } - sq := make(chan policy.Policy, qSize) usq := make(chan string, qSize) stChan := make(chan *StatItem, 4) tc := make(chan bool, 1) @@ -99,7 +95,6 @@ func NewScheduler(config *Configuration) *Scheduler { return &Scheduler{ config: config, policies: store, - scheduleQueue: sq, unscheduleQueue: usq, statChan: stChan, terminateChan: tc, @@ -125,33 +120,26 @@ func (sch *Scheduler) Start() { } }() defer func() { + //Exit and clear. sch.isRunning = false + //Stop all watchers. + for _, wt := range sch.policies.GetAll() { + wt.Stop() + } + //Clear resources + sch.policies.Clear() + log.Infof("Policy scheduler stop at %s\n", time.Now().UTC().Format(time.RFC3339)) }() for { select { case <-sch.terminateChan: //Exit return - case p := <-sch.scheduleQueue: - //Schedule the policy. - watcher := NewWatcher(p, sch.statChan, sch.unscheduleQueue) - - //Keep the policy for future use after it's successfully scheduled. - sch.policies.Put(p.Name(), watcher) - - //Enable it. - watcher.Start() - - sch.statChan <- &StatItem{statSchedulePolicy, 1, nil} case name := <-sch.unscheduleQueue: - //Find the watcher. - watcher := sch.policies.Remove(name) - if watcher != nil && watcher.IsRunning() { - watcher.Stop() + //Unscheduled when policy is completed. + if err := sch.UnSchedule(name); err != nil { + log.Error(err.Error()) } - - sch.statChan <- &StatItem{statUnSchedulePolicy, 1, nil} - case stat := <-sch.statChan: { switch stat.Type { @@ -199,18 +187,8 @@ func (sch *Scheduler) Stop() { return } - //Terminate damon firstly to stop receiving signals. + //Terminate damon to stop receiving signals. sch.terminateChan <- true - - //Stop all watchers. - for _, wt := range sch.policies.GetAll() { - wt.Stop() - } - - //Clear resources - sch.policies.Clear() - - log.Infof("Policy scheduler stop at %s\n", time.Now().UTC().Format(time.RFC3339)) } //Schedule and enable the policy. @@ -233,7 +211,16 @@ func (sch *Scheduler) Schedule(scheduledPolicy policy.Policy) error { } //Schedule the policy. - sch.scheduleQueue <- scheduledPolicy + watcher := NewWatcher(scheduledPolicy, sch.statChan, sch.unscheduleQueue) + //Enable it. + watcher.Start() + + //Keep the policy for future use after it's successfully scheduled. + sch.policies.Put(scheduledPolicy.Name(), watcher) + + //Update stats and log info. + log.Infof("Policy %s is scheduled", scheduledPolicy.Name()) + sch.statChan <- &StatItem{statSchedulePolicy, 1, nil} return nil } @@ -249,7 +236,17 @@ func (sch *Scheduler) UnSchedule(policyName string) error { } //Unschedule the policy. - sch.unscheduleQueue <- policyName + //Find the watcher. + watcher := sch.policies.Remove(policyName) + if watcher != nil && watcher.IsRunning() { + watcher.Stop() + + //Update stats and log info. + log.Infof("Policy %s is unscheduled", policyName) + sch.statChan <- &StatItem{statUnSchedulePolicy, 1, nil} + } else { + log.Warningf("Inconsistent worker status for policy '%s'.\n", policyName) + } return nil } @@ -263,3 +260,13 @@ func (sch *Scheduler) IsRunning() bool { func (sch *Scheduler) HasScheduled(policyName string) bool { return sch.policies.Exists(policyName) } + +//GetPolicy is used to get related policy reference by its name. +func (sch *Scheduler) GetPolicy(policyName string) policy.Policy { + wk := sch.policies.Get(policyName) + if wk != nil { + return wk.p + } + + return nil +} diff --git a/src/common/scheduler/scheduler_test.go b/src/common/scheduler/scheduler_test.go index f038e1802..4e7379494 100644 --- a/src/common/scheduler/scheduler_test.go +++ b/src/common/scheduler/scheduler_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/vmware/harbor/src/common/scheduler/policy" "github.com/vmware/harbor/src/common/scheduler/task" ) @@ -61,6 +62,10 @@ func (fp *fakePolicy) Disable() error { return nil } +func (fp *fakePolicy) Equal(policy.Policy) bool { + return false +} + type fakeTask struct { number int } @@ -98,13 +103,11 @@ func TestScheduler(t *testing.T) { if DefaultScheduler.Schedule(fp) != nil { t.Fatal("Schedule policy failed") } - //Waiting for everything is stable - time.Sleep(1 * time.Second) if DefaultScheduler.policies.Size() == 0 { t.Fatal("No policy in the store after calling Schedule()") } - if DefaultScheduler.stats.PolicyCount != 1 { - t.Fatal("Policy stats do not match") + if DefaultScheduler.GetPolicy(fp.Name()) == nil { + t.Fatal("Failed to get poicy by name") } time.Sleep(2 * time.Second) @@ -121,12 +124,11 @@ func TestScheduler(t *testing.T) { if DefaultScheduler.UnSchedule(fp.Name()) != nil { t.Fatal("Unschedule policy failed") } - //Waiting for everything is stable - time.Sleep(1 * time.Second) - if DefaultScheduler.stats.PolicyCount != 0 { + if DefaultScheduler.policies.Size() != 0 { t.Fatal("Policy count does not match after calling UnSchedule()") } + <-time.After(1 * time.Second) copiedValue := DefaultScheduler.stats.CompletedTasks <-time.After(2 * time.Second) diff --git a/src/common/scheduler/watcher.go b/src/common/scheduler/watcher.go index feb2f7739..9827ab37c 100644 --- a/src/common/scheduler/watcher.go +++ b/src/common/scheduler/watcher.go @@ -56,6 +56,7 @@ func (wc *Watcher) Start() { defer func() { wc.isRunning = false + log.Infof("Work for policy %s is stopped.\n", wc.p.Name()) }() evalChan, err := pl.Evaluate() diff --git a/src/common/utils/utils.go b/src/common/utils/utils.go index 5436e17b3..25e1b16fe 100644 --- a/src/common/utils/utils.go +++ b/src/common/utils/utils.go @@ -16,6 +16,7 @@ package utils import ( "crypto/rand" + "encoding/json" "errors" "fmt" "net" @@ -129,45 +130,21 @@ func ParseTimeStamp(timestamp string) (*time.Time, error) { } //ConvertMapToStruct is used to fill the specified struct with map. -func ConvertMapToStruct(object interface{}, valuesInMap map[string]interface{}) error { +func ConvertMapToStruct(object interface{}, values interface{}) error { if object == nil { - return fmt.Errorf("nil struct is not supported") + return errors.New("nil struct is not supported") } if reflect.TypeOf(object).Kind() != reflect.Ptr { - return fmt.Errorf("object should be referred by pointer") + return errors.New("object should be referred by pointer") } - for k, v := range valuesInMap { - if err := setField(object, k, v); err != nil { - return err - } + bytes, err := json.Marshal(values) + if err != nil { + return err } - return nil -} - -func setField(object interface{}, field string, value interface{}) error { - structValue := reflect.ValueOf(object).Elem() - - structFieldValue := structValue.FieldByName(field) - if !structFieldValue.IsValid() { - return fmt.Errorf("No such field: %s in obj", field) - } - - if !structFieldValue.CanSet() { - return fmt.Errorf("Cannot set value for field %s", field) - } - - structFieldType := structFieldValue.Type() - val := reflect.ValueOf(value) - if structFieldType != val.Type() { - return errors.New("Provided value type didn't match object field type") - } - - structFieldValue.Set(val) - - return nil + return json.Unmarshal(bytes, object) } // ParseProjectIDOrName parses value to ID(int64) or name(string) diff --git a/src/ui/api/config.go b/src/ui/api/config.go index e1854d702..4a9e03ad4 100644 --- a/src/ui/api/config.go +++ b/src/ui/api/config.go @@ -192,7 +192,9 @@ func (c *ConfigAPI) Put() { } //Everything is ok, detect the configurations to confirm if the option we are caring is changed. - watchConfigChanges(cfg) + if err := watchConfigChanges(cfg); err != nil { + log.Errorf("Failed to watch configuration change with error: %s\n", err) + } } // Reset system configurations diff --git a/src/ui/api/utils.go b/src/ui/api/utils.go index 0919c31c8..9ec629218 100644 --- a/src/ui/api/utils.go +++ b/src/ui/api/utils.go @@ -17,11 +17,9 @@ package api import ( "bytes" "encoding/json" - "errors" "fmt" "io/ioutil" "net/http" - "reflect" "sort" "strings" @@ -501,33 +499,7 @@ func transformVulnerabilities(layerWithVuln *models.ClairLayerEnvelope) []*model } //Watch the configuration changes. +//Wrap the same method in common utils. func watchConfigChanges(cfg map[string]interface{}) error { - if cfg == nil { - return errors.New("Empty configurations") - } - - //Currently only watch the scan all policy change. - if v, ok := cfg[notifier.ScanAllPolicyTopic]; ok { - if reflect.TypeOf(v).Kind() == reflect.Map { - policyCfg := &models.ScanAllPolicy{} - if err := utils.ConvertMapToStruct(policyCfg, v.(map[string]interface{})); err != nil { - return err - } - - policyNotification := notifier.ScanPolicyNotification{ - Type: policyCfg.Type, - DailyTime: 0, - } - - if t, yes := policyCfg.Parm["daily_time"]; yes { - if reflect.TypeOf(t).Kind() == reflect.Int { - policyNotification.DailyTime = (int64)(t.(int)) - } - } - - return notifier.Publish(notifier.ScanAllPolicyTopic, policyNotification) - } - } - - return nil + return notifier.WatchConfigChanges(cfg) } diff --git a/src/ui/router.go b/src/ui/router.go index 79ff26e1e..46027a1cc 100644 --- a/src/ui/router.go +++ b/src/ui/router.go @@ -30,34 +30,35 @@ func initRouters() { beego.SetStaticPath("/static", "./static") beego.SetStaticPath("/i18n", "./static/i18n") - //Page Controllers: - beego.Router("/", &controllers.IndexController{}) - beego.Router("/sign-in", &controllers.IndexController{}) - beego.Router("/sign-up", &controllers.IndexController{}) - beego.Router("/reset_password", &controllers.IndexController{}) - - beego.Router("/harbor", &controllers.IndexController{}) - - beego.Router("/harbor/sign-in", &controllers.IndexController{}) - beego.Router("/harbor/sign-up", &controllers.IndexController{}) - beego.Router("/harbor/dashboard", &controllers.IndexController{}) - beego.Router("/harbor/projects", &controllers.IndexController{}) - beego.Router("/harbor/projects/:id/repositories", &controllers.IndexController{}) - beego.Router("/harbor/projects/:id/replications", &controllers.IndexController{}) - beego.Router("/harbor/projects/:id/members", &controllers.IndexController{}) - beego.Router("/harbor/projects/:id/logs", &controllers.IndexController{}) - beego.Router("/harbor/tags/:id/*", &controllers.IndexController{}) - - beego.Router("/harbor/users", &controllers.IndexController{}) - beego.Router("/harbor/logs", &controllers.IndexController{}) - beego.Router("/harbor/replications", &controllers.IndexController{}) - beego.Router("/harbor/replications/endpoints", &controllers.IndexController{}) - beego.Router("/harbor/replications/rules", &controllers.IndexController{}) - beego.Router("/harbor/tags", &controllers.IndexController{}) - beego.Router("/harbor/configs", &controllers.IndexController{}) - // standalone if !config.WithAdmiral() { + //Disable page access in integration mode. + //Page Controllers: + beego.Router("/", &controllers.IndexController{}) + beego.Router("/sign-in", &controllers.IndexController{}) + beego.Router("/sign-up", &controllers.IndexController{}) + beego.Router("/reset_password", &controllers.IndexController{}) + + beego.Router("/harbor", &controllers.IndexController{}) + + beego.Router("/harbor/sign-in", &controllers.IndexController{}) + beego.Router("/harbor/sign-up", &controllers.IndexController{}) + beego.Router("/harbor/dashboard", &controllers.IndexController{}) + beego.Router("/harbor/projects", &controllers.IndexController{}) + beego.Router("/harbor/projects/:id/repositories", &controllers.IndexController{}) + beego.Router("/harbor/projects/:id/replications", &controllers.IndexController{}) + beego.Router("/harbor/projects/:id/members", &controllers.IndexController{}) + beego.Router("/harbor/projects/:id/logs", &controllers.IndexController{}) + beego.Router("/harbor/tags/:id/*", &controllers.IndexController{}) + + beego.Router("/harbor/users", &controllers.IndexController{}) + beego.Router("/harbor/logs", &controllers.IndexController{}) + beego.Router("/harbor/replications", &controllers.IndexController{}) + beego.Router("/harbor/replications/endpoints", &controllers.IndexController{}) + beego.Router("/harbor/replications/rules", &controllers.IndexController{}) + beego.Router("/harbor/tags", &controllers.IndexController{}) + beego.Router("/harbor/configs", &controllers.IndexController{}) + beego.Router("/login", &controllers.CommonController{}, "post:Login") beego.Router("/log_out", &controllers.CommonController{}, "get:LogOut") beego.Router("/reset", &controllers.CommonController{}, "post:ResetPassword")