Merge branch 'master' of https://github.com/steven-zou/harbor into fork_master

This commit is contained in:
Steven Zou 2017-10-27 18:19:39 +08:00
commit 410908d863
62 changed files with 916 additions and 247 deletions

21
.gitmessage Normal file
View File

@ -0,0 +1,21 @@
SUBJECT HERE
#
# Please provide your commit's subject above this line.
#
# Guideline (http://chris.beams.io/posts/git-commit/)
#
# 1. Separate subject from body with a blank line
# 2. Limit the subject line to 50 characters
# 3. Capitalize the first letter subject line
# 4. Do not end the subject line with a period
# 5. Use the imperative mood in the subject line
# 6. Wrap the body at 72 characters
# 7. Use the body to explain what and why vs. how
#
# Description of your commit should go below. Make sure to leave
# one empty line after your description.
#
#
BODY LINE1
BODY LINE2

View File

@ -96,7 +96,6 @@ script:
- go test -race -i ./src/ui ./src/adminserver ./src/jobservice - go test -race -i ./src/ui ./src/adminserver ./src/jobservice
- sudo -E env "PATH=$PATH" ./tests/coverage4gotest.sh - sudo -E env "PATH=$PATH" ./tests/coverage4gotest.sh
- goveralls -coverprofile=profile.cov -service=travis-ci - goveralls -coverprofile=profile.cov -service=travis-ci
- docker-compose -f make/docker-compose.test.yml down - docker-compose -f make/docker-compose.test.yml down
- sudo rm -rf /data/config/* - sudo rm -rf /data/config/*
- ls /data/cert - ls /data/cert

View File

@ -86,7 +86,7 @@ NGINXVERSION=1.11.13
PHOTONVERSION=1.0 PHOTONVERSION=1.0
NOTARYVERSION=server-0.5.0 NOTARYVERSION=server-0.5.0
NOTARYSIGNERVERSION=signer-0.5.0 NOTARYSIGNERVERSION=signer-0.5.0
MARIADBVERSION=mariadb-10.1.10 MARIADBVERSION=10.2.8
HTTPPROXY= HTTPPROXY=
REBUILDCLARITYFLAG=false REBUILDCLARITYFLAG=false
NEWCLARITYVERSION= NEWCLARITYVERSION=
@ -241,7 +241,7 @@ DOCKERCOMPOSE_LIST=-f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
ifeq ($(NOTARYFLAG), true) ifeq ($(NOTARYFLAG), true)
DOCKERSAVE_PARA+= vmware/notary-photon:$(NOTARYVERSION) vmware/notary-photon:$(NOTARYSIGNERVERSION) \ DOCKERSAVE_PARA+= vmware/notary-photon:$(NOTARYVERSION) vmware/notary-photon:$(NOTARYSIGNERVERSION) \
vmware/harbor-notary-db:$(MARIADBVERSION) vmware/mariadb-photon:$(MARIADBVERSION)
PACKAGE_OFFLINE_PARA+= $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME) PACKAGE_OFFLINE_PARA+= $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME)
PACKAGE_ONLINE_PARA+= $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME) PACKAGE_ONLINE_PARA+= $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME)
DOCKERCOMPOSE_LIST+= -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) DOCKERCOMPOSE_LIST+= -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME)
@ -371,7 +371,7 @@ package_offline: compile build modify_sourcefiles modify_composefile
echo "pulling notary and harbor-notary-db..."; \ echo "pulling notary and harbor-notary-db..."; \
$(DOCKERPULL) vmware/notary-photon:$(NOTARYVERSION); \ $(DOCKERPULL) vmware/notary-photon:$(NOTARYVERSION); \
$(DOCKERPULL) vmware/notary-photon:$(NOTARYSIGNERVERSION); \ $(DOCKERPULL) vmware/notary-photon:$(NOTARYSIGNERVERSION); \
$(DOCKERPULL) vmware/harbor-notary-db:$(MARIADBVERSION); \ $(DOCKERPULL) vmware/mariadb-photon:$(MARIADBVERSION); \
fi fi
@if [ "$(CLAIRFLAG)" = "true" ] ; then \ @if [ "$(CLAIRFLAG)" = "true" ] ; then \
echo "pulling claiy and postgres..."; \ echo "pulling claiy and postgres..."; \

View File

@ -34,11 +34,11 @@ Refer to **[User Guide](docs/user_guide.md)** for more details on how to use Har
### Community ### Community
**Slack:** Join Harbor's community for discussion and ask questions: [VMware {code}](https://code.vmware.com/join/), Channel: #harbor. **Slack:** Join Harbor's community for discussion and ask questions: [VMware {code}](https://code.vmware.com/join/), Channel: #harbor.
**Email:** harbor@ vmware.com . **Email:** harbor@vmware.com .
More info on [partners and users](partners.md). More info on [partners and users](partners.md).
### Contribution ### Contribution
We welcome contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a pull request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). Contact us for any questions: harbor @vmware.com . We welcome contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a pull request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). Contact us for any questions: harbor@vmware.com .
### Demos ### Demos
* ![play](docs/img/video.png) **Content Trust** ( [youtube](https://www.youtube.com/watch?v=pPklSTJZY2E) , [Tencent Video](https://v.qq.com/x/page/n0553fzzrnf.html) ) * ![play](docs/img/video.png) **Content Trust** ( [youtube](https://www.youtube.com/watch?v=pPklSTJZY2E) , [Tencent Video](https://v.qq.com/x/page/n0553fzzrnf.html) )
@ -56,7 +56,7 @@ This project uses open source components which have additional licensing terms.
* MySQL 5.6: [docker image](https://hub.docker.com/_/mysql/), [license](https://github.com/docker-library/mysql/blob/master/LICENSE) * MySQL 5.6: [docker image](https://hub.docker.com/_/mysql/), [license](https://github.com/docker-library/mysql/blob/master/LICENSE)
### Commercial Support ### Commercial Support
If you need commercial support of Harbor, please contact us for more information: harbor@ vmware.com . If you need commercial support of Harbor, please contact us for more information: harbor@vmware.com .

View File

@ -4,7 +4,7 @@ import json
import logging import logging
import requests import requests
logging.basicConfig(level=logging.WARNING) logger = logging.getLogger(__name__)
class HarborClient(object): class HarborClient(object):
def __init__(self, host, user, password, protocol="http"): def __init__(self, host, user, password, protocol="http"):
@ -23,15 +23,15 @@ class HarborClient(object):
if login_data.status_code == 200: if login_data.status_code == 200:
session_id = login_data.cookies.get('beegosessionID') session_id = login_data.cookies.get('beegosessionID')
self.session_id = session_id self.session_id = session_id
logging.debug("Successfully login, session id: {}".format( logger.debug("Successfully login, session id: {}".format(
session_id)) session_id))
else: else:
logging.error("Fail to login, please try again") logger.error("Fail to login, please try again")
def logout(self): def logout(self):
requests.get('%s://%s/log_out' % (self.protocol, self.host), requests.get('%s://%s/log_out' % (self.protocol, self.host),
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
logging.debug("Successfully logout") logger.debug("Successfully logout")
# GET /search # GET /search
def search(self, query_string): def search(self, query_string):
@ -42,9 +42,9 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug("Successfully get search result: {}".format(result)) logger.debug("Successfully get search result: {}".format(result))
else: else:
logging.error("Fail to get search result") logger.error("Fail to get search result")
return result return result
# GET /projects # GET /projects
@ -55,10 +55,10 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug("Successfully get projects result: {}".format( logger.debug("Successfully get projects result: {}".format(
result)) result))
else: else:
logging.error("Fail to get projects result") logger.error("Fail to get projects result")
return result return result
# HEAD /projects # HEAD /projects
@ -70,14 +70,14 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = True result = True
logging.debug( logger.debug(
"Successfully check project exist, result: {}".format(result)) "Successfully check project exist, result: {}".format(result))
elif response.status_code == 404: elif response.status_code == 404:
result = False result = False
logging.debug( logger.debug(
"Successfully check project exist, result: {}".format(result)) "Successfully check project exist, result: {}".format(result))
else: else:
logging.error("Fail to check project exist") logger.error("Fail to check project exist")
return result return result
# POST /projects # POST /projects
@ -91,11 +91,11 @@ class HarborClient(object):
data=request_body, verify=False) data=request_body, verify=False)
if response.status_code == 201: if response.status_code == 201:
result = True result = True
logging.debug( logger.debug(
"Successfully create project with project name: {}".format( "Successfully create project with project name: {}".format(
project_name)) project_name))
else: else:
logging.error( logger.error(
"Fail to create project with project name: {}, response code: {}".format( "Fail to create project with project name: {}, response code: {}".format(
project_name, response.status_code)) project_name, response.status_code))
return result return result
@ -108,11 +108,11 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug( logger.debug(
"Successfully create project with project id: {}".format( "Successfully create project with project id: {}".format(
project_id)) project_id))
else: else:
logging.error( logger.error(
"Fail to create project with project id: {}, response code: {}".format( "Fail to create project with project id: {}, response code: {}".format(
project_id, response.status_code)) project_id, response.status_code))
return result return result
@ -128,11 +128,11 @@ class HarborClient(object):
data=request_body, verify=False) data=request_body, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = True result = True
logging.debug( logger.debug(
"Successfully add project member with project id: {}".format( "Successfully add project member with project id: {}".format(
project_id)) project_id))
else: else:
logging.error( logger.error(
"Fail to add project member with project id: {}, response code: {}".format( "Fail to add project member with project id: {}, response code: {}".format(
project_id, response.status_code)) project_id, response.status_code))
return result return result
@ -146,10 +146,10 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = True result = True
logging.debug("Successfully delete member with id: {}".format( logger.debug("Successfully delete member with id: {}".format(
user_id)) user_id))
else: else:
logging.error("Fail to delete member with id: {}, response code: {}" logger.error("Fail to delete member with id: {}, response code: {}"
.format(user_id, response.status_code)) .format(user_id, response.status_code))
return result return result
@ -164,11 +164,11 @@ class HarborClient(object):
data=request_body, verify=False) data=request_body, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = True result = True
logging.debug( logger.debug(
"Success to set project id: {} with publicity: {}".format( "Success to set project id: {} with publicity: {}".format(
project_id, is_public)) project_id, is_public))
else: else:
logging.error( logger.error(
"Fail to set publicity to project id: {} with status code: {}".format( "Fail to set publicity to project id: {} with status code: {}".format(
project_id, response.status_code)) project_id, response.status_code))
return result return result
@ -181,9 +181,9 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug("Successfully get statistics: {}".format(result)) logger.debug("Successfully get statistics: {}".format(result))
else: else:
logging.error("Fail to get statistics result with status code: {}" logger.error("Fail to get statistics result with status code: {}"
.format(response.status_code)) .format(response.status_code))
return result return result
@ -196,9 +196,9 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug("Successfully get users result: {}".format(result)) logger.debug("Successfully get users result: {}".format(result))
else: else:
logging.error("Fail to get users result with status code: {}" logger.error("Fail to get users result with status code: {}"
.format(response.status_code)) .format(response.status_code))
return result return result
@ -210,9 +210,9 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug("Successfully get users result: {}".format(result)) logger.debug("Successfully get users result: {}".format(result))
else: else:
logging.error("Fail to get users result with status code: {}" logger.error("Fail to get users result with status code: {}"
.format(response.status_code)) .format(response.status_code))
return result return result
@ -230,10 +230,10 @@ class HarborClient(object):
data=request_body, verify=False) data=request_body, verify=False)
if response.status_code == 201: if response.status_code == 201:
result = True result = True
logging.debug("Successfully create user with username: {}".format( logger.debug("Successfully create user with username: {}".format(
username)) username))
else: else:
logging.error( logger.error(
"Fail to create user with username: {}, response code: {}".format( "Fail to create user with username: {}, response code: {}".format(
username, response.status_code)) username, response.status_code))
return result return result
@ -252,11 +252,11 @@ class HarborClient(object):
data=request_body, verify=False) data=request_body, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = True result = True
logging.debug( logger.debug(
"Successfully update user profile with user id: {}".format( "Successfully update user profile with user id: {}".format(
user_id)) user_id))
else: else:
logging.error( logger.error(
"Fail to update user profile with user id: {}, response code: {}".format( "Fail to update user profile with user id: {}, response code: {}".format(
user_id, response.status_code)) user_id, response.status_code))
return result return result
@ -270,10 +270,10 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = True result = True
logging.debug("Successfully delete user with id: {}".format( logger.debug("Successfully delete user with id: {}".format(
user_id)) user_id))
else: else:
logging.error("Fail to delete user with id: {}, response code: {}" logger.error("Fail to delete user with id: {}, response code: {}"
.format(user_id, response.status_code)) .format(user_id, response.status_code))
return result return result
@ -289,10 +289,10 @@ class HarborClient(object):
data=request_body, verify=False) data=request_body, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = True result = True
logging.debug( logger.debug(
"Successfully change password for user id: {}".format(user_id)) "Successfully change password for user id: {}".format(user_id))
else: else:
logging.error("Fail to change password for user id: {}".format( logger.error("Fail to change password for user id: {}".format(
user_id)) user_id))
return result return result
@ -308,11 +308,11 @@ class HarborClient(object):
data=request_body, verify=False) data=request_body, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = True result = True
logging.debug( logger.debug(
"Successfully promote user as admin with user id: {}".format( "Successfully promote user as admin with user id: {}".format(
user_id)) user_id))
else: else:
logging.error( logger.error(
"Fail to promote user as admin with user id: {}, response code: {}".format( "Fail to promote user as admin with user id: {}, response code: {}".format(
user_id, response.status_code)) user_id, response.status_code))
return result return result
@ -327,11 +327,11 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug( logger.debug(
"Successfully get repositories with id: {}, result: {}".format( "Successfully get repositories with id: {}, result: {}".format(
project_id, result)) project_id, result))
else: else:
logging.error("Fail to get repositories result with id: {}, response code: {}".format( logger.error("Fail to get repositories result with id: {}, response code: {}".format(
project_id, response.status_code)) project_id, response.status_code))
return result return result
@ -344,10 +344,10 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = True result = True
logging.debug("Successfully delete a tag of repository: {}".format( logger.debug("Successfully delete a tag of repository: {}".format(
repo_name)) repo_name))
else: else:
logging.error("Fail to delete repository with name: {}, response code: {}".format( logger.error("Fail to delete repository with name: {}, response code: {}".format(
repo_name, response.status_code)) repo_name, response.status_code))
return result return result
@ -360,10 +360,10 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = True result = True
logging.debug("Successfully delete repository: {}".format( logger.debug("Successfully delete repository: {}".format(
repo_name)) repo_name))
else: else:
logging.error("Fail to delete repository with name: {}, response code: {}".format( logger.error("Fail to delete repository with name: {}, response code: {}".format(
repo_name, response.status_code)) repo_name, response.status_code))
return result return result
@ -376,11 +376,11 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug( logger.debug(
"Successfully get tag with repo name: {}, result: {}".format( "Successfully get tag with repo name: {}, result: {}".format(
repo_name, result)) repo_name, result))
else: else:
logging.error("Fail to get tags with repo name: {}, response code: {}".format( logger.error("Fail to get tags with repo name: {}, response code: {}".format(
repo_name, response.status_code)) repo_name, response.status_code))
return result return result
@ -393,11 +393,11 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug( logger.debug(
"Successfully get manifests with repo name: {}, tag: {}, result: {}".format( "Successfully get manifests with repo name: {}, tag: {}, result: {}".format(
repo_name, tag, result)) repo_name, tag, result))
else: else:
logging.error( logger.error(
"Fail to get manifests with repo name: {}, tag: {}".format( "Fail to get manifests with repo name: {}, tag: {}".format(
repo_name, tag)) repo_name, tag))
return result return result
@ -412,11 +412,11 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug( logger.debug(
"Successfully get top accessed repositories, result: {}".format( "Successfully get top accessed repositories, result: {}".format(
result)) result))
else: else:
logging.error("Fail to get top accessed repositories") logger.error("Fail to get top accessed repositories")
return result return result
# GET /logs # GET /logs
@ -427,9 +427,9 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug("Successfully get logs") logger.debug("Successfully get logs")
else: else:
logging.error("Fail to get logs and response code: {}".format( logger.error("Fail to get logs and response code: {}".format(
response.status_code)) response.status_code))
return result return result
@ -441,10 +441,10 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug( logger.debug(
"Successfully get systeminfo, result: {}".format(result)) "Successfully get systeminfo, result: {}".format(result))
else: else:
logging.error("Fail to get systeminfo, response code: {}".format(response.status_code)) logger.error("Fail to get systeminfo, response code: {}".format(response.status_code))
return result return result
# Get /configurations # Get /configurations
@ -455,8 +455,8 @@ class HarborClient(object):
cookies={'beegosessionID': self.session_id}, verify=False) cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
logging.debug( logger.debug(
"Successfully get configurations, result: {}".format(result)) "Successfully get configurations, result: {}".format(result))
else: else:
logging.error("Fail to get configurations, response code: {}".format(response.status_code)) logger.error("Fail to get configurations, response code: {}".format(response.status_code))
return result return result

View File

@ -2339,6 +2339,9 @@ definitions:
type: integer type: integer
format: int format: int
description: Reserved field. description: Reserved field.
insecure:
type: boolean
description: Whether or not the certificate will be verified when Harbor tries to access the server.
creation_time: creation_time:
type: string type: string
description: The create time of the policy. description: The create time of the policy.
@ -2360,6 +2363,9 @@ definitions:
password: password:
type: string type: string
description: The target server password. description: The target server password.
insecure:
type: boolean
description: Whether or not the certificate will be verified when Harbor tries to access the server.
PingTarget: PingTarget:
type: object type: object
properties: properties:
@ -2372,6 +2378,9 @@ definitions:
password: password:
type: string type: string
description: The target server password. description: The target server password.
insecure:
type: boolean
description: Whether or not the certificate will be verified when Harbor tries to access the server.
PutTarget: PutTarget:
type: object type: object
properties: properties:
@ -2387,6 +2396,9 @@ definitions:
password: password:
type: string type: string
description: The target server password. description: The target server password.
insecure:
type: boolean
description: Whether or not the certificate will be verified when Harbor tries to access the server.
HasAdminRole: HasAdminRole:
type: object type: object
properties: properties:

View File

@ -1,8 +1,6 @@
FROM mysql:5.6.35 FROM vmware/mariadb-photon:10.2.8
WORKDIR /tmp COPY registry.sql /docker-entrypoint-initdb.d/
COPY registry-flag.sh /docker-entrypoint-initdb.d/
COPY upgrade.sh /docker-entrypoint-updatedb.d/
ADD registry.sql r.sql
ADD docker-entrypoint.sh /entrypoint.sh
RUN chmod u+x /entrypoint.sh

View File

@ -1,44 +0,0 @@
#!/bin/bash
set -e
if [ ! -d '/var/lib/mysql/mysql' -a "${1%_safe}" = 'mysqld' ]; then
if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" ]; then
echo >&2 'error: database is uninitialized and MYSQL_ROOT_PASSWORD not set'
echo >&2 ' Did you forget to add -e MYSQL_ROOT_PASSWORD=... ? v2'
exit 1
fi
mysql_install_db --user=mysql --datadir=/var/lib/mysql
# These statements _must_ be on individual lines, and _must_ end with
# semicolons (no line breaks or comments are permitted).
# TODO proper SQL escaping on ALL the things D:
printf -v MYSQL_ROOT_PASSWORD "%q" ${MYSQL_ROOT_PASSWORD}
TEMP_FILE='/tmp/mysql-first-time.sql'
cat > "$TEMP_FILE" <<-EOSQL
DELETE FROM mysql.user ;
CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;
DROP DATABASE IF EXISTS test ;
EOSQL
if [ "$MYSQL_DATABASE" ]; then
echo "CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE ;" >> "$TEMP_FILE"
fi
if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" >> "$TEMP_FILE"
if [ "$MYSQL_DATABASE" ]; then
echo "GRANT ALL ON $MYSQL_DATABASE.* TO '$MYSQL_USER'@'%' ;" >> "$TEMP_FILE"
fi
fi
echo 'FLUSH PRIVILEGES ;' >> "$TEMP_FILE"
cat /tmp/r.sql >> "$TEMP_FILE"
set -- "$@" --init-file="$TEMP_FILE"
fi
chown -R mysql:mysql /var/lib/mysql
exec "$@"

View File

@ -0,0 +1,3 @@
#!/bin/sh
touch /var/lib/mysql/created_in_mariadb.flag
echo "dumped flag for MariaDB"

View File

@ -163,6 +163,7 @@ create table replication_target (
1 means it's a regulart registry 1 means it's a regulart registry
*/ */
target_type tinyint(1) NOT NULL DEFAULT 0, target_type tinyint(1) NOT NULL DEFAULT 0,
insecure tinyint(1) NOT NULL DEFAULT 0,
creation_time timestamp default CURRENT_TIMESTAMP, creation_time timestamp default CURRENT_TIMESTAMP,
update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
PRIMARY KEY (id) PRIMARY KEY (id)

View File

@ -158,6 +158,7 @@ create table replication_target (
1 means it's a regulart registry 1 means it's a regulart registry
*/ */
target_type tinyint(1) NOT NULL DEFAULT 0, target_type tinyint(1) NOT NULL DEFAULT 0,
insecure tinyint(1) NOT NULL DEFAULT 0,
creation_time timestamp default CURRENT_TIMESTAMP, creation_time timestamp default CURRENT_TIMESTAMP,
update_time timestamp default CURRENT_TIMESTAMP update_time timestamp default CURRENT_TIMESTAMP
); );

28
make/common/db/upgrade.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash
set +e
if [ ! -f /var/lib/mysql/created_in_mariadb.flag ]; then
echo "Maria DB flag not found, the DB was created in mysql image, running upgrade..."
mysqld >/dev/null 2>&1 &
pid="$!"
for i in {30..0}; do
mysqladmin -uroot -p$MYSQL_ROOT_PASSWORD processlist >/dev/null 2>&1
if [ $? = 0 ]; then
break
fi
echo 'Waiting for MySQL start...'
sleep 1
done
if [ "$i" = 0 ]; then
echo >&2 'MySQL failed to start.'
exit 1
fi
set -e
mysql_upgrade -p$MYSQL_ROOT_PASSWORD
echo 'Finished upgrading'
if ! kill -s TERM "$pid" || ! wait "$pid"; then
echo >&2 'Failed to stop MySQL for upgrading.'
exit 1
fi
else
echo "DB was created in Maria DB, skip upgrade."
fi

View File

@ -0,0 +1,25 @@
FROM vmware/photon:1.0
#The Docker Daemon has to be running with storage backend btrfs when building the image
RUN tdnf distro-sync -y || echo \
&& tdnf install -y sed shadow procps-ng gawk gzip sudo net-tools \
&& groupadd -r -g 999 mysql && useradd --no-log-init -r -g 999 -u 999 mysql \
&& tdnf install -y mariadb-server mariadb \
&& mkdir /docker-entrypoint-initdb.d /docker-entrypoint-updatedb.d \
&& rm -fr /var/lib/mysql \
&& mkdir -p /var/lib/mysql /var/run/mysqld \
&& chown -R mysql:mysql /var/lib/mysql /var/run/mysqld \
&& chmod 777 /var/run/mysqld /docker-entrypoint-initdb.d /docker-entrypoint-updatedb.d \
&& tdnf clean all
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
COPY my.cnf /etc/
RUN ln -s usr/local/bin/docker-entrypoint.sh /
VOLUME /var/lib/mysql
EXPOSE 3306
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["mysqld"]

View File

@ -0,0 +1,203 @@
#!/bin/bash
set -eo pipefail
shopt -s nullglob
# if command starts with an option, prepend mysqld
if [ "${1:0:1}" = '-' ]; then
set -- mysqld "$@"
fi
# skip setup if they want an option that stops mysqld
wantHelp=
for arg; do
case "$arg" in
-'?'|--help|--print-defaults|-V|--version)
wantHelp=1
break
;;
esac
done
# usage: file_env VAR [DEFAULT]
# ie: file_env 'XYZ_DB_PASSWORD' 'example'
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
file_env() {
local var="$1"
local fileVar="${var}_FILE"
local def="${2:-}"
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
exit 1
fi
local val="$def"
if [ "${!var:-}" ]; then
val="${!var}"
elif [ "${!fileVar:-}" ]; then
val="$(< "${!fileVar}")"
fi
export "$var"="$val"
unset "$fileVar"
}
_check_config() {
toRun=( "$@" --verbose --help --log-bin-index="$(mktemp -u)" )
if ! errors="$("${toRun[@]}" 2>&1 >/dev/null)"; then
cat >&2 <<-EOM
ERROR: mysqld failed while attempting to check config
command was: "${toRun[*]}"
$errors
EOM
exit 1
fi
}
# Fetch value from server config
# We use mysqld --verbose --help instead of my_print_defaults because the
# latter only show values present in config files, and not server defaults
_get_config() {
local conf="$1"; shift
"$@" --verbose --help --log-bin-index="$(mktemp -u)" 2>/dev/null | awk '$1 == "'"$conf"'" { print $2; exit }'
}
# allow the container to be started with `--user`
if [ "$1" = 'mysqld' -a -z "$wantHelp" -a "$(id -u)" = '0' ]; then
_check_config "$@"
DATADIR="$(_get_config 'datadir' "$@")"
mkdir -p "$DATADIR"
chown -R mysql:mysql "$DATADIR"
if [ -d '/docker-entrypoint-initdb.d' ]; then
chmod -R +rx /docker-entrypoint-updatedb.d
fi
if [ -d '/docker-entrypoint-updatedb.d' ]; then
chmod -R +rx /docker-entrypoint-updatedb.d
fi
exec sudo -u mysql -E "$BASH_SOURCE" "$@"
fi
if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then
# still need to check config, container may have started with --user
_check_config "$@"
# Get config
DATADIR="$(_get_config 'datadir' "$@")"
if [ ! -d "$DATADIR/mysql" ]; then
file_env 'MYSQL_ROOT_PASSWORD'
if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" ]; then
echo >&2 'error: database is uninitialized and password option is not specified '
echo >&2 ' You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD'
exit 1
fi
mkdir -p "$DATADIR"
echo 'Initializing database'
cd /usr
mysql_install_db --datadir="$DATADIR" --rpm
cd -
echo 'Database initialized'
SOCKET="$(_get_config 'socket' "$@")"
"$@" --skip-networking --socket="${SOCKET}" &
pid="$!"
mysql=( mysql --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" )
for i in {30..0}; do
if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then
break
fi
echo 'MySQL init process in progress...'
sleep 1
done
if [ "$i" = 0 ]; then
echo >&2 'MySQL init process failed.'
exit 1
fi
if [ -z "$MYSQL_INITDB_SKIP_TZINFO" ]; then
# sed is for https://bugs.mysql.com/bug.php?id=20545
mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/' | "${mysql[@]}" mysql
fi
rootCreate=
# default root to listen for connections from anywhere
file_env 'MYSQL_ROOT_HOST' '%'
if [ ! -z "$MYSQL_ROOT_HOST" -a "$MYSQL_ROOT_HOST" != 'localhost' ]; then
# no, we don't care if read finds a terminating character in this heredoc
# https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151
read -r -d '' rootCreate <<-EOSQL || true
CREATE USER 'root'@'${MYSQL_ROOT_HOST}' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
GRANT ALL ON *.* TO 'root'@'${MYSQL_ROOT_HOST}' WITH GRANT OPTION ;
EOSQL
fi
"${mysql[@]}" <<-EOSQL
-- What's done in this file shouldn't be replicated
-- or products like mysql-fabric won't work
SET @@SESSION.SQL_LOG_BIN=0;
DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ;
SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MYSQL_ROOT_PASSWORD}') ;
GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ;
${rootCreate}
DROP DATABASE IF EXISTS test ;
FLUSH PRIVILEGES ;
EOSQL
if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then
mysql+=( -p"${MYSQL_ROOT_PASSWORD}" )
fi
file_env 'MYSQL_DATABASE'
if [ "$MYSQL_DATABASE" ]; then
echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" | "${mysql[@]}"
mysql+=( "$MYSQL_DATABASE" )
fi
file_env 'MYSQL_USER'
file_env 'MYSQL_PASSWORD'
if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" | "${mysql[@]}"
if [ "$MYSQL_DATABASE" ]; then
echo "GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;" | "${mysql[@]}"
fi
echo 'FLUSH PRIVILEGES ;' | "${mysql[@]}"
fi
echo
for f in /docker-entrypoint-initdb.d/*; do
case "$f" in
*.sh) echo "$0: running $f"; . "$f" ;;
*.sql) echo "$0: running $f"; "${mysql[@]}" < "$f"; echo ;;
*.sql.gz) echo "$0: running $f"; gunzip -c "$f" | "${mysql[@]}"; echo ;;
*) echo "$0: ignoring $f" ;;
esac
echo
done
if ! kill -s TERM "$pid" || ! wait "$pid"; then
echo >&2 'MySQL init process failed.'
exit 1
fi
echo
echo 'MySQL init process done. Ready for start up.'
echo
fi
for f in /docker-entrypoint-updatedb.d/*; do
case "$f" in
*.sh) echo "$0: running $f"; . "$f" ;;
### Not supported for now... until needed
# *.sql) echo "$0: running $f"; "${mysql[@]}" < "$f"; echo ;;
# *.sql.gz) echo "$0: running $f"; gunzip -c "$f" | "${mysql[@]}"; echo ;;
*) echo "$0: ignoring $f" ;;
esac
echo
done
fi
exec "$@"

191
make/common/mariadb/my.cnf Normal file
View File

@ -0,0 +1,191 @@
# MariaDB database server configuration file.
#
# You can copy this file to one of:
# - "/etc/mysql/my.cnf" to set global options,
# - "~/.my.cnf" to set user-specific options.
#
# One can use all long options that the program supports.
# Run program with --help to get a list of available options and with
# --print-defaults to see which it would actually understand and use.
#
# For explanations see
# http://dev.mysql.com/doc/mysql/en/server-system-variables.html
# This will be passed to all mysql clients
# It has been reported that passwords should be enclosed with ticks/quotes
# escpecially if they contain "#" chars...
# Remember to edit /etc/mysql/debian.cnf when changing the socket location.
[client]
port = 3306
socket = /var/run/mysqld/mysqld.sock
# Here is entries for some specific programs
# The following values assume you have at least 32M ram
# This was formally known as [safe_mysqld]. Both versions are currently parsed.
[mysqld_safe]
socket = /var/run/mysqld/mysqld.sock
nice = 0
[mysqld]
skip-host-cache
#skip-name-resolve
#
# * Basic Settings
#
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc_messages_dir = /usr/share/mysql
lc_messages = en_US
skip-external-locking
#
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
#bind-address = 127.0.0.1
#
# * Fine Tuning
#
max_connections = 100
connect_timeout = 5
wait_timeout = 600
max_allowed_packet = 16M
thread_cache_size = 128
sort_buffer_size = 4M
bulk_insert_buffer_size = 16M
tmp_table_size = 32M
max_heap_table_size = 32M
#
# * MyISAM
#
# This replaces the startup script and checks MyISAM tables if needed
# the first time they are touched. On error, make copy and try a repair.
myisam_recover_options = BACKUP
key_buffer_size = 128M
#open-files-limit = 2000
table_open_cache = 400
myisam_sort_buffer_size = 512M
concurrent_insert = 2
read_buffer_size = 2M
read_rnd_buffer_size = 1M
#
# * Query Cache Configuration
#
# Cache only tiny result sets, so we can fit more in the query cache.
query_cache_limit = 128K
query_cache_size = 64M
# for more write intensive setups, set to DEMAND or OFF
#query_cache_type = DEMAND
#
# * Logging and Replication
#
# Both location gets rotated by the cronjob.
# Be aware that this log type is a performance killer.
# As of 5.1 you can enable the log at runtime!
#general_log_file = /var/log/mysql/mysql.log
#general_log = 1
#
# Error logging goes to syslog due to /etc/mysql/conf.d/mysqld_safe_syslog.cnf.
#
# we do want to know about network errors and such
#log_warnings = 2
#
# Enable the slow query log to see queries with especially long duration
#slow_query_log[={0|1}]
slow_query_log_file = /var/log/mysql/mariadb-slow.log
long_query_time = 10
#log_slow_rate_limit = 1000
#log_slow_verbosity = query_plan
#log-queries-not-using-indexes
#log_slow_admin_statements
#
# The following can be used as easy to replay backup logs or for replication.
# note: if you are setting up a replication slave, see README.Debian about
# other settings you may need to change.
#server-id = 1
#report_host = master1
#auto_increment_increment = 2
#auto_increment_offset = 1
#log_bin = /var/log/mysql/mariadb-bin
#log_bin_index = /var/log/mysql/mariadb-bin.index
# not fab for performance, but safer
#sync_binlog = 1
expire_logs_days = 10
max_binlog_size = 100M
# slaves
#relay_log = /var/log/mysql/relay-bin
#relay_log_index = /var/log/mysql/relay-bin.index
#relay_log_info_file = /var/log/mysql/relay-bin.info
#log_slave_updates
#read_only
#
# If applications support it, this stricter sql_mode prevents some
# mistakes like inserting invalid dates etc.
#sql_mode = NO_ENGINE_SUBSTITUTION,TRADITIONAL
#
# * InnoDB
#
# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/.
# Read the manual for more InnoDB related options. There are many!
default_storage_engine = InnoDB
# you can't just change log file size, requires special procedure
#innodb_log_file_size = 50M
innodb_buffer_pool_size = 256M
innodb_log_buffer_size = 8M
innodb_file_per_table = 1
innodb_open_files = 400
innodb_io_capacity = 400
innodb_flush_method = O_DIRECT
#
# * Security Features
#
# Read the manual, too, if you want chroot!
# chroot = /var/lib/mysql/
#
# For generating SSL certificates I recommend the OpenSSL GUI "tinyca".
#
# ssl-ca=/etc/mysql/cacert.pem
# ssl-cert=/etc/mysql/server-cert.pem
# ssl-key=/etc/mysql/server-key.pem
#
# * Galera-related settings
#
[galera]
# Mandatory settings
#wsrep_on=ON
#wsrep_provider=
#wsrep_cluster_address=
#binlog_format=row
#default_storage_engine=InnoDB
#innodb_autoinc_lock_mode=2
#
# Allow server to accept connections on all interfaces.
#
#bind-address=0.0.0.0
#
# Optional setting
#wsrep_slave_threads=1
#innodb_flush_log_at_trx_commit=0
[mysqldump]
quick
quote-names
max_allowed_packet = 16M
[mysql]
#no-auto-rehash # faster start of mysql but no tab completition
[isamchk]
key_buffer = 16M
#
# * IMPORTANT: Additional settings that can override those from this file!
# The files must end with '.cnf', otherwise they'll be ignored.
#
!includedir /etc/my.cnf.d/

View File

@ -1,6 +1,6 @@
FROM vmware/photon:1.0 FROM vmware/photon:1.0
RUN tdnf distro-sync -y \ RUN tdnf distro-sync -y || echo \
&& tdnf install -y nginx \ && tdnf install -y nginx \
&& ln -sf /dev/stdout /var/log/nginx/access.log \ && ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log \ && ln -sf /dev/stderr /var/log/nginx/error.log \

View File

@ -3,7 +3,7 @@ FROM vmware/photon:1.0
ENV PGDATA /var/lib/postgresql/data ENV PGDATA /var/lib/postgresql/data
RUN touch /etc/localtime.bak \ RUN touch /etc/localtime.bak \
&& tdnf distro-sync -y \ && tdnf distro-sync -y || echo \
&& tdnf install -y sed shadow gzip postgresql\ && tdnf install -y sed shadow gzip postgresql\
&& groupadd -r postgres --gid=999 \ && groupadd -r postgres --gid=999 \
&& useradd -r -g postgres --uid=999 postgres \ && useradd -r -g postgres --uid=999 postgres \

View File

@ -1,9 +1,11 @@
FROM library/photon:1.0 FROM vmware/photon:1.0
#base image for rsyslog base on photon #base image for rsyslog base on photon
RUN tdnf install -y cronie rsyslog shadow tar gzip \ RUN tdnf distro-sync -y || echo \
&& tdnf install -y cronie rsyslog shadow tar gzip \
&& mkdir /etc/rsyslog.d/ \ && mkdir /etc/rsyslog.d/ \
&& mkdir /var/spool/rsyslog \ && mkdir /var/spool/rsyslog \
&& groupadd syslog \ && groupadd syslog \
&& useradd -g syslog syslog && useradd -g syslog syslog \
&& tdnf clean all

View File

@ -27,7 +27,6 @@ EMAIL_FROM=$email_from
EMAIL_IDENTITY=$email_identity EMAIL_IDENTITY=$email_identity
HARBOR_ADMIN_PASSWORD=$harbor_admin_password HARBOR_ADMIN_PASSWORD=$harbor_admin_password
PROJECT_CREATION_RESTRICTION=$project_creation_restriction PROJECT_CREATION_RESTRICTION=$project_creation_restriction
VERIFY_REMOTE_CERT=$verify_remote_cert
MAX_JOB_WORKERS=$max_job_workers MAX_JOB_WORKERS=$max_job_workers
UI_SECRET=$ui_secret UI_SECRET=$ui_secret
JOBSERVICE_SECRET=$jobservice_secret JOBSERVICE_SECRET=$jobservice_secret

View File

@ -49,7 +49,7 @@ services:
syslog-address: "tcp://127.0.0.1:1514" syslog-address: "tcp://127.0.0.1:1514"
tag: "notary-signer" tag: "notary-signer"
notary-db: notary-db:
image: vmware/harbor-notary-db:mariadb-10.1.10 image: vmware/mariadb-photon:10.2.8
container_name: notary-db container_name: notary-db
restart: always restart: always
networks: networks:

View File

@ -1,7 +1,7 @@
FROM vmware/photon:1.0-20170928 FROM vmware/photon:1.0
RUN tdnf erase vim -y \ RUN tdnf erase vim -y \
&& tdnf distro-sync -y \ && tdnf distro-sync -y || echo \
&& tdnf clean all \ && tdnf clean all \
&& mkdir /harbor/ && mkdir /harbor/
COPY ./make/dev/adminserver/harbor_adminserver /harbor/ COPY ./make/dev/adminserver/harbor_adminserver /harbor/

View File

@ -1,6 +1,8 @@
FROM library/photon:1.0 FROM vmware/photon:1.0
RUN mkdir /harbor/ RUN mkdir /harbor/ \
&& tdnf distro-sync -y || echo \
&& tdnf clean all
COPY ./make/dev/jobservice/harbor_jobservice /harbor/ COPY ./make/dev/jobservice/harbor_jobservice /harbor/
RUN chmod u+x /harbor/harbor_jobservice RUN chmod u+x /harbor/harbor_jobservice

View File

@ -149,7 +149,6 @@ if protocol == "https":
customize_crt = rcp.get("configuration", "customize_crt") customize_crt = rcp.get("configuration", "customize_crt")
max_job_workers = rcp.get("configuration", "max_job_workers") max_job_workers = rcp.get("configuration", "max_job_workers")
token_expiration = rcp.get("configuration", "token_expiration") token_expiration = rcp.get("configuration", "token_expiration")
verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
proj_cre_restriction = rcp.get("configuration", "project_creation_restriction") proj_cre_restriction = rcp.get("configuration", "project_creation_restriction")
secretkey_path = rcp.get("configuration", "secretkey_path") secretkey_path = rcp.get("configuration", "secretkey_path")
if rcp.has_option("configuration", "admiral_url"): if rcp.has_option("configuration", "admiral_url"):
@ -239,7 +238,6 @@ render(os.path.join(templates_dir, "adminserver", "env"),
email_identity=email_identity, email_identity=email_identity,
harbor_admin_password=harbor_admin_password, harbor_admin_password=harbor_admin_password,
project_creation_restriction=proj_cre_restriction, project_creation_restriction=proj_cre_restriction,
verify_remote_cert=verify_remote_cert,
max_job_workers=max_job_workers, max_job_workers=max_job_workers,
ui_secret=ui_secret, ui_secret=ui_secret,
jobservice_secret=jobservice_secret, jobservice_secret=jobservice_secret,

View File

@ -111,10 +111,6 @@ var (
env: "MAX_JOB_WORKERS", env: "MAX_JOB_WORKERS",
parse: parseStringToInt, parse: parseStringToInt,
}, },
common.VerifyRemoteCert: &parser{
env: "VERIFY_REMOTE_CERT",
parse: parseStringToBool,
},
common.ProjectCreationRestriction: "PROJECT_CREATION_RESTRICTION", common.ProjectCreationRestriction: "PROJECT_CREATION_RESTRICTION",
common.AdminInitialPassword: "HARBOR_ADMIN_PASSWORD", common.AdminInitialPassword: "HARBOR_ADMIN_PASSWORD",
common.AdmiralEndpoint: "ADMIRAL_URL", common.AdmiralEndpoint: "ADMIRAL_URL",

View File

@ -57,7 +57,6 @@ const (
EmailIdentity = "email_identity" EmailIdentity = "email_identity"
EmailInsecure = "email_insecure" EmailInsecure = "email_insecure"
ProjectCreationRestriction = "project_creation_restriction" ProjectCreationRestriction = "project_creation_restriction"
VerifyRemoteCert = "verify_remote_cert"
MaxJobWorkers = "max_job_workers" MaxJobWorkers = "max_job_workers"
TokenExpiration = "token_expiration" TokenExpiration = "token_expiration"
CfgExpiration = "cfg_expiration" CfgExpiration = "cfg_expiration"

View File

@ -76,7 +76,7 @@ func DeleteRepTarget(id int64) error {
func UpdateRepTarget(target models.RepTarget) error { func UpdateRepTarget(target models.RepTarget) error {
o := GetOrmer() o := GetOrmer()
target.UpdateTime = time.Now() target.UpdateTime = time.Now()
_, err := o.Update(&target, "URL", "Name", "Username", "Password", "UpdateTime") _, err := o.Update(&target, "URL", "Name", "Username", "Password", "Insecure", "UpdateTime")
return err return err
} }

View File

@ -105,6 +105,7 @@ type RepTarget struct {
Username string `orm:"column(username)" json:"username"` Username string `orm:"column(username)" json:"username"`
Password string `orm:"column(password)" json:"password"` Password string `orm:"column(password)" json:"password"`
Type int `orm:"column(target_type)" json:"type"` Type int `orm:"column(target_type)" json:"type"`
Insecure bool `orm:"column(insecure)" json:"insecure"`
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"` CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"` UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
} }

View File

@ -0,0 +1,73 @@
// 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 clair
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/clair/test"
)
var (
notificationID = "ec45ec87-bfc8-4129-a1c3-d2b82622175a"
layerName = "03adedf41d4e0ea1b2458546a5b4717bf5f24b23489b25589e20c692aaf84d19"
client *Client
)
func TestMain(m *testing.M) {
mockClairServer := test.NewMockServer()
defer mockClairServer.Close()
client = NewClient(mockClairServer.URL, nil)
rc := m.Run()
if rc != 0 {
os.Exit(rc)
}
}
func TestListNamespaces(t *testing.T) {
assert := assert.New(t)
ns, err := client.ListNamespaces()
assert.Nil(err)
assert.Equal(25, len(ns))
}
func TestNotifications(t *testing.T) {
assert := assert.New(t)
n, err := client.GetNotification(notificationID)
assert.Nil(err)
assert.Equal(notificationID, n.Name)
_, err = client.GetNotification("noexist")
assert.NotNil(err)
err = client.DeleteNotification(notificationID)
assert.Nil(err)
}
func TestLaysers(t *testing.T) {
assert := assert.New(t)
layer := models.ClairLayer{
Name: "fakelayer",
ParentName: "parent",
Path: "http://registry:5000/layers/xxx",
}
err := client.ScanLayer(layer)
assert.Nil(err)
data, err := client.GetResult(layerName)
assert.Nil(err)
assert.Equal(layerName, data.Layer.Name)
_, err = client.GetResult("notexist")
assert.NotNil(err)
}

View File

@ -0,0 +1,62 @@
{
"Notification": {
"Name": "ec45ec87-bfc8-4129-a1c3-d2b82622175a",
"Created": "1456247389",
"Notified": "1456246708",
"Limit": 2,
"Page": "gAAAAABWzJaC2JCH6Apr_R1f2EkjGdibnrKOobTcYXBWl6t0Cw6Q04ENGIymB6XlZ3Zi0bYt2c-2cXe43fvsJ7ECZhZz4P8C8F9efr_SR0HPiejzQTuG0qAzeO8klogFfFjSz2peBvgP",
"NextPage": "gAAAAABWzJaCTyr6QXP2aYsCwEZfWIkU2GkNplSMlTOhLJfiR3LorBv8QYgEIgyOvZRmHQEzJKvkI6TP2PkRczBkcD17GE89btaaKMqEX14yHDgyfQvdasW1tj3-5bBRt0esKi9ym5En",
"New": {
"Vulnerability": {
"Name": "CVE-TEST",
"NamespaceName": "debian:8",
"Description": "New CVE",
"Severity": "Low",
"FixedIn": [
{
"Name": "grep",
"NamespaceName": "debian:8",
"Version": "2.25"
}
]
},
"OrderedLayersIntroducingVulnerability": [
{
"Index": 1,
"LayerName": "523ef1d23f222195488575f52a39c729c76a8c5630c9a194139cb246fb212da6"
},
{
"Index": 2,
"LayerName": "3b59c795b34670618fbcace4dac7a27c5ecec156812c9e2c90d3f4be1916b12d"
}
],
"LayersIntroducingVulnerability": [
"523ef1d23f222195488575f52a39c729c76a8c5630c9a194139cb246fb212da6",
"3b59c795b34670618fbcace4dac7a27c5ecec156812c9e2c90d182371916b12d"
]
},
"Old": {
"Vulnerability": {
"Name": "CVE-TEST",
"NamespaceName": "debian:8",
"Description": "New CVE",
"Severity": "Low",
"FixedIn": []
},
"OrderedLayersIntroducingVulnerability": [
{
"Index": 1,
"LayerName": "523ef1d23f222195488575f52a39c729c76a8c5630c9a194139cb246fb212da6"
},
{
"Index": 2,
"LayerName": "3b59c795b34670618fbcace4dac7a27c5ecec156812c9e2c90d3f4be1916b12d"
}
],
"LayersIntroducingVulnerability": [
"3b59c795b34670618fbcace4dac7a27c5ecec156812c9e2c90d3f4be1916b12d",
"523ef1d23f222195488575f52a39c729c76a8c5630c9a194139cb246fb212da6"
]
}
}
}

View File

@ -0,0 +1 @@
{"Namespaces":[{"Name":"debian:7","VersionFormat":"dpkg"},{"Name":"debian:unstable","VersionFormat":"dpkg"},{"Name":"debian:9","VersionFormat":"dpkg"},{"Name":"debian:10","VersionFormat":"dpkg"},{"Name":"debian:8","VersionFormat":"dpkg"},{"Name":"alpine:v3.6","VersionFormat":"dpkg"},{"Name":"alpine:v3.5","VersionFormat":"dpkg"},{"Name":"alpine:v3.4","VersionFormat":"dpkg"},{"Name":"alpine:v3.3","VersionFormat":"dpkg"},{"Name":"oracle:6","VersionFormat":"rpm"},{"Name":"oracle:7","VersionFormat":"rpm"},{"Name":"oracle:5","VersionFormat":"rpm"},{"Name":"ubuntu:14.04","VersionFormat":"dpkg"},{"Name":"ubuntu:15.10","VersionFormat":"dpkg"},{"Name":"ubuntu:17.04","VersionFormat":"dpkg"},{"Name":"ubuntu:16.04","VersionFormat":"dpkg"},{"Name":"ubuntu:12.04","VersionFormat":"dpkg"},{"Name":"ubuntu:13.04","VersionFormat":"dpkg"},{"Name":"ubuntu:14.10","VersionFormat":"dpkg"},{"Name":"ubuntu:12.10","VersionFormat":"dpkg"},{"Name":"ubuntu:16.10","VersionFormat":"dpkg"},{"Name":"ubuntu:15.04","VersionFormat":"dpkg"},{"Name":"centos:6","VersionFormat":"rpm"},{"Name":"centos:7","VersionFormat":"rpm"},{"Name":"centos:5","VersionFormat":"rpm"}]}

View File

@ -0,0 +1,117 @@
// 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 test
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"path"
"runtime"
"strings"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
)
func currPath() string {
_, f, _, ok := runtime.Caller(0)
if !ok {
panic("Failed to get current directory")
}
return path.Dir(f)
}
func serveFile(rw http.ResponseWriter, p string) {
data, err := ioutil.ReadFile(p)
if err != nil {
http.Error(rw, err.Error(), 500)
}
_, err2 := rw.Write(data)
if err2 != nil {
http.Error(rw, err2.Error(), 500)
}
}
type notificationHandler struct {
id string
}
func (n *notificationHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
suffix := strings.TrimPrefix(req.URL.Path, "/v1/notifications/")
if req.Method == http.MethodDelete {
rw.WriteHeader(200)
} else if req.Method == http.MethodGet {
if strings.HasPrefix(suffix, n.id) {
serveFile(rw, path.Join(currPath(), "notification.json"))
} else {
rw.WriteHeader(404)
}
} else {
rw.WriteHeader(http.StatusMethodNotAllowed)
}
}
type layerHandler struct {
name string
}
func (l *layerHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if req.Method == http.MethodPost {
data, err := ioutil.ReadAll(req.Body)
defer req.Body.Close()
if err != nil {
http.Error(rw, err.Error(), 500)
}
layer := &models.ClairLayerEnvelope{}
if err := json.Unmarshal(data, layer); err != nil {
http.Error(rw, err.Error(), http.StatusBadRequest)
}
rw.WriteHeader(http.StatusCreated)
} else if req.Method == http.MethodGet {
name := strings.TrimPrefix(req.URL.Path, "/v1/layers/")
if name == l.name {
serveFile(rw, path.Join(currPath(), "total-12.json"))
} else {
http.Error(rw, fmt.Sprintf("Invalid layer name: %s", name), http.StatusNotFound)
}
} else {
http.Error(rw, "", http.StatusMethodNotAllowed)
}
}
// NewMockServer ...
func NewMockServer() *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/v1/namespaces", func(rw http.ResponseWriter, req *http.Request) {
if req.Method == http.MethodGet {
serveFile(rw, path.Join(currPath(), "ns.json"))
} else {
rw.WriteHeader(http.StatusMethodNotAllowed)
}
})
mux.Handle("/v1/notifications/", &notificationHandler{id: "ec45ec87-bfc8-4129-a1c3-d2b82622175a"})
mux.Handle("/v1/layers", &layerHandler{name: "03adedf41d4e0ea1b2458546a5b4717bf5f24b23489b25589e20c692aaf84d19"})
mux.Handle("/v1/layers/", &layerHandler{name: "03adedf41d4e0ea1b2458546a5b4717bf5f24b23489b25589e20c692aaf84d19"})
mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
log.Infof("method: %s, path: %s", req.Method, req.URL.Path)
rw.WriteHeader(http.StatusNotFound)
},
)
return httptest.NewServer(mux)
}

View File

@ -53,7 +53,6 @@ var adminServerDefaultConfig = map[string]interface{}{
common.EmailInsecure: false, common.EmailInsecure: false,
common.EmailIdentity: "", common.EmailIdentity: "",
common.ProjectCreationRestriction: common.ProCrtRestrAdmOnly, common.ProjectCreationRestriction: common.ProCrtRestrAdmOnly,
common.VerifyRemoteCert: false,
common.MaxJobWorkers: 3, common.MaxJobWorkers: 3,
common.TokenExpiration: 30, common.TokenExpiration: 30,
common.CfgExpiration: 5, common.CfgExpiration: 5,

View File

@ -74,15 +74,6 @@ func initKeyProvider() {
keyProvider = comcfg.NewFileKeyProvider(path) keyProvider = comcfg.NewFileKeyProvider(path)
} }
// VerifyRemoteCert returns bool value.
func VerifyRemoteCert() (bool, error) {
cfg, err := mg.Get()
if err != nil {
return true, err
}
return cfg[common.VerifyRemoteCert].(bool), nil
}
// Database ... // Database ...
func Database() (*models.Database, error) { func Database() (*models.Database, error) {
cfg, err := mg.Get() cfg, err := mg.Get()

View File

@ -49,10 +49,6 @@ func TestConfig(t *testing.T) {
t.Fatalf("failed to initialize configurations: %v", err) t.Fatalf("failed to initialize configurations: %v", err)
} }
if _, err := VerifyRemoteCert(); err != nil {
t.Fatalf("failed to get verify remote cert: %v", err)
}
if _, err := Database(); err != nil { if _, err := Database(); err != nil {
t.Fatalf("failed to get database settings: %v", err) t.Fatalf("failed to get database settings: %v", err)
} }

View File

@ -106,7 +106,7 @@ func TestRepJob(t *testing.T) {
j, err := dao.GetRepJob(repJobID) j, err := dao.GetRepJob(repJobID)
assert.Equal(models.JobRetrying, j.Status) assert.Equal(models.JobRetrying, j.Status)
assert.Equal(1, rj.parm.Enabled) assert.Equal(1, rj.parm.Enabled)
assert.True(rj.parm.Insecure) assert.False(rj.parm.Insecure)
rj2 := NewRepJob(99999) rj2 := NewRepJob(99999)
err = rj2.Init() err = rj2.Init()
assert.NotNil(err) assert.NotNil(err)

View File

@ -120,17 +120,12 @@ func (rj *RepJob) Init() error {
if err != nil { if err != nil {
return err return err
} }
verify, err := config.VerifyRemoteCert()
if err != nil {
return err
}
rj.parm = &RepJobParm{ rj.parm = &RepJobParm{
LocalRegURL: regURL, LocalRegURL: regURL,
Repository: job.Repository, Repository: job.Repository,
Tags: job.TagList, Tags: job.TagList,
Enabled: policy.Enabled, Enabled: policy.Enabled,
Operation: job.Operation, Operation: job.Operation,
Insecure: !verify,
} }
if policy.Enabled == 0 { if policy.Enabled == 0 {
//worker will cancel this job //worker will cancel this job
@ -159,6 +154,7 @@ func (rj *RepJob) Init() error {
} }
rj.parm.TargetPassword = pwd rj.parm.TargetPassword = pwd
rj.parm.Insecure = target.Insecure
return nil return nil
} }

View File

@ -49,7 +49,6 @@ var (
common.EmailIdentity, common.EmailIdentity,
common.EmailInsecure, common.EmailInsecure,
common.ProjectCreationRestriction, common.ProjectCreationRestriction,
common.VerifyRemoteCert,
common.TokenExpiration, common.TokenExpiration,
common.ScanAllPolicy, common.ScanAllPolicy,
} }
@ -81,7 +80,6 @@ var (
common.EmailSSL, common.EmailSSL,
common.EmailInsecure, common.EmailInsecure,
common.SelfRegistration, common.SelfRegistration,
common.VerifyRemoteCert,
} }
passwordKeys = []string{ passwordKeys = []string{

View File

@ -61,7 +61,7 @@ func TestPutConfig(t *testing.T) {
apiTest := newHarborAPI() apiTest := newHarborAPI()
cfg := map[string]interface{}{ cfg := map[string]interface{}{
common.VerifyRemoteCert: false, common.TokenExpiration: 60,
} }
code, err := apiTest.PutConfig(*admin, cfg) code, err := apiTest.PutConfig(*admin, cfg)
@ -104,13 +104,13 @@ func TestResetConfig(t *testing.T) {
return return
} }
value, ok := cfgs[common.VerifyRemoteCert] value, ok := cfgs[common.TokenExpiration]
if !ok { if !ok {
t.Errorf("%s not found", common.VerifyRemoteCert) t.Errorf("%s not found", common.TokenExpiration)
return return
} }
assert.Equal(value.Value.(bool), true, "unexpected value") assert.Equal(int(value.Value.(float64)), 30, "unexpected 30")
ccc, err := config.GetSystemCfg() ccc, err := config.GetSystemCfg()
if err != nil { if err != nil {

View File

@ -58,13 +58,8 @@ func (t *TargetAPI) Prepare() {
} }
} }
func (t *TargetAPI) ping(endpoint, username, password string) { func (t *TargetAPI) ping(endpoint, username, password string, insecure bool) {
verify, err := config.VerifyRemoteCert() registry, err := newRegistryClient(endpoint, insecure, username, password)
if err != nil {
log.Errorf("failed to check whether insecure or not: %v", err)
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
registry, err := newRegistryClient(endpoint, !verify, username, password)
if err != nil { if err != nil {
// timeout, dns resolve error, connection refused, etc. // timeout, dns resolve error, connection refused, etc.
if urlErr, ok := err.(*url.Error); ok { if urlErr, ok := err.(*url.Error); ok {
@ -105,6 +100,7 @@ func (t *TargetAPI) PingByID() {
endpoint := target.URL endpoint := target.URL
username := target.Username username := target.Username
password := target.Password password := target.Password
insecure := target.Insecure
if len(password) != 0 { if len(password) != 0 {
password, err = utils.ReversibleDecrypt(password, t.secretKey) password, err = utils.ReversibleDecrypt(password, t.secretKey)
if err != nil { if err != nil {
@ -112,7 +108,7 @@ func (t *TargetAPI) PingByID() {
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
} }
} }
t.ping(endpoint, username, password) t.ping(endpoint, username, password, insecure)
} }
// Ping validates whether the target is reachable and whether the credential is valid // Ping validates whether the target is reachable and whether the credential is valid
@ -121,6 +117,7 @@ func (t *TargetAPI) Ping() {
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
Insecure bool `json:"insecure"`
}{} }{}
t.DecodeJSONReq(&req) t.DecodeJSONReq(&req)
@ -128,7 +125,7 @@ func (t *TargetAPI) Ping() {
t.CustomAbort(http.StatusBadRequest, "endpoint is required") t.CustomAbort(http.StatusBadRequest, "endpoint is required")
} }
t.ping(req.Endpoint, req.Username, req.Password) t.ping(req.Endpoint, req.Username, req.Password, req.Insecure)
} }
// Get ... // Get ...
@ -255,6 +252,7 @@ func (t *TargetAPI) Put() {
Endpoint *string `json:"endpoint"` Endpoint *string `json:"endpoint"`
Username *string `json:"username"` Username *string `json:"username"`
Password *string `json:"password"` Password *string `json:"password"`
Insecure *bool `json:"insecure"`
}{} }{}
t.DecodeJSONReq(&req) t.DecodeJSONReq(&req)
@ -273,6 +271,9 @@ func (t *TargetAPI) Put() {
if req.Password != nil { if req.Password != nil {
target.Password = *req.Password target.Password = *req.Password
} }
if req.Insecure != nil {
target.Insecure = *req.Insecure
}
t.Validate(target) t.Validate(target)

View File

@ -276,15 +276,6 @@ func OnlyAdminCreateProject() (bool, error) {
return cfg[common.ProjectCreationRestriction].(string) == common.ProCrtRestrAdmOnly, nil return cfg[common.ProjectCreationRestriction].(string) == common.ProCrtRestrAdmOnly, nil
} }
// VerifyRemoteCert returns bool value.
func VerifyRemoteCert() (bool, error) {
cfg, err := mg.Get()
if err != nil {
return true, err
}
return cfg[common.VerifyRemoteCert].(bool), nil
}
// Email returns email server settings // Email returns email server settings
func Email() (*models.Email, error) { func Email() (*models.Email, error) {
cfg, err := mg.Get() cfg, err := mg.Get()

View File

@ -108,10 +108,6 @@ func TestConfig(t *testing.T) {
t.Fatalf("failed to get onldy admin create project: %v", err) t.Fatalf("failed to get onldy admin create project: %v", err)
} }
if _, err := VerifyRemoteCert(); err != nil {
t.Fatalf("failed to get verify remote cert: %v", err)
}
if _, err := Email(); err != nil { if _, err := Email(); err != nil {
t.Fatalf("failed to get email settings: %v", err) t.Fatalf("failed to get email settings: %v", err)
} }

View File

@ -38,6 +38,10 @@ export const CREATE_EDIT_ENDPOINT_TEMPLATE: string = `
<label for="destination_password" class="col-md-4 form-group-label-override">{{ 'DESTINATION.PASSWORD' | translate }}</label> <label for="destination_password" class="col-md-4 form-group-label-override">{{ 'DESTINATION.PASSWORD' | translate }}</label>
<input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing" [readonly]="!editable" [(ngModel)]="target.password" size="20" name="password" #password="ngModel" (focus)="clearPassword($event)"> <input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing" [readonly]="!editable" [(ngModel)]="target.password" size="20" name="password" #password="ngModel" (focus)="clearPassword($event)">
</div> </div>
<div class="form-group">
<label for="destination_insecure" class="col-md-4 form-group-label-override">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
<clr-checkbox #insecure class="col-md-8" name="insecure" id="destination_insecure" [(ngModel)]="target.insecure"></clr-checkbox>
</div>
<div class="form-group"> <div class="form-group">
<label for="spin" class="col-md-4"></label> <label for="spin" class="col-md-4"></label>
<span class="col-md-8 spinner spinner-inline" [hidden]="!inProgress"></span> <span class="col-md-8 spinner spinner-inline" [hidden]="!inProgress"></span>

View File

@ -21,6 +21,7 @@ describe('CreateEditEndpointComponent (inline template)', () => {
"name": "target_01", "name": "target_01",
"username": "admin", "username": "admin",
"password": "", "password": "",
"insecure": false,
"type": 0 "type": 0
}; };

View File

@ -91,7 +91,8 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
(this.target.endpoint && this.target.endpoint.trim() !== "") || (this.target.endpoint && this.target.endpoint.trim() !== "") ||
(this.target.name && this.target.name.trim() !== "") || (this.target.name && this.target.name.trim() !== "") ||
(this.target.username && this.target.username.trim() !== "") || (this.target.username && this.target.username.trim() !== "") ||
(this.target.password && this.target.password.trim() !== "")); (this.target.password && this.target.password.trim() !== "")) ||
this.target.insecure;
} else { } else {
//Edit //Edit
return !compareValue(this.target, this.initVal); return !compareValue(this.target, this.initVal);
@ -104,26 +105,29 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
this.targetForm && this.targetForm &&
this.targetForm.valid && this.targetForm.valid &&
this.editable && this.editable &&
(this.targetNameHasChanged || this.endpointHasChanged); (this.targetNameHasChanged || this.endpointHasChanged || this.checkboxHasChanged);
} }
public get inProgress(): boolean { public get inProgress(): boolean {
return this.onGoing || this.testOngoing; return this.onGoing || this.testOngoing;
} }
public get checkboxHasChanged(): boolean {
return (this.target.insecure !== this.initVal.insecure) ? true : false;
}
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.valueChangesSub) { if (this.valueChangesSub) {
this.valueChangesSub.unsubscribe(); this.valueChangesSub.unsubscribe();
} }
} }
initEndpoint(): Endpoint { initEndpoint(): Endpoint {
return { return {
endpoint: "", endpoint: "",
name: "", name: "",
username: "", username: "",
password: "", password: "",
insecure: false,
type: 0 type: 0
}; };
} }
@ -275,20 +279,28 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
if (this.onGoing) { if (this.onGoing) {
return;//Avoid duplicated submitting return;//Avoid duplicated submitting
} }
if (!(this.targetNameHasChanged || this.endpointHasChanged)) { if (!(this.targetNameHasChanged || this.endpointHasChanged || this.checkboxHasChanged)) {
return;//Avoid invalid submitting return;//Avoid invalid submitting
} }
let payload: Endpoint = this.initEndpoint(); let payload: Endpoint = this.initEndpoint();
if (this.targetNameHasChanged) { if (this.targetNameHasChanged) {
payload.name = this.target.name; payload.name = this.target.name;
delete payload.endpoint; }else {
delete payload.name;
} }
if (this.endpointHasChanged) { if (this.endpointHasChanged) {
payload.endpoint = this.target.endpoint; payload.endpoint = this.target.endpoint;
payload.username = this.target.username; payload.username = this.target.username;
payload.password = this.target.password; payload.password = this.target.password;
delete payload.name; }else {
delete payload.endpoint;
} }
if (this.checkboxHasChanged) {
payload.insecure = this.target.insecure;
}else {
delete payload.insecure;
}
if (!this.target.id) { return; } if (!this.target.id) { return; }
this.onGoing = true; this.onGoing = true;
@ -317,7 +329,7 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
handleErrorMessageKey(status: number): string { handleErrorMessageKey(status: number): string {
switch (status) { switch (status) {
case 409: this case 409:
return 'DESTINATION.CONFLICT_NAME'; return 'DESTINATION.CONFLICT_NAME';
case 400: case 400:
return 'DESTINATION.INVALID_NAME'; return 'DESTINATION.INVALID_NAME';
@ -356,7 +368,7 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
keyNumber++; keyNumber++;
} }
} }
if (keyNumber !== 4) { if (keyNumber !== 5) {
return; return;
} }

View File

@ -110,6 +110,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
"name": "target_01", "name": "target_01",
"username": "admin", "username": "admin",
"password": "", "password": "",
"insecure": false,
"type": 0 "type": 0
}, },
{ {
@ -118,6 +119,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
"name": "target_02", "name": "target_02",
"username": "AAA", "username": "AAA",
"password": "", "password": "",
"insecure": false,
"type": 0 "type": 0
}, },
{ {
@ -126,6 +128,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
"name": "target_03", "name": "target_03",
"username": "admin", "username": "admin",
"password": "", "password": "",
"insecure": false,
"type": 0 "type": 0
}, },
{ {
@ -134,6 +137,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
"name": "target_04", "name": "target_04",
"username": "", "username": "",
"password": "", "password": "",
"insecure": true,
"type": 0 "type": 0
} }
]; ];

View File

@ -123,6 +123,7 @@ export class CreateEditRuleComponent implements AfterViewChecked {
name: '', name: '',
username: '', username: '',
password: '', password: '',
insecure: false,
type: 0 type: 0
}; };
} }

View File

@ -19,6 +19,7 @@ export const ENDPOINT_TEMPLATE: string = `
<clr-datagrid [clrDgLoading]="loading"> <clr-datagrid [clrDgLoading]="loading">
<clr-dg-column [clrDgField]="'name'">{{'DESTINATION.NAME' | translate}}</clr-dg-column> <clr-dg-column [clrDgField]="'name'">{{'DESTINATION.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'endpoint'">{{'DESTINATION.URL' | translate}}</clr-dg-column> <clr-dg-column [clrDgField]="'endpoint'">{{'DESTINATION.URL' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'insecure'">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="creationTimeComparator">{{'DESTINATION.CREATION_TIME' | translate}}</clr-dg-column> <clr-dg-column [clrDgSortBy]="creationTimeComparator">{{'DESTINATION.CREATION_TIME' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'DESTINATION.PLACEHOLDER' | translate }}</clr-dg-placeholder> <clr-dg-placeholder>{{'DESTINATION.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *clrDgItems="let t of targets" [clrDgItem]='t'> <clr-dg-row *clrDgItems="let t of targets" [clrDgItem]='t'>
@ -28,6 +29,9 @@ export const ENDPOINT_TEMPLATE: string = `
</clr-dg-action-overflow> </clr-dg-action-overflow>
<clr-dg-cell>{{t.name}}</clr-dg-cell> <clr-dg-cell>{{t.name}}</clr-dg-cell>
<clr-dg-cell>{{t.endpoint}}</clr-dg-cell> <clr-dg-cell>{{t.endpoint}}</clr-dg-cell>
<clr-dg-cell>
<clr-checkbox name="insecure" [clrChecked]="t.insecure"> </clr-checkbox>
</clr-dg-cell>
<clr-dg-cell>{{t.creation_time | date: 'short'}}</clr-dg-cell> <clr-dg-cell>{{t.creation_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row> </clr-dg-row>
<clr-dg-footer> <clr-dg-footer>

View File

@ -25,6 +25,7 @@ describe('EndpointComponent (inline template)', () => {
"name": "target_01", "name": "target_01",
"username": "admin", "username": "admin",
"password": "", "password": "",
"insecure": true,
"type": 0 "type": 0
}, },
{ {
@ -33,6 +34,7 @@ describe('EndpointComponent (inline template)', () => {
"name": "target_02", "name": "target_02",
"username": "AAA", "username": "AAA",
"password": "", "password": "",
"insecure": false,
"type": 0 "type": 0
}, },
{ {
@ -41,6 +43,7 @@ describe('EndpointComponent (inline template)', () => {
"name": "target_03", "name": "target_03",
"username": "admin", "username": "admin",
"password": "", "password": "",
"insecure": false,
"type": 0 "type": 0
}, },
{ {
@ -49,6 +52,7 @@ describe('EndpointComponent (inline template)', () => {
"name": "target_04", "name": "target_04",
"username": "", "username": "",
"password": "", "password": "",
"insecure": false,
"type": 0 "type": 0
} }
]; ];
@ -59,6 +63,7 @@ describe('EndpointComponent (inline template)', () => {
"name": "target_01", "name": "target_01",
"username": "admin", "username": "admin",
"password": "", "password": "",
"insecure": false,
"type": 0 "type": 0
}; };

View File

@ -69,6 +69,7 @@ export class EndpointComponent implements OnInit {
name: "", name: "",
username: "", username: "",
password: "", password: "",
insecure: false,
type: 0 type: 0
}; };
} }

View File

@ -107,6 +107,7 @@ describe('Replication Component (inline template)', ()=>{
"name": "target_01", "name": "target_01",
"username": "admin", "username": "admin",
"password": "", "password": "",
"insecure": false,
"type": 0 "type": 0
}, },
{ {
@ -115,6 +116,7 @@ describe('Replication Component (inline template)', ()=>{
"name": "target_02", "name": "target_02",
"username": "AAA", "username": "AAA",
"password": "", "password": "",
"insecure": false,
"type": 0 "type": 0
}, },
{ {
@ -123,6 +125,7 @@ describe('Replication Component (inline template)', ()=>{
"name": "target_03", "name": "target_03",
"username": "admin", "username": "admin",
"password": "", "password": "",
"insecure": false,
"type": 0 "type": 0
}, },
{ {
@ -131,6 +134,7 @@ describe('Replication Component (inline template)', ()=>{
"name": "target_04", "name": "target_04",
"username": "", "username": "",
"password": "", "password": "",
"insecure": false,
"type": 0 "type": 0
} }
]; ];

View File

@ -73,6 +73,7 @@ export interface Endpoint extends Base {
name: string; name: string;
username?: string; username?: string;
password?: string; password?: string;
insecure: boolean;
type: number; type: number;
} }

View File

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

View File

@ -6,9 +6,6 @@
<li role="presentation" class="nav-item"> <li role="presentation" class="nav-item">
<button id="config-auth" class="btn btn-link nav-link active" aria-controls="authentication" [class.active]='isCurrentTabLink("config-auth")' type="button" (click)='tabLinkClick("config-auth")'>{{'CONFIG.AUTH' | translate }}</button> <button id="config-auth" class="btn btn-link nav-link active" aria-controls="authentication" [class.active]='isCurrentTabLink("config-auth")' type="button" (click)='tabLinkClick("config-auth")'>{{'CONFIG.AUTH' | translate }}</button>
</li> </li>
<li role="presentation" class="nav-item">
<button id="config-replication" class="btn btn-link nav-link" aria-controls="replication" [class.active]='isCurrentTabLink("config-replication")' type="button" (click)='tabLinkClick("config-replication")'>{{'CONFIG.REPLICATION' | translate }}</button>
</li>
<li role="presentation" class="nav-item"> <li role="presentation" class="nav-item">
<button id="config-email" class="btn btn-link nav-link" aria-controls="email" [class.active]='isCurrentTabLink("config-email")' type="button" (click)='tabLinkClick("config-email")'>{{'CONFIG.EMAIL' | translate }}</button> <button id="config-email" class="btn btn-link nav-link" aria-controls="email" [class.active]='isCurrentTabLink("config-email")' type="button" (click)='tabLinkClick("config-email")'>{{'CONFIG.EMAIL' | translate }}</button>
</li> </li>
@ -22,9 +19,6 @@
<section id="authentication" role="tabpanel" aria-labelledby="config-auth" [hidden]='!isCurrentTabContent("authentication")'> <section id="authentication" role="tabpanel" aria-labelledby="config-auth" [hidden]='!isCurrentTabContent("authentication")'>
<config-auth [ldapConfig]="allConfig"></config-auth> <config-auth [ldapConfig]="allConfig"></config-auth>
</section> </section>
<section id="replication" role="tabpanel" aria-labelledby="config-replication" [hidden]='!isCurrentTabContent("replication")'>
<replication-config [(replicationConfig)]="allConfig"></replication-config>
</section>
<section id="email" role="tabpanel" aria-labelledby="config-email" [hidden]='!isCurrentTabContent("email")'> <section id="email" role="tabpanel" aria-labelledby="config-email" [hidden]='!isCurrentTabContent("email")'>
<config-email [mailConfig]="allConfig"></config-email> <config-email [mailConfig]="allConfig"></config-email>
</section> </section>

View File

@ -30,7 +30,6 @@ import {
Configuration, Configuration,
StringValueItem, StringValueItem,
ComplexValueItem, ComplexValueItem,
ReplicationConfigComponent,
SystemSettingsComponent, SystemSettingsComponent,
VulnerabilityConfigComponent, VulnerabilityConfigComponent,
ClairDBStatus ClairDBStatus
@ -59,7 +58,6 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
testingMailOnGoing: boolean = false; testingMailOnGoing: boolean = false;
testingLDAPOnGoing: boolean = false; testingLDAPOnGoing: boolean = false;
@ViewChild(ReplicationConfigComponent) replicationConfig: ReplicationConfigComponent;
@ViewChild(SystemSettingsComponent) systemSettingsConfig: SystemSettingsComponent; @ViewChild(SystemSettingsComponent) systemSettingsConfig: SystemSettingsComponent;
@ViewChild(VulnerabilityConfigComponent) vulnerabilityConfig: VulnerabilityConfigComponent; @ViewChild(VulnerabilityConfigComponent) vulnerabilityConfig: VulnerabilityConfigComponent;
@ViewChild(ConfigurationEmailComponent) mailConfig: ConfigurationEmailComponent; @ViewChild(ConfigurationEmailComponent) mailConfig: ConfigurationEmailComponent;
@ -170,9 +168,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
} }
public isValid(): boolean { public isValid(): boolean {
return this.replicationConfig && return this.systemSettingsConfig &&
this.replicationConfig.isValid &&
this.systemSettingsConfig &&
this.systemSettingsConfig.isValid && this.systemSettingsConfig.isValid &&
this.mailConfig && this.mailConfig &&
this.mailConfig.isValid() && this.mailConfig.isValid() &&

View File

@ -57,19 +57,25 @@ if [[ $DRONE_BRANCH == "master" || $DRONE_BRANCH == *"refs/tags"* || $DRONE_BRAN
echo "Package Harbor build." echo "Package Harbor build."
pybot --removekeywords TAG:secret --include Bundle tests/robot-cases/Group0-Distro-Harbor pybot --removekeywords TAG:secret --include Bundle tests/robot-cases/Group0-Distro-Harbor
echo "Running full CI for $DRONE_BUILD_EVENT on $DRONE_BRANCH" echo "Running full CI for $DRONE_BUILD_EVENT on $DRONE_BRANCH"
pybot -v ip:$container_ip --removekeywords TAG:secret --include BAT tests/robot-cases/Group0-BAT
upload_latest_build=true upload_latest_build=true
pybot -v ip:$container_ip --removekeywords TAG:secret --include BAT tests/robot-cases/Group0-BAT
elif (echo $buildinfo | grep -q "\[Specific CI="); then elif (echo $buildinfo | grep -q "\[Specific CI="); then
buildtype=$(echo $buildinfo | grep "\[Specific CI=") buildtype=$(echo $buildinfo | grep "\[Specific CI=")
testsuite=$(echo $buildtype | awk -v FS="(=|])" '{print $2}') testsuite=$(echo $buildtype | awk -v FS="(=|])" '{print $2}')
pybot -v ip:$container_ip --removekeywords TAG:secret --suite $testsuite --suite Regression tests/robot-cases pybot -v ip:$container_ip --removekeywords TAG:secret --suite $testsuite --suite Regression tests/robot-cases
elif (echo $buildinfo | grep -q "\[Full CI\]"); then elif (echo $buildinfo | grep -q "\[Full CI\]"); then
upload_build=true
pybot -v ip:$container_ip --removekeywords TAG:secret --exclude skip tests/robot-cases pybot -v ip:$container_ip --removekeywords TAG:secret --exclude skip tests/robot-cases
elif (echo $buildinfo | grep -q "\[Skip CI\]"); then elif (echo $buildinfo | grep -q "\[Skip CI\]"); then
echo "Skip CI." echo "Skip CI."
elif (echo $buildinfo | grep -q "\[Upload Build\]"); then
upload_latest_build=true
upload_build=true
echo "Package Harbor build."
pybot --removekeywords TAG:secret --include Bundle tests/robot-cases/Group0-Distro-Harbor
echo "Running full CI for $DRONE_BUILD_EVENT on $DRONE_BRANCH"
pybot -v ip:$container_ip --removekeywords TAG:secret --include BAT tests/robot-cases/Group0-BAT
else else
# default mode is BAT. # default mode is BAT.
pybot -v ip:$container_ip --removekeywords TAG:secret --include BAT tests/robot-cases/Group0-BAT pybot -v ip:$container_ip --removekeywords TAG:secret --include BAT tests/robot-cases/Group0-BAT
fi fi
@ -90,6 +96,13 @@ else
echo "No log output file to upload" echo "No log output file to upload"
fi fi
## --------------------------------------------- Upload Harbor Build File ---------------------------------------
if [ $upload_build == true ] && [ $rc -eq 0 ]; then
harbor_build_bundle=$(basename harbor-offline-installer-*.tgz)
gsutil cp $harbor_build_bundle gs://harbor-builds
gsutil -D setacl public-read gs://harbor-builds/$harbor_build_bundle &> /dev/null
fi
## --------------------------------------------- Upload Harbor Latest Build File --------------------------------------- ## --------------------------------------------- Upload Harbor Latest Build File ---------------------------------------
if [ $upload_latest_build == true ] && [ $rc -eq 0 ]; then if [ $upload_latest_build == true ] && [ $rc -eq 0 ]; then
echo "update latest build file." echo "update latest build file."

View File

@ -102,7 +102,7 @@ Project Creation Should Not Display
Switch To System Settings Switch To System Settings
Sleep 1 Sleep 1
Click Element xpath=//clr-main-container//nav//ul/li[3] Click Element xpath=//clr-main-container//nav//ul/li[3]
Click Element xpath=//config//ul/li[4] Click Element xpath=//*[@id="config-system"]
Modify Token Expiration Modify Token Expiration
[Arguments] ${minutes} [Arguments] ${minutes}

View File

@ -176,23 +176,6 @@ Test Case - Edit Self-Registration
Enable Self Reg Enable Self Reg
Close Browser Close Browser
Test Case - Edit Verify Remote Cert
Init Chrome Driver
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Switch To System Replication
Check Verify Remote Cert
Logout Harbor
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Switch To System Replication
Should Verify Remote Cert Be Enabled
#restore setting
Check Verify Remote Cert
Close Browser
Test Case - Edit Email Settings Test Case - Edit Email Settings
Init Chrome Driver Init Chrome Driver
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}

View File

@ -1,5 +1,4 @@
#!/bin/sh #!/bin/sh
set -e set -e
if docker ps --filter "status=restarting" | grep 'vmware'; then if docker ps --filter "status=restarting" | grep 'vmware'; then

View File

@ -1,16 +1,11 @@
FROM mysql:5.6 FROM vmware/mariadb-photon:10.2.8
MAINTAINER bhe@vmware.com RUN tdnf distro-sync || echo \
&& tdnf install -y mariadb-devel python2 python2-devel python-pip gcc\
RUN sed -i -e 's/us.archive.ubuntu.com/archive.ubuntu.com/g' /etc/apt/sources.list linux-api-headers glibc-devel binutils zlib-devel openssl-devel \
&& pip install mysqlclient alembic \
RUN apt-get update && tdnf clean all \
&& mkdir -p /harbor-migration
RUN apt-get install -y curl python python-pip git python-mysqldb
RUN pip install alembic
RUN mkdir -p /harbor-migration
WORKDIR /harbor-migration WORKDIR /harbor-migration

View File

@ -30,7 +30,7 @@ script_location = migration_harbor
# are written from script.py.mako # are written from script.py.mako
# output_encoding = utf-8 # output_encoding = utf-8
sqlalchemy.url = mysql://$DB_USR:$DB_PWD@localhost:3306/registry sqlalchemy.url = mysql://$DB_USR:$DB_PWD@localhost:3306/registry?unix_socket=/var/run/mysqld/mysqld.sock
# Logging configuration # Logging configuration
[loggers] [loggers]

View File

@ -54,4 +54,5 @@ Changelog for harbor database schema
- create table `project_metadata` - create table `project_metadata`
- insert data into table `project_metadata` - insert data into table `project_metadata`
- delete column `public` from table `project` - delete column `public` from table `project`
- add column `insecure` to table `replication_target`

View File

@ -8,8 +8,6 @@ fi
source ./alembic.tpl > ./alembic.ini source ./alembic.tpl > ./alembic.ini
WAITTIME=60
DBCNF="-hlocalhost -u${DB_USR}" DBCNF="-hlocalhost -u${DB_USR}"
#prevent shell to print insecure message #prevent shell to print insecure message
@ -44,24 +42,21 @@ fi
echo 'Trying to start mysql server...' echo 'Trying to start mysql server...'
DBRUN=0 DBRUN=0
mysqld & mysqld &
for i in $(seq 1 $WAITTIME); do for i in {60..0}; do
echo "$(/usr/sbin/service mysql status)" mysqladmin -u$DB_USR -p$DB_PWD processlist >/dev/null 2>&1
if [[ "$(/usr/sbin/service mysql status)" =~ "not running" ]]; then if [ $? = 0 ]; then
sleep 1
else
DBRUN=1
break break
fi fi
echo 'Waiting for MySQL start...'
sleep 1
done done
if [ "$i" = 0 ]; then
if [[ $DBRUN -eq 0 ]]; then
echo "timeout. Can't run mysql server." echo "timeout. Can't run mysql server."
if [[ $1 = "test" ]]; then if [[ $1 = "test" ]]; then
echo "test failed." echo "test failed."
fi fi
exit 1 exit 1
fi fi
if [[ $1 = "test" ]]; then if [[ $1 = "test" ]]; then
echo "test passed." echo "test passed."
exit 0 exit 0