From 948e5ad1c5c6e0af611a467563717149777d4c80 Mon Sep 17 00:00:00 2001 From: wy65701436 Date: Fri, 1 Apr 2016 04:42:13 -0700 Subject: [PATCH 01/21] change register to api/users --- api/user.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++ routers/router.go | 3 +- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/api/user.go b/api/user.go index 482ab319b..985edf1cb 100644 --- a/api/user.go +++ b/api/user.go @@ -16,8 +16,10 @@ package api import ( + "fmt" "net/http" "strconv" + "strings" "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" @@ -31,9 +33,18 @@ type UserAPI struct { userID int } +const userNameMaxLen int = 20 +const passwordMaxLen int = 20 +const realNameMaxLen int = 20 +const commentsMaxLen int = 20 + // Prepare validates the URL and parms func (ua *UserAPI) Prepare() { + if ua.Ctx.Input.IsPost() { + return + } + ua.currentUserID = ua.ValidateUser() id := ua.Ctx.Input.Param(":id") if id == "current" { @@ -117,6 +128,44 @@ func (ua *UserAPI) Put() { //currently only for toggle admin, so no request body dao.ToggleUserAdminRole(userQuery) } +// Post ... +func (ua *UserAPI) Post() { + username := strings.TrimSpace(ua.GetString("username")) + password := strings.TrimSpace(ua.GetString("password")) + email := strings.TrimSpace(ua.GetString("email")) + realname := strings.TrimSpace(ua.GetString("realname")) + comment := strings.TrimSpace(ua.GetString("comment")) + + err := validateUserReq(ua) + if err != nil { + log.Errorf("Invalid user request, error: %v", err) + ua.RenderError(http.StatusBadRequest, "Invalid request for creating user") + return + } + + user := models.User{Username: username, Email: email, Realname: realname, Password: password, Comment: comment} + exist, err := dao.UserExists(user, "email") + if err != nil { + log.Errorf("Error occurred in UserExists:", err) + } + if exist { + ua.RenderError(http.StatusConflict, "") + return + } + + userID, err := dao.Register(user) + if err != nil { + log.Errorf("Error occurred in Register:", err) + ua.RenderError(http.StatusInternalServerError, "Internal error.") + return + } + if userID == 0 { + log.Errorf("Error happened on registing new user in db.") + ua.RenderError(http.StatusInternalServerError, "Internal error.") + } + +} + // Delete ... func (ua *UserAPI) Delete() { exist, err := dao.IsAdminRole(ua.currentUserID) @@ -136,3 +185,42 @@ func (ua *UserAPI) Delete() { return } } + +func validateUserReq(ua *UserAPI) error { + userName := ua.GetString("username") + if len(userName) == 0 { + return fmt.Errorf("User name can not be empty") + } + if len(userName) > userNameMaxLen { + return fmt.Errorf("User name is too long") + } + + password := ua.GetString("password") + if len(password) == 0 { + return fmt.Errorf("Password can not be empty") + } + if len(password) >= passwordMaxLen { + return fmt.Errorf("Password can is too long") + } + + realName := ua.GetString("realname") + if len(realName) == 0 { + return fmt.Errorf("Real name can not be empty") + } + if len(realName) >= realNameMaxLen { + return fmt.Errorf("Real name is too long") + } + + email := ua.GetString("email") + if len(email) == 0 { + return fmt.Errorf("Email can not be empty") + } + + comments := ua.GetString("comment") + if len(comments) != 0 { + if len(comments) >= commentsMaxLen { + return fmt.Errorf("Comments is too long") + } + } + return nil +} diff --git a/routers/router.go b/routers/router.go index 764a3c7e6..e8dab001f 100644 --- a/routers/router.go +++ b/routers/router.go @@ -32,7 +32,7 @@ func init() { beego.Router("/login", &controllers.CommonController{}, "post:Login") beego.Router("/logout", &controllers.CommonController{}, "get:Logout") beego.Router("/language", &controllers.CommonController{}, "get:SwitchLanguage") - beego.Router("/signUp", &controllers.CommonController{}, "post:SignUp") + // beego.Router("/signUp", &controllers.CommonController{}, "post:SignUp") beego.Router("/userExists", &controllers.CommonController{}, "post:UserExists") beego.Router("/reset", &controllers.CommonController{}, "post:ResetPassword") beego.Router("/sendEmail", &controllers.CommonController{}, "get:SendEmail") @@ -55,6 +55,7 @@ func init() { beego.Router("/api/projects/:pid/members/?:mid", &api.ProjectMemberAPI{}) beego.Router("/api/projects/?:id", &api.ProjectAPI{}) beego.Router("/api/projects/:id/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog") + beego.Router("/api/users", &api.UserAPI{}) beego.Router("/api/users/?:id", &api.UserAPI{}) beego.Router("/api/repositories", &api.RepositoryAPI{}) beego.Router("/api/repositories/tags", &api.RepositoryAPI{}, "get:GetTags") From fca2838b332851d322c34cea0658677154415b7c Mon Sep 17 00:00:00 2001 From: wy65701436 Date: Fri, 1 Apr 2016 04:56:43 -0700 Subject: [PATCH 02/21] update api/users --- static/resources/js/register.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/resources/js/register.js b/static/resources/js/register.js index cbd133671..7f0b21d4a 100644 --- a/static/resources/js/register.js +++ b/static/resources/js/register.js @@ -37,7 +37,7 @@ jQuery(function(){ var realname = $.trim($("#Realname").val()); var comment = $.trim($("#Comment").val()); $.ajax({ - url : '/signUp', + url : "/api/users", data:{username: username, password: password, realname: realname, comment: comment, email: email}, type: "POST", beforeSend: function(e){ From d82ecec4625879b58eac61c6ae8172539ad7ee85 Mon Sep 17 00:00:00 2001 From: "perhapszzy@sina.com" Date: Sun, 3 Apr 2016 11:27:56 +0800 Subject: [PATCH 03/21] add k8s yamls and dockerfiles. --- k8s/dockerfiles/config/db/env | 1 + k8s/dockerfiles/config/nginx/cert/.gitignore | 0 k8s/dockerfiles/config/nginx/nginx.conf | 66 ++++++++++++++ k8s/dockerfiles/config/nginx/nginx.https.conf | 85 +++++++++++++++++++ k8s/dockerfiles/config/registry/config.yml | 33 +++++++ k8s/dockerfiles/config/registry/root.crt | 15 ++++ k8s/dockerfiles/config/ui/app.conf | 16 ++++ k8s/dockerfiles/config/ui/env | 11 +++ k8s/dockerfiles/config/ui/private_key.pem | 15 ++++ k8s/dockerfiles/proxy-dockerfile | 5 ++ k8s/dockerfiles/registry-dockerfile | 6 ++ k8s/dockerfiles/ui-dockerfile | 7 ++ k8s/mysql-rc.yaml | 30 +++++++ k8s/mysql-svc.yaml | 11 +++ k8s/proxy-rc.yaml | 22 +++++ k8s/proxy-svc.yaml | 15 ++++ k8s/registry-rc.yaml | 28 ++++++ k8s/registry-svc.yaml | 15 ++++ k8s/ui-rc.yaml | 49 +++++++++++ k8s/ui-svc.yaml | 11 +++ 20 files changed, 441 insertions(+) create mode 100644 k8s/dockerfiles/config/db/env create mode 100644 k8s/dockerfiles/config/nginx/cert/.gitignore create mode 100644 k8s/dockerfiles/config/nginx/nginx.conf create mode 100644 k8s/dockerfiles/config/nginx/nginx.https.conf create mode 100644 k8s/dockerfiles/config/registry/config.yml create mode 100644 k8s/dockerfiles/config/registry/root.crt create mode 100644 k8s/dockerfiles/config/ui/app.conf create mode 100644 k8s/dockerfiles/config/ui/env create mode 100644 k8s/dockerfiles/config/ui/private_key.pem create mode 100644 k8s/dockerfiles/proxy-dockerfile create mode 100644 k8s/dockerfiles/registry-dockerfile create mode 100644 k8s/dockerfiles/ui-dockerfile create mode 100644 k8s/mysql-rc.yaml create mode 100644 k8s/mysql-svc.yaml create mode 100644 k8s/proxy-rc.yaml create mode 100644 k8s/proxy-svc.yaml create mode 100644 k8s/registry-rc.yaml create mode 100644 k8s/registry-svc.yaml create mode 100644 k8s/ui-rc.yaml create mode 100644 k8s/ui-svc.yaml diff --git a/k8s/dockerfiles/config/db/env b/k8s/dockerfiles/config/db/env new file mode 100644 index 000000000..2637dc082 --- /dev/null +++ b/k8s/dockerfiles/config/db/env @@ -0,0 +1 @@ +MYSQL_ROOT_PASSWORD=root123 diff --git a/k8s/dockerfiles/config/nginx/cert/.gitignore b/k8s/dockerfiles/config/nginx/cert/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/k8s/dockerfiles/config/nginx/nginx.conf b/k8s/dockerfiles/config/nginx/nginx.conf new file mode 100644 index 000000000..8168137ea --- /dev/null +++ b/k8s/dockerfiles/config/nginx/nginx.conf @@ -0,0 +1,66 @@ +worker_processes auto; + +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +http { + tcp_nodelay on; + + # this is necessary for us to be able to disable request buffering in all cases + proxy_http_version 1.1; + + + upstream registry { + server registry:5000; + } + + upstream ui { + server ui:80; + } + + + server { + listen 80; + + # disable any limits to avoid HTTP 413 for large image uploads + client_max_body_size 0; + + location / { + proxy_pass http://ui/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_request_buffering off; + } + + location /v1/ { + return 404; + } + + location /v2/ { + proxy_pass http://registry/v2/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_request_buffering off; + + } + + location /service/ { + proxy_pass http://ui/service/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_request_buffering off; + } + } +} diff --git a/k8s/dockerfiles/config/nginx/nginx.https.conf b/k8s/dockerfiles/config/nginx/nginx.https.conf new file mode 100644 index 000000000..7e03b9585 --- /dev/null +++ b/k8s/dockerfiles/config/nginx/nginx.https.conf @@ -0,0 +1,85 @@ +worker_processes auto; + +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +http { + tcp_nodelay on; + + # this is necessary for us to be able to disable request buffering in all cases + proxy_http_version 1.1; + + + upstream registry { + server registry:5000; + } + + upstream ui { + server ui:80; + } + + + server { + listen 443 ssl; + server_name harbordomain.com; + + # SSL + ssl_certificate /etc/nginx/cert/harbordomain.crt; + ssl_certificate_key /etc/nginx/cert/harbordomain.key; + + # Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ssl_protocols TLSv1.1 TLSv1.2; + ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + + # disable any limits to avoid HTTP 413 for large image uploads + client_max_body_size 0; + + # required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486) + chunked_transfer_encoding on; + + location / { + proxy_pass http://ui/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_request_buffering off; + } + + location /v1/ { + return 404; + } + + location /v2/ { + proxy_pass http://registry/v2/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_request_buffering off; + + } + + location /service/ { + proxy_pass http://ui/service/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_request_buffering off; + } + } + server { + listen 80; + server_name harbordomain.com; + rewrite ^/(.*) https://$server_name$1 permanent; + } +} diff --git a/k8s/dockerfiles/config/registry/config.yml b/k8s/dockerfiles/config/registry/config.yml new file mode 100644 index 000000000..15a77ac96 --- /dev/null +++ b/k8s/dockerfiles/config/registry/config.yml @@ -0,0 +1,33 @@ +version: 0.1 +log: + level: debug + fields: + service: registry +storage: + cache: + layerinfo: inmemory + filesystem: + rootdirectory: /storage + maintenance: + uploadpurging: + enabled: false +http: + addr: :5000 + secret: placeholder + debug: + addr: localhost:5001 +auth: + token: + issuer: registry-token-issuer + realm: http://localhost/service/token + rootcertbundle: /etc/registry/root.crt + service: token-service + +notifications: + endpoints: + - name: harbor + disabled: false + url: http://localhost/service/notifications + timeout: 500 + threshold: 5 + backoff: 1000 diff --git a/k8s/dockerfiles/config/registry/root.crt b/k8s/dockerfiles/config/registry/root.crt new file mode 100644 index 000000000..326d8080a --- /dev/null +++ b/k8s/dockerfiles/config/registry/root.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICWDCCAcGgAwIBAgIJAN1nLuloDeHNMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwMTI3MDQyMDM1WhcNNDMwNjE0MDQyMDM1WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB +gQClak/4HO7EeLU0w/BhtVENPLOqU0AP2QjVUdg1qhNiDWVrbWx9KYHqz5Kn0n2+ +fxdZo3o7ZY5/2+hhgkKh1z6Kge9XGgune6z4fx2J/X2Se8WsGeQUTiND8ngSnsCA +NtYFwW50SbUZPtyf5XjAfKRofZem51OxbxzN3217L/ubKwIDAQABo1AwTjAdBgNV +HQ4EFgQU5EG2VrB3I6G/TudUpz+kBgQXSvYwHwYDVR0jBBgwFoAU5EG2VrB3I6G/ +TudUpz+kBgQXSvYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQAx+2eo +oOm0YNy9KQ81+7GQkKVWoPQXjAGGgZuZj8WCFepYqUSJ4q5qbuVCY8WbGcHVk2Rx +Jg1XDCmMjBgYP6S0ikezBRqSmNA3G6oFiydTKBfPs6RNalsB0C78Xk5l5+PIyd2R +jFKOKoMpkjwfeJv2j64WNGoBgqj7XRBoJ11a4g== +-----END CERTIFICATE----- diff --git a/k8s/dockerfiles/config/ui/app.conf b/k8s/dockerfiles/config/ui/app.conf new file mode 100644 index 000000000..5465292d2 --- /dev/null +++ b/k8s/dockerfiles/config/ui/app.conf @@ -0,0 +1,16 @@ +appname = registry +runmode = dev + +[lang] +types = en-US|zh-CN +names = en-US|zh-CN + +[dev] +httpport = 80 + +[mail] +host = smtp.mydomain.com +port = 25 +username = sample_admin@mydomain.com +password = abc +from = admin diff --git a/k8s/dockerfiles/config/ui/env b/k8s/dockerfiles/config/ui/env new file mode 100644 index 000000000..0ce9998a6 --- /dev/null +++ b/k8s/dockerfiles/config/ui/env @@ -0,0 +1,11 @@ +MYSQL_HOST=mysql +MYSQL_USR=root +REGISTRY_URL=http://registry:5000 +CONFIG_PATH=/etc/ui/app.conf +HARBOR_REG_URL=localhost +HARBOR_ADMIN_PASSWORD=Harbor12345 +HARBOR_URL=http://localhost +AUTH_MODE=db_auth +LDAP_URL=ldaps://ldap.mydomain.com +LDAP_BASE_DN=uid=%s,ou=people,dc=mydomain,dc=com +LOG_LEVEL=debug diff --git a/k8s/dockerfiles/config/ui/private_key.pem b/k8s/dockerfiles/config/ui/private_key.pem new file mode 100644 index 000000000..6c68cacb3 --- /dev/null +++ b/k8s/dockerfiles/config/ui/private_key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQClak/4HO7EeLU0w/BhtVENPLOqU0AP2QjVUdg1qhNiDWVrbWx9 +KYHqz5Kn0n2+fxdZo3o7ZY5/2+hhgkKh1z6Kge9XGgune6z4fx2J/X2Se8WsGeQU +TiND8ngSnsCANtYFwW50SbUZPtyf5XjAfKRofZem51OxbxzN3217L/ubKwIDAQAB +AoGBAITMMuNYJwAogCGaZHOs4yMjZoIJT9bpQMQxbsi2f9UqOA/ky0I4foqKloyQ +2k6DLbXTHqBsydgwLgGKWAAiE5xIR2bPMUNSLgjbA2eLly3aOR/0FJ5n09k2EmGg +Am7tLP+6yneXWKVi3HI3NzXriVjWK94WHGGC1b9F+n5CY/2RAkEA1d62OJUNve2k +IY6/b6T0BdssFo3VFcm22vnayEL/wcYrnRfF9Pb5wM4HUUqwVelKTouivXg60GNK +ZKYAx5CtHwJBAMYAEf5u0CQ/8URcwBuMkm0LzK4AM2x1nGs7gIxAEFhu1Z4xPjVe +MtIxuHhDhlLvD760uccmo5yE72QJ1ZrYBHUCQQCAxLZMPRpoB4QyHEOREe1G9V6H +OeBZXPk2wQcEWqqo3gt2a1DqHCXl+2aWgHTJVUxDHHngwFoRDCdHkFeZ0LcbAkAj +T8/luI2WaXD16DS6tQ9IM1qFjbOeHDuRRENgv+wqWVnvpIibq/kUU5m6mRBTqh78 +u+6F/fYf6/VluftGalAhAkAukdMtt+sksq2e7Qw2dRr5GXtXjt+Otjj0NaJENmWk +a7SgAs34EOWtbd0XGYpZFrg134MzQGbweFeEUTj++e8p +-----END RSA PRIVATE KEY----- diff --git a/k8s/dockerfiles/proxy-dockerfile b/k8s/dockerfiles/proxy-dockerfile new file mode 100644 index 000000000..f724ac148 --- /dev/null +++ b/k8s/dockerfiles/proxy-dockerfile @@ -0,0 +1,5 @@ +FROM library/nginx:1.9 + +ADD ./config/nginx /etc/nginx + + diff --git a/k8s/dockerfiles/registry-dockerfile b/k8s/dockerfiles/registry-dockerfile new file mode 100644 index 000000000..5f105caae --- /dev/null +++ b/k8s/dockerfiles/registry-dockerfile @@ -0,0 +1,6 @@ +FROM library/registry:2.3.0 + +ADD ./config/registry/ /etc/registry/ + +CMD ["/etc/registry/config.yml"] + diff --git a/k8s/dockerfiles/ui-dockerfile b/k8s/dockerfiles/ui-dockerfile new file mode 100644 index 000000000..dbd76dbd4 --- /dev/null +++ b/k8s/dockerfiles/ui-dockerfile @@ -0,0 +1,7 @@ +FROM index.caicloud.io/caicloud/harbor_deploy_ui:org + +ADD ./config/ui/app.conf /etc/ui/app.conf +ADD ./config/ui/private_key.pem /etc/ui/private_key.pem + + + diff --git a/k8s/mysql-rc.yaml b/k8s/mysql-rc.yaml new file mode 100644 index 000000000..4f8b5ceaf --- /dev/null +++ b/k8s/mysql-rc.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: mysql + labels: + name: mysql +spec: + replicas: 1 + selector: + name: mysql + template: + metadata: + labels: + name: mysql + spec: + containers: + - name: mysql + image: index.caicloud.io/caicloud/harbor_deploy_mysql:latest + imagePullPolicy: Always + ports: + - containerPort: 3306 + env: + - name: MYSQL_ROOT_PASSWORD + value: root123 + volumeMounts: + - name: mysql-storage + mountPath: /var/lib/mysql + volumes: + - name: mysql-storage + emptyDir: {} diff --git a/k8s/mysql-svc.yaml b/k8s/mysql-svc.yaml new file mode 100644 index 000000000..0572e8c29 --- /dev/null +++ b/k8s/mysql-svc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: mysql + labels: + name: mysql +spec: + ports: + - port: 3306 + selector: + name: mysql diff --git a/k8s/proxy-rc.yaml b/k8s/proxy-rc.yaml new file mode 100644 index 000000000..65b84e245 --- /dev/null +++ b/k8s/proxy-rc.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: proxy + labels: + name: proxy +spec: + replicas: 1 + selector: + name: proxy + template: + metadata: + labels: + name: proxy + spec: + containers: + - name: proxy + image: index.caicloud.io/caicloud/harbor_proxy:latest + imagePullPolicy: Always + ports: + - containerPort: 80 + - containerPort: 443 diff --git a/k8s/proxy-svc.yaml b/k8s/proxy-svc.yaml new file mode 100644 index 000000000..29711bd5f --- /dev/null +++ b/k8s/proxy-svc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: proxy + labels: + name: proxy +spec: + type: LoadBalancer + ports: + - name: bbb + port: 80 + - name: aaa + port: 443 + selector: + name: proxy diff --git a/k8s/registry-rc.yaml b/k8s/registry-rc.yaml new file mode 100644 index 000000000..e330cdc44 --- /dev/null +++ b/k8s/registry-rc.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: registry + labels: + name: registry +spec: + replicas: 1 + selector: + name: registry + template: + metadata: + labels: + name: registry + spec: + containers: + - name: registry + image: index.caicloud.io/caicloud/harbor_registry:2.3.0 + imagePullPolicy: Always + ports: + - containerPort: 5000 + - containerPort: 5001 + volumeMounts: + - name: storage + mountPath: /storage + volumes: + - name: storage + emptyDir: {} diff --git a/k8s/registry-svc.yaml b/k8s/registry-svc.yaml new file mode 100644 index 000000000..29c9ae017 --- /dev/null +++ b/k8s/registry-svc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: registry + labels: + name: registry +spec: + type: LoadBalancer + ports: + - name: internal + port: 5000 + - name: external + port: 5001 + selector: + name: registry diff --git a/k8s/ui-rc.yaml b/k8s/ui-rc.yaml new file mode 100644 index 000000000..665e803ee --- /dev/null +++ b/k8s/ui-rc.yaml @@ -0,0 +1,49 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: ui + labels: + name: ui +spec: + replicas: 1 + selector: + name: ui + template: + metadata: + labels: + name: ui + spec: + containers: + - name: ui + image: index.caicloud.io/caicloud/harbor_deploy_ui:latest + imagePullPolicy: Always + env: + - name: MYSQL_HOST + value: mysql + - name: MYSQL_PORT + value: "3306" + - name: MYSQL_USR + value: root + - name: MYSQL_PWD + value: root123 + - name: REGISTRY_URL + value: http://registry:5000 + - name: CONFIG_PATH + value: /etc/ui/app.conf + - name: HARBOR_REG_URL + value: localhost + - name: HARBOR_ADMIN_PASSWORD + value: Harbor12345 + - name: HARBOR_URL + value: http://localhost + - name: AUTH_MODE + value: db_auth + - name: LDAP_URL + value: ldaps://ldap.mydomain.com + - name: LDAP_BASE_DN + value: uid=%s,ou=people,dc=mydomain,dc=com + - name: LOG_LEVEL + value: debug + ports: + - containerPort: 80 + diff --git a/k8s/ui-svc.yaml b/k8s/ui-svc.yaml new file mode 100644 index 000000000..ce11e9138 --- /dev/null +++ b/k8s/ui-svc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: ui + labels: + name: ui +spec: + ports: + - port: 80 + selector: + name: ui From be0d71d61db9dce7b31a45be7d92fa70623659de Mon Sep 17 00:00:00 2001 From: wy65701436 Date: Tue, 5 Apr 2016 02:30:23 -0700 Subject: [PATCH 04/21] update /api/users to parse json data --- api/user.go | 37 ++++++++++++++++----------------- static/resources/js/register.js | 5 +++-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/api/user.go b/api/user.go index 985edf1cb..16decb5f0 100644 --- a/api/user.go +++ b/api/user.go @@ -19,7 +19,7 @@ import ( "fmt" "net/http" "strconv" - "strings" + // "strings" "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" @@ -33,6 +33,14 @@ type UserAPI struct { userID int } +type userReq struct { + UserName string `json:"username"` + Password string `json:"password"` + Realname string `json:"realname"` + Email string `json:"email"` + Comment string `json:"comment"` +} + const userNameMaxLen int = 20 const passwordMaxLen int = 20 const realNameMaxLen int = 20 @@ -130,20 +138,17 @@ func (ua *UserAPI) Put() { //currently only for toggle admin, so no request body // Post ... func (ua *UserAPI) Post() { - username := strings.TrimSpace(ua.GetString("username")) - password := strings.TrimSpace(ua.GetString("password")) - email := strings.TrimSpace(ua.GetString("email")) - realname := strings.TrimSpace(ua.GetString("realname")) - comment := strings.TrimSpace(ua.GetString("comment")) + var req userReq + ua.DecodeJSONReq(&req) - err := validateUserReq(ua) + err := validateUserReq(req) if err != nil { log.Errorf("Invalid user request, error: %v", err) ua.RenderError(http.StatusBadRequest, "Invalid request for creating user") return } - user := models.User{Username: username, Email: email, Realname: realname, Password: password, Comment: comment} + user := models.User{Username: req.UserName, Email: req.Email, Realname: req.Realname, Password: req.Password, Comment: req.Comment} exist, err := dao.UserExists(user, "email") if err != nil { log.Errorf("Error occurred in UserExists:", err) @@ -186,8 +191,8 @@ func (ua *UserAPI) Delete() { } } -func validateUserReq(ua *UserAPI) error { - userName := ua.GetString("username") +func validateUserReq(req userReq) error { + userName := req.UserName if len(userName) == 0 { return fmt.Errorf("User name can not be empty") } @@ -195,7 +200,7 @@ func validateUserReq(ua *UserAPI) error { return fmt.Errorf("User name is too long") } - password := ua.GetString("password") + password := req.Password if len(password) == 0 { return fmt.Errorf("Password can not be empty") } @@ -203,7 +208,7 @@ func validateUserReq(ua *UserAPI) error { return fmt.Errorf("Password can is too long") } - realName := ua.GetString("realname") + realName := req.Realname if len(realName) == 0 { return fmt.Errorf("Real name can not be empty") } @@ -211,16 +216,10 @@ func validateUserReq(ua *UserAPI) error { return fmt.Errorf("Real name is too long") } - email := ua.GetString("email") + email := req.Email if len(email) == 0 { return fmt.Errorf("Email can not be empty") } - comments := ua.GetString("comment") - if len(comments) != 0 { - if len(comments) >= commentsMaxLen { - return fmt.Errorf("Comments is too long") - } - } return nil } diff --git a/static/resources/js/register.js b/static/resources/js/register.js index 7f0b21d4a..7c4793d35 100644 --- a/static/resources/js/register.js +++ b/static/resources/js/register.js @@ -38,8 +38,9 @@ jQuery(function(){ var comment = $.trim($("#Comment").val()); $.ajax({ url : "/api/users", - data:{username: username, password: password, realname: realname, comment: comment, email: email}, + data: JSON.stringify({username: username, password: password, realname: realname, comment: comment, email: email}), type: "POST", + contentType: "application/json; charset=UTF-8", beforeSend: function(e){ $("#btnPageSignUp").prop("disabled", true); }, @@ -55,7 +56,7 @@ jQuery(function(){ }); } }, - error:function(jqxhr, status, error){ + error:function(xhr, status, error){ $("#dlgModal") .dialogModal({ "title": i18n.getMessage("title_sign_up"), From fb918578992af0fe60cebe45082d3b32c411d536 Mon Sep 17 00:00:00 2001 From: wy65701436 Date: Tue, 5 Apr 2016 02:35:00 -0700 Subject: [PATCH 05/21] remove disabled import --- api/user.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/user.go b/api/user.go index 16decb5f0..d20ef9363 100644 --- a/api/user.go +++ b/api/user.go @@ -19,7 +19,6 @@ import ( "fmt" "net/http" "strconv" - // "strings" "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" From f54b942fd765713dc12c831111b59b85cfc03ea4 Mon Sep 17 00:00:00 2001 From: wy65701436 Date: Tue, 5 Apr 2016 02:45:47 -0700 Subject: [PATCH 06/21] fix log issue --- api/user.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/user.go b/api/user.go index d20ef9363..4a8101ba8 100644 --- a/api/user.go +++ b/api/user.go @@ -150,7 +150,7 @@ func (ua *UserAPI) Post() { user := models.User{Username: req.UserName, Email: req.Email, Realname: req.Realname, Password: req.Password, Comment: req.Comment} exist, err := dao.UserExists(user, "email") if err != nil { - log.Errorf("Error occurred in UserExists:", err) + log.Errorf("Error occurred in UserExists: %v", err) } if exist { ua.RenderError(http.StatusConflict, "") @@ -159,7 +159,7 @@ func (ua *UserAPI) Post() { userID, err := dao.Register(user) if err != nil { - log.Errorf("Error occurred in Register:", err) + log.Errorf("Error occurred in Register: %v", err) ua.RenderError(http.StatusInternalServerError, "Internal error.") return } From 5139952cb4cf4a92da87e1a18b7a3817be11f402 Mon Sep 17 00:00:00 2001 From: wy65701436 Date: Tue, 5 Apr 2016 03:01:04 -0700 Subject: [PATCH 07/21] update js error xhr to jQery xhr --- static/resources/js/register.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/resources/js/register.js b/static/resources/js/register.js index 7c4793d35..72d28de86 100644 --- a/static/resources/js/register.js +++ b/static/resources/js/register.js @@ -56,7 +56,7 @@ jQuery(function(){ }); } }, - error:function(xhr, status, error){ + error:function(jqxhr, status, error){ $("#dlgModal") .dialogModal({ "title": i18n.getMessage("title_sign_up"), From b76acb638199fb1126b0c95ce4391e291bbda3b8 Mon Sep 17 00:00:00 2001 From: wy65701436 Date: Tue, 5 Apr 2016 20:26:24 -0700 Subject: [PATCH 08/21] remove validate step as the duplication, and user model.User instead of new struct. --- api/user.go | 75 ++------------------------------------------------ models/user.go | 10 +++---- 2 files changed, 8 insertions(+), 77 deletions(-) diff --git a/api/user.go b/api/user.go index 4a8101ba8..4e6136214 100644 --- a/api/user.go +++ b/api/user.go @@ -16,7 +16,6 @@ package api import ( - "fmt" "net/http" "strconv" @@ -32,19 +31,6 @@ type UserAPI struct { userID int } -type userReq struct { - UserName string `json:"username"` - Password string `json:"password"` - Realname string `json:"realname"` - Email string `json:"email"` - Comment string `json:"comment"` -} - -const userNameMaxLen int = 20 -const passwordMaxLen int = 20 -const realNameMaxLen int = 20 -const commentsMaxLen int = 20 - // Prepare validates the URL and parms func (ua *UserAPI) Prepare() { @@ -137,37 +123,15 @@ func (ua *UserAPI) Put() { //currently only for toggle admin, so no request body // Post ... func (ua *UserAPI) Post() { - var req userReq - ua.DecodeJSONReq(&req) + user := models.User{} + ua.DecodeJSONReq(&user) - err := validateUserReq(req) - if err != nil { - log.Errorf("Invalid user request, error: %v", err) - ua.RenderError(http.StatusBadRequest, "Invalid request for creating user") - return - } - - user := models.User{Username: req.UserName, Email: req.Email, Realname: req.Realname, Password: req.Password, Comment: req.Comment} - exist, err := dao.UserExists(user, "email") - if err != nil { - log.Errorf("Error occurred in UserExists: %v", err) - } - if exist { - ua.RenderError(http.StatusConflict, "") - return - } - - userID, err := dao.Register(user) + _, err := dao.Register(user) if err != nil { log.Errorf("Error occurred in Register: %v", err) ua.RenderError(http.StatusInternalServerError, "Internal error.") return } - if userID == 0 { - log.Errorf("Error happened on registing new user in db.") - ua.RenderError(http.StatusInternalServerError, "Internal error.") - } - } // Delete ... @@ -189,36 +153,3 @@ func (ua *UserAPI) Delete() { return } } - -func validateUserReq(req userReq) error { - userName := req.UserName - if len(userName) == 0 { - return fmt.Errorf("User name can not be empty") - } - if len(userName) > userNameMaxLen { - return fmt.Errorf("User name is too long") - } - - password := req.Password - if len(password) == 0 { - return fmt.Errorf("Password can not be empty") - } - if len(password) >= passwordMaxLen { - return fmt.Errorf("Password can is too long") - } - - realName := req.Realname - if len(realName) == 0 { - return fmt.Errorf("Real name can not be empty") - } - if len(realName) >= realNameMaxLen { - return fmt.Errorf("Real name is too long") - } - - email := req.Email - if len(email) == 0 { - return fmt.Errorf("Email can not be empty") - } - - return nil -} diff --git a/models/user.go b/models/user.go index 82b8bc221..29fb41c13 100644 --- a/models/user.go +++ b/models/user.go @@ -22,11 +22,11 @@ import ( // User holds the details of a user. type User struct { UserID int `orm:"column(user_id)" json:"UserId"` - Username string `orm:"column(username)"` - Email string `orm:"column(email)"` - Password string `orm:"column(password)"` - Realname string `orm:"column(realname)"` - Comment string `orm:"column(comment)"` + Username string `orm:"column(username)" json:"username"` + Email string `orm:"column(email)" json:"email"` + Password string `orm:"column(password)" json:"password"` + Realname string `orm:"column(realname)" json:"realname"` + Comment string `orm:"column(comment)" json:"comment"` Deleted int `orm:"column(deleted)"` Rolename string RoleID int `json:"RoleId"` From 943d6ebd4cb6849bddf9fcc7b3c70acf7d1c32a3 Mon Sep 17 00:00:00 2001 From: wy65701436 Date: Fri, 8 Apr 2016 00:25:52 -0700 Subject: [PATCH 09/21] merge code with api/user; refactor api/users --- api/user.go | 67 ++++++++++++++++++++++++++++++++++------------------ ui/router.go | 1 - 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/api/user.go b/api/user.go index 4e6136214..ed433397a 100644 --- a/api/user.go +++ b/api/user.go @@ -17,7 +17,9 @@ package api import ( "net/http" + "os" "strconv" + "strings" "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" @@ -27,15 +29,32 @@ import ( // UserAPI handles request to /api/users/{} type UserAPI struct { BaseAPI - currentUserID int - userID int + currentUserID int + userID int + SelfRegistration bool + IsAdmin bool + AuthMode string } // Prepare validates the URL and parms func (ua *UserAPI) Prepare() { + authMode := strings.ToLower(os.Getenv("AUTH_MODE")) + if authMode == "" { + authMode = "db_auth" + } + ua.AuthMode = authMode + + selfRegistration := strings.ToLower(os.Getenv("SELF_REGISTRATION")) + if selfRegistration == "on" { + ua.SelfRegistration = true + } + if ua.Ctx.Input.IsPost() { - return + sessionUserID := ua.GetSession("userId") + if sessionUserID == nil { + return + } } ua.currentUserID = ua.ValidateUser() @@ -60,18 +79,19 @@ func (ua *UserAPI) Prepare() { ua.CustomAbort(http.StatusNotFound, "") } } + + var err error + ua.IsAdmin, err = dao.IsAdminRole(ua.currentUserID) + if err != nil { + log.Errorf("Error occurred in IsAdminRole:%v", err) + ua.CustomAbort(http.StatusInternalServerError, "Internal error.") + } } // Get ... func (ua *UserAPI) Get() { - exist, err := dao.IsAdminRole(ua.currentUserID) - if err != nil { - log.Errorf("Error occurred in IsAdminRole, error: %v", err) - ua.CustomAbort(http.StatusInternalServerError, "Internal error.") - } - if ua.userID == 0 { //list users - if !exist { + if !ua.IsAdmin { log.Errorf("Current user, id: %d does not have admin role, can not list users", ua.currentUserID) ua.RenderError(http.StatusForbidden, "User does not have admin role") return @@ -89,7 +109,7 @@ func (ua *UserAPI) Get() { } ua.Data["json"] = userList - } else if ua.userID == ua.currentUserID || exist { + } else if ua.userID == ua.currentUserID || ua.IsAdmin { userQuery := models.User{UserID: ua.userID} u, err := dao.GetUser(userQuery) if err != nil { @@ -107,12 +127,7 @@ func (ua *UserAPI) Get() { // Put ... func (ua *UserAPI) Put() { //currently only for toggle admin, so no request body - exist, err := dao.IsAdminRole(ua.currentUserID) - if err != nil { - log.Errorf("Error occurred in IsAdminRole, error: %v", err) - ua.CustomAbort(http.StatusInternalServerError, "Internal error.") - } - if !exist { + if !ua.IsAdmin { log.Warningf("current user, id: %d does not have admin role, can not update other user's role", ua.currentUserID) ua.RenderError(http.StatusForbidden, "User does not have admin role") return @@ -123,6 +138,16 @@ func (ua *UserAPI) Put() { //currently only for toggle admin, so no request body // Post ... func (ua *UserAPI) Post() { + + if !(ua.AuthMode == "db_auth") { + ua.CustomAbort(http.StatusForbidden, "") + } + + if !(ua.SelfRegistration || ua.IsAdmin) { + log.Warning("Registration can only be used by admin role user when self-registration is off.") + ua.CustomAbort(http.StatusForbidden, "") + } + user := models.User{} ua.DecodeJSONReq(&user) @@ -136,16 +161,12 @@ func (ua *UserAPI) Post() { // Delete ... func (ua *UserAPI) Delete() { - exist, err := dao.IsAdminRole(ua.currentUserID) - if err != nil { - log.Errorf("Error occurred in IsAdminRole, error: %v", err) - ua.CustomAbort(http.StatusInternalServerError, "Internal error.") - } - if !exist { + if !ua.IsAdmin { log.Warningf("current user, id: %d does not have admin role, can not remove user", ua.currentUserID) ua.RenderError(http.StatusForbidden, "User does not have admin role") return } + var err error err = dao.DeleteUser(ua.userID) if err != nil { log.Errorf("Failed to delete data from database, error: %v", err) diff --git a/ui/router.go b/ui/router.go index 1d7f15d03..a1f9067c1 100644 --- a/ui/router.go +++ b/ui/router.go @@ -32,7 +32,6 @@ func initRouters() { beego.Router("/login", &controllers.CommonController{}, "post:Login") beego.Router("/logout", &controllers.CommonController{}, "get:Logout") beego.Router("/language", &controllers.CommonController{}, "get:SwitchLanguage") - // beego.Router("/signUp", &controllers.CommonController{}, "post:SignUp") beego.Router("/userExists", &controllers.CommonController{}, "post:UserExists") beego.Router("/reset", &controllers.CommonController{}, "post:ResetPassword") beego.Router("/sendEmail", &controllers.CommonController{}, "get:SendEmail") From 8ad4b96d08f9476eeab7ec2759e5ffc98e799140 Mon Sep 17 00:00:00 2001 From: "perhapszzy@sina.com" Date: Wed, 13 Apr 2016 07:40:08 +0800 Subject: [PATCH 10/21] add README about how to start harbor on kubernetes. --- .../dockerfiles/config/registry/config.yml | 4 +- Deploy/kubernetes/registry-rc.yaml | 1 - Deploy/kubernetes/registry-svc.yaml | 3 - README.md | 46 +++++++-- README.md~ | 95 ------------------- 5 files changed, 39 insertions(+), 110 deletions(-) delete mode 100644 README.md~ diff --git a/Deploy/kubernetes/dockerfiles/config/registry/config.yml b/Deploy/kubernetes/dockerfiles/config/registry/config.yml index fa17e953b..8612d4647 100644 --- a/Deploy/kubernetes/dockerfiles/config/registry/config.yml +++ b/Deploy/kubernetes/dockerfiles/config/registry/config.yml @@ -19,7 +19,7 @@ http: auth: token: issuer: registry-token-issuer - realm: http://registry/service/token + realm: http://harbor.caicloud.io/service/token rootcertbundle: /etc/registry/root.crt service: token-service @@ -27,7 +27,7 @@ notifications: endpoints: - name: harbor disabled: false - url: http://registry/service/notifications + url: http://harbor.caicloud.io/service/notifications timeout: 500 threshold: 5 backoff: 1000 diff --git a/Deploy/kubernetes/registry-rc.yaml b/Deploy/kubernetes/registry-rc.yaml index d5c42e396..5322bec6f 100644 --- a/Deploy/kubernetes/registry-rc.yaml +++ b/Deploy/kubernetes/registry-rc.yaml @@ -20,7 +20,6 @@ spec: ports: - containerPort: 5000 - containerPort: 5001 - - containerPort: 53 volumeMounts: - name: storage mountPath: /storage diff --git a/Deploy/kubernetes/registry-svc.yaml b/Deploy/kubernetes/registry-svc.yaml index d2b06efd8..0af327529 100644 --- a/Deploy/kubernetes/registry-svc.yaml +++ b/Deploy/kubernetes/registry-svc.yaml @@ -5,13 +5,10 @@ metadata: labels: name: registry spec: - type: LoadBalancer ports: - name: internal port: 5000 - name: external port: 5001 - - name: aa - port: 53 selector: name: registry diff --git a/README.md b/README.md index b2b65d523..0c4491a8d 100644 --- a/README.md +++ b/README.md @@ -53,12 +53,32 @@ The host must be connected to the Internet. ``` 4. Deploy harbor on kubernetes. - To start harbor on kubernetes, you just need to run: - ``` - kubectl create -f Deploy/kubernetes - ``` - - All images needed to start harbor on kubernetes are already pushed to dockerhub, but if you want to build your own images, you can run the following commends: + For now, it's a little tricky to start harbor on kubernetes because + 1. registry uses https, so we need cert or workaround to avoid errors like this: + + ``` + Error response from daemon: invalid registry endpoint https://{HOST}/v0/: unable to ping registry endpoint https://{HOST}/v0/ + v2 ping attempt failed with error: Get https://{HOST}/v2/: EOF + v1 ping attempt failed with error: Get https://{HOST}/v1/_ping: EOF. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry {HOST}` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/{HOST}/ca.crt + ``` + + There is a workaround if you don't have a cert. The workaround is to add the host into the list of insecure registry by editting the ```/etc/default/docker``` file: + ``` + sudo vi /etc/default/docker + ``` + add the line at the end of file: + ``` + DOCKER_OPTS="$DOCKER_OPTS --insecure-registry={HOST}" + ``` + and restart docker service + ``` + sudo service docker restart + ``` + 2. The registry config file need to know the IP (or DNS name) of the registry, but on kubernetes, you won't know the IP before the service is created. There are several workarounds to solve this problem for now: + - Use DNS name and link th DNS name with the IP after the service is created. + - Rebuild the registry image with the service IP after the service is created and use ```kubectl rolling-update``` to update to the new image. + + To start harbor on kubernetes, you first need to change the host name at the registry config file and build the images by running: ``` cd Deploy docker-compose build @@ -66,10 +86,13 @@ The host must be connected to the Internet. docker build -f kubernetes/dockerfiles/registry-dockerfile -t {your_account}/registry . docker build -f kubernetes/dockerfiles/ui-dockerfile -t {your_account}/deploy_ui . docker tag deploy_mysql {your_account}/deploy_mysql + docker push {your_account}/proxy + docker push {your_account}/registry + docker push {your_account}/deploy_ui + docker push {your_account}/deploy_mysql ``` - - where "your_account" is your own registry. If you choose to use your own images, you'll need to push these images to your registry and - update the "image" field in the *-rc.yaml files at before running the ```kubectl create```: + + where "your_account" is your own registry. Then you need to update the "image" field in the ```*-rc.yaml``` files at: ``` Deploy/kubernetes/mysql-rc.yaml Deploy/kubernetes/proxy-rc.yaml @@ -77,6 +100,11 @@ The host must be connected to the Internet. Deploy/kubernetes/ui-rc.yaml ``` + Finally you can start the jobs by running: + ``` + kubectl create -f Deploy/kubernetes + ``` + **NOTE:** To simplify the installation process, a pre-built installation package of Harbor is provided so that you don't need to clone the source code. By using this package, you can even install Harbor onto a host that is not connected to the Internet. For details on how to download and use this installation package, please refer to [Installation and Configuration Guide](docs/installation_guide.md) . diff --git a/README.md~ b/README.md~ deleted file mode 100644 index b2b65d523..000000000 --- a/README.md~ +++ /dev/null @@ -1,95 +0,0 @@ -# Harbor - -[![Build Status](https://travis-ci.org/vmware/harbor.svg?branch=master)](https://travis-ci.org/vmware/harbor) - -![alg tag](https://cloud.githubusercontent.com/assets/2390463/13484557/088a1000-e13a-11e5-87d4-a64366365bef.png) - -> Project Harbor is initiated by VMware China R&D as a Cloud Application Accelerator (CAA) project. CAA provides a set of tools to improve the productivity of cloud developers in China and other countries. CAA includes tools like registry server, mirror server, decentralized image distributor, etc. - -Project Harbor is an enterprise-class registry server. It extends the open source Docker Registry server by adding more functionalities usually required by an enterprise. Harbor is designed to be deployed in a private environment of an organization. A private registry is important for organizations who care much about security. In addition, a private registry improves productivity by eliminating the need to download images from the public network. This is very helpful to container users who do not have a good network to the Internet. - -### Features -* **Role Based Access Control**: Users and docker repositories are organized via "projects", a user can have different permission for images under a namespace. -* **Graphical user portal**: User can easily browse, search docker repositories, manage projects/namespaces. -* **AD/LDAP support**: Harbor integrates with existing AD/LDAP of the enterprise for user authentication and management. -* **Auditing**: All the operations to the repositories are tracked and can be used for auditing purpose. -* **Internationalization**: Localized for English and Chinese languages. More languages can be added. -* **RESTful API**: RESTful APIs are provided for most administrative operations of Harbor. The integration with other management softwares becomes easy. - -### Getting Started -Harbor is self-contained and can be easily deployed via docker-compose. The below are quick-start steps. Refer to the [Installation and Configuration Guide](docs/installation_guide.md) for detail information. - -**System requirements:** -Harbor only works with docker 1.10+ and docker-compose 1.6.0+ . -The host must be connected to the Internet. - -1. Get the source code: - - ```sh - $ git clone https://github.com/vmware/harbor - ``` -2. Edit the file **Deploy/harbor.cfg**, make necessary configuration changes such as hostname, admin password and mail server. Refer to [Installation and Configuration Guide](docs/installation_guide.md) for more info. - - -3. Install Harbor by the following commands. It may take a while for the docker-compose process to finish. - ```sh - $ cd Deploy - - $ ./prepare - Generated configuration file: ./config/ui/env - Generated configuration file: ./config/ui/app.conf - Generated configuration file: ./config/registry/config.yml - Generated configuration file: ./config/db/env - - $ docker-compose up - ``` - - If everything works fine, you can open a browser to visit the admin portal at http://reg.yourdomain.com . The default administrator username and password are admin/Harbor12345 . - - Create a new project, e.g. myproject, in the admin portal. You can then use docker commands to login and push images. The default port of Harbor registry server is 80: - ```sh - $ docker login reg.yourdomain.com - $ docker push reg.yourdomain.com/myproject/myrepo - ``` - -4. Deploy harbor on kubernetes. - To start harbor on kubernetes, you just need to run: - ``` - kubectl create -f Deploy/kubernetes - ``` - - All images needed to start harbor on kubernetes are already pushed to dockerhub, but if you want to build your own images, you can run the following commends: - ``` - cd Deploy - docker-compose build - docker build -f kubernetes/dockerfiles/proxy-dockerfile -t {your_account}/proxy . - docker build -f kubernetes/dockerfiles/registry-dockerfile -t {your_account}/registry . - docker build -f kubernetes/dockerfiles/ui-dockerfile -t {your_account}/deploy_ui . - docker tag deploy_mysql {your_account}/deploy_mysql - ``` - - where "your_account" is your own registry. If you choose to use your own images, you'll need to push these images to your registry and - update the "image" field in the *-rc.yaml files at before running the ```kubectl create```: - ``` - Deploy/kubernetes/mysql-rc.yaml - Deploy/kubernetes/proxy-rc.yaml - Deploy/kubernetes/registry-rc.yaml - Deploy/kubernetes/ui-rc.yaml - ``` - -**NOTE:** -To simplify the installation process, a pre-built installation package of Harbor is provided so that you don't need to clone the source code. By using this package, you can even install Harbor onto a host that is not connected to the Internet. For details on how to download and use this installation package, please refer to [Installation and Configuration Guide](docs/installation_guide.md) . - -For information on how to use Harbor, please see [User Guide](docs/user_guide.md) . - -### Contribution -We welcome contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a pull request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). - -### License -Harbor is available under the [Apache 2 license](LICENSE). - -### Partners -DataMan     SlamTec - -### Users -MaDaiLiCai From 09e5e8baf6e5437540a9acc6c64d146d67c0ccb7 Mon Sep 17 00:00:00 2001 From: "perhapszzy@sina.com" Date: Wed, 13 Apr 2016 08:53:42 +0800 Subject: [PATCH 11/21] reuse config files --- .../dockerfiles/config/registry/root.crt | 15 --------------- Deploy/kubernetes/dockerfiles/config/ui/app.conf | 16 ---------------- Deploy/kubernetes/dockerfiles/config/ui/env | 11 ----------- .../dockerfiles/config/ui/private_key.pem | 15 --------------- Deploy/kubernetes/dockerfiles/proxy-dockerfile | 2 -- .../registry/config.yml => registry-config.yml} | 0 .../kubernetes/dockerfiles/registry-dockerfile | 4 ++-- Deploy/kubernetes/dockerfiles/ui-dockerfile | 7 ++----- 8 files changed, 4 insertions(+), 66 deletions(-) delete mode 100644 Deploy/kubernetes/dockerfiles/config/registry/root.crt delete mode 100644 Deploy/kubernetes/dockerfiles/config/ui/app.conf delete mode 100644 Deploy/kubernetes/dockerfiles/config/ui/env delete mode 100644 Deploy/kubernetes/dockerfiles/config/ui/private_key.pem rename Deploy/kubernetes/dockerfiles/{config/registry/config.yml => registry-config.yml} (100%) diff --git a/Deploy/kubernetes/dockerfiles/config/registry/root.crt b/Deploy/kubernetes/dockerfiles/config/registry/root.crt deleted file mode 100644 index 326d8080a..000000000 --- a/Deploy/kubernetes/dockerfiles/config/registry/root.crt +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICWDCCAcGgAwIBAgIJAN1nLuloDeHNMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQwHhcNMTYwMTI3MDQyMDM1WhcNNDMwNjE0MDQyMDM1WjBF -MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 -ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB -gQClak/4HO7EeLU0w/BhtVENPLOqU0AP2QjVUdg1qhNiDWVrbWx9KYHqz5Kn0n2+ -fxdZo3o7ZY5/2+hhgkKh1z6Kge9XGgune6z4fx2J/X2Se8WsGeQUTiND8ngSnsCA -NtYFwW50SbUZPtyf5XjAfKRofZem51OxbxzN3217L/ubKwIDAQABo1AwTjAdBgNV -HQ4EFgQU5EG2VrB3I6G/TudUpz+kBgQXSvYwHwYDVR0jBBgwFoAU5EG2VrB3I6G/ -TudUpz+kBgQXSvYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQAx+2eo -oOm0YNy9KQ81+7GQkKVWoPQXjAGGgZuZj8WCFepYqUSJ4q5qbuVCY8WbGcHVk2Rx -Jg1XDCmMjBgYP6S0ikezBRqSmNA3G6oFiydTKBfPs6RNalsB0C78Xk5l5+PIyd2R -jFKOKoMpkjwfeJv2j64WNGoBgqj7XRBoJ11a4g== ------END CERTIFICATE----- diff --git a/Deploy/kubernetes/dockerfiles/config/ui/app.conf b/Deploy/kubernetes/dockerfiles/config/ui/app.conf deleted file mode 100644 index 5465292d2..000000000 --- a/Deploy/kubernetes/dockerfiles/config/ui/app.conf +++ /dev/null @@ -1,16 +0,0 @@ -appname = registry -runmode = dev - -[lang] -types = en-US|zh-CN -names = en-US|zh-CN - -[dev] -httpport = 80 - -[mail] -host = smtp.mydomain.com -port = 25 -username = sample_admin@mydomain.com -password = abc -from = admin diff --git a/Deploy/kubernetes/dockerfiles/config/ui/env b/Deploy/kubernetes/dockerfiles/config/ui/env deleted file mode 100644 index aba7fedd3..000000000 --- a/Deploy/kubernetes/dockerfiles/config/ui/env +++ /dev/null @@ -1,11 +0,0 @@ -MYSQL_HOST=mysql -MYSQL_USR=root -REGISTRY_URL=http://registry:5000 -CONFIG_PATH=/etc/ui/app.conf -HARBOR_REG_URL=proxy -HARBOR_ADMIN_PASSWORD=Harbor12345 -HARBOR_URL=ui -AUTH_MODE=db_auth -LDAP_URL=ldaps://ldap.mydomain.com -LDAP_BASE_DN=uid=%s,ou=people,dc=mydomain,dc=com -LOG_LEVEL=debug diff --git a/Deploy/kubernetes/dockerfiles/config/ui/private_key.pem b/Deploy/kubernetes/dockerfiles/config/ui/private_key.pem deleted file mode 100644 index 6c68cacb3..000000000 --- a/Deploy/kubernetes/dockerfiles/config/ui/private_key.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQClak/4HO7EeLU0w/BhtVENPLOqU0AP2QjVUdg1qhNiDWVrbWx9 -KYHqz5Kn0n2+fxdZo3o7ZY5/2+hhgkKh1z6Kge9XGgune6z4fx2J/X2Se8WsGeQU -TiND8ngSnsCANtYFwW50SbUZPtyf5XjAfKRofZem51OxbxzN3217L/ubKwIDAQAB -AoGBAITMMuNYJwAogCGaZHOs4yMjZoIJT9bpQMQxbsi2f9UqOA/ky0I4foqKloyQ -2k6DLbXTHqBsydgwLgGKWAAiE5xIR2bPMUNSLgjbA2eLly3aOR/0FJ5n09k2EmGg -Am7tLP+6yneXWKVi3HI3NzXriVjWK94WHGGC1b9F+n5CY/2RAkEA1d62OJUNve2k -IY6/b6T0BdssFo3VFcm22vnayEL/wcYrnRfF9Pb5wM4HUUqwVelKTouivXg60GNK -ZKYAx5CtHwJBAMYAEf5u0CQ/8URcwBuMkm0LzK4AM2x1nGs7gIxAEFhu1Z4xPjVe -MtIxuHhDhlLvD760uccmo5yE72QJ1ZrYBHUCQQCAxLZMPRpoB4QyHEOREe1G9V6H -OeBZXPk2wQcEWqqo3gt2a1DqHCXl+2aWgHTJVUxDHHngwFoRDCdHkFeZ0LcbAkAj -T8/luI2WaXD16DS6tQ9IM1qFjbOeHDuRRENgv+wqWVnvpIibq/kUU5m6mRBTqh78 -u+6F/fYf6/VluftGalAhAkAukdMtt+sksq2e7Qw2dRr5GXtXjt+Otjj0NaJENmWk -a7SgAs34EOWtbd0XGYpZFrg134MzQGbweFeEUTj++e8p ------END RSA PRIVATE KEY----- diff --git a/Deploy/kubernetes/dockerfiles/proxy-dockerfile b/Deploy/kubernetes/dockerfiles/proxy-dockerfile index f724ac148..fbc967233 100644 --- a/Deploy/kubernetes/dockerfiles/proxy-dockerfile +++ b/Deploy/kubernetes/dockerfiles/proxy-dockerfile @@ -1,5 +1,3 @@ FROM library/nginx:1.9 ADD ./config/nginx /etc/nginx - - diff --git a/Deploy/kubernetes/dockerfiles/config/registry/config.yml b/Deploy/kubernetes/dockerfiles/registry-config.yml similarity index 100% rename from Deploy/kubernetes/dockerfiles/config/registry/config.yml rename to Deploy/kubernetes/dockerfiles/registry-config.yml diff --git a/Deploy/kubernetes/dockerfiles/registry-dockerfile b/Deploy/kubernetes/dockerfiles/registry-dockerfile index 3921087c0..9fb39c5c4 100644 --- a/Deploy/kubernetes/dockerfiles/registry-dockerfile +++ b/Deploy/kubernetes/dockerfiles/registry-dockerfile @@ -1,6 +1,6 @@ FROM library/registry:2.3.0 -ADD ./kubernetes/dockerfiles/config/registry/ /etc/registry/ +ADD ./config/registry/ /etc/registry/ +ADD ./kubernetes/dockerfiles/registry-config.yml /etc/registry/config.yml CMD ["/etc/registry/config.yml"] - diff --git a/Deploy/kubernetes/dockerfiles/ui-dockerfile b/Deploy/kubernetes/dockerfiles/ui-dockerfile index df7f74fb3..c9714139c 100644 --- a/Deploy/kubernetes/dockerfiles/ui-dockerfile +++ b/Deploy/kubernetes/dockerfiles/ui-dockerfile @@ -1,7 +1,4 @@ FROM deploy_ui -ADD ./kubernetes/dockerfiles/config/ui/app.conf /etc/ui/app.conf -ADD ./kubernetes/dockerfiles/config/ui/private_key.pem /etc/ui/private_key.pem - - - +ADD ./config/ui/app.conf /etc/ui/app.conf +ADD ./config/ui/private_key.pem /etc/ui/private_key.pem From 66feb10df8380f0b29eef1d09c9926b0189b485b Mon Sep 17 00:00:00 2001 From: Tan Jiang Date: Thu, 14 Apr 2016 11:04:27 +0800 Subject: [PATCH 12/21] expose 443 port on nginx --- Deploy/docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Deploy/docker-compose.yml b/Deploy/docker-compose.yml index 0bead2e5d..a00421fb7 100644 --- a/Deploy/docker-compose.yml +++ b/Deploy/docker-compose.yml @@ -57,6 +57,7 @@ services: - ./config/nginx:/etc/nginx ports: - 80:80 + - 443:443 depends_on: - mysql - registry From b404cdfe90d59c2f92fd39a980eb2badd5abefad Mon Sep 17 00:00:00 2001 From: Henry Zhang Date: Thu, 14 Apr 2016 15:57:42 +0800 Subject: [PATCH 13/21] update documents --- AUTHORS | 3 ++- README.md | 3 +++ docs/configure_https.md | 10 +++++++--- docs/img/beegoLogo.png | Bin 0 -> 5306 bytes docs/installation_guide.md | 13 ++++++++----- 5 files changed, 20 insertions(+), 9 deletions(-) create mode 100644 docs/img/beegoLogo.png diff --git a/AUTHORS b/AUTHORS index f6e2e7e0e..d292ef8c7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,5 +1,6 @@ # This file lists all individuals having contributed content to the repository. +Alexander Zeitler Amanda Zhang Benniu Ji Bobby Zhang @@ -9,8 +10,8 @@ Haining Henry Zhang Hao Xia Jack Liu Kun Wang +Peng Zhao Shan Zhu Victoria Zheng Wenkai Yin Yan Wang - diff --git a/README.md b/README.md index 88b93ad34..d94dd1b3c 100644 --- a/README.md +++ b/README.md @@ -68,3 +68,6 @@ Harbor is available under the [Apache 2 license](LICENSE). ### Users MaDaiLiCai + +### Supporting Technologies +Harbor is powered by beego, an open source framework to build and develop applications in the Go way. diff --git a/docs/configure_https.md b/docs/configure_https.md index e366cb5d7..ccb667c35 100644 --- a/docs/configure_https.md +++ b/docs/configure_https.md @@ -1,8 +1,8 @@ -#Configure Harbor with HTTPS Access +#Configuring Harbor with HTTPS Access Because Harbor does not ship with any certificates, it uses HTTP by default to serve registry requests. This makes it relatively simple to configure. However, it is highly recommended that security be enabled for any production environment. Harbor has an Nginx instance as a reverse proxy for all services, you can configure Nginx to enable https. -##Get a certificate +##Getting a certificate Assuming that your registry's **hostname** is **reg.yourdomain.com**, and that its DNS record points to the host where you are running Harbor. You first should get a certificate from a CA. The certificate usually contains a .crt file and a .key file, for example, **yourdomain.com.crt** and **yourdomain.com.key**. @@ -22,7 +22,7 @@ In a test or development environment, you may choose to use a self-signed certif ``` 3) Generate the certificate of your registry host: -You need to configure openssl first. On Ubuntu, the config file locates at /etc/ssl/openssl.cnf. Refer to openssl document for more information. The default CA directory of openssl is called demoCA. Let's create necessary directories and files: +You need to configure openssl first. On Ubuntu, the config file locates at **/etc/ssl/openssl.cnf**. Refer to openssl document for more information. The default CA directory of openssl is called demoCA. Let's create necessary directories and files: ``` mkdir demoCA cd demoCA @@ -41,6 +41,10 @@ After obtaining the **yourdomain.com.crt** and **yourdomain.com.key** files, cha cd Deploy/config/nginx ``` Create a new directory cert/, if it does not exist. Then copy **yourdomain.com.crt** and **yourdomain.com.key** to cert/. +``` + cp yourdomain.com.crt cert/ + cp yourdomain.com.key cert/ +``` Rename the existing configuration file of Nginx: ``` diff --git a/docs/img/beegoLogo.png b/docs/img/beegoLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd801385d7c954d8d6881cdd36303177dc4c1e7 GIT binary patch literal 5306 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 M07*qoM6N<$f&igDQUCw| literal 0 HcmV?d00001 diff --git a/docs/installation_guide.md b/docs/installation_guide.md index 1c4aa4bd4..6b17cc3b5 100644 --- a/docs/installation_guide.md +++ b/docs/installation_guide.md @@ -28,7 +28,7 @@ Before installing Harbor, you should configure the parameters in the file **harb At minimum, you need to change the **hostname** attribute in **harbor.cfg**. The description of each attribute is as follows: **hostname**: The hostname for a user to access the user interface and the registry service. It should be the IP address or the fully qualified domain name (FQDN) of your target machine, for example 192.168.1.10 or reg.yourdomain.com . Do NOT use localhost or 127.0.0.1 for the hostname because the registry service needs to be accessed by external clients. -**ui_url_protocol**: The protocol for accessing the user interface and the token/notification service, by default it is http. +**ui_url_protocol**: The protocol for accessing the user interface and the token/notification service, by default it is http. To set up https protocol, refer to [Configuring Harbor with HTTPS](configure_https.md). **Email settings**: the following 5 attributes are used to send an email to reset a user's password, they are not mandatory unless the password reset function is needed in Harbor. * email_server = smtp.mydomain.com * email_server_port = 25 @@ -40,8 +40,9 @@ At minimum, you need to change the **hostname** attribute in **harbor.cfg**. The **auth_mode**: The authentication mode of Harbor. By default it is *db_auth*, i.e. the credentials are stored in a database. Please set it to *ldap_auth* if you want to verify user's credentials against an LDAP server. **ldap_url**: The URL for LDAP endpoint, for example ldaps://ldap.mydomain.com. It is only used when **auth_mode** is set to *ldap_auth*. **ldap_basedn**: The basedn template for verifying the user's credentials against LDAP, for example uid=%s,ou=people,dc=mydomain,dc=com. It is only used when **auth_mode** is set to *ldap_auth*. -**db_password**: The password of root user of mySQL database. +**db_password**: The password of root user of mySQL database. Change this password for any production use. **self_registration**: The flag to turn on or off the user self-registration function. If this flag is turned off, only an admin user can create new users in Harbor. The default value is on. +NOTE: When **auth_mode** is *ldap_auth*, the self-registration feature is always disabled, therefore, this flag is ignored. #### Building and starting Harbor After configuring harbor.cfg, build and start Harbor by the following commands. Because it requires downloading necessary files from the Internet, it may take a while for the docker-compose process to finish. @@ -61,7 +62,7 @@ After configuring harbor.cfg, build and start Harbor by the following commands. If everything works fine, you can open a browser to visit the admin portal at http://reg.yourdomain.com . The default administrator username and password are admin/Harbor12345 . -Create a new project, e.g. myproject, in the admin portal. You can then use docker commands to login and push images. The default port of Harbor registry server is 80: +Log in to the admin portal and create a new project, e.g. myproject. You can then use docker commands to login and push images. The default port of Harbor registry server is 80: ```sh $ docker login reg.yourdomain.com $ docker push reg.yourdomain.com/myproject/myrepo @@ -121,8 +122,10 @@ $ cd ../ $ tar -cvzf harbor_offline-0.1.1.tgz harbor ``` -The file **harbor_offline-0.1.1.tgz** contains the images saved by previously steps and the files required to start Harbor. -You can use tools such as scp to transfer the file **harbor_offline-0.1.1.tgz** to the target machine that does not have Internet connection. On the target machine, you can execute the following commands to start Harbor. Again, before running the **prepare** script, be sure to update **harbor.cfg** to reflect the right configuration of the target machine. (Refer to Section [Configure Harbor](#configuring-harbor) .) +The file **harbor_offline-0.1.1.tgz** contains the images saved by previous steps and the other files required to start Harbor. +You can use tools such as scp to transfer the file **harbor_offline-0.1.1.tgz** to the target machine that does not have Internet connection. +On the target machine, you can execute the following commands to start Harbor. Again, before running the **prepare** script, +be sure to update **harbor.cfg** to reflect the right configuration of the target machine. (Refer to Section [Configuring Harbor](#configuring-harbor) .) ``` $ tar -xzvf harbor_offline-0.1.1.tgz $ cd harbor From a9a12bacfab0298b63b6d29a5eeceee2bba039a8 Mon Sep 17 00:00:00 2001 From: Henry Zhang Date: Thu, 14 Apr 2016 17:27:09 +0800 Subject: [PATCH 14/21] update document --- README.md | 4 ++-- docs/configure_https.md | 2 +- docs/img/beegoLogo.png | Bin 5306 -> 4015 bytes docs/installation_guide.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d94dd1b3c..64df2368e 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ The host must be connected to the Internet. If everything works fine, you can open a browser to visit the admin portal at http://reg.yourdomain.com . The default administrator username and password are admin/Harbor12345 . -Create a new project, e.g. myproject, in the admin portal. You can then use docker commands to login and push images. The default port of Harbor registry server is 80: +Log in to the admin portal and create a new project, e.g. myproject. You can then use docker commands to login and push images. The default port of Harbor registry server is 80: ```sh $ docker login reg.yourdomain.com $ docker push reg.yourdomain.com/myproject/myrepo @@ -70,4 +70,4 @@ Harbor is available under the [Apache 2 license](LICENSE). MaDaiLiCai ### Supporting Technologies -Harbor is powered by beego, an open source framework to build and develop applications in the Go way. +beego Harbor is powered by Beego, an open source framework to build and develop applications in the Go way. diff --git a/docs/configure_https.md b/docs/configure_https.md index ccb667c35..003fcfd6a 100644 --- a/docs/configure_https.md +++ b/docs/configure_https.md @@ -40,7 +40,7 @@ After obtaining the **yourdomain.com.crt** and **yourdomain.com.key** files, cha ``` cd Deploy/config/nginx ``` -Create a new directory cert/, if it does not exist. Then copy **yourdomain.com.crt** and **yourdomain.com.key** to cert/. +Create a new directory cert/, if it does not exist. Then copy **yourdomain.com.crt** and **yourdomain.com.key** to cert/, e.g. : ``` cp yourdomain.com.crt cert/ cp yourdomain.com.key cert/ diff --git a/docs/img/beegoLogo.png b/docs/img/beegoLogo.png index 4cd801385d7c954d8d6881cdd36303177dc4c1e7..ada97519d27f5cd1ca4cf3fc9ffb1ec87a47aae4 100644 GIT binary patch delta 3912 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 Xh6# diff --git a/docs/installation_guide.md b/docs/installation_guide.md index 6b17cc3b5..56d0c5152 100644 --- a/docs/installation_guide.md +++ b/docs/installation_guide.md @@ -28,7 +28,7 @@ Before installing Harbor, you should configure the parameters in the file **harb At minimum, you need to change the **hostname** attribute in **harbor.cfg**. The description of each attribute is as follows: **hostname**: The hostname for a user to access the user interface and the registry service. It should be the IP address or the fully qualified domain name (FQDN) of your target machine, for example 192.168.1.10 or reg.yourdomain.com . Do NOT use localhost or 127.0.0.1 for the hostname because the registry service needs to be accessed by external clients. -**ui_url_protocol**: The protocol for accessing the user interface and the token/notification service, by default it is http. To set up https protocol, refer to [Configuring Harbor with HTTPS](configure_https.md). +**ui_url_protocol**: The protocol for accessing the user interface and the token/notification service, by default it is http. To set up the https protocol, refer to [Configuring Harbor with HTTPS Access](configure_https.md). **Email settings**: the following 5 attributes are used to send an email to reset a user's password, they are not mandatory unless the password reset function is needed in Harbor. * email_server = smtp.mydomain.com * email_server_port = 25 @@ -97,7 +97,7 @@ $ sudo docker-compose up -d ...... ``` -### Deploying Harbor to a target machine that does not have Internet access +### Deploying Harbor to a host which does not have Internet access When you run *docker-compose up* to start Harbor, it will pull base images from Docker Hub and build new images for the containers. This process requires accessing the Internet. If you want to deploy Harbor to a host that is not connected to the Internet, you need to prepare Harbor on a machine that has access to the Internet. After that, you export the images as tgz files and transfer them to the target machine. Then load the tgz file into Docker's local image repo. #### Building and saving images for offline installation From c66edd02d074b3339cd7b2619e6df53ecf7cd2aa Mon Sep 17 00:00:00 2001 From: Henry Zhang Date: Thu, 14 Apr 2016 17:40:03 +0800 Subject: [PATCH 15/21] update docs --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index d292ef8c7..8e9802ff4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,7 +10,7 @@ Haining Henry Zhang Hao Xia Jack Liu Kun Wang -Peng Zhao +Peng Zhao Shan Zhu Victoria Zheng Wenkai Yin From 7165156f2d935a879859770bda8b4611ad252864 Mon Sep 17 00:00:00 2001 From: wy65701436 Date: Thu, 14 Apr 2016 21:33:48 -0700 Subject: [PATCH 16/21] update per comments, add support for basic auth --- api/user.go | 21 ++++++++++++++++----- controllers/register.go | 28 ---------------------------- static/resources/js/register.js | 33 +++++++++++++++------------------ 3 files changed, 31 insertions(+), 51 deletions(-) diff --git a/api/user.go b/api/user.go index ed433397a..d4c0c1851 100644 --- a/api/user.go +++ b/api/user.go @@ -29,11 +29,14 @@ import ( // UserAPI handles request to /api/users/{} type UserAPI struct { BaseAPI - currentUserID int - userID int - SelfRegistration bool - IsAdmin bool - AuthMode string + currentUserID int + userID int + SelfRegistration bool + IsAdmin bool + AuthMode string + IsBasicAuth bool + UserNameInBasicAuth string + PasswordInBasicAuth string } // Prepare validates the URL and parms @@ -51,6 +54,8 @@ func (ua *UserAPI) Prepare() { } if ua.Ctx.Input.IsPost() { + ua.UserNameInBasicAuth, ua.PasswordInBasicAuth, ua.IsBasicAuth = ua.Ctx.Request.BasicAuth() + sessionUserID := ua.GetSession("userId") if sessionUserID == nil { return @@ -151,12 +156,18 @@ func (ua *UserAPI) Post() { user := models.User{} ua.DecodeJSONReq(&user) + if ua.IsBasicAuth { + user.Username = ua.UserNameInBasicAuth + user.Password = ua.PasswordInBasicAuth + } + _, err := dao.Register(user) if err != nil { log.Errorf("Error occurred in Register: %v", err) ua.RenderError(http.StatusInternalServerError, "Internal error.") return } + } // Delete ... diff --git a/controllers/register.go b/controllers/register.go index dd1a80eab..d8ed05715 100644 --- a/controllers/register.go +++ b/controllers/register.go @@ -17,7 +17,6 @@ package controllers import ( "net/http" - "strings" "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" @@ -65,33 +64,6 @@ func (ac *AddUserController) Get() { } } -// SignUp insert data into DB based on data in form. -func (cc *CommonController) SignUp() { - - if !(cc.AuthMode == "db_auth") { - cc.CustomAbort(http.StatusForbidden, "") - } - - if !(cc.SelfRegistration || cc.IsAdmin) { - log.Warning("Registration can only be used by admin role user when self-registration is off.") - cc.CustomAbort(http.StatusForbidden, "") - } - - username := strings.TrimSpace(cc.GetString("username")) - email := strings.TrimSpace(cc.GetString("email")) - realname := strings.TrimSpace(cc.GetString("realname")) - password := strings.TrimSpace(cc.GetString("password")) - comment := strings.TrimSpace(cc.GetString("comment")) - - user := models.User{Username: username, Email: email, Realname: realname, Password: password, Comment: comment} - - _, err := dao.Register(user) - if err != nil { - log.Errorf("Error occurred in Register: %v", err) - cc.CustomAbort(http.StatusInternalServerError, "Internal error.") - } -} - // UserExists checks if user exists when user input value in sign in form. func (cc *CommonController) UserExists() { target := cc.GetString("target") diff --git a/static/resources/js/register.js b/static/resources/js/register.js index ebfe60dd0..8f970946f 100644 --- a/static/resources/js/register.js +++ b/static/resources/js/register.js @@ -38,15 +38,25 @@ jQuery(function(){ var comment = $.trim($("#Comment").val()); var isAdmin = $("#isAdmin").val(); - $.ajax({ + new AjaxUtil({ url : "/api/users", - data: JSON.stringify({username: username, password: password, realname: realname, comment: comment, email: email}), + data: {"username": username, "password": password, "realname": realname, "comment": comment, "email": email}, type: "POST", - contentType: "application/json; charset=UTF-8", beforeSend: function(e){ $("#btnPageSignUp").prop("disabled", true); }, - success: function(data, status, xhr){ + error:function(jqxhr, status, error){ + $("#dlgModal") + .dialogModal({ + "title": i18n.getMessage("title_sign_up"), + "content": i18n.getMessage("internal_error"), + "callback": function(){ + return; + } + }); + }, + complete: function(xhr, status){ + $("#btnPageSignUp").prop("disabled", false); if(xhr && xhr.status == 200){ $("#dlgModal") .dialogModal({ @@ -61,21 +71,8 @@ jQuery(function(){ } }); } - }, - error:function(jqxhr, status, error){ - $("#dlgModal") - .dialogModal({ - "title": i18n.getMessage("title_sign_up"), - "content": i18n.getMessage("internal_error"), - "callback": function(){ - return; - } - }); - }, - complete: function(){ - $("#btnPageSignUp").prop("disabled", false); } - }); + }).exec(); }); }); }); \ No newline at end of file From fc24341c1730ffe6d60c9a8587ed8a44affb7c50 Mon Sep 17 00:00:00 2001 From: wy65701436 Date: Fri, 15 Apr 2016 01:55:33 -0700 Subject: [PATCH 17/21] update per comments, add support for basic auth --- api/user.go | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/api/user.go b/api/user.go index d4c0c1851..58aa29ec3 100644 --- a/api/user.go +++ b/api/user.go @@ -29,14 +29,11 @@ import ( // UserAPI handles request to /api/users/{} type UserAPI struct { BaseAPI - currentUserID int - userID int - SelfRegistration bool - IsAdmin bool - AuthMode string - IsBasicAuth bool - UserNameInBasicAuth string - PasswordInBasicAuth string + currentUserID int + userID int + SelfRegistration bool + IsAdmin bool + AuthMode string } // Prepare validates the URL and parms @@ -54,10 +51,9 @@ func (ua *UserAPI) Prepare() { } if ua.Ctx.Input.IsPost() { - ua.UserNameInBasicAuth, ua.PasswordInBasicAuth, ua.IsBasicAuth = ua.Ctx.Request.BasicAuth() - sessionUserID := ua.GetSession("userId") - if sessionUserID == nil { + _, _, ok := ua.Ctx.Request.BasicAuth() + if sessionUserID == nil && !ok { return } } @@ -91,6 +87,7 @@ func (ua *UserAPI) Prepare() { log.Errorf("Error occurred in IsAdminRole:%v", err) ua.CustomAbort(http.StatusInternalServerError, "Internal error.") } + } // Get ... @@ -156,11 +153,6 @@ func (ua *UserAPI) Post() { user := models.User{} ua.DecodeJSONReq(&user) - if ua.IsBasicAuth { - user.Username = ua.UserNameInBasicAuth - user.Password = ua.PasswordInBasicAuth - } - _, err := dao.Register(user) if err != nil { log.Errorf("Error occurred in Register: %v", err) From 0936e2448a85df956f5802ac4de355353011e8f8 Mon Sep 17 00:00:00 2001 From: saga92 Date: Fri, 15 Apr 2016 17:23:40 +0800 Subject: [PATCH 18/21] add python3 compatibility --- Deploy/prepare | 57 +++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/Deploy/prepare b/Deploy/prepare index 7a6b88966..a9b7520e3 100755 --- a/Deploy/prepare +++ b/Deploy/prepare @@ -1,34 +1,43 @@ #!/usr/bin/python - -import ConfigParser -import StringIO -import os +# -*- coding: utf-8 -*- +from __future__ import print_function, unicode_literals # We require Python 2.6 or later from string import Template +import os +import six +from io import open + +if six.PY2: + import ConfigParser as ConfigParser + import StringIO as StringIO + +if six.PY3: + import configparser as ConfigParser + import io as StringIO #Read configurations conf = StringIO.StringIO() conf.write("[configuration]\n") conf.write(open("harbor.cfg").read()) conf.seek(0, os.SEEK_SET) -cp = ConfigParser.RawConfigParser() -cp.readfp(conf) +rcp = ConfigParser.RawConfigParser() +rcp.readfp(conf) -hostname = cp.get("configuration", "hostname") -ui_url = cp.get("configuration", "ui_url_protocol") + "://" + hostname -email_server = cp.get("configuration", "email_server") -email_server_port = cp.get("configuration", "email_server_port") -email_username = cp.get("configuration", "email_username") -email_password = cp.get("configuration", "email_password") -email_from = cp.get("configuration", "email_from") -harbor_admin_password = cp.get("configuration", "harbor_admin_password") -auth_mode = cp.get("configuration", "auth_mode") -ldap_url = cp.get("configuration", "ldap_url") -ldap_basedn = cp.get("configuration", "ldap_basedn") -db_password = cp.get("configuration", "db_password") -self_registration = cp.get("configuration", "self_registration") +hostname = rcp.get("configuration", "hostname") +ui_url = rcp.get("configuration", "ui_url_protocol") + "://" + hostname +email_server = rcp.get("configuration", "email_server") +email_server_port = rcp.get("configuration", "email_server_port") +email_username = rcp.get("configuration", "email_username") +email_password = rcp.get("configuration", "email_password") +email_from = rcp.get("configuration", "email_from") +harbor_admin_password = rcp.get("configuration", "harbor_admin_password") +auth_mode = rcp.get("configuration", "auth_mode") +ldap_url = rcp.get("configuration", "ldap_url") +ldap_basedn = rcp.get("configuration", "ldap_basedn") +db_password = rcp.get("configuration", "db_password") +self_registration = rcp.get("configuration", "self_registration") ######## -base_dir = os.path.dirname(__file__) +base_dir = os.path.dirname(__file__) config_dir = os.path.join(base_dir, "config") templates_dir = os.path.join(base_dir, "templates") @@ -45,17 +54,17 @@ def render(src, dest, **kw): t = Template(open(src, 'r').read()) with open(dest, 'w') as f: f.write(t.substitute(**kw)) - print "Generated configuration file: %s" % dest + print("Generated configuration file: %s" % dest) ui_conf_env = os.path.join(config_dir, "ui", "env") -ui_conf = os.path.join(config_dir, "ui", "app.conf") +ui_conf = os.path.join(config_dir, "ui", "app.conf") registry_conf = os.path.join(config_dir, "registry", "config.yml") db_conf_env = os.path.join(config_dir, "db", "env") conf_files = [ ui_conf, ui_conf_env, registry_conf, db_conf_env ] for f in conf_files: if os.path.exists(f): - print "Clearing the configuration file: %s" % f + print("Clearing the configuration file: %s" % f) os.remove(f) render(os.path.join(templates_dir, "ui", "env"), @@ -86,4 +95,4 @@ render(os.path.join(templates_dir, "db", "env"), db_conf_env, db_password=db_password) -print "The configuration files are ready, please use docker-compose to start the service." +print("The configuration files are ready, please use docker-compose to start the service.") From 327ca7304b6ad917db4dfcf648ea489198191723 Mon Sep 17 00:00:00 2001 From: saga92 Date: Fri, 15 Apr 2016 17:23:40 +0800 Subject: [PATCH 19/21] add python3 compatibility --- Deploy/prepare | 57 +++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/Deploy/prepare b/Deploy/prepare index 7a6b88966..a9b7520e3 100755 --- a/Deploy/prepare +++ b/Deploy/prepare @@ -1,34 +1,43 @@ #!/usr/bin/python - -import ConfigParser -import StringIO -import os +# -*- coding: utf-8 -*- +from __future__ import print_function, unicode_literals # We require Python 2.6 or later from string import Template +import os +import six +from io import open + +if six.PY2: + import ConfigParser as ConfigParser + import StringIO as StringIO + +if six.PY3: + import configparser as ConfigParser + import io as StringIO #Read configurations conf = StringIO.StringIO() conf.write("[configuration]\n") conf.write(open("harbor.cfg").read()) conf.seek(0, os.SEEK_SET) -cp = ConfigParser.RawConfigParser() -cp.readfp(conf) +rcp = ConfigParser.RawConfigParser() +rcp.readfp(conf) -hostname = cp.get("configuration", "hostname") -ui_url = cp.get("configuration", "ui_url_protocol") + "://" + hostname -email_server = cp.get("configuration", "email_server") -email_server_port = cp.get("configuration", "email_server_port") -email_username = cp.get("configuration", "email_username") -email_password = cp.get("configuration", "email_password") -email_from = cp.get("configuration", "email_from") -harbor_admin_password = cp.get("configuration", "harbor_admin_password") -auth_mode = cp.get("configuration", "auth_mode") -ldap_url = cp.get("configuration", "ldap_url") -ldap_basedn = cp.get("configuration", "ldap_basedn") -db_password = cp.get("configuration", "db_password") -self_registration = cp.get("configuration", "self_registration") +hostname = rcp.get("configuration", "hostname") +ui_url = rcp.get("configuration", "ui_url_protocol") + "://" + hostname +email_server = rcp.get("configuration", "email_server") +email_server_port = rcp.get("configuration", "email_server_port") +email_username = rcp.get("configuration", "email_username") +email_password = rcp.get("configuration", "email_password") +email_from = rcp.get("configuration", "email_from") +harbor_admin_password = rcp.get("configuration", "harbor_admin_password") +auth_mode = rcp.get("configuration", "auth_mode") +ldap_url = rcp.get("configuration", "ldap_url") +ldap_basedn = rcp.get("configuration", "ldap_basedn") +db_password = rcp.get("configuration", "db_password") +self_registration = rcp.get("configuration", "self_registration") ######## -base_dir = os.path.dirname(__file__) +base_dir = os.path.dirname(__file__) config_dir = os.path.join(base_dir, "config") templates_dir = os.path.join(base_dir, "templates") @@ -45,17 +54,17 @@ def render(src, dest, **kw): t = Template(open(src, 'r').read()) with open(dest, 'w') as f: f.write(t.substitute(**kw)) - print "Generated configuration file: %s" % dest + print("Generated configuration file: %s" % dest) ui_conf_env = os.path.join(config_dir, "ui", "env") -ui_conf = os.path.join(config_dir, "ui", "app.conf") +ui_conf = os.path.join(config_dir, "ui", "app.conf") registry_conf = os.path.join(config_dir, "registry", "config.yml") db_conf_env = os.path.join(config_dir, "db", "env") conf_files = [ ui_conf, ui_conf_env, registry_conf, db_conf_env ] for f in conf_files: if os.path.exists(f): - print "Clearing the configuration file: %s" % f + print("Clearing the configuration file: %s" % f) os.remove(f) render(os.path.join(templates_dir, "ui", "env"), @@ -86,4 +95,4 @@ render(os.path.join(templates_dir, "db", "env"), db_conf_env, db_password=db_password) -print "The configuration files are ready, please use docker-compose to start the service." +print("The configuration files are ready, please use docker-compose to start the service.") From 7a0cd17b5ba67294d54e3dc88b9af43fad6e4fb7 Mon Sep 17 00:00:00 2001 From: saga92 Date: Fri, 15 Apr 2016 17:23:40 +0800 Subject: [PATCH 20/21] add python3 compatibility --- Deploy/prepare | 57 +++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/Deploy/prepare b/Deploy/prepare index 7a6b88966..a9b7520e3 100755 --- a/Deploy/prepare +++ b/Deploy/prepare @@ -1,34 +1,43 @@ #!/usr/bin/python - -import ConfigParser -import StringIO -import os +# -*- coding: utf-8 -*- +from __future__ import print_function, unicode_literals # We require Python 2.6 or later from string import Template +import os +import six +from io import open + +if six.PY2: + import ConfigParser as ConfigParser + import StringIO as StringIO + +if six.PY3: + import configparser as ConfigParser + import io as StringIO #Read configurations conf = StringIO.StringIO() conf.write("[configuration]\n") conf.write(open("harbor.cfg").read()) conf.seek(0, os.SEEK_SET) -cp = ConfigParser.RawConfigParser() -cp.readfp(conf) +rcp = ConfigParser.RawConfigParser() +rcp.readfp(conf) -hostname = cp.get("configuration", "hostname") -ui_url = cp.get("configuration", "ui_url_protocol") + "://" + hostname -email_server = cp.get("configuration", "email_server") -email_server_port = cp.get("configuration", "email_server_port") -email_username = cp.get("configuration", "email_username") -email_password = cp.get("configuration", "email_password") -email_from = cp.get("configuration", "email_from") -harbor_admin_password = cp.get("configuration", "harbor_admin_password") -auth_mode = cp.get("configuration", "auth_mode") -ldap_url = cp.get("configuration", "ldap_url") -ldap_basedn = cp.get("configuration", "ldap_basedn") -db_password = cp.get("configuration", "db_password") -self_registration = cp.get("configuration", "self_registration") +hostname = rcp.get("configuration", "hostname") +ui_url = rcp.get("configuration", "ui_url_protocol") + "://" + hostname +email_server = rcp.get("configuration", "email_server") +email_server_port = rcp.get("configuration", "email_server_port") +email_username = rcp.get("configuration", "email_username") +email_password = rcp.get("configuration", "email_password") +email_from = rcp.get("configuration", "email_from") +harbor_admin_password = rcp.get("configuration", "harbor_admin_password") +auth_mode = rcp.get("configuration", "auth_mode") +ldap_url = rcp.get("configuration", "ldap_url") +ldap_basedn = rcp.get("configuration", "ldap_basedn") +db_password = rcp.get("configuration", "db_password") +self_registration = rcp.get("configuration", "self_registration") ######## -base_dir = os.path.dirname(__file__) +base_dir = os.path.dirname(__file__) config_dir = os.path.join(base_dir, "config") templates_dir = os.path.join(base_dir, "templates") @@ -45,17 +54,17 @@ def render(src, dest, **kw): t = Template(open(src, 'r').read()) with open(dest, 'w') as f: f.write(t.substitute(**kw)) - print "Generated configuration file: %s" % dest + print("Generated configuration file: %s" % dest) ui_conf_env = os.path.join(config_dir, "ui", "env") -ui_conf = os.path.join(config_dir, "ui", "app.conf") +ui_conf = os.path.join(config_dir, "ui", "app.conf") registry_conf = os.path.join(config_dir, "registry", "config.yml") db_conf_env = os.path.join(config_dir, "db", "env") conf_files = [ ui_conf, ui_conf_env, registry_conf, db_conf_env ] for f in conf_files: if os.path.exists(f): - print "Clearing the configuration file: %s" % f + print("Clearing the configuration file: %s" % f) os.remove(f) render(os.path.join(templates_dir, "ui", "env"), @@ -86,4 +95,4 @@ render(os.path.join(templates_dir, "db", "env"), db_conf_env, db_password=db_password) -print "The configuration files are ready, please use docker-compose to start the service." +print("The configuration files are ready, please use docker-compose to start the service.") From 43fe8be0727fcbf53d0a83d9949f8f5d8326d0d5 Mon Sep 17 00:00:00 2001 From: saga92 Date: Mon, 18 Apr 2016 16:01:44 +0800 Subject: [PATCH 21/21] remove six library dependency --- Deploy/prepare | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Deploy/prepare b/Deploy/prepare index a9b7520e3..5571e3c20 100755 --- a/Deploy/prepare +++ b/Deploy/prepare @@ -3,14 +3,14 @@ from __future__ import print_function, unicode_literals # We require Python 2.6 or later from string import Template import os -import six +import sys from io import open -if six.PY2: +if sys.version_info[:3][0] == 2: import ConfigParser as ConfigParser import StringIO as StringIO -if six.PY3: +if sys.version_info[:3][0] == 3: import configparser as ConfigParser import io as StringIO