From 67612aa2e3913187f6d94c7b4ca8c9300ead613c Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Mon, 20 Mar 2017 17:20:31 +0800 Subject: [PATCH] abstract adminserver client into a single package --- src/adminserver/api/cfg_test.go | 20 +- src/adminserver/client/auth/auth.go | 51 +++++ src/adminserver/client/auth/auth_test.go | 44 ++++ src/adminserver/client/client.go | 184 ++++++++++++++++ src/adminserver/client/client_test.go | 82 +++++++ src/adminserver/systemcfg/systemcfg.go | 93 ++++---- src/adminserver/systemcfg/systemcfg_test.go | 18 +- src/common/config/config.go | 224 ++------------------ src/common/const.go | 65 ++++++ src/common/utils/ldap/ldap_test.go | 40 ++-- src/common/utils/test/adminserver.go | 102 +++++---- src/jobservice/config/config.go | 48 +++-- src/ui/api/config.go | 176 +++++++-------- src/ui/api/config_test.go | 29 ++- src/ui/api/systeminfo.go | 12 +- src/ui/auth/ldap/ldap_test.go | 40 ++-- src/ui/config/config.go | 115 +++++----- 17 files changed, 818 insertions(+), 525 deletions(-) create mode 100644 src/adminserver/client/auth/auth.go create mode 100644 src/adminserver/client/auth/auth_test.go create mode 100644 src/adminserver/client/client.go create mode 100644 src/adminserver/client/client_test.go create mode 100644 src/common/const.go diff --git a/src/adminserver/api/cfg_test.go b/src/adminserver/api/cfg_test.go index bfaf2fd7f..5e07c4b1e 100644 --- a/src/adminserver/api/cfg_test.go +++ b/src/adminserver/api/cfg_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/vmware/harbor/src/adminserver/systemcfg" - comcfg "github.com/vmware/harbor/src/common/config" + "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/utils/test" ) @@ -43,7 +43,7 @@ func TestConfigAPI(t *testing.T) { secret := "secret" envs := map[string]string{ - "AUTH_MODE": comcfg.DBAuth, + "AUTH_MODE": common.DBAuth, "JSON_CFG_STORE_PATH": configPath, "KEY_PATH": secretKeyPath, "UI_SECRET": secret, @@ -97,7 +97,7 @@ func TestConfigAPI(t *testing.T) { return } - scope := int(m[comcfg.LDAPScope].(float64)) + scope := int(m[common.LDAPScope].(float64)) if scope != 3 { t.Errorf("unexpected ldap scope: %d != %d", scope, 3) return @@ -105,7 +105,7 @@ func TestConfigAPI(t *testing.T) { // modify configurations c := map[string]interface{}{ - comcfg.AUTHMode: comcfg.LDAPAuth, + common.AUTHMode: common.LDAPAuth, } b, err := json.Marshal(c) @@ -155,9 +155,9 @@ func TestConfigAPI(t *testing.T) { return } - mode := m[comcfg.AUTHMode].(string) - if mode != comcfg.LDAPAuth { - t.Errorf("unexpected auth mode: %s != %s", mode, comcfg.LDAPAuth) + mode := m[common.AUTHMode].(string) + if mode != common.LDAPAuth { + t.Errorf("unexpected auth mode: %s != %s", mode, common.LDAPAuth) return } @@ -203,9 +203,9 @@ func TestConfigAPI(t *testing.T) { return } - mode = m[comcfg.AUTHMode].(string) - if mode != comcfg.DBAuth { - t.Errorf("unexpected auth mode: %s != %s", mode, comcfg.LDAPAuth) + mode = m[common.AUTHMode].(string) + if mode != common.DBAuth { + t.Errorf("unexpected auth mode: %s != %s", mode, common.LDAPAuth) return } } diff --git a/src/adminserver/client/auth/auth.go b/src/adminserver/client/auth/auth.go new file mode 100644 index 000000000..3779f8e7f --- /dev/null +++ b/src/adminserver/client/auth/auth.go @@ -0,0 +1,51 @@ +/* + Copyright (c) 2016 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 auth + +import ( + "net/http" +) + +// Authorizer authorizes request +type Authorizer interface { + Authorize(*http.Request) error +} + +// NewSecretAuthorizer returns an instance of secretAuthorizer +func NewSecretAuthorizer(cookieName, secret string) Authorizer { + return &secretAuthorizer{ + cookieName: cookieName, + secret: secret, + } +} + +type secretAuthorizer struct { + cookieName string + secret string +} + +func (s *secretAuthorizer) Authorize(req *http.Request) error { + if req == nil { + return nil + } + + req.AddCookie(&http.Cookie{ + Name: s.cookieName, + Value: s.secret, + }) + + return nil +} diff --git a/src/adminserver/client/auth/auth_test.go b/src/adminserver/client/auth/auth_test.go new file mode 100644 index 000000000..62cb79dec --- /dev/null +++ b/src/adminserver/client/auth/auth_test.go @@ -0,0 +1,44 @@ +/* + Copyright (c) 2016 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 auth + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAuthorize(t *testing.T) { + cookieName := "secret" + secret := "secret" + authorizer := NewSecretAuthorizer(cookieName, secret) + req, err := http.NewRequest("", "", nil) + if !assert.Nil(t, err, "unexpected error") { + return + } + + err = authorizer.Authorize(req) + if !assert.Nil(t, err, "unexpected error") { + return + } + + cookie, err := req.Cookie(cookieName) + if !assert.Nil(t, err, "unexpected error") { + return + } + assert.Equal(t, secret, cookie.Value, "unexpected cookie") +} diff --git a/src/adminserver/client/client.go b/src/adminserver/client/client.go new file mode 100644 index 000000000..afe70d339 --- /dev/null +++ b/src/adminserver/client/client.go @@ -0,0 +1,184 @@ +/* + Copyright (c) 2016 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 client + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "strings" + + "github.com/vmware/harbor/src/adminserver/client/auth" + "github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage" + "github.com/vmware/harbor/src/common/utils" +) + +// Client defines methods that an Adminserver client should implement +type Client interface { + // Ping tests the connection with server + Ping() error + // GetCfgs returns system configurations + GetCfgs() (map[string]interface{}, error) + // UpdateCfgs updates system configurations + UpdateCfgs(map[string]interface{}) error + // ResetCfgs resets system configuratoins form environment variables + ResetCfgs() error + // Capacity returns the capacity of image storage + Capacity() (*imagestorage.Capacity, error) +} + +// NewClient return an instance of Adminserver client +func NewClient(baseURL string, authorizer auth.Authorizer) Client { + baseURL = strings.TrimRight(baseURL, "/") + if !strings.Contains(baseURL, "://") { + baseURL = "http://" + baseURL + } + return &client{ + baseURL: baseURL, + client: &http.Client{}, + authorizer: authorizer, + } +} + +type client struct { + baseURL string + client *http.Client + authorizer auth.Authorizer +} + +// do creates request and authorizes it if authorizer is not nil +func (c *client) do(method, relativePath string, body io.Reader) (*http.Response, error) { + url := c.baseURL + relativePath + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + + if c.authorizer != nil { + if err := c.authorizer.Authorize(req); err != nil { + return nil, err + } + } + return c.client.Do(req) +} + +func (c *client) Ping() error { + addr := strings.Split(c.baseURL, "://")[1] + if !strings.Contains(addr, ":") { + addr = addr + ":80" + } + + return utils.TestTCPConn(addr, 60, 2) +} + +// GetCfgs ... +func (c *client) GetCfgs() (map[string]interface{}, error) { + resp, err := c.do(http.MethodGet, "/api/configurations", nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get configurations: %d %s", + resp.StatusCode, b) + } + + cfgs := map[string]interface{}{} + if err = json.Unmarshal(b, &cfgs); err != nil { + return nil, err + } + + return cfgs, nil +} + +// UpdateCfgs ... +func (c *client) UpdateCfgs(cfgs map[string]interface{}) error { + data, err := json.Marshal(cfgs) + if err != nil { + return err + } + + resp, err := c.do(http.MethodPut, "/api/configurations", bytes.NewReader(data)) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + return fmt.Errorf("failed to update configurations: %d %s", + resp.StatusCode, b) + } + + return nil +} + +// ResetCfgs ... +func (c *client) ResetCfgs() error { + resp, err := c.do(http.MethodPost, "/api/configurations/reset", nil) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + return fmt.Errorf("failed to reset configurations: %d %s", + resp.StatusCode, b) + } + + return nil +} + +// Capacity ... +func (c *client) Capacity() (*imagestorage.Capacity, error) { + resp, err := c.do(http.MethodGet, "/api/systeminfo/capacity", nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get capacity: %d %s", + resp.StatusCode, b) + } + + capacity := &imagestorage.Capacity{} + if err = json.Unmarshal(b, capacity); err != nil { + return nil, err + } + + return capacity, nil +} diff --git a/src/adminserver/client/client_test.go b/src/adminserver/client/client_test.go new file mode 100644 index 000000000..7c82c231c --- /dev/null +++ b/src/adminserver/client/client_test.go @@ -0,0 +1,82 @@ +/* + Copyright (c) 2016 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 client + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/utils/test" +) + +var c Client + +func TestMain(m *testing.M) { + + server, err := test.NewAdminserver(nil) + if err != nil { + fmt.Printf("failed to create adminserver: %v", err) + os.Exit(1) + } + + c = NewClient(server.URL, nil) + + os.Exit(m.Run()) +} + +func TestPing(t *testing.T) { + err := c.Ping() + assert.Nil(t, err, "unexpected error") +} + +func TestGetCfgs(t *testing.T) { + cfgs, err := c.GetCfgs() + if !assert.Nil(t, err, "unexpected error") { + return + } + + assert.Equal(t, common.DBAuth, cfgs[common.AUTHMode], "unexpected configuration") +} + +func TestUpdateCfgs(t *testing.T) { + cfgs := map[string]interface{}{ + common.AUTHMode: common.LDAPAuth, + } + err := c.UpdateCfgs(cfgs) + if !assert.Nil(t, err, "unexpected error") { + return + } +} + +func TestResetCfgs(t *testing.T) { + err := c.ResetCfgs() + if !assert.Nil(t, err, "unexpected error") { + return + } +} + +func TestCapacity(t *testing.T) { + capacity, err := c.Capacity() + if !assert.Nil(t, err, "unexpected error") { + return + } + + assert.Equal(t, uint64(100), capacity.Total) + assert.Equal(t, uint64(90), capacity.Free) +} diff --git a/src/adminserver/systemcfg/systemcfg.go b/src/adminserver/systemcfg/systemcfg.go index 2d33b063e..51de78d34 100644 --- a/src/adminserver/systemcfg/systemcfg.go +++ b/src/adminserver/systemcfg/systemcfg.go @@ -23,6 +23,7 @@ import ( "github.com/vmware/harbor/src/adminserver/systemcfg/store" "github.com/vmware/harbor/src/adminserver/systemcfg/store/json" + "github.com/vmware/harbor/src/common" comcfg "github.com/vmware/harbor/src/common/config" "github.com/vmware/harbor/src/common/utils" "github.com/vmware/harbor/src/common/utils/log" @@ -40,82 +41,82 @@ var ( // attrs need to be encrypted or decrypted attrs = []string{ - comcfg.EmailPassword, - comcfg.LDAPSearchPwd, - comcfg.MySQLPassword, - comcfg.AdminInitialPassword, + common.EmailPassword, + common.LDAPSearchPwd, + common.MySQLPassword, + common.AdminInitialPassword, } // all configurations need read from environment variables allEnvs = map[string]interface{}{ - comcfg.ExtEndpoint: "EXT_ENDPOINT", - comcfg.AUTHMode: "AUTH_MODE", - comcfg.SelfRegistration: &parser{ + common.ExtEndpoint: "EXT_ENDPOINT", + common.AUTHMode: "AUTH_MODE", + common.SelfRegistration: &parser{ env: "SELF_REGISTRATION", parse: parseStringToBool, }, - comcfg.DatabaseType: "DATABASE_TYPE", - comcfg.MySQLHost: "MYSQL_HOST", - comcfg.MySQLPort: &parser{ + common.DatabaseType: "DATABASE_TYPE", + common.MySQLHost: "MYSQL_HOST", + common.MySQLPort: &parser{ env: "MYSQL_PORT", parse: parseStringToInt, }, - comcfg.MySQLUsername: "MYSQL_USR", - comcfg.MySQLPassword: "MYSQL_PWD", - comcfg.MySQLDatabase: "MYSQL_DATABASE", - comcfg.SQLiteFile: "SQLITE_FILE", - comcfg.LDAPURL: "LDAP_URL", - comcfg.LDAPSearchDN: "LDAP_SEARCH_DN", - comcfg.LDAPSearchPwd: "LDAP_SEARCH_PWD", - comcfg.LDAPBaseDN: "LDAP_BASE_DN", - comcfg.LDAPFilter: "LDAP_FILTER", - comcfg.LDAPUID: "LDAP_UID", - comcfg.LDAPScope: &parser{ + common.MySQLUsername: "MYSQL_USR", + common.MySQLPassword: "MYSQL_PWD", + common.MySQLDatabase: "MYSQL_DATABASE", + common.SQLiteFile: "SQLITE_FILE", + common.LDAPURL: "LDAP_URL", + common.LDAPSearchDN: "LDAP_SEARCH_DN", + common.LDAPSearchPwd: "LDAP_SEARCH_PWD", + common.LDAPBaseDN: "LDAP_BASE_DN", + common.LDAPFilter: "LDAP_FILTER", + common.LDAPUID: "LDAP_UID", + common.LDAPScope: &parser{ env: "LDAP_SCOPE", parse: parseStringToInt, }, - comcfg.LDAPTimeout: &parser{ + common.LDAPTimeout: &parser{ env: "LDAP_TIMEOUT", parse: parseStringToInt, }, - comcfg.EmailHost: "EMAIL_HOST", - comcfg.EmailPort: &parser{ + common.EmailHost: "EMAIL_HOST", + common.EmailPort: &parser{ env: "EMAIL_PORT", parse: parseStringToInt, }, - comcfg.EmailUsername: "EMAIL_USR", - comcfg.EmailPassword: "EMAIL_PWD", - comcfg.EmailSSL: &parser{ + common.EmailUsername: "EMAIL_USR", + common.EmailPassword: "EMAIL_PWD", + common.EmailSSL: &parser{ env: "EMAIL_SSL", parse: parseStringToBool, }, - comcfg.EmailFrom: "EMAIL_FROM", - comcfg.EmailIdentity: "EMAIL_IDENTITY", - comcfg.RegistryURL: "REGISTRY_URL", - comcfg.TokenExpiration: &parser{ + common.EmailFrom: "EMAIL_FROM", + common.EmailIdentity: "EMAIL_IDENTITY", + common.RegistryURL: "REGISTRY_URL", + common.TokenExpiration: &parser{ env: "TOKEN_EXPIRATION", parse: parseStringToInt, }, - comcfg.UseCompressedJS: &parser{ + common.UseCompressedJS: &parser{ env: "USE_COMPRESSED_JS", parse: parseStringToBool, }, - comcfg.CfgExpiration: &parser{ + common.CfgExpiration: &parser{ env: "CFG_EXPIRATION", parse: parseStringToInt, }, - comcfg.MaxJobWorkers: &parser{ + common.MaxJobWorkers: &parser{ env: "MAX_JOB_WORKERS", parse: parseStringToInt, }, - comcfg.VerifyRemoteCert: &parser{ + common.VerifyRemoteCert: &parser{ env: "VERIFY_REMOTE_CERT", parse: parseStringToBool, }, - comcfg.ProjectCreationRestriction: "PROJECT_CREATION_RESTRICTION", - comcfg.AdminInitialPassword: "HARBOR_ADMIN_PASSWORD", - comcfg.AdmiralEndpoint: "ADMIRAL_URL", - comcfg.WithNotary: &parser{ + common.ProjectCreationRestriction: "PROJECT_CREATION_RESTRICTION", + common.AdminInitialPassword: "HARBOR_ADMIN_PASSWORD", + common.AdmiralEndpoint: "ADMIRAL_URL", + common.WithNotary: &parser{ env: "WITH_NOTARY", parse: parseStringToBool, }, @@ -124,23 +125,23 @@ var ( // configurations need read from environment variables // every time the system startup repeatLoadEnvs = map[string]interface{}{ - comcfg.ExtEndpoint: "EXT_ENDPOINT", - comcfg.MySQLPassword: "MYSQL_PWD", - comcfg.MaxJobWorkers: &parser{ + common.ExtEndpoint: "EXT_ENDPOINT", + common.MySQLPassword: "MYSQL_PWD", + common.MaxJobWorkers: &parser{ env: "MAX_JOB_WORKERS", parse: parseStringToInt, }, // TODO remove this config? - comcfg.UseCompressedJS: &parser{ + common.UseCompressedJS: &parser{ env: "USE_COMPRESSED_JS", parse: parseStringToBool, }, - comcfg.CfgExpiration: &parser{ + common.CfgExpiration: &parser{ env: "CFG_EXPIRATION", parse: parseStringToInt, }, - comcfg.AdmiralEndpoint: "ADMIRAL_URL", - comcfg.WithNotary: &parser{ + common.AdmiralEndpoint: "ADMIRAL_URL", + common.WithNotary: &parser{ env: "WITH_NOTARY", parse: parseStringToBool, }, diff --git a/src/adminserver/systemcfg/systemcfg_test.go b/src/adminserver/systemcfg/systemcfg_test.go index d65e19ea3..1d41fe7bf 100644 --- a/src/adminserver/systemcfg/systemcfg_test.go +++ b/src/adminserver/systemcfg/systemcfg_test.go @@ -19,7 +19,7 @@ import ( "os" "testing" - comcfg "github.com/vmware/harbor/src/common/config" + "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/utils/test" ) @@ -54,7 +54,7 @@ func TestSystemcfg(t *testing.T) { } m := map[string]string{ - "AUTH_MODE": comcfg.DBAuth, + "AUTH_MODE": common.DBAuth, "LDAP_SCOPE": "1", "LDAP_TIMEOUT": "30", "MYSQL_PORT": "3306", @@ -93,13 +93,13 @@ func TestSystemcfg(t *testing.T) { return } - if cfg[comcfg.AUTHMode] != comcfg.DBAuth { + if cfg[common.AUTHMode] != common.DBAuth { t.Errorf("unexpected auth mode: %s != %s", - cfg[comcfg.AUTHMode], comcfg.DBAuth) + cfg[common.AUTHMode], common.DBAuth) return } - cfg[comcfg.AUTHMode] = comcfg.LDAPAuth + cfg[common.AUTHMode] = common.LDAPAuth if err = UpdateSystemCfg(cfg); err != nil { t.Errorf("failed to update system configurations: %v", err) @@ -112,9 +112,9 @@ func TestSystemcfg(t *testing.T) { return } - if cfg[comcfg.AUTHMode] != comcfg.LDAPAuth { + if cfg[common.AUTHMode] != common.LDAPAuth { t.Errorf("unexpected auth mode: %s != %s", - cfg[comcfg.AUTHMode], comcfg.DBAuth) + cfg[common.AUTHMode], common.DBAuth) return } @@ -129,9 +129,9 @@ func TestSystemcfg(t *testing.T) { return } - if cfg[comcfg.AUTHMode] != comcfg.DBAuth { + if cfg[common.AUTHMode] != common.DBAuth { t.Errorf("unexpected auth mode: %s != %s", - cfg[comcfg.AUTHMode], comcfg.DBAuth) + cfg[common.AUTHMode], common.DBAuth) return } } diff --git a/src/common/config/config.go b/src/common/config/config.go index 55baccd6d..3e8faeecf 100644 --- a/src/common/config/config.go +++ b/src/common/config/config.go @@ -17,83 +17,26 @@ package config import ( - "bytes" - "encoding/json" "fmt" - "io/ioutil" - "net/http" - "strings" "time" "github.com/astaxie/beego/cache" - "github.com/vmware/harbor/src/common/utils" - "github.com/vmware/harbor/src/common/utils/log" -) - -// const variables -const ( - DBAuth = "db_auth" - LDAPAuth = "ldap_auth" - ProCrtRestrEveryone = "everyone" - ProCrtRestrAdmOnly = "adminonly" - LDAPScopeBase = "1" - LDAPScopeOnelevel = "2" - LDAPScopeSubtree = "3" - - ExtEndpoint = "ext_endpoint" - AUTHMode = "auth_mode" - DatabaseType = "database_type" - MySQLHost = "mysql_host" - MySQLPort = "mysql_port" - MySQLUsername = "mysql_username" - MySQLPassword = "mysql_password" - MySQLDatabase = "mysql_database" - SQLiteFile = "sqlite_file" - SelfRegistration = "self_registration" - LDAPURL = "ldap_url" - LDAPSearchDN = "ldap_search_dn" - LDAPSearchPwd = "ldap_search_password" - LDAPBaseDN = "ldap_base_dn" - LDAPUID = "ldap_uid" - LDAPFilter = "ldap_filter" - LDAPScope = "ldap_scope" - LDAPTimeout = "ldap_timeout" - TokenServiceURL = "token_service_url" - RegistryURL = "registry_url" - EmailHost = "email_host" - EmailPort = "email_port" - EmailUsername = "email_username" - EmailPassword = "email_password" - EmailFrom = "email_from" - EmailSSL = "email_ssl" - EmailIdentity = "email_identity" - ProjectCreationRestriction = "project_creation_restriction" - VerifyRemoteCert = "verify_remote_cert" - MaxJobWorkers = "max_job_workers" - TokenExpiration = "token_expiration" - CfgExpiration = "cfg_expiration" - JobLogDir = "job_log_dir" - UseCompressedJS = "use_compressed_js" - AdminInitialPassword = "admin_initial_password" - AdmiralEndpoint = "admiral_url" - WithNotary = "with_notary" + "github.com/vmware/harbor/src/adminserver/client" + "github.com/vmware/harbor/src/common" ) // Manager manages configurations type Manager struct { - Loader *Loader - Parser *Parser + client client.Client Cache bool cache cache.Cache key string } // NewManager returns an instance of Manager -// url: the url from which loader loads configurations -func NewManager(url, secret string, enableCache bool) *Manager { +func NewManager(client client.Client, enableCache bool) *Manager { m := &Manager{ - Loader: NewLoader(url, secret), - Parser: &Parser{}, + client: client, } if enableCache { @@ -105,19 +48,9 @@ func NewManager(url, secret string, enableCache bool) *Manager { return m } -// Init loader -func (m *Manager) Init() error { - return m.Loader.Init() -} - // Load configurations, if cache is enabled, cache the configurations func (m *Manager) Load() (map[string]interface{}, error) { - b, err := m.Loader.Load() - if err != nil { - return nil, err - } - - c, err := m.Parser.Parse(b) + c, err := m.client.GetCfgs() if err != nil { return nil, err } @@ -127,7 +60,15 @@ func (m *Manager) Load() (map[string]interface{}, error) { if err != nil { return nil, err } - if err = m.cache.Put(m.key, c, + + // copy the configuration map so that later modification to the + // map does not effect the cached value + cachedCfgs := map[string]interface{}{} + for k, v := range c { + cachedCfgs[k] = v + } + + if err = m.cache.Put(m.key, cachedCfgs, time.Duration(expi)*time.Second); err != nil { return nil, err } @@ -138,7 +79,7 @@ func (m *Manager) Load() (map[string]interface{}, error) { // Reset configurations func (m *Manager) Reset() error { - return m.Loader.Reset() + return m.client.ResetCfgs() } func getCfgExpiration(m map[string]interface{}) (int, error) { @@ -146,7 +87,7 @@ func getCfgExpiration(m map[string]interface{}) (int, error) { return 0, fmt.Errorf("can not get cfg expiration as configurations are null") } - expi, ok := m[CfgExpiration] + expi, ok := m[common.CfgExpiration] if !ok { return 0, fmt.Errorf("cfg expiration is not set") } @@ -167,133 +108,6 @@ func (m *Manager) Get() (map[string]interface{}, error) { } // Upload configurations -func (m *Manager) Upload(b []byte) error { - return m.Loader.Upload(b) -} - -// Loader loads and uploads configurations -type Loader struct { - url string - secret string - client *http.Client -} - -// NewLoader ... -func NewLoader(url, secret string) *Loader { - return &Loader{ - url: url, - secret: secret, - client: &http.Client{}, - } -} - -// Init waits remote server to be ready by testing connections with it -func (l *Loader) Init() error { - addr := l.url - if strings.Contains(addr, "://") { - addr = strings.Split(addr, "://")[1] - } - - if !strings.Contains(addr, ":") { - addr = addr + ":80" - } - - return utils.TestTCPConn(addr, 60, 2) -} - -// Load configurations from remote server -func (l *Loader) Load() ([]byte, error) { - log.Debug("loading configurations...") - req, err := http.NewRequest("GET", l.url+"/api/configurations", nil) - if err != nil { - return nil, err - } - - req.AddCookie(&http.Cookie{ - Name: "secret", - Value: l.secret, - }) - - resp, err := l.client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("%d", resp.StatusCode) - } - - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - log.Debug("configurations load completed") - return b, nil -} - -// Upload configurations to remote server -func (l *Loader) Upload(b []byte) error { - req, err := http.NewRequest("PUT", l.url+"/api/configurations", bytes.NewReader(b)) - if err != nil { - return err - } - - req.AddCookie(&http.Cookie{ - Name: "secret", - Value: l.secret, - }) - - resp, err := l.client.Do(req) - if err != nil { - return err - } - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("unexpected http status code: %d", resp.StatusCode) - } - - log.Debug("configurations uploaded") - - return nil -} - -// Reset sends configurations resetting command to -// remote server -func (l *Loader) Reset() error { - req, err := http.NewRequest("POST", l.url+"/api/configurations/reset", nil) - if err != nil { - return err - } - - req.AddCookie(&http.Cookie{ - Name: "secret", - Value: l.secret, - }) - - resp, err := l.client.Do(req) - if err != nil { - return err - } - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("unexpected http status code: %d", resp.StatusCode) - } - - log.Debug("configurations resetted") - - return nil -} - -// Parser parses configurations -type Parser struct { -} - -// Parse parses []byte to a map configuration -func (p *Parser) Parse(b []byte) (map[string]interface{}, error) { - c := map[string]interface{}{} - if err := json.Unmarshal(b, &c); err != nil { - return nil, err - } - return c, nil +func (m *Manager) Upload(cfgs map[string]interface{}) error { + return m.client.UpdateCfgs(cfgs) } diff --git a/src/common/const.go b/src/common/const.go new file mode 100644 index 000000000..6c18fc58d --- /dev/null +++ b/src/common/const.go @@ -0,0 +1,65 @@ +/* + Copyright (c) 2016 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 common + +// const variables +const ( + DBAuth = "db_auth" + LDAPAuth = "ldap_auth" + ProCrtRestrEveryone = "everyone" + ProCrtRestrAdmOnly = "adminonly" + LDAPScopeBase = "1" + LDAPScopeOnelevel = "2" + LDAPScopeSubtree = "3" + + ExtEndpoint = "ext_endpoint" + AUTHMode = "auth_mode" + DatabaseType = "database_type" + MySQLHost = "mysql_host" + MySQLPort = "mysql_port" + MySQLUsername = "mysql_username" + MySQLPassword = "mysql_password" + MySQLDatabase = "mysql_database" + SQLiteFile = "sqlite_file" + SelfRegistration = "self_registration" + LDAPURL = "ldap_url" + LDAPSearchDN = "ldap_search_dn" + LDAPSearchPwd = "ldap_search_password" + LDAPBaseDN = "ldap_base_dn" + LDAPUID = "ldap_uid" + LDAPFilter = "ldap_filter" + LDAPScope = "ldap_scope" + LDAPTimeout = "ldap_timeout" + TokenServiceURL = "token_service_url" + RegistryURL = "registry_url" + EmailHost = "email_host" + EmailPort = "email_port" + EmailUsername = "email_username" + EmailPassword = "email_password" + EmailFrom = "email_from" + EmailSSL = "email_ssl" + EmailIdentity = "email_identity" + ProjectCreationRestriction = "project_creation_restriction" + VerifyRemoteCert = "verify_remote_cert" + MaxJobWorkers = "max_job_workers" + TokenExpiration = "token_expiration" + CfgExpiration = "cfg_expiration" + JobLogDir = "job_log_dir" + UseCompressedJS = "use_compressed_js" + AdminInitialPassword = "admin_initial_password" + AdmiralEndpoint = "admiral_url" + WithNotary = "with_notary" +) diff --git a/src/common/utils/ldap/ldap_test.go b/src/common/utils/ldap/ldap_test.go index 7fb3f3143..60cf7c34a 100644 --- a/src/common/utils/ldap/ldap_test.go +++ b/src/common/utils/ldap/ldap_test.go @@ -21,7 +21,7 @@ import ( "os" "testing" - "github.com/vmware/harbor/src/common/config" + "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/common/utils/log" @@ -30,24 +30,24 @@ import ( ) var adminServerLdapTestConfig = map[string]interface{}{ - config.ExtEndpoint: "host01.com", - config.AUTHMode: "ldap_auth", - config.DatabaseType: "mysql", - config.MySQLHost: "127.0.0.1", - config.MySQLPort: 3306, - config.MySQLUsername: "root", - config.MySQLPassword: "root123", - config.MySQLDatabase: "registry", - config.SQLiteFile: "/tmp/registry.db", + common.ExtEndpoint: "host01.com", + common.AUTHMode: "ldap_auth", + common.DatabaseType: "mysql", + common.MySQLHost: "127.0.0.1", + common.MySQLPort: 3306, + common.MySQLUsername: "root", + common.MySQLPassword: "root123", + common.MySQLDatabase: "registry", + common.SQLiteFile: "/tmp/registry.db", //config.SelfRegistration: true, - config.LDAPURL: "ldap://127.0.0.1", - config.LDAPSearchDN: "cn=admin,dc=example,dc=com", - config.LDAPSearchPwd: "admin", - config.LDAPBaseDN: "dc=example,dc=com", - config.LDAPUID: "uid", - config.LDAPFilter: "", - config.LDAPScope: 3, - config.LDAPTimeout: 30, + common.LDAPURL: "ldap://127.0.0.1", + common.LDAPSearchDN: "cn=admin,dc=example,dc=com", + common.LDAPSearchPwd: "admin", + common.LDAPBaseDN: "dc=example,dc=com", + common.LDAPUID: "uid", + common.LDAPFilter: "", + common.LDAPScope: 3, + common.LDAPTimeout: 30, // config.TokenServiceURL: "", // config.RegistryURL: "", // config.EmailHost: "", @@ -61,10 +61,10 @@ var adminServerLdapTestConfig = map[string]interface{}{ // config.VerifyRemoteCert: false, // config.MaxJobWorkers: 3, // config.TokenExpiration: 30, - config.CfgExpiration: 5, + common.CfgExpiration: 5, // config.JobLogDir: "/var/log/jobs", // config.UseCompressedJS: true, - config.AdminInitialPassword: "password", + common.AdminInitialPassword: "password", } func TestMain(t *testing.T) { diff --git a/src/common/utils/test/adminserver.go b/src/common/utils/test/adminserver.go index 3b683b5d8..0a0741d1d 100644 --- a/src/common/utils/test/adminserver.go +++ b/src/common/utils/test/adminserver.go @@ -20,46 +20,47 @@ import ( "net/http" "net/http/httptest" - "github.com/vmware/harbor/src/common/config" + "github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage" + "github.com/vmware/harbor/src/common" ) var adminServerDefaultConfig = map[string]interface{}{ - config.ExtEndpoint: "https://host01.com", - config.AUTHMode: config.DBAuth, - config.DatabaseType: "mysql", - config.MySQLHost: "127.0.0.1", - config.MySQLPort: 3306, - config.MySQLUsername: "user01", - config.MySQLPassword: "password", - config.MySQLDatabase: "registry", - config.SQLiteFile: "/tmp/registry.db", - config.SelfRegistration: true, - config.LDAPURL: "ldap://127.0.0.1", - config.LDAPSearchDN: "uid=searchuser,ou=people,dc=mydomain,dc=com", - config.LDAPSearchPwd: "password", - config.LDAPBaseDN: "ou=people,dc=mydomain,dc=com", - config.LDAPUID: "uid", - config.LDAPFilter: "", - config.LDAPScope: 3, - config.LDAPTimeout: 30, - config.TokenServiceURL: "http://token_service", - config.RegistryURL: "http://registry", - config.EmailHost: "127.0.0.1", - config.EmailPort: 25, - config.EmailUsername: "user01", - config.EmailPassword: "password", - config.EmailFrom: "from", - config.EmailSSL: true, - config.EmailIdentity: "", - config.ProjectCreationRestriction: config.ProCrtRestrAdmOnly, - config.VerifyRemoteCert: false, - config.MaxJobWorkers: 3, - config.TokenExpiration: 30, - config.CfgExpiration: 5, - config.UseCompressedJS: true, - config.AdminInitialPassword: "password", - config.AdmiralEndpoint: "http://www.vmware.com", - config.WithNotary: false, + common.ExtEndpoint: "https://host01.com", + common.AUTHMode: common.DBAuth, + common.DatabaseType: "mysql", + common.MySQLHost: "127.0.0.1", + common.MySQLPort: 3306, + common.MySQLUsername: "user01", + common.MySQLPassword: "password", + common.MySQLDatabase: "registry", + common.SQLiteFile: "/tmp/registry.db", + common.SelfRegistration: true, + common.LDAPURL: "ldap://127.0.0.1", + common.LDAPSearchDN: "uid=searchuser,ou=people,dc=mydomain,dc=com", + common.LDAPSearchPwd: "password", + common.LDAPBaseDN: "ou=people,dc=mydomain,dc=com", + common.LDAPUID: "uid", + common.LDAPFilter: "", + common.LDAPScope: 3, + common.LDAPTimeout: 30, + common.TokenServiceURL: "http://token_service", + common.RegistryURL: "http://registry", + common.EmailHost: "127.0.0.1", + common.EmailPort: 25, + common.EmailUsername: "user01", + common.EmailPassword: "password", + common.EmailFrom: "from", + common.EmailSSL: true, + common.EmailIdentity: "", + common.ProjectCreationRestriction: common.ProCrtRestrAdmOnly, + common.VerifyRemoteCert: false, + common.MaxJobWorkers: 3, + common.TokenExpiration: 30, + common.CfgExpiration: 5, + common.UseCompressedJS: true, + common.AdminInitialPassword: "password", + common.AdmiralEndpoint: "http://www.vmware.com", + common.WithNotary: false, } // NewAdminserver returns a mock admin server @@ -101,5 +102,32 @@ func NewAdminserver(config map[string]interface{}) (*httptest.Server, error) { }), }) + capacityHandler, err := NewCapacityHandle() + if err != nil { + return nil, err + } + m = append(m, &RequestHandlerMapping{ + Method: "GET", + Pattern: "/api/systeminfo/capacity", + Handler: capacityHandler, + }) + return NewServer(m...), nil } + +// NewCapacityHandle ... +func NewCapacityHandle() (func(http.ResponseWriter, *http.Request), error) { + capacity := imagestorage.Capacity{ + Total: 100, + Free: 90, + } + b, err := json.Marshal(capacity) + if err != nil { + return nil, err + } + resp := &Response{ + StatusCode: http.StatusOK, + Body: b, + } + return Handler(resp), nil +} diff --git a/src/jobservice/config/config.go b/src/jobservice/config/config.go index d93871bf7..4cc10c45b 100644 --- a/src/jobservice/config/config.go +++ b/src/jobservice/config/config.go @@ -16,21 +16,28 @@ package config import ( + "fmt" "os" + "github.com/vmware/harbor/src/adminserver/client" + "github.com/vmware/harbor/src/adminserver/client/auth" + "github.com/vmware/harbor/src/common" comcfg "github.com/vmware/harbor/src/common/config" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils/log" ) const ( - defaultKeyPath string = "/etc/jobservice/key" - defaultLogDir string = "/var/log/jobs" + defaultKeyPath string = "/etc/jobservice/key" + defaultLogDir string = "/var/log/jobs" + secretCookieName string = "secret" ) var ( - mg *comcfg.Manager - keyProvider comcfg.KeyProvider + // AdminserverClient is a client for adminserver + AdminserverClient client.Client + mg *comcfg.Manager + keyProvider comcfg.KeyProvider ) // Init configurations @@ -42,12 +49,15 @@ func Init() error { if len(adminServerURL) == 0 { adminServerURL = "http://adminserver" } - mg = comcfg.NewManager(adminServerURL, JobserviceSecret(), true) - - if err := mg.Init(); err != nil { - return err + log.Infof("initializing client for adminserver %s ...", adminServerURL) + authorizer := auth.NewSecretAuthorizer(secretCookieName, UISecret()) + AdminserverClient = client.NewClient(adminServerURL, authorizer) + if err := AdminserverClient.Ping(); err != nil { + return fmt.Errorf("failed to ping adminserver: %v", err) } + mg = comcfg.NewManager(AdminserverClient, true) + if _, err := mg.Load(); err != nil { return err } @@ -71,7 +81,7 @@ func VerifyRemoteCert() (bool, error) { if err != nil { return true, err } - return cfg[comcfg.VerifyRemoteCert].(bool), nil + return cfg[common.VerifyRemoteCert].(bool), nil } // Database ... @@ -81,16 +91,16 @@ func Database() (*models.Database, error) { return nil, err } database := &models.Database{} - database.Type = cfg[comcfg.DatabaseType].(string) + database.Type = cfg[common.DatabaseType].(string) mysql := &models.MySQL{} - mysql.Host = cfg[comcfg.MySQLHost].(string) - mysql.Port = int(cfg[comcfg.MySQLPort].(float64)) - mysql.Username = cfg[comcfg.MySQLUsername].(string) - mysql.Password = cfg[comcfg.MySQLPassword].(string) - mysql.Database = cfg[comcfg.MySQLDatabase].(string) + mysql.Host = cfg[common.MySQLHost].(string) + mysql.Port = int(cfg[common.MySQLPort].(float64)) + mysql.Username = cfg[common.MySQLUsername].(string) + mysql.Password = cfg[common.MySQLPassword].(string) + mysql.Database = cfg[common.MySQLDatabase].(string) database.MySQL = mysql sqlite := &models.SQLite{} - sqlite.File = cfg[comcfg.SQLiteFile].(string) + sqlite.File = cfg[common.SQLiteFile].(string) database.SQLite = sqlite return database, nil @@ -102,7 +112,7 @@ func MaxJobWorkers() (int, error) { if err != nil { return 0, err } - return int(cfg[comcfg.MaxJobWorkers].(float64)), nil + return int(cfg[common.MaxJobWorkers].(float64)), nil } // LocalUIURL returns the local ui url, job service will use this URL to call API hosted on ui process @@ -116,7 +126,7 @@ func LocalRegURL() (string, error) { if err != nil { return "", err } - return cfg[comcfg.RegistryURL].(string), nil + return cfg[common.RegistryURL].(string), nil } // LogDir returns the absolute path to which the log file will be written @@ -151,7 +161,7 @@ func ExtEndpoint() (string, error) { if err != nil { return "", err } - return cfg[comcfg.ExtEndpoint].(string), nil + return cfg[common.ExtEndpoint].(string), nil } // InternalTokenServiceEndpoint ... diff --git a/src/ui/api/config.go b/src/ui/api/config.go index 3411f1749..22ccea112 100644 --- a/src/ui/api/config.go +++ b/src/ui/api/config.go @@ -20,8 +20,8 @@ import ( "net/http" "strconv" + "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/api" - comcfg "github.com/vmware/harbor/src/common/config" "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/ui/config" @@ -30,65 +30,65 @@ import ( var ( // valid keys of configurations which user can modify validKeys = []string{ - comcfg.ExtEndpoint, - comcfg.AUTHMode, - comcfg.DatabaseType, - comcfg.MySQLHost, - comcfg.MySQLPort, - comcfg.MySQLUsername, - comcfg.MySQLPassword, - comcfg.MySQLDatabase, - comcfg.SQLiteFile, - comcfg.SelfRegistration, - comcfg.LDAPURL, - comcfg.LDAPSearchDN, - comcfg.LDAPSearchPwd, - comcfg.LDAPBaseDN, - comcfg.LDAPUID, - comcfg.LDAPFilter, - comcfg.LDAPScope, - comcfg.LDAPTimeout, - comcfg.TokenServiceURL, - comcfg.RegistryURL, - comcfg.EmailHost, - comcfg.EmailPort, - comcfg.EmailUsername, - comcfg.EmailPassword, - comcfg.EmailFrom, - comcfg.EmailSSL, - comcfg.EmailIdentity, - comcfg.ProjectCreationRestriction, - comcfg.VerifyRemoteCert, - comcfg.MaxJobWorkers, - comcfg.TokenExpiration, - comcfg.CfgExpiration, - comcfg.JobLogDir, - comcfg.UseCompressedJS, - comcfg.AdminInitialPassword, + common.ExtEndpoint, + common.AUTHMode, + common.DatabaseType, + common.MySQLHost, + common.MySQLPort, + common.MySQLUsername, + common.MySQLPassword, + common.MySQLDatabase, + common.SQLiteFile, + common.SelfRegistration, + common.LDAPURL, + common.LDAPSearchDN, + common.LDAPSearchPwd, + common.LDAPBaseDN, + common.LDAPUID, + common.LDAPFilter, + common.LDAPScope, + common.LDAPTimeout, + common.TokenServiceURL, + common.RegistryURL, + common.EmailHost, + common.EmailPort, + common.EmailUsername, + common.EmailPassword, + common.EmailFrom, + common.EmailSSL, + common.EmailIdentity, + common.ProjectCreationRestriction, + common.VerifyRemoteCert, + common.MaxJobWorkers, + common.TokenExpiration, + common.CfgExpiration, + common.JobLogDir, + common.UseCompressedJS, + common.AdminInitialPassword, } numKeys = []string{ - comcfg.EmailPort, - comcfg.LDAPScope, - comcfg.LDAPTimeout, - comcfg.MySQLPort, - comcfg.MaxJobWorkers, - comcfg.TokenExpiration, - comcfg.CfgExpiration, + common.EmailPort, + common.LDAPScope, + common.LDAPTimeout, + common.MySQLPort, + common.MaxJobWorkers, + common.TokenExpiration, + common.CfgExpiration, } boolKeys = []string{ - comcfg.EmailSSL, - comcfg.SelfRegistration, - comcfg.VerifyRemoteCert, - comcfg.UseCompressedJS, + common.EmailSSL, + common.SelfRegistration, + common.VerifyRemoteCert, + common.UseCompressedJS, } passwordKeys = []string{ - comcfg.AdminInitialPassword, - comcfg.EmailPassword, - comcfg.LDAPSearchPwd, - comcfg.MySQLPassword, + common.AdminInitialPassword, + common.EmailPassword, + common.LDAPSearchPwd, + common.MySQLPassword, } ) @@ -157,7 +157,7 @@ func (c *ConfigAPI) Put() { c.CustomAbort(http.StatusBadRequest, err.Error()) } - if value, ok := cfg[comcfg.AUTHMode]; ok { + if value, ok := cfg[common.AUTHMode]; ok { mode, err := config.AuthMode() if err != nil { log.Errorf("failed to get auth mode: %v", err) @@ -174,7 +174,7 @@ func (c *ConfigAPI) Put() { if !flag { c.CustomAbort(http.StatusBadRequest, fmt.Sprintf("%s can not be modified as new users have been inserted into database", - comcfg.AUTHMode)) + common.AUTHMode)) } } } @@ -213,14 +213,14 @@ func validateCfg(c map[string]string) (bool, error) { return isSysErr, err } - if value, ok := c[comcfg.AUTHMode]; ok { - if value != comcfg.DBAuth && value != comcfg.LDAPAuth { - return isSysErr, fmt.Errorf("invalid %s, shoud be %s or %s", comcfg.AUTHMode, comcfg.DBAuth, comcfg.LDAPAuth) + if value, ok := c[common.AUTHMode]; ok { + if value != common.DBAuth && value != common.LDAPAuth { + return isSysErr, fmt.Errorf("invalid %s, shoud be %s or %s", common.AUTHMode, common.DBAuth, common.LDAPAuth) } mode = value } - if mode == comcfg.LDAPAuth { + if mode == common.LDAPAuth { ldap, err := config.LDAP() if err != nil { isSysErr = true @@ -228,46 +228,46 @@ func validateCfg(c map[string]string) (bool, error) { } if len(ldap.URL) == 0 { - if _, ok := c[comcfg.LDAPURL]; !ok { - return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPURL) + if _, ok := c[common.LDAPURL]; !ok { + return isSysErr, fmt.Errorf("%s is missing", common.LDAPURL) } } if len(ldap.BaseDN) == 0 { - if _, ok := c[comcfg.LDAPBaseDN]; !ok { - return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPBaseDN) + if _, ok := c[common.LDAPBaseDN]; !ok { + return isSysErr, fmt.Errorf("%s is missing", common.LDAPBaseDN) } } if len(ldap.UID) == 0 { - if _, ok := c[comcfg.LDAPUID]; !ok { - return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPUID) + if _, ok := c[common.LDAPUID]; !ok { + return isSysErr, fmt.Errorf("%s is missing", common.LDAPUID) } } if ldap.Scope == 0 { - if _, ok := c[comcfg.LDAPScope]; !ok { - return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPScope) + if _, ok := c[common.LDAPScope]; !ok { + return isSysErr, fmt.Errorf("%s is missing", common.LDAPScope) } } } - if ldapURL, ok := c[comcfg.LDAPURL]; ok && len(ldapURL) == 0 { - return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPURL) + if ldapURL, ok := c[common.LDAPURL]; ok && len(ldapURL) == 0 { + return isSysErr, fmt.Errorf("%s is empty", common.LDAPURL) } - if baseDN, ok := c[comcfg.LDAPBaseDN]; ok && len(baseDN) == 0 { - return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPBaseDN) + if baseDN, ok := c[common.LDAPBaseDN]; ok && len(baseDN) == 0 { + return isSysErr, fmt.Errorf("%s is empty", common.LDAPBaseDN) } - if uID, ok := c[comcfg.LDAPUID]; ok && len(uID) == 0 { - return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPUID) + if uID, ok := c[common.LDAPUID]; ok && len(uID) == 0 { + return isSysErr, fmt.Errorf("%s is empty", common.LDAPUID) } - if scope, ok := c[comcfg.LDAPScope]; ok && - scope != comcfg.LDAPScopeBase && - scope != comcfg.LDAPScopeOnelevel && - scope != comcfg.LDAPScopeSubtree { + if scope, ok := c[common.LDAPScope]; ok && + scope != common.LDAPScopeBase && + scope != common.LDAPScopeOnelevel && + scope != common.LDAPScopeSubtree { return isSysErr, fmt.Errorf("invalid %s, should be %s, %s or %s", - comcfg.LDAPScope, - comcfg.LDAPScopeBase, - comcfg.LDAPScopeOnelevel, - comcfg.LDAPScopeSubtree) + common.LDAPScope, + common.LDAPScopeBase, + common.LDAPScopeOnelevel, + common.LDAPScopeSubtree) } for _, k := range boolKeys { @@ -293,19 +293,19 @@ func validateCfg(c map[string]string) (bool, error) { return isSysErr, fmt.Errorf("invalid %s: %s", k, v) } - if (k == comcfg.EmailPort || - k == comcfg.MySQLPort) && n > 65535 { + if (k == common.EmailPort || + k == common.MySQLPort) && n > 65535 { return isSysErr, fmt.Errorf("invalid %s: %s", k, v) } } - if crt, ok := c[comcfg.ProjectCreationRestriction]; ok && - crt != comcfg.ProCrtRestrEveryone && - crt != comcfg.ProCrtRestrAdmOnly { + if crt, ok := c[common.ProjectCreationRestriction]; ok && + crt != common.ProCrtRestrEveryone && + crt != common.ProCrtRestrAdmOnly { return isSysErr, fmt.Errorf("invalid %s, should be %s or %s", - comcfg.ProjectCreationRestriction, - comcfg.ProCrtRestrAdmOnly, - comcfg.ProCrtRestrEveryone) + common.ProjectCreationRestriction, + common.ProCrtRestrAdmOnly, + common.ProCrtRestrEveryone) } return isSysErr, nil @@ -363,7 +363,7 @@ func convertForGet(cfg map[string]interface{}) (map[string]*value, error) { if err != nil { return nil, err } - result[comcfg.AUTHMode].Editable = flag + result[common.AUTHMode].Editable = flag return result, nil } diff --git a/src/ui/api/config_test.go b/src/ui/api/config_test.go index b4016e224..e6f3a6c10 100644 --- a/src/ui/api/config_test.go +++ b/src/ui/api/config_test.go @@ -20,7 +20,8 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/vmware/harbor/src/common/config" + "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/ui/config" ) func TestGetConfig(t *testing.T) { @@ -46,8 +47,13 @@ func TestGetConfig(t *testing.T) { return } - mode := cfg[config.AUTHMode].Value.(string) - assert.Equal(config.DBAuth, mode, fmt.Sprintf("the auth mode should be %s", config.DBAuth)) + mode := cfg[common.AUTHMode].Value.(string) + assert.Equal(common.DBAuth, mode, fmt.Sprintf("the auth mode should be %s", common.DBAuth)) + ccc, err := config.GetSystemCfg() + if err != nil { + t.Logf("failed to get system configurations: %v", err) + } + t.Logf("%v", ccc) } func TestPutConfig(t *testing.T) { @@ -56,7 +62,7 @@ func TestPutConfig(t *testing.T) { apiTest := newHarborAPI() cfg := map[string]string{ - config.VerifyRemoteCert: "0", + common.VerifyRemoteCert: "0", } code, err := apiTest.PutConfig(*admin, cfg) @@ -67,6 +73,11 @@ func TestPutConfig(t *testing.T) { if !assert.Equal(200, code, "the status code of modifying configurations with admin user should be 200") { return } + ccc, err := config.GetSystemCfg() + if err != nil { + t.Logf("failed to get system configurations: %v", err) + } + t.Logf("%v", ccc) } func TestResetConfig(t *testing.T) { @@ -94,11 +105,17 @@ func TestResetConfig(t *testing.T) { return } - value, ok := cfgs[config.VerifyRemoteCert] + value, ok := cfgs[common.VerifyRemoteCert] if !ok { - t.Errorf("%s not found", config.VerifyRemoteCert) + t.Errorf("%s not found", common.VerifyRemoteCert) return } assert.Equal(value.Value.(bool), true, "unexpected value") + + ccc, err := config.GetSystemCfg() + if err != nil { + t.Logf("failed to get system configurations: %v", err) + } + t.Logf("%v", ccc) } diff --git a/src/ui/api/systeminfo.go b/src/ui/api/systeminfo.go index 767d96d7f..b5da26c05 100644 --- a/src/ui/api/systeminfo.go +++ b/src/ui/api/systeminfo.go @@ -7,8 +7,8 @@ import ( "strings" "syscall" + "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/api" - comcfg "github.com/vmware/harbor/src/common/config" "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/ui/config" @@ -108,18 +108,18 @@ func (sia *SystemInfoAPI) GetGeneralInfo() { sia.CustomAbort(http.StatusInternalServerError, "Unexpected error") } var registryURL string - if l := strings.Split(cfg[comcfg.ExtEndpoint].(string), "://"); len(l) > 1 { + if l := strings.Split(cfg[common.ExtEndpoint].(string), "://"); len(l) > 1 { registryURL = l[1] } else { registryURL = l[0] } info := GeneralInfo{ - AdmiralEndpoint: cfg[comcfg.AdmiralEndpoint].(string), + AdmiralEndpoint: cfg[common.AdmiralEndpoint].(string), WithAdmiral: config.WithAdmiral(), WithNotary: config.WithNotary(), - AuthMode: cfg[comcfg.AUTHMode].(string), - ProjectCreationRestrict: cfg[comcfg.ProjectCreationRestriction].(string), - SelfRegistration: cfg[comcfg.SelfRegistration].(bool), + AuthMode: cfg[common.AUTHMode].(string), + ProjectCreationRestrict: cfg[common.ProjectCreationRestriction].(string), + SelfRegistration: cfg[common.SelfRegistration].(bool), RegistryURL: registryURL, } sia.Data["json"] = info diff --git a/src/ui/auth/ldap/ldap_test.go b/src/ui/auth/ldap/ldap_test.go index 11bb924ff..9b001ae55 100644 --- a/src/ui/auth/ldap/ldap_test.go +++ b/src/ui/auth/ldap/ldap_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/vmware/harbor/src/common/config" + "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/common/utils/log" @@ -15,24 +15,24 @@ import ( ) var adminServerLdapTestConfig = map[string]interface{}{ - config.ExtEndpoint: "host01.com", - config.AUTHMode: "ldap_auth", - config.DatabaseType: "mysql", - config.MySQLHost: "127.0.0.1", - config.MySQLPort: 3306, - config.MySQLUsername: "root", - config.MySQLPassword: "root123", - config.MySQLDatabase: "registry", - config.SQLiteFile: "/tmp/registry.db", + common.ExtEndpoint: "host01.com", + common.AUTHMode: "ldap_auth", + common.DatabaseType: "mysql", + common.MySQLHost: "127.0.0.1", + common.MySQLPort: 3306, + common.MySQLUsername: "root", + common.MySQLPassword: "root123", + common.MySQLDatabase: "registry", + common.SQLiteFile: "/tmp/registry.db", //config.SelfRegistration: true, - config.LDAPURL: "ldap://127.0.0.1", - config.LDAPSearchDN: "cn=admin,dc=example,dc=com", - config.LDAPSearchPwd: "admin", - config.LDAPBaseDN: "dc=example,dc=com", - config.LDAPUID: "uid", - config.LDAPFilter: "", - config.LDAPScope: 3, - config.LDAPTimeout: 30, + common.LDAPURL: "ldap://127.0.0.1", + common.LDAPSearchDN: "cn=admin,dc=example,dc=com", + common.LDAPSearchPwd: "admin", + common.LDAPBaseDN: "dc=example,dc=com", + common.LDAPUID: "uid", + common.LDAPFilter: "", + common.LDAPScope: 3, + common.LDAPTimeout: 30, // config.TokenServiceURL: "", // config.RegistryURL: "", // config.EmailHost: "", @@ -46,10 +46,10 @@ var adminServerLdapTestConfig = map[string]interface{}{ // config.VerifyRemoteCert: false, // config.MaxJobWorkers: 3, // config.TokenExpiration: 30, - config.CfgExpiration: 5, + common.CfgExpiration: 5, // config.JobLogDir: "/var/log/jobs", // config.UseCompressedJS: true, - config.AdminInitialPassword: "password", + common.AdminInitialPassword: "password", } func TestMain(t *testing.T) { diff --git a/src/ui/config/config.go b/src/ui/config/config.go index da9095e73..f500f9966 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -16,20 +16,28 @@ package config import ( - "encoding/json" + "fmt" "os" "strings" + "github.com/vmware/harbor/src/adminserver/client" + "github.com/vmware/harbor/src/adminserver/client/auth" + "github.com/vmware/harbor/src/common" comcfg "github.com/vmware/harbor/src/common/config" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils/log" ) -const defaultKeyPath string = "/etc/ui/key" +const ( + defaultKeyPath string = "/etc/ui/key" + secretCookieName string = "secret" +) var ( - mg *comcfg.Manager - keyProvider comcfg.KeyProvider + // AdminserverClient is a client for adminserver + AdminserverClient client.Client + mg *comcfg.Manager + keyProvider comcfg.KeyProvider ) // Init configurations @@ -41,14 +49,17 @@ func Init() error { if len(adminServerURL) == 0 { adminServerURL = "http://adminserver" } - log.Debugf("admin server URL: %s", adminServerURL) - mg = comcfg.NewManager(adminServerURL, UISecret(), true) - if err := mg.Init(); err != nil { - return err + log.Infof("initializing client for adminserver %s ...", adminServerURL) + authorizer := auth.NewSecretAuthorizer(secretCookieName, UISecret()) + AdminserverClient = client.NewClient(adminServerURL, authorizer) + if err := AdminserverClient.Ping(); err != nil { + return fmt.Errorf("failed to ping adminserver: %v", err) } - if _, err := mg.Load(); err != nil { + mg = comcfg.NewManager(AdminserverClient, true) + + if err := Load(); err != nil { return err } @@ -78,26 +89,12 @@ func Reset() error { // Upload uploads all system configutations to admin server func Upload(cfg map[string]interface{}) error { - b, err := json.Marshal(cfg) - if err != nil { - return err - } - return mg.Upload(b) + return mg.Upload(cfg) } // GetSystemCfg returns the system configurations func GetSystemCfg() (map[string]interface{}, error) { - raw, err := mg.Loader.Load() - if err != nil { - return nil, err - } - - c, err := mg.Parser.Parse(raw) - if err != nil { - return nil, err - } - - return c, nil + return mg.Load() } // AuthMode ... @@ -106,7 +103,7 @@ func AuthMode() (string, error) { if err != nil { return "", err } - return cfg[comcfg.AUTHMode].(string), nil + return cfg[common.AUTHMode].(string), nil } // LDAP returns the setting of ldap server @@ -117,14 +114,14 @@ func LDAP() (*models.LDAP, error) { } ldap := &models.LDAP{} - ldap.URL = cfg[comcfg.LDAPURL].(string) - ldap.SearchDN = cfg[comcfg.LDAPSearchDN].(string) - ldap.SearchPassword = cfg[comcfg.LDAPSearchPwd].(string) - ldap.BaseDN = cfg[comcfg.LDAPBaseDN].(string) - ldap.UID = cfg[comcfg.LDAPUID].(string) - ldap.Filter = cfg[comcfg.LDAPFilter].(string) - ldap.Scope = int(cfg[comcfg.LDAPScope].(float64)) - ldap.Timeout = int(cfg[comcfg.LDAPTimeout].(float64)) + ldap.URL = cfg[common.LDAPURL].(string) + ldap.SearchDN = cfg[common.LDAPSearchDN].(string) + ldap.SearchPassword = cfg[common.LDAPSearchPwd].(string) + ldap.BaseDN = cfg[common.LDAPBaseDN].(string) + ldap.UID = cfg[common.LDAPUID].(string) + ldap.Filter = cfg[common.LDAPFilter].(string) + ldap.Scope = int(cfg[common.LDAPScope].(float64)) + ldap.Timeout = int(cfg[common.LDAPTimeout].(float64)) return ldap, nil } @@ -135,7 +132,7 @@ func TokenExpiration() (int, error) { if err != nil { return 0, err } - return int(cfg[comcfg.TokenExpiration].(float64)), nil + return int(cfg[common.TokenExpiration].(float64)), nil } // ExtEndpoint returns the external URL of Harbor: protocol://host:port @@ -144,7 +141,7 @@ func ExtEndpoint() (string, error) { if err != nil { return "", err } - return cfg[comcfg.ExtEndpoint].(string), nil + return cfg[common.ExtEndpoint].(string), nil } // ExtURL returns the external URL: host:port @@ -171,7 +168,7 @@ func SelfRegistration() (bool, error) { if err != nil { return false, err } - return cfg[comcfg.SelfRegistration].(bool), nil + return cfg[common.SelfRegistration].(bool), nil } // RegistryURL ... @@ -180,7 +177,7 @@ func RegistryURL() (string, error) { if err != nil { return "", err } - return cfg[comcfg.RegistryURL].(string), nil + return cfg[common.RegistryURL].(string), nil } // InternalJobServiceURL returns jobservice URL for internal communication between Harbor containers @@ -205,7 +202,7 @@ func InitialAdminPassword() (string, error) { if err != nil { return "", err } - return cfg[comcfg.AdminInitialPassword].(string), nil + return cfg[common.AdminInitialPassword].(string), nil } // OnlyAdminCreateProject returns the flag to restrict that only sys admin can create project @@ -214,7 +211,7 @@ func OnlyAdminCreateProject() (bool, error) { if err != nil { return true, err } - return cfg[comcfg.ProjectCreationRestriction].(string) == comcfg.ProCrtRestrAdmOnly, nil + return cfg[common.ProjectCreationRestriction].(string) == common.ProCrtRestrAdmOnly, nil } // VerifyRemoteCert returns bool value. @@ -223,7 +220,7 @@ func VerifyRemoteCert() (bool, error) { if err != nil { return true, err } - return cfg[comcfg.VerifyRemoteCert].(bool), nil + return cfg[common.VerifyRemoteCert].(bool), nil } // Email returns email server settings @@ -234,13 +231,13 @@ func Email() (*models.Email, error) { } email := &models.Email{} - email.Host = cfg[comcfg.EmailHost].(string) - email.Port = int(cfg[comcfg.EmailPort].(float64)) - email.Username = cfg[comcfg.EmailUsername].(string) - email.Password = cfg[comcfg.EmailPassword].(string) - email.SSL = cfg[comcfg.EmailSSL].(bool) - email.From = cfg[comcfg.EmailFrom].(string) - email.Identity = cfg[comcfg.EmailIdentity].(string) + email.Host = cfg[common.EmailHost].(string) + email.Port = int(cfg[common.EmailPort].(float64)) + email.Username = cfg[common.EmailUsername].(string) + email.Password = cfg[common.EmailPassword].(string) + email.SSL = cfg[common.EmailSSL].(bool) + email.From = cfg[common.EmailFrom].(string) + email.Identity = cfg[common.EmailIdentity].(string) return email, nil } @@ -252,16 +249,16 @@ func Database() (*models.Database, error) { return nil, err } database := &models.Database{} - database.Type = cfg[comcfg.DatabaseType].(string) + database.Type = cfg[common.DatabaseType].(string) mysql := &models.MySQL{} - mysql.Host = cfg[comcfg.MySQLHost].(string) - mysql.Port = int(cfg[comcfg.MySQLPort].(float64)) - mysql.Username = cfg[comcfg.MySQLUsername].(string) - mysql.Password = cfg[comcfg.MySQLPassword].(string) - mysql.Database = cfg[comcfg.MySQLDatabase].(string) + mysql.Host = cfg[common.MySQLHost].(string) + mysql.Port = int(cfg[common.MySQLPort].(float64)) + mysql.Username = cfg[common.MySQLUsername].(string) + mysql.Password = cfg[common.MySQLPassword].(string) + mysql.Database = cfg[common.MySQLDatabase].(string) database.MySQL = mysql sqlite := &models.SQLite{} - sqlite.File = cfg[comcfg.SQLiteFile].(string) + sqlite.File = cfg[common.SQLiteFile].(string) database.SQLite = sqlite return database, nil @@ -286,7 +283,7 @@ func WithNotary() bool { log.Errorf("Failed to get configuration, will return WithNotary == false") return false } - return cfg[comcfg.WithNotary].(bool) + return cfg[common.WithNotary].(bool) } // AdmiralEndpoint returns the URL of admiral, if Harbor is not deployed with admiral it should return an empty string. @@ -297,10 +294,10 @@ func AdmiralEndpoint() string { return "" } - if e, ok := cfg[comcfg.AdmiralEndpoint].(string); !ok || e == "NA" { - cfg[comcfg.AdmiralEndpoint] = "" + if e, ok := cfg[common.AdmiralEndpoint].(string); !ok || e == "NA" { + cfg[common.AdmiralEndpoint] = "" } - return cfg[comcfg.AdmiralEndpoint].(string) + return cfg[common.AdmiralEndpoint].(string) } // WithAdmiral returns a bool to indicate if Harbor's deployed with admiral.