Merge pull request #1916 from vmware/dev

merge dev branch to master.
This commit is contained in:
Daniel Jiang 2017-04-05 17:18:54 +08:00 committed by GitHub
commit b7a196f7aa
998 changed files with 77155 additions and 20197 deletions

23
.gitignore vendored
View File

@ -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

View File

@ -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
@ -81,17 +86,23 @@ script:
- export MYSQL_HOST=$IP
- export REGISTRY_URL=$IP:5000
- echo $REGISTRY_URL
- ./tests/pushimage.sh
- ./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

812
Makefile
View File

@ -1,368 +1,444 @@
# Makefile for Harbor project
#
# Targets:
#
# 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_golangimage:
# compile from golang image
# for example: make compile_golangimage -e GOBUILDIMAGE= \
# harborgo:1.6.2
# 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
#
# install: include compile binarys, build images, prepare specific \
# version composefile and startup Harbor instance
#
# start: startup Harbor instance
#
# down: shutdown Harbor instance
#
# package_online:
# prepare online install package
# for example: make package_online -e DEVFLAG=false\
# REGISTRYSERVER=reg-bj.eng.vmware.com \
# REGISTRYPROJECTNAME=harborrelease
#
# package_offline:
# prepare offline install package
#
# pushimage: push Harbor images to specific registry server
# for example: make pushimage -e DEVFLAG=false REGISTRYUSER=admin \
# REGISTRYPASSWORD=***** \
# REGISTRYSERVER=reg-bj.eng.vmware.com/ \
# REGISTRYPROJECTNAME=harborrelease
# note**: need add "/" on end of REGISTRYSERVER. If not setting \
# this value will push images directly to dockerhub.
# make pushimage -e DEVFLAG=false REGISTRYUSER=vmware \
# REGISTRYPASSWORD=***** \
# REGISTRYPROJECTNAME=vmware
#
# clean: 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:
# cleanpackageremove specific version tag
# cleanpackage: remove online/offline install package
#
# other example:
# clean specific version binarys and images:
# make clean -e VERSIONTAG=[TAG]
# note**: If commit new code to github, the git commit TAG will \
# change. Better use this commond clean previous images and \
# files with specific TAG.
# By default DEVFLAG=true, if you want to release new version of Harbor, \
# should setting the flag to false.
# make XXXX -e DEVFLAG=false
SHELL := /bin/bash
BUILDPATH=$(CURDIR)
MAKEPATH=$(BUILDPATH)/make
MAKEDEVPATH=$(MAKEPATH)/dev
SRCPATH=./src
TOOLSPATH=$(BUILDPATH)/tools
GOBASEPATH=/go/src/github.com/vmware
CHECKENVCMD=checkenv.sh
BASEIMAGE=photon
COMPILETAG=compile_normal
REGISTRYSERVER=
REGISTRYPROJECTNAME=vmware
DEVFLAG=true
# docker parameters
DOCKERCMD=$(shell which docker)
DOCKERBUILD=$(DOCKERCMD) build
DOCKERRMIMAGE=$(DOCKERCMD) rmi
DOCKERPULL=$(DOCKERCMD) pull
DOCKERIMASES=$(DOCKERCMD) images
DOCKERSAVE=$(DOCKERCMD) save
DOCKERCOMPOSECMD=$(shell which docker-compose)
DOCKERTAG=$(DOCKERCMD) tag
# go parameters
GOCMD=$(shell which go)
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOINSTALL=$(GOCMD) install
GOTEST=$(GOCMD) test
GODEP=$(GOTEST) -i
GOFMT=gofmt -w
GOBUILDIMAGE=reg.mydomain.com/library/harborgo[:tag]
GOBUILDPATH=$(GOBASEPATH)/harbor
GOIMAGEBUILDCMD=/usr/local/go/bin/go
GOIMAGEBUILD=$(GOIMAGEBUILDCMD) build
GOBUILDPATH_UI=$(GOBUILDPATH)/src/ui
GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/src/jobservice
GOBUILDMAKEPATH=$(GOBUILDPATH)/make
GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/dev/ui
GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice
GOLANGDOCKERFILENAME=Dockerfile.golang
# binary
UISOURCECODE=$(SRCPATH)/ui
UIBINARYPATH=$(MAKEDEVPATH)/ui
UIBINARYNAME=harbor_ui
JOBSERVICESOURCECODE=$(SRCPATH)/jobservice
JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice
JOBSERVICEBINARYNAME=harbor_jobservice
# prepare parameters
PREPAREPATH=$(TOOLSPATH)
PREPARECMD=prepare
# configfile
CONFIGPATH=$(MAKEPATH)
CONFIGFILE=harbor.cfg
# makefile
MAKEFILEPATH_PHOTON=$(MAKEPATH)/photon
MAKEFILEPATH_UBUNTU=$(MAKEPATH)/ubuntu
# common dockerfile
DOCKERFILEPATH_COMMON=$(MAKEPATH)/common
DOCKERFILEPATH_DB=$(DOCKERFILEPATH_COMMON)/db
DOCKERFILENAME_DB=Dockerfile
# docker image name
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
# version prepare
VERSIONFILEPATH=$(SRCPATH)/ui/views/sections
VERSIONFILENAME=header-content.htm
GITCMD=$(shell which git)
GITTAG=$(GITCMD) describe --tags
ifeq ($(DEVFLAG), true)
VERSIONTAG=dev
else
VERSIONTAG=$(shell $(GITTAG))
endif
SEDCMD=$(shell which sed)
# package
TARCMD=$(shell which tar)
ZIPCMD=$(shell which gzip)
DOCKERIMGFILE=harbor
HARBORPKG=harbor
# pushimage
PUSHSCRIPTPATH=$(MAKEPATH)
PUSHSCRIPTNAME=pushimage.sh
REGISTRYUSER=user
REGISTRYPASSWORD=default
version:
@if [ "$(DEVFLAG)" = "false" ] ; then \
$(SEDCMD) -i 's/version=\"{{.Version}}\"/version=\"$(VERSIONTAG)\"/' -i $(VERSIONFILEPATH)/$(VERSIONFILENAME) ; \
fi
check_environment:
@$(MAKEPATH)/$(CHECKENVCMD)
compile_ui:
@echo "compiling binary for ui..."
@$(GOBUILD) -o $(UIBINARYPATH)/$(UIBINARYNAME) $(UISOURCECODE)
@echo "Done."
compile_jobservice:
@echo "compiling binary for 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) .
@echo "Done."
compile_golangimage:
@echo "compiling binary for ui (golang image)..."
@echo $(GOBASEPATH)
@echo $(GOBUILDPATH)
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_UI) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_UI)/$(UIBINARYNAME)
@echo "Done."
@echo "compiling binary for jobservice (golang image)..."
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_JOBSERVICE) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_JOBSERVICE)/$(JOBSERVICEBINARYNAME)
@echo "Done."
compile:check_environment $(COMPILETAG)
prepare:
@echo "preparing..."
@$(MAKEPATH)/$(PREPARECMD) -conf $(CONFIGPATH)/$(CONFIGFILE)
build_common: version
@echo "buildging db container for photon..."
@cd $(DOCKERFILEPATH_DB) && $(DOCKERBUILD) -f $(DOCKERFILENAME_DB) -t $(DOCKERIMAGENAME_DB):$(VERSIONTAG) .
@echo "Done."
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)
install: compile build prepare modify_composefile
@echo "loading harbor images..."
@$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) up -d
@echo "Install complete. You can visit harbor now."
package_online: modify_composefile
@echo "packing online package ..."
@cp -r make $(HARBORPKG)
@if [ -n "$(REGISTRYSERVER)" ] ; then \
$(SEDCMD) -i 's/image\: vmware/image\: $(REGISTRYSERVER)\/$(REGISTRYPROJECTNAME)/' \
$(HARBORPKG)/docker-compose.yml ; \
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)
@rm -rf $(HARBORPKG)
@echo "Done."
package_offline: compile build modify_composefile
@echo "packing offline package ..."
@cp -r make $(HARBORPKG)
@cp LICENSE $(HARBORPKG)/LICENSE
@cp NOTICE $(HARBORPKG)/NOTICE
@echo "pulling nginx and registry..."
@$(DOCKERPULL) registry:2.5.0
@$(DOCKERPULL) nginx:1.11.5
@echo "saving harbor docker image"
@$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
nginx:1.11.5 registry:2.5.0 photon:1.0
@$(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)
@rm -rf $(HARBORPKG)
@echo "Done."
pushimage:
@echo "pushing harbor images ..."
@$(DOCKERTAG) $(DOCKERIMAGENAME_UI):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
@$(DOCKERTAG) $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
@$(DOCKERTAG) $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
@$(DOCKERTAG) $(DOCKERIMAGENAME_DB):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG)
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG)
start:
@echo "loading harbor images..."
@$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml up -d
@echo "Start complete. You can visit harbor now."
down:
@echo "stoping harbor instance..."
@$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml down
@echo "Done."
cleanbinary:
@echo "cleaning binary..."
@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_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 nginx:1.11.5
cleandockercomposefile:
@echo "cleaning $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml"
@if [ -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ] ; then rm $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ; fi
cleanversiontag:
@echo "cleaning version TAG"
@$(SEDCMD) -i 's/version=\"$(VERSIONTAG)\"/version=\"{{.Version}}\"/' -i $(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
.PHONY: cleanall
cleanall: cleanbinary cleanimage cleandockercomposefile cleanversiontag cleanpackage
clean:
@echo " make cleanall: remove binary, Harbor images, specific version docker-compose"
@echo " file, specific version tag, online and offline install package"
@echo " make cleanbinary: remove ui and jobservice binary"
@echo " make cleanimage: remove Harbor images"
@echo " make cleandockercomposefile: remove specific version docker-compose"
@echo " make cleanversiontag: cleanpackageremove specific version tag"
@echo " make cleanpackage: remove online and offline install package"
all: install
# Makefile for Harbor project
#
# Targets:
#
# all: prepare env, compile binarys, build images and install images
# prepare: prepare env
# compile: compile adminserver, ui and jobservice code
#
# compile_golangimage:
# compile from golang image
# for example: make compile_golangimage -e GOBUILDIMAGE= \
# 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 baseimage
#
# install: include compile binarys, build images, prepare specific \
# version composefile and startup Harbor instance
#
# start: startup Harbor instance
#
# down: shutdown Harbor instance
#
# package_online:
# prepare online install package
# for example: make package_online -e DEVFLAG=false\
# REGISTRYSERVER=reg-bj.eng.vmware.com \
# REGISTRYPROJECTNAME=harborrelease
#
# package_offline:
# prepare offline install package
#
# pushimage: push Harbor images to specific registry server
# for example: make pushimage -e DEVFLAG=false REGISTRYUSER=admin \
# REGISTRYPASSWORD=***** \
# REGISTRYSERVER=reg-bj.eng.vmware.com/ \
# REGISTRYPROJECTNAME=harborrelease
# note**: need add "/" on end of REGISTRYSERVER. If not setting \
# this value will push images directly to dockerhub.
# make pushimage -e DEVFLAG=false REGISTRYUSER=vmware \
# REGISTRYPASSWORD=***** \
# REGISTRYPROJECTNAME=vmware
#
# clean: remove binary, Harbor images, specific version docker-compose \
# file, specific version tag and online/offline install package
# cleanbinary: remove adminserver, ui and jobservice binary
# cleanimage: remove Harbor images
# cleandockercomposefile:
# remove specific version docker-compose
# cleanversiontag:
# cleanpackageremove specific version tag
# cleanpackage: remove online/offline install package
#
# other example:
# clean specific version binarys and images:
# make clean -e VERSIONTAG=[TAG]
# note**: If commit new code to github, the git commit TAG will \
# change. Better use this commond clean previous images and \
# files with specific TAG.
# By default DEVFLAG=true, if you want to release new version of Harbor, \
# should setting the flag to false.
# make XXXX -e DEVFLAG=false
SHELL := /bin/bash
BUILDPATH=$(CURDIR)
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
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)
DOCKERBUILD=$(DOCKERCMD) build
DOCKERRMIMAGE=$(DOCKERCMD) rmi
DOCKERPULL=$(DOCKERCMD) pull
DOCKERIMASES=$(DOCKERCMD) images
DOCKERSAVE=$(DOCKERCMD) save
DOCKERCOMPOSECMD=$(shell which docker-compose)
DOCKERTAG=$(DOCKERCMD) tag
# go parameters
GOCMD=$(shell which go)
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOINSTALL=$(GOCMD) install
GOTEST=$(GOCMD) test
GODEP=$(GOTEST) -i
GOFMT=gofmt -w
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
JOBSERVICESOURCECODE=$(SRCPATH)/jobservice
JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice
JOBSERVICEBINARYNAME=harbor_jobservice
# prepare parameters
PREPAREPATH=$(TOOLSPATH)
PREPARECMD=prepare
# configfile
CONFIGPATH=$(MAKEPATH)
CONFIGFILE=harbor.cfg
# makefile
MAKEFILEPATH_PHOTON=$(MAKEPATH)/photon
# common dockerfile
DOCKERFILEPATH_COMMON=$(MAKEPATH)/common
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=$(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=$(GITTAGVERSION)
endif
SEDCMD=$(shell which sed)
# package
TARCMD=$(shell which tar)
ZIPCMD=$(shell which gzip)
DOCKERIMGFILE=harbor
HARBORPKG=harbor
# pushimage
PUSHSCRIPTPATH=$(MAKEPATH)
PUSHSCRIPTNAME=pushimage.sh
REGISTRYUSER=user
REGISTRYPASSWORD=default
version:
@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)
@echo "Done."
compile_jobservice:
@echo "compiling binary for jobservice..."
@$(GOBUILD) -o $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) $(JOBSERVICESOURCECODE)
@echo "Done."
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."
@echo "compiling binary for ui (golang image)..."
@echo $(GOBASEPATH)
@echo $(GOBUILDPATH)
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_UI) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_UI)/$(UIBINARYNAME)
@echo "Done."
@echo "compiling binary for jobservice (golang image)..."
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_JOBSERVICE) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_JOBSERVICE)/$(JOBSERVICEBINARYNAME)
@echo "Done."
compile:check_environment $(COMPILETAG)
prepare:
@echo "preparing..."
@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..."
@cd $(DOCKERFILEPATH_DB) && $(DOCKERBUILD) -f $(DOCKERFILENAME_DB) -t $(DOCKERIMAGENAME_DB):$(VERSIONTAG) .
@echo "Done."
build_photon: build_common
make -f $(MAKEFILEPATH_PHOTON)/Makefile build -e DEVFLAG=$(DEVFLAG)
build: build_$(BASEIMAGE)
modify_composefile:
@echo "preparing docker-compose file..."
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSETPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
@$(SEDCMD) -i 's/__version__/$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
install: compile build prepare modify_composefile start
package_online: modify_composefile
@echo "packing online package ..."
@cp -r make $(HARBORPKG)
@if [ -n "$(REGISTRYSERVER)" ] ; then \
$(SEDCMD) -i 's/image\: vmware/image\: $(REGISTRYSERVER)\/$(REGISTRYPROJECTNAME)/' \
$(HARBORPKG)/docker-compose.yml ; \
fi
@cp LICENSE $(HARBORPKG)/LICENSE
@cp NOTICE $(HARBORPKG)/NOTICE
@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."
package_offline: compile build modify_composefile
@echo "packing offline package ..."
@cp -r make $(HARBORPKG)
@cp LICENSE $(HARBORPKG)/LICENSE
@cp NOTICE $(HARBORPKG)/NOTICE
@echo "pulling nginx and registry..."
@$(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"
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
$(DOCKERSAVE) $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
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
@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)
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
@$(DOCKERTAG) $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
@$(DOCKERTAG) $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
@$(DOCKERTAG) $(DOCKERIMAGENAME_DB):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG)
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG)
start:
@echo "loading harbor images..."
@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..."
@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:$(REGISTRYVERSION)
# - $(DOCKERRMIMAGE) -f nginx:1.11.5
cleandockercomposefile:
@echo "cleaning $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml"
@if [ -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ] ; then rm $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ; fi
cleanversiontag:
@echo "cleaning version TAG"
@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-$(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
clean:
@echo " make cleanall: remove binary, Harbor images, specific version docker-compose"
@echo " file, specific version tag, online and offline install package"
@echo " make cleanbinary: remove ui and jobservice binary"
@echo " make cleanimage: remove Harbor images"
@echo " make cleandockercomposefile: remove specific version docker-compose"
@echo " make cleanversiontag: cleanpackageremove specific version tag"
@echo " make cleanpackage: remove online and offline install package"
all: install

1
VERSION Normal file
View File

@ -0,0 +1 @@
dev

View File

@ -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

View File

@ -1,291 +1,292 @@
# Installation and Configuration Guide
Harbor can be installed by one of three approaches:
- **Online installer:** The installer downloads Harbor's images from Docker hub. For this reason, the installer is very small in size.
- **Offline installer:** Use this installer when the host does not have an Internet connection. The installer contains pre-built images so its size is larger.
- **Virtual Appliance:** If you are installing Harbor as the registry component of vSphere Integrated Containers (VIC), or using Harbor as a standalone registry on vSphere platform, download the OVA version of Harbor.
All installers can be downloaded from the **[official release](https://github.com/vmware/harbor/releases)** page.
To install Harbor's virtual appliance, refer to the **[Harbor Installation Guide for Virtual Appliance](installation_guide_ova.md)**.
This guide describes the steps to install and configure Harbor by using the online or offline installer. The installation processes are almost the same.
If you run a previous version of Harbor, you may need to migrate the data to fit the new database schema. For more details, please refer to **[Data Migration Guide](migration_guide.md)**.
In addition, the deployment instructions on Kubernetes has been created by the community. Refer to set up [Harbor on Kubernetes](kubernetes_deployment.md) for details.
## Prerequisites for the target host
Harbor is deployed as several Docker containers, and, therefore, can be deployed on any Linux distribution that supports Docker. The target host requires Python, Docker, and Docker Compose to be installed.
* Python should be version 2.7 or higher. Note that you may have to install Python on Linux distributions (Gentoo, Arch) that do not come with a Python interpreter installed by default
* Docker engine should be version 1.10 or higher. For installation instructions, please refer to: https://docs.docker.com/engine/installation/
* Docker Compose needs to be version 1.6.0 or higher. For installation instructions, please refer to: https://docs.docker.com/compose/install/
## Installation Steps
The installation steps boil down to the following
1. Download the installer;
2. Configure **harbor.cfg**;
3. Run **install.sh** to install and start Harbor;
#### Downloading the installer:
The binary of the installer can be downloaded from the [release](https://github.com/vmware/harbor/releases) page. Choose either online or offline installer. Use *tar* command to extract the package.
Online installer:
```
$ tar xvf harbor-online-installer-<version>.tgz
```
Offline installer:
```
$ tar xvf harbor-offline-installer-<version>.tgz
```
#### Configuring Harbor
Configuration parameters are located in the file **harbor.cfg**.
The parameters are described below - note that at the very least, you will need to change the **hostname** attribute.
* **hostname**: The target host's hostname, which is used to access the UI and the registry service. It should be the IP address or the fully qualified domain name (FQDN) of your target machine, e.g., `192.168.1.10` or `reg.yourdomain.com`. _Do NOT use `localhost` or `127.0.0.1` for the hostname - the registry service needs to be accessible by external clients!_
* **ui_url_protocol**: (**http** or **https**. Default is **http**) The protocol used to access the UI and the token/notification service. By default, this is _http_. To set up the https protocol, refer to **[Configuring Harbor with HTTPS Access](configure_https.md)**.
* **Email settings**: These parameters are needed for Harbor to be able to send a user a "password reset" email, and are only necessary if that functionality is needed. Also, do note that by default SSL connectivity is _not_ enabled - if your SMTP server requires SSL, but does _not_ support STARTTLS, then you should enable SSL by setting **email_ssl = true**.
* email_server = smtp.mydomain.com
* email_server_port = 25
* email_username = sample_admin@mydomain.com
* email_password = abc
* 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** ._
* **auth_mode**: The type of authentication that is used. By default, it is **db_auth**, i.e. the credentials are stored in a database. For LDAP authentication, set this to **ldap_auth**.
* **ldap_url**: The LDAP endpoint URL (e.g. `ldaps://ldap.mydomain.com`). _Only used when **auth_mode** is set to *ldap_auth* ._
* **ldap_searchdn**: The DN of a user who has the permission to search an LDAP/AD server (e.g. `uid=admin,ou=people,dc=mydomain,dc=com`).
* **ldap_search_pwd**: The password of the user specified by *ldap_searchdn*.
* **ldap_basedn**: The base DN to look up a user, e.g. `ou=people,dc=mydomain,dc=com`. _Only used when **auth_mode** is set to *ldap_auth* ._
* **ldap_filter**:The search filter for looking up a user, e.g. `(objectClass=person)`.
* **ldap_uid**: The attribute used to match a user during a LDAP search, it could be uid, cn, email or other attributes.
* **ldap_scope**: The scope to search for a user, 1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE. Default is 3.
* **db_password**: The root password for the MySQL database used for **db_auth**. _Change this password for any production use!_
* **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.
* **token_expiration**: The expiration time (in minutes) of a token created by token service, default is 30 minutes.
* **verify_remote_cert**: (**on** or **off**. Default is **on**) This flag determines whether or not to verify SSL/TLS certificate when Harbor communicates with a remote registry instance. Setting this attribute to **off** bypasses the SSL/TLS verification, which is often used when the remote instance has a self-signed or untrusted certificate.
* **customize_crt**: (**on** or **off**. Default is **on**) When this attribute is **on**, the prepare script creates private key and root certificate for the generation/verification of the registry's token. The following attributes:**crt_country**, **crt_state**, **crt_location**, **crt_organization**, **crt_organizationalunit**, **crt_commonname**, **crt_email** are used as parameters for generating the keys. Set this attribute to **off** when the key and root certificate are supplied by external sources. Refer to [Customize Key and Certificate of Harbor Token Service](customize_token_service.md) for more info.
#### Configuring storage backend (optional)
By default, Harbor stores images on your local filesystem. In a production environment, you may consider
using other storage backend instead of the local filesystem, like S3, Openstack Swift, Ceph, etc.
What you need to update is the section of `storage` in the file `common/templates/registry/config.yml`.
For example, if you use Openstack Swift as your storage backend, the section may look like this:
```
storage:
swift:
username: admin
password: ADMIN_PASS
authurl: http://keystone_addr:35357/v3/auth
tenant: admin
domain: default
region: regionOne
container: docker_images
```
_NOTE: For detailed information on storage backend of a registry, refer to [Registry Configuration Reference](https://docs.docker.com/registry/configuration/) ._
#### Finishing installation and starting Harbor
Once **harbor.cfg** and storage backend (optional) are configured, install and start Harbor using the ```install.sh``` script. Note that it may take some time for the online installer to download Harbor images from Docker hub.
```sh
$ sudo ./install.sh
```
If everything worked properly, you should be able to open a browser to visit the admin portal at **http://reg.yourdomain.com** (change *reg.yourdomain.com* to the hostname configured in your harbor.cfg). Note that the default administrator username/password are admin/Harbor12345 .
Log in to the admin portal and create a new project, e.g. `myproject`. You can then use docker commands to login and push images (By default, the registry server listens on port 80):
```sh
$ docker login reg.yourdomain.com
$ docker push reg.yourdomain.com/myproject/myrepo:mytag
```
**IMPORTANT:** The default installation of Harbor uses _HTTP_ - as such, you will need to add the option `--insecure-registry` to your client's Docker daemon and restart the Docker service.
For information on how to use Harbor, please refer to **[User Guide of Harbor](user_guide.md)** .
#### Configuring Harbor with HTTPS access
Harbor does not ship with any certificates, and, by default, uses HTTP to serve requests. While this makes it relatively simple to set up and run - especially for a development or testing environment - it is **not** recommended for a production environment. To enable HTTPS, please refer to **[Configuring Harbor with HTTPS Access](configure_https.md)**.
### Managing Harbor's lifecycle
You can use docker-compose to manage the lifecycle of Harbor. Some useful commands are listed as follows (must run in the same directory as *docker-compose.yml*).
Stopping Harbor:
```
$ sudo docker-compose stop
Stopping nginx ... done
Stopping harbor-jobservice ... done
Stopping harbor-ui ... done
Stopping harbor-db ... done
Stopping registry ... done
Stopping harbor-log ... done
```
Restarting Harbor after stopping:
```
$ sudo docker-compose start
Starting log ... done
Starting ui ... done
Starting mysql ... done
Starting jobservice ... done
Starting registry ... done
Starting proxy ... done
```
To change Harbor's configuration, first stop existing Harbor instance, update harbor.cfg, and then run install.sh again:
```
$ sudo docker-compose down
$ vim harbor.cfg
$ sudo install.sh
```
Removing Harbor's containers while keeping the image data and Harbor's database files on the file system:
```
$ sudo docker-compose down
```
Removing Harbor's database and image data (for a clean re-installation):
```sh
$ rm -r /data/database
$ rm -r /data/registry
```
Please check the [Docker Compose command-line reference](https://docs.docker.com/compose/reference/) for more on docker-compose.
### Persistent data and log files
By default, registry data is persisted in the target host's `/data/` directory. This data remains unchanged even when Harbor's containers are removed and/or recreated.
In addition, Harbor uses *rsyslog* to collect the logs of each container. By default, these log files are stored in the directory `/var/log/harbor/` on the target host for troubleshooting.
## Configuring Harbor listening on a customized port
By default, Harbor listens on port 80(HTTP) and 443(HTTPS, if configured) for both admin portal and docker commands, you can configure it with a customized one.
### For HTTP protocol
1.Modify docker-compose.yml
Replace the first "80" to a customized port, e.g. 8888:80.
```
proxy:
image: library/nginx:1.11.5
restart: always
volumes:
- ./config/nginx:/etc/nginx
ports:
- 8888:80
- 443:443
depends_on:
- mysql
- registry
- ui
- log
logging:
driver: "syslog"
options:
syslog-address: "tcp://127.0.0.1:1514"
tag: "proxy"
```
2.Modify templates/registry/config.yml
Add the customized port, e.g. ":8888", after "$ui_url".
```
auth:
token:
issuer: registry-token-issuer
realm: $ui_url:8888/service/token
rootcertbundle: /etc/registry/root.crt
service: token-service
```
3.Run install.sh to update and start Harbor.
```sh
$ sudo docker-compose down
$ sudo install.sh
```
### For HTTPS protocol
1.Enable HTTPS in Harbor by following this [guide](https://github.com/vmware/harbor/blob/master/docs/configure_https.md).
2.Modify docker-compose.yml
Replace the first "443" to a customized port, e.g. 4443:443.
```
proxy:
image: library/nginx:1.11.5
restart: always
volumes:
- ./config/nginx:/etc/nginx
ports:
- 80:80
- 4443:443
depends_on:
- mysql
- registry
- ui
- log
logging:
driver: "syslog"
options:
syslog-address: "tcp://127.0.0.1:1514"
tag: "proxy"
```
3.Modify templates/registry/config.yml
Add the customized port, e.g. ":4443", after "$ui_url".
```
auth:
token:
issuer: registry-token-issuer
realm: $ui_url:4443/service/token
rootcertbundle: /etc/registry/root.crt
service: token-service
```
4.Run install.sh to update and start Harbor.
```sh
$ sudo docker-compose down
$ sudo install.sh
```
## Troubleshooting
1. When Harbor does not work properly, run the below commands to find out if all containers of Harbor are in **UP** status:
```
$ sudo docker-compose ps
Name Command State Ports
-----------------------------------------------------------------------------------------------------
harbor-db docker-entrypoint.sh mysqld Up 3306/tcp
harbor-jobservice /harbor/harbor_jobservice Up
harbor-log /bin/sh -c crond && rsyslo ... Up 0.0.0.0:1514->514/tcp
harbor-ui /harbor/harbor_ui Up
nginx nginx -g daemon off; Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
registry /entrypoint.sh serve /etc/ ... Up 5000/tcp
```
If a container is not in **UP** state, check the log file of that container in directory ```/var/log/harbor```. For example, if the container ```harbor-ui``` is not running, you should look at the log file ```ui.log```.
2.When setting up Harbor behind an nginx proxy or elastic load balancing, look for the line below, in `common/templates/nginx/nginx.http.conf` and remove it from the sections if the proxy already has similar settings: `location /`, `location /v2/` and `location /service/`.
```
proxy_set_header X-Forwarded-Proto $scheme;
```
And run the following commands to restart Harbor:
```sh
$ sudo docker-compose down
$ sudo ./prepare
$ sudo docker-compose up -d
```
# Installation and Configuration Guide
Harbor can be installed by one of three approaches:
- **Online installer:** The installer downloads Harbor's images from Docker hub. For this reason, the installer is very small in size.
- **Offline installer:** Use this installer when the host does not have an Internet connection. The installer contains pre-built images so its size is larger.
- **Virtual Appliance:** If you are installing Harbor as the registry component of vSphere Integrated Containers (VIC), or using Harbor as a standalone registry on vSphere platform, download the OVA version of Harbor.
All installers can be downloaded from the **[official release](https://github.com/vmware/harbor/releases)** page.
To install Harbor's virtual appliance, refer to the **[Harbor Installation Guide for Virtual Appliance](installation_guide_ova.md)**.
This guide describes the steps to install and configure Harbor by using the online or offline installer. The installation processes are almost the same.
If you run a previous version of Harbor, you may need to migrate the data to fit the new database schema. For more details, please refer to **[Data Migration Guide](migration_guide.md)**.
In addition, the deployment instructions on Kubernetes has been created by the community. Refer to set up [Harbor on Kubernetes](kubernetes_deployment.md) for details.
## Prerequisites for the target host
Harbor is deployed as several Docker containers, and, therefore, can be deployed on any Linux distribution that supports Docker. The target host requires Python, Docker, and Docker Compose to be installed.
* Python should be version 2.7 or higher. Note that you may have to install Python on Linux distributions (Gentoo, Arch) that do not come with a Python interpreter installed by default
* Docker engine should be version 1.10 or higher. For installation instructions, please refer to: https://docs.docker.com/engine/installation/
* Docker Compose needs to be version 1.6.0 or higher. For installation instructions, please refer to: https://docs.docker.com/compose/install/
## Installation Steps
The installation steps boil down to the following
1. Download the installer;
2. Configure **harbor.cfg**;
3. Run **install.sh** to install and start Harbor;
#### Downloading the installer:
The binary of the installer can be downloaded from the [release](https://github.com/vmware/harbor/releases) page. Choose either online or offline installer. Use *tar* command to extract the package.
Online installer:
```
$ tar xvf harbor-online-installer-<version>.tgz
```
Offline installer:
```
$ tar xvf harbor-offline-installer-<version>.tgz
```
#### Configuring Harbor
Configuration parameters are located in the file **harbor.cfg**.
The parameters are described below - note that at the very least, you will need to change the **hostname** attribute.
* **hostname**: The target host's hostname, which is used to access the UI and the registry service. It should be the IP address or the fully qualified domain name (FQDN) of your target machine, e.g., `192.168.1.10` or `reg.yourdomain.com`. _Do NOT use `localhost` or `127.0.0.1` for the hostname - the registry service needs to be accessible by external clients!_
* **ui_url_protocol**: (**http** or **https**. Default is **http**) The protocol used to access the UI and the token/notification service. By default, this is _http_. To set up the https protocol, refer to **[Configuring Harbor with HTTPS Access](configure_https.md)**.
* **Email settings**: These parameters are needed for Harbor to be able to send a user a "password reset" email, and are only necessary if that functionality is needed. Also, do note that by default SSL connectivity is _not_ enabled - if your SMTP server requires SSL, but does _not_ support STARTTLS, then you should enable SSL by setting **email_ssl = true**.
* email_server = smtp.mydomain.com
* email_server_port = 25
* email_username = sample_admin@mydomain.com
* email_password = abc
* 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** ._
* **auth_mode**: The type of authentication that is used. By default, it is **db_auth**, i.e. the credentials are stored in a database. For LDAP authentication, set this to **ldap_auth**.
* **ldap_url**: The LDAP endpoint URL (e.g. `ldaps://ldap.mydomain.com`). _Only used when **auth_mode** is set to *ldap_auth* ._
* **ldap_searchdn**: The DN of a user who has the permission to search an LDAP/AD server (e.g. `uid=admin,ou=people,dc=mydomain,dc=com`).
* **ldap_search_pwd**: The password of the user specified by *ldap_searchdn*.
* **ldap_basedn**: The base DN to look up a user, e.g. `ou=people,dc=mydomain,dc=com`. _Only used when **auth_mode** is set to *ldap_auth* ._
* **ldap_filter**:The search filter for looking up a user, e.g. `(objectClass=person)`.
* **ldap_uid**: The attribute used to match a user during a LDAP search, it could be uid, cn, email or other attributes.
* **ldap_scope**: The scope to search for a user, 1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE. Default is 3.
* **db_password**: The root password for the MySQL database used for **db_auth**. _Change this password for any production use!_
* **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.
* **verify_remote_cert**: (**on** or **off**. Default is **on**) This flag determines whether or not to verify SSL/TLS certificate when Harbor communicates with a remote registry instance. Setting this attribute to **off** bypasses the SSL/TLS verification, which is often used when the remote instance has a self-signed or untrusted certificate.
* **customize_crt**: (**on** or **off**. Default is **on**) When this attribute is **on**, the prepare script creates private key and root certificate for the generation/verification of the registry's token. The following attributes:**crt_country**, **crt_state**, **crt_location**, **crt_organization**, **crt_organizationalunit**, **crt_commonname**, **crt_email** are used as parameters for generating the keys. Set this attribute to **off** when the key and root certificate are supplied by external sources. Refer to [Customize Key and Certificate of Harbor Token Service](customize_token_service.md) for more info.
#### Configuring storage backend (optional)
By default, Harbor stores images on your local filesystem. In a production environment, you may consider
using other storage backend instead of the local filesystem, like S3, Openstack Swift, Ceph, etc.
What you need to update is the section of `storage` in the file `common/templates/registry/config.yml`.
For example, if you use Openstack Swift as your storage backend, the section may look like this:
```
storage:
swift:
username: admin
password: ADMIN_PASS
authurl: http://keystone_addr:35357/v3/auth
tenant: admin
domain: default
region: regionOne
container: docker_images
```
_NOTE: For detailed information on storage backend of a registry, refer to [Registry Configuration Reference](https://docs.docker.com/registry/configuration/) ._
#### Finishing installation and starting Harbor
Once **harbor.cfg** and storage backend (optional) are configured, install and start Harbor using the ```install.sh``` script. Note that it may take some time for the online installer to download Harbor images from Docker hub.
```sh
$ sudo ./install.sh
```
If everything worked properly, you should be able to open a browser to visit the admin portal at **http://reg.yourdomain.com** (change *reg.yourdomain.com* to the hostname configured in your harbor.cfg). Note that the default administrator username/password are admin/Harbor12345 .
Log in to the admin portal and create a new project, e.g. `myproject`. You can then use docker commands to login and push images (By default, the registry server listens on port 80):
```sh
$ docker login reg.yourdomain.com
$ docker push reg.yourdomain.com/myproject/myrepo:mytag
```
**IMPORTANT:** The default installation of Harbor uses _HTTP_ - as such, you will need to add the option `--insecure-registry` to your client's Docker daemon and restart the Docker service.
For information on how to use Harbor, please refer to **[User Guide of Harbor](user_guide.md)** .
#### Configuring Harbor with HTTPS access
Harbor does not ship with any certificates, and, by default, uses HTTP to serve requests. While this makes it relatively simple to set up and run - especially for a development or testing environment - it is **not** recommended for a production environment. To enable HTTPS, please refer to **[Configuring Harbor with HTTPS Access](configure_https.md)**.
### Managing Harbor's lifecycle
You can use docker-compose to manage the lifecycle of Harbor. Some useful commands are listed as follows (must run in the same directory as *docker-compose.yml*).
Stopping Harbor:
```
$ sudo docker-compose stop
Stopping nginx ... done
Stopping harbor-jobservice ... done
Stopping harbor-ui ... done
Stopping harbor-db ... done
Stopping registry ... done
Stopping harbor-log ... done
```
Restarting Harbor after stopping:
```
$ sudo docker-compose start
Starting log ... done
Starting ui ... done
Starting mysql ... done
Starting jobservice ... done
Starting registry ... done
Starting proxy ... done
```
To change Harbor's configuration, first stop existing Harbor instance, update harbor.cfg, and then run install.sh again:
```
$ sudo docker-compose down
$ vim harbor.cfg
$ sudo install.sh
```
Removing Harbor's containers while keeping the image data and Harbor's database files on the file system:
```
$ sudo docker-compose down
```
Removing Harbor's database and image data (for a clean re-installation):
```sh
$ rm -r /data/database
$ rm -r /data/registry
```
Please check the [Docker Compose command-line reference](https://docs.docker.com/compose/reference/) for more on docker-compose.
### Persistent data and log files
By default, registry data is persisted in the target host's `/data/` directory. This data remains unchanged even when Harbor's containers are removed and/or recreated.
In addition, Harbor uses *rsyslog* to collect the logs of each container. By default, these log files are stored in the directory `/var/log/harbor/` on the target host for troubleshooting.
## Configuring Harbor listening on a customized port
By default, Harbor listens on port 80(HTTP) and 443(HTTPS, if configured) for both admin portal and docker commands, you can configure it with a customized one.
### For HTTP protocol
1.Modify docker-compose.yml
Replace the first "80" to a customized port, e.g. 8888:80.
```
proxy:
image: library/nginx:1.11.5
restart: always
volumes:
- ./config/nginx:/etc/nginx
ports:
- 8888:80
- 443:443
depends_on:
- mysql
- registry
- ui
- log
logging:
driver: "syslog"
options:
syslog-address: "tcp://127.0.0.1:1514"
tag: "proxy"
```
2.Modify templates/registry/config.yml
Add the customized port, e.g. ":8888", after "$ui_url".
```
auth:
token:
issuer: registry-token-issuer
realm: $ui_url:8888/service/token
rootcertbundle: /etc/registry/root.crt
service: token-service
```
3.Run install.sh to update and start Harbor.
```sh
$ sudo docker-compose down
$ sudo install.sh
```
### For HTTPS protocol
1.Enable HTTPS in Harbor by following this [guide](https://github.com/vmware/harbor/blob/master/docs/configure_https.md).
2.Modify docker-compose.yml
Replace the first "443" to a customized port, e.g. 4443:443.
```
proxy:
image: library/nginx:1.11.5
restart: always
volumes:
- ./config/nginx:/etc/nginx
ports:
- 80:80
- 4443:443
depends_on:
- mysql
- registry
- ui
- log
logging:
driver: "syslog"
options:
syslog-address: "tcp://127.0.0.1:1514"
tag: "proxy"
```
3.Modify templates/registry/config.yml
Add the customized port, e.g. ":4443", after "$ui_url".
```
auth:
token:
issuer: registry-token-issuer
realm: $ui_url:4443/service/token
rootcertbundle: /etc/registry/root.crt
service: token-service
```
4.Run install.sh to update and start Harbor.
```sh
$ sudo docker-compose down
$ sudo install.sh
```
## Troubleshooting
1. When Harbor does not work properly, run the below commands to find out if all containers of Harbor are in **UP** status:
```
$ sudo docker-compose ps
Name Command State Ports
-----------------------------------------------------------------------------------------------------
harbor-db docker-entrypoint.sh mysqld Up 3306/tcp
harbor-jobservice /harbor/harbor_jobservice Up
harbor-log /bin/sh -c crond && rsyslo ... Up 0.0.0.0:1514->514/tcp
harbor-ui /harbor/harbor_ui Up
nginx nginx -g daemon off; Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
registry /entrypoint.sh serve /etc/ ... Up 5000/tcp
```
If a container is not in **UP** state, check the log file of that container in directory ```/var/log/harbor```. For example, if the container ```harbor-ui``` is not running, you should look at the log file ```ui.log```.
2.When setting up Harbor behind an nginx proxy or elastic load balancing, look for the line below, in `common/templates/nginx/nginx.http.conf` and remove it from the sections if the proxy already has similar settings: `location /`, `location /v2/` and `location /service/`.
```
proxy_set_header X-Forwarded-Proto $scheme;
```
And run the following commands to restart Harbor:
```sh
$ sudo docker-compose down
$ sudo ./prepare
$ sudo docker-compose up -d
```

View File

@ -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:
@ -1309,7 +1371,229 @@ paths:
403:
description: User does not have permission of admin role.
500:
description: Unexpected internal errors.
description: Unexpected internal errors.
/systeminfo:
get:
summary: Get general system info
description: |
This API is for retrieving general system info, this can be called by anonymous request.
tags:
- Products
responses:
200:
description: Get general info successfully.
schema:
type: object
items:
$ref: "#/definitions/GeneralInfo"
500:
description: Unexpected internal error.
/systeminfo/volumes:
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
View 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
View 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"
```

View File

@ -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
```

View 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

View File

@ -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

View File

@ -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;

View File

@ -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;
}
@ -20,7 +20,15 @@ http {
upstream ui {
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;

View 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;
}
}

View File

@ -0,0 +1,4 @@
upstream notary-server {
server notary-server:4443;
}

View File

@ -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.%"

View File

@ -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.%";

View 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-----

View 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-----

View 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-----

View 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"
}
}
}

View 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"
}
}

View File

@ -0,0 +1,2 @@
NOTARY_SIGNER_DEFAULTALIAS=$alias

View File

@ -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:

View File

@ -6,13 +6,4 @@ types = en-US|zh-CN
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
httpport = 80

View File

@ -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

View 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"]

View 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

View File

@ -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:

View File

@ -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

View 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"]

View 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
}
}
}

View 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/

View 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>

View File

@ -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"]

View 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

View 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

View File

@ -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

View File

@ -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************************
#############

View File

@ -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)" ]
then
note "stopping existing Harbor instance ..."
docker-compose -f docker-compose*.yml down
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 -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

View File

@ -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

View File

@ -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)

View 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"]

View File

@ -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"]

View File

@ -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")
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.")
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('--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))
@ -187,14 +192,12 @@ if protocol == "https":
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):
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)
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")
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.")

View File

@ -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

View File

@ -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"]

View File

@ -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

View File

@ -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

View 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
}

View 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)
}
}

View 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
}
}

View 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
}

View 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
}
}

View 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)
}
}
}

View 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
}

View 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")
}
}

View 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
}

View 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")
}

View 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
}

View 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)
}

View 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
}

View 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")
}
}

View 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
View 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)
}
}

View 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)
}

View 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)
}
}
}

View 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
}

View 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)
}

View 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"])
}

View 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
}

View 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
}
}

View 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
}

View 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])
}

View 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)
}

View 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
}

View 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 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")
}

View 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)
}

View File

@ -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()
}

View File

@ -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")
}

View File

@ -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
}
if m.Cache {
expi, err := getCfgExpiration(c)
if err != nil {
return nil, err
}
// SQLiteSetting wraps the settings of a SQLite DB
type SQLiteSetting struct {
FilePath string
}
// 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
}
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 err = m.cache.Put(m.key, cachedCfgs,
time.Duration(expi)*time.Second); err != nil {
return nil, err
}
setting := MySQLSetting{
mySQLDB,
raw["MYSQL_USR"],
raw["MYSQL_PWD"],
raw["MYSQL_HOST"],
raw["MYSQL_PORT"],
}
config["mysql"] = setting
} else if db == "sqlite" {
f := raw["SQLITE_FILE"]
if len(f) == 0 {
f = "registry.db"
}
setting := SQLiteSetting{
f,
}
config["sqlite"] = setting
} else {
return fmt.Errorf("Invalid DB: %s", db)
}
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
return c, nil
}
var commonConfig *Config
// Reset configurations
func (m *Manager) Reset() error {
return m.client.ResetCfgs()
}
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{},
func getCfgExpiration(m map[string]interface{}) (int, error) {
if m == nil {
return 0, fmt.Errorf("can not get cfg expiration as configurations are null")
}
if err := commonConfig.Load(); err != nil {
panic(err)
expi, ok := m[common.CfgExpiration]
if !ok {
return 0, fmt.Errorf("cfg expiration is not set")
}
return int(expi.(float64)), nil
}
// Reload will reload the configuration.
func Reload() error {
return commonConfig.Load()
// 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()
}
// 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)
}

View File

@ -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

View 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
}

View 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
View 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"
)

View File

@ -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
View 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
}

View 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)
}
}
}

View File

@ -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) {

View File

@ -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"

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View 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
View 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"`
}

View File

@ -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)

View 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
}

View 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