From 299bf31337e5037375cb5682fc83edefd7eb7b8e Mon Sep 17 00:00:00 2001 From: root Date: Mon, 15 May 2017 02:51:51 +0000 Subject: [PATCH 01/19] Add the discription of notary in User Guide --- docs/user_guide.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/user_guide.md b/docs/user_guide.md index e45bca95e..219572f99 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -242,6 +242,9 @@ If you want to enable content trust to ensure that images are signed, please set export DOCKER_CONTENT_TRUST=1 export DOCKER_CONTENT_TRUST_SERVER=https://10.117.169.182:4443 ``` +If you push the image for the first time, You will be asked to enter the root key passphrase. This will be needed every time you push a new image while the ``DOCKER_CONTENT_TRUST`` flag is set. +The root key is generated at: ``/root/.docker/trust/private/root_keys`` +You will also be asked to enter a new passphrase for the image. This is generated at ``/root/.docker/trust/private/tuf_keys/[registry name] /[imagepath]``. If you are using a self-signed cert, make sure to copy the CA cert into ```/etc/docker/certs.d/10.117.169.182``` and ```$HOME/.docker/tls/10.117.169.182:4443/```. When an image is signed, it is indicated in the Web UI. **Note: Replace "10.117.169.182" with the IP address or domain name of your Harbor node. In order to use content trust, HTTPS must be enabled in Harbor.** From f9480b92b59bdf7bb71ba553ca24d345eac18443 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 18 Aug 2017 18:11:15 +0000 Subject: [PATCH 02/19] k8s deployment --- .../adminserver/adminserver.rc.yaml | 232 ++++++++++++++++++ .../adminserver/adminserver.svc.yaml | 9 + make/kubernetes/jobservice/jobservice.rc.yaml | 4 +- make/kubernetes/prepare | 18 +- make/kubernetes/templates/adminserver.cm.yaml | 47 ++++ make/kubernetes/templates/jobservice.cm.yaml | 2 +- make/kubernetes/templates/ui.cm.yaml | 2 +- make/kubernetes/ui/ui.rc.yaml | 6 +- 8 files changed, 312 insertions(+), 8 deletions(-) create mode 100644 make/kubernetes/adminserver/adminserver.rc.yaml create mode 100644 make/kubernetes/adminserver/adminserver.svc.yaml create mode 100644 make/kubernetes/templates/adminserver.cm.yaml diff --git a/make/kubernetes/adminserver/adminserver.rc.yaml b/make/kubernetes/adminserver/adminserver.rc.yaml new file mode 100644 index 000000000..9843c1107 --- /dev/null +++ b/make/kubernetes/adminserver/adminserver.rc.yaml @@ -0,0 +1,232 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: adminserver-rc + labels: + name: adminserver-rc +spec: + replicas: 1 + selector: + name: adminserver-apps + template: + metadata: + labels: + name: adminserver-apps + spec: + containers: + - name: adminserver-app + image: 192.168.56.201:5000/vmware/harbor-adminserver:dev + imagePullPolicy: IfNotPresent + env: + - name: LOG_LEVEL + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: LOG_LEVEL + - name: JSON_CFG_STORE_PATH + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: JSON_CFG_STORE_PATH + - name: EXT_ENDPOINT + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: EXT_ENDPOINT + - name: AUTH_MODE + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: AUTH_MODE + - name: SELF_REGISTRATION + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: SELF_REGISTRATION + - name: LDAP_URL + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: LDAP_URL + - name: LDAP_SEARCH_DN + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: LDAP_SEARCH_DN + - name: LDAP_SEARCH_PWD + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: LDAP_SEARCH_PWD + - name: LDAP_BASE_DN + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: LDAP_BASE_DN + - name: LDAP_FILTER + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: LDAP_FILTER + - name: LDAP_UID + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: LDAP_UID + - name: LDAP_SCOPE + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: LDAP_SCOPE + - name: LDAP_TIMEOUT + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: LDAP_TIMEOUT + - name: DATABASE_TYPE + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: DATABASE_TYPE + - name: MYSQL_HOST + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: MYSQL_HOST + - name: MYSQL_PORT + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: MYSQL_PORT + - name: MYSQL_USR + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: MYSQL_USR + - name: MYSQL_PWD + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: MYSQL_PWD + - name: MYSQL_DATABASE + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: MYSQL_DATABASE + - name: REGISTRY_URL + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: REGISTRY_URL + - name: TOKEN_SERVICE_URL + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: TOKEN_SERVICE_URL + - name: EMAIL_HOST + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: EMAIL_HOST + - name: EMAIL_PORT + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: EMAIL_PORT + - name: EMAIL_USR + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: EMAIL_USR + - name: EMAIL_PWD + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: EMAIL_PWD + - name: EMAIL_SSL + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: EMAIL_SSL + - name: EMAIL_FROM + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: EMAIL_FROM + - name: EMAIL_IDENTITY + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: EMAIL_IDENTITY + - name: HARBOR_ADMIN_PASSWORD + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: HARBOR_ADMIN_PASSWORD + - name: PROJECT_CREATION_RESTRICTION + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: PROJECT_CREATION_RESTRICTION + - name: VERIFY_REMOTE_CERT + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: VERIFY_REMOTE_CERT + - name: MAX_JOB_WORKERS + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: MAX_JOB_WORKERS + - name: UI_SECRET + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: UI_SECRET + - name: JOBSERVICE_SECRET + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: JOBSERVICE_SECRET + - name: TOKEN_EXPIRATION + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: TOKEN_EXPIRATION + - name: CFG_EXPIRATION + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: CFG_EXPIRATION + - name: GODEBUG + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: GODEBUG + - name: ADMIRAL_URL + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: ADMIRAL_URL + - name: WITH_NOTARY + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: WITH_NOTARY + - name: RESET + valueFrom: + configMapKeyRef: + name: harbor-adminserver-config + key: RESET + ports: + - containerPort: 80 + volumeMounts: + - name: config + mountPath: /etc/adminserver/ + volumes: + - name: config + configMap: + name: harbor-adminserver-config + items: + - key: SECRET_KEY + path: key diff --git a/make/kubernetes/adminserver/adminserver.svc.yaml b/make/kubernetes/adminserver/adminserver.svc.yaml new file mode 100644 index 000000000..4e3fc7be4 --- /dev/null +++ b/make/kubernetes/adminserver/adminserver.svc.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Service +metadata: + name: adminserver +spec: + ports: + - port: 80 + selector: + name: adminserver-apps diff --git a/make/kubernetes/jobservice/jobservice.rc.yaml b/make/kubernetes/jobservice/jobservice.rc.yaml index 3b62a8ae0..60f2bf65b 100644 --- a/make/kubernetes/jobservice/jobservice.rc.yaml +++ b/make/kubernetes/jobservice/jobservice.rc.yaml @@ -43,11 +43,11 @@ spec: configMapKeyRef: name: harbor-jobservice-config key: UI_SECRET - - name: SECRET_KEY + - name: JOBSERVICE_SECRET valueFrom: configMapKeyRef: name: harbor-jobservice-config - key: SECRET_KEY + key: JOBSERVICE_SECRET - name: CONFIG_PATH valueFrom: configMapKeyRef: diff --git a/make/kubernetes/prepare b/make/kubernetes/prepare index 465fec7ac..fb70e21b8 100644 --- a/make/kubernetes/prepare +++ b/make/kubernetes/prepare @@ -29,6 +29,8 @@ parser.add_argument('-k', default='', dest='private_key', help='[Optional] path of harbor https private key(pem)') parser.add_argument('-c', default='', dest='cert', help='[Optional] harbor path of https cert(pem)') +parser.add_argument('-j', default='', + dest='jobservice_secret', help="[Optional] path of harbor secret key(16 characters)") parser.add_argument('-s', default='', dest='secret_key', help="[Optional] path of harbor secret key(16 characters)") @@ -99,7 +101,20 @@ else: cert_path = '' -# read secret key +# read jobservice secret key +if args.jobservice_secret != '': + if os.path.isfile(args.jobservice_secret): + key = '' + with open(args.jobservice_secret, 'r') as skey: + key = skey.read() + if len(key) != 16: + raise Exception('Error: The length of secret key has to be 16 characters!') + set_config('jobservice_secret', key) +else: + set_config('jobservice_secret', ''.join(random.choice( + string.ascii_letters + string.digits) for i in range(16))) + +# read ldap secret key if args.secret_key != '': if os.path.isfile(args.secret_key): key = '' @@ -199,3 +214,4 @@ generate_template(os.path.join(template_dir, 'jobservice.cm.yaml'), os.path.join generate_template(os.path.join(template_dir, 'mysql.cm.yaml'), os.path.join(output_dir, 'mysql/mysql.cm.yaml')) generate_template(os.path.join(template_dir, 'nginx.cm.yaml'), os.path.join(output_dir, 'nginx/nginx.cm.yaml')) generate_template(os.path.join(template_dir, 'registry.cm.yaml'), os.path.join(output_dir, 'registry/registry.cm.yaml')) +generate_template(os.path.join(template_dir, 'adminserver.cm.yaml'), os.path.join(output_dir, 'adminserver/adminserver.cm.yaml')) diff --git a/make/kubernetes/templates/adminserver.cm.yaml b/make/kubernetes/templates/adminserver.cm.yaml new file mode 100644 index 000000000..e5552c2d9 --- /dev/null +++ b/make/kubernetes/templates/adminserver.cm.yaml @@ -0,0 +1,47 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: harbor-adminserver-config +data: + LOG_LEVEL: debug + AUTH_MODE: db_auth + SELF_REGISTRATION: "on" + LDAP_URL: ldaps://ldap.mydomain.com + LDAP_SEARCH_DN: + LDAP_SEARCH_PWD: + LDAP_BASE_DN: "ou=people,dc=mydomain,dc=com" + LDAP_FILTER: + LDAP_UID: uid + LDAP_SCOPE: "3" + LDAP_TIMEOUT: "5" + 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: smtp.mydomain.com + EMAIL_PORT: "25" + EMAIL_USR: sample_admin@mydomain.com + EMAIL_PWD: abc + EMAIL_SSL: "false" + EMAIL_FROM: "admin " + EMAIL_IDENTITY: + HARBOR_ADMIN_PASSWORD: "{{harbor_admin_password}}" + PROJECT_CREATION_RESTRICTION: everyone + VERIFY_REMOTE_CERT: "on" + MAX_JOB_WORKERS: "{{max_job_workers}}" + UI_SECRET: "{{ui_secret}}" + JOBSERVICE_SECRET: "{{jobservice_secret}}" + TOKEN_EXPIRATION: "30" + CFG_EXPIRATION: "5" + GODEBUG: "netdns=cgo" + ADMIRAL_URL: NA + WITH_NOTARY: "False" + RESET: "false" + EXT_ENDPOINT: "{{ui_url}}" + TOKEN_URL: http://ui + JSON_CFG_STORE_PATH: "/etc/config/config.json" + SECRET_KEY: "{{secret_key}}" diff --git a/make/kubernetes/templates/jobservice.cm.yaml b/make/kubernetes/templates/jobservice.cm.yaml index b2123c757..c1702c2d6 100644 --- a/make/kubernetes/templates/jobservice.cm.yaml +++ b/make/kubernetes/templates/jobservice.cm.yaml @@ -8,7 +8,7 @@ data: MYSQL_USR: root MYSQL_PWD: "{{db_password}}" UI_SECRET: "{{ui_secret}}" - SECRET_KEY: "{{secret_key}}" + JOBSERVICE_SECRET: "{{jobservice_secret}}" CONFIG_PATH: /etc/jobservice/app.conf REGISTRY_URL: http://registry:5000 VERIFY_REMOTE_CERT: "{{verify_remote_cert}}" diff --git a/make/kubernetes/templates/ui.cm.yaml b/make/kubernetes/templates/ui.cm.yaml index 4df0a2a0c..d1fec20ae 100644 --- a/make/kubernetes/templates/ui.cm.yaml +++ b/make/kubernetes/templates/ui.cm.yaml @@ -22,7 +22,7 @@ data: LDAP_SCOPE: "{{ldap_scope}}" LOG_LEVEL: debug UI_SECRET: "{{ui_secret}}" - SECRET_KEY: "{{secret_key}}" + JOBSERVICE_SECRET: "{{jobservice_secre}}" GODEBUG: netdns=cgo EXT_ENDPOINT: "{{ui_url}}" TOKEN_URL: http://ui diff --git a/make/kubernetes/ui/ui.rc.yaml b/make/kubernetes/ui/ui.rc.yaml index a14902307..f19252085 100644 --- a/make/kubernetes/ui/ui.rc.yaml +++ b/make/kubernetes/ui/ui.rc.yaml @@ -113,11 +113,11 @@ spec: configMapKeyRef: name: harbor-ui-config key: UI_SECRET - - name: SECRET_KEY + - name: JOBSERVICE_SECRET valueFrom: configMapKeyRef: name: harbor-ui-config - key: SECRET_KEY + key: JOBSERVICE_SECRET - name: GODEBUG valueFrom: configMapKeyRef: @@ -171,4 +171,4 @@ spec: - key: config path: app.conf - key: pkey - path: private_key.pem \ No newline at end of file + path: private_key.pem From f2ef10d2e526b85e6dcf28fe1ce5d0aa303045ea Mon Sep 17 00:00:00 2001 From: supereagle Date: Tue, 22 Aug 2017 10:31:08 +0800 Subject: [PATCH 03/19] add release url in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 11a9c4293..9b6c0efc5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Coverage Status](https://coveralls.io/repos/github/vmware/harbor/badge.svg?branch=master)](https://coveralls.io/github/vmware/harbor?branch=master) **Note**: The `master` branch may be in an *unstable or even broken state* during development. -Please use [releases] instead of the `master` branch in order to get stable binaries. +Please use [releases](https://github.com/vmware/harbor/releases) instead of the `master` branch in order to get stable binaries. Harbor From 96bc432b4f8435c8df58f6b6d0077135c0b2cb1e Mon Sep 17 00:00:00 2001 From: Masataka Mizukoshi Date: Thu, 31 Aug 2017 04:57:02 +0000 Subject: [PATCH 04/19] fix typo --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index d9a6735d7..2d70c96b1 100644 --- a/Makefile +++ b/Makefile @@ -178,7 +178,7 @@ DOCKERIMAGENAME_UI=vmware/harbor-ui DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice DOCKERIMAGENAME_LOG=vmware/harbor-log DOCKERIMAGENAME_DB=vmware/harbor-db -DOCKERIMAGENAME_CLATIRY=vmware/harbor-clarity-ui-builder +DOCKERIMAGENAME_CLARITY=vmware/harbor-clarity-ui-builder DOCKERIMAGENAME_POSTGRESQL=vmware/postgresql # docker-compose files DOCKERCOMPOSEFILEPATH=$(MAKEPATH) @@ -399,10 +399,10 @@ refresh_clarity_builder: $(SEDCMD) -i 's/__proxy__/ /g' $(DOCKERFILE_CLARITY) ; \ fi ; \ echo "build new clarity image.."; \ - $(DOCKERBUILD) -f $(DOCKERFILE_CLARITY) -t $(DOCKERIMAGENAME_CLATIRY):$(NEWCLARITYVERSION) . ; \ + $(DOCKERBUILD) -f $(DOCKERFILE_CLARITY) -t $(DOCKERIMAGENAME_CLARITY):$(NEWCLARITYVERSION) . ; \ echo "push clarity image.."; \ - $(DOCKERTAG) $(DOCKERIMAGENAME_CLATIRY):$(NEWCLARITYVERSION) $(DOCKERIMAGENAME_CLATIRY):$(NEWCLARITYVERSION); \ - $(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_CLATIRY):$(NEWCLARITYVERSION) \ + $(DOCKERTAG) $(DOCKERIMAGENAME_CLARITY):$(NEWCLARITYVERSION) $(DOCKERIMAGENAME_CLARITY):$(NEWCLARITYVERSION); \ + $(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_CLARITY):$(NEWCLARITYVERSION) \ $(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER); \ echo "remove local clarity image.."; \ $(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(NEWCLARITYVERSION); \ From 923a8d65b1438b0390755dccfbdde83d9acfdc9b Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Wed, 30 Aug 2017 15:15:44 +0800 Subject: [PATCH 05/19] expose insecure flag in api --- src/adminserver/systemcfg/systemcfg.go | 4 ++++ src/common/const.go | 1 + src/common/models/config.go | 1 + src/common/utils/test/adminserver.go | 1 + src/ui/api/config.go | 2 ++ src/ui/api/email.go | 7 +++++-- src/ui/config/config.go | 1 + src/ui/controllers/base.go | 3 ++- 8 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/adminserver/systemcfg/systemcfg.go b/src/adminserver/systemcfg/systemcfg.go index 77e87b5bf..36fcb49f7 100644 --- a/src/adminserver/systemcfg/systemcfg.go +++ b/src/adminserver/systemcfg/systemcfg.go @@ -91,6 +91,10 @@ var ( env: "EMAIL_SSL", parse: parseStringToBool, }, + common.EmailInsecure: &parser{ + env: "EMAIL_INSECURE", + parse: parseStringToBool, + }, common.EmailFrom: "EMAIL_FROM", common.EmailIdentity: "EMAIL_IDENTITY", common.RegistryURL: "REGISTRY_URL", diff --git a/src/common/const.go b/src/common/const.go index 78516f003..f59b1d904 100644 --- a/src/common/const.go +++ b/src/common/const.go @@ -55,6 +55,7 @@ const ( EmailFrom = "email_from" EmailSSL = "email_ssl" EmailIdentity = "email_identity" + EmailInsecure = "email_insecure" ProjectCreationRestriction = "project_creation_restriction" VerifyRemoteCert = "verify_remote_cert" MaxJobWorkers = "max_job_workers" diff --git a/src/common/models/config.go b/src/common/models/config.go index a2444e10d..ba5c91863 100644 --- a/src/common/models/config.go +++ b/src/common/models/config.go @@ -65,6 +65,7 @@ type Email struct { SSL bool `json:"ssl"` Identity string `json:"identity"` From string `json:"from"` + Insecure bool `json:"insecure"` } /* diff --git a/src/common/utils/test/adminserver.go b/src/common/utils/test/adminserver.go index 862fdd653..69eaa5014 100644 --- a/src/common/utils/test/adminserver.go +++ b/src/common/utils/test/adminserver.go @@ -50,6 +50,7 @@ var adminServerDefaultConfig = map[string]interface{}{ common.EmailPassword: "password", common.EmailFrom: "from", common.EmailSSL: true, + common.EmailInsecure: false, common.EmailIdentity: "", common.ProjectCreationRestriction: common.ProCrtRestrAdmOnly, common.VerifyRemoteCert: false, diff --git a/src/ui/api/config.go b/src/ui/api/config.go index 191dcc4f8..c76453dc8 100644 --- a/src/ui/api/config.go +++ b/src/ui/api/config.go @@ -47,6 +47,7 @@ var ( common.EmailFrom, common.EmailSSL, common.EmailIdentity, + common.EmailInsecure, common.ProjectCreationRestriction, common.VerifyRemoteCert, common.TokenExpiration, @@ -78,6 +79,7 @@ var ( boolKeys = []string{ common.EmailSSL, + common.EmailInsecure, common.SelfRegistration, common.VerifyRemoteCert, } diff --git a/src/ui/api/email.go b/src/ui/api/email.go index f0ef12173..9dd3d67b8 100644 --- a/src/ui/api/email.go +++ b/src/ui/api/email.go @@ -51,7 +51,7 @@ func (e *EmailAPI) Prepare() { func (e *EmailAPI) Ping() { var host, username, password, identity string var port int - var ssl bool + var ssl, insecure bool body := e.Ctx.Input.CopyBody(1 << 32) if body == nil || len(body) == 0 { cfg, err := config.Email() @@ -66,6 +66,7 @@ func (e *EmailAPI) Ping() { password = cfg.Password identity = cfg.Identity ssl = cfg.SSL + insecure = cfg.Insecure } else { settings := &struct { Host string `json:"email_host"` @@ -74,6 +75,7 @@ func (e *EmailAPI) Ping() { Password *string `json:"email_password"` SSL bool `json:"email_ssl"` Identity string `json:"email_identity"` + Insecure bool `json:"email_insecure"` }{} e.DecodeJSONReq(&settings) @@ -98,11 +100,12 @@ func (e *EmailAPI) Ping() { password = *settings.Password identity = settings.Identity ssl = settings.SSL + insecure = settings.Insecure } addr := net.JoinHostPort(host, strconv.Itoa(port)) if err := email.Ping(addr, identity, username, - password, pingEmailTimeout, ssl, false); err != nil { + password, pingEmailTimeout, ssl, insecure); err != nil { log.Debugf("ping %s failed: %v", addr, err) e.CustomAbort(http.StatusBadRequest, err.Error()) } diff --git a/src/ui/config/config.go b/src/ui/config/config.go index f98b7ed96..47aeabe8b 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -298,6 +298,7 @@ func Email() (*models.Email, error) { email.SSL = cfg[common.EmailSSL].(bool) email.From = cfg[common.EmailFrom].(string) email.Identity = cfg[common.EmailIdentity].(string) + email.Insecure = cfg[common.EmailInsecure].(bool) return email, nil } diff --git a/src/ui/controllers/base.go b/src/ui/controllers/base.go index b83af84a5..bac64b5a4 100644 --- a/src/ui/controllers/base.go +++ b/src/ui/controllers/base.go @@ -171,7 +171,8 @@ func (cc *CommonController) SendEmail() { settings.Username, settings.Password, 60, settings.SSL, - false, settings.From, + settings.Insecure, + settings.From, []string{email}, "Reset Harbor user password", message.String()) From 74dfeed7df7256b2e2da9f04bcd6d2ba6864a857 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Mon, 4 Sep 2017 15:12:54 +0800 Subject: [PATCH 06/19] update swagger --- docs/swagger.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 9bf728160..7415625bc 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2727,6 +2727,9 @@ definitions: email_ssl: type: boolean description: When it's set to true the system will access Email server via TLS by default. If it's set to false, it still will handle "STARTTLS" from server side. + email_insecure: + type: boolean + description: Whether or not the certificate will be verified when Harbor tries to access the email server. ldap_url: type: string description: The URL of LDAP server. From 99c1168e293bde89b4bb13222547035af3a3e0d1 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Mon, 11 Sep 2017 12:47:17 +0800 Subject: [PATCH 07/19] update description of new feature for different roles --- docs/user_guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user_guide.md b/docs/user_guide.md index b55edceaa..da1fbecf0 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -24,11 +24,11 @@ Harbor manages images through projects. Users can be added into one project as a * **Guest**: Guest has read-only privilege for a specified project. * **Developer**: Developer has read and write privileges for a project. -* **ProjectAdmin**: When creating a new project, you will be assigned the "ProjectAdmin" role to the project. Besides read-write privileges, the "ProjectAdmin" also has some management privileges, such as adding and removing members. +* **ProjectAdmin**: When creating a new project, you will be assigned the "ProjectAdmin" role to the project. Besides read-write privileges, the "ProjectAdmin" also has some management privileges, such as adding and removing members, starting a vulnerability scan. Besides the above three roles, there are two system-wide roles: -* **SysAdmin**: "SysAdmin" has the most privileges. In addition to the privileges mentioned above, "SysAdmin" can also list all projects, set an ordinary user as administrator and delete users. The public project "library" is also owned by the administrator. +* **SysAdmin**: "SysAdmin" has the most privileges. In addition to the privileges mentioned above, "SysAdmin" can also list all projects, set an ordinary user as administrator, delete users and set vulnerability scan policy for all images. The public project "library" is also owned by the administrator. * **Anonymous**: When a user is not logged in, the user is considered as an "Anonymous" user. An anonymous user has no access to private projects and has read-only access to public projects. ## User account From 84d66d85faf0c106072fcb701ebed4551c3d15eb Mon Sep 17 00:00:00 2001 From: weibaohui Date: Mon, 11 Sep 2017 15:13:24 +0800 Subject: [PATCH 08/19] Correct spelling Correct spelling --- src/common/api/base.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/api/base.go b/src/common/api/base.go index 255bd0ff8..e381a3bb3 100644 --- a/src/common/api/base.go +++ b/src/common/api/base.go @@ -195,9 +195,9 @@ func (b *BaseAPI) GetUserIDForRequest() (int, bool, bool) { // Redirect does redirection to resource URI with http header status code. func (b *BaseAPI) Redirect(statusCode int, resouceID string) { requestURI := b.Ctx.Request.RequestURI - resoucreURI := requestURI + "/" + resouceID + resourceURI := requestURI + "/" + resouceID - b.Ctx.Redirect(statusCode, resoucreURI) + b.Ctx.Redirect(statusCode, resourceURI) } // GetIDFromURL checks the ID in request URL @@ -221,7 +221,7 @@ func (b *BaseAPI) SetPaginationHeader(total, page, pageSize int64) { link := "" - // SetPaginationHeader setprevious link + // SetPaginationHeader set previous link if page > 1 && (page-1)*pageSize <= total { u := *(b.Ctx.Request.URL) q := u.Query() @@ -233,7 +233,7 @@ func (b *BaseAPI) SetPaginationHeader(total, page, pageSize int64) { link += fmt.Sprintf("<%s>; rel=\"prev\"", u.String()) } - // SetPaginationHeader setnext link + // SetPaginationHeader set next link if pageSize*page < total { u := *(b.Ctx.Request.URL) q := u.Query() From 75566bffd1a106f2a7d3130b306d5abde0c556e3 Mon Sep 17 00:00:00 2001 From: "Deng, Qian" Date: Thu, 7 Sep 2017 18:29:05 +0800 Subject: [PATCH 09/19] refactory testcase for project member management and replace tab to space --- tests/resources/Docker-Util.robot | 30 ++- .../Harbor-Pages/Configuration.robot | 95 ++++---- tests/resources/Harbor-Pages/HomePage.robot | 16 +- .../Harbor-Pages/Project-Members.robot | 206 +++++++++++++----- .../Project-Members_Elements.robot | 7 +- tests/resources/Harbor-Pages/Project.robot | 112 +++++----- .../resources/Harbor-Pages/Replication.robot | 16 +- .../resources/Harbor-Pages/UserProfile.robot | 10 +- tests/robot-cases/Group0-BAT/BAT.robot | 92 +++++--- 9 files changed, 371 insertions(+), 213 deletions(-) diff --git a/tests/resources/Docker-Util.robot b/tests/resources/Docker-Util.robot index dd081c1b9..16ca3eb51 100644 --- a/tests/resources/Docker-Util.robot +++ b/tests/resources/Docker-Util.robot @@ -40,11 +40,24 @@ Push image Log To Console \nRunning docker push ${image}... ${rc}= Run And Return Rc docker pull ${image} ${rc} ${output}= Run And Return Rc And Output docker login -u ${user} -p ${pwd} ${ip} - Log To Console ${output} + Log To Console ${output} Should Be Equal As Integers ${rc} 0 ${rc}= Run And Return Rc docker tag ${image} ${ip}/${project}/${image} ${rc} ${output}= Run And Return Rc And Output docker push ${ip}/${project}/${image} - Log To Console ${output} + Log To Console ${output} + Should Be Equal As Integers ${rc} 0 + ${rc}= Run And Return Rc docker logout ${ip} + +Push Image With Tag + [Arguments] ${ip} ${user} ${pwd} ${project} ${image} ${tag} + Log To Console \nRunning docker push ${image}... + ${rc}= Run And Return Rc docker pull ${image} + ${rc} ${output}= Run And Return Rc And Output docker login -u ${user} -p ${pwd} ${ip} + Log To Console ${output} + Should Be Equal As Integers ${rc} 0 + ${rc}= Run And Return Rc docker tag ${image} ${tag} + ${rc} ${output}= Run And Return Rc And Output docker push ${tag} + Log To Console ${output} Should Be Equal As Integers ${rc} 0 ${rc}= Run And Return Rc docker logout ${ip} @@ -56,6 +69,19 @@ Cannot Pull image Log To Console ${output} Should Not Be Equal As Integers ${rc} 0 +Cannot Push image + [Arguments] ${ip} ${user} ${pwd} ${project} ${image} + Log To Console \nRunning docker push ${image}... + ${rc}= Run And Return Rc docker pull ${image} + ${rc} ${output}= Run And Return Rc And Output docker login -u ${user} -p ${pwd} ${ip} + Log To Console ${output} + Should Be Equal As Integers ${rc} 0 + ${rc}= Run And Return Rc docker tag ${image} ${ip}/${project}/${image} + ${rc} ${output}= Run And Return Rc And Output docker push ${ip}/${project}/${image} + Log To Console ${output} + Should Not Be Equal As Integers ${rc} 0 + ${rc}= Run And Return Rc docker logout ${ip} + Wait Until Container Stops [Arguments] ${container} :FOR ${idx} IN RANGE 0 60 diff --git a/tests/resources/Harbor-Pages/Configuration.robot b/tests/resources/Harbor-Pages/Configuration.robot index e7255c66e..e9b07c9e8 100644 --- a/tests/resources/Harbor-Pages/Configuration.robot +++ b/tests/resources/Harbor-Pages/Configuration.robot @@ -46,96 +46,96 @@ Switch To Configure Sleep 2 Set Pro Create Admin Only - #set limit to admin only - Sleep 2 + #set limit to admin only + Sleep 2 Click Element xpath=//clr-main-container//nav//ul/li[3] - Sleep 1 + Sleep 1 Click Element xpath=//select[@id="proCreation"] Click Element xpath=//select[@id="proCreation"]//option[@value="adminonly"] - Sleep 1 + Sleep 1 Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] - Capture Page Screenshot AdminCreateOnly.png - + Capture Page Screenshot AdminCreateOnly.png + Set Pro Create Every One - #set limit to Every One + #set limit to Every One Click Element xpath=//clr-main-container//nav//ul/li[3] - Sleep 1 + Sleep 1 Click Element xpath=//select[@id="proCreation"] Click Element xpath=//select[@id="proCreation"]//option[@value="everyone"] - Sleep 1 + Sleep 1 Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] Sleep 2 - Capture Page Screenshot EveryoneCreate.png + Capture Page Screenshot EveryoneCreate.png Disable Self Reg - Click Element xpath=//clr-main-container//nav//ul/li[3] + Click Element xpath=//clr-main-container//nav//ul/li[3] Mouse Down xpath=${self_reg_xpath} Mouse Up xpath=${self_reg_xpath} - Sleep 1 - Self Reg Should Be Disabled + Sleep 1 + Self Reg Should Be Disabled Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] - Capture Page Screenshot DisableSelfReg.png - Sleep 1 + Capture Page Screenshot DisableSelfReg.png + Sleep 1 Enable Self Reg - Mouse Down xpath=${self_reg_xpath} + Mouse Down xpath=${self_reg_xpath} Mouse Up xpath=${self_reg_xpath} - Sleep 1 - Self Reg Should Be Enabled + Sleep 1 + Self Reg Should Be Enabled Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] - Capture Page Screenshot EnableSelfReg.png - Sleep 1 - + Capture Page Screenshot EnableSelfReg.png + Sleep 1 + Self Reg Should Be Disabled - Checkbox Should Not Be Selected xpath=${self_reg_xpath} + Checkbox Should Not Be Selected xpath=${self_reg_xpath} Self Reg Should Be Enabled - Checkbox Should Be Selected xpath=${self_reg_xpath} - + Checkbox Should Be Selected xpath=${self_reg_xpath} + Project Creation Should Display Page Should Contain Element xpath=${project_create_xpath} Project Creation Should Not Display Page Should Not Contain Element xpath=${project_create_xpath} - + ## System settings Switch To System Settings Sleep 1 Click Element xpath=//clr-main-container//nav//ul/li[3] Click Element xpath=//config//ul/li[4] - + Modify Token Expiration - [Arguments] ${minutes} - Input Text xpath=//*[@id="tokenExpiration"] ${minutes} + [Arguments] ${minutes} + Input Text xpath=//*[@id="tokenExpiration"] ${minutes} Click Button xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] - Sleep 1 - + Sleep 1 + Token Must Be Match - [Arguments] ${minutes} - Textfield Value Should Be xpath=//*[@id="tokenExpiration"] ${minutes} + [Arguments] ${minutes} + Textfield Value Should Be xpath=//*[@id="tokenExpiration"] ${minutes} ## Replication Check Verify Remote Cert Mouse Down xpath=//*[@id="clr-checkbox-verifyRemoteCert"] Mouse Up xpath=//*[@id="clr-checkbox-verifyRemoteCert"] Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] - Capture Page Screenshot RemoteCert.png - Sleep 1 + Capture Page Screenshot RemoteCert.png + Sleep 1 Switch To System Replication Sleep 1 Switch To Configure Click Element xpath=//*[@id="config-replication"] - Sleep 1 - + Sleep 1 + Should Verify Remote Cert Be Enabled - Checkbox Should Not Be Selected xpath=//*[@id="clr-checkbox-verifyRemoteCert"] - + Checkbox Should Not Be Selected xpath=//*[@id="clr-checkbox-verifyRemoteCert"] + ## Email Switch To Email - Switch To Configure - Click Element xpath=//*[@id="config-email"] - Sleep 1 + Switch To Configure + Click Element xpath=//*[@id="config-email"] + Sleep 1 Config Email Input Text xpath=//*[@id="mailServer"] smtp.vmware.com @@ -143,13 +143,13 @@ Config Email Input Text xpath=//*[@id="emailUsername"] example@vmware.com Input Text xpath=//*[@id="emailPassword"] example Input Text xpath=//*[@id="emailFrom"] example - Sleep 1 - Mouse Down xpath=//*[@id="clr-checkbox-emailSSL"] + Sleep 1 + Mouse Down xpath=//*[@id="clr-checkbox-emailSSL"] Mouse Up xpath=//*[@id="clr-checkbox-emailSSL"] - Sleep 1 + Sleep 1 Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] - Sleep 6 - + Sleep 6 + Verify Email Textfield Value Should Be xpath=//*[@id="mailServer"] smtp.vmware.com Textfield Value Should Be xpath=//*[@id="emailPort"] 25 @@ -168,5 +168,4 @@ Set Scan All To Daily sleep 1 click element //config//div/button[contains(.,'SAVE')] Click Scan Now - click element //vulnerability-config//button[contains(.,'SCAN')] - + click element //vulnerability-config//button[contains(.,'SCAN')] \ No newline at end of file diff --git a/tests/resources/Harbor-Pages/HomePage.robot b/tests/resources/Harbor-Pages/HomePage.robot index dc001d41b..7eac05e5d 100644 --- a/tests/resources/Harbor-Pages/HomePage.robot +++ b/tests/resources/Harbor-Pages/HomePage.robot @@ -22,32 +22,32 @@ ${HARBOR_VERSION} v1.1.1 *** Keywords *** Sign In Harbor [Arguments] ${url} ${user} ${pw} - Go To ${url} + Go To ${url} Sleep 5 ${title}= Get Title Log To Console ${title} Should Be Equal ${title} Harbor - Sleep 2 + Sleep 2 Input Text login_username ${user} Input Text login_password ${pw} Sleep 2 Click button css=.btn sleep 5 - Log To Console ${user} + Log To Console ${user} Wait Until Page Contains ${user} Sign Up Should Not Display - Page Should Not Contain Element xpath=${sign_up_button_xpath} + Page Should Not Contain Element xpath=${sign_up_button_xpath} Create An New User [Arguments] ${url} ${username} ${email} ${realname} ${newPassword} ${comment} - Go To ${url} + Go To ${url} sleep 5 ${title}= Get Title Log To Console ${title} Should Be Equal ${title} Harbor - ${d}= Get Current Date result_format=%m%s - Sleep 5 + ${d}= Get Current Date result_format=%m%s + Sleep 5 Click Element xpath=${sign_up_for_an_account_xpath} sleep 3 Input Text xpath=${username_xpath} ${username} @@ -70,4 +70,4 @@ Create An New User Click button css=.btn sleep 5 Wait Until Page Contains ${username} - Sleep 3 + Sleep 3 diff --git a/tests/resources/Harbor-Pages/Project-Members.robot b/tests/resources/Harbor-Pages/Project-Members.robot index 0682daa40..e97dfaec4 100644 --- a/tests/resources/Harbor-Pages/Project-Members.robot +++ b/tests/resources/Harbor-Pages/Project-Members.robot @@ -21,69 +21,167 @@ ${HARBOR_VERSION} v1.1.1 *** Keywords *** Go Into Project - [Arguments] ${project} - Sleep 2 - Click Element xpath=//*[@id="search_input"] - Sleep 2 - Input Text xpath=//*[@id="search_input"] ${project} - Sleep 8 - Wait Until Page Contains ${project} - Click Element xpath=//*[@id="results"]/list-project-ro/clr-datagrid/div/div/div/div/div[2]/clr-dg-row[1]/clr-dg-row-master/clr-dg-cell[1]/a - Sleep 2 - Capture Page Screenshot gointo_${project}.png + [Arguments] ${project} + Sleep 2 + Click Element xpath=//*[@id="search_input"] + Sleep 2 + Input Text xpath=//*[@id="search_input"] ${project} + Sleep 8 + Wait Until Page Contains ${project} + Click Element xpath=//*[@id="results"]/list-project-ro/clr-datagrid/div/div/div/div/div[2]/clr-dg-row[1]/clr-dg-row-master/clr-dg-cell[1]/a + Sleep 2 + Capture Page Screenshot gointo_${project}.png Go Into Project2 - [Arguments] ${project} - Sleep 2 - Capture Page Screenshot gointo1_${project}.png - # search icon - Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/project/div/div/div[2]/div[2]/hbr-filter/span/clr-icon/svg - Sleep 2 - # text search project - Input Text xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/project/div/div/div[2]/div[2]/hbr-filter/span/input ${project} - Sleep 5 - Wait Until Page Contains ${project} - Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/project/div/div/list-project/clr-datagrid/div/div/div/div/div[2]/clr-dg-row/clr-dg-row-master/clr-dg-cell[2]/a - Sleep 3 - Capture Page Screenshot gointo2_${project}.png - + [Arguments] ${project} + Sleep 2 + Capture Page Screenshot gointo1_${project}.png + # search icon + Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/project/div/div/div[2]/div[2]/hbr-filter/span/clr-icon/svg + Sleep 2 + # text search project + Input Text xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/project/div/div/div[2]/div[2]/hbr-filter/span/input ${project} + Sleep 5 + Wait Until Page Contains ${project} + Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/project/div/div/list-project/clr-datagrid/div/div/div/div/div[2]/clr-dg-row/clr-dg-row-master/clr-dg-cell[2]/a + Sleep 3 + Capture Page Screenshot gointo2_${project}.png + Add User To Project Admin - [Arguments] ${project} ${user} - Go Into Project2 - Sleep 2 - Click Element xpath=${project_member_tag_xpath} - Sleep 1 + [Arguments] ${project} ${user} + Go Into Project2 + Sleep 2 + Click Element xpath=${project_member_tag_xpath} + Sleep 1 Click Element xpath=${project_member_add_button_xpath} Sleep 2 Input Text xpath=${project_member_add_username_xpath} ${user} - Sleep 3 + Sleep 3 Click Element xpath=${project_member_add_admin_xpath} Click Element xpath=${project_member_add_save_button_xpath} - Sleep 4 - + Sleep 4 + Search Project Member - [Arguments] ${project} ${user} - Go Into Project ${project} - Sleep 2 - Click Element xpath=${project_member_tag_xpath} - Sleep 1 - Click Element xpath=${project_member_search_button_xpath} - Sleep 1 - Click Element xpath=${project_member_search_text_xpath} - Sleep 2 - Wait Until Page Contains ${user} - + [Arguments] ${project} ${user} + Go Into Project ${project} + Sleep 2 + Click Element xpath=//clr-dg-cell//a[contains(.,"${project}")] + Sleep 1 + Click Element xpath=${project_member_search_button_xpath} + Sleep 1 + Click Element xpath=${project_member_search_text_xpath} + Sleep 2 + Wait Until Page Contains ${user} + Change Project Member Role - [Arguments] ${project} ${user} ${role} - Click Element xpath=//clr-dg-cell//a[contains(.,"${project}")] + [Arguments] ${project} ${user} ${role} + Click Element xpath=//clr-dg-cell//a[contains(.,"${project}")] Sleep 2 - Click Element xpath=${project_member_tag_xpath} - Sleep 1 - Click Element xpath=//project-detail//clr-dg-row-master[contains(.,"${user}")]//clr-dg-action-overflow - Sleep 1 + Click Element xpath=${project_member_tag_xpath} + Sleep 1 + Click Element xpath=//project-detail//clr-dg-row-master[contains(.,'${user}')]//clr-dg-action-overflow + Sleep 1 Click Element xpath=//project-detail//clr-dg-action-overflow//button[contains(.,"${role}")] - Sleep 2 - Wait Until Page Contains ${role} - - - \ No newline at end of file + Sleep 2 + Wait Until Page Contains ${role} + +User Can Change Role + [arguments] ${username} + Page Should Contain Element xpath=//project-detail//clr-dg-row-master[contains(.,'${username}')]//clr-dg-action-overflow + +User Can Not Change Role + [arguments] ${username} + Page Should Contain Element xpath=//project-detail//clr-dg-row-master[contains(.,'${username}')]//clr-dg-action-overflow[@hidden=""] + +Non-admin View Member Account + [arguments] ${times} + Xpath Should Match X Times //project-detail//clr-dg-action-overflow[@hidden=""] ${times} + +User Can Not Add Member + Page Should Not Contain Element xpath=${project_member_search_button_xpath2} + +Add Guest Member To Project + [arguments] ${member} + Click Element xpath=${project_member_search_button_xpath2} + Sleep 1 + Input Text xpath=${project_member_add_username_xpath} ${member} + #select guest + Mouse Down xpath=${project_member_guest_radio_checkbox} + Mouse Up xpath=${project_member_guest_radio_checkbox} + Click Button xpath=${project_member_add_button_xpath2} + Sleep 1 + +Delete Project Member + [arguments] ${member} + Click Element xpath=//project-detail//clr-dg-row-master[contains(.,'${member}')]//clr-dg-action-overflow + Click Element xpath=${project_member_delete_button_xpath} + Sleep 1 + Click Element xpath=${project_member_delete_confirmation_xpath} + Sleep 1 + +User Should Be Owner Of Project + [Arguments] ${user} ${pwd} ${project} + Sign In Harbor ${HARBOR_URL} ${user} ${pwd} + Go Into Project ${project} + Switch To Member + User Can Not Change Role ${user} + Push image ${ip} ${user} ${pwd} ${project} hello-world + Logout Harbor + +User Should Not Be A Member Of Project + [Arguments] ${user} ${pwd} ${project} + Sign In Harbor ${HARBOR_URL} ${user} ${pwd} + Project Should Not Display ${project} + Logout Harbor + Cannot Pull image ${ip} ${user} ${pwd} ${project} ${ip}/${project}/hello-world + Cannot Push image ${ip} ${user} ${pwd} ${project} ${ip}/${project}/hello-world + +Manage Project Member + [Arguments] ${admin} ${pwd} ${project} ${user} ${op} + Sign In Harbor ${HARBOR_URL} ${admin} ${pwd} + Go Into Project ${project} + Switch To Member + Run Keyword If '${op}' == 'Add' Add Guest Member To Project ${user} + ... ELSE IF '${op}' == 'Remove' Delete Project Member ${user} + ... ELSE Change Project Member Role ${project} ${user} ${role} + Logout Harbor + +Change User Role In Project + [Arguments] ${admin} ${pwd} ${project} ${user} ${role} + Sign In Harbor ${HARBOR_URL} ${admin} ${pwd} + Change Project Member Role ${project} ${user} ${role} + Logout Harbor + +User Should Be Guest + [Arguments] ${user} ${pwd} ${project} + Sign In Harbor ${HARBOR_URL} ${user} ${pwd} + Project Should Display ${project} + Go Into Project ${project} + Switch To Member + Non-admin View Member Account 2 + User Can Not Add Member + Logout Harbor + Pull image ${ip} ${user} ${pwd} ${project} hello-world + Cannot Push image ${ip} ${user} ${pwd} ${project} hello-world + +User Should Be Developer + [Arguments] ${user} ${pwd} ${project} + Sign In Harbor ${HARBOR_URL} ${user} ${pwd} + Project Should Display ${project} + Go Into Project ${project} + Switch To Member + Non-admin View Member Account 2 + User Can Not Add Member + Logout Harbor + Push Image With Tag ${ip} ${user} ${pwd} ${project} hello-world ${ip}/${project}/hello-world:v1 + +User Should Be Admin + [Arguments] ${user} ${pwd} ${project} ${guest} + Sign In Harbor ${HARBOR_URL} ${user} ${pwd} + Project Should Display ${project} + Go Into Project ${project} + Switch To Member + Add Guest Member To Project ${guest} + User Can Change Role ${guest} + Logout Harbor + Push Image With Tag ${ip} ${user} ${pwd} ${project} hello-world ${ip}/${project}/hello-world:v2 \ No newline at end of file diff --git a/tests/resources/Harbor-Pages/Project-Members_Elements.robot b/tests/resources/Harbor-Pages/Project-Members_Elements.robot index 088fe2f98..e39290e6b 100644 --- a/tests/resources/Harbor-Pages/Project-Members_Elements.robot +++ b/tests/resources/Harbor-Pages/Project-Members_Elements.robot @@ -22,4 +22,9 @@ ${project_member_add_username_xpath} //*[@id="member_name"] ${project_member_add_admin_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/ng-component/div/div[1]/div/div[1]/add-member/clr-modal/div/div[1]/div/div[1]/div/div[2]/form/section/div[2]/div[1]/label ${project_member_add_save_button_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/ng-component/div/div[1]/div/div[1]/add-member/clr-modal/div/div[1]/div/div[1]/div/div[3]/button[2] ${project_member_search_button_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/ng-component/div/div[1]/div/div[2]/hbr-filter/span/clr-icon/svg -${project_member_search_text_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/ng-component/div/div[1]/div/div[2]/hbr-filter/span/input \ No newline at end of file +${project_member_search_text_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/ng-component/div/div[1]/div/div[2]/hbr-filter/span/input +${project_member_search_button_xpath2} //project-detail//button//clr-icon +${project_member_add_button_xpath2} //project-detail//add-member//button[2] +${project_member_guest_radio_checkbox} //project-detail//form//input[@id='checkrads_guest'] +${project_member_delete_button_xpath} //project-detail//clr-dg-cell//clr-dg-action-overflow//button[contains(.,"Delete")] +${project_member_delete_confirmation_xpath} //confiramtion-dialog//button[2] \ No newline at end of file diff --git a/tests/resources/Harbor-Pages/Project.robot b/tests/resources/Harbor-Pages/Project.robot index 552fc57b5..d6bebf6c4 100644 --- a/tests/resources/Harbor-Pages/Project.robot +++ b/tests/resources/Harbor-Pages/Project.robot @@ -21,73 +21,77 @@ ${HARBOR_VERSION} v1.1.1 *** Keywords *** Create An New Project - [Arguments] ${projectname} ${public}=false - Sleep 1 - Click Button css=${create_project_button_css} - Sleep 1 - Log To Console Project Name: ${projectname} - Input Text xpath=${project_name_xpath} ${projectname} - Sleep 3 - Run Keyword If '${public}' == 'true' Click Element xpath=${project_public_xpath} - Click Element css=${project_save_css} - Sleep 4 - Wait Until Page Contains ${projectname} - Wait Until Page Contains Project Admin - + [Arguments] ${projectname} ${public}=false + Sleep 1 + Click Button css=${create_project_button_css} + Sleep 1 + Log To Console Project Name: ${projectname} + Input Text xpath=${project_name_xpath} ${projectname} + Sleep 3 + Run Keyword If '${public}' == 'true' Click Element xpath=${project_public_xpath} + Click Element css=${project_save_css} + Sleep 4 + Wait Until Page Contains ${projectname} + Wait Until Page Contains Project Admin + Create An New Project With New User - [Arguments] ${url} ${username} ${email} ${realname} ${newPassword} ${comment} ${projectname} ${public} - Create An New User url=${url} username=${username} email=${email} realname=${realname} newPassword=${newPassword} comment=${comment} + [Arguments] ${url} ${username} ${email} ${realname} ${newPassword} ${comment} ${projectname} ${public} + Create An New User url=${url} username=${username} email=${email} realname=${realname} newPassword=${newPassword} comment=${comment} Logout Harbor Sign In Harbor ${url} ${username} ${newPassword} - Create An New Project ${projectname} ${public} + Create An New Project ${projectname} ${public} Sleep 1 #It's the log of project. Go To Project Log - Click Element xpath=//project-detail//ul/li[3] - Sleep 2 - + Click Element xpath=//project-detail//ul/li[3] + Sleep 2 + +Switch To Member + Click Element xpath=//project-detail//li[2] + Sleep 1 + Switch To Log - Click Element xpath=${log_xpath} - Sleep 1 + Click Element xpath=${log_xpath} + Sleep 1 Switch To Replication - Click Element xpath=${replication_xpath} - Sleep 1 + Click Element xpath=${replication_xpath} + Sleep 1 Back To projects - Click Element xpath=${projects_xpath} - Sleep 1 + Click Element xpath=${projects_xpath} + Sleep 1 Project Should Display - [Arguments] ${projectname} - Page Should Contain Element xpath=//project//list-project//clr-dg-cell/a[contains(.,'${projectname}')] + [Arguments] ${projectname} + Page Should Contain Element xpath=//project//list-project//clr-dg-cell/a[contains(.,'${projectname}')] Project Should Not Display - [Arguments] ${projectname} - Page Should Not Contain Element xpath=//project//list-project//clr-dg-cell/a[contains(.,'${projectname}')] + [Arguments] ${projectname} + Page Should Not Contain Element xpath=//project//list-project//clr-dg-cell/a[contains(.,'${projectname}')] Search Private Projects - Click element xpath=//select - Click element xpath=//select/option[@value=1] - Sleep 1 - Capture Page Screenshot SearchPrivateProjects.png + Click element xpath=//select + Click element xpath=//select/option[@value=1] + Sleep 1 + Capture Page Screenshot SearchPrivateProjects.png Make Project Private - [Arguments] ${projectname} - Sleep 1 - Click element xpath=//project//list-project//clr-dg-row-master[contains(.,'${projectname}')]//clr-dg-action-overflow - Click element xpath=//project//list-project//clr-dg-action-overflow//button[contains(.,"Make Private")] + [Arguments] ${projectname} + Sleep 1 + Click element xpath=//project//list-project//clr-dg-row-master[contains(.,'${projectname}')]//clr-dg-action-overflow + Click element xpath=//project//list-project//clr-dg-action-overflow//button[contains(.,"Make Private")] Make Project Public - [Arguments] ${projectname} - Sleep 1 - Click element xpath=//project//list-project//clr-dg-row-master[contains(.,'${projectname}')]//clr-dg-action-overflow - Click element xpath=//project//list-project//clr-dg-action-overflow//button[contains(.,"Make Public")] + [Arguments] ${projectname} + Sleep 1 + Click element xpath=//project//list-project//clr-dg-row-master[contains(.,'${projectname}')]//clr-dg-action-overflow + Click element xpath=//project//list-project//clr-dg-action-overflow//button[contains(.,"Make Public")] Delete Repo - [Arguments] ${projectname} - Click Element xpath=//project-detail//clr-dg-row-master[contains(.,"${projectname}")]//clr-dg-action-overflow + [Arguments] ${projectname} + Click Element xpath=//project-detail//clr-dg-row-master[contains(.,"${projectname}")]//clr-dg-action-overflow Sleep 1 Click Element xpath=//clr-dg-action-overflow//button[contains(.,"Delete")] Sleep 1 @@ -95,18 +99,18 @@ Delete Repo Sleep 2 Advanced Search Should Display - Page Should Contain Element xpath=//audit-log//div[@class="flex-xs-middle"]/button + Page Should Contain Element xpath=//audit-log//div[@class="flex-xs-middle"]/button # it's not a common keywords, only used into log case. Do Log Advanced Search - Capture Page Screenshot LogAdvancedSearch.png - Sleep 1 - Page Should Contain Element xpath=//clr-dg-row[contains(.,"pull")] - Page Should Contain Element xpath=//clr-dg-row[contains(.,"push")] - Page Should Contain Element xpath=//clr-dg-row[contains(.,"create")] - Page Should Contain Element xpath=//clr-dg-row[contains(.,"delete")] - Sleep 1 - Click Element xpath=//audit-log//div[@class="flex-xs-middle"]/button + Capture Page Screenshot LogAdvancedSearch.png + Sleep 1 + Page Should Contain Element xpath=//clr-dg-row[contains(.,"pull")] + Page Should Contain Element xpath=//clr-dg-row[contains(.,"push")] + Page Should Contain Element xpath=//clr-dg-row[contains(.,"create")] + Page Should Contain Element xpath=//clr-dg-row[contains(.,"delete")] + Sleep 1 + Click Element xpath=//audit-log//div[@class="flex-xs-middle"]/button Sleep 1 Click Element xpath=//project-detail//audit-log//clr-dropdown/button Sleep 1 @@ -147,13 +151,13 @@ Expand Repo [Arguments] ${projectname} Click Element //repository//clr-dg-row-master[contains(.,'${projectname}')]//button/clr-icon sleep 1 - + Scan Repo [Arguments] ${projectname} Click Element //hbr-tag//clr-dg-row-master[contains(.,'${projectname}')]//clr-dg-action-overflow Click Element //hbr-tag//clr-dg-row-master[contains(.,'${projectname}')]//clr-dg-action-overflow//button[contains(.,'Scan')] Sleep 15 - + Summary Chart Should Display [Arguments] ${projectname} Page Should Contain Element //clr-dg-row-master[contains(.,'${projectname}')]//hbr-vulnerability-bar//hbr-vulnerability-summary-chart \ No newline at end of file diff --git a/tests/resources/Harbor-Pages/Replication.robot b/tests/resources/Harbor-Pages/Replication.robot index e8cd5579c..7c1eb174e 100644 --- a/tests/resources/Harbor-Pages/Replication.robot +++ b/tests/resources/Harbor-Pages/Replication.robot @@ -21,23 +21,23 @@ ${HARBOR_VERSION} v1.1.1 *** Keywords *** Create An New Rule With New Endpoint - [Arguments] ${policy_name} ${policy_description} ${destination_name} ${destination_url} ${destination_username} ${destination_password} + [Arguments] ${policy_name} ${policy_description} ${destination_name} ${destination_url} ${destination_username} ${destination_password} Click element xpath=${new_name_xpath} Sleep 2 - Input Text xpath=${policy_name_xpath} ${policy_name} + Input Text xpath=${policy_name_xpath} ${policy_name} Input Text xpath=${policy_description_xpath} ${policy_description} - + Click element xpath=${policy_enable_checkbox} Click element xpath=${policy_endpoint_checkbox} - + Input text xpath=${destination_name_xpath} ${destination_name} Input text xpath=${destination_url_xpath} ${destination_url} Input text xpath=${destination_username_xpath} ${destination_username} Input text xpath=${destination_password_xpath} ${destination_password} Click element xpath=${replicaton_save_xpath} Sleep 5 - Capture Page Screenshot rule_${policy_name}.png - Wait Until Page Contains ${policy_name} - Wait Until Page Contains ${policy_description} - Wait Until Page Contains ${destination_name} \ No newline at end of file + Capture Page Screenshot rule_${policy_name}.png + Wait Until Page Contains ${policy_name} + Wait Until Page Contains ${policy_description} + Wait Until Page Contains ${destination_name} \ No newline at end of file diff --git a/tests/resources/Harbor-Pages/UserProfile.robot b/tests/resources/Harbor-Pages/UserProfile.robot index 375d1fb30..4d7f462a8 100644 --- a/tests/resources/Harbor-Pages/UserProfile.robot +++ b/tests/resources/Harbor-Pages/UserProfile.robot @@ -45,12 +45,12 @@ Update User Comment Sleep 2 Logout Harbor - Wait Until Element Is Visible xpath=/html/body/harbor-app/harbor-shell/clr-main-container/navigator/clr-header/div[3]/clr-dropdown[2]/button/span - Wait Until Element Is Enabled xpath=/html/body/harbor-app/harbor-shell/clr-main-container/navigator/clr-header/div[3]/clr-dropdown[2]/button/span + Wait Until Element Is Visible xpath=/html/body/harbor-app/harbor-shell/clr-main-container/navigator/clr-header/div[3]/clr-dropdown[2]/button/span + Wait Until Element Is Enabled xpath=/html/body/harbor-app/harbor-shell/clr-main-container/navigator/clr-header/div[3]/clr-dropdown[2]/button/span Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/navigator/clr-header/div[3]/clr-dropdown[2]/button/span Sleep 2 Click Element xpath=//harbor-app/harbor-shell/clr-main-container/navigator/clr-header//clr-dropdown//a[4] - Sleep 1 - Capture Page Screenshot Logout.png - Sleep 2 + Sleep 1 + Capture Page Screenshot Logout.png + Sleep 2 Wait Until Keyword Succeeds 5x 1 Page Should Contain Element xpath=//sign-in//form//*[@class="title"] diff --git a/tests/robot-cases/Group0-BAT/BAT.robot b/tests/robot-cases/Group0-BAT/BAT.robot index 42ed89267..5a75ef029 100644 --- a/tests/robot-cases/Group0-BAT/BAT.robot +++ b/tests/robot-cases/Group0-BAT/BAT.robot @@ -24,7 +24,7 @@ ${HARBOR_URL} http://localhost *** Test Cases *** Test Case - Create An New User Init Chrome Driver - ${d}= Get Current Date result_format=%m%s + ${d}= Get Current Date result_format=%m%s Create An New User url=${HARBOR_URL} username=tester${d} email=tester${d}@vmware.com realname=harbortest newPassword=Test1@34 comment=harbortest Close Browser @@ -64,7 +64,7 @@ Test Case - User View Projects Create An New Project test${d}2 Create An New Project test${d}3 Switch To Log - Capture Page Screenshot UserViewProjects.png + Capture Page Screenshot UserViewProjects.png Wait Until Page Contains test${d}1 Wait Until Page Contains test${d}2 Wait Until Page Contains test${d}3 @@ -76,7 +76,7 @@ Test Case - Push Image Create An New User url=${HARBOR_URL} username=tester${d} email=tester${d}@vmware.com realname=harbortest newPassword=Test1@34 comment=harbortest Create An New Project test${d} - Push image ${ip} tester${d} Test1@34 test${d} hello-world:latest + Push image ${ip} tester${d} Test1@34 test${d} hello-world:latest Go Into Project test${d} Wait Until Page Contains test${d}/hello-world @@ -84,18 +84,18 @@ Test Case - User View Logs Init Chrome Driver ${d}= Get Current Date result_format=%m%s - Create An New Project With New User url=${HARBOR_URL} username=tester${d} email=tester${d}@vmware.com realname=tester${d} newPassword=Test1@34 comment=harbor projectname=project${d} public=true + Create An New Project With New User url=${HARBOR_URL} username=tester${d} email=tester${d}@vmware.com realname=tester${d} newPassword=Test1@34 comment=harbor projectname=project${d} public=true - Push image ${ip} tester${d} Test1@34 project${d} busybox:latest + Push image ${ip} tester${d} Test1@34 project${d} busybox:latest Pull image ${ip} tester${d} Test1@34 project${d} busybox:latest - Go Into Project project${d} - Delete Repo project${d} + Go Into Project project${d} + Delete Repo project${d} - Go To Project Log - Advanced Search Should Display + Go To Project Log + Advanced Search Should Display - Do Log Advanced Search + Do Log Advanced Search Close Browser Test Case - Manage project publicity @@ -135,25 +135,25 @@ Test Case - Manage project publicity Logout Harbor Sign In Harbor ${HARBOR_URL} userb${d} Test1@34 Project Should Display project${d} - Close Browser + Close Browser Test Case - Edit Project Creation - # create normal user and login + # create normal user and login Init Chrome Driver ${d}= Get Current Date result_format=%m%s Create An New User url=${HARBOR_URL} username=tester${d} email=tester${d}@vmware.com realname=harbortest newPassword=Test1@34 comment=harbortest - Project Creation Should Display + Project Creation Should Display Logout Harbor - Sleep 3 + Sleep 3 Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} - Set Pro Create Admin Only + Set Pro Create Admin Only Logout Harbor - Sign In Harbor ${HARBOR_URL} tester${d} Test1@34 - Project Creation Should Not Display - Logout Harbor + Sign In Harbor ${HARBOR_URL} tester${d} Test1@34 + Project Creation Should Not Display + Logout Harbor Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Set Pro Create Every One @@ -163,16 +163,16 @@ Test Case - Edit Self-Registration Init Chrome Driver Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Disable Self Reg - Logout Harbor + Logout Harbor Sign Up Should Not Display Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} - Switch To Configure + Switch To Configure Self Reg Should Be Disabled Sleep 1 - #restore setting + #restore setting Enable Self Reg Close Browser @@ -197,13 +197,13 @@ Test Case - Edit Email Settings Init Chrome Driver Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} - Switch To Email + Switch To Email Config Email Logout Harbor Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} - Switch To Email + Switch To Email Verify Email Close Browser @@ -211,15 +211,15 @@ Test Case - Edit Email Settings Test Case - Edit Token Expire Init Chrome Driver Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} - Switch To System Settings - Modify Token Expiration 20 + Switch To System Settings + Modify Token Expiration 20 Logout Harbor - Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} - Switch To System Settings + Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} + Switch To System Settings Token Must Be Match 20 - #reset to default + #reset to default Modify Token Expiration 30 Close Browser @@ -228,10 +228,10 @@ Test Case - Create An Replication Rule New Endpoint ${d}= Get current date result_format=%m%s Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Create An New Project project${d} - Go Into Project project${d} + Go Into Project project${d} Switch To Replication Create An New Rule With New Endpoint policy_name=test_policy_${d} policy_description=test_description destination_name=test_destination_name_${d} destination_url=test_destination_url_${d} destination_username=test_destination_username destination_password=test_destination_password - Close Browser + Close Browser Test Case - Scan A Tag Init Chrome Driver @@ -242,7 +242,33 @@ Test Case - Scan A Tag Expand Repo project${d} Scan Repo project${d} Summary Chart Should Display project${d} - Close Browser + Close Browser + +Test Case-Manage Project Member + Init Chrome Driver + ${d}= Get current Date result_format=%m%s + + Create An New Project With New User url=${HARBOR_URL} username=alice${d} email=alice${d}@vmware.com realname=alice${d} newPassword=Test1@34 comment=harbor projectname=project${d} public=false + Push image ip=${ip} user=alice${d} pwd=Test1@34 project=project${d} image=hello-world + Logout Harbor + Create An New User url=${HARBOR_URL} username=bob${d} email=bob${d}@vmware.com realname=bob${d} newPassword=Test1@34 comment=habor + Logout Harbor + Create An New User url=${HARBOR_URL} username=carol${d} email=carol${d}@vmware.com realname=carol${d} newPassword=Test1@34 comment=harbor + Logout Harbor + + User Should Be Owner Of Project alice${d} Test1@34 project${d} + User Should Not Be A Member Of Project bob${d} Test1@34 project${d} + Manage Project Member alice${d} Test1@34 project${d} bob${d} Add + User Should Be Guest bob${d} Test1@34 project${d} + Change User Role In Project alice${d} Test1@34 project${d} bob${d} Developer + User Should Be Developer bob${d} Test1@34 project${d} + Change User Role In Project alice${d} Test1@34 project${d} bob${d} Admin + User Should Be Admin bob${d} Test1@34 project${d} carol${d} + Manage Project Member alice${d} Test1@34 project${d} bob${d} Remove + User Should Not Be A Member Of Project bob${d} Test1@34 project${d} + User Should Be Guest carol${d} Test1@34 project${d} + + Close Browser Test Case - Assign Sys Admin Init Chrome Driver @@ -273,7 +299,7 @@ Test Case - Admin Push Signed Image ${rc} ${output}= Run And Return Rc And Output docker pull hello-world:latest Log To Console ${output} - Push image ${ip} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} library hello-world:latest + Push image ${ip} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} library hello-world:latest ${rc} ${output}= Run And Return Rc And Output ./tests/robot-cases/Group9-Content-trust/notary-push-image.sh Log To Console ${output} @@ -289,4 +315,4 @@ Test Case - Admin Push Un-Signed Image Log To Console ${output} Test Case - Clean Harbor Images - Down Harbor with_notary=true \ No newline at end of file + Down Harbor with_notary=true From 2ebcc454baa5cfa03858c692ee0c8d134bc9f65a Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Fri, 15 Sep 2017 13:29:27 +0800 Subject: [PATCH 10/19] support image size --- docs/swagger.yaml | 3 +++ src/ui/api/repository.go | 58 +++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 9bf728160..920d320b9 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2586,6 +2586,9 @@ definitions: name: type: string description: The name of the tag. + size: + type: integer + description: The size of the image. architecture: type: string description: The architecture of the image. diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 6e15559d0..ab0ac0ad8 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -54,9 +54,10 @@ type repoResp struct { UpdateTime time.Time `json:"update_time"` } -type tag struct { +type tagDetail struct { Digest string `json:"digest"` Name string `json:"name"` + Size int64 `json:"size"` Architecture string `json:"architecture"` OS string `json:"os"` DockerVersion string `json:"docker_version"` @@ -65,7 +66,7 @@ type tag struct { } type tagResp struct { - tag + tagDetail Signature *notary.Target `json:"signature"` ScanOverview *models.ImgScanOverview `json:"scan_overview,omitempty"` } @@ -390,17 +391,13 @@ func assemble(client *registry.Repository, repository string, for _, t := range tags { item := &tagResp{} - // tag configuration - digest, _, cfg, err := getV2Manifest(client, t) + // the detail information of tag + tagDetail, err := getTagDetail(client, t) if err != nil { - cfg = &tag{ - Digest: digest, - Name: t, - } log.Errorf("failed to get v2 manifest of %s:%s: %v", repository, t, err) } - if cfg != nil { - item.tag = *cfg + if tagDetail != nil { + item.tagDetail = *tagDetail } // scan overview @@ -425,40 +422,45 @@ func assemble(client *registry.Repository, repository string, return result } -// get v2 manifest of tag, returns digest, manifest, -// manifest config and error. The manifest config contains -// architecture, os, author, etc. -func getV2Manifest(client *registry.Repository, tagName string) ( - string, *schema2.DeserializedManifest, *tag, error) { - digest, _, payload, err := client.PullManifest(tagName, []string{schema2.MediaTypeManifest}) - if err != nil { - return "", nil, nil, err +// getTagDetail returns the detail information for v2 manifest image +// The information contains architecture, os, author, size, etc. +func getTagDetail(client *registry.Repository, tag string) (*tagDetail, error) { + detail := &tagDetail{ + Name: tag, } + digest, _, payload, err := client.PullManifest(tag, []string{schema2.MediaTypeManifest}) + if err != nil { + return detail, err + } + detail.Digest = digest + manifest := &schema2.DeserializedManifest{} if err = manifest.UnmarshalJSON(payload); err != nil { - return digest, nil, nil, err + return detail, err + } + + // size of manifest + size of layers + detail.Size = int64(len(payload)) + for _, ref := range manifest.References() { + detail.Size += ref.Size } _, reader, err := client.PullBlob(manifest.Target().Digest.String()) if err != nil { - return digest, manifest, nil, err + return detail, err } configData, err := ioutil.ReadAll(reader) if err != nil { - return digest, manifest, nil, err + return detail, err } - config := &tag{} - if err = json.Unmarshal(configData, config); err != nil { - return digest, manifest, nil, err + if err = json.Unmarshal(configData, detail); err != nil { + return detail, err } - config.Name = tagName - config.Digest = digest - - return digest, manifest, config, nil + return detail, nil } // GetManifests returns the manifest of a tag From 5afc0b5d28b48e8d706cc162d59cda49d1670109 Mon Sep 17 00:00:00 2001 From: wangyan Date: Mon, 18 Sep 2017 15:46:40 +0800 Subject: [PATCH 11/19] update notary case --- tests/robot-cases/Group0-BAT/BAT.robot | 1 - .../Group9-Content-trust/notary-push-image.sh | 18 +++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/robot-cases/Group0-BAT/BAT.robot b/tests/robot-cases/Group0-BAT/BAT.robot index 5a75ef029..36b6033d9 100644 --- a/tests/robot-cases/Group0-BAT/BAT.robot +++ b/tests/robot-cases/Group0-BAT/BAT.robot @@ -300,7 +300,6 @@ Test Case - Admin Push Signed Image Log To Console ${output} Push image ${ip} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} library hello-world:latest - ${rc} ${output}= Run And Return Rc And Output ./tests/robot-cases/Group9-Content-trust/notary-push-image.sh Log To Console ${output} Should Be Equal As Integers ${rc} 0 diff --git a/tests/robot-cases/Group9-Content-trust/notary-push-image.sh b/tests/robot-cases/Group9-Content-trust/notary-push-image.sh index 975653ad8..3c4849829 100755 --- a/tests/robot-cases/Group9-Content-trust/notary-push-image.sh +++ b/tests/robot-cases/Group9-Content-trust/notary-push-image.sh @@ -1,12 +1,24 @@ #!/bin/bash -docker pull tomcat +docker pull tomcat:latest IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'` +PASSHRASE='Harbor12345' + +echo $IP + export DOCKER_CONTENT_TRUST=1 export DOCKER_CONTENT_TRUST_SERVER=https://$IP:4443 -docker login -u admin -p Harbor12345 $IP +export NOTARY_ROOT_PASSPHRASE=$PASSHRASE +export NOTARY_TARGETS_PASSPHRASE=$PASSHRASE +export NOTARY_SNAPSHOT_PASSPHRASE=$PASSHRASE +export DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE=$PASSHRASE +export DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=$PASSHRASE +export DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE=$PASSHRASE +export DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE=$PASSHRASE +docker login -u admin -p Harbor12345 $IP docker tag tomcat $IP/library/tomcat:latest -python ./tests/robot-cases/Group9-Content-trust/notary-push-image.py +docker push $IP/library/tomcat:latest + From f0946b63cf889c812030fa06ee9009e4295396a9 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Tue, 19 Sep 2017 17:16:54 +0800 Subject: [PATCH 12/19] fix code style issues reported by golint --- .../systemcfg/store/json/driver_json.go | 6 +----- src/common/dao/repository_test.go | 5 +---- src/common/dao/sqlite.go | 6 +----- src/jobservice/job/job_test.go | 17 ++++++----------- src/ui/api/user.go | 5 +---- 5 files changed, 10 insertions(+), 29 deletions(-) diff --git a/src/adminserver/systemcfg/store/json/driver_json.go b/src/adminserver/systemcfg/store/json/driver_json.go index f16bc9560..4e795a52a 100644 --- a/src/adminserver/systemcfg/store/json/driver_json.go +++ b/src/adminserver/systemcfg/store/json/driver_json.go @@ -115,9 +115,5 @@ func (c *cfgStore) Write(config map[string]interface{}) error { return err } - if err = ioutil.WriteFile(c.path, b, 0600); err != nil { - return err - } - - return nil + return ioutil.WriteFile(c.path, b, 0600) } diff --git a/src/common/dao/repository_test.go b/src/common/dao/repository_test.go index 74eadc8a2..aefbce0e3 100644 --- a/src/common/dao/repository_test.go +++ b/src/common/dao/repository_test.go @@ -267,8 +267,5 @@ func deleteRepository(name string) error { } func clearRepositoryData() error { - if err := ClearTable(models.RepoTable); err != nil { - return err - } - return nil + return ClearTable(models.RepoTable) } diff --git a/src/common/dao/sqlite.go b/src/common/dao/sqlite.go index e65a29a71..6e7e1ff79 100644 --- a/src/common/dao/sqlite.go +++ b/src/common/dao/sqlite.go @@ -42,11 +42,7 @@ func (s *sqlite) Register(alias ...string) error { if len(alias) != 0 { an = alias[0] } - if err := orm.RegisterDataBase(an, "sqlite3", s.file); err != nil { - return err - } - - return nil + return orm.RegisterDataBase(an, "sqlite3", s.file) } // Name returns the name of SQLite diff --git a/src/jobservice/job/job_test.go b/src/jobservice/job/job_test.go index 48f7b608a..5a66c9e1c 100644 --- a/src/jobservice/job/job_test.go +++ b/src/jobservice/job/job_test.go @@ -15,6 +15,10 @@ package job import ( "fmt" + "os" + "strconv" + "testing" + "github.com/stretchr/testify/assert" "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/dao" @@ -22,9 +26,6 @@ import ( "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/test" "github.com/vmware/harbor/src/jobservice/config" - "os" - "strconv" - "testing" ) var repJobID, scanJobID int64 @@ -192,10 +193,7 @@ func clearRepJobData() error { if err := dao.ClearTable(models.RepPolicyTable); err != nil { return err } - if err := dao.ClearTable(models.RepTargetTable); err != nil { - return err - } - return nil + return dao.ClearTable(models.RepTargetTable) } func prepareScanJobData() error { @@ -220,8 +218,5 @@ func clearScanJobData() error { if err := dao.ClearTable(models.ScanJobTable); err != nil { return err } - if err := dao.ClearTable(models.ScanOverviewTable); err != nil { - return err - } - return nil + return dao.ClearTable(models.ScanOverviewTable) } diff --git a/src/ui/api/user.go b/src/ui/api/user.go index 19ff9b612..d96b02dbd 100644 --- a/src/ui/api/user.go +++ b/src/ui/api/user.go @@ -357,10 +357,7 @@ func validate(user models.User) error { if isIllegalLength(user.Password, 8, 20) { return fmt.Errorf("password with illegal length") } - if err := commonValidate(user); err != nil { - return err - } - return nil + return commonValidate(user) } //commonValidate validates email, realname, comment information when user register or change their profile From dc4f2ece722cac4c93dcb374306e60f9af332aee Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Wed, 20 Sep 2017 14:30:26 +0800 Subject: [PATCH 13/19] readjust package structure --- src/common/security/admiral/context.go | 6 ++--- src/common/security/local/context.go | 6 ++--- src/common/security/local/context_test.go | 4 +-- src/ui/api/base.go | 4 +-- src/ui/api/utils.go | 8 +++--- src/ui/config/config.go | 16 ++++++------ src/ui/filter/security.go | 26 +++++++++---------- src/ui/filter/security_test.go | 8 +++--- .../pmsdriver/admiral/admiral.go} | 2 +- .../pmsdriver/admiral/admiral_test.go} | 2 +- .../pms => promgr/pmsdriver/admiral}/token.go | 2 +- .../pmsdriver/admiral}/token_test.go | 2 +- .../pm.go => promgr/pmsdriver/local/local.go} | 2 +- .../pmsdriver/local/local_test.go} | 2 +- .../pm.go => promgr/promgr.go} | 6 ++--- src/ui/proxy/interceptor_test.go | 6 ++--- src/ui/proxy/interceptors.go | 6 ++--- src/ui/service/token/authutils.go | 4 +-- src/ui/service/token/creator.go | 8 +++--- 19 files changed, 60 insertions(+), 60 deletions(-) rename src/ui/{projectmanager/pms/pm.go => promgr/pmsdriver/admiral/admiral.go} (99%) rename src/ui/{projectmanager/pms/pm_test.go => promgr/pmsdriver/admiral/admiral_test.go} (99%) rename src/ui/{projectmanager/pms => promgr/pmsdriver/admiral}/token.go (98%) rename src/ui/{projectmanager/pms => promgr/pmsdriver/admiral}/token_test.go (98%) rename src/ui/{projectmanager/db/pm.go => promgr/pmsdriver/local/local.go} (99%) rename src/ui/{projectmanager/db/pm_test.go => promgr/pmsdriver/local/local_test.go} (99%) rename src/ui/{projectmanager/pm.go => promgr/promgr.go} (91%) diff --git a/src/common/security/admiral/context.go b/src/common/security/admiral/context.go index c10370c33..85e20a4de 100644 --- a/src/common/security/admiral/context.go +++ b/src/common/security/admiral/context.go @@ -19,18 +19,18 @@ import ( "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/security/admiral/authcontext" "github.com/vmware/harbor/src/common/utils/log" - "github.com/vmware/harbor/src/ui/projectmanager" + "github.com/vmware/harbor/src/ui/promgr" ) // SecurityContext implements security.Context interface based on // auth context and project manager type SecurityContext struct { ctx *authcontext.AuthContext - pm projectmanager.ProjectManager + pm promgr.ProMgr } // NewSecurityContext ... -func NewSecurityContext(ctx *authcontext.AuthContext, pm projectmanager.ProjectManager) *SecurityContext { +func NewSecurityContext(ctx *authcontext.AuthContext, pm promgr.ProMgr) *SecurityContext { return &SecurityContext{ ctx: ctx, pm: pm, diff --git a/src/common/security/local/context.go b/src/common/security/local/context.go index a70cc989c..22ab07080 100644 --- a/src/common/security/local/context.go +++ b/src/common/security/local/context.go @@ -19,17 +19,17 @@ import ( "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils/log" - "github.com/vmware/harbor/src/ui/projectmanager" + "github.com/vmware/harbor/src/ui/promgr" ) // SecurityContext implements security.Context interface based on database type SecurityContext struct { user *models.User - pm projectmanager.ProjectManager + pm promgr.ProMgr } // NewSecurityContext ... -func NewSecurityContext(user *models.User, pm projectmanager.ProjectManager) *SecurityContext { +func NewSecurityContext(user *models.User, pm promgr.ProMgr) *SecurityContext { return &SecurityContext{ user: user, pm: pm, diff --git a/src/common/security/local/context_test.go b/src/common/security/local/context_test.go index 742379f31..7e46a24cd 100644 --- a/src/common/security/local/context_test.go +++ b/src/common/security/local/context_test.go @@ -25,7 +25,7 @@ import ( "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils/log" - "github.com/vmware/harbor/src/ui/projectmanager/db" + "github.com/vmware/harbor/src/ui/promgr/pmsdriver/local" ) var ( @@ -47,7 +47,7 @@ var ( Email: "guestUser@vmware.com", } - pm = &db.ProjectManager{} + pm = &local.ProjectManager{} ) func TestMain(m *testing.M) { diff --git a/src/ui/api/base.go b/src/ui/api/base.go index f33967926..5f567a355 100644 --- a/src/ui/api/base.go +++ b/src/ui/api/base.go @@ -21,7 +21,7 @@ import ( "github.com/vmware/harbor/src/common/security" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/ui/filter" - "github.com/vmware/harbor/src/ui/projectmanager" + "github.com/vmware/harbor/src/ui/promgr" ) // BaseController ... @@ -31,7 +31,7 @@ type BaseController struct { SecurityCtx security.Context // ProjectMgr is the project manager which abstracts the operations // related to projects - ProjectMgr projectmanager.ProjectManager + ProjectMgr promgr.ProMgr } const ( diff --git a/src/ui/api/utils.go b/src/ui/api/utils.go index 0b9fd2047..bf9013289 100644 --- a/src/ui/api/utils.go +++ b/src/ui/api/utils.go @@ -33,7 +33,7 @@ import ( "github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry/auth" "github.com/vmware/harbor/src/ui/config" - "github.com/vmware/harbor/src/ui/projectmanager" + "github.com/vmware/harbor/src/ui/promgr" "github.com/vmware/harbor/src/ui/service/token" uiutils "github.com/vmware/harbor/src/ui/utils" ) @@ -166,7 +166,7 @@ func postReplicationAction(policyID int64, acton string) error { } // SyncRegistry syncs the repositories of registry with database. -func SyncRegistry(pm projectmanager.ProjectManager) error { +func SyncRegistry(pm promgr.ProMgr) error { log.Infof("Start syncing repositories from registry to DB... ") @@ -254,7 +254,7 @@ func catalog() ([]string, error) { } func diffRepos(reposInRegistry []string, reposInDB []string, - pm projectmanager.ProjectManager) ([]string, []string, error) { + pm promgr.ProMgr) ([]string, []string, error) { var needsAdd []string var needsDel []string @@ -359,7 +359,7 @@ func diffRepos(reposInRegistry []string, reposInDB []string, return needsAdd, needsDel, nil } -func projectExists(pm projectmanager.ProjectManager, repository string) (bool, error) { +func projectExists(pm promgr.ProMgr, repository string) (bool, error) { project, _ := utils.ParseRepository(repository) return pm.Exist(project) } diff --git a/src/ui/config/config.go b/src/ui/config/config.go index 47aeabe8b..f1207b73c 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -29,9 +29,9 @@ import ( "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/secret" "github.com/vmware/harbor/src/common/utils/log" - "github.com/vmware/harbor/src/ui/projectmanager" - "github.com/vmware/harbor/src/ui/projectmanager/db" - "github.com/vmware/harbor/src/ui/projectmanager/pms" + "github.com/vmware/harbor/src/ui/promgr" + "github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral" + "github.com/vmware/harbor/src/ui/promgr/pmsdriver/local" ) const ( @@ -46,14 +46,14 @@ var ( // AdminserverClient is a client for adminserver AdminserverClient client.Client // GlobalProjectMgr is initialized based on the deploy mode - GlobalProjectMgr projectmanager.ProjectManager + GlobalProjectMgr promgr.ProMgr mg *comcfg.Manager keyProvider comcfg.KeyProvider // AdmiralClient is initialized only under integration deploy mode // and can be passed to project manager as a parameter AdmiralClient *http.Client // TokenReader is used in integration mode to read token - TokenReader pms.TokenReader + TokenReader admiral.TokenReader ) // Init configurations @@ -108,7 +108,7 @@ func initProjectManager() { if !WithAdmiral() { // standalone log.Info("initializing the project manager based on database...") - GlobalProjectMgr = &db.ProjectManager{} + GlobalProjectMgr = &local.ProjectManager{} return } @@ -128,10 +128,10 @@ func initProjectManager() { path = defaultTokenFilePath } log.Infof("service token file path: %s", path) - TokenReader = &pms.FileTokenReader{ + TokenReader = &admiral.FileTokenReader{ Path: path, } - GlobalProjectMgr = pms.NewProjectManager(AdmiralClient, + GlobalProjectMgr = admiral.NewProjectManager(AdmiralClient, AdmiralEndpoint(), TokenReader) } diff --git a/src/ui/filter/security.go b/src/ui/filter/security.go index 9d92757ad..116298ce8 100644 --- a/src/ui/filter/security.go +++ b/src/ui/filter/security.go @@ -25,15 +25,15 @@ import ( "github.com/vmware/harbor/src/common/models" secstore "github.com/vmware/harbor/src/common/secret" "github.com/vmware/harbor/src/common/security" - "github.com/vmware/harbor/src/common/security/admiral" + admr "github.com/vmware/harbor/src/common/security/admiral" "github.com/vmware/harbor/src/common/security/admiral/authcontext" "github.com/vmware/harbor/src/common/security/local" "github.com/vmware/harbor/src/common/security/secret" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/ui/auth" "github.com/vmware/harbor/src/ui/config" - "github.com/vmware/harbor/src/ui/projectmanager" - "github.com/vmware/harbor/src/ui/projectmanager/pms" + "github.com/vmware/harbor/src/ui/promgr" + "github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral" ) type key string @@ -192,7 +192,7 @@ func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool { log.Debug("using global project manager...") pm := config.GlobalProjectMgr log.Debug("creating admiral security context...") - securCtx := admiral.NewSecurityContext(authCtx, pm) + securCtx := admr.NewSecurityContext(authCtx, pm) setSecurCtxAndPM(ctx.Request, securCtx, pm) return true @@ -265,12 +265,12 @@ func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool { } log.Debug("creating PMS project manager...") - pm := pms.NewProjectManager(config.AdmiralClient, - config.AdmiralEndpoint(), &pms.RawTokenReader{ + pm := admiral.NewProjectManager(config.AdmiralClient, + config.AdmiralEndpoint(), &admiral.RawTokenReader{ Token: token, }) log.Debug("creating admiral security context...") - securCtx := admiral.NewSecurityContext(authContext, pm) + securCtx := admr.NewSecurityContext(authContext, pm) setSecurCtxAndPM(ctx.Request, securCtx, pm) return true @@ -283,14 +283,14 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool { log.Debug("user information is nil") var securCtx security.Context - var pm projectmanager.ProjectManager + var pm promgr.ProMgr if config.WithAdmiral() { // integration with admiral log.Debug("creating PMS project manager...") - pm = pms.NewProjectManager(config.AdmiralClient, + pm = admiral.NewProjectManager(config.AdmiralClient, config.AdmiralEndpoint(), nil) log.Debug("creating admiral security context...") - securCtx = admiral.NewSecurityContext(nil, pm) + securCtx = admr.NewSecurityContext(nil, pm) } else { // standalone log.Debug("using local database project manager") @@ -302,7 +302,7 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool { return true } -func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm projectmanager.ProjectManager) { +func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm promgr.ProMgr) { addToReqContext(req, securCtxKey, ctx) addToReqContext(req, pmKey, pm) } @@ -331,7 +331,7 @@ func GetSecurityContext(req *http.Request) (security.Context, error) { } // GetProjectManager tries to get project manager from request and returns it -func GetProjectManager(req *http.Request) (projectmanager.ProjectManager, error) { +func GetProjectManager(req *http.Request) (promgr.ProMgr, error) { if req == nil { return nil, fmt.Errorf("request is nil") } @@ -341,7 +341,7 @@ func GetProjectManager(req *http.Request) (projectmanager.ProjectManager, error) return nil, fmt.Errorf("the project manager got from request is nil") } - p, ok := pm.(projectmanager.ProjectManager) + p, ok := pm.(promgr.ProMgr) if !ok { return nil, fmt.Errorf("the variable got from request is not project manager type") } diff --git a/src/ui/filter/security_test.go b/src/ui/filter/security_test.go index 9d08209cb..84acefcbd 100644 --- a/src/ui/filter/security_test.go +++ b/src/ui/filter/security_test.go @@ -37,8 +37,8 @@ import ( _ "github.com/vmware/harbor/src/ui/auth/db" _ "github.com/vmware/harbor/src/ui/auth/ldap" "github.com/vmware/harbor/src/ui/config" - "github.com/vmware/harbor/src/ui/projectmanager" - "github.com/vmware/harbor/src/ui/projectmanager/db" + "github.com/vmware/harbor/src/ui/promgr" + driver_local "github.com/vmware/harbor/src/ui/promgr/pmsdriver/local" ) func TestMain(m *testing.M) { @@ -316,9 +316,9 @@ func TestGetProjectManager(t *testing.T) { req, err = http.NewRequest("", "", nil) assert.Nil(t, err) req = req.WithContext(context.WithValue(req.Context(), - pmKey, &db.ProjectManager{})) + pmKey, &driver_local.ProjectManager{})) pm, err = GetProjectManager(req) assert.Nil(t, err) - _, ok := pm.(projectmanager.ProjectManager) + _, ok := pm.(promgr.ProMgr) assert.True(t, ok) } diff --git a/src/ui/projectmanager/pms/pm.go b/src/ui/promgr/pmsdriver/admiral/admiral.go similarity index 99% rename from src/ui/projectmanager/pms/pm.go rename to src/ui/promgr/pmsdriver/admiral/admiral.go index 14944248b..05fc6fce3 100644 --- a/src/ui/projectmanager/pms/pm.go +++ b/src/ui/promgr/pmsdriver/admiral/admiral.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package pms +package admiral import ( "bytes" diff --git a/src/ui/projectmanager/pms/pm_test.go b/src/ui/promgr/pmsdriver/admiral/admiral_test.go similarity index 99% rename from src/ui/projectmanager/pms/pm_test.go rename to src/ui/promgr/pmsdriver/admiral/admiral_test.go index c8965699a..2c37c2b90 100644 --- a/src/ui/projectmanager/pms/pm_test.go +++ b/src/ui/promgr/pmsdriver/admiral/admiral_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package pms +package admiral import ( "net/http" diff --git a/src/ui/projectmanager/pms/token.go b/src/ui/promgr/pmsdriver/admiral/token.go similarity index 98% rename from src/ui/projectmanager/pms/token.go rename to src/ui/promgr/pmsdriver/admiral/token.go index 1ecb60895..f04a8a2ed 100644 --- a/src/ui/projectmanager/pms/token.go +++ b/src/ui/promgr/pmsdriver/admiral/token.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package pms +package admiral import ( "io/ioutil" diff --git a/src/ui/projectmanager/pms/token_test.go b/src/ui/promgr/pmsdriver/admiral/token_test.go similarity index 98% rename from src/ui/projectmanager/pms/token_test.go rename to src/ui/promgr/pmsdriver/admiral/token_test.go index 642a45d06..43e2bae91 100644 --- a/src/ui/projectmanager/pms/token_test.go +++ b/src/ui/promgr/pmsdriver/admiral/token_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package pms +package admiral import ( "io/ioutil" diff --git a/src/ui/projectmanager/db/pm.go b/src/ui/promgr/pmsdriver/local/local.go similarity index 99% rename from src/ui/projectmanager/db/pm.go rename to src/ui/promgr/pmsdriver/local/local.go index f723d4516..177a10c13 100644 --- a/src/ui/projectmanager/db/pm.go +++ b/src/ui/promgr/pmsdriver/local/local.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package db +package local import ( "fmt" diff --git a/src/ui/projectmanager/db/pm_test.go b/src/ui/promgr/pmsdriver/local/local_test.go similarity index 99% rename from src/ui/projectmanager/db/pm_test.go rename to src/ui/promgr/pmsdriver/local/local_test.go index 8e7148fe0..96f29850a 100644 --- a/src/ui/projectmanager/db/pm_test.go +++ b/src/ui/promgr/pmsdriver/local/local_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package db +package local import ( "os" diff --git a/src/ui/projectmanager/pm.go b/src/ui/promgr/promgr.go similarity index 91% rename from src/ui/projectmanager/pm.go rename to src/ui/promgr/promgr.go index 4a66966e1..a5b5802a2 100644 --- a/src/ui/projectmanager/pm.go +++ b/src/ui/promgr/promgr.go @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package projectmanager +package promgr import ( "github.com/vmware/harbor/src/common/models" ) -// ProjectManager is the project mamager which abstracts the operations related +// ProMgr is the project mamager which abstracts the operations related // to projects -type ProjectManager interface { +type ProMgr interface { Get(projectIDOrName interface{}) (*models.Project, error) IsPublic(projectIDOrName interface{}) (bool, error) Exist(projectIDOrName interface{}) (bool, error) diff --git a/src/ui/proxy/interceptor_test.go b/src/ui/proxy/interceptor_test.go index dc382d090..e6e431dd0 100644 --- a/src/ui/proxy/interceptor_test.go +++ b/src/ui/proxy/interceptor_test.go @@ -9,7 +9,7 @@ import ( notarytest "github.com/vmware/harbor/src/common/utils/notary/test" utilstest "github.com/vmware/harbor/src/common/utils/test" "github.com/vmware/harbor/src/ui/config" - "github.com/vmware/harbor/src/ui/projectmanager/pms" + "github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral" "net/http" "net/http/httptest" @@ -148,8 +148,8 @@ func TestPMSPolicyChecker(t *testing.T) { panic(err) } - pm := pms.NewProjectManager(http.DefaultClient, - admiralEndpoint, &pms.RawTokenReader{ + pm := admiral.NewProjectManager(http.DefaultClient, + admiralEndpoint, &admiral.RawTokenReader{ Token: "token", }) name := "project_for_test_get_sev_low" diff --git a/src/ui/proxy/interceptors.go b/src/ui/proxy/interceptors.go index 03046667a..09bf10570 100644 --- a/src/ui/proxy/interceptors.go +++ b/src/ui/proxy/interceptors.go @@ -9,7 +9,7 @@ import ( "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/notary" "github.com/vmware/harbor/src/ui/config" - "github.com/vmware/harbor/src/ui/projectmanager" + "github.com/vmware/harbor/src/ui/promgr" uiutils "github.com/vmware/harbor/src/ui/utils" "context" @@ -88,7 +88,7 @@ func (ec envPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity) } type pmsPolicyChecker struct { - pm projectmanager.ProjectManager + pm promgr.ProMgr } func (pc pmsPolicyChecker) contentTrustEnabled(name string) bool { @@ -109,7 +109,7 @@ func (pc pmsPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity) } // newPMSPolicyChecker returns an instance of an pmsPolicyChecker -func newPMSPolicyChecker(pm projectmanager.ProjectManager) policyChecker { +func newPMSPolicyChecker(pm promgr.ProMgr) policyChecker { return &pmsPolicyChecker{ pm: pm, } diff --git a/src/ui/service/token/authutils.go b/src/ui/service/token/authutils.go index 22069fd89..a6bdcf84c 100644 --- a/src/ui/service/token/authutils.go +++ b/src/ui/service/token/authutils.go @@ -29,7 +29,7 @@ import ( "github.com/vmware/harbor/src/common/security" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/ui/config" - promgr "github.com/vmware/harbor/src/ui/projectmanager" + "github.com/vmware/harbor/src/ui/promgr" ) const ( @@ -81,7 +81,7 @@ func GetResourceActions(scopes []string) []*token.ResourceActions { //filterAccess iterate a list of resource actions and try to use the filter that matches the resource type to filter the actions. func filterAccess(access []*token.ResourceActions, ctx security.Context, - pm promgr.ProjectManager, filters map[string]accessFilter) error { + pm promgr.ProMgr, filters map[string]accessFilter) error { var err error for _, a := range access { f, ok := filters[a.Type] diff --git a/src/ui/service/token/creator.go b/src/ui/service/token/creator.go index c9ece13b3..3194cdb2d 100644 --- a/src/ui/service/token/creator.go +++ b/src/ui/service/token/creator.go @@ -26,7 +26,7 @@ import ( "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/filter" - promgr "github.com/vmware/harbor/src/ui/projectmanager" + "github.com/vmware/harbor/src/ui/promgr" ) var creatorMap map[string]Creator @@ -127,13 +127,13 @@ func parseImg(s string) (*image, error) { // An accessFilter will filter access based on userinfo type accessFilter interface { - filter(ctx security.Context, pm promgr.ProjectManager, a *token.ResourceActions) error + filter(ctx security.Context, pm promgr.ProMgr, a *token.ResourceActions) error } type registryFilter struct { } -func (reg registryFilter) filter(ctx security.Context, pm promgr.ProjectManager, +func (reg registryFilter) filter(ctx security.Context, pm promgr.ProMgr, a *token.ResourceActions) error { //Do not filter if the request is to access registry catalog if a.Name != "catalog" { @@ -151,7 +151,7 @@ type repositoryFilter struct { parser imageParser } -func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProjectManager, +func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProMgr, a *token.ResourceActions) error { //clear action list to assign to new acess element after perm check. img, err := rep.parser.parse(a.Name) From 148c6ecc53aa66aa451c178aa48963de14f07c8c Mon Sep 17 00:00:00 2001 From: "Fuhui Peng (c)" Date: Thu, 21 Sep 2017 14:50:15 +0800 Subject: [PATCH 14/19] =?UTF-8?q?fix=20issue=20about=20replication=20rule?= =?UTF-8?q?=20delete=20and=20username=20autocomplete=20and=E2=80=A6=20=20?= =?UTF-8?q?=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../list-replication-rule.component.ts | 52 +++++++++++++++---- .../src/replication/replication.component.ts | 30 ++++++++++- .../repository-stackview.component.spec.ts | 1 + src/ui_ng/lib/src/service/interface.ts | 1 + src/ui_ng/lib/src/service/tag.service.spec.ts | 1 + .../lib/src/tag/tag-detail.component.spec.ts | 1 + src/ui_ng/lib/src/tag/tag-detail.component.ts | 1 + src/ui_ng/lib/src/tag/tag.component.css.ts | 5 ++ src/ui_ng/lib/src/tag/tag.component.html.ts | 6 +-- src/ui_ng/lib/src/tag/tag.component.spec.ts | 1 + src/ui_ng/lib/src/tag/tag.component.ts | 16 ++++++ src/ui_ng/package.json | 2 +- .../add-member/add-member.component.css | 22 ++++++++ .../add-member/add-member.component.html | 14 +++-- .../member/add-member/add-member.component.ts | 34 ++++++++++++ src/ui_ng/src/i18n/lang/en-us-lang.json | 3 ++ src/ui_ng/src/i18n/lang/es-es-lang.json | 3 ++ src/ui_ng/src/i18n/lang/zh-cn-lang.json | 3 ++ 18 files changed, 176 insertions(+), 20 deletions(-) diff --git a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts index 96c525738..56d1e137b 100644 --- a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts +++ b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts @@ -26,7 +26,7 @@ import { } from '@angular/core'; import { ReplicationService } from '../service/replication.service'; -import { ReplicationRule } from '../service/interface'; +import {ReplicationJob, ReplicationJobItem, ReplicationRule} from '../service/interface'; import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component'; import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message'; @@ -70,6 +70,7 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { rules: ReplicationRule[]; changedRules: ReplicationRule[]; ruleName: string; + canDeleteRule: boolean; @ViewChild('toggleConfirmDialog') toggleConfirmDialog: ConfirmationDialogComponent; @@ -199,15 +200,48 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { this.toggleConfirmDialog.open(toggleConfirmMessage); } + jobList(): Promise { + let ruleData: ReplicationJobItem[]; + this.canDeleteRule = true; + let count: number = 0; + return toPromise(this.replicationService + .getJobs(this.selectedId)) + .then(response => { + ruleData = response.data; + if (ruleData.length) { + ruleData.forEach(job => { + if ((job.status === 'pending') || (job.status === 'running') || (job.status === 'retrying')) { + count ++; + } + }); + } + this.canDeleteRule = count > 0 ? false : true; + }) + .catch(error => this.errorHandler.error(error)); + } + deleteRule(rule: ReplicationRule) { - let deletionMessage: ConfirmationMessage = new ConfirmationMessage( - 'REPLICATION.DELETION_TITLE', - 'REPLICATION.DELETION_SUMMARY', - rule.name || '', - rule.id, - ConfirmationTargets.POLICY, - ConfirmationButtons.DELETE_CANCEL); - this.deletionConfirmDialog.open(deletionMessage); + this.jobList().then(() => { + let deletionMessage: ConfirmationMessage; + if (!this.canDeleteRule) { + deletionMessage = new ConfirmationMessage( + 'REPLICATION.DELETION_TITLE_FAILURE', + 'REPLICATION.DELETION_SUMMARY_FAILURE', + rule.name || '', + rule.id, + ConfirmationTargets.POLICY, + ConfirmationButtons.CLOSE); + } else { + deletionMessage = new ConfirmationMessage( + 'REPLICATION.DELETION_TITLE', + 'REPLICATION.DELETION_SUMMARY', + rule.name || '', + rule.id, + ConfirmationTargets.POLICY, + ConfirmationButtons.DELETE_CANCEL); + } + this.deletionConfirmDialog.open(deletionMessage); + }); } } \ No newline at end of file diff --git a/src/ui_ng/lib/src/replication/replication.component.ts b/src/ui_ng/lib/src/replication/replication.component.ts index 168626672..9e6ce673f 100644 --- a/src/ui_ng/lib/src/replication/replication.component.ts +++ b/src/ui_ng/lib/src/replication/replication.component.ts @@ -11,7 +11,7 @@ // 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. -import { Component, OnInit, ViewChild, Input, Output, EventEmitter } from '@angular/core'; +import { Component, OnInit, ViewChild, Input, Output, OnDestroy, EventEmitter } from '@angular/core'; import { ResponseOptions, RequestOptions } from '@angular/http'; import { NgModel } from '@angular/forms'; @@ -41,6 +41,8 @@ import { REPLICATION_STYLE } from './replication.component.css'; import { JobLogViewerComponent } from '../job-log-viewer/index'; import { State } from "clarity-angular"; +import {Observable} from "rxjs/Observable"; +import {Subscription} from "rxjs/Subscription"; const ruleStatus: { [key: string]: any } = [ { 'key': 'all', 'description': 'REPLICATION.ALL_STATUS' }, @@ -79,7 +81,7 @@ export class SearchOption { template: REPLICATION_TEMPLATE, styles: [REPLICATION_STYLE] }) -export class ReplicationComponent implements OnInit { +export class ReplicationComponent implements OnInit, OnDestroy { @Input() projectId: number | string; @Input() withReplicationJob: boolean; @@ -124,6 +126,7 @@ export class ReplicationComponent implements OnInit { pageSize: number = DEFAULT_PAGE_SIZE; currentState: State; jobsLoading: boolean = false; + timerDelay: Subscription; constructor( private errorHandler: ErrorHandler, @@ -145,6 +148,12 @@ export class ReplicationComponent implements OnInit { this.currentJobSearchOption = 0; } + ngOnDestroy() { + if (this.timerDelay) { + this.timerDelay.unsubscribe(); + } + } + openModal(): void { this.createEditPolicyComponent.openCreateEditRule(true); } @@ -197,6 +206,23 @@ export class ReplicationComponent implements OnInit { this.totalCount = response.metadata.xTotalCount; this.jobs = response.data; + if (!this.timerDelay) { + this.timerDelay = Observable.timer(10000, 10000).subscribe(() => { + let count: number = 0; + this.jobs.forEach((job) => { + if ((job.status === 'pending') || (job.status === 'running') || (job.status === 'retrying')) { + count ++; + } + }); + if (count > 0) { + this.clrLoadJobs(this.currentState); + }else { + this.timerDelay.unsubscribe(); + this.timerDelay = null; + } + }); + } + //Do filtering and sorting this.jobs = doFiltering(this.jobs, state); this.jobs = doSorting(this.jobs, state); diff --git a/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.spec.ts b/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.spec.ts index 8d16be171..c54e9ad0e 100644 --- a/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.spec.ts +++ b/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.spec.ts @@ -75,6 +75,7 @@ describe('RepositoryComponentStackview (inline template)', () => { { "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", "name": "1.11.5", + "size": "2049", "architecture": "amd64", "os": "linux", "docker_version": "1.12.3", diff --git a/src/ui_ng/lib/src/service/interface.ts b/src/ui_ng/lib/src/service/interface.ts index efe104cc4..a1de71e8a 100644 --- a/src/ui_ng/lib/src/service/interface.ts +++ b/src/ui_ng/lib/src/service/interface.ts @@ -51,6 +51,7 @@ export interface Repository { export interface Tag extends Base { digest: string; name: string; + size: string; architecture: string; os: string; docker_version: string; diff --git a/src/ui_ng/lib/src/service/tag.service.spec.ts b/src/ui_ng/lib/src/service/tag.service.spec.ts index 9ccd122e5..3cdbb4cd7 100644 --- a/src/ui_ng/lib/src/service/tag.service.spec.ts +++ b/src/ui_ng/lib/src/service/tag.service.spec.ts @@ -14,6 +14,7 @@ describe('TagService', () => { { "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", "name": "1.11.5", + "size": "2049", "architecture": "amd64", "os": "linux", "docker_version": "1.12.3", diff --git a/src/ui_ng/lib/src/tag/tag-detail.component.spec.ts b/src/ui_ng/lib/src/tag/tag-detail.component.spec.ts index 2073c2164..5c5328fdc 100644 --- a/src/ui_ng/lib/src/tag/tag-detail.component.spec.ts +++ b/src/ui_ng/lib/src/tag/tag-detail.component.spec.ts @@ -43,6 +43,7 @@ describe('TagDetailComponent (inline template)', () => { let mockTag: Tag = { "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", "name": "nginx", + "size": "2049", "architecture": "amd64", "os": "linux", "docker_version": "1.12.3", diff --git a/src/ui_ng/lib/src/tag/tag-detail.component.ts b/src/ui_ng/lib/src/tag/tag-detail.component.ts index cfcf3281a..cce0e8c62 100644 --- a/src/ui_ng/lib/src/tag/tag-detail.component.ts +++ b/src/ui_ng/lib/src/tag/tag-detail.component.ts @@ -24,6 +24,7 @@ export class TagDetailComponent implements OnInit { @Input() repositoryId: string; tagDetails: Tag = { name: "--", + size: "--", author: "--", created: new Date(), architecture: "--", diff --git a/src/ui_ng/lib/src/tag/tag.component.css.ts b/src/ui_ng/lib/src/tag/tag.component.css.ts index 80335b50b..e969fbe86 100644 --- a/src/ui_ng/lib/src/tag/tag.component.css.ts +++ b/src/ui_ng/lib/src/tag/tag.component.css.ts @@ -43,4 +43,9 @@ export const TAG_STYLE = ` color: red; margin-right: 6px; } + +:host >>> .datagrid clr-dg-column { + min-width: 80px; +} + `; \ No newline at end of file diff --git a/src/ui_ng/lib/src/tag/tag.component.html.ts b/src/ui_ng/lib/src/tag/tag.component.html.ts index c5481cbda..6b8e07eb6 100644 --- a/src/ui_ng/lib/src/tag/tag.component.html.ts +++ b/src/ui_ng/lib/src/tag/tag.component.html.ts @@ -16,14 +16,13 @@ export const TAG_TEMPLATE = `

{{repoName}}

{{'REPOSITORY.TAG' | translate}} + {{'REPOSITORY.SIZE' | translate}} {{'REPOSITORY.PULL_COMMAND' | translate}} {{'VULNERABILITY.SINGULAR' | translate}} {{'REPOSITORY.SIGNED' | translate}} {{'REPOSITORY.AUTHOR' | translate}} {{'REPOSITORY.CREATED' | translate}} {{'REPOSITORY.DOCKER_VERSION' | translate}} - {{'REPOSITORY.ARCHITECTURE' | translate}} - {{'REPOSITORY.OS' | translate}} {{'TGA.PLACEHOLDER' | translate }} @@ -35,6 +34,7 @@ export const TAG_TEMPLATE = ` {{t.name}} {{t.name}} + {{t.size}} docker pull {{registryUrl}}/{{repoName}}:{{t.name}} @@ -50,8 +50,6 @@ export const TAG_TEMPLATE = ` {{t.author}} {{t.created | date: 'short'}} {{t.docker_version}} - {{t.architecture}} - {{t.os}} {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}} diff --git a/src/ui_ng/lib/src/tag/tag.component.spec.ts b/src/ui_ng/lib/src/tag/tag.component.spec.ts index 2f14b4824..2dfb306c1 100644 --- a/src/ui_ng/lib/src/tag/tag.component.spec.ts +++ b/src/ui_ng/lib/src/tag/tag.component.spec.ts @@ -29,6 +29,7 @@ describe('TagComponent (inline template)', () => { { "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", "name": "1.11.5", + "size": "2049", "architecture": "amd64", "os": "linux", "docker_version": "1.12.3", diff --git a/src/ui_ng/lib/src/tag/tag.component.ts b/src/ui_ng/lib/src/tag/tag.component.ts index a056e6759..663c31e2a 100644 --- a/src/ui_ng/lib/src/tag/tag.component.ts +++ b/src/ui_ng/lib/src/tag/tag.component.ts @@ -159,6 +159,9 @@ export class TagComponent implements OnInit { if (t.signature !== null) { signatures.push(t.name); } + + //size + t.size = this.sizeTransform(t.size); }); this.tags = items; let signedName: {[key: string]: string[]} = {}; @@ -177,6 +180,19 @@ export class TagComponent implements OnInit { setTimeout(() => clearInterval(hnd), 5000); } + sizeTransform(tagSize: string): string { + let size: number = Number.parseInt(tagSize); + if (Math.pow(1024, 1) <= size && size < Math.pow(1024, 2)) { + return (size / Math.pow(1024, 1)).toFixed(2) + 'KB'; + } else if (Math.pow(1024, 2) <= size && size < Math.pow(1024, 3)) { + return (size / Math.pow(1024, 2)).toFixed(2) + 'MB'; + } else if (Math.pow(1024, 3) <= size && size < Math.pow(1024, 4)) { + return (size / Math.pow(1024, 3)).toFixed(2) + 'MB'; + } else { + return size + 'B'; + } + } + deleteTag(tag: Tag) { if (tag) { let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons; diff --git a/src/ui_ng/package.json b/src/ui_ng/package.json index b9b228e80..7a921074c 100644 --- a/src/ui_ng/package.json +++ b/src/ui_ng/package.json @@ -31,7 +31,7 @@ "clarity-icons": "^0.9.8", "clarity-ui": "^0.9.8", "core-js": "^2.4.1", - "harbor-ui": "0.4.60", + "harbor-ui": "0.4.71", "intl": "^1.2.5", "mutationobserver-shim": "^0.3.2", "ngx-cookie": "^1.0.0", diff --git a/src/ui_ng/src/app/project/member/add-member/add-member.component.css b/src/ui_ng/src/app/project/member/add-member/add-member.component.css index 0bcac82e9..8bf7f2952 100644 --- a/src/ui_ng/src/app/project/member/add-member/add-member.component.css +++ b/src/ui_ng/src/app/project/member/add-member/add-member.component.css @@ -1,4 +1,26 @@ .form-group-label-override { font-size: 14px; font-weight: 400; +} +.selectBox{ + position: absolute; + width: 100%; + height: auto; + border: 1px solid #ccc; + background-color: white; + border: 1px solid rgba(0,0,0,.15); + border-right-width: 2px; + border-bottom-width: 2px; + border-radius: 6px; + box-shadow: 0 5px 10px rgba(0,0,0,.2); + z-index: 100; +} +.selectBox ul li{ + list-style: none; + padding: 3px 20px +} +.selectBox ul li:hover{ + color: #262626; + background-image: linear-gradient(180deg,#f5f5f5 0,#e8e8e8); + background-repeat: repeat-x; } \ No newline at end of file diff --git a/src/ui_ng/src/app/project/member/add-member/add-member.component.html b/src/ui_ng/src/app/project/member/add-member/add-member.component.html index 7bc567e28..c754a366b 100644 --- a/src/ui_ng/src/app/project/member/add-member/add-member.component.html +++ b/src/ui_ng/src/app/project/member/add-member/add-member.component.html @@ -6,16 +6,22 @@
-
diff --git a/src/ui_ng/src/app/project/member/add-member/add-member.component.ts b/src/ui_ng/src/app/project/member/add-member/add-member.component.ts index e62b6883f..25c007a8d 100644 --- a/src/ui_ng/src/app/project/member/add-member/add-member.component.ts +++ b/src/ui_ng/src/app/project/member/add-member/add-member.component.ts @@ -25,6 +25,7 @@ import { Response } from '@angular/http'; import { NgForm } from '@angular/forms'; import { MemberService } from '../member.service'; +import { UserService } from '../../../user/user.service'; import { MessageHandlerService } from '../../../shared/message-handler/message-handler.service'; import { InlineAlertComponent } from '../../../shared/inline-alert/inline-alert.component'; @@ -36,11 +37,13 @@ import { Member } from '../member'; import { Subject } from 'rxjs/Subject'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; +import {User} from "../../../user/user"; @Component({ selector: 'add-member', templateUrl: 'add-member.component.html', styleUrls: ['add-member.component.css'], + providers: [UserService], changeDetection: ChangeDetectionStrategy.OnPush }) export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy { @@ -69,13 +72,21 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy { memberTooltip: string = 'MEMBER.USERNAME_IS_REQUIRED'; nameChecker: Subject = new Subject(); checkOnGoing: boolean = false; + selectUserName: string[] = []; + userLists: User[]; constructor(private memberService: MemberService, + private userService: UserService, private messageHandlerService: MessageHandlerService, private translateService: TranslateService, private ref: ChangeDetectorRef) { } ngOnInit(): void { + this.userService.getUsers() + .then(users => { + this.userLists = users; + }); + this.nameChecker .debounceTime(500) .distinctUntilChanged() @@ -97,6 +108,20 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy { .catch(error => { this.checkOnGoing = false; }); + //username autocomplete + if (this.userLists.length) { + this.selectUserName = []; + this.userLists.filter(data => { + if (data.username.startsWith(cont.value)) { + if (this.selectUserName.length < 10) { + this.selectUserName.push(data.username); + } + } + }); + setTimeout(() => { + setInterval(() => this.ref.markForCheck(), 100); + }, 1000); + } } else { this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED'; } @@ -148,6 +173,11 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy { }, 1000); } + selectedName(username: string) { + this.member.username = username; + this.selectUserName = []; + } + onCancel() { if (this.hasChanged) { this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' }); @@ -157,6 +187,9 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy { } } + leaveInput() { + this.selectUserName = []; + } ngAfterViewChecked(): void { if (this.memberForm !== this.currentForm) { this.memberForm = this.currentForm; @@ -189,6 +222,7 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy { this.member.username = ''; this.isMemberNameValid = true; this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED'; + this.selectUserName = []; } handleValidation(): void { diff --git a/src/ui_ng/src/i18n/lang/en-us-lang.json b/src/ui_ng/src/i18n/lang/en-us-lang.json index 0968db2bb..dfecf5a1a 100644 --- a/src/ui_ng/src/i18n/lang/en-us-lang.json +++ b/src/ui_ng/src/i18n/lang/en-us-lang.json @@ -215,6 +215,8 @@ "FILTER_JOBS_PLACEHOLDER": "Filter Jobs", "DELETION_TITLE": "Confirm Rule Deletion", "DELETION_SUMMARY": "Do you want to delete rule {{param}}?", + "DELETION_TITLE_FAILURE": "failed to delete Rule Deletion", + "DELETION_SUMMARY_FAILURE": "{{param}} have pending/running/retrying status", "FILTER_TARGETS_PLACEHOLDER": "Filter Endpoints", "DELETION_TITLE_TARGET": "Confirm Endpoint Deletion", "DELETION_SUMMARY_TARGET": "Do you want to delete the endpoint {{param}}?", @@ -330,6 +332,7 @@ "DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted.\nDelete from Notary via this command:\n{{param}}", "FILTER_FOR_REPOSITORIES": "Filter Repositories", "TAG": "Tag", + "SIZE": "Size", "SIGNED": "Signed", "AUTHOR": "Author", "CREATED": "Creation Time", diff --git a/src/ui_ng/src/i18n/lang/es-es-lang.json b/src/ui_ng/src/i18n/lang/es-es-lang.json index d78a2d73c..50e93e292 100644 --- a/src/ui_ng/src/i18n/lang/es-es-lang.json +++ b/src/ui_ng/src/i18n/lang/es-es-lang.json @@ -215,6 +215,8 @@ "FILTER_JOBS_PLACEHOLDER": "Filtrar Trabajos", "DELETION_TITLE": "Confirmar Eliminación de Regla", "DELETION_SUMMARY": "¿Quiere eliminar la regla {{param}}?", + "DELETION_TITLE_FAILURE": "failed to delete Rule Deletion", + "DELETION_SUMMARY_FAILURE": "{{param}} have pending/running/retrying status", "FILTER_TARGETS_PLACEHOLDER": "Filtrar Endpoints", "DELETION_TITLE_TARGET": "Confirmar Eliminación de Endpoint", "DELETION_SUMMARY_TARGET": "¿Quiere eliminar el endpoint {{param}}?", @@ -331,6 +333,7 @@ "DELETION_SUMMARY_TAG_DENIED": "La etiqueta debe ser eliminada de la Notaría antes de eliminarla.\nEliminarla de la Notaría con este comando:\n{{param}}", "FILTER_FOR_REPOSITORIES": "Filtrar Repositorios", "TAG": "Etiqueta", + "SIZE": "Size", "SIGNED": "Firmada", "AUTHOR": "Autor", "CREATED": "Fecha de creación", diff --git a/src/ui_ng/src/i18n/lang/zh-cn-lang.json b/src/ui_ng/src/i18n/lang/zh-cn-lang.json index 00bb9f2f6..c03a41050 100644 --- a/src/ui_ng/src/i18n/lang/zh-cn-lang.json +++ b/src/ui_ng/src/i18n/lang/zh-cn-lang.json @@ -215,6 +215,8 @@ "FILTER_JOBS_PLACEHOLDER": "过滤任务", "DELETION_TITLE": "删除规则确认", "DELETION_SUMMARY": "确认删除规则 {{param}}?", + "DELETION_TITLE_FAILURE": "规则确认删除失败", + "DELETION_SUMMARY_FAILURE": "{{param}} 有 pending/running/retrying 状态,不能删除", "FILTER_TARGETS_PLACEHOLDER": "过滤目标", "DELETION_TITLE_TARGET": "删除目标确认", "DELETION_SUMMARY_TARGET": "确认删除目标 {{param}}?", @@ -330,6 +332,7 @@ "DELETION_SUMMARY_TAG_DENIED": "要删除此镜像标签必须首先从Notary中删除。\n请执行如下Notary命令删除:\n{{param}}", "FILTER_FOR_REPOSITORIES": "过滤镜像仓库", "TAG": "标签", + "SIZE": "大小", "SIGNED": "已签名", "AUTHOR": "作者", "CREATED": "创建时间", From fb690a972f1cc6ac1d749109fdddf9dbf31e85fb Mon Sep 17 00:00:00 2001 From: yixingj Date: Fri, 22 Sep 2017 10:46:02 +0800 Subject: [PATCH 15/19] Make Harbor database configurable --- make/common/templates/adminserver/env | 6 +++--- make/harbor.cfg | 9 +++++++++ make/prepare | 6 ++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/make/common/templates/adminserver/env b/make/common/templates/adminserver/env index 53b6244aa..0bdb92c49 100644 --- a/make/common/templates/adminserver/env +++ b/make/common/templates/adminserver/env @@ -11,9 +11,9 @@ LDAP_UID=$ldap_uid LDAP_SCOPE=$ldap_scope LDAP_TIMEOUT=$ldap_timeout DATABASE_TYPE=mysql -MYSQL_HOST=mysql -MYSQL_PORT=3306 -MYSQL_USR=root +MYSQL_HOST=$db_host +MYSQL_PORT=$db_port +MYSQL_USR=$db_user MYSQL_PWD=$db_password MYSQL_DATABASE=registry REGISTRY_URL=http://registry:5000 diff --git a/make/harbor.cfg b/make/harbor.cfg index 93720ea09..dab57ba2b 100644 --- a/make/harbor.cfg +++ b/make/harbor.cfg @@ -11,6 +11,15 @@ ui_url_protocol = http #The password for the root user of mysql db, change this before any production use. db_password = root123 +#the address of the mysql database. Don't change in standalone model +db_host = mysql + +#The port of mysql database host +db_port = 3306 + +#The user name of mysql database +db_user = root + #Maximum number of job workers in job service max_job_workers = 3 diff --git a/make/prepare b/make/prepare index 7cde72116..c15fc2035 100755 --- a/make/prepare +++ b/make/prepare @@ -139,6 +139,9 @@ ldap_uid = rcp.get("configuration", "ldap_uid") ldap_scope = rcp.get("configuration", "ldap_scope") ldap_timeout = rcp.get("configuration", "ldap_timeout") db_password = rcp.get("configuration", "db_password") +db_host = rcp.get("configuration", "db_host") +db_port = rcp.get("configuration", "db_port") +db_user = rcp.get("configuration", "db_user") self_registration = rcp.get("configuration", "self_registration") if protocol == "https": cert_path = rcp.get("configuration", "ssl_cert") @@ -210,6 +213,9 @@ render(os.path.join(templates_dir, "adminserver", "env"), ldap_scope=ldap_scope, ldap_timeout=ldap_timeout, db_password=db_password, + db_host=db_host, + db_port=db_port, + db_user=db_user, email_host=email_host, email_port=email_port, email_usr=email_usr, From 357004fbf11ff42aa65c9aa0bbe30fa8c394bb0b Mon Sep 17 00:00:00 2001 From: yixingj Date: Fri, 22 Sep 2017 10:46:02 +0800 Subject: [PATCH 16/19] Make Harbor database configurable --- make/common/templates/adminserver/env | 6 +++--- make/harbor.cfg | 12 +++++++++++- make/prepare | 6 ++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/make/common/templates/adminserver/env b/make/common/templates/adminserver/env index 53b6244aa..0bdb92c49 100644 --- a/make/common/templates/adminserver/env +++ b/make/common/templates/adminserver/env @@ -11,9 +11,9 @@ LDAP_UID=$ldap_uid LDAP_SCOPE=$ldap_scope LDAP_TIMEOUT=$ldap_timeout DATABASE_TYPE=mysql -MYSQL_HOST=mysql -MYSQL_PORT=3306 -MYSQL_USR=root +MYSQL_HOST=$db_host +MYSQL_PORT=$db_port +MYSQL_USR=$db_user MYSQL_PWD=$db_password MYSQL_DATABASE=registry REGISTRY_URL=http://registry:5000 diff --git a/make/harbor.cfg b/make/harbor.cfg index 93720ea09..8ad8e1853 100644 --- a/make/harbor.cfg +++ b/make/harbor.cfg @@ -101,6 +101,16 @@ project_creation_restriction = everyone #Determine whether the job service should verify the ssl cert when it connects to a remote registry. #Set this flag to off when the remote registry uses a self-signed or untrusted certificate. verify_remote_cert = on + +#The follow configurations are for Harbor HA mode only + +#the address of the mysql database. +db_host = mysql + +#The port of mysql database host +db_port = 3306 + +#The user name of mysql database +db_user = root #************************END INITIAL PROPERTIES************************ ############# - diff --git a/make/prepare b/make/prepare index 7cde72116..c15fc2035 100755 --- a/make/prepare +++ b/make/prepare @@ -139,6 +139,9 @@ ldap_uid = rcp.get("configuration", "ldap_uid") ldap_scope = rcp.get("configuration", "ldap_scope") ldap_timeout = rcp.get("configuration", "ldap_timeout") db_password = rcp.get("configuration", "db_password") +db_host = rcp.get("configuration", "db_host") +db_port = rcp.get("configuration", "db_port") +db_user = rcp.get("configuration", "db_user") self_registration = rcp.get("configuration", "self_registration") if protocol == "https": cert_path = rcp.get("configuration", "ssl_cert") @@ -210,6 +213,9 @@ render(os.path.join(templates_dir, "adminserver", "env"), ldap_scope=ldap_scope, ldap_timeout=ldap_timeout, db_password=db_password, + db_host=db_host, + db_port=db_port, + db_user=db_user, email_host=email_host, email_port=email_port, email_usr=email_usr, From 2a53c64c5949f0010bd89690b9b15c8a0f4e863e Mon Sep 17 00:00:00 2001 From: yixingj Date: Mon, 25 Sep 2017 13:33:45 +0800 Subject: [PATCH 17/19] move database configuration to HA only seciton --- make/harbor.cfg | 9 --------- 1 file changed, 9 deletions(-) diff --git a/make/harbor.cfg b/make/harbor.cfg index 57c716062..8ad8e1853 100644 --- a/make/harbor.cfg +++ b/make/harbor.cfg @@ -11,15 +11,6 @@ ui_url_protocol = http #The password for the root user of mysql db, change this before any production use. db_password = root123 -#the address of the mysql database. Don't change in standalone model -db_host = mysql - -#The port of mysql database host -db_port = 3306 - -#The user name of mysql database -db_user = root - #Maximum number of job workers in job service max_job_workers = 3 From 9e0961b3f4184300bed1d3f8b943ff7ddbf428b2 Mon Sep 17 00:00:00 2001 From: "Deng, Qian" Date: Mon, 25 Sep 2017 14:16:41 +0800 Subject: [PATCH 18/19] fix_tag_link_and_scanning_state --- src/ui_ng/lib/src/tag/tag.component.html.ts | 4 ++-- src/ui_ng/lib/src/tag/tag.component.ts | 9 ++++++++- .../src/vulnerability-scanning/result-tip.component.ts | 8 +++++--- src/ui_ng/package.json | 2 +- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/ui_ng/lib/src/tag/tag.component.html.ts b/src/ui_ng/lib/src/tag/tag.component.html.ts index 6b8e07eb6..eb77c920c 100644 --- a/src/ui_ng/lib/src/tag/tag.component.html.ts +++ b/src/ui_ng/lib/src/tag/tag.component.html.ts @@ -30,7 +30,7 @@ export const TAG_TEMPLATE = ` - + {{t.name}} {{t.name}} @@ -56,4 +56,4 @@ export const TAG_TEMPLATE = ` {{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}     -`; \ No newline at end of file +`; diff --git a/src/ui_ng/lib/src/tag/tag.component.ts b/src/ui_ng/lib/src/tag/tag.component.ts index 663c31e2a..f934cf120 100644 --- a/src/ui_ng/lib/src/tag/tag.component.ts +++ b/src/ui_ng/lib/src/tag/tag.component.ts @@ -253,7 +253,7 @@ export class TagComponent implements OnInit { } } - //Get vulnerability scanning status + //Get vulnerability scanning status scanStatus(t: Tag): string { if (t && t.scan_overview && t.scan_overview.scan_status) { return t.scan_overview.scan_status; @@ -262,6 +262,13 @@ export class TagComponent implements OnInit { return VULNERABILITY_SCAN_STATUS.unknown; } + existObservablePackage(t: Tag): boolean { + return t.scan_overview && + t.scan_overview.components && + t.scan_overview.components.total && + t.scan_overview.components.total > 0 ? true : false; + } + //Whether show the 'scan now' menu canScanNow(t: Tag): boolean { if (!this.withClair) { return false; } diff --git a/src/ui_ng/lib/src/vulnerability-scanning/result-tip.component.ts b/src/ui_ng/lib/src/vulnerability-scanning/result-tip.component.ts index 0ada90c09..f729bc1c3 100644 --- a/src/ui_ng/lib/src/vulnerability-scanning/result-tip.component.ts +++ b/src/ui_ng/lib/src/vulnerability-scanning/result-tip.component.ts @@ -45,7 +45,9 @@ export class ResultTipComponent implements OnInit { level = VulnerabilitySeverity.LOW; }else if (this._unknownCount && this._unknownCount >= 1) { level = VulnerabilitySeverity.UNKNOWN; - }else { + }else if (this.totalPackages == 0) { + level = VulnerabilitySeverity.UNKNOWN; + } else { level = VulnerabilitySeverity.NONE; } return level; @@ -105,8 +107,8 @@ export class ResultTipComponent implements OnInit { let m: number = this.totalPackages; if (m === 0) { - //If no packages recognized, then show green bar - if (severity === VulnerabilitySeverity.NONE) { + //If no packages recognized, then show grey + if (severity === VulnerabilitySeverity.UNKNOWN) { return MAX_TIP_WIDTH + 'px'; } else { return 0 + 'px'; diff --git a/src/ui_ng/package.json b/src/ui_ng/package.json index 7a921074c..9432f633b 100644 --- a/src/ui_ng/package.json +++ b/src/ui_ng/package.json @@ -31,7 +31,7 @@ "clarity-icons": "^0.9.8", "clarity-ui": "^0.9.8", "core-js": "^2.4.1", - "harbor-ui": "0.4.71", + "harbor-ui": "0.4.72", "intl": "^1.2.5", "mutationobserver-shim": "^0.3.2", "ngx-cookie": "^1.0.0", From e79334a445255dce9d4266751c82ccfb65f5ec73 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Tue, 26 Sep 2017 16:41:08 +0800 Subject: [PATCH 19/19] Add interfaces to implement project level policy (#3271) * add interfaces to implement project level policy --- src/common/models/pro_meta.go | 39 +++++ src/common/models/project.go | 43 +++-- src/common/security/admiral/context.go | 4 +- src/common/security/local/context.go | 4 +- src/common/security/local/context_test.go | 3 +- src/common/utils/utils.go | 9 - src/common/utils/utils_test.go | 8 - src/ui/api/base.go | 2 +- src/ui/api/project.go | 22 +-- src/ui/api/repository.go | 12 +- src/ui/api/search.go | 3 +- src/ui/api/statistic.go | 14 +- src/ui/api/utils.go | 8 +- src/ui/config/config.go | 63 ++++--- src/ui/filter/security.go | 35 ++-- src/ui/filter/security_test.go | 4 +- src/ui/promgr/metamgr/metamgr.go | 65 ++++++++ src/ui/promgr/metamgr/metamgr_test.go | 17 ++ .../promgr/pmsdriver/admiral/admiral_test.go | 2 +- src/ui/promgr/pmsdriver/driver.go | 36 ++++ src/ui/promgr/pmsdriver/local/local.go | 107 ++++++------ src/ui/promgr/pmsdriver/local/local_test.go | 112 ++----------- src/ui/promgr/promgr.go | 156 ++++++++++++++++-- src/ui/promgr/promgr_test.go | 120 ++++++++++++++ src/ui/proxy/interceptor_test.go | 9 +- src/ui/proxy/interceptors.go | 4 +- src/ui/service/token/authutils.go | 2 +- src/ui/service/token/creator.go | 8 +- 28 files changed, 614 insertions(+), 297 deletions(-) create mode 100644 src/common/models/pro_meta.go create mode 100644 src/ui/promgr/metamgr/metamgr.go create mode 100644 src/ui/promgr/metamgr/metamgr_test.go create mode 100644 src/ui/promgr/pmsdriver/driver.go create mode 100644 src/ui/promgr/promgr_test.go diff --git a/src/common/models/pro_meta.go b/src/common/models/pro_meta.go new file mode 100644 index 000000000..4e1b82761 --- /dev/null +++ b/src/common/models/pro_meta.go @@ -0,0 +1,39 @@ +// Copyright (c) 2017 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 + +import ( + "time" +) + +// keys of project metadata +const ( + ProMetaPublic = "public" + ProMetaEnableContentTrust = "enable_content_trust" + ProMetaPreventVul = "prevent_vul" + ProMetaSeverity = "severity" + ProMetaAutoScan = "auto_scan" +) + +// ProjectMetadata holds the metadata of a project. +type ProjectMetadata struct { + ID int64 `orm:"pk;auto;column(id)" json:"id"` + ProjectID int64 `orm:"column(project_id)" json:"project_id"` + Name string `orm:"column(name)" json:"name"` + Value string `orm:"column(value)" json:"value"` + CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"` + UpdateTime time.Time `orm:"column(update_time)" json:"update_time"` + Deleted int `orm:"column(deleted)" json:"deleted"` +} diff --git a/src/common/models/project.go b/src/common/models/project.go index 0039b241f..a51f4c4eb 100644 --- a/src/common/models/project.go +++ b/src/common/models/project.go @@ -21,24 +21,25 @@ import ( // Project holds the details of a project. // TODO remove useless attrs type Project struct { - ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"` - OwnerID int `orm:"column(owner_id)" json:"owner_id"` - Name string `orm:"column(name)" json:"name"` - CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"` - CreationTimeStr string `orm:"-" json:"creation_time_str"` - Deleted int `orm:"column(deleted)" json:"deleted"` - //UserID int `json:"UserId"` - OwnerName string `orm:"-" json:"owner_name"` - Public int `orm:"column(public)" json:"public"` - //This field does not have correspondent column in DB, this is just for UI to disable button - Togglable bool `orm:"-"` - UpdateTime time.Time `orm:"update_time" json:"update_time"` - Role int `orm:"-" json:"current_user_role_id"` - RepoCount int `orm:"-" json:"repo_count"` - EnableContentTrust bool `orm:"-" json:"enable_content_trust"` - PreventVulnerableImagesFromRunning bool `orm:"-" json:"prevent_vulnerable_images_from_running"` - PreventVulnerableImagesFromRunningSeverity string `orm:"-" json:"prevent_vulnerable_images_from_running_severity"` - AutomaticallyScanImagesOnPush bool `orm:"-" json:"automatically_scan_images_on_push"` + ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"` + OwnerID int `orm:"column(owner_id)" json:"owner_id"` + Name string `orm:"column(name)" json:"name"` + CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"` + UpdateTime time.Time `orm:"update_time" json:"update_time"` + Deleted int `orm:"column(deleted)" json:"deleted"` + CreationTimeStr string `orm:"-" json:"creation_time_str"` + OwnerName string `orm:"-" json:"owner_name"` + Togglable bool `orm:"-"` + Role int `orm:"-" json:"current_user_role_id"` + RepoCount int `orm:"-" json:"repo_count"` + Metadata map[string]interface{} `orm:"-" json:"metadata"` + + // TODO remove + Public int `orm:"column(public)" json:"public"` + EnableContentTrust bool `orm:"-" json:"enable_content_trust"` + PreventVulnerableImagesFromRunning bool `orm:"-" json:"prevent_vulnerable_images_from_running"` + PreventVulnerableImagesFromRunningSeverity string `orm:"-" json:"prevent_vulnerable_images_from_running_severity"` + AutomaticallyScanImagesOnPush bool `orm:"-" json:"automatically_scan_images_on_push"` } // ProjectSorter holds an array of projects @@ -109,3 +110,9 @@ type ProjectRequest struct { PreventVulnerableImagesFromRunningSeverity string `json:"prevent_vulnerable_images_from_running_severity"` AutomaticallyScanImagesOnPush bool `json:"automatically_scan_images_on_push"` } + +// ProjectQueryResult ... +type ProjectQueryResult struct { + Total int64 + Projects []*Project +} diff --git a/src/common/security/admiral/context.go b/src/common/security/admiral/context.go index 85e20a4de..56816139f 100644 --- a/src/common/security/admiral/context.go +++ b/src/common/security/admiral/context.go @@ -26,11 +26,11 @@ import ( // auth context and project manager type SecurityContext struct { ctx *authcontext.AuthContext - pm promgr.ProMgr + pm promgr.ProjectManager } // NewSecurityContext ... -func NewSecurityContext(ctx *authcontext.AuthContext, pm promgr.ProMgr) *SecurityContext { +func NewSecurityContext(ctx *authcontext.AuthContext, pm promgr.ProjectManager) *SecurityContext { return &SecurityContext{ ctx: ctx, pm: pm, diff --git a/src/common/security/local/context.go b/src/common/security/local/context.go index 22ab07080..8111423f2 100644 --- a/src/common/security/local/context.go +++ b/src/common/security/local/context.go @@ -25,11 +25,11 @@ import ( // SecurityContext implements security.Context interface based on database type SecurityContext struct { user *models.User - pm promgr.ProMgr + pm promgr.ProjectManager } // NewSecurityContext ... -func NewSecurityContext(user *models.User, pm promgr.ProMgr) *SecurityContext { +func NewSecurityContext(user *models.User, pm promgr.ProjectManager) *SecurityContext { return &SecurityContext{ user: user, pm: pm, diff --git a/src/common/security/local/context_test.go b/src/common/security/local/context_test.go index 7e46a24cd..d3cb0724b 100644 --- a/src/common/security/local/context_test.go +++ b/src/common/security/local/context_test.go @@ -25,6 +25,7 @@ import ( "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils/log" + "github.com/vmware/harbor/src/ui/promgr" "github.com/vmware/harbor/src/ui/promgr/pmsdriver/local" ) @@ -47,7 +48,7 @@ var ( Email: "guestUser@vmware.com", } - pm = &local.ProjectManager{} + pm = promgr.NewDefaultProjectManager(local.NewDriver(), true) ) func TestMain(m *testing.M) { diff --git a/src/common/utils/utils.go b/src/common/utils/utils.go index 25e1b16fe..01be8ac45 100644 --- a/src/common/utils/utils.go +++ b/src/common/utils/utils.go @@ -159,19 +159,10 @@ func ParseProjectIDOrName(value interface{}) (int64, string, error) { case int: i := value.(int) id = int64(i) - if id == 0 { - return 0, "", fmt.Errorf("invalid ID: 0") - } case int64: id = value.(int64) - if id == 0 { - return 0, "", fmt.Errorf("invalid ID: 0") - } case string: name = value.(string) - if len(name) == 0 { - return 0, "", fmt.Errorf("empty name") - } default: return 0, "", fmt.Errorf("unsupported type") } diff --git a/src/common/utils/utils_test.go b/src/common/utils/utils_test.go index 82b7b5e9e..24539fc54 100644 --- a/src/common/utils/utils_test.go +++ b/src/common/utils/utils_test.go @@ -217,14 +217,6 @@ func TestParseHarborIDOrName(t *testing.T) { id, name, err := ParseProjectIDOrName(nil) assert.NotNil(t, err) - // invalid ID - id, name, err = ParseProjectIDOrName(0) - assert.NotNil(t, err) - - // invalid name - id, name, err = ParseProjectIDOrName("") - assert.NotNil(t, err) - // valid int ID id, name, err = ParseProjectIDOrName(1) assert.Nil(t, err) diff --git a/src/ui/api/base.go b/src/ui/api/base.go index 5f567a355..4b40b3563 100644 --- a/src/ui/api/base.go +++ b/src/ui/api/base.go @@ -31,7 +31,7 @@ type BaseController struct { SecurityCtx security.Context // ProjectMgr is the project manager which abstracts the operations // related to projects - ProjectMgr promgr.ProMgr + ProjectMgr promgr.ProjectManager } const ( diff --git a/src/ui/api/project.go b/src/ui/api/project.go index 1b7a6f2a8..83f0f310c 100644 --- a/src/ui/api/project.go +++ b/src/ui/api/project.go @@ -109,7 +109,7 @@ func (p *ProjectAPI) Post() { return } - exist, err := p.ProjectMgr.Exist(pro.Name) + exist, err := p.ProjectMgr.Exists(pro.Name) if err != nil { p.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", pro.Name), err) @@ -325,19 +325,13 @@ func (p *ProjectAPI) List() { } } - total, err := p.ProjectMgr.GetTotal(query, base) + result, err := p.ProjectMgr.List(query, base) if err != nil { - p.ParseAndHandleError("failed to get total of projects", err) + p.ParseAndHandleError("failed to list projects", err) return } - projects, err := p.ProjectMgr.GetAll(query, base) - if err != nil { - p.ParseAndHandleError("failed to get projects", err) - return - } - - for _, project := range projects { + for _, project := range result.Projects { if p.SecurityCtx.IsAuthenticated() { roles := p.SecurityCtx.GetProjectRoles(project.ProjectID) if len(roles) != 0 { @@ -359,8 +353,8 @@ func (p *ProjectAPI) List() { project.RepoCount = len(repos) } - p.SetPaginationHeader(total, page, size) - p.Data["json"] = projects + p.SetPaginationHeader(result.Total, page, size) + p.Data["json"] = result.Projects p.ServeJSON() } @@ -385,7 +379,9 @@ func (p *ProjectAPI) ToggleProjectPublic() { if err := p.ProjectMgr.Update(p.project.ProjectID, &models.Project{ - Public: req.Public, + Metadata: map[string]interface{}{ + models.ProMetaPublic: req.Public, + }, }); err != nil { p.ParseAndHandleError(fmt.Sprintf("failed to update project %d", p.project.ProjectID), err) diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index ab0ac0ad8..7d01cd45e 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -84,7 +84,7 @@ func (ra *RepositoryAPI) Get() { return } - exist, err := ra.ProjectMgr.Exist(projectID) + exist, err := ra.ProjectMgr.Exists(projectID) if err != nil { ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %d", projectID), err) @@ -335,7 +335,7 @@ func (ra *RepositoryAPI) GetTags() { repoName := ra.GetString(":splat") projectName, _ := utils.ParseRepository(repoName) - exist, err := ra.ProjectMgr.Exist(projectName) + exist, err := ra.ProjectMgr.Exists(projectName) if err != nil { ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", projectName), err) @@ -478,7 +478,7 @@ func (ra *RepositoryAPI) GetManifests() { } projectName, _ := utils.ParseRepository(repoName) - exist, err := ra.ProjectMgr.Exist(projectName) + exist, err := ra.ProjectMgr.Exists(projectName) if err != nil { ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", projectName), err) @@ -610,7 +610,7 @@ func (ra *RepositoryAPI) GetSignatures() { repoName := ra.GetString(":splat") projectName, _ := utils.ParseRepository(repoName) - exist, err := ra.ProjectMgr.Exist(projectName) + exist, err := ra.ProjectMgr.Exists(projectName) if err != nil { ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", projectName), err) @@ -651,7 +651,7 @@ func (ra *RepositoryAPI) ScanImage() { repoName := ra.GetString(":splat") tag := ra.GetString(":tag") projectName, _ := utils.ParseRepository(repoName) - exist, err := ra.ProjectMgr.Exist(projectName) + exist, err := ra.ProjectMgr.Exists(projectName) if err != nil { ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", projectName), err) @@ -794,7 +794,7 @@ func getSignatures(username, repository string) (map[string][]notary.Target, err func (ra *RepositoryAPI) checkExistence(repository, tag string) (bool, string, error) { project, _ := utils.ParseRepository(repository) - exist, err := ra.ProjectMgr.Exist(project) + exist, err := ra.ProjectMgr.Exists(project) if err != nil { return false, "", err } diff --git a/src/ui/api/search.go b/src/ui/api/search.go index bf260edac..630c32b74 100644 --- a/src/ui/api/search.go +++ b/src/ui/api/search.go @@ -48,11 +48,12 @@ func (s *SearchAPI) Get() { var err error if isSysAdmin { - projects, err = s.ProjectMgr.GetAll(nil) + result, err := s.ProjectMgr.List(nil) if err != nil { s.ParseAndHandleError("failed to get projects", err) return } + projects = result.Projects } else { projects, err = s.ProjectMgr.GetPublic() if err != nil { diff --git a/src/ui/api/statistic.go b/src/ui/api/statistic.go index 7a2e9c57e..f2ab3283e 100644 --- a/src/ui/api/statistic.go +++ b/src/ui/api/statistic.go @@ -77,15 +77,15 @@ func (s *StatisticAPI) Get() { statistic[PubRC] = n if s.SecurityCtx.IsSysAdmin() { - n, err := s.ProjectMgr.GetTotal(nil) + result, err := s.ProjectMgr.List(nil) if err != nil { log.Errorf("failed to get total of projects: %v", err) s.CustomAbort(http.StatusInternalServerError, "") } - statistic[TPC] = n - statistic[PriPC] = n - statistic[PubPC] + statistic[TPC] = result.Total + statistic[PriPC] = result.Total - statistic[PubPC] - n, err = dao.GetTotalOfRepositories("") + n, err := dao.GetTotalOfRepositories("") if err != nil { log.Errorf("failed to get total of repositories: %v", err) s.CustomAbort(http.StatusInternalServerError, "") @@ -94,7 +94,7 @@ func (s *StatisticAPI) Get() { statistic[PriRC] = n - statistic[PubRC] } else { value := false - projects, err := s.ProjectMgr.GetAll(&models.ProjectQueryParam{ + result, err := s.ProjectMgr.List(&models.ProjectQueryParam{ Public: &value, Member: &models.MemberQuery{ Name: s.username, @@ -106,10 +106,10 @@ func (s *StatisticAPI) Get() { return } - statistic[PriPC] = (int64)(len(projects)) + statistic[PriPC] = result.Total ids := []int64{} - for _, p := range projects { + for _, p := range result.Projects { ids = append(ids, p.ProjectID) } diff --git a/src/ui/api/utils.go b/src/ui/api/utils.go index bf9013289..a0bb4fb39 100644 --- a/src/ui/api/utils.go +++ b/src/ui/api/utils.go @@ -166,7 +166,7 @@ func postReplicationAction(policyID int64, acton string) error { } // SyncRegistry syncs the repositories of registry with database. -func SyncRegistry(pm promgr.ProMgr) error { +func SyncRegistry(pm promgr.ProjectManager) error { log.Infof("Start syncing repositories from registry to DB... ") @@ -254,7 +254,7 @@ func catalog() ([]string, error) { } func diffRepos(reposInRegistry []string, reposInDB []string, - pm promgr.ProMgr) ([]string, []string, error) { + pm promgr.ProjectManager) ([]string, []string, error) { var needsAdd []string var needsDel []string @@ -359,9 +359,9 @@ func diffRepos(reposInRegistry []string, reposInDB []string, return needsAdd, needsDel, nil } -func projectExists(pm promgr.ProMgr, repository string) (bool, error) { +func projectExists(pm promgr.ProjectManager, repository string) (bool, error) { project, _ := utils.ParseRepository(repository) - return pm.Exist(project) + return pm.Exists(project) } func initRegistryClient() (r *registry.Registry, err error) { diff --git a/src/ui/config/config.go b/src/ui/config/config.go index f1207b73c..f9b021b76 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -15,7 +15,7 @@ package config import ( - "crypto/tls" + //"crypto/tls" "encoding/json" "fmt" "net/http" @@ -30,6 +30,7 @@ import ( "github.com/vmware/harbor/src/common/secret" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/ui/promgr" + "github.com/vmware/harbor/src/ui/promgr/pmsdriver" "github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral" "github.com/vmware/harbor/src/ui/promgr/pmsdriver/local" ) @@ -46,7 +47,7 @@ var ( // AdminserverClient is a client for adminserver AdminserverClient client.Client // GlobalProjectMgr is initialized based on the deploy mode - GlobalProjectMgr promgr.ProMgr + GlobalProjectMgr promgr.ProjectManager mg *comcfg.Manager keyProvider comcfg.KeyProvider // AdmiralClient is initialized only under integration deploy mode @@ -105,34 +106,41 @@ func initSecretStore() { } func initProjectManager() { - if !WithAdmiral() { + var driver pmsdriver.PMSDriver + if WithAdmiral() { + // TODO add support for admiral + /* + // integration with admiral + log.Info("initializing the project manager based on PMS...") + // TODO read ca/cert file and pass it to the TLS config + AdmiralClient = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } + + path := os.Getenv("SERVICE_TOKEN_FILE_PATH") + if len(path) == 0 { + path = defaultTokenFilePath + } + log.Infof("service token file path: %s", path) + TokenReader = &admiral.FileTokenReader{ + Path: path, + } + GlobalProjectMgr = admiral.NewProjectManager(AdmiralClient, + AdmiralEndpoint(), TokenReader) + */ + GlobalProjectMgr = nil + } else { // standalone - log.Info("initializing the project manager based on database...") - GlobalProjectMgr = &local.ProjectManager{} - return + log.Info("initializing the project manager based on local database...") + driver = local.NewDriver() + // TODO move the statement out of the else block when admiral driver is completed + GlobalProjectMgr = promgr.NewDefaultProjectManager(driver, true) } - // integration with admiral - log.Info("initializing the project manager based on PMS...") - // TODO read ca/cert file and pass it to the TLS config - AdmiralClient = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, - } - - path := os.Getenv("SERVICE_TOKEN_FILE_PATH") - if len(path) == 0 { - path = defaultTokenFilePath - } - log.Infof("service token file path: %s", path) - TokenReader = &admiral.FileTokenReader{ - Path: path, - } - GlobalProjectMgr = admiral.NewProjectManager(AdmiralClient, - AdmiralEndpoint(), TokenReader) } // Load configurations @@ -379,6 +387,7 @@ func AdmiralEndpoint() string { log.Errorf("Failed to get configuration, will return empty string as admiral's endpoint, error: %v", err) return "" } + if e, ok := cfg[common.AdmiralEndpoint].(string); !ok || e == "NA" { return "" } diff --git a/src/ui/filter/security.go b/src/ui/filter/security.go index 116298ce8..3f3d14d9f 100644 --- a/src/ui/filter/security.go +++ b/src/ui/filter/security.go @@ -33,7 +33,7 @@ import ( "github.com/vmware/harbor/src/ui/auth" "github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/promgr" - "github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral" + //"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral" ) type key string @@ -264,11 +264,16 @@ func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool { return false } - log.Debug("creating PMS project manager...") - pm := admiral.NewProjectManager(config.AdmiralClient, - config.AdmiralEndpoint(), &admiral.RawTokenReader{ - Token: token, - }) + /* + log.Debug("creating PMS project manager...") + pm := admiral.NewProjectManager(config.AdmiralClient, + config.AdmiralEndpoint(), &admiral.RawTokenReader{ + Token: token, + }) + */ + // TODO create the DefaultProjectManager with the real admiral PMSDriver + pm := promgr.NewDefaultProjectManager(nil, false) + log.Debug("creating admiral security context...") securCtx := admr.NewSecurityContext(authContext, pm) setSecurCtxAndPM(ctx.Request, securCtx, pm) @@ -283,12 +288,16 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool { log.Debug("user information is nil") var securCtx security.Context - var pm promgr.ProMgr + var pm promgr.ProjectManager if config.WithAdmiral() { // integration with admiral - log.Debug("creating PMS project manager...") - pm = admiral.NewProjectManager(config.AdmiralClient, - config.AdmiralEndpoint(), nil) + /* + log.Debug("creating PMS project manager...") + pm = admiral.NewProjectManager(config.AdmiralClient, + config.AdmiralEndpoint(), nil) + */ + // TODO create the DefaultProjectManager with the real admiral PMSDriver + pm = promgr.NewDefaultProjectManager(nil, false) log.Debug("creating admiral security context...") securCtx = admr.NewSecurityContext(nil, pm) } else { @@ -302,7 +311,7 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool { return true } -func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm promgr.ProMgr) { +func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm promgr.ProjectManager) { addToReqContext(req, securCtxKey, ctx) addToReqContext(req, pmKey, pm) } @@ -331,7 +340,7 @@ func GetSecurityContext(req *http.Request) (security.Context, error) { } // GetProjectManager tries to get project manager from request and returns it -func GetProjectManager(req *http.Request) (promgr.ProMgr, error) { +func GetProjectManager(req *http.Request) (promgr.ProjectManager, error) { if req == nil { return nil, fmt.Errorf("request is nil") } @@ -341,7 +350,7 @@ func GetProjectManager(req *http.Request) (promgr.ProMgr, error) { return nil, fmt.Errorf("the project manager got from request is nil") } - p, ok := pm.(promgr.ProMgr) + p, ok := pm.(promgr.ProjectManager) if !ok { return nil, fmt.Errorf("the variable got from request is not project manager type") } diff --git a/src/ui/filter/security_test.go b/src/ui/filter/security_test.go index 84acefcbd..c03ea6155 100644 --- a/src/ui/filter/security_test.go +++ b/src/ui/filter/security_test.go @@ -316,9 +316,9 @@ func TestGetProjectManager(t *testing.T) { req, err = http.NewRequest("", "", nil) assert.Nil(t, err) req = req.WithContext(context.WithValue(req.Context(), - pmKey, &driver_local.ProjectManager{})) + pmKey, promgr.NewDefaultProjectManager(driver_local.NewDriver(), true))) pm, err = GetProjectManager(req) assert.Nil(t, err) - _, ok := pm.(promgr.ProMgr) + _, ok := pm.(promgr.ProjectManager) assert.True(t, ok) } diff --git a/src/ui/promgr/metamgr/metamgr.go b/src/ui/promgr/metamgr/metamgr.go new file mode 100644 index 000000000..0b0cb6605 --- /dev/null +++ b/src/ui/promgr/metamgr/metamgr.go @@ -0,0 +1,65 @@ +// Copyright (c) 2017 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 metamgr + +import ( + "github.com/vmware/harbor/src/common/dao" + "github.com/vmware/harbor/src/common/models" +) + +// ProjectMetadataManaegr defines the operations that a project metadata manager should +// implement +type ProjectMetadataManaegr interface { + // Add metadatas for project specified by projectID + Add(projectID int64, meta map[string]interface{}) error + // Delete metadatas whose keys are specified in parameter meta, if it + // is absent, delete all + Delete(projecdtID int64, meta ...[]string) error + // Update metadatas + Update(projectID int64, meta map[string]interface{}) error + // Get metadatas whose keys are specified in parameter meta, if it is + // absent, get all + Get(projectID int64, meta ...[]string) (map[string]interface{}, error) +} + +type defaultProjectMetadataManaegr struct{} + +// NewDefaultProjectMetadataManager ... +func NewDefaultProjectMetadataManager() ProjectMetadataManaegr { + return &defaultProjectMetadataManaegr{} +} + +// TODO add implement +func (d *defaultProjectMetadataManaegr) Add(projectID int64, meta map[string]interface{}) error { + return nil +} + +func (d *defaultProjectMetadataManaegr) Delete(projectID int64, meta ...[]string) error { + return nil +} + +func (d *defaultProjectMetadataManaegr) Update(projectID int64, meta map[string]interface{}) error { + // TODO remove the logic + public, ok := meta[models.ProMetaPublic] + if ok { + return dao.ToggleProjectPublicity(projectID, public.(int)) + } + + return nil +} + +func (d *defaultProjectMetadataManaegr) Get(projectID int64, meta ...[]string) (map[string]interface{}, error) { + return nil, nil +} diff --git a/src/ui/promgr/metamgr/metamgr_test.go b/src/ui/promgr/metamgr/metamgr_test.go new file mode 100644 index 000000000..59ef6fc6d --- /dev/null +++ b/src/ui/promgr/metamgr/metamgr_test.go @@ -0,0 +1,17 @@ +// Copyright (c) 2017 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 metamgr + +// TODO add test cases diff --git a/src/ui/promgr/pmsdriver/admiral/admiral_test.go b/src/ui/promgr/pmsdriver/admiral/admiral_test.go index 2c37c2b90..e57ae139c 100644 --- a/src/ui/promgr/pmsdriver/admiral/admiral_test.go +++ b/src/ui/promgr/pmsdriver/admiral/admiral_test.go @@ -196,7 +196,7 @@ func TestGet(t *testing.T) { // get by invalid ID project, err := pm.Get(int64(0)) - assert.NotNil(t, err) + assert.Nil(t, err) assert.Nil(t, project) // get by invalid name diff --git a/src/ui/promgr/pmsdriver/driver.go b/src/ui/promgr/pmsdriver/driver.go new file mode 100644 index 000000000..def00fc54 --- /dev/null +++ b/src/ui/promgr/pmsdriver/driver.go @@ -0,0 +1,36 @@ +// Copyright (c) 2017 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 pmsdriver + +import ( + "github.com/vmware/harbor/src/common/models" +) + +// PMSDriver defines the operations that a project management service driver +// should implement +type PMSDriver interface { + // Get a project by ID or name + Get(projectIDOrName interface{}) (*models.Project, error) + // Create a project + Create(*models.Project) (int64, error) + // Delete a project by ID or name + Delete(projectIDOrName interface{}) error + // Update the properties of a project + Update(projectIDOrName interface{}, project *models.Project) error + // List lists projects according to the query conditions + // TODO remove base + List(query *models.ProjectQueryParam, + base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error) +} diff --git a/src/ui/promgr/pmsdriver/local/local.go b/src/ui/promgr/pmsdriver/local/local.go index 177a10c13..804913600 100644 --- a/src/ui/promgr/pmsdriver/local/local.go +++ b/src/ui/promgr/pmsdriver/local/local.go @@ -21,61 +21,39 @@ import ( "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" + "github.com/vmware/harbor/src/common/utils" errutil "github.com/vmware/harbor/src/common/utils/error" "github.com/vmware/harbor/src/common/utils/log" + "github.com/vmware/harbor/src/ui/promgr/pmsdriver" ) const dupProjectPattern = `Duplicate entry '\w+' for key 'name'` -// ProjectManager implements pm.PM interface based on database -type ProjectManager struct{} +type driver struct { +} + +// NewDriver returns an instance of driver +func NewDriver() pmsdriver.PMSDriver { + return &driver{} +} // Get ... -func (p *ProjectManager) Get(projectIDOrName interface{}) ( +func (d *driver) Get(projectIDOrName interface{}) ( *models.Project, error) { - switch projectIDOrName.(type) { - case string: - return dao.GetProjectByName(projectIDOrName.(string)) - case int64: - return dao.GetProjectByID(projectIDOrName.(int64)) - default: - return nil, fmt.Errorf("unsupported type of %v, must be string or int64", projectIDOrName) - } -} - -// Exist ... -func (p *ProjectManager) Exist(projectIDOrName interface{}) (bool, error) { - project, err := p.Get(projectIDOrName) + id, name, err := utils.ParseProjectIDOrName(projectIDOrName) if err != nil { - return false, err - } - return project != nil, nil -} - -// IsPublic returns whether the project is public or not -func (p *ProjectManager) IsPublic(projectIDOrName interface{}) (bool, error) { - project, err := p.Get(projectIDOrName) - if err != nil { - return false, err + return nil, err } - if project == nil { - return false, nil + if id > 0 { + return dao.GetProjectByID(id) } - return project.Public == 1, nil -} - -// GetPublic returns all public projects -func (p *ProjectManager) GetPublic() ([]*models.Project, error) { - t := true - return p.GetAll(&models.ProjectQueryParam{ - Public: &t, - }) + return dao.GetProjectByName(name) } // Create ... -func (p *ProjectManager) Create(project *models.Project) (int64, error) { +func (d *driver) Create(project *models.Project) (int64, error) { if project == nil { return 0, fmt.Errorf("project is nil") } @@ -128,10 +106,13 @@ func (p *ProjectManager) Create(project *models.Project) (int64, error) { } // Delete ... -func (p *ProjectManager) Delete(projectIDOrName interface{}) error { - id, ok := projectIDOrName.(int64) - if !ok { - project, err := p.Get(projectIDOrName) +func (d *driver) Delete(projectIDOrName interface{}) error { + id, name, err := utils.ParseProjectIDOrName(projectIDOrName) + if err != nil { + return err + } + if len(name) > 0 { + project, err := dao.GetProjectByName(name) if err != nil { return err } @@ -142,27 +123,31 @@ func (p *ProjectManager) Delete(projectIDOrName interface{}) error { } // Update ... -func (p *ProjectManager) Update(projectIDOrName interface{}, +func (d *driver) Update(projectIDOrName interface{}, project *models.Project) error { - id, ok := projectIDOrName.(int64) - if !ok { - pro, err := p.Get(projectIDOrName) - if err != nil { - return err - } - id = pro.ProjectID + // nil implement + return nil +} + +// TODO remove base +// List returns a project list according to the query parameters +func (d *driver) List(query *models.ProjectQueryParam, + base ...*models.BaseProjectCollection) ( + *models.ProjectQueryResult, error) { + total, err := dao.GetTotalOfProjects(query, base...) + if err != nil { + return nil, err } - return dao.ToggleProjectPublicity(id, project.Public) + projects, err := dao.GetProjects(query, base...) + if err != nil { + return nil, err + } + return &models.ProjectQueryResult{ + Total: total, + Projects: projects, + }, nil } -// GetAll returns a project list according to the query parameters -func (p *ProjectManager) GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ( - []*models.Project, error) { - return dao.GetProjects(query, base...) -} - -// GetTotal returns the total count according to the query parameters -func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ( - int64, error) { - return dao.GetTotalOfProjects(query, base...) +func (d *driver) EnableExternalMetaMgr() bool { + return true } diff --git a/src/ui/promgr/pmsdriver/local/local_test.go b/src/ui/promgr/pmsdriver/local/local_test.go index 96f29850a..8a88b0f8f 100644 --- a/src/ui/promgr/pmsdriver/local/local_test.go +++ b/src/ui/promgr/pmsdriver/local/local_test.go @@ -71,7 +71,7 @@ func TestMain(m *testing.M) { } func TestGet(t *testing.T) { - pm := &ProjectManager{} + pm := &driver{} // project name project, err := pm.Get("library") @@ -95,45 +95,8 @@ func TestGet(t *testing.T) { assert.NotNil(t, err) } -func TestExist(t *testing.T) { - pm := &ProjectManager{} - - // exist project - exist, err := pm.Exist("library") - assert.Nil(t, err) - assert.True(t, exist) - - // non-exist project - exist, err = pm.Exist("non-exist-project") - assert.Nil(t, err) - assert.False(t, exist) -} - -func TestIsPublic(t *testing.T) { - pms := &ProjectManager{} - // public project - public, err := pms.IsPublic("library") - assert.Nil(t, err) - assert.True(t, public) - // non exist project - public, err = pms.IsPublic("non_exist_project") - assert.Nil(t, err) - assert.False(t, public) -} - -func TestGetPublic(t *testing.T) { - pm := &ProjectManager{} - projects, err := pm.GetPublic() - assert.Nil(t, err) - assert.NotEqual(t, 0, len(projects)) - - for _, project := range projects { - assert.Equal(t, 1, project.Public) - } -} - func TestCreateAndDelete(t *testing.T) { - pm := &ProjectManager{} + pm := &driver{} // nil project _, err := pm.Create(nil) @@ -183,63 +146,12 @@ func TestCreateAndDelete(t *testing.T) { } func TestUpdate(t *testing.T) { - pm := &ProjectManager{} - - id, err := pm.Create(&models.Project{ - Name: "test", - OwnerID: 1, - }) - assert.Nil(t, err) - defer pm.Delete(id) - - project, err := pm.Get(id) - assert.Nil(t, err) - assert.Equal(t, 0, project.Public) - - project.Public = 1 - assert.Nil(t, pm.Update(id, project)) - - project, err = pm.Get(id) - assert.Nil(t, err) - assert.Equal(t, 1, project.Public) + pm := &driver{} + assert.Nil(t, pm.Update(1, nil)) } -func TestGetTotal(t *testing.T) { - pm := &ProjectManager{} - - id, err := pm.Create(&models.Project{ - Name: "get_total_test", - OwnerID: 1, - Public: 1, - }) - assert.Nil(t, err) - defer pm.Delete(id) - - // get by name - total, err := pm.GetTotal(&models.ProjectQueryParam{ - Name: "get_total_test", - }) - assert.Nil(t, err) - assert.Equal(t, int64(1), total) - - // get by owner - total, err = pm.GetTotal(&models.ProjectQueryParam{ - Owner: "admin", - }) - assert.Nil(t, err) - assert.NotEqual(t, 0, total) - - // get by public - value := true - total, err = pm.GetTotal(&models.ProjectQueryParam{ - Public: &value, - }) - assert.Nil(t, err) - assert.NotEqual(t, 0, total) -} - -func TestGetAll(t *testing.T) { - pm := &ProjectManager{} +func TestList(t *testing.T) { + pm := &driver{} id, err := pm.Create(&models.Project{ Name: "get_all_test", @@ -250,19 +162,19 @@ func TestGetAll(t *testing.T) { defer pm.Delete(id) // get by name - projects, err := pm.GetAll(&models.ProjectQueryParam{ + result, err := pm.List(&models.ProjectQueryParam{ Name: "get_all_test", }) assert.Nil(t, err) - assert.Equal(t, id, projects[0].ProjectID) + assert.Equal(t, id, result.Projects[0].ProjectID) // get by owner - projects, err = pm.GetAll(&models.ProjectQueryParam{ + result, err = pm.List(&models.ProjectQueryParam{ Owner: "admin", }) assert.Nil(t, err) exist := false - for _, project := range projects { + for _, project := range result.Projects { if project.ProjectID == id { exist = true break @@ -272,12 +184,12 @@ func TestGetAll(t *testing.T) { // get by public value := true - projects, err = pm.GetAll(&models.ProjectQueryParam{ + result, err = pm.List(&models.ProjectQueryParam{ Public: &value, }) assert.Nil(t, err) exist = false - for _, project := range projects { + for _, project := range result.Projects { if project.ProjectID == id { exist = true break diff --git a/src/ui/promgr/promgr.go b/src/ui/promgr/promgr.go index a5b5802a2..c4c5de961 100644 --- a/src/ui/promgr/promgr.go +++ b/src/ui/promgr/promgr.go @@ -15,22 +15,158 @@ package promgr import ( + "fmt" + "github.com/vmware/harbor/src/common/models" + "github.com/vmware/harbor/src/common/utils/log" + "github.com/vmware/harbor/src/ui/promgr/metamgr" + "github.com/vmware/harbor/src/ui/promgr/pmsdriver" ) -// ProMgr is the project mamager which abstracts the operations related +// ProjectManager is the project mamager which abstracts the operations related // to projects -type ProMgr interface { +type ProjectManager interface { Get(projectIDOrName interface{}) (*models.Project, error) - IsPublic(projectIDOrName interface{}) (bool, error) - Exist(projectIDOrName interface{}) (bool, error) - // get all public project - GetPublic() ([]*models.Project, error) Create(*models.Project) (int64, error) Delete(projectIDOrName interface{}) error Update(projectIDOrName interface{}, project *models.Project) error - // GetAll returns a project list according to the query parameters - GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error) - // GetTotal returns the total count according to the query parameters - GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error) + // TODO remove base + List(query *models.ProjectQueryParam, + base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error) + IsPublic(projectIDOrName interface{}) (bool, error) + Exists(projectIDOrName interface{}) (bool, error) + // get all public project + GetPublic() ([]*models.Project, error) +} + +type defaultProjectManager struct { + pmsDriver pmsdriver.PMSDriver + metaMgrEnabled bool // if metaMgrEnabled is enabled, metaMgr will be used to CURD metadata + metaMgr metamgr.ProjectMetadataManaegr +} + +// NewDefaultProjectManager returns an instance of defaultProjectManager, +// if metaMgrEnabled is true, a project metadata manager will be created +// and used to CURD metadata +func NewDefaultProjectManager(driver pmsdriver.PMSDriver, metaMgrEnabled bool) ProjectManager { + mgr := &defaultProjectManager{ + pmsDriver: driver, + metaMgrEnabled: metaMgrEnabled, + } + if metaMgrEnabled { + mgr.metaMgr = metamgr.NewDefaultProjectMetadataManager() + } + return mgr +} + +func (d *defaultProjectManager) Get(projectIDOrName interface{}) (*models.Project, error) { + project, err := d.pmsDriver.Get(projectIDOrName) + if err != nil { + return nil, err + } + + if project != nil && d.metaMgrEnabled { + meta, err := d.metaMgr.Get(project.ProjectID) + if err != nil { + return nil, err + } + if len(project.Metadata) == 0 { + project.Metadata = make(map[string]interface{}) + } + for k, v := range meta { + project.Metadata[k] = v + } + } + return project, nil +} +func (d *defaultProjectManager) Create(project *models.Project) (int64, error) { + id, err := d.pmsDriver.Create(project) + if err != nil { + return 0, err + } + if len(project.Metadata) > 0 && d.metaMgrEnabled { + if err = d.metaMgr.Add(id, project.Metadata); err != nil { + log.Errorf("failed to add metadata for project %s: %v", project.Name, err) + } + } + return id, nil +} + +func (d *defaultProjectManager) Delete(projectIDOrName interface{}) error { + project, err := d.Get(projectIDOrName) + if err != nil { + return err + } + if project == nil { + return nil + } + if project.Metadata != nil && d.metaMgrEnabled { + if err = d.metaMgr.Delete(project.ProjectID); err != nil { + return err + } + } + return d.pmsDriver.Delete(project.ProjectID) +} + +func (d *defaultProjectManager) Update(projectIDOrName interface{}, project *models.Project) error { + if len(project.Metadata) > 0 && d.metaMgrEnabled { + pro, err := d.Get(projectIDOrName) + if err != nil { + return err + } + if pro == nil { + return fmt.Errorf("project %v not found", projectIDOrName) + } + if err = d.metaMgr.Update(pro.ProjectID, project.Metadata); err != nil { + return err + } + } + + return d.pmsDriver.Update(projectIDOrName, project) +} + +// TODO remove base +func (d *defaultProjectManager) List(query *models.ProjectQueryParam, + base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error) { + result, err := d.pmsDriver.List(query, base...) + if err != nil { + return nil, err + } + if d.metaMgrEnabled { + for _, project := range result.Projects { + meta, err := d.metaMgr.Get(project.ProjectID) + if err != nil { + return nil, err + } + project.Metadata = meta + } + } + return result, nil +} + +func (d *defaultProjectManager) IsPublic(projectIDOrName interface{}) (bool, error) { + project, err := d.Get(projectIDOrName) + if err != nil { + return false, err + } + if project == nil { + return false, nil + } + return project.Public == 1, nil +} + +func (d *defaultProjectManager) Exists(projectIDOrName interface{}) (bool, error) { + project, err := d.Get(projectIDOrName) + return project != nil, err +} + +func (d *defaultProjectManager) GetPublic() ([]*models.Project, error) { + value := true + result, err := d.List(&models.ProjectQueryParam{ + Public: &value, + }) + if err != nil { + return nil, err + } + return result.Projects, nil } diff --git a/src/ui/promgr/promgr_test.go b/src/ui/promgr/promgr_test.go new file mode 100644 index 000000000..87e827fd2 --- /dev/null +++ b/src/ui/promgr/promgr_test.go @@ -0,0 +1,120 @@ +// Copyright (c) 2017 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 promgr + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/vmware/harbor/src/common/models" + "github.com/vmware/harbor/src/ui/promgr/pmsdriver" +) + +type fakePMSDriver struct { + project *models.Project +} + +func newFakePMSDriver() pmsdriver.PMSDriver { + return &fakePMSDriver{ + project: &models.Project{ + ProjectID: 1, + Name: "library", + Public: 1, + }, + } +} + +func (f *fakePMSDriver) Get(projectIDOrName interface{}) (*models.Project, error) { + return f.project, nil +} + +func (f *fakePMSDriver) Create(*models.Project) (int64, error) { + return 1, nil +} + +func (f *fakePMSDriver) Delete(projectIDOrName interface{}) error { + return nil +} + +func (f *fakePMSDriver) Update(projectIDOrName interface{}, project *models.Project) error { + return nil +} + +func (f *fakePMSDriver) List(query *models.ProjectQueryParam, + base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error) { + return &models.ProjectQueryResult{ + Total: 1, + Projects: []*models.Project{f.project}, + }, nil +} + +var ( + proMgr = NewDefaultProjectManager(newFakePMSDriver(), false) +) + +func TestGet(t *testing.T) { + project, err := proMgr.Get(1) + require.Nil(t, err) + assert.Equal(t, int64(1), project.ProjectID) +} + +func TestCreate(t *testing.T) { + id, err := proMgr.Create(&models.Project{ + Name: "library", + OwnerID: 1, + }) + require.Nil(t, err) + assert.Equal(t, int64(1), id) +} + +func TestDelete(t *testing.T) { + assert.Nil(t, proMgr.Delete(1)) +} + +func TestUpdate(t *testing.T) { + assert.Nil(t, proMgr.Update(1, + &models.Project{ + Metadata: map[string]interface{}{ + models.ProMetaPublic: true, + }, + })) +} + +func TestList(t *testing.T) { + result, err := proMgr.List(nil) + require.Nil(t, err) + assert.Equal(t, int64(1), result.Total) + assert.Equal(t, int64(1), result.Projects[0].ProjectID) +} + +func TestIsPublic(t *testing.T) { + public, err := proMgr.IsPublic(1) + require.Nil(t, err) + assert.True(t, public) +} + +func TestExist(t *testing.T) { + exist, err := proMgr.Exists(1) + require.Nil(t, err) + assert.True(t, exist) +} + +func TestGetPublic(t *testing.T) { + projects, err := proMgr.GetPublic() + require.Nil(t, err) + assert.Equal(t, 1, len(projects)) + assert.Equal(t, 1, projects[0].Public) +} diff --git a/src/ui/proxy/interceptor_test.go b/src/ui/proxy/interceptor_test.go index e6e431dd0..65fdf1ebe 100644 --- a/src/ui/proxy/interceptor_test.go +++ b/src/ui/proxy/interceptor_test.go @@ -2,14 +2,14 @@ package proxy import ( "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + //"github.com/stretchr/testify/require" "github.com/vmware/harbor/src/adminserver/client" "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/models" notarytest "github.com/vmware/harbor/src/common/utils/notary/test" utilstest "github.com/vmware/harbor/src/common/utils/test" "github.com/vmware/harbor/src/ui/config" - "github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral" + //"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral" "net/http" "net/http/httptest" @@ -128,6 +128,8 @@ func TestEnvPolicyChecker(t *testing.T) { assert.Equal(sev, models.SevNone) } +// TODO uncheck after admiral pms driver is implemented +/* func TestPMSPolicyChecker(t *testing.T) { var defaultConfigAdmiral = map[string]interface{}{ common.ExtEndpoint: "https://" + endpoint, @@ -147,7 +149,6 @@ func TestPMSPolicyChecker(t *testing.T) { if err := config.Init(); err != nil { panic(err) } - pm := admiral.NewProjectManager(http.DefaultClient, admiralEndpoint, &admiral.RawTokenReader{ Token: "token", @@ -175,7 +176,7 @@ func TestPMSPolicyChecker(t *testing.T) { assert.False(t, projectVulnerableEnabled) assert.Equal(t, projectVulnerableSeverity, models.SevLow) } - +*/ func TestMatchNotaryDigest(t *testing.T) { assert := assert.New(t) //The data from common/utils/notary/helper_test.go diff --git a/src/ui/proxy/interceptors.go b/src/ui/proxy/interceptors.go index 09bf10570..a8ebcb4fc 100644 --- a/src/ui/proxy/interceptors.go +++ b/src/ui/proxy/interceptors.go @@ -88,7 +88,7 @@ func (ec envPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity) } type pmsPolicyChecker struct { - pm promgr.ProMgr + pm promgr.ProjectManager } func (pc pmsPolicyChecker) contentTrustEnabled(name string) bool { @@ -109,7 +109,7 @@ func (pc pmsPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity) } // newPMSPolicyChecker returns an instance of an pmsPolicyChecker -func newPMSPolicyChecker(pm promgr.ProMgr) policyChecker { +func newPMSPolicyChecker(pm promgr.ProjectManager) policyChecker { return &pmsPolicyChecker{ pm: pm, } diff --git a/src/ui/service/token/authutils.go b/src/ui/service/token/authutils.go index a6bdcf84c..18d2fa31a 100644 --- a/src/ui/service/token/authutils.go +++ b/src/ui/service/token/authutils.go @@ -81,7 +81,7 @@ func GetResourceActions(scopes []string) []*token.ResourceActions { //filterAccess iterate a list of resource actions and try to use the filter that matches the resource type to filter the actions. func filterAccess(access []*token.ResourceActions, ctx security.Context, - pm promgr.ProMgr, filters map[string]accessFilter) error { + pm promgr.ProjectManager, filters map[string]accessFilter) error { var err error for _, a := range access { f, ok := filters[a.Type] diff --git a/src/ui/service/token/creator.go b/src/ui/service/token/creator.go index 3194cdb2d..df3b14e3d 100644 --- a/src/ui/service/token/creator.go +++ b/src/ui/service/token/creator.go @@ -127,13 +127,13 @@ func parseImg(s string) (*image, error) { // An accessFilter will filter access based on userinfo type accessFilter interface { - filter(ctx security.Context, pm promgr.ProMgr, a *token.ResourceActions) error + filter(ctx security.Context, pm promgr.ProjectManager, a *token.ResourceActions) error } type registryFilter struct { } -func (reg registryFilter) filter(ctx security.Context, pm promgr.ProMgr, +func (reg registryFilter) filter(ctx security.Context, pm promgr.ProjectManager, a *token.ResourceActions) error { //Do not filter if the request is to access registry catalog if a.Name != "catalog" { @@ -151,7 +151,7 @@ type repositoryFilter struct { parser imageParser } -func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProMgr, +func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProjectManager, a *token.ResourceActions) error { //clear action list to assign to new acess element after perm check. img, err := rep.parser.parse(a.Name) @@ -161,7 +161,7 @@ func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProMgr, project := img.namespace permission := "" - exist, err := pm.Exist(project) + exist, err := pm.Exists(project) if err != nil { return err }