mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-24 17:47:46 +01:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
546a8327aa
8
Makefile
8
Makefile
@ -178,7 +178,7 @@ DOCKERIMAGENAME_UI=vmware/harbor-ui
|
||||
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
||||
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
||||
DOCKERIMAGENAME_DB=vmware/harbor-db
|
||||
DOCKERIMAGENAME_CLATIRY=vmware/harbor-clarity-ui-builder
|
||||
DOCKERIMAGENAME_CLARITY=vmware/harbor-clarity-ui-builder
|
||||
DOCKERIMAGENAME_POSTGRESQL=vmware/postgresql
|
||||
# docker-compose files
|
||||
DOCKERCOMPOSEFILEPATH=$(MAKEPATH)
|
||||
@ -399,10 +399,10 @@ refresh_clarity_builder:
|
||||
$(SEDCMD) -i 's/__proxy__/ /g' $(DOCKERFILE_CLARITY) ; \
|
||||
fi ; \
|
||||
echo "build new clarity image.."; \
|
||||
$(DOCKERBUILD) -f $(DOCKERFILE_CLARITY) -t $(DOCKERIMAGENAME_CLATIRY):$(NEWCLARITYVERSION) . ; \
|
||||
$(DOCKERBUILD) -f $(DOCKERFILE_CLARITY) -t $(DOCKERIMAGENAME_CLARITY):$(NEWCLARITYVERSION) . ; \
|
||||
echo "push clarity image.."; \
|
||||
$(DOCKERTAG) $(DOCKERIMAGENAME_CLATIRY):$(NEWCLARITYVERSION) $(DOCKERIMAGENAME_CLATIRY):$(NEWCLARITYVERSION); \
|
||||
$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_CLATIRY):$(NEWCLARITYVERSION) \
|
||||
$(DOCKERTAG) $(DOCKERIMAGENAME_CLARITY):$(NEWCLARITYVERSION) $(DOCKERIMAGENAME_CLARITY):$(NEWCLARITYVERSION); \
|
||||
$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_CLARITY):$(NEWCLARITYVERSION) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER); \
|
||||
echo "remove local clarity image.."; \
|
||||
$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(NEWCLARITYVERSION); \
|
||||
|
@ -4,7 +4,7 @@
|
||||
[![Coverage Status](https://coveralls.io/repos/github/vmware/harbor/badge.svg?branch=master)](https://coveralls.io/github/vmware/harbor?branch=master)
|
||||
|
||||
**Note**: The `master` branch may be in an *unstable or even broken state* during development.
|
||||
Please use [releases] instead of the `master` branch in order to get stable binaries.
|
||||
Please use [releases](https://github.com/vmware/harbor/releases) instead of the `master` branch in order to get stable binaries.
|
||||
|
||||
<img alt="Harbor" src="docs/img/harbor_logo.png">
|
||||
|
||||
|
@ -2586,6 +2586,9 @@ definitions:
|
||||
name:
|
||||
type: string
|
||||
description: The name of the tag.
|
||||
size:
|
||||
type: integer
|
||||
description: The size of the image.
|
||||
architecture:
|
||||
type: string
|
||||
description: The architecture of the image.
|
||||
@ -2727,6 +2730,9 @@ definitions:
|
||||
email_ssl:
|
||||
type: boolean
|
||||
description: When it's set to true the system will access Email server via TLS by default. If it's set to false, it still will handle "STARTTLS" from server side.
|
||||
email_insecure:
|
||||
type: boolean
|
||||
description: Whether or not the certificate will be verified when Harbor tries to access the email server.
|
||||
ldap_url:
|
||||
type: string
|
||||
description: The URL of LDAP server.
|
||||
|
@ -24,11 +24,11 @@ Harbor manages images through projects. Users can be added into one project as a
|
||||
|
||||
* **Guest**: Guest has read-only privilege for a specified project.
|
||||
* **Developer**: Developer has read and write privileges for a project.
|
||||
* **ProjectAdmin**: When creating a new project, you will be assigned the "ProjectAdmin" role to the project. Besides read-write privileges, the "ProjectAdmin" also has some management privileges, such as adding and removing members.
|
||||
* **ProjectAdmin**: When creating a new project, you will be assigned the "ProjectAdmin" role to the project. Besides read-write privileges, the "ProjectAdmin" also has some management privileges, such as adding and removing members, starting a vulnerability scan.
|
||||
|
||||
Besides the above three roles, there are two system-wide roles:
|
||||
|
||||
* **SysAdmin**: "SysAdmin" has the most privileges. In addition to the privileges mentioned above, "SysAdmin" can also list all projects, set an ordinary user as administrator and delete users. The public project "library" is also owned by the administrator.
|
||||
* **SysAdmin**: "SysAdmin" has the most privileges. In addition to the privileges mentioned above, "SysAdmin" can also list all projects, set an ordinary user as administrator, delete users and set vulnerability scan policy for all images. The public project "library" is also owned by the administrator.
|
||||
* **Anonymous**: When a user is not logged in, the user is considered as an "Anonymous" user. An anonymous user has no access to private projects and has read-only access to public projects.
|
||||
|
||||
## User account
|
||||
@ -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_SERVER=https://10.117.169.182:4443
|
||||
```
|
||||
If you push the image for the first time, You will be asked to enter the root key passphrase. This will be needed every time you push a new image while the ``DOCKER_CONTENT_TRUST`` flag is set.
|
||||
The root key is generated at: ``/root/.docker/trust/private/root_keys``
|
||||
You will also be asked to enter a new passphrase for the image. This is generated at ``/root/.docker/trust/private/tuf_keys/[registry name] /[imagepath]``.
|
||||
If you are using a self-signed cert, make sure to copy the CA cert into ```/etc/docker/certs.d/10.117.169.182``` and ```$HOME/.docker/tls/10.117.169.182:4443/```. When an image is signed, it is indicated in the Web UI.
|
||||
**Note: Replace "10.117.169.182" with the IP address or domain name of your Harbor node. In order to use content trust, HTTPS must be enabled in Harbor.**
|
||||
|
||||
|
@ -11,9 +11,9 @@ LDAP_UID=$ldap_uid
|
||||
LDAP_SCOPE=$ldap_scope
|
||||
LDAP_TIMEOUT=$ldap_timeout
|
||||
DATABASE_TYPE=mysql
|
||||
MYSQL_HOST=mysql
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_USR=root
|
||||
MYSQL_HOST=$db_host
|
||||
MYSQL_PORT=$db_port
|
||||
MYSQL_USR=$db_user
|
||||
MYSQL_PWD=$db_password
|
||||
MYSQL_DATABASE=registry
|
||||
REGISTRY_URL=http://registry:5000
|
||||
|
@ -101,6 +101,16 @@ project_creation_restriction = everyone
|
||||
#Determine whether the job service should verify the ssl cert when it connects to a remote registry.
|
||||
#Set this flag to off when the remote registry uses a self-signed or untrusted certificate.
|
||||
verify_remote_cert = on
|
||||
|
||||
#The follow configurations are for Harbor HA mode only
|
||||
|
||||
#the address of the mysql database.
|
||||
db_host = mysql
|
||||
|
||||
#The port of mysql database host
|
||||
db_port = 3306
|
||||
|
||||
#The user name of mysql database
|
||||
db_user = root
|
||||
#************************END INITIAL PROPERTIES************************
|
||||
#############
|
||||
|
||||
|
232
make/kubernetes/adminserver/adminserver.rc.yaml
Normal file
232
make/kubernetes/adminserver/adminserver.rc.yaml
Normal 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
|
9
make/kubernetes/adminserver/adminserver.svc.yaml
Normal file
9
make/kubernetes/adminserver/adminserver.svc.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: adminserver
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
selector:
|
||||
name: adminserver-apps
|
@ -43,11 +43,11 @@ spec:
|
||||
configMapKeyRef:
|
||||
name: harbor-jobservice-config
|
||||
key: UI_SECRET
|
||||
- name: SECRET_KEY
|
||||
- name: JOBSERVICE_SECRET
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: harbor-jobservice-config
|
||||
key: SECRET_KEY
|
||||
key: JOBSERVICE_SECRET
|
||||
- name: CONFIG_PATH
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
|
@ -29,6 +29,8 @@ parser.add_argument('-k', default='',
|
||||
dest='private_key', help='[Optional] path of harbor https private key(pem)')
|
||||
parser.add_argument('-c', default='',
|
||||
dest='cert', help='[Optional] harbor path of https cert(pem)')
|
||||
parser.add_argument('-j', default='',
|
||||
dest='jobservice_secret', help="[Optional] path of harbor secret key(16 characters)")
|
||||
parser.add_argument('-s', default='',
|
||||
dest='secret_key', help="[Optional] path of harbor secret key(16 characters)")
|
||||
|
||||
@ -99,7 +101,20 @@ else:
|
||||
cert_path = ''
|
||||
|
||||
|
||||
# read secret key
|
||||
# read jobservice secret key
|
||||
if args.jobservice_secret != '':
|
||||
if os.path.isfile(args.jobservice_secret):
|
||||
key = ''
|
||||
with open(args.jobservice_secret, 'r') as skey:
|
||||
key = skey.read()
|
||||
if len(key) != 16:
|
||||
raise Exception('Error: The length of secret key has to be 16 characters!')
|
||||
set_config('jobservice_secret', key)
|
||||
else:
|
||||
set_config('jobservice_secret', ''.join(random.choice(
|
||||
string.ascii_letters + string.digits) for i in range(16)))
|
||||
|
||||
# read ldap secret key
|
||||
if args.secret_key != '':
|
||||
if os.path.isfile(args.secret_key):
|
||||
key = ''
|
||||
@ -199,3 +214,4 @@ generate_template(os.path.join(template_dir, 'jobservice.cm.yaml'), os.path.join
|
||||
generate_template(os.path.join(template_dir, 'mysql.cm.yaml'), os.path.join(output_dir, 'mysql/mysql.cm.yaml'))
|
||||
generate_template(os.path.join(template_dir, 'nginx.cm.yaml'), os.path.join(output_dir, 'nginx/nginx.cm.yaml'))
|
||||
generate_template(os.path.join(template_dir, 'registry.cm.yaml'), os.path.join(output_dir, 'registry/registry.cm.yaml'))
|
||||
generate_template(os.path.join(template_dir, 'adminserver.cm.yaml'), os.path.join(output_dir, 'adminserver/adminserver.cm.yaml'))
|
||||
|
47
make/kubernetes/templates/adminserver.cm.yaml
Normal file
47
make/kubernetes/templates/adminserver.cm.yaml
Normal 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}}"
|
@ -8,7 +8,7 @@ data:
|
||||
MYSQL_USR: root
|
||||
MYSQL_PWD: "{{db_password}}"
|
||||
UI_SECRET: "{{ui_secret}}"
|
||||
SECRET_KEY: "{{secret_key}}"
|
||||
JOBSERVICE_SECRET: "{{jobservice_secret}}"
|
||||
CONFIG_PATH: /etc/jobservice/app.conf
|
||||
REGISTRY_URL: http://registry:5000
|
||||
VERIFY_REMOTE_CERT: "{{verify_remote_cert}}"
|
||||
|
@ -22,7 +22,7 @@ data:
|
||||
LDAP_SCOPE: "{{ldap_scope}}"
|
||||
LOG_LEVEL: debug
|
||||
UI_SECRET: "{{ui_secret}}"
|
||||
SECRET_KEY: "{{secret_key}}"
|
||||
JOBSERVICE_SECRET: "{{jobservice_secre}}"
|
||||
GODEBUG: netdns=cgo
|
||||
EXT_ENDPOINT: "{{ui_url}}"
|
||||
TOKEN_URL: http://ui
|
||||
|
@ -113,11 +113,11 @@ spec:
|
||||
configMapKeyRef:
|
||||
name: harbor-ui-config
|
||||
key: UI_SECRET
|
||||
- name: SECRET_KEY
|
||||
- name: JOBSERVICE_SECRET
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: harbor-ui-config
|
||||
key: SECRET_KEY
|
||||
key: JOBSERVICE_SECRET
|
||||
- name: GODEBUG
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
|
@ -139,6 +139,9 @@ ldap_uid = rcp.get("configuration", "ldap_uid")
|
||||
ldap_scope = rcp.get("configuration", "ldap_scope")
|
||||
ldap_timeout = rcp.get("configuration", "ldap_timeout")
|
||||
db_password = rcp.get("configuration", "db_password")
|
||||
db_host = rcp.get("configuration", "db_host")
|
||||
db_port = rcp.get("configuration", "db_port")
|
||||
db_user = rcp.get("configuration", "db_user")
|
||||
self_registration = rcp.get("configuration", "self_registration")
|
||||
if protocol == "https":
|
||||
cert_path = rcp.get("configuration", "ssl_cert")
|
||||
@ -210,6 +213,9 @@ render(os.path.join(templates_dir, "adminserver", "env"),
|
||||
ldap_scope=ldap_scope,
|
||||
ldap_timeout=ldap_timeout,
|
||||
db_password=db_password,
|
||||
db_host=db_host,
|
||||
db_port=db_port,
|
||||
db_user=db_user,
|
||||
email_host=email_host,
|
||||
email_port=email_port,
|
||||
email_usr=email_usr,
|
||||
|
@ -115,9 +115,5 @@ func (c *cfgStore) Write(config map[string]interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(c.path, b, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return ioutil.WriteFile(c.path, b, 0600)
|
||||
}
|
||||
|
@ -91,6 +91,10 @@ var (
|
||||
env: "EMAIL_SSL",
|
||||
parse: parseStringToBool,
|
||||
},
|
||||
common.EmailInsecure: &parser{
|
||||
env: "EMAIL_INSECURE",
|
||||
parse: parseStringToBool,
|
||||
},
|
||||
common.EmailFrom: "EMAIL_FROM",
|
||||
common.EmailIdentity: "EMAIL_IDENTITY",
|
||||
common.RegistryURL: "REGISTRY_URL",
|
||||
|
@ -195,9 +195,9 @@ func (b *BaseAPI) GetUserIDForRequest() (int, bool, bool) {
|
||||
// Redirect does redirection to resource URI with http header status code.
|
||||
func (b *BaseAPI) Redirect(statusCode int, resouceID string) {
|
||||
requestURI := b.Ctx.Request.RequestURI
|
||||
resoucreURI := requestURI + "/" + resouceID
|
||||
resourceURI := requestURI + "/" + resouceID
|
||||
|
||||
b.Ctx.Redirect(statusCode, resoucreURI)
|
||||
b.Ctx.Redirect(statusCode, resourceURI)
|
||||
}
|
||||
|
||||
// GetIDFromURL checks the ID in request URL
|
||||
|
@ -55,6 +55,7 @@ const (
|
||||
EmailFrom = "email_from"
|
||||
EmailSSL = "email_ssl"
|
||||
EmailIdentity = "email_identity"
|
||||
EmailInsecure = "email_insecure"
|
||||
ProjectCreationRestriction = "project_creation_restriction"
|
||||
VerifyRemoteCert = "verify_remote_cert"
|
||||
MaxJobWorkers = "max_job_workers"
|
||||
|
@ -267,8 +267,5 @@ func deleteRepository(name string) error {
|
||||
}
|
||||
|
||||
func clearRepositoryData() error {
|
||||
if err := ClearTable(models.RepoTable); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return ClearTable(models.RepoTable)
|
||||
}
|
||||
|
@ -42,11 +42,7 @@ func (s *sqlite) Register(alias ...string) error {
|
||||
if len(alias) != 0 {
|
||||
an = alias[0]
|
||||
}
|
||||
if err := orm.RegisterDataBase(an, "sqlite3", s.file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return orm.RegisterDataBase(an, "sqlite3", s.file)
|
||||
}
|
||||
|
||||
// Name returns the name of SQLite
|
||||
|
@ -65,6 +65,7 @@ type Email struct {
|
||||
SSL bool `json:"ssl"`
|
||||
Identity string `json:"identity"`
|
||||
From string `json:"from"`
|
||||
Insecure bool `json:"insecure"`
|
||||
}
|
||||
|
||||
/*
|
||||
|
39
src/common/models/pro_meta.go
Normal file
39
src/common/models/pro_meta.go
Normal 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"`
|
||||
}
|
@ -25,16 +25,17 @@ type Project struct {
|
||||
OwnerID int `orm:"column(owner_id)" json:"owner_id"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"`
|
||||
CreationTimeStr string `orm:"-" json:"creation_time_str"`
|
||||
Deleted int `orm:"column(deleted)" json:"deleted"`
|
||||
//UserID int `json:"UserId"`
|
||||
OwnerName string `orm:"-" json:"owner_name"`
|
||||
Public int `orm:"column(public)" json:"public"`
|
||||
//This field does not have correspondent column in DB, this is just for UI to disable button
|
||||
Togglable bool `orm:"-"`
|
||||
UpdateTime time.Time `orm:"update_time" json:"update_time"`
|
||||
Deleted int `orm:"column(deleted)" json:"deleted"`
|
||||
CreationTimeStr string `orm:"-" json:"creation_time_str"`
|
||||
OwnerName string `orm:"-" json:"owner_name"`
|
||||
Togglable bool `orm:"-"`
|
||||
Role int `orm:"-" json:"current_user_role_id"`
|
||||
RepoCount int `orm:"-" json:"repo_count"`
|
||||
Metadata map[string]interface{} `orm:"-" json:"metadata"`
|
||||
|
||||
// TODO remove
|
||||
Public int `orm:"column(public)" json:"public"`
|
||||
EnableContentTrust bool `orm:"-" json:"enable_content_trust"`
|
||||
PreventVulnerableImagesFromRunning bool `orm:"-" json:"prevent_vulnerable_images_from_running"`
|
||||
PreventVulnerableImagesFromRunningSeverity string `orm:"-" json:"prevent_vulnerable_images_from_running_severity"`
|
||||
@ -109,3 +110,9 @@ type ProjectRequest struct {
|
||||
PreventVulnerableImagesFromRunningSeverity string `json:"prevent_vulnerable_images_from_running_severity"`
|
||||
AutomaticallyScanImagesOnPush bool `json:"automatically_scan_images_on_push"`
|
||||
}
|
||||
|
||||
// ProjectQueryResult ...
|
||||
type ProjectQueryResult struct {
|
||||
Total int64
|
||||
Projects []*Project
|
||||
}
|
||||
|
@ -19,18 +19,18 @@ import (
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/security/admiral/authcontext"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
"github.com/vmware/harbor/src/ui/promgr"
|
||||
)
|
||||
|
||||
// SecurityContext implements security.Context interface based on
|
||||
// auth context and project manager
|
||||
type SecurityContext struct {
|
||||
ctx *authcontext.AuthContext
|
||||
pm projectmanager.ProjectManager
|
||||
pm promgr.ProjectManager
|
||||
}
|
||||
|
||||
// NewSecurityContext ...
|
||||
func NewSecurityContext(ctx *authcontext.AuthContext, pm projectmanager.ProjectManager) *SecurityContext {
|
||||
func NewSecurityContext(ctx *authcontext.AuthContext, pm promgr.ProjectManager) *SecurityContext {
|
||||
return &SecurityContext{
|
||||
ctx: ctx,
|
||||
pm: pm,
|
||||
|
@ -19,17 +19,17 @@ import (
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
"github.com/vmware/harbor/src/ui/promgr"
|
||||
)
|
||||
|
||||
// SecurityContext implements security.Context interface based on database
|
||||
type SecurityContext struct {
|
||||
user *models.User
|
||||
pm projectmanager.ProjectManager
|
||||
pm promgr.ProjectManager
|
||||
}
|
||||
|
||||
// NewSecurityContext ...
|
||||
func NewSecurityContext(user *models.User, pm projectmanager.ProjectManager) *SecurityContext {
|
||||
func NewSecurityContext(user *models.User, pm promgr.ProjectManager) *SecurityContext {
|
||||
return &SecurityContext{
|
||||
user: user,
|
||||
pm: pm,
|
||||
|
@ -25,7 +25,8 @@ import (
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager/db"
|
||||
"github.com/vmware/harbor/src/ui/promgr"
|
||||
"github.com/vmware/harbor/src/ui/promgr/pmsdriver/local"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -47,7 +48,7 @@ var (
|
||||
Email: "guestUser@vmware.com",
|
||||
}
|
||||
|
||||
pm = &db.ProjectManager{}
|
||||
pm = promgr.NewDefaultProjectManager(local.NewDriver(), true)
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
@ -50,6 +50,7 @@ var adminServerDefaultConfig = map[string]interface{}{
|
||||
common.EmailPassword: "password",
|
||||
common.EmailFrom: "from",
|
||||
common.EmailSSL: true,
|
||||
common.EmailInsecure: false,
|
||||
common.EmailIdentity: "",
|
||||
common.ProjectCreationRestriction: common.ProCrtRestrAdmOnly,
|
||||
common.VerifyRemoteCert: false,
|
||||
|
@ -159,19 +159,10 @@ func ParseProjectIDOrName(value interface{}) (int64, string, error) {
|
||||
case int:
|
||||
i := value.(int)
|
||||
id = int64(i)
|
||||
if id == 0 {
|
||||
return 0, "", fmt.Errorf("invalid ID: 0")
|
||||
}
|
||||
case int64:
|
||||
id = value.(int64)
|
||||
if id == 0 {
|
||||
return 0, "", fmt.Errorf("invalid ID: 0")
|
||||
}
|
||||
case string:
|
||||
name = value.(string)
|
||||
if len(name) == 0 {
|
||||
return 0, "", fmt.Errorf("empty name")
|
||||
}
|
||||
default:
|
||||
return 0, "", fmt.Errorf("unsupported type")
|
||||
}
|
||||
|
@ -217,14 +217,6 @@ func TestParseHarborIDOrName(t *testing.T) {
|
||||
id, name, err := ParseProjectIDOrName(nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// invalid ID
|
||||
id, name, err = ParseProjectIDOrName(0)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// invalid name
|
||||
id, name, err = ParseProjectIDOrName("")
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// valid int ID
|
||||
id, name, err = ParseProjectIDOrName(1)
|
||||
assert.Nil(t, err)
|
||||
|
@ -15,6 +15,10 @@ package job
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
@ -22,9 +26,6 @@ import (
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/utils/test"
|
||||
"github.com/vmware/harbor/src/jobservice/config"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var repJobID, scanJobID int64
|
||||
@ -192,10 +193,7 @@ func clearRepJobData() error {
|
||||
if err := dao.ClearTable(models.RepPolicyTable); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dao.ClearTable(models.RepTargetTable); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return dao.ClearTable(models.RepTargetTable)
|
||||
}
|
||||
|
||||
func prepareScanJobData() error {
|
||||
@ -220,8 +218,5 @@ func clearScanJobData() error {
|
||||
if err := dao.ClearTable(models.ScanJobTable); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dao.ClearTable(models.ScanOverviewTable); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return dao.ClearTable(models.ScanOverviewTable)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
"github.com/vmware/harbor/src/common/security"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/filter"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
"github.com/vmware/harbor/src/ui/promgr"
|
||||
)
|
||||
|
||||
// BaseController ...
|
||||
@ -31,7 +31,7 @@ type BaseController struct {
|
||||
SecurityCtx security.Context
|
||||
// ProjectMgr is the project manager which abstracts the operations
|
||||
// related to projects
|
||||
ProjectMgr projectmanager.ProjectManager
|
||||
ProjectMgr promgr.ProjectManager
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -47,6 +47,7 @@ var (
|
||||
common.EmailFrom,
|
||||
common.EmailSSL,
|
||||
common.EmailIdentity,
|
||||
common.EmailInsecure,
|
||||
common.ProjectCreationRestriction,
|
||||
common.VerifyRemoteCert,
|
||||
common.TokenExpiration,
|
||||
@ -78,6 +79,7 @@ var (
|
||||
|
||||
boolKeys = []string{
|
||||
common.EmailSSL,
|
||||
common.EmailInsecure,
|
||||
common.SelfRegistration,
|
||||
common.VerifyRemoteCert,
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ func (e *EmailAPI) Prepare() {
|
||||
func (e *EmailAPI) Ping() {
|
||||
var host, username, password, identity string
|
||||
var port int
|
||||
var ssl bool
|
||||
var ssl, insecure bool
|
||||
body := e.Ctx.Input.CopyBody(1 << 32)
|
||||
if body == nil || len(body) == 0 {
|
||||
cfg, err := config.Email()
|
||||
@ -66,6 +66,7 @@ func (e *EmailAPI) Ping() {
|
||||
password = cfg.Password
|
||||
identity = cfg.Identity
|
||||
ssl = cfg.SSL
|
||||
insecure = cfg.Insecure
|
||||
} else {
|
||||
settings := &struct {
|
||||
Host string `json:"email_host"`
|
||||
@ -74,6 +75,7 @@ func (e *EmailAPI) Ping() {
|
||||
Password *string `json:"email_password"`
|
||||
SSL bool `json:"email_ssl"`
|
||||
Identity string `json:"email_identity"`
|
||||
Insecure bool `json:"email_insecure"`
|
||||
}{}
|
||||
e.DecodeJSONReq(&settings)
|
||||
|
||||
@ -98,11 +100,12 @@ func (e *EmailAPI) Ping() {
|
||||
password = *settings.Password
|
||||
identity = settings.Identity
|
||||
ssl = settings.SSL
|
||||
insecure = settings.Insecure
|
||||
}
|
||||
|
||||
addr := net.JoinHostPort(host, strconv.Itoa(port))
|
||||
if err := email.Ping(addr, identity, username,
|
||||
password, pingEmailTimeout, ssl, false); err != nil {
|
||||
password, pingEmailTimeout, ssl, insecure); err != nil {
|
||||
log.Debugf("ping %s failed: %v", addr, err)
|
||||
e.CustomAbort(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ func (p *ProjectAPI) Post() {
|
||||
return
|
||||
}
|
||||
|
||||
exist, err := p.ProjectMgr.Exist(pro.Name)
|
||||
exist, err := p.ProjectMgr.Exists(pro.Name)
|
||||
if err != nil {
|
||||
p.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
|
||||
pro.Name), err)
|
||||
@ -325,19 +325,13 @@ func (p *ProjectAPI) List() {
|
||||
}
|
||||
}
|
||||
|
||||
total, err := p.ProjectMgr.GetTotal(query, base)
|
||||
result, err := p.ProjectMgr.List(query, base)
|
||||
if err != nil {
|
||||
p.ParseAndHandleError("failed to get total of projects", err)
|
||||
p.ParseAndHandleError("failed to list projects", err)
|
||||
return
|
||||
}
|
||||
|
||||
projects, err := p.ProjectMgr.GetAll(query, base)
|
||||
if err != nil {
|
||||
p.ParseAndHandleError("failed to get projects", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, project := range projects {
|
||||
for _, project := range result.Projects {
|
||||
if p.SecurityCtx.IsAuthenticated() {
|
||||
roles := p.SecurityCtx.GetProjectRoles(project.ProjectID)
|
||||
if len(roles) != 0 {
|
||||
@ -359,8 +353,8 @@ func (p *ProjectAPI) List() {
|
||||
project.RepoCount = len(repos)
|
||||
}
|
||||
|
||||
p.SetPaginationHeader(total, page, size)
|
||||
p.Data["json"] = projects
|
||||
p.SetPaginationHeader(result.Total, page, size)
|
||||
p.Data["json"] = result.Projects
|
||||
p.ServeJSON()
|
||||
}
|
||||
|
||||
@ -385,7 +379,9 @@ func (p *ProjectAPI) ToggleProjectPublic() {
|
||||
|
||||
if err := p.ProjectMgr.Update(p.project.ProjectID,
|
||||
&models.Project{
|
||||
Public: req.Public,
|
||||
Metadata: map[string]interface{}{
|
||||
models.ProMetaPublic: req.Public,
|
||||
},
|
||||
}); err != nil {
|
||||
p.ParseAndHandleError(fmt.Sprintf("failed to update project %d",
|
||||
p.project.ProjectID), err)
|
||||
|
@ -54,9 +54,10 @@ type repoResp struct {
|
||||
UpdateTime time.Time `json:"update_time"`
|
||||
}
|
||||
|
||||
type tag struct {
|
||||
type tagDetail struct {
|
||||
Digest string `json:"digest"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Architecture string `json:"architecture"`
|
||||
OS string `json:"os"`
|
||||
DockerVersion string `json:"docker_version"`
|
||||
@ -65,7 +66,7 @@ type tag struct {
|
||||
}
|
||||
|
||||
type tagResp struct {
|
||||
tag
|
||||
tagDetail
|
||||
Signature *notary.Target `json:"signature"`
|
||||
ScanOverview *models.ImgScanOverview `json:"scan_overview,omitempty"`
|
||||
}
|
||||
@ -83,7 +84,7 @@ func (ra *RepositoryAPI) Get() {
|
||||
return
|
||||
}
|
||||
|
||||
exist, err := ra.ProjectMgr.Exist(projectID)
|
||||
exist, err := ra.ProjectMgr.Exists(projectID)
|
||||
if err != nil {
|
||||
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %d",
|
||||
projectID), err)
|
||||
@ -334,7 +335,7 @@ func (ra *RepositoryAPI) GetTags() {
|
||||
repoName := ra.GetString(":splat")
|
||||
|
||||
projectName, _ := utils.ParseRepository(repoName)
|
||||
exist, err := ra.ProjectMgr.Exist(projectName)
|
||||
exist, err := ra.ProjectMgr.Exists(projectName)
|
||||
if err != nil {
|
||||
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
|
||||
projectName), err)
|
||||
@ -390,17 +391,13 @@ func assemble(client *registry.Repository, repository string,
|
||||
for _, t := range tags {
|
||||
item := &tagResp{}
|
||||
|
||||
// tag configuration
|
||||
digest, _, cfg, err := getV2Manifest(client, t)
|
||||
// the detail information of tag
|
||||
tagDetail, err := getTagDetail(client, t)
|
||||
if err != nil {
|
||||
cfg = &tag{
|
||||
Digest: digest,
|
||||
Name: t,
|
||||
}
|
||||
log.Errorf("failed to get v2 manifest of %s:%s: %v", repository, t, err)
|
||||
}
|
||||
if cfg != nil {
|
||||
item.tag = *cfg
|
||||
if tagDetail != nil {
|
||||
item.tagDetail = *tagDetail
|
||||
}
|
||||
|
||||
// scan overview
|
||||
@ -425,40 +422,45 @@ func assemble(client *registry.Repository, repository string,
|
||||
return result
|
||||
}
|
||||
|
||||
// get v2 manifest of tag, returns digest, manifest,
|
||||
// manifest config and error. The manifest config contains
|
||||
// architecture, os, author, etc.
|
||||
func getV2Manifest(client *registry.Repository, tagName string) (
|
||||
string, *schema2.DeserializedManifest, *tag, error) {
|
||||
digest, _, payload, err := client.PullManifest(tagName, []string{schema2.MediaTypeManifest})
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
// getTagDetail returns the detail information for v2 manifest image
|
||||
// The information contains architecture, os, author, size, etc.
|
||||
func getTagDetail(client *registry.Repository, tag string) (*tagDetail, error) {
|
||||
detail := &tagDetail{
|
||||
Name: tag,
|
||||
}
|
||||
|
||||
digest, _, payload, err := client.PullManifest(tag, []string{schema2.MediaTypeManifest})
|
||||
if err != nil {
|
||||
return detail, err
|
||||
}
|
||||
detail.Digest = digest
|
||||
|
||||
manifest := &schema2.DeserializedManifest{}
|
||||
if err = manifest.UnmarshalJSON(payload); err != nil {
|
||||
return digest, nil, nil, err
|
||||
return detail, err
|
||||
}
|
||||
|
||||
// size of manifest + size of layers
|
||||
detail.Size = int64(len(payload))
|
||||
for _, ref := range manifest.References() {
|
||||
detail.Size += ref.Size
|
||||
}
|
||||
|
||||
_, reader, err := client.PullBlob(manifest.Target().Digest.String())
|
||||
if err != nil {
|
||||
return digest, manifest, nil, err
|
||||
return detail, err
|
||||
}
|
||||
|
||||
configData, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return digest, manifest, nil, err
|
||||
return detail, err
|
||||
}
|
||||
|
||||
config := &tag{}
|
||||
if err = json.Unmarshal(configData, config); err != nil {
|
||||
return digest, manifest, nil, err
|
||||
if err = json.Unmarshal(configData, detail); err != nil {
|
||||
return detail, err
|
||||
}
|
||||
|
||||
config.Name = tagName
|
||||
config.Digest = digest
|
||||
|
||||
return digest, manifest, config, nil
|
||||
return detail, nil
|
||||
}
|
||||
|
||||
// GetManifests returns the manifest of a tag
|
||||
@ -476,7 +478,7 @@ func (ra *RepositoryAPI) GetManifests() {
|
||||
}
|
||||
|
||||
projectName, _ := utils.ParseRepository(repoName)
|
||||
exist, err := ra.ProjectMgr.Exist(projectName)
|
||||
exist, err := ra.ProjectMgr.Exists(projectName)
|
||||
if err != nil {
|
||||
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
|
||||
projectName), err)
|
||||
@ -608,7 +610,7 @@ func (ra *RepositoryAPI) GetSignatures() {
|
||||
repoName := ra.GetString(":splat")
|
||||
|
||||
projectName, _ := utils.ParseRepository(repoName)
|
||||
exist, err := ra.ProjectMgr.Exist(projectName)
|
||||
exist, err := ra.ProjectMgr.Exists(projectName)
|
||||
if err != nil {
|
||||
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
|
||||
projectName), err)
|
||||
@ -649,7 +651,7 @@ func (ra *RepositoryAPI) ScanImage() {
|
||||
repoName := ra.GetString(":splat")
|
||||
tag := ra.GetString(":tag")
|
||||
projectName, _ := utils.ParseRepository(repoName)
|
||||
exist, err := ra.ProjectMgr.Exist(projectName)
|
||||
exist, err := ra.ProjectMgr.Exists(projectName)
|
||||
if err != nil {
|
||||
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
|
||||
projectName), err)
|
||||
@ -792,7 +794,7 @@ func getSignatures(username, repository string) (map[string][]notary.Target, err
|
||||
|
||||
func (ra *RepositoryAPI) checkExistence(repository, tag string) (bool, string, error) {
|
||||
project, _ := utils.ParseRepository(repository)
|
||||
exist, err := ra.ProjectMgr.Exist(project)
|
||||
exist, err := ra.ProjectMgr.Exists(project)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
@ -48,11 +48,12 @@ func (s *SearchAPI) Get() {
|
||||
var err error
|
||||
|
||||
if isSysAdmin {
|
||||
projects, err = s.ProjectMgr.GetAll(nil)
|
||||
result, err := s.ProjectMgr.List(nil)
|
||||
if err != nil {
|
||||
s.ParseAndHandleError("failed to get projects", err)
|
||||
return
|
||||
}
|
||||
projects = result.Projects
|
||||
} else {
|
||||
projects, err = s.ProjectMgr.GetPublic()
|
||||
if err != nil {
|
||||
|
@ -77,15 +77,15 @@ func (s *StatisticAPI) Get() {
|
||||
statistic[PubRC] = n
|
||||
|
||||
if s.SecurityCtx.IsSysAdmin() {
|
||||
n, err := s.ProjectMgr.GetTotal(nil)
|
||||
result, err := s.ProjectMgr.List(nil)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of projects: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
statistic[TPC] = n
|
||||
statistic[PriPC] = n - statistic[PubPC]
|
||||
statistic[TPC] = result.Total
|
||||
statistic[PriPC] = result.Total - statistic[PubPC]
|
||||
|
||||
n, err = dao.GetTotalOfRepositories("")
|
||||
n, err := dao.GetTotalOfRepositories("")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of repositories: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
@ -94,7 +94,7 @@ func (s *StatisticAPI) Get() {
|
||||
statistic[PriRC] = n - statistic[PubRC]
|
||||
} else {
|
||||
value := false
|
||||
projects, err := s.ProjectMgr.GetAll(&models.ProjectQueryParam{
|
||||
result, err := s.ProjectMgr.List(&models.ProjectQueryParam{
|
||||
Public: &value,
|
||||
Member: &models.MemberQuery{
|
||||
Name: s.username,
|
||||
@ -106,10 +106,10 @@ func (s *StatisticAPI) Get() {
|
||||
return
|
||||
}
|
||||
|
||||
statistic[PriPC] = (int64)(len(projects))
|
||||
statistic[PriPC] = result.Total
|
||||
|
||||
ids := []int64{}
|
||||
for _, p := range projects {
|
||||
for _, p := range result.Projects {
|
||||
ids = append(ids, p.ProjectID)
|
||||
}
|
||||
|
||||
|
@ -357,10 +357,7 @@ func validate(user models.User) error {
|
||||
if isIllegalLength(user.Password, 8, 20) {
|
||||
return fmt.Errorf("password with illegal length")
|
||||
}
|
||||
if err := commonValidate(user); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return commonValidate(user)
|
||||
}
|
||||
|
||||
//commonValidate validates email, realname, comment information when user register or change their profile
|
||||
|
@ -33,7 +33,7 @@ import (
|
||||
"github.com/vmware/harbor/src/common/utils/registry"
|
||||
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
"github.com/vmware/harbor/src/ui/promgr"
|
||||
"github.com/vmware/harbor/src/ui/service/token"
|
||||
uiutils "github.com/vmware/harbor/src/ui/utils"
|
||||
)
|
||||
@ -166,7 +166,7 @@ func postReplicationAction(policyID int64, acton string) error {
|
||||
}
|
||||
|
||||
// SyncRegistry syncs the repositories of registry with database.
|
||||
func SyncRegistry(pm projectmanager.ProjectManager) error {
|
||||
func SyncRegistry(pm promgr.ProjectManager) error {
|
||||
|
||||
log.Infof("Start syncing repositories from registry to DB... ")
|
||||
|
||||
@ -254,7 +254,7 @@ func catalog() ([]string, error) {
|
||||
}
|
||||
|
||||
func diffRepos(reposInRegistry []string, reposInDB []string,
|
||||
pm projectmanager.ProjectManager) ([]string, []string, error) {
|
||||
pm promgr.ProjectManager) ([]string, []string, error) {
|
||||
var needsAdd []string
|
||||
var needsDel []string
|
||||
|
||||
@ -359,9 +359,9 @@ func diffRepos(reposInRegistry []string, reposInDB []string,
|
||||
return needsAdd, needsDel, nil
|
||||
}
|
||||
|
||||
func projectExists(pm projectmanager.ProjectManager, repository string) (bool, error) {
|
||||
func projectExists(pm promgr.ProjectManager, repository string) (bool, error) {
|
||||
project, _ := utils.ParseRepository(repository)
|
||||
return pm.Exist(project)
|
||||
return pm.Exists(project)
|
||||
}
|
||||
|
||||
func initRegistryClient() (r *registry.Registry, err error) {
|
||||
|
@ -15,7 +15,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
//"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@ -29,9 +29,10 @@ import (
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/secret"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager/db"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager/pms"
|
||||
"github.com/vmware/harbor/src/ui/promgr"
|
||||
"github.com/vmware/harbor/src/ui/promgr/pmsdriver"
|
||||
"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
|
||||
"github.com/vmware/harbor/src/ui/promgr/pmsdriver/local"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -46,14 +47,14 @@ var (
|
||||
// AdminserverClient is a client for adminserver
|
||||
AdminserverClient client.Client
|
||||
// GlobalProjectMgr is initialized based on the deploy mode
|
||||
GlobalProjectMgr projectmanager.ProjectManager
|
||||
GlobalProjectMgr promgr.ProjectManager
|
||||
mg *comcfg.Manager
|
||||
keyProvider comcfg.KeyProvider
|
||||
// AdmiralClient is initialized only under integration deploy mode
|
||||
// and can be passed to project manager as a parameter
|
||||
AdmiralClient *http.Client
|
||||
// TokenReader is used in integration mode to read token
|
||||
TokenReader pms.TokenReader
|
||||
TokenReader admiral.TokenReader
|
||||
)
|
||||
|
||||
// Init configurations
|
||||
@ -105,13 +106,10 @@ func initSecretStore() {
|
||||
}
|
||||
|
||||
func initProjectManager() {
|
||||
if !WithAdmiral() {
|
||||
// standalone
|
||||
log.Info("initializing the project manager based on database...")
|
||||
GlobalProjectMgr = &db.ProjectManager{}
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
@ -128,11 +126,21 @@ func initProjectManager() {
|
||||
path = defaultTokenFilePath
|
||||
}
|
||||
log.Infof("service token file path: %s", path)
|
||||
TokenReader = &pms.FileTokenReader{
|
||||
TokenReader = &admiral.FileTokenReader{
|
||||
Path: path,
|
||||
}
|
||||
GlobalProjectMgr = pms.NewProjectManager(AdmiralClient,
|
||||
GlobalProjectMgr = admiral.NewProjectManager(AdmiralClient,
|
||||
AdmiralEndpoint(), TokenReader)
|
||||
*/
|
||||
GlobalProjectMgr = nil
|
||||
} else {
|
||||
// standalone
|
||||
log.Info("initializing the project manager based on local database...")
|
||||
driver = local.NewDriver()
|
||||
// TODO move the statement out of the else block when admiral driver is completed
|
||||
GlobalProjectMgr = promgr.NewDefaultProjectManager(driver, true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Load configurations
|
||||
@ -298,6 +306,7 @@ func Email() (*models.Email, error) {
|
||||
email.SSL = cfg[common.EmailSSL].(bool)
|
||||
email.From = cfg[common.EmailFrom].(string)
|
||||
email.Identity = cfg[common.EmailIdentity].(string)
|
||||
email.Insecure = cfg[common.EmailInsecure].(bool)
|
||||
|
||||
return email, nil
|
||||
}
|
||||
@ -378,6 +387,7 @@ func AdmiralEndpoint() string {
|
||||
log.Errorf("Failed to get configuration, will return empty string as admiral's endpoint, error: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
if e, ok := cfg[common.AdmiralEndpoint].(string); !ok || e == "NA" {
|
||||
return ""
|
||||
}
|
||||
|
@ -171,7 +171,8 @@ func (cc *CommonController) SendEmail() {
|
||||
settings.Username,
|
||||
settings.Password,
|
||||
60, settings.SSL,
|
||||
false, settings.From,
|
||||
settings.Insecure,
|
||||
settings.From,
|
||||
[]string{email},
|
||||
"Reset Harbor user password",
|
||||
message.String())
|
||||
|
@ -25,15 +25,15 @@ import (
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
secstore "github.com/vmware/harbor/src/common/secret"
|
||||
"github.com/vmware/harbor/src/common/security"
|
||||
"github.com/vmware/harbor/src/common/security/admiral"
|
||||
admr "github.com/vmware/harbor/src/common/security/admiral"
|
||||
"github.com/vmware/harbor/src/common/security/admiral/authcontext"
|
||||
"github.com/vmware/harbor/src/common/security/local"
|
||||
"github.com/vmware/harbor/src/common/security/secret"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/auth"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager/pms"
|
||||
"github.com/vmware/harbor/src/ui/promgr"
|
||||
//"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
|
||||
)
|
||||
|
||||
type key string
|
||||
@ -192,7 +192,7 @@ func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||
log.Debug("using global project manager...")
|
||||
pm := config.GlobalProjectMgr
|
||||
log.Debug("creating admiral security context...")
|
||||
securCtx := admiral.NewSecurityContext(authCtx, pm)
|
||||
securCtx := admr.NewSecurityContext(authCtx, pm)
|
||||
|
||||
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
||||
return true
|
||||
@ -264,13 +264,18 @@ func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
log.Debug("creating PMS project manager...")
|
||||
pm := pms.NewProjectManager(config.AdmiralClient,
|
||||
config.AdmiralEndpoint(), &pms.RawTokenReader{
|
||||
pm := admiral.NewProjectManager(config.AdmiralClient,
|
||||
config.AdmiralEndpoint(), &admiral.RawTokenReader{
|
||||
Token: token,
|
||||
})
|
||||
*/
|
||||
// TODO create the DefaultProjectManager with the real admiral PMSDriver
|
||||
pm := promgr.NewDefaultProjectManager(nil, false)
|
||||
|
||||
log.Debug("creating admiral security context...")
|
||||
securCtx := admiral.NewSecurityContext(authContext, pm)
|
||||
securCtx := admr.NewSecurityContext(authContext, pm)
|
||||
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
||||
|
||||
return true
|
||||
@ -283,14 +288,18 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||
log.Debug("user information is nil")
|
||||
|
||||
var securCtx security.Context
|
||||
var pm projectmanager.ProjectManager
|
||||
var pm promgr.ProjectManager
|
||||
if config.WithAdmiral() {
|
||||
// integration with admiral
|
||||
/*
|
||||
log.Debug("creating PMS project manager...")
|
||||
pm = pms.NewProjectManager(config.AdmiralClient,
|
||||
pm = admiral.NewProjectManager(config.AdmiralClient,
|
||||
config.AdmiralEndpoint(), nil)
|
||||
*/
|
||||
// TODO create the DefaultProjectManager with the real admiral PMSDriver
|
||||
pm = promgr.NewDefaultProjectManager(nil, false)
|
||||
log.Debug("creating admiral security context...")
|
||||
securCtx = admiral.NewSecurityContext(nil, pm)
|
||||
securCtx = admr.NewSecurityContext(nil, pm)
|
||||
} else {
|
||||
// standalone
|
||||
log.Debug("using local database project manager")
|
||||
@ -302,7 +311,7 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm projectmanager.ProjectManager) {
|
||||
func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm promgr.ProjectManager) {
|
||||
addToReqContext(req, securCtxKey, ctx)
|
||||
addToReqContext(req, pmKey, pm)
|
||||
}
|
||||
@ -331,7 +340,7 @@ func GetSecurityContext(req *http.Request) (security.Context, error) {
|
||||
}
|
||||
|
||||
// GetProjectManager tries to get project manager from request and returns it
|
||||
func GetProjectManager(req *http.Request) (projectmanager.ProjectManager, error) {
|
||||
func GetProjectManager(req *http.Request) (promgr.ProjectManager, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("request is nil")
|
||||
}
|
||||
@ -341,7 +350,7 @@ func GetProjectManager(req *http.Request) (projectmanager.ProjectManager, error)
|
||||
return nil, fmt.Errorf("the project manager got from request is nil")
|
||||
}
|
||||
|
||||
p, ok := pm.(projectmanager.ProjectManager)
|
||||
p, ok := pm.(promgr.ProjectManager)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("the variable got from request is not project manager type")
|
||||
}
|
||||
|
@ -37,8 +37,8 @@ import (
|
||||
_ "github.com/vmware/harbor/src/ui/auth/db"
|
||||
_ "github.com/vmware/harbor/src/ui/auth/ldap"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager/db"
|
||||
"github.com/vmware/harbor/src/ui/promgr"
|
||||
driver_local "github.com/vmware/harbor/src/ui/promgr/pmsdriver/local"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@ -316,9 +316,9 @@ func TestGetProjectManager(t *testing.T) {
|
||||
req, err = http.NewRequest("", "", nil)
|
||||
assert.Nil(t, err)
|
||||
req = req.WithContext(context.WithValue(req.Context(),
|
||||
pmKey, &db.ProjectManager{}))
|
||||
pmKey, promgr.NewDefaultProjectManager(driver_local.NewDriver(), true)))
|
||||
pm, err = GetProjectManager(req)
|
||||
assert.Nil(t, err)
|
||||
_, ok := pm.(projectmanager.ProjectManager)
|
||||
_, ok := pm.(promgr.ProjectManager)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
65
src/ui/promgr/metamgr/metamgr.go
Normal file
65
src/ui/promgr/metamgr/metamgr.go
Normal 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
|
||||
}
|
17
src/ui/promgr/metamgr/metamgr_test.go
Normal file
17
src/ui/promgr/metamgr/metamgr_test.go
Normal 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
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pms
|
||||
package admiral
|
||||
|
||||
import (
|
||||
"bytes"
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pms
|
||||
package admiral
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@ -196,7 +196,7 @@ func TestGet(t *testing.T) {
|
||||
|
||||
// get by invalid ID
|
||||
project, err := pm.Get(int64(0))
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, project)
|
||||
|
||||
// get by invalid name
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pms
|
||||
package admiral
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pms
|
||||
package admiral
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
@ -12,25 +12,25 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package projectmanager
|
||||
package pmsdriver
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
)
|
||||
|
||||
// ProjectManager is the project mamager which abstracts the operations related
|
||||
// to projects
|
||||
type ProjectManager interface {
|
||||
// PMSDriver defines the operations that a project management service driver
|
||||
// should implement
|
||||
type PMSDriver interface {
|
||||
// Get a project by ID or name
|
||||
Get(projectIDOrName interface{}) (*models.Project, error)
|
||||
IsPublic(projectIDOrName interface{}) (bool, error)
|
||||
Exist(projectIDOrName interface{}) (bool, error)
|
||||
// get all public project
|
||||
GetPublic() ([]*models.Project, error)
|
||||
// Create a project
|
||||
Create(*models.Project) (int64, error)
|
||||
// Delete a project by ID or name
|
||||
Delete(projectIDOrName interface{}) error
|
||||
// Update the properties of a project
|
||||
Update(projectIDOrName interface{}, project *models.Project) error
|
||||
// GetAll returns a project list according to the query parameters
|
||||
GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error)
|
||||
// GetTotal returns the total count according to the query parameters
|
||||
GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error)
|
||||
// List lists projects according to the query conditions
|
||||
// TODO remove base
|
||||
List(query *models.ProjectQueryParam,
|
||||
base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error)
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package db
|
||||
package local
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -21,61 +21,39 @@ import (
|
||||
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
errutil "github.com/vmware/harbor/src/common/utils/error"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/promgr/pmsdriver"
|
||||
)
|
||||
|
||||
const dupProjectPattern = `Duplicate entry '\w+' for key 'name'`
|
||||
|
||||
// ProjectManager implements pm.PM interface based on database
|
||||
type ProjectManager struct{}
|
||||
type driver struct {
|
||||
}
|
||||
|
||||
// NewDriver returns an instance of driver
|
||||
func NewDriver() pmsdriver.PMSDriver {
|
||||
return &driver{}
|
||||
}
|
||||
|
||||
// Get ...
|
||||
func (p *ProjectManager) Get(projectIDOrName interface{}) (
|
||||
func (d *driver) Get(projectIDOrName interface{}) (
|
||||
*models.Project, error) {
|
||||
switch projectIDOrName.(type) {
|
||||
case string:
|
||||
return dao.GetProjectByName(projectIDOrName.(string))
|
||||
case int64:
|
||||
return dao.GetProjectByID(projectIDOrName.(int64))
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type of %v, must be string or int64", projectIDOrName)
|
||||
}
|
||||
}
|
||||
|
||||
// Exist ...
|
||||
func (p *ProjectManager) Exist(projectIDOrName interface{}) (bool, error) {
|
||||
project, err := p.Get(projectIDOrName)
|
||||
id, name, err := utils.ParseProjectIDOrName(projectIDOrName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return project != nil, nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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 id > 0 {
|
||||
return dao.GetProjectByID(id)
|
||||
}
|
||||
|
||||
if project == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return project.Public == 1, nil
|
||||
}
|
||||
|
||||
// GetPublic returns all public projects
|
||||
func (p *ProjectManager) GetPublic() ([]*models.Project, error) {
|
||||
t := true
|
||||
return p.GetAll(&models.ProjectQueryParam{
|
||||
Public: &t,
|
||||
})
|
||||
return dao.GetProjectByName(name)
|
||||
}
|
||||
|
||||
// Create ...
|
||||
func (p *ProjectManager) Create(project *models.Project) (int64, error) {
|
||||
func (d *driver) Create(project *models.Project) (int64, error) {
|
||||
if project == nil {
|
||||
return 0, fmt.Errorf("project is nil")
|
||||
}
|
||||
@ -128,10 +106,13 @@ func (p *ProjectManager) Create(project *models.Project) (int64, error) {
|
||||
}
|
||||
|
||||
// Delete ...
|
||||
func (p *ProjectManager) Delete(projectIDOrName interface{}) error {
|
||||
id, ok := projectIDOrName.(int64)
|
||||
if !ok {
|
||||
project, err := p.Get(projectIDOrName)
|
||||
func (d *driver) Delete(projectIDOrName interface{}) error {
|
||||
id, name, err := utils.ParseProjectIDOrName(projectIDOrName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(name) > 0 {
|
||||
project, err := dao.GetProjectByName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -142,27 +123,31 @@ func (p *ProjectManager) Delete(projectIDOrName interface{}) error {
|
||||
}
|
||||
|
||||
// Update ...
|
||||
func (p *ProjectManager) Update(projectIDOrName interface{},
|
||||
func (d *driver) Update(projectIDOrName interface{},
|
||||
project *models.Project) error {
|
||||
id, ok := projectIDOrName.(int64)
|
||||
if !ok {
|
||||
pro, err := p.Get(projectIDOrName)
|
||||
// nil implement
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO remove base
|
||||
// List returns a project list according to the query parameters
|
||||
func (d *driver) List(query *models.ProjectQueryParam,
|
||||
base ...*models.BaseProjectCollection) (
|
||||
*models.ProjectQueryResult, error) {
|
||||
total, err := dao.GetTotalOfProjects(query, base...)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
id = pro.ProjectID
|
||||
projects, err := dao.GetProjects(query, base...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dao.ToggleProjectPublicity(id, project.Public)
|
||||
return &models.ProjectQueryResult{
|
||||
Total: total,
|
||||
Projects: projects,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAll returns a project list according to the query parameters
|
||||
func (p *ProjectManager) GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (
|
||||
[]*models.Project, error) {
|
||||
return dao.GetProjects(query, base...)
|
||||
}
|
||||
|
||||
// GetTotal returns the total count according to the query parameters
|
||||
func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (
|
||||
int64, error) {
|
||||
return dao.GetTotalOfProjects(query, base...)
|
||||
func (d *driver) EnableExternalMetaMgr() bool {
|
||||
return true
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package db
|
||||
package local
|
||||
|
||||
import (
|
||||
"os"
|
||||
@ -71,7 +71,7 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
pm := &driver{}
|
||||
|
||||
// project name
|
||||
project, err := pm.Get("library")
|
||||
@ -95,45 +95,8 @@ func TestGet(t *testing.T) {
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestExist(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
|
||||
// exist project
|
||||
exist, err := pm.Exist("library")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, exist)
|
||||
|
||||
// non-exist project
|
||||
exist, err = pm.Exist("non-exist-project")
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, exist)
|
||||
}
|
||||
|
||||
func TestIsPublic(t *testing.T) {
|
||||
pms := &ProjectManager{}
|
||||
// public project
|
||||
public, err := pms.IsPublic("library")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, public)
|
||||
// non exist project
|
||||
public, err = pms.IsPublic("non_exist_project")
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, public)
|
||||
}
|
||||
|
||||
func TestGetPublic(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
projects, err := pm.GetPublic()
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, 0, len(projects))
|
||||
|
||||
for _, project := range projects {
|
||||
assert.Equal(t, 1, project.Public)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAndDelete(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
pm := &driver{}
|
||||
|
||||
// nil project
|
||||
_, err := pm.Create(nil)
|
||||
@ -183,63 +146,12 @@ func TestCreateAndDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
|
||||
id, err := pm.Create(&models.Project{
|
||||
Name: "test",
|
||||
OwnerID: 1,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
defer pm.Delete(id)
|
||||
|
||||
project, err := pm.Get(id)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, project.Public)
|
||||
|
||||
project.Public = 1
|
||||
assert.Nil(t, pm.Update(id, project))
|
||||
|
||||
project, err = pm.Get(id)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, project.Public)
|
||||
pm := &driver{}
|
||||
assert.Nil(t, pm.Update(1, nil))
|
||||
}
|
||||
|
||||
func TestGetTotal(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
|
||||
id, err := pm.Create(&models.Project{
|
||||
Name: "get_total_test",
|
||||
OwnerID: 1,
|
||||
Public: 1,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
defer pm.Delete(id)
|
||||
|
||||
// get by name
|
||||
total, err := pm.GetTotal(&models.ProjectQueryParam{
|
||||
Name: "get_total_test",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
|
||||
// get by owner
|
||||
total, err = pm.GetTotal(&models.ProjectQueryParam{
|
||||
Owner: "admin",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, 0, total)
|
||||
|
||||
// get by public
|
||||
value := true
|
||||
total, err = pm.GetTotal(&models.ProjectQueryParam{
|
||||
Public: &value,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, 0, total)
|
||||
}
|
||||
|
||||
func TestGetAll(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
func TestList(t *testing.T) {
|
||||
pm := &driver{}
|
||||
|
||||
id, err := pm.Create(&models.Project{
|
||||
Name: "get_all_test",
|
||||
@ -250,19 +162,19 @@ func TestGetAll(t *testing.T) {
|
||||
defer pm.Delete(id)
|
||||
|
||||
// get by name
|
||||
projects, err := pm.GetAll(&models.ProjectQueryParam{
|
||||
result, err := pm.List(&models.ProjectQueryParam{
|
||||
Name: "get_all_test",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, id, projects[0].ProjectID)
|
||||
assert.Equal(t, id, result.Projects[0].ProjectID)
|
||||
|
||||
// get by owner
|
||||
projects, err = pm.GetAll(&models.ProjectQueryParam{
|
||||
result, err = pm.List(&models.ProjectQueryParam{
|
||||
Owner: "admin",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
exist := false
|
||||
for _, project := range projects {
|
||||
for _, project := range result.Projects {
|
||||
if project.ProjectID == id {
|
||||
exist = true
|
||||
break
|
||||
@ -272,12 +184,12 @@ func TestGetAll(t *testing.T) {
|
||||
|
||||
// get by public
|
||||
value := true
|
||||
projects, err = pm.GetAll(&models.ProjectQueryParam{
|
||||
result, err = pm.List(&models.ProjectQueryParam{
|
||||
Public: &value,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
exist = false
|
||||
for _, project := range projects {
|
||||
for _, project := range result.Projects {
|
||||
if project.ProjectID == id {
|
||||
exist = true
|
||||
break
|
172
src/ui/promgr/promgr.go
Normal file
172
src/ui/promgr/promgr.go
Normal 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
|
||||
}
|
120
src/ui/promgr/promgr_test.go
Normal file
120
src/ui/promgr/promgr_test.go
Normal 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)
|
||||
}
|
@ -2,14 +2,14 @@ package proxy
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
//"github.com/stretchr/testify/require"
|
||||
"github.com/vmware/harbor/src/adminserver/client"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
notarytest "github.com/vmware/harbor/src/common/utils/notary/test"
|
||||
utilstest "github.com/vmware/harbor/src/common/utils/test"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager/pms"
|
||||
//"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
|
||||
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -128,6 +128,8 @@ func TestEnvPolicyChecker(t *testing.T) {
|
||||
assert.Equal(sev, models.SevNone)
|
||||
}
|
||||
|
||||
// TODO uncheck after admiral pms driver is implemented
|
||||
/*
|
||||
func TestPMSPolicyChecker(t *testing.T) {
|
||||
var defaultConfigAdmiral = map[string]interface{}{
|
||||
common.ExtEndpoint: "https://" + endpoint,
|
||||
@ -147,9 +149,8 @@ func TestPMSPolicyChecker(t *testing.T) {
|
||||
if err := config.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pm := pms.NewProjectManager(http.DefaultClient,
|
||||
admiralEndpoint, &pms.RawTokenReader{
|
||||
pm := admiral.NewProjectManager(http.DefaultClient,
|
||||
admiralEndpoint, &admiral.RawTokenReader{
|
||||
Token: "token",
|
||||
})
|
||||
name := "project_for_test_get_sev_low"
|
||||
@ -175,7 +176,7 @@ func TestPMSPolicyChecker(t *testing.T) {
|
||||
assert.False(t, projectVulnerableEnabled)
|
||||
assert.Equal(t, projectVulnerableSeverity, models.SevLow)
|
||||
}
|
||||
|
||||
*/
|
||||
func TestMatchNotaryDigest(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
//The data from common/utils/notary/helper_test.go
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/utils/notary"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
"github.com/vmware/harbor/src/ui/promgr"
|
||||
uiutils "github.com/vmware/harbor/src/ui/utils"
|
||||
|
||||
"context"
|
||||
@ -88,7 +88,7 @@ func (ec envPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity)
|
||||
}
|
||||
|
||||
type pmsPolicyChecker struct {
|
||||
pm projectmanager.ProjectManager
|
||||
pm promgr.ProjectManager
|
||||
}
|
||||
|
||||
func (pc pmsPolicyChecker) contentTrustEnabled(name string) bool {
|
||||
@ -109,7 +109,7 @@ func (pc pmsPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity)
|
||||
}
|
||||
|
||||
// newPMSPolicyChecker returns an instance of an pmsPolicyChecker
|
||||
func newPMSPolicyChecker(pm projectmanager.ProjectManager) policyChecker {
|
||||
func newPMSPolicyChecker(pm promgr.ProjectManager) policyChecker {
|
||||
return &pmsPolicyChecker{
|
||||
pm: pm,
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ import (
|
||||
"github.com/vmware/harbor/src/common/security"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
promgr "github.com/vmware/harbor/src/ui/projectmanager"
|
||||
"github.com/vmware/harbor/src/ui/promgr"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -26,7 +26,7 @@ import (
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
"github.com/vmware/harbor/src/ui/filter"
|
||||
promgr "github.com/vmware/harbor/src/ui/projectmanager"
|
||||
"github.com/vmware/harbor/src/ui/promgr"
|
||||
)
|
||||
|
||||
var creatorMap map[string]Creator
|
||||
@ -161,7 +161,7 @@ func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProjectManage
|
||||
project := img.namespace
|
||||
permission := ""
|
||||
|
||||
exist, err := pm.Exist(project)
|
||||
exist, err := pm.Exists(project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import { ReplicationService } from '../service/replication.service';
|
||||
import { ReplicationRule } from '../service/interface';
|
||||
import {ReplicationJob, ReplicationJobItem, ReplicationRule} from '../service/interface';
|
||||
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
|
||||
@ -70,6 +70,7 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
|
||||
rules: ReplicationRule[];
|
||||
changedRules: ReplicationRule[];
|
||||
ruleName: string;
|
||||
canDeleteRule: boolean;
|
||||
|
||||
@ViewChild('toggleConfirmDialog')
|
||||
toggleConfirmDialog: ConfirmationDialogComponent;
|
||||
@ -199,15 +200,48 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
|
||||
this.toggleConfirmDialog.open(toggleConfirmMessage);
|
||||
}
|
||||
|
||||
jobList(): Promise<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) {
|
||||
let deletionMessage: ConfirmationMessage = new ConfirmationMessage(
|
||||
this.jobList().then(() => {
|
||||
let deletionMessage: ConfirmationMessage;
|
||||
if (!this.canDeleteRule) {
|
||||
deletionMessage = new ConfirmationMessage(
|
||||
'REPLICATION.DELETION_TITLE_FAILURE',
|
||||
'REPLICATION.DELETION_SUMMARY_FAILURE',
|
||||
rule.name || '',
|
||||
rule.id,
|
||||
ConfirmationTargets.POLICY,
|
||||
ConfirmationButtons.CLOSE);
|
||||
} else {
|
||||
deletionMessage = new ConfirmationMessage(
|
||||
'REPLICATION.DELETION_TITLE',
|
||||
'REPLICATION.DELETION_SUMMARY',
|
||||
rule.name || '',
|
||||
rule.id,
|
||||
ConfirmationTargets.POLICY,
|
||||
ConfirmationButtons.DELETE_CANCEL);
|
||||
}
|
||||
this.deletionConfirmDialog.open(deletionMessage);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -11,7 +11,7 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component, OnInit, ViewChild, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild, Input, Output, OnDestroy, EventEmitter } from '@angular/core';
|
||||
import { ResponseOptions, RequestOptions } from '@angular/http';
|
||||
import { NgModel } from '@angular/forms';
|
||||
|
||||
@ -41,6 +41,8 @@ import { REPLICATION_STYLE } from './replication.component.css';
|
||||
|
||||
import { JobLogViewerComponent } from '../job-log-viewer/index';
|
||||
import { State } from "clarity-angular";
|
||||
import {Observable} from "rxjs/Observable";
|
||||
import {Subscription} from "rxjs/Subscription";
|
||||
|
||||
const ruleStatus: { [key: string]: any } = [
|
||||
{ 'key': 'all', 'description': 'REPLICATION.ALL_STATUS' },
|
||||
@ -79,7 +81,7 @@ export class SearchOption {
|
||||
template: REPLICATION_TEMPLATE,
|
||||
styles: [REPLICATION_STYLE]
|
||||
})
|
||||
export class ReplicationComponent implements OnInit {
|
||||
export class ReplicationComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Input() projectId: number | string;
|
||||
@Input() withReplicationJob: boolean;
|
||||
@ -124,6 +126,7 @@ export class ReplicationComponent implements OnInit {
|
||||
pageSize: number = DEFAULT_PAGE_SIZE;
|
||||
currentState: State;
|
||||
jobsLoading: boolean = false;
|
||||
timerDelay: Subscription;
|
||||
|
||||
constructor(
|
||||
private errorHandler: ErrorHandler,
|
||||
@ -145,6 +148,12 @@ export class ReplicationComponent implements OnInit {
|
||||
this.currentJobSearchOption = 0;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.timerDelay) {
|
||||
this.timerDelay.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
openModal(): void {
|
||||
this.createEditPolicyComponent.openCreateEditRule(true);
|
||||
}
|
||||
@ -197,6 +206,23 @@ export class ReplicationComponent implements OnInit {
|
||||
this.totalCount = response.metadata.xTotalCount;
|
||||
this.jobs = response.data;
|
||||
|
||||
if (!this.timerDelay) {
|
||||
this.timerDelay = Observable.timer(10000, 10000).subscribe(() => {
|
||||
let count: number = 0;
|
||||
this.jobs.forEach((job) => {
|
||||
if ((job.status === 'pending') || (job.status === 'running') || (job.status === 'retrying')) {
|
||||
count ++;
|
||||
}
|
||||
});
|
||||
if (count > 0) {
|
||||
this.clrLoadJobs(this.currentState);
|
||||
}else {
|
||||
this.timerDelay.unsubscribe();
|
||||
this.timerDelay = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Do filtering and sorting
|
||||
this.jobs = doFiltering<ReplicationJobItem>(this.jobs, state);
|
||||
this.jobs = doSorting<ReplicationJobItem>(this.jobs, state);
|
||||
|
@ -75,6 +75,7 @@ describe('RepositoryComponentStackview (inline template)', () => {
|
||||
{
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "1.11.5",
|
||||
"size": "2049",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
|
@ -51,6 +51,7 @@ export interface Repository {
|
||||
export interface Tag extends Base {
|
||||
digest: string;
|
||||
name: string;
|
||||
size: string;
|
||||
architecture: string;
|
||||
os: string;
|
||||
docker_version: string;
|
||||
|
@ -14,6 +14,7 @@ describe('TagService', () => {
|
||||
{
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "1.11.5",
|
||||
"size": "2049",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
|
@ -43,6 +43,7 @@ describe('TagDetailComponent (inline template)', () => {
|
||||
let mockTag: Tag = {
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "nginx",
|
||||
"size": "2049",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
|
@ -24,6 +24,7 @@ export class TagDetailComponent implements OnInit {
|
||||
@Input() repositoryId: string;
|
||||
tagDetails: Tag = {
|
||||
name: "--",
|
||||
size: "--",
|
||||
author: "--",
|
||||
created: new Date(),
|
||||
architecture: "--",
|
||||
|
@ -43,4 +43,9 @@ export const TAG_STYLE = `
|
||||
color: red;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
:host >>> .datagrid clr-dg-column {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
`;
|
@ -16,14 +16,13 @@ export const TAG_TEMPLATE = `
|
||||
<h2 *ngIf="!isEmbedded" class="sub-header-title">{{repoName}}</h2>
|
||||
<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]="'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="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: 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: 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-row *clrDgItems="let t of tags" [clrDgItem]='t'>
|
||||
<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" (click)="showDigestId(t)">{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</button>
|
||||
</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>
|
||||
<span *ngSwitchDefault>{{t.name}}</span>
|
||||
</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="width: 160px;" *ngIf="withClair">
|
||||
<hbr-vulnerability-bar [repoName]="repoName" [tagId]="t.name" [summary]="t.scan_overview"></hbr-vulnerability-bar>
|
||||
@ -50,8 +50,6 @@ export const TAG_TEMPLATE = `
|
||||
<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: 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-footer>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
|
||||
|
@ -29,6 +29,7 @@ describe('TagComponent (inline template)', () => {
|
||||
{
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "1.11.5",
|
||||
"size": "2049",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
|
@ -159,6 +159,9 @@ export class TagComponent implements OnInit {
|
||||
if (t.signature !== null) {
|
||||
signatures.push(t.name);
|
||||
}
|
||||
|
||||
//size
|
||||
t.size = this.sizeTransform(t.size);
|
||||
});
|
||||
this.tags = items;
|
||||
let signedName: {[key: string]: string[]} = {};
|
||||
@ -177,6 +180,19 @@ export class TagComponent implements OnInit {
|
||||
setTimeout(() => clearInterval(hnd), 5000);
|
||||
}
|
||||
|
||||
sizeTransform(tagSize: string): string {
|
||||
let size: number = Number.parseInt(tagSize);
|
||||
if (Math.pow(1024, 1) <= size && size < Math.pow(1024, 2)) {
|
||||
return (size / Math.pow(1024, 1)).toFixed(2) + 'KB';
|
||||
} else if (Math.pow(1024, 2) <= size && size < Math.pow(1024, 3)) {
|
||||
return (size / Math.pow(1024, 2)).toFixed(2) + 'MB';
|
||||
} else if (Math.pow(1024, 3) <= size && size < Math.pow(1024, 4)) {
|
||||
return (size / Math.pow(1024, 3)).toFixed(2) + 'MB';
|
||||
} else {
|
||||
return size + 'B';
|
||||
}
|
||||
}
|
||||
|
||||
deleteTag(tag: Tag) {
|
||||
if (tag) {
|
||||
let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons;
|
||||
@ -246,6 +262,13 @@ export class TagComponent implements OnInit {
|
||||
return VULNERABILITY_SCAN_STATUS.unknown;
|
||||
}
|
||||
|
||||
existObservablePackage(t: Tag): boolean {
|
||||
return t.scan_overview &&
|
||||
t.scan_overview.components &&
|
||||
t.scan_overview.components.total &&
|
||||
t.scan_overview.components.total > 0 ? true : false;
|
||||
}
|
||||
|
||||
//Whether show the 'scan now' menu
|
||||
canScanNow(t: Tag): boolean {
|
||||
if (!this.withClair) { return false; }
|
||||
|
@ -45,6 +45,8 @@ export class ResultTipComponent implements OnInit {
|
||||
level = VulnerabilitySeverity.LOW;
|
||||
}else if (this._unknownCount && this._unknownCount >= 1) {
|
||||
level = VulnerabilitySeverity.UNKNOWN;
|
||||
}else if (this.totalPackages == 0) {
|
||||
level = VulnerabilitySeverity.UNKNOWN;
|
||||
} else {
|
||||
level = VulnerabilitySeverity.NONE;
|
||||
}
|
||||
@ -105,8 +107,8 @@ export class ResultTipComponent implements OnInit {
|
||||
let m: number = this.totalPackages;
|
||||
|
||||
if (m === 0) {
|
||||
//If no packages recognized, then show green bar
|
||||
if (severity === VulnerabilitySeverity.NONE) {
|
||||
//If no packages recognized, then show grey
|
||||
if (severity === VulnerabilitySeverity.UNKNOWN) {
|
||||
return MAX_TIP_WIDTH + 'px';
|
||||
} else {
|
||||
return 0 + 'px';
|
||||
|
@ -31,7 +31,7 @@
|
||||
"clarity-icons": "^0.9.8",
|
||||
"clarity-ui": "^0.9.8",
|
||||
"core-js": "^2.4.1",
|
||||
"harbor-ui": "0.4.60",
|
||||
"harbor-ui": "0.4.72",
|
||||
"intl": "^1.2.5",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
"ngx-cookie": "^1.0.0",
|
||||
|
@ -2,3 +2,25 @@
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.selectBox{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border: 1px solid #ccc;
|
||||
background-color: white;
|
||||
border: 1px solid rgba(0,0,0,.15);
|
||||
border-right-width: 2px;
|
||||
border-bottom-width: 2px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||
z-index: 100;
|
||||
}
|
||||
.selectBox ul li{
|
||||
list-style: none;
|
||||
padding: 3px 20px
|
||||
}
|
||||
.selectBox ul li:hover{
|
||||
color: #262626;
|
||||
background-image: linear-gradient(180deg,#f5f5f5 0,#e8e8e8);
|
||||
background-repeat: repeat-x;
|
||||
}
|
@ -6,16 +6,22 @@
|
||||
<section class="form-block">
|
||||
<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" 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"
|
||||
class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" (mouseleave)="leaveInput()">
|
||||
<input type="text" id="member_name" [(ngModel)]="member.username"
|
||||
name="member_name"
|
||||
size="20"
|
||||
#memberName="ngModel"
|
||||
required
|
||||
(keyup)='handleValidation()'>
|
||||
(keyup)='handleValidation()' autocomplete="off">
|
||||
<span class="tooltip-content">
|
||||
{{ memberTooltip | translate }}
|
||||
</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>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkOnGoing"></span>
|
||||
</div>
|
||||
|
@ -25,6 +25,7 @@ import { Response } from '@angular/http';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { MemberService } from '../member.service';
|
||||
import { UserService } from '../../../user/user.service';
|
||||
|
||||
import { MessageHandlerService } from '../../../shared/message-handler/message-handler.service';
|
||||
import { InlineAlertComponent } from '../../../shared/inline-alert/inline-alert.component';
|
||||
@ -36,11 +37,13 @@ import { Member } from '../member';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import {User} from "../../../user/user";
|
||||
|
||||
@Component({
|
||||
selector: 'add-member',
|
||||
templateUrl: 'add-member.component.html',
|
||||
styleUrls: ['add-member.component.css'],
|
||||
providers: [UserService],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
@ -69,13 +72,21 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
memberTooltip: string = 'MEMBER.USERNAME_IS_REQUIRED';
|
||||
nameChecker: Subject<string> = new Subject<string>();
|
||||
checkOnGoing: boolean = false;
|
||||
selectUserName: string[] = [];
|
||||
userLists: User[];
|
||||
|
||||
constructor(private memberService: MemberService,
|
||||
private userService: UserService,
|
||||
private messageHandlerService: MessageHandlerService,
|
||||
private translateService: TranslateService,
|
||||
private ref: ChangeDetectorRef) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.userService.getUsers()
|
||||
.then(users => {
|
||||
this.userLists = users;
|
||||
});
|
||||
|
||||
this.nameChecker
|
||||
.debounceTime(500)
|
||||
.distinctUntilChanged()
|
||||
@ -97,6 +108,20 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
.catch(error => {
|
||||
this.checkOnGoing = false;
|
||||
});
|
||||
//username autocomplete
|
||||
if (this.userLists.length) {
|
||||
this.selectUserName = [];
|
||||
this.userLists.filter(data => {
|
||||
if (data.username.startsWith(cont.value)) {
|
||||
if (this.selectUserName.length < 10) {
|
||||
this.selectUserName.push(data.username);
|
||||
}
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
setInterval(() => this.ref.markForCheck(), 100);
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED';
|
||||
}
|
||||
@ -148,6 +173,11 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
selectedName(username: string) {
|
||||
this.member.username = username;
|
||||
this.selectUserName = [];
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
if (this.hasChanged) {
|
||||
this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' });
|
||||
@ -157,6 +187,9 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
leaveInput() {
|
||||
this.selectUserName = [];
|
||||
}
|
||||
ngAfterViewChecked(): void {
|
||||
if (this.memberForm !== this.currentForm) {
|
||||
this.memberForm = this.currentForm;
|
||||
@ -189,6 +222,7 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
this.member.username = '';
|
||||
this.isMemberNameValid = true;
|
||||
this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED';
|
||||
this.selectUserName = [];
|
||||
}
|
||||
|
||||
handleValidation(): void {
|
||||
|
@ -215,6 +215,8 @@
|
||||
"FILTER_JOBS_PLACEHOLDER": "Filter Jobs",
|
||||
"DELETION_TITLE": "Confirm Rule Deletion",
|
||||
"DELETION_SUMMARY": "Do you want to delete rule {{param}}?",
|
||||
"DELETION_TITLE_FAILURE": "failed to delete Rule Deletion",
|
||||
"DELETION_SUMMARY_FAILURE": "{{param}} have pending/running/retrying status",
|
||||
"FILTER_TARGETS_PLACEHOLDER": "Filter Endpoints",
|
||||
"DELETION_TITLE_TARGET": "Confirm Endpoint Deletion",
|
||||
"DELETION_SUMMARY_TARGET": "Do you want to delete the endpoint {{param}}?",
|
||||
@ -330,6 +332,7 @@
|
||||
"DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted.\nDelete from Notary via this command:\n{{param}}",
|
||||
"FILTER_FOR_REPOSITORIES": "Filter Repositories",
|
||||
"TAG": "Tag",
|
||||
"SIZE": "Size",
|
||||
"SIGNED": "Signed",
|
||||
"AUTHOR": "Author",
|
||||
"CREATED": "Creation Time",
|
||||
|
@ -215,6 +215,8 @@
|
||||
"FILTER_JOBS_PLACEHOLDER": "Filtrar Trabajos",
|
||||
"DELETION_TITLE": "Confirmar Eliminación de Regla",
|
||||
"DELETION_SUMMARY": "¿Quiere eliminar la regla {{param}}?",
|
||||
"DELETION_TITLE_FAILURE": "failed to delete Rule Deletion",
|
||||
"DELETION_SUMMARY_FAILURE": "{{param}} have pending/running/retrying status",
|
||||
"FILTER_TARGETS_PLACEHOLDER": "Filtrar Endpoints",
|
||||
"DELETION_TITLE_TARGET": "Confirmar Eliminación de Endpoint",
|
||||
"DELETION_SUMMARY_TARGET": "¿Quiere eliminar el endpoint {{param}}?",
|
||||
@ -331,6 +333,7 @@
|
||||
"DELETION_SUMMARY_TAG_DENIED": "La etiqueta debe ser eliminada de la Notaría antes de eliminarla.\nEliminarla de la Notaría con este comando:\n{{param}}",
|
||||
"FILTER_FOR_REPOSITORIES": "Filtrar Repositorios",
|
||||
"TAG": "Etiqueta",
|
||||
"SIZE": "Size",
|
||||
"SIGNED": "Firmada",
|
||||
"AUTHOR": "Autor",
|
||||
"CREATED": "Fecha de creación",
|
||||
|
@ -215,6 +215,8 @@
|
||||
"FILTER_JOBS_PLACEHOLDER": "过滤任务",
|
||||
"DELETION_TITLE": "删除规则确认",
|
||||
"DELETION_SUMMARY": "确认删除规则 {{param}}?",
|
||||
"DELETION_TITLE_FAILURE": "规则确认删除失败",
|
||||
"DELETION_SUMMARY_FAILURE": "{{param}} 有 pending/running/retrying 状态,不能删除",
|
||||
"FILTER_TARGETS_PLACEHOLDER": "过滤目标",
|
||||
"DELETION_TITLE_TARGET": "删除目标确认",
|
||||
"DELETION_SUMMARY_TARGET": "确认删除目标 {{param}}?",
|
||||
@ -330,6 +332,7 @@
|
||||
"DELETION_SUMMARY_TAG_DENIED": "要删除此镜像标签必须首先从Notary中删除。\n请执行如下Notary命令删除:\n{{param}}",
|
||||
"FILTER_FOR_REPOSITORIES": "过滤镜像仓库",
|
||||
"TAG": "标签",
|
||||
"SIZE": "大小",
|
||||
"SIGNED": "已签名",
|
||||
"AUTHOR": "作者",
|
||||
"CREATED": "创建时间",
|
||||
|
@ -48,6 +48,19 @@ Push image
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
${rc}= Run And Return Rc docker logout ${ip}
|
||||
|
||||
Push Image With Tag
|
||||
[Arguments] ${ip} ${user} ${pwd} ${project} ${image} ${tag}
|
||||
Log To Console \nRunning docker push ${image}...
|
||||
${rc}= Run And Return Rc docker pull ${image}
|
||||
${rc} ${output}= Run And Return Rc And Output docker login -u ${user} -p ${pwd} ${ip}
|
||||
Log To Console ${output}
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
${rc}= Run And Return Rc docker tag ${image} ${tag}
|
||||
${rc} ${output}= Run And Return Rc And Output docker push ${tag}
|
||||
Log To Console ${output}
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
${rc}= Run And Return Rc docker logout ${ip}
|
||||
|
||||
Cannot Pull image
|
||||
[Arguments] ${ip} ${user} ${pwd} ${project} ${image}
|
||||
${rc} ${output}= Run And Return Rc And Output docker login -u ${user} -p ${pwd} ${ip}
|
||||
@ -56,6 +69,19 @@ Cannot Pull image
|
||||
Log To Console ${output}
|
||||
Should Not Be Equal As Integers ${rc} 0
|
||||
|
||||
Cannot Push image
|
||||
[Arguments] ${ip} ${user} ${pwd} ${project} ${image}
|
||||
Log To Console \nRunning docker push ${image}...
|
||||
${rc}= Run And Return Rc docker pull ${image}
|
||||
${rc} ${output}= Run And Return Rc And Output docker login -u ${user} -p ${pwd} ${ip}
|
||||
Log To Console ${output}
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
${rc}= Run And Return Rc docker tag ${image} ${ip}/${project}/${image}
|
||||
${rc} ${output}= Run And Return Rc And Output docker push ${ip}/${project}/${image}
|
||||
Log To Console ${output}
|
||||
Should Not Be Equal As Integers ${rc} 0
|
||||
${rc}= Run And Return Rc docker logout ${ip}
|
||||
|
||||
Wait Until Container Stops
|
||||
[Arguments] ${container}
|
||||
:FOR ${idx} IN RANGE 0 60
|
||||
|
@ -169,4 +169,3 @@ Set Scan All To Daily
|
||||
click element //config//div/button[contains(.,'SAVE')]
|
||||
Click Scan Now
|
||||
click element //vulnerability-config//button[contains(.,'SCAN')]
|
||||
|
||||
|
@ -65,7 +65,7 @@ Search Project Member
|
||||
[Arguments] ${project} ${user}
|
||||
Go Into Project ${project}
|
||||
Sleep 2
|
||||
Click Element xpath=${project_member_tag_xpath}
|
||||
Click Element xpath=//clr-dg-cell//a[contains(.,"${project}")]
|
||||
Sleep 1
|
||||
Click Element xpath=${project_member_search_button_xpath}
|
||||
Sleep 1
|
||||
@ -79,11 +79,109 @@ Change Project Member Role
|
||||
Sleep 2
|
||||
Click Element xpath=${project_member_tag_xpath}
|
||||
Sleep 1
|
||||
Click Element xpath=//project-detail//clr-dg-row-master[contains(.,"${user}")]//clr-dg-action-overflow
|
||||
Click Element xpath=//project-detail//clr-dg-row-master[contains(.,'${user}')]//clr-dg-action-overflow
|
||||
Sleep 1
|
||||
Click Element xpath=//project-detail//clr-dg-action-overflow//button[contains(.,"${role}")]
|
||||
Sleep 2
|
||||
Wait Until Page Contains ${role}
|
||||
|
||||
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
|
@ -23,3 +23,8 @@ ${project_member_add_admin_xpath} /html/body/harbor-app/harbor-shell/clr-main-c
|
||||
${project_member_add_save_button_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/ng-component/div/div[1]/div/div[1]/add-member/clr-modal/div/div[1]/div/div[1]/div/div[3]/button[2]
|
||||
${project_member_search_button_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/ng-component/div/div[1]/div/div[2]/hbr-filter/span/clr-icon/svg
|
||||
${project_member_search_text_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/ng-component/div/div[1]/div/div[2]/hbr-filter/span/input
|
||||
${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]
|
@ -47,6 +47,10 @@ Go To Project Log
|
||||
Click Element xpath=//project-detail//ul/li[3]
|
||||
Sleep 2
|
||||
|
||||
Switch To Member
|
||||
Click Element xpath=//project-detail//li[2]
|
||||
Sleep 1
|
||||
|
||||
Switch To Log
|
||||
Click Element xpath=${log_xpath}
|
||||
Sleep 1
|
||||
|
@ -244,6 +244,32 @@ Test Case - Scan A Tag
|
||||
Summary Chart Should Display project${d}
|
||||
Close Browser
|
||||
|
||||
Test Case-Manage Project Member
|
||||
Init Chrome Driver
|
||||
${d}= Get current Date result_format=%m%s
|
||||
|
||||
Create An New Project With New User url=${HARBOR_URL} username=alice${d} email=alice${d}@vmware.com realname=alice${d} newPassword=Test1@34 comment=harbor projectname=project${d} public=false
|
||||
Push image ip=${ip} user=alice${d} pwd=Test1@34 project=project${d} image=hello-world
|
||||
Logout Harbor
|
||||
Create An New User url=${HARBOR_URL} username=bob${d} email=bob${d}@vmware.com realname=bob${d} newPassword=Test1@34 comment=habor
|
||||
Logout Harbor
|
||||
Create An New User url=${HARBOR_URL} username=carol${d} email=carol${d}@vmware.com realname=carol${d} newPassword=Test1@34 comment=harbor
|
||||
Logout Harbor
|
||||
|
||||
User Should Be Owner Of Project alice${d} Test1@34 project${d}
|
||||
User Should Not Be A Member Of Project bob${d} Test1@34 project${d}
|
||||
Manage Project Member alice${d} Test1@34 project${d} bob${d} Add
|
||||
User Should Be Guest bob${d} Test1@34 project${d}
|
||||
Change User Role In Project alice${d} Test1@34 project${d} bob${d} Developer
|
||||
User Should Be Developer bob${d} Test1@34 project${d}
|
||||
Change User Role In Project alice${d} Test1@34 project${d} bob${d} Admin
|
||||
User Should Be Admin bob${d} Test1@34 project${d} carol${d}
|
||||
Manage Project Member alice${d} Test1@34 project${d} bob${d} Remove
|
||||
User Should Not Be A Member Of Project bob${d} Test1@34 project${d}
|
||||
User Should Be Guest carol${d} Test1@34 project${d}
|
||||
|
||||
Close Browser
|
||||
|
||||
Test Case - Assign Sys Admin
|
||||
Init Chrome Driver
|
||||
${d}= Get Current Date result_format=%m%s
|
||||
@ -274,7 +300,6 @@ Test Case - Admin Push Signed Image
|
||||
Log To Console ${output}
|
||||
|
||||
Push image ${ip} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} library hello-world:latest
|
||||
|
||||
${rc} ${output}= Run And Return Rc And Output ./tests/robot-cases/Group9-Content-trust/notary-push-image.sh
|
||||
Log To Console ${output}
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
|
@ -1,12 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker pull tomcat
|
||||
docker pull tomcat:latest
|
||||
|
||||
IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
|
||||
PASSHRASE='Harbor12345'
|
||||
|
||||
echo $IP
|
||||
|
||||
export DOCKER_CONTENT_TRUST=1
|
||||
export DOCKER_CONTENT_TRUST_SERVER=https://$IP:4443
|
||||
|
||||
docker login -u admin -p Harbor12345 $IP
|
||||
export NOTARY_ROOT_PASSPHRASE=$PASSHRASE
|
||||
export NOTARY_TARGETS_PASSPHRASE=$PASSHRASE
|
||||
export NOTARY_SNAPSHOT_PASSPHRASE=$PASSHRASE
|
||||
export DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE=$PASSHRASE
|
||||
export DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=$PASSHRASE
|
||||
export DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE=$PASSHRASE
|
||||
export DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE=$PASSHRASE
|
||||
|
||||
docker login -u admin -p Harbor12345 $IP
|
||||
docker tag tomcat $IP/library/tomcat:latest
|
||||
python ./tests/robot-cases/Group9-Content-trust/notary-push-image.py
|
||||
docker push $IP/library/tomcat:latest
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user