mirror of
https://github.com/goharbor/harbor.git
synced 2024-10-04 08:18:03 +02:00
commit
b7a196f7aa
23
.gitignore
vendored
23
.gitignore
vendored
@ -1,8 +1,31 @@
|
||||
harbor
|
||||
make/common/config/*
|
||||
make/dev/adminserver/harbor_adminserver
|
||||
make/dev/ui/harbor_ui
|
||||
make/dev/jobservice/harbor_jobservice
|
||||
src/adminserver/adminserver
|
||||
src/ui/ui
|
||||
src/jobservice/jobservice
|
||||
src/common/dao/dao.test
|
||||
*.pyc
|
||||
jobservice/test
|
||||
|
||||
src/ui/static/*.html
|
||||
src/ui/static/*.bundle.js
|
||||
src/ui/static/*.bundle.js.map
|
||||
src/ui/static/harbor-log.*.png
|
||||
|
||||
src/ui_ng/coverage/
|
||||
src/ui_ng/dist/
|
||||
src/ui_ng/html-report/
|
||||
src/ui_ng/node_modules/
|
||||
src/ui_ng/typings/
|
||||
**/*npm-debug.log.*
|
||||
**/*yarn-error.log.*
|
||||
.idea/
|
||||
.DS_Store
|
||||
**/node_modules
|
||||
**/ssl/
|
||||
**/proxy.config.json
|
||||
|
||||
|
||||
|
35
.travis.yml
35
.travis.yml
@ -3,7 +3,7 @@ sudo: true
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.6.2
|
||||
- 1.7.3
|
||||
|
||||
go_import_path: github.com/vmware/harbor
|
||||
|
||||
@ -13,25 +13,25 @@ services:
|
||||
dist: trusty
|
||||
|
||||
env:
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_PORT: 3306
|
||||
DB_USR: root
|
||||
DB_PWD: root123
|
||||
MYSQL_HOST: localhost
|
||||
MYSQL_PORT: 3306
|
||||
MYSQL_USR: root
|
||||
MYSQL_PWD: root123
|
||||
MYSQL_DATABASE: registry
|
||||
SQLITE_FILE: /tmp/registry.db
|
||||
ADMIN_SERVER_URL: http://127.0.0.1:8888
|
||||
DOCKER_COMPOSE_VERSION: 1.7.1
|
||||
HARBOR_ADMIN: admin
|
||||
HARBOR_ADMIN_PASSWD: Harbor12345
|
||||
UI_SECRET: tempString
|
||||
MAX_JOB_WORKERS: 3
|
||||
SECRET_KEY: 1234567890123456
|
||||
AUTH_MODE: db_auth
|
||||
SELF_REGISTRATION: "on"
|
||||
SELF_REGISTRATION: on
|
||||
KEY_PATH: /data/secretkey
|
||||
|
||||
before_install:
|
||||
- sudo ./tests/hostcfg.sh
|
||||
- sudo ./tests/generateCerts.sh
|
||||
- sudo ./make/prepare
|
||||
|
||||
install:
|
||||
@ -70,9 +70,14 @@ install:
|
||||
before_script:
|
||||
# create tables and load data
|
||||
# - mysql < ./make/db/registry.sql -uroot --verbose
|
||||
- sudo sqlite3 /registry.db < make/common/db/registry_sqlite.sql
|
||||
- sudo sqlite3 /tmp/registry.db < make/common/db/registry_sqlite.sql
|
||||
- sudo chmod 777 /tmp/registry.db
|
||||
|
||||
script:
|
||||
- sudo mkdir -p /etc/ui/ca/
|
||||
- sudo mv ./tests/ca.crt /etc/ui/ca/
|
||||
- sudo mkdir -p /harbor
|
||||
- sudo mv ./VERSION /harbor/VERSION
|
||||
- sudo service mysql stop
|
||||
- sudo ./tests/testprepare.sh
|
||||
- docker-compose -f ./make/docker-compose.test.yml up -d
|
||||
@ -82,16 +87,22 @@ script:
|
||||
- export REGISTRY_URL=$IP:5000
|
||||
- echo $REGISTRY_URL
|
||||
- ./tests/pushimage.sh
|
||||
- cd tests
|
||||
- sudo ./ldapprepare.sh
|
||||
- cd ..
|
||||
- go test -i ./src/ui ./src/adminserver ./src/jobservice
|
||||
- ./tests/coverage4gotest.sh
|
||||
- goveralls -coverprofile=profile.cov -service=travis-ci
|
||||
|
||||
- docker-compose -f make/docker-compose.test.yml down
|
||||
|
||||
- docker-compose -f make/dev/docker-compose.yml up -d
|
||||
- sudo rm -rf /data/config/*
|
||||
- ls /data/cert
|
||||
- sudo make install GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:0.8.4 NOTARYFLAG=true
|
||||
|
||||
- docker ps
|
||||
- go run tests/startuptest.go http://localhost/
|
||||
- go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD}
|
||||
- ./tests/notarytest.sh
|
||||
- ./tests/startuptest.sh
|
||||
- ./tests/userlogintest.sh ${HARBOR_ADMIN} ${HARBOR_ADMIN_PASSWD}
|
||||
|
||||
# - sudo ./tests/testprepare.sh
|
||||
# - go test -v ./tests/apitests
|
||||
|
202
Makefile
202
Makefile
@ -4,21 +4,17 @@
|
||||
#
|
||||
# all: prepare env, compile binarys, build images and install images
|
||||
# prepare: prepare env
|
||||
# compile: compile ui and jobservice code
|
||||
# compile_buildgolangimage:
|
||||
# compile local building golang image
|
||||
# forexample : make compile_buildgolangimage -e \
|
||||
# GOBUILDIMAGE=harborgo:1.6.2
|
||||
# compile: compile adminserver, ui and jobservice code
|
||||
#
|
||||
# compile_golangimage:
|
||||
# compile from golang image
|
||||
# for example: make compile_golangimage -e GOBUILDIMAGE= \
|
||||
# harborgo:1.6.2
|
||||
# compile_ui, compile_jobservice: compile specific binary
|
||||
# golang:1.7.3
|
||||
# compile_adminserver, compile_ui, compile_jobservice: compile specific binary
|
||||
#
|
||||
# build: build Harbor docker images (defuault: build_photon)
|
||||
# for example: make build -e BASEIMAGE=photon
|
||||
# build_photon: build Harbor docker images from photon bsaeimage
|
||||
# build_ubuntu: build Harbor docker images from ubuntu baseimage
|
||||
# build_photon: build Harbor docker images from photon baseimage
|
||||
#
|
||||
# install: include compile binarys, build images, prepare specific \
|
||||
# version composefile and startup Harbor instance
|
||||
@ -49,7 +45,7 @@
|
||||
#
|
||||
# clean: remove binary, Harbor images, specific version docker-compose \
|
||||
# file, specific version tag and online/offline install package
|
||||
# cleanbinary: remove ui and jobservice binary
|
||||
# cleanbinary: remove adminserver, ui and jobservice binary
|
||||
# cleanimage: remove Harbor images
|
||||
# cleandockercomposefile:
|
||||
# remove specific version docker-compose
|
||||
@ -73,6 +69,8 @@ MAKEPATH=$(BUILDPATH)/make
|
||||
MAKEDEVPATH=$(MAKEPATH)/dev
|
||||
SRCPATH=./src
|
||||
TOOLSPATH=$(BUILDPATH)/tools
|
||||
UIPATH=$(BUILDPATH)/src/ui
|
||||
UINGPATH=$(BUILDPATH)/src/ui_ng
|
||||
GOBASEPATH=/go/src/github.com/vmware
|
||||
CHECKENVCMD=checkenv.sh
|
||||
BASEIMAGE=photon
|
||||
@ -80,6 +78,19 @@ COMPILETAG=compile_normal
|
||||
REGISTRYSERVER=
|
||||
REGISTRYPROJECTNAME=vmware
|
||||
DEVFLAG=true
|
||||
NOTARYFLAG=false
|
||||
REGISTRYVERSION=photon-2.6.0
|
||||
NGINXVERSION=1.11.5
|
||||
PHOTONVERSION=1.0
|
||||
NOTARYVERSION=server-0.5.0
|
||||
NOTARYSIGNERVERSION=signer-0.5.0
|
||||
MARIADBVERSION=mariadb-10.1.10
|
||||
HTTPPROXY=
|
||||
|
||||
#clarity parameters
|
||||
CLARITYIMAGE=danieljt/harbor-clarity-base[:tag]
|
||||
CLARITYSEEDPATH=/clarity-seed
|
||||
CLARITYBUILDSCRIPT=/entrypoint.sh
|
||||
|
||||
# docker parameters
|
||||
DOCKERCMD=$(shell which docker)
|
||||
@ -103,14 +114,19 @@ GOBUILDIMAGE=reg.mydomain.com/library/harborgo[:tag]
|
||||
GOBUILDPATH=$(GOBASEPATH)/harbor
|
||||
GOIMAGEBUILDCMD=/usr/local/go/bin/go
|
||||
GOIMAGEBUILD=$(GOIMAGEBUILDCMD) build
|
||||
GOBUILDPATH_ADMINSERVER=$(GOBUILDPATH)/src/adminserver
|
||||
GOBUILDPATH_UI=$(GOBUILDPATH)/src/ui
|
||||
GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/src/jobservice
|
||||
GOBUILDMAKEPATH=$(GOBUILDPATH)/make
|
||||
GOBUILDMAKEPATH_ADMINSERVER=$(GOBUILDMAKEPATH)/dev/adminserver
|
||||
GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/dev/ui
|
||||
GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice
|
||||
GOLANGDOCKERFILENAME=Dockerfile.golang
|
||||
|
||||
# binary
|
||||
ADMINSERVERSOURCECODE=$(SRCPATH)/adminserver
|
||||
ADMINSERVERBINARYPATH=$(MAKEDEVPATH)/adminserver
|
||||
ADMINSERVERBINARYNAME=harbor_adminserver
|
||||
UISOURCECODE=$(SRCPATH)/ui
|
||||
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
||||
UIBINARYNAME=harbor_ui
|
||||
@ -128,7 +144,6 @@ CONFIGFILE=harbor.cfg
|
||||
|
||||
# makefile
|
||||
MAKEFILEPATH_PHOTON=$(MAKEPATH)/photon
|
||||
MAKEFILEPATH_UBUNTU=$(MAKEPATH)/ubuntu
|
||||
|
||||
# common dockerfile
|
||||
DOCKERFILEPATH_COMMON=$(MAKEPATH)/common
|
||||
@ -136,26 +151,28 @@ DOCKERFILEPATH_DB=$(DOCKERFILEPATH_COMMON)/db
|
||||
DOCKERFILENAME_DB=Dockerfile
|
||||
|
||||
# docker image name
|
||||
DOCKERIMAGENAME_ADMINSERVER=vmware/harbor-adminserver
|
||||
DOCKERIMAGENAME_UI=vmware/harbor-ui
|
||||
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
||||
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
||||
DOCKERIMAGENAME_DB=vmware/harbor-db
|
||||
|
||||
|
||||
# docker-compose files
|
||||
DOCKERCOMPOSEFILEPATH=$(MAKEPATH)
|
||||
DOCKERCOMPOSETPLFILENAME=docker-compose.tpl
|
||||
DOCKERCOMPOSEFILENAME=docker-compose.yml
|
||||
DOCKERCOMPOSENOTARYFILENAME=docker-compose.notary.yml
|
||||
|
||||
# version prepare
|
||||
VERSIONFILEPATH=$(SRCPATH)/ui/views/sections
|
||||
VERSIONFILENAME=header-content.htm
|
||||
VERSIONFILEPATH=$(CURDIR)
|
||||
VERSIONFILENAME=VERSION
|
||||
GITCMD=$(shell which git)
|
||||
GITTAG=$(GITCMD) describe --tags
|
||||
GITTAGVERSION=$(shell git describe --tags || echo UNKNOWN)
|
||||
ifeq ($(DEVFLAG), true)
|
||||
VERSIONTAG=dev
|
||||
else
|
||||
VERSIONTAG=$(shell $(GITTAG))
|
||||
VERSIONTAG=$(GITTAGVERSION)
|
||||
endif
|
||||
|
||||
SEDCMD=$(shell which sed)
|
||||
@ -173,13 +190,16 @@ REGISTRYUSER=user
|
||||
REGISTRYPASSWORD=default
|
||||
|
||||
version:
|
||||
@if [ "$(DEVFLAG)" = "false" ] ; then \
|
||||
$(SEDCMD) -i 's/version=\"{{.Version}}\"/version=\"$(VERSIONTAG)\"/' -i $(VERSIONFILEPATH)/$(VERSIONFILENAME) ; \
|
||||
fi
|
||||
@printf $(GITTAGVERSION) > $(VERSIONFILEPATH)/$(VERSIONFILENAME);
|
||||
|
||||
check_environment:
|
||||
@$(MAKEPATH)/$(CHECKENVCMD)
|
||||
|
||||
compile_adminserver:
|
||||
@echo "compiling binary for adminserver..."
|
||||
@$(GOBUILD) -o $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) $(ADMINSERVERSOURCECODE)
|
||||
@echo "Done."
|
||||
|
||||
compile_ui:
|
||||
@echo "compiling binary for ui..."
|
||||
@$(GOBUILD) -o $(UIBINARYPATH)/$(UIBINARYNAME) $(UISOURCECODE)
|
||||
@ -190,14 +210,24 @@ compile_jobservice:
|
||||
@$(GOBUILD) -o $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) $(JOBSERVICESOURCECODE)
|
||||
@echo "Done."
|
||||
|
||||
compile_normal: compile_ui compile_jobservice
|
||||
|
||||
compile_buildgolangimage:
|
||||
@echo "compiling golang image for harbor ..."
|
||||
@$(DOCKERBUILD) -t $(GOBUILDIMAGE) -f $(TOOLSPATH)/$(GOLANGDOCKERFILENAME) .
|
||||
compile_clarity:
|
||||
@echo "compiling binary for clarity ui..."
|
||||
@if [ "$(HTTPPROXY)" != "" ] ; then \
|
||||
$(DOCKERCMD) run --rm -v $(UIPATH)/static:$(CLARITYSEEDPATH)/dist -v $(UINGPATH)/src:$(CLARITYSEEDPATH)/src $(CLARITYIMAGE) $(SHELL) $(CLARITYBUILDSCRIPT) -p $(HTTPPROXY); \
|
||||
else \
|
||||
$(DOCKERCMD) run --rm -v $(UIPATH)/static:$(CLARITYSEEDPATH)/dist -v $(UINGPATH)/src:$(CLARITYSEEDPATH)/src $(CLARITYIMAGE) $(SHELL) $(CLARITYBUILDSCRIPT); \
|
||||
fi
|
||||
@echo "Done."
|
||||
|
||||
compile_normal: compile_clarity compile_adminserver compile_ui compile_jobservice
|
||||
|
||||
compile_golangimage: compile_clarity
|
||||
@echo "compiling binary for adminserver (golang image)..."
|
||||
@echo $(GOBASEPATH)
|
||||
@echo $(GOBUILDPATH)
|
||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_ADMINSERVER) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_ADMINSERVER)/$(ADMINSERVERBINARYNAME)
|
||||
@echo "Done."
|
||||
|
||||
compile_golangimage:
|
||||
@echo "compiling binary for ui (golang image)..."
|
||||
@echo $(GOBASEPATH)
|
||||
@echo $(GOBUILDPATH)
|
||||
@ -212,7 +242,11 @@ compile:check_environment $(COMPILETAG)
|
||||
|
||||
prepare:
|
||||
@echo "preparing..."
|
||||
@$(MAKEPATH)/$(PREPARECMD) -conf $(CONFIGPATH)/$(CONFIGFILE)
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(MAKEPATH)/$(PREPARECMD) --conf $(CONFIGPATH)/$(CONFIGFILE) --with-notary; \
|
||||
else \
|
||||
$(MAKEPATH)/$(PREPARECMD) --conf $(CONFIGPATH)/$(CONFIGFILE) ; \
|
||||
fi
|
||||
|
||||
build_common: version
|
||||
@echo "buildging db container for photon..."
|
||||
@ -222,20 +256,14 @@ build_common: version
|
||||
build_photon: build_common
|
||||
make -f $(MAKEFILEPATH_PHOTON)/Makefile build -e DEVFLAG=$(DEVFLAG)
|
||||
|
||||
build_ubuntu: build_common
|
||||
make -f $(MAKEFILEPATH_UBUNTU)/Makefile build -e DEVFLAG=$(DEVFLAG)
|
||||
|
||||
build: build_$(BASEIMAGE)
|
||||
|
||||
modify_composefile:
|
||||
@echo "preparing docker-compose file..."
|
||||
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSETPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||
@$(SEDCMD) -i 's/image\: vmware.*/&:$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||
@$(SEDCMD) -i 's/__version__/$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||
|
||||
install: compile build prepare modify_composefile
|
||||
@echo "loading harbor images..."
|
||||
@$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) up -d
|
||||
@echo "Install complete. You can visit harbor now."
|
||||
install: compile build prepare modify_composefile start
|
||||
|
||||
package_online: modify_composefile
|
||||
@echo "packing online package ..."
|
||||
@ -246,15 +274,20 @@ package_online: modify_composefile
|
||||
fi
|
||||
@cp LICENSE $(HARBORPKG)/LICENSE
|
||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||
@$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
||||
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/common/config\
|
||||
--exclude=$(HARBORPKG)/common/log --exclude=$(HARBORPKG)/ubuntu \
|
||||
--exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \
|
||||
--exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \
|
||||
--exclude=$(HARBORPKG)/checkenv.sh \
|
||||
--exclude=$(HARBORPKG)/jsminify.sh \
|
||||
--exclude=$(HARBORPKG)/pushimage.sh \
|
||||
$(HARBORPKG)
|
||||
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(TARCMD) -zcvf harbor-online-installer-$(GITTAGVERSION).tgz \
|
||||
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
||||
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME); \
|
||||
else \
|
||||
$(TARCMD) -zcvf harbor-online-installer-$(GITTAGVERSION).tgz \
|
||||
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
||||
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||
$(HARBORPKG)/harbor.cfg ; \
|
||||
fi
|
||||
|
||||
@rm -rf $(HARBORPKG)
|
||||
@echo "Done."
|
||||
@ -267,32 +300,60 @@ package_offline: compile build modify_composefile
|
||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||
|
||||
@echo "pulling nginx and registry..."
|
||||
@$(DOCKERPULL) registry:2.5.0
|
||||
@$(DOCKERPULL) nginx:1.11.5
|
||||
@$(DOCKERPULL) vmware/registry:$(REGISTRYVERSION)
|
||||
@$(DOCKERPULL) nginx:$(NGINXVERSION)
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
echo "pulling notary and harbor-notary-db..."; \
|
||||
$(DOCKERPULL) vmware/notary-photon:$(NOTARYVERSION); \
|
||||
$(DOCKERPULL) vmware/notary-photon:$(NOTARYSIGNERVERSION); \
|
||||
$(DOCKERPULL) vmware/harbor-notary-db:$(MARIADBVERSION); \
|
||||
fi
|
||||
|
||||
@echo "saving harbor docker image"
|
||||
@$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(DOCKERSAVE) $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||
nginx:1.11.5 registry:2.5.0 photon:1.0
|
||||
nginx:$(NGINXVERSION) vmware/registry:$(REGISTRYVERSION) photon:$(PHOTONVERSION) \
|
||||
vmware/notary-photon:$(NOTARYVERSION) vmware/notary-photon:$(NOTARYSIGNERVERSION) \
|
||||
vmware/harbor-notary-db:$(MARIADBVERSION) | gzip > $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tar.gz; \
|
||||
else \
|
||||
$(DOCKERSAVE) $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||
nginx:$(NGINXVERSION) vmware/registry:$(REGISTRYVERSION) \
|
||||
photon:$(PHOTONVERSION) | gzip > $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tar.gz; \
|
||||
fi
|
||||
|
||||
@$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
||||
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/common/config\
|
||||
--exclude=$(HARBORPKG)/common/log --exclude=$(HARBORPKG)/ubuntu \
|
||||
--exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \
|
||||
--exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \
|
||||
--exclude=$(HARBORPKG)/checkenv.sh \
|
||||
--exclude=$(HARBORPKG)/jsminify.sh \
|
||||
--exclude=$(HARBORPKG)/pushimage.sh \
|
||||
$(HARBORPKG)
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(TARCMD) -zcvf harbor-offline-installer-$(GITTAGVERSION).tgz \
|
||||
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tar.gz \
|
||||
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||
$(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME) ; \
|
||||
else \
|
||||
$(TARCMD) -zcvf harbor-offline-installer-$(GITTAGVERSION).tgz \
|
||||
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tar.gz \
|
||||
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) ; \
|
||||
fi
|
||||
|
||||
@rm -rf $(HARBORPKG)
|
||||
@echo "Done."
|
||||
|
||||
pushimage:
|
||||
@echo "pushing harbor images ..."
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_UI):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
@ -315,26 +376,41 @@ pushimage:
|
||||
|
||||
start:
|
||||
@echo "loading harbor images..."
|
||||
@$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml up -d
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) up -d ; \
|
||||
else \
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) up -d ; \
|
||||
fi
|
||||
@echo "Start complete. You can visit harbor now."
|
||||
|
||||
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..."
|
||||
@$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml down
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) down -v ; \
|
||||
else \
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) down -v ; \
|
||||
fi
|
||||
@echo "Done."
|
||||
|
||||
cleanbinary:
|
||||
@echo "cleaning binary..."
|
||||
@if [ -f $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ] ; then rm $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ; fi
|
||||
@if [ -f $(UIBINARYPATH)/$(UIBINARYNAME) ] ; then rm $(UIBINARYPATH)/$(UIBINARYNAME) ; fi
|
||||
@if [ -f $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ] ; then rm $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ; fi
|
||||
|
||||
cleanimage:
|
||||
@echo "cleaning image for photon..."
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||
# - $(DOCKERRMIMAGE) -f registry:2.5.0
|
||||
# - $(DOCKERRMIMAGE) -f registry:$(REGISTRYVERSION)
|
||||
# - $(DOCKERRMIMAGE) -f nginx:1.11.5
|
||||
|
||||
cleandockercomposefile:
|
||||
@ -343,15 +419,15 @@ cleandockercomposefile:
|
||||
|
||||
cleanversiontag:
|
||||
@echo "cleaning version TAG"
|
||||
@$(SEDCMD) -i 's/version=\"$(VERSIONTAG)\"/version=\"{{.Version}}\"/' -i $(VERSIONFILEPATH)/$(VERSIONFILENAME)
|
||||
@rm -rf $(VERSIONFILEPATH)/$(VERSIONFILENAME)
|
||||
|
||||
cleanpackage:
|
||||
@echo "cleaning harbor install package"
|
||||
@if [ -d $(BUILDPATH)/harbor ] ; then rm -rf $(BUILDPATH)/harbor ; fi
|
||||
@if [ -f $(BUILDPATH)/harbor-online-installer-$(VERSIONTAG).tgz ] ; \
|
||||
then rm $(BUILDPATH)/harbor-online-installer-$(VERSIONTAG).tgz ; fi
|
||||
@if [ -f $(BUILDPATH)/harbor-offline-installer-$(VERSIONTAG).tgz ] ; \
|
||||
then rm $(BUILDPATH)/harbor-offline-installer-$(VERSIONTAG).tgz ; fi
|
||||
@if [ -f $(BUILDPATH)/harbor-online-installer-$(GITTAGVERSION).tgz ] ; \
|
||||
then rm $(BUILDPATH)/harbor-online-installer-$(GITTAGVERSION).tgz ; fi
|
||||
@if [ -f $(BUILDPATH)/harbor-offline-installer-$(GITTAGVERSION).tgz ] ; \
|
||||
then rm $(BUILDPATH)/harbor-offline-installer-$(GITTAGVERSION).tgz ; fi
|
||||
|
||||
.PHONY: cleanall
|
||||
cleanall: cleanbinary cleanimage cleandockercomposefile cleanversiontag cleanpackage
|
||||
|
@ -24,26 +24,7 @@ golang* | 1.6.0 +
|
||||
$ git clone https://github.com/vmware/harbor
|
||||
```
|
||||
|
||||
## Step 3: Resolving dependencies of Go language
|
||||
You can compile the source code by using a Golang dev image. In this case, you can skip this step.
|
||||
|
||||
If you are building Harbor using your own Go compiling environment. You need to install LDAP packages manually.
|
||||
|
||||
For PhotonOS:
|
||||
|
||||
```sh
|
||||
$ tdnf install -y sed apr-util-ldap
|
||||
```
|
||||
|
||||
For Ubuntu:
|
||||
|
||||
```sh
|
||||
$ apt-get update && apt-get install -y libldap2-dev
|
||||
```
|
||||
|
||||
For other platforms, please consult the relevant documentation of installing LDAP package.
|
||||
|
||||
## Step 4: Building and installing Harbor
|
||||
## Step 3: Building and installing Harbor
|
||||
|
||||
### Configuration
|
||||
|
||||
@ -58,18 +39,18 @@ Edit the file **make/harbor.cfg** and make necessary configuration changes such
|
||||
|
||||
You can compile the code by one of the three approaches:
|
||||
|
||||
#### I. Create a Golang dev image, then build Harbor
|
||||
#### I. Build with offical Golang image
|
||||
|
||||
* Build Golang dev image:
|
||||
* Get offcial Golang image from docker hub:
|
||||
|
||||
```sh
|
||||
$ make compile_buildgolangimage -e GOBUILDIMAGE=harborgo:1.6.2
|
||||
$ docker pull golang:1.7.3
|
||||
```
|
||||
|
||||
* Build, install and bring up Harbor:
|
||||
|
||||
```sh
|
||||
$ make install -e GOBUILDIMAGE=harborgo:1.6.2 COMPILETAG=compile_golangimage
|
||||
$ make install -e GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage
|
||||
```
|
||||
|
||||
#### II. Compile code with your own Golang environment, then build Harbor
|
||||
@ -142,12 +123,10 @@ Target | Description
|
||||
all | prepare env, compile binaries, build images and install images
|
||||
prepare | prepare env
|
||||
compile | compile ui and jobservice code
|
||||
compile_golangimage | compile local golang image
|
||||
compile_ui | compile ui binary
|
||||
compile_jobservice | compile jobservice binary
|
||||
build | build Harbor docker images (default: using build_photon)
|
||||
build_photon | build Harbor docker images from Photon OS base image
|
||||
build_ubuntu | build Harbor docker images from Ubuntu base image
|
||||
install | compile binaries, build images, prepare specific version of compose file and startup Harbor instance
|
||||
start | startup Harbor instance
|
||||
down | shutdown Harbor instance
|
||||
@ -163,20 +142,6 @@ cleanpackage | remove online/offline install package
|
||||
|
||||
#### EXAMPLE:
|
||||
|
||||
#### Build a golang dev image (for building Harbor):
|
||||
|
||||
```sh
|
||||
$ make compile_golangimage -e GOBUILDIMAGE= [$YOURIMAGE]
|
||||
|
||||
```
|
||||
|
||||
#### Build Harbor images based on Ubuntu
|
||||
|
||||
```sh
|
||||
$ make build -e BASEIMAGE=ubuntu
|
||||
|
||||
```
|
||||
|
||||
#### Push Harbor images to specific registry server
|
||||
|
||||
```sh
|
||||
|
@ -56,7 +56,7 @@ The parameters are described below - note that at the very least, you will need
|
||||
* email_server_port = 25
|
||||
* email_username = sample_admin@mydomain.com
|
||||
* email_password = abc
|
||||
* email_from = `admin \<sample_admin@mydomain.com\>`
|
||||
* email_from = admin <sample_admin@mydomain.com>
|
||||
* email_ssl = false
|
||||
|
||||
* **harbor_admin_password**: The administrator's initial password. This password only takes effect for the first time Harbor launches. After that, this setting is ignored and the administrator's password should be set in the UI. _Note that the default username/password are **admin/Harbor12345** ._
|
||||
@ -72,6 +72,7 @@ The parameters are described below - note that at the very least, you will need
|
||||
* **self_registration**: (**on** or **off**. Default is **on**) Enable / Disable the ability for a user to register themselves. When disabled, new users can only be created by the Admin user, only an admin user can create new users in Harbor. _NOTE: When **auth_mode** is set to **ldap_auth**, self-registration feature is **always** disabled, and this flag is ignored._
|
||||
* **use_compressed_js**: (**on** or **off**. Default is **on**) For production use, turn this flag to **on**. In development mode, set it to **off** so that js files can be modified separately.
|
||||
* **max_job_workers**: (default value is **3**) The maximum number of replication workers in job service. For each image replication job, a worker synchronizes all tags of a repository to the remote destination. Increasing this number allows more concurrent replication jobs in the system. However, since each worker consumes a certain amount of network/CPU/IO resources, please carefully pick the value of this attribute based on the hardware resource of the host.
|
||||
* **secretkey_path**: The path of key for encrypt or decrypt the password of a remote registry in a replication policy.
|
||||
|
||||
* **token_expiration**: The expiration time (in minutes) of a token created by token service, default is 30 minutes.
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# this is an example of the Uber API
|
||||
# as a demonstration of an API spec in YAML
|
||||
swagger: '2.0'
|
||||
info:
|
||||
title: Harbor API
|
||||
@ -517,7 +515,6 @@ paths:
|
||||
responses:
|
||||
200:
|
||||
description: Get current user information successfully.
|
||||
in: body
|
||||
schema:
|
||||
$ref: '#/definitions/User'
|
||||
401:
|
||||
@ -659,6 +656,11 @@ paths:
|
||||
format: int32
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
- name: detail
|
||||
in: query
|
||||
type: boolean
|
||||
required: false
|
||||
description: Get detail info or not.
|
||||
- name: q
|
||||
in: query
|
||||
type: string
|
||||
@ -680,11 +682,11 @@ paths:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Searched for respositories successfully.
|
||||
description: If detail is false, the response body is a string array which contains the names of repositories, or the response body contains an object array as described in schema.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
$ref: '#/definitions/Repository'
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of repositories
|
||||
@ -700,26 +702,27 @@ paths:
|
||||
description: Project ID does not exist.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/repositories/{repo_name}/tags/{tag}:
|
||||
delete:
|
||||
summary: Delete a repository or a tag in a repository.
|
||||
summary: Delete a tag in a repository.
|
||||
description: |
|
||||
This endpoint let user delete repositories and tags with repo name and tag.
|
||||
This endpoint let user delete tags with repo name and tag.
|
||||
parameters:
|
||||
- name: repo_name
|
||||
in: query
|
||||
in: path
|
||||
type: string
|
||||
required: true
|
||||
description: The name of repository which will be deleted.
|
||||
- name: tag
|
||||
in: query
|
||||
in: path
|
||||
type: string
|
||||
required: false
|
||||
required: true
|
||||
description: Tag of a repository.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Delete repository or tag successfully.
|
||||
description: Delete tag successfully.
|
||||
400:
|
||||
description: Invalid repo_name.
|
||||
401:
|
||||
@ -728,41 +731,69 @@ paths:
|
||||
description: Repository or tag not found.
|
||||
403:
|
||||
description: Forbidden.
|
||||
/repositories/tags:
|
||||
/repositories/{repo_name}/tags:
|
||||
get:
|
||||
summary: Get tags of a relevant repository.
|
||||
description: |
|
||||
This endpoint aims to retrieve tags from a relevant repository.
|
||||
parameters:
|
||||
- name: repo_name
|
||||
in: query
|
||||
in: path
|
||||
type: string
|
||||
required: true
|
||||
description: Relevant repository name.
|
||||
- name: detail
|
||||
in: query
|
||||
type: boolean
|
||||
required: false
|
||||
description: If detail is true, the manifests is returned too.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Retrieved tags from a relevant repository successfully.
|
||||
description: If detail is false, the response body is a string array, or the response body contains the manifest informations as described in schema.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
$ref: '#/definitions/DetailedTag'
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/repositories/manifests:
|
||||
delete:
|
||||
summary: Delete all tags of a repository.
|
||||
description: |
|
||||
This endpoint let user delete all tags with repo name.
|
||||
parameters:
|
||||
- name: repo_name
|
||||
in: path
|
||||
type: string
|
||||
required: true
|
||||
description: The name of repository which will be deleted.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Delete successfully.
|
||||
400:
|
||||
description: Invalid repo_name.
|
||||
401:
|
||||
description: Unauthorized.
|
||||
404:
|
||||
description: Repository not found.
|
||||
403:
|
||||
description: Forbidden.
|
||||
/repositories/{repo_name}/tags/{tag}/manifest:
|
||||
get:
|
||||
summary: Get manifests of a relevant repository.
|
||||
description: |
|
||||
This endpoint aims to retreive manifests from a relevant repository.
|
||||
parameters:
|
||||
- name: repo_name
|
||||
in: query
|
||||
in: path
|
||||
type: string
|
||||
required: true
|
||||
description: Repository name
|
||||
- name: tag
|
||||
in: query
|
||||
in: path
|
||||
type: string
|
||||
required: true
|
||||
description: Tag name
|
||||
@ -777,11 +808,37 @@ paths:
|
||||
200:
|
||||
description: Retrieved manifests from a relevant repository successfully.
|
||||
schema:
|
||||
$ref: '#/definitions/Repository'
|
||||
$ref: '#/definitions/Manifest'
|
||||
404:
|
||||
description: Retrieved manifests from a relevant repository not found.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/repositories/{repo_name}/signatures:
|
||||
get:
|
||||
summary: Get signature information of a repository
|
||||
description: |
|
||||
This endpoint aims to retrieve signature information of a repository, the data is
|
||||
from the nested notary instance of Harbor.
|
||||
If the repository does not have any signature information in notary, this API will
|
||||
return an empty list with response code 200, instead of 404
|
||||
parameters:
|
||||
- name: repo_name
|
||||
in: path
|
||||
type: string
|
||||
required: true
|
||||
description: repository name.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Retrieved signatures.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/RepoSignature'
|
||||
500:
|
||||
description: Server side error.
|
||||
|
||||
/repositories/top:
|
||||
get:
|
||||
summary: Get public repositories which are accessed most.
|
||||
@ -794,15 +851,20 @@ paths:
|
||||
format: int32
|
||||
required: false
|
||||
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:
|
||||
- Products
|
||||
responses:
|
||||
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:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/TopRepo'
|
||||
$ref: '#/definitions/Repository'
|
||||
400:
|
||||
description: Bad request because of invalid count.
|
||||
500:
|
||||
@ -1310,6 +1372,228 @@ paths:
|
||||
description: User does not have permission of admin role.
|
||||
500:
|
||||
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:
|
||||
get:
|
||||
summary: Get system volume info (total/free size).
|
||||
description: |
|
||||
This endpoint is for retrieving system volume info that only provides for admin user.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Get system volumes successfully.
|
||||
schema:
|
||||
type: object
|
||||
items:
|
||||
$ref: '#/definitions/SystemInfo'
|
||||
401:
|
||||
description: User need to log in first.
|
||||
403:
|
||||
description: User does not have permission of admin role.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/systeminfo/getcert:
|
||||
get:
|
||||
summary: Get default root certificate under OVA deployment.
|
||||
description: |
|
||||
This endpoint is for downloading a default root certificate that only provides for admin user under OVA deployment.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Get default root certificate successfully.
|
||||
401:
|
||||
description: User need to log in first.
|
||||
403:
|
||||
description: User does not have permission of admin role.
|
||||
404:
|
||||
description: Not found the default root certificate.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/ldap/ping:
|
||||
post:
|
||||
summary: Ping available ldap service.
|
||||
description: |
|
||||
This endpoint ping the available ldap service for test related configuration parameters.
|
||||
parameters:
|
||||
- name: ldapconf
|
||||
in: body
|
||||
description: ldap configuration. support input ldap service configuration. If it's a empty request, will load current configuration from the system.
|
||||
required: false
|
||||
schema:
|
||||
$ref: '#/definitions/LdapConf'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Ping ldap service successfully.
|
||||
400:
|
||||
description: Inviald ldap configuration parameters.
|
||||
401:
|
||||
description: User need to login first.
|
||||
403:
|
||||
description: Only admin has this authority.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/ldap/users/search:
|
||||
post:
|
||||
summary: Search available ldap users.
|
||||
description: |
|
||||
This endpoint searches the available ldap users based on related configuration parameters. Support searched by input ladp configuration, load configuration from the system and specific filter.
|
||||
parameters:
|
||||
- name: username
|
||||
in: query
|
||||
type: string
|
||||
required: false
|
||||
description: Registered user ID
|
||||
- name: ldap_conf
|
||||
in: body
|
||||
description: ldap search configuration. ldapconf field can input ldap service configuration. If this item are blank, will load default configuration will load current configuration from the system.
|
||||
required: false
|
||||
schema:
|
||||
$ref: '#/definitions/LdapConf'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Search ldap users successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/LdapUsers'
|
||||
400:
|
||||
description: Inviald ldap configuration parameters.
|
||||
401:
|
||||
description: User need to login first.
|
||||
403:
|
||||
description: Only admin has this authority.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/ldap/users/import:
|
||||
post:
|
||||
summary: Import selected available ldap users.
|
||||
description: |
|
||||
This endpoint adds the selected available ldap users to harbor based on related configuration parameters from the system. System will try to guess the user email address and realname, add to harbor user information.
|
||||
If have errors when import user, will return the list of importing failed uid and the failed reason.
|
||||
parameters:
|
||||
- name: uid_list
|
||||
in: body
|
||||
description: The uid listed for importing. This list will check users validity of ldap service based on configuration from the system.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/LdapImportUsers'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Add ldap users successfully.
|
||||
401:
|
||||
description: User need to login first.
|
||||
403:
|
||||
description: Only admin has this authority.
|
||||
500:
|
||||
description: Failed import some users.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/LdapFailedImportUsers'
|
||||
/configurations:
|
||||
get:
|
||||
summary: Get system configurations.
|
||||
description: |
|
||||
This endpoint is for retrieving system configurations that only provides for admin user.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Get system configurations successfully. The response body is a map.
|
||||
schema:
|
||||
type: object
|
||||
401:
|
||||
description: User need to log in first.
|
||||
403:
|
||||
description: User does not have permission of admin role.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
put:
|
||||
summary: Modify system configurations.
|
||||
description: |
|
||||
This endpoint is for modifying system configurations that only provides for admin user.
|
||||
tags:
|
||||
- Products
|
||||
parameters:
|
||||
- name: configurations
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
description: The configurations map need to be modified, the following are keys "auth_mode", "email_from", "email_host", "email_identity", "email_password", "email_port", "email_ssl", "email_username", "ldap_base_dn", "ldap_filter", "ldap_scope", "ldap_search_dn", "ldap_search_password", "ldap_timeout", "ldap_uid", "ldap_url", "project_creation_restriction", "self_registration", "verify_remote_cert".
|
||||
responses:
|
||||
200:
|
||||
description: Modify system configurations successfully.
|
||||
401:
|
||||
description: User need to log in first.
|
||||
403:
|
||||
description: User does not have permission of admin role.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/configurations/reset:
|
||||
post:
|
||||
summary: Reset system configurations.
|
||||
description: |
|
||||
Reset system configurations from environment variables. Can only be accessed by admin user.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Reset system configurations successfully.
|
||||
401:
|
||||
description: User need to log in first.
|
||||
403:
|
||||
description: User does not have permission of admin role.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/email/ping:
|
||||
post:
|
||||
summary: Test connection and authentication with email server.
|
||||
description: |
|
||||
Test connection and authentication with email server.
|
||||
parameters:
|
||||
- name: settings
|
||||
in: body
|
||||
description: Email server settings, if some of the settings are not assigned, they will be read from system configuration.
|
||||
required: false
|
||||
schema:
|
||||
$ref: '#/definitions/EmailServerSetting'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Ping email server successfully.
|
||||
400:
|
||||
description: Inviald email server settings.
|
||||
401:
|
||||
description: User need to login first.
|
||||
403:
|
||||
description: Only admin has this authority.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
definitions:
|
||||
Search:
|
||||
type: object
|
||||
@ -1318,26 +1602,12 @@ definitions:
|
||||
description: Search results of the projects that matched the filter keywords.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/SearchProject'
|
||||
$ref: '#/definitions/Project'
|
||||
repositories:
|
||||
description: Search results of the repositories that matched the filter keywords.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/SearchRepository'
|
||||
SearchProject:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The ID of project
|
||||
name:
|
||||
type: string
|
||||
description: The name of the project
|
||||
public:
|
||||
type: integer
|
||||
format: int
|
||||
description: The flag to indicate the publicity of the project (1 is public, 0 is non-public)
|
||||
SearchRepository:
|
||||
type: object
|
||||
properties:
|
||||
@ -1353,6 +1623,12 @@ definitions:
|
||||
repository_name:
|
||||
type: string
|
||||
description: The name of the repository
|
||||
pull_count:
|
||||
type: integer
|
||||
description: The count how many times the repository is pulled
|
||||
tags_count:
|
||||
type: integer
|
||||
description: The count of tags in the repository
|
||||
ProjectReq:
|
||||
type: object
|
||||
properties:
|
||||
@ -1403,7 +1679,7 @@ definitions:
|
||||
repo_count:
|
||||
type: integer
|
||||
description: The number of the repositories under this project.
|
||||
Repository:
|
||||
Manifest:
|
||||
type: object
|
||||
properties:
|
||||
manifest:
|
||||
@ -1522,7 +1798,7 @@ definitions:
|
||||
TopRepo:
|
||||
type: object
|
||||
properties:
|
||||
repo_name:
|
||||
name:
|
||||
type: string
|
||||
description: The name of the repo
|
||||
count:
|
||||
@ -1741,3 +2017,179 @@ definitions:
|
||||
comment:
|
||||
type: string
|
||||
description: The new comment.
|
||||
Storage:
|
||||
type: object
|
||||
properties:
|
||||
total:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Total volume size.
|
||||
free:
|
||||
type: integer
|
||||
format: int64
|
||||
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.
|
||||
has_ca_root:
|
||||
type: boolean
|
||||
description: Indicate whether there is a ca root cert file ready for download in the file system.
|
||||
harbor_version:
|
||||
type: string
|
||||
description: The build version of Harbor.
|
||||
SystemInfo:
|
||||
type: object
|
||||
properties:
|
||||
storage:
|
||||
type: array
|
||||
description: The storage of system.
|
||||
items:
|
||||
$ref: '#/definitions/Storage'
|
||||
LdapConf:
|
||||
type: object
|
||||
properties:
|
||||
ldap_url:
|
||||
type: string
|
||||
description: The url of ldap service.
|
||||
ldap_search_dn:
|
||||
type: string
|
||||
description: The search dn of ldap service.
|
||||
ldap_search_password:
|
||||
type: string
|
||||
description: The search password of ldap service.
|
||||
ldap_base_dn:
|
||||
type: string
|
||||
description: The base dn of ldap service.
|
||||
ldap_filter:
|
||||
type: string
|
||||
description: The serach filter of ldap service.
|
||||
ldap_uid:
|
||||
type: string
|
||||
description: The serach uid from ldap service attributes.
|
||||
ldap_scope:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The serach scope of ldap service.
|
||||
ldap_connection_timeout:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The connect timeout of ldap service(second).
|
||||
LdapUsers:
|
||||
type: object
|
||||
properties:
|
||||
ldap_username:
|
||||
type: string
|
||||
description: search ldap user name based on ldapconf.
|
||||
ldap_realname:
|
||||
type: string
|
||||
description: system will try to guess the user realname form "uid" or "cn" attribute.
|
||||
ldap_email:
|
||||
type: string
|
||||
description: system will try to guess the user email address form "mail" or "email" attribute.
|
||||
LdapImportUsers:
|
||||
type: object
|
||||
properties:
|
||||
ldap_uid_list:
|
||||
type: array
|
||||
description: selected uid list
|
||||
items:
|
||||
type: string
|
||||
LdapFailedImportUsers:
|
||||
type: object
|
||||
properties:
|
||||
ldap_uid:
|
||||
type: string
|
||||
description: the uid can't add to system.
|
||||
error:
|
||||
type: string
|
||||
description: fail reason.
|
||||
EmailServerSetting:
|
||||
type: object
|
||||
properties:
|
||||
email_host:
|
||||
type: string
|
||||
description: The host of email server.
|
||||
email_port:
|
||||
type: integer
|
||||
description: The port of email server.
|
||||
email_username:
|
||||
type: string
|
||||
description: The username of email server.
|
||||
email_password:
|
||||
type: string
|
||||
description: The password of email server.
|
||||
email_ssl:
|
||||
type: boolean
|
||||
description: Use ssl/tls or not.
|
||||
email_identity:
|
||||
type: string
|
||||
description: The dentity of email server.
|
||||
RepoSignature:
|
||||
type: object
|
||||
properties:
|
||||
tag:
|
||||
type: string
|
||||
description: The tag of image.
|
||||
hashes:
|
||||
type: object
|
||||
description: The JSON object of the hash of the image.
|
||||
DetailedTag:
|
||||
type: object
|
||||
properties:
|
||||
tag:
|
||||
type: string
|
||||
description: The tag of image.
|
||||
manifest:
|
||||
type: object
|
||||
description: The detail of manifest.
|
||||
Repository:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: The ID of repository.
|
||||
name:
|
||||
type: string
|
||||
description: The name of repository.
|
||||
owner_id:
|
||||
type: integer
|
||||
description: The owner ID of repository.
|
||||
project_id:
|
||||
type: integer
|
||||
description: The project ID of repository.
|
||||
description:
|
||||
type: string
|
||||
description: The description of repository.
|
||||
pull_count:
|
||||
type: integer
|
||||
description: The pull count of repository.
|
||||
star_count:
|
||||
type: integer
|
||||
description: The star count of repository.
|
||||
tags_count:
|
||||
type: integer
|
||||
description: The tags count of repository.
|
||||
creation_time:
|
||||
type: string
|
||||
description: The creation time of repository.
|
||||
update_time:
|
||||
type: string
|
||||
description: The update time of repository.
|
||||
|
51
docs/use_make.md
Normal file
51
docs/use_make.md
Normal file
@ -0,0 +1,51 @@
|
||||
### Variables
|
||||
Variable | Description
|
||||
-------------------|-------------
|
||||
BASEIMAGE | Container base image, default: photon
|
||||
DEVFLAG | Build model flag, default: dev
|
||||
COMPILETAG | Compile model flag, default: compile_normal (local golang build)
|
||||
GOBUILDIMAGE | Golang image to compile harbor go source code.
|
||||
CLARITYIMAGE | Clarity image that based on Node to compile UI.
|
||||
NOTARYFLAG | Whether to enable notary in harbor, default:false
|
||||
HTTPPROXY | Clarity proxy to build UI.
|
||||
|
||||
|
||||
### Targets
|
||||
Target | Description
|
||||
--------------------|-------------
|
||||
all | prepare env, compile binaries, build images and install images
|
||||
prepare | prepare env
|
||||
compile | compile ui and jobservice code
|
||||
compile_ui | compile ui binary
|
||||
compile_jobservice | compile jobservice binary
|
||||
compile_clarity | compile clarity ui binary
|
||||
compile_adminserver | compile admin server binary
|
||||
build | build Harbor docker images (default: using build_photon)
|
||||
build_photon | build Harbor docker images from Photon OS base image
|
||||
install | compile binaries, build images, prepare specific version of compose file and startup Harbor instance
|
||||
start | startup Harbor instance
|
||||
down | shutdown Harbor instance
|
||||
package_online | prepare online install package
|
||||
package_offline | prepare offline install package
|
||||
pushimage | push Harbor images to specific registry server
|
||||
clean all | remove binary, Harbor images, specific version docker-compose file, specific version tag and online/offline install package
|
||||
cleanbinary | remove ui and jobservice binary
|
||||
cleanimage | remove Harbor images
|
||||
cleandockercomposefile | remove specific version docker-compose
|
||||
cleanversiontag | remove specific version tag
|
||||
cleanpackage | remove online/offline install package
|
||||
version | set harbor version
|
||||
|
||||
#### EXAMPLE:
|
||||
|
||||
#### Build and run harbor from source code.
|
||||
make install GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:0.8.4 NOTARYFLAG=true HTTPPROXY=
|
||||
|
||||
### Package offline installer
|
||||
make package_offline GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:0.8.4 NOTARYFLAG=true HTTPPROXY=
|
||||
|
||||
### Start harbor with notary
|
||||
make -e NOTARYFLAG=true start
|
||||
|
||||
### Stop harbor with notary
|
||||
make -e NOTARYFLAG=true down
|
22
docs/use_notary.md
Normal file
22
docs/use_notary.md
Normal file
@ -0,0 +1,22 @@
|
||||
### Setup
|
||||
In harbor.cfg, make sure the attribute ```ui_url_protocol``` is set to ```https```, and the attributes ```ssl_cert``` and ```ssl_cert_key``` are pointed to valid certificates. For more information about generating https certificate please refer to: [Configuring HTTPS for Harbor](configure_https.md)
|
||||
|
||||
### Copy Root Certificate
|
||||
Suppose the Harbor instance is hosted on a machine ```192.168.0.5```
|
||||
If you are using a self-signed cetificate, make sure to copy the CA root cert to ```/etc/docker/certs.d/192.168.0.5/``` and ```~/.docker/tls/192.168.0.5:4443/```
|
||||
|
||||
### Enable Docker Content Trust
|
||||
It can be done via setting envrironment variables:
|
||||
|
||||
```
|
||||
export DOCKER_CONTENT_TRUST=1
|
||||
export DOCKER_CONTENT_TRUST_SERVER=https://192.168.0.5:4443
|
||||
```
|
||||
|
||||
### Set alias for notary (optional)
|
||||
Because by default the local directory for storing meta files for notary client is different from docker client. If you want to use notary client to manipulate the keys/meta files generated by Docker Content Trust, please set the alias to reduce the effort:
|
||||
|
||||
```
|
||||
alias notary="notary -s https//192.168.0.5:4443 -d ~/.docker/trust --tlscacert /etc/docker/certs.d/192.168.0.5/ca.crt"
|
||||
|
||||
```
|
@ -191,14 +191,14 @@ Run the below commands on the host which Harbor is deployed on to preview what f
|
||||
|
||||
```sh
|
||||
$ docker-compose stop
|
||||
$ docker run -it --name gc --rm --volumes-from registry registry:2.5.0 garbage-collect --dry-run /etc/registry/config.yml
|
||||
$ docker run -it --name gc --rm --volumes-from registry registry:2.6.0 garbage-collect --dry-run /etc/registry/config.yml
|
||||
```
|
||||
**NOTE:** The above option "--dry-run" will print the progress without removing any data.
|
||||
|
||||
Verify the result of the above test, then use the below commands to perform garbage collection and restart Harbor.
|
||||
|
||||
```sh
|
||||
$ docker run -it --name gc --rm --volumes-from registry registry:2.5.0 garbage-collect /etc/registry/config.yml
|
||||
$ docker run -it --name gc --rm --volumes-from registry registry:2.6.0 garbage-collect /etc/registry/config.yml
|
||||
$ docker-compose start
|
||||
```
|
||||
|
||||
|
39
make/common/templates/adminserver/env
Normal file
39
make/common/templates/adminserver/env
Normal file
@ -0,0 +1,39 @@
|
||||
LOG_LEVEL=debug
|
||||
EXT_ENDPOINT=$ui_url
|
||||
AUTH_MODE=$auth_mode
|
||||
SELF_REGISTRATION=$self_registration
|
||||
LDAP_URL=$ldap_url
|
||||
LDAP_SEARCH_DN=$ldap_searchdn
|
||||
LDAP_SEARCH_PWD=$ldap_search_pwd
|
||||
LDAP_BASE_DN=$ldap_basedn
|
||||
LDAP_FILTER=$ldap_filter
|
||||
LDAP_UID=$ldap_uid
|
||||
LDAP_SCOPE=$ldap_scope
|
||||
LDAP_TIMEOUT=$ldap_timeout
|
||||
DATABASE_TYPE=mysql
|
||||
MYSQL_HOST=mysql
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_USR=root
|
||||
MYSQL_PWD=$db_password
|
||||
MYSQL_DATABASE=registry
|
||||
REGISTRY_URL=http://registry:5000
|
||||
TOKEN_SERVICE_URL=http://ui/service/token
|
||||
EMAIL_HOST=$email_host
|
||||
EMAIL_PORT=$email_port
|
||||
EMAIL_USR=$email_usr
|
||||
EMAIL_PWD=$email_pwd
|
||||
EMAIL_SSL=$email_ssl
|
||||
EMAIL_FROM=$email_from
|
||||
EMAIL_IDENTITY=$email_identity
|
||||
HARBOR_ADMIN_PASSWORD=$harbor_admin_password
|
||||
PROJECT_CREATION_RESTRICTION=$project_creation_restriction
|
||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||
MAX_JOB_WORKERS=$max_job_workers
|
||||
UI_SECRET=$ui_secret
|
||||
JOBSERVICE_SECRET=$jobservice_secret
|
||||
TOKEN_EXPIRATION=$token_expiration
|
||||
CFG_EXPIRATION=5
|
||||
GODEBUG=netdns=cgo
|
||||
ADMIRAL_URL=$admiral_url
|
||||
WITH_NOTARY=$with_notary
|
||||
RESET=false
|
@ -1,15 +1,5 @@
|
||||
MYSQL_HOST=mysql
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_USR=root
|
||||
MYSQL_PWD=$db_password
|
||||
UI_SECRET=$ui_secret
|
||||
SECRET_KEY=$secret_key
|
||||
CONFIG_PATH=/etc/jobservice/app.conf
|
||||
REGISTRY_URL=http://registry:5000
|
||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||
MAX_JOB_WORKERS=$max_job_workers
|
||||
LOG_LEVEL=debug
|
||||
LOG_DIR=/var/log/jobs
|
||||
CONFIG_PATH=/etc/jobservice/app.conf
|
||||
UI_SECRET=$ui_secret
|
||||
JOBSERVICE_SECRET=$jobservice_secret
|
||||
GODEBUG=netdns=cgo
|
||||
EXT_ENDPOINT=$ui_url
|
||||
TOKEN_ENDPOINT=http://ui
|
||||
|
@ -21,6 +21,12 @@ http {
|
||||
server ui:80;
|
||||
}
|
||||
|
||||
log_format timed_combined '$$remote_addr - '
|
||||
'"$$request" $$status $$body_bytes_sent '
|
||||
'"$$http_referer" "$$http_user_agent" '
|
||||
'$$request_time $$upstream_response_time $$pipe';
|
||||
|
||||
access_log /dev/stdout timed_combined;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
@ -8,11 +8,11 @@ events {
|
||||
|
||||
http {
|
||||
tcp_nodelay on;
|
||||
include /etc/nginx/conf.d/*.upstream.conf;
|
||||
|
||||
# this is necessary for us to be able to disable request buffering in all cases
|
||||
proxy_http_version 1.1;
|
||||
|
||||
|
||||
upstream registry {
|
||||
server registry:5000;
|
||||
}
|
||||
@ -21,6 +21,14 @@ http {
|
||||
server ui:80;
|
||||
}
|
||||
|
||||
log_format timed_combined '$$remote_addr - '
|
||||
'"$$request" $$status $$body_bytes_sent '
|
||||
'"$$http_referer" "$$http_user_agent" '
|
||||
'$$request_time $$upstream_response_time $$pipe';
|
||||
|
||||
access_log /dev/stdout timed_combined;
|
||||
|
||||
include /etc/nginx/conf.d/*.server.conf;
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
|
33
make/common/templates/nginx/notary.server.conf
Normal file
33
make/common/templates/nginx/notary.server.conf
Normal file
@ -0,0 +1,33 @@
|
||||
server {
|
||||
listen 4443 ssl;
|
||||
|
||||
# ssl
|
||||
ssl_certificate $ssl_cert;
|
||||
ssl_certificate_key $ssl_cert_key;
|
||||
|
||||
# recommendations from https://raymii.org/s/tutorials/strong_ssl_security_on_nginx.html
|
||||
ssl_protocols tlsv1.1 tlsv1.2;
|
||||
ssl_ciphers '!aNULL:kECDH+AESGCM:ECDH+AESGCM:RSA+AESGCM:kECDH+AES:ECDH+AES:RSA+AES:';
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:ssl:10m;
|
||||
|
||||
# disable any limits to avoid http 413 for large image uploads
|
||||
client_max_body_size 0;
|
||||
|
||||
# required to avoid http 411: see issue #1486 (https://github.com/docker/docker/issues/1486)
|
||||
chunked_transfer_encoding on;
|
||||
|
||||
location /v2/ {
|
||||
proxy_pass http://notary-server/v2/;
|
||||
proxy_set_header Host $$http_host;
|
||||
proxy_set_header X-Real-IP $$remote_addr;
|
||||
proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
|
||||
|
||||
# When setting up Harbor behind other proxy, such as an Nginx instance, remove the below line if the proxy already has similar settings.
|
||||
proxy_set_header X-Forwarded-Proto $$scheme;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
}
|
||||
|
4
make/common/templates/nginx/notary.upstream.conf
Normal file
4
make/common/templates/nginx/notary.upstream.conf
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
upstream notary-server {
|
||||
server notary-server:4443;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
CREATE DATABASE IF NOT EXISTS `notaryserver`;
|
||||
|
||||
CREATE USER "server"@"notary-server.%" IDENTIFIED BY "";
|
||||
|
||||
GRANT
|
||||
ALL PRIVILEGES ON `notaryserver`.*
|
||||
TO "server"@"notary-server.%"
|
@ -0,0 +1,7 @@
|
||||
CREATE DATABASE IF NOT EXISTS `notarysigner`;
|
||||
|
||||
CREATE USER "signer"@"notary-signer.%" IDENTIFIED BY "";
|
||||
|
||||
GRANT
|
||||
ALL PRIVILEGES ON `notarysigner`.*
|
||||
TO "signer"@"notary-signer.%";
|
32
make/common/templates/notary/notary-signer-ca.crt
Normal file
32
make/common/templates/notary/notary-signer-ca.crt
Normal file
@ -0,0 +1,32 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFhjCCA26gAwIBAgIJALJdsE+BUxypMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV
|
||||
BAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0G
|
||||
A1UECgwGRG9ja2VyMRowGAYDVQQDDBFOb3RhcnkgVGVzdGluZyBDQTAeFw0xNzAx
|
||||
MjMwNjAzMzZaFw0yNzAxMjEwNjAzMzZaMF8xCzAJBgNVBAYTAlVTMQswCQYDVQQI
|
||||
DAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0GA1UECgwGRG9ja2VyMRow
|
||||
GAYDVQQDDBFOb3RhcnkgVGVzdGluZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
|
||||
ADCCAgoCggIBALIZNBcIoQDJql5w+XULXq9W3tmD47xnf+IG4u7hkDVPCT4xRG74
|
||||
LBoSuFyPUrfT+tsibMlNG6XRtSfLQdNNeQuyIuiilNXV0kXB0RR3TrhxCaKdhRU5
|
||||
oQGfpYMvbPNFB7WU/5aAiQutHH85hEMPECf1qPjq8YlUaXJLGFY3WRkW+OOBZ78U
|
||||
00PqKlvC1kR/NbsV3IkMrO+vWWJQrPFusyYjQ511eQXnRtt8P0Qic0azPffQDVxC
|
||||
WUe47hmdQ1AULbxQ9AZcPlMI7UFqo+/w/4hPEGJMeOWirLvHLXg4nsOwy7DfWl/n
|
||||
MqLdJOC/KNfQVAQtkteeZZkkIIV1gxTPYsJqPNwkP9GdJK1A8NW1ef75v7xbQCPY
|
||||
03QQonBEK7ny7b1xXGGgJzXvK9RP0UUwjt/815c4d0cgUHsy4yuvl2F44EObRshk
|
||||
fjJVsN/0wrtq4QLE5ZvbeO+7to8dLcRxkmB8axhxahega7akUyY0WxZ+iSn6fzft
|
||||
/xeCcs/L10V5z0kK4PbiNnooDzV4B6Dy/5oyNExw0jgpD0mzOK5aLb0tXGqFT/ZJ
|
||||
9vydelBq5q4jLV7SHhHM1dBJSv1fl7vOpDlEr7LBd4YAO2BowoyGLHtLhgYybXF+
|
||||
CZ9ywPb1dIIcdK5IVeZECNHMSBuhCRZUu+aun8tRcdSgLEX7mQ/GKWELAgMBAAGj
|
||||
RTBDMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgFGMB0GA1UdDgQW
|
||||
BBSWWbcCebeEgZlWk2/k+abh+bEFpDANBgkqhkiG9w0BAQsFAAOCAgEAQ9gA3Q4b
|
||||
r2+ZJdIDoDzCNdtHQbb/d1NiUP/Na1MFo7omR3MnKGXy3dIp9IrQq6ROhlqUhDvl
|
||||
pZegYhTbunTVv1KKJ+5n1hY6pG/Jr8oLY3b9i4qwDLKfQGm5PmrfwAtqbLSfY2M0
|
||||
2AZyAhCdGbqB7WpTdG1J7DzGbVVWAtS05e24Mu0qZJvpHdtl4+t89vXgJ/bPrPxF
|
||||
cpAlT9DOtobTEqrXZeS937F1qNyIgyBki+7mtxkwng5cf3zQM2BJ9lSFQJOBSRDr
|
||||
haMcnaPI4pknO7OfYf5W9LaS1Dx/U/NeMBfnVBd9NjUw+TMjy2MdMLUaLa9EF7Jo
|
||||
Gjk+fKaTaUgO8I487wHPMeoEA4A4dEePzGrybRLfl1ZYGQ0xcgunz64n2xfQIy2y
|
||||
swiyaofYlLxzHzOL0N+Y76P0ic37t9R2F5ggNhfbXhClK2h4HmdjRRRt3VkxR4AD
|
||||
7OM09bEhlZby34HOlCaC0PHKwYBMjneAG3ycPN88YTMYR2/KizExe71ayNwX2KHL
|
||||
ib1nOZgZT6s+YvgsZ7lRmMD4iqjuAEh5SRAcWlolVif8bAy09BkY1vwrtgV73q88
|
||||
heEbsCE1fsfk1OfH5W4yjjiSDZFRt5oTCPQWJp+2P0RJ9LCxcbf0RrCg3hg5rD9N
|
||||
lVTA0dsixv5zF3wTuad9inhk9Rmlq1KoaqA=
|
||||
-----END CERTIFICATE-----
|
32
make/common/templates/notary/notary-signer.crt
Normal file
32
make/common/templates/notary/notary-signer.crt
Normal file
@ -0,0 +1,32 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFdjCCA14CCQCeVwANSZmmiDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC
|
||||
VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCVBhbG8gQWx0bzEVMBMG
|
||||
A1UECgwMVk13YXJlLCBJbmMuMQ8wDQYDVQQLDAZIYXJib3IxJDAiBgNVBAMMG1Nl
|
||||
bGYtc2lnbmVkIGJ5IFZNd2FyZSwgSW5jLjAeFw0xNzAzMjQwNTMyMDBaFw0yNzAz
|
||||
MjIwNTMyMDBaMHUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIw
|
||||
EAYDVQQHDAlQYWxvIEFsdG8xFTATBgNVBAoMDFZNd2FyZSwgSW5jLjEPMA0GA1UE
|
||||
CwwGSGFyYm9yMRUwEwYDVQQDDAxub3RhcnlzaWduZXIwggIiMA0GCSqGSIb3DQEB
|
||||
AQUAA4ICDwAwggIKAoICAQC6TV2RCoH8d1g6xFvDo4FL9v+pGLe5+bu9ryjTaLbN
|
||||
dH/Cmf5/8WrmgJ3vG2Ksk796J7qsVddwvQkZn6NwDm2Tm+ETMCG85yEA3jl4Kr9R
|
||||
XfWHYWEavv0vsq6M+bUSSq7VJAhgk4wfx6qJBnFX2qKpODeYLHaHxU1EnIXrStNf
|
||||
IqR4Eu0Xre8jAkzrDdaFy/KnX4HGgNdz413CXzBCKEuu3VJj07ZvonnTzOgoLvh8
|
||||
+PCoQ2M4OBPT9gHqUov1I8nWnrjc+HuM1BW3YIGCB5TV9x0Y7hjvkr4E38gbJURj
|
||||
uDwg8jof4lMRmU/FHXFLt1ucGwNFUJdPwI7dyEKRA03Lr7htfP5sa9tmv3L93dKD
|
||||
po1gW1LsfiM3Cur5jARM/hBA+eYJr12Laf9oL59r8JmweqF3zRSwGSY336XoR/Fv
|
||||
/PAFs9vfKKWZp0uiRtuY9JZNRTF8trnfNf1957bND+DS2HWPmWkw4yK6CGa0s55X
|
||||
adiDt4gDFvKjl68dBWZoHutY+cZy/hK1D5uqagcX1kzbr/Pzy1gsq9FBBwaTJqBu
|
||||
YIAsSuzP+7NNZXoPd3rg13V93pbZr8eQN5VOQIBZK83xZEtHSJBEdUSuBOo3JS7j
|
||||
/rjEnspRqOI4soFnx1vaK0TrRyzJ5KBOuGpW4u8/ZUdIq8KIE30Mj/XI/sgAPr5j
|
||||
UQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBjqYBm/FRqyMH2hnHA0TMXY/WPufJ8
|
||||
TX10daELCAYJCEETXmUt1i7dnFxdAZXTnHENHdNYiS4nGBfqMLmODtcAamcv6Dcl
|
||||
JnyQPt3QlCDPKkcHgz3y4tvDDx6M5rFWYzN9QLiWAYrunIk1R4Jj7FODrM6/NODE
|
||||
0Mz1czWfsmLfX/jF80SsxnY1DCLKGgo6/RID3xTp4eIMboxCfeH2/yDA+6YPyYbV
|
||||
Si4ccwo9Foq0IYU8bimPNTyBQ0N+8ajcn328ql6aazmr894Ch5pWA3Qxaa98FcKS
|
||||
zokBvmmCuvCJ9HOmxKWdFEhSRS9GWxn7wg78UIlLP/8RfUrsecBJHgyhWRA7Qs3K
|
||||
keiG68Zrhn456IdMxjCZXgJ7gAAe77n4Cz8sFEHAvnAg9JLNEHuEBV5H1Hb7TzET
|
||||
k0lPiEY78QjutOpqHsWiagqSjlGEMqKI9c8WxXHh9030T/6NnWkdXFo+4HaEZEpp
|
||||
0JryASS53B5SwLIPrn0Y2/io/kRgbglGktPt6Ex0DwW3f96lcz3me34Nw+HOYYnz
|
||||
b0cz7JqJZgFXfEnykic3IwZs7m7Xrl9B/vvaVub9Fb5LQ7rIzrO7VkoILov/G41B
|
||||
Pd4/kagjXDTWd+UBMvZF6YGjr+TUZi5ooi7bvQ3X6N9WNYKW4a1DOokz9janStiL
|
||||
MrTKyOEOBi0Aew==
|
||||
-----END CERTIFICATE-----
|
52
make/common/templates/notary/notary-signer.key
Normal file
52
make/common/templates/notary/notary-signer.key
Normal file
@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC6TV2RCoH8d1g6
|
||||
xFvDo4FL9v+pGLe5+bu9ryjTaLbNdH/Cmf5/8WrmgJ3vG2Ksk796J7qsVddwvQkZ
|
||||
n6NwDm2Tm+ETMCG85yEA3jl4Kr9RXfWHYWEavv0vsq6M+bUSSq7VJAhgk4wfx6qJ
|
||||
BnFX2qKpODeYLHaHxU1EnIXrStNfIqR4Eu0Xre8jAkzrDdaFy/KnX4HGgNdz413C
|
||||
XzBCKEuu3VJj07ZvonnTzOgoLvh8+PCoQ2M4OBPT9gHqUov1I8nWnrjc+HuM1BW3
|
||||
YIGCB5TV9x0Y7hjvkr4E38gbJURjuDwg8jof4lMRmU/FHXFLt1ucGwNFUJdPwI7d
|
||||
yEKRA03Lr7htfP5sa9tmv3L93dKDpo1gW1LsfiM3Cur5jARM/hBA+eYJr12Laf9o
|
||||
L59r8JmweqF3zRSwGSY336XoR/Fv/PAFs9vfKKWZp0uiRtuY9JZNRTF8trnfNf19
|
||||
57bND+DS2HWPmWkw4yK6CGa0s55XadiDt4gDFvKjl68dBWZoHutY+cZy/hK1D5uq
|
||||
agcX1kzbr/Pzy1gsq9FBBwaTJqBuYIAsSuzP+7NNZXoPd3rg13V93pbZr8eQN5VO
|
||||
QIBZK83xZEtHSJBEdUSuBOo3JS7j/rjEnspRqOI4soFnx1vaK0TrRyzJ5KBOuGpW
|
||||
4u8/ZUdIq8KIE30Mj/XI/sgAPr5jUQIDAQABAoICAQCqIgbFcqwcK7zWBgWrFsD3
|
||||
53u4J4t4+df6NGB7F9CAtdgKlej1XDl8gI46Em89HLwqyOdPhCD3opoR3Vg69+IX
|
||||
f62+gSD+SrA4A7jFxXvryXt0g3hTHYFHssx2j39NUghxOrOvxm6bgxJ4ifqt+Uq8
|
||||
cEtM26Xu/T4/3xTpN+7pnVBHGzmLe1q8RNiLe5qhmwtgz/ZKmdSnz0YLQDRo5jWf
|
||||
Xhxkb63WKrFIu4JzV9my/v9/GfMdHxD0a196ZqHLX0Buj4pQuVbS18dxLF94qIXC
|
||||
FCZtYtpAxmhjOR2btJ/M1S2MBMkR3vRvSOuxHd8d/zdYys5k2WElArs1TDGGDldW
|
||||
jp3FYkoygsdWTs056HM1Y9F8dV2KAWfAhEQD8mBIGVjMrCqpnyZcK6JkqVg9c7YW
|
||||
IYQ2JRwsHq58FMNa3TLTvf/OClhEfSbRWAF0AhMTpnSUgP06cbJeXyzqzHdE37hv
|
||||
74OBx7KNoS+PEQ3lVgbHsWoUzf3SqB1IOzLyzuEUgHqON2GKmmCNcRMBi3DuV9tw
|
||||
Q8LWynNxhD8vyBkmo0kAd/FwgXrxJTGdYvxyn29I7QanCTH7o8wtjSE0jj9Qo7oC
|
||||
McAYGR6oTAjrT78KhI7aZJU5nuA6ySSCJRa6et1CC+SseWknyMMJ5HTo8l7jjXJA
|
||||
9hjNGGs6giOxznizf+2YAQKCAQEA9wRQk4yN402tfuicvfQBnFUtcpqctWSgGc0T
|
||||
qzWJgH/W07FMUHzAvqCgsYMMaeteXOMZH7jijvtIlhYfIg5w+RJ9PSsSu680OzGN
|
||||
R31+l2B/QzRAHUJ6+OVgWxAn6awU1mYLaiwVmSNWEnjAPE4XeSK708OOganI3pBQ
|
||||
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-----
|
28
make/common/templates/notary/server-config.json
Normal file
28
make/common/templates/notary/server-config.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"server": {
|
||||
"http_addr": ":4443"
|
||||
},
|
||||
"trust_service": {
|
||||
"type": "remote",
|
||||
"hostname": "notarysigner",
|
||||
"port": "7899",
|
||||
"tls_ca_file": "./notary-signer-ca.crt",
|
||||
"key_algorithm": "ecdsa"
|
||||
},
|
||||
"logging": {
|
||||
"level": "debug"
|
||||
},
|
||||
"storage": {
|
||||
"backend": "mysql",
|
||||
"db_url": "server@tcp(mysql:3306)/notaryserver?parseTime=True"
|
||||
},
|
||||
"auth": {
|
||||
"type": "token",
|
||||
"options": {
|
||||
"realm": "$token_endpoint/service/token",
|
||||
"service": "harbor-notary",
|
||||
"issuer": "harbor-token-issuer",
|
||||
"rootcertbundle": "/config/root.crt"
|
||||
}
|
||||
}
|
||||
}
|
15
make/common/templates/notary/signer-config.json
Normal file
15
make/common/templates/notary/signer-config.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"server": {
|
||||
"grpc_addr": ":7899",
|
||||
"tls_cert_file": "./notary-signer.crt",
|
||||
"tls_key_file": "./notary-signer.key"
|
||||
},
|
||||
"logging": {
|
||||
"level": "debug"
|
||||
},
|
||||
"storage": {
|
||||
"backend": "mysql",
|
||||
"db_url": "signer@tcp(mysql:3306)/notarysigner?parseTime=True",
|
||||
"default_alias":"defaultalias"
|
||||
}
|
||||
}
|
2
make/common/templates/notary/signer_env
Normal file
2
make/common/templates/notary/signer_env
Normal file
@ -0,0 +1,2 @@
|
||||
NOTARY_SIGNER_DEFAULTALIAS=$alias
|
||||
|
@ -20,10 +20,10 @@ http:
|
||||
addr: localhost:5001
|
||||
auth:
|
||||
token:
|
||||
issuer: registry-token-issuer
|
||||
issuer: harbor-token-issuer
|
||||
realm: $ui_url/service/token
|
||||
rootcertbundle: /etc/registry/root.crt
|
||||
service: token-service
|
||||
service: harbor-registry
|
||||
|
||||
notifications:
|
||||
endpoints:
|
||||
|
@ -7,12 +7,3 @@ names = en-US|zh-CN
|
||||
|
||||
[dev]
|
||||
httpport = 80
|
||||
|
||||
[mail]
|
||||
identity = $email_identity
|
||||
host = $email_server
|
||||
port = $email_server_port
|
||||
username = $email_username
|
||||
password = $email_password
|
||||
from = $email_from
|
||||
ssl = $email_ssl
|
||||
|
@ -1,29 +1,5 @@
|
||||
MYSQL_HOST=mysql
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_USR=root
|
||||
MYSQL_PWD=$db_password
|
||||
REGISTRY_URL=http://registry:5000
|
||||
JOB_SERVICE_URL=http://jobservice
|
||||
UI_URL=http://ui
|
||||
CONFIG_PATH=/etc/ui/app.conf
|
||||
EXT_REG_URL=$hostname
|
||||
HARBOR_ADMIN_PASSWORD=$harbor_admin_password
|
||||
AUTH_MODE=$auth_mode
|
||||
LDAP_URL=$ldap_url
|
||||
LDAP_SEARCH_DN=$ldap_searchdn
|
||||
LDAP_SEARCH_PWD=$ldap_search_pwd
|
||||
LDAP_BASE_DN=$ldap_basedn
|
||||
LDAP_FILTER=$ldap_filter
|
||||
LDAP_UID=$ldap_uid
|
||||
LDAP_SCOPE=$ldap_scope
|
||||
UI_SECRET=$ui_secret
|
||||
SECRET_KEY=$secret_key
|
||||
SELF_REGISTRATION=$self_registration
|
||||
USE_COMPRESSED_JS=$use_compressed_js
|
||||
LOG_LEVEL=debug
|
||||
CONFIG_PATH=/etc/ui/app.conf
|
||||
UI_SECRET=$ui_secret
|
||||
JOBSERVICE_SECRET=$jobservice_secret
|
||||
GODEBUG=netdns=cgo
|
||||
EXT_ENDPOINT=$ui_url
|
||||
TOKEN_ENDPOINT=http://ui
|
||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||
TOKEN_EXPIRATION=$token_expiration
|
||||
PROJECT_CREATION_RESTRICTION=$project_creation_restriction
|
||||
|
12
make/dev/adminserver/Dockerfile
Normal file
12
make/dev/adminserver/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
||||
FROM golang:1.7.3
|
||||
|
||||
MAINTAINER yinw@vmware.com
|
||||
|
||||
COPY . /go/src/github.com/vmware/harbor
|
||||
|
||||
WORKDIR /go/src/github.com/vmware/harbor/src/adminserver
|
||||
|
||||
RUN go build -v -a -o /go/bin/harbor_adminserver \
|
||||
&& chmod u+x /go/bin/harbor_adminserver
|
||||
WORKDIR /go/bin/
|
||||
ENTRYPOINT ["/go/bin/harbor_adminserver"]
|
7
make/dev/docker-compose-clarity.yml
Normal file
7
make/dev/docker-compose-clarity.yml
Normal file
@ -0,0 +1,7 @@
|
||||
version: '2'
|
||||
services:
|
||||
nodeclarity:
|
||||
image : danieljt/harbor-clarity-base:0.8.1
|
||||
volumes:
|
||||
- ../../src/ui/static/new-ui:/clarity-seed/dist
|
||||
- ../../src/ui_ng:/clarity-seed
|
@ -3,14 +3,14 @@ services:
|
||||
log:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: make/ubuntu/log/Dockerfile
|
||||
dockerfile: make/photon/log/Dockerfile
|
||||
restart: always
|
||||
volumes:
|
||||
- /var/log/harbor/:/var/log/docker/
|
||||
ports:
|
||||
- 1514:514
|
||||
registry:
|
||||
image: library/registry:2.5.0
|
||||
image: library/registry:2.6.0
|
||||
restart: always
|
||||
volumes:
|
||||
- /data/registry:/storage
|
||||
@ -40,6 +40,24 @@ services:
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "mysql"
|
||||
adminserver:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: make/dev/adminserver/Dockerfile
|
||||
env_file:
|
||||
- ../common/config/adminserver/env
|
||||
restart: always
|
||||
volumes:
|
||||
- /data/config/:/etc/adminserver/
|
||||
- /data/secretkey:/etc/adminserver/key
|
||||
- /data/:/data/
|
||||
depends_on:
|
||||
- log
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "adminserver"
|
||||
ui:
|
||||
build:
|
||||
context: ../../
|
||||
@ -50,8 +68,12 @@ services:
|
||||
volumes:
|
||||
- ../common/config/ui/app.conf:/etc/ui/app.conf
|
||||
- ../common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
||||
- /data/secretkey:/etc/ui/key
|
||||
- /data/ca_download/:/etc/ui/ca/
|
||||
depends_on:
|
||||
- log
|
||||
- adminserver
|
||||
- registry
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
@ -67,8 +89,10 @@ services:
|
||||
volumes:
|
||||
- /data/job_logs:/var/log/jobs
|
||||
- ../common/config/jobservice/app.conf:/etc/jobservice/app.conf
|
||||
- /data/secretkey:/etc/jobservice/key
|
||||
depends_on:
|
||||
- ui
|
||||
- adminserver
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
|
@ -1,11 +1,7 @@
|
||||
FROM golang:1.6.2
|
||||
FROM golang:1.7.3
|
||||
|
||||
MAINTAINER jiangd@vmware.com
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y libldap2-dev \
|
||||
&& rm -r /var/lib/apt/lists/*
|
||||
|
||||
COPY . /go/src/github.com/vmware/harbor
|
||||
|
||||
WORKDIR /go/src/github.com/vmware/harbor/src/jobservice
|
||||
|
18
make/dev/nodeclarity/Dockerfile
Normal file
18
make/dev/nodeclarity/Dockerfile
Normal file
@ -0,0 +1,18 @@
|
||||
FROM node:7.5.0
|
||||
|
||||
RUN mkdir -p /clarity-seed
|
||||
|
||||
COPY src/ui_ng/package.json /clarity-seed
|
||||
COPY src/ui_ng/tslint.json /clarity-seed
|
||||
COPY src/ui_ng/typings.json /clarity-seed
|
||||
COPY src/ui_ng/yarn.lock /clarity-seed
|
||||
COPY make/dev/nodeclarity/angular-cli.json /clarity-seed
|
||||
COPY make/dev/nodeclarity/entrypoint.sh /
|
||||
|
||||
WORKDIR /clarity-seed
|
||||
|
||||
RUN npm install -g @angular/cli && \
|
||||
npm install && \
|
||||
chmod u+x /entrypoint.sh
|
||||
|
||||
VOLUME ["/clarity-seed", "/clarity-seed/dist"]
|
67
make/dev/nodeclarity/angular-cli.json
Normal file
67
make/dev/nodeclarity/angular-cli.json
Normal file
@ -0,0 +1,67 @@
|
||||
{
|
||||
"project": {
|
||||
"version": "1.0.0-beta.20-4",
|
||||
"name": "clarity-seed"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"root": "src",
|
||||
"outDir": "dist",
|
||||
"assets": [
|
||||
"images",
|
||||
"favicon.ico"
|
||||
],
|
||||
"index": "index.html",
|
||||
"main": "main.ts",
|
||||
"test": "test.ts",
|
||||
"tsconfig": "tsconfig.json",
|
||||
"prefix": "app",
|
||||
"mobile": false,
|
||||
"styles": [
|
||||
"../node_modules/clarity-icons/clarity-icons.min.css",
|
||||
"../node_modules/clarity-ui/clarity-ui.min.css",
|
||||
"styles.css"
|
||||
],
|
||||
"scripts": [
|
||||
"../node_modules/core-js/client/shim.min.js",
|
||||
"../node_modules/mutationobserver-shim/dist/mutationobserver.min.js",
|
||||
"../node_modules/@webcomponents/custom-elements/custom-elements.min.js",
|
||||
"../node_modules/clarity-icons/clarity-icons.min.js",
|
||||
"../node_modules/web-animations-js/web-animations.min.js"
|
||||
],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
"dev": "environments/environment.ts",
|
||||
"prod": "environments/environment.prod.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"addons": [],
|
||||
"packages": [],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "./protractor.config.js"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "./karma.conf.js"
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"styleExt": "scss",
|
||||
"prefixInterfaces": false,
|
||||
"inline": {
|
||||
"style": false,
|
||||
"template": false
|
||||
},
|
||||
"spec": {
|
||||
"class": false,
|
||||
"component": true,
|
||||
"directive": true,
|
||||
"module": false,
|
||||
"pipe": true,
|
||||
"service": true
|
||||
}
|
||||
}
|
||||
}
|
25
make/dev/nodeclarity/entrypoint.sh
Normal file
25
make/dev/nodeclarity/entrypoint.sh
Normal file
@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd /clarity-seed
|
||||
rm -rf dist/*
|
||||
|
||||
npm_proxy=
|
||||
|
||||
while getopts p: option
|
||||
do
|
||||
case "${option}"
|
||||
in
|
||||
p) npm_proxy=${OPTARG};;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ! -z "$npm_proxy" -a "$npm_proxy" != " " ]; then
|
||||
npm config set proxy $npm_proxy
|
||||
fi
|
||||
|
||||
npm install
|
||||
ng build
|
||||
|
||||
cp -r ./src/i18n/ dist/
|
||||
|
13
make/dev/nodeclarity/index.html
Normal file
13
make/dev/nodeclarity/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Clarity Seed App</title>
|
||||
<base href="/ng">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico?v=2">
|
||||
</head>
|
||||
<body>
|
||||
<harbor-app>Loading...</harbor-app>
|
||||
<script type="text/javascript" src="/ng/inline.bundle.js"></script><script type="text/javascript" src="/ng/scripts.bundle.js"></script><script type="text/javascript" src="/ng/styles.bundle.js"></script><script type="text/javascript" src="/ng/vendor.bundle.js"></script><script type="text/javascript" src="/ng/main.bundle.js"></script></body>
|
||||
</html>
|
@ -1,11 +1,7 @@
|
||||
FROM golang:1.6.2
|
||||
FROM golang:1.7.3
|
||||
|
||||
MAINTAINER jiangd@vmware.com
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y libldap2-dev \
|
||||
&& rm -r /var/lib/apt/lists/*
|
||||
|
||||
COPY src/. /go/src/github.com/vmware/harbor/src
|
||||
WORKDIR /go/src/github.com/vmware/harbor/src/ui
|
||||
|
||||
@ -18,14 +14,8 @@ ENV MYSQL_USR root \
|
||||
COPY src/ui/views /go/bin/views
|
||||
COPY src/ui/static /go/bin/static
|
||||
COPY src/favicon.ico /go/bin/favicon.ico
|
||||
COPY make/jsminify.sh /tmp/jsminify.sh
|
||||
|
||||
RUN chmod u+x /go/bin/harbor_ui \
|
||||
&& sed -i 's/TLS_CACERT/#TLS_CAERT/g' /etc/ldap/ldap.conf \
|
||||
&& sed -i '$a\TLS_REQCERT allow' /etc/ldap/ldap.conf \
|
||||
&& timestamp=`date '+%s'` \
|
||||
&& /tmp/jsminify.sh /go/bin/views/sections/script-include.htm /go/bin/static/resources/js/harbor.app.min.$timestamp.js /go/bin/ \
|
||||
&& sed -i "s/harbor\.app\.min\.js/harbor\.app\.min\.$timestamp\.js/g" /go/bin/views/sections/script-min-include.htm
|
||||
RUN chmod u+x /go/bin/harbor_ui
|
||||
|
||||
WORKDIR /go/bin/
|
||||
ENTRYPOINT ["/go/bin/harbor_ui"]
|
||||
|
9
make/docker-compose-clarity.yml
Normal file
9
make/docker-compose-clarity.yml
Normal file
@ -0,0 +1,9 @@
|
||||
version: '2'
|
||||
services:
|
||||
nodeclarity:
|
||||
image : danieljt/harbor-clarity-base:0.8.0
|
||||
volumes:
|
||||
- ../src/ui/static/new-ui:/clarity-seed/dist
|
||||
- ../src/ui_ng/src/app:/clarity-seed/src/app
|
||||
depends_on:
|
||||
- ui
|
76
make/docker-compose.notary.yml
Normal file
76
make/docker-compose.notary.yml
Normal file
@ -0,0 +1,76 @@
|
||||
version: '2'
|
||||
services:
|
||||
ui:
|
||||
networks:
|
||||
- harbor-notary
|
||||
proxy:
|
||||
networks:
|
||||
- harbor-notary
|
||||
notary-server:
|
||||
image: vmware/notary-photon:server-0.5.0
|
||||
container_name: notary-server
|
||||
networks:
|
||||
- notary-mdb
|
||||
- notary-sig
|
||||
- harbor-notary
|
||||
volumes:
|
||||
- ./common/config/notary:/config
|
||||
entrypoint: /usr/bin/env sh
|
||||
command: -c "/migrations/migrate.sh && notary-server -config=/config/server-config.json -logf=logfmt"
|
||||
depends_on:
|
||||
- notary-db
|
||||
- notary-signer
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "notary-server"
|
||||
notary-signer:
|
||||
image: vmware/notary-photon:signer-0.5.0
|
||||
container_name: notary-signer
|
||||
networks:
|
||||
notary-mdb:
|
||||
notary-sig:
|
||||
aliases:
|
||||
- notarysigner
|
||||
volumes:
|
||||
- ./common/config/notary:/config
|
||||
env_file:
|
||||
- ./common/config/notary/signer_env
|
||||
entrypoint: /usr/bin/env sh
|
||||
command: -c "/migrations/migrate.sh && notary-signer -config=/config/signer-config.json -logf=logfmt"
|
||||
depends_on:
|
||||
- notary-db
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "notary-signer"
|
||||
notary-db:
|
||||
image: vmware/harbor-notary-db:mariadb-10.1.10
|
||||
container_name: notary-db
|
||||
networks:
|
||||
notary-mdb:
|
||||
aliases:
|
||||
- mysql
|
||||
volumes:
|
||||
- ./common/config/notary/mysql-initdb.d:/docker-entrypoint-initdb.d
|
||||
- /data/notary-db:/var/lib/mysql
|
||||
environment:
|
||||
- TERM=dumb
|
||||
- MYSQL_ALLOW_EMPTY_PASSWORD="true"
|
||||
command: mysqld --innodb_file_per_table
|
||||
depends_on:
|
||||
- log
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "notary-db"
|
||||
networks:
|
||||
harbor-notary:
|
||||
external: false
|
||||
notary-mdb:
|
||||
external: false
|
||||
notary-sig:
|
||||
external: false
|
@ -1,20 +1,24 @@
|
||||
version: '2'
|
||||
services:
|
||||
log:
|
||||
image: vmware/harbor-log
|
||||
image: vmware/harbor-log:__version__
|
||||
container_name: harbor-log
|
||||
restart: always
|
||||
volumes:
|
||||
- /var/log/harbor/:/var/log/docker/:z
|
||||
ports:
|
||||
- 1514:514
|
||||
networks:
|
||||
- harbor
|
||||
registry:
|
||||
image: library/registry:2.5.0
|
||||
image: vmware/registry:photon-2.6.0
|
||||
container_name: registry
|
||||
restart: always
|
||||
volumes:
|
||||
- /data/registry:/storage:z
|
||||
- ./common/config/registry/:/etc/registry/:z
|
||||
networks:
|
||||
- harbor
|
||||
environment:
|
||||
- GODEBUG=netdns=cgo
|
||||
command:
|
||||
@ -27,11 +31,13 @@ services:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "registry"
|
||||
mysql:
|
||||
image: vmware/harbor-db
|
||||
image: vmware/harbor-db:__version__
|
||||
container_name: harbor-db
|
||||
restart: always
|
||||
volumes:
|
||||
- /data/database:/var/lib/mysql:z
|
||||
networks:
|
||||
- harbor
|
||||
env_file:
|
||||
- ./common/config/db/env
|
||||
depends_on:
|
||||
@ -41,8 +47,27 @@ services:
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "mysql"
|
||||
adminserver:
|
||||
image: vmware/harbor-adminserver:__version__
|
||||
container_name: harbor-adminserver
|
||||
env_file:
|
||||
- ./common/config/adminserver/env
|
||||
restart: always
|
||||
volumes:
|
||||
- /data/config/:/etc/adminserver/
|
||||
- /data/secretkey:/etc/adminserver/key
|
||||
- /data/:/data/
|
||||
networks:
|
||||
- harbor
|
||||
depends_on:
|
||||
- log
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "adminserver"
|
||||
ui:
|
||||
image: vmware/harbor-ui
|
||||
image: vmware/harbor-ui:__version__
|
||||
container_name: harbor-ui
|
||||
env_file:
|
||||
- ./common/config/ui/env
|
||||
@ -50,16 +75,21 @@ services:
|
||||
volumes:
|
||||
- ./common/config/ui/app.conf:/etc/ui/app.conf:z
|
||||
- ./common/config/ui/private_key.pem:/etc/ui/private_key.pem:z
|
||||
- /data:/harbor_storage:z
|
||||
- /data/secretkey:/etc/ui/key:z
|
||||
- /data/ca_download/:/etc/ui/ca/:z
|
||||
networks:
|
||||
- harbor
|
||||
depends_on:
|
||||
- log
|
||||
- adminserver
|
||||
- registry
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "ui"
|
||||
jobservice:
|
||||
image: vmware/harbor-jobservice
|
||||
image: vmware/harbor-jobservice:__version__
|
||||
container_name: harbor-jobservice
|
||||
env_file:
|
||||
- ./common/config/jobservice/env
|
||||
@ -67,8 +97,12 @@ services:
|
||||
volumes:
|
||||
- /data/job_logs:/var/log/jobs:z
|
||||
- ./common/config/jobservice/app.conf:/etc/jobservice/app.conf:z
|
||||
- /data/secretkey:/etc/jobservice/key:z
|
||||
networks:
|
||||
- harbor
|
||||
depends_on:
|
||||
- ui
|
||||
- adminserver
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
@ -80,9 +114,12 @@ services:
|
||||
restart: always
|
||||
volumes:
|
||||
- ./common/config/nginx:/etc/nginx:z
|
||||
networks:
|
||||
- harbor
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
- 4443:4443
|
||||
depends_on:
|
||||
- mysql
|
||||
- registry
|
||||
@ -93,3 +130,7 @@ services:
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "proxy"
|
||||
networks:
|
||||
harbor:
|
||||
external: false
|
||||
|
||||
|
@ -8,6 +8,33 @@ hostname = reg.mydomain.com
|
||||
#It can be set to https if ssl is enabled on nginx.
|
||||
ui_url_protocol = http
|
||||
|
||||
#The password for the root user of mysql db, change this before any production use.
|
||||
db_password = root123
|
||||
|
||||
#Maximum number of job workers in job service
|
||||
max_job_workers = 3
|
||||
|
||||
#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
|
||||
#for generating token to access the registry. If the value is off the default key/cert will be used.
|
||||
#This flag also controls the creation of the notary signer's cert.
|
||||
customize_crt = on
|
||||
|
||||
#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_key = /data/cert/server.key
|
||||
|
||||
#The path of secretkey storage
|
||||
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
|
||||
#only take effect in the first boot, the subsequent changes of these properties
|
||||
#should be performed on web ui
|
||||
#************************BEGIN INITIAL PROPERTIES************************
|
||||
|
||||
#Email account settings for sending out password resetting emails.
|
||||
|
||||
#Email server uses the given username and password to authenticate on TLS connections to host and act as identity.
|
||||
@ -52,46 +79,22 @@ ldap_uid = uid
|
||||
#the scope to search for users, 1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE
|
||||
ldap_scope = 3
|
||||
|
||||
#The password for the root user of mysql db, change this before any production use.
|
||||
db_password = root123
|
||||
#Timeout (in seconds) when connecting to an LDAP Server. The default value (and most reasonable) is 5 seconds.
|
||||
ldap_timeout = 5
|
||||
|
||||
#Turn on or off the self-registration feature
|
||||
self_registration = on
|
||||
|
||||
#Determine whether the UI should use compressed js files.
|
||||
#For production, set it to on. For development, set it to off.
|
||||
use_compressed_js = on
|
||||
|
||||
#Maximum number of job workers in job service
|
||||
max_job_workers = 3
|
||||
|
||||
#The expiration time (in minute) of token created by token service, default is 30 minutes
|
||||
token_expiration = 30
|
||||
|
||||
#Determine whether the job service should verify the ssl cert when it connects to a remote registry.
|
||||
#Set this flag to off when the remote registry uses a self-signed or untrusted certificate.
|
||||
verify_remote_cert = on
|
||||
|
||||
#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
|
||||
#for generating token to access the registry. If the value is off, a key/certificate must
|
||||
#be supplied for token generation.
|
||||
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 flag to control what users have permission to create projects
|
||||
#Be default everyone can create a project, set to "adminonly" such that only admin can create project.
|
||||
project_creation_restriction = everyone
|
||||
|
||||
#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_key = /data/cert/server.key
|
||||
#Determine whether the job service should verify the ssl cert when it connects to a remote registry.
|
||||
#Set this flag to off when the remote registry uses a self-signed or untrusted certificate.
|
||||
verify_remote_cert = on
|
||||
#************************BEGIN INITIAL PROPERTIES************************
|
||||
#############
|
||||
|
||||
|
@ -49,14 +49,20 @@ note() { printf "\n${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\n"
|
||||
set -e
|
||||
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
|
||||
|
||||
# notary is not enabled by default
|
||||
with_notary=$false
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--help)
|
||||
note "$usage"
|
||||
exit 0;;
|
||||
--with-notary)
|
||||
with_notary=true;;
|
||||
*)
|
||||
note "$usage"
|
||||
exit 1;;
|
||||
@ -134,10 +140,10 @@ h2 "[Step $item]: checking installation environment ..."; let item+=1
|
||||
check_docker
|
||||
check_dockercompose
|
||||
|
||||
if [ -f harbor*.tgz ]
|
||||
if [ -f harbor*.tar.gz ]
|
||||
then
|
||||
h2 "[Step $item]: loading Harbor images ..."; let item+=1
|
||||
docker load -i ./harbor*.tgz
|
||||
docker load -i ./harbor*.tar.gz
|
||||
fi
|
||||
echo ""
|
||||
|
||||
@ -146,19 +152,38 @@ if [ -n "$host" ]
|
||||
then
|
||||
sed "s/^hostname = .*/hostname = $host/g" -i ./harbor.cfg
|
||||
fi
|
||||
./prepare
|
||||
if [ $with_notary ]
|
||||
then
|
||||
./prepare --with-notary
|
||||
else
|
||||
./prepare
|
||||
fi
|
||||
echo ""
|
||||
|
||||
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
|
||||
if [ -n "$(docker-compose -f docker-compose.yml -f docker-compose.notary.yml ps -q)" ]
|
||||
then
|
||||
note "stopping existing Harbor instance ..."
|
||||
docker-compose -f docker-compose*.yml down
|
||||
docker-compose -f docker-compose.yml -f docker-compose.notary.yml down -v
|
||||
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 -v
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
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
|
||||
hostname=reg.mydomain.com
|
||||
|
@ -1,71 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
echo "This shell will minify the Javascript in Harbor project."
|
||||
echo "Usage: #jsminify [src] [dest] [basedir]"
|
||||
|
||||
#prepare workspace
|
||||
rm -rf $2 /tmp/harbor.app.temp.js
|
||||
|
||||
if [ -z $3 ]
|
||||
then
|
||||
BASEPATH=/go/bin
|
||||
else
|
||||
BASEPATH=$3
|
||||
fi
|
||||
|
||||
#concat the js files from js include file
|
||||
echo "Concat js files..."
|
||||
|
||||
cat $1 | while read LINE || [[ -n $LINE ]]
|
||||
do
|
||||
if [ -n "$LINE" ]
|
||||
then
|
||||
TEMP="$BASEPATH""$LINE"
|
||||
cat `echo "$TEMP" | sed 's/<script src=\"//g' | sed 's/\"><\/script>//g'` >> /tmp/harbor.app.temp.js
|
||||
printf "\n" >> /tmp/harbor.app.temp.js
|
||||
fi
|
||||
done
|
||||
|
||||
# If you want run this script on Mac OS X,
|
||||
# I suggest you install gnu-sed (whth --with-default-names option).
|
||||
# $ brew install gnu-sed --with-default-names
|
||||
# Reference:
|
||||
# http://stackoverflow.com/a/27834828/3167471
|
||||
|
||||
#remove space
|
||||
echo "Remove space.."
|
||||
sed 's/ \+/ /g' -i /tmp/harbor.app.temp.js
|
||||
|
||||
#remove '//' and '/*'
|
||||
echo "Remove '//'and '/*' annotation..."
|
||||
sed '/^\/\//'d -i /tmp/harbor.app.temp.js
|
||||
sed '/\/\*/{/\*\//d;:a;N;/\*\//d;ba};s,//.*,,' -i /tmp/harbor.app.temp.js
|
||||
|
||||
cat > $2 << EOF
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
EOF
|
||||
|
||||
#remove '\n'
|
||||
echo "Remove CR ..."
|
||||
cat /tmp/harbor.app.temp.js | tr -d '\n' >> $2
|
||||
|
||||
#clear workspace
|
||||
rm -rf /tmp/harbor.app.temp.js
|
||||
|
||||
echo "Done."
|
||||
exit 0
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
# Targets:
|
||||
#
|
||||
# build: build harbor photon images
|
||||
# clean: clean ui and jobservice harbor images
|
||||
# clean: clean adminserver, ui and jobservice harbor images
|
||||
|
||||
# common
|
||||
SHELL := /bin/bash
|
||||
@ -22,6 +22,9 @@ DOCKERRMIMAGE=$(DOCKERCMD) rmi
|
||||
DOCKERIMASES=$(DOCKERCMD) images
|
||||
|
||||
# binary
|
||||
ADMINSERVERSOURCECODE=$(SRCPATH)/adminserver
|
||||
ADMINSERVERBINARYPATH=$(MAKEDEVPATH)/adminserver
|
||||
ADMINSERVERBINARYNAME=harbor_adminserver
|
||||
UISOURCECODE=$(SRCPATH)/ui
|
||||
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
||||
UIBINARYNAME=harbor_ui
|
||||
@ -31,6 +34,9 @@ JOBSERVICEBINARYNAME=harbor_jobservice
|
||||
|
||||
# photon dockerfile
|
||||
DOCKERFILEPATH=$(MAKEPATH)/photon
|
||||
DOCKERFILEPATH_ADMINSERVER=$(DOCKERFILEPATH)/adminserver
|
||||
DOCKERFILENAME_ADMINSERVER=Dockerfile
|
||||
DOCKERIMAGENAME_ADMINSERVER=vmware/harbor-adminserver
|
||||
DOCKERFILEPATH_UI=$(DOCKERFILEPATH)/ui
|
||||
DOCKERFILENAME_UI=Dockerfile
|
||||
DOCKERIMAGENAME_UI=vmware/harbor-ui
|
||||
@ -56,6 +62,10 @@ check_environment:
|
||||
@$(MAKEPATH)/$(CHECKENVCMD)
|
||||
|
||||
build:
|
||||
@echo "building adminserver container for photon..."
|
||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_ADMINSERVER)/$(DOCKERFILENAME_ADMINSERVER) -t $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) .
|
||||
@echo "Done."
|
||||
|
||||
@echo "building ui container for photon..."
|
||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_UI)/$(DOCKERFILENAME_UI) -t $(DOCKERIMAGENAME_UI):$(VERSIONTAG) .
|
||||
@echo "Done."
|
||||
@ -70,6 +80,7 @@ build:
|
||||
|
||||
cleanimage:
|
||||
@echo "cleaning image for photon..."
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||
|
8
make/photon/adminserver/Dockerfile
Normal file
8
make/photon/adminserver/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
FROM library/photon:1.0
|
||||
|
||||
RUN mkdir /harbor/
|
||||
COPY ./make/dev/adminserver/harbor_adminserver /harbor/
|
||||
|
||||
RUN chmod u+x /harbor/harbor_adminserver
|
||||
WORKDIR /harbor/
|
||||
ENTRYPOINT ["/harbor/harbor_adminserver"]
|
@ -1,20 +1,15 @@
|
||||
FROM library/photon:1.0
|
||||
|
||||
RUN mkdir /harbor/
|
||||
RUN tdnf install -y sed apr-util-ldap
|
||||
|
||||
COPY ./make/dev/ui/harbor_ui /harbor/
|
||||
|
||||
COPY ./src/ui/views /harbor/views
|
||||
COPY ./src/ui/static /harbor/static
|
||||
COPY ./src/favicon.ico /harbor/favicon.ico
|
||||
COPY ./make/jsminify.sh /tmp/jsminify.sh
|
||||
COPY ./VERSION /harbor/VERSION
|
||||
|
||||
RUN chmod u+x /harbor/harbor_ui \
|
||||
&& timestamp=`date '+%s'` \
|
||||
&& /tmp/jsminify.sh /harbor/views/sections/script-include.htm /harbor/static/resources/js/harbor.app.min.$timestamp.js /harbor/ \
|
||||
&& sed -i "s/harbor\.app\.min\.js/harbor\.app\.min\.$timestamp\.js/g" /harbor/views/sections/script-min-include.htm \
|
||||
&& echo "TLS_REQCERT allow" >> /etc/openldap/ldap.conf
|
||||
RUN chmod u+x /harbor/harbor_ui
|
||||
|
||||
WORKDIR /harbor/
|
||||
ENTRYPOINT ["/harbor/harbor_ui"]
|
||||
|
302
make/prepare
302
make/prepare
@ -19,8 +19,10 @@ if sys.version_info[:3][0] == 3:
|
||||
import configparser as ConfigParser
|
||||
import io as StringIO
|
||||
|
||||
def validate(conf):
|
||||
def validate(conf, args):
|
||||
protocol = rcp.get("configuration", "ui_url_protocol")
|
||||
if protocol != "https" and args.notary_mode:
|
||||
raise Exception("Error: the protocol must be https when Harbor is deployed with Notary")
|
||||
if protocol == "https":
|
||||
if not rcp.has_option("configuration", "ssl_cert"):
|
||||
raise Exception("Error: The protocol is https but attribute ssl_cert is not set")
|
||||
@ -38,32 +40,63 @@ def validate(conf):
|
||||
raise Exception("Error invalid value for project_creation_restriction: %s" % project_creation)
|
||||
|
||||
def get_secret_key(path):
|
||||
key_file = os.path.join(path, "secretkey")
|
||||
secret_key = _get_secret(path, "secretkey")
|
||||
if len(secret_key) != 16:
|
||||
raise Exception("secret key's length has to be 16 chars, current length: %d" % len(secret_key))
|
||||
return secret_key
|
||||
|
||||
def get_alias(path):
|
||||
alias = _get_secret(path, "defaultalias", length=8)
|
||||
return alias
|
||||
|
||||
def _get_secret(folder, filename, length=16):
|
||||
key_file = os.path.join(folder, filename)
|
||||
if os.path.isfile(key_file):
|
||||
with open(key_file, 'r') as f:
|
||||
key = f.read()
|
||||
print("loaded secret key")
|
||||
if len(key) != 16:
|
||||
raise Exception("secret key's length has to be 16 chars, current length: %d" % len(key))
|
||||
print("loaded secret from file: %s" % key_file)
|
||||
return key
|
||||
if not os.path.isdir(path):
|
||||
os.makedirs(path, mode=0600)
|
||||
key = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
||||
if not os.path.isdir(folder):
|
||||
os.makedirs(folder, mode=0600)
|
||||
key = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(length))
|
||||
with open(key_file, 'w') as f:
|
||||
f.write(key)
|
||||
print("generated and saved secret key")
|
||||
print("Generated and saved secret to file: %s" % key_file)
|
||||
return key
|
||||
|
||||
def prep_conf_dir(root, name):
|
||||
absolute_path = os.path.join(root, name)
|
||||
if not os.path.exists(absolute_path):
|
||||
os.makedirs(absolute_path)
|
||||
return absolute_path
|
||||
|
||||
def render(src, dest, **kw):
|
||||
t = Template(open(src, 'r').read())
|
||||
with open(dest, 'w') as f:
|
||||
f.write(t.substitute(**kw))
|
||||
print("Generated configuration file: %s" % dest)
|
||||
|
||||
base_dir = os.path.dirname(__file__)
|
||||
config_dir = os.path.join(base_dir, "common/config")
|
||||
templates_dir = os.path.join(base_dir, "common/templates")
|
||||
def delfile(src):
|
||||
if os.path.isfile(src):
|
||||
try:
|
||||
os.remove(src)
|
||||
print("Clearing the configuration file: %s" % src)
|
||||
except:
|
||||
pass
|
||||
elif os.path.isdir(src):
|
||||
for item in os.listdir(src):
|
||||
itemsrc=os.path.join(src,item)
|
||||
delfile(itemsrc)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-conf', dest='cfgfile', default=base_dir+'/harbor.cfg',type=str,help="the path of Harbor configuration file")
|
||||
parser.add_argument('--data-volume', dest='data_volume', default='/data/',type=str,help="the path of Harbor data volume, which is set in template of docker-compose.")
|
||||
|
||||
parser.add_argument('--conf', dest='cfgfile', default=base_dir+'/harbor.cfg',type=str,help="the path of Harbor configuration file")
|
||||
parser.add_argument('--with-notary', dest='notary_mode', default=False, action='store_true', help="the Harbor instance is to be deployed with notary")
|
||||
args = parser.parse_args()
|
||||
|
||||
delfile(config_dir)
|
||||
#Read configurations
|
||||
conf = StringIO.StringIO()
|
||||
conf.write("[configuration]\n")
|
||||
@ -72,16 +105,16 @@ conf.seek(0, os.SEEK_SET)
|
||||
rcp = ConfigParser.RawConfigParser()
|
||||
rcp.readfp(conf)
|
||||
|
||||
validate(rcp)
|
||||
validate(rcp, args)
|
||||
|
||||
hostname = rcp.get("configuration", "hostname")
|
||||
protocol = rcp.get("configuration", "ui_url_protocol")
|
||||
ui_url = protocol + "://" + hostname
|
||||
email_identity = rcp.get("configuration", "email_identity")
|
||||
email_server = rcp.get("configuration", "email_server")
|
||||
email_server_port = rcp.get("configuration", "email_server_port")
|
||||
email_username = rcp.get("configuration", "email_username")
|
||||
email_password = rcp.get("configuration", "email_password")
|
||||
email_host = rcp.get("configuration", "email_server")
|
||||
email_port = rcp.get("configuration", "email_server_port")
|
||||
email_usr = rcp.get("configuration", "email_username")
|
||||
email_pwd = rcp.get("configuration", "email_password")
|
||||
email_from = rcp.get("configuration", "email_from")
|
||||
email_ssl = rcp.get("configuration", "email_ssl")
|
||||
harbor_admin_password = rcp.get("configuration", "harbor_admin_password")
|
||||
@ -102,56 +135,40 @@ else:
|
||||
ldap_filter = ""
|
||||
ldap_uid = rcp.get("configuration", "ldap_uid")
|
||||
ldap_scope = rcp.get("configuration", "ldap_scope")
|
||||
ldap_timeout = rcp.get("configuration", "ldap_timeout")
|
||||
db_password = rcp.get("configuration", "db_password")
|
||||
self_registration = rcp.get("configuration", "self_registration")
|
||||
use_compressed_js = rcp.get("configuration", "use_compressed_js")
|
||||
if protocol == "https":
|
||||
cert_path = rcp.get("configuration", "ssl_cert")
|
||||
cert_key_path = rcp.get("configuration", "ssl_cert_key")
|
||||
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")
|
||||
token_expiration = rcp.get("configuration", "token_expiration")
|
||||
verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
|
||||
proj_cre_restriction = rcp.get("configuration", "project_creation_restriction")
|
||||
#secret_key = rcp.get("configuration", "secret_key")
|
||||
secret_key = get_secret_key(args.data_volume)
|
||||
secretkey_path = rcp.get("configuration", "secretkey_path")
|
||||
if rcp.has_option("configuration", "admiral_url"):
|
||||
admiral_url = rcp.get("configuration", "admiral_url")
|
||||
else:
|
||||
admiral_url = ""
|
||||
secret_key = get_secret_key(secretkey_path)
|
||||
########
|
||||
|
||||
ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
||||
jobservice_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
||||
|
||||
ui_config_dir = os.path.join(config_dir,"ui")
|
||||
if not os.path.exists(ui_config_dir):
|
||||
os.makedirs(os.path.join(config_dir, "ui"))
|
||||
adminserver_config_dir = os.path.join(config_dir,"adminserver")
|
||||
if not os.path.exists(adminserver_config_dir):
|
||||
os.makedirs(os.path.join(config_dir, "adminserver"))
|
||||
|
||||
db_config_dir = os.path.join(config_dir, "db")
|
||||
if not os.path.exists(db_config_dir):
|
||||
os.makedirs(os.path.join(config_dir, "db"))
|
||||
|
||||
job_config_dir = os.path.join(config_dir, "jobservice")
|
||||
if not os.path.exists(job_config_dir):
|
||||
os.makedirs(job_config_dir)
|
||||
|
||||
registry_config_dir = os.path.join(config_dir, "registry")
|
||||
if not os.path.exists(registry_config_dir):
|
||||
os.makedirs(registry_config_dir)
|
||||
|
||||
nginx_config_dir = os.path.join(config_dir, "nginx")
|
||||
if not os.path.exists(nginx_config_dir):
|
||||
os.makedirs(nginx_config_dir)
|
||||
|
||||
def render(src, dest, **kw):
|
||||
t = Template(open(src, 'r').read())
|
||||
with open(dest, 'w') as f:
|
||||
f.write(t.substitute(**kw))
|
||||
print("Generated configuration file: %s" % dest)
|
||||
ui_config_dir = prep_conf_dir(config_dir,"ui")
|
||||
db_config_dir = prep_conf_dir(config_dir, "db")
|
||||
job_config_dir = prep_conf_dir(config_dir, "jobservice")
|
||||
registry_config_dir = prep_conf_dir(config_dir, "registry")
|
||||
nginx_config_dir = prep_conf_dir (config_dir, "nginx")
|
||||
nginx_conf_d = prep_conf_dir(nginx_config_dir, "conf.d")
|
||||
|
||||
adminserver_conf_env = os.path.join(config_dir, "adminserver", "env")
|
||||
ui_conf_env = os.path.join(config_dir, "ui", "env")
|
||||
ui_conf = os.path.join(config_dir, "ui", "app.conf")
|
||||
jobservice_conf = os.path.join(config_dir, "jobservice", "app.conf")
|
||||
@ -160,18 +177,6 @@ db_conf_env = os.path.join(config_dir, "db", "env")
|
||||
job_conf_env = os.path.join(config_dir, "jobservice", "env")
|
||||
nginx_conf = os.path.join(config_dir, "nginx", "nginx.conf")
|
||||
cert_dir = os.path.join(config_dir, "nginx", "cert")
|
||||
def delfile(src):
|
||||
if os.path.isfile(src):
|
||||
try:
|
||||
os.remove(src)
|
||||
print("Clearing the configuration file: %s" % src)
|
||||
except:
|
||||
pass
|
||||
elif os.path.isdir(src):
|
||||
for item in os.listdir(src):
|
||||
itemsrc=os.path.join(src,item)
|
||||
delfile(itemsrc)
|
||||
delfile(config_dir)
|
||||
|
||||
if protocol == "https":
|
||||
target_cert_path = os.path.join(cert_dir, os.path.basename(cert_path))
|
||||
@ -188,13 +193,11 @@ else:
|
||||
render(os.path.join(templates_dir, "nginx", "nginx.http.conf"),
|
||||
nginx_conf)
|
||||
|
||||
render(os.path.join(templates_dir, "ui", "env"),
|
||||
ui_conf_env,
|
||||
hostname=hostname,
|
||||
db_password=db_password,
|
||||
render(os.path.join(templates_dir, "adminserver", "env"),
|
||||
adminserver_conf_env,
|
||||
ui_url=ui_url,
|
||||
auth_mode=auth_mode,
|
||||
harbor_admin_password=harbor_admin_password,
|
||||
self_registration=self_registration,
|
||||
ldap_url=ldap_url,
|
||||
ldap_searchdn =ldap_searchdn,
|
||||
ldap_search_pwd =ldap_search_pwd,
|
||||
@ -202,26 +205,33 @@ render(os.path.join(templates_dir, "ui", "env"),
|
||||
ldap_filter=ldap_filter,
|
||||
ldap_uid=ldap_uid,
|
||||
ldap_scope=ldap_scope,
|
||||
self_registration=self_registration,
|
||||
use_compressed_js=use_compressed_js,
|
||||
ui_secret=ui_secret,
|
||||
secret_key=secret_key,
|
||||
verify_remote_cert=verify_remote_cert,
|
||||
project_creation_restriction=proj_cre_restriction,
|
||||
token_expiration=token_expiration)
|
||||
|
||||
render(os.path.join(templates_dir, "ui", "app.conf"),
|
||||
ui_conf,
|
||||
email_identity=email_identity,
|
||||
email_server=email_server,
|
||||
email_server_port=email_server_port,
|
||||
email_username=email_username,
|
||||
email_password=email_password,
|
||||
email_from=email_from,
|
||||
ldap_timeout=ldap_timeout,
|
||||
db_password=db_password,
|
||||
email_host=email_host,
|
||||
email_port=email_port,
|
||||
email_usr=email_usr,
|
||||
email_pwd=email_pwd,
|
||||
email_ssl=email_ssl,
|
||||
ui_url=ui_url)
|
||||
email_from=email_from,
|
||||
email_identity=email_identity,
|
||||
harbor_admin_password=harbor_admin_password,
|
||||
project_creation_restriction=proj_cre_restriction,
|
||||
verify_remote_cert=verify_remote_cert,
|
||||
max_job_workers=max_job_workers,
|
||||
ui_secret=ui_secret,
|
||||
jobservice_secret=jobservice_secret,
|
||||
token_expiration=token_expiration,
|
||||
admiral_url=admiral_url,
|
||||
with_notary=args.notary_mode
|
||||
)
|
||||
|
||||
render(os.path.join(templates_dir, "registry", "config.yml"),
|
||||
render(os.path.join(templates_dir, "ui", "env"),
|
||||
ui_conf_env,
|
||||
ui_secret=ui_secret,
|
||||
jobservice_secret=jobservice_secret,)
|
||||
|
||||
render(os.path.join(templates_dir, "registry",
|
||||
"config.yml"),
|
||||
registry_conf,
|
||||
ui_url=ui_url)
|
||||
|
||||
@ -231,16 +241,16 @@ render(os.path.join(templates_dir, "db", "env"),
|
||||
|
||||
render(os.path.join(templates_dir, "jobservice", "env"),
|
||||
job_conf_env,
|
||||
db_password=db_password,
|
||||
ui_secret=ui_secret,
|
||||
max_job_workers=max_job_workers,
|
||||
secret_key=secret_key,
|
||||
ui_url=ui_url,
|
||||
verify_remote_cert=verify_remote_cert)
|
||||
jobservice_secret=jobservice_secret)
|
||||
|
||||
print("Generated configuration file: %s" % jobservice_conf)
|
||||
shutil.copyfile(os.path.join(templates_dir, "jobservice", "app.conf"), jobservice_conf)
|
||||
|
||||
print("Generated configuration file: %s" % ui_conf)
|
||||
shutil.copyfile(os.path.join(templates_dir, "ui", "app.conf"), ui_conf)
|
||||
|
||||
|
||||
def validate_crt_subj(dirty_subj):
|
||||
subj_list = [item for item in dirty_subj.strip().split("/") \
|
||||
if len(item.split("=")) == 2 and len(item.split("=")[1]) > 0]
|
||||
@ -251,51 +261,107 @@ FNULL = open(os.devnull, 'w')
|
||||
from functools import wraps
|
||||
def stat_decorator(func):
|
||||
@wraps(func)
|
||||
def check_wrapper(*args, **kwargs):
|
||||
stat = func(*args, **kwargs)
|
||||
message = "Generated configuration file: %s" % kwargs['path'] \
|
||||
if stat == 0 else "Fail to generate %s" % kwargs['path']
|
||||
def check_wrapper(*args, **kw):
|
||||
stat = func(*args, **kw)
|
||||
message = "Generated certificate, key file: %s, cert file: %s" % (kw['key_path'], kw['cert_path']) \
|
||||
if stat == 0 else "Fail to generate key file: %s, cert file: %s" % (kw['key_path'], kw['cert_path'])
|
||||
print(message)
|
||||
if stat != 0:
|
||||
sys.exit(1)
|
||||
return check_wrapper
|
||||
|
||||
@stat_decorator
|
||||
def check_private_key_stat(*args, **kwargs):
|
||||
return subprocess.call(["openssl", "genrsa", "-out", kwargs['path'], "4096"],\
|
||||
stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
def create_root_cert(subj, key_path="./k.key", cert_path="./cert.crt"):
|
||||
rc = subprocess.call(["openssl", "genrsa", "-out", key_path, "4096"], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
if rc != 0:
|
||||
return rc
|
||||
return subprocess.call(["openssl", "req", "-new", "-x509", "-key", key_path,\
|
||||
"-out", cert_path, "-days", "3650", "-subj", subj], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
|
||||
@stat_decorator
|
||||
def check_certificate_stat(*args, **kwargs):
|
||||
dirty_subj = "/C={0}/ST={1}/L={2}/O={3}/OU={4}/CN={5}/emailAddress={6}"\
|
||||
.format(crt_country, crt_state, crt_location, crt_organization,\
|
||||
crt_organizationalunit, crt_commonname, crt_email)
|
||||
subj = validate_crt_subj(dirty_subj)
|
||||
return subprocess.call(["openssl", "req", "-new", "-x509", "-key",\
|
||||
private_key_pem, "-out", root_crt, "-days", "3650", "-subj", subj], \
|
||||
stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
def create_cert(subj, ca_key, ca_cert, key_path="./k.key", cert_path="./cert.crt"):
|
||||
cert_dir = os.path.dirname(cert_path)
|
||||
csr_path = os.path.join(cert_dir, "tmp.csr")
|
||||
rc = subprocess.call(["openssl", "req", "-newkey", "rsa:4096", "-nodes","-sha256","-keyout", key_path,\
|
||||
"-out", csr_path, "-subj", subj], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
if rc != 0:
|
||||
return rc
|
||||
return subprocess.call(["openssl", "x509", "-req", "-days", "3650", "-in", csr_path, "-CA", \
|
||||
ca_cert, "-CAkey", ca_key, "-CAcreateserial", "-out", cert_path], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
|
||||
def openssl_is_installed(stat):
|
||||
if stat == 0:
|
||||
return True
|
||||
else:
|
||||
def openssl_installed():
|
||||
shell_stat = subprocess.check_call(["which", "openssl"], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
if shell_stat != 0:
|
||||
print("Cannot find openssl installed in this computer\nUse default SSL certificate file")
|
||||
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)
|
||||
if openssl_is_installed(shell_stat):
|
||||
empty_subj = "/C=/ST=/L=/O=/CN=/"
|
||||
private_key_pem = os.path.join(config_dir, "ui", "private_key.pem")
|
||||
root_crt = os.path.join(config_dir, "registry", "root.crt")
|
||||
|
||||
check_private_key_stat(path=private_key_pem)
|
||||
check_certificate_stat(path=root_crt)
|
||||
|
||||
create_root_cert(empty_subj, key_path=private_key_pem, cert_path=root_crt)
|
||||
else:
|
||||
print("Generated 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"))
|
||||
print("Generated 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"))
|
||||
|
||||
if args.notary_mode:
|
||||
notary_config_dir = prep_conf_dir(config_dir, "notary")
|
||||
notary_temp_dir = os.path.join(templates_dir, "notary")
|
||||
print("Copying sql file for notary DB")
|
||||
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.copytree(os.path.join(notary_temp_dir, "mysql-initdb.d"), os.path.join(notary_config_dir, "mysql-initdb.d"))
|
||||
if customize_crt == 'on' and openssl_installed():
|
||||
try:
|
||||
temp_cert_dir = os.path.join(base_dir, "cert_tmp")
|
||||
if not os.path.exists(temp_cert_dir):
|
||||
os.makedirs(temp_cert_dir)
|
||||
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, Inc./OU=Harbor/CN=notarysigner"
|
||||
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_cert_path = os.path.join(temp_cert_dir, "notary-signer.crt")
|
||||
signer_key_path = os.path.join(temp_cert_dir, "notary-signer.key")
|
||||
create_root_cert(ca_subj, key_path=signer_ca_key, cert_path=signer_ca_cert)
|
||||
create_cert(cert_subj, signer_ca_key, signer_ca_cert, key_path=signer_key_path, cert_path=signer_cert_path)
|
||||
print("Copying certs for notary signer")
|
||||
shutil.copy2(signer_cert_path, notary_config_dir)
|
||||
shutil.copy2(signer_key_path, notary_config_dir)
|
||||
shutil.copy2(signer_ca_cert, notary_config_dir)
|
||||
finally:
|
||||
srl_tmp = os.path.join(os.getcwd(), ".srl")
|
||||
if os.path.isfile(srl_tmp):
|
||||
os.remove(srl_tmp)
|
||||
if os.path.isdir(temp_cert_dir):
|
||||
shutil.rmtree(temp_cert_dir, True)
|
||||
else:
|
||||
print("Copying certs for notary signer")
|
||||
shutil.copy2(os.path.join(notary_temp_dir, "notary-signer.crt"), notary_config_dir)
|
||||
shutil.copy2(os.path.join(notary_temp_dir, "notary-signer.key"), notary_config_dir)
|
||||
shutil.copy2(os.path.join(notary_temp_dir, "notary-signer-ca.crt"), notary_config_dir)
|
||||
shutil.copy2(os.path.join(registry_config_dir, "root.crt"), notary_config_dir)
|
||||
print("Copying notary signer configuration file")
|
||||
shutil.copy2(os.path.join(notary_temp_dir, "signer-config.json"), notary_config_dir)
|
||||
render(os.path.join(notary_temp_dir, "server-config.json"),
|
||||
os.path.join(notary_config_dir, "server-config.json"),
|
||||
token_endpoint=ui_url)
|
||||
|
||||
print("Copying nginx configuration file for notary")
|
||||
shutil.copy2(os.path.join(templates_dir, "nginx", "notary.upstream.conf"), nginx_conf_d)
|
||||
render(os.path.join(templates_dir, "nginx", "notary.server.conf"),
|
||||
os.path.join(nginx_conf_d, "notary.server.conf"),
|
||||
ssl_cert = os.path.join("/etc/nginx/cert", os.path.basename(target_cert_path)),
|
||||
ssl_cert_key = os.path.join("/etc/nginx/cert", os.path.basename(target_cert_key_path)))
|
||||
|
||||
|
||||
default_alias = get_alias(secretkey_path)
|
||||
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.")
|
||||
|
||||
|
@ -1,79 +0,0 @@
|
||||
# Makefile for a harbor project
|
||||
#
|
||||
# Targets:
|
||||
#
|
||||
# build: build harbor ubuntu images
|
||||
# clean: clean ui and jobservice harbor images
|
||||
|
||||
# common
|
||||
SHELL := /bin/bash
|
||||
BUILDPATH=$(CURDIR)
|
||||
MAKEPATH=$(BUILDPATH)/make
|
||||
MAKEDEVPATH=$(MAKEPATH)/dev
|
||||
SRCPATH=./src
|
||||
TOOLSPATH=$(BUILDPATH)/tools
|
||||
CHECKENVCMD=checkenv.sh
|
||||
DEVFLAG=true
|
||||
|
||||
# docker parameters
|
||||
DOCKERCMD=$(shell which docker)
|
||||
DOCKERBUILD=$(DOCKERCMD) build
|
||||
DOCKERRMIMAGE=$(DOCKERCMD) rmi
|
||||
DOCKERIMASES=$(DOCKERCMD) images
|
||||
|
||||
# binary
|
||||
UISOURCECODE=$(SRCPATH)/ui
|
||||
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
||||
UIBINARYNAME=harbor_ui
|
||||
JOBSERVICESOURCECODE=$(SRCPATH)/jobservice
|
||||
JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice
|
||||
JOBSERVICEBINARYNAME=harbor_jobservice
|
||||
|
||||
# ubuntu dockerfile
|
||||
DOCKERFILEPATH=$(MAKEPATH)/ubuntu
|
||||
DOCKERFILEPATH_UI=$(DOCKERFILEPATH)/ui
|
||||
DOCKERFILENAME_UI=Dockerfile
|
||||
DOCKERIMAGENAME_UI=vmware/harbor-ui
|
||||
DOCKERFILEPATH_JOBSERVICE=$(DOCKERFILEPATH)/jobservice
|
||||
DOCKERFILENAME_JOBSERVICE=Dockerfile
|
||||
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
||||
DOCKERFILEPATH_LOG=$(DOCKERFILEPATH)/log
|
||||
DOCKERFILENAME_LOG=Dockerfile
|
||||
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
||||
|
||||
# version prepare
|
||||
VERSIONFILEPATH=$(SRCPATH)/views/sections
|
||||
VERSIONFILENAME=header-content.htm
|
||||
GITCMD=$(shell which git)
|
||||
GITTAG=$(GITCMD) describe --tags
|
||||
ifeq ($(DEVFLAG), true)
|
||||
VERSIONTAG=dev
|
||||
else
|
||||
VERSIONTAG=$(shell $(GITTAG))
|
||||
endif
|
||||
|
||||
check_environment:
|
||||
@$(MAKEPATH)/$(CHECKENVCMD)
|
||||
|
||||
build:
|
||||
@echo "building ui container for ubuntu..."
|
||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_UI)/$(DOCKERFILENAME_UI) -t $(DOCKERIMAGENAME_UI):$(VERSIONTAG) .
|
||||
@echo "Done."
|
||||
|
||||
@echo "building jobservice container for ubuntu..."
|
||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_JOBSERVICE)/$(DOCKERFILENAME_JOBSERVICE) -t $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) .
|
||||
@echo "Done."
|
||||
|
||||
@echo "building log container for ubuntu..."
|
||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_LOG)/$(DOCKERFILENAME_LOG) -t $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) .
|
||||
@echo "Done."
|
||||
|
||||
cleanimage:
|
||||
@echo "cleaning image for ubuntu..."
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||
|
||||
.PHONY: clean
|
||||
clean: cleanimage
|
||||
|
@ -1,14 +0,0 @@
|
||||
FROM golang:1.6.2
|
||||
|
||||
MAINTAINER jiangd@vmware.com
|
||||
|
||||
RUN apt-get update && apt-get install -y libldap2-dev \
|
||||
&& rm -r /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir /harbor/
|
||||
COPY ./make/dev/jobservice/harbor_jobservice /harbor/
|
||||
|
||||
RUN chmod u+x /harbor/harbor_jobservice
|
||||
|
||||
WORKDIR /harbor/
|
||||
ENTRYPOINT ["/harbor/harbor_jobservice"]
|
@ -1,18 +0,0 @@
|
||||
FROM library/ubuntu:14.04
|
||||
|
||||
RUN rm /etc/rsyslog.d/* && rm /etc/rsyslog.conf
|
||||
|
||||
ADD make/common/log/rsyslog.conf /etc/rsyslog.conf
|
||||
|
||||
# rotate logs weekly
|
||||
# notes: file name cannot contain dot, or the script will not run
|
||||
ADD make/common/log/rotate.sh /etc/cron.weekly/rotate
|
||||
|
||||
# rsyslog configuration file for docker
|
||||
ADD make/common/log/rsyslog_docker.conf /etc/rsyslog.d/
|
||||
|
||||
VOLUME /var/log/docker/
|
||||
|
||||
EXPOSE 514
|
||||
|
||||
CMD cron && rsyslogd -n
|
@ -1,31 +0,0 @@
|
||||
FROM golang:1.6.2
|
||||
|
||||
MAINTAINER jiangd@vmware.com
|
||||
|
||||
RUN apt-get update && apt-get install -y libldap2-dev \
|
||||
&& rm -r /var/lib/apt/lists/*
|
||||
|
||||
ENV MYSQL_USR root \
|
||||
MYSQL_PWD root \
|
||||
REGISTRY_URL localhost:5000
|
||||
|
||||
RUN mkdir /harbor/
|
||||
COPY ./make/dev/ui/harbor_ui /harbor/
|
||||
|
||||
COPY ./src/ui/views /harbor/views
|
||||
COPY ./src/ui/static /harbor/static
|
||||
COPY ./src/favicon.ico /harbor/favicon.ico
|
||||
COPY ./make/jsminify.sh /tmp/jsminify.sh
|
||||
|
||||
RUN chmod u+x /harbor/harbor_ui \
|
||||
&& sed -i 's/TLS_CACERT/#TLS_CAERT/g' /etc/ldap/ldap.conf \
|
||||
&& sed -i '$a\TLS_REQCERT allow' /etc/ldap/ldap.conf \
|
||||
&& timestamp=`date '+%s'` \
|
||||
&& /tmp/jsminify.sh /harbor/views/sections/script-include.htm /harbor/static/resources/js/harbor.app.min.$timestamp.js /harbor/ \
|
||||
&& sed -i "s/harbor\.app\.min\.js/harbor\.app\.min\.$timestamp\.js/g" /harbor/views/sections/script-min-include.htm
|
||||
|
||||
WORKDIR /harbor/
|
||||
ENTRYPOINT ["/harbor/harbor_ui"]
|
||||
|
||||
EXPOSE 80
|
||||
|
49
src/adminserver/api/base.go
Normal file
49
src/adminserver/api/base.go
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func handleInternalServerError(w http.ResponseWriter) {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError),
|
||||
http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func handleBadRequestError(w http.ResponseWriter, error string) {
|
||||
http.Error(w, error, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func handleUnauthorized(w http.ResponseWriter) {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized),
|
||||
http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
// response status code will be written automatically if there is an error
|
||||
func writeJSON(w http.ResponseWriter, v interface{}) error {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
handleInternalServerError(w)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = w.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
32
src/adminserver/api/base_test.go
Normal file
32
src/adminserver/api/base_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleInternalServerError(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
handleInternalServerError(w)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
}
|
78
src/adminserver/api/cfg.go
Normal file
78
src/adminserver/api/cfg.go
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/vmware/harbor/src/adminserver/systemcfg"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
// ListCfgs lists configurations
|
||||
func ListCfgs(w http.ResponseWriter, r *http.Request) {
|
||||
cfg, err := systemcfg.CfgStore.Read()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get system configurations: %v", err)
|
||||
handleInternalServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
if err = writeJSON(w, cfg); err != nil {
|
||||
log.Errorf("failed to write response: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateCfgs updates configurations
|
||||
func UpdateCfgs(w http.ResponseWriter, r *http.Request) {
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Errorf("failed to read request body: %v", err)
|
||||
handleInternalServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
m := map[string]interface{}{}
|
||||
if err = json.Unmarshal(b, &m); err != nil {
|
||||
handleBadRequestError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemcfg.CfgStore.Write(m); err != nil {
|
||||
log.Errorf("failed to update system configurations: %v", err)
|
||||
handleInternalServerError(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ResetCfgs resets configurations from environment variables
|
||||
func ResetCfgs(w http.ResponseWriter, r *http.Request) {
|
||||
cfgs := map[string]interface{}{}
|
||||
if err := systemcfg.LoadFromEnv(cfgs, true); err != nil {
|
||||
log.Errorf("failed to reset system configurations: %v", err)
|
||||
handleInternalServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := systemcfg.CfgStore.Write(cfgs); err != nil {
|
||||
log.Errorf("failed to write system configurations to storage: %v", err)
|
||||
handleInternalServerError(w)
|
||||
return
|
||||
}
|
||||
}
|
169
src/adminserver/api/cfg_test.go
Normal file
169
src/adminserver/api/cfg_test.go
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/adminserver/systemcfg"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
)
|
||||
|
||||
type fakeCfgStore struct {
|
||||
cfgs map[string]interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeCfgStore) Name() string {
|
||||
return "fake"
|
||||
}
|
||||
|
||||
func (f *fakeCfgStore) Read() (map[string]interface{}, error) {
|
||||
return f.cfgs, f.err
|
||||
}
|
||||
|
||||
func (f *fakeCfgStore) Write(cfgs map[string]interface{}) error {
|
||||
f.cfgs = cfgs
|
||||
return f.err
|
||||
}
|
||||
|
||||
func TestListCfgs(t *testing.T) {
|
||||
// 500
|
||||
systemcfg.CfgStore = &fakeCfgStore{
|
||||
cfgs: nil,
|
||||
err: errors.New("error"),
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
ListCfgs(w, nil)
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
|
||||
// 200
|
||||
key := "key"
|
||||
value := "value"
|
||||
cfgs := map[string]interface{}{
|
||||
key: value,
|
||||
}
|
||||
systemcfg.CfgStore = &fakeCfgStore{
|
||||
cfgs: cfgs,
|
||||
err: nil,
|
||||
}
|
||||
w = httptest.NewRecorder()
|
||||
ListCfgs(w, nil)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
result, err := parse(w.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse response body: %v", err)
|
||||
}
|
||||
assert.Equal(t, value, result[key])
|
||||
}
|
||||
|
||||
func TestUpdateCfgs(t *testing.T) {
|
||||
// 400
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequest("", "", bytes.NewReader([]byte{'a'}))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
|
||||
UpdateCfgs(w, r)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
|
||||
// 500
|
||||
systemcfg.CfgStore = &fakeCfgStore{
|
||||
cfgs: nil,
|
||||
err: errors.New("error"),
|
||||
}
|
||||
w = httptest.NewRecorder()
|
||||
r, err = http.NewRequest("", "", bytes.NewBufferString("{}"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
|
||||
UpdateCfgs(w, r)
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
|
||||
// 200
|
||||
key := "key"
|
||||
value := "value"
|
||||
systemcfg.CfgStore = &fakeCfgStore{
|
||||
cfgs: nil,
|
||||
err: nil,
|
||||
}
|
||||
w = httptest.NewRecorder()
|
||||
r, err = http.NewRequest("", "",
|
||||
bytes.NewBufferString(fmt.Sprintf(`{"%s":"%s"}`, key, value)))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
|
||||
UpdateCfgs(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
}
|
||||
|
||||
func TestResetCfgs(t *testing.T) {
|
||||
// 500
|
||||
systemcfg.CfgStore = &fakeCfgStore{
|
||||
cfgs: nil,
|
||||
err: errors.New("error"),
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
ResetCfgs(w, nil)
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
|
||||
// 200
|
||||
os.Clearenv()
|
||||
key := "LDAP_URL"
|
||||
value := "ldap://ldap.com"
|
||||
if err := os.Setenv(key, value); err != nil {
|
||||
t.Fatalf("failed to set env: %v", err)
|
||||
}
|
||||
store := &fakeCfgStore{
|
||||
cfgs: nil,
|
||||
err: nil,
|
||||
}
|
||||
systemcfg.CfgStore = store
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
ResetCfgs(w, nil)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, value, store.cfgs[common.LDAPURL])
|
||||
}
|
||||
|
||||
func parse(reader io.Reader) (map[string]interface{}, error) {
|
||||
b, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := map[string]interface{}{}
|
||||
if err := json.Unmarshal(b, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
38
src/adminserver/api/systeminfo.go
Normal file
38
src/adminserver/api/systeminfo.go
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
// Capacity handles /api/systeminfo/capacity and returns system capacity
|
||||
func Capacity(w http.ResponseWriter, r *http.Request) {
|
||||
capacity, err := imagestorage.GlobalDriver.Cap()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get capacity: %v", err)
|
||||
handleInternalServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
if err = writeJSON(w, capacity); err != nil {
|
||||
log.Errorf("failed to write response: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
74
src/adminserver/api/systeminfo_test.go
Normal file
74
src/adminserver/api/systeminfo_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage"
|
||||
)
|
||||
|
||||
type fakeImageStorageDriver struct {
|
||||
capacity *imagestorage.Capacity
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeImageStorageDriver) Name() string {
|
||||
return "fake"
|
||||
}
|
||||
|
||||
func (f *fakeImageStorageDriver) Cap() (*imagestorage.Capacity, error) {
|
||||
return f.capacity, f.err
|
||||
}
|
||||
|
||||
func TestCapacity(t *testing.T) {
|
||||
cases := []struct {
|
||||
driver imagestorage.Driver
|
||||
responseCode int
|
||||
capacity *imagestorage.Capacity
|
||||
}{
|
||||
{&fakeImageStorageDriver{nil, errors.New("error")}, http.StatusInternalServerError, nil},
|
||||
{&fakeImageStorageDriver{&imagestorage.Capacity{100, 90}, nil}, http.StatusOK, &imagestorage.Capacity{100, 90}},
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("", "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
for _, c := range cases {
|
||||
imagestorage.GlobalDriver = c.driver
|
||||
w := httptest.NewRecorder()
|
||||
Capacity(w, req)
|
||||
assert.Equal(t, c.responseCode, w.Code, "unexpected response code")
|
||||
if c.responseCode == http.StatusOK {
|
||||
b, err := ioutil.ReadAll(w.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read from response body: %v", err)
|
||||
}
|
||||
capacity := &imagestorage.Capacity{}
|
||||
if err = json.Unmarshal(b, capacity); err != nil {
|
||||
t.Fatalf("failed to unmarshal: %v", err)
|
||||
}
|
||||
assert.Equal(t, c.capacity, capacity)
|
||||
}
|
||||
}
|
||||
}
|
65
src/adminserver/auth/auth.go
Normal file
65
src/adminserver/auth/auth.go
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Authenticator defines Authenticate function to authenticate requests
|
||||
type Authenticator interface {
|
||||
// Authenticate the request, if there is no error, the bool value
|
||||
// determines whether the request is authenticated or not
|
||||
Authenticate(req *http.Request) (bool, error)
|
||||
}
|
||||
|
||||
type secretAuthenticator struct {
|
||||
secrets map[string]string
|
||||
}
|
||||
|
||||
// NewSecretAuthenticator returns an instance of secretAuthenticator
|
||||
func NewSecretAuthenticator(secrets map[string]string) Authenticator {
|
||||
return &secretAuthenticator{
|
||||
secrets: secrets,
|
||||
}
|
||||
}
|
||||
|
||||
// Authenticate the request according the secret
|
||||
func (s *secretAuthenticator) Authenticate(req *http.Request) (bool, error) {
|
||||
if len(s.secrets) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
secret, err := req.Cookie("secret")
|
||||
if err != nil {
|
||||
if err == http.ErrNoCookie {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
if secret == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for _, v := range s.secrets {
|
||||
if secret.Value == v {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
57
src/adminserver/auth/auth_test.go
Normal file
57
src/adminserver/auth/auth_test.go
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAuthenticate(t *testing.T) {
|
||||
secret := "correct"
|
||||
req1, err := http.NewRequest("", "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
|
||||
req2, err := http.NewRequest("", "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
req2.AddCookie(&http.Cookie{
|
||||
Name: "secret",
|
||||
Value: secret,
|
||||
})
|
||||
|
||||
cases := []struct {
|
||||
secrets map[string]string
|
||||
req *http.Request
|
||||
result bool
|
||||
}{
|
||||
{nil, req1, true},
|
||||
{map[string]string{"secret1": "incorrect"}, req2, false},
|
||||
{map[string]string{"secret1": "incorrect", "secret2": secret}, req2, true},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
authenticator := NewSecretAuthenticator(c.secrets)
|
||||
authenticated, err := authenticator.Authenticate(c.req)
|
||||
assert.Nil(t, err, "unexpected error")
|
||||
assert.Equal(t, c.result, authenticated, "unexpected result")
|
||||
}
|
||||
}
|
51
src/adminserver/client/auth/auth.go
Normal file
51
src/adminserver/client/auth/auth.go
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Authorizer authorizes request
|
||||
type Authorizer interface {
|
||||
Authorize(*http.Request) error
|
||||
}
|
||||
|
||||
// NewSecretAuthorizer returns an instance of secretAuthorizer
|
||||
func NewSecretAuthorizer(cookieName, secret string) Authorizer {
|
||||
return &secretAuthorizer{
|
||||
cookieName: cookieName,
|
||||
secret: secret,
|
||||
}
|
||||
}
|
||||
|
||||
type secretAuthorizer struct {
|
||||
cookieName string
|
||||
secret string
|
||||
}
|
||||
|
||||
func (s *secretAuthorizer) Authorize(req *http.Request) error {
|
||||
if req == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: s.cookieName,
|
||||
Value: s.secret,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
44
src/adminserver/client/auth/auth_test.go
Normal file
44
src/adminserver/client/auth/auth_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAuthorize(t *testing.T) {
|
||||
cookieName := "secret"
|
||||
secret := "secret"
|
||||
authorizer := NewSecretAuthorizer(cookieName, secret)
|
||||
req, err := http.NewRequest("", "", nil)
|
||||
if !assert.Nil(t, err, "unexpected error") {
|
||||
return
|
||||
}
|
||||
|
||||
err = authorizer.Authorize(req)
|
||||
if !assert.Nil(t, err, "unexpected error") {
|
||||
return
|
||||
}
|
||||
|
||||
cookie, err := req.Cookie(cookieName)
|
||||
if !assert.Nil(t, err, "unexpected error") {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, secret, cookie.Value, "unexpected cookie")
|
||||
}
|
184
src/adminserver/client/client.go
Normal file
184
src/adminserver/client/client.go
Normal file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/src/adminserver/client/auth"
|
||||
"github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage"
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
)
|
||||
|
||||
// Client defines methods that an Adminserver client should implement
|
||||
type Client interface {
|
||||
// Ping tests the connection with server
|
||||
Ping() error
|
||||
// GetCfgs returns system configurations
|
||||
GetCfgs() (map[string]interface{}, error)
|
||||
// UpdateCfgs updates system configurations
|
||||
UpdateCfgs(map[string]interface{}) error
|
||||
// ResetCfgs resets system configuratoins form environment variables
|
||||
ResetCfgs() error
|
||||
// Capacity returns the capacity of image storage
|
||||
Capacity() (*imagestorage.Capacity, error)
|
||||
}
|
||||
|
||||
// NewClient return an instance of Adminserver client
|
||||
func NewClient(baseURL string, authorizer auth.Authorizer) Client {
|
||||
baseURL = strings.TrimRight(baseURL, "/")
|
||||
if !strings.Contains(baseURL, "://") {
|
||||
baseURL = "http://" + baseURL
|
||||
}
|
||||
return &client{
|
||||
baseURL: baseURL,
|
||||
client: &http.Client{},
|
||||
authorizer: authorizer,
|
||||
}
|
||||
}
|
||||
|
||||
type client struct {
|
||||
baseURL string
|
||||
client *http.Client
|
||||
authorizer auth.Authorizer
|
||||
}
|
||||
|
||||
// do creates request and authorizes it if authorizer is not nil
|
||||
func (c *client) do(method, relativePath string, body io.Reader) (*http.Response, error) {
|
||||
url := c.baseURL + relativePath
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.authorizer != nil {
|
||||
if err := c.authorizer.Authorize(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.client.Do(req)
|
||||
}
|
||||
|
||||
func (c *client) Ping() error {
|
||||
addr := strings.Split(c.baseURL, "://")[1]
|
||||
if !strings.Contains(addr, ":") {
|
||||
addr = addr + ":80"
|
||||
}
|
||||
|
||||
return utils.TestTCPConn(addr, 60, 2)
|
||||
}
|
||||
|
||||
// GetCfgs ...
|
||||
func (c *client) GetCfgs() (map[string]interface{}, error) {
|
||||
resp, err := c.do(http.MethodGet, "/api/configurations", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to get configurations: %d %s",
|
||||
resp.StatusCode, b)
|
||||
}
|
||||
|
||||
cfgs := map[string]interface{}{}
|
||||
if err = json.Unmarshal(b, &cfgs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfgs, nil
|
||||
}
|
||||
|
||||
// UpdateCfgs ...
|
||||
func (c *client) UpdateCfgs(cfgs map[string]interface{}) error {
|
||||
data, err := json.Marshal(cfgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := c.do(http.MethodPut, "/api/configurations", bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("failed to update configurations: %d %s",
|
||||
resp.StatusCode, b)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetCfgs ...
|
||||
func (c *client) ResetCfgs() error {
|
||||
resp, err := c.do(http.MethodPost, "/api/configurations/reset", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("failed to reset configurations: %d %s",
|
||||
resp.StatusCode, b)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Capacity ...
|
||||
func (c *client) Capacity() (*imagestorage.Capacity, error) {
|
||||
resp, err := c.do(http.MethodGet, "/api/systeminfo/capacity", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to get capacity: %d %s",
|
||||
resp.StatusCode, b)
|
||||
}
|
||||
|
||||
capacity := &imagestorage.Capacity{}
|
||||
if err = json.Unmarshal(b, capacity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return capacity, nil
|
||||
}
|
82
src/adminserver/client/client_test.go
Normal file
82
src/adminserver/client/client_test.go
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/utils/test"
|
||||
)
|
||||
|
||||
var c Client
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
server, err := test.NewAdminserver(nil)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create adminserver: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
c = NewClient(server.URL, nil)
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestPing(t *testing.T) {
|
||||
err := c.Ping()
|
||||
assert.Nil(t, err, "unexpected error")
|
||||
}
|
||||
|
||||
func TestGetCfgs(t *testing.T) {
|
||||
cfgs, err := c.GetCfgs()
|
||||
if !assert.Nil(t, err, "unexpected error") {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, common.DBAuth, cfgs[common.AUTHMode], "unexpected configuration")
|
||||
}
|
||||
|
||||
func TestUpdateCfgs(t *testing.T) {
|
||||
cfgs := map[string]interface{}{
|
||||
common.AUTHMode: common.LDAPAuth,
|
||||
}
|
||||
err := c.UpdateCfgs(cfgs)
|
||||
if !assert.Nil(t, err, "unexpected error") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetCfgs(t *testing.T) {
|
||||
err := c.ResetCfgs()
|
||||
if !assert.Nil(t, err, "unexpected error") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapacity(t *testing.T) {
|
||||
capacity, err := c.Capacity()
|
||||
if !assert.Nil(t, err, "unexpected error") {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, uint64(100), capacity.Total)
|
||||
assert.Equal(t, uint64(90), capacity.Free)
|
||||
}
|
78
src/adminserver/handlers/handler.go
Normal file
78
src/adminserver/handlers/handler.go
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
gorilla_handlers "github.com/gorilla/handlers"
|
||||
"github.com/vmware/harbor/src/adminserver/auth"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
// NewHandler returns a gorilla router which is wrapped by authenticate handler
|
||||
// and logging handler
|
||||
func NewHandler() http.Handler {
|
||||
h := newRouter()
|
||||
secrets := map[string]string{
|
||||
"uiSecret": os.Getenv("UI_SECRET"),
|
||||
"jobserviceSecret": os.Getenv("JOBSERVICE_SECRET"),
|
||||
}
|
||||
h = newAuthHandler(auth.NewSecretAuthenticator(secrets), h)
|
||||
h = gorilla_handlers.LoggingHandler(os.Stdout, h)
|
||||
return h
|
||||
}
|
||||
|
||||
type authHandler struct {
|
||||
authenticator auth.Authenticator
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func newAuthHandler(authenticator auth.Authenticator, handler http.Handler) http.Handler {
|
||||
return &authHandler{
|
||||
authenticator: authenticator,
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if a.authenticator == nil {
|
||||
if a.handler != nil {
|
||||
a.handler.ServeHTTP(w, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
valid, err := a.authenticator.Authenticate(r)
|
||||
if err != nil {
|
||||
log.Errorf("failed to authenticate request: %v", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError),
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !valid {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized),
|
||||
http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if a.handler != nil {
|
||||
a.handler.ServeHTTP(w, r)
|
||||
}
|
||||
return
|
||||
}
|
73
src/adminserver/handlers/handlers_test.go
Normal file
73
src/adminserver/handlers/handlers_test.go
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/adminserver/auth"
|
||||
)
|
||||
|
||||
type fakeAuthenticator struct {
|
||||
authenticated bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeAuthenticator) Authenticate(req *http.Request) (bool, error) {
|
||||
return f.authenticated, f.err
|
||||
}
|
||||
|
||||
type fakeHandler struct {
|
||||
responseCode int
|
||||
}
|
||||
|
||||
func (f *fakeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(f.responseCode)
|
||||
}
|
||||
|
||||
func TestNewAuthHandler(t *testing.T) {
|
||||
cases := []struct {
|
||||
authenticator auth.Authenticator
|
||||
handler http.Handler
|
||||
responseCode int
|
||||
}{
|
||||
|
||||
{nil, nil, http.StatusOK},
|
||||
{&fakeAuthenticator{
|
||||
authenticated: false,
|
||||
err: nil,
|
||||
}, nil, http.StatusUnauthorized},
|
||||
{&fakeAuthenticator{
|
||||
authenticated: false,
|
||||
err: errors.New("error"),
|
||||
}, nil, http.StatusInternalServerError},
|
||||
{&fakeAuthenticator{
|
||||
authenticated: true,
|
||||
err: nil,
|
||||
}, &fakeHandler{http.StatusNotFound}, http.StatusNotFound},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
handler := newAuthHandler(c.authenticator, c.handler)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, nil)
|
||||
assert.Equal(t, c.responseCode, w.Code, "unexpected response code")
|
||||
}
|
||||
}
|
32
src/adminserver/handlers/router.go
Normal file
32
src/adminserver/handlers/router.go
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/vmware/harbor/src/adminserver/api"
|
||||
)
|
||||
|
||||
func newRouter() http.Handler {
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/api/configurations", api.ListCfgs).Methods("GET")
|
||||
r.HandleFunc("/api/configurations", api.UpdateCfgs).Methods("PUT")
|
||||
r.HandleFunc("/api/configurations/reset", api.ResetCfgs).Methods("POST")
|
||||
r.HandleFunc("/api/systeminfo/capacity", api.Capacity).Methods("GET")
|
||||
return r
|
||||
}
|
64
src/adminserver/main.go
Normal file
64
src/adminserver/main.go
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/vmware/harbor/src/adminserver/handlers"
|
||||
syscfg "github.com/vmware/harbor/src/adminserver/systemcfg"
|
||||
sysinfo "github.com/vmware/harbor/src/adminserver/systeminfo"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
// Server for admin component
|
||||
type Server struct {
|
||||
Port string
|
||||
Handler http.Handler
|
||||
}
|
||||
|
||||
// Serve the API
|
||||
func (s *Server) Serve() error {
|
||||
server := &http.Server{
|
||||
Addr: ":" + s.Port,
|
||||
Handler: s.Handler,
|
||||
}
|
||||
|
||||
return server.ListenAndServe()
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.Info("initializing system configurations...")
|
||||
if err := syscfg.Init(); err != nil {
|
||||
log.Fatalf("failed to initialize the system: %v", err)
|
||||
}
|
||||
log.Info("system initialization completed")
|
||||
|
||||
sysinfo.Init()
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if len(port) == 0 {
|
||||
port = "80"
|
||||
}
|
||||
server := &Server{
|
||||
Port: port,
|
||||
Handler: handlers.NewHandler(),
|
||||
}
|
||||
if err := server.Serve(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
61
src/adminserver/systemcfg/encrypt/encrypt.go
Normal file
61
src/adminserver/systemcfg/encrypt/encrypt.go
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
comcfg "github.com/vmware/harbor/src/common/config"
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
)
|
||||
|
||||
// Encryptor encrypts or decrypts a strings
|
||||
type Encryptor interface {
|
||||
// Encrypt encrypts plaintext
|
||||
Encrypt(string) (string, error)
|
||||
// Decrypt decrypts ciphertext
|
||||
Decrypt(string) (string, error)
|
||||
}
|
||||
|
||||
// AESEncryptor uses AES to encrypt or decrypt string
|
||||
type AESEncryptor struct {
|
||||
keyProvider comcfg.KeyProvider
|
||||
keyParams map[string]interface{}
|
||||
}
|
||||
|
||||
// NewAESEncryptor returns an instance of an AESEncryptor
|
||||
func NewAESEncryptor(keyProvider comcfg.KeyProvider,
|
||||
keyParams map[string]interface{}) Encryptor {
|
||||
return &AESEncryptor{
|
||||
keyProvider: keyProvider,
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt ...
|
||||
func (a *AESEncryptor) Encrypt(plaintext string) (string, error) {
|
||||
key, err := a.keyProvider.Get(a.keyParams)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return utils.ReversibleEncrypt(plaintext, key)
|
||||
}
|
||||
|
||||
// Decrypt ...
|
||||
func (a *AESEncryptor) Decrypt(ciphertext string) (string, error) {
|
||||
key, err := a.keyProvider.Get(a.keyParams)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return utils.ReversibleDecrypt(ciphertext, key)
|
||||
}
|
93
src/adminserver/systemcfg/encrypt/encrypt_test.go
Normal file
93
src/adminserver/systemcfg/encrypt/encrypt_test.go
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
comcfg "github.com/vmware/harbor/src/common/config"
|
||||
)
|
||||
|
||||
type fakeKeyProvider struct {
|
||||
key string
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeKeyProvider) Get(params map[string]interface{}) (
|
||||
string, error) {
|
||||
return f.key, f.err
|
||||
}
|
||||
|
||||
func TestEncrypt(t *testing.T) {
|
||||
cases := []struct {
|
||||
plaintext string
|
||||
keyProvider comcfg.KeyProvider
|
||||
err bool
|
||||
}{
|
||||
{"", &fakeKeyProvider{"", errors.New("error")}, true},
|
||||
{"text", &fakeKeyProvider{"1234567890123456", nil}, false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
encrptor := NewAESEncryptor(c.keyProvider, nil)
|
||||
ciphertext, err := encrptor.Encrypt(c.plaintext)
|
||||
if c.err {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
str, err := encrptor.Decrypt(ciphertext)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, c.plaintext, str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecrypt(t *testing.T) {
|
||||
plaintext := "text"
|
||||
key := "1234567890123456"
|
||||
|
||||
encrptor := NewAESEncryptor(&fakeKeyProvider{
|
||||
key: key,
|
||||
err: nil,
|
||||
}, nil)
|
||||
|
||||
ciphertext, err := encrptor.Encrypt(plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to encrpt %s: %v", plaintext, err)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
ciphertext string
|
||||
keyProvider comcfg.KeyProvider
|
||||
err bool
|
||||
}{
|
||||
{"", &fakeKeyProvider{"", errors.New("error")}, true},
|
||||
{ciphertext, &fakeKeyProvider{key, nil}, false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
encrptor := NewAESEncryptor(c.keyProvider, nil)
|
||||
str, err := encrptor.Decrypt(c.ciphertext)
|
||||
if c.err {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, plaintext, str)
|
||||
}
|
||||
}
|
||||
}
|
27
src/adminserver/systemcfg/store/driver.go
Normal file
27
src/adminserver/systemcfg/store/driver.go
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package store
|
||||
|
||||
// Driver defines methods that a configuration store driver must implement
|
||||
type Driver interface {
|
||||
// Name returns a human-readable name of the driver
|
||||
Name() string
|
||||
// Read reads all the configurations from store
|
||||
Read() (map[string]interface{}, error)
|
||||
// Write writes the configurations to store, the configurations can be
|
||||
// part of all
|
||||
Write(map[string]interface{}) error
|
||||
}
|
98
src/adminserver/systemcfg/store/encrypt/driver.go
Normal file
98
src/adminserver/systemcfg/store/encrypt/driver.go
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/adminserver/systemcfg/encrypt"
|
||||
"github.com/vmware/harbor/src/adminserver/systemcfg/store"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "encrypt"
|
||||
)
|
||||
|
||||
// cfgStore wraps a store.Driver with an encryptor
|
||||
type cfgStore struct {
|
||||
// attrs need to be encrypted and decrypted
|
||||
keys []string
|
||||
encryptor encrypt.Encryptor
|
||||
store store.Driver
|
||||
}
|
||||
|
||||
// NewCfgStore returns an instance of cfgStore
|
||||
// keys are the attrs need to be encrypted or decrypted
|
||||
func NewCfgStore(encryptor encrypt.Encryptor,
|
||||
keys []string, store store.Driver) store.Driver {
|
||||
return &cfgStore{
|
||||
keys: keys,
|
||||
encryptor: encryptor,
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cfgStore) Name() string {
|
||||
return name
|
||||
}
|
||||
|
||||
func (c *cfgStore) Read() (map[string]interface{}, error) {
|
||||
m, err := c.store.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, key := range c.keys {
|
||||
v, ok := m[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
log.Warningf("%v is not string, skip decrypt", v)
|
||||
continue
|
||||
}
|
||||
|
||||
text, err := c.encryptor.Decrypt(str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[key] = text
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *cfgStore) Write(m map[string]interface{}) error {
|
||||
for _, key := range c.keys {
|
||||
v, ok := m[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
log.Warningf("%v is not string, skip encrypt", v)
|
||||
continue
|
||||
}
|
||||
|
||||
ciphertext, err := c.encryptor.Encrypt(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m[key] = ciphertext
|
||||
}
|
||||
return c.store.Write(m)
|
||||
}
|
82
src/adminserver/systemcfg/store/encrypt/driver_test.go
Normal file
82
src/adminserver/systemcfg/store/encrypt/driver_test.go
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type fakeCfgStore struct {
|
||||
cfgs map[string]interface{}
|
||||
}
|
||||
|
||||
func (f *fakeCfgStore) Name() string {
|
||||
return "fake"
|
||||
}
|
||||
|
||||
func (f *fakeCfgStore) Read() (map[string]interface{}, error) {
|
||||
return f.cfgs, nil
|
||||
}
|
||||
|
||||
func (f *fakeCfgStore) Write(cfgs map[string]interface{}) error {
|
||||
f.cfgs = cfgs
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeEncryptor struct {
|
||||
}
|
||||
|
||||
func (f *fakeEncryptor) Encrypt(plaintext string) (string, error) {
|
||||
return "encrypted" + plaintext, nil
|
||||
}
|
||||
|
||||
func (f *fakeEncryptor) Decrypt(ciphertext string) (string, error) {
|
||||
return "decrypted" + ciphertext, nil
|
||||
}
|
||||
|
||||
func TestName(t *testing.T) {
|
||||
driver := NewCfgStore(nil, nil, nil)
|
||||
assert.Equal(t, name, driver.Name())
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
keys := []string{"key"}
|
||||
driver := NewCfgStore(&fakeEncryptor{}, keys, &fakeCfgStore{
|
||||
cfgs: map[string]interface{}{"key": "value"},
|
||||
})
|
||||
|
||||
cfgs, err := driver.Read()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "decryptedvalue", cfgs["key"])
|
||||
}
|
||||
|
||||
func TestWrite(t *testing.T) {
|
||||
keys := []string{"key"}
|
||||
store := &fakeCfgStore{
|
||||
cfgs: map[string]interface{}{},
|
||||
}
|
||||
driver := NewCfgStore(&fakeEncryptor{}, keys, store)
|
||||
|
||||
cfgs := map[string]interface{}{
|
||||
"key": "value",
|
||||
}
|
||||
|
||||
err := driver.Write(cfgs)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "encryptedvalue", store.cfgs["key"])
|
||||
}
|
124
src/adminserver/systemcfg/store/json/driver_json.go
Normal file
124
src/adminserver/systemcfg/store/json/driver_json.go
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/vmware/harbor/src/adminserver/systemcfg/store"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// the default path of configuration file
|
||||
defaultPath = "/etc/harbor/config.json"
|
||||
)
|
||||
|
||||
type cfgStore struct {
|
||||
path string // the path of cfg file
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewCfgStore returns an instance of cfgStore that stores the configurations
|
||||
// in a json file. The file will be created if it does not exist.
|
||||
func NewCfgStore(path ...string) (store.Driver, error) {
|
||||
p := defaultPath
|
||||
if len(path) > 0 && len(path[0]) > 0 {
|
||||
p = path[0]
|
||||
}
|
||||
|
||||
log.Debugf("path of configuration file: %s", p)
|
||||
|
||||
if _, err := os.Stat(p); os.IsNotExist(err) {
|
||||
log.Infof("the configuration file %s does not exist, creating it...", p)
|
||||
if err = os.MkdirAll(filepath.Dir(p), 0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = ioutil.WriteFile(p, []byte{}, 0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &cfgStore{
|
||||
path: p,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Name ...
|
||||
func (c *cfgStore) Name() string {
|
||||
return "JSON"
|
||||
}
|
||||
|
||||
// Read ...
|
||||
func (c *cfgStore) Read() (map[string]interface{}, error) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return read(c.path)
|
||||
}
|
||||
|
||||
func read(path string) (map[string]interface{}, error) {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// empty file
|
||||
if len(b) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
config := map[string]interface{}{}
|
||||
if err = json.Unmarshal(b, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Write ...
|
||||
func (c *cfgStore) Write(config map[string]interface{}) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
cfg, err := read(c.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg == nil {
|
||||
cfg = config
|
||||
} else {
|
||||
for k, v := range config {
|
||||
cfg[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(c.path, b, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
52
src/adminserver/systemcfg/store/json/driver_json_test.go
Normal file
52
src/adminserver/systemcfg/store/json/driver_json_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadWrite(t *testing.T) {
|
||||
path := "/tmp/config.json"
|
||||
store, err := NewCfgStore(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create json cfg store: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.Remove(path); err != nil {
|
||||
t.Fatalf("failed to remove the json file %s: %v", path, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if store.Name() != "JSON" {
|
||||
t.Errorf("unexpected name: %s != %s", store.Name(), "JSON")
|
||||
return
|
||||
}
|
||||
|
||||
config := map[string]interface{}{
|
||||
"key": "value",
|
||||
}
|
||||
if err := store.Write(config); err != nil {
|
||||
t.Errorf("failed to write configurations to json file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = store.Read(); err != nil {
|
||||
t.Errorf("failed to read configurations from json file: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
246
src/adminserver/systemcfg/systemcfg.go
Normal file
246
src/adminserver/systemcfg/systemcfg.go
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package systemcfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
enpt "github.com/vmware/harbor/src/adminserver/systemcfg/encrypt"
|
||||
"github.com/vmware/harbor/src/adminserver/systemcfg/store"
|
||||
"github.com/vmware/harbor/src/adminserver/systemcfg/store/encrypt"
|
||||
"github.com/vmware/harbor/src/adminserver/systemcfg/store/json"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
comcfg "github.com/vmware/harbor/src/common/config"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultJSONCfgStorePath string = "/etc/adminserver/config.json"
|
||||
defaultKeyPath string = "/etc/adminserver/key"
|
||||
)
|
||||
|
||||
var (
|
||||
// CfgStore is a storage driver that configurations
|
||||
// can be read from and wrote to
|
||||
CfgStore store.Driver
|
||||
|
||||
// attrs need to be encrypted or decrypted
|
||||
attrs = []string{
|
||||
common.EmailPassword,
|
||||
common.LDAPSearchPwd,
|
||||
common.MySQLPassword,
|
||||
common.AdminInitialPassword,
|
||||
}
|
||||
|
||||
// all configurations need read from environment variables
|
||||
allEnvs = map[string]interface{}{
|
||||
common.ExtEndpoint: "EXT_ENDPOINT",
|
||||
common.AUTHMode: "AUTH_MODE",
|
||||
common.SelfRegistration: &parser{
|
||||
env: "SELF_REGISTRATION",
|
||||
parse: parseStringToBool,
|
||||
},
|
||||
common.DatabaseType: "DATABASE_TYPE",
|
||||
common.MySQLHost: "MYSQL_HOST",
|
||||
common.MySQLPort: &parser{
|
||||
env: "MYSQL_PORT",
|
||||
parse: parseStringToInt,
|
||||
},
|
||||
common.MySQLUsername: "MYSQL_USR",
|
||||
common.MySQLPassword: "MYSQL_PWD",
|
||||
common.MySQLDatabase: "MYSQL_DATABASE",
|
||||
common.SQLiteFile: "SQLITE_FILE",
|
||||
common.LDAPURL: "LDAP_URL",
|
||||
common.LDAPSearchDN: "LDAP_SEARCH_DN",
|
||||
common.LDAPSearchPwd: "LDAP_SEARCH_PWD",
|
||||
common.LDAPBaseDN: "LDAP_BASE_DN",
|
||||
common.LDAPFilter: "LDAP_FILTER",
|
||||
common.LDAPUID: "LDAP_UID",
|
||||
common.LDAPScope: &parser{
|
||||
env: "LDAP_SCOPE",
|
||||
parse: parseStringToInt,
|
||||
},
|
||||
common.LDAPTimeout: &parser{
|
||||
env: "LDAP_TIMEOUT",
|
||||
parse: parseStringToInt,
|
||||
},
|
||||
common.EmailHost: "EMAIL_HOST",
|
||||
common.EmailPort: &parser{
|
||||
env: "EMAIL_PORT",
|
||||
parse: parseStringToInt,
|
||||
},
|
||||
common.EmailUsername: "EMAIL_USR",
|
||||
common.EmailPassword: "EMAIL_PWD",
|
||||
common.EmailSSL: &parser{
|
||||
env: "EMAIL_SSL",
|
||||
parse: parseStringToBool,
|
||||
},
|
||||
common.EmailFrom: "EMAIL_FROM",
|
||||
common.EmailIdentity: "EMAIL_IDENTITY",
|
||||
common.RegistryURL: "REGISTRY_URL",
|
||||
common.TokenExpiration: &parser{
|
||||
env: "TOKEN_EXPIRATION",
|
||||
parse: parseStringToInt,
|
||||
},
|
||||
common.CfgExpiration: &parser{
|
||||
env: "CFG_EXPIRATION",
|
||||
parse: parseStringToInt,
|
||||
},
|
||||
common.MaxJobWorkers: &parser{
|
||||
env: "MAX_JOB_WORKERS",
|
||||
parse: parseStringToInt,
|
||||
},
|
||||
common.VerifyRemoteCert: &parser{
|
||||
env: "VERIFY_REMOTE_CERT",
|
||||
parse: parseStringToBool,
|
||||
},
|
||||
common.ProjectCreationRestriction: "PROJECT_CREATION_RESTRICTION",
|
||||
common.AdminInitialPassword: "HARBOR_ADMIN_PASSWORD",
|
||||
common.AdmiralEndpoint: "ADMIRAL_URL",
|
||||
common.WithNotary: &parser{
|
||||
env: "WITH_NOTARY",
|
||||
parse: parseStringToBool,
|
||||
},
|
||||
}
|
||||
|
||||
// configurations need read from environment variables
|
||||
// every time the system startup
|
||||
repeatLoadEnvs = map[string]interface{}{
|
||||
common.ExtEndpoint: "EXT_ENDPOINT",
|
||||
common.MySQLPassword: "MYSQL_PWD",
|
||||
common.MaxJobWorkers: &parser{
|
||||
env: "MAX_JOB_WORKERS",
|
||||
parse: parseStringToInt,
|
||||
},
|
||||
common.CfgExpiration: &parser{
|
||||
env: "CFG_EXPIRATION",
|
||||
parse: parseStringToInt,
|
||||
},
|
||||
common.AdmiralEndpoint: "ADMIRAL_URL",
|
||||
common.WithNotary: &parser{
|
||||
env: "WITH_NOTARY",
|
||||
parse: parseStringToBool,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
// the name of env
|
||||
env string
|
||||
// parse the value of env, e.g. parse string to int or
|
||||
// parse string to bool
|
||||
parse func(string) (interface{}, error)
|
||||
}
|
||||
|
||||
func parseStringToInt(str string) (interface{}, error) {
|
||||
if len(str) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return strconv.Atoi(str)
|
||||
}
|
||||
|
||||
func parseStringToBool(str string) (interface{}, error) {
|
||||
return strings.ToLower(str) == "true" ||
|
||||
strings.ToLower(str) == "on", nil
|
||||
}
|
||||
|
||||
// Init system configurations. If env RESET is set or configurations
|
||||
// read from storage driver is null, load all configurations from env
|
||||
func Init() (err error) {
|
||||
if err = initCfgStore(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loadAll := false
|
||||
cfgs := map[string]interface{}{}
|
||||
|
||||
if os.Getenv("RESET") == "true" {
|
||||
log.Info("RESET is set, will load all configurations from environment variables")
|
||||
loadAll = true
|
||||
}
|
||||
|
||||
if !loadAll {
|
||||
cfgs, err = CfgStore.Read()
|
||||
if cfgs == nil {
|
||||
log.Info("configurations read from storage driver are null, will load them from environment variables")
|
||||
loadAll = true
|
||||
cfgs = map[string]interface{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if err = LoadFromEnv(cfgs, loadAll); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return CfgStore.Write(cfgs)
|
||||
}
|
||||
|
||||
func initCfgStore() (err error) {
|
||||
path := os.Getenv("JSON_CFG_STORE_PATH")
|
||||
if len(path) == 0 {
|
||||
path = defaultJSONCfgStorePath
|
||||
}
|
||||
log.Infof("the path of json configuration storage: %s", path)
|
||||
|
||||
CfgStore, err = json.NewCfgStore(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
kp := os.Getenv("KEY_PATH")
|
||||
if len(kp) == 0 {
|
||||
kp = defaultKeyPath
|
||||
}
|
||||
log.Infof("the path of key used by key provider: %s", kp)
|
||||
|
||||
encryptor := enpt.NewAESEncryptor(
|
||||
comcfg.NewFileKeyProvider(kp), nil)
|
||||
|
||||
CfgStore = encrypt.NewCfgStore(encryptor, attrs, CfgStore)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadFromEnv loads the configurations from allEnvs, if all is false, it just loads
|
||||
// the repeatLoadEnvs
|
||||
func LoadFromEnv(cfgs map[string]interface{}, all bool) error {
|
||||
envs := repeatLoadEnvs
|
||||
if all {
|
||||
envs = allEnvs
|
||||
}
|
||||
|
||||
for k, v := range envs {
|
||||
if str, ok := v.(string); ok {
|
||||
cfgs[k] = os.Getenv(str)
|
||||
continue
|
||||
}
|
||||
|
||||
if parser, ok := v.(*parser); ok {
|
||||
i, err := parser.parse(os.Getenv(parser.env))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfgs[k] = i
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("%v is not string or parse type", v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
99
src/adminserver/systemcfg/systemcfg_test.go
Normal file
99
src/adminserver/systemcfg/systemcfg_test.go
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package systemcfg
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
)
|
||||
|
||||
func TestParseStringToInt(t *testing.T) {
|
||||
cases := []struct {
|
||||
input string
|
||||
result int
|
||||
}{
|
||||
{"1", 1},
|
||||
{"-1", -1},
|
||||
{"0", 0},
|
||||
{"", 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
i, err := parseStringToInt(c.input)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, c.result, i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseStringToBool(t *testing.T) {
|
||||
cases := []struct {
|
||||
input string
|
||||
result bool
|
||||
}{
|
||||
{"true", true},
|
||||
{"on", true},
|
||||
{"TRUE", true},
|
||||
{"ON", true},
|
||||
{"other", false},
|
||||
{"", false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
b, _ := parseStringToBool(c.input)
|
||||
assert.Equal(t, c.result, b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitCfgStore(t *testing.T) {
|
||||
os.Clearenv()
|
||||
path := "/tmp/config.json"
|
||||
if err := os.Setenv("JSON_CFG_STORE_PATH", path); err != nil {
|
||||
t.Fatalf("failed to set env: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(path)
|
||||
err := initCfgStore()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestLoadFromEnv(t *testing.T) {
|
||||
os.Clearenv()
|
||||
ldapURL := "ldap://ldap.com"
|
||||
extEndpoint := "http://harbor.com"
|
||||
if err := os.Setenv("LDAP_URL", ldapURL); err != nil {
|
||||
t.Fatalf("failed to set env: %v", err)
|
||||
}
|
||||
cfgs := map[string]interface{}{}
|
||||
err := LoadFromEnv(cfgs, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ldapURL, cfgs[common.LDAPURL])
|
||||
|
||||
os.Clearenv()
|
||||
if err := os.Setenv("LDAP_URL", ldapURL); err != nil {
|
||||
t.Fatalf("failed to set env: %v", err)
|
||||
}
|
||||
if err := os.Setenv("EXT_ENDPOINT", extEndpoint); err != nil {
|
||||
t.Fatalf("failed to set env: %v", err)
|
||||
}
|
||||
|
||||
cfgs = map[string]interface{}{}
|
||||
err = LoadFromEnv(cfgs, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, extEndpoint, cfgs[common.ExtEndpoint])
|
||||
assert.Equal(t, nil, cfgs[common.LDAPURL])
|
||||
}
|
35
src/adminserver/systeminfo/imagestorage/driver.go
Normal file
35
src/adminserver/systeminfo/imagestorage/driver.go
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package imagestorage
|
||||
|
||||
// GlobalDriver is a global image storage driver
|
||||
var GlobalDriver Driver
|
||||
|
||||
// Capacity holds information about capaticy of image storage
|
||||
type Capacity struct {
|
||||
// total size(byte)
|
||||
Total uint64 `json:"total"`
|
||||
// available size(byte)
|
||||
Free uint64 `json:"free"`
|
||||
}
|
||||
|
||||
// Driver defines methods that an image storage driver must implement
|
||||
type Driver interface {
|
||||
// Name returns a human-readable name of the driver
|
||||
Name() string
|
||||
// Cap returns the capacity of the image storage
|
||||
Cap() (*Capacity, error)
|
||||
}
|
56
src/adminserver/systeminfo/imagestorage/filesystem/driver.go
Normal file
56
src/adminserver/systeminfo/imagestorage/filesystem/driver.go
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
storage "github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage"
|
||||
)
|
||||
|
||||
const (
|
||||
driverName = "filesystem"
|
||||
)
|
||||
|
||||
type driver struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// NewDriver returns an instance of filesystem driver
|
||||
func NewDriver(path string) storage.Driver {
|
||||
return &driver{
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns a human-readable name of the fielsystem driver
|
||||
func (d *driver) Name() string {
|
||||
return driverName
|
||||
}
|
||||
|
||||
// Cap returns the capacity of the filesystem storage
|
||||
func (d *driver) Cap() (*storage.Capacity, error) {
|
||||
var stat syscall.Statfs_t
|
||||
err := syscall.Statfs(d.path, &stat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &storage.Capacity{
|
||||
Total: stat.Blocks * uint64(stat.Bsize),
|
||||
Free: stat.Bavail * uint64(stat.Bsize),
|
||||
}, nil
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestName(t *testing.T) {
|
||||
path := "/tmp"
|
||||
driver := NewDriver(path)
|
||||
assert.Equal(t, driver.Name(), driverName, "unexpected driver name")
|
||||
}
|
||||
|
||||
func TestCap(t *testing.T) {
|
||||
path := "/tmp"
|
||||
driver := NewDriver(path)
|
||||
_, err := driver.Cap()
|
||||
assert.Nil(t, err, "unexpected error")
|
||||
}
|
32
src/adminserver/systeminfo/systeminfo.go
Normal file
32
src/adminserver/systeminfo/systeminfo.go
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package systeminfo
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage"
|
||||
"github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage/filesystem"
|
||||
)
|
||||
|
||||
// Init image storage driver
|
||||
func Init() {
|
||||
path := os.Getenv("IMAGE_STORE_PATH")
|
||||
if len(path) == 0 {
|
||||
path = "/data"
|
||||
}
|
||||
imagestorage.GlobalDriver = filesystem.NewDriver(path)
|
||||
}
|
@ -22,7 +22,6 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/astaxie/beego/validation"
|
||||
"github.com/vmware/harbor/src/common/config"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
@ -120,6 +119,8 @@ func (b *BaseAPI) GetUserIDForRequest() (int, bool, bool) {
|
||||
user = nil
|
||||
}
|
||||
if user != nil {
|
||||
b.SetSession("userId", user.UserID)
|
||||
b.SetSession("username", user.Username)
|
||||
// User login successfully no further check required.
|
||||
return user.UserID, false, true
|
||||
}
|
||||
@ -210,8 +211,3 @@ func (b *BaseAPI) GetPaginationParams() (page, pageSize int64) {
|
||||
|
||||
return page, pageSize
|
||||
}
|
||||
|
||||
// GetIsInsecure ...
|
||||
func GetIsInsecure() bool {
|
||||
return !config.VerifyRemoteCert()
|
||||
}
|
||||
|
@ -13,21 +13,3 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/common/config"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetIsInsecure(t *testing.T) {
|
||||
os.Setenv("VERIFY_REMOTE_CERT", "off")
|
||||
err := config.Reload()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to load config, error: %v", err)
|
||||
}
|
||||
if !GetIsInsecure() {
|
||||
t.Errorf("GetIsInsecure() should be true when VERIFY_REMOTE_CERT is off, in fact: false")
|
||||
}
|
||||
os.Unsetenv("VERIFY_REMOTE_CERT")
|
||||
}
|
||||
|
@ -18,161 +18,96 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/cache"
|
||||
"github.com/vmware/harbor/src/adminserver/client"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
)
|
||||
|
||||
// ConfLoader is the interface to load configurations
|
||||
type ConfLoader interface {
|
||||
// Load will load configuration from different source into a string map, the values in the map will be parsed in to configurations.
|
||||
Load() (map[string]string, error)
|
||||
// Manager manages configurations
|
||||
type Manager struct {
|
||||
client client.Client
|
||||
Cache bool
|
||||
cache cache.Cache
|
||||
key string
|
||||
}
|
||||
|
||||
// EnvConfigLoader loads the config from env vars.
|
||||
type EnvConfigLoader struct {
|
||||
Keys []string
|
||||
}
|
||||
|
||||
// Load ...
|
||||
func (ec *EnvConfigLoader) Load() (map[string]string, error) {
|
||||
m := make(map[string]string)
|
||||
for _, k := range ec.Keys {
|
||||
m[k] = os.Getenv(k)
|
||||
// NewManager returns an instance of Manager
|
||||
func NewManager(client client.Client, enableCache bool) *Manager {
|
||||
m := &Manager{
|
||||
client: client,
|
||||
}
|
||||
return m, nil
|
||||
|
||||
if enableCache {
|
||||
m.Cache = true
|
||||
m.cache = cache.NewMemoryCache()
|
||||
m.key = "cfg"
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// ConfParser ...
|
||||
type ConfParser interface {
|
||||
|
||||
//Parse parse the input raw map into a config map
|
||||
Parse(raw map[string]string, config map[string]interface{}) error
|
||||
}
|
||||
|
||||
// Config wraps a map for the processed configuration values,
|
||||
// and loader parser to read configuration from external source and process the values.
|
||||
type Config struct {
|
||||
Config map[string]interface{}
|
||||
Loader ConfLoader
|
||||
Parser ConfParser
|
||||
}
|
||||
|
||||
// Load reload the configurations
|
||||
func (conf *Config) Load() error {
|
||||
rawMap, err := conf.Loader.Load()
|
||||
// Load configurations, if cache is enabled, cache the configurations
|
||||
func (m *Manager) Load() (map[string]interface{}, error) {
|
||||
c, err := m.client.GetCfgs()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
err = conf.Parser.Parse(rawMap, conf.Config)
|
||||
return err
|
||||
}
|
||||
|
||||
// MySQLSetting wraps the settings of a MySQL DB
|
||||
type MySQLSetting struct {
|
||||
Database string
|
||||
User string
|
||||
Password string
|
||||
Host string
|
||||
Port string
|
||||
}
|
||||
|
||||
// SQLiteSetting wraps the settings of a SQLite DB
|
||||
type SQLiteSetting struct {
|
||||
FilePath string
|
||||
}
|
||||
|
||||
type commonParser struct{}
|
||||
|
||||
// Parse parses the db settings, veryfy_remote_cert, ext_endpoint, token_endpoint
|
||||
func (cp *commonParser) Parse(raw map[string]string, config map[string]interface{}) error {
|
||||
db := strings.ToLower(raw["DATABASE"])
|
||||
if db == "mysql" || db == "" {
|
||||
db = "mysql"
|
||||
mySQLDB := raw["MYSQL_DATABASE"]
|
||||
if len(mySQLDB) == 0 {
|
||||
mySQLDB = "registry"
|
||||
if m.Cache {
|
||||
expi, err := getCfgExpiration(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setting := MySQLSetting{
|
||||
mySQLDB,
|
||||
raw["MYSQL_USR"],
|
||||
raw["MYSQL_PWD"],
|
||||
raw["MYSQL_HOST"],
|
||||
raw["MYSQL_PORT"],
|
||||
|
||||
// copy the configuration map so that later modification to the
|
||||
// map does not effect the cached value
|
||||
cachedCfgs := map[string]interface{}{}
|
||||
for k, v := range c {
|
||||
cachedCfgs[k] = v
|
||||
}
|
||||
config["mysql"] = setting
|
||||
} else if db == "sqlite" {
|
||||
f := raw["SQLITE_FILE"]
|
||||
if len(f) == 0 {
|
||||
f = "registry.db"
|
||||
|
||||
if err = m.cache.Put(m.key, cachedCfgs,
|
||||
time.Duration(expi)*time.Second); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setting := SQLiteSetting{
|
||||
f,
|
||||
}
|
||||
config["sqlite"] = setting
|
||||
} else {
|
||||
return fmt.Errorf("Invalid DB: %s", db)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Reset configurations
|
||||
func (m *Manager) Reset() error {
|
||||
return m.client.ResetCfgs()
|
||||
}
|
||||
|
||||
func getCfgExpiration(m map[string]interface{}) (int, error) {
|
||||
if m == nil {
|
||||
return 0, fmt.Errorf("can not get cfg expiration as configurations are null")
|
||||
}
|
||||
config["database"] = db
|
||||
|
||||
//By default it's true
|
||||
config["verify_remote_cert"] = raw["VERIFY_REMOTE_CERT"] != "off"
|
||||
|
||||
config["ext_endpoint"] = raw["EXT_ENDPOINT"]
|
||||
config["token_endpoint"] = raw["TOKEN_ENDPOINT"]
|
||||
config["log_level"] = raw["LOG_LEVEL"]
|
||||
return nil
|
||||
}
|
||||
|
||||
var commonConfig *Config
|
||||
|
||||
func init() {
|
||||
commonKeys := []string{"DATABASE", "MYSQL_DATABASE", "MYSQL_USR", "MYSQL_PWD", "MYSQL_HOST", "MYSQL_PORT", "SQLITE_FILE", "VERIFY_REMOTE_CERT", "EXT_ENDPOINT", "TOKEN_ENDPOINT", "LOG_LEVEL"}
|
||||
commonConfig = &Config{
|
||||
Config: make(map[string]interface{}),
|
||||
Loader: &EnvConfigLoader{Keys: commonKeys},
|
||||
Parser: &commonParser{},
|
||||
expi, ok := m[common.CfgExpiration]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("cfg expiration is not set")
|
||||
}
|
||||
if err := commonConfig.Load(); err != nil {
|
||||
panic(err)
|
||||
|
||||
return int(expi.(float64)), nil
|
||||
}
|
||||
|
||||
// Get : if cache is enabled, read configurations from cache,
|
||||
// if cache is null or cache is disabled it loads configurations directly
|
||||
func (m *Manager) Get() (map[string]interface{}, error) {
|
||||
if m.Cache {
|
||||
c := m.cache.Get(m.key)
|
||||
if c != nil {
|
||||
return c.(map[string]interface{}), nil
|
||||
}
|
||||
}
|
||||
return m.Load()
|
||||
}
|
||||
|
||||
// Reload will reload the configuration.
|
||||
func Reload() error {
|
||||
return commonConfig.Load()
|
||||
}
|
||||
|
||||
// Database returns the DB type in configuration.
|
||||
func Database() string {
|
||||
return commonConfig.Config["database"].(string)
|
||||
}
|
||||
|
||||
// MySQL returns the mysql setting in configuration.
|
||||
func MySQL() MySQLSetting {
|
||||
return commonConfig.Config["mysql"].(MySQLSetting)
|
||||
}
|
||||
|
||||
// SQLite returns the SQLite setting
|
||||
func SQLite() SQLiteSetting {
|
||||
return commonConfig.Config["sqlite"].(SQLiteSetting)
|
||||
}
|
||||
|
||||
// VerifyRemoteCert returns bool value.
|
||||
func VerifyRemoteCert() bool {
|
||||
return commonConfig.Config["verify_remote_cert"].(bool)
|
||||
}
|
||||
|
||||
// ExtEndpoint ...
|
||||
func ExtEndpoint() string {
|
||||
return commonConfig.Config["ext_endpoint"].(string)
|
||||
}
|
||||
|
||||
// TokenEndpoint returns the endpoint string of token service, which can be accessed by internal service of Harbor.
|
||||
func TokenEndpoint() string {
|
||||
return commonConfig.Config["token_endpoint"].(string)
|
||||
}
|
||||
|
||||
// LogLevel returns the log level in string format.
|
||||
func LogLevel() string {
|
||||
return commonConfig.Config["log_level"].(string)
|
||||
// Upload configurations
|
||||
func (m *Manager) Upload(cfgs map[string]interface{}) error {
|
||||
return m.client.UpdateCfgs(cfgs)
|
||||
}
|
||||
|
@ -14,98 +14,5 @@
|
||||
*/
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnvConfLoader(t *testing.T) {
|
||||
os.Unsetenv("KEY2")
|
||||
os.Setenv("KEY1", "V1")
|
||||
os.Setenv("KEY3", "V3")
|
||||
keys := []string{"KEY1", "KEY2"}
|
||||
ecl := EnvConfigLoader{
|
||||
keys,
|
||||
}
|
||||
m, err := ecl.Load()
|
||||
if err != nil {
|
||||
t.Errorf("Error loading the configuration via env: %v", err)
|
||||
}
|
||||
if m["KEY1"] != "V1" {
|
||||
t.Errorf("The value for key KEY1 should be V1, but infact: %s", m["KEY1"])
|
||||
}
|
||||
if len(m["KEY2"]) > 0 {
|
||||
t.Errorf("The value for key KEY2 should be emptye, but infact: %s", m["KEY2"])
|
||||
}
|
||||
if _, ok := m["KEY3"]; ok {
|
||||
t.Errorf("The KEY3 should not be in result as it's not in the initial key list")
|
||||
}
|
||||
os.Unsetenv("KEY1")
|
||||
os.Unsetenv("KEY3")
|
||||
}
|
||||
|
||||
func TestCommonConfig(t *testing.T) {
|
||||
|
||||
mysql := MySQLSetting{"registry", "root", "password", "127.0.0.1", "3306"}
|
||||
sqlite := SQLiteSetting{"file.db"}
|
||||
verify := "off"
|
||||
ext := "http://harbor"
|
||||
token := "http://token"
|
||||
loglevel := "info"
|
||||
|
||||
os.Setenv("DATABASE", "")
|
||||
os.Setenv("MYSQL_DATABASE", mysql.Database)
|
||||
os.Setenv("MYSQL_USR", mysql.User)
|
||||
os.Setenv("MYSQL_PWD", mysql.Password)
|
||||
os.Setenv("MYSQL_HOST", mysql.Host)
|
||||
os.Setenv("MYSQL_PORT", mysql.Port)
|
||||
os.Setenv("SQLITE_FILE", sqlite.FilePath)
|
||||
os.Setenv("VERIFY_REMOTE_CERT", verify)
|
||||
os.Setenv("EXT_ENDPOINT", ext)
|
||||
os.Setenv("TOKEN_ENDPOINT", token)
|
||||
os.Setenv("LOG_LEVEL", loglevel)
|
||||
|
||||
err := Reload()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when loading the configurations, error: %v", err)
|
||||
}
|
||||
if Database() != "mysql" {
|
||||
t.Errorf("Expected Database value: mysql, fact: %s", mysql)
|
||||
}
|
||||
if MySQL() != mysql {
|
||||
t.Errorf("Expected MySQL setting: %+v, fact: %+v", mysql, MySQL())
|
||||
}
|
||||
if VerifyRemoteCert() {
|
||||
t.Errorf("Expected VerifyRemoteCert: false, env var: %s, fact: %v", verify, VerifyRemoteCert())
|
||||
}
|
||||
if ExtEndpoint() != ext {
|
||||
t.Errorf("Expected ExtEndpoint: %s, fact: %s", ext, ExtEndpoint())
|
||||
}
|
||||
if TokenEndpoint() != token {
|
||||
t.Errorf("Expected TokenEndpoint: %s, fact: %s", token, TokenEndpoint())
|
||||
}
|
||||
if LogLevel() != loglevel {
|
||||
t.Errorf("Expected LogLevel: %s, fact: %s", loglevel, LogLevel())
|
||||
}
|
||||
os.Setenv("DATABASE", "sqlite")
|
||||
err = Reload()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when loading the configurations, error: %v", err)
|
||||
}
|
||||
if SQLite() != sqlite {
|
||||
t.Errorf("Expected SQLite setting: %+v, fact %+v", sqlite, SQLite())
|
||||
}
|
||||
|
||||
os.Unsetenv("DATABASE")
|
||||
os.Unsetenv("MYSQL_DATABASE")
|
||||
os.Unsetenv("MYSQL_USR")
|
||||
os.Unsetenv("MYSQL_PWD")
|
||||
os.Unsetenv("MYSQL_HOST")
|
||||
os.Unsetenv("MYSQL_PORT")
|
||||
os.Unsetenv("SQLITE_FILE")
|
||||
os.Unsetenv("VERIFY_REMOTE_CERT")
|
||||
os.Unsetenv("EXT_ENDPOINT")
|
||||
os.Unsetenv("TOKEN_ENDPOINT")
|
||||
os.Unsetenv("LOG_LEVEL")
|
||||
|
||||
}
|
||||
// the functions in common/config/config.go have been tested
|
||||
// by cases in UI and Jobservice
|
||||
|
47
src/common/config/keyprovider.go
Normal file
47
src/common/config/keyprovider.go
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// KeyProvider provides the key used to encrypt and decrypt attrs
|
||||
type KeyProvider interface {
|
||||
// Get returns the key
|
||||
// params can be used to pass parameters in different implements
|
||||
Get(params map[string]interface{}) (string, error)
|
||||
}
|
||||
|
||||
// FileKeyProvider reads key from file
|
||||
type FileKeyProvider struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// NewFileKeyProvider returns an instance of FileKeyProvider
|
||||
// path: where the key should be read from
|
||||
func NewFileKeyProvider(path string) KeyProvider {
|
||||
return &FileKeyProvider{
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the key read from file
|
||||
func (f *FileKeyProvider) Get(params map[string]interface{}) (string, error) {
|
||||
b, err := ioutil.ReadFile(f.path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
43
src/common/config/keyprovider_test.go
Normal file
43
src/common/config/keyprovider_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetOfFileKeyProvider(t *testing.T) {
|
||||
path := "/tmp/key"
|
||||
key := "key_content"
|
||||
|
||||
if err := ioutil.WriteFile(path, []byte(key), 0777); err != nil {
|
||||
t.Errorf("failed to write to file %s: %v", path, err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(path)
|
||||
|
||||
provider := NewFileKeyProvider(path)
|
||||
k, err := provider.Get(nil)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get key from the file provider: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if k != key {
|
||||
t.Errorf("unexpected key: %s != %s", k, key)
|
||||
return
|
||||
}
|
||||
}
|
64
src/common/const.go
Normal file
64
src/common/const.go
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package common
|
||||
|
||||
// const variables
|
||||
const (
|
||||
DBAuth = "db_auth"
|
||||
LDAPAuth = "ldap_auth"
|
||||
ProCrtRestrEveryone = "everyone"
|
||||
ProCrtRestrAdmOnly = "adminonly"
|
||||
LDAPScopeBase = "1"
|
||||
LDAPScopeOnelevel = "2"
|
||||
LDAPScopeSubtree = "3"
|
||||
|
||||
ExtEndpoint = "ext_endpoint"
|
||||
AUTHMode = "auth_mode"
|
||||
DatabaseType = "database_type"
|
||||
MySQLHost = "mysql_host"
|
||||
MySQLPort = "mysql_port"
|
||||
MySQLUsername = "mysql_username"
|
||||
MySQLPassword = "mysql_password"
|
||||
MySQLDatabase = "mysql_database"
|
||||
SQLiteFile = "sqlite_file"
|
||||
SelfRegistration = "self_registration"
|
||||
LDAPURL = "ldap_url"
|
||||
LDAPSearchDN = "ldap_search_dn"
|
||||
LDAPSearchPwd = "ldap_search_password"
|
||||
LDAPBaseDN = "ldap_base_dn"
|
||||
LDAPUID = "ldap_uid"
|
||||
LDAPFilter = "ldap_filter"
|
||||
LDAPScope = "ldap_scope"
|
||||
LDAPTimeout = "ldap_timeout"
|
||||
TokenServiceURL = "token_service_url"
|
||||
RegistryURL = "registry_url"
|
||||
EmailHost = "email_host"
|
||||
EmailPort = "email_port"
|
||||
EmailUsername = "email_username"
|
||||
EmailPassword = "email_password"
|
||||
EmailFrom = "email_from"
|
||||
EmailSSL = "email_ssl"
|
||||
EmailIdentity = "email_identity"
|
||||
ProjectCreationRestriction = "project_creation_restriction"
|
||||
VerifyRemoteCert = "verify_remote_cert"
|
||||
MaxJobWorkers = "max_job_workers"
|
||||
TokenExpiration = "token_expiration"
|
||||
CfgExpiration = "cfg_expiration"
|
||||
JobLogDir = "job_log_dir"
|
||||
AdminInitialPassword = "admin_initial_password"
|
||||
AdmiralEndpoint = "admiral_url"
|
||||
WithNotary = "with_notary"
|
||||
)
|
@ -17,11 +17,12 @@ package dao
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/vmware/harbor/src/common/config"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
@ -39,27 +40,32 @@ type Database interface {
|
||||
}
|
||||
|
||||
// InitDatabase initializes the database
|
||||
func InitDatabase() {
|
||||
database, err := getDatabase()
|
||||
func InitDatabase(database *models.Database) error {
|
||||
db, err := getDatabase(database)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("initializing database: %s", database.String())
|
||||
if err := database.Register(); err != nil {
|
||||
panic(err)
|
||||
log.Infof("initializing database: %s", db.String())
|
||||
if err := db.Register(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("initialize database completed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDatabase() (db Database, err error) {
|
||||
switch config.Database() {
|
||||
func getDatabase(database *models.Database) (db Database, err error) {
|
||||
switch database.Type {
|
||||
case "", "mysql":
|
||||
db = NewMySQL(config.MySQL().Host, config.MySQL().Port, config.MySQL().User,
|
||||
config.MySQL().Password, config.MySQL().Database)
|
||||
db = NewMySQL(database.MySQL.Host,
|
||||
strconv.Itoa(database.MySQL.Port),
|
||||
database.MySQL.Username,
|
||||
database.MySQL.Password,
|
||||
database.MySQL.Database)
|
||||
case "sqlite":
|
||||
db = NewSQLite(config.SQLite().FilePath)
|
||||
db = NewSQLite(database.SQLite.File)
|
||||
default:
|
||||
err = fmt.Errorf("invalid database: %s", config.Database())
|
||||
err = fmt.Errorf("invalid database: %s", database.Type)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
32
src/common/dao/config.go
Normal file
32
src/common/dao/config.go
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
)
|
||||
|
||||
// AuthModeCanBeModified determines whether auth mode can be
|
||||
// modified or not. Auth mode can modified when there is only admin
|
||||
// user in database.
|
||||
func AuthModeCanBeModified() (bool, error) {
|
||||
c, err := GetOrmer().QueryTable(&models.User{}).Count()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// admin and anonymous
|
||||
return c == 2, nil
|
||||
}
|
72
src/common/dao/config_test.go
Normal file
72
src/common/dao/config_test.go
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
)
|
||||
|
||||
func TestAuthModeCanBeModified(t *testing.T) {
|
||||
c, err := GetOrmer().QueryTable(&models.User{}).Count()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to count users: %v", err)
|
||||
}
|
||||
|
||||
if c == 2 {
|
||||
flag, err := AuthModeCanBeModified()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to determine whether auth mode can be modified: %v", err)
|
||||
}
|
||||
if !flag {
|
||||
t.Errorf("unexpected result: %t != %t", flag, true)
|
||||
}
|
||||
|
||||
user := models.User{
|
||||
Username: "user_for_config_test",
|
||||
Email: "user_for_config_test@vmware.com",
|
||||
Password: "P@ssword",
|
||||
Realname: "user_for_config_test",
|
||||
}
|
||||
id, err := Register(user)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to register user: %v", err)
|
||||
}
|
||||
defer func(id int64) {
|
||||
if err := deleteUser(id); err != nil {
|
||||
t.Fatalf("failed to delete user %d: %v", id, err)
|
||||
}
|
||||
}(id)
|
||||
|
||||
flag, err = AuthModeCanBeModified()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to determine whether auth mode can be modified: %v", err)
|
||||
}
|
||||
if flag {
|
||||
t.Errorf("unexpected result: %t != %t", flag, false)
|
||||
}
|
||||
|
||||
} else {
|
||||
flag, err := AuthModeCanBeModified()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to determine whether auth mode can be modified: %v", err)
|
||||
}
|
||||
if flag {
|
||||
t.Errorf("unexpected result: %t != %t", flag, false)
|
||||
}
|
||||
}
|
||||
}
|
@ -17,10 +17,12 @@ package dao
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
//"github.com/vmware/harbor/src/common/config"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
@ -42,7 +44,7 @@ func execUpdate(o orm.Ormer, sql string, params ...interface{}) error {
|
||||
func clearUp(username string) {
|
||||
var err error
|
||||
|
||||
o := orm.NewOrm()
|
||||
o := GetOrmer()
|
||||
o.Begin()
|
||||
|
||||
err = execUpdate(o, `delete
|
||||
@ -156,53 +158,63 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func testForMySQL(m *testing.M) int {
|
||||
db := os.Getenv("DATABASE")
|
||||
defer os.Setenv("DATABASE", db)
|
||||
|
||||
os.Setenv("DATABASE", "mysql")
|
||||
|
||||
dbHost := os.Getenv("DB_HOST")
|
||||
dbHost := os.Getenv("MYSQL_HOST")
|
||||
if len(dbHost) == 0 {
|
||||
log.Fatalf("environment variable DB_HOST is not set")
|
||||
log.Fatalf("environment variable MYSQL_HOST is not set")
|
||||
}
|
||||
dbUser := os.Getenv("DB_USR")
|
||||
dbUser := os.Getenv("MYSQL_USR")
|
||||
if len(dbUser) == 0 {
|
||||
log.Fatalf("environment variable DB_USR is not set")
|
||||
log.Fatalf("environment variable MYSQL_USR is not set")
|
||||
}
|
||||
dbPort := os.Getenv("DB_PORT")
|
||||
if len(dbPort) == 0 {
|
||||
log.Fatalf("environment variable DB_PORT is not set")
|
||||
dbPortStr := os.Getenv("MYSQL_PORT")
|
||||
if len(dbPortStr) == 0 {
|
||||
log.Fatalf("environment variable MYSQL_PORT is not set")
|
||||
}
|
||||
dbPort, err := strconv.Atoi(dbPortStr)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid MYSQL_PORT: %v", err)
|
||||
}
|
||||
dbPassword := os.Getenv("DB_PWD")
|
||||
|
||||
log.Infof("DB_HOST: %s, DB_USR: %s, DB_PORT: %s, DB_PWD: %s\n", dbHost, dbUser, dbPort, dbPassword)
|
||||
dbPassword := os.Getenv("MYSQL_PWD")
|
||||
dbDatabase := os.Getenv("MYSQL_DATABASE")
|
||||
if len(dbDatabase) == 0 {
|
||||
log.Fatalf("environment variable MYSQL_DATABASE is not set")
|
||||
}
|
||||
|
||||
os.Setenv("MYSQL_HOST", dbHost)
|
||||
os.Setenv("MYSQL_PORT", dbPort)
|
||||
os.Setenv("MYSQL_USR", dbUser)
|
||||
os.Setenv("MYSQL_PWD", dbPassword)
|
||||
database := &models.Database{
|
||||
Type: "mysql",
|
||||
MySQL: &models.MySQL{
|
||||
Host: dbHost,
|
||||
Port: dbPort,
|
||||
Username: dbUser,
|
||||
Password: dbPassword,
|
||||
Database: dbDatabase,
|
||||
},
|
||||
}
|
||||
|
||||
return testForAll(m)
|
||||
log.Infof("MYSQL_HOST: %s, MYSQL_USR: %s, MYSQL_PORT: %s, MYSQL_PWD: %s\n", dbHost, dbUser, dbPort, dbPassword)
|
||||
|
||||
return testForAll(m, database)
|
||||
}
|
||||
|
||||
func testForSQLite(m *testing.M) int {
|
||||
db := os.Getenv("DATABASE")
|
||||
defer os.Setenv("DATABASE", db)
|
||||
|
||||
os.Setenv("DATABASE", "sqlite")
|
||||
|
||||
file := os.Getenv("SQLITE_FILE")
|
||||
if len(file) == 0 {
|
||||
os.Setenv("SQLITE_FILE", "/registry.db")
|
||||
defer os.Setenv("SQLITE_FILE", "")
|
||||
log.Fatalf("environment variable SQLITE_FILE is not set")
|
||||
}
|
||||
|
||||
return testForAll(m)
|
||||
database := &models.Database{
|
||||
Type: "sqlite",
|
||||
SQLite: &models.SQLite{
|
||||
File: file,
|
||||
},
|
||||
}
|
||||
|
||||
return testForAll(m, database)
|
||||
}
|
||||
|
||||
func testForAll(m *testing.M) int {
|
||||
os.Setenv("AUTH_MODE", "db_auth")
|
||||
initDatabaseForTest()
|
||||
func testForAll(m *testing.M, database *models.Database) int {
|
||||
initDatabaseForTest(database)
|
||||
clearUp(username)
|
||||
|
||||
return m.Run()
|
||||
@ -210,8 +222,8 @@ func testForAll(m *testing.M) int {
|
||||
|
||||
var defaultRegistered = false
|
||||
|
||||
func initDatabaseForTest() {
|
||||
database, err := getDatabase()
|
||||
func initDatabaseForTest(db *models.Database) {
|
||||
database, err := getDatabase(db)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -226,6 +238,12 @@ func initDatabaseForTest() {
|
||||
if err := database.Register(alias); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if alias != "default" {
|
||||
if err = globalOrm.Using(alias); err != nil {
|
||||
log.Fatalf("failed to create new orm: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
@ -980,13 +998,6 @@ func TestGetRecentLogs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTopRepos(t *testing.T) {
|
||||
_, err := GetTopRepos(10)
|
||||
if err != nil {
|
||||
t.Fatalf("error occured in getting top repos, error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var targetID, policyID, policyID2, policyID3, jobID, jobID2, jobID3 int64
|
||||
|
||||
func TestAddRepTarget(t *testing.T) {
|
||||
|
@ -16,15 +16,11 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
_ "github.com/go-sql-driver/mysql" //register mysql driver
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
)
|
||||
|
||||
type mysql struct {
|
||||
@ -48,7 +44,8 @@ func NewMySQL(host, port, usr, pwd, database string) Database {
|
||||
|
||||
// Register registers MySQL as the underlying database used
|
||||
func (m *mysql) Register(alias ...string) error {
|
||||
if err := m.testConn(m.host, m.port); err != nil {
|
||||
|
||||
if err := utils.TestTCPConn(m.host+":"+m.port, 60, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -65,30 +62,6 @@ func (m *mysql) Register(alias ...string) error {
|
||||
return orm.RegisterDataBase(an, "mysql", conn)
|
||||
}
|
||||
|
||||
func (m *mysql) testConn(host, port string) error {
|
||||
ch := make(chan int, 1)
|
||||
go func() {
|
||||
var err error
|
||||
var c net.Conn
|
||||
for {
|
||||
c, err = net.DialTimeout("tcp", host+":"+port, 20*time.Second)
|
||||
if err == nil {
|
||||
c.Close()
|
||||
ch <- 1
|
||||
} else {
|
||||
log.Errorf("failed to connect to db, retry after 2 seconds :%v", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-ch:
|
||||
return nil
|
||||
case <-time.After(60 * time.Second):
|
||||
return errors.New("failed to connect to database after 60 seconds")
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of MySQL
|
||||
func (m *mysql) Name() string {
|
||||
return "MySQL"
|
||||
|
@ -168,10 +168,15 @@ func ToggleProjectPublicity(projectID int64, publicity int) error {
|
||||
// 2. the prject is public or the user is a member of the project
|
||||
func SearchProjects(userID int) ([]models.Project, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select distinct p.project_id, p.name, p.public
|
||||
|
||||
sql :=
|
||||
`select distinct p.project_id, p.name, p.public,
|
||||
p.owner_id, p.creation_time, p.update_time
|
||||
from project p
|
||||
left join project_member pm on p.project_id = pm.project_id
|
||||
where (pm.user_id = ? or p.public = 1) and p.deleted = 0`
|
||||
left join project_member pm
|
||||
on p.project_id = pm.project_id
|
||||
where (pm.user_id = ? or p.public = 1)
|
||||
and p.deleted = 0 `
|
||||
|
||||
var projects []models.Project
|
||||
|
||||
|
@ -50,7 +50,8 @@ func GetRepositoryByName(name string) (*models.RepoRecord, error) {
|
||||
func GetAllRepositories() ([]models.RepoRecord, error) {
|
||||
o := GetOrmer()
|
||||
var repos []models.RepoRecord
|
||||
_, err := o.QueryTable("repository").All(&repos)
|
||||
_, err := o.QueryTable("repository").
|
||||
OrderBy("Name").All(&repos)
|
||||
return repos, err
|
||||
}
|
||||
|
||||
@ -102,23 +103,29 @@ func GetRepositoryByProjectName(name string) ([]*models.RepoRecord, error) {
|
||||
}
|
||||
|
||||
//GetTopRepos returns the most popular repositories
|
||||
func GetTopRepos(count int) ([]models.TopRepo, error) {
|
||||
topRepos := []models.TopRepo{}
|
||||
|
||||
func GetTopRepos(userID int, count int) ([]*models.RepoRecord, error) {
|
||||
sql :=
|
||||
`select r.repository_id, r.name, r.owner_id,
|
||||
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
|
||||
where (
|
||||
p.deleted = 0 and (
|
||||
p.public = 1 or (
|
||||
? <> ? and (
|
||||
exists (
|
||||
select 1 from user u
|
||||
where u.user_id = ? and u.sysadmin_flag = 1
|
||||
) or exists (
|
||||
select 1 from project_member pm
|
||||
where pm.project_id = p.project_id and pm.user_id = ?
|
||||
)))))
|
||||
order by r.pull_count desc, r.name limit ?`
|
||||
repositories := []*models.RepoRecord{}
|
||||
if _, err := GetOrmer().QueryTable(&models.RepoRecord{}).
|
||||
OrderBy("-PullCount", "Name").Limit(count).All(&repositories); err != nil {
|
||||
return topRepos, err
|
||||
}
|
||||
_, err := GetOrmer().Raw(sql, userID, NonExistUserID, userID, userID, count).QueryRows(&repositories)
|
||||
|
||||
for _, repository := range repositories {
|
||||
topRepos = append(topRepos, models.TopRepo{
|
||||
RepoName: repository.Name,
|
||||
AccessCount: repository.PullCount,
|
||||
})
|
||||
}
|
||||
|
||||
return topRepos, nil
|
||||
return repositories, err
|
||||
}
|
||||
|
||||
// GetTotalOfRepositories ...
|
||||
@ -169,3 +176,34 @@ func GetTotalOfUserRelevantRepositories(userID int, name string) (int64, error)
|
||||
err := GetOrmer().Raw(sql, params).QueryRow(&total)
|
||||
return total, err
|
||||
}
|
||||
|
||||
// GetTotalOfRepositoriesByProject ...
|
||||
func GetTotalOfRepositoriesByProject(projectID int64, name string) (int64, error) {
|
||||
qs := GetOrmer().QueryTable(&models.RepoRecord{}).
|
||||
Filter("ProjectID", projectID)
|
||||
|
||||
if len(name) != 0 {
|
||||
qs = qs.Filter("Name__contains", name)
|
||||
}
|
||||
|
||||
return qs.Count()
|
||||
}
|
||||
|
||||
// GetRepositoriesByProject ...
|
||||
func GetRepositoriesByProject(projectID int64, name string,
|
||||
limit, offset int64) ([]*models.RepoRecord, error) {
|
||||
|
||||
repositories := []*models.RepoRecord{}
|
||||
|
||||
qs := GetOrmer().QueryTable(&models.RepoRecord{}).
|
||||
Filter("ProjectID", projectID)
|
||||
|
||||
if len(name) != 0 {
|
||||
qs = qs.Filter("Name__contains", name)
|
||||
}
|
||||
|
||||
_, err := qs.Limit(limit).
|
||||
Offset(offset).All(&repositories)
|
||||
|
||||
return repositories, err
|
||||
}
|
||||
|
@ -16,8 +16,11 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
)
|
||||
|
||||
@ -160,6 +163,208 @@ func TestGetTotalOfUserRelevantRepositories(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTopRepos(t *testing.T) {
|
||||
var err error
|
||||
require := require.New(t)
|
||||
|
||||
require.NoError(GetOrmer().Begin())
|
||||
defer func() {
|
||||
require.NoError(GetOrmer().Rollback())
|
||||
}()
|
||||
|
||||
admin, err := GetUser(models.User{Username: "admin"})
|
||||
require.NoError(err)
|
||||
|
||||
user := models.User{
|
||||
Username: "user",
|
||||
Password: "user",
|
||||
Email: "user@test.com",
|
||||
}
|
||||
userID, err := Register(user)
|
||||
require.NoError(err)
|
||||
user.UserID = int(userID)
|
||||
|
||||
//
|
||||
// public project with 1 repository
|
||||
// non-public project with 2 repositories visible by admin
|
||||
// non-public project with 1 repository visible by admin and user
|
||||
// deleted public project with 1 repository
|
||||
//
|
||||
|
||||
project1 := models.Project{
|
||||
OwnerID: admin.UserID,
|
||||
Name: "project1",
|
||||
CreationTime: time.Now(),
|
||||
OwnerName: admin.Username,
|
||||
Public: 0,
|
||||
}
|
||||
project1.ProjectID, err = AddProject(project1)
|
||||
require.NoError(err)
|
||||
|
||||
project2 := models.Project{
|
||||
OwnerID: user.UserID,
|
||||
Name: "project2",
|
||||
CreationTime: time.Now(),
|
||||
OwnerName: user.Username,
|
||||
Public: 0,
|
||||
}
|
||||
project2.ProjectID, err = AddProject(project2)
|
||||
require.NoError(err)
|
||||
|
||||
err = AddRepository(*repository)
|
||||
require.NoError(err)
|
||||
|
||||
repository1 := &models.RepoRecord{
|
||||
Name: fmt.Sprintf("%v/repository1", project1.Name),
|
||||
OwnerName: admin.Username,
|
||||
ProjectName: project1.Name,
|
||||
}
|
||||
err = AddRepository(*repository1)
|
||||
require.NoError(err)
|
||||
require.NoError(IncreasePullCount(repository1.Name))
|
||||
repository1, err = GetRepositoryByName(repository1.Name)
|
||||
require.NoError(err)
|
||||
|
||||
repository2 := &models.RepoRecord{
|
||||
Name: fmt.Sprintf("%v/repository2", project1.Name),
|
||||
OwnerName: admin.Username,
|
||||
ProjectName: project1.Name,
|
||||
}
|
||||
err = AddRepository(*repository2)
|
||||
require.NoError(err)
|
||||
require.NoError(IncreasePullCount(repository2.Name))
|
||||
require.NoError(IncreasePullCount(repository2.Name))
|
||||
repository2, err = GetRepositoryByName(repository2.Name)
|
||||
require.NoError(err)
|
||||
|
||||
repository3 := &models.RepoRecord{
|
||||
Name: fmt.Sprintf("%v/repository3", project2.Name),
|
||||
OwnerName: admin.Username,
|
||||
ProjectName: project2.Name,
|
||||
}
|
||||
err = AddRepository(*repository3)
|
||||
require.NoError(err)
|
||||
require.NoError(IncreasePullCount(repository3.Name))
|
||||
require.NoError(IncreasePullCount(repository3.Name))
|
||||
require.NoError(IncreasePullCount(repository3.Name))
|
||||
repository3, err = GetRepositoryByName(repository3.Name)
|
||||
require.NoError(err)
|
||||
|
||||
deletedPublicProject := models.Project{
|
||||
OwnerID: admin.UserID,
|
||||
Name: "public-deleted",
|
||||
CreationTime: time.Now(),
|
||||
OwnerName: admin.Username,
|
||||
Public: 1,
|
||||
}
|
||||
deletedPublicProject.ProjectID, err = AddProject(deletedPublicProject)
|
||||
require.NoError(err)
|
||||
deletedPublicRepository1 := &models.RepoRecord{
|
||||
Name: fmt.Sprintf("%v/repository1", deletedPublicProject.Name),
|
||||
OwnerName: admin.Username,
|
||||
ProjectName: deletedPublicProject.Name,
|
||||
}
|
||||
err = AddRepository(*deletedPublicRepository1)
|
||||
require.NoError(err)
|
||||
err = DeleteProject(deletedPublicProject.ProjectID)
|
||||
require.NoError(err)
|
||||
|
||||
var topRepos []*models.RepoRecord
|
||||
|
||||
// anonymous should retrieve public non-deleted repositories
|
||||
topRepos, err = GetTopRepos(NonExistUserID, 100)
|
||||
require.NoError(err)
|
||||
require.Len(topRepos, 1)
|
||||
require.Equal(topRepos[0].Name, repository.Name)
|
||||
|
||||
// admin should retrieve all repositories
|
||||
topRepos, err = GetTopRepos(admin.UserID, 100)
|
||||
require.NoError(err)
|
||||
require.Len(topRepos, 4)
|
||||
|
||||
// user should retrieve visible repositories
|
||||
topRepos, err = GetTopRepos(user.UserID, 100)
|
||||
require.NoError(err)
|
||||
require.Len(topRepos, 2)
|
||||
|
||||
// limit by count
|
||||
topRepos, err = GetTopRepos(admin.UserID, 3)
|
||||
require.NoError(err)
|
||||
require.Len(topRepos, 3)
|
||||
}
|
||||
|
||||
func TestGetTotalOfRepositoriesByProject(t *testing.T) {
|
||||
var projectID int64 = 1
|
||||
repoName := "library/total_count"
|
||||
|
||||
total, err := GetTotalOfRepositoriesByProject(projectID, repoName)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get total of repositoreis of project %d: %v", projectID, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := addRepository(&models.RepoRecord{
|
||||
Name: repoName,
|
||||
OwnerName: "admin",
|
||||
ProjectName: "library",
|
||||
}); err != nil {
|
||||
t.Errorf("failed to add repository %s: %v", repoName, err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := deleteRepository(repoName); err != nil {
|
||||
t.Errorf("failed to delete repository %s: %v", name, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
n, err := GetTotalOfRepositoriesByProject(projectID, repoName)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get total of repositoreis of project %d: %v", projectID, err)
|
||||
return
|
||||
}
|
||||
|
||||
if n != total+1 {
|
||||
t.Errorf("unexpected total: %d != %d", n, total+1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRepositoriesByProject(t *testing.T) {
|
||||
var projectID int64 = 1
|
||||
repoName := "library/repository"
|
||||
|
||||
if err := addRepository(&models.RepoRecord{
|
||||
Name: repoName,
|
||||
OwnerName: "admin",
|
||||
ProjectName: "library",
|
||||
}); err != nil {
|
||||
t.Errorf("failed to add repository %s: %v", repoName, err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := deleteRepository(repoName); err != nil {
|
||||
t.Errorf("failed to delete repository %s: %v", name, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
repositories, err := GetRepositoriesByProject(projectID, repoName, 10, 0)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get repositoreis of project %d: %v", projectID, err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Log(repositories)
|
||||
|
||||
for _, repository := range repositories {
|
||||
if repository.Name == repoName {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
t.Errorf("repository %s not found", repoName)
|
||||
}
|
||||
|
||||
func addRepository(repository *models.RepoRecord) error {
|
||||
return AddRepository(*repository)
|
||||
}
|
||||
|
@ -38,6 +38,11 @@ func TestDeleteUser(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to register user: %v", err)
|
||||
}
|
||||
defer func(id int64) {
|
||||
if err := deleteUser(id); err != nil {
|
||||
t.Fatalf("failed to delete user %d: %v", id, err)
|
||||
}
|
||||
}(id)
|
||||
|
||||
err = DeleteUser(int(id))
|
||||
if err != nil {
|
||||
@ -67,3 +72,11 @@ func TestDeleteUser(t *testing.T) {
|
||||
expected)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteUser(id int64) error {
|
||||
if _, err := GetOrmer().QueryTable(&models.User{}).
|
||||
Filter("UserID", id).Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
99
src/common/models/config.go
Normal file
99
src/common/models/config.go
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package models
|
||||
|
||||
/*
|
||||
// Authentication ...
|
||||
type Authentication struct {
|
||||
Mode string `json:"mode"`
|
||||
SelfRegistration bool `json:"self_registration"`
|
||||
LDAP *LDAP `json:"ldap,omitempty"`
|
||||
}
|
||||
*/
|
||||
|
||||
// LDAP ...
|
||||
type LDAP struct {
|
||||
URL string `json:"url"`
|
||||
SearchDN string `json:"search_dn"`
|
||||
SearchPassword string `json:"search_password"`
|
||||
BaseDN string `json:"base_dn"`
|
||||
Filter string `json:"filter"`
|
||||
UID string `json:"uid"`
|
||||
Scope int `json:"scope"`
|
||||
Timeout int `json:"timeout"` // in second
|
||||
}
|
||||
|
||||
// Database ...
|
||||
type Database struct {
|
||||
Type string `json:"type"`
|
||||
MySQL *MySQL `json:"mysql,omitempty"`
|
||||
SQLite *SQLite `json:"sqlite,omitempty"`
|
||||
}
|
||||
|
||||
// MySQL ...
|
||||
type MySQL struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Database string `json:"database"`
|
||||
}
|
||||
|
||||
// SQLite ...
|
||||
type SQLite struct {
|
||||
File string `json:"file"`
|
||||
}
|
||||
|
||||
// Email ...
|
||||
type Email struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
SSL bool `json:"ssl"`
|
||||
Identity string `json:"identity"`
|
||||
From string `json:"from"`
|
||||
}
|
||||
|
||||
/*
|
||||
// Registry ...
|
||||
type Registry struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// TokenService ...
|
||||
type TokenService struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// SystemCfg holds all configurations of system
|
||||
type SystemCfg struct {
|
||||
DomainName string `json:"domain_name"` // Harbor external URL: protocal://host:port
|
||||
Authentication *Authentication `json:"authentication"`
|
||||
Database *Database `json:"database"`
|
||||
TokenService *TokenService `json:"token_service"`
|
||||
Registry *Registry `json:"registry"`
|
||||
Email *Email `json:"email"`
|
||||
VerifyRemoteCert bool `json:"verify_remote_cert"`
|
||||
ProjectCreationRestriction string `json:"project_creation_restriction"`
|
||||
MaxJobWorkers int `json:"max_job_workers"`
|
||||
JobLogDir string `json:"job_log_dir"`
|
||||
InitialAdminPwd string `json:"initial_admin_pwd,omitempty"`
|
||||
TokenExpiration int `json:"token_expiration"` // in minute
|
||||
SecretKey string `json:"secret_key,omitempty"`
|
||||
CfgExpiration int `json:"cfg_expiration"`
|
||||
}
|
||||
*/
|
46
src/common/models/ldap.go
Normal file
46
src/common/models/ldap.go
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package models
|
||||
|
||||
// LdapConf holds information about ldap configuration
|
||||
type LdapConf struct {
|
||||
LdapURL string `json:"ldap_url"`
|
||||
LdapSearchDn string `json:"ldap_search_dn"`
|
||||
LdapSearchPassword string `json:"ldap_search_password"`
|
||||
LdapBaseDn string `json:"ldap_base_dn"`
|
||||
LdapFilter string `json:"ldap_filter"`
|
||||
LdapUID string `json:"ldap_uid"`
|
||||
LdapScope int `json:"ldap_scope"`
|
||||
LdapConnectionTimeout int `json:"ldap_connection_timeout"`
|
||||
}
|
||||
|
||||
// LdapUser ...
|
||||
type LdapUser struct {
|
||||
Username string `json:"ldap_username"`
|
||||
Email string `json:"ldap_email"`
|
||||
Realname string `json:"ldap_realname"`
|
||||
}
|
||||
|
||||
//LdapImportUser ...
|
||||
type LdapImportUser struct {
|
||||
LdapUIDList []string `json:"ldap_uid_list"`
|
||||
}
|
||||
|
||||
// LdapFailedImportUser ...
|
||||
type LdapFailedImportUser struct {
|
||||
UID string `json:"uid"`
|
||||
Error string `json:"err_msg"`
|
||||
}
|
@ -44,7 +44,7 @@ const (
|
||||
//RepOpDelete represents the operation of a job to remove repository from a remote registry/harbor instance.
|
||||
RepOpDelete string = "delete"
|
||||
//UISecretCookie is the cookie name to contain the UI secret
|
||||
UISecretCookie string = "uisecret"
|
||||
UISecretCookie string = "secret"
|
||||
)
|
||||
|
||||
// RepPolicy is the model for a replication policy, which associate to a project and a target (destination)
|
||||
|
154
src/common/utils/email/mail.go
Normal file
154
src/common/utils/email/mail.go
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package email
|
||||
|
||||
import (
|
||||
tlspkg "crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
// Send ...
|
||||
func Send(addr, identity, username, password string,
|
||||
timeout int, tls, insecure bool, from string,
|
||||
to []string, subject, message string) error {
|
||||
|
||||
client, err := newClient(addr, identity, username,
|
||||
password, timeout, tls, insecure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
if err = client.Mail(from); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, t := range to {
|
||||
if err = client.Rcpt(t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template := "From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-version: 1.0;\r\nContent-Type: text/html; charset=\"UTF-8\"\r\n\n%s\r\n"
|
||||
data := fmt.Sprintf(template, from,
|
||||
strings.Join(to, ","), subject, message)
|
||||
|
||||
_, err = w.Write([]byte(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.Quit()
|
||||
}
|
||||
|
||||
// Ping tests the connection and authentication with email server
|
||||
// If tls is true, a secure connection is established, or Ping
|
||||
// trys to upgrate the insecure connection to a secure one if
|
||||
// email server supports it.
|
||||
// Ping doesn't verify the server's certificate and hostname when
|
||||
// needed if the parameter insecure is ture
|
||||
func Ping(addr, identity, username, password string,
|
||||
timeout int, tls, insecure bool) error {
|
||||
client, err := newClient(addr, identity, username, password,
|
||||
timeout, tls, insecure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// caller needs to close the client
|
||||
func newClient(addr, identity, username, password string,
|
||||
timeout int, tls, insecure bool) (*smtp.Client, error) {
|
||||
log.Debugf("establishing TCP connection with %s ...", addr)
|
||||
conn, err := net.DialTimeout("tcp", addr,
|
||||
time.Duration(timeout)*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tls {
|
||||
log.Debugf("establishing SSL/TLS connection with %s ...", addr)
|
||||
tlsConn := tlspkg.Client(conn, &tlspkg.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: insecure,
|
||||
})
|
||||
if err = tlsConn.Handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn = tlsConn
|
||||
}
|
||||
|
||||
log.Debugf("creating SMTP client for %s ...", host)
|
||||
client, err := smtp.NewClient(conn, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//try to swith to SSL/TLS
|
||||
if !tls {
|
||||
if ok, _ := client.Extension("STARTTLS"); ok {
|
||||
log.Debugf("switching the connection with %s to SSL/TLS ...", addr)
|
||||
if err = client.StartTLS(&tlspkg.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: insecure,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
log.Debugf("the email server %s does not support STARTTLS", addr)
|
||||
}
|
||||
}
|
||||
|
||||
if ok, _ := client.Extension("AUTH"); ok {
|
||||
log.Debug("authenticating the client...")
|
||||
// only support plain auth
|
||||
if err = client.Auth(smtp.PlainAuth(identity,
|
||||
username, password, host)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
log.Debugf("the email server %s does not support AUTH, skip",
|
||||
addr)
|
||||
}
|
||||
|
||||
log.Debug("create smtp client successfully")
|
||||
|
||||
return client, nil
|
||||
}
|
103
src/common/utils/email/mail_test.go
Normal file
103
src/common/utils/email/mail_test.go
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package email
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSend(t *testing.T) {
|
||||
addr := "smtp.gmail.com:465"
|
||||
identity := ""
|
||||
username := "harbortestonly@gmail.com"
|
||||
password := "harborharbor"
|
||||
timeout := 60
|
||||
tls := true
|
||||
insecure := false
|
||||
from := "from"
|
||||
to := []string{username}
|
||||
subject := "subject"
|
||||
message := "message"
|
||||
|
||||
// tls connection
|
||||
tls = true
|
||||
err := Send(addr, identity, username, password,
|
||||
timeout, tls, insecure, from, to,
|
||||
subject, message)
|
||||
assert.Nil(t, err)
|
||||
|
||||
/*not work on travis
|
||||
// non-tls connection
|
||||
addr = "smtp.gmail.com:25"
|
||||
tls = false
|
||||
err = Send(addr, identity, username, password,
|
||||
timeout, tls, insecure, from, to,
|
||||
subject, message)
|
||||
assert.Nil(t, err)
|
||||
*/
|
||||
|
||||
//invalid username/password
|
||||
username = "invalid_username"
|
||||
err = Send(addr, identity, username, password,
|
||||
timeout, tls, insecure, from, to,
|
||||
subject, message)
|
||||
if err == nil {
|
||||
t.Errorf("there should be an auth error")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "535") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPing(t *testing.T) {
|
||||
addr := "smtp.gmail.com:465"
|
||||
identity := ""
|
||||
username := "harbortestonly@gmail.com"
|
||||
password := "harborharbor"
|
||||
timeout := 0
|
||||
tls := true
|
||||
insecure := false
|
||||
|
||||
// tls connection
|
||||
err := Ping(addr, identity, username, password,
|
||||
timeout, tls, insecure)
|
||||
assert.Nil(t, err)
|
||||
|
||||
/*not work on travis
|
||||
// non-tls connection
|
||||
addr = "smtp.gmail.com:25"
|
||||
tls = false
|
||||
err = Ping(addr, identity, username, password,
|
||||
timeout, tls, insecure)
|
||||
assert.Nil(t, err)
|
||||
*/
|
||||
|
||||
//invalid username/password
|
||||
username = "invalid_username"
|
||||
err = Ping(addr, identity, username, password,
|
||||
timeout, tls, insecure)
|
||||
if err == nil {
|
||||
t.Errorf("there should be an auth error")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "535") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user