diff --git a/.travis.yml b/.travis.yml index b0dd4c556..80a1df4a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -101,7 +101,7 @@ script: - docker ps - ./tests/notarytest.sh - - go run tests/startuptest.go https://localhost/ + - ./tests/startuptest.sh - go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD} # - sudo ./tests/testprepare.sh diff --git a/Makefile b/Makefile index 08f5a0fde..719c23d05 100644 --- a/Makefile +++ b/Makefile @@ -1,443 +1,443 @@ -# 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=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 -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: - @printf $(VERSIONTAG) > $(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/image\: vmware.*/&:$(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-$(VERSIONTAG).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-$(VERSIONTAG).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) 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) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \ - $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \ - $(DOCKERIMAGENAME_UI):$(VERSIONTAG) \ - $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \ - $(DOCKERIMAGENAME_DB):$(VERSIONTAG) \ - $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \ - nginx:$(NGINXVERSION) registry:$(REGISTRYVERSION) photon:$(PHOTONVERSION) \ - vmware/notary-photon:$(NOTARYVERSION) vmware/notary-photon:$(NOTARYSIGNERVERSION) vmware/harbor-notary-db:$(MARIADBVERSION); \ - else \ - $(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \ - $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \ - $(DOCKERIMAGENAME_UI):$(VERSIONTAG) \ - $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \ - $(DOCKERIMAGENAME_DB):$(VERSIONTAG) \ - $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \ - nginx:$(NGINXVERSION) registry:$(REGISTRYVERSION) photon:$(PHOTONVERSION) ; \ - fi - - @if [ "$(NOTARYFLAG)" = "true" ] ; then \ - $(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \ - $(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \ - $(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \ - $(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \ - $(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \ - $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME) ; \ - else \ - $(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \ - $(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \ - $(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \ - $(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \ - $(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) ; \ - 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 ; \ - else \ - $(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) down ; \ - 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-$(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 +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: + @printf $(VERSIONTAG) > $(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-$(VERSIONTAG).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-$(VERSIONTAG).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) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \ + $(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); \ + else \ + $(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \ + $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \ + $(DOCKERIMAGENAME_UI):$(VERSIONTAG) \ + $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \ + $(DOCKERIMAGENAME_DB):$(VERSIONTAG) \ + $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \ + nginx:$(NGINXVERSION) vmware/registry:$(REGISTRYVERSION) photon:$(PHOTONVERSION) ; \ + fi + + @if [ "$(NOTARYFLAG)" = "true" ] ; then \ + $(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \ + $(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \ + $(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \ + $(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \ + $(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \ + $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME) ; \ + else \ + $(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \ + $(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \ + $(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \ + $(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \ + $(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) ; \ + 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-$(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 diff --git a/docs/use_make.md b/docs/use_make.md new file mode 100644 index 000000000..d8fd774cc --- /dev/null +++ b/docs/use_make.md @@ -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=danieljt/harbor-clarity-base:0.8.4 NOTARYFLAG=true HTTPPROXY=http://proxy.vmware.com:3128 + +### Package offline installer +make package_offline GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage CLARITYIMAGE=danieljt/harbor-clarity-base:0.8.4 NOTARYFLAG=true HTTPPROXY=http://proxy.vmware.com:3128 + +### Start harbor with notary +make -e NOTARYFLAG=true start + +### Stop harbor with notary +make -e NOTARYFLAG=true down \ No newline at end of file diff --git a/docs/use_notary.md b/docs/use_notary.md new file mode 100644 index 000000000..08d2dfbb0 --- /dev/null +++ b/docs/use_notary.md @@ -0,0 +1,23 @@ +### 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/``` + +### 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/notary +``` + +### 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 -d ~/.docker/trust --tlscacert / +etc/docker/certs.d/192.168.0.5/ca.crt" + +``` diff --git a/make/common/templates/notary/notary-signer-ca.crt b/make/common/templates/notary/notary-signer-ca.crt new file mode 100644 index 000000000..02e78443e --- /dev/null +++ b/make/common/templates/notary/notary-signer-ca.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +l7WOOMS8XTJr7KLDUXkAeic= +-----END CERTIFICATE----- diff --git a/make/common/templates/notary/notary-signer.crt b/make/common/templates/notary/notary-signer.crt index 9e9478998..1189dfd7b 100644 --- a/make/common/templates/notary/notary-signer.crt +++ b/make/common/templates/notary/notary-signer.crt @@ -1,63 +1,32 @@ -----BEGIN CERTIFICATE----- -MIIFBDCCAuygAwIBAgIJAMbWdVJcKhXYMA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNV -BAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0G -A1UECgwGRG9ja2VyMScwJQYDVQQDDB5Ob3RhcnkgSW50ZXJtZWRpYXRlIFRlc3Rp -bmcgQ0EwHhcNMTcwMTIzMDYwMzM3WhcNMTkwMjEyMDYwMzM3WjBbMQswCQYDVQQG -EwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNV -BAoMBkRvY2tlcjEWMBQGA1UEAwwNbm90YXJ5LXNpZ25lcjCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBANhO8+K9xT6M9dQC90Hxs6bmTXWQzE5oV2kLeVKq -OjwAvGt6wBE2XJCAbTS3FORIOyoOVQDVCv2Pk2lZXGWqSrH8SY2umjRJIhPDiqN9 -V5M/gcmMm2EUgwmp2l4bsDk1MQ6GSbud5kjYGZcp9uXxAVO8tfLVLQF7ohJYqiex -JN+fZkQyxTgSqrI7MKK1pUvGX/fa6EXzpKwxTQPJXiG/ZQW0Pn+gdrz+/Cf0PcVy -V/Ghc2RR+WjKzqqAiDUJoEtKm/xQVRcSPbagVLCe0KZr7VmtDWnHsUv9ZB9BRNlI -lRVDOhVDCCcMu/zEtcxuH8ja7fafi5xNt6vCBmHuCXQtTUsCAwEAAaOBuTCBtjAf -BgNVHSMEGDAWgBQjgpNYJjU9Ei7nadpOhHm59FPiKTAMBgNVHRMBAf8EAjAAMB0G -A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAOBgNVHQ8BAf8EBAMCBaAwNwYD -VR0RBDAwLoINbm90YXJ5LXNpZ25lcoIMbm90YXJ5c2lnbmVygglsb2NhbGhvc3SH -BAp1BI4wHQYDVR0OBBYEFLv4/22eN7pe8IzCbL+gKr2i/o6VMA0GCSqGSIb3DQEB -CwUAA4ICAQBzBcFgcwtr7oNP7WPyG64mRXHFs1qGCoDZO3D2dZPF/vUKnyPWI6+i -Ozu1Lmvd6QUQ5C0m91D6RidKKy3ENz2MgUo8NNj3QY3XzassiLnNOtpo1ed6U3BG -2w05gaLTTFywnpOgPy180U6f5uNSHGxY/fq9dN+8YR/MqGOht74q36x0swkPegG/ -+0SLloKOJw1wBzZ4nCLmED08DWNnuNTAj5IIVjApzqZbTh4+z6H1lmN3b7XwmiWw -+y7Jx8k74h5JmqKQnV+3lN0DlCc1BCbtH2fbKOmAKeu4gMniw5FBo75wYrPIet+Z -E3G2Zg+T6fjTXAnLGT3S0RVn/CW1lLR6RgkoFgURRZoJyTWrg+1yu4ZOgEz+bot2 -/hMAr/fjo+Dd6ReFrgGkpTyWYtPhYusori1W8KW138CVrJmSs6p2ss1Ixh8uIOaQ -iFmlX/ZXXbvkz3FGQS9LfBdESO3MGjiJTcnXE0DTnXf6RmdlUfNwxsZbIliFa0TQ -E/JjIJYQzWmtkJbUdC02GUMjUJAM7SxmP7tU9CmMmjUI28Nno0XtPN2WsAszaiLh -JYLJCi7rqaLo0oZuaXVIrgBpQ0qEC1XXS5sCQL+xvMSYvke/rhwIPItWt7Ww/9yj -QDIi1nzzX86lbKd095pNX4sUfFx6j4caR8iENgJDfWnqynAzj1Y21A== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 -XJCKYD/1jYIB +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----- diff --git a/make/common/templates/notary/notary-signer.key b/make/common/templates/notary/notary-signer.key index 2db6e2ce9..3973cec7b 100644 --- a/make/common/templates/notary/notary-signer.key +++ b/make/common/templates/notary/notary-signer.key @@ -1,28 +1,52 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEA2E7z4r3FPoz11AL3QfGzpuZNdZDMTmhXaQt5Uqo6PAC8a3rA -ETZckIBtNLcU5Eg7Kg5VANUK/Y+TaVlcZapKsfxJja6aNEkiE8OKo31Xkz+ByYyb -YRSDCanaXhuwOTUxDoZJu53mSNgZlyn25fEBU7y18tUtAXuiEliqJ7Ek359mRDLF -OBKqsjsworWlS8Zf99roRfOkrDFNA8leIb9lBbQ+f6B2vP78J/Q9xXJX8aFzZFH5 -aMrOqoCINQmgS0qb/FBVFxI9tqBUsJ7QpmvtWa0NacexS/1kH0FE2UiVFUM6FUMI -Jwy7/MS1zG4fyNrt9p+LnE23q8IGYe4JdC1NSwIDAQABAoIBAHykYhyRxYrZpv3Y -B6pUIHVX1+Ka4V98+IFrPynHNW9F7UzxmqNQc95AYq0xojQ4+v6s64ZjPMYHaaYW -/AsJKamN+sRNjEX8rko9LzIuE7yhp6QABbjXHPsAiPgZdF5CrFX2Q558yinHfFeC -sualDWK3JxEajaiBGU8BEGt2xAymuWACGblrM1aAEZa8B84TW3CzzcdyzAkn8P3e -piJCe+DWMc33441r0KlV5GruwF9ewXiWzZtXAOiP/0xEDICFdlFWbO39myMpxDdU -Y0uZ+zmn2G3gz2tz25thH0Wl7mDQ3AA0VlHurgPBBEekeZPQmjiKW+F4slCzXvuy -kW/urIECgYEA/LhY+OWlZVXzIEly7z1/cU9/WImqTs2uRKDeQHMwZrd7D9BXkJuQ -jPN+jZlMYBBrxoaCywbMrgB80Z3MgGHaSx9OIDEZmaxyuQv0zQJCMogysYkbCcaD -mHYnyAf7OXa708Z168WAisEhrwa/DXBn3/hPoBkrbMsuPF/J+tEP7lsCgYEA2x2g -86SitgPVeNV3iuZ6D/SV0QIbDWOYoST2GQn2LnfALIOrzpXRClOSQZ2pGtg9gYo1 -owUyyOSv2Fke93p3ufHv3Gqvjl55lzBVV0siHkEXwHcol36DDGQcskVnXJqaL3IF -tiOisuJS9A7PW7gEi0miyGzzB/kh/IEWHKqLL9ECgYEAoBOFB+MuqMmQftsHWlLx -7qwUVdidb90IjZ/4J4rPFcESyimFzas8HIv/lWGM5yx/l/iL0F42N+FHLt9tMcTJ -qNvjeLChLp307RGNtm2/0JJEyf+2iLKdmGz/Nc0YbIWw46vJ9dXcXgeHdn4ndjPF -GDEI/rfysa7hUoy6O41BMhECgYBPJsLPgHdufLAOeD44pM0PGnFMERCoo4OtImbr -4JdXbdazvdTASYo7yriYj1VY5yhAtSZu/x+7RjDnXDo9d7XsK6NT4g4Mxb/yh3ks -kW1/tE/aLLEzGHZKcZeUJlISN57e6Ld7dh/9spf4pajuHuk1T6JH+GNKTAqk5hSQ -wmKJIQKBgCGBWGvJrCeT5X9oHdrlHj2YoKvIIG1eibagcjcKemD7sWzi7Q4P7JIo -xeX8K1WVxdBpo4/RiQcGFmwSmSUKwwr1dO00xtjxIl7ip4DU+WAM7CdmcOIOMbr4 -rP9T/wy1ZBkERCIw2ElybTzB8yuOlNLuOMhUeU55xUMFNYYrWEp2 ------END RSA PRIVATE KEY----- - +-----BEGIN PRIVATE KEY----- +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 +tajKl+aoc+CDxUYbilzhhKetWWF/cJY= +-----END PRIVATE KEY----- diff --git a/make/common/templates/notary/root-ca.crt b/make/common/templates/notary/root-ca.crt deleted file mode 100644 index c30df3cbf..000000000 --- a/make/common/templates/notary/root-ca.crt +++ /dev/null @@ -1,32 +0,0 @@ ------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----- diff --git a/make/common/templates/notary/server-config.json b/make/common/templates/notary/server-config.json index 8e6af5d22..dc8ffba31 100644 --- a/make/common/templates/notary/server-config.json +++ b/make/common/templates/notary/server-config.json @@ -6,7 +6,7 @@ "type": "remote", "hostname": "notarysigner", "port": "7899", - "tls_ca_file": "./root-ca.crt", + "tls_ca_file": "./notary-signer-ca.crt", "key_algorithm": "ecdsa" }, "logging": { diff --git a/make/docker-compose.notary.yml b/make/docker-compose.notary.yml index 0b6340366..102bf3641 100644 --- a/make/docker-compose.notary.yml +++ b/make/docker-compose.notary.yml @@ -60,6 +60,8 @@ services: - TERM=dumb - MYSQL_ALLOW_EMPTY_PASSWORD="true" command: mysqld --innodb_file_per_table + depends_on: + - log logging: driver: "syslog" options: diff --git a/make/docker-compose.tpl b/make/docker-compose.tpl index ae8630425..decf381ee 100644 --- a/make/docker-compose.tpl +++ b/make/docker-compose.tpl @@ -1,7 +1,7 @@ version: '2' services: log: - image: vmware/harbor-log + image: vmware/harbor-log:__version__ container_name: harbor-log restart: always volumes: @@ -11,7 +11,7 @@ services: networks: - harbor registry: - image: registry:2.6.0 + image: vmware/registry:photon-2.6.0 container_name: registry restart: always volumes: @@ -31,7 +31,7 @@ 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: @@ -48,7 +48,7 @@ services: syslog-address: "tcp://127.0.0.1:1514" tag: "mysql" adminserver: - image: vmware/harbor-adminserver + image: vmware/harbor-adminserver:__version__ container_name: harbor-adminserver env_file: - ./common/config/adminserver/env @@ -67,7 +67,7 @@ services: 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 @@ -88,7 +88,7 @@ services: 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 diff --git a/make/harbor.cfg b/make/harbor.cfg index 844daa49e..8c9575585 100644 --- a/make/harbor.cfg +++ b/make/harbor.cfg @@ -20,19 +20,10 @@ 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, a key/certificate must -#be supplied for token generation. +#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 -#Information of your organization for certificate -crt_country = CN -crt_state = State -crt_location = CN -crt_organization = organization -crt_organizationalunit = organizational unit -crt_commonname = example.com -crt_email = example@example.com - #The path of cert and key files for nginx, they are applied only the protocol is set to https ssl_cert = /data/cert/server.crt ssl_cert_key = /data/cert/server.key diff --git a/make/install.sh b/make/install.sh index 54c2b10be..def0189f0 100755 --- a/make/install.sh +++ b/make/install.sh @@ -166,13 +166,13 @@ 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 + 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 + docker-compose -f docker-compose.yml down -v fi fi echo "" diff --git a/make/prepare b/make/prepare index 2d0ddc67a..6a3d34c06 100755 --- a/make/prepare +++ b/make/prepare @@ -135,13 +135,6 @@ 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") @@ -262,52 +255,54 @@ 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")) -FNULL.close() if args.notary_mode: notary_config_dir = prep_conf_dir(config_dir, "notary") notary_temp_dir = os.path.join(templates_dir, "notary") @@ -315,11 +310,27 @@ if args.notary_mode: 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")) - #TODO:generate certs? - 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, "root-ca.crt"), notary_config_dir) + if customize_crt == 'on' and openssl_installed(): + 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) + 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") @@ -335,6 +346,6 @@ if args.notary_mode: default_alias = ''.join(random.choice(string.ascii_letters) for i in range(8)) render(os.path.join(notary_temp_dir, "signer_env"), os.path.join(notary_config_dir, "signer_env"), alias = default_alias) - +FNULL.close() print("The configuration files are ready, please use docker-compose to start the service.") diff --git a/src/ui/views/reset-password-mail.tpl b/src/ui/views/reset-password-mail.tpl new file mode 100644 index 000000000..31fb438ef --- /dev/null +++ b/src/ui/views/reset-password-mail.tpl @@ -0,0 +1,21 @@ + + + + +

Please click this link to reset your password:

+ {{.URL}}/reset_password?reset_uuid={{.UUID}} + + diff --git a/src/ui_ng/src/app/base/harbor-shell/harbor-shell.component.ts b/src/ui_ng/src/app/base/harbor-shell/harbor-shell.component.ts index 765ccf10b..e4c9c56a4 100644 --- a/src/ui_ng/src/app/base/harbor-shell/harbor-shell.component.ts +++ b/src/ui_ng/src/app/base/harbor-shell/harbor-shell.component.ts @@ -120,15 +120,8 @@ export class HarborShellComponent implements OnInit, OnDestroy { //Handle the global search event and then let the result page to trigger api doSearch(event: string): void { if (event === "") { - if (!this.isSearchResultsOpened) { - //Will not open search result panel if term is empty - return; - } else { - //If opened, then close the search result panel - this.isSearchResultsOpened = false; - this.searchResultComponet.close(); - return; - } + //Do nothing + return; } //Once this method is called //the search results page must be opened diff --git a/src/ui_ng/src/app/config/config.component.1.html b/src/ui_ng/src/app/config/config.component.1.html new file mode 100644 index 000000000..a4be64f0c --- /dev/null +++ b/src/ui_ng/src/app/config/config.component.1.html @@ -0,0 +1,62 @@ +
+

{{'CONFIG.TITLE' | translate }}

+ + + {{'CONFIG.AUTH' | translate }} + {{'CONFIG.REPLICATION' | translate }} + {{'CONFIG.EMAIL' | translate }} + {{'CONFIG.SYSTEM' | translate }} + + + + + +
+
+
+ + + + + {{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }} + + +
+
+
+
+ + + + +
+
+
+ + + + + {{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}} + +
+
+
+
+
+
+ + + + + +
+
\ No newline at end of file diff --git a/src/ui_ng/src/app/config/config.component.html b/src/ui_ng/src/app/config/config.component.html index 8ab32e45f..9f3656f31 100644 --- a/src/ui_ng/src/app/config/config.component.html +++ b/src/ui_ng/src/app/config/config.component.html @@ -1,39 +1,47 @@

{{'CONFIG.TITLE' | translate }}

- - {{'CONFIG.AUTH' | translate }} - {{'CONFIG.REPLICATION' | translate }} - {{'CONFIG.EMAIL' | translate }} - {{'CONFIG.SYSTEM' | translate }} - - - - - -
-
-
- - - - - {{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }} - - -
-
-
-
- - - - -
-
-
- -
+
+ +
diff --git a/src/ui_ng/src/app/config/config.component.ts b/src/ui_ng/src/app/config/config.component.ts index 15f353489..c40cc3a01 100644 --- a/src/ui_ng/src/app/config/config.component.ts +++ b/src/ui_ng/src/app/config/config.component.ts @@ -16,8 +16,15 @@ import { ConfigurationAuthComponent } from './auth/config-auth.component'; import { ConfigurationEmailComponent } from './email/config-email.component'; import { AppConfigService } from '../app-config.service'; +import { SessionService } from '../shared/session.service'; const fakePass = "fakepassword"; +const TabLinkContentMap = { + "config-auth": "authentication", + "config-replication": "replication", + "config-email": "email", + "config-system": "system_settings" +}; @Component({ selector: 'config', @@ -27,7 +34,7 @@ const fakePass = "fakepassword"; export class ConfigurationComponent implements OnInit, OnDestroy { private onGoing: boolean = false; allConfig: Configuration = new Configuration(); - private currentTabId: string = ""; + private currentTabId: string = "config-auth";//default tab private originalCopy: Configuration; private confirmSub: Subscription; private testingOnGoing: boolean = false; @@ -41,17 +48,76 @@ export class ConfigurationComponent implements OnInit, OnDestroy { private msgService: MessageService, private configService: ConfigurationService, private confirmService: ConfirmationDialogService, - private appConfigService: AppConfigService) { } + private appConfigService: AppConfigService, + private session: SessionService) { } + + private isCurrentTabLink(tabId: string): boolean { + return this.currentTabId === tabId; + } + + private isCurrentTabContent(contentId: string): boolean { + return TabLinkContentMap[this.currentTabId] === contentId; + } + + private hasUnsavedChangesOfCurrentTab(): any { + let allChanges = this.getChanges(); + if (this.isEmpty(allChanges)) { + return null; + } + + let properties = []; + switch (this.currentTabId) { + case "config-auth": + for (let prop in allChanges) { + if (prop.startsWith("ldap_")) { + return allChanges; + } + } + properties = ["auth_mode", "project_creation_restriction", "self_registration"]; + break; + case "config-email": + for (let prop in allChanges) { + if (prop.startsWith("email_")) { + return allChanges; + } + } + return null; + case "config-replication": + properties = ["verify_remote_cert"]; + break; + case "config-system": + properties = ["token_expiration"]; + break; + default: + return null; + } + + for (let prop in allChanges) { + if (properties.indexOf(prop) != -1) { + return allChanges; + } + } + + return null; + } ngOnInit(): void { //First load - this.retrieveConfig(); + //Double confirm the current use has admin role + let currentUser = this.session.getCurrentUser(); + if (currentUser && currentUser.has_admin_role > 0) { + this.retrieveConfig(); + } this.confirmSub = this.confirmService.confirmationConfirm$.subscribe(confirmation => { if (confirmation && - confirmation.state === ConfirmationState.CONFIRMED && - confirmation.source === ConfirmationTargets.CONFIG) { - this.reset(confirmation.data); + confirmation.state === ConfirmationState.CONFIRMED) { + if (confirmation.source === ConfirmationTargets.CONFIG) { + this.reset(confirmation.data); + } else if (confirmation.source === ConfirmationTargets.CONFIG_TAB) { + this.reset(confirmation.data["changes"]); + this.currentTabId = confirmation.data["tabId"]; + } } }); } @@ -104,8 +170,15 @@ export class ConfigurationComponent implements OnInit, OnDestroy { return this.authConfig && this.authConfig.isValid(); } - public tabLinkChanged(tabLink: any) { - this.currentTabId = tabLink.id; + public tabLinkClick(tabLink: string) { + //Whether has unsave changes in current tab + let changes = this.hasUnsavedChangesOfCurrentTab(); + if (!changes) { + this.currentTabId = tabLink; + return; + } + + this.confirmUnsavedTabChanges(changes, tabLink); } /** @@ -154,14 +227,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy { public cancel(): void { let changes = this.getChanges(); if (!this.isEmpty(changes)) { - let msg = new ConfirmationMessage( - "CONFIG.CONFIRM_TITLE", - "CONFIG.CONFIRM_SUMMARY", - "", - changes, - ConfirmationTargets.CONFIG - ); - this.confirmService.openComfirmDialog(msg); + this.confirmUnsavedChanges(changes); } else { //Inprop situation, should not come here console.error("Nothing changed"); @@ -218,6 +284,33 @@ export class ConfigurationComponent implements OnInit, OnDestroy { }); } + private confirmUnsavedChanges(changes: any) { + let msg = new ConfirmationMessage( + "CONFIG.CONFIRM_TITLE", + "CONFIG.CONFIRM_SUMMARY", + "", + changes, + ConfirmationTargets.CONFIG + ); + + this.confirmService.openComfirmDialog(msg); + } + + private confirmUnsavedTabChanges(changes: any, tabId: string){ + let msg = new ConfirmationMessage( + "CONFIG.CONFIRM_TITLE", + "CONFIG.CONFIRM_SUMMARY", + "", + { + "changes": changes, + "tabId": tabId + }, + ConfirmationTargets.CONFIG_TAB + ); + + this.confirmService.openComfirmDialog(msg); + } + private retrieveConfig(): void { this.onGoing = true; this.configService.getConfiguration() diff --git a/src/ui_ng/src/app/global-message/message.component.ts b/src/ui_ng/src/app/global-message/message.component.ts index 6767c56b4..80e46efae 100644 --- a/src/ui_ng/src/app/global-message/message.component.ts +++ b/src/ui_ng/src/app/global-message/message.component.ts @@ -18,6 +18,7 @@ export class MessageComponent implements OnInit { globalMessage: Message = new Message(); globalMessageOpened: boolean; messageText: string = ""; + private timer: any = null; constructor( private messageService: MessageService, @@ -48,7 +49,7 @@ export class MessageComponent implements OnInit { // Make the message alert bar dismiss after several intervals. //Only for this case - setInterval(() => this.onClose(), dismissInterval); + this.timer = setTimeout(() => this.onClose(), dismissInterval); } ); } @@ -56,15 +57,9 @@ export class MessageComponent implements OnInit { //Translate or refactor the message shown to user translateMessage(msg: Message): void { - if (!msg) { - return; - } - - let key = ""; - if (!msg.message) { - key = "UNKNOWN_ERROR"; - } else { - key = typeof msg.message === "string" ? msg.message.trim() : msg.message; + let key = "UNKNOWN_ERROR", param = ""; + if (msg && msg.message) { + key = (typeof msg.message === "string" ? msg.message.trim() : msg.message); if (key === "") { key = "UNKNOWN_ERROR"; } @@ -73,13 +68,11 @@ export class MessageComponent implements OnInit { //Override key for HTTP 401 and 403 if (this.globalMessage.statusCode === httpStatusCode.Unauthorized) { key = "UNAUTHORIZED_ERROR"; - } - - if (this.globalMessage.statusCode === httpStatusCode.Forbidden) { + } else if (this.globalMessage.statusCode === httpStatusCode.Forbidden) { key = "FORBIDDEN_ERROR"; - } + } - this.translate.get(key).subscribe((res: string) => this.messageText = res); + this.translate.get(key, { 'param': param }).subscribe((res: string) => this.messageText = res); } public get needAuth(): boolean { @@ -98,6 +91,9 @@ export class MessageComponent implements OnInit { } onClose() { + if (this.timer) { + clearTimeout(this.timer); + } this.globalMessageOpened = false; } } \ No newline at end of file diff --git a/src/ui_ng/src/app/harbor-routing.module.ts b/src/ui_ng/src/app/harbor-routing.module.ts index 775d3523c..583071bfb 100644 --- a/src/ui_ng/src/app/harbor-routing.module.ts +++ b/src/ui_ng/src/app/harbor-routing.module.ts @@ -33,6 +33,8 @@ import { AuthCheckGuard } from './shared/route/auth-user-activate.service'; import { SignInGuard } from './shared/route/sign-in-guard-activate.service'; import { LeavingConfigRouteDeactivate } from './shared/route/leaving-config-deactivate.service'; +import { MemberGuard } from './shared/route/member-guard-activate.service'; + const harborRoutes: Routes = [ { path: '', redirectTo: 'harbor', pathMatch: 'full' }, { path: 'password-reset', component: ResetPasswordComponent }, @@ -74,11 +76,15 @@ const harborRoutes: Routes = [ }, { path: 'tags/:id/:repo', - component: TagRepositoryComponent + component: TagRepositoryComponent, + resolve: { + projectResolver: ProjectRoutingResolver + } }, { path: 'projects/:id', component: ProjectDetailComponent, + canActivate: [MemberGuard], resolve: { projectResolver: ProjectRoutingResolver }, @@ -89,7 +95,8 @@ const harborRoutes: Routes = [ }, { path: 'replication', - component: ReplicationComponent + component: ReplicationComponent, + canActivate: [SystemAdminGuard] }, { path: 'member', diff --git a/src/ui_ng/src/app/log/audit-log.service.ts b/src/ui_ng/src/app/log/audit-log.service.ts index 72e4b1a07..28be16021 100644 --- a/src/ui_ng/src/app/log/audit-log.service.ts +++ b/src/ui_ng/src/app/log/audit-log.service.ts @@ -1,8 +1,6 @@ import { Injectable } from '@angular/core'; import { Http, Headers, RequestOptions } from '@angular/http'; -import { BaseService } from '../service/base.service'; - import { AuditLog } from './audit-log'; import { Observable } from 'rxjs/Observable'; @@ -13,7 +11,7 @@ import 'rxjs/add/observable/throw'; export const logEndpoint = "/api/logs"; @Injectable() -export class AuditLogService extends BaseService { +export class AuditLogService { private httpOptions = new RequestOptions({ headers: new Headers({ "Content-Type": 'application/json', @@ -21,9 +19,7 @@ export class AuditLogService extends BaseService { }) }); - constructor(private http: Http) { - super(); - } + constructor(private http: Http) {} listAuditLogs(queryParam: AuditLog): Observable { return this.http @@ -36,13 +32,12 @@ export class AuditLogService extends BaseService { username: queryParam.username }) .map(response => response) - .catch(error => this.handleError(error)); + .catch(error => Observable.throw(error)); } getRecentLogs(lines: number): Observable { return this.http.get(logEndpoint + "?lines=" + lines, this.httpOptions) .map(response => response.json() as AuditLog[]) - .catch(error => this.handleError(error)); + .catch(error => Observable.throw(error)); } - } \ No newline at end of file diff --git a/src/ui_ng/src/app/log/recent-log.component.css b/src/ui_ng/src/app/log/recent-log.component.css index b92cb32ec..2d08305e0 100644 --- a/src/ui_ng/src/app/log/recent-log.component.css +++ b/src/ui_ng/src/app/log/recent-log.component.css @@ -2,25 +2,18 @@ margin-top: 0px !important; } -.filter-log { - float: right; - margin-right: 24px; - position: relative; - top: 8px; -} - .action-head-pos { - position: relative; - top: 20px; + padding-right: 18px; } .refresh-btn { - position: absolute; - right: -4px; - top: 8px; cursor: pointer; } +.refresh-btn:hover { + color: #00bfff; +} + .custom-lines-button { padding: 0px !important; min-width: 25px !important; @@ -29,4 +22,21 @@ .lines-button-toggole { font-size: 16px; text-decoration: underline; +} + +.log-select { + width: 180px; + display: inline-block; + top: 1px; +} + +.item-divider { + height: 24px; + display: inline-block; + width: 1px; + background-color: #ccc; + opacity: 0.55; + margin-left: 12px; + top: 8px; + position: relative; } \ No newline at end of file diff --git a/src/ui_ng/src/app/log/recent-log.component.html b/src/ui_ng/src/app/log/recent-log.component.html index 10bfe1963..890890eee 100644 --- a/src/ui_ng/src/app/log/recent-log.component.html +++ b/src/ui_ng/src/app/log/recent-log.component.html @@ -1,20 +1,24 @@
-

{{'SIDE_NAV.LOGS' | translate}}

-
- - - - - - - - - - - +

{{'SIDE_NAV.LOGS' | translate}} + {{logNumber}} +

+
+
+
+
+ +
+
+ + - + +
diff --git a/src/ui_ng/src/app/log/recent-log.component.ts b/src/ui_ng/src/app/log/recent-log.component.ts index b0bbeb474..2c20e921a 100644 --- a/src/ui_ng/src/app/log/recent-log.component.ts +++ b/src/ui_ng/src/app/log/recent-log.component.ts @@ -34,17 +34,22 @@ export class RecentLogComponent implements OnInit { this.retrieveLogs(); } - public get inProgress(): boolean { - return this.onGoing; + private handleOnchange($event: any) { + if (event && event.target && event.srcElement["value"]) { + this.lines = event.srcElement["value"]; + if (this.lines < 10) { + this.lines = 10; + } + this.retrieveLogs(); + } } - public setLines(lines: number): void { - this.lines = lines; - if (this.lines < 10) { - this.lines = 10; - } + public get logNumber(): number { + return this.recentLogs?this.recentLogs.length:0; + } - this.retrieveLogs(); + public get inProgress(): boolean { + return this.onGoing; } public doFilter(terms: string): void { @@ -60,7 +65,7 @@ export class RecentLogComponent implements OnInit { this.retrieveLogs(); } - public formatDateTime(dateTime: string){ + public formatDateTime(dateTime: string) { let dt: Date = new Date(dateTime); return dt.toLocaleString(); } diff --git a/src/ui_ng/src/app/project/create-project/create-project.component.html b/src/ui_ng/src/app/project/create-project/create-project.component.html index 725f538a6..785ba7fe9 100644 --- a/src/ui_ng/src/app/project/create-project/create-project.component.html +++ b/src/ui_ng/src/app/project/create-project/create-project.component.html @@ -31,6 +31,6 @@
diff --git a/src/ui_ng/src/app/project/create-project/create-project.component.ts b/src/ui_ng/src/app/project/create-project/create-project.component.ts index f06c91079..d05af9694 100644 --- a/src/ui_ng/src/app/project/create-project/create-project.component.ts +++ b/src/ui_ng/src/app/project/create-project/create-project.component.ts @@ -13,7 +13,6 @@ import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.com import { TranslateService } from '@ngx-translate/core'; - @Component({ selector: 'create-project', templateUrl: 'create-project.component.html', @@ -50,6 +49,7 @@ export class CreateProjectComponent implements AfterViewChecked { .subscribe( status=>{ this.create.emit(true); + this.messageService.announceMessage(status, 'PROJECT.CREATED_SUCCESS', AlertType.SUCCESS); this.createProjectOpened = false; }, error=>{ diff --git a/src/ui_ng/src/app/project/list-project/list-project.component.html b/src/ui_ng/src/app/project/list-project/list-project.component.html index 7b80c45b8..c04437c00 100644 --- a/src/ui_ng/src/app/project/list-project/list-project.component.html +++ b/src/ui_ng/src/app/project/list-project/list-project.component.html @@ -1,20 +1,20 @@ {{'PROJECT.NAME' | translate}} {{'PROJECT.PUBLIC_OR_PRIVATE' | translate}} + {{'PROJECT.ROLE' | translate}} {{'PROJECT.REPO_COUNT'| translate}} {{'PROJECT.CREATION_TIME' | translate}} - {{'PROJECT.DESCRIPTION' | translate}} - - + + {{p.name}} {{ (p.public === 1 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}} + {{roleInfo[p.current_user_role_id] | translate}} {{p.repo_count}} {{p.creation_time}} - {{p.description}} {{totalRecordCount || (projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}} diff --git a/src/ui_ng/src/app/project/list-project/list-project.component.ts b/src/ui_ng/src/app/project/list-project/list-project.component.ts index 81f2c5e7a..1a4c8b5ea 100644 --- a/src/ui_ng/src/app/project/list-project/list-project.component.ts +++ b/src/ui_ng/src/app/project/list-project/list-project.component.ts @@ -5,7 +5,7 @@ import { ProjectService } from '../project.service'; import { SessionService } from '../../shared/session.service'; import { SearchTriggerService } from '../../base/global-search/search-trigger.service'; -import { ListMode } from '../../shared/shared.const'; +import { ListMode, ProjectTypes, RoleInfo } from '../../shared/shared.const'; import { State } from 'clarity-angular'; @@ -24,6 +24,8 @@ export class ListProjectComponent implements OnInit { @Input() totalRecordCount: number; pageOffset: number = 1; + @Input() filteredType: string; + @Output() paginate = new EventEmitter(); @Output() toggle = new EventEmitter(); @@ -31,6 +33,8 @@ export class ListProjectComponent implements OnInit { @Input() mode: string = ListMode.FULL; + roleInfo = RoleInfo; + constructor( private session: SessionService, private router: Router, @@ -43,6 +47,15 @@ export class ListProjectComponent implements OnInit { return this.mode === ListMode.FULL && this.session.getCurrentUser() != null; } + get showRoleInfo(): boolean { + return this.listFullMode && this.filteredType === ProjectTypes[0]; + } + + public get isSystemAdmin(): boolean { + let account = this.session.getCurrentUser(); + return account != null && account.has_admin_role > 0; + } + goToLink(proId: number): void { this.searchTrigger.closeSearch(false); diff --git a/src/ui_ng/src/app/project/member/add-member/add-member.component.html b/src/ui_ng/src/app/project/member/add-member/add-member.component.html index 7d0df444b..55764ae35 100644 --- a/src/ui_ng/src/app/project/member/add-member/add-member.component.html +++ b/src/ui_ng/src/app/project/member/add-member/add-member.component.html @@ -19,15 +19,15 @@
- +
- +
- +
@@ -36,6 +36,6 @@
diff --git a/src/ui_ng/src/app/project/member/add-member/add-member.component.ts b/src/ui_ng/src/app/project/member/add-member/add-member.component.ts index b5181ba52..a4ef85df3 100644 --- a/src/ui_ng/src/app/project/member/add-member/add-member.component.ts +++ b/src/ui_ng/src/app/project/member/add-member/add-member.component.ts @@ -51,6 +51,7 @@ export class AddMemberComponent implements AfterViewChecked { .addMember(this.projectId, this.member.username, +this.member.role_id) .subscribe( response=>{ + this.messageService.announceMessage(response, 'MEMBER.ADDED_SUCCESS', AlertType.SUCCESS); console.log('Added member successfully.'); this.added.emit(true); this.addMemberOpened = false; @@ -112,9 +113,11 @@ export class AddMemberComponent implements AfterViewChecked { } openAddMemberModal(): void { + this.memberForm.reset(); this.member = new Member(); this.addMemberOpened = true; this.hasChanged = false; + this.member.role_id = 1; } } \ No newline at end of file diff --git a/src/ui_ng/src/app/project/member/member.component.html b/src/ui_ng/src/app/project/member/member.component.html index 62fd7da07..ac4a5ea5f 100644 --- a/src/ui_ng/src/app/project/member/member.component.html +++ b/src/ui_ng/src/app/project/member/member.component.html @@ -2,7 +2,7 @@
- +
@@ -17,15 +17,15 @@ {{'MEMBER.NAME' | translate}} {{'MEMBER.ROLE' | translate}} - - - - - - + + + + + + - {{u.username}} - {{roleInfo[u.role_id] | translate}} + {{m.username}} + {{roleInfo[m.role_id] | translate}} {{ (members ? members.length : 0) }} {{'MEMBER.ITEMS' | translate}} diff --git a/src/ui_ng/src/app/project/member/member.component.ts b/src/ui_ng/src/app/project/member/member.component.ts index 55f4555a9..765f67ee6 100644 --- a/src/ui_ng/src/app/project/member/member.component.ts +++ b/src/ui_ng/src/app/project/member/member.component.ts @@ -15,6 +15,8 @@ import { ConfirmationDialogService } from '../../shared/confirmation-dialog/conf import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message'; import { SessionService } from '../../shared/session.service'; +import { RoleInfo } from '../../shared/shared.const'; + import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/catch'; @@ -22,7 +24,7 @@ import 'rxjs/add/operator/map'; import 'rxjs/add/observable/throw'; import { Subscription } from 'rxjs/Subscription'; -export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' }; +import { Project } from '../../project/project'; @Component({ moduleId: module.id, @@ -31,21 +33,22 @@ export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', }) export class MemberComponent implements OnInit, OnDestroy { - currentUser: SessionUser; members: Member[]; projectId: number; - roleInfo = roleInfo; + roleInfo = RoleInfo; private delSub: Subscription; @ViewChild(AddMemberComponent) addMemberComponent: AddMemberComponent; + currentUser: SessionUser; + hasProjectAdminRole: boolean; + constructor(private route: ActivatedRoute, private router: Router, private memberService: MemberService, private messageService: MessageService, private deletionDialogService: ConfirmationDialogService, - session: SessionService) { - //Get current user from registered resolver. - this.currentUser = session.getCurrentUser(); + private session: SessionService) { + this.delSub = deletionDialogService.confirmationConfirm$.subscribe(message => { if (message && message.state === ConfirmationState.CONFIRMED && @@ -54,7 +57,8 @@ export class MemberComponent implements OnInit, OnDestroy { .deleteMember(this.projectId, message.data) .subscribe( response => { - console.log('Successful change role with user ' + message.data); + this.messageService.announceMessage(response, 'MEMBER.DELETED_SUCCESS', AlertType.SUCCESS); + console.log('Successful delete member: ' + message.data); this.retrieve(this.projectId, ''); }, error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + message.data, AlertType.DANGER) @@ -71,8 +75,7 @@ export class MemberComponent implements OnInit, OnDestroy { error => { this.router.navigate(['/harbor', 'projects']); this.messageService.announceMessage(error.status, 'Failed to get project member with project ID:' + projectId, AlertType.DANGER); - } - ); + }); } ngOnDestroy() { @@ -85,6 +88,15 @@ export class MemberComponent implements OnInit, OnDestroy { //Get projectId from route params snapshot. this.projectId = +this.route.snapshot.parent.params['id']; console.log('Get projectId from route params snapshot:' + this.projectId); + + this.currentUser = this.session.getCurrentUser(); + //Get current user from registered resolver. + let resolverData = this.route.snapshot.parent.data; + if(resolverData) { + this.hasProjectAdminRole = (resolverData['projectResolver']).has_project_admin_role; + } + + this.retrieve(this.projectId, ''); } @@ -97,24 +109,27 @@ export class MemberComponent implements OnInit, OnDestroy { this.retrieve(this.projectId, ''); } - changeRole(userId: number, roleId: number) { - this.memberService - .changeMemberRole(this.projectId, userId, roleId) - .subscribe( - response => { - console.log('Successful change role with user ' + userId + ' to roleId ' + roleId); - this.retrieve(this.projectId, ''); - }, - error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + userId + ' to roleId ' + roleId, AlertType.DANGER) - ); + changeRole(m: Member, roleId: number) { + if(m) { + this.memberService + .changeMemberRole(this.projectId, m.user_id, roleId) + .subscribe( + response => { + this.messageService.announceMessage(response, 'MEMBER.SWITCHED_SUCCESS', AlertType.SUCCESS); + console.log('Successful change role with user ' + m.user_id + ' to roleId ' + roleId); + this.retrieve(this.projectId, ''); + }, + error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + m.user_id + ' to roleId ' + roleId, AlertType.DANGER) + ); + } } - deleteMember(userId: number) { + deleteMember(m: Member) { let deletionMessage: ConfirmationMessage = new ConfirmationMessage( 'MEMBER.DELETION_TITLE', 'MEMBER.DELETION_SUMMARY', - userId + "", - userId, + m.username, + m.user_id, ConfirmationTargets.PROJECT_MEMBER ); this.deletionDialogService.openComfirmDialog(deletionMessage); diff --git a/src/ui_ng/src/app/project/member/member.service.ts b/src/ui_ng/src/app/project/member/member.service.ts index 5578b1083..28cb0c895 100644 --- a/src/ui_ng/src/app/project/member/member.service.ts +++ b/src/ui_ng/src/app/project/member/member.service.ts @@ -6,22 +6,19 @@ import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/map'; import 'rxjs/add/observable/throw'; -import { BaseService } from '../../service/base.service'; import { Member } from './member'; @Injectable() -export class MemberService extends BaseService { +export class MemberService { - constructor(private http: Http) { - super(); - } + constructor(private http: Http) {} listMembers(projectId: number, username: string): Observable { console.log('Get member from project_id:' + projectId + ', username:' + username); return this.http .get(`/api/projects/${projectId}/members?username=${username}`) - .map(response=>response.json()) - .catch(error=>this.handleError(error)); + .map(response=>response.json() as Member[]) + .catch(error=>Observable.throw(error)); } addMember(projectId: number, username: string, roleId: number): Observable { diff --git a/src/ui_ng/src/app/project/project-detail/project-detail.component.html b/src/ui_ng/src/app/project/project-detail/project-detail.component.html index ccfd09356..b33359980 100644 --- a/src/ui_ng/src/app/project/project-detail/project-detail.component.html +++ b/src/ui_ng/src/app/project/project-detail/project-detail.component.html @@ -5,10 +5,10 @@ - -
+ + diff --git a/src/ui_ng/src/app/project/project.component.ts b/src/ui_ng/src/app/project/project.component.ts index ed7b4e387..f87525334 100644 --- a/src/ui_ng/src/app/project/project.component.ts +++ b/src/ui_ng/src/app/project/project.component.ts @@ -23,7 +23,9 @@ import { Subscription } from 'rxjs/Subscription'; import { State } from 'clarity-angular'; -const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' }; +import { AppConfigService } from '../app-config.service'; +import { SessionService } from '../shared/session.service'; +import { ProjectTypes } from '../shared/shared.const'; @Component({ moduleId: module.id, @@ -35,7 +37,7 @@ export class ProjectComponent implements OnInit, OnDestroy { selected = []; changedProjects: Project[]; - projectTypes = types; + projectTypes = ProjectTypes; @ViewChild(CreateProjectComponent) creationProject: CreateProjectComponent; @@ -59,6 +61,8 @@ export class ProjectComponent implements OnInit, OnDestroy { constructor( private projectService: ProjectService, private messageService: MessageService, + private appConfigService: AppConfigService, + private sessionService: SessionService, private deletionDialogService: ConfirmationDialogService) { this.subscription = deletionDialogService.confirmationConfirm$.subscribe(message => { if (message && @@ -69,6 +73,7 @@ export class ProjectComponent implements OnInit, OnDestroy { .deleteProject(projectId) .subscribe( response => { + this.messageService.announceMessage(response, 'PROJECT.DELETED_SUCCESS', AlertType.SUCCESS); console.log('Successful delete project with ID:' + projectId); this.retrieve(); }, @@ -76,11 +81,13 @@ export class ProjectComponent implements OnInit, OnDestroy { ); } }); + } ngOnInit(): void { this.projectName = ''; this.isPublic = 0; + } ngOnDestroy(): void { @@ -89,6 +96,19 @@ export class ProjectComponent implements OnInit, OnDestroy { } } + get projectCreationRestriction(): boolean { + let account = this.sessionService.getCurrentUser(); + if(account) { + switch(this.appConfigService.getConfig().project_creation_restriction) { + case 'adminonly': + return (account.has_admin_role === 1); + case 'everyone': + return true; + } + } + return false; + } + retrieve(state?: State): void { if (state) { this.page = state.page.to + 1; @@ -123,7 +143,7 @@ export class ProjectComponent implements OnInit, OnDestroy { } doFilterProjects(filteredType: number): void { - console.log('Filter projects with type:' + types[filteredType]); + console.log('Filter projects with type:' + this.projectTypes[filteredType]); this.isPublic = filteredType; this.currentFilteredType = filteredType; this.retrieve(); @@ -135,7 +155,10 @@ export class ProjectComponent implements OnInit, OnDestroy { this.projectService .toggleProjectPublic(p.project_id, p.public) .subscribe( - response => console.log('Successful toggled project_id:' + p.project_id), + response => { + this.messageService.announceMessage(response, 'PROJECT.TOGGLED_SUCCESS', AlertType.SUCCESS); + console.log('Successful toggled project_id:' + p.project_id); + }, error => this.messageService.announceMessage(error.status, error, AlertType.WARNING) ); } diff --git a/src/ui_ng/src/app/project/project.service.ts b/src/ui_ng/src/app/project/project.service.ts index d78aabaf0..50c637fe7 100644 --- a/src/ui_ng/src/app/project/project.service.ts +++ b/src/ui_ng/src/app/project/project.service.ts @@ -3,8 +3,6 @@ import { Injectable } from '@angular/core'; import { Http, Headers, RequestOptions, Response, URLSearchParams } from '@angular/http'; import { Project } from './project'; -import { BaseService } from '../service/base.service'; - import { Message } from '../global-message/message'; import { Observable } from 'rxjs/Observable'; @@ -22,11 +20,10 @@ export class ProjectService { constructor(private http: Http) {} - getProject(projectId: number): Promise { + getProject(projectId: number): Observable { return this.http .get(`/api/projects/${projectId}`) - .toPromise() - .then(response=>response.json() as Project) + .map(response=>response.json()) .catch(error=>Observable.throw(error)); } @@ -70,4 +67,11 @@ export class ProjectService { .catch(error=>Observable.throw(error)); } + checkProjectMember(projectId: number): Observable { + return this.http + .get(`/api/projects/${projectId}/members`) + .map(response=>response.json()) + .catch(error=>Observable.throw(error)); + } + } \ No newline at end of file diff --git a/src/ui_ng/src/app/project/project.ts b/src/ui_ng/src/app/project/project.ts index 1c6d73014..3304c6d5f 100644 --- a/src/ui_ng/src/app/project/project.ts +++ b/src/ui_ng/src/app/project/project.ts @@ -29,4 +29,6 @@ export class Project { update_time: Date; current_user_role_id: number; repo_count: number; + has_project_admin_role: boolean; + is_member: boolean; } \ No newline at end of file diff --git a/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.ts b/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.ts index 549c0086f..cdf9a0004 100644 --- a/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.ts +++ b/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.ts @@ -108,6 +108,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked { .createTarget(this.target) .subscribe( response=>{ + this.messageService.announceMessage(response, 'DESTINATION.CREATED_SUCCESS', AlertType.SUCCESS); console.log('Successful added target.'); this.createEditDestinationOpened = false; this.reload.emit(true); @@ -129,7 +130,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked { .get(errorMessageKey) .subscribe(res=>{ this.messageService.announceMessage(error.status, errorMessageKey, AlertType.DANGER); - this.inlineAlert.showInlineError(errorMessageKey); + this.inlineAlert.showInlineError(res); }); } ); @@ -139,6 +140,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked { .updateTarget(this.target) .subscribe( response=>{ + this.messageService.announceMessage(response, 'DESTINATION.UPDATED_SUCCESS', AlertType.SUCCESS); console.log('Successful updated target.'); this.createEditDestinationOpened = false; this.reload.emit(true); @@ -158,7 +160,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked { this.translateService .get(errorMessageKey) .subscribe(res=>{ - this.inlineAlert.showInlineError(errorMessageKey); + this.inlineAlert.showInlineError(res); this.messageService.announceMessage(error.status, errorMessageKey, AlertType.DANGER); }); } diff --git a/src/ui_ng/src/app/replication/destination/destination.component.ts b/src/ui_ng/src/app/replication/destination/destination.component.ts index c85b5bbd0..376b79119 100644 --- a/src/ui_ng/src/app/replication/destination/destination.component.ts +++ b/src/ui_ng/src/app/replication/destination/destination.component.ts @@ -43,14 +43,15 @@ export class DestinationComponent implements OnInit { .deleteTarget(targetId) .subscribe( response => { + this.messageService.announceMessage(response, 'DESTINATION.DELETED_SUCCESS', AlertType.SUCCESS); console.log('Successful deleted target with ID:' + targetId); this.reload(); }, - error => this.messageService - .announceMessage(error.status, - 'Failed to delete target with ID:' + targetId + ', error:' + error, - AlertType.DANGER) - ); + error => { + this.messageService + .announceMessage(error.status,'DESTINATION.DELETED_FAILED', AlertType.DANGER); + console.log('Failed to delete target with ID:' + targetId + ', error:' + error); + }); } }); } diff --git a/src/ui_ng/src/app/replication/replication.service.ts b/src/ui_ng/src/app/replication/replication.service.ts index bec392cfd..a8526fc19 100644 --- a/src/ui_ng/src/app/replication/replication.service.ts +++ b/src/ui_ng/src/app/replication/replication.service.ts @@ -1,8 +1,6 @@ import { Injectable } from '@angular/core'; import { Http, URLSearchParams, Response } from '@angular/http'; -import { BaseService } from '../service/base.service'; - import { Policy } from './policy'; import { Job } from './job'; import { Target } from './target'; @@ -14,10 +12,8 @@ import 'rxjs/add/observable/throw'; import 'rxjs/add/operator/mergeMap'; @Injectable() -export class ReplicationService extends BaseService { - constructor(private http: Http) { - super(); - } +export class ReplicationService { + constructor(private http: Http) {} listPolicies(policyName: string, projectId?: any): Observable { if(!projectId) { diff --git a/src/ui_ng/src/app/repository/list-repository/list-repository.component.html b/src/ui_ng/src/app/repository/list-repository/list-repository.component.html index 77ebb0461..6d483b344 100644 --- a/src/ui_ng/src/app/repository/list-repository/list-repository.component.html +++ b/src/ui_ng/src/app/repository/list-repository/list-repository.component.html @@ -3,7 +3,7 @@ {{'REPOSITORY.TAGS_COUNT' | translate}} {{'REPOSITORY.PULL_COUNT' | translate}} - + diff --git a/src/ui_ng/src/app/repository/list-repository/list-repository.component.ts b/src/ui_ng/src/app/repository/list-repository/list-repository.component.ts index 16d699b65..b33a1b06f 100644 --- a/src/ui_ng/src/app/repository/list-repository/list-repository.component.ts +++ b/src/ui_ng/src/app/repository/list-repository/list-repository.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { Router, NavigationExtras } from '@angular/router'; import { Repository } from '../repository'; import { State } from 'clarity-angular'; @@ -7,14 +7,18 @@ import { SearchTriggerService } from '../../base/global-search/search-trigger.se import { SessionService } from '../../shared/session.service'; import { ListMode } from '../../shared/shared.const'; +import { SessionUser } from '../../shared/session-user'; + @Component({ selector: 'list-repository', templateUrl: 'list-repository.component.html' }) -export class ListRepositoryComponent { +export class ListRepositoryComponent implements OnInit { @Input() projectId: number; @Input() repositories: Repository[]; + + @Output() delete = new EventEmitter(); @Input() totalPage: number; @@ -22,6 +26,7 @@ export class ListRepositoryComponent { @Output() paginate = new EventEmitter(); @Input() mode: string = ListMode.FULL; + @Input() hasProjectAdminRole: boolean; pageOffset: number = 1; @@ -30,6 +35,8 @@ export class ListRepositoryComponent { private searchTrigger: SearchTriggerService, private session: SessionService) { } + ngOnInit() {} + deleteRepo(repoName: string) { this.delete.emit(repoName); } diff --git a/src/ui_ng/src/app/repository/repository.component.html b/src/ui_ng/src/app/repository/repository.component.html index 5e506a53a..bafef0007 100644 --- a/src/ui_ng/src/app/repository/repository.component.html +++ b/src/ui_ng/src/app/repository/repository.component.html @@ -8,6 +8,6 @@
- +
\ No newline at end of file diff --git a/src/ui_ng/src/app/repository/repository.component.ts b/src/ui_ng/src/app/repository/repository.component.ts index ec9f3e04f..d1869c6be 100644 --- a/src/ui_ng/src/app/repository/repository.component.ts +++ b/src/ui_ng/src/app/repository/repository.component.ts @@ -14,6 +14,8 @@ import { Subscription } from 'rxjs/Subscription'; import { State } from 'clarity-angular'; +import { Project } from '../project/project'; + const repositoryTypes = [ { key: '0', description: 'REPOSITORY.MY_REPOSITORY' }, { key: '1', description: 'REPOSITORY.PUBLIC_REPOSITORY' } @@ -39,6 +41,8 @@ export class RepositoryComponent implements OnInit { totalPage: number; totalRecordCount: number; + hasProjectAdminRole: boolean; + subscription: Subscription; constructor( @@ -60,17 +64,22 @@ export class RepositoryComponent implements OnInit { .subscribe( response => { this.refresh(); + this.messageService.announceMessage(response, 'REPOSITORY.DELETED_REPO_SUCCESS', AlertType.SUCCESS); console.log('Successful deleted repo:' + repoName); }, error => this.messageService.announceMessage(error.status, 'Failed to delete repo:' + repoName, AlertType.DANGER) ); } - } - ); + }); + } ngOnInit(): void { this.projectId = this.route.snapshot.parent.params['id']; + let resolverData = this.route.snapshot.parent.data; + if(resolverData) { + this.hasProjectAdminRole = (resolverData['projectResolver']).has_project_admin_role; + } this.currentRepositoryType = this.repositoryTypes[0]; this.lastFilteredRepoName = ''; this.retrieve(); diff --git a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.html b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.html index 4b15ed9f3..5dff71967 100644 --- a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.html +++ b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.html @@ -3,19 +3,19 @@ {{'REPOSITORY.TAG' | translate}} {{'REPOSITORY.PULL_COMMAND' | translate}} - {{'REPOSITORY.SIGNED' | translate}} + {{'REPOSITORY.SIGNED' | translate}} {{'REPOSITORY.AUTHOR' | translate}} {{'REPOSITORY.CREATED' | translate}} {{'REPOSITORY.DOCKER_VERSION' | translate}} {{'REPOSITORY.ARCHITECTURE' | translate}} {{'REPOSITORY.OS' | translate}} - + {{t.tag}} {{t.pullCommand}} - + diff --git a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts index cb623a6d7..4a91096b3 100644 --- a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts +++ b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts @@ -10,10 +10,15 @@ import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmati import { Subscription } from 'rxjs/Subscription'; +import { Tag } from '../tag'; import { TagView } from '../tag-view'; import { AppConfigService } from '../../app-config.service'; +import { SessionService } from '../../shared/session.service'; + +import { Project } from '../../project/project'; + @Component({ moduleId: module.id, selector: 'tag-repository', @@ -25,8 +30,11 @@ export class TagRepositoryComponent implements OnInit, OnDestroy { projectId: number; repoName: string; + hasProjectAdminRole: boolean = false; + tags: TagView[]; registryUrl: string; + withNotary: boolean; private subscription: Subscription; @@ -35,7 +43,9 @@ export class TagRepositoryComponent implements OnInit, OnDestroy { private messageService: MessageService, private deletionDialogService: ConfirmationDialogService, private repositoryService: RepositoryService, - private appConfigService: AppConfigService) { + private appConfigService: AppConfigService, + private session: SessionService){ + this.subscription = this.deletionDialogService.confirmationConfirm$.subscribe( message => { if (message && @@ -52,6 +62,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy { .subscribe( response => { this.retrieve(); + this.messageService.announceMessage(response, 'REPOSITORY.DELETED_TAG_SUCCESS', AlertType.SUCCESS); console.log('Deleted repo:' + this.repoName + ' with tag:' + tagName); }, error => this.messageService.announceMessage(error.status, 'Failed to delete tag:' + tagName + ' under repo:' + this.repoName, AlertType.DANGER) @@ -59,15 +70,20 @@ export class TagRepositoryComponent implements OnInit, OnDestroy { } } } - } - ) + }); } ngOnInit() { + let resolverData = this.route.snapshot.data; + console.log(JSON.stringify(resolverData)); + if(resolverData) { + this.hasProjectAdminRole = (resolverData['projectResolver']).has_project_admin_role; + } this.projectId = this.route.snapshot.params['id']; this.repoName = this.route.snapshot.params['repo']; this.tags = []; this.registryUrl = this.appConfigService.getConfig().registry_url; + this.withNotary = this.appConfigService.getConfig().with_notary; this.retrieve(); } @@ -79,25 +95,35 @@ export class TagRepositoryComponent implements OnInit, OnDestroy { retrieve() { this.tags = []; - this.repositoryService - .listTagsWithVerifiedSignatures(this.repoName) - .subscribe( - items => { - items.forEach(t => { - let tag = new TagView(); - tag.tag = t.tag; - let data = JSON.parse(t.manifest.history[0].v1Compatibility); - tag.architecture = data['architecture']; - tag.author = data['author']; - tag.signed = t.signed; - tag.created = data['created']; - tag.dockerVersion = data['docker_version']; - tag.pullCommand = 'docker pull ' + this.registryUrl + '/' + t.manifest.name + ':' + t.tag; - tag.os = data['os']; - this.tags.push(tag); - }); - }, - error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER)); + if(this.withNotary) { + this.repositoryService + .listTagsWithVerifiedSignatures(this.repoName) + .subscribe( + items => this.listTags(items), + error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER)); + } else { + this.repositoryService + .listTags(this.repoName) + .subscribe( + items => this.listTags(items), + error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER)); + } + } + + private listTags(tags: Tag[]): void { + tags.forEach(t => { + let tag = new TagView(); + tag.tag = t.tag; + let data = JSON.parse(t.manifest.history[0].v1Compatibility); + tag.architecture = data['architecture']; + tag.author = data['author']; + tag.signed = t.signed; + tag.created = data['created']; + tag.dockerVersion = data['docker_version']; + tag.pullCommand = 'docker pull ' + this.registryUrl + '/' + t.manifest.name + ':' + t.tag; + tag.os = data['os']; + this.tags.push(tag); + }); } deleteTag(tag: TagView) { diff --git a/src/ui_ng/src/app/service/auth-guard.service.ts b/src/ui_ng/src/app/service/auth-guard.service.ts deleted file mode 100644 index 4c1fbcea4..000000000 --- a/src/ui_ng/src/app/service/auth-guard.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@angular/core'; -import { CanActivate } from '@angular/router'; - -export class AuthGuard implements CanActivate { - canActivate() { - return true; - } -} \ No newline at end of file diff --git a/src/ui_ng/src/app/service/base.service.ts b/src/ui_ng/src/app/service/base.service.ts deleted file mode 100644 index 7e79b2049..000000000 --- a/src/ui_ng/src/app/service/base.service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Http, Response,} from '@angular/http'; - -export class BaseService { - - protected handleError(error: Response | any): Promise { - // In a real world app, we might use a remote logging infrastructure - let errMsg: string; - console.log(typeof error); - if (error instanceof Response) { - const body = error.json() || ''; - const err = body.error || JSON.stringify(body); - errMsg = `${error.status} - ${error.statusText || ''} ${err}`; - } else { - errMsg = error.message ? error.message : error.toString(); - } - return Promise.reject(error); - } -} \ No newline at end of file diff --git a/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.ts b/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.ts index bf9699454..2c4caa413 100644 --- a/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.ts +++ b/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.ts @@ -183,6 +183,7 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked { .createPolicy(this.getPolicyByForm()) .subscribe( response=>{ + this.messageService.announceMessage(response, 'REPLICATION.CREATED_SUCCESS', AlertType.SUCCESS); console.log('Successful created policy: ' + response); this.createEditPolicyOpened = false; this.reload.emit(true); @@ -199,6 +200,7 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked { .createOrUpdatePolicyWithNewTarget(this.getPolicyByForm(), this.getTargetByForm()) .subscribe( response=>{ + this.messageService.announceMessage(response, 'REPLICATION.UPDATED_SUCCESS', AlertType.SUCCESS); console.log('Successful created policy and target:' + response); this.createEditPolicyOpened = false; this.reload.emit(true); diff --git a/src/ui_ng/src/app/shared/list-policy/list-policy.component.ts b/src/ui_ng/src/app/shared/list-policy/list-policy.component.ts index 523241764..39dedb2ad 100644 --- a/src/ui_ng/src/app/shared/list-policy/list-policy.component.ts +++ b/src/ui_ng/src/app/shared/list-policy/list-policy.component.ts @@ -50,7 +50,10 @@ export class ListPolicyComponent implements OnDestroy { this.replicationService .enablePolicy(policy.id, policy.enabled) .subscribe( - res => console.log('Successful toggled policy status'), + response => { + this.messageService.announceMessage(response, 'REPLICATION.TOGGLED_SUCCESS', AlertType.SUCCESS); + console.log('Successful toggled policy status') + }, error => this.messageService.announceMessage(error.status, "Failed to toggle policy status.", AlertType.DANGER) ); } @@ -67,10 +70,11 @@ export class ListPolicyComponent implements OnDestroy { .deletePolicy(message.data) .subscribe( response => { + this.messageService.announceMessage(response, 'REPLICATION.DELETED_SUCCESS', AlertType.SUCCESS); console.log('Successful delete policy with ID:' + message.data); this.reload.emit(true); }, - error => this.messageService.announceMessage(error.status, 'Failed to delete policy with ID:' + message.data, AlertType.DANGER) + error => this.messageService.announceMessage(error.status, 'REPLICATION.DELETED_FAILED', AlertType.DANGER) ); } } diff --git a/src/ui_ng/src/app/shared/route/member-guard-activate.service.ts b/src/ui_ng/src/app/shared/route/member-guard-activate.service.ts new file mode 100644 index 000000000..18632bbab --- /dev/null +++ b/src/ui_ng/src/app/shared/route/member-guard-activate.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, Router, + ActivatedRouteSnapshot, + RouterStateSnapshot, + CanActivateChild +} from '@angular/router'; +import { SessionService } from '../../shared/session.service'; +import { ProjectService } from '../../project/project.service'; +import { CommonRoutes } from '../../shared/shared.const'; + +@Injectable() +export class MemberGuard implements CanActivate, CanActivateChild { + constructor( + private sessionService: SessionService, + private projectService: ProjectService, + private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise | boolean { + let projectId = route.params['id']; + this.sessionService.setProjectMembers([]); + return new Promise((resolve, reject) => { + this.projectService.checkProjectMember(projectId) + .subscribe( + res=>{ + this.sessionService.setProjectMembers(res); + return resolve(true) + }, + error => { + //Add exception for repository in project detail router activation. + if(state.url.endsWith('repository')) { + return resolve(true); + } + this.router.navigate([CommonRoutes.HARBOR_DEFAULT]); + return resolve(false); + }); + }); + } + + canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise | boolean { + return this.canActivate(route, state); + } +} diff --git a/src/ui_ng/src/app/shared/session.service.ts b/src/ui_ng/src/app/shared/session.service.ts index 33c234c64..213d262be 100644 --- a/src/ui_ng/src/app/shared/session.service.ts +++ b/src/ui_ng/src/app/shared/session.service.ts @@ -3,6 +3,8 @@ import { Headers, Http, URLSearchParams } from '@angular/http'; import 'rxjs/add/operator/toPromise'; import { SessionUser } from './session-user'; +import { Member } from '../project/member/member'; + import { SignInCredential } from './sign-in-credential'; import { enLang } from '../shared/shared.const' @@ -27,6 +29,8 @@ const langMap = { export class SessionService { currentUser: SessionUser = null; + projectMembers: Member[]; + private headers = new Headers({ "Content-Type": 'application/json' }); @@ -143,4 +147,12 @@ export class SessionService { }) .catch(error => this.handleError(error)); } + + setProjectMembers(projectMembers: Member[]): void { + this.projectMembers = projectMembers; + } + + getProjectMembers(): Member[] { + return this.projectMembers; + } } \ No newline at end of file diff --git a/src/ui_ng/src/app/shared/shared.const.ts b/src/ui_ng/src/app/shared/shared.const.ts index f73949688..830b00baf 100644 --- a/src/ui_ng/src/app/shared/shared.const.ts +++ b/src/ui_ng/src/app/shared/shared.const.ts @@ -24,7 +24,8 @@ export const enum ConfirmationTargets { REPOSITORY, TAG, CONFIG, - CONFIG_ROUTE + CONFIG_ROUTE, + CONFIG_TAB }; export const enum ActionType { @@ -51,4 +52,7 @@ export const CookieKeyOfAdmiral = "admiral.endpoint.latest"; export const enum ConfirmationState { NA, CONFIRMED, CANCEL -} \ No newline at end of file +} + +export const ProjectTypes = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' }; +export const RoleInfo = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' }; diff --git a/src/ui_ng/src/app/shared/shared.module.ts b/src/ui_ng/src/app/shared/shared.module.ts index 5c6d2a42c..29ae3dba3 100644 --- a/src/ui_ng/src/app/shared/shared.module.ts +++ b/src/ui_ng/src/app/shared/shared.module.ts @@ -32,6 +32,7 @@ import { StatisticsComponent } from './statictics/statistics.component'; import { StatisticsPanelComponent } from './statictics/statistics-panel.component'; import { SignInGuard } from './route/sign-in-guard-activate.service'; import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate.service'; +import { MemberGuard } from './route/member-guard-activate.service'; @NgModule({ imports: [ @@ -79,7 +80,8 @@ import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate. SystemAdminGuard, AuthCheckGuard, SignInGuard, - LeavingConfigRouteDeactivate + LeavingConfigRouteDeactivate, + MemberGuard ] }) export class SharedModule { diff --git a/src/ui_ng/src/app/shared/statictics/statistics-panel.component.html b/src/ui_ng/src/app/shared/statictics/statistics-panel.component.html index e7a860ed4..df9828631 100644 --- a/src/ui_ng/src/app/shared/statictics/statistics-panel.component.html +++ b/src/ui_ng/src/app/shared/statictics/statistics-panel.component.html @@ -1,25 +1,41 @@ -
-

{{'STATISTICS.TITLE' | translate }}

- -
-
-{{'STATISTICS.PRO_ITEM' | translate }} -
-
- - - -
-
-
-
- {{'STATISTICS.REPO_ITEM' | translate }} +
+
+
+
+
+
+ {{'STATISTICS.PRO_ITEM' | translate }} +
+
+ {{'STATISTICS.REPO_ITEM' | translate }} +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
Storage
-
- - - -
-
-
\ No newline at end of file diff --git a/src/ui_ng/src/app/shared/statictics/statistics.component.css b/src/ui_ng/src/app/shared/statictics/statistics.component.css index 51b62a853..f308d3377 100644 --- a/src/ui_ng/src/app/shared/statictics/statistics.component.css +++ b/src/ui_ng/src/app/shared/statictics/statistics.component.css @@ -1,30 +1,57 @@ .statistic-wrapper { - padding: 12px; - margin: 12px; - text-align: center; + padding: 4px; + margin: 4px; + text-align: right; vertical-align: middle; - height: 72px; - min-width: 108px; - max-width: 216px; + height: 30px; display: inline-block; } .statistic-data { - font-size: 48px; - font-weight: bolder; - font-family: "Metropolis"; - line-height: 48px; + font-size: 16px; + font-weight: 900; + font-family: "semibold"; + line-height: 16px; } .statistic-text { - font-size: 24px; - font-weight: 400; - line-height: 24px; + font-size: 10px; + line-height: 10px; text-transform: uppercase; - font-family: "Metropolis"; + font-family: "semibold"; +} + +.statistic-column-block { + display: inline-block; + text-align: right; } .statistic-column-title { position: relative; - top: 40%; + text-transform: uppercase; + font-size: 14px; +} + +.statistic-column-title-pro { + top: -10px; +} + +.statistic-column-title-repo { + top: 3px; +} + +.statistic-item-divider { + height: 54px; + display: inline-block; + width: 1px; + background-color: #ccc; + opacity: 0.55; + margin-left: 4px; + margin-right: 12px; + position: relative; + top: 3px; +} + +.statistic-block { + display: inline-block; } \ No newline at end of file diff --git a/src/ui_ng/src/app/shared/statictics/statistics.component.html b/src/ui_ng/src/app/shared/statictics/statistics.component.html index 642ed916a..2d98c617e 100644 --- a/src/ui_ng/src/app/shared/statictics/statistics.component.html +++ b/src/ui_ng/src/app/shared/statictics/statistics.component.html @@ -1,4 +1,4 @@
- {{data.number}} - {{data.label}} + {{data}} + {{label}}
\ No newline at end of file diff --git a/src/ui_ng/src/app/shared/statictics/statistics.component.ts b/src/ui_ng/src/app/shared/statictics/statistics.component.ts index f1e5563fb..bcb028b61 100644 --- a/src/ui_ng/src/app/shared/statictics/statistics.component.ts +++ b/src/ui_ng/src/app/shared/statictics/statistics.component.ts @@ -7,5 +7,6 @@ import { Component, Input } from '@angular/core'; }) export class StatisticsComponent { - @Input() data: any; + @Input() label: string; + @Input() data: number = 0; } \ No newline at end of file diff --git a/src/ui_ng/src/i18n/lang/en-lang.json b/src/ui_ng/src/i18n/lang/en-lang.json index f1c99ff45..d1efbb68b 100644 --- a/src/ui_ng/src/i18n/lang/en-lang.json +++ b/src/ui_ng/src/i18n/lang/en-lang.json @@ -110,10 +110,10 @@ "PROJECT": { "PROJECTS": "Projects", "NAME": "Project Name", + "ROLE": "Role", "PUBLIC_OR_PRIVATE": "Public", "REPO_COUNT": "Repositories Count", "CREATION_TIME": "Creation Time", - "DESCRIPTION": "Description", "PUBLIC": "Public", "PRIVATE": "Private", "MAKE": "Make", @@ -132,7 +132,10 @@ "DELETION_TITLE": "Confirm project deletion", "DELETION_SUMMARY": "Do you want to delete project {{param}}?", "FILTER_PLACEHOLDER": "Filter Projects", - "REPLICATION_RULE": "Replication Rule" + "REPLICATION_RULE": "Replication Rule", + "CREATED_SUCCESS": "Created project successfully.", + "DELETED_SUCCESS": "Deleted project successfully.", + "TOGGLED_SUCCESS": "Toggled project successfully." }, "PROJECT_DETAIL": { "REPOSITORIES": "Repositories", @@ -159,7 +162,10 @@ "UNKNOWN_ERROR": "Unknown error occurred while adding member.", "FILTER_PLACEHOLDER": "Filter Members", "DELETION_TITLE": "Confirm project member deletion", - "DELETION_SUMMARY": "Do you want to delete project member {{param}}?" + "DELETION_SUMMARY": "Do you want to delete project member {{param}}?", + "ADDED_SUCCESS": "Added member successfully.", + "DELETED_SUCCESS": "Deleted member successfully.", + "SWITCHED_SUCCESS": "Switched member role successfully." }, "AUDIT_LOG": { "USERNAME": "Username", @@ -235,7 +241,12 @@ "TOGGLE_ENABLE_TITLE": "Enable Policy", "CONFIRM_TOGGLE_ENABLE_POLICY": "After enabling the replication policy, all repositories under the project will be replicated to the destination registry. Please confirm to continue.", "TOGGLE_DISABLE_TITLE": "Disable Policy", - "CONFIRM_TOGGLE_DISABLE_POLICY": "After disabling the policy, all unfinished replication jobs of this policy will be stopped and canceled. Please confirm to continue." + "CONFIRM_TOGGLE_DISABLE_POLICY": "After disabling the policy, all unfinished replication jobs of this policy will be stopped and canceled. Please confirm to continue.", + "CREATED_SUCCESS": "Created policy successfully.", + "UPDATED_SUCCESS": "Updated policy successfully.", + "DELETED_SUCCESS": "Deleted policy successfully.", + "DELETED_FAILED": "Deleted policy failed.", + "TOGGLED_SUCCESS": "Toggled policy status successfully." }, "DESTINATION": { "NEW_ENDPOINT": "New Endpoint", @@ -257,7 +268,11 @@ "INVALID_NAME": "Invalid destination name.", "FAILED_TO_GET_TARGET": "Failed to get endpoint.", "CREATION_TIME": "Creation Time", - "ITEMS": "item(s)" + "ITEMS": "item(s)", + "CREATED_SUCCESS": "Created destination successfully.", + "UPDATED_SUCCESS": "Updated destination successfully.", + "DELETED_SUCCESS": "Deleted destination successfully.", + "DELETED_FAILED": "Deleted destination failed." }, "REPOSITORY": { "COPY_ID": "Copy ID", @@ -275,7 +290,7 @@ "DELETION_SUMMARY_TAG": "Do you want to delete tag {{param}}?", "DELETION_TITLE_TAG_DENIED": "Signed Tag can't be deleted", "DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted. {{param}}", - "FILTER_FOR_REPOSITORIES": "Filter for repositories", + "FILTER_FOR_REPOSITORIES": "Filter Repositories", "TAG": "Tag", "SIGNED": "Signed", "AUTHOR": "Author", @@ -286,7 +301,9 @@ "SHOW_DETAILS": "Show Details", "REPOSITORIES": "Repositories", "ITEMS": "item(s)", - "POP_REPOS": "Popular Repositories" + "POP_REPOS": "Popular Repositories", + "DELETED_REPO_SUCCESS": "Deleted repository successfully.", + "DELETED_TAG_SUCCESS": "Deleted tag successfully." }, "ALERT": { "FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet, do you really want to cancel?" @@ -310,7 +327,7 @@ "EMAIL": "Email", "SYSTEM": "System Settings", "CONFIRM_TITLE": "Confirm to cancel", - "CONFIRM_SUMMARY": "Some changes are not saved yet, do you really want to leave?", + "CONFIRM_SUMMARY": "Some changes are not saved yet, do you really want to discard?", "SAVE_SUCCESS": "Configurations have been successfully saved", "MAIL_SERVER": "Email Server", "MAIL_SERVER_PORT": "Email Server Port", @@ -386,7 +403,8 @@ "IN_PROGRESS": "Search...", "BACK": "Back" }, - "UNKNOWN_ERROR": "Some unknown errors HAVE occurred. Please try again later", + "UNKNOWN_ERROR": "Unknown errors have occurred. Please try again later", "UNAUTHORIZED_ERROR": "Your session is invalid or has expired. You need to sign in to continue the operation", - "FORBIDDEN_ERROR": "You are not allowed to perform this operation" + "FORBIDDEN_ERROR": "You are not allowed to perform this operation", + "GENERAL_ERROR": "Errors have occurred when performing service call: {{param}}" } \ No newline at end of file diff --git a/src/ui_ng/src/i18n/lang/zh-lang.json b/src/ui_ng/src/i18n/lang/zh-lang.json index bd2b0ef05..3250b2369 100644 --- a/src/ui_ng/src/i18n/lang/zh-lang.json +++ b/src/ui_ng/src/i18n/lang/zh-lang.json @@ -110,10 +110,10 @@ "PROJECT": { "PROJECTS": "项目", "NAME": "项目名称", + "ROLE": "角色", "PUBLIC_OR_PRIVATE": "公开", "REPO_COUNT": "镜像仓库数", "CREATION_TIME": "创建时间", - "DESCRIPTION": "描述", "PUBLIC": "公开", "PRIVATE": "私有", "MAKE": "设为", @@ -132,7 +132,10 @@ "DELETION_TITLE": "删除项目确认", "DELETION_SUMMARY": "你确认删除项目 {{param}}?", "FILTER_PLACEHOLDER": "过滤项目", - "REPLICATION_RULE": "复制策略" + "REPLICATION_RULE": "复制策略", + "CREATED_SUCCESS": "创建项目成功。", + "DELETED_SUCCESS": "删除项目成功。", + "TOGGLED_SUCCESS": "切换状态成功。" }, "PROJECT_DETAIL": { "REPOSITORIES": "镜像仓库", @@ -159,7 +162,10 @@ "UNKNOWN_ERROR": "添加成员时发生未知错误。", "FILTER_PLACEHOLDER": "过滤成员", "DELETION_TITLE": "删除项目成员确认", - "DELETION_SUMMARY": "你确认删除项目成员 {{param}}?" + "DELETION_SUMMARY": "你确认删除项目成员 {{param}}?", + "ADDED_SUCCESS": "新增成员成功。", + "DELETED_SUCCESS": "删除成员成功", + "SWITCHED_SUCCESS": "切换角色成功" }, "AUDIT_LOG": { "USERNAME": "用户名", @@ -235,7 +241,12 @@ "TOGGLE_ENABLE_TITLE": "启用策略", "CONFIRM_TOGGLE_ENABLE_POLICY": "启用策略后,该项目下的所有镜像仓库将复制到目标实例。请确认继续。", "TOGGLE_DISABLE_TITLE": "停用策略", - "CONFIRM_TOGGLE_DISABLE_POLICY": "停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。" + "CONFIRM_TOGGLE_DISABLE_POLICY": "停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。", + "CREATED_SUCCESS": "创建复制策略成功。", + "UPDATED_SUCCESS": "更新复制策略成功。", + "DELETED_SUCCESS": "删除复制策略成功。", + "DELETED_FAILED": "删除复制策略失败。", + "TOGGLED_SUCCESS": "切换复制策略状态成功。" }, "DESTINATION": { "NEW_ENDPOINT": "新建目标", @@ -257,7 +268,11 @@ "INVALID_NAME": "无效的目标名称。", "FAILED_TO_GET_TARGET": "获取目标失败。", "CREATION_TIME": "创建时间", - "ITEMS": "条记录" + "ITEMS": "条记录", + "CREATED_SUCCESS": "创建目标成功。", + "UPDATED_SUCCESS": "更新目标成功。", + "DELETED_SUCCESS": "删除目标成功。", + "DELETED_FAILED": "删除目标失败。" }, "REPOSITORY": { "COPY_ID": "复制ID", @@ -286,7 +301,9 @@ "SHOW_DETAILS": "显示详细", "REPOSITORIES": "镜像仓库", "ITEMS": "条记录", - "POP_REPOS": "受欢迎的镜像库" + "POP_REPOS": "受欢迎的镜像库", + "DELETED_REPO_SUCCESS": "删除镜像仓库成功。", + "DELETED_TAG_SUCCESS": "删除镜像标签成功。" }, "ALERT": { "FORM_CHANGE_CONFIRMATION": "表单内容改变,确认取消?" @@ -378,8 +395,8 @@ "TITLE": "统计", "PRO_ITEM": "项目", "REPO_ITEM": "镜像库", - "INDEX_MY": "私有的", - "INDEX_PUB": "公开的", + "INDEX_MY": "私有", + "INDEX_PUB": "公开", "INDEX_TOTAL": "总计" }, "SEARCH": { @@ -388,5 +405,6 @@ }, "UNKNOWN_ERROR": "发生未知错误,请稍后再试", "UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续", - "FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限" + "FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限", + "GENERAL_ERROR": "调用后台服务时出现错误: {{param}}" } \ No newline at end of file diff --git a/tests/startuptest.go b/tests/startuptest.go deleted file mode 100644 index 5330b45b5..000000000 --- a/tests/startuptest.go +++ /dev/null @@ -1,49 +0,0 @@ -// Fetch prints the content found at a URL. -package main - -import ( - "crypto/tls" - "fmt" - "io/ioutil" - "net/http" - "os" - "strings" - "time" -) - -func main() { - time.Sleep(60 * time.Second) - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - var client = &http.Client{ - Timeout: time.Second * 30, - Transport: tr, - } - - for _, url := range os.Args[1:] { - - resp, err := client.Get(url) - if err != nil { - fmt.Fprintf(os.Stderr, "fetch: %v\n", err) - os.Exit(1) - } - b, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err) - os.Exit(1) - } - // fmt.Printf("%s", b) - - if strings.Contains(string(b), "Harbor") { - fmt.Printf("sucess!\n") - } else { - fmt.Println("the response does not contain \"Harbor\"!") - - fmt.Println(string(b)) - os.Exit(1) - } - - } -} diff --git a/tests/startuptest.sh b/tests/startuptest.sh new file mode 100755 index 000000000..7a41cd768 --- /dev/null +++ b/tests/startuptest.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +set +e + +TIMEOUT=12 +while [ $TIMEOUT -gt 0 ]; do + STATUS=$(curl --insecure -s -o /dev/null -w '%{http_code}' https://localhost/) + if [ $STATUS -eq 200 ]; then + break + fi + TIMEOUT=$(($TIMEOUT - 1)) + sleep 5 +done + +if [ $TIMEOUT -eq 0 ]; then + echo "Harbor cannot reach within one minute." + exit 1 +fi + +curl --insecure -s -L -H "Accept: application/json" https://localhost/ | grep "Harbor" > /dev/null +if [ $? -eq 0 ]; then + echo "Harbor is running success." +else + echo "Harbor is running fail." + exit 1 +fi + +