mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 02:35:17 +01:00
Merge remote-tracking branch 'upstream/dev' into dev-revised
This commit is contained in:
commit
e80840c2d3
50
Makefile
50
Makefile
@ -78,7 +78,7 @@ COMPILETAG=compile_normal
|
|||||||
REGISTRYSERVER=
|
REGISTRYSERVER=
|
||||||
REGISTRYPROJECTNAME=vmware
|
REGISTRYPROJECTNAME=vmware
|
||||||
DEVFLAG=true
|
DEVFLAG=true
|
||||||
NORTARYFLAG=false
|
NOTARYFLAG=false
|
||||||
|
|
||||||
#clarity parameters
|
#clarity parameters
|
||||||
CLARITYIMAGE=danieljt/harbor-clarity-base[:tag]
|
CLARITYIMAGE=danieljt/harbor-clarity-base[:tag]
|
||||||
@ -209,7 +209,7 @@ compile_clarity:
|
|||||||
@$(DOCKERCMD) run --rm -v $(UIPATH)/static/new-ui:$(CLARITYSEEDPATH)/dist -v $(UINGPATH)/src:$(CLARITYSEEDPATH)/src -v $(UINGPATH)/src/app:$(CLARITYSEEDPATH)/src/app $(CLARITYIMAGE) $(SHELL) $(CLARITYBUILDSCRIPT)
|
@$(DOCKERCMD) run --rm -v $(UIPATH)/static/new-ui:$(CLARITYSEEDPATH)/dist -v $(UINGPATH)/src:$(CLARITYSEEDPATH)/src -v $(UINGPATH)/src/app:$(CLARITYSEEDPATH)/src/app $(CLARITYIMAGE) $(SHELL) $(CLARITYBUILDSCRIPT)
|
||||||
@echo "Done."
|
@echo "Done."
|
||||||
|
|
||||||
compile_normal: compile_clarity, compile_adminserver compile_ui compile_jobservice
|
compile_normal: compile_clarity compile_adminserver compile_ui compile_jobservice
|
||||||
|
|
||||||
compile_golangimage: compile_clarity
|
compile_golangimage: compile_clarity
|
||||||
@echo "compiling binary for adminserver (golang image)..."
|
@echo "compiling binary for adminserver (golang image)..."
|
||||||
@ -264,18 +264,24 @@ package_online: modify_composefile
|
|||||||
fi
|
fi
|
||||||
@cp LICENSE $(HARBORPKG)/LICENSE
|
@cp LICENSE $(HARBORPKG)/LICENSE
|
||||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||||
@$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
|
||||||
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/common/config\
|
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||||
--exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \
|
$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
||||||
--exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \
|
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
||||||
--exclude=$(HARBORPKG)/checkenv.sh \
|
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
||||||
--exclude=$(HARBORPKG)/jsminify.sh \
|
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||||
--exclude=$(HARBORPKG)/pushimage.sh \
|
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME); \
|
||||||
$(HARBORPKG)
|
else \
|
||||||
|
$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
||||||
|
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
||||||
|
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
||||||
|
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||||
|
$(HARBORPKG)/harbor.cfg ; \
|
||||||
|
fi
|
||||||
|
|
||||||
@rm -rf $(HARBORPKG)
|
@rm -rf $(HARBORPKG)
|
||||||
@echo "Done."
|
@echo "Done."
|
||||||
|
|
||||||
package_offline: compile build modify_composefile
|
package_offline: compile build modify_composefile
|
||||||
@echo "packing offline package ..."
|
@echo "packing offline package ..."
|
||||||
@cp -r make $(HARBORPKG)
|
@cp -r make $(HARBORPKG)
|
||||||
@ -313,6 +319,21 @@ package_offline: compile build modify_composefile
|
|||||||
nginx:1.11.5 registry:2.5.1 photon:1.0 ; \
|
nginx:1.11.5 registry:2.5.1 photon:1.0 ; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||||
|
$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
||||||
|
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||||
|
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
||||||
|
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
||||||
|
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) ; \
|
||||||
|
else \
|
||||||
|
$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
||||||
|
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||||
|
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
||||||
|
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
||||||
|
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||||
|
$(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME) ; \
|
||||||
|
fi
|
||||||
|
|
||||||
@rm -rf $(HARBORPKG)
|
@rm -rf $(HARBORPKG)
|
||||||
@echo "Done."
|
@echo "Done."
|
||||||
|
|
||||||
@ -353,6 +374,11 @@ start:
|
|||||||
@echo "Start complete. You can visit harbor now."
|
@echo "Start complete. You can visit harbor now."
|
||||||
|
|
||||||
down:
|
down:
|
||||||
|
@echo "Please make sure to set -e NOTARYFLAG=true if you are using Notary in Harbor, otherwise the Notary containers cannot be stop automaticlly."
|
||||||
|
@while [ -z "$$CONTINUE" ]; do \
|
||||||
|
read -r -p "Type anything but Y or y to exit. [Y/N]: " CONTINUE; \
|
||||||
|
done ; \
|
||||||
|
[ $$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 ; \
|
||||||
|
@ -827,15 +827,20 @@ paths:
|
|||||||
format: int32
|
format: int32
|
||||||
required: false
|
required: false
|
||||||
description: The number of the requested public repositories, default is 10 if not provided.
|
description: The number of the requested public repositories, default is 10 if not provided.
|
||||||
|
- name: detail
|
||||||
|
in: query
|
||||||
|
type: boolean
|
||||||
|
required: false
|
||||||
|
description: Get detail info or not.
|
||||||
tags:
|
tags:
|
||||||
- Products
|
- Products
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: Retrieved top repositories successfully.
|
description: If detail is true, the response is described as the schema, or the response contains a TopRepo array.
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/TopRepo'
|
$ref: '#/definitions/Repository'
|
||||||
400:
|
400:
|
||||||
description: Bad request because of invalid count.
|
description: Bad request because of invalid count.
|
||||||
500:
|
500:
|
||||||
@ -1343,6 +1348,22 @@ paths:
|
|||||||
description: User does not have permission of admin role.
|
description: User does not have permission of admin role.
|
||||||
500:
|
500:
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
|
/systeminfo:
|
||||||
|
get:
|
||||||
|
summary: Get general system info
|
||||||
|
description: |
|
||||||
|
This API is for retrieving general system info, this can be called by anonymous request.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Get general info successfully.
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/GeneralInfo"
|
||||||
|
500:
|
||||||
|
description: Unexpected internal error.
|
||||||
/systeminfo/volumes:
|
/systeminfo/volumes:
|
||||||
get:
|
get:
|
||||||
summary: Get system volume info (total/free size).
|
summary: Get system volume info (total/free size).
|
||||||
@ -1737,7 +1758,7 @@ definitions:
|
|||||||
TopRepo:
|
TopRepo:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
repo_name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the repo
|
description: The name of the repo
|
||||||
count:
|
count:
|
||||||
@ -1967,6 +1988,27 @@ definitions:
|
|||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
description: Free volume size.
|
description: Free volume size.
|
||||||
|
GeneralInfo:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
with_notary:
|
||||||
|
type: boolean
|
||||||
|
description: If the Harbor instance is deployed with nested notary.
|
||||||
|
with_admiral:
|
||||||
|
type: boolean
|
||||||
|
description: If the Harbor instance is deployed with Admiral.
|
||||||
|
admiral_endpoint:
|
||||||
|
type: string
|
||||||
|
description: The url of the endpoint of admiral instance.
|
||||||
|
auth_mode:
|
||||||
|
type: string
|
||||||
|
description: The auth mode of current Harbor instance.
|
||||||
|
project_creation_restriction:
|
||||||
|
type: string
|
||||||
|
description: Indicate who can create projects, it could be 'adminonly' or 'everyone'.
|
||||||
|
self_registration:
|
||||||
|
type: boolean
|
||||||
|
description: Indicate whether the Harbor instance enable user to register himself.
|
||||||
SystemInfo:
|
SystemInfo:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -34,4 +34,6 @@ JOBSERVICE_SECRET=$jobservice_secret
|
|||||||
TOKEN_EXPIRATION=$token_expiration
|
TOKEN_EXPIRATION=$token_expiration
|
||||||
CFG_EXPIRATION=5
|
CFG_EXPIRATION=5
|
||||||
USE_COMPRESSED_JS=$use_compressed_js
|
USE_COMPRESSED_JS=$use_compressed_js
|
||||||
GODEBUG=netdns=cgo
|
GODEBUG=netdns=cgo
|
||||||
|
ADMIRAL_URL=$admiral_url
|
||||||
|
WITH_NOTARY=$with_notary
|
||||||
|
@ -40,6 +40,9 @@ ssl_cert_key = /data/cert/server.key
|
|||||||
#The path of secretkey storage
|
#The path of secretkey storage
|
||||||
secretkey_path = /data
|
secretkey_path = /data
|
||||||
|
|
||||||
|
#Admiral's url, comment this attribute, or set its value to to NA when Harbor is standalone
|
||||||
|
admiral_url = NA
|
||||||
|
|
||||||
#NOTES: The properties between BEGIN INITIAL PROPERTIES and END INITIAL PROPERTIES
|
#NOTES: The properties between BEGIN INITIAL PROPERTIES and END INITIAL PROPERTIES
|
||||||
#only take effect in the first boot, the subsequent changes of these properties
|
#only take effect in the first boot, the subsequent changes of these properties
|
||||||
#should be performed on web ui
|
#should be performed on web ui
|
||||||
|
@ -49,14 +49,20 @@ note() { printf "\n${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\n"
|
|||||||
set -e
|
set -e
|
||||||
set +o noglob
|
set +o noglob
|
||||||
|
|
||||||
usage=$'Please set hostname and other necessary attributes in harbor.cfg first. DO NOT use localhost or 127.0.0.1 for hostname, because Harbor needs to be accessed by external clients.'
|
usage=$'Please set hostname and other necessary attributes in harbor.cfg first. DO NOT use localhost or 127.0.0.1 for hostname, because Harbor needs to be accessed by external clients.
|
||||||
|
Please set --with-notary if needs enable Notary in Harbor, and set ui_url_protocol/ssl_cert/ssl_cert_key in harbor.cfg bacause notary must run under https.'
|
||||||
item=0
|
item=0
|
||||||
|
|
||||||
|
# notary is not enabled by default
|
||||||
|
with_notary=$false
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
--help)
|
--help)
|
||||||
note "$usage"
|
note "$usage"
|
||||||
exit 0;;
|
exit 0;;
|
||||||
|
--with-notary)
|
||||||
|
with_notary=true;;
|
||||||
*)
|
*)
|
||||||
note "$usage"
|
note "$usage"
|
||||||
exit 1;;
|
exit 1;;
|
||||||
@ -146,19 +152,38 @@ if [ -n "$host" ]
|
|||||||
then
|
then
|
||||||
sed "s/^hostname = .*/hostname = $host/g" -i ./harbor.cfg
|
sed "s/^hostname = .*/hostname = $host/g" -i ./harbor.cfg
|
||||||
fi
|
fi
|
||||||
./prepare
|
if [ $with_notary ]
|
||||||
|
then
|
||||||
|
./prepare --with-notary
|
||||||
|
else
|
||||||
|
./prepare
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
h2 "[Step $item]: checking existing instance of Harbor ..."; let item+=1
|
h2 "[Step $item]: checking existing instance of Harbor ..."; let item+=1
|
||||||
if [ -n "$(docker-compose -f docker-compose*.yml ps -q)" ]
|
if [ $with_notary ]
|
||||||
then
|
then
|
||||||
note "stopping existing Harbor instance ..."
|
if [ -n "$(docker-compose -f docker-compose.yml -f docker-compose.notary.yml ps -q)" ]
|
||||||
docker-compose -f docker-compose*.yml down
|
then
|
||||||
|
note "stopping existing Harbor instance ..."
|
||||||
|
docker-compose -f docker-compose.yml -f docker-compose.notary.yml down
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ -n "$(docker-compose -f docker-compose.yml ps -q)" ]
|
||||||
|
then
|
||||||
|
note "stopping existing Harbor instance ..."
|
||||||
|
docker-compose -f docker-compose.yml down
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
h2 "[Step $item]: starting Harbor ..."
|
h2 "[Step $item]: starting Harbor ..."
|
||||||
docker-compose -f docker-compose*.yml up -d
|
if [ $with_notary ]
|
||||||
|
then
|
||||||
|
docker-compose -f docker-compose.yml -f docker-compose.notary.yml up -d
|
||||||
|
else
|
||||||
|
docker-compose -f docker-compose.yml up -d
|
||||||
|
fi
|
||||||
|
|
||||||
protocol=http
|
protocol=http
|
||||||
hostname=reg.mydomain.com
|
hostname=reg.mydomain.com
|
||||||
|
50
make/prepare
50
make/prepare
@ -147,6 +147,7 @@ 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")
|
||||||
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")
|
||||||
|
admiral_url = rcp.get("configuration", "admiral_url")
|
||||||
secret_key = get_secret_key(secretkey_path)
|
secret_key = get_secret_key(secretkey_path)
|
||||||
########
|
########
|
||||||
|
|
||||||
@ -190,10 +191,10 @@ else:
|
|||||||
nginx_conf)
|
nginx_conf)
|
||||||
|
|
||||||
render(os.path.join(templates_dir, "adminserver", "env"),
|
render(os.path.join(templates_dir, "adminserver", "env"),
|
||||||
adminserver_conf_env,
|
adminserver_conf_env,
|
||||||
ui_url=ui_url,
|
ui_url=ui_url,
|
||||||
auth_mode=auth_mode,
|
auth_mode=auth_mode,
|
||||||
self_registration=self_registration,
|
self_registration=self_registration,
|
||||||
ldap_url=ldap_url,
|
ldap_url=ldap_url,
|
||||||
ldap_searchdn =ldap_searchdn,
|
ldap_searchdn =ldap_searchdn,
|
||||||
ldap_search_pwd =ldap_search_pwd,
|
ldap_search_pwd =ldap_search_pwd,
|
||||||
@ -203,27 +204,29 @@ render(os.path.join(templates_dir, "adminserver", "env"),
|
|||||||
ldap_scope=ldap_scope,
|
ldap_scope=ldap_scope,
|
||||||
ldap_timeout=ldap_timeout,
|
ldap_timeout=ldap_timeout,
|
||||||
db_password=db_password,
|
db_password=db_password,
|
||||||
email_host=email_host,
|
email_host=email_host,
|
||||||
email_port=email_port,
|
email_port=email_port,
|
||||||
email_usr=email_usr,
|
email_usr=email_usr,
|
||||||
email_pwd=email_pwd,
|
email_pwd=email_pwd,
|
||||||
email_ssl=email_ssl,
|
email_ssl=email_ssl,
|
||||||
email_from=email_from,
|
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=proj_cre_restriction,
|
project_creation_restriction=proj_cre_restriction,
|
||||||
verify_remote_cert=verify_remote_cert,
|
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,
|
||||||
token_expiration=token_expiration,
|
token_expiration=token_expiration,
|
||||||
use_compressed_js=use_compressed_js
|
admiral_url=admiral_url,
|
||||||
|
with_notary=args.notary_mode,
|
||||||
|
use_compressed_js=use_compressed_js
|
||||||
)
|
)
|
||||||
|
|
||||||
render(os.path.join(templates_dir, "ui", "env"),
|
render(os.path.join(templates_dir, "ui", "env"),
|
||||||
ui_conf_env,
|
ui_conf_env,
|
||||||
ui_secret=ui_secret,
|
ui_secret=ui_secret,
|
||||||
jobservice_secret=jobservice_secret,)
|
jobservice_secret=jobservice_secret,)
|
||||||
|
|
||||||
render(os.path.join(templates_dir, "registry",
|
render(os.path.join(templates_dir, "registry",
|
||||||
"config.yml"),
|
"config.yml"),
|
||||||
@ -237,8 +240,8 @@ render(os.path.join(templates_dir, "db", "env"),
|
|||||||
render(os.path.join(templates_dir, "jobservice", "env"),
|
render(os.path.join(templates_dir, "jobservice", "env"),
|
||||||
job_conf_env,
|
job_conf_env,
|
||||||
ui_secret=ui_secret,
|
ui_secret=ui_secret,
|
||||||
jobservice_secret=jobservice_secret)
|
jobservice_secret=jobservice_secret)
|
||||||
|
|
||||||
print("Generated configuration file: %s" % jobservice_conf)
|
print("Generated configuration file: %s" % jobservice_conf)
|
||||||
shutil.copyfile(os.path.join(templates_dir, "jobservice", "app.conf"), jobservice_conf)
|
shutil.copyfile(os.path.join(templates_dir, "jobservice", "app.conf"), jobservice_conf)
|
||||||
|
|
||||||
@ -328,5 +331,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)
|
||||||
|
|
||||||
|
|
||||||
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.")
|
||||||
|
|
||||||
|
@ -114,6 +114,11 @@ var (
|
|||||||
},
|
},
|
||||||
comcfg.ProjectCreationRestriction: "PROJECT_CREATION_RESTRICTION",
|
comcfg.ProjectCreationRestriction: "PROJECT_CREATION_RESTRICTION",
|
||||||
comcfg.AdminInitialPassword: "HARBOR_ADMIN_PASSWORD",
|
comcfg.AdminInitialPassword: "HARBOR_ADMIN_PASSWORD",
|
||||||
|
comcfg.AdmiralEndpoint: "ADMIRAL_URL",
|
||||||
|
comcfg.WithNotary: &parser{
|
||||||
|
env: "WITH_NOTARY",
|
||||||
|
parse: parseStringToBool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// configurations need read from environment variables
|
// configurations need read from environment variables
|
||||||
@ -134,6 +139,11 @@ var (
|
|||||||
env: "CFG_EXPIRATION",
|
env: "CFG_EXPIRATION",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
},
|
},
|
||||||
|
comcfg.AdmiralEndpoint: "ADMIRAL_URL",
|
||||||
|
comcfg.WithNotary: &parser{
|
||||||
|
env: "WITH_NOTARY",
|
||||||
|
parse: parseStringToBool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -75,6 +75,8 @@ const (
|
|||||||
JobLogDir = "job_log_dir"
|
JobLogDir = "job_log_dir"
|
||||||
UseCompressedJS = "use_compressed_js"
|
UseCompressedJS = "use_compressed_js"
|
||||||
AdminInitialPassword = "admin_initial_password"
|
AdminInitialPassword = "admin_initial_password"
|
||||||
|
AdmiralEndpoint = "admiral_url"
|
||||||
|
WithNotary = "with_notary"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manager manages configurations
|
// Manager manages configurations
|
||||||
|
@ -103,10 +103,12 @@ func GetRepositoryByProjectName(name string) ([]*models.RepoRecord, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//GetTopRepos returns the most popular repositories
|
//GetTopRepos returns the most popular repositories
|
||||||
func GetTopRepos(userID int, count int) ([]models.TopRepo, error) {
|
func GetTopRepos(userID int, count int) ([]*models.RepoRecord, error) {
|
||||||
topRepos := []models.TopRepo{}
|
sql :=
|
||||||
|
`select r.repository_id, r.name, r.owner_id,
|
||||||
sql := `select r.name, r.pull_count from repository r
|
r.project_id, r.description, r.pull_count,
|
||||||
|
r.star_count, r.creation_time, r.update_time
|
||||||
|
from repository r
|
||||||
inner join project p on r.project_id = p.project_id
|
inner join project p on r.project_id = p.project_id
|
||||||
where (
|
where (
|
||||||
p.deleted = 0 and (
|
p.deleted = 0 and (
|
||||||
@ -122,18 +124,8 @@ func GetTopRepos(userID int, count int) ([]models.TopRepo, error) {
|
|||||||
order by r.pull_count desc, r.name limit ?`
|
order by r.pull_count desc, r.name limit ?`
|
||||||
repositories := []*models.RepoRecord{}
|
repositories := []*models.RepoRecord{}
|
||||||
_, err := GetOrmer().Raw(sql, userID, NonExistUserID, userID, userID, count).QueryRows(&repositories)
|
_, err := GetOrmer().Raw(sql, userID, NonExistUserID, userID, userID, count).QueryRows(&repositories)
|
||||||
if err != nil {
|
|
||||||
return topRepos, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, repository := range repositories {
|
return repositories, err
|
||||||
topRepos = append(topRepos, models.TopRepo{
|
|
||||||
RepoName: repository.Name,
|
|
||||||
AccessCount: repository.PullCount,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return topRepos, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTotalOfRepositories ...
|
// GetTotalOfRepositories ...
|
||||||
|
@ -269,75 +269,28 @@ func TestGetTopRepos(t *testing.T) {
|
|||||||
err = DeleteProject(deletedPublicProject.ProjectID)
|
err = DeleteProject(deletedPublicProject.ProjectID)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
var topRepos []models.TopRepo
|
var topRepos []*models.RepoRecord
|
||||||
|
|
||||||
// anonymous should retrieve public non-deleted repositories
|
// anonymous should retrieve public non-deleted repositories
|
||||||
topRepos, err = GetTopRepos(NonExistUserID, 100)
|
topRepos, err = GetTopRepos(NonExistUserID, 100)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.Len(topRepos, 1)
|
require.Len(topRepos, 1)
|
||||||
require.Equal(topRepos, []models.TopRepo{
|
require.Equal(topRepos[0].Name, repository.Name)
|
||||||
models.TopRepo{
|
|
||||||
RepoName: repository.Name,
|
|
||||||
AccessCount: repository.PullCount,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// admin should retrieve all repositories
|
// admin should retrieve all repositories
|
||||||
topRepos, err = GetTopRepos(admin.UserID, 100)
|
topRepos, err = GetTopRepos(admin.UserID, 100)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.Len(topRepos, 4)
|
require.Len(topRepos, 4)
|
||||||
require.Equal(topRepos, []models.TopRepo{
|
|
||||||
models.TopRepo{
|
|
||||||
RepoName: repository3.Name,
|
|
||||||
AccessCount: repository3.PullCount,
|
|
||||||
},
|
|
||||||
models.TopRepo{
|
|
||||||
RepoName: repository2.Name,
|
|
||||||
AccessCount: repository2.PullCount,
|
|
||||||
},
|
|
||||||
models.TopRepo{
|
|
||||||
RepoName: repository1.Name,
|
|
||||||
AccessCount: repository1.PullCount,
|
|
||||||
},
|
|
||||||
models.TopRepo{
|
|
||||||
RepoName: repository.Name,
|
|
||||||
AccessCount: repository.PullCount,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// user should retrieve visible repositories
|
// user should retrieve visible repositories
|
||||||
topRepos, err = GetTopRepos(user.UserID, 100)
|
topRepos, err = GetTopRepos(user.UserID, 100)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.Len(topRepos, 2)
|
require.Len(topRepos, 2)
|
||||||
require.Equal(topRepos, []models.TopRepo{
|
|
||||||
models.TopRepo{
|
|
||||||
RepoName: repository3.Name,
|
|
||||||
AccessCount: repository3.PullCount,
|
|
||||||
},
|
|
||||||
models.TopRepo{
|
|
||||||
RepoName: repository.Name,
|
|
||||||
AccessCount: repository.PullCount,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// limit by count
|
// limit by count
|
||||||
topRepos, err = GetTopRepos(admin.UserID, 3)
|
topRepos, err = GetTopRepos(admin.UserID, 3)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.Len(topRepos, 3)
|
require.Len(topRepos, 3)
|
||||||
require.Equal(topRepos, []models.TopRepo{
|
|
||||||
models.TopRepo{
|
|
||||||
RepoName: repository3.Name,
|
|
||||||
AccessCount: repository3.PullCount,
|
|
||||||
},
|
|
||||||
models.TopRepo{
|
|
||||||
RepoName: repository2.Name,
|
|
||||||
AccessCount: repository2.PullCount,
|
|
||||||
},
|
|
||||||
models.TopRepo{
|
|
||||||
RepoName: repository1.Name,
|
|
||||||
AccessCount: repository1.PullCount,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetTotalOfRepositoriesByProject(t *testing.T) {
|
func TestGetTotalOfRepositoriesByProject(t *testing.T) {
|
||||||
|
@ -28,7 +28,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
notaryEndpoint = "http://notary-server:4443"
|
|
||||||
notaryCachePath = "/root/notary"
|
notaryCachePath = "/root/notary"
|
||||||
trustPin trustpinning.TrustPinConfig
|
trustPin trustpinning.TrustPinConfig
|
||||||
mockRetriever notary.PassRetriever
|
mockRetriever notary.PassRetriever
|
||||||
@ -55,7 +54,7 @@ func init() {
|
|||||||
// GetTargets is a help function called by API to fetch signature information of a given repository.
|
// GetTargets is a help function called by API to fetch signature information of a given repository.
|
||||||
// Per docker's convention the repository should contain the information of endpoint, i.e. it should look
|
// Per docker's convention the repository should contain the information of endpoint, i.e. it should look
|
||||||
// like "10.117.4.117/library/ubuntu", instead of "library/ubuntu" (fqRepo for fully-qualified repo)
|
// like "10.117.4.117/library/ubuntu", instead of "library/ubuntu" (fqRepo for fully-qualified repo)
|
||||||
func GetTargets(username string, fqRepo string) ([]Target, error) {
|
func GetTargets(notaryEndpoint string, username string, fqRepo string) ([]Target, error) {
|
||||||
res := []Target{}
|
res := []Target{}
|
||||||
authorizer := auth.NewNotaryUsernameTokenAuthorizer(username, "repository", fqRepo, "pull")
|
authorizer := auth.NewNotaryUsernameTokenAuthorizer(username, "repository", fqRepo, "pull")
|
||||||
store, err := auth.NewAuthorizerStore(strings.Split(notaryEndpoint, "//")[1], true, authorizer)
|
store, err := auth.NewAuthorizerStore(strings.Split(notaryEndpoint, "//")[1], true, authorizer)
|
||||||
|
@ -5,17 +5,18 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
notarytest "github.com/vmware/harbor/src/common/utils/notary/test"
|
notarytest "github.com/vmware/harbor/src/common/utils/notary/test"
|
||||||
|
|
||||||
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var endpoint = "10.117.4.142"
|
var endpoint = "10.117.4.142"
|
||||||
|
var notaryServer *httptest.Server
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
notaryServer := notarytest.NewNotaryServer(endpoint)
|
notaryServer = notarytest.NewNotaryServer(endpoint)
|
||||||
defer notaryServer.Close()
|
defer notaryServer.Close()
|
||||||
notaryEndpoint = notaryServer.URL
|
|
||||||
notaryCachePath = "/tmp/notary"
|
notaryCachePath = "/tmp/notary"
|
||||||
result := m.Run()
|
result := m.Run()
|
||||||
if result != 0 {
|
if result != 0 {
|
||||||
@ -24,12 +25,12 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetTargets(t *testing.T) {
|
func TestGetTargets(t *testing.T) {
|
||||||
targets, err := GetTargets("admin", path.Join(endpoint, "notary-demo/busybox"))
|
targets, err := GetTargets(notaryServer.URL, "admin", path.Join(endpoint, "notary-demo/busybox"))
|
||||||
assert.Nil(t, err, fmt.Sprintf("Unexpected error: %v", err))
|
assert.Nil(t, err, fmt.Sprintf("Unexpected error: %v", err))
|
||||||
assert.Equal(t, 1, len(targets), "")
|
assert.Equal(t, 1, len(targets), "")
|
||||||
assert.Equal(t, "1.0", targets[0].Tag, "")
|
assert.Equal(t, "1.0", targets[0].Tag, "")
|
||||||
|
|
||||||
targets, err = GetTargets("admin", path.Join(endpoint, "notary-demo/notexist"))
|
targets, err = GetTargets(notaryServer.URL, "admin", path.Join(endpoint, "notary-demo/notexist"))
|
||||||
assert.Nil(t, err, fmt.Sprintf("Unexpected error: %v", err))
|
assert.Nil(t, err, fmt.Sprintf("Unexpected error: %v", err))
|
||||||
assert.Equal(t, 0, len(targets), "Targets list should be empty for non exist repo.")
|
assert.Equal(t, 0, len(targets), "Targets list should be empty for non exist repo.")
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,8 @@ var adminServerDefaultConfig = map[string]interface{}{
|
|||||||
config.CfgExpiration: 5,
|
config.CfgExpiration: 5,
|
||||||
config.UseCompressedJS: true,
|
config.UseCompressedJS: true,
|
||||||
config.AdminInitialPassword: "password",
|
config.AdminInitialPassword: "password",
|
||||||
|
config.AdmiralEndpoint: "http://www.vmware.com",
|
||||||
|
config.WithNotary: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdminserver returns a mock admin server
|
// NewAdminserver returns a mock admin server
|
||||||
|
@ -97,6 +97,7 @@ func init() {
|
|||||||
beego.Router("/api/policies/replication", &RepPolicyAPI{}, "get:List")
|
beego.Router("/api/policies/replication", &RepPolicyAPI{}, "get:List")
|
||||||
beego.Router("/api/policies/replication", &RepPolicyAPI{}, "post:Post;delete:Delete")
|
beego.Router("/api/policies/replication", &RepPolicyAPI{}, "post:Post;delete:Delete")
|
||||||
beego.Router("/api/policies/replication/:id([0-9]+)/enablement", &RepPolicyAPI{}, "put:UpdateEnablement")
|
beego.Router("/api/policies/replication/:id([0-9]+)/enablement", &RepPolicyAPI{}, "put:UpdateEnablement")
|
||||||
|
beego.Router("/api/systeminfo", &SystemInfoAPI{}, "get:GetGeneralInfo")
|
||||||
beego.Router("/api/systeminfo/volumes", &SystemInfoAPI{}, "get:GetVolumeInfo")
|
beego.Router("/api/systeminfo/volumes", &SystemInfoAPI{}, "get:GetVolumeInfo")
|
||||||
beego.Router("/api/systeminfo/getcert", &SystemInfoAPI{}, "get:GetCert")
|
beego.Router("/api/systeminfo/getcert", &SystemInfoAPI{}, "get:GetCert")
|
||||||
beego.Router("/api/ldap/ping", &LdapAPI{}, "post:Ping")
|
beego.Router("/api/ldap/ping", &LdapAPI{}, "post:Ping")
|
||||||
@ -558,7 +559,8 @@ func (a testapi) GetReposManifests(authInfo usrInfo, repoName string, tag string
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Get public repositories which are accessed most
|
//Get public repositories which are accessed most
|
||||||
func (a testapi) GetReposTop(authInfo usrInfo, count string) (int, error) {
|
func (a testapi) GetReposTop(authInfo usrInfo, count,
|
||||||
|
detail string) (int, interface{}, error) {
|
||||||
_sling := sling.New().Get(a.basePath)
|
_sling := sling.New().Get(a.basePath)
|
||||||
|
|
||||||
path := "/api/repositories/top"
|
path := "/api/repositories/top"
|
||||||
@ -566,12 +568,36 @@ func (a testapi) GetReposTop(authInfo usrInfo, count string) (int, error) {
|
|||||||
_sling = _sling.Path(path)
|
_sling = _sling.Path(path)
|
||||||
|
|
||||||
type QueryParams struct {
|
type QueryParams struct {
|
||||||
Count string `url:"count"`
|
Count string `url:"count"`
|
||||||
|
Detail string `url:"detail"`
|
||||||
}
|
}
|
||||||
|
|
||||||
_sling = _sling.QueryStruct(&QueryParams{Count: count})
|
_sling = _sling.QueryStruct(&QueryParams{
|
||||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
Count: count,
|
||||||
return httpStatusCode, err
|
Detail: detail,
|
||||||
|
})
|
||||||
|
code, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if code != http.StatusOK {
|
||||||
|
return code, body, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if detail == "true" || detail == "1" {
|
||||||
|
result := []*repoResp{}
|
||||||
|
if err = json.Unmarshal(body, &result); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
return http.StatusOK, result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := []*models.TopRepo{}
|
||||||
|
if err = json.Unmarshal(body, &result); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
return http.StatusOK, result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------Targets Test---------------------------------------//
|
//-------------------------Targets Test---------------------------------------//
|
||||||
@ -953,6 +979,11 @@ func (a testapi) VolumeInfoGet(authInfo usrInfo) (int, apilib.SystemInfo, error)
|
|||||||
return httpStatusCode, successPayLoad, err
|
return httpStatusCode, successPayLoad, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a testapi) GetGeneralInfo() (int, []byte, error) {
|
||||||
|
_sling := sling.New().Get(a.basePath).Path("/api/systeminfo")
|
||||||
|
return request(_sling, jsonAcceptHeader)
|
||||||
|
}
|
||||||
|
|
||||||
//Get system cert
|
//Get system cert
|
||||||
func (a testapi) CertGet(authInfo usrInfo) (int, []byte, error) {
|
func (a testapi) CertGet(authInfo usrInfo) (int, []byte, error) {
|
||||||
_sling := sling.New().Get(a.basePath)
|
_sling := sling.New().Get(a.basePath)
|
||||||
|
@ -139,6 +139,10 @@ func getRepositories(projectID int64, keyword string,
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return populateTagsCount(repositories)
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateTagsCount(repositories []*models.RepoRecord) ([]*repoResp, error) {
|
||||||
result := []*repoResp{}
|
result := []*repoResp{}
|
||||||
for _, repository := range repositories {
|
for _, repository := range repositories {
|
||||||
repo := &repoResp{
|
repo := &repoResp{
|
||||||
@ -160,7 +164,6 @@ func getRepositories(projectID int64, keyword string,
|
|||||||
repo.TagsCount = int64(len(tags))
|
repo.TagsCount = int64(len(tags))
|
||||||
result = append(result, repo)
|
result = append(result, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,7 +534,30 @@ func (ra *RepositoryAPI) GetTopRepos() {
|
|||||||
log.Errorf("failed to get top repos: %v", err)
|
log.Errorf("failed to get top repos: %v", err)
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
||||||
}
|
}
|
||||||
ra.Data["json"] = repos
|
|
||||||
|
detail := ra.GetString("detail") == "1" || ra.GetString("detail") == "true"
|
||||||
|
if !detail {
|
||||||
|
result := []*models.TopRepo{}
|
||||||
|
|
||||||
|
for _, repo := range repos {
|
||||||
|
result = append(result, &models.TopRepo{
|
||||||
|
RepoName: repo.Name,
|
||||||
|
AccessCount: repo.PullCount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ra.Data["json"] = result
|
||||||
|
ra.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := populateTagsCount(repos)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to popultate tags count to repositories: %v", err)
|
||||||
|
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
||||||
|
}
|
||||||
|
|
||||||
|
ra.Data["json"] = result
|
||||||
ra.ServeJSON()
|
ra.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -554,7 +580,7 @@ func (ra *RepositoryAPI) GetSignatures() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("Error when getting username: %v", err)
|
log.Warningf("Error when getting username: %v", err)
|
||||||
}
|
}
|
||||||
targets, err := notary.GetTargets(username, fqRepo)
|
targets, err := notary.GetTargets(config.InternalNotaryEndpoint(), username, fqRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error while fetching signature from notary: %v", err)
|
log.Errorf("Error while fetching signature from notary: %v", err)
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetRepos(t *testing.T) {
|
func TestGetRepos(t *testing.T) {
|
||||||
@ -193,9 +194,6 @@ func TestGetReposManifests(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetReposTop(t *testing.T) {
|
func TestGetReposTop(t *testing.T) {
|
||||||
var httpStatusCode int
|
|
||||||
var err error
|
|
||||||
var count string
|
|
||||||
|
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
apiTest := newHarborAPI()
|
apiTest := newHarborAPI()
|
||||||
@ -203,24 +201,46 @@ func TestGetReposTop(t *testing.T) {
|
|||||||
fmt.Println("Testing ReposTop Get API")
|
fmt.Println("Testing ReposTop Get API")
|
||||||
//-------------------case 1 : response code = 200------------------------//
|
//-------------------case 1 : response code = 200------------------------//
|
||||||
fmt.Println("case 1 : response code = 200")
|
fmt.Println("case 1 : response code = 200")
|
||||||
count = "1"
|
count := "1"
|
||||||
httpStatusCode, err = apiTest.GetReposTop(*admin, count)
|
detail := "false"
|
||||||
|
code, repos, err := apiTest.GetReposTop(*admin, count, detail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error whihle get reposTop to show the most popular public repositories ", err.Error())
|
t.Errorf("failed to get the most popular repositories: %v", err)
|
||||||
t.Log(err)
|
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
assert.Equal(int(200), code, "response code should be 200")
|
||||||
|
if r, ok := repos.([]*models.TopRepo); ok {
|
||||||
|
assert.Equal(int(1), len(r), "the length should be 1")
|
||||||
|
assert.Equal(r[0].RepoName, "library/docker", "the name of repository should be library/docker")
|
||||||
|
} else {
|
||||||
|
t.Error("the repositories should in simple style as the detail is false")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------case 2 : response code = 400------------------------//
|
//-------------------case 2 : response code = 400------------------------//
|
||||||
fmt.Println("case 2 : response code = 400,invalid count")
|
fmt.Println("case 2 : response code = 400,invalid count")
|
||||||
count = "cc"
|
count = "cc"
|
||||||
httpStatusCode, err = apiTest.GetReposTop(*admin, count)
|
code, _, err = apiTest.GetReposTop(*admin, count, detail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error whihle get reposTop to show the most popular public repositories ", err.Error())
|
t.Errorf("failed to get the most popular repositories: %v", err)
|
||||||
t.Log(err)
|
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
|
assert.Equal(int(400), code, "response code should be 400")
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------case 3 : response code = 200------------------------//
|
||||||
|
fmt.Println("case 3 : response code = 200")
|
||||||
|
count = "1"
|
||||||
|
detail = "true"
|
||||||
|
code, repos, err = apiTest.GetReposTop(*admin, count, detail)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get the most popular repositories: %v", err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(int(200), code, "response code should be 200")
|
||||||
|
if r, ok := repos.([]*repoResp); ok {
|
||||||
|
assert.Equal(int(1), len(r), "the length should be 1")
|
||||||
|
assert.Equal(r[0].Name, "library/docker", "the name of repository should be library/docker")
|
||||||
|
} else {
|
||||||
|
t.Error("the repositories should in detail style as the detail is true")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
|
@ -4,11 +4,14 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/api"
|
"github.com/vmware/harbor/src/common/api"
|
||||||
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
//SystemInfoAPI handle requests for getting system info /api/systeminfo
|
//SystemInfoAPI handle requests for getting system info /api/systeminfo
|
||||||
@ -32,8 +35,19 @@ type Storage struct {
|
|||||||
Free uint64 `json:"free"`
|
Free uint64 `json:"free"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare for validating user if an admin.
|
//GeneralInfo wraps common systeminfo for anonymous request
|
||||||
func (sia *SystemInfoAPI) Prepare() {
|
type GeneralInfo struct {
|
||||||
|
WithNotary bool `json:"with_notary"`
|
||||||
|
WithAdmiral bool `json:"with_admiral"`
|
||||||
|
AdmiralEndpoint string `json:"admiral_endpoint"`
|
||||||
|
AuthMode string `json:"auth_mode"`
|
||||||
|
RegistryURL string `json:"registry_url"`
|
||||||
|
ProjectCreationRestrict string `json:"project_creation_restriction"`
|
||||||
|
SelfRegistration bool `json:"self_registration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate for validating user if an admin.
|
||||||
|
func (sia *SystemInfoAPI) validate() {
|
||||||
sia.currentUserID = sia.ValidateUser()
|
sia.currentUserID = sia.ValidateUser()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -46,6 +60,7 @@ func (sia *SystemInfoAPI) Prepare() {
|
|||||||
|
|
||||||
// GetVolumeInfo gets specific volume storage info.
|
// GetVolumeInfo gets specific volume storage info.
|
||||||
func (sia *SystemInfoAPI) GetVolumeInfo() {
|
func (sia *SystemInfoAPI) GetVolumeInfo() {
|
||||||
|
sia.validate()
|
||||||
if !sia.isAdmin {
|
if !sia.isAdmin {
|
||||||
sia.RenderError(http.StatusForbidden, "User does not have admin role.")
|
sia.RenderError(http.StatusForbidden, "User does not have admin role.")
|
||||||
return
|
return
|
||||||
@ -71,6 +86,7 @@ func (sia *SystemInfoAPI) GetVolumeInfo() {
|
|||||||
|
|
||||||
//GetCert gets default self-signed certificate.
|
//GetCert gets default self-signed certificate.
|
||||||
func (sia *SystemInfoAPI) GetCert() {
|
func (sia *SystemInfoAPI) GetCert() {
|
||||||
|
sia.validate()
|
||||||
if sia.isAdmin {
|
if sia.isAdmin {
|
||||||
if _, err := os.Stat(defaultRootCert); !os.IsNotExist(err) {
|
if _, err := os.Stat(defaultRootCert); !os.IsNotExist(err) {
|
||||||
sia.Ctx.Output.Header("Content-Type", "application/octet-stream")
|
sia.Ctx.Output.Header("Content-Type", "application/octet-stream")
|
||||||
@ -83,3 +99,29 @@ func (sia *SystemInfoAPI) GetCert() {
|
|||||||
}
|
}
|
||||||
sia.CustomAbort(http.StatusForbidden, "")
|
sia.CustomAbort(http.StatusForbidden, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetGeneralInfo returns the general system info, which is to be called by anonymous user
|
||||||
|
func (sia *SystemInfoAPI) GetGeneralInfo() {
|
||||||
|
cfg, err := config.GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error occured getting config: %v", err)
|
||||||
|
sia.CustomAbort(http.StatusInternalServerError, "Unexpected error")
|
||||||
|
}
|
||||||
|
var registryURL string
|
||||||
|
if l := strings.Split(cfg[comcfg.ExtEndpoint].(string), "://"); len(l) > 1 {
|
||||||
|
registryURL = l[1]
|
||||||
|
} else {
|
||||||
|
registryURL = l[0]
|
||||||
|
}
|
||||||
|
info := GeneralInfo{
|
||||||
|
AdmiralEndpoint: cfg[comcfg.AdmiralEndpoint].(string),
|
||||||
|
WithAdmiral: config.WithAdmiral(),
|
||||||
|
WithNotary: config.WithNotary(),
|
||||||
|
AuthMode: cfg[comcfg.AUTHMode].(string),
|
||||||
|
ProjectCreationRestrict: cfg[comcfg.ProjectCreationRestriction].(string),
|
||||||
|
SelfRegistration: cfg[comcfg.SelfRegistration].(bool),
|
||||||
|
RegistryURL: registryURL,
|
||||||
|
}
|
||||||
|
sia.Data["json"] = info
|
||||||
|
sia.ServeJSON()
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
@ -37,6 +38,18 @@ func TestGetVolumeInfo(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetGeneralInfo(t *testing.T) {
|
||||||
|
apiTest := newHarborAPI()
|
||||||
|
code, body, err := apiTest.GetGeneralInfo()
|
||||||
|
assert := assert.New(t)
|
||||||
|
assert.Nil(err, fmt.Sprintf("Unexpected Error: %v", err))
|
||||||
|
assert.Equal(200, code, fmt.Sprintf("Unexpected status code: %d", code))
|
||||||
|
g := &GeneralInfo{}
|
||||||
|
err = json.Unmarshal(body, g)
|
||||||
|
assert.Nil(err, fmt.Sprintf("Unexpected Error: %v", err))
|
||||||
|
assert.Equal(false, g.WithNotary, "with notary should be false")
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetCert(t *testing.T) {
|
func TestGetCert(t *testing.T) {
|
||||||
fmt.Println("Testing Get Cert")
|
fmt.Println("Testing Get Cert")
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
@ -18,6 +18,7 @@ package config
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
comcfg "github.com/vmware/harbor/src/common/config"
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
@ -132,7 +133,7 @@ func TokenExpiration() (int, error) {
|
|||||||
return int(cfg[comcfg.TokenExpiration].(float64)), nil
|
return int(cfg[comcfg.TokenExpiration].(float64)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtEndpoint returns the external URL of Harbor: protocal://host:port
|
// ExtEndpoint returns the external URL of Harbor: protocol://host:port
|
||||||
func ExtEndpoint() (string, error) {
|
func ExtEndpoint() (string, error) {
|
||||||
cfg, err := mg.Get()
|
cfg, err := mg.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -141,6 +142,19 @@ func ExtEndpoint() (string, error) {
|
|||||||
return cfg[comcfg.ExtEndpoint].(string), nil
|
return cfg[comcfg.ExtEndpoint].(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtURL returns the external URL: host:port
|
||||||
|
func ExtURL() (string, error) {
|
||||||
|
endpoint, err := ExtEndpoint()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
l := strings.Split(endpoint, "://")
|
||||||
|
if len(l) > 0 {
|
||||||
|
return l[1], nil
|
||||||
|
}
|
||||||
|
return endpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SecretKey returns the secret key to encrypt the password of target
|
// SecretKey returns the secret key to encrypt the password of target
|
||||||
func SecretKey() (string, error) {
|
func SecretKey() (string, error) {
|
||||||
return keyProvider.Get(nil)
|
return keyProvider.Get(nil)
|
||||||
@ -174,6 +188,12 @@ func InternalTokenServiceEndpoint() string {
|
|||||||
return "http://ui/service/token"
|
return "http://ui/service/token"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InternalNotaryEndpoint returns notary server endpoint for internal communication between Harbor containers
|
||||||
|
// This is currently a conventional value and can be unaccessible when Harbor is not deployed with Notary.
|
||||||
|
func InternalNotaryEndpoint() string {
|
||||||
|
return "http://notary-server:4443"
|
||||||
|
}
|
||||||
|
|
||||||
// InitialAdminPassword returns the initial password for administrator
|
// InitialAdminPassword returns the initial password for administrator
|
||||||
func InitialAdminPassword() (string, error) {
|
func InitialAdminPassword() (string, error) {
|
||||||
cfg, err := mg.Get()
|
cfg, err := mg.Get()
|
||||||
@ -253,3 +273,32 @@ func UISecret() string {
|
|||||||
func JobserviceSecret() string {
|
func JobserviceSecret() string {
|
||||||
return os.Getenv("JOBSERVICE_SECRET")
|
return os.Getenv("JOBSERVICE_SECRET")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithNotary returns a bool value to indicate if Harbor's deployed with Notary
|
||||||
|
func WithNotary() bool {
|
||||||
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get configuration, will return WithNotary == false")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return cfg[comcfg.WithNotary].(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdmiralEndpoint returns the URL of admiral, if Harbor is not deployed with admiral it should return an empty string.
|
||||||
|
func AdmiralEndpoint() string {
|
||||||
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get configuration, will return empty string as admiral's endpoint")
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if e, ok := cfg[comcfg.AdmiralEndpoint].(string); !ok || e == "NA" {
|
||||||
|
cfg[comcfg.AdmiralEndpoint] = ""
|
||||||
|
}
|
||||||
|
return cfg[comcfg.AdmiralEndpoint].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAdmiral returns a bool to indicate if Harbor's deployed with admiral.
|
||||||
|
func WithAdmiral() bool {
|
||||||
|
return len(AdmiralEndpoint()) > 0
|
||||||
|
}
|
||||||
|
@ -120,4 +120,24 @@ func TestConfig(t *testing.T) {
|
|||||||
if _, err := Database(); err != nil {
|
if _, err := Database(); err != nil {
|
||||||
t.Fatalf("failed to get database: %v", err)
|
t.Fatalf("failed to get database: %v", err)
|
||||||
}
|
}
|
||||||
|
if InternalNotaryEndpoint() != "http://notary-server:4443" {
|
||||||
|
t.Errorf("Unexpected notary endpoint: %s", InternalNotaryEndpoint())
|
||||||
|
}
|
||||||
|
if WithNotary() {
|
||||||
|
t.Errorf("Withnotary should be false")
|
||||||
|
}
|
||||||
|
if !WithAdmiral() {
|
||||||
|
t.Errorf("WithAdmiral should be true")
|
||||||
|
}
|
||||||
|
if AdmiralEndpoint() != "http://www.vmware.com" {
|
||||||
|
t.Errorf("Unexpected admiral endpoint: %s", AdmiralEndpoint())
|
||||||
|
}
|
||||||
|
|
||||||
|
extURL, err := ExtURL()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error getting external URL: %v", err)
|
||||||
|
}
|
||||||
|
if extURL != "host01.com" {
|
||||||
|
t.Errorf(`extURL should be "host01.com".`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,7 @@ func initRouters() {
|
|||||||
beego.Router("/api/logs", &api.LogAPI{})
|
beego.Router("/api/logs", &api.LogAPI{})
|
||||||
beego.Router("/api/configurations", &api.ConfigAPI{})
|
beego.Router("/api/configurations", &api.ConfigAPI{})
|
||||||
|
|
||||||
|
beego.Router("/api/systeminfo", &api.SystemInfoAPI{}, "get:GetGeneralInfo")
|
||||||
beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
|
beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
|
||||||
beego.Router("/api/systeminfo/getcert", &api.SystemInfoAPI{}, "get:GetCert")
|
beego.Router("/api/systeminfo/getcert", &api.SystemInfoAPI{}, "get:GetCert")
|
||||||
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
|
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
|
||||||
|
@ -43,14 +43,14 @@ func InitCreators() {
|
|||||||
},
|
},
|
||||||
"registry": ®istryFilter{},
|
"registry": ®istryFilter{},
|
||||||
}
|
}
|
||||||
ext, err := config.ExtEndpoint()
|
ext, err := config.ExtURL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("Failed to get ext enpoint, err: %v, the token service will not be functional with notary requests", err)
|
log.Warningf("Failed to get ext url, err: %v, the token service will not be functional with notary requests", err)
|
||||||
} else {
|
} else {
|
||||||
notaryFilterMap = map[string]accessFilter{
|
notaryFilterMap = map[string]accessFilter{
|
||||||
"repository": &repositoryFilter{
|
"repository": &repositoryFilter{
|
||||||
parser: &endpointParser{
|
parser: &endpointParser{
|
||||||
endpoint: strings.Split(ext, "//")[1],
|
endpoint: ext,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user