mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-25 03:35:21 +01:00
configure harbor
This commit is contained in:
parent
ad4da5f043
commit
b62a958250
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,8 +1,11 @@
|
|||||||
harbor
|
harbor
|
||||||
make/common/config/*
|
make/common/config/*
|
||||||
|
make/dev/adminserver/harbor_adminserver
|
||||||
make/dev/ui/harbor_ui
|
make/dev/ui/harbor_ui
|
||||||
make/dev/jobservice/harbor_jobservice
|
make/dev/jobservice/harbor_jobservice
|
||||||
|
src/adminserver/adminserver
|
||||||
src/ui/ui
|
src/ui/ui
|
||||||
src/jobservice/jobservice
|
src/jobservice/jobservice
|
||||||
|
src/common/dao/dao.test
|
||||||
*.pyc
|
*.pyc
|
||||||
jobservice/test
|
jobservice/test
|
||||||
|
37
make/common/templates/adminserver/env
Normal file
37
make/common/templates/adminserver/env
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
LOG_LEVEL=debug
|
||||||
|
EXT_ENDPOINT=$ui_url
|
||||||
|
AUTH_MODE=$auth_mode
|
||||||
|
SELF_REGISTRATION=$self_registration
|
||||||
|
LDAP_URL=$ldap_url
|
||||||
|
LDAP_SEARCH_DN=$ldap_searchdn
|
||||||
|
LDAP_SEARCH_PWD=$ldap_search_pwd
|
||||||
|
LDAP_BASE_DN=$ldap_basedn
|
||||||
|
LDAP_FILTER=$ldap_filter
|
||||||
|
LDAP_UID=$ldap_uid
|
||||||
|
LDAP_SCOPE=$ldap_scope
|
||||||
|
DATABASE_TYPE=mysql
|
||||||
|
MYSQL_HOST=mysql
|
||||||
|
MYSQL_PORT=3306
|
||||||
|
MYSQL_USR=root
|
||||||
|
MYSQL_PWD=$db_password
|
||||||
|
MYSQL_DATABASE=registry
|
||||||
|
REGISTRY_URL=http://registry:5000
|
||||||
|
TOKEN_SERVICE_URL=http://ui/service/token
|
||||||
|
EMAIL_HOST=$email_host
|
||||||
|
EMAIL_PORT=$email_port
|
||||||
|
EMAIL_USR=$email_usr
|
||||||
|
EMAIL_PWD=$email_pwd
|
||||||
|
EMAIL_TLS=$email_tls
|
||||||
|
EMAIL_FROM=$email_from
|
||||||
|
EMAIL_IDENTITY=$email_identity
|
||||||
|
HARBOR_ADMIN_PASSWORD=$harbor_admin_password
|
||||||
|
PROJECT_CREATION_RESTRICTION=$project_creation_restriction
|
||||||
|
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||||
|
MAX_JOB_WORKERS=$max_job_workers
|
||||||
|
LOG_DIR=/var/log/jobs
|
||||||
|
UI_SECRET=$ui_secret
|
||||||
|
SECRET_KEY=$secret_key
|
||||||
|
TOKEN_EXPIRATION=$token_expiration
|
||||||
|
CFG_EXPIRATION=$cfg_expiration
|
||||||
|
USE_COMPRESSED_JS=$use_compressed_js
|
||||||
|
GODEBUG=netdns=cgo
|
@ -1,15 +1,5 @@
|
|||||||
MYSQL_HOST=mysql
|
|
||||||
MYSQL_PORT=3306
|
|
||||||
MYSQL_USR=root
|
|
||||||
MYSQL_PWD=$db_password
|
|
||||||
UI_SECRET=$ui_secret
|
|
||||||
SECRET_KEY=$secret_key
|
|
||||||
CONFIG_PATH=/etc/jobservice/app.conf
|
|
||||||
REGISTRY_URL=http://registry:5000
|
|
||||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
|
||||||
MAX_JOB_WORKERS=$max_job_workers
|
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
LOG_DIR=/var/log/jobs
|
UI_SECRET=$ui_secret
|
||||||
|
CONFIG_PATH=/etc/jobservice/app.conf
|
||||||
|
MAX_JOB_WORKERS=$max_job_workers
|
||||||
GODEBUG=netdns=cgo
|
GODEBUG=netdns=cgo
|
||||||
EXT_ENDPOINT=$ui_url
|
|
||||||
TOKEN_ENDPOINT=http://ui
|
|
||||||
|
@ -7,12 +7,3 @@ names = en-US|zh-CN
|
|||||||
|
|
||||||
[dev]
|
[dev]
|
||||||
httpport = 80
|
httpport = 80
|
||||||
|
|
||||||
[mail]
|
|
||||||
identity = $email_identity
|
|
||||||
host = $email_server
|
|
||||||
port = $email_server_port
|
|
||||||
username = $email_username
|
|
||||||
password = $email_password
|
|
||||||
from = $email_from
|
|
||||||
ssl = $email_ssl
|
|
||||||
|
@ -1,29 +1,4 @@
|
|||||||
MYSQL_HOST=mysql
|
|
||||||
MYSQL_PORT=3306
|
|
||||||
MYSQL_USR=root
|
|
||||||
MYSQL_PWD=$db_password
|
|
||||||
REGISTRY_URL=http://registry:5000
|
|
||||||
JOB_SERVICE_URL=http://jobservice
|
|
||||||
UI_URL=http://ui
|
|
||||||
CONFIG_PATH=/etc/ui/app.conf
|
|
||||||
EXT_REG_URL=$hostname
|
|
||||||
HARBOR_ADMIN_PASSWORD=$harbor_admin_password
|
|
||||||
AUTH_MODE=$auth_mode
|
|
||||||
LDAP_URL=$ldap_url
|
|
||||||
LDAP_SEARCH_DN=$ldap_searchdn
|
|
||||||
LDAP_SEARCH_PWD=$ldap_search_pwd
|
|
||||||
LDAP_BASE_DN=$ldap_basedn
|
|
||||||
LDAP_FILTER=$ldap_filter
|
|
||||||
LDAP_UID=$ldap_uid
|
|
||||||
LDAP_SCOPE=$ldap_scope
|
|
||||||
UI_SECRET=$ui_secret
|
|
||||||
SECRET_KEY=$secret_key
|
|
||||||
SELF_REGISTRATION=$self_registration
|
|
||||||
USE_COMPRESSED_JS=$use_compressed_js
|
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
CONFIG_PATH=/etc/ui/app.conf
|
||||||
|
UI_SECRET=$ui_secret
|
||||||
GODEBUG=netdns=cgo
|
GODEBUG=netdns=cgo
|
||||||
EXT_ENDPOINT=$ui_url
|
|
||||||
TOKEN_ENDPOINT=http://ui
|
|
||||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
|
||||||
TOKEN_EXPIRATION=$token_expiration
|
|
||||||
PROJECT_CREATION_RESTRICTION=$project_creation_restriction
|
|
||||||
|
12
make/dev/adminserver/Dockerfile
Normal file
12
make/dev/adminserver/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM golang:1.7.3
|
||||||
|
|
||||||
|
MAINTAINER yinw@vmware.com
|
||||||
|
|
||||||
|
COPY . /go/src/github.com/vmware/harbor
|
||||||
|
|
||||||
|
WORKDIR /go/src/github.com/vmware/harbor/src/adminserver
|
||||||
|
|
||||||
|
RUN go build -v -a -o /go/bin/harbor_adminserver \
|
||||||
|
&& chmod u+x /go/bin/harbor_adminserver
|
||||||
|
WORKDIR /go/bin/
|
||||||
|
ENTRYPOINT ["/go/bin/harbor_adminserver"]
|
@ -40,6 +40,22 @@ services:
|
|||||||
options:
|
options:
|
||||||
syslog-address: "tcp://127.0.0.1:1514"
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
tag: "mysql"
|
tag: "mysql"
|
||||||
|
adminserver:
|
||||||
|
build:
|
||||||
|
context: ../../
|
||||||
|
dockerfile: make/dev/adminserver/Dockerfile
|
||||||
|
env_file:
|
||||||
|
- ../common/config/adminserver/env
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /data/config/:/etc/harbor/
|
||||||
|
depends_on:
|
||||||
|
- log
|
||||||
|
logging:
|
||||||
|
driver: "syslog"
|
||||||
|
options:
|
||||||
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
|
tag: "adminserver"
|
||||||
ui:
|
ui:
|
||||||
build:
|
build:
|
||||||
context: ../../
|
context: ../../
|
||||||
@ -52,6 +68,8 @@ services:
|
|||||||
- ../common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
- ../common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
||||||
depends_on:
|
depends_on:
|
||||||
- log
|
- log
|
||||||
|
- adminserver
|
||||||
|
- registry
|
||||||
logging:
|
logging:
|
||||||
driver: "syslog"
|
driver: "syslog"
|
||||||
options:
|
options:
|
||||||
@ -69,6 +87,7 @@ services:
|
|||||||
- ../common/config/jobservice/app.conf:/etc/jobservice/app.conf
|
- ../common/config/jobservice/app.conf:/etc/jobservice/app.conf
|
||||||
depends_on:
|
depends_on:
|
||||||
- ui
|
- ui
|
||||||
|
- adminserver
|
||||||
logging:
|
logging:
|
||||||
driver: "syslog"
|
driver: "syslog"
|
||||||
options:
|
options:
|
||||||
|
@ -41,6 +41,21 @@ services:
|
|||||||
options:
|
options:
|
||||||
syslog-address: "tcp://127.0.0.1:1514"
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
tag: "mysql"
|
tag: "mysql"
|
||||||
|
adminserver:
|
||||||
|
image: vmware/harbor-adminserver
|
||||||
|
container_name: harbor-adminserver
|
||||||
|
env_file:
|
||||||
|
- ./common/config/adminserver/env
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /data/config/:/etc/harbor/
|
||||||
|
depends_on:
|
||||||
|
- log
|
||||||
|
logging:
|
||||||
|
driver: "syslog"
|
||||||
|
options:
|
||||||
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
|
tag: "adminserver"
|
||||||
ui:
|
ui:
|
||||||
image: vmware/harbor-ui
|
image: vmware/harbor-ui
|
||||||
container_name: harbor-ui
|
container_name: harbor-ui
|
||||||
@ -53,6 +68,8 @@ services:
|
|||||||
- /data:/harbor_storage
|
- /data:/harbor_storage
|
||||||
depends_on:
|
depends_on:
|
||||||
- log
|
- log
|
||||||
|
- adminserver
|
||||||
|
- registry
|
||||||
logging:
|
logging:
|
||||||
driver: "syslog"
|
driver: "syslog"
|
||||||
options:
|
options:
|
||||||
@ -69,6 +86,7 @@ services:
|
|||||||
- ./common/config/jobservice/app.conf:/etc/jobservice/app.conf
|
- ./common/config/jobservice/app.conf:/etc/jobservice/app.conf
|
||||||
depends_on:
|
depends_on:
|
||||||
- ui
|
- ui
|
||||||
|
- adminserver
|
||||||
logging:
|
logging:
|
||||||
driver: "syslog"
|
driver: "syslog"
|
||||||
options:
|
options:
|
||||||
|
8
make/photon/adminserver/Dockerfile
Normal file
8
make/photon/adminserver/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
FROM library/photon:1.0
|
||||||
|
|
||||||
|
RUN mkdir /harbor/
|
||||||
|
COPY ./make/dev/adminserver/harbor_adminserver /harbor/
|
||||||
|
|
||||||
|
RUN chmod u+x /harbor/harbor_adminserver
|
||||||
|
WORKDIR /harbor/
|
||||||
|
ENTRYPOINT ["/harbor/harbor_adminserver"]
|
29
src/adminserver/api/base.go
Normal file
29
src/adminserver/api/base.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
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 api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleInternalServerError(w http.ResponseWriter) {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError),
|
||||||
|
http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleBadRequestError(w http.ResponseWriter, error string) {
|
||||||
|
http.Error(w, error, http.StatusBadRequest)
|
||||||
|
}
|
161
src/adminserver/api/cfg.go
Normal file
161
src/adminserver/api/cfg.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
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 api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
cfg "github.com/vmware/harbor/src/adminserver/systemcfg"
|
||||||
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListCfgs lists configurations
|
||||||
|
func ListCfgs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cfg, err := cfg.GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get system configurations: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.MarshalIndent(cfg, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to marshal configurations: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = w.Write(b); err != nil {
|
||||||
|
log.Errorf("failed to write response: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCfgs updates configurations
|
||||||
|
func UpdateCfgs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
b, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to read request body: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &map[string]string{}
|
||||||
|
if err = json.Unmarshal(b, m); err != nil {
|
||||||
|
handleBadRequestError(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(m)
|
||||||
|
|
||||||
|
system, err := cfg.GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := populate(system, *m); err != nil {
|
||||||
|
log.Errorf("failed to populate system configurations: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(system.Authentication.SelfRegistration)
|
||||||
|
|
||||||
|
if err = cfg.UpdateSystemCfg(system); err != nil {
|
||||||
|
log.Errorf("failed to update system configurations: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate attrs of cfg according to m
|
||||||
|
func populate(cfg *models.SystemCfg, m map[string]string) error {
|
||||||
|
if mode, ok := m[comcfg.AUTH_MODE]; ok {
|
||||||
|
cfg.Authentication.Mode = mode
|
||||||
|
}
|
||||||
|
if value, ok := m[comcfg.SELF_REGISTRATION]; ok {
|
||||||
|
cfg.Authentication.SelfRegistration = value == "true"
|
||||||
|
}
|
||||||
|
if url, ok := m[comcfg.LDAP_URL]; ok {
|
||||||
|
cfg.Authentication.LDAP.URL = url
|
||||||
|
}
|
||||||
|
if dn, ok := m[comcfg.LDAP_SEARCH_DN]; ok {
|
||||||
|
cfg.Authentication.LDAP.SearchDN = dn
|
||||||
|
}
|
||||||
|
if pwd, ok := m[comcfg.LDAP_SEARCH_PWD]; ok {
|
||||||
|
cfg.Authentication.LDAP.SearchPwd = pwd
|
||||||
|
}
|
||||||
|
if dn, ok := m[comcfg.LDAP_BASE_DN]; ok {
|
||||||
|
cfg.Authentication.LDAP.BaseDN = dn
|
||||||
|
}
|
||||||
|
if uid, ok := m[comcfg.LDAP_UID]; ok {
|
||||||
|
cfg.Authentication.LDAP.UID = uid
|
||||||
|
}
|
||||||
|
if filter, ok := m[comcfg.LDAP_FILTER]; ok {
|
||||||
|
cfg.Authentication.LDAP.Filter = filter
|
||||||
|
}
|
||||||
|
if scope, ok := m[comcfg.LDAP_SCOPE]; ok {
|
||||||
|
i, err := strconv.Atoi(scope)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg.Authentication.LDAP.Scope = i
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := m[comcfg.EMAIL_SERVER]; ok {
|
||||||
|
cfg.Email.Host = value
|
||||||
|
}
|
||||||
|
if value, ok := m[comcfg.EMAIL_SERVER_PORT]; ok {
|
||||||
|
cfg.Email.Port = value
|
||||||
|
}
|
||||||
|
if value, ok := m[comcfg.EMAIL_USERNAME]; ok {
|
||||||
|
cfg.Email.Username = value
|
||||||
|
}
|
||||||
|
if value, ok := m[comcfg.EMAIL_PWD]; ok {
|
||||||
|
cfg.Email.Host = value
|
||||||
|
}
|
||||||
|
if value, ok := m[comcfg.EMAIL_SSL]; ok {
|
||||||
|
cfg.Email.Password = value
|
||||||
|
}
|
||||||
|
if value, ok := m[comcfg.EMAIL_FROM]; ok {
|
||||||
|
cfg.Email.From = value
|
||||||
|
}
|
||||||
|
if value, ok := m[comcfg.EMAIL_IDENTITY]; ok {
|
||||||
|
cfg.Email.Identity = value
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := m[comcfg.PROJECT_CREATION_RESTRICTION]; ok {
|
||||||
|
cfg.ProjectCreationRestriction = value
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := m[comcfg.VERIFY_REMOTE_CERT]; ok {
|
||||||
|
cfg.VerifyRemoteCert = value == "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := m[comcfg.MAX_JOB_WORKERS]; ok {
|
||||||
|
if i, err := strconv.Atoi(value); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
cfg.MaxJobWorkers = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
60
src/adminserver/main.go
Normal file
60
src/adminserver/main.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
cfg "github.com/vmware/harbor/src/adminserver/systemcfg"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server for admin component
|
||||||
|
type Server struct {
|
||||||
|
Port string
|
||||||
|
Handler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve the API
|
||||||
|
func (s *Server) Serve() error {
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: ":" + s.Port,
|
||||||
|
Handler: s.Handler,
|
||||||
|
}
|
||||||
|
|
||||||
|
return server.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Info("initializing system configurations...")
|
||||||
|
if err := cfg.Init(); err != nil {
|
||||||
|
log.Fatalf("failed to initialize the system: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("system initialization completed")
|
||||||
|
|
||||||
|
port := os.Getenv("PORT")
|
||||||
|
if len(port) == 0 {
|
||||||
|
port = "80"
|
||||||
|
}
|
||||||
|
server := &Server{
|
||||||
|
Port: port,
|
||||||
|
Handler: newHandler(),
|
||||||
|
}
|
||||||
|
if err := server.Serve(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
30
src/adminserver/router.go
Normal file
30
src/adminserver/router.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/vmware/harbor/src/adminserver/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newHandler() http.Handler {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/api/configurations", api.ListCfgs).Methods("GET")
|
||||||
|
r.HandleFunc("/api/configurations", api.UpdateCfgs).Methods("PUT")
|
||||||
|
return r
|
||||||
|
}
|
30
src/adminserver/systemcfg/driver.go
Normal file
30
src/adminserver/systemcfg/driver.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
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 systemcfg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Driver defines methods that a configuration store driver must implement
|
||||||
|
type Driver interface {
|
||||||
|
// Name returns a human-readable name of the driver
|
||||||
|
Name() string
|
||||||
|
// Read reads the configurations from store
|
||||||
|
Read() (*models.SystemCfg, error)
|
||||||
|
// Write writes the configurations to store
|
||||||
|
Write(*models.SystemCfg) error
|
||||||
|
}
|
104
src/adminserver/systemcfg/driver_json.go
Normal file
104
src/adminserver/systemcfg/driver_json.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
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 systemcfg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// the default path of configuration file
|
||||||
|
defaultPath = "/etc/harbor/config.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cfgStore struct {
|
||||||
|
path string // the path of cfg file
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCfgStore returns an instance of cfgStore that stores the configurations
|
||||||
|
// in a json file. The file will be created if it does not exist.
|
||||||
|
func NewCfgStore(path ...string) (Driver, error) {
|
||||||
|
p := defaultPath
|
||||||
|
if len(path) != 0 {
|
||||||
|
p = path[0]
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(p); os.IsNotExist(err) {
|
||||||
|
log.Infof("the configuration file %s does not exist, creating it...", p)
|
||||||
|
if err = os.MkdirAll(filepath.Dir(p), 0600); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = ioutil.WriteFile(p, []byte{}, 0600); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfgStore{
|
||||||
|
path: p,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name ...
|
||||||
|
func (c *cfgStore) Name() string {
|
||||||
|
return "JSON"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read ...
|
||||||
|
func (c *cfgStore) Read() (*models.SystemCfg, error) {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
b, err := ioutil.ReadFile(c.path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty file
|
||||||
|
if len(b) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &models.SystemCfg{}
|
||||||
|
if err = json.Unmarshal(b, config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write ...
|
||||||
|
func (c *cfgStore) Write(config *models.SystemCfg) error {
|
||||||
|
b, err := json.MarshalIndent(config, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
|
||||||
|
if err = ioutil.WriteFile(c.path, b, 0600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
50
src/adminserver/systemcfg/driver_json_test.go
Normal file
50
src/adminserver/systemcfg/driver_json_test.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
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 systemcfg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadWrite(t *testing.T) {
|
||||||
|
path := "/tmp/config.json"
|
||||||
|
store, err := NewCfgStore(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create json cfg store: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := os.Remove(path); err != nil {
|
||||||
|
t.Fatalf("failed to remove the json file %s: %v", path, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
config := &cfg.SystemCfg{
|
||||||
|
Authentication: &cfg.Authentication{
|
||||||
|
LDAP: &cfg.LDAP{},
|
||||||
|
},
|
||||||
|
Database: &cfg.Database{
|
||||||
|
MySQL: &cfg.MySQL{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := store.Write(config); err != nil {
|
||||||
|
t.Fatalf("failed to write configurations to json file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = store.Read(); err != nil {
|
||||||
|
t.Fatalf("failed to read configurations from json file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
176
src/adminserver/systemcfg/systemcfg.go
Normal file
176
src/adminserver/systemcfg/systemcfg.go
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
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 systemcfg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var store Driver
|
||||||
|
|
||||||
|
// Init system configurations. Read from config store first, if null read from env
|
||||||
|
func Init() (err error) {
|
||||||
|
s := getCfgStore()
|
||||||
|
switch s {
|
||||||
|
case "json":
|
||||||
|
store, err = NewCfgStore()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported configuration store driver %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("configuration store driver: %s", store.Name())
|
||||||
|
cfg, err := store.Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg == nil {
|
||||||
|
log.Info("configurations read from store driver are null, initializing system from environment variables...")
|
||||||
|
cfg, err = initFromEnv()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//read the following attrs from env every time boots up,
|
||||||
|
//and sync them into cfg store
|
||||||
|
cfg.DomainName = os.Getenv("EXT_ENDPOINT")
|
||||||
|
cfg.Database.MySQL.Password = os.Getenv("MYSQL_PWD")
|
||||||
|
cfg.JobLogDir = os.Getenv("LOG_DIR")
|
||||||
|
cfg.CompressJS = os.Getenv("USE_COMPRESSED_JS") == "on"
|
||||||
|
exp, err := strconv.Atoi(os.Getenv("TOKEN_EXPIRATION"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg.TokenExpiration = exp
|
||||||
|
cfg.SecretKey = os.Getenv("SECRET_KEY")
|
||||||
|
|
||||||
|
cfgExp, err := strconv.Atoi(os.Getenv("CFG_EXPIRATION"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg.CfgExpiration = cfgExp
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = store.Write(cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCfgStore() string {
|
||||||
|
return "json"
|
||||||
|
}
|
||||||
|
|
||||||
|
func initFromEnv() (*models.SystemCfg, error) {
|
||||||
|
cfg := &models.SystemCfg{}
|
||||||
|
cfg.DomainName = os.Getenv("EXT_ENDPOINT")
|
||||||
|
cfg.Authentication = &models.Authentication{
|
||||||
|
Mode: os.Getenv("AUTH_MODE"),
|
||||||
|
SelfRegistration: os.Getenv("SELF_REGISTRATION") == "true",
|
||||||
|
LDAP: &models.LDAP{
|
||||||
|
URL: os.Getenv("LDAP_URL"),
|
||||||
|
SearchDN: os.Getenv("LDAP_SEARCH_DN"),
|
||||||
|
SearchPwd: os.Getenv("LDAP_SEARCH_PWD"),
|
||||||
|
BaseDN: os.Getenv("LDAP_BASE_DN"),
|
||||||
|
Filter: os.Getenv("LDAP_FILTER"),
|
||||||
|
UID: os.Getenv("LDAP_UID"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
scope, err := strconv.Atoi(os.Getenv("LDAP_SCOPE"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.Authentication.LDAP.Scope = scope
|
||||||
|
cfg.Database = &models.Database{
|
||||||
|
Type: os.Getenv("DATABASE_TYPE"),
|
||||||
|
MySQL: &models.MySQL{
|
||||||
|
Host: os.Getenv("MYSQL_HOST"),
|
||||||
|
Username: os.Getenv("MYSQL_USR"),
|
||||||
|
Password: os.Getenv("MYSQL_PWD"),
|
||||||
|
Database: os.Getenv("MYSQL_DATABASE"),
|
||||||
|
},
|
||||||
|
SQLite: &models.SQLite{
|
||||||
|
File: os.Getenv("SQLITE_FILE"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
port, err := strconv.Atoi(os.Getenv("MYSQL_PORT"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.Database.MySQL.Port = port
|
||||||
|
|
||||||
|
cfg.TokenService = &models.TokenService{
|
||||||
|
URL: os.Getenv("TOKEN_SERVICE_URL"),
|
||||||
|
}
|
||||||
|
cfg.Registry = &models.Registry{
|
||||||
|
URL: os.Getenv("REGISTRY_URL"),
|
||||||
|
}
|
||||||
|
cfg.Email = &models.Email{
|
||||||
|
Host: os.Getenv("EMAIL_HOST"),
|
||||||
|
Port: os.Getenv("EMAIL_PORT"),
|
||||||
|
Username: os.Getenv("EMAIL_USR"),
|
||||||
|
Password: os.Getenv("EMAIL_PWD"),
|
||||||
|
TLS: os.Getenv("EMAIL_TLS") == "true",
|
||||||
|
From: os.Getenv("EMAIL_FROM"),
|
||||||
|
Identity: os.Getenv("EMAIL_IDENTITY"),
|
||||||
|
}
|
||||||
|
cfg.VerifyRemoteCert = os.Getenv("VERIFY_REMOTE_CERT") == "true"
|
||||||
|
cfg.ProjectCreationRestriction = os.Getenv("PROJECT_CREATION_RESTRICTION")
|
||||||
|
|
||||||
|
workers, err := strconv.Atoi(os.Getenv("MAX_JOB_WORKERS"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.MaxJobWorkers = workers
|
||||||
|
cfg.JobLogDir = os.Getenv("LOG_DIR")
|
||||||
|
cfg.InitialAdminPwd = os.Getenv("HARBOR_ADMIN_PASSWORD")
|
||||||
|
cfg.CompressJS = os.Getenv("USE_COMPRESSED_JS") == "on"
|
||||||
|
|
||||||
|
tokenExp, err := strconv.Atoi(os.Getenv("TOKEN_EXPIRATION"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.TokenExpiration = tokenExp
|
||||||
|
|
||||||
|
cfg.SecretKey = os.Getenv("SECRET_KEY")
|
||||||
|
|
||||||
|
cfgExp, err := strconv.Atoi(os.Getenv("CFG_EXPIRATION"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.CfgExpiration = cfgExp
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemCfg returns the system configurations
|
||||||
|
func GetSystemCfg() (*models.SystemCfg, error) {
|
||||||
|
return store.Read()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSystemCfg updates the system configurations
|
||||||
|
func UpdateSystemCfg(cfg *models.SystemCfg) error {
|
||||||
|
return store.Write(cfg)
|
||||||
|
}
|
@ -22,11 +22,11 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/astaxie/beego/validation"
|
"github.com/astaxie/beego/validation"
|
||||||
"github.com/vmware/harbor/src/common/config"
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/auth"
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
)
|
)
|
||||||
@ -212,6 +212,10 @@ func (b *BaseAPI) GetPaginationParams() (page, pageSize int64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetIsInsecure ...
|
// GetIsInsecure ...
|
||||||
func GetIsInsecure() bool {
|
func GetIsInsecure() (bool, error) {
|
||||||
return !config.VerifyRemoteCert()
|
verify, err := config.VerifyRemoteCert()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !verify, nil
|
||||||
}
|
}
|
||||||
|
@ -17,162 +17,119 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfLoader is the interface to load configurations
|
const (
|
||||||
type ConfLoader interface {
|
//auth mode
|
||||||
// Load will load configuration from different source into a string map, the values in the map will be parsed in to configurations.
|
DB_AUTH = "db_auth"
|
||||||
Load() (map[string]string, error)
|
LDAP_AUTH = "ldap_auth"
|
||||||
|
|
||||||
|
//project_creation_restriction
|
||||||
|
PRO_CRT_RESTR_EVERYONE = "everyone"
|
||||||
|
PRO_CRT_RESTR_ADM_ONLY = "adminonly"
|
||||||
|
|
||||||
|
LDAP_SCOPE_BASE = "1"
|
||||||
|
LDAP_SCOPE_ONELEVEL = "2"
|
||||||
|
LDAP_SCOPE_SUBTREE = "3"
|
||||||
|
|
||||||
|
AUTH_MODE = "auth_mode"
|
||||||
|
SELF_REGISTRATION = "self_registration"
|
||||||
|
LDAP_URL = "ldap_url"
|
||||||
|
LDAP_SEARCH_DN = "ldap_search_dn"
|
||||||
|
LDAP_SEARCH_PWD = "ldap_search_pwd"
|
||||||
|
LDAP_BASE_DN = "ldap_base_dn"
|
||||||
|
LDAP_UID = "ldap_uid"
|
||||||
|
LDAP_FILTER = "ldap_filter"
|
||||||
|
LDAP_SCOPE = "ldap_scope"
|
||||||
|
EMAIL_SERVER = "email_server"
|
||||||
|
EMAIL_SERVER_PORT = "email_server_port"
|
||||||
|
EMAIL_USERNAME = "email_server_username"
|
||||||
|
EMAIL_PWD = "email_server_pwd"
|
||||||
|
EMAIL_FROM = "email_from"
|
||||||
|
EMAIL_SSL = "email_ssl"
|
||||||
|
EMAIL_IDENTITY = "email_identity"
|
||||||
|
PROJECT_CREATION_RESTRICTION = "project_creation_restriction"
|
||||||
|
VERIFY_REMOTE_CERT = "verify_remote_cert"
|
||||||
|
MAX_JOB_WORKERS = "max_job_workers"
|
||||||
|
CFG_EXPIRATION = "cfg_expiration"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
Key string
|
||||||
|
Cache cache.Cache
|
||||||
|
Loader *Loader
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnvConfigLoader loads the config from env vars.
|
func NewManager(key, url string) *Manager {
|
||||||
type EnvConfigLoader struct {
|
return &Manager{
|
||||||
Keys []string
|
Key: key,
|
||||||
}
|
Cache: cache.NewMemoryCache(),
|
||||||
|
Loader: NewLoader(url),
|
||||||
// Load ...
|
|
||||||
func (ec *EnvConfigLoader) Load() (map[string]string, error) {
|
|
||||||
m := make(map[string]string)
|
|
||||||
for _, k := range ec.Keys {
|
|
||||||
m[k] = os.Getenv(k)
|
|
||||||
}
|
}
|
||||||
return m, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfParser ...
|
func (m *Manager) GetFromCache() interface{} {
|
||||||
type ConfParser interface {
|
value := m.Cache.Get(m.Key)
|
||||||
|
if value != nil {
|
||||||
//Parse parse the input raw map into a config map
|
return value
|
||||||
Parse(raw map[string]string, config map[string]interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config wraps a map for the processed configuration values,
|
|
||||||
// and loader parser to read configuration from external source and process the values.
|
|
||||||
type Config struct {
|
|
||||||
Config map[string]interface{}
|
|
||||||
Loader ConfLoader
|
|
||||||
Parser ConfParser
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load reload the configurations
|
|
||||||
func (conf *Config) Load() error {
|
|
||||||
rawMap, err := conf.Loader.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
err = conf.Parser.Parse(rawMap, conf.Config)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MySQLSetting wraps the settings of a MySQL DB
|
|
||||||
type MySQLSetting struct {
|
|
||||||
Database string
|
|
||||||
User string
|
|
||||||
Password string
|
|
||||||
Host string
|
|
||||||
Port string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SQLiteSetting wraps the settings of a SQLite DB
|
|
||||||
type SQLiteSetting struct {
|
|
||||||
FilePath string
|
|
||||||
}
|
|
||||||
|
|
||||||
type commonParser struct{}
|
|
||||||
|
|
||||||
// Parse parses the db settings, veryfy_remote_cert, ext_endpoint, token_endpoint
|
|
||||||
func (cp *commonParser) Parse(raw map[string]string, config map[string]interface{}) error {
|
|
||||||
db := strings.ToLower(raw["DATABASE"])
|
|
||||||
if db == "mysql" || db == "" {
|
|
||||||
db = "mysql"
|
|
||||||
mySQLDB := raw["MYSQL_DATABASE"]
|
|
||||||
if len(mySQLDB) == 0 {
|
|
||||||
mySQLDB = "registry"
|
|
||||||
}
|
|
||||||
setting := MySQLSetting{
|
|
||||||
mySQLDB,
|
|
||||||
raw["MYSQL_USR"],
|
|
||||||
raw["MYSQL_PWD"],
|
|
||||||
raw["MYSQL_HOST"],
|
|
||||||
raw["MYSQL_PORT"],
|
|
||||||
}
|
|
||||||
config["mysql"] = setting
|
|
||||||
} else if db == "sqlite" {
|
|
||||||
f := raw["SQLITE_FILE"]
|
|
||||||
if len(f) == 0 {
|
|
||||||
f = "registry.db"
|
|
||||||
}
|
|
||||||
setting := SQLiteSetting{
|
|
||||||
f,
|
|
||||||
}
|
|
||||||
config["sqlite"] = setting
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("Invalid DB: %s", db)
|
|
||||||
}
|
|
||||||
config["database"] = db
|
|
||||||
|
|
||||||
//By default it's true
|
|
||||||
config["verify_remote_cert"] = raw["VERIFY_REMOTE_CERT"] != "off"
|
|
||||||
|
|
||||||
config["ext_endpoint"] = raw["EXT_ENDPOINT"]
|
|
||||||
config["token_endpoint"] = raw["TOKEN_ENDPOINT"]
|
|
||||||
config["log_level"] = raw["LOG_LEVEL"]
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var commonConfig *Config
|
type Loader struct {
|
||||||
|
url string
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func NewLoader(url string) *Loader {
|
||||||
commonKeys := []string{"DATABASE", "MYSQL_DATABASE", "MYSQL_USR", "MYSQL_PWD", "MYSQL_HOST", "MYSQL_PORT", "SQLITE_FILE", "VERIFY_REMOTE_CERT", "EXT_ENDPOINT", "TOKEN_ENDPOINT", "LOG_LEVEL"}
|
return &Loader{
|
||||||
commonConfig = &Config{
|
url: url,
|
||||||
Config: make(map[string]interface{}),
|
client: &http.Client{},
|
||||||
Loader: &EnvConfigLoader{Keys: commonKeys},
|
|
||||||
Parser: &commonParser{},
|
|
||||||
}
|
|
||||||
if err := commonConfig.Load(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload will reload the configuration.
|
func (l *Loader) Init() error {
|
||||||
func Reload() error {
|
addr := l.url
|
||||||
return commonConfig.Load()
|
if strings.Contains(addr, "://") {
|
||||||
|
addr = strings.Split(addr, "://")[1]
|
||||||
|
}
|
||||||
|
return utils.TestTCPConn(addr, 60, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database returns the DB type in configuration.
|
func (l *Loader) Load() ([]byte, error) {
|
||||||
func Database() string {
|
resp, err := l.client.Get(l.url + "/api/configurations")
|
||||||
return commonConfig.Config["database"].(string)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MySQL returns the mysql setting in configuration.
|
func (l *Loader) Upload(b []byte) error {
|
||||||
func MySQL() MySQLSetting {
|
req, err := http.NewRequest("PUT", l.url+"/api/configurations", bytes.NewReader(b))
|
||||||
return commonConfig.Config["mysql"].(MySQLSetting)
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
resp, err := l.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// SQLite returns the SQLite setting
|
if resp.StatusCode != http.StatusOK {
|
||||||
func SQLite() SQLiteSetting {
|
return fmt.Errorf("unexpected http status code: %d", resp.StatusCode)
|
||||||
return commonConfig.Config["sqlite"].(SQLiteSetting)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyRemoteCert returns bool value.
|
return nil
|
||||||
func VerifyRemoteCert() bool {
|
|
||||||
return commonConfig.Config["verify_remote_cert"].(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtEndpoint ...
|
|
||||||
func ExtEndpoint() string {
|
|
||||||
return commonConfig.Config["ext_endpoint"].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TokenEndpoint returns the endpoint string of token service, which can be accessed by internal service of Harbor.
|
|
||||||
func TokenEndpoint() string {
|
|
||||||
return commonConfig.Config["token_endpoint"].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogLevel returns the log level in string format.
|
|
||||||
func LogLevel() string {
|
|
||||||
return commonConfig.Config["log_level"].(string)
|
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,12 @@ package dao
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
"github.com/vmware/harbor/src/common/config"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,27 +40,32 @@ type Database interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitDatabase initializes the database
|
// InitDatabase initializes the database
|
||||||
func InitDatabase() {
|
func InitDatabase(database *models.Database) error {
|
||||||
database, err := getDatabase()
|
db, err := getDatabase(database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("initializing database: %s", database.String())
|
log.Infof("initializing database: %s", db.String())
|
||||||
if err := database.Register(); err != nil {
|
if err := db.Register(); err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
log.Info("initialize database completed")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDatabase() (db Database, err error) {
|
func getDatabase(database *models.Database) (db Database, err error) {
|
||||||
switch config.Database() {
|
switch database.Type {
|
||||||
case "", "mysql":
|
case "", "mysql":
|
||||||
db = NewMySQL(config.MySQL().Host, config.MySQL().Port, config.MySQL().User,
|
db = NewMySQL(database.MySQL.Host,
|
||||||
config.MySQL().Password, config.MySQL().Database)
|
strconv.Itoa(database.MySQL.Port),
|
||||||
|
database.MySQL.Username,
|
||||||
|
database.MySQL.Password,
|
||||||
|
database.MySQL.Database)
|
||||||
case "sqlite":
|
case "sqlite":
|
||||||
db = NewSQLite(config.SQLite().FilePath)
|
db = NewSQLite(database.SQLite.File)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("invalid database: %s", config.Database())
|
err = fmt.Errorf("invalid database: %s", database.Type)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
32
src/common/dao/config.go
Normal file
32
src/common/dao/config.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
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 dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthModeCanBeModified determines whether auth mode can be
|
||||||
|
// modified or not. Auth mode can modified when there is only admin
|
||||||
|
// user in database.
|
||||||
|
func AuthModeCanBeModified() (bool, error) {
|
||||||
|
c, err := GetOrmer().QueryTable(&models.User{}).Count()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c == 1, nil
|
||||||
|
}
|
236
src/common/dao/config_test.go
Normal file
236
src/common/dao/config_test.go
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
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 dao
|
||||||
|
|
||||||
|
/*
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func deleteConfigByKey(key string) error {
|
||||||
|
if _, err := GetOrmer().Raw("delete from properties where k = ?", key).
|
||||||
|
Exec(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfigByKey(t *testing.T) {
|
||||||
|
cfg := &models.Config{
|
||||||
|
Key: "key",
|
||||||
|
Value: "value",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := InsertConfig(cfg); err != nil {
|
||||||
|
t.Fatalf("failed to insert configuration into table: %v", err)
|
||||||
|
}
|
||||||
|
defer func(key string) {
|
||||||
|
if err := deleteConfigByKey(key); err != nil {
|
||||||
|
t.Fatalf("failed to delete configuration %s: %v", key, err)
|
||||||
|
}
|
||||||
|
}(cfg.Key)
|
||||||
|
|
||||||
|
config, err := GetConfigByKey(cfg.Key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get configuration by key %s: %v", cfg.Key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config == nil {
|
||||||
|
t.Fatal("configuration is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Value != cfg.Value {
|
||||||
|
t.Fatalf("unexpected value: %s != %s", config.Value, cfg.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListConfigs(t *testing.T) {
|
||||||
|
configs, err := ListConfigs()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to list configurations: %v", err)
|
||||||
|
}
|
||||||
|
size := len(configs)
|
||||||
|
|
||||||
|
cfg := &models.Config{
|
||||||
|
Key: "key",
|
||||||
|
Value: "value",
|
||||||
|
}
|
||||||
|
if err := InsertConfig(cfg); err != nil {
|
||||||
|
t.Fatalf("failed to insert configuration into table: %v", err)
|
||||||
|
}
|
||||||
|
defer func(key string) {
|
||||||
|
if err := deleteConfigByKey(key); err != nil {
|
||||||
|
t.Fatalf("failed to delete configuration %s: %v", key, err)
|
||||||
|
}
|
||||||
|
}(cfg.Key)
|
||||||
|
|
||||||
|
configs, err = ListConfigs()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to list configurations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if size+1 != len(configs) {
|
||||||
|
t.Fatalf("unexpected length of configurations: %d != %d", len(configs), size+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsertConfig(t *testing.T) {
|
||||||
|
cfg := &models.Config{
|
||||||
|
Key: "key1",
|
||||||
|
Value: "value1",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := InsertConfig(cfg); err != nil {
|
||||||
|
t.Fatalf("failed to insert configuration into table: %v", err)
|
||||||
|
}
|
||||||
|
defer func(key string) {
|
||||||
|
if err := deleteConfigByKey(key); err != nil {
|
||||||
|
t.Fatalf("failed to delete configuration %s: %v", key, err)
|
||||||
|
}
|
||||||
|
}(cfg.Key)
|
||||||
|
|
||||||
|
config, err := GetConfigByKey(cfg.Key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get configuration by key %s: %v", cfg.Key, err)
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
t.Fatal("configuration is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Value != cfg.Value {
|
||||||
|
t.Fatalf("unexpected value: %s != %s", config.Value, cfg.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateConfig(t *testing.T) {
|
||||||
|
cfg := &models.Config{
|
||||||
|
Key: "key",
|
||||||
|
Value: "value",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := InsertConfig(cfg); err != nil {
|
||||||
|
t.Fatalf("failed to insert configuration into table: %v", err)
|
||||||
|
}
|
||||||
|
defer func(key string) {
|
||||||
|
if err := deleteConfigByKey(key); err != nil {
|
||||||
|
t.Fatalf("failed to delete configuration %s: %v", key, err)
|
||||||
|
}
|
||||||
|
}(cfg.Key)
|
||||||
|
|
||||||
|
newCfg := &models.Config{
|
||||||
|
Key: "key",
|
||||||
|
Value: "new_value",
|
||||||
|
}
|
||||||
|
if err := UpdateConfig(newCfg); err != nil {
|
||||||
|
t.Fatalf("failed to update configuration: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := GetConfigByKey(cfg.Key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get configuration by key %s: %v", cfg.Key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config == nil {
|
||||||
|
t.Fatal("configuration is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Value != newCfg.Value {
|
||||||
|
t.Fatalf("unexpected value: %s != %s", config.Value, newCfg.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsertOrUpdateConfigs(t *testing.T) {
|
||||||
|
cfg1 := &models.Config{
|
||||||
|
Key: "key1",
|
||||||
|
Value: "value1",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := InsertConfig(cfg1); err != nil {
|
||||||
|
t.Fatalf("failed to insert configuration into table: %v", err)
|
||||||
|
}
|
||||||
|
defer func(key string) {
|
||||||
|
if err := deleteConfigByKey(key); err != nil {
|
||||||
|
t.Fatalf("failed to delete configuration %s: %v", key, err)
|
||||||
|
}
|
||||||
|
}(cfg1.Key)
|
||||||
|
|
||||||
|
cfg2 := &models.Config{
|
||||||
|
Key: "key2",
|
||||||
|
Value: "value2",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := InsertOrUpdateConfigs([]*models.Config{cfg1, cfg2}); err != nil {
|
||||||
|
t.Fatalf("failed to insert or update configurations: %v", err)
|
||||||
|
}
|
||||||
|
defer func(key string) {
|
||||||
|
if err := deleteConfigByKey(key); err != nil {
|
||||||
|
t.Fatalf("failed to delete configuration %s: %v", key, err)
|
||||||
|
}
|
||||||
|
}(cfg2.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthModeCanBeModified(t *testing.T) {
|
||||||
|
c, err := GetOrmer().QueryTable(&models.User{}).Count()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to count users: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == 1 {
|
||||||
|
flag, err := AuthModeCanBeModified()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to determine whether auth mode can be modified: %v", err)
|
||||||
|
}
|
||||||
|
if !flag {
|
||||||
|
t.Errorf("unexpected result: %t != %t", flag, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
user := models.User{
|
||||||
|
Username: "user_for_config_test",
|
||||||
|
Email: "user_for_config_test@vmware.com",
|
||||||
|
Password: "P@ssword",
|
||||||
|
Realname: "user_for_config_test",
|
||||||
|
}
|
||||||
|
id, err := Register(user)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to register user: %v", err)
|
||||||
|
}
|
||||||
|
defer func(id int64) {
|
||||||
|
if err := deleteUser(id); err != nil {
|
||||||
|
t.Fatalf("failed to delete user %d: %v", id, err)
|
||||||
|
}
|
||||||
|
}(id)
|
||||||
|
|
||||||
|
flag, err = AuthModeCanBeModified()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to determine whether auth mode can be modified: %v", err)
|
||||||
|
}
|
||||||
|
if flag {
|
||||||
|
t.Errorf("unexpected result: %t != %t", flag, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
flag, err := AuthModeCanBeModified()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to determine whether auth mode can be modified: %v", err)
|
||||||
|
}
|
||||||
|
if flag {
|
||||||
|
t.Errorf("unexpected result: %t != %t", flag, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
@ -17,10 +17,12 @@ package dao
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
|
//"github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
@ -42,7 +44,7 @@ func execUpdate(o orm.Ormer, sql string, params ...interface{}) error {
|
|||||||
func clearUp(username string) {
|
func clearUp(username string) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
o.Begin()
|
o.Begin()
|
||||||
|
|
||||||
err = execUpdate(o, `delete
|
err = execUpdate(o, `delete
|
||||||
@ -156,53 +158,63 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testForMySQL(m *testing.M) int {
|
func testForMySQL(m *testing.M) int {
|
||||||
db := os.Getenv("DATABASE")
|
dbHost := os.Getenv("MYSQL_HOST")
|
||||||
defer os.Setenv("DATABASE", db)
|
|
||||||
|
|
||||||
os.Setenv("DATABASE", "mysql")
|
|
||||||
|
|
||||||
dbHost := os.Getenv("DB_HOST")
|
|
||||||
if len(dbHost) == 0 {
|
if len(dbHost) == 0 {
|
||||||
log.Fatalf("environment variable DB_HOST is not set")
|
log.Fatalf("environment variable MYSQL_HOST is not set")
|
||||||
}
|
}
|
||||||
dbUser := os.Getenv("DB_USR")
|
dbUser := os.Getenv("MYSQL_USR")
|
||||||
if len(dbUser) == 0 {
|
if len(dbUser) == 0 {
|
||||||
log.Fatalf("environment variable DB_USR is not set")
|
log.Fatalf("environment variable MYSQL_USR is not set")
|
||||||
}
|
}
|
||||||
dbPort := os.Getenv("DB_PORT")
|
dbPortStr := os.Getenv("MYSQL_PORT")
|
||||||
if len(dbPort) == 0 {
|
if len(dbPortStr) == 0 {
|
||||||
log.Fatalf("environment variable DB_PORT is not set")
|
log.Fatalf("environment variable MYSQL_PORT is not set")
|
||||||
|
}
|
||||||
|
dbPort, err := strconv.Atoi(dbPortStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("invalid MYSQL_PORT: %v", err)
|
||||||
}
|
}
|
||||||
dbPassword := os.Getenv("DB_PWD")
|
|
||||||
|
|
||||||
log.Infof("DB_HOST: %s, DB_USR: %s, DB_PORT: %s, DB_PWD: %s\n", dbHost, dbUser, dbPort, dbPassword)
|
dbPassword := os.Getenv("MYSQL_PWD")
|
||||||
|
dbDatabase := os.Getenv("MYSQL_DATABASE")
|
||||||
|
if len(dbDatabase) == 0 {
|
||||||
|
log.Fatalf("environment variable MYSQL_DATABASE is not set")
|
||||||
|
}
|
||||||
|
|
||||||
os.Setenv("MYSQL_HOST", dbHost)
|
database := &models.Database{
|
||||||
os.Setenv("MYSQL_PORT", dbPort)
|
Type: "mysql",
|
||||||
os.Setenv("MYSQL_USR", dbUser)
|
MySQL: &models.MySQL{
|
||||||
os.Setenv("MYSQL_PWD", dbPassword)
|
Host: dbHost,
|
||||||
|
Port: dbPort,
|
||||||
|
Username: dbUser,
|
||||||
|
Password: dbPassword,
|
||||||
|
Database: dbDatabase,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
return testForAll(m)
|
log.Infof("MYSQL_HOST: %s, MYSQL_USR: %s, MYSQL_PORT: %s, MYSQL_PWD: %s\n", dbHost, dbUser, dbPort, dbPassword)
|
||||||
|
|
||||||
|
return testForAll(m, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testForSQLite(m *testing.M) int {
|
func testForSQLite(m *testing.M) int {
|
||||||
db := os.Getenv("DATABASE")
|
|
||||||
defer os.Setenv("DATABASE", db)
|
|
||||||
|
|
||||||
os.Setenv("DATABASE", "sqlite")
|
|
||||||
|
|
||||||
file := os.Getenv("SQLITE_FILE")
|
file := os.Getenv("SQLITE_FILE")
|
||||||
if len(file) == 0 {
|
if len(file) == 0 {
|
||||||
os.Setenv("SQLITE_FILE", "/registry.db")
|
log.Fatalf("environment variable SQLITE_FILE is not set")
|
||||||
defer os.Setenv("SQLITE_FILE", "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return testForAll(m)
|
database := &models.Database{
|
||||||
|
Type: "sqlite",
|
||||||
|
SQLite: &models.SQLite{
|
||||||
|
File: file,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return testForAll(m, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testForAll(m *testing.M) int {
|
func testForAll(m *testing.M, database *models.Database) int {
|
||||||
os.Setenv("AUTH_MODE", "db_auth")
|
initDatabaseForTest(database)
|
||||||
initDatabaseForTest()
|
|
||||||
clearUp(username)
|
clearUp(username)
|
||||||
|
|
||||||
return m.Run()
|
return m.Run()
|
||||||
@ -210,8 +222,8 @@ func testForAll(m *testing.M) int {
|
|||||||
|
|
||||||
var defaultRegistered = false
|
var defaultRegistered = false
|
||||||
|
|
||||||
func initDatabaseForTest() {
|
func initDatabaseForTest(db *models.Database) {
|
||||||
database, err := getDatabase()
|
database, err := getDatabase(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -226,6 +238,12 @@ func initDatabaseForTest() {
|
|||||||
if err := database.Register(alias); err != nil {
|
if err := database.Register(alias); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if alias != "default" {
|
||||||
|
if err = globalOrm.Using(alias); err != nil {
|
||||||
|
log.Fatalf("failed to create new orm: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegister(t *testing.T) {
|
func TestRegister(t *testing.T) {
|
||||||
|
@ -16,15 +16,11 @@
|
|||||||
package dao
|
package dao
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
_ "github.com/go-sql-driver/mysql" //register mysql driver
|
_ "github.com/go-sql-driver/mysql" //register mysql driver
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mysql struct {
|
type mysql struct {
|
||||||
@ -48,7 +44,8 @@ func NewMySQL(host, port, usr, pwd, database string) Database {
|
|||||||
|
|
||||||
// Register registers MySQL as the underlying database used
|
// Register registers MySQL as the underlying database used
|
||||||
func (m *mysql) Register(alias ...string) error {
|
func (m *mysql) Register(alias ...string) error {
|
||||||
if err := m.testConn(m.host, m.port); err != nil {
|
|
||||||
|
if err := utils.TestTCPConn(m.host+":"+m.port, 60, 2); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,30 +62,6 @@ func (m *mysql) Register(alias ...string) error {
|
|||||||
return orm.RegisterDataBase(an, "mysql", conn)
|
return orm.RegisterDataBase(an, "mysql", conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mysql) testConn(host, port string) error {
|
|
||||||
ch := make(chan int, 1)
|
|
||||||
go func() {
|
|
||||||
var err error
|
|
||||||
var c net.Conn
|
|
||||||
for {
|
|
||||||
c, err = net.DialTimeout("tcp", host+":"+port, 20*time.Second)
|
|
||||||
if err == nil {
|
|
||||||
c.Close()
|
|
||||||
ch <- 1
|
|
||||||
} else {
|
|
||||||
log.Errorf("failed to connect to db, retry after 2 seconds :%v", err)
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-ch:
|
|
||||||
return nil
|
|
||||||
case <-time.After(60 * time.Second):
|
|
||||||
return errors.New("failed to connect to database after 60 seconds")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of MySQL
|
// Name returns the name of MySQL
|
||||||
func (m *mysql) Name() string {
|
func (m *mysql) Name() string {
|
||||||
return "MySQL"
|
return "MySQL"
|
||||||
|
@ -38,6 +38,11 @@ func TestDeleteUser(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to register user: %v", err)
|
t.Fatalf("failed to register user: %v", err)
|
||||||
}
|
}
|
||||||
|
defer func(id int64) {
|
||||||
|
if err := deleteUser(id); err != nil {
|
||||||
|
t.Fatalf("failed to delete user %d: %v", id, err)
|
||||||
|
}
|
||||||
|
}(id)
|
||||||
|
|
||||||
err = DeleteUser(int(id))
|
err = DeleteUser(int(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -67,3 +72,11 @@ func TestDeleteUser(t *testing.T) {
|
|||||||
expected)
|
expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteUser(id int64) error {
|
||||||
|
if _, err := GetOrmer().QueryTable(&models.User{}).
|
||||||
|
Filter("UserID", id).Delete(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
95
src/common/models/config.go
Normal file
95
src/common/models/config.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
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 models
|
||||||
|
|
||||||
|
// Authentication ...
|
||||||
|
type Authentication struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
SelfRegistration bool `json:"self_registration"`
|
||||||
|
LDAP *LDAP `json:"ldap,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LDAP ...
|
||||||
|
type LDAP struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
SearchDN string `json:"search_dn"`
|
||||||
|
SearchPwd string `json:"search_pwd"`
|
||||||
|
BaseDN string `json:"base_dn"`
|
||||||
|
Filter string `json:"filter"`
|
||||||
|
UID string `json:"uid"`
|
||||||
|
Scope int `json:"scope"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database ...
|
||||||
|
type Database struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
MySQL *MySQL `json:"mysql,omitempty"`
|
||||||
|
SQLite *SQLite `json:"sqlite,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQL ...
|
||||||
|
type MySQL struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Database string `json:"database"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SQLite ...
|
||||||
|
type SQLite struct {
|
||||||
|
File string `json:"file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email ...
|
||||||
|
type Email struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port string `json:"port"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
TLS bool `json:"tls"`
|
||||||
|
Identity string `json:"identity"`
|
||||||
|
From string `json:"from"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registry ...
|
||||||
|
type Registry struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenService ...
|
||||||
|
type TokenService struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemCfg holds all configurations of system
|
||||||
|
type SystemCfg struct {
|
||||||
|
DomainName string `json:"domain_name"` // Harbor external URL: protocal://host:port
|
||||||
|
Authentication *Authentication `json:"authentication"`
|
||||||
|
Database *Database `json:"database"`
|
||||||
|
TokenService *TokenService `json:"token_service"`
|
||||||
|
Registry *Registry `json:"registry"`
|
||||||
|
Email *Email `json:"email"`
|
||||||
|
VerifyRemoteCert bool `json:"verify_remote_cert"`
|
||||||
|
ProjectCreationRestriction string `json:"project_creation_restriction"`
|
||||||
|
MaxJobWorkers int `json:"max_job_workers"`
|
||||||
|
JobLogDir string `json:"job_log_dir"`
|
||||||
|
InitialAdminPwd string `json:"initial_admin_pwd"`
|
||||||
|
CompressJS bool `json:"compress_js"` //TODO remove
|
||||||
|
TokenExpiration int `json:"token_expiration"` // in minute
|
||||||
|
SecretKey string `json:"secret_key"`
|
||||||
|
CfgExpiration int `json:"cfg_expiration"`
|
||||||
|
}
|
@ -13,17 +13,19 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package utils
|
package email
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"strings"
|
//"strings"
|
||||||
|
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
//"github.com/astaxie/beego"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mail holds information about content of Email
|
// Mail holds information about content of Email
|
||||||
@ -34,24 +36,15 @@ type Mail struct {
|
|||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailConfig holds information about Email configurations
|
var mc models.Email
|
||||||
type MailConfig struct {
|
|
||||||
Identity string
|
|
||||||
Host string
|
|
||||||
Port string
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
TLS bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var mc MailConfig
|
|
||||||
|
|
||||||
// SendMail sends Email according to the configurations
|
// SendMail sends Email according to the configurations
|
||||||
func (m Mail) SendMail() error {
|
func (m Mail) SendMail() error {
|
||||||
|
mc, err := config.Email()
|
||||||
if mc.Host == "" {
|
if err != nil {
|
||||||
loadConfig()
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mailTemplate, err := template.ParseFiles("views/mail.tpl")
|
mailTemplate, err := template.ParseFiles("views/mail.tpl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -123,6 +116,7 @@ func sendMailWithTLS(m Mail, auth smtp.Auth, content []byte) error {
|
|||||||
return client.Quit()
|
return client.Quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func loadConfig() {
|
func loadConfig() {
|
||||||
config, err := beego.AppConfig.GetSection("mail")
|
config, err := beego.AppConfig.GetSection("mail")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -142,3 +136,4 @@ func loadConfig() {
|
|||||||
TLS: useTLS,
|
TLS: useTLS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
@ -22,8 +22,6 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger = New(os.Stdout, NewTextFormatter(), WarningLevel)
|
var logger = New(os.Stdout, NewTextFormatter(), WarningLevel)
|
||||||
@ -31,7 +29,7 @@ var logger = New(os.Stdout, NewTextFormatter(), WarningLevel)
|
|||||||
func init() {
|
func init() {
|
||||||
logger.callDepth = 4
|
logger.callDepth = 4
|
||||||
|
|
||||||
lvl := config.LogLevel()
|
lvl := os.Getenv("LOG_LEVEL")
|
||||||
if len(lvl) == 0 {
|
if len(lvl) == 0 {
|
||||||
logger.SetLevel(InfoLevel)
|
logger.SetLevel(InfoLevel)
|
||||||
return
|
return
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/config"
|
//"github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/common/utils/registry"
|
"github.com/vmware/harbor/src/common/utils/registry"
|
||||||
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
||||||
@ -234,12 +234,15 @@ func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []
|
|||||||
// 2. the realm field returned by registry is an IP which can not reachable
|
// 2. the realm field returned by registry is an IP which can not reachable
|
||||||
// inside Harbor
|
// inside Harbor
|
||||||
func tokenURL(realm string) string {
|
func tokenURL(realm string) string {
|
||||||
extEndpoint := config.ExtEndpoint()
|
//TODO
|
||||||
tokenEndpoint := config.TokenEndpoint()
|
/*
|
||||||
if len(extEndpoint) != 0 && len(tokenEndpoint) != 0 &&
|
extEndpoint := config.ExtEndpoint()
|
||||||
strings.Contains(realm, extEndpoint) {
|
tokenEndpoint := config.TokenEndpoint()
|
||||||
realm = strings.TrimRight(tokenEndpoint, "/") + "/service/token"
|
if len(extEndpoint) != 0 && len(tokenEndpoint) != 0 &&
|
||||||
}
|
strings.Contains(realm, extEndpoint) {
|
||||||
|
realm = strings.TrimRight(tokenEndpoint, "/") + "/service/token"
|
||||||
|
}
|
||||||
|
*/
|
||||||
return realm
|
return realm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,10 +16,14 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FormatEndpoint formats endpoint
|
// FormatEndpoint formats endpoint
|
||||||
@ -70,3 +74,37 @@ func GenerateRandomString() string {
|
|||||||
}
|
}
|
||||||
return string(result)
|
return string(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeout in second
|
||||||
|
func TestTCPConn(addr string, timeout, interval int) error {
|
||||||
|
success := make(chan int)
|
||||||
|
cancel := make(chan int)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-cancel:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
conn, err := net.DialTimeout("tcp", addr, time.Duration(timeout)*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to connect to tcp://%s, retry after %d seconds :%v",
|
||||||
|
addr, interval, err)
|
||||||
|
time.Sleep(time.Duration(interval) * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
success <- 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-success:
|
||||||
|
return nil
|
||||||
|
case <-time.After(time.Duration(timeout) * time.Second):
|
||||||
|
cancel <- 1
|
||||||
|
return fmt.Errorf("failed to connect to tcp:%s after %d seconds", addr, timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,12 +25,12 @@ import (
|
|||||||
|
|
||||||
"github.com/vmware/harbor/src/common/api"
|
"github.com/vmware/harbor/src/common/api"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/jobservice/job"
|
|
||||||
"github.com/vmware/harbor/src/jobservice/config"
|
|
||||||
"github.com/vmware/harbor/src/jobservice/utils"
|
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
u "github.com/vmware/harbor/src/common/utils"
|
u "github.com/vmware/harbor/src/common/utils"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/config"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/job"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReplicationJob handles /api/replicationJobs /api/replicationJobs/:id/log
|
// ReplicationJob handles /api/replicationJobs /api/replicationJobs/:id/log
|
||||||
@ -171,7 +171,13 @@ func (rj *ReplicationJob) GetLog() {
|
|||||||
rj.RenderError(http.StatusBadRequest, "Invalid job id")
|
rj.RenderError(http.StatusBadRequest, "Invalid job id")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logFile := utils.GetJobLogPath(jid)
|
logFile, err := utils.GetJobLogPath(jid)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get log path of job %s: %v", idStr, err)
|
||||||
|
rj.RenderError(http.StatusInternalServerError,
|
||||||
|
http.StatusText(http.StatusInternalServerError))
|
||||||
|
return
|
||||||
|
}
|
||||||
rj.Ctx.Output.Download(logFile)
|
rj.Ctx.Output.Download(logFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,121 +16,149 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
//"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultMaxWorkers int = 10
|
var mg *comcfg.Manager
|
||||||
|
|
||||||
var maxJobWorkers int
|
// Configuration holds configurations of Jobservice
|
||||||
var localUIURL string
|
type Configuration struct {
|
||||||
var localRegURL string
|
Database *models.Database `json:"database"`
|
||||||
var logDir string
|
Registry *models.Registry `json:"registry"`
|
||||||
var uiSecret string
|
VerifyRemoteCert bool `json:"verify_remote_cert"`
|
||||||
var secretKey string
|
MaxJobWorkers int `json:"max_job_workers"`
|
||||||
var verifyRemoteCert string
|
JobLogDir string `json:"job_log_dir"`
|
||||||
|
SecretKey string `json:"secret_key"`
|
||||||
|
CfgExpiration int `json:"cfg_expiration"`
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func Init() error {
|
||||||
maxWorkersEnv := os.Getenv("MAX_JOB_WORKERS")
|
adminServerURL := os.Getenv("ADMIN_SERVER_URL")
|
||||||
maxWorkers64, err := strconv.ParseInt(maxWorkersEnv, 10, 32)
|
if len(adminServerURL) == 0 {
|
||||||
maxJobWorkers = int(maxWorkers64)
|
adminServerURL = "http://admin_server"
|
||||||
|
}
|
||||||
|
mg = comcfg.NewManager("cfg", adminServerURL)
|
||||||
|
|
||||||
|
if err := mg.Loader.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := load(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := LogDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("Failed to parse max works setting, error: %v, the default value: %d will be used", err, defaultMaxWorkers)
|
return err
|
||||||
maxJobWorkers = defaultMaxWorkers
|
}
|
||||||
|
if err := os.MkdirAll(path, 0600); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
localRegURL = os.Getenv("REGISTRY_URL")
|
return nil
|
||||||
if len(localRegURL) == 0 {
|
}
|
||||||
localRegURL = "http://registry:5000"
|
|
||||||
|
// get returns configurations of jobservice from cache,
|
||||||
|
// if cache is null, it loads first
|
||||||
|
func get() (*Configuration, error) {
|
||||||
|
cfg := mg.GetFromCache()
|
||||||
|
if cfg != nil {
|
||||||
|
return cfg.(*Configuration), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
localUIURL = os.Getenv("UI_URL")
|
if err := load(); err != nil {
|
||||||
if len(localUIURL) == 0 {
|
return nil, err
|
||||||
localUIURL = "http://ui"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logDir = os.Getenv("LOG_DIR")
|
return mg.GetFromCache().(*Configuration), nil
|
||||||
if len(logDir) == 0 {
|
}
|
||||||
logDir = "/var/log"
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(logDir)
|
// load loads configurations of jobservice and puts them into cache
|
||||||
defer f.Close()
|
func load() error {
|
||||||
|
raw, err := mg.Loader.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
finfo, err := f.Stat()
|
|
||||||
|
cfg := &Configuration{}
|
||||||
|
if err = json.Unmarshal(raw, cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.Cache.Put(mg.Key, cfg,
|
||||||
|
time.Duration(cfg.CfgExpiration)*time.Second); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyRemoteCert returns bool value.
|
||||||
|
func VerifyRemoteCert() (bool, error) {
|
||||||
|
cfg, err := get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return true, err
|
||||||
}
|
|
||||||
if !finfo.IsDir() {
|
|
||||||
panic(fmt.Sprintf("%s is not a direcotry", logDir))
|
|
||||||
}
|
}
|
||||||
|
return cfg.VerifyRemoteCert, nil
|
||||||
|
}
|
||||||
|
|
||||||
uiSecret = os.Getenv("UI_SECRET")
|
// Database ...
|
||||||
if len(uiSecret) == 0 {
|
func Database() (*models.Database, error) {
|
||||||
panic("UI Secret is not set")
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return cfg.Database, nil
|
||||||
verifyRemoteCert = os.Getenv("VERIFY_REMOTE_CERT")
|
|
||||||
if len(verifyRemoteCert) == 0 {
|
|
||||||
verifyRemoteCert = "on"
|
|
||||||
}
|
|
||||||
|
|
||||||
configPath := os.Getenv("CONFIG_PATH")
|
|
||||||
if len(configPath) != 0 {
|
|
||||||
log.Infof("Config path: %s", configPath)
|
|
||||||
beego.LoadAppConfig("ini", configPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
secretKey = os.Getenv("SECRET_KEY")
|
|
||||||
if len(secretKey) != 16 {
|
|
||||||
panic("The length of secretkey has to be 16 characters!")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("config: maxJobWorkers: %d", maxJobWorkers)
|
|
||||||
log.Debugf("config: localUIURL: %s", localUIURL)
|
|
||||||
log.Debugf("config: localRegURL: %s", localRegURL)
|
|
||||||
log.Debugf("config: verifyRemoteCert: %s", verifyRemoteCert)
|
|
||||||
log.Debugf("config: logDir: %s", logDir)
|
|
||||||
log.Debugf("config: uiSecret: ******")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxJobWorkers ...
|
// MaxJobWorkers ...
|
||||||
func MaxJobWorkers() int {
|
func MaxJobWorkers() (int, error) {
|
||||||
return maxJobWorkers
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cfg.MaxJobWorkers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalUIURL returns the local ui url, job service will use this URL to call API hosted on ui process
|
// LocalUIURL returns the local ui url, job service will use this URL to call API hosted on ui process
|
||||||
func LocalUIURL() string {
|
func LocalUIURL() string {
|
||||||
return localUIURL
|
return "http://ui"
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalRegURL returns the local registry url, job service will use this URL to pull image from the registry
|
// LocalRegURL returns the local registry url, job service will use this URL to pull image from the registry
|
||||||
func LocalRegURL() string {
|
func LocalRegURL() (string, error) {
|
||||||
return localRegURL
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg.Registry.URL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogDir returns the absolute path to which the log file will be written
|
// LogDir returns the absolute path to which the log file will be written
|
||||||
func LogDir() string {
|
func LogDir() (string, error) {
|
||||||
return logDir
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg.JobLogDir, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UISecret will return the value of secret cookie for jobsevice to call UI API.
|
// UISecret will return the value of secret cookie for jobsevice to call UI API.
|
||||||
func UISecret() string {
|
func UISecret() string {
|
||||||
return uiSecret
|
return os.Getenv("UI_SECRET")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecretKey will return the secret key for encryption/decryption password in target.
|
// SecretKey will return the secret key for encryption/decryption password in target.
|
||||||
func SecretKey() string {
|
func SecretKey() (string, error) {
|
||||||
return secretKey
|
cfg, err := get()
|
||||||
}
|
if err != nil {
|
||||||
|
return "", err
|
||||||
// VerifyRemoteCert return the flag to tell jobservice whether or not verify the cert of remote registry
|
}
|
||||||
func VerifyRemoteCert() bool {
|
return cfg.SecretKey, nil
|
||||||
return verifyRemoteCert != "off"
|
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,12 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/jobservice/config"
|
|
||||||
"github.com/vmware/harbor/src/jobservice/replication"
|
|
||||||
"github.com/vmware/harbor/src/jobservice/utils"
|
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
uti "github.com/vmware/harbor/src/common/utils"
|
uti "github.com/vmware/harbor/src/common/utils"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/config"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/replication"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RepJobParm wraps the parm of a job
|
// RepJobParm wraps the parm of a job
|
||||||
@ -184,14 +184,17 @@ func (sm *SM) Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset resets the state machine so it will start handling another job.
|
// Reset resets the state machine so it will start handling another job.
|
||||||
func (sm *SM) Reset(jid int64) error {
|
func (sm *SM) Reset(jid int64) (err error) {
|
||||||
//To ensure the new jobID is visible to the thread to stop the SM
|
//To ensure the new jobID is visible to the thread to stop the SM
|
||||||
sm.lock.Lock()
|
sm.lock.Lock()
|
||||||
sm.JobID = jid
|
sm.JobID = jid
|
||||||
sm.desiredState = ""
|
sm.desiredState = ""
|
||||||
sm.lock.Unlock()
|
sm.lock.Unlock()
|
||||||
|
|
||||||
sm.Logger = utils.NewLogger(sm.JobID)
|
sm.Logger, err = utils.NewLogger(sm.JobID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
//init parms
|
//init parms
|
||||||
job, err := dao.GetRepJob(sm.JobID)
|
job, err := dao.GetRepJob(sm.JobID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -207,13 +210,22 @@ func (sm *SM) Reset(jid int64) error {
|
|||||||
if policy == nil {
|
if policy == nil {
|
||||||
return fmt.Errorf("The policy doesn't exist in DB, policy id:%d", job.PolicyID)
|
return fmt.Errorf("The policy doesn't exist in DB, policy id:%d", job.PolicyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
regURL, err := config.LocalRegURL()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verify, err := config.VerifyRemoteCert()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
sm.Parms = &RepJobParm{
|
sm.Parms = &RepJobParm{
|
||||||
LocalRegURL: config.LocalRegURL(),
|
LocalRegURL: regURL,
|
||||||
Repository: job.Repository,
|
Repository: job.Repository,
|
||||||
Tags: job.TagList,
|
Tags: job.TagList,
|
||||||
Enabled: policy.Enabled,
|
Enabled: policy.Enabled,
|
||||||
Operation: job.Operation,
|
Operation: job.Operation,
|
||||||
Insecure: !config.VerifyRemoteCert(),
|
Insecure: !verify,
|
||||||
}
|
}
|
||||||
if policy.Enabled == 0 {
|
if policy.Enabled == 0 {
|
||||||
//worker will cancel this job
|
//worker will cancel this job
|
||||||
@ -231,7 +243,11 @@ func (sm *SM) Reset(jid int64) error {
|
|||||||
pwd := target.Password
|
pwd := target.Password
|
||||||
|
|
||||||
if len(pwd) != 0 {
|
if len(pwd) != 0 {
|
||||||
pwd, err = uti.ReversibleDecrypt(pwd, config.SecretKey())
|
key, err := config.SecretKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pwd, err = uti.ReversibleDecrypt(pwd, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to decrypt password: %v", err)
|
return fmt.Errorf("failed to decrypt password: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,9 @@ package job
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/jobservice/config"
|
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type workerPool struct {
|
type workerPool struct {
|
||||||
@ -111,17 +111,22 @@ func NewWorker(id int) *Worker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitWorkerPool create workers according to configuration.
|
// InitWorkerPool create workers according to configuration.
|
||||||
func InitWorkerPool() {
|
func InitWorkerPool() error {
|
||||||
WorkerPool = &workerPool{
|
n, err := config.MaxJobWorkers()
|
||||||
workerChan: make(chan *Worker, config.MaxJobWorkers()),
|
if err != nil {
|
||||||
workerList: make([]*Worker, 0, config.MaxJobWorkers()),
|
return err
|
||||||
}
|
}
|
||||||
for i := 0; i < config.MaxJobWorkers(); i++ {
|
WorkerPool = &workerPool{
|
||||||
|
workerChan: make(chan *Worker, n),
|
||||||
|
workerList: make([]*Worker, 0, n),
|
||||||
|
}
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
worker := NewWorker(i)
|
worker := NewWorker(i)
|
||||||
WorkerPool.workerList = append(WorkerPool.workerList, worker)
|
WorkerPool.workerList = append(WorkerPool.workerList, worker)
|
||||||
worker.Start()
|
worker.Start()
|
||||||
log.Debugf("worker %d started", worker.ID)
|
log.Debugf("worker %d started", worker.ID)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch will listen to the jobQueue of job service and try to pick a free worker from the worker pool and assign the job to it.
|
// Dispatch will listen to the jobQueue of job service and try to pick a free worker from the worker pool and assign the job to it.
|
||||||
|
@ -18,13 +18,28 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/jobservice/job"
|
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/config"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/job"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
dao.InitDatabase()
|
log.Info("initializing configurations...")
|
||||||
|
if err := config.Init(); err != nil {
|
||||||
|
log.Fatalf("failed to initialize configurations: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("configurations initialization completed")
|
||||||
|
|
||||||
|
database, err := config.Database()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to get database configurations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dao.InitDatabase(database); err != nil {
|
||||||
|
log.Fatalf("failed to initialize database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
initRouters()
|
initRouters()
|
||||||
job.InitWorkerPool()
|
job.InitWorkerPool()
|
||||||
go job.Dispatch()
|
go job.Dispatch()
|
||||||
@ -48,3 +63,13 @@ func resumeJobs() {
|
|||||||
log.Warningf("Failed to jobs to resume, error: %v", err)
|
log.Warningf("Failed to jobs to resume, error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func init() {
|
||||||
|
configPath := os.Getenv("CONFIG_PATH")
|
||||||
|
if len(configPath) != 0 {
|
||||||
|
log.Infof("Config path: %s", configPath)
|
||||||
|
beego.LoadAppConfig("ini", configPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
@ -18,16 +18,20 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/jobservice/config"
|
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewLogger create a logger for a speicified job
|
// NewLogger create a logger for a speicified job
|
||||||
func NewLogger(jobID int64) *log.Logger {
|
func NewLogger(jobID int64) (*log.Logger, error) {
|
||||||
logFile := GetJobLogPath(jobID)
|
logFile, err := GetJobLogPath(jobID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
d := filepath.Dir(logFile)
|
d := filepath.Dir(logFile)
|
||||||
if _, err := os.Stat(d); os.IsNotExist(err) {
|
if _, err := os.Stat(d); os.IsNotExist(err) {
|
||||||
err := os.MkdirAll(d, 0660)
|
err := os.MkdirAll(d, 0660)
|
||||||
@ -40,11 +44,11 @@ func NewLogger(jobID int64) *log.Logger {
|
|||||||
log.Errorf("Failed to open log file %s, the log of job %d will be printed to standard output, the error: %v", logFile, jobID, err)
|
log.Errorf("Failed to open log file %s, the log of job %d will be printed to standard output, the error: %v", logFile, jobID, err)
|
||||||
f = os.Stdout
|
f = os.Stdout
|
||||||
}
|
}
|
||||||
return log.New(f, log.NewTextFormatter(), log.InfoLevel)
|
return log.New(f, log.NewTextFormatter(), log.InfoLevel), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetJobLogPath returns the absolute path in which the job log file is located.
|
// GetJobLogPath returns the absolute path in which the job log file is located.
|
||||||
func GetJobLogPath(jobID int64) string {
|
func GetJobLogPath(jobID int64) (string, error) {
|
||||||
f := fmt.Sprintf("job_%d.log", jobID)
|
f := fmt.Sprintf("job_%d.log", jobID)
|
||||||
k := jobID / 1000
|
k := jobID / 1000
|
||||||
p := ""
|
p := ""
|
||||||
@ -61,6 +65,10 @@ func GetJobLogPath(jobID int64) string {
|
|||||||
|
|
||||||
p = filepath.Join(d, p)
|
p = filepath.Join(d, p)
|
||||||
}
|
}
|
||||||
p = filepath.Join(config.LogDir(), p, f)
|
base, err := config.LogDir()
|
||||||
return p
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
p = filepath.Join(base, p, f)
|
||||||
|
return p, nil
|
||||||
}
|
}
|
||||||
|
245
src/ui/api/config.go
Normal file
245
src/ui/api/config.go
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
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 api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
//"strings"
|
||||||
|
|
||||||
|
"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/models"
|
||||||
|
//"github.com/vmware/harbor/src/common/utils"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigAPI struct {
|
||||||
|
api.BaseAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare validates the user
|
||||||
|
func (c *ConfigAPI) Prepare() {
|
||||||
|
userID := c.ValidateUser()
|
||||||
|
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to check the role of user: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isSysAdmin {
|
||||||
|
c.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns configurations
|
||||||
|
func (c *ConfigAPI) Get() {
|
||||||
|
cfg, err := config.GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get configurations: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO filter attr in sys config
|
||||||
|
|
||||||
|
c.Data["json"] = cfg
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put updates configurations
|
||||||
|
func (c *ConfigAPI) Put() {
|
||||||
|
m := map[string]string{}
|
||||||
|
c.DecodeJSONReq(&m)
|
||||||
|
if err := validateCfg(m); err != nil {
|
||||||
|
c.CustomAbort(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := m[comcfg.AUTH_MODE]; ok {
|
||||||
|
mode, err := config.AuthMode()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get auth mode: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode != value {
|
||||||
|
flag, err := authModeCanBeModified()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to determine whether auth mode can be modified: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !flag {
|
||||||
|
c.CustomAbort(http.StatusBadRequest,
|
||||||
|
fmt.Sprintf("%s can not be modified as new users have been inserted into database",
|
||||||
|
comcfg.AUTH_MODE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(m)
|
||||||
|
|
||||||
|
if err := config.Upload(m); err != nil {
|
||||||
|
log.Errorf("failed to upload configurations: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := config.Load(); err != nil {
|
||||||
|
log.Errorf("failed to load configurations: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO ldap timeout, scope value
|
||||||
|
func validateCfg(c map[string]string) error {
|
||||||
|
if value, ok := c[comcfg.AUTH_MODE]; ok {
|
||||||
|
if value != comcfg.DB_AUTH && value != comcfg.LDAP_AUTH {
|
||||||
|
return fmt.Errorf("invalid %s, shoud be %s or %s", comcfg.AUTH_MODE, comcfg.DB_AUTH, comcfg.LDAP_AUTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value == comcfg.LDAP_AUTH {
|
||||||
|
if _, ok := c[comcfg.LDAP_URL]; !ok {
|
||||||
|
return fmt.Errorf("%s is missing", comcfg.LDAP_URL)
|
||||||
|
}
|
||||||
|
if _, ok := c[comcfg.LDAP_BASE_DN]; !ok {
|
||||||
|
return fmt.Errorf("%s is missing", comcfg.LDAP_BASE_DN)
|
||||||
|
}
|
||||||
|
if _, ok := c[comcfg.LDAP_UID]; !ok {
|
||||||
|
return fmt.Errorf("%s is missing", comcfg.LDAP_UID)
|
||||||
|
}
|
||||||
|
if _, ok := c[comcfg.LDAP_SCOPE]; !ok {
|
||||||
|
return fmt.Errorf("%s is missing", comcfg.LDAP_SCOPE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ldapURL, ok := c[comcfg.LDAP_URL]; ok && len(ldapURL) == 0 {
|
||||||
|
return fmt.Errorf("%s is empty", comcfg.LDAP_URL)
|
||||||
|
}
|
||||||
|
if baseDN, ok := c[comcfg.LDAP_BASE_DN]; ok && len(baseDN) == 0 {
|
||||||
|
return fmt.Errorf("%s is empty", comcfg.LDAP_BASE_DN)
|
||||||
|
}
|
||||||
|
if uID, ok := c[comcfg.LDAP_UID]; ok && len(uID) == 0 {
|
||||||
|
return fmt.Errorf("%s is empty", comcfg.LDAP_UID)
|
||||||
|
}
|
||||||
|
if scope, ok := c[comcfg.LDAP_SCOPE]; ok &&
|
||||||
|
scope != comcfg.LDAP_SCOPE_BASE &&
|
||||||
|
scope != comcfg.LDAP_SCOPE_ONELEVEL &&
|
||||||
|
scope != comcfg.LDAP_SCOPE_SUBTREE {
|
||||||
|
return fmt.Errorf("invalid %s, should be %s, %s or %s",
|
||||||
|
comcfg.LDAP_SCOPE,
|
||||||
|
comcfg.LDAP_SCOPE_BASE,
|
||||||
|
comcfg.LDAP_SCOPE_ONELEVEL,
|
||||||
|
comcfg.LDAP_SCOPE_SUBTREE)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self, ok := c[comcfg.SELF_REGISTRATION]; ok &&
|
||||||
|
self != "true" && self != "false" {
|
||||||
|
return fmt.Errorf("%s should be %s or %s",
|
||||||
|
comcfg.SELF_REGISTRATION, "true", "false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if port, ok := c[comcfg.EMAIL_SERVER_PORT]; ok {
|
||||||
|
if p, err := strconv.Atoi(port); err != nil || p < 0 || p > 65535 {
|
||||||
|
return fmt.Errorf("invalid %s", comcfg.EMAIL_SERVER_PORT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ssl, ok := c[comcfg.EMAIL_SSL]; ok && ssl != "true" && ssl != "false" {
|
||||||
|
return fmt.Errorf("%s should be true or false", comcfg.EMAIL_SSL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if crt, ok := c[comcfg.PROJECT_CREATION_RESTRICTION]; ok &&
|
||||||
|
crt != comcfg.PRO_CRT_RESTR_EVERYONE &&
|
||||||
|
crt != comcfg.PRO_CRT_RESTR_ADM_ONLY {
|
||||||
|
return fmt.Errorf("invalid %s, should be %s or %s",
|
||||||
|
comcfg.PROJECT_CREATION_RESTRICTION,
|
||||||
|
comcfg.PRO_CRT_RESTR_ADM_ONLY,
|
||||||
|
comcfg.PRO_CRT_RESTR_EVERYONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
if verify, ok := c[comcfg.VERIFY_REMOTE_CERT]; ok && verify != "true" && verify != "false" {
|
||||||
|
return fmt.Errorf("invalid %s, should be true or false", comcfg.VERIFY_REMOTE_CERT)
|
||||||
|
}
|
||||||
|
|
||||||
|
if worker, ok := c[comcfg.MAX_JOB_WORKERS]; ok {
|
||||||
|
if w, err := strconv.Atoi(worker); err != nil || w <= 0 {
|
||||||
|
return fmt.Errorf("invalid %s", comcfg.MAX_JOB_WORKERS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func convert() ([]*models.Config, error) {
|
||||||
|
cfgs := []*models.Config{}
|
||||||
|
var err error
|
||||||
|
pwdKeys := []string{config.LDAP_SEARCH_PWD, config.EMAIL_PWD}
|
||||||
|
for _, pwdKey := range pwdKeys {
|
||||||
|
if pwd, ok := c[pwdKey]; ok && len(pwd) != 0 {
|
||||||
|
c[pwdKey], err = utils.ReversibleEncrypt(pwd, ui_cfg.SecretKey())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range configKeys {
|
||||||
|
if value, ok := c[key]; ok {
|
||||||
|
cfgs = append(cfgs, &models.Config{
|
||||||
|
Key: key,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfgs, nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
//[]*models.Config >> cfgForGet
|
||||||
|
func convert(cfg *config.Configuration) (map[string]interface{}, error) {
|
||||||
|
result := map[string]interface{}{}
|
||||||
|
|
||||||
|
for _, config := range configs {
|
||||||
|
cfg[config.Key] = &value{
|
||||||
|
Value: config.Value,
|
||||||
|
Editable: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dels := []string{config.LDAP_SEARCH_PWD, config.EMAIL_PWD}
|
||||||
|
for _, del := range dels {
|
||||||
|
if _, ok := cfg[del]; ok {
|
||||||
|
delete(cfg, del)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flag, err := authModeCanBeModified()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg[config.AUTH_MODE].Editable = flag
|
||||||
|
|
||||||
|
return cfgForGet(cfg), nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
func authModeCanBeModified() (bool, error) {
|
||||||
|
return dao.AuthModeCanBeModified()
|
||||||
|
}
|
@ -77,7 +77,13 @@ func (p *ProjectAPI) Post() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to check admin role: %v", err)
|
log.Errorf("Failed to check admin role: %v", err)
|
||||||
}
|
}
|
||||||
if !isSysAdmin && config.OnlyAdminCreateProject() {
|
|
||||||
|
onlyAdmin, err := config.OnlyAdminCreateProject()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to determine whether only admin can create projects: %v", err)
|
||||||
|
p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
if !isSysAdmin && onlyAdmin {
|
||||||
log.Errorf("Only sys admin can create project")
|
log.Errorf("Only sys admin can create project")
|
||||||
p.RenderError(http.StatusForbidden, "Only system admin can create project")
|
p.RenderError(http.StatusForbidden, "Only system admin can create project")
|
||||||
return
|
return
|
||||||
|
@ -361,11 +361,19 @@ func (ra *RepositoryAPI) GetManifests() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {
|
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {
|
||||||
endpoint := config.InternalRegistryURL()
|
endpoint, err := config.RegistryURL()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
insecure, err := api.GetIsInsecure()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
username, password, ok := ra.Ctx.Request.BasicAuth()
|
username, password, ok := ra.Ctx.Request.BasicAuth()
|
||||||
if ok {
|
if ok {
|
||||||
return newRepositoryClient(endpoint, api.GetIsInsecure(), username, password,
|
return newRepositoryClient(endpoint, insecure, username, password,
|
||||||
repoName, "repository", repoName, "pull", "push", "*")
|
repoName, "repository", repoName, "pull", "push", "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +382,7 @@ func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return cache.NewRepositoryClient(endpoint, api.GetIsInsecure(), username, repoName,
|
return cache.NewRepositoryClient(endpoint, insecure, username, repoName,
|
||||||
"repository", repoName, "pull", "push", "*")
|
"repository", repoName, "pull", "push", "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,12 @@ type TargetAPI struct {
|
|||||||
|
|
||||||
// Prepare validates the user
|
// Prepare validates the user
|
||||||
func (t *TargetAPI) Prepare() {
|
func (t *TargetAPI) Prepare() {
|
||||||
t.secretKey = config.SecretKey()
|
var err error
|
||||||
|
t.secretKey, err = config.SecretKey()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get secret key: %v", err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
userID := t.ValidateUser()
|
userID := t.ValidateUser()
|
||||||
isSysAdmin, err := dao.IsAdminRole(userID)
|
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||||
@ -97,7 +102,12 @@ func (t *TargetAPI) Ping() {
|
|||||||
password = t.GetString("password")
|
password = t.GetString("password")
|
||||||
}
|
}
|
||||||
|
|
||||||
registry, err := newRegistryClient(endpoint, api.GetIsInsecure(), username, password,
|
insecure, err := api.GetIsInsecure()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to check whether insecure or not: %v", err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
registry, err := newRegistryClient(endpoint, insecure, username, password,
|
||||||
"", "", "")
|
"", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// timeout, dns resolve error, connection refused, etc.
|
// timeout, dns resolve error, connection refused, etc.
|
||||||
|
@ -46,10 +46,21 @@ type passwordReq struct {
|
|||||||
|
|
||||||
// Prepare validates the URL and parms
|
// Prepare validates the URL and parms
|
||||||
func (ua *UserAPI) Prepare() {
|
func (ua *UserAPI) Prepare() {
|
||||||
|
mode, err := config.AuthMode()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get auth mode: %v", err)
|
||||||
|
ua.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
ua.AuthMode = config.AuthMode()
|
ua.AuthMode = mode
|
||||||
|
|
||||||
ua.SelfRegistration = config.SelfRegistration()
|
self, err := config.SelfRegistration()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get self registration: %v", err)
|
||||||
|
ua.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
ua.SelfRegistration = self
|
||||||
|
|
||||||
if ua.Ctx.Input.IsPost() {
|
if ua.Ctx.Input.IsPost() {
|
||||||
sessionUserID := ua.GetSession("userId")
|
sessionUserID := ua.GetSession("userId")
|
||||||
@ -82,7 +93,6 @@ func (ua *UserAPI) Prepare() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
ua.IsAdmin, err = dao.IsAdminRole(ua.currentUserID)
|
ua.IsAdmin, err = dao.IsAdminRole(ua.currentUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error occurred in IsAdminRole:%v", err)
|
log.Errorf("Error occurred in IsAdminRole:%v", err)
|
||||||
@ -234,7 +244,7 @@ func (ua *UserAPI) Delete() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AuthMode() == "ldap_auth" {
|
if ua.AuthMode == "ldap_auth" {
|
||||||
ua.CustomAbort(http.StatusForbidden, "user can not be deleted in LDAP authentication mode")
|
ua.CustomAbort(http.StatusForbidden, "user can not be deleted in LDAP authentication mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,11 +20,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
@ -242,7 +240,7 @@ func addAuthentication(req *http.Request) {
|
|||||||
// SyncRegistry syncs the repositories of registry with database.
|
// SyncRegistry syncs the repositories of registry with database.
|
||||||
func SyncRegistry() error {
|
func SyncRegistry() error {
|
||||||
|
|
||||||
log.Debugf("Start syncing repositories from registry to DB... ")
|
log.Infof("Start syncing repositories from registry to DB... ")
|
||||||
|
|
||||||
reposInRegistry, err := catalog()
|
reposInRegistry, err := catalog()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -304,7 +302,7 @@ func SyncRegistry() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Sync repositories from registry to DB is done.")
|
log.Infof("Sync repositories from registry to DB is done.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,7 +348,10 @@ func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove the workaround when the bug of registry is fixed
|
// TODO remove the workaround when the bug of registry is fixed
|
||||||
endpoint := config.InternalRegistryURL()
|
endpoint, err := config.RegistryURL()
|
||||||
|
if err != nil {
|
||||||
|
return needsAdd, needsDel, err
|
||||||
|
}
|
||||||
client, err := cache.NewRepositoryClient(endpoint, true,
|
client, err := cache.NewRepositoryClient(endpoint, true,
|
||||||
"admin", repoInR, "repository", repoInR)
|
"admin", repoInR, "repository", repoInR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -372,7 +373,10 @@ func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string
|
|||||||
j++
|
j++
|
||||||
} else {
|
} else {
|
||||||
// TODO remove the workaround when the bug of registry is fixed
|
// TODO remove the workaround when the bug of registry is fixed
|
||||||
endpoint := config.InternalRegistryURL()
|
endpoint, err := config.RegistryURL()
|
||||||
|
if err != nil {
|
||||||
|
return needsAdd, needsDel, err
|
||||||
|
}
|
||||||
client, err := cache.NewRepositoryClient(endpoint, true,
|
client, err := cache.NewRepositoryClient(endpoint, true,
|
||||||
"admin", repoInR, "repository", repoInR)
|
"admin", repoInR, "repository", repoInR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -422,32 +426,18 @@ func projectExists(repository string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initRegistryClient() (r *registry.Registry, err error) {
|
func initRegistryClient() (r *registry.Registry, err error) {
|
||||||
endpoint := config.InternalRegistryURL()
|
endpoint, err := config.RegistryURL()
|
||||||
|
if err != nil {
|
||||||
addr := endpoint
|
return nil, err
|
||||||
if strings.Contains(endpoint, "/") {
|
|
||||||
addr = endpoint[strings.LastIndex(endpoint, "/")+1:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan int, 1)
|
addr := endpoint
|
||||||
go func() {
|
if strings.Contains(endpoint, "://") {
|
||||||
var err error
|
addr = strings.Split(endpoint, "://")[1]
|
||||||
var c net.Conn
|
}
|
||||||
for {
|
|
||||||
c, err = net.DialTimeout("tcp", addr, 20*time.Second)
|
if err := utils.TestTCPConn(addr, 60, 2); err != nil {
|
||||||
if err == nil {
|
return nil, err
|
||||||
c.Close()
|
|
||||||
ch <- 1
|
|
||||||
} else {
|
|
||||||
log.Errorf("failed to connect to registry client, retry after 2 seconds :%v", err)
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-ch:
|
|
||||||
case <-time.After(60 * time.Second):
|
|
||||||
panic("Failed to connect to registry client after 60 seconds")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registryClient, err := cache.NewRegistryClient(endpoint, true, "admin",
|
registryClient, err := cache.NewRegistryClient(endpoint, true, "admin",
|
||||||
|
@ -50,7 +50,10 @@ func Register(name string, authenticator Authenticator) {
|
|||||||
// Login authenticates user credentials based on setting.
|
// Login authenticates user credentials based on setting.
|
||||||
func Login(m models.AuthModel) (*models.User, error) {
|
func Login(m models.AuthModel) (*models.User, error) {
|
||||||
|
|
||||||
var authMode = config.AuthMode()
|
authMode, err := config.AuthMode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if authMode == "" || m.Principal == "admin" {
|
if authMode == "" || m.Principal == "admin" {
|
||||||
authMode = "db_auth"
|
authMode = "db_auth"
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,13 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
|||||||
return nil, fmt.Errorf("the principal contains meta char: %q", c)
|
return nil, fmt.Errorf("the principal contains meta char: %q", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ldapURL := config.LDAP().URL
|
|
||||||
|
settings, err := config.LDAP()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ldapURL := settings.URL
|
||||||
if ldapURL == "" {
|
if ldapURL == "" {
|
||||||
return nil, errors.New("can not get any available LDAP_URL")
|
return nil, errors.New("can not get any available LDAP_URL")
|
||||||
}
|
}
|
||||||
@ -57,16 +63,16 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
|||||||
}
|
}
|
||||||
ldap.SetOption(openldap.LDAP_OPT_PROTOCOL_VERSION, openldap.LDAP_VERSION3)
|
ldap.SetOption(openldap.LDAP_OPT_PROTOCOL_VERSION, openldap.LDAP_VERSION3)
|
||||||
|
|
||||||
ldapBaseDn := config.LDAP().BaseDn
|
ldapBaseDn := settings.BaseDN
|
||||||
if ldapBaseDn == "" {
|
if ldapBaseDn == "" {
|
||||||
return nil, errors.New("can not get any available LDAP_BASE_DN")
|
return nil, errors.New("can not get any available LDAP_BASE_DN")
|
||||||
}
|
}
|
||||||
log.Debug("baseDn:", ldapBaseDn)
|
log.Debug("baseDn:", ldapBaseDn)
|
||||||
|
|
||||||
ldapSearchDn := config.LDAP().SearchDn
|
ldapSearchDn := settings.SearchDN
|
||||||
if ldapSearchDn != "" {
|
if ldapSearchDn != "" {
|
||||||
log.Debug("Search DN: ", ldapSearchDn)
|
log.Debug("Search DN: ", ldapSearchDn)
|
||||||
ldapSearchPwd := config.LDAP().SearchPwd
|
ldapSearchPwd := settings.SearchPwd
|
||||||
err = ldap.Bind(ldapSearchDn, ldapSearchPwd)
|
err = ldap.Bind(ldapSearchDn, ldapSearchPwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Bind search dn error", err)
|
log.Debug("Bind search dn error", err)
|
||||||
@ -74,8 +80,8 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attrName := config.LDAP().UID
|
attrName := settings.UID
|
||||||
filter := config.LDAP().Filter
|
filter := settings.Filter
|
||||||
if filter != "" {
|
if filter != "" {
|
||||||
filter = "(&" + filter + "(" + attrName + "=" + m.Principal + "))"
|
filter = "(&" + filter + "(" + attrName + "=" + m.Principal + "))"
|
||||||
} else {
|
} else {
|
||||||
@ -83,11 +89,11 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
|||||||
}
|
}
|
||||||
log.Debug("one or more filter", filter)
|
log.Debug("one or more filter", filter)
|
||||||
|
|
||||||
ldapScope := config.LDAP().Scope
|
ldapScope := settings.Scope
|
||||||
var scope int
|
var scope int
|
||||||
if ldapScope == "1" {
|
if ldapScope == 1 {
|
||||||
scope = openldap.LDAP_SCOPE_BASE
|
scope = openldap.LDAP_SCOPE_BASE
|
||||||
} else if ldapScope == "2" {
|
} else if ldapScope == 2 {
|
||||||
scope = openldap.LDAP_SCOPE_ONELEVEL
|
scope = openldap.LDAP_SCOPE_ONELEVEL
|
||||||
} else {
|
} else {
|
||||||
scope = openldap.LDAP_SCOPE_SUBTREE
|
scope = openldap.LDAP_SCOPE_SUBTREE
|
||||||
|
@ -13,143 +13,225 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package config provides methods to get configurations required by code in src/ui
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"encoding/json"
|
||||||
"strings"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
commonConfig "github.com/vmware/harbor/src/common/config"
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LDAPSetting wraps the setting of an LDAP server
|
var mg *comcfg.Manager
|
||||||
type LDAPSetting struct {
|
|
||||||
URL string
|
type Configuration struct {
|
||||||
BaseDn string
|
DomainName string `json:"domain_name"` // Harbor external URL: protocal://host:port
|
||||||
SearchDn string
|
Authentication *models.Authentication `json:"authentication"`
|
||||||
SearchPwd string
|
Database *models.Database `json:"database"`
|
||||||
UID string
|
TokenService *models.TokenService `json:"token_service"`
|
||||||
Filter string
|
Registry *models.Registry `json:"registry"`
|
||||||
Scope string
|
Email *models.Email `json:"email"`
|
||||||
|
VerifyRemoteCert bool `json:"verify_remote_cert"`
|
||||||
|
ProjectCreationRestriction string `json:"project_creation_restriction"`
|
||||||
|
InitialAdminPwd string `json:"initial_admin_pwd"`
|
||||||
|
//TODO remove
|
||||||
|
CompressJS bool `json:"compress_js"`
|
||||||
|
TokenExpiration int `json:"token_expiration"`
|
||||||
|
SecretKey string `json:"secret_key"`
|
||||||
|
CfgExpiration int `json:"cfg_expiration`
|
||||||
}
|
}
|
||||||
|
|
||||||
type uiParser struct{}
|
func Init() error {
|
||||||
|
adminServerURL := os.Getenv("ADMIN_SERVER_URL")
|
||||||
|
if len(adminServerURL) == 0 {
|
||||||
|
adminServerURL = "http://admin_server"
|
||||||
|
}
|
||||||
|
mg = comcfg.NewManager("cfg", adminServerURL)
|
||||||
|
|
||||||
// Parse parses the auth settings url settings and other configuration consumed by code under src/ui
|
if err := mg.Loader.Init(); err != nil {
|
||||||
func (up *uiParser) Parse(raw map[string]string, config map[string]interface{}) error {
|
return err
|
||||||
mode := raw["AUTH_MODE"]
|
|
||||||
if mode == "ldap_auth" {
|
|
||||||
setting := LDAPSetting{
|
|
||||||
URL: raw["LDAP_URL"],
|
|
||||||
BaseDn: raw["LDAP_BASE_DN"],
|
|
||||||
SearchDn: raw["LDAP_SEARCH_DN"],
|
|
||||||
SearchPwd: raw["LDAP_SEARCH_PWD"],
|
|
||||||
UID: raw["LDAP_UID"],
|
|
||||||
Filter: raw["LDAP_FILTER"],
|
|
||||||
Scope: raw["LDAP_SCOPE"],
|
|
||||||
}
|
|
||||||
config["ldap"] = setting
|
|
||||||
}
|
}
|
||||||
config["auth_mode"] = mode
|
|
||||||
var tokenExpiration = 30 //minutes
|
if err := Load(); err != nil {
|
||||||
if len(raw["TOKEN_EXPIRATION"]) > 0 {
|
return err
|
||||||
i, err := strconv.Atoi(raw["TOKEN_EXPIRATION"])
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("failed to parse token expiration: %v, using default value %d", err, tokenExpiration)
|
|
||||||
} else if i <= 0 {
|
|
||||||
log.Warningf("invalid token expiration, using default value: %d minutes", tokenExpiration)
|
|
||||||
} else {
|
|
||||||
tokenExpiration = i
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
config["token_exp"] = tokenExpiration
|
|
||||||
config["admin_password"] = raw["HARBOR_ADMIN_PASSWORD"]
|
|
||||||
config["ext_reg_url"] = raw["EXT_REG_URL"]
|
|
||||||
config["ui_secret"] = raw["UI_SECRET"]
|
|
||||||
config["secret_key"] = raw["SECRET_KEY"]
|
|
||||||
config["self_registration"] = raw["SELF_REGISTRATION"] != "off"
|
|
||||||
config["admin_create_project"] = strings.ToLower(raw["PROJECT_CREATION_RESTRICTION"]) == "adminonly"
|
|
||||||
registryURL := raw["REGISTRY_URL"]
|
|
||||||
registryURL = strings.TrimRight(registryURL, "/")
|
|
||||||
config["internal_registry_url"] = registryURL
|
|
||||||
jobserviceURL := raw["JOB_SERVICE_URL"]
|
|
||||||
jobserviceURL = strings.TrimRight(jobserviceURL, "/")
|
|
||||||
config["internal_jobservice_url"] = jobserviceURL
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var uiConfig *commonConfig.Config
|
// Get returns configurations of UI, if cache is null, it loads first
|
||||||
|
func get() (*Configuration, error) {
|
||||||
|
cfg := mg.GetFromCache()
|
||||||
|
if cfg != nil {
|
||||||
|
return cfg.(*Configuration), nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
if err := Load(); err != nil {
|
||||||
uiKeys := []string{"AUTH_MODE", "LDAP_URL", "LDAP_BASE_DN", "LDAP_SEARCH_DN", "LDAP_SEARCH_PWD", "LDAP_UID", "LDAP_FILTER", "LDAP_SCOPE", "TOKEN_EXPIRATION", "HARBOR_ADMIN_PASSWORD", "EXT_REG_URL", "UI_SECRET", "SECRET_KEY", "SELF_REGISTRATION", "PROJECT_CREATION_RESTRICTION", "REGISTRY_URL", "JOB_SERVICE_URL"}
|
return nil, err
|
||||||
uiConfig = &commonConfig.Config{
|
|
||||||
Config: make(map[string]interface{}),
|
|
||||||
Loader: &commonConfig.EnvConfigLoader{Keys: uiKeys},
|
|
||||||
Parser: &uiParser{},
|
|
||||||
}
|
|
||||||
if err := uiConfig.Load(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return mg.GetFromCache().(*Configuration), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload ...
|
// Load loads configurations of UI and puts them into cache
|
||||||
func Reload() error {
|
func Load() error {
|
||||||
return uiConfig.Load()
|
raw, err := mg.Loader.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &Configuration{}
|
||||||
|
if err = json.Unmarshal(raw, cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.Cache.Put(mg.Key, cfg,
|
||||||
|
time.Duration(cfg.CfgExpiration)*time.Second); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload uploads all system configutations to admin server
|
||||||
|
func Upload(cfg map[string]string) error {
|
||||||
|
b, err := json.Marshal(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return mg.Loader.Upload(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemCfg returns the system configurations
|
||||||
|
func GetSystemCfg() (*models.SystemCfg, error) {
|
||||||
|
raw, err := mg.Loader.Load()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &models.SystemCfg{}
|
||||||
|
if err = json.Unmarshal(raw, cfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthMode ...
|
// AuthMode ...
|
||||||
func AuthMode() string {
|
func AuthMode() (string, error) {
|
||||||
return uiConfig.Config["auth_mode"].(string)
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg.Authentication.Mode, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LDAP returns the setting of ldap server
|
// LDAP returns the setting of ldap server
|
||||||
func LDAP() LDAPSetting {
|
func LDAP() (*models.LDAP, error) {
|
||||||
return uiConfig.Config["ldap"].(LDAPSetting)
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cfg.Authentication.LDAP, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenExpiration returns the token expiration time (in minute)
|
// TokenExpiration returns the token expiration time (in minute)
|
||||||
func TokenExpiration() int {
|
func TokenExpiration() (int, error) {
|
||||||
return uiConfig.Config["token_exp"].(int)
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cfg.TokenExpiration, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtRegistryURL returns the registry URL to exposed to external client
|
// DomainName returns the external URL of Harbor: protocal://host:port
|
||||||
func ExtRegistryURL() string {
|
func DomainName() (string, error) {
|
||||||
return uiConfig.Config["ext_reg_url"].(string)
|
cfg, err := get()
|
||||||
}
|
if err != nil {
|
||||||
|
return "", err
|
||||||
// UISecret returns the value of UI secret cookie, used for communication between UI and JobService
|
}
|
||||||
func UISecret() string {
|
return cfg.DomainName, nil
|
||||||
return uiConfig.Config["ui_secret"].(string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecretKey returns the secret key to encrypt the password of target
|
// SecretKey returns the secret key to encrypt the password of target
|
||||||
func SecretKey() string {
|
func SecretKey() (string, error) {
|
||||||
return uiConfig.Config["secret_key"].(string)
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg.SecretKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelfRegistration returns the enablement of self registration
|
// SelfRegistration returns the enablement of self registration
|
||||||
func SelfRegistration() bool {
|
func SelfRegistration() (bool, error) {
|
||||||
return uiConfig.Config["self_registration"].(bool)
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return cfg.Authentication.SelfRegistration, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalRegistryURL returns registry URL for internal communication between Harbor containers
|
// RegistryURL ...
|
||||||
func InternalRegistryURL() string {
|
func RegistryURL() (string, error) {
|
||||||
return uiConfig.Config["internal_registry_url"].(string)
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg.Registry.URL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalJobServiceURL returns jobservice URL for internal communication between Harbor containers
|
// InternalJobServiceURL returns jobservice URL for internal communication between Harbor containers
|
||||||
func InternalJobServiceURL() string {
|
func InternalJobServiceURL() string {
|
||||||
return uiConfig.Config["internal_jobservice_url"].(string)
|
return "http://jobservice"
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitialAdminPassword returns the initial password for administrator
|
// InitialAdminPassword returns the initial password for administrator
|
||||||
func InitialAdminPassword() string {
|
func InitialAdminPassword() (string, error) {
|
||||||
return uiConfig.Config["admin_password"].(string)
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg.InitialAdminPwd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
// OnlyAdminCreateProject returns the flag to restrict that only sys admin can create project
|
// OnlyAdminCreateProject returns the flag to restrict that only sys admin can create project
|
||||||
func OnlyAdminCreateProject() bool {
|
func OnlyAdminCreateProject() (bool, error) {
|
||||||
return uiConfig.Config["admin_create_project"].(bool)
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return cfg.ProjectCreationRestriction == comcfg.PRO_CRT_RESTR_ADM_ONLY, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyRemoteCert returns bool value.
|
||||||
|
func VerifyRemoteCert() (bool, error) {
|
||||||
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return cfg.VerifyRemoteCert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Email() (*models.Email, error) {
|
||||||
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cfg.Email, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Database() (*models.Database, error) {
|
||||||
|
cfg, err := get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cfg.Database, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// UISecret returns the value of UI secret cookie, used for communication between UI and JobService
|
||||||
|
func UISecret() string {
|
||||||
|
return os.Getenv("UI_SECRET")
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,12 @@ func (b *BaseController) Prepare() {
|
|||||||
b.Data["CurLang"] = curLang.Name
|
b.Data["CurLang"] = curLang.Name
|
||||||
b.Data["RestLangs"] = restLangs
|
b.Data["RestLangs"] = restLangs
|
||||||
|
|
||||||
authMode := config.AuthMode()
|
authMode, err := config.AuthMode()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get auth mode: %v", err)
|
||||||
|
b.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
if authMode == "" {
|
if authMode == "" {
|
||||||
authMode = "db_auth"
|
authMode = "db_auth"
|
||||||
}
|
}
|
||||||
@ -120,9 +125,13 @@ func (b *BaseController) Prepare() {
|
|||||||
b.UseCompressedJS = false
|
b.UseCompressedJS = false
|
||||||
}
|
}
|
||||||
|
|
||||||
b.SelfRegistration = config.SelfRegistration()
|
b.SelfRegistration, err = config.SelfRegistration()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get self registration: %v", err)
|
||||||
|
b.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
b.Data["SelfRegistration"] = config.SelfRegistration()
|
b.Data["SelfRegistration"] = b.SelfRegistration
|
||||||
|
|
||||||
sessionUserID := b.GetSession("userId")
|
sessionUserID := b.GetSession("userId")
|
||||||
if sessionUserID != nil {
|
if sessionUserID != nil {
|
||||||
|
@ -6,12 +6,12 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
|
||||||
"github.com/vmware/harbor/src/common/config"
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
|
email_util "github.com/vmware/harbor/src/common/utils/email"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type messageDetail struct {
|
type messageDetail struct {
|
||||||
@ -49,7 +49,11 @@ func (cc *CommonController) SendEmail() {
|
|||||||
|
|
||||||
message := new(bytes.Buffer)
|
message := new(bytes.Buffer)
|
||||||
|
|
||||||
harborURL := config.ExtEndpoint()
|
harborURL, err := config.DomainName()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get domain name: %v", err)
|
||||||
|
cc.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
if harborURL == "" {
|
if harborURL == "" {
|
||||||
harborURL = "localhost"
|
harborURL = "localhost"
|
||||||
}
|
}
|
||||||
@ -65,14 +69,14 @@ func (cc *CommonController) SendEmail() {
|
|||||||
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := beego.AppConfig.GetSection("mail")
|
emailSettings, err := config.Email()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Can not load app.conf: %v", err)
|
log.Errorf("failed to get email configurations: %v", err)
|
||||||
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
||||||
}
|
}
|
||||||
|
|
||||||
mail := utils.Mail{
|
mail := email_util.Mail{
|
||||||
From: config["from"],
|
From: emailSettings.From,
|
||||||
To: []string{email},
|
To: []string{email},
|
||||||
Subject: cc.Tr("reset_email_subject"),
|
Subject: cc.Tr("reset_email_subject"),
|
||||||
Message: message.String()}
|
Message: message.String()}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
@ -23,6 +25,11 @@ func (pc *ProjectController) Get() {
|
|||||||
isSysAdmin = false
|
isSysAdmin = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pc.Data["CanCreate"] = !config.OnlyAdminCreateProject() || isSysAdmin
|
onlyAdmin, err := config.OnlyAdminCreateProject()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to determine whether only admin can create projects: %v", err)
|
||||||
|
pc.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
pc.Data["CanCreate"] = !onlyAdmin || isSysAdmin
|
||||||
pc.Forward("page_title_project", "project.htm")
|
pc.Forward("page_title_project", "project.htm")
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,6 +15,11 @@ type RepositoryController struct {
|
|||||||
|
|
||||||
// Get renders repository page
|
// Get renders repository page
|
||||||
func (rc *RepositoryController) Get() {
|
func (rc *RepositoryController) Get() {
|
||||||
rc.Data["HarborRegUrl"] = config.ExtRegistryURL()
|
url, err := config.DomainName()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get domain name: %v", err)
|
||||||
|
rc.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
rc.Data["HarborRegUrl"] = strings.Split(url, "://")[1]
|
||||||
rc.Forward("page_title_repository", "repository.htm")
|
rc.Forward("page_title_repository", "repository.htm")
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,6 @@ func updateInitPassword(userID int, password string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||||
//TODO
|
//TODO
|
||||||
redisURL := os.Getenv("_REDIS_URL")
|
redisURL := os.Getenv("_REDIS_URL")
|
||||||
@ -72,12 +71,28 @@ func main() {
|
|||||||
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
|
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
|
||||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = redisURL
|
beego.BConfig.WebConfig.Session.SessionProviderConfig = redisURL
|
||||||
}
|
}
|
||||||
//
|
|
||||||
beego.AddTemplateExt("htm")
|
beego.AddTemplateExt("htm")
|
||||||
|
|
||||||
dao.InitDatabase()
|
log.Info("initializing configurations...")
|
||||||
|
if err := config.Init(); err != nil {
|
||||||
|
log.Fatalf("failed to initialize configurations: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("configurations initialization completed")
|
||||||
|
|
||||||
if err := updateInitPassword(adminUserID, config.InitialAdminPassword()); err != nil {
|
database, err := config.Database()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to get database configuration: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dao.InitDatabase(database); err != nil {
|
||||||
|
log.Fatalf("failed to initialize database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
password, err := config.InitialAdminPassword()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to get admin's initia password: %v", err)
|
||||||
|
}
|
||||||
|
if err := updateInitPassword(adminUserID, password); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
initRouters()
|
initRouters()
|
||||||
|
@ -84,6 +84,7 @@ func initRouters() {
|
|||||||
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
||||||
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
||||||
beego.Router("/api/logs", &api.LogAPI{})
|
beego.Router("/api/logs", &api.LogAPI{})
|
||||||
|
beego.Router("/api/configurations", &api.ConfigAPI{})
|
||||||
|
|
||||||
beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
|
beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
|
||||||
beego.Router("/api/systeminfo/getcert", &api.SystemInfoAPI{}, "get:GetCert")
|
beego.Router("/api/systeminfo/getcert", &api.SystemInfoAPI{}, "get:GetCert")
|
||||||
|
@ -37,13 +37,6 @@ const (
|
|||||||
privateKey = "/etc/ui/private_key.pem"
|
privateKey = "/etc/ui/private_key.pem"
|
||||||
)
|
)
|
||||||
|
|
||||||
var expiration int //minutes
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
expiration = config.TokenExpiration()
|
|
||||||
log.Infof("token expiration: %d minutes", expiration)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResourceActions ...
|
// GetResourceActions ...
|
||||||
func GetResourceActions(scopes []string) []*token.ResourceActions {
|
func GetResourceActions(scopes []string) []*token.ResourceActions {
|
||||||
log.Debugf("scopes: %+v", scopes)
|
log.Debugf("scopes: %+v", scopes)
|
||||||
@ -91,7 +84,12 @@ func FilterAccess(username string, a *token.ResourceActions) {
|
|||||||
repoLength := len(repoSplit)
|
repoLength := len(repoSplit)
|
||||||
if repoLength > 1 { //Only check the permission when the requested image has a namespace, i.e. project
|
if repoLength > 1 { //Only check the permission when the requested image has a namespace, i.e. project
|
||||||
var projectName string
|
var projectName string
|
||||||
registryURL := config.ExtRegistryURL()
|
registryURL, err := config.DomainName()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get domain name: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
registryURL = strings.Split(registryURL, "://")[1]
|
||||||
if repoSplit[0] == registryURL {
|
if repoSplit[0] == registryURL {
|
||||||
projectName = repoSplit[1]
|
projectName = repoSplit[1]
|
||||||
log.Infof("Detected Registry URL in Project Name. Assuming this is a notary request and setting Project Name as %s\n", projectName)
|
log.Infof("Detected Registry URL in Project Name. Assuming this is a notary request and setting Project Name as %s\n", projectName)
|
||||||
@ -153,6 +151,11 @@ func MakeToken(username, service string, access []*token.ResourceActions) (token
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, nil, err
|
return "", 0, nil, err
|
||||||
}
|
}
|
||||||
|
expiration, err := config.TokenExpiration()
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
tk, expiresIn, issuedAt, err := makeTokenCore(issuer, username, service, expiration, access, pk)
|
tk, expiresIn, issuedAt, err := makeTokenCore(issuer, username, service, expiration, access, pk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, nil, err
|
return "", 0, nil, err
|
||||||
|
Loading…
Reference in New Issue
Block a user