Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Steven Zou 2017-09-27 10:59:15 +08:00
commit 546a8327aa
88 changed files with 1625 additions and 648 deletions

View File

@ -178,7 +178,7 @@ DOCKERIMAGENAME_UI=vmware/harbor-ui
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
DOCKERIMAGENAME_LOG=vmware/harbor-log DOCKERIMAGENAME_LOG=vmware/harbor-log
DOCKERIMAGENAME_DB=vmware/harbor-db DOCKERIMAGENAME_DB=vmware/harbor-db
DOCKERIMAGENAME_CLATIRY=vmware/harbor-clarity-ui-builder DOCKERIMAGENAME_CLARITY=vmware/harbor-clarity-ui-builder
DOCKERIMAGENAME_POSTGRESQL=vmware/postgresql DOCKERIMAGENAME_POSTGRESQL=vmware/postgresql
# docker-compose files # docker-compose files
DOCKERCOMPOSEFILEPATH=$(MAKEPATH) DOCKERCOMPOSEFILEPATH=$(MAKEPATH)
@ -399,10 +399,10 @@ refresh_clarity_builder:
$(SEDCMD) -i 's/__proxy__/ /g' $(DOCKERFILE_CLARITY) ; \ $(SEDCMD) -i 's/__proxy__/ /g' $(DOCKERFILE_CLARITY) ; \
fi ; \ fi ; \
echo "build new clarity image.."; \ 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.."; \ echo "push clarity image.."; \
$(DOCKERTAG) $(DOCKERIMAGENAME_CLATIRY):$(NEWCLARITYVERSION) $(DOCKERIMAGENAME_CLATIRY):$(NEWCLARITYVERSION); \ $(DOCKERTAG) $(DOCKERIMAGENAME_CLARITY):$(NEWCLARITYVERSION) $(DOCKERIMAGENAME_CLARITY):$(NEWCLARITYVERSION); \
$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_CLATIRY):$(NEWCLARITYVERSION) \ $(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_CLARITY):$(NEWCLARITYVERSION) \
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER); \ $(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER); \
echo "remove local clarity image.."; \ echo "remove local clarity image.."; \
$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(NEWCLARITYVERSION); \ $(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(NEWCLARITYVERSION); \

View File

@ -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) [![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. **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.
<img alt="Harbor" src="docs/img/harbor_logo.png"> <img alt="Harbor" src="docs/img/harbor_logo.png">

View File

@ -2586,6 +2586,9 @@ definitions:
name: name:
type: string type: string
description: The name of the tag. description: The name of the tag.
size:
type: integer
description: The size of the image.
architecture: architecture:
type: string type: string
description: The architecture of the image. description: The architecture of the image.
@ -2727,6 +2730,9 @@ definitions:
email_ssl: email_ssl:
type: boolean 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. 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: ldap_url:
type: string type: string
description: The URL of LDAP server. description: The URL of LDAP server.

View File

@ -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. * **Guest**: Guest has read-only privilege for a specified project.
* **Developer**: Developer has read and write privileges for a 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: 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. * **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 ## User account
@ -244,6 +244,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=1
export DOCKER_CONTENT_TRUST_SERVER=https://10.117.169.182:4443 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. 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.** **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.**

View File

@ -11,9 +11,9 @@ LDAP_UID=$ldap_uid
LDAP_SCOPE=$ldap_scope LDAP_SCOPE=$ldap_scope
LDAP_TIMEOUT=$ldap_timeout LDAP_TIMEOUT=$ldap_timeout
DATABASE_TYPE=mysql DATABASE_TYPE=mysql
MYSQL_HOST=mysql MYSQL_HOST=$db_host
MYSQL_PORT=3306 MYSQL_PORT=$db_port
MYSQL_USR=root MYSQL_USR=$db_user
MYSQL_PWD=$db_password MYSQL_PWD=$db_password
MYSQL_DATABASE=registry MYSQL_DATABASE=registry
REGISTRY_URL=http://registry:5000 REGISTRY_URL=http://registry:5000

View File

@ -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. #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. #Set this flag to off when the remote registry uses a self-signed or untrusted certificate.
verify_remote_cert = on 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************************ #************************END INITIAL PROPERTIES************************
############# #############

View File

@ -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

View File

@ -0,0 +1,9 @@
apiVersion: v1
kind: Service
metadata:
name: adminserver
spec:
ports:
- port: 80
selector:
name: adminserver-apps

View File

@ -43,11 +43,11 @@ spec:
configMapKeyRef: configMapKeyRef:
name: harbor-jobservice-config name: harbor-jobservice-config
key: UI_SECRET key: UI_SECRET
- name: SECRET_KEY - name: JOBSERVICE_SECRET
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: harbor-jobservice-config name: harbor-jobservice-config
key: SECRET_KEY key: JOBSERVICE_SECRET
- name: CONFIG_PATH - name: CONFIG_PATH
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:

View File

@ -29,6 +29,8 @@ parser.add_argument('-k', default='',
dest='private_key', help='[Optional] path of harbor https private key(pem)') dest='private_key', help='[Optional] path of harbor https private key(pem)')
parser.add_argument('-c', default='', parser.add_argument('-c', default='',
dest='cert', help='[Optional] harbor path of https cert(pem)') 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='', parser.add_argument('-s', default='',
dest='secret_key', help="[Optional] path of harbor secret key(16 characters)") dest='secret_key', help="[Optional] path of harbor secret key(16 characters)")
@ -99,7 +101,20 @@ else:
cert_path = '' 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 args.secret_key != '':
if os.path.isfile(args.secret_key): if os.path.isfile(args.secret_key):
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, '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, '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, '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'))

View File

@ -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 <sample_admin@mydomain.com>"
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}}"

View File

@ -8,7 +8,7 @@ data:
MYSQL_USR: root MYSQL_USR: root
MYSQL_PWD: "{{db_password}}" MYSQL_PWD: "{{db_password}}"
UI_SECRET: "{{ui_secret}}" UI_SECRET: "{{ui_secret}}"
SECRET_KEY: "{{secret_key}}" JOBSERVICE_SECRET: "{{jobservice_secret}}"
CONFIG_PATH: /etc/jobservice/app.conf CONFIG_PATH: /etc/jobservice/app.conf
REGISTRY_URL: http://registry:5000 REGISTRY_URL: http://registry:5000
VERIFY_REMOTE_CERT: "{{verify_remote_cert}}" VERIFY_REMOTE_CERT: "{{verify_remote_cert}}"

View File

@ -22,7 +22,7 @@ data:
LDAP_SCOPE: "{{ldap_scope}}" LDAP_SCOPE: "{{ldap_scope}}"
LOG_LEVEL: debug LOG_LEVEL: debug
UI_SECRET: "{{ui_secret}}" UI_SECRET: "{{ui_secret}}"
SECRET_KEY: "{{secret_key}}" JOBSERVICE_SECRET: "{{jobservice_secre}}"
GODEBUG: netdns=cgo GODEBUG: netdns=cgo
EXT_ENDPOINT: "{{ui_url}}" EXT_ENDPOINT: "{{ui_url}}"
TOKEN_URL: http://ui TOKEN_URL: http://ui

View File

@ -113,11 +113,11 @@ spec:
configMapKeyRef: configMapKeyRef:
name: harbor-ui-config name: harbor-ui-config
key: UI_SECRET key: UI_SECRET
- name: SECRET_KEY - name: JOBSERVICE_SECRET
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: harbor-ui-config name: harbor-ui-config
key: SECRET_KEY key: JOBSERVICE_SECRET
- name: GODEBUG - name: GODEBUG
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
@ -171,4 +171,4 @@ spec:
- key: config - key: config
path: app.conf path: app.conf
- key: pkey - key: pkey
path: private_key.pem path: private_key.pem

View File

@ -139,6 +139,9 @@ ldap_uid = rcp.get("configuration", "ldap_uid")
ldap_scope = rcp.get("configuration", "ldap_scope") ldap_scope = rcp.get("configuration", "ldap_scope")
ldap_timeout = rcp.get("configuration", "ldap_timeout") ldap_timeout = rcp.get("configuration", "ldap_timeout")
db_password = rcp.get("configuration", "db_password") 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") self_registration = rcp.get("configuration", "self_registration")
if protocol == "https": if protocol == "https":
cert_path = rcp.get("configuration", "ssl_cert") cert_path = rcp.get("configuration", "ssl_cert")
@ -210,6 +213,9 @@ render(os.path.join(templates_dir, "adminserver", "env"),
ldap_scope=ldap_scope, ldap_scope=ldap_scope,
ldap_timeout=ldap_timeout, ldap_timeout=ldap_timeout,
db_password=db_password, db_password=db_password,
db_host=db_host,
db_port=db_port,
db_user=db_user,
email_host=email_host, email_host=email_host,
email_port=email_port, email_port=email_port,
email_usr=email_usr, email_usr=email_usr,

View File

@ -115,9 +115,5 @@ func (c *cfgStore) Write(config map[string]interface{}) error {
return err return err
} }
if err = ioutil.WriteFile(c.path, b, 0600); err != nil { return ioutil.WriteFile(c.path, b, 0600)
return err
}
return nil
} }

View File

@ -91,6 +91,10 @@ var (
env: "EMAIL_SSL", env: "EMAIL_SSL",
parse: parseStringToBool, parse: parseStringToBool,
}, },
common.EmailInsecure: &parser{
env: "EMAIL_INSECURE",
parse: parseStringToBool,
},
common.EmailFrom: "EMAIL_FROM", common.EmailFrom: "EMAIL_FROM",
common.EmailIdentity: "EMAIL_IDENTITY", common.EmailIdentity: "EMAIL_IDENTITY",
common.RegistryURL: "REGISTRY_URL", common.RegistryURL: "REGISTRY_URL",

View File

@ -195,9 +195,9 @@ func (b *BaseAPI) GetUserIDForRequest() (int, bool, bool) {
// Redirect does redirection to resource URI with http header status code. // Redirect does redirection to resource URI with http header status code.
func (b *BaseAPI) Redirect(statusCode int, resouceID string) { func (b *BaseAPI) Redirect(statusCode int, resouceID string) {
requestURI := b.Ctx.Request.RequestURI 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 // GetIDFromURL checks the ID in request URL
@ -221,7 +221,7 @@ func (b *BaseAPI) SetPaginationHeader(total, page, pageSize int64) {
link := "" link := ""
// SetPaginationHeader setprevious link // SetPaginationHeader set previous link
if page > 1 && (page-1)*pageSize <= total { if page > 1 && (page-1)*pageSize <= total {
u := *(b.Ctx.Request.URL) u := *(b.Ctx.Request.URL)
q := u.Query() q := u.Query()
@ -233,7 +233,7 @@ func (b *BaseAPI) SetPaginationHeader(total, page, pageSize int64) {
link += fmt.Sprintf("<%s>; rel=\"prev\"", u.String()) link += fmt.Sprintf("<%s>; rel=\"prev\"", u.String())
} }
// SetPaginationHeader setnext link // SetPaginationHeader set next link
if pageSize*page < total { if pageSize*page < total {
u := *(b.Ctx.Request.URL) u := *(b.Ctx.Request.URL)
q := u.Query() q := u.Query()

View File

@ -55,6 +55,7 @@ const (
EmailFrom = "email_from" EmailFrom = "email_from"
EmailSSL = "email_ssl" EmailSSL = "email_ssl"
EmailIdentity = "email_identity" EmailIdentity = "email_identity"
EmailInsecure = "email_insecure"
ProjectCreationRestriction = "project_creation_restriction" ProjectCreationRestriction = "project_creation_restriction"
VerifyRemoteCert = "verify_remote_cert" VerifyRemoteCert = "verify_remote_cert"
MaxJobWorkers = "max_job_workers" MaxJobWorkers = "max_job_workers"

View File

@ -267,8 +267,5 @@ func deleteRepository(name string) error {
} }
func clearRepositoryData() error { func clearRepositoryData() error {
if err := ClearTable(models.RepoTable); err != nil { return ClearTable(models.RepoTable)
return err
}
return nil
} }

View File

@ -42,11 +42,7 @@ func (s *sqlite) Register(alias ...string) error {
if len(alias) != 0 { if len(alias) != 0 {
an = alias[0] an = alias[0]
} }
if err := orm.RegisterDataBase(an, "sqlite3", s.file); err != nil { return orm.RegisterDataBase(an, "sqlite3", s.file)
return err
}
return nil
} }
// Name returns the name of SQLite // Name returns the name of SQLite

View File

@ -65,6 +65,7 @@ type Email struct {
SSL bool `json:"ssl"` SSL bool `json:"ssl"`
Identity string `json:"identity"` Identity string `json:"identity"`
From string `json:"from"` From string `json:"from"`
Insecure bool `json:"insecure"`
} }
/* /*

View File

@ -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"`
}

View File

@ -21,24 +21,25 @@ import (
// Project holds the details of a project. // Project holds the details of a project.
// TODO remove useless attrs // TODO remove useless attrs
type Project struct { type Project struct {
ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"` ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"`
OwnerID int `orm:"column(owner_id)" json:"owner_id"` OwnerID int `orm:"column(owner_id)" json:"owner_id"`
Name string `orm:"column(name)" json:"name"` Name string `orm:"column(name)" json:"name"`
CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"` CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"`
CreationTimeStr string `orm:"-" json:"creation_time_str"` UpdateTime time.Time `orm:"update_time" json:"update_time"`
Deleted int `orm:"column(deleted)" json:"deleted"` Deleted int `orm:"column(deleted)" json:"deleted"`
//UserID int `json:"UserId"` CreationTimeStr string `orm:"-" json:"creation_time_str"`
OwnerName string `orm:"-" json:"owner_name"` OwnerName string `orm:"-" json:"owner_name"`
Public int `orm:"column(public)" json:"public"` Togglable bool `orm:"-"`
//This field does not have correspondent column in DB, this is just for UI to disable button Role int `orm:"-" json:"current_user_role_id"`
Togglable bool `orm:"-"` RepoCount int `orm:"-" json:"repo_count"`
UpdateTime time.Time `orm:"update_time" json:"update_time"` Metadata map[string]interface{} `orm:"-" json:"metadata"`
Role int `orm:"-" json:"current_user_role_id"`
RepoCount int `orm:"-" json:"repo_count"` // TODO remove
EnableContentTrust bool `orm:"-" json:"enable_content_trust"` Public int `orm:"column(public)" json:"public"`
PreventVulnerableImagesFromRunning bool `orm:"-" json:"prevent_vulnerable_images_from_running"` EnableContentTrust bool `orm:"-" json:"enable_content_trust"`
PreventVulnerableImagesFromRunningSeverity string `orm:"-" json:"prevent_vulnerable_images_from_running_severity"` PreventVulnerableImagesFromRunning bool `orm:"-" json:"prevent_vulnerable_images_from_running"`
AutomaticallyScanImagesOnPush bool `orm:"-" json:"automatically_scan_images_on_push"` 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 // ProjectSorter holds an array of projects
@ -109,3 +110,9 @@ type ProjectRequest struct {
PreventVulnerableImagesFromRunningSeverity string `json:"prevent_vulnerable_images_from_running_severity"` PreventVulnerableImagesFromRunningSeverity string `json:"prevent_vulnerable_images_from_running_severity"`
AutomaticallyScanImagesOnPush bool `json:"automatically_scan_images_on_push"` AutomaticallyScanImagesOnPush bool `json:"automatically_scan_images_on_push"`
} }
// ProjectQueryResult ...
type ProjectQueryResult struct {
Total int64
Projects []*Project
}

View File

@ -19,18 +19,18 @@ import (
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/security/admiral/authcontext" "github.com/vmware/harbor/src/common/security/admiral/authcontext"
"github.com/vmware/harbor/src/common/utils/log" "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 // SecurityContext implements security.Context interface based on
// auth context and project manager // auth context and project manager
type SecurityContext struct { type SecurityContext struct {
ctx *authcontext.AuthContext ctx *authcontext.AuthContext
pm projectmanager.ProjectManager pm promgr.ProjectManager
} }
// NewSecurityContext ... // NewSecurityContext ...
func NewSecurityContext(ctx *authcontext.AuthContext, pm projectmanager.ProjectManager) *SecurityContext { func NewSecurityContext(ctx *authcontext.AuthContext, pm promgr.ProjectManager) *SecurityContext {
return &SecurityContext{ return &SecurityContext{
ctx: ctx, ctx: ctx,
pm: pm, pm: pm,

View File

@ -19,17 +19,17 @@ import (
"github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/projectmanager" "github.com/vmware/harbor/src/ui/promgr"
) )
// SecurityContext implements security.Context interface based on database // SecurityContext implements security.Context interface based on database
type SecurityContext struct { type SecurityContext struct {
user *models.User user *models.User
pm projectmanager.ProjectManager pm promgr.ProjectManager
} }
// NewSecurityContext ... // NewSecurityContext ...
func NewSecurityContext(user *models.User, pm projectmanager.ProjectManager) *SecurityContext { func NewSecurityContext(user *models.User, pm promgr.ProjectManager) *SecurityContext {
return &SecurityContext{ return &SecurityContext{
user: user, user: user,
pm: pm, pm: pm,

View File

@ -25,7 +25,8 @@ import (
"github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/projectmanager/db" "github.com/vmware/harbor/src/ui/promgr"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver/local"
) )
var ( var (
@ -47,7 +48,7 @@ var (
Email: "guestUser@vmware.com", Email: "guestUser@vmware.com",
} }
pm = &db.ProjectManager{} pm = promgr.NewDefaultProjectManager(local.NewDriver(), true)
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {

View File

@ -50,6 +50,7 @@ var adminServerDefaultConfig = map[string]interface{}{
common.EmailPassword: "password", common.EmailPassword: "password",
common.EmailFrom: "from", common.EmailFrom: "from",
common.EmailSSL: true, common.EmailSSL: true,
common.EmailInsecure: false,
common.EmailIdentity: "", common.EmailIdentity: "",
common.ProjectCreationRestriction: common.ProCrtRestrAdmOnly, common.ProjectCreationRestriction: common.ProCrtRestrAdmOnly,
common.VerifyRemoteCert: false, common.VerifyRemoteCert: false,

View File

@ -159,19 +159,10 @@ func ParseProjectIDOrName(value interface{}) (int64, string, error) {
case int: case int:
i := value.(int) i := value.(int)
id = int64(i) id = int64(i)
if id == 0 {
return 0, "", fmt.Errorf("invalid ID: 0")
}
case int64: case int64:
id = value.(int64) id = value.(int64)
if id == 0 {
return 0, "", fmt.Errorf("invalid ID: 0")
}
case string: case string:
name = value.(string) name = value.(string)
if len(name) == 0 {
return 0, "", fmt.Errorf("empty name")
}
default: default:
return 0, "", fmt.Errorf("unsupported type") return 0, "", fmt.Errorf("unsupported type")
} }

View File

@ -217,14 +217,6 @@ func TestParseHarborIDOrName(t *testing.T) {
id, name, err := ParseProjectIDOrName(nil) id, name, err := ParseProjectIDOrName(nil)
assert.NotNil(t, err) 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 // valid int ID
id, name, err = ParseProjectIDOrName(1) id, name, err = ParseProjectIDOrName(1)
assert.Nil(t, err) assert.Nil(t, err)

View File

@ -15,6 +15,10 @@ package job
import ( import (
"fmt" "fmt"
"os"
"strconv"
"testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/dao" "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/log"
"github.com/vmware/harbor/src/common/utils/test" "github.com/vmware/harbor/src/common/utils/test"
"github.com/vmware/harbor/src/jobservice/config" "github.com/vmware/harbor/src/jobservice/config"
"os"
"strconv"
"testing"
) )
var repJobID, scanJobID int64 var repJobID, scanJobID int64
@ -192,10 +193,7 @@ func clearRepJobData() error {
if err := dao.ClearTable(models.RepPolicyTable); err != nil { if err := dao.ClearTable(models.RepPolicyTable); err != nil {
return err return err
} }
if err := dao.ClearTable(models.RepTargetTable); err != nil { return dao.ClearTable(models.RepTargetTable)
return err
}
return nil
} }
func prepareScanJobData() error { func prepareScanJobData() error {
@ -220,8 +218,5 @@ func clearScanJobData() error {
if err := dao.ClearTable(models.ScanJobTable); err != nil { if err := dao.ClearTable(models.ScanJobTable); err != nil {
return err return err
} }
if err := dao.ClearTable(models.ScanOverviewTable); err != nil { return dao.ClearTable(models.ScanOverviewTable)
return err
}
return nil
} }

View File

@ -21,7 +21,7 @@ import (
"github.com/vmware/harbor/src/common/security" "github.com/vmware/harbor/src/common/security"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/filter" "github.com/vmware/harbor/src/ui/filter"
"github.com/vmware/harbor/src/ui/projectmanager" "github.com/vmware/harbor/src/ui/promgr"
) )
// BaseController ... // BaseController ...
@ -31,7 +31,7 @@ type BaseController struct {
SecurityCtx security.Context SecurityCtx security.Context
// ProjectMgr is the project manager which abstracts the operations // ProjectMgr is the project manager which abstracts the operations
// related to projects // related to projects
ProjectMgr projectmanager.ProjectManager ProjectMgr promgr.ProjectManager
} }
const ( const (

View File

@ -47,6 +47,7 @@ var (
common.EmailFrom, common.EmailFrom,
common.EmailSSL, common.EmailSSL,
common.EmailIdentity, common.EmailIdentity,
common.EmailInsecure,
common.ProjectCreationRestriction, common.ProjectCreationRestriction,
common.VerifyRemoteCert, common.VerifyRemoteCert,
common.TokenExpiration, common.TokenExpiration,
@ -78,6 +79,7 @@ var (
boolKeys = []string{ boolKeys = []string{
common.EmailSSL, common.EmailSSL,
common.EmailInsecure,
common.SelfRegistration, common.SelfRegistration,
common.VerifyRemoteCert, common.VerifyRemoteCert,
} }

View File

@ -51,7 +51,7 @@ func (e *EmailAPI) Prepare() {
func (e *EmailAPI) Ping() { func (e *EmailAPI) Ping() {
var host, username, password, identity string var host, username, password, identity string
var port int var port int
var ssl bool var ssl, insecure bool
body := e.Ctx.Input.CopyBody(1 << 32) body := e.Ctx.Input.CopyBody(1 << 32)
if body == nil || len(body) == 0 { if body == nil || len(body) == 0 {
cfg, err := config.Email() cfg, err := config.Email()
@ -66,6 +66,7 @@ func (e *EmailAPI) Ping() {
password = cfg.Password password = cfg.Password
identity = cfg.Identity identity = cfg.Identity
ssl = cfg.SSL ssl = cfg.SSL
insecure = cfg.Insecure
} else { } else {
settings := &struct { settings := &struct {
Host string `json:"email_host"` Host string `json:"email_host"`
@ -74,6 +75,7 @@ func (e *EmailAPI) Ping() {
Password *string `json:"email_password"` Password *string `json:"email_password"`
SSL bool `json:"email_ssl"` SSL bool `json:"email_ssl"`
Identity string `json:"email_identity"` Identity string `json:"email_identity"`
Insecure bool `json:"email_insecure"`
}{} }{}
e.DecodeJSONReq(&settings) e.DecodeJSONReq(&settings)
@ -98,11 +100,12 @@ func (e *EmailAPI) Ping() {
password = *settings.Password password = *settings.Password
identity = settings.Identity identity = settings.Identity
ssl = settings.SSL ssl = settings.SSL
insecure = settings.Insecure
} }
addr := net.JoinHostPort(host, strconv.Itoa(port)) addr := net.JoinHostPort(host, strconv.Itoa(port))
if err := email.Ping(addr, identity, username, 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) log.Debugf("ping %s failed: %v", addr, err)
e.CustomAbort(http.StatusBadRequest, err.Error()) e.CustomAbort(http.StatusBadRequest, err.Error())
} }

View File

@ -109,7 +109,7 @@ func (p *ProjectAPI) Post() {
return return
} }
exist, err := p.ProjectMgr.Exist(pro.Name) exist, err := p.ProjectMgr.Exists(pro.Name)
if err != nil { if err != nil {
p.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", p.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
pro.Name), err) 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 { if err != nil {
p.ParseAndHandleError("failed to get total of projects", err) p.ParseAndHandleError("failed to list projects", err)
return return
} }
projects, err := p.ProjectMgr.GetAll(query, base) for _, project := range result.Projects {
if err != nil {
p.ParseAndHandleError("failed to get projects", err)
return
}
for _, project := range projects {
if p.SecurityCtx.IsAuthenticated() { if p.SecurityCtx.IsAuthenticated() {
roles := p.SecurityCtx.GetProjectRoles(project.ProjectID) roles := p.SecurityCtx.GetProjectRoles(project.ProjectID)
if len(roles) != 0 { if len(roles) != 0 {
@ -359,8 +353,8 @@ func (p *ProjectAPI) List() {
project.RepoCount = len(repos) project.RepoCount = len(repos)
} }
p.SetPaginationHeader(total, page, size) p.SetPaginationHeader(result.Total, page, size)
p.Data["json"] = projects p.Data["json"] = result.Projects
p.ServeJSON() p.ServeJSON()
} }
@ -385,7 +379,9 @@ func (p *ProjectAPI) ToggleProjectPublic() {
if err := p.ProjectMgr.Update(p.project.ProjectID, if err := p.ProjectMgr.Update(p.project.ProjectID,
&models.Project{ &models.Project{
Public: req.Public, Metadata: map[string]interface{}{
models.ProMetaPublic: req.Public,
},
}); err != nil { }); err != nil {
p.ParseAndHandleError(fmt.Sprintf("failed to update project %d", p.ParseAndHandleError(fmt.Sprintf("failed to update project %d",
p.project.ProjectID), err) p.project.ProjectID), err)

View File

@ -54,9 +54,10 @@ type repoResp struct {
UpdateTime time.Time `json:"update_time"` UpdateTime time.Time `json:"update_time"`
} }
type tag struct { type tagDetail struct {
Digest string `json:"digest"` Digest string `json:"digest"`
Name string `json:"name"` Name string `json:"name"`
Size int64 `json:"size"`
Architecture string `json:"architecture"` Architecture string `json:"architecture"`
OS string `json:"os"` OS string `json:"os"`
DockerVersion string `json:"docker_version"` DockerVersion string `json:"docker_version"`
@ -65,7 +66,7 @@ type tag struct {
} }
type tagResp struct { type tagResp struct {
tag tagDetail
Signature *notary.Target `json:"signature"` Signature *notary.Target `json:"signature"`
ScanOverview *models.ImgScanOverview `json:"scan_overview,omitempty"` ScanOverview *models.ImgScanOverview `json:"scan_overview,omitempty"`
} }
@ -83,7 +84,7 @@ func (ra *RepositoryAPI) Get() {
return return
} }
exist, err := ra.ProjectMgr.Exist(projectID) exist, err := ra.ProjectMgr.Exists(projectID)
if err != nil { if err != nil {
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %d", ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %d",
projectID), err) projectID), err)
@ -334,7 +335,7 @@ func (ra *RepositoryAPI) GetTags() {
repoName := ra.GetString(":splat") repoName := ra.GetString(":splat")
projectName, _ := utils.ParseRepository(repoName) projectName, _ := utils.ParseRepository(repoName)
exist, err := ra.ProjectMgr.Exist(projectName) exist, err := ra.ProjectMgr.Exists(projectName)
if err != nil { if err != nil {
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
projectName), err) projectName), err)
@ -390,17 +391,13 @@ func assemble(client *registry.Repository, repository string,
for _, t := range tags { for _, t := range tags {
item := &tagResp{} item := &tagResp{}
// tag configuration // the detail information of tag
digest, _, cfg, err := getV2Manifest(client, t) tagDetail, err := getTagDetail(client, t)
if err != nil { if err != nil {
cfg = &tag{
Digest: digest,
Name: t,
}
log.Errorf("failed to get v2 manifest of %s:%s: %v", repository, t, err) log.Errorf("failed to get v2 manifest of %s:%s: %v", repository, t, err)
} }
if cfg != nil { if tagDetail != nil {
item.tag = *cfg item.tagDetail = *tagDetail
} }
// scan overview // scan overview
@ -425,40 +422,45 @@ func assemble(client *registry.Repository, repository string,
return result return result
} }
// get v2 manifest of tag, returns digest, manifest, // getTagDetail returns the detail information for v2 manifest image
// manifest config and error. The manifest config contains // The information contains architecture, os, author, size, etc.
// architecture, os, author, etc. func getTagDetail(client *registry.Repository, tag string) (*tagDetail, error) {
func getV2Manifest(client *registry.Repository, tagName string) ( detail := &tagDetail{
string, *schema2.DeserializedManifest, *tag, error) { Name: tag,
digest, _, payload, err := client.PullManifest(tagName, []string{schema2.MediaTypeManifest})
if err != nil {
return "", nil, nil, err
} }
digest, _, payload, err := client.PullManifest(tag, []string{schema2.MediaTypeManifest})
if err != nil {
return detail, err
}
detail.Digest = digest
manifest := &schema2.DeserializedManifest{} manifest := &schema2.DeserializedManifest{}
if err = manifest.UnmarshalJSON(payload); err != nil { 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()) _, reader, err := client.PullBlob(manifest.Target().Digest.String())
if err != nil { if err != nil {
return digest, manifest, nil, err return detail, err
} }
configData, err := ioutil.ReadAll(reader) configData, err := ioutil.ReadAll(reader)
if err != nil { if err != nil {
return digest, manifest, nil, err return detail, err
} }
config := &tag{} if err = json.Unmarshal(configData, detail); err != nil {
if err = json.Unmarshal(configData, config); err != nil { return detail, err
return digest, manifest, nil, err
} }
config.Name = tagName return detail, nil
config.Digest = digest
return digest, manifest, config, nil
} }
// GetManifests returns the manifest of a tag // GetManifests returns the manifest of a tag
@ -476,7 +478,7 @@ func (ra *RepositoryAPI) GetManifests() {
} }
projectName, _ := utils.ParseRepository(repoName) projectName, _ := utils.ParseRepository(repoName)
exist, err := ra.ProjectMgr.Exist(projectName) exist, err := ra.ProjectMgr.Exists(projectName)
if err != nil { if err != nil {
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
projectName), err) projectName), err)
@ -608,7 +610,7 @@ func (ra *RepositoryAPI) GetSignatures() {
repoName := ra.GetString(":splat") repoName := ra.GetString(":splat")
projectName, _ := utils.ParseRepository(repoName) projectName, _ := utils.ParseRepository(repoName)
exist, err := ra.ProjectMgr.Exist(projectName) exist, err := ra.ProjectMgr.Exists(projectName)
if err != nil { if err != nil {
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
projectName), err) projectName), err)
@ -649,7 +651,7 @@ func (ra *RepositoryAPI) ScanImage() {
repoName := ra.GetString(":splat") repoName := ra.GetString(":splat")
tag := ra.GetString(":tag") tag := ra.GetString(":tag")
projectName, _ := utils.ParseRepository(repoName) projectName, _ := utils.ParseRepository(repoName)
exist, err := ra.ProjectMgr.Exist(projectName) exist, err := ra.ProjectMgr.Exists(projectName)
if err != nil { if err != nil {
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
projectName), err) projectName), err)
@ -792,7 +794,7 @@ func getSignatures(username, repository string) (map[string][]notary.Target, err
func (ra *RepositoryAPI) checkExistence(repository, tag string) (bool, string, error) { func (ra *RepositoryAPI) checkExistence(repository, tag string) (bool, string, error) {
project, _ := utils.ParseRepository(repository) project, _ := utils.ParseRepository(repository)
exist, err := ra.ProjectMgr.Exist(project) exist, err := ra.ProjectMgr.Exists(project)
if err != nil { if err != nil {
return false, "", err return false, "", err
} }

View File

@ -48,11 +48,12 @@ func (s *SearchAPI) Get() {
var err error var err error
if isSysAdmin { if isSysAdmin {
projects, err = s.ProjectMgr.GetAll(nil) result, err := s.ProjectMgr.List(nil)
if err != nil { if err != nil {
s.ParseAndHandleError("failed to get projects", err) s.ParseAndHandleError("failed to get projects", err)
return return
} }
projects = result.Projects
} else { } else {
projects, err = s.ProjectMgr.GetPublic() projects, err = s.ProjectMgr.GetPublic()
if err != nil { if err != nil {

View File

@ -77,15 +77,15 @@ func (s *StatisticAPI) Get() {
statistic[PubRC] = n statistic[PubRC] = n
if s.SecurityCtx.IsSysAdmin() { if s.SecurityCtx.IsSysAdmin() {
n, err := s.ProjectMgr.GetTotal(nil) result, err := s.ProjectMgr.List(nil)
if err != nil { if err != nil {
log.Errorf("failed to get total of projects: %v", err) log.Errorf("failed to get total of projects: %v", err)
s.CustomAbort(http.StatusInternalServerError, "") s.CustomAbort(http.StatusInternalServerError, "")
} }
statistic[TPC] = n statistic[TPC] = result.Total
statistic[PriPC] = n - statistic[PubPC] statistic[PriPC] = result.Total - statistic[PubPC]
n, err = dao.GetTotalOfRepositories("") n, err := dao.GetTotalOfRepositories("")
if err != nil { if err != nil {
log.Errorf("failed to get total of repositories: %v", err) log.Errorf("failed to get total of repositories: %v", err)
s.CustomAbort(http.StatusInternalServerError, "") s.CustomAbort(http.StatusInternalServerError, "")
@ -94,7 +94,7 @@ func (s *StatisticAPI) Get() {
statistic[PriRC] = n - statistic[PubRC] statistic[PriRC] = n - statistic[PubRC]
} else { } else {
value := false value := false
projects, err := s.ProjectMgr.GetAll(&models.ProjectQueryParam{ result, err := s.ProjectMgr.List(&models.ProjectQueryParam{
Public: &value, Public: &value,
Member: &models.MemberQuery{ Member: &models.MemberQuery{
Name: s.username, Name: s.username,
@ -106,10 +106,10 @@ func (s *StatisticAPI) Get() {
return return
} }
statistic[PriPC] = (int64)(len(projects)) statistic[PriPC] = result.Total
ids := []int64{} ids := []int64{}
for _, p := range projects { for _, p := range result.Projects {
ids = append(ids, p.ProjectID) ids = append(ids, p.ProjectID)
} }

View File

@ -357,10 +357,7 @@ func validate(user models.User) error {
if isIllegalLength(user.Password, 8, 20) { if isIllegalLength(user.Password, 8, 20) {
return fmt.Errorf("password with illegal length") return fmt.Errorf("password with illegal length")
} }
if err := commonValidate(user); err != nil { return commonValidate(user)
return err
}
return nil
} }
//commonValidate validates email, realname, comment information when user register or change their profile //commonValidate validates email, realname, comment information when user register or change their profile

View File

@ -33,7 +33,7 @@ import (
"github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry"
"github.com/vmware/harbor/src/common/utils/registry/auth" "github.com/vmware/harbor/src/common/utils/registry/auth"
"github.com/vmware/harbor/src/ui/config" "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" "github.com/vmware/harbor/src/ui/service/token"
uiutils "github.com/vmware/harbor/src/ui/utils" 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. // SyncRegistry syncs the repositories of registry with database.
func SyncRegistry(pm projectmanager.ProjectManager) error { func SyncRegistry(pm promgr.ProjectManager) error {
log.Infof("Start syncing repositories from registry to DB... ") log.Infof("Start syncing repositories from registry to DB... ")
@ -254,7 +254,7 @@ func catalog() ([]string, error) {
} }
func diffRepos(reposInRegistry []string, reposInDB []string, func diffRepos(reposInRegistry []string, reposInDB []string,
pm projectmanager.ProjectManager) ([]string, []string, error) { pm promgr.ProjectManager) ([]string, []string, error) {
var needsAdd []string var needsAdd []string
var needsDel []string var needsDel []string
@ -359,9 +359,9 @@ func diffRepos(reposInRegistry []string, reposInDB []string,
return needsAdd, needsDel, nil return needsAdd, needsDel, nil
} }
func projectExists(pm projectmanager.ProjectManager, repository string) (bool, error) { func projectExists(pm promgr.ProjectManager, repository string) (bool, error) {
project, _ := utils.ParseRepository(repository) project, _ := utils.ParseRepository(repository)
return pm.Exist(project) return pm.Exists(project)
} }
func initRegistryClient() (r *registry.Registry, err error) { func initRegistryClient() (r *registry.Registry, err error) {

View File

@ -15,7 +15,7 @@
package config package config
import ( import (
"crypto/tls" //"crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@ -29,9 +29,10 @@ import (
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/secret" "github.com/vmware/harbor/src/common/secret"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/projectmanager" "github.com/vmware/harbor/src/ui/promgr"
"github.com/vmware/harbor/src/ui/projectmanager/db" "github.com/vmware/harbor/src/ui/promgr/pmsdriver"
"github.com/vmware/harbor/src/ui/projectmanager/pms" "github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver/local"
) )
const ( const (
@ -46,14 +47,14 @@ var (
// AdminserverClient is a client for adminserver // AdminserverClient is a client for adminserver
AdminserverClient client.Client AdminserverClient client.Client
// GlobalProjectMgr is initialized based on the deploy mode // GlobalProjectMgr is initialized based on the deploy mode
GlobalProjectMgr projectmanager.ProjectManager GlobalProjectMgr promgr.ProjectManager
mg *comcfg.Manager mg *comcfg.Manager
keyProvider comcfg.KeyProvider keyProvider comcfg.KeyProvider
// AdmiralClient is initialized only under integration deploy mode // AdmiralClient is initialized only under integration deploy mode
// and can be passed to project manager as a parameter // and can be passed to project manager as a parameter
AdmiralClient *http.Client AdmiralClient *http.Client
// TokenReader is used in integration mode to read token // TokenReader is used in integration mode to read token
TokenReader pms.TokenReader TokenReader admiral.TokenReader
) )
// Init configurations // Init configurations
@ -105,34 +106,41 @@ func initSecretStore() {
} }
func initProjectManager() { 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 // standalone
log.Info("initializing the project manager based on database...") log.Info("initializing the project manager based on local database...")
GlobalProjectMgr = &db.ProjectManager{} driver = local.NewDriver()
return // 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 = &pms.FileTokenReader{
Path: path,
}
GlobalProjectMgr = pms.NewProjectManager(AdmiralClient,
AdmiralEndpoint(), TokenReader)
} }
// Load configurations // Load configurations
@ -298,6 +306,7 @@ func Email() (*models.Email, error) {
email.SSL = cfg[common.EmailSSL].(bool) email.SSL = cfg[common.EmailSSL].(bool)
email.From = cfg[common.EmailFrom].(string) email.From = cfg[common.EmailFrom].(string)
email.Identity = cfg[common.EmailIdentity].(string) email.Identity = cfg[common.EmailIdentity].(string)
email.Insecure = cfg[common.EmailInsecure].(bool)
return email, nil return email, nil
} }
@ -378,6 +387,7 @@ func AdmiralEndpoint() string {
log.Errorf("Failed to get configuration, will return empty string as admiral's endpoint, error: %v", err) log.Errorf("Failed to get configuration, will return empty string as admiral's endpoint, error: %v", err)
return "" return ""
} }
if e, ok := cfg[common.AdmiralEndpoint].(string); !ok || e == "NA" { if e, ok := cfg[common.AdmiralEndpoint].(string); !ok || e == "NA" {
return "" return ""
} }

View File

@ -171,7 +171,8 @@ func (cc *CommonController) SendEmail() {
settings.Username, settings.Username,
settings.Password, settings.Password,
60, settings.SSL, 60, settings.SSL,
false, settings.From, settings.Insecure,
settings.From,
[]string{email}, []string{email},
"Reset Harbor user password", "Reset Harbor user password",
message.String()) message.String())

View File

@ -25,15 +25,15 @@ import (
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
secstore "github.com/vmware/harbor/src/common/secret" secstore "github.com/vmware/harbor/src/common/secret"
"github.com/vmware/harbor/src/common/security" "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/admiral/authcontext"
"github.com/vmware/harbor/src/common/security/local" "github.com/vmware/harbor/src/common/security/local"
"github.com/vmware/harbor/src/common/security/secret" "github.com/vmware/harbor/src/common/security/secret"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/auth" "github.com/vmware/harbor/src/ui/auth"
"github.com/vmware/harbor/src/ui/config" "github.com/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/projectmanager/pms" //"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
) )
type key string type key string
@ -192,7 +192,7 @@ func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
log.Debug("using global project manager...") log.Debug("using global project manager...")
pm := config.GlobalProjectMgr pm := config.GlobalProjectMgr
log.Debug("creating admiral security context...") log.Debug("creating admiral security context...")
securCtx := admiral.NewSecurityContext(authCtx, pm) securCtx := admr.NewSecurityContext(authCtx, pm)
setSecurCtxAndPM(ctx.Request, securCtx, pm) setSecurCtxAndPM(ctx.Request, securCtx, pm)
return true return true
@ -264,13 +264,18 @@ func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
return false return false
} }
log.Debug("creating PMS project manager...") /*
pm := pms.NewProjectManager(config.AdmiralClient, log.Debug("creating PMS project manager...")
config.AdmiralEndpoint(), &pms.RawTokenReader{ pm := admiral.NewProjectManager(config.AdmiralClient,
Token: token, 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...") log.Debug("creating admiral security context...")
securCtx := admiral.NewSecurityContext(authContext, pm) securCtx := admr.NewSecurityContext(authContext, pm)
setSecurCtxAndPM(ctx.Request, securCtx, pm) setSecurCtxAndPM(ctx.Request, securCtx, pm)
return true return true
@ -283,14 +288,18 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
log.Debug("user information is nil") log.Debug("user information is nil")
var securCtx security.Context var securCtx security.Context
var pm projectmanager.ProjectManager var pm promgr.ProjectManager
if config.WithAdmiral() { if config.WithAdmiral() {
// integration with admiral // integration with admiral
log.Debug("creating PMS project manager...") /*
pm = pms.NewProjectManager(config.AdmiralClient, log.Debug("creating PMS project manager...")
config.AdmiralEndpoint(), nil) 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...") log.Debug("creating admiral security context...")
securCtx = admiral.NewSecurityContext(nil, pm) securCtx = admr.NewSecurityContext(nil, pm)
} else { } else {
// standalone // standalone
log.Debug("using local database project manager") log.Debug("using local database project manager")
@ -302,7 +311,7 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
return true return true
} }
func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm projectmanager.ProjectManager) { func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm promgr.ProjectManager) {
addToReqContext(req, securCtxKey, ctx) addToReqContext(req, securCtxKey, ctx)
addToReqContext(req, pmKey, pm) 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 // 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.ProjectManager, error) {
if req == nil { if req == nil {
return nil, fmt.Errorf("request is nil") return nil, fmt.Errorf("request is nil")
} }
@ -341,7 +350,7 @@ func GetProjectManager(req *http.Request) (projectmanager.ProjectManager, error)
return nil, fmt.Errorf("the project manager got from request is nil") return nil, fmt.Errorf("the project manager got from request is nil")
} }
p, ok := pm.(projectmanager.ProjectManager) p, ok := pm.(promgr.ProjectManager)
if !ok { if !ok {
return nil, fmt.Errorf("the variable got from request is not project manager type") return nil, fmt.Errorf("the variable got from request is not project manager type")
} }

View File

@ -37,8 +37,8 @@ import (
_ "github.com/vmware/harbor/src/ui/auth/db" _ "github.com/vmware/harbor/src/ui/auth/db"
_ "github.com/vmware/harbor/src/ui/auth/ldap" _ "github.com/vmware/harbor/src/ui/auth/ldap"
"github.com/vmware/harbor/src/ui/config" "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/projectmanager/db" driver_local "github.com/vmware/harbor/src/ui/promgr/pmsdriver/local"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -316,9 +316,9 @@ func TestGetProjectManager(t *testing.T) {
req, err = http.NewRequest("", "", nil) req, err = http.NewRequest("", "", nil)
assert.Nil(t, err) assert.Nil(t, err)
req = req.WithContext(context.WithValue(req.Context(), req = req.WithContext(context.WithValue(req.Context(),
pmKey, &db.ProjectManager{})) pmKey, promgr.NewDefaultProjectManager(driver_local.NewDriver(), true)))
pm, err = GetProjectManager(req) pm, err = GetProjectManager(req)
assert.Nil(t, err) assert.Nil(t, err)
_, ok := pm.(projectmanager.ProjectManager) _, ok := pm.(promgr.ProjectManager)
assert.True(t, ok) assert.True(t, ok)
} }

View File

@ -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
}

View File

@ -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

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package pms package admiral
import ( import (
"bytes" "bytes"

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package pms package admiral
import ( import (
"net/http" "net/http"
@ -196,7 +196,7 @@ func TestGet(t *testing.T) {
// get by invalid ID // get by invalid ID
project, err := pm.Get(int64(0)) project, err := pm.Get(int64(0))
assert.NotNil(t, err) assert.Nil(t, err)
assert.Nil(t, project) assert.Nil(t, project)
// get by invalid name // get by invalid name

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package pms package admiral
import ( import (
"io/ioutil" "io/ioutil"

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package pms package admiral
import ( import (
"io/ioutil" "io/ioutil"

View File

@ -12,25 +12,25 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package projectmanager package pmsdriver
import ( import (
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
) )
// ProjectManager is the project mamager which abstracts the operations related // PMSDriver defines the operations that a project management service driver
// to projects // should implement
type ProjectManager interface { type PMSDriver interface {
// Get a project by ID or name
Get(projectIDOrName interface{}) (*models.Project, error) Get(projectIDOrName interface{}) (*models.Project, error)
IsPublic(projectIDOrName interface{}) (bool, error) // Create a project
Exist(projectIDOrName interface{}) (bool, error)
// get all public project
GetPublic() ([]*models.Project, error)
Create(*models.Project) (int64, error) Create(*models.Project) (int64, error)
// Delete a project by ID or name
Delete(projectIDOrName interface{}) error Delete(projectIDOrName interface{}) error
// Update the properties of a project
Update(projectIDOrName interface{}, project *models.Project) error Update(projectIDOrName interface{}, project *models.Project) error
// GetAll returns a project list according to the query parameters // List lists projects according to the query conditions
GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error) // TODO remove base
// GetTotal returns the total count according to the query parameters List(query *models.ProjectQueryParam,
GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error) base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error)
} }

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package db package local
import ( import (
"fmt" "fmt"
@ -21,61 +21,39 @@ import (
"github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils"
errutil "github.com/vmware/harbor/src/common/utils/error" errutil "github.com/vmware/harbor/src/common/utils/error"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver"
) )
const dupProjectPattern = `Duplicate entry '\w+' for key 'name'` const dupProjectPattern = `Duplicate entry '\w+' for key 'name'`
// ProjectManager implements pm.PM interface based on database type driver struct {
type ProjectManager struct{} }
// NewDriver returns an instance of driver
func NewDriver() pmsdriver.PMSDriver {
return &driver{}
}
// Get ... // Get ...
func (p *ProjectManager) Get(projectIDOrName interface{}) ( func (d *driver) Get(projectIDOrName interface{}) (
*models.Project, error) { *models.Project, error) {
switch projectIDOrName.(type) { id, name, err := utils.ParseProjectIDOrName(projectIDOrName)
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)
if err != nil { if err != nil {
return false, err return nil, 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
} }
if project == nil { if id > 0 {
return false, nil return dao.GetProjectByID(id)
} }
return project.Public == 1, nil return dao.GetProjectByName(name)
}
// GetPublic returns all public projects
func (p *ProjectManager) GetPublic() ([]*models.Project, error) {
t := true
return p.GetAll(&models.ProjectQueryParam{
Public: &t,
})
} }
// Create ... // Create ...
func (p *ProjectManager) Create(project *models.Project) (int64, error) { func (d *driver) Create(project *models.Project) (int64, error) {
if project == nil { if project == nil {
return 0, fmt.Errorf("project is nil") return 0, fmt.Errorf("project is nil")
} }
@ -128,10 +106,13 @@ func (p *ProjectManager) Create(project *models.Project) (int64, error) {
} }
// Delete ... // Delete ...
func (p *ProjectManager) Delete(projectIDOrName interface{}) error { func (d *driver) Delete(projectIDOrName interface{}) error {
id, ok := projectIDOrName.(int64) id, name, err := utils.ParseProjectIDOrName(projectIDOrName)
if !ok { if err != nil {
project, err := p.Get(projectIDOrName) return err
}
if len(name) > 0 {
project, err := dao.GetProjectByName(name)
if err != nil { if err != nil {
return err return err
} }
@ -142,27 +123,31 @@ func (p *ProjectManager) Delete(projectIDOrName interface{}) error {
} }
// Update ... // Update ...
func (p *ProjectManager) Update(projectIDOrName interface{}, func (d *driver) Update(projectIDOrName interface{},
project *models.Project) error { project *models.Project) error {
id, ok := projectIDOrName.(int64) // nil implement
if !ok { return nil
pro, err := p.Get(projectIDOrName) }
if err != nil {
return err // TODO remove base
} // List returns a project list according to the query parameters
id = pro.ProjectID 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 (d *driver) EnableExternalMetaMgr() bool {
func (p *ProjectManager) GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ( return true
[]*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...)
} }

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package db package local
import ( import (
"os" "os"
@ -71,7 +71,7 @@ func TestMain(m *testing.M) {
} }
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
pm := &ProjectManager{} pm := &driver{}
// project name // project name
project, err := pm.Get("library") project, err := pm.Get("library")
@ -95,45 +95,8 @@ func TestGet(t *testing.T) {
assert.NotNil(t, err) 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) { func TestCreateAndDelete(t *testing.T) {
pm := &ProjectManager{} pm := &driver{}
// nil project // nil project
_, err := pm.Create(nil) _, err := pm.Create(nil)
@ -183,63 +146,12 @@ func TestCreateAndDelete(t *testing.T) {
} }
func TestUpdate(t *testing.T) { func TestUpdate(t *testing.T) {
pm := &ProjectManager{} pm := &driver{}
assert.Nil(t, pm.Update(1, nil))
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)
} }
func TestGetTotal(t *testing.T) { func TestList(t *testing.T) {
pm := &ProjectManager{} pm := &driver{}
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{}
id, err := pm.Create(&models.Project{ id, err := pm.Create(&models.Project{
Name: "get_all_test", Name: "get_all_test",
@ -250,19 +162,19 @@ func TestGetAll(t *testing.T) {
defer pm.Delete(id) defer pm.Delete(id)
// get by name // get by name
projects, err := pm.GetAll(&models.ProjectQueryParam{ result, err := pm.List(&models.ProjectQueryParam{
Name: "get_all_test", Name: "get_all_test",
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, id, projects[0].ProjectID) assert.Equal(t, id, result.Projects[0].ProjectID)
// get by owner // get by owner
projects, err = pm.GetAll(&models.ProjectQueryParam{ result, err = pm.List(&models.ProjectQueryParam{
Owner: "admin", Owner: "admin",
}) })
assert.Nil(t, err) assert.Nil(t, err)
exist := false exist := false
for _, project := range projects { for _, project := range result.Projects {
if project.ProjectID == id { if project.ProjectID == id {
exist = true exist = true
break break
@ -272,12 +184,12 @@ func TestGetAll(t *testing.T) {
// get by public // get by public
value := true value := true
projects, err = pm.GetAll(&models.ProjectQueryParam{ result, err = pm.List(&models.ProjectQueryParam{
Public: &value, Public: &value,
}) })
assert.Nil(t, err) assert.Nil(t, err)
exist = false exist = false
for _, project := range projects { for _, project := range result.Projects {
if project.ProjectID == id { if project.ProjectID == id {
exist = true exist = true
break break

172
src/ui/promgr/promgr.go Normal file
View File

@ -0,0 +1,172 @@
// 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 (
"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"
)
// ProjectManager is the project mamager which abstracts the operations related
// to projects
type ProjectManager interface {
Get(projectIDOrName interface{}) (*models.Project, error)
Create(*models.Project) (int64, error)
Delete(projectIDOrName interface{}) error
Update(projectIDOrName interface{}, project *models.Project) 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
}

View File

@ -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)
}

View File

@ -2,14 +2,14 @@ package proxy
import ( import (
"github.com/stretchr/testify/assert" "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/adminserver/client"
"github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
notarytest "github.com/vmware/harbor/src/common/utils/notary/test" notarytest "github.com/vmware/harbor/src/common/utils/notary/test"
utilstest "github.com/vmware/harbor/src/common/utils/test" utilstest "github.com/vmware/harbor/src/common/utils/test"
"github.com/vmware/harbor/src/ui/config" "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"
"net/http/httptest" "net/http/httptest"
@ -128,6 +128,8 @@ func TestEnvPolicyChecker(t *testing.T) {
assert.Equal(sev, models.SevNone) assert.Equal(sev, models.SevNone)
} }
// TODO uncheck after admiral pms driver is implemented
/*
func TestPMSPolicyChecker(t *testing.T) { func TestPMSPolicyChecker(t *testing.T) {
var defaultConfigAdmiral = map[string]interface{}{ var defaultConfigAdmiral = map[string]interface{}{
common.ExtEndpoint: "https://" + endpoint, common.ExtEndpoint: "https://" + endpoint,
@ -147,9 +149,8 @@ func TestPMSPolicyChecker(t *testing.T) {
if err := config.Init(); err != nil { if err := config.Init(); err != nil {
panic(err) panic(err)
} }
pm := admiral.NewProjectManager(http.DefaultClient,
pm := pms.NewProjectManager(http.DefaultClient, admiralEndpoint, &admiral.RawTokenReader{
admiralEndpoint, &pms.RawTokenReader{
Token: "token", Token: "token",
}) })
name := "project_for_test_get_sev_low" name := "project_for_test_get_sev_low"
@ -175,7 +176,7 @@ func TestPMSPolicyChecker(t *testing.T) {
assert.False(t, projectVulnerableEnabled) assert.False(t, projectVulnerableEnabled)
assert.Equal(t, projectVulnerableSeverity, models.SevLow) assert.Equal(t, projectVulnerableSeverity, models.SevLow)
} }
*/
func TestMatchNotaryDigest(t *testing.T) { func TestMatchNotaryDigest(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
//The data from common/utils/notary/helper_test.go //The data from common/utils/notary/helper_test.go

View File

@ -9,7 +9,7 @@ import (
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/utils/notary" "github.com/vmware/harbor/src/common/utils/notary"
"github.com/vmware/harbor/src/ui/config" "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" uiutils "github.com/vmware/harbor/src/ui/utils"
"context" "context"
@ -88,7 +88,7 @@ func (ec envPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity)
} }
type pmsPolicyChecker struct { type pmsPolicyChecker struct {
pm projectmanager.ProjectManager pm promgr.ProjectManager
} }
func (pc pmsPolicyChecker) contentTrustEnabled(name string) bool { 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 // newPMSPolicyChecker returns an instance of an pmsPolicyChecker
func newPMSPolicyChecker(pm projectmanager.ProjectManager) policyChecker { func newPMSPolicyChecker(pm promgr.ProjectManager) policyChecker {
return &pmsPolicyChecker{ return &pmsPolicyChecker{
pm: pm, pm: pm,
} }

View File

@ -29,7 +29,7 @@ import (
"github.com/vmware/harbor/src/common/security" "github.com/vmware/harbor/src/common/security"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
promgr "github.com/vmware/harbor/src/ui/projectmanager" "github.com/vmware/harbor/src/ui/promgr"
) )
const ( const (

View File

@ -26,7 +26,7 @@ import (
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/filter" "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 var creatorMap map[string]Creator
@ -161,7 +161,7 @@ func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProjectManage
project := img.namespace project := img.namespace
permission := "" permission := ""
exist, err := pm.Exist(project) exist, err := pm.Exists(project)
if err != nil { if err != nil {
return err return err
} }

View File

@ -26,7 +26,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { ReplicationService } from '../service/replication.service'; 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 { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message'; import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
@ -70,6 +70,7 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
rules: ReplicationRule[]; rules: ReplicationRule[];
changedRules: ReplicationRule[]; changedRules: ReplicationRule[];
ruleName: string; ruleName: string;
canDeleteRule: boolean;
@ViewChild('toggleConfirmDialog') @ViewChild('toggleConfirmDialog')
toggleConfirmDialog: ConfirmationDialogComponent; toggleConfirmDialog: ConfirmationDialogComponent;
@ -199,15 +200,48 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
this.toggleConfirmDialog.open(toggleConfirmMessage); this.toggleConfirmDialog.open(toggleConfirmMessage);
} }
jobList(): Promise<void> {
let ruleData: ReplicationJobItem[];
this.canDeleteRule = true;
let count: number = 0;
return toPromise<ReplicationJob>(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) { deleteRule(rule: ReplicationRule) {
let deletionMessage: ConfirmationMessage = new ConfirmationMessage( this.jobList().then(() => {
'REPLICATION.DELETION_TITLE', let deletionMessage: ConfirmationMessage;
'REPLICATION.DELETION_SUMMARY', if (!this.canDeleteRule) {
rule.name || '', deletionMessage = new ConfirmationMessage(
rule.id, 'REPLICATION.DELETION_TITLE_FAILURE',
ConfirmationTargets.POLICY, 'REPLICATION.DELETION_SUMMARY_FAILURE',
ConfirmationButtons.DELETE_CANCEL); rule.name || '',
this.deletionConfirmDialog.open(deletionMessage); 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);
});
} }
} }

View File

@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // 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 { ResponseOptions, RequestOptions } from '@angular/http';
import { NgModel } from '@angular/forms'; import { NgModel } from '@angular/forms';
@ -41,6 +41,8 @@ import { REPLICATION_STYLE } from './replication.component.css';
import { JobLogViewerComponent } from '../job-log-viewer/index'; import { JobLogViewerComponent } from '../job-log-viewer/index';
import { State } from "clarity-angular"; import { State } from "clarity-angular";
import {Observable} from "rxjs/Observable";
import {Subscription} from "rxjs/Subscription";
const ruleStatus: { [key: string]: any } = [ const ruleStatus: { [key: string]: any } = [
{ 'key': 'all', 'description': 'REPLICATION.ALL_STATUS' }, { 'key': 'all', 'description': 'REPLICATION.ALL_STATUS' },
@ -79,7 +81,7 @@ export class SearchOption {
template: REPLICATION_TEMPLATE, template: REPLICATION_TEMPLATE,
styles: [REPLICATION_STYLE] styles: [REPLICATION_STYLE]
}) })
export class ReplicationComponent implements OnInit { export class ReplicationComponent implements OnInit, OnDestroy {
@Input() projectId: number | string; @Input() projectId: number | string;
@Input() withReplicationJob: boolean; @Input() withReplicationJob: boolean;
@ -124,6 +126,7 @@ export class ReplicationComponent implements OnInit {
pageSize: number = DEFAULT_PAGE_SIZE; pageSize: number = DEFAULT_PAGE_SIZE;
currentState: State; currentState: State;
jobsLoading: boolean = false; jobsLoading: boolean = false;
timerDelay: Subscription;
constructor( constructor(
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
@ -145,6 +148,12 @@ export class ReplicationComponent implements OnInit {
this.currentJobSearchOption = 0; this.currentJobSearchOption = 0;
} }
ngOnDestroy() {
if (this.timerDelay) {
this.timerDelay.unsubscribe();
}
}
openModal(): void { openModal(): void {
this.createEditPolicyComponent.openCreateEditRule(true); this.createEditPolicyComponent.openCreateEditRule(true);
} }
@ -197,6 +206,23 @@ export class ReplicationComponent implements OnInit {
this.totalCount = response.metadata.xTotalCount; this.totalCount = response.metadata.xTotalCount;
this.jobs = response.data; 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 //Do filtering and sorting
this.jobs = doFiltering<ReplicationJobItem>(this.jobs, state); this.jobs = doFiltering<ReplicationJobItem>(this.jobs, state);
this.jobs = doSorting<ReplicationJobItem>(this.jobs, state); this.jobs = doSorting<ReplicationJobItem>(this.jobs, state);

View File

@ -75,6 +75,7 @@ describe('RepositoryComponentStackview (inline template)', () => {
{ {
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
"name": "1.11.5", "name": "1.11.5",
"size": "2049",
"architecture": "amd64", "architecture": "amd64",
"os": "linux", "os": "linux",
"docker_version": "1.12.3", "docker_version": "1.12.3",

View File

@ -51,6 +51,7 @@ export interface Repository {
export interface Tag extends Base { export interface Tag extends Base {
digest: string; digest: string;
name: string; name: string;
size: string;
architecture: string; architecture: string;
os: string; os: string;
docker_version: string; docker_version: string;

View File

@ -14,6 +14,7 @@ describe('TagService', () => {
{ {
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
"name": "1.11.5", "name": "1.11.5",
"size": "2049",
"architecture": "amd64", "architecture": "amd64",
"os": "linux", "os": "linux",
"docker_version": "1.12.3", "docker_version": "1.12.3",

View File

@ -43,6 +43,7 @@ describe('TagDetailComponent (inline template)', () => {
let mockTag: Tag = { let mockTag: Tag = {
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
"name": "nginx", "name": "nginx",
"size": "2049",
"architecture": "amd64", "architecture": "amd64",
"os": "linux", "os": "linux",
"docker_version": "1.12.3", "docker_version": "1.12.3",

View File

@ -24,6 +24,7 @@ export class TagDetailComponent implements OnInit {
@Input() repositoryId: string; @Input() repositoryId: string;
tagDetails: Tag = { tagDetails: Tag = {
name: "--", name: "--",
size: "--",
author: "--", author: "--",
created: new Date(), created: new Date(),
architecture: "--", architecture: "--",

View File

@ -43,4 +43,9 @@ export const TAG_STYLE = `
color: red; color: red;
margin-right: 6px; margin-right: 6px;
} }
:host >>> .datagrid clr-dg-column {
min-width: 80px;
}
`; `;

View File

@ -16,14 +16,13 @@ export const TAG_TEMPLATE = `
<h2 *ngIf="!isEmbedded" class="sub-header-title">{{repoName}}</h2> <h2 *ngIf="!isEmbedded" class="sub-header-title">{{repoName}}</h2>
<clr-datagrid [clrDgLoading]="loading" [class.embeded-datagrid]="isEmbedded"> <clr-datagrid [clrDgLoading]="loading" [class.embeded-datagrid]="isEmbedded">
<clr-dg-column style="width: 80px;" [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column> <clr-dg-column style="width: 80px;" [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
<clr-dg-column style="width: 80px;" [clrDgField]="'size'">{{'REPOSITORY.SIZE' | translate}}</clr-dg-column>
<clr-dg-column style="min-width: 180px;">{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column> <clr-dg-column style="min-width: 180px;">{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
<clr-dg-column style="width: 160px;" *ngIf="withClair">{{'VULNERABILITY.SINGULAR' | translate}}</clr-dg-column> <clr-dg-column style="width: 160px;" *ngIf="withClair">{{'VULNERABILITY.SINGULAR' | translate}}</clr-dg-column>
<clr-dg-column style="width: 80px;" *ngIf="withNotary">{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column> <clr-dg-column style="width: 80px;" *ngIf="withNotary">{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
<clr-dg-column style="width: 100px;">{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column> <clr-dg-column style="width: 100px;">{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
<clr-dg-column style="width: 160px;"[clrDgSortBy]="createdComparator">{{'REPOSITORY.CREATED' | translate}}</clr-dg-column> <clr-dg-column style="width: 160px;"[clrDgSortBy]="createdComparator">{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
<clr-dg-column style="width: 80px;" [clrDgField]="'docker_version'" *ngIf="!withClair">{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column> <clr-dg-column style="width: 80px;" [clrDgField]="'docker_version'" *ngIf="!withClair">{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
<clr-dg-column style="width: 80px;" [clrDgField]="'architecture'" *ngIf="!withClair">{{'REPOSITORY.ARCHITECTURE' | translate}}</clr-dg-column>
<clr-dg-column style="width: 80px;" [clrDgField]="'os'" *ngIf="!withClair">{{'REPOSITORY.OS' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'TGA.PLACEHOLDER' | translate }}</clr-dg-placeholder> <clr-dg-placeholder>{{'TGA.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *clrDgItems="let t of tags" [clrDgItem]='t'> <clr-dg-row *clrDgItems="let t of tags" [clrDgItem]='t'>
<clr-dg-action-overflow> <clr-dg-action-overflow>
@ -31,10 +30,11 @@ export const TAG_TEMPLATE = `
<button class="action-item" *ngIf="hasProjectAdminRole" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button> <button class="action-item" *ngIf="hasProjectAdminRole" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button>
<button class="action-item" (click)="showDigestId(t)">{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</button> <button class="action-item" (click)="showDigestId(t)">{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</button>
</clr-dg-action-overflow> </clr-dg-action-overflow>
<clr-dg-cell style="width: 80px;" [ngSwitch]="withClair"> <clr-dg-cell style="width: 80px;" [ngSwitch]="existObservablePackage(t)">
<a *ngSwitchCase="true" href="javascript:void(0)" (click)="onTagClick(t)">{{t.name}}</a> <a *ngSwitchCase="true" href="javascript:void(0)" (click)="onTagClick(t)">{{t.name}}</a>
<span *ngSwitchDefault>{{t.name}}</span> <span *ngSwitchDefault>{{t.name}}</span>
</clr-dg-cell> </clr-dg-cell>
<clr-dg-cell style="width: 80px;">{{t.size}}</clr-dg-cell>
<clr-dg-cell style="min-width: 180px;" class="truncated" title="docker pull {{registryUrl}}/{{repoName}}:{{t.name}}">docker pull {{registryUrl}}/{{repoName}}:{{t.name}}</clr-dg-cell> <clr-dg-cell style="min-width: 180px;" class="truncated" title="docker pull {{registryUrl}}/{{repoName}}:{{t.name}}">docker pull {{registryUrl}}/{{repoName}}:{{t.name}}</clr-dg-cell>
<clr-dg-cell style="width: 160px;" *ngIf="withClair"> <clr-dg-cell style="width: 160px;" *ngIf="withClair">
<hbr-vulnerability-bar [repoName]="repoName" [tagId]="t.name" [summary]="t.scan_overview"></hbr-vulnerability-bar> <hbr-vulnerability-bar [repoName]="repoName" [tagId]="t.name" [summary]="t.scan_overview"></hbr-vulnerability-bar>
@ -50,12 +50,10 @@ export const TAG_TEMPLATE = `
<clr-dg-cell style="width: 100px;">{{t.author}}</clr-dg-cell> <clr-dg-cell style="width: 100px;">{{t.author}}</clr-dg-cell>
<clr-dg-cell style="width: 160px;">{{t.created | date: 'short'}}</clr-dg-cell> <clr-dg-cell style="width: 160px;">{{t.created | date: 'short'}}</clr-dg-cell>
<clr-dg-cell style="width: 80px;" *ngIf="!withClair">{{t.docker_version}}</clr-dg-cell> <clr-dg-cell style="width: 80px;" *ngIf="!withClair">{{t.docker_version}}</clr-dg-cell>
<clr-dg-cell style="width: 80px;" *ngIf="!withClair">{{t.architecture}}</clr-dg-cell>
<clr-dg-cell style="width: 80px;" *ngIf="!withClair">{{t.os}}</clr-dg-cell>
</clr-dg-row> </clr-dg-row>
<clr-dg-footer> <clr-dg-footer>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span> <span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}&nbsp;&nbsp;&nbsp;&nbsp; {{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}&nbsp;&nbsp;&nbsp;&nbsp;
<clr-dg-pagination #pagination [clrDgPageSize]="10"></clr-dg-pagination> <clr-dg-pagination #pagination [clrDgPageSize]="10"></clr-dg-pagination>
</clr-dg-footer> </clr-dg-footer>
</clr-datagrid>`; </clr-datagrid>`;

View File

@ -29,6 +29,7 @@ describe('TagComponent (inline template)', () => {
{ {
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
"name": "1.11.5", "name": "1.11.5",
"size": "2049",
"architecture": "amd64", "architecture": "amd64",
"os": "linux", "os": "linux",
"docker_version": "1.12.3", "docker_version": "1.12.3",

View File

@ -159,6 +159,9 @@ export class TagComponent implements OnInit {
if (t.signature !== null) { if (t.signature !== null) {
signatures.push(t.name); signatures.push(t.name);
} }
//size
t.size = this.sizeTransform(t.size);
}); });
this.tags = items; this.tags = items;
let signedName: {[key: string]: string[]} = {}; let signedName: {[key: string]: string[]} = {};
@ -177,6 +180,19 @@ export class TagComponent implements OnInit {
setTimeout(() => clearInterval(hnd), 5000); 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) { deleteTag(tag: Tag) {
if (tag) { if (tag) {
let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons; let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons;
@ -237,7 +253,7 @@ export class TagComponent implements OnInit {
} }
} }
//Get vulnerability scanning status //Get vulnerability scanning status
scanStatus(t: Tag): string { scanStatus(t: Tag): string {
if (t && t.scan_overview && t.scan_overview.scan_status) { if (t && t.scan_overview && t.scan_overview.scan_status) {
return t.scan_overview.scan_status; return t.scan_overview.scan_status;
@ -246,6 +262,13 @@ export class TagComponent implements OnInit {
return VULNERABILITY_SCAN_STATUS.unknown; 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 //Whether show the 'scan now' menu
canScanNow(t: Tag): boolean { canScanNow(t: Tag): boolean {
if (!this.withClair) { return false; } if (!this.withClair) { return false; }

View File

@ -45,7 +45,9 @@ export class ResultTipComponent implements OnInit {
level = VulnerabilitySeverity.LOW; level = VulnerabilitySeverity.LOW;
}else if (this._unknownCount && this._unknownCount >= 1) { }else if (this._unknownCount && this._unknownCount >= 1) {
level = VulnerabilitySeverity.UNKNOWN; level = VulnerabilitySeverity.UNKNOWN;
}else { }else if (this.totalPackages == 0) {
level = VulnerabilitySeverity.UNKNOWN;
} else {
level = VulnerabilitySeverity.NONE; level = VulnerabilitySeverity.NONE;
} }
return level; return level;
@ -105,8 +107,8 @@ export class ResultTipComponent implements OnInit {
let m: number = this.totalPackages; let m: number = this.totalPackages;
if (m === 0) { if (m === 0) {
//If no packages recognized, then show green bar //If no packages recognized, then show grey
if (severity === VulnerabilitySeverity.NONE) { if (severity === VulnerabilitySeverity.UNKNOWN) {
return MAX_TIP_WIDTH + 'px'; return MAX_TIP_WIDTH + 'px';
} else { } else {
return 0 + 'px'; return 0 + 'px';

View File

@ -31,7 +31,7 @@
"clarity-icons": "^0.9.8", "clarity-icons": "^0.9.8",
"clarity-ui": "^0.9.8", "clarity-ui": "^0.9.8",
"core-js": "^2.4.1", "core-js": "^2.4.1",
"harbor-ui": "0.4.60", "harbor-ui": "0.4.72",
"intl": "^1.2.5", "intl": "^1.2.5",
"mutationobserver-shim": "^0.3.2", "mutationobserver-shim": "^0.3.2",
"ngx-cookie": "^1.0.0", "ngx-cookie": "^1.0.0",

View File

@ -1,4 +1,26 @@
.form-group-label-override { .form-group-label-override {
font-size: 14px; font-size: 14px;
font-weight: 400; 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;
} }

View File

@ -6,16 +6,22 @@
<section class="form-block"> <section class="form-block">
<div class="form-group"> <div class="form-group">
<label for="member_name" class="col-md-4 form-group-label-override required">{{'MEMBER.NAME' | translate}}</label> <label for="member_name" class="col-md-4 form-group-label-override required">{{'MEMBER.NAME' | translate}}</label>
<label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="!isMemberNameValid" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"> <label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="!isMemberNameValid"
<input type="text" id="member_name" [(ngModel)]="member.username" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" (mouseleave)="leaveInput()">
name="member_name" <input type="text" id="member_name" [(ngModel)]="member.username"
name="member_name"
size="20" size="20"
#memberName="ngModel" #memberName="ngModel"
required required
(keyup)='handleValidation()'> (keyup)='handleValidation()' autocomplete="off">
<span class="tooltip-content"> <span class="tooltip-content">
{{ memberTooltip | translate }} {{ memberTooltip | translate }}
</span> </span>
<div class="selectBox" [style.display]="selectUserName.length ? 'block' : 'none'" >
<ul>
<li *ngFor="let name of selectUserName" (click)="selectedName(name)">{{name}}</li>
</ul>
</div>
</label> </label>
<span class="spinner spinner-inline" [hidden]="!checkOnGoing"></span> <span class="spinner spinner-inline" [hidden]="!checkOnGoing"></span>
</div> </div>

View File

@ -25,6 +25,7 @@ import { Response } from '@angular/http';
import { NgForm } from '@angular/forms'; import { NgForm } from '@angular/forms';
import { MemberService } from '../member.service'; import { MemberService } from '../member.service';
import { UserService } from '../../../user/user.service';
import { MessageHandlerService } from '../../../shared/message-handler/message-handler.service'; import { MessageHandlerService } from '../../../shared/message-handler/message-handler.service';
import { InlineAlertComponent } from '../../../shared/inline-alert/inline-alert.component'; import { InlineAlertComponent } from '../../../shared/inline-alert/inline-alert.component';
@ -36,11 +37,13 @@ import { Member } from '../member';
import { Subject } from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/distinctUntilChanged';
import {User} from "../../../user/user";
@Component({ @Component({
selector: 'add-member', selector: 'add-member',
templateUrl: 'add-member.component.html', templateUrl: 'add-member.component.html',
styleUrls: ['add-member.component.css'], styleUrls: ['add-member.component.css'],
providers: [UserService],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy { export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
@ -69,13 +72,21 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
memberTooltip: string = 'MEMBER.USERNAME_IS_REQUIRED'; memberTooltip: string = 'MEMBER.USERNAME_IS_REQUIRED';
nameChecker: Subject<string> = new Subject<string>(); nameChecker: Subject<string> = new Subject<string>();
checkOnGoing: boolean = false; checkOnGoing: boolean = false;
selectUserName: string[] = [];
userLists: User[];
constructor(private memberService: MemberService, constructor(private memberService: MemberService,
private userService: UserService,
private messageHandlerService: MessageHandlerService, private messageHandlerService: MessageHandlerService,
private translateService: TranslateService, private translateService: TranslateService,
private ref: ChangeDetectorRef) { } private ref: ChangeDetectorRef) { }
ngOnInit(): void { ngOnInit(): void {
this.userService.getUsers()
.then(users => {
this.userLists = users;
});
this.nameChecker this.nameChecker
.debounceTime(500) .debounceTime(500)
.distinctUntilChanged() .distinctUntilChanged()
@ -97,6 +108,20 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
.catch(error => { .catch(error => {
this.checkOnGoing = false; 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 { } else {
this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED'; this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED';
} }
@ -148,6 +173,11 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
}, 1000); }, 1000);
} }
selectedName(username: string) {
this.member.username = username;
this.selectUserName = [];
}
onCancel() { onCancel() {
if (this.hasChanged) { if (this.hasChanged) {
this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' }); this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' });
@ -157,6 +187,9 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
} }
} }
leaveInput() {
this.selectUserName = [];
}
ngAfterViewChecked(): void { ngAfterViewChecked(): void {
if (this.memberForm !== this.currentForm) { if (this.memberForm !== this.currentForm) {
this.memberForm = this.currentForm; this.memberForm = this.currentForm;
@ -189,6 +222,7 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
this.member.username = ''; this.member.username = '';
this.isMemberNameValid = true; this.isMemberNameValid = true;
this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED'; this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED';
this.selectUserName = [];
} }
handleValidation(): void { handleValidation(): void {

View File

@ -215,6 +215,8 @@
"FILTER_JOBS_PLACEHOLDER": "Filter Jobs", "FILTER_JOBS_PLACEHOLDER": "Filter Jobs",
"DELETION_TITLE": "Confirm Rule Deletion", "DELETION_TITLE": "Confirm Rule Deletion",
"DELETION_SUMMARY": "Do you want to delete rule {{param}}?", "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", "FILTER_TARGETS_PLACEHOLDER": "Filter Endpoints",
"DELETION_TITLE_TARGET": "Confirm Endpoint Deletion", "DELETION_TITLE_TARGET": "Confirm Endpoint Deletion",
"DELETION_SUMMARY_TARGET": "Do you want to delete the endpoint {{param}}?", "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}}", "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", "FILTER_FOR_REPOSITORIES": "Filter Repositories",
"TAG": "Tag", "TAG": "Tag",
"SIZE": "Size",
"SIGNED": "Signed", "SIGNED": "Signed",
"AUTHOR": "Author", "AUTHOR": "Author",
"CREATED": "Creation Time", "CREATED": "Creation Time",

View File

@ -215,6 +215,8 @@
"FILTER_JOBS_PLACEHOLDER": "Filtrar Trabajos", "FILTER_JOBS_PLACEHOLDER": "Filtrar Trabajos",
"DELETION_TITLE": "Confirmar Eliminación de Regla", "DELETION_TITLE": "Confirmar Eliminación de Regla",
"DELETION_SUMMARY": "¿Quiere eliminar la regla {{param}}?", "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", "FILTER_TARGETS_PLACEHOLDER": "Filtrar Endpoints",
"DELETION_TITLE_TARGET": "Confirmar Eliminación de Endpoint", "DELETION_TITLE_TARGET": "Confirmar Eliminación de Endpoint",
"DELETION_SUMMARY_TARGET": "¿Quiere eliminar el endpoint {{param}}?", "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}}", "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", "FILTER_FOR_REPOSITORIES": "Filtrar Repositorios",
"TAG": "Etiqueta", "TAG": "Etiqueta",
"SIZE": "Size",
"SIGNED": "Firmada", "SIGNED": "Firmada",
"AUTHOR": "Autor", "AUTHOR": "Autor",
"CREATED": "Fecha de creación", "CREATED": "Fecha de creación",

View File

@ -215,6 +215,8 @@
"FILTER_JOBS_PLACEHOLDER": "过滤任务", "FILTER_JOBS_PLACEHOLDER": "过滤任务",
"DELETION_TITLE": "删除规则确认", "DELETION_TITLE": "删除规则确认",
"DELETION_SUMMARY": "确认删除规则 {{param}}?", "DELETION_SUMMARY": "确认删除规则 {{param}}?",
"DELETION_TITLE_FAILURE": "规则确认删除失败",
"DELETION_SUMMARY_FAILURE": "{{param}} 有 pending/running/retrying 状态,不能删除",
"FILTER_TARGETS_PLACEHOLDER": "过滤目标", "FILTER_TARGETS_PLACEHOLDER": "过滤目标",
"DELETION_TITLE_TARGET": "删除目标确认", "DELETION_TITLE_TARGET": "删除目标确认",
"DELETION_SUMMARY_TARGET": "确认删除目标 {{param}}?", "DELETION_SUMMARY_TARGET": "确认删除目标 {{param}}?",
@ -330,6 +332,7 @@
"DELETION_SUMMARY_TAG_DENIED": "要删除此镜像标签必须首先从Notary中删除。\n请执行如下Notary命令删除:\n{{param}}", "DELETION_SUMMARY_TAG_DENIED": "要删除此镜像标签必须首先从Notary中删除。\n请执行如下Notary命令删除:\n{{param}}",
"FILTER_FOR_REPOSITORIES": "过滤镜像仓库", "FILTER_FOR_REPOSITORIES": "过滤镜像仓库",
"TAG": "标签", "TAG": "标签",
"SIZE": "大小",
"SIGNED": "已签名", "SIGNED": "已签名",
"AUTHOR": "作者", "AUTHOR": "作者",
"CREATED": "创建时间", "CREATED": "创建时间",

View File

@ -40,11 +40,24 @@ Push image
Log To Console \nRunning docker push ${image}... Log To Console \nRunning docker push ${image}...
${rc}= Run And Return Rc docker pull ${image} ${rc}= Run And Return Rc docker pull ${image}
${rc} ${output}= Run And Return Rc And Output docker login -u ${user} -p ${pwd} ${ip} ${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 Should Be Equal As Integers ${rc} 0
${rc}= Run And Return Rc docker tag ${image} ${ip}/${project}/${image} ${rc}= Run And Return Rc docker tag ${image} ${ip}/${project}/${image}
${rc} ${output}= Run And Return Rc And Output docker push ${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 Should Be Equal As Integers ${rc} 0
${rc}= Run And Return Rc docker logout ${ip} ${rc}= Run And Return Rc docker logout ${ip}
@ -56,6 +69,19 @@ Cannot Pull image
Log To Console ${output} Log To Console ${output}
Should Not Be Equal As Integers ${rc} 0 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 Wait Until Container Stops
[Arguments] ${container} [Arguments] ${container}
:FOR ${idx} IN RANGE 0 60 :FOR ${idx} IN RANGE 0 60

View File

@ -46,96 +46,96 @@ Switch To Configure
Sleep 2 Sleep 2
Set Pro Create Admin Only Set Pro Create Admin Only
#set limit to admin only #set limit to admin only
Sleep 2 Sleep 2
Click Element xpath=//clr-main-container//nav//ul/li[3] 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"]
Click Element xpath=//select[@id="proCreation"]//option[@value="adminonly"] 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] 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 Pro Create Every One
#set limit to Every One #set limit to Every One
Click Element xpath=//clr-main-container//nav//ul/li[3] 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"]
Click Element xpath=//select[@id="proCreation"]//option[@value="everyone"] 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] Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1]
Sleep 2 Sleep 2
Capture Page Screenshot EveryoneCreate.png Capture Page Screenshot EveryoneCreate.png
Disable Self Reg 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 Down xpath=${self_reg_xpath}
Mouse Up xpath=${self_reg_xpath} Mouse Up xpath=${self_reg_xpath}
Sleep 1 Sleep 1
Self Reg Should Be Disabled 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] 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 Capture Page Screenshot DisableSelfReg.png
Sleep 1 Sleep 1
Enable Self Reg Enable Self Reg
Mouse Down xpath=${self_reg_xpath} Mouse Down xpath=${self_reg_xpath}
Mouse Up xpath=${self_reg_xpath} Mouse Up xpath=${self_reg_xpath}
Sleep 1 Sleep 1
Self Reg Should Be Enabled 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] 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 Capture Page Screenshot EnableSelfReg.png
Sleep 1 Sleep 1
Self Reg Should Be Disabled 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 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 Project Creation Should Display
Page Should Contain Element xpath=${project_create_xpath} Page Should Contain Element xpath=${project_create_xpath}
Project Creation Should Not Display Project Creation Should Not Display
Page Should Not Contain Element xpath=${project_create_xpath} Page Should Not Contain Element xpath=${project_create_xpath}
## System settings ## System settings
Switch To System Settings Switch To System Settings
Sleep 1 Sleep 1
Click Element xpath=//clr-main-container//nav//ul/li[3] Click Element xpath=//clr-main-container//nav//ul/li[3]
Click Element xpath=//config//ul/li[4] Click Element xpath=//config//ul/li[4]
Modify Token Expiration Modify Token Expiration
[Arguments] ${minutes} [Arguments] ${minutes}
Input Text xpath=//*[@id="tokenExpiration"] ${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] 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 Token Must Be Match
[Arguments] ${minutes} [Arguments] ${minutes}
Textfield Value Should Be xpath=//*[@id="tokenExpiration"] ${minutes} Textfield Value Should Be xpath=//*[@id="tokenExpiration"] ${minutes}
## Replication ## Replication
Check Verify Remote Cert Check Verify Remote Cert
Mouse Down xpath=//*[@id="clr-checkbox-verifyRemoteCert"] Mouse Down xpath=//*[@id="clr-checkbox-verifyRemoteCert"]
Mouse Up 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] 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 Capture Page Screenshot RemoteCert.png
Sleep 1 Sleep 1
Switch To System Replication Switch To System Replication
Sleep 1 Sleep 1
Switch To Configure Switch To Configure
Click Element xpath=//*[@id="config-replication"] Click Element xpath=//*[@id="config-replication"]
Sleep 1 Sleep 1
Should Verify Remote Cert Be Enabled 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 ## Email
Switch To Email Switch To Email
Switch To Configure Switch To Configure
Click Element xpath=//*[@id="config-email"] Click Element xpath=//*[@id="config-email"]
Sleep 1 Sleep 1
Config Email Config Email
Input Text xpath=//*[@id="mailServer"] smtp.vmware.com 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="emailUsername"] example@vmware.com
Input Text xpath=//*[@id="emailPassword"] example Input Text xpath=//*[@id="emailPassword"] example
Input Text xpath=//*[@id="emailFrom"] example<example@vmware.com> Input Text xpath=//*[@id="emailFrom"] example<example@vmware.com>
Sleep 1 Sleep 1
Mouse Down xpath=//*[@id="clr-checkbox-emailSSL"] Mouse Down xpath=//*[@id="clr-checkbox-emailSSL"]
Mouse Up 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] 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 Verify Email
Textfield Value Should Be xpath=//*[@id="mailServer"] smtp.vmware.com Textfield Value Should Be xpath=//*[@id="mailServer"] smtp.vmware.com
Textfield Value Should Be xpath=//*[@id="emailPort"] 25 Textfield Value Should Be xpath=//*[@id="emailPort"] 25
@ -168,5 +168,4 @@ Set Scan All To Daily
sleep 1 sleep 1
click element //config//div/button[contains(.,'SAVE')] click element //config//div/button[contains(.,'SAVE')]
Click Scan Now Click Scan Now
click element //vulnerability-config//button[contains(.,'SCAN')] click element //vulnerability-config//button[contains(.,'SCAN')]

View File

@ -22,32 +22,32 @@ ${HARBOR_VERSION} v1.1.1
*** Keywords *** *** Keywords ***
Sign In Harbor Sign In Harbor
[Arguments] ${url} ${user} ${pw} [Arguments] ${url} ${user} ${pw}
Go To ${url} Go To ${url}
Sleep 5 Sleep 5
${title}= Get Title ${title}= Get Title
Log To Console ${title} Log To Console ${title}
Should Be Equal ${title} Harbor Should Be Equal ${title} Harbor
Sleep 2 Sleep 2
Input Text login_username ${user} Input Text login_username ${user}
Input Text login_password ${pw} Input Text login_password ${pw}
Sleep 2 Sleep 2
Click button css=.btn Click button css=.btn
sleep 5 sleep 5
Log To Console ${user} Log To Console ${user}
Wait Until Page Contains ${user} Wait Until Page Contains ${user}
Sign Up Should Not Display 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 Create An New User
[Arguments] ${url} ${username} ${email} ${realname} ${newPassword} ${comment} [Arguments] ${url} ${username} ${email} ${realname} ${newPassword} ${comment}
Go To ${url} Go To ${url}
sleep 5 sleep 5
${title}= Get Title ${title}= Get Title
Log To Console ${title} Log To Console ${title}
Should Be Equal ${title} Harbor Should Be Equal ${title} Harbor
${d}= Get Current Date result_format=%m%s ${d}= Get Current Date result_format=%m%s
Sleep 5 Sleep 5
Click Element xpath=${sign_up_for_an_account_xpath} Click Element xpath=${sign_up_for_an_account_xpath}
sleep 3 sleep 3
Input Text xpath=${username_xpath} ${username} Input Text xpath=${username_xpath} ${username}
@ -70,4 +70,4 @@ Create An New User
Click button css=.btn Click button css=.btn
sleep 5 sleep 5
Wait Until Page Contains ${username} Wait Until Page Contains ${username}
Sleep 3 Sleep 3

View File

@ -21,69 +21,167 @@ ${HARBOR_VERSION} v1.1.1
*** Keywords *** *** Keywords ***
Go Into Project Go Into Project
[Arguments] ${project} [Arguments] ${project}
Sleep 2 Sleep 2
Click Element xpath=//*[@id="search_input"] Click Element xpath=//*[@id="search_input"]
Sleep 2 Sleep 2
Input Text xpath=//*[@id="search_input"] ${project} Input Text xpath=//*[@id="search_input"] ${project}
Sleep 8 Sleep 8
Wait Until Page Contains ${project} 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 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 Sleep 2
Capture Page Screenshot gointo_${project}.png Capture Page Screenshot gointo_${project}.png
Go Into Project2 Go Into Project2
[Arguments] ${project} [Arguments] ${project}
Sleep 2 Sleep 2
Capture Page Screenshot gointo1_${project}.png Capture Page Screenshot gointo1_${project}.png
# search icon # 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 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 Sleep 2
# text search project # 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} 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 Sleep 5
Wait Until Page Contains ${project} 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 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 Sleep 3
Capture Page Screenshot gointo2_${project}.png Capture Page Screenshot gointo2_${project}.png
Add User To Project Admin Add User To Project Admin
[Arguments] ${project} ${user} [Arguments] ${project} ${user}
Go Into Project2 Go Into Project2
Sleep 2 Sleep 2
Click Element xpath=${project_member_tag_xpath} Click Element xpath=${project_member_tag_xpath}
Sleep 1 Sleep 1
Click Element xpath=${project_member_add_button_xpath} Click Element xpath=${project_member_add_button_xpath}
Sleep 2 Sleep 2
Input Text xpath=${project_member_add_username_xpath} ${user} 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_admin_xpath}
Click Element xpath=${project_member_add_save_button_xpath} Click Element xpath=${project_member_add_save_button_xpath}
Sleep 4 Sleep 4
Search Project Member Search Project Member
[Arguments] ${project} ${user} [Arguments] ${project} ${user}
Go Into Project ${project} Go Into Project ${project}
Sleep 2 Sleep 2
Click Element xpath=${project_member_tag_xpath} Click Element xpath=//clr-dg-cell//a[contains(.,"${project}")]
Sleep 1 Sleep 1
Click Element xpath=${project_member_search_button_xpath} Click Element xpath=${project_member_search_button_xpath}
Sleep 1 Sleep 1
Click Element xpath=${project_member_search_text_xpath} Click Element xpath=${project_member_search_text_xpath}
Sleep 2 Sleep 2
Wait Until Page Contains ${user} Wait Until Page Contains ${user}
Change Project Member Role Change Project Member Role
[Arguments] ${project} ${user} ${role} [Arguments] ${project} ${user} ${role}
Click Element xpath=//clr-dg-cell//a[contains(.,"${project}")] Click Element xpath=//clr-dg-cell//a[contains(.,"${project}")]
Sleep 2 Sleep 2
Click Element xpath=${project_member_tag_xpath} Click Element xpath=${project_member_tag_xpath}
Sleep 1 Sleep 1
Click Element xpath=//project-detail//clr-dg-row-master[contains(.,"${user}")]//clr-dg-action-overflow Click Element xpath=//project-detail//clr-dg-row-master[contains(.,'${user}')]//clr-dg-action-overflow
Sleep 1 Sleep 1
Click Element xpath=//project-detail//clr-dg-action-overflow//button[contains(.,"${role}")] Click Element xpath=//project-detail//clr-dg-action-overflow//button[contains(.,"${role}")]
Sleep 2 Sleep 2
Wait Until Page Contains ${role} 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

View File

@ -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_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_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_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 ${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]

View File

@ -21,73 +21,77 @@ ${HARBOR_VERSION} v1.1.1
*** Keywords *** *** Keywords ***
Create An New Project Create An New Project
[Arguments] ${projectname} ${public}=false [Arguments] ${projectname} ${public}=false
Sleep 1 Sleep 1
Click Button css=${create_project_button_css} Click Button css=${create_project_button_css}
Sleep 1 Sleep 1
Log To Console Project Name: ${projectname} Log To Console Project Name: ${projectname}
Input Text xpath=${project_name_xpath} ${projectname} Input Text xpath=${project_name_xpath} ${projectname}
Sleep 3 Sleep 3
Run Keyword If '${public}' == 'true' Click Element xpath=${project_public_xpath} Run Keyword If '${public}' == 'true' Click Element xpath=${project_public_xpath}
Click Element css=${project_save_css} Click Element css=${project_save_css}
Sleep 4 Sleep 4
Wait Until Page Contains ${projectname} Wait Until Page Contains ${projectname}
Wait Until Page Contains Project Admin Wait Until Page Contains Project Admin
Create An New Project With New User Create An New Project With New User
[Arguments] ${url} ${username} ${email} ${realname} ${newPassword} ${comment} ${projectname} ${public} [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} Create An New User url=${url} username=${username} email=${email} realname=${realname} newPassword=${newPassword} comment=${comment}
Logout Harbor Logout Harbor
Sign In Harbor ${url} ${username} ${newPassword} Sign In Harbor ${url} ${username} ${newPassword}
Create An New Project ${projectname} ${public} Create An New Project ${projectname} ${public}
Sleep 1 Sleep 1
#It's the log of project. #It's the log of project.
Go To Project Log Go To Project Log
Click Element xpath=//project-detail//ul/li[3] Click Element xpath=//project-detail//ul/li[3]
Sleep 2 Sleep 2
Switch To Member
Click Element xpath=//project-detail//li[2]
Sleep 1
Switch To Log Switch To Log
Click Element xpath=${log_xpath} Click Element xpath=${log_xpath}
Sleep 1 Sleep 1
Switch To Replication Switch To Replication
Click Element xpath=${replication_xpath} Click Element xpath=${replication_xpath}
Sleep 1 Sleep 1
Back To projects Back To projects
Click Element xpath=${projects_xpath} Click Element xpath=${projects_xpath}
Sleep 1 Sleep 1
Project Should Display Project Should Display
[Arguments] ${projectname} [Arguments] ${projectname}
Page Should Contain Element xpath=//project//list-project//clr-dg-cell/a[contains(.,'${projectname}')] Page Should Contain Element xpath=//project//list-project//clr-dg-cell/a[contains(.,'${projectname}')]
Project Should Not Display Project Should Not Display
[Arguments] ${projectname} [Arguments] ${projectname}
Page Should Not Contain Element xpath=//project//list-project//clr-dg-cell/a[contains(.,'${projectname}')] Page Should Not Contain Element xpath=//project//list-project//clr-dg-cell/a[contains(.,'${projectname}')]
Search Private Projects Search Private Projects
Click element xpath=//select Click element xpath=//select
Click element xpath=//select/option[@value=1] Click element xpath=//select/option[@value=1]
Sleep 1 Sleep 1
Capture Page Screenshot SearchPrivateProjects.png Capture Page Screenshot SearchPrivateProjects.png
Make Project Private Make Project Private
[Arguments] ${projectname} [Arguments] ${projectname}
Sleep 1 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-row-master[contains(.,'${projectname}')]//clr-dg-action-overflow
Click element xpath=//project//list-project//clr-dg-action-overflow//button[contains(.,"Make Private")] Click element xpath=//project//list-project//clr-dg-action-overflow//button[contains(.,"Make Private")]
Make Project Public Make Project Public
[Arguments] ${projectname} [Arguments] ${projectname}
Sleep 1 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-row-master[contains(.,'${projectname}')]//clr-dg-action-overflow
Click element xpath=//project//list-project//clr-dg-action-overflow//button[contains(.,"Make Public")] Click element xpath=//project//list-project//clr-dg-action-overflow//button[contains(.,"Make Public")]
Delete Repo Delete Repo
[Arguments] ${projectname} [Arguments] ${projectname}
Click Element xpath=//project-detail//clr-dg-row-master[contains(.,"${projectname}")]//clr-dg-action-overflow Click Element xpath=//project-detail//clr-dg-row-master[contains(.,"${projectname}")]//clr-dg-action-overflow
Sleep 1 Sleep 1
Click Element xpath=//clr-dg-action-overflow//button[contains(.,"Delete")] Click Element xpath=//clr-dg-action-overflow//button[contains(.,"Delete")]
Sleep 1 Sleep 1
@ -95,18 +99,18 @@ Delete Repo
Sleep 2 Sleep 2
Advanced Search Should Display 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. # it's not a common keywords, only used into log case.
Do Log Advanced Search Do Log Advanced Search
Capture Page Screenshot LogAdvancedSearch.png Capture Page Screenshot LogAdvancedSearch.png
Sleep 1 Sleep 1
Page Should Contain Element xpath=//clr-dg-row[contains(.,"pull")] 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(.,"push")]
Page Should Contain Element xpath=//clr-dg-row[contains(.,"create")] Page Should Contain Element xpath=//clr-dg-row[contains(.,"create")]
Page Should Contain Element xpath=//clr-dg-row[contains(.,"delete")] Page Should Contain Element xpath=//clr-dg-row[contains(.,"delete")]
Sleep 1 Sleep 1
Click Element xpath=//audit-log//div[@class="flex-xs-middle"]/button Click Element xpath=//audit-log//div[@class="flex-xs-middle"]/button
Sleep 1 Sleep 1
Click Element xpath=//project-detail//audit-log//clr-dropdown/button Click Element xpath=//project-detail//audit-log//clr-dropdown/button
Sleep 1 Sleep 1
@ -147,13 +151,13 @@ Expand Repo
[Arguments] ${projectname} [Arguments] ${projectname}
Click Element //repository//clr-dg-row-master[contains(.,'${projectname}')]//button/clr-icon Click Element //repository//clr-dg-row-master[contains(.,'${projectname}')]//button/clr-icon
sleep 1 sleep 1
Scan Repo Scan Repo
[Arguments] ${projectname} [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
Click Element //hbr-tag//clr-dg-row-master[contains(.,'${projectname}')]//clr-dg-action-overflow//button[contains(.,'Scan')] Click Element //hbr-tag//clr-dg-row-master[contains(.,'${projectname}')]//clr-dg-action-overflow//button[contains(.,'Scan')]
Sleep 15 Sleep 15
Summary Chart Should Display Summary Chart Should Display
[Arguments] ${projectname} [Arguments] ${projectname}
Page Should Contain Element //clr-dg-row-master[contains(.,'${projectname}')]//hbr-vulnerability-bar//hbr-vulnerability-summary-chart Page Should Contain Element //clr-dg-row-master[contains(.,'${projectname}')]//hbr-vulnerability-bar//hbr-vulnerability-summary-chart

View File

@ -21,23 +21,23 @@ ${HARBOR_VERSION} v1.1.1
*** Keywords *** *** Keywords ***
Create An New Rule With New Endpoint 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} Click element xpath=${new_name_xpath}
Sleep 2 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} Input Text xpath=${policy_description_xpath} ${policy_description}
Click element xpath=${policy_enable_checkbox} Click element xpath=${policy_enable_checkbox}
Click element xpath=${policy_endpoint_checkbox} Click element xpath=${policy_endpoint_checkbox}
Input text xpath=${destination_name_xpath} ${destination_name} Input text xpath=${destination_name_xpath} ${destination_name}
Input text xpath=${destination_url_xpath} ${destination_url} Input text xpath=${destination_url_xpath} ${destination_url}
Input text xpath=${destination_username_xpath} ${destination_username} Input text xpath=${destination_username_xpath} ${destination_username}
Input text xpath=${destination_password_xpath} ${destination_password} Input text xpath=${destination_password_xpath} ${destination_password}
Click element xpath=${replicaton_save_xpath} Click element xpath=${replicaton_save_xpath}
Sleep 5 Sleep 5
Capture Page Screenshot rule_${policy_name}.png Capture Page Screenshot rule_${policy_name}.png
Wait Until Page Contains ${policy_name} Wait Until Page Contains ${policy_name}
Wait Until Page Contains ${policy_description} Wait Until Page Contains ${policy_description}
Wait Until Page Contains ${destination_name} Wait Until Page Contains ${destination_name}

View File

@ -45,12 +45,12 @@ Update User Comment
Sleep 2 Sleep 2
Logout Harbor 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 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 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 Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/navigator/clr-header/div[3]/clr-dropdown[2]/button/span
Sleep 2 Sleep 2
Click Element xpath=//harbor-app/harbor-shell/clr-main-container/navigator/clr-header//clr-dropdown//a[4] Click Element xpath=//harbor-app/harbor-shell/clr-main-container/navigator/clr-header//clr-dropdown//a[4]
Sleep 1 Sleep 1
Capture Page Screenshot Logout.png Capture Page Screenshot Logout.png
Sleep 2 Sleep 2
Wait Until Keyword Succeeds 5x 1 Page Should Contain Element xpath=//sign-in//form//*[@class="title"] Wait Until Keyword Succeeds 5x 1 Page Should Contain Element xpath=//sign-in//form//*[@class="title"]

View File

@ -24,7 +24,7 @@ ${HARBOR_URL} http://localhost
*** Test Cases *** *** Test Cases ***
Test Case - Create An New User Test Case - Create An New User
Init Chrome Driver 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 Create An New User url=${HARBOR_URL} username=tester${d} email=tester${d}@vmware.com realname=harbortest newPassword=Test1@34 comment=harbortest
Close Browser Close Browser
@ -64,7 +64,7 @@ Test Case - User View Projects
Create An New Project test${d}2 Create An New Project test${d}2
Create An New Project test${d}3 Create An New Project test${d}3
Switch To Log 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}1
Wait Until Page Contains test${d}2 Wait Until Page Contains test${d}2
Wait Until Page Contains test${d}3 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 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} 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} Go Into Project test${d}
Wait Until Page Contains test${d}/hello-world Wait Until Page Contains test${d}/hello-world
@ -84,18 +84,18 @@ Test Case - User View Logs
Init Chrome Driver Init Chrome Driver
${d}= Get Current Date result_format=%m%s ${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 Pull image ${ip} tester${d} Test1@34 project${d} busybox:latest
Go Into Project project${d} Go Into Project project${d}
Delete Repo project${d} Delete Repo project${d}
Go To Project Log Go To Project Log
Advanced Search Should Display Advanced Search Should Display
Do Log Advanced Search Do Log Advanced Search
Close Browser Close Browser
Test Case - Manage project publicity Test Case - Manage project publicity
@ -135,25 +135,25 @@ Test Case - Manage project publicity
Logout Harbor Logout Harbor
Sign In Harbor ${HARBOR_URL} userb${d} Test1@34 Sign In Harbor ${HARBOR_URL} userb${d} Test1@34
Project Should Display project${d} Project Should Display project${d}
Close Browser Close Browser
Test Case - Edit Project Creation Test Case - Edit Project Creation
# create normal user and login # create normal user and login
Init Chrome Driver 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 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 Logout Harbor
Sleep 3 Sleep 3
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Set Pro Create Admin Only Set Pro Create Admin Only
Logout Harbor Logout Harbor
Sign In Harbor ${HARBOR_URL} tester${d} Test1@34 Sign In Harbor ${HARBOR_URL} tester${d} Test1@34
Project Creation Should Not Display Project Creation Should Not Display
Logout Harbor Logout Harbor
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Set Pro Create Every One Set Pro Create Every One
@ -163,16 +163,16 @@ Test Case - Edit Self-Registration
Init Chrome Driver Init Chrome Driver
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Disable Self Reg Disable Self Reg
Logout Harbor Logout Harbor
Sign Up Should Not Display Sign Up Should Not Display
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Switch To Configure Switch To Configure
Self Reg Should Be Disabled Self Reg Should Be Disabled
Sleep 1 Sleep 1
#restore setting #restore setting
Enable Self Reg Enable Self Reg
Close Browser Close Browser
@ -197,13 +197,13 @@ Test Case - Edit Email Settings
Init Chrome Driver Init Chrome Driver
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Switch To Email Switch To Email
Config Email Config Email
Logout Harbor Logout Harbor
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Switch To Email Switch To Email
Verify Email Verify Email
Close Browser Close Browser
@ -211,15 +211,15 @@ Test Case - Edit Email Settings
Test Case - Edit Token Expire Test Case - Edit Token Expire
Init Chrome Driver Init Chrome Driver
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Switch To System Settings Switch To System Settings
Modify Token Expiration 20 Modify Token Expiration 20
Logout Harbor Logout Harbor
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Switch To System Settings Switch To System Settings
Token Must Be Match 20 Token Must Be Match 20
#reset to default #reset to default
Modify Token Expiration 30 Modify Token Expiration 30
Close Browser Close Browser
@ -228,10 +228,10 @@ Test Case - Create An Replication Rule New Endpoint
${d}= Get current date result_format=%m%s ${d}= Get current date result_format=%m%s
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Create An New Project project${d} Create An New Project project${d}
Go Into Project project${d} Go Into Project project${d}
Switch To Replication 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 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 Test Case - Scan A Tag
Init Chrome Driver Init Chrome Driver
@ -242,7 +242,33 @@ Test Case - Scan A Tag
Expand Repo project${d} Expand Repo project${d}
Scan Repo project${d} Scan Repo project${d}
Summary Chart Should Display 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 Test Case - Assign Sys Admin
Init Chrome Driver Init Chrome Driver
@ -273,8 +299,7 @@ Test Case - Admin Push Signed Image
${rc} ${output}= Run And Return Rc And Output docker pull hello-world:latest ${rc} ${output}= Run And Return Rc And Output docker pull hello-world:latest
Log To Console ${output} 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 ${rc} ${output}= Run And Return Rc And Output ./tests/robot-cases/Group9-Content-trust/notary-push-image.sh
Log To Console ${output} Log To Console ${output}
Should Be Equal As Integers ${rc} 0 Should Be Equal As Integers ${rc} 0
@ -289,4 +314,4 @@ Test Case - Admin Push Un-Signed Image
Log To Console ${output} Log To Console ${output}
Test Case - Clean Harbor Images Test Case - Clean Harbor Images
Down Harbor with_notary=true Down Harbor with_notary=true

View File

@ -1,12 +1,24 @@
#!/bin/bash #!/bin/bash
docker pull tomcat docker pull tomcat:latest
IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'` 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=1
export DOCKER_CONTENT_TRUST_SERVER=https://$IP:4443 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 docker tag tomcat $IP/library/tomcat:latest
python ./tests/robot-cases/Group9-Content-trust/notary-push-image.py docker push $IP/library/tomcat:latest