merge with dev branch

This commit is contained in:
Tan Jiang 2017-03-24 14:40:34 +08:00
commit a33f4151e2
43 changed files with 427 additions and 396 deletions

View File

@ -101,7 +101,7 @@ script:
- docker ps - docker ps
- ./tests/notarytest.sh - ./tests/notarytest.sh
- go run tests/startuptest.go https://localhost/ - ./tests/startuptest.sh
- go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD} - go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD}
# - sudo ./tests/testprepare.sh # - sudo ./tests/testprepare.sh

View File

@ -390,9 +390,9 @@ down:
[ $$CONTINUE = "y" ] || [ $$CONTINUE = "Y" ] || (echo "Exiting."; exit 1;) [ $$CONTINUE = "y" ] || [ $$CONTINUE = "Y" ] || (echo "Exiting."; exit 1;)
@echo "stoping harbor instance..." @echo "stoping harbor instance..."
@if [ "$(NOTARYFLAG)" = "true" ] ; then \ @if [ "$(NOTARYFLAG)" = "true" ] ; then \
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) down ; \ $(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) down -v ; \
else \ else \
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) down ; \ $(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) down -v ; \
fi fi
@echo "Done." @echo "Done."

View File

@ -1,63 +1,32 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIFBDCCAuygAwIBAgIJAMbWdVJcKhXYMA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNV MIIFdjCCA14CCQCeVwANSZmmiDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC
BAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0G VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCVBhbG8gQWx0bzEVMBMG
A1UECgwGRG9ja2VyMScwJQYDVQQDDB5Ob3RhcnkgSW50ZXJtZWRpYXRlIFRlc3Rp A1UECgwMVk13YXJlLCBJbmMuMQ8wDQYDVQQLDAZIYXJib3IxJDAiBgNVBAMMG1Nl
bmcgQ0EwHhcNMTcwMTIzMDYwMzM3WhcNMTkwMjEyMDYwMzM3WjBbMQswCQYDVQQG bGYtc2lnbmVkIGJ5IFZNd2FyZSwgSW5jLjAeFw0xNzAzMjQwNTMyMDBaFw0yNzAz
EwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNV MjIwNTMyMDBaMHUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIw
BAoMBkRvY2tlcjEWMBQGA1UEAwwNbm90YXJ5LXNpZ25lcjCCASIwDQYJKoZIhvcN EAYDVQQHDAlQYWxvIEFsdG8xFTATBgNVBAoMDFZNd2FyZSwgSW5jLjEPMA0GA1UE
AQEBBQADggEPADCCAQoCggEBANhO8+K9xT6M9dQC90Hxs6bmTXWQzE5oV2kLeVKq CwwGSGFyYm9yMRUwEwYDVQQDDAxub3RhcnlzaWduZXIwggIiMA0GCSqGSIb3DQEB
OjwAvGt6wBE2XJCAbTS3FORIOyoOVQDVCv2Pk2lZXGWqSrH8SY2umjRJIhPDiqN9 AQUAA4ICDwAwggIKAoICAQC6TV2RCoH8d1g6xFvDo4FL9v+pGLe5+bu9ryjTaLbN
V5M/gcmMm2EUgwmp2l4bsDk1MQ6GSbud5kjYGZcp9uXxAVO8tfLVLQF7ohJYqiex dH/Cmf5/8WrmgJ3vG2Ksk796J7qsVddwvQkZn6NwDm2Tm+ETMCG85yEA3jl4Kr9R
JN+fZkQyxTgSqrI7MKK1pUvGX/fa6EXzpKwxTQPJXiG/ZQW0Pn+gdrz+/Cf0PcVy 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
-----END CERTIFICATE----- b0cz7JqJZgFXfEnykic3IwZs7m7Xrl9B/vvaVub9Fb5LQ7rIzrO7VkoILov/G41B
-----BEGIN CERTIFICATE----- Pd4/kagjXDTWd+UBMvZF6YGjr+TUZi5ooi7bvQ3X6N9WNYKW4a1DOokz9janStiL
MIIF1TCCA72gAwIBAgIJAMk2DFRLRSRRMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV MrTKyOEOBi0Aew==
BAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0G
A1UECgwGRG9ja2VyMRowGAYDVQQDDBFOb3RhcnkgVGVzdGluZyBDQTAeFw0xNzAx
MjMwNjAzMzdaFw0yNzAxMjEwNjAzMzdaMGwxCzAJBgNVBAYTAlVTMQswCQYDVQQI
DAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0GA1UECgwGRG9ja2VyMScw
JQYDVQQDDB5Ob3RhcnkgSW50ZXJtZWRpYXRlIFRlc3RpbmcgQ0EwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCu+ldASegXuhXrA7mnk4nybTEomHnV8zJ/
uU6+8bWIo+htD8zgiONuk1uEww0p/nWtIZqm7xpLsklMp0CWRA8EAeUnxfNJ37ks
7nZuJ+YDtw77fC0IUJSWqFbro75nPMyegMqajT7IDWfLeTrIlgUmDu/45AWdbE2w
BrRgejqkL1yeQPaldgr97g00swbTd7wzWn1o6025Frm0kDEIqMJlkB61cHiVGZNu
oeDBZcFiwa/Ek/keDG3Y2R6cDQzZa8aEZG9i3Cmo0nGviojr+06JxQ8IkVc5P72e
Fb/jgX/NvRaqeBnJrZoiPnuMoMag/ynGC9fuIAGz25fKOuGOf52x+swzQB2ZVtxA
BIgIZIbMTURKknqbl6LAh46onQUVF+3h9E9Te3a4Oh7SvSGLYfEbWprPKo1J3lI9
ApU19TBhKUrj7dsJT3gri7f71NC2RLraZbpK3d8PWKMc/q4ffoRCeW+TPjYreC/d
7LdykAwYB2AGyHCLHkkkJC86n6wAsk/TaoTgjflyyQ35FNikUYqNF/rVuc+0Oj5R
odPk8y2vB7VvPvWWlttcr7OMqVVAymQvDOTb+5T6EI/LdHejjDMMI5lt6rVUU+uq
kGMYGiHtWG5JqQdhUBpISYuF74cS5aVRmnhK6O2ylMpmlWYq4128SRv8EEAPNcN9
V/RrOF9RsQIDAQABo4GGMIGDMB8GA1UdIwQYMBaAFJZZtwJ5t4SBmVaTb+T5puH5
sQWkMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
AQUFBwMCMA4GA1UdDwEB/wQEAwIBRjAdBgNVHQ4EFgQUI4KTWCY1PRIu52naToR5
ufRT4ikwDQYJKoZIhvcNAQELBQADggIBAI64zW1o24R8K7qsE8FO3UHJQdizR1RC
FvMDgXGDSYMUg4QkEvHYYOoFH1zMd1HNUuLDO231dtw23kshNY/kdKfdFJktT3Dz
50r/hl2090uZIOk9aLv7swG0voA6A8CI2qyXEXW9Le8xrnrJUU5T+3YDxseHokTT
XT9hLd1iSNH5gi3tOaJ4KNbHc2zhKtQSUZbxguapUIUXStiQLz06itQu3i1fLdMd
L3yRJID4aWU+Dmm5AQ6F3ticIpzFmJyAsTM2BMiTnlSJPK3LA2WYMBOVD6r9yo08
cEpi6Vo8pZdsnHWaIaIkO4UR7iBwmkT0h8HfNZ4uEoViiMsxqNVsQBfJR/9DzAXz
ctO6JtNJdNwn2zlv4NCIcV0AdncVf049uOtTBWIqRn1IHQ8d119lQAMXZZMSNKBI
lIYFCKMh95XI6mK6VFsFKs2wSDiSH4ZOqIwr4urmr1opLNJ5T5Ck18YwJafgCH4p
1BcgR06wuw5ckIuUyUwiakiGINZcrzUnAoRtEKsVi/PQAC+45veo8Lcvwnj5X0vg
PKudwiJivo7Umvj1xEVyVIy+22cyDk/yLTVI0sS2Kpuwd+PLE16C5+nPr8wKEWqL
ccotlod4ZDVb6vNU5VRUSu4bSYBry/FbftPNgAwfH8ufSddeJMjTQ+V69XrQZ5Ex
XJCKYD/1jYIB
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -1,28 +1,52 @@
-----BEGIN RSA PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
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
-----END RSA PRIVATE KEY----- 8zOHj+j6uV8ddG79D6FqNJHAQwpou/p+XO/BGDFgX22x4F68Z0gCQcmoyAE7ppOp
dqq3lPoDbRQ02/5cqaIA6dhmfjK2cpz4y1nUxffzY7qJjpoB/YSdR66cCNiYcJzp
fMVBXhF9Iyj/Cah1w+hc0NOy9dW15afFaLFK0zrtAzEaVxH/0QKCAQEAwRPOwSCl
XrMYXmc91TF6XbhErILHK/pIEOIMF09KNJvSjY0188Ram/pFbPRYh0cIyASmRGXL
Qq5B1Qi0vx5TCq1OCrW2yeE7zboAlnADhk1u9N8YmL6JrCKVGQO7wFD3V8uphXdM
tixNa5WvJ6eE5Vq+SVy99V5pQgb8ErrISlW4MYK7LI7DruSDuM2tHtiOcXcdTVej
1stXJZkH46RYvxxid9tRzfiB8K5ziZfLwPNf2wRyj1J4ojn5pPNhhfkjJ24LCZGt
JxwSXqdP+4x7by6x3mU+hutU/lF3jl+0edSnU0cZ6lvuq2T5YGgda/VXlv1ZFQUw
rwUXD9unU+aLgQKCAQEA9R74/pI5sthAVHFsKStb9dComtNGstI59aCF5h3oZvV1
Lvj/q9dARWqMS9qplOoV58MMCWikmhJNw3IMTvVZsjBgyzRVEJ4aDKttcQXde0Ys
w3m0LdTsxtSHu5XapY032FHG/gLlI+Pm48mjqbQsou6OyOOEJLNhO0qmqc/2tB4T
v6PdTM9enAYnqCcCTQSlTfSTNJJOYT2OTuRB4U7hUvQoGTSOInrmwLRDNBjQuCso
/zNQCQbu2P6EPYmam5yjZDTUxqZL+G/GvK49Fp9JXlQc5ycke7rD+uwa3s+3wCtG
rH9gJitfQZrxj+Cj9EOwj0bfJLbac6ZD0CkH5GNeIQKCAQBdoGFOPapzdZ2HicDu
NQQFlmmWzgQPS1rO9Q6v7v8o67b6dVOIVdsqb/5ii0qyrruPYtHNsR8TwrShvYsI
cogKUWfawatV0ibR6DSIvuC2q632iIjA6QSRuGNcsfbFl32Z0WTvF57XaDxSw08g
h5dmMM69fH+REKsyHXj3DCQ8B70+JQrm3IP/t0g4wWQF5TWNyBkpfCoy6n/j94Vf
2j4+zmDhhjTxEGTSdYYJXtarRllhN5Ll9TQSVtK8LllIQjvNzwsDJOU2ZeJyi+e5
L7Jbg+U01xuvCUc52/+Bxt8ZhQlu1Le4ccQW0Ows19AMnfhPe6NLEi09cdZxFi7Z
/J4BAoIBABCzkBDFxZdfWYt69VBt9PSG8eJ6avny3hXCtKaHIQb+aD5nKjRP0DVh
gyutCo6RasMEc6D1tJGyR/Xvhm64q4JPb5UbSaRQiVYKdgRtMM9pZeBkcBtNs18K
yMx5ajgYorrbi86hXHX7q+JYP8MCbcqqAUSl/Hi8nPxc1foTiCNDf4kGoHvXmoxt
0tA65tFFQhEA6KBn68SDkyTsl/zb5Sx0GJY4kZkOeF3GaxPFX12skgXv95GJUskX
88RJsH4Qqqtzbzj8R241BH8OrcOoyELc6xPioEqUHKVxSIf2ylITbj0UQHd2u0mN
tajKl+aoc+CDxUYbilzhhKetWWF/cJY=
-----END PRIVATE KEY-----

View File

@ -118,6 +118,7 @@ services:
ports: ports:
- 80:80 - 80:80
- 443:443 - 443:443
- 4443:4443
depends_on: depends_on:
- mysql - mysql
- registry - registry

View File

@ -20,19 +20,10 @@ max_job_workers = 3
#Determine whether or not to generate certificate for the registry's token. #Determine whether or not to generate certificate for the registry's token.
#If the value is on, the prepare script creates new root cert and private key #If the value is on, the prepare script creates new root cert and private key
#for generating token to access the registry. If the value is off, a key/certificate must #for generating token to access the registry. If the value is off the default key/cert will be used.
#be supplied for token generation. #This flag also controls the creation of the notary signer's cert.
customize_crt = on customize_crt = on
#Information of your organization for certificate
crt_country = CN
crt_state = State
crt_location = CN
crt_organization = organization
crt_organizationalunit = organizational unit
crt_commonname = example.com
crt_email = example@example.com
#The path of cert and key files for nginx, they are applied only the protocol is set to https #The path of cert and key files for nginx, they are applied only the protocol is set to https
ssl_cert = /data/cert/server.crt ssl_cert = /data/cert/server.crt
ssl_cert_key = /data/cert/server.key ssl_cert_key = /data/cert/server.key

View File

@ -166,13 +166,13 @@ then
if [ -n "$(docker-compose -f docker-compose.yml -f docker-compose.notary.yml ps -q)" ] if [ -n "$(docker-compose -f docker-compose.yml -f docker-compose.notary.yml ps -q)" ]
then then
note "stopping existing Harbor instance ..." note "stopping existing Harbor instance ..."
docker-compose -f docker-compose.yml -f docker-compose.notary.yml down docker-compose -f docker-compose.yml -f docker-compose.notary.yml down -v
fi fi
else else
if [ -n "$(docker-compose -f docker-compose.yml ps -q)" ] if [ -n "$(docker-compose -f docker-compose.yml ps -q)" ]
then then
note "stopping existing Harbor instance ..." note "stopping existing Harbor instance ..."
docker-compose -f docker-compose.yml down docker-compose -f docker-compose.yml down -v
fi fi
fi fi
echo "" echo ""

View File

@ -135,13 +135,6 @@ if protocol == "https":
cert_path = rcp.get("configuration", "ssl_cert") cert_path = rcp.get("configuration", "ssl_cert")
cert_key_path = rcp.get("configuration", "ssl_cert_key") cert_key_path = rcp.get("configuration", "ssl_cert_key")
customize_crt = rcp.get("configuration", "customize_crt") customize_crt = rcp.get("configuration", "customize_crt")
crt_country = rcp.get("configuration", "crt_country")
crt_state = rcp.get("configuration", "crt_state")
crt_location = rcp.get("configuration", "crt_location")
crt_organization = rcp.get("configuration", "crt_organization")
crt_organizationalunit = rcp.get("configuration", "crt_organizationalunit")
crt_commonname = rcp.get("configuration", "crt_commonname")
crt_email = rcp.get("configuration", "crt_email")
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") verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
@ -273,44 +266,43 @@ def stat_decorator(func):
@stat_decorator @stat_decorator
def create_root_cert(subj, key_path="./k.key", cert_path="./cert.crt"): def create_root_cert(subj, key_path="./k.key", cert_path="./cert.crt"):
rc = subprocess.call(["openssl", "genrsa", "-out", key_path, "4096"]) rc = subprocess.call(["openssl", "genrsa", "-out", key_path, "4096"], stdout=FNULL, stderr=subprocess.STDOUT)
if rc != 0: if rc != 0:
return rc return rc
return subprocess.call(["openssl", "req", "-new", "-x509", "-key", key_path,\ return subprocess.call(["openssl", "req", "-new", "-x509", "-key", key_path,\
"-out", cert_path, "-days", "3650", "-subj", subj]) "-out", cert_path, "-days", "3650", "-subj", subj], stdout=FNULL, stderr=subprocess.STDOUT)
@stat_decorator @stat_decorator
def create_cert(subj, ca_key, ca_cert, key_path="./k.key", cert_path="./cert.crt"): def create_cert(subj, ca_key, ca_cert, key_path="./k.key", cert_path="./cert.crt"):
cert_dir = os.path.dirname(cert_path) cert_dir = os.path.dirname(cert_path)
csr_path = os.path.join(cert_dir, "tmp.csr") csr_path = os.path.join(cert_dir, "tmp.csr")
rc = subprocess.call(["openssl", "req", "-newkey", "rsa:4096", "-nodes","-sha256","-keyout", key_path,\ rc = subprocess.call(["openssl", "req", "-newkey", "rsa:4096", "-nodes","-sha256","-keyout", key_path,\
"-out", csr_path, "-subj", subj]) "-out", csr_path, "-subj", subj], stdout=FNULL, stderr=subprocess.STDOUT)
if rc != 0: if rc != 0:
return rc return rc
return subprocess.call(["openssl", "x509", "-req", "-days", "3650", "-in", csr_path, "-CA", \ return subprocess.call(["openssl", "x509", "-req", "-days", "3650", "-in", csr_path, "-CA", \
ca_cert, "-CAkey", ca_key, "-CAcreateserial", "-out", cert_path]) ca_cert, "-CAkey", ca_key, "-CAcreateserial", "-out", cert_path], stdout=FNULL, stderr=subprocess.STDOUT)
def openssl_is_installed(stat): def openssl_installed():
if stat == 0: shell_stat = subprocess.check_call(["which", "openssl"], stdout=FNULL, stderr=subprocess.STDOUT)
return True if shell_stat != 0:
else:
print("Cannot find openssl installed in this computer\nUse default SSL certificate file") print("Cannot find openssl installed in this computer\nUse default SSL certificate file")
return False return False
return True
if customize_crt == 'on': if customize_crt == 'on' and openssl_installed():
shell_stat = subprocess.check_call(["which", "openssl"], stdout=FNULL, stderr=subprocess.STDOUT) shell_stat = subprocess.check_call(["which", "openssl"], stdout=FNULL, stderr=subprocess.STDOUT)
if openssl_is_installed(shell_stat): empty_subj = "/C=/ST=/L=/O=/CN=/"
empty_subj = "/C=/ST=/L=/O=/CN=/" private_key_pem = os.path.join(config_dir, "ui", "private_key.pem")
private_key_pem = os.path.join(config_dir, "ui", "private_key.pem") root_crt = os.path.join(config_dir, "registry", "root.crt")
root_crt = os.path.join(config_dir, "registry", "root.crt") create_root_cert(empty_subj, key_path=private_key_pem, cert_path=root_crt)
create_root_cert(empty_subj, key_path=private_key_pem, cert_path=root_crt)
else: else:
print("Copied configuration file: %s" % ui_config_dir + "private_key.pem") print("Copied configuration file: %s" % ui_config_dir + "private_key.pem")
shutil.copyfile(os.path.join(templates_dir, "ui", "private_key.pem"), os.path.join(ui_config_dir, "private_key.pem")) shutil.copyfile(os.path.join(templates_dir, "ui", "private_key.pem"), os.path.join(ui_config_dir, "private_key.pem"))
print("Copied configuration file: %s" % registry_config_dir + "root.crt") print("Copied configuration file: %s" % registry_config_dir + "root.crt")
shutil.copyfile(os.path.join(templates_dir, "registry", "root.crt"), os.path.join(registry_config_dir, "root.crt")) shutil.copyfile(os.path.join(templates_dir, "registry", "root.crt"), os.path.join(registry_config_dir, "root.crt"))
FNULL.close()
if args.notary_mode: if args.notary_mode:
notary_config_dir = prep_conf_dir(config_dir, "notary") notary_config_dir = prep_conf_dir(config_dir, "notary")
notary_temp_dir = os.path.join(templates_dir, "notary") notary_temp_dir = os.path.join(templates_dir, "notary")
@ -318,13 +310,12 @@ if args.notary_mode:
if os.path.exists(os.path.join(notary_config_dir, "mysql-initdb.d")): if os.path.exists(os.path.join(notary_config_dir, "mysql-initdb.d")):
shutil.rmtree(os.path.join(notary_config_dir, "mysql-initdb.d")) shutil.rmtree(os.path.join(notary_config_dir, "mysql-initdb.d"))
shutil.copytree(os.path.join(notary_temp_dir, "mysql-initdb.d"), os.path.join(notary_config_dir, "mysql-initdb.d")) shutil.copytree(os.path.join(notary_temp_dir, "mysql-initdb.d"), os.path.join(notary_config_dir, "mysql-initdb.d"))
#TODO:generate certs? if customize_crt == 'on' and openssl_installed():
if customize_crt == 'on':
temp_cert_dir = os.path.join(base_dir, "cert_tmp") temp_cert_dir = os.path.join(base_dir, "cert_tmp")
if not os.path.exists(temp_cert_dir): if not os.path.exists(temp_cert_dir):
os.makedirs(temp_cert_dir) os.makedirs(temp_cert_dir)
ca_subj = "/C=US/ST=California/L=Palo Alto/O=Vmware/CN=Self Signed CA/" ca_subj = "/C=US/ST=California/L=Palo Alto/O=VMware, Inc./OU=Harbor/CN=Self-signed by VMware, Inc."
cert_subj = "/C=US/ST=California/L=Palo Alto/O=Vmware/CN=notarysigner/" cert_subj = "/C=US/ST=California/L=Palo Alto/O=VMware, Inc./OU=Harbor/CN=notarysigner"
signer_ca_cert = os.path.join(temp_cert_dir, "notary-signer-ca.crt") signer_ca_cert = os.path.join(temp_cert_dir, "notary-signer-ca.crt")
signer_ca_key = os.path.join(temp_cert_dir, "notary-signer-ca.key") signer_ca_key = os.path.join(temp_cert_dir, "notary-signer-ca.key")
signer_cert_path = os.path.join(temp_cert_dir, "notary-signer.crt") signer_cert_path = os.path.join(temp_cert_dir, "notary-signer.crt")
@ -355,6 +346,6 @@ if args.notary_mode:
default_alias = ''.join(random.choice(string.ascii_letters) for i in range(8)) default_alias = ''.join(random.choice(string.ascii_letters) for i in range(8))
render(os.path.join(notary_temp_dir, "signer_env"), os.path.join(notary_config_dir, "signer_env"), alias = default_alias) render(os.path.join(notary_temp_dir, "signer_env"), os.path.join(notary_config_dir, "signer_env"), alias = default_alias)
FNULL.close()
print("The configuration files are ready, please use docker-compose to start the service.") print("The configuration files are ready, please use docker-compose to start the service.")

View File

@ -0,0 +1,21 @@
<!--
Copyright (c) 2016 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.
-->
<!DOCTYPE html>
<html>
<body>
<p>Please click this link to reset your password:</p>
<a href="{{.URL}}/reset_password?reset_uuid={{.UUID}}">{{.URL}}/reset_password?reset_uuid={{.UUID}}</a>
</body>
</html>

View File

@ -76,7 +76,10 @@ const harborRoutes: Routes = [
}, },
{ {
path: 'tags/:id/:repo', path: 'tags/:id/:repo',
component: TagRepositoryComponent component: TagRepositoryComponent,
resolve: {
projectResolver: ProjectRoutingResolver
}
}, },
{ {
path: 'projects/:id', path: 'projects/:id',

View File

@ -1,8 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions } from '@angular/http'; import { Http, Headers, RequestOptions } from '@angular/http';
import { BaseService } from '../service/base.service';
import { AuditLog } from './audit-log'; import { AuditLog } from './audit-log';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
@ -13,7 +11,7 @@ import 'rxjs/add/observable/throw';
export const logEndpoint = "/api/logs"; export const logEndpoint = "/api/logs";
@Injectable() @Injectable()
export class AuditLogService extends BaseService { export class AuditLogService {
private httpOptions = new RequestOptions({ private httpOptions = new RequestOptions({
headers: new Headers({ headers: new Headers({
"Content-Type": 'application/json', "Content-Type": 'application/json',
@ -21,9 +19,7 @@ export class AuditLogService extends BaseService {
}) })
}); });
constructor(private http: Http) { constructor(private http: Http) {}
super();
}
listAuditLogs(queryParam: AuditLog): Observable<any> { listAuditLogs(queryParam: AuditLog): Observable<any> {
return this.http return this.http
@ -36,12 +32,12 @@ export class AuditLogService extends BaseService {
username: queryParam.username username: queryParam.username
}) })
.map(response => response) .map(response => response)
.catch(error => this.handleError(error)); .catch(error => Observable.throw(error));
} }
getRecentLogs(lines: number): Observable<AuditLog[]> { getRecentLogs(lines: number): Observable<AuditLog[]> {
return this.http.get(logEndpoint + "?lines=" + lines, this.httpOptions) return this.http.get(logEndpoint + "?lines=" + lines, this.httpOptions)
.map(response => response.json() as AuditLog[]) .map(response => response.json() as AuditLog[])
.catch(error => this.handleError(error)); .catch(error => Observable.throw(error));
} }
} }

View File

@ -31,6 +31,6 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button> <button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" [disabled]="!projectForm.form.valid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button> <button type="button" class="btn btn-primary" [disabled]="projectForm.form.invalid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
</div> </div>
</clr-modal> </clr-modal>

View File

@ -1,9 +1,9 @@
<clr-datagrid (clrDgRefresh)="refresh($event)"> <clr-datagrid (clrDgRefresh)="refresh($event)">
<clr-dg-column>{{'PROJECT.NAME' | translate}}</clr-dg-column> <clr-dg-column>{{'PROJECT.NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'PROJECT.PUBLIC_OR_PRIVATE' | translate}}</clr-dg-column> <clr-dg-column>{{'PROJECT.PUBLIC_OR_PRIVATE' | translate}}</clr-dg-column>
<clr-dg-column *ngIf="showRoleInfo">{{'PROJECT.ROLE' | translate}}</clr-dg-column>
<clr-dg-column>{{'PROJECT.REPO_COUNT'| translate}}</clr-dg-column> <clr-dg-column>{{'PROJECT.REPO_COUNT'| translate}}</clr-dg-column>
<clr-dg-column>{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column> <clr-dg-column>{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
<clr-dg-column>{{'PROJECT.DESCRIPTION' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let p of projects" [clrDgItem]="p"> <clr-dg-row *ngFor="let p of projects" [clrDgItem]="p">
<clr-dg-action-overflow [hidden]="!listFullMode || p.current_user_role_id !== 1"> <clr-dg-action-overflow [hidden]="!listFullMode || p.current_user_role_id !== 1">
<button class="action-item" (click)="newReplicationRule(p)" [hidden]="!isSystemAdmin">{{'PROJECT.REPLICATION_RULE' | translate}}</button> <button class="action-item" (click)="newReplicationRule(p)" [hidden]="!isSystemAdmin">{{'PROJECT.REPLICATION_RULE' | translate}}</button>
@ -12,9 +12,9 @@
</clr-dg-action-overflow> </clr-dg-action-overflow>
<clr-dg-cell><a href="javascript:void(0)" (click)="goToLink(p.project_id)">{{p.name}}</a></clr-dg-cell> <clr-dg-cell><a href="javascript:void(0)" (click)="goToLink(p.project_id)">{{p.name}}</a></clr-dg-cell>
<clr-dg-cell>{{ (p.public === 1 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}</clr-dg-cell> <clr-dg-cell>{{ (p.public === 1 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}</clr-dg-cell>
<clr-dg-cell *ngIf="showRoleInfo">{{roleInfo[p.current_user_role_id] | translate}}</clr-dg-cell>
<clr-dg-cell>{{p.repo_count}}</clr-dg-cell> <clr-dg-cell>{{p.repo_count}}</clr-dg-cell>
<clr-dg-cell>{{p.creation_time}}</clr-dg-cell> <clr-dg-cell>{{p.creation_time}}</clr-dg-cell>
<clr-dg-cell>{{p.description}}</clr-dg-cell>
</clr-dg-row> </clr-dg-row>
<clr-dg-footer> <clr-dg-footer>
{{totalRecordCount || (projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}} {{totalRecordCount || (projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}}

View File

@ -5,7 +5,7 @@ import { ProjectService } from '../project.service';
import { SessionService } from '../../shared/session.service'; import { SessionService } from '../../shared/session.service';
import { SearchTriggerService } from '../../base/global-search/search-trigger.service'; import { SearchTriggerService } from '../../base/global-search/search-trigger.service';
import { ListMode } from '../../shared/shared.const'; import { ListMode, ProjectTypes, RoleInfo } from '../../shared/shared.const';
import { State } from 'clarity-angular'; import { State } from 'clarity-angular';
@ -24,6 +24,8 @@ export class ListProjectComponent implements OnInit {
@Input() totalRecordCount: number; @Input() totalRecordCount: number;
pageOffset: number = 1; pageOffset: number = 1;
@Input() filteredType: string;
@Output() paginate = new EventEmitter<State>(); @Output() paginate = new EventEmitter<State>();
@Output() toggle = new EventEmitter<Project>(); @Output() toggle = new EventEmitter<Project>();
@ -31,6 +33,8 @@ export class ListProjectComponent implements OnInit {
@Input() mode: string = ListMode.FULL; @Input() mode: string = ListMode.FULL;
roleInfo = RoleInfo;
constructor( constructor(
private session: SessionService, private session: SessionService,
private router: Router, private router: Router,
@ -43,6 +47,10 @@ export class ListProjectComponent implements OnInit {
return this.mode === ListMode.FULL && this.session.getCurrentUser() != null; return this.mode === ListMode.FULL && this.session.getCurrentUser() != null;
} }
get showRoleInfo(): boolean {
return this.listFullMode && this.filteredType === ProjectTypes[0];
}
public get isSystemAdmin(): boolean { public get isSystemAdmin(): boolean {
let account = this.session.getCurrentUser(); let account = this.session.getCurrentUser();
return account != null && account.has_admin_role > 0; return account != null && account.has_admin_role > 0;

View File

@ -36,6 +36,6 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button> <button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" [disabled]="!memberForm.form.valid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button> <button type="button" class="btn btn-primary" [disabled]="memberForm.form.invalid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
</div> </div>
</clr-modal> </clr-modal>

View File

@ -17,15 +17,15 @@
<clr-datagrid> <clr-datagrid>
<clr-dg-column>{{'MEMBER.NAME' | translate}}</clr-dg-column> <clr-dg-column>{{'MEMBER.NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column> <clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let u of members"> <clr-dg-row *ngFor="let m of members">
<clr-dg-action-overflow [hidden]="u.user_id === currentUser.user_id || !hasProjectAdminRole"> <clr-dg-action-overflow [hidden]="m.user_id === currentUser.user_id || !hasProjectAdminRole">
<button class="action-item" (click)="changeRole(u.user_id, 1)">{{'MEMBER.PROJECT_ADMIN' | translate}}</button> <button class="action-item" (click)="changeRole(m, 1)">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
<button class="action-item" (click)="changeRole(u.user_id, 2)">{{'MEMBER.DEVELOPER' | translate}}</button> <button class="action-item" (click)="changeRole(m, 2)">{{'MEMBER.DEVELOPER' | translate}}</button>
<button class="action-item" (click)="changeRole(u.user_id, 3)">{{'MEMBER.GUEST' | translate}}</button> <button class="action-item" (click)="changeRole(m, 3)">{{'MEMBER.GUEST' | translate}}</button>
<button class="action-item" (click)="deleteMember(u.user_id)">{{'MEMBER.DELETE' | translate}}</button> <button class="action-item" (click)="deleteMember(m)">{{'MEMBER.DELETE' | translate}}</button>
</clr-dg-action-overflow> </clr-dg-action-overflow>
<clr-dg-cell>{{u.username}}</clr-dg-cell> <clr-dg-cell>{{m.username}}</clr-dg-cell>
<clr-dg-cell>{{roleInfo[u.role_id] | translate}}</clr-dg-cell> <clr-dg-cell>{{roleInfo[m.role_id] | translate}}</clr-dg-cell>
</clr-dg-row> </clr-dg-row>
<clr-dg-footer>{{ (members ? members.length : 0) }} {{'MEMBER.ITEMS' | translate}}</clr-dg-footer> <clr-dg-footer>{{ (members ? members.length : 0) }} {{'MEMBER.ITEMS' | translate}}</clr-dg-footer>
</clr-datagrid> </clr-datagrid>

View File

@ -15,6 +15,8 @@ import { ConfirmationDialogService } from '../../shared/confirmation-dialog/conf
import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message'; import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message';
import { SessionService } from '../../shared/session.service'; import { SessionService } from '../../shared/session.service';
import { RoleInfo } from '../../shared/shared.const';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/catch';
@ -22,7 +24,7 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw'; import 'rxjs/add/observable/throw';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' }; import { Project } from '../../project/project';
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
@ -31,31 +33,22 @@ export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER',
}) })
export class MemberComponent implements OnInit, OnDestroy { export class MemberComponent implements OnInit, OnDestroy {
currentUser: SessionUser;
members: Member[]; members: Member[];
projectId: number; projectId: number;
roleInfo = roleInfo; roleInfo = RoleInfo;
private delSub: Subscription; private delSub: Subscription;
@ViewChild(AddMemberComponent) @ViewChild(AddMemberComponent)
addMemberComponent: AddMemberComponent; addMemberComponent: AddMemberComponent;
currentUser: SessionUser;
hasProjectAdminRole: boolean; hasProjectAdminRole: boolean;
constructor(private route: ActivatedRoute, private router: Router, constructor(private route: ActivatedRoute, private router: Router,
private memberService: MemberService, private messageService: MessageService, private memberService: MemberService, private messageService: MessageService,
private deletionDialogService: ConfirmationDialogService, private deletionDialogService: ConfirmationDialogService,
session: SessionService) { private session: SessionService) {
//Get current user from registered resolver.
this.currentUser = session.getCurrentUser();
let projectMembers: Member[] = session.getProjectMembers();
if(this.currentUser && projectMembers) {
let currentMember = projectMembers.find(m=>m.user_id === this.currentUser.user_id);
if(currentMember) {
this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin');
}
}
this.delSub = deletionDialogService.confirmationConfirm$.subscribe(message => { this.delSub = deletionDialogService.confirmationConfirm$.subscribe(message => {
if (message && if (message &&
message.state === ConfirmationState.CONFIRMED && message.state === ConfirmationState.CONFIRMED &&
@ -82,8 +75,7 @@ export class MemberComponent implements OnInit, OnDestroy {
error => { error => {
this.router.navigate(['/harbor', 'projects']); this.router.navigate(['/harbor', 'projects']);
this.messageService.announceMessage(error.status, 'Failed to get project member with project ID:' + projectId, AlertType.DANGER); this.messageService.announceMessage(error.status, 'Failed to get project member with project ID:' + projectId, AlertType.DANGER);
} });
);
} }
ngOnDestroy() { ngOnDestroy() {
@ -97,6 +89,15 @@ export class MemberComponent implements OnInit, OnDestroy {
this.projectId = +this.route.snapshot.parent.params['id']; this.projectId = +this.route.snapshot.parent.params['id'];
console.log('Get projectId from route params snapshot:' + this.projectId); console.log('Get projectId from route params snapshot:' + this.projectId);
this.currentUser = this.session.getCurrentUser();
//Get current user from registered resolver.
let resolverData = this.route.snapshot.parent.data;
if(resolverData) {
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
}
this.retrieve(this.projectId, ''); this.retrieve(this.projectId, '');
} }
@ -108,25 +109,27 @@ export class MemberComponent implements OnInit, OnDestroy {
this.retrieve(this.projectId, ''); this.retrieve(this.projectId, '');
} }
changeRole(userId: number, roleId: number) { changeRole(m: Member, roleId: number) {
this.memberService if(m) {
.changeMemberRole(this.projectId, userId, roleId) this.memberService
.subscribe( .changeMemberRole(this.projectId, m.user_id, roleId)
response => { .subscribe(
this.messageService.announceMessage(response, 'MEMBER.SWITCHED_SUCCESS', AlertType.SUCCESS); response => {
console.log('Successful change role with user ' + userId + ' to roleId ' + roleId); this.messageService.announceMessage(response, 'MEMBER.SWITCHED_SUCCESS', AlertType.SUCCESS);
this.retrieve(this.projectId, ''); console.log('Successful change role with user ' + m.user_id + ' to roleId ' + roleId);
}, this.retrieve(this.projectId, '');
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + userId + ' to roleId ' + roleId, AlertType.DANGER) },
); error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + m.user_id + ' to roleId ' + roleId, AlertType.DANGER)
);
}
} }
deleteMember(userId: number) { deleteMember(m: Member) {
let deletionMessage: ConfirmationMessage = new ConfirmationMessage( let deletionMessage: ConfirmationMessage = new ConfirmationMessage(
'MEMBER.DELETION_TITLE', 'MEMBER.DELETION_TITLE',
'MEMBER.DELETION_SUMMARY', 'MEMBER.DELETION_SUMMARY',
userId + "", m.username,
userId, m.user_id,
ConfirmationTargets.PROJECT_MEMBER ConfirmationTargets.PROJECT_MEMBER
); );
this.deletionDialogService.openComfirmDialog(deletionMessage); this.deletionDialogService.openComfirmDialog(deletionMessage);

View File

@ -6,22 +6,19 @@ import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw'; import 'rxjs/add/observable/throw';
import { BaseService } from '../../service/base.service';
import { Member } from './member'; import { Member } from './member';
@Injectable() @Injectable()
export class MemberService extends BaseService { export class MemberService {
constructor(private http: Http) { constructor(private http: Http) {}
super();
}
listMembers(projectId: number, username: string): Observable<Member[]> { listMembers(projectId: number, username: string): Observable<Member[]> {
console.log('Get member from project_id:' + projectId + ', username:' + username); console.log('Get member from project_id:' + projectId + ', username:' + username);
return this.http return this.http
.get(`/api/projects/${projectId}/members?username=${username}`) .get(`/api/projects/${projectId}/members?username=${username}`)
.map(response=>response.json()) .map(response=>response.json() as Member[])
.catch(error=>this.handleError(error)); .catch(error=>Observable.throw(error));
} }
addMember(projectId: number, username: string, roleId: number): Observable<any> { addMember(projectId: number, username: string, roleId: number): Observable<any> {

View File

@ -5,10 +5,10 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" routerLink="repository" routerLinkActive="active">{{'PROJECT_DETAIL.REPOSITORIES' | translate}}</a> <a class="nav-link" routerLink="repository" routerLinkActive="active">{{'PROJECT_DETAIL.REPOSITORIES' | translate}}</a>
</li> </li>
<li class="nav-item" *ngIf="isSessionValid"> <li class="nav-item" *ngIf="isSystemAdmin || isMember">
<a class="nav-link" routerLink="member" routerLinkActive="active">{{'PROJECT_DETAIL.USERS' | translate}}</a> <a class="nav-link" routerLink="member" routerLinkActive="active">{{'PROJECT_DETAIL.USERS' | translate}}</a>
</li> </li>
<li class="nav-item" *ngIf="isSessionValid"> <li class="nav-item" *ngIf="isSystemAdmin || isMember">
<a class="nav-link" routerLink="log" routerLinkActive="active">{{'PROJECT_DETAIL.LOGS' | translate}}</a> <a class="nav-link" routerLink="log" routerLinkActive="active">{{'PROJECT_DETAIL.LOGS' | translate}}</a>
</li> </li>
<li class="nav-item" *ngIf="isSessionValid && isSystemAdmin"> <li class="nav-item" *ngIf="isSessionValid && isSystemAdmin">

View File

@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { Project } from '../project'; import { Project } from '../project';
import { SessionService } from '../../shared/session.service'; import { SessionService } from '../../shared/session.service';
import { ProjectService } from '../../project/project.service';
@Component({ @Component({
selector: 'project-detail', selector: 'project-detail',
@ -13,13 +14,18 @@ import { SessionService } from '../../shared/session.service';
export class ProjectDetailComponent { export class ProjectDetailComponent {
currentProject: Project; currentProject: Project;
isMember: boolean;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private sessionService: SessionService) { private sessionService: SessionService,
this.route.data.subscribe(data=>this.currentProject = <Project>data['projectResolver']); private projectService: ProjectService) {
this.route.data.subscribe(data=>{
this.currentProject = <Project>data['projectResolver'];
this.isMember = this.currentProject.is_member;
});
} }
public get isSystemAdmin(): boolean { public get isSystemAdmin(): boolean {

View File

@ -3,23 +3,43 @@ import { Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@a
import { Project } from './project'; import { Project } from './project';
import { ProjectService } from './project.service'; import { ProjectService } from './project.service';
import { SessionService } from '../shared/session.service';
import 'rxjs/add/operator/mergeMap';
@Injectable() @Injectable()
export class ProjectRoutingResolver implements Resolve<Project>{ export class ProjectRoutingResolver implements Resolve<Project>{
constructor(private projectService: ProjectService, private router: Router) {} constructor(
private sessionService: SessionService,
private projectService: ProjectService,
private router: Router) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Project> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Project> {
let projectId = route.params['id']; let projectId = route.params['id'];
console.log('Project resolver, projectID:' + projectId);
return this.projectService return this.projectService
.getProject(projectId) .getProject(projectId)
.then(project=> { .toPromise()
if(project) { .then((project: Project)=> {
return project; if(project) {
} else { let currentUser = this.sessionService.getCurrentUser();
this.router.navigate(['/harbor', 'projects']); let projectMembers = this.sessionService.getProjectMembers();
return null; if(currentUser && projectMembers) {
} let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id);
if(currentMember) {
project.is_member = true;
project.has_project_admin_role = (currentMember.role_name === 'projectAdmin') || currentUser.has_admin_role === 1;
}
}
return project;
} else {
this.router.navigate(['/harbor', 'projects']);
return null;
}
}).catch(error=>{
this.router.navigate(['/harbor', 'projects']);
return null;
}); });
} }
} }

View File

@ -1,11 +1,13 @@
.header-title { .header-title {
margin-top: 0; margin-top: 12px;
} }
.option-left { .option-left {
padding-left: 12px; padding-left: 12px;
margin-top: 12px; margin-top: 12px;
} }
.option-right { .option-right {
padding-right: 16px; padding-right: 16px;
margin-top: 18px; margin-top: 18px;
} }

View File

@ -1,6 +1,9 @@
<div class="row"> <div class="row" style="margin-right: 12px;">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12"> <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<h2 class="header-title">{{'PROJECT.PROJECTS' | translate}}</h2> <h2 class="header-title">{{'PROJECT.PROJECTS' | translate}}</h2>
<div>
<statistics-panel></statistics-panel>
</div>
<div class="row flex-items-xs-between"> <div class="row flex-items-xs-between">
<div class="option-left"> <div class="option-left">
<button *ngIf="projectCreationRestriction" class="btn btn-primary" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'PROJECT.PROJECT' | translate}}</button> <button *ngIf="projectCreationRestriction" class="btn btn-primary" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'PROJECT.PROJECT' | translate}}</button>
@ -18,11 +21,13 @@
</div> </div>
</clr-dropdown> </clr-dropdown>
<grid-filter filterPlaceholder='{{"PROJECT.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearchProjects($event)"></grid-filter> <grid-filter filterPlaceholder='{{"PROJECT.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearchProjects($event)"></grid-filter>
<a href="javascript:void(0)" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></a> <a href="javascript:void(0)" (click)="refresh()">
<clr-icon shape="refresh"></clr-icon>
</a>
</div> </div>
</div> </div>
</div> <list-project [projects]="changedProjects" [filteredType]="projectTypes[currentFilteredType]" (toggle)="toggleProject($event)" (delete)="deleteProject($event)" (paginate)="retrieve($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount"></list-project>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12"> </div>
<list-project [projects]="changedProjects" (toggle)="toggleProject($event)" (delete)="deleteProject($event)" (paginate)="retrieve($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount"></list-project>
</div>
</div> </div>

View File

@ -25,9 +25,7 @@ import { State } from 'clarity-angular';
import { AppConfigService } from '../app-config.service'; import { AppConfigService } from '../app-config.service';
import { SessionService } from '../shared/session.service'; import { SessionService } from '../shared/session.service';
import { ProjectTypes } from '../shared/shared.const';
const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' };
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
@ -39,7 +37,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
selected = []; selected = [];
changedProjects: Project[]; changedProjects: Project[];
projectTypes = types; projectTypes = ProjectTypes;
@ViewChild(CreateProjectComponent) @ViewChild(CreateProjectComponent)
creationProject: CreateProjectComponent; creationProject: CreateProjectComponent;
@ -145,7 +143,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
} }
doFilterProjects(filteredType: number): void { doFilterProjects(filteredType: number): void {
console.log('Filter projects with type:' + types[filteredType]); console.log('Filter projects with type:' + this.projectTypes[filteredType]);
this.isPublic = filteredType; this.isPublic = filteredType;
this.currentFilteredType = filteredType; this.currentFilteredType = filteredType;
this.retrieve(); this.retrieve();

View File

@ -3,8 +3,6 @@ import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions, Response, URLSearchParams } from '@angular/http'; import { Http, Headers, RequestOptions, Response, URLSearchParams } from '@angular/http';
import { Project } from './project'; import { Project } from './project';
import { BaseService } from '../service/base.service';
import { Message } from '../global-message/message'; import { Message } from '../global-message/message';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
@ -22,11 +20,10 @@ export class ProjectService {
constructor(private http: Http) {} constructor(private http: Http) {}
getProject(projectId: number): Promise<Project> { getProject(projectId: number): Observable<any> {
return this.http return this.http
.get(`/api/projects/${projectId}`) .get(`/api/projects/${projectId}`)
.toPromise() .map(response=>response.json())
.then(response=>response.json() as Project)
.catch(error=>Observable.throw(error)); .catch(error=>Observable.throw(error));
} }

View File

@ -29,4 +29,6 @@ export class Project {
update_time: Date; update_time: Date;
current_user_role_id: number; current_user_role_id: number;
repo_count: number; repo_count: number;
has_project_admin_role: boolean;
is_member: boolean;
} }

View File

@ -1,8 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Http, URLSearchParams, Response } from '@angular/http'; import { Http, URLSearchParams, Response } from '@angular/http';
import { BaseService } from '../service/base.service';
import { Policy } from './policy'; import { Policy } from './policy';
import { Job } from './job'; import { Job } from './job';
import { Target } from './target'; import { Target } from './target';
@ -14,10 +12,8 @@ import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/mergeMap'; import 'rxjs/add/operator/mergeMap';
@Injectable() @Injectable()
export class ReplicationService extends BaseService { export class ReplicationService {
constructor(private http: Http) { constructor(private http: Http) {}
super();
}
listPolicies(policyName: string, projectId?: any): Observable<Policy[]> { listPolicies(policyName: string, projectId?: any): Observable<Policy[]> {
if(!projectId) { if(!projectId) {

View File

@ -1,4 +1,4 @@
import { Component, Input, Output, EventEmitter } from '@angular/core'; import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { Router, NavigationExtras } from '@angular/router'; import { Router, NavigationExtras } from '@angular/router';
import { Repository } from '../repository'; import { Repository } from '../repository';
import { State } from 'clarity-angular'; import { State } from 'clarity-angular';
@ -8,16 +8,17 @@ import { SessionService } from '../../shared/session.service';
import { ListMode } from '../../shared/shared.const'; import { ListMode } from '../../shared/shared.const';
import { SessionUser } from '../../shared/session-user'; import { SessionUser } from '../../shared/session-user';
import { Member } from '../../project/member/member';
@Component({ @Component({
selector: 'list-repository', selector: 'list-repository',
templateUrl: 'list-repository.component.html' templateUrl: 'list-repository.component.html'
}) })
export class ListRepositoryComponent { export class ListRepositoryComponent implements OnInit {
@Input() projectId: number; @Input() projectId: number;
@Input() repositories: Repository[]; @Input() repositories: Repository[];
@Output() delete = new EventEmitter<string>(); @Output() delete = new EventEmitter<string>();
@Input() totalPage: number; @Input() totalPage: number;
@ -25,25 +26,16 @@ export class ListRepositoryComponent {
@Output() paginate = new EventEmitter<State>(); @Output() paginate = new EventEmitter<State>();
@Input() mode: string = ListMode.FULL; @Input() mode: string = ListMode.FULL;
@Input() hasProjectAdminRole: boolean;
pageOffset: number = 1; pageOffset: number = 1;
hasProjectAdminRole: boolean;
constructor( constructor(
private router: Router, private router: Router,
private searchTrigger: SearchTriggerService, private searchTrigger: SearchTriggerService,
private session: SessionService) { private session: SessionService) { }
//Get current user from registered resolver.
let currentUser = session.getCurrentUser(); ngOnInit() {}
let projectMembers: Member[] = session.getProjectMembers();
if(currentUser && projectMembers) {
let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id);
if(currentMember) {
this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin');
}
}
}
deleteRepo(repoName: string) { deleteRepo(repoName: string) {
this.delete.emit(repoName); this.delete.emit(repoName);

View File

@ -8,6 +8,6 @@
</div> </div>
</div> </div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12"> <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<list-repository [projectId]="projectId" [repositories]="changedRepositories" (delete)="deleteRepo($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount" (paginate)="retrieve($event)"></list-repository> <list-repository [projectId]="projectId" [repositories]="changedRepositories" (delete)="deleteRepo($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount" [hasProjectAdminRole]="hasProjectAdminRole" (paginate)="retrieve($event)"></list-repository>
</div> </div>
</div> </div>

View File

@ -14,6 +14,8 @@ import { Subscription } from 'rxjs/Subscription';
import { State } from 'clarity-angular'; import { State } from 'clarity-angular';
import { Project } from '../project/project';
const repositoryTypes = [ const repositoryTypes = [
{ key: '0', description: 'REPOSITORY.MY_REPOSITORY' }, { key: '0', description: 'REPOSITORY.MY_REPOSITORY' },
{ key: '1', description: 'REPOSITORY.PUBLIC_REPOSITORY' } { key: '1', description: 'REPOSITORY.PUBLIC_REPOSITORY' }
@ -39,6 +41,8 @@ export class RepositoryComponent implements OnInit {
totalPage: number; totalPage: number;
totalRecordCount: number; totalRecordCount: number;
hasProjectAdminRole: boolean;
subscription: Subscription; subscription: Subscription;
constructor( constructor(
@ -66,12 +70,16 @@ export class RepositoryComponent implements OnInit {
error => this.messageService.announceMessage(error.status, 'Failed to delete repo:' + repoName, AlertType.DANGER) error => this.messageService.announceMessage(error.status, 'Failed to delete repo:' + repoName, AlertType.DANGER)
); );
} }
} });
);
} }
ngOnInit(): void { ngOnInit(): void {
this.projectId = this.route.snapshot.parent.params['id']; this.projectId = this.route.snapshot.parent.params['id'];
let resolverData = this.route.snapshot.parent.data;
if(resolverData) {
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
}
this.currentRepositoryType = this.repositoryTypes[0]; this.currentRepositoryType = this.repositoryTypes[0];
this.lastFilteredRepoName = ''; this.lastFilteredRepoName = '';
this.retrieve(); this.retrieve();

View File

@ -16,7 +16,8 @@ import { TagView } from '../tag-view';
import { AppConfigService } from '../../app-config.service'; import { AppConfigService } from '../../app-config.service';
import { SessionService } from '../../shared/session.service'; import { SessionService } from '../../shared/session.service';
import { Member } from '../../project/member/member';
import { Project } from '../../project/project';
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
@ -29,7 +30,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
projectId: number; projectId: number;
repoName: string; repoName: string;
hasProjectAdminRole: boolean; hasProjectAdminRole: boolean = false;
tags: TagView[]; tags: TagView[];
registryUrl: string; registryUrl: string;
@ -45,15 +46,6 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
private appConfigService: AppConfigService, private appConfigService: AppConfigService,
private session: SessionService){ private session: SessionService){
let currentUser = session.getCurrentUser();
let projectMembers: Member[] = session.getProjectMembers();
if(currentUser && projectMembers) {
let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id);
if(currentMember) {
this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin');
}
}
this.subscription = this.deletionDialogService.confirmationConfirm$.subscribe( this.subscription = this.deletionDialogService.confirmationConfirm$.subscribe(
message => { message => {
if (message && if (message &&
@ -78,11 +70,15 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
} }
} }
} }
} });
)
} }
ngOnInit() { ngOnInit() {
let resolverData = this.route.snapshot.data;
console.log(JSON.stringify(resolverData));
if(resolverData) {
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
}
this.projectId = this.route.snapshot.params['id']; this.projectId = this.route.snapshot.params['id'];
this.repoName = this.route.snapshot.params['repo']; this.repoName = this.route.snapshot.params['repo'];
this.tags = []; this.tags = [];
@ -100,17 +96,17 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
retrieve() { retrieve() {
this.tags = []; this.tags = [];
if(this.withNotary) { if(this.withNotary) {
this.repositoryService this.repositoryService
.listTagsWithVerifiedSignatures(this.repoName) .listTagsWithVerifiedSignatures(this.repoName)
.subscribe( .subscribe(
items => this.listTags(items), items => this.listTags(items),
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER)); error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
} else { } else {
this.repositoryService this.repositoryService
.listTags(this.repoName) .listTags(this.repoName)
.subscribe( .subscribe(
items => this.listTags(items), items => this.listTags(items),
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER)); error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
} }
} }

View File

@ -1,8 +0,0 @@
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
export class AuthGuard implements CanActivate {
canActivate() {
return true;
}
}

View File

@ -1,18 +0,0 @@
import { Http, Response,} from '@angular/http';
export class BaseService {
protected handleError(error: Response | any): Promise<any> {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
console.log(typeof error);
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
return Promise.reject(error);
}
}

View File

@ -17,7 +17,8 @@ export class MemberGuard implements CanActivate, CanActivateChild {
private router: Router) {} private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean {
let projectId: number = route.params['id']; let projectId = route.params['id'];
this.sessionService.setProjectMembers([]);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.projectService.checkProjectMember(projectId) this.projectService.checkProjectMember(projectId)
.subscribe( .subscribe(
@ -26,6 +27,10 @@ export class MemberGuard implements CanActivate, CanActivateChild {
return resolve(true) return resolve(true)
}, },
error => { error => {
//Add exception for repository in project detail router activation.
if(state.url.endsWith('repository')) {
return resolve(true);
}
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]); this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
return resolve(false); return resolve(false);
}); });

View File

@ -52,4 +52,7 @@ export const CookieKeyOfAdmiral = "admiral.endpoint.latest";
export const enum ConfirmationState { export const enum ConfirmationState {
NA, CONFIRMED, CANCEL NA, CONFIRMED, CANCEL
} }
export const ProjectTypes = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' };
export const RoleInfo = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' };

View File

@ -1,25 +1,41 @@
<div class="card card-block"> <div class="row flex-items-xs-between flex-items-xs-middle">
<h3 class="card-title">{{'STATISTICS.TITLE' | translate }}</h3> <div></div>
<span class="card-text"> <div id="right_statistic_panel" style="margin-right: 18px;">
<div class="row"> <div class="statistic-block">
<div class="col-xs-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"> <div class="statistic-column-block">
<span class="statistic-column-title">{{'STATISTICS.PRO_ITEM' | translate }}</span> <div>
</div> <span class="statistic-column-title statistic-column-title-pro">{{'STATISTICS.PRO_ITEM' | translate }}</span>
<div class="col-xs-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"> </div>
<statistics [data]='{number: originalCopy.my_project_count, label: "my"}'></statistics> <div>
<statistics [data]='{number: originalCopy.public_project_count, label: "pub"}'></statistics> <span class="statistic-column-title statistic-column-title-repo">{{'STATISTICS.REPO_ITEM' | translate }}</span>
<statistics [data]='{number: originalCopy.total_project_count, label: "total"}' *ngIf="isValidSession"></statistics> </div>
</div> </div>
</div> <div class="statistic-column-block" style="margin-left: 16px;">
<div class="row"> <div>
<div class="col-xs-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"> <statistics [data]='originalCopy.my_project_count' [label]='"STATISTICS.INDEX_MY" | translate'></statistics>
<span class="statistic-column-title">{{'STATISTICS.REPO_ITEM' | translate }}</span> </div>
<div>
<statistics [data]='originalCopy.my_repo_count' [label]='"STATISTICS.INDEX_MY" | translate'></statistics>
</div>
</div>
<div class="statistic-column-block" style="margin-left: 28px;">
<div>
<statistics [data]='originalCopy.public_project_count' [label]='"STATISTICS.INDEX_PUB" | translate'></statistics>
</div>
<div>
<statistics [data]='originalCopy.public_repo_count' [label]='"STATISTICS.INDEX_PUB" | translate'></statistics>
</div>
</div>
<div class="statistic-column-block" style="margin-left: 28px;">
<div>
<statistics [data]='originalCopy.total_project_count' [label]='"STATISTICS.INDEX_TOTAL" | translate' *ngIf="isValidSession"></statistics>
</div>
<div>
<statistics [data]='originalCopy.total_repo_count' [label]='"STATISTICS.INDEX_TOTAL" | translate' *ngIf="isValidSession"></statistics>
</div>
</div>
</div>
<div class="statistic-item-divider"></div>
<div class="statistic-block">Storage</div>
</div> </div>
<div class="col-xs-10 col-sm-10 col-md-10 col-lg-10 col-xl-10">
<statistics [data]='{number: originalCopy.my_repo_count, label: "my"}'></statistics>
<statistics [data]='{number: originalCopy.public_repo_count, label: "pub"}'></statistics>
<statistics [data]='{number: originalCopy.total_repo_count, label: "total"}' *ngIf="isValidSession"></statistics>
</div>
</div>
</span>
</div> </div>

View File

@ -1,30 +1,57 @@
.statistic-wrapper { .statistic-wrapper {
padding: 12px; padding: 4px;
margin: 12px; margin: 4px;
text-align: center; text-align: right;
vertical-align: middle; vertical-align: middle;
height: 72px; height: 30px;
min-width: 108px;
max-width: 216px;
display: inline-block; display: inline-block;
} }
.statistic-data { .statistic-data {
font-size: 48px; font-size: 16px;
font-weight: bolder; font-weight: 900;
font-family: "Metropolis"; font-family: "semibold";
line-height: 48px; line-height: 16px;
} }
.statistic-text { .statistic-text {
font-size: 24px; font-size: 10px;
font-weight: 400; line-height: 10px;
line-height: 24px;
text-transform: uppercase; text-transform: uppercase;
font-family: "Metropolis"; font-family: "semibold";
}
.statistic-column-block {
display: inline-block;
text-align: right;
} }
.statistic-column-title { .statistic-column-title {
position: relative; position: relative;
top: 40%; text-transform: uppercase;
font-size: 14px;
}
.statistic-column-title-pro {
top: -10px;
}
.statistic-column-title-repo {
top: 3px;
}
.statistic-item-divider {
height: 54px;
display: inline-block;
width: 1px;
background-color: #ccc;
opacity: 0.55;
margin-left: 4px;
margin-right: 12px;
position: relative;
top: 3px;
}
.statistic-block {
display: inline-block;
} }

View File

@ -1,4 +1,4 @@
<div class="statistic-wrapper"> <div class="statistic-wrapper">
<span class="statistic-data">{{data.number}}</span> <span class="statistic-data">{{data}}</span>
<span class="statistic-text">{{data.label}}</span> <span class="statistic-text">{{label}}</span>
</div> </div>

View File

@ -7,5 +7,6 @@ import { Component, Input } from '@angular/core';
}) })
export class StatisticsComponent { export class StatisticsComponent {
@Input() data: any; @Input() label: string;
@Input() data: number = 0;
} }

View File

@ -110,10 +110,10 @@
"PROJECT": { "PROJECT": {
"PROJECTS": "Projects", "PROJECTS": "Projects",
"NAME": "Project Name", "NAME": "Project Name",
"ROLE": "Role",
"PUBLIC_OR_PRIVATE": "Public", "PUBLIC_OR_PRIVATE": "Public",
"REPO_COUNT": "Repositories Count", "REPO_COUNT": "Repositories Count",
"CREATION_TIME": "Creation Time", "CREATION_TIME": "Creation Time",
"DESCRIPTION": "Description",
"PUBLIC": "Public", "PUBLIC": "Public",
"PRIVATE": "Private", "PRIVATE": "Private",
"MAKE": "Make", "MAKE": "Make",
@ -290,7 +290,7 @@
"DELETION_SUMMARY_TAG": "Do you want to delete tag {{param}}?", "DELETION_SUMMARY_TAG": "Do you want to delete tag {{param}}?",
"DELETION_TITLE_TAG_DENIED": "Signed Tag can't be deleted", "DELETION_TITLE_TAG_DENIED": "Signed Tag can't be deleted",
"DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted. {{param}}", "DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted. {{param}}",
"FILTER_FOR_REPOSITORIES": "Filter for repositories", "FILTER_FOR_REPOSITORIES": "Filter Repositories",
"TAG": "Tag", "TAG": "Tag",
"SIGNED": "Signed", "SIGNED": "Signed",
"AUTHOR": "Author", "AUTHOR": "Author",

View File

@ -110,10 +110,10 @@
"PROJECT": { "PROJECT": {
"PROJECTS": "项目", "PROJECTS": "项目",
"NAME": "项目名称", "NAME": "项目名称",
"ROLE": "角色",
"PUBLIC_OR_PRIVATE": "公开", "PUBLIC_OR_PRIVATE": "公开",
"REPO_COUNT": "镜像仓库数", "REPO_COUNT": "镜像仓库数",
"CREATION_TIME": "创建时间", "CREATION_TIME": "创建时间",
"DESCRIPTION": "描述",
"PUBLIC": "公开", "PUBLIC": "公开",
"PRIVATE": "私有", "PRIVATE": "私有",
"MAKE": "设为", "MAKE": "设为",
@ -395,8 +395,8 @@
"TITLE": "统计", "TITLE": "统计",
"PRO_ITEM": "项目", "PRO_ITEM": "项目",
"REPO_ITEM": "镜像库", "REPO_ITEM": "镜像库",
"INDEX_MY": "私有", "INDEX_MY": "私有",
"INDEX_PUB": "公开", "INDEX_PUB": "公开",
"INDEX_TOTAL": "总计" "INDEX_TOTAL": "总计"
}, },
"SEARCH": { "SEARCH": {

View File

@ -1,49 +0,0 @@
// Fetch prints the content found at a URL.
package main
import (
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
)
func main() {
time.Sleep(60 * time.Second)
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
var client = &http.Client{
Timeout: time.Second * 30,
Transport: tr,
}
for _, url := range os.Args[1:] {
resp, err := client.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
os.Exit(1)
}
// fmt.Printf("%s", b)
if strings.Contains(string(b), "Harbor") {
fmt.Printf("sucess!\n")
} else {
fmt.Println("the response does not contain \"Harbor\"!")
fmt.Println(string(b))
os.Exit(1)
}
}
}

28
tests/startuptest.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/sh
set +e
TIMEOUT=12
while [ $TIMEOUT -gt 0 ]; do
STATUS=$(curl --insecure -s -o /dev/null -w '%{http_code}' https://localhost/)
if [ $STATUS -eq 200 ]; then
break
fi
TIMEOUT=$(($TIMEOUT - 1))
sleep 5
done
if [ $TIMEOUT -eq 0 ]; then
echo "Harbor cannot reach within one minute."
exit 1
fi
curl --insecure -s -L -H "Accept: application/json" https://localhost/ | grep "Harbor" > /dev/null
if [ $? -eq 0 ]; then
echo "Harbor is running success."
else
echo "Harbor is running fail."
exit 1
fi