mirror of https://github.com/goharbor/harbor.git
package vmware/registry into offline package
This commit is contained in:
commit
980101eab5
|
@ -10,7 +10,10 @@ src/common/dao/dao.test
|
||||||
*.pyc
|
*.pyc
|
||||||
jobservice/test
|
jobservice/test
|
||||||
|
|
||||||
src/ui/static/dist/
|
src/ui/static/*.html
|
||||||
|
src/ui/static/*.bundle.js
|
||||||
|
src/ui/static/*.bundle.js.map
|
||||||
|
src/ui/static/harbor-log.*.png
|
||||||
|
|
||||||
src/ui_ng/coverage/
|
src/ui_ng/coverage/
|
||||||
src/ui_ng/dist/
|
src/ui_ng/dist/
|
||||||
|
|
|
@ -76,6 +76,8 @@ before_script:
|
||||||
script:
|
script:
|
||||||
- sudo mkdir -p /harbor_storage/ca_download
|
- sudo mkdir -p /harbor_storage/ca_download
|
||||||
- sudo mv ./tests/ca.crt /harbor_storage/ca_download
|
- sudo mv ./tests/ca.crt /harbor_storage/ca_download
|
||||||
|
- sudo mkdir -p /harbor
|
||||||
|
- sudo mv ./VERSION /harbor/VERSION
|
||||||
- sudo service mysql stop
|
- sudo service mysql stop
|
||||||
- sudo ./tests/testprepare.sh
|
- sudo ./tests/testprepare.sh
|
||||||
- docker-compose -f ./make/docker-compose.test.yml up -d
|
- docker-compose -f ./make/docker-compose.test.yml up -d
|
||||||
|
@ -98,6 +100,7 @@ script:
|
||||||
- sudo make install GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage CLARITYIMAGE=danieljt/harbor-clarity-base:0.8.4 NOTARYFLAG=true
|
- sudo make install GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage CLARITYIMAGE=danieljt/harbor-clarity-base:0.8.4 NOTARYFLAG=true
|
||||||
|
|
||||||
- docker ps
|
- docker ps
|
||||||
|
- ./tests/notarytest.sh
|
||||||
- go run tests/startuptest.go https://localhost/
|
- go run tests/startuptest.go https://localhost/
|
||||||
- go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD}
|
- go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD}
|
||||||
|
|
||||||
|
|
888
Makefile
888
Makefile
|
@ -1,445 +1,443 @@
|
||||||
# Makefile for Harbor project
|
# Makefile for Harbor project
|
||||||
#
|
#
|
||||||
# Targets:
|
# Targets:
|
||||||
#
|
#
|
||||||
# all: prepare env, compile binarys, build images and install images
|
# all: prepare env, compile binarys, build images and install images
|
||||||
# prepare: prepare env
|
# prepare: prepare env
|
||||||
# compile: compile adminserver, ui and jobservice code
|
# compile: compile adminserver, ui and jobservice code
|
||||||
#
|
#
|
||||||
# compile_golangimage:
|
# compile_golangimage:
|
||||||
# compile from golang image
|
# compile from golang image
|
||||||
# for example: make compile_golangimage -e GOBUILDIMAGE= \
|
# for example: make compile_golangimage -e GOBUILDIMAGE= \
|
||||||
# golang:1.7.3
|
# golang:1.7.3
|
||||||
# compile_adminserver, compile_ui, compile_jobservice: compile specific binary
|
# compile_adminserver, compile_ui, compile_jobservice: compile specific binary
|
||||||
#
|
#
|
||||||
# build: build Harbor docker images (defuault: build_photon)
|
# build: build Harbor docker images (defuault: build_photon)
|
||||||
# for example: make build -e BASEIMAGE=photon
|
# for example: make build -e BASEIMAGE=photon
|
||||||
# build_photon: build Harbor docker images from photon baseimage
|
# build_photon: build Harbor docker images from photon baseimage
|
||||||
#
|
#
|
||||||
# install: include compile binarys, build images, prepare specific \
|
# install: include compile binarys, build images, prepare specific \
|
||||||
# version composefile and startup Harbor instance
|
# version composefile and startup Harbor instance
|
||||||
#
|
#
|
||||||
# start: startup Harbor instance
|
# start: startup Harbor instance
|
||||||
#
|
#
|
||||||
# down: shutdown Harbor instance
|
# down: shutdown Harbor instance
|
||||||
#
|
#
|
||||||
# package_online:
|
# package_online:
|
||||||
# prepare online install package
|
# prepare online install package
|
||||||
# for example: make package_online -e DEVFLAG=false\
|
# for example: make package_online -e DEVFLAG=false\
|
||||||
# REGISTRYSERVER=reg-bj.eng.vmware.com \
|
# REGISTRYSERVER=reg-bj.eng.vmware.com \
|
||||||
# REGISTRYPROJECTNAME=harborrelease
|
# REGISTRYPROJECTNAME=harborrelease
|
||||||
#
|
#
|
||||||
# package_offline:
|
# package_offline:
|
||||||
# prepare offline install package
|
# prepare offline install package
|
||||||
#
|
#
|
||||||
# pushimage: push Harbor images to specific registry server
|
# pushimage: push Harbor images to specific registry server
|
||||||
# for example: make pushimage -e DEVFLAG=false REGISTRYUSER=admin \
|
# for example: make pushimage -e DEVFLAG=false REGISTRYUSER=admin \
|
||||||
# REGISTRYPASSWORD=***** \
|
# REGISTRYPASSWORD=***** \
|
||||||
# REGISTRYSERVER=reg-bj.eng.vmware.com/ \
|
# REGISTRYSERVER=reg-bj.eng.vmware.com/ \
|
||||||
# REGISTRYPROJECTNAME=harborrelease
|
# REGISTRYPROJECTNAME=harborrelease
|
||||||
# note**: need add "/" on end of REGISTRYSERVER. If not setting \
|
# note**: need add "/" on end of REGISTRYSERVER. If not setting \
|
||||||
# this value will push images directly to dockerhub.
|
# this value will push images directly to dockerhub.
|
||||||
# make pushimage -e DEVFLAG=false REGISTRYUSER=vmware \
|
# make pushimage -e DEVFLAG=false REGISTRYUSER=vmware \
|
||||||
# REGISTRYPASSWORD=***** \
|
# REGISTRYPASSWORD=***** \
|
||||||
# REGISTRYPROJECTNAME=vmware
|
# REGISTRYPROJECTNAME=vmware
|
||||||
#
|
#
|
||||||
# clean: remove binary, Harbor images, specific version docker-compose \
|
# clean: remove binary, Harbor images, specific version docker-compose \
|
||||||
# file, specific version tag and online/offline install package
|
# file, specific version tag and online/offline install package
|
||||||
# cleanbinary: remove adminserver, ui and jobservice binary
|
# cleanbinary: remove adminserver, ui and jobservice binary
|
||||||
# cleanimage: remove Harbor images
|
# cleanimage: remove Harbor images
|
||||||
# cleandockercomposefile:
|
# cleandockercomposefile:
|
||||||
# remove specific version docker-compose
|
# remove specific version docker-compose
|
||||||
# cleanversiontag:
|
# cleanversiontag:
|
||||||
# cleanpackageremove specific version tag
|
# cleanpackageremove specific version tag
|
||||||
# cleanpackage: remove online/offline install package
|
# cleanpackage: remove online/offline install package
|
||||||
#
|
#
|
||||||
# other example:
|
# other example:
|
||||||
# clean specific version binarys and images:
|
# clean specific version binarys and images:
|
||||||
# make clean -e VERSIONTAG=[TAG]
|
# make clean -e VERSIONTAG=[TAG]
|
||||||
# note**: If commit new code to github, the git commit TAG will \
|
# note**: If commit new code to github, the git commit TAG will \
|
||||||
# change. Better use this commond clean previous images and \
|
# change. Better use this commond clean previous images and \
|
||||||
# files with specific TAG.
|
# files with specific TAG.
|
||||||
# By default DEVFLAG=true, if you want to release new version of Harbor, \
|
# By default DEVFLAG=true, if you want to release new version of Harbor, \
|
||||||
# should setting the flag to false.
|
# should setting the flag to false.
|
||||||
# make XXXX -e DEVFLAG=false
|
# make XXXX -e DEVFLAG=false
|
||||||
|
|
||||||
SHELL := /bin/bash
|
SHELL := /bin/bash
|
||||||
BUILDPATH=$(CURDIR)
|
BUILDPATH=$(CURDIR)
|
||||||
MAKEPATH=$(BUILDPATH)/make
|
MAKEPATH=$(BUILDPATH)/make
|
||||||
MAKEDEVPATH=$(MAKEPATH)/dev
|
MAKEDEVPATH=$(MAKEPATH)/dev
|
||||||
SRCPATH=./src
|
SRCPATH=./src
|
||||||
TOOLSPATH=$(BUILDPATH)/tools
|
TOOLSPATH=$(BUILDPATH)/tools
|
||||||
UIPATH=$(BUILDPATH)/src/ui
|
UIPATH=$(BUILDPATH)/src/ui
|
||||||
UINGPATH=$(BUILDPATH)/src/ui_ng
|
UINGPATH=$(BUILDPATH)/src/ui_ng
|
||||||
GOBASEPATH=/go/src/github.com/vmware
|
GOBASEPATH=/go/src/github.com/vmware
|
||||||
CHECKENVCMD=checkenv.sh
|
CHECKENVCMD=checkenv.sh
|
||||||
BASEIMAGE=photon
|
BASEIMAGE=photon
|
||||||
COMPILETAG=compile_normal
|
COMPILETAG=compile_normal
|
||||||
REGISTRYSERVER=
|
REGISTRYSERVER=
|
||||||
REGISTRYPROJECTNAME=vmware
|
REGISTRYPROJECTNAME=vmware
|
||||||
DEVFLAG=true
|
DEVFLAG=true
|
||||||
NOTARYFLAG=false
|
NOTARYFLAG=false
|
||||||
REGISTRYVERSION=photon-2.6.0
|
REGISTRYVERSION=photon-2.6.0
|
||||||
NGINXVERSION=1.11.5
|
NGINXVERSION=1.11.5
|
||||||
PHOTONVERSION=1.0
|
PHOTONVERSION=1.0
|
||||||
NOTARYVERSION=server-0.5.0
|
NOTARYVERSION=server-0.5.0
|
||||||
NOTARYSIGNERVERSION=signer-0.5.0
|
NOTARYSIGNERVERSION=signer-0.5.0
|
||||||
MARIADBVERSION=10.1.10
|
MARIADBVERSION=mariadb-10.1.10
|
||||||
HTTPPROXY=
|
HTTPPROXY=
|
||||||
|
|
||||||
#clarity parameters
|
#clarity parameters
|
||||||
CLARITYIMAGE=danieljt/harbor-clarity-base[:tag]
|
CLARITYIMAGE=danieljt/harbor-clarity-base[:tag]
|
||||||
CLARITYSEEDPATH=/clarity-seed
|
CLARITYSEEDPATH=/clarity-seed
|
||||||
CLARITYBUILDSCRIPT=/entrypoint.sh
|
CLARITYBUILDSCRIPT=/entrypoint.sh
|
||||||
|
|
||||||
# docker parameters
|
# docker parameters
|
||||||
DOCKERCMD=$(shell which docker)
|
DOCKERCMD=$(shell which docker)
|
||||||
DOCKERBUILD=$(DOCKERCMD) build
|
DOCKERBUILD=$(DOCKERCMD) build
|
||||||
DOCKERRMIMAGE=$(DOCKERCMD) rmi
|
DOCKERRMIMAGE=$(DOCKERCMD) rmi
|
||||||
DOCKERPULL=$(DOCKERCMD) pull
|
DOCKERPULL=$(DOCKERCMD) pull
|
||||||
DOCKERIMASES=$(DOCKERCMD) images
|
DOCKERIMASES=$(DOCKERCMD) images
|
||||||
DOCKERSAVE=$(DOCKERCMD) save
|
DOCKERSAVE=$(DOCKERCMD) save
|
||||||
DOCKERCOMPOSECMD=$(shell which docker-compose)
|
DOCKERCOMPOSECMD=$(shell which docker-compose)
|
||||||
DOCKERTAG=$(DOCKERCMD) tag
|
DOCKERTAG=$(DOCKERCMD) tag
|
||||||
|
|
||||||
# go parameters
|
# go parameters
|
||||||
GOCMD=$(shell which go)
|
GOCMD=$(shell which go)
|
||||||
GOBUILD=$(GOCMD) build
|
GOBUILD=$(GOCMD) build
|
||||||
GOCLEAN=$(GOCMD) clean
|
GOCLEAN=$(GOCMD) clean
|
||||||
GOINSTALL=$(GOCMD) install
|
GOINSTALL=$(GOCMD) install
|
||||||
GOTEST=$(GOCMD) test
|
GOTEST=$(GOCMD) test
|
||||||
GODEP=$(GOTEST) -i
|
GODEP=$(GOTEST) -i
|
||||||
GOFMT=gofmt -w
|
GOFMT=gofmt -w
|
||||||
GOBUILDIMAGE=reg.mydomain.com/library/harborgo[:tag]
|
GOBUILDIMAGE=reg.mydomain.com/library/harborgo[:tag]
|
||||||
GOBUILDPATH=$(GOBASEPATH)/harbor
|
GOBUILDPATH=$(GOBASEPATH)/harbor
|
||||||
GOIMAGEBUILDCMD=/usr/local/go/bin/go
|
GOIMAGEBUILDCMD=/usr/local/go/bin/go
|
||||||
GOIMAGEBUILD=$(GOIMAGEBUILDCMD) build
|
GOIMAGEBUILD=$(GOIMAGEBUILDCMD) build
|
||||||
GOBUILDPATH_ADMINSERVER=$(GOBUILDPATH)/src/adminserver
|
GOBUILDPATH_ADMINSERVER=$(GOBUILDPATH)/src/adminserver
|
||||||
GOBUILDPATH_UI=$(GOBUILDPATH)/src/ui
|
GOBUILDPATH_UI=$(GOBUILDPATH)/src/ui
|
||||||
GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/src/jobservice
|
GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/src/jobservice
|
||||||
GOBUILDMAKEPATH=$(GOBUILDPATH)/make
|
GOBUILDMAKEPATH=$(GOBUILDPATH)/make
|
||||||
GOBUILDMAKEPATH_ADMINSERVER=$(GOBUILDMAKEPATH)/dev/adminserver
|
GOBUILDMAKEPATH_ADMINSERVER=$(GOBUILDMAKEPATH)/dev/adminserver
|
||||||
GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/dev/ui
|
GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/dev/ui
|
||||||
GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice
|
GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice
|
||||||
GOLANGDOCKERFILENAME=Dockerfile.golang
|
GOLANGDOCKERFILENAME=Dockerfile.golang
|
||||||
|
|
||||||
# binary
|
# binary
|
||||||
ADMINSERVERSOURCECODE=$(SRCPATH)/adminserver
|
ADMINSERVERSOURCECODE=$(SRCPATH)/adminserver
|
||||||
ADMINSERVERBINARYPATH=$(MAKEDEVPATH)/adminserver
|
ADMINSERVERBINARYPATH=$(MAKEDEVPATH)/adminserver
|
||||||
ADMINSERVERBINARYNAME=harbor_adminserver
|
ADMINSERVERBINARYNAME=harbor_adminserver
|
||||||
UISOURCECODE=$(SRCPATH)/ui
|
UISOURCECODE=$(SRCPATH)/ui
|
||||||
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
||||||
UIBINARYNAME=harbor_ui
|
UIBINARYNAME=harbor_ui
|
||||||
JOBSERVICESOURCECODE=$(SRCPATH)/jobservice
|
JOBSERVICESOURCECODE=$(SRCPATH)/jobservice
|
||||||
JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice
|
JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice
|
||||||
JOBSERVICEBINARYNAME=harbor_jobservice
|
JOBSERVICEBINARYNAME=harbor_jobservice
|
||||||
|
|
||||||
# prepare parameters
|
# prepare parameters
|
||||||
PREPAREPATH=$(TOOLSPATH)
|
PREPAREPATH=$(TOOLSPATH)
|
||||||
PREPARECMD=prepare
|
PREPARECMD=prepare
|
||||||
|
|
||||||
# configfile
|
# configfile
|
||||||
CONFIGPATH=$(MAKEPATH)
|
CONFIGPATH=$(MAKEPATH)
|
||||||
CONFIGFILE=harbor.cfg
|
CONFIGFILE=harbor.cfg
|
||||||
|
|
||||||
# makefile
|
# makefile
|
||||||
MAKEFILEPATH_PHOTON=$(MAKEPATH)/photon
|
MAKEFILEPATH_PHOTON=$(MAKEPATH)/photon
|
||||||
|
|
||||||
# common dockerfile
|
# common dockerfile
|
||||||
DOCKERFILEPATH_COMMON=$(MAKEPATH)/common
|
DOCKERFILEPATH_COMMON=$(MAKEPATH)/common
|
||||||
DOCKERFILEPATH_DB=$(DOCKERFILEPATH_COMMON)/db
|
DOCKERFILEPATH_DB=$(DOCKERFILEPATH_COMMON)/db
|
||||||
DOCKERFILENAME_DB=Dockerfile
|
DOCKERFILENAME_DB=Dockerfile
|
||||||
|
|
||||||
# docker image name
|
# docker image name
|
||||||
DOCKERIMAGENAME_ADMINSERVER=vmware/harbor-adminserver
|
DOCKERIMAGENAME_ADMINSERVER=vmware/harbor-adminserver
|
||||||
DOCKERIMAGENAME_UI=vmware/harbor-ui
|
DOCKERIMAGENAME_UI=vmware/harbor-ui
|
||||||
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
||||||
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
||||||
DOCKERIMAGENAME_DB=vmware/harbor-db
|
DOCKERIMAGENAME_DB=vmware/harbor-db
|
||||||
|
|
||||||
# docker-compose files
|
# docker-compose files
|
||||||
DOCKERCOMPOSEFILEPATH=$(MAKEPATH)
|
DOCKERCOMPOSEFILEPATH=$(MAKEPATH)
|
||||||
DOCKERCOMPOSETPLFILENAME=docker-compose.tpl
|
DOCKERCOMPOSETPLFILENAME=docker-compose.tpl
|
||||||
DOCKERCOMPOSEFILENAME=docker-compose.yml
|
DOCKERCOMPOSEFILENAME=docker-compose.yml
|
||||||
DOCKERCOMPOSENOTARYFILENAME=docker-compose.notary.yml
|
DOCKERCOMPOSENOTARYFILENAME=docker-compose.notary.yml
|
||||||
|
|
||||||
# version prepare
|
# version prepare
|
||||||
VERSIONFILEPATH=$(SRCPATH)/ui/views/sections
|
VERSIONFILEPATH=$(CURDIR)
|
||||||
VERSIONFILENAME=header-content.htm
|
VERSIONFILENAME=VERSION
|
||||||
GITCMD=$(shell which git)
|
GITCMD=$(shell which git)
|
||||||
GITTAG=$(GITCMD) describe --tags
|
GITTAG=$(GITCMD) describe --tags
|
||||||
ifeq ($(DEVFLAG), true)
|
ifeq ($(DEVFLAG), true)
|
||||||
VERSIONTAG=dev
|
VERSIONTAG=dev
|
||||||
else
|
else
|
||||||
VERSIONTAG=$(shell $(GITTAG))
|
VERSIONTAG=$(shell $(GITTAG))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
SEDCMD=$(shell which sed)
|
SEDCMD=$(shell which sed)
|
||||||
|
|
||||||
# package
|
# package
|
||||||
TARCMD=$(shell which tar)
|
TARCMD=$(shell which tar)
|
||||||
ZIPCMD=$(shell which gzip)
|
ZIPCMD=$(shell which gzip)
|
||||||
DOCKERIMGFILE=harbor
|
DOCKERIMGFILE=harbor
|
||||||
HARBORPKG=harbor
|
HARBORPKG=harbor
|
||||||
|
|
||||||
# pushimage
|
# pushimage
|
||||||
PUSHSCRIPTPATH=$(MAKEPATH)
|
PUSHSCRIPTPATH=$(MAKEPATH)
|
||||||
PUSHSCRIPTNAME=pushimage.sh
|
PUSHSCRIPTNAME=pushimage.sh
|
||||||
REGISTRYUSER=user
|
REGISTRYUSER=user
|
||||||
REGISTRYPASSWORD=default
|
REGISTRYPASSWORD=default
|
||||||
|
|
||||||
version:
|
version:
|
||||||
@if [ "$(DEVFLAG)" = "false" ] ; then \
|
@printf $(VERSIONTAG) > $(VERSIONFILEPATH)/$(VERSIONFILENAME);
|
||||||
$(SEDCMD) -i 's/version=\"{{.Version}}\"/version=\"$(VERSIONTAG)\"/' -i $(VERSIONFILEPATH)/$(VERSIONFILENAME) ; \
|
|
||||||
fi
|
check_environment:
|
||||||
|
@$(MAKEPATH)/$(CHECKENVCMD)
|
||||||
check_environment:
|
|
||||||
@$(MAKEPATH)/$(CHECKENVCMD)
|
compile_adminserver:
|
||||||
|
@echo "compiling binary for adminserver..."
|
||||||
compile_adminserver:
|
@$(GOBUILD) -o $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) $(ADMINSERVERSOURCECODE)
|
||||||
@echo "compiling binary for adminserver..."
|
@echo "Done."
|
||||||
@$(GOBUILD) -o $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) $(ADMINSERVERSOURCECODE)
|
|
||||||
@echo "Done."
|
compile_ui:
|
||||||
|
@echo "compiling binary for ui..."
|
||||||
compile_ui:
|
@$(GOBUILD) -o $(UIBINARYPATH)/$(UIBINARYNAME) $(UISOURCECODE)
|
||||||
@echo "compiling binary for ui..."
|
@echo "Done."
|
||||||
@$(GOBUILD) -o $(UIBINARYPATH)/$(UIBINARYNAME) $(UISOURCECODE)
|
|
||||||
@echo "Done."
|
compile_jobservice:
|
||||||
|
@echo "compiling binary for jobservice..."
|
||||||
compile_jobservice:
|
@$(GOBUILD) -o $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) $(JOBSERVICESOURCECODE)
|
||||||
@echo "compiling binary for jobservice..."
|
@echo "Done."
|
||||||
@$(GOBUILD) -o $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) $(JOBSERVICESOURCECODE)
|
|
||||||
@echo "Done."
|
compile_clarity:
|
||||||
|
@echo "compiling binary for clarity ui..."
|
||||||
compile_clarity:
|
@if [ "$(HTTPPROXY)" != "" ] ; then \
|
||||||
@echo "compiling binary for clarity ui..."
|
$(DOCKERCMD) run --rm -v $(UIPATH)/static:$(CLARITYSEEDPATH)/dist -v $(UINGPATH)/src:$(CLARITYSEEDPATH)/src $(CLARITYIMAGE) $(SHELL) $(CLARITYBUILDSCRIPT) -p $(HTTPPROXY); \
|
||||||
@if [ "$(HTTPPROXY)" != "" ] ; then \
|
else \
|
||||||
$(DOCKERCMD) run --rm -v $(UIPATH)/static:$(CLARITYSEEDPATH)/dist -v $(UINGPATH)/src:$(CLARITYSEEDPATH)/src $(CLARITYIMAGE) $(SHELL) $(CLARITYBUILDSCRIPT) -p $(HTTPPROXY); \
|
$(DOCKERCMD) run --rm -v $(UIPATH)/static:$(CLARITYSEEDPATH)/dist -v $(UINGPATH)/src:$(CLARITYSEEDPATH)/src $(CLARITYIMAGE) $(SHELL) $(CLARITYBUILDSCRIPT); \
|
||||||
else \
|
fi
|
||||||
$(DOCKERCMD) run --rm -v $(UIPATH)/static:$(CLARITYSEEDPATH)/dist -v $(UINGPATH)/src:$(CLARITYSEEDPATH)/src $(CLARITYIMAGE) $(SHELL) $(CLARITYBUILDSCRIPT); \
|
@echo "Done."
|
||||||
fi
|
|
||||||
@echo "Done."
|
compile_normal: compile_clarity compile_adminserver compile_ui compile_jobservice
|
||||||
|
|
||||||
compile_normal: compile_clarity compile_adminserver compile_ui compile_jobservice
|
compile_golangimage: compile_clarity
|
||||||
|
@echo "compiling binary for adminserver (golang image)..."
|
||||||
compile_golangimage: compile_clarity
|
@echo $(GOBASEPATH)
|
||||||
@echo "compiling binary for adminserver (golang image)..."
|
@echo $(GOBUILDPATH)
|
||||||
@echo $(GOBASEPATH)
|
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_ADMINSERVER) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_ADMINSERVER)/$(ADMINSERVERBINARYNAME)
|
||||||
@echo $(GOBUILDPATH)
|
@echo "Done."
|
||||||
@$(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 "compiling binary for ui (golang image)..."
|
@echo $(GOBUILDPATH)
|
||||||
@echo $(GOBASEPATH)
|
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_UI) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_UI)/$(UIBINARYNAME)
|
||||||
@echo $(GOBUILDPATH)
|
@echo "Done."
|
||||||
@$(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 "compiling binary for jobservice (golang image)..."
|
@echo "Done."
|
||||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_JOBSERVICE) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_JOBSERVICE)/$(JOBSERVICEBINARYNAME)
|
|
||||||
@echo "Done."
|
compile:check_environment $(COMPILETAG)
|
||||||
|
|
||||||
compile:check_environment $(COMPILETAG)
|
prepare:
|
||||||
|
@echo "preparing..."
|
||||||
prepare:
|
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||||
@echo "preparing..."
|
$(MAKEPATH)/$(PREPARECMD) --conf $(CONFIGPATH)/$(CONFIGFILE) --with-notary; \
|
||||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
else \
|
||||||
$(MAKEPATH)/$(PREPARECMD) --conf $(CONFIGPATH)/$(CONFIGFILE) --with-notary; \
|
$(MAKEPATH)/$(PREPARECMD) --conf $(CONFIGPATH)/$(CONFIGFILE) ; \
|
||||||
else \
|
fi
|
||||||
$(MAKEPATH)/$(PREPARECMD) --conf $(CONFIGPATH)/$(CONFIGFILE) ; \
|
|
||||||
fi
|
build_common: version
|
||||||
|
@echo "buildging db container for photon..."
|
||||||
build_common: version
|
@cd $(DOCKERFILEPATH_DB) && $(DOCKERBUILD) -f $(DOCKERFILENAME_DB) -t $(DOCKERIMAGENAME_DB):$(VERSIONTAG) .
|
||||||
@echo "buildging db container for photon..."
|
@echo "Done."
|
||||||
@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_photon: build_common
|
|
||||||
make -f $(MAKEFILEPATH_PHOTON)/Makefile build -e DEVFLAG=$(DEVFLAG)
|
build: build_$(BASEIMAGE)
|
||||||
|
|
||||||
build: build_$(BASEIMAGE)
|
modify_composefile:
|
||||||
|
@echo "preparing docker-compose file..."
|
||||||
modify_composefile:
|
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSETPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||||
@echo "preparing docker-compose file..."
|
@$(SEDCMD) -i 's/__version__/$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||||
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSETPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
|
||||||
@$(SEDCMD) -i 's/__version__/$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
install: compile build prepare modify_composefile start
|
||||||
|
|
||||||
install: compile build prepare modify_composefile start
|
package_online: modify_composefile
|
||||||
|
@echo "packing online package ..."
|
||||||
package_online: modify_composefile
|
@cp -r make $(HARBORPKG)
|
||||||
@echo "packing online package ..."
|
@if [ -n "$(REGISTRYSERVER)" ] ; then \
|
||||||
@cp -r make $(HARBORPKG)
|
$(SEDCMD) -i 's/image\: vmware/image\: $(REGISTRYSERVER)\/$(REGISTRYPROJECTNAME)/' \
|
||||||
@if [ -n "$(REGISTRYSERVER)" ] ; then \
|
$(HARBORPKG)/docker-compose.yml ; \
|
||||||
$(SEDCMD) -i 's/image\: vmware/image\: $(REGISTRYSERVER)\/$(REGISTRYPROJECTNAME)/' \
|
fi
|
||||||
$(HARBORPKG)/docker-compose.yml ; \
|
@cp LICENSE $(HARBORPKG)/LICENSE
|
||||||
fi
|
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||||
@cp LICENSE $(HARBORPKG)/LICENSE
|
|
||||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||||
|
$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
||||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
||||||
$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
||||||
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME); \
|
||||||
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
else \
|
||||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME); \
|
$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
||||||
else \
|
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
||||||
$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
||||||
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
$(HARBORPKG)/harbor.cfg ; \
|
||||||
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
fi
|
||||||
$(HARBORPKG)/harbor.cfg ; \
|
|
||||||
fi
|
@rm -rf $(HARBORPKG)
|
||||||
|
@echo "Done."
|
||||||
@rm -rf $(HARBORPKG)
|
|
||||||
@echo "Done."
|
package_offline: compile build modify_composefile
|
||||||
|
@echo "packing offline package ..."
|
||||||
package_offline: compile build modify_composefile
|
@cp -r make $(HARBORPKG)
|
||||||
@echo "packing offline package ..."
|
|
||||||
@cp -r make $(HARBORPKG)
|
@cp LICENSE $(HARBORPKG)/LICENSE
|
||||||
|
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||||
@cp LICENSE $(HARBORPKG)/LICENSE
|
|
||||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
@echo "pulling nginx and registry..."
|
||||||
|
@$(DOCKERPULL) registry:$(REGISTRYVERSION)
|
||||||
@echo "pulling nginx and registry..."
|
@$(DOCKERPULL) nginx:$(NGINXVERSION)
|
||||||
@$(DOCKERPULL) registry:$(REGISTRYVERSION)
|
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||||
@$(DOCKERPULL) nginx:$(NGINXVERSION)
|
echo "pulling notary and harbor-notary-db..."; \
|
||||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
$(DOCKERPULL) vmware/notary-photon:$(NOTARYVERSION); \
|
||||||
echo "pulling notary and mariadb..."; \
|
$(DOCKERPULL) vmware/notary-photon:$(NOTARYSIGNERVERSION); \
|
||||||
$(DOCKERPULL) vmware/notary-photon:$(NOTARYVERSION); \
|
$(DOCKERPULL) vmware/harbor-notary-db:$(MARIADBVERSION); \
|
||||||
$(DOCKERPULL) vmware/notary-photon:$(NOTARYSIGNERVERSION); \
|
fi
|
||||||
$(DOCKERPULL) mariadb:$(MARIADBVERSION); \
|
|
||||||
fi
|
@echo "saving harbor docker image"
|
||||||
|
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||||
@echo "saving harbor docker image"
|
$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||||
$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
nginx:$(NGINXVERSION) vmware/registry:$(REGISTRYVERSION) photon:$(PHOTONVERSION) \
|
||||||
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
vmware/notary-photon:$(NOTARYVERSION) vmware/notary-photon:$(NOTARYSIGNERVERSION) vmware/harbor-notary-db:$(MARIADBVERSION); \
|
||||||
nginx:$(NGINXVERSION) vmware/registry:$(REGISTRYVERSION) photon:$(PHOTONVERSION) \
|
else \
|
||||||
vmware/notary-photon:$(NOTARYVERSION) vmware/notary-photon:$(NOTARYSIGNERVERSION) mariadb:$(MARIADBVERSION); \
|
$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||||
else \
|
$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||||
$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
nginx:$(NGINXVERSION) vmware/registry:$(REGISTRYVERSION) photon:$(PHOTONVERSION) ; \
|
||||||
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
fi
|
||||||
nginx:$(NGINXVERSION) registry:$(REGISTRYVERSION) photon:$(PHOTONVERSION) ; \
|
|
||||||
fi
|
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||||
|
$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
||||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||||
$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
||||||
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
||||||
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
$(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME) ; \
|
||||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
else \
|
||||||
$(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME) ; \
|
$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
||||||
else \
|
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||||
$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
||||||
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
||||||
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) ; \
|
||||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
fi
|
||||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) ; \
|
|
||||||
fi
|
@rm -rf $(HARBORPKG)
|
||||||
|
@echo "Done."
|
||||||
@rm -rf $(HARBORPKG)
|
|
||||||
@echo "Done."
|
pushimage:
|
||||||
|
@echo "pushing harbor images ..."
|
||||||
pushimage:
|
@$(DOCKERTAG) $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||||
@echo "pushing harbor images ..."
|
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
@$(DOCKERRMIMAGE) $(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) \
|
||||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_UI):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
@$(DOCKERRMIMAGE) $(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) \
|
||||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
@$(DOCKERRMIMAGE) $(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) \
|
||||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
@$(DOCKERRMIMAGE) $(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) \
|
||||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_DB):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
||||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
|
||||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
start:
|
||||||
|
@echo "loading harbor images..."
|
||||||
start:
|
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||||
@echo "loading harbor images..."
|
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) up -d ; \
|
||||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
else \
|
||||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) up -d ; \
|
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) up -d ; \
|
||||||
else \
|
fi
|
||||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) up -d ; \
|
@echo "Start complete. You can visit harbor now."
|
||||||
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."
|
||||||
down:
|
@while [ -z "$$CONTINUE" ]; do \
|
||||||
@echo "Please make sure to set -e NOTARYFLAG=true if you are using Notary in Harbor, otherwise the Notary containers cannot be stop automaticlly."
|
read -r -p "Type anything but Y or y to exit. [Y/N]: " CONTINUE; \
|
||||||
@while [ -z "$$CONTINUE" ]; do \
|
done ; \
|
||||||
read -r -p "Type anything but Y or y to exit. [Y/N]: " CONTINUE; \
|
[ $$CONTINUE = "y" ] || [ $$CONTINUE = "Y" ] || (echo "Exiting."; exit 1;)
|
||||||
done ; \
|
@echo "stoping harbor instance..."
|
||||||
[ $$CONTINUE = "y" ] || [ $$CONTINUE = "Y" ] || (echo "Exiting."; exit 1;)
|
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||||
@echo "stoping harbor instance..."
|
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) down ; \
|
||||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
else \
|
||||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) down ; \
|
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) down ; \
|
||||||
else \
|
fi
|
||||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) down ; \
|
@echo "Done."
|
||||||
fi
|
|
||||||
@echo "Done."
|
cleanbinary:
|
||||||
|
@echo "cleaning binary..."
|
||||||
cleanbinary:
|
@if [ -f $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ] ; then rm $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ; fi
|
||||||
@echo "cleaning binary..."
|
@if [ -f $(UIBINARYPATH)/$(UIBINARYNAME) ] ; then rm $(UIBINARYPATH)/$(UIBINARYNAME) ; fi
|
||||||
@if [ -f $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ] ; then rm $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ; fi
|
@if [ -f $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ] ; then rm $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ; 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..."
|
||||||
cleanimage:
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||||
@echo "cleaning image for photon..."
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
# - $(DOCKERRMIMAGE) -f registry:$(REGISTRYVERSION)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
# - $(DOCKERRMIMAGE) -f nginx:1.11.5
|
||||||
# - $(DOCKERRMIMAGE) -f registry:$(REGISTRYVERSION)
|
|
||||||
# - $(DOCKERRMIMAGE) -f nginx:1.11.5
|
cleandockercomposefile:
|
||||||
|
@echo "cleaning $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml"
|
||||||
cleandockercomposefile:
|
@if [ -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ] ; then rm $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ; fi
|
||||||
@echo "cleaning $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml"
|
|
||||||
@if [ -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ] ; then rm $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ; fi
|
cleanversiontag:
|
||||||
|
@echo "cleaning version TAG"
|
||||||
cleanversiontag:
|
@rm -rf $(VERSIONFILEPATH)/$(VERSIONFILENAME)
|
||||||
@echo "cleaning version TAG"
|
|
||||||
@$(SEDCMD) -i 's/version=\"$(VERSIONTAG)\"/version=\"{{.Version}}\"/' -i $(VERSIONFILEPATH)/$(VERSIONFILENAME)
|
cleanpackage:
|
||||||
|
@echo "cleaning harbor install package"
|
||||||
cleanpackage:
|
@if [ -d $(BUILDPATH)/harbor ] ; then rm -rf $(BUILDPATH)/harbor ; fi
|
||||||
@echo "cleaning harbor install package"
|
@if [ -f $(BUILDPATH)/harbor-online-installer-$(VERSIONTAG).tgz ] ; \
|
||||||
@if [ -d $(BUILDPATH)/harbor ] ; then rm -rf $(BUILDPATH)/harbor ; fi
|
then rm $(BUILDPATH)/harbor-online-installer-$(VERSIONTAG).tgz ; fi
|
||||||
@if [ -f $(BUILDPATH)/harbor-online-installer-$(VERSIONTAG).tgz ] ; \
|
@if [ -f $(BUILDPATH)/harbor-offline-installer-$(VERSIONTAG).tgz ] ; \
|
||||||
then rm $(BUILDPATH)/harbor-online-installer-$(VERSIONTAG).tgz ; fi
|
then rm $(BUILDPATH)/harbor-offline-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
|
||||||
.PHONY: cleanall
|
|
||||||
cleanall: cleanbinary cleanimage cleandockercomposefile cleanversiontag cleanpackage
|
clean:
|
||||||
|
@echo " make cleanall: remove binary, Harbor images, specific version docker-compose"
|
||||||
clean:
|
@echo " file, specific version tag, online and offline install package"
|
||||||
@echo " make cleanall: remove binary, Harbor images, specific version docker-compose"
|
@echo " make cleanbinary: remove ui and jobservice binary"
|
||||||
@echo " file, specific version tag, online and offline install package"
|
@echo " make cleanimage: remove Harbor images"
|
||||||
@echo " make cleanbinary: remove ui and jobservice binary"
|
@echo " make cleandockercomposefile: remove specific version docker-compose"
|
||||||
@echo " make cleanimage: remove Harbor images"
|
@echo " make cleanversiontag: cleanpackageremove specific version tag"
|
||||||
@echo " make cleandockercomposefile: remove specific version docker-compose"
|
@echo " make cleanpackage: remove online and offline install package"
|
||||||
@echo " make cleanversiontag: cleanpackageremove specific version tag"
|
|
||||||
@echo " make cleanpackage: remove online and offline install package"
|
all: install
|
||||||
|
|
||||||
all: install
|
|
||||||
|
|
|
@ -2028,6 +2028,9 @@ definitions:
|
||||||
has_ca_root:
|
has_ca_root:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Indicate whether there is a ca root cert file ready for download in the file system.
|
description: Indicate whether there is a ca root cert file ready for download in the file system.
|
||||||
|
harbor_version:
|
||||||
|
type: string
|
||||||
|
description: The build version of Harbor.
|
||||||
SystemInfo:
|
SystemInfo:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -50,6 +50,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- /data/config/:/etc/adminserver/
|
- /data/config/:/etc/adminserver/
|
||||||
- /data/secretkey:/etc/adminserver/key
|
- /data/secretkey:/etc/adminserver/key
|
||||||
|
- /data/:/data/
|
||||||
depends_on:
|
depends_on:
|
||||||
- log
|
- log
|
||||||
logging:
|
logging:
|
||||||
|
|
|
@ -56,6 +56,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- /data/config/:/etc/adminserver/
|
- /data/config/:/etc/adminserver/
|
||||||
- /data/secretkey:/etc/adminserver/key
|
- /data/secretkey:/etc/adminserver/key
|
||||||
|
- /data/:/data/
|
||||||
networks:
|
networks:
|
||||||
- harbor
|
- harbor
|
||||||
depends_on:
|
depends_on:
|
||||||
|
@ -74,7 +75,6 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./common/config/ui/app.conf:/etc/ui/app.conf
|
- ./common/config/ui/app.conf:/etc/ui/app.conf
|
||||||
- ./common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
- ./common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
||||||
- /data:/harbor_storage
|
|
||||||
- /data/secretkey:/etc/ui/key
|
- /data/secretkey:/etc/ui/key
|
||||||
networks:
|
networks:
|
||||||
- harbor
|
- harbor
|
||||||
|
|
|
@ -7,6 +7,7 @@ COPY ./make/dev/ui/harbor_ui /harbor/
|
||||||
COPY ./src/ui/views /harbor/views
|
COPY ./src/ui/views /harbor/views
|
||||||
COPY ./src/ui/static /harbor/static
|
COPY ./src/ui/static /harbor/static
|
||||||
COPY ./src/favicon.ico /harbor/favicon.ico
|
COPY ./src/favicon.ico /harbor/favicon.ico
|
||||||
|
COPY ./VERSION /harbor/VERSION
|
||||||
|
|
||||||
RUN chmod u+x /harbor/harbor_ui
|
RUN chmod u+x /harbor/harbor_ui
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/adminserver/systemcfg"
|
"github.com/vmware/harbor/src/adminserver/systemcfg"
|
||||||
comcfg "github.com/vmware/harbor/src/common/config"
|
"github.com/vmware/harbor/src/common"
|
||||||
"github.com/vmware/harbor/src/common/utils/test"
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ func TestConfigAPI(t *testing.T) {
|
||||||
|
|
||||||
secret := "secret"
|
secret := "secret"
|
||||||
envs := map[string]string{
|
envs := map[string]string{
|
||||||
"AUTH_MODE": comcfg.DBAuth,
|
"AUTH_MODE": common.DBAuth,
|
||||||
"JSON_CFG_STORE_PATH": configPath,
|
"JSON_CFG_STORE_PATH": configPath,
|
||||||
"KEY_PATH": secretKeyPath,
|
"KEY_PATH": secretKeyPath,
|
||||||
"UI_SECRET": secret,
|
"UI_SECRET": secret,
|
||||||
|
@ -97,7 +97,7 @@ func TestConfigAPI(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
scope := int(m[comcfg.LDAPScope].(float64))
|
scope := int(m[common.LDAPScope].(float64))
|
||||||
if scope != 3 {
|
if scope != 3 {
|
||||||
t.Errorf("unexpected ldap scope: %d != %d", scope, 3)
|
t.Errorf("unexpected ldap scope: %d != %d", scope, 3)
|
||||||
return
|
return
|
||||||
|
@ -105,7 +105,7 @@ func TestConfigAPI(t *testing.T) {
|
||||||
|
|
||||||
// modify configurations
|
// modify configurations
|
||||||
c := map[string]interface{}{
|
c := map[string]interface{}{
|
||||||
comcfg.AUTHMode: comcfg.LDAPAuth,
|
common.AUTHMode: common.LDAPAuth,
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(c)
|
b, err := json.Marshal(c)
|
||||||
|
@ -155,9 +155,9 @@ func TestConfigAPI(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mode := m[comcfg.AUTHMode].(string)
|
mode := m[common.AUTHMode].(string)
|
||||||
if mode != comcfg.LDAPAuth {
|
if mode != common.LDAPAuth {
|
||||||
t.Errorf("unexpected auth mode: %s != %s", mode, comcfg.LDAPAuth)
|
t.Errorf("unexpected auth mode: %s != %s", mode, common.LDAPAuth)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,9 +203,9 @@ func TestConfigAPI(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mode = m[comcfg.AUTHMode].(string)
|
mode = m[common.AUTHMode].(string)
|
||||||
if mode != comcfg.DBAuth {
|
if mode != common.DBAuth {
|
||||||
t.Errorf("unexpected auth mode: %s != %s", mode, comcfg.LDAPAuth)
|
t.Errorf("unexpected auth mode: %s != %s", mode, common.LDAPAuth)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Authorizer authorizes request
|
||||||
|
type Authorizer interface {
|
||||||
|
Authorize(*http.Request) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSecretAuthorizer returns an instance of secretAuthorizer
|
||||||
|
func NewSecretAuthorizer(cookieName, secret string) Authorizer {
|
||||||
|
return &secretAuthorizer{
|
||||||
|
cookieName: cookieName,
|
||||||
|
secret: secret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type secretAuthorizer struct {
|
||||||
|
cookieName string
|
||||||
|
secret string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *secretAuthorizer) Authorize(req *http.Request) error {
|
||||||
|
if req == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
req.AddCookie(&http.Cookie{
|
||||||
|
Name: s.cookieName,
|
||||||
|
Value: s.secret,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuthorize(t *testing.T) {
|
||||||
|
cookieName := "secret"
|
||||||
|
secret := "secret"
|
||||||
|
authorizer := NewSecretAuthorizer(cookieName, secret)
|
||||||
|
req, err := http.NewRequest("", "", nil)
|
||||||
|
if !assert.Nil(t, err, "unexpected error") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = authorizer.Authorize(req)
|
||||||
|
if !assert.Nil(t, err, "unexpected error") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie, err := req.Cookie(cookieName)
|
||||||
|
if !assert.Nil(t, err, "unexpected error") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, secret, cookie.Value, "unexpected cookie")
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/adminserver/client/auth"
|
||||||
|
"github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage"
|
||||||
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client defines methods that an Adminserver client should implement
|
||||||
|
type Client interface {
|
||||||
|
// Ping tests the connection with server
|
||||||
|
Ping() error
|
||||||
|
// GetCfgs returns system configurations
|
||||||
|
GetCfgs() (map[string]interface{}, error)
|
||||||
|
// UpdateCfgs updates system configurations
|
||||||
|
UpdateCfgs(map[string]interface{}) error
|
||||||
|
// ResetCfgs resets system configuratoins form environment variables
|
||||||
|
ResetCfgs() error
|
||||||
|
// Capacity returns the capacity of image storage
|
||||||
|
Capacity() (*imagestorage.Capacity, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient return an instance of Adminserver client
|
||||||
|
func NewClient(baseURL string, authorizer auth.Authorizer) Client {
|
||||||
|
baseURL = strings.TrimRight(baseURL, "/")
|
||||||
|
if !strings.Contains(baseURL, "://") {
|
||||||
|
baseURL = "http://" + baseURL
|
||||||
|
}
|
||||||
|
return &client{
|
||||||
|
baseURL: baseURL,
|
||||||
|
client: &http.Client{},
|
||||||
|
authorizer: authorizer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
baseURL string
|
||||||
|
client *http.Client
|
||||||
|
authorizer auth.Authorizer
|
||||||
|
}
|
||||||
|
|
||||||
|
// do creates request and authorizes it if authorizer is not nil
|
||||||
|
func (c *client) do(method, relativePath string, body io.Reader) (*http.Response, error) {
|
||||||
|
url := c.baseURL + relativePath
|
||||||
|
req, err := http.NewRequest(method, url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.authorizer != nil {
|
||||||
|
if err := c.authorizer.Authorize(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.client.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Ping() error {
|
||||||
|
addr := strings.Split(c.baseURL, "://")[1]
|
||||||
|
if !strings.Contains(addr, ":") {
|
||||||
|
addr = addr + ":80"
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.TestTCPConn(addr, 60, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCfgs ...
|
||||||
|
func (c *client) GetCfgs() (map[string]interface{}, error) {
|
||||||
|
resp, err := c.do(http.MethodGet, "/api/configurations", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("failed to get configurations: %d %s",
|
||||||
|
resp.StatusCode, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgs := map[string]interface{}{}
|
||||||
|
if err = json.Unmarshal(b, &cfgs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCfgs ...
|
||||||
|
func (c *client) UpdateCfgs(cfgs map[string]interface{}) error {
|
||||||
|
data, err := json.Marshal(cfgs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.do(http.MethodPut, "/api/configurations", bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to update configurations: %d %s",
|
||||||
|
resp.StatusCode, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetCfgs ...
|
||||||
|
func (c *client) ResetCfgs() error {
|
||||||
|
resp, err := c.do(http.MethodPost, "/api/configurations/reset", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to reset configurations: %d %s",
|
||||||
|
resp.StatusCode, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capacity ...
|
||||||
|
func (c *client) Capacity() (*imagestorage.Capacity, error) {
|
||||||
|
resp, err := c.do(http.MethodGet, "/api/systeminfo/capacity", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("failed to get capacity: %d %s",
|
||||||
|
resp.StatusCode, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
capacity := &imagestorage.Capacity{}
|
||||||
|
if err = json.Unmarshal(b, capacity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return capacity, nil
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var c Client
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
|
||||||
|
server, err := test.NewAdminserver(nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to create adminserver: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
c = NewClient(server.URL, nil)
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPing(t *testing.T) {
|
||||||
|
err := c.Ping()
|
||||||
|
assert.Nil(t, err, "unexpected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCfgs(t *testing.T) {
|
||||||
|
cfgs, err := c.GetCfgs()
|
||||||
|
if !assert.Nil(t, err, "unexpected error") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, common.DBAuth, cfgs[common.AUTHMode], "unexpected configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateCfgs(t *testing.T) {
|
||||||
|
cfgs := map[string]interface{}{
|
||||||
|
common.AUTHMode: common.LDAPAuth,
|
||||||
|
}
|
||||||
|
err := c.UpdateCfgs(cfgs)
|
||||||
|
if !assert.Nil(t, err, "unexpected error") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResetCfgs(t *testing.T) {
|
||||||
|
err := c.ResetCfgs()
|
||||||
|
if !assert.Nil(t, err, "unexpected error") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCapacity(t *testing.T) {
|
||||||
|
capacity, err := c.Capacity()
|
||||||
|
if !assert.Nil(t, err, "unexpected error") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, uint64(100), capacity.Total)
|
||||||
|
assert.Equal(t, uint64(90), capacity.Free)
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import (
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/adminserver/systemcfg/store"
|
"github.com/vmware/harbor/src/adminserver/systemcfg/store"
|
||||||
"github.com/vmware/harbor/src/adminserver/systemcfg/store/json"
|
"github.com/vmware/harbor/src/adminserver/systemcfg/store/json"
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
comcfg "github.com/vmware/harbor/src/common/config"
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
@ -40,82 +41,82 @@ var (
|
||||||
|
|
||||||
// attrs need to be encrypted or decrypted
|
// attrs need to be encrypted or decrypted
|
||||||
attrs = []string{
|
attrs = []string{
|
||||||
comcfg.EmailPassword,
|
common.EmailPassword,
|
||||||
comcfg.LDAPSearchPwd,
|
common.LDAPSearchPwd,
|
||||||
comcfg.MySQLPassword,
|
common.MySQLPassword,
|
||||||
comcfg.AdminInitialPassword,
|
common.AdminInitialPassword,
|
||||||
}
|
}
|
||||||
|
|
||||||
// all configurations need read from environment variables
|
// all configurations need read from environment variables
|
||||||
allEnvs = map[string]interface{}{
|
allEnvs = map[string]interface{}{
|
||||||
comcfg.ExtEndpoint: "EXT_ENDPOINT",
|
common.ExtEndpoint: "EXT_ENDPOINT",
|
||||||
comcfg.AUTHMode: "AUTH_MODE",
|
common.AUTHMode: "AUTH_MODE",
|
||||||
comcfg.SelfRegistration: &parser{
|
common.SelfRegistration: &parser{
|
||||||
env: "SELF_REGISTRATION",
|
env: "SELF_REGISTRATION",
|
||||||
parse: parseStringToBool,
|
parse: parseStringToBool,
|
||||||
},
|
},
|
||||||
comcfg.DatabaseType: "DATABASE_TYPE",
|
common.DatabaseType: "DATABASE_TYPE",
|
||||||
comcfg.MySQLHost: "MYSQL_HOST",
|
common.MySQLHost: "MYSQL_HOST",
|
||||||
comcfg.MySQLPort: &parser{
|
common.MySQLPort: &parser{
|
||||||
env: "MYSQL_PORT",
|
env: "MYSQL_PORT",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
},
|
},
|
||||||
comcfg.MySQLUsername: "MYSQL_USR",
|
common.MySQLUsername: "MYSQL_USR",
|
||||||
comcfg.MySQLPassword: "MYSQL_PWD",
|
common.MySQLPassword: "MYSQL_PWD",
|
||||||
comcfg.MySQLDatabase: "MYSQL_DATABASE",
|
common.MySQLDatabase: "MYSQL_DATABASE",
|
||||||
comcfg.SQLiteFile: "SQLITE_FILE",
|
common.SQLiteFile: "SQLITE_FILE",
|
||||||
comcfg.LDAPURL: "LDAP_URL",
|
common.LDAPURL: "LDAP_URL",
|
||||||
comcfg.LDAPSearchDN: "LDAP_SEARCH_DN",
|
common.LDAPSearchDN: "LDAP_SEARCH_DN",
|
||||||
comcfg.LDAPSearchPwd: "LDAP_SEARCH_PWD",
|
common.LDAPSearchPwd: "LDAP_SEARCH_PWD",
|
||||||
comcfg.LDAPBaseDN: "LDAP_BASE_DN",
|
common.LDAPBaseDN: "LDAP_BASE_DN",
|
||||||
comcfg.LDAPFilter: "LDAP_FILTER",
|
common.LDAPFilter: "LDAP_FILTER",
|
||||||
comcfg.LDAPUID: "LDAP_UID",
|
common.LDAPUID: "LDAP_UID",
|
||||||
comcfg.LDAPScope: &parser{
|
common.LDAPScope: &parser{
|
||||||
env: "LDAP_SCOPE",
|
env: "LDAP_SCOPE",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
},
|
},
|
||||||
comcfg.LDAPTimeout: &parser{
|
common.LDAPTimeout: &parser{
|
||||||
env: "LDAP_TIMEOUT",
|
env: "LDAP_TIMEOUT",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
},
|
},
|
||||||
comcfg.EmailHost: "EMAIL_HOST",
|
common.EmailHost: "EMAIL_HOST",
|
||||||
comcfg.EmailPort: &parser{
|
common.EmailPort: &parser{
|
||||||
env: "EMAIL_PORT",
|
env: "EMAIL_PORT",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
},
|
},
|
||||||
comcfg.EmailUsername: "EMAIL_USR",
|
common.EmailUsername: "EMAIL_USR",
|
||||||
comcfg.EmailPassword: "EMAIL_PWD",
|
common.EmailPassword: "EMAIL_PWD",
|
||||||
comcfg.EmailSSL: &parser{
|
common.EmailSSL: &parser{
|
||||||
env: "EMAIL_SSL",
|
env: "EMAIL_SSL",
|
||||||
parse: parseStringToBool,
|
parse: parseStringToBool,
|
||||||
},
|
},
|
||||||
comcfg.EmailFrom: "EMAIL_FROM",
|
common.EmailFrom: "EMAIL_FROM",
|
||||||
comcfg.EmailIdentity: "EMAIL_IDENTITY",
|
common.EmailIdentity: "EMAIL_IDENTITY",
|
||||||
comcfg.RegistryURL: "REGISTRY_URL",
|
common.RegistryURL: "REGISTRY_URL",
|
||||||
comcfg.TokenExpiration: &parser{
|
common.TokenExpiration: &parser{
|
||||||
env: "TOKEN_EXPIRATION",
|
env: "TOKEN_EXPIRATION",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
},
|
},
|
||||||
comcfg.UseCompressedJS: &parser{
|
common.UseCompressedJS: &parser{
|
||||||
env: "USE_COMPRESSED_JS",
|
env: "USE_COMPRESSED_JS",
|
||||||
parse: parseStringToBool,
|
parse: parseStringToBool,
|
||||||
},
|
},
|
||||||
comcfg.CfgExpiration: &parser{
|
common.CfgExpiration: &parser{
|
||||||
env: "CFG_EXPIRATION",
|
env: "CFG_EXPIRATION",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
},
|
},
|
||||||
comcfg.MaxJobWorkers: &parser{
|
common.MaxJobWorkers: &parser{
|
||||||
env: "MAX_JOB_WORKERS",
|
env: "MAX_JOB_WORKERS",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
},
|
},
|
||||||
comcfg.VerifyRemoteCert: &parser{
|
common.VerifyRemoteCert: &parser{
|
||||||
env: "VERIFY_REMOTE_CERT",
|
env: "VERIFY_REMOTE_CERT",
|
||||||
parse: parseStringToBool,
|
parse: parseStringToBool,
|
||||||
},
|
},
|
||||||
comcfg.ProjectCreationRestriction: "PROJECT_CREATION_RESTRICTION",
|
common.ProjectCreationRestriction: "PROJECT_CREATION_RESTRICTION",
|
||||||
comcfg.AdminInitialPassword: "HARBOR_ADMIN_PASSWORD",
|
common.AdminInitialPassword: "HARBOR_ADMIN_PASSWORD",
|
||||||
comcfg.AdmiralEndpoint: "ADMIRAL_URL",
|
common.AdmiralEndpoint: "ADMIRAL_URL",
|
||||||
comcfg.WithNotary: &parser{
|
common.WithNotary: &parser{
|
||||||
env: "WITH_NOTARY",
|
env: "WITH_NOTARY",
|
||||||
parse: parseStringToBool,
|
parse: parseStringToBool,
|
||||||
},
|
},
|
||||||
|
@ -124,23 +125,23 @@ var (
|
||||||
// configurations need read from environment variables
|
// configurations need read from environment variables
|
||||||
// every time the system startup
|
// every time the system startup
|
||||||
repeatLoadEnvs = map[string]interface{}{
|
repeatLoadEnvs = map[string]interface{}{
|
||||||
comcfg.ExtEndpoint: "EXT_ENDPOINT",
|
common.ExtEndpoint: "EXT_ENDPOINT",
|
||||||
comcfg.MySQLPassword: "MYSQL_PWD",
|
common.MySQLPassword: "MYSQL_PWD",
|
||||||
comcfg.MaxJobWorkers: &parser{
|
common.MaxJobWorkers: &parser{
|
||||||
env: "MAX_JOB_WORKERS",
|
env: "MAX_JOB_WORKERS",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
},
|
},
|
||||||
// TODO remove this config?
|
// TODO remove this config?
|
||||||
comcfg.UseCompressedJS: &parser{
|
common.UseCompressedJS: &parser{
|
||||||
env: "USE_COMPRESSED_JS",
|
env: "USE_COMPRESSED_JS",
|
||||||
parse: parseStringToBool,
|
parse: parseStringToBool,
|
||||||
},
|
},
|
||||||
comcfg.CfgExpiration: &parser{
|
common.CfgExpiration: &parser{
|
||||||
env: "CFG_EXPIRATION",
|
env: "CFG_EXPIRATION",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
},
|
},
|
||||||
comcfg.AdmiralEndpoint: "ADMIRAL_URL",
|
common.AdmiralEndpoint: "ADMIRAL_URL",
|
||||||
comcfg.WithNotary: &parser{
|
common.WithNotary: &parser{
|
||||||
env: "WITH_NOTARY",
|
env: "WITH_NOTARY",
|
||||||
parse: parseStringToBool,
|
parse: parseStringToBool,
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
comcfg "github.com/vmware/harbor/src/common/config"
|
"github.com/vmware/harbor/src/common"
|
||||||
"github.com/vmware/harbor/src/common/utils/test"
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ func TestSystemcfg(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
m := map[string]string{
|
m := map[string]string{
|
||||||
"AUTH_MODE": comcfg.DBAuth,
|
"AUTH_MODE": common.DBAuth,
|
||||||
"LDAP_SCOPE": "1",
|
"LDAP_SCOPE": "1",
|
||||||
"LDAP_TIMEOUT": "30",
|
"LDAP_TIMEOUT": "30",
|
||||||
"MYSQL_PORT": "3306",
|
"MYSQL_PORT": "3306",
|
||||||
|
@ -93,13 +93,13 @@ func TestSystemcfg(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg[comcfg.AUTHMode] != comcfg.DBAuth {
|
if cfg[common.AUTHMode] != common.DBAuth {
|
||||||
t.Errorf("unexpected auth mode: %s != %s",
|
t.Errorf("unexpected auth mode: %s != %s",
|
||||||
cfg[comcfg.AUTHMode], comcfg.DBAuth)
|
cfg[common.AUTHMode], common.DBAuth)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg[comcfg.AUTHMode] = comcfg.LDAPAuth
|
cfg[common.AUTHMode] = common.LDAPAuth
|
||||||
|
|
||||||
if err = UpdateSystemCfg(cfg); err != nil {
|
if err = UpdateSystemCfg(cfg); err != nil {
|
||||||
t.Errorf("failed to update system configurations: %v", err)
|
t.Errorf("failed to update system configurations: %v", err)
|
||||||
|
@ -112,9 +112,9 @@ func TestSystemcfg(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg[comcfg.AUTHMode] != comcfg.LDAPAuth {
|
if cfg[common.AUTHMode] != common.LDAPAuth {
|
||||||
t.Errorf("unexpected auth mode: %s != %s",
|
t.Errorf("unexpected auth mode: %s != %s",
|
||||||
cfg[comcfg.AUTHMode], comcfg.DBAuth)
|
cfg[common.AUTHMode], common.DBAuth)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,9 +129,9 @@ func TestSystemcfg(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg[comcfg.AUTHMode] != comcfg.DBAuth {
|
if cfg[common.AUTHMode] != common.DBAuth {
|
||||||
t.Errorf("unexpected auth mode: %s != %s",
|
t.Errorf("unexpected auth mode: %s != %s",
|
||||||
cfg[comcfg.AUTHMode], comcfg.DBAuth)
|
cfg[common.AUTHMode], common.DBAuth)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,83 +17,26 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/cache"
|
"github.com/astaxie/beego/cache"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/adminserver/client"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common"
|
||||||
)
|
|
||||||
|
|
||||||
// const variables
|
|
||||||
const (
|
|
||||||
DBAuth = "db_auth"
|
|
||||||
LDAPAuth = "ldap_auth"
|
|
||||||
ProCrtRestrEveryone = "everyone"
|
|
||||||
ProCrtRestrAdmOnly = "adminonly"
|
|
||||||
LDAPScopeBase = "1"
|
|
||||||
LDAPScopeOnelevel = "2"
|
|
||||||
LDAPScopeSubtree = "3"
|
|
||||||
|
|
||||||
ExtEndpoint = "ext_endpoint"
|
|
||||||
AUTHMode = "auth_mode"
|
|
||||||
DatabaseType = "database_type"
|
|
||||||
MySQLHost = "mysql_host"
|
|
||||||
MySQLPort = "mysql_port"
|
|
||||||
MySQLUsername = "mysql_username"
|
|
||||||
MySQLPassword = "mysql_password"
|
|
||||||
MySQLDatabase = "mysql_database"
|
|
||||||
SQLiteFile = "sqlite_file"
|
|
||||||
SelfRegistration = "self_registration"
|
|
||||||
LDAPURL = "ldap_url"
|
|
||||||
LDAPSearchDN = "ldap_search_dn"
|
|
||||||
LDAPSearchPwd = "ldap_search_password"
|
|
||||||
LDAPBaseDN = "ldap_base_dn"
|
|
||||||
LDAPUID = "ldap_uid"
|
|
||||||
LDAPFilter = "ldap_filter"
|
|
||||||
LDAPScope = "ldap_scope"
|
|
||||||
LDAPTimeout = "ldap_timeout"
|
|
||||||
TokenServiceURL = "token_service_url"
|
|
||||||
RegistryURL = "registry_url"
|
|
||||||
EmailHost = "email_host"
|
|
||||||
EmailPort = "email_port"
|
|
||||||
EmailUsername = "email_username"
|
|
||||||
EmailPassword = "email_password"
|
|
||||||
EmailFrom = "email_from"
|
|
||||||
EmailSSL = "email_ssl"
|
|
||||||
EmailIdentity = "email_identity"
|
|
||||||
ProjectCreationRestriction = "project_creation_restriction"
|
|
||||||
VerifyRemoteCert = "verify_remote_cert"
|
|
||||||
MaxJobWorkers = "max_job_workers"
|
|
||||||
TokenExpiration = "token_expiration"
|
|
||||||
CfgExpiration = "cfg_expiration"
|
|
||||||
JobLogDir = "job_log_dir"
|
|
||||||
UseCompressedJS = "use_compressed_js"
|
|
||||||
AdminInitialPassword = "admin_initial_password"
|
|
||||||
AdmiralEndpoint = "admiral_url"
|
|
||||||
WithNotary = "with_notary"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manager manages configurations
|
// Manager manages configurations
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
Loader *Loader
|
client client.Client
|
||||||
Parser *Parser
|
|
||||||
Cache bool
|
Cache bool
|
||||||
cache cache.Cache
|
cache cache.Cache
|
||||||
key string
|
key string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager returns an instance of Manager
|
// NewManager returns an instance of Manager
|
||||||
// url: the url from which loader loads configurations
|
func NewManager(client client.Client, enableCache bool) *Manager {
|
||||||
func NewManager(url, secret string, enableCache bool) *Manager {
|
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
Loader: NewLoader(url, secret),
|
client: client,
|
||||||
Parser: &Parser{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if enableCache {
|
if enableCache {
|
||||||
|
@ -105,19 +48,9 @@ func NewManager(url, secret string, enableCache bool) *Manager {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init loader
|
|
||||||
func (m *Manager) Init() error {
|
|
||||||
return m.Loader.Init()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load configurations, if cache is enabled, cache the configurations
|
// Load configurations, if cache is enabled, cache the configurations
|
||||||
func (m *Manager) Load() (map[string]interface{}, error) {
|
func (m *Manager) Load() (map[string]interface{}, error) {
|
||||||
b, err := m.Loader.Load()
|
c, err := m.client.GetCfgs()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := m.Parser.Parse(b)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -127,7 +60,15 @@ func (m *Manager) Load() (map[string]interface{}, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = m.cache.Put(m.key, c,
|
|
||||||
|
// copy the configuration map so that later modification to the
|
||||||
|
// map does not effect the cached value
|
||||||
|
cachedCfgs := map[string]interface{}{}
|
||||||
|
for k, v := range c {
|
||||||
|
cachedCfgs[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = m.cache.Put(m.key, cachedCfgs,
|
||||||
time.Duration(expi)*time.Second); err != nil {
|
time.Duration(expi)*time.Second); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -138,7 +79,7 @@ func (m *Manager) Load() (map[string]interface{}, error) {
|
||||||
|
|
||||||
// Reset configurations
|
// Reset configurations
|
||||||
func (m *Manager) Reset() error {
|
func (m *Manager) Reset() error {
|
||||||
return m.Loader.Reset()
|
return m.client.ResetCfgs()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCfgExpiration(m map[string]interface{}) (int, error) {
|
func getCfgExpiration(m map[string]interface{}) (int, error) {
|
||||||
|
@ -146,7 +87,7 @@ func getCfgExpiration(m map[string]interface{}) (int, error) {
|
||||||
return 0, fmt.Errorf("can not get cfg expiration as configurations are null")
|
return 0, fmt.Errorf("can not get cfg expiration as configurations are null")
|
||||||
}
|
}
|
||||||
|
|
||||||
expi, ok := m[CfgExpiration]
|
expi, ok := m[common.CfgExpiration]
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, fmt.Errorf("cfg expiration is not set")
|
return 0, fmt.Errorf("cfg expiration is not set")
|
||||||
}
|
}
|
||||||
|
@ -167,133 +108,6 @@ func (m *Manager) Get() (map[string]interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload configurations
|
// Upload configurations
|
||||||
func (m *Manager) Upload(b []byte) error {
|
func (m *Manager) Upload(cfgs map[string]interface{}) error {
|
||||||
return m.Loader.Upload(b)
|
return m.client.UpdateCfgs(cfgs)
|
||||||
}
|
|
||||||
|
|
||||||
// Loader loads and uploads configurations
|
|
||||||
type Loader struct {
|
|
||||||
url string
|
|
||||||
secret string
|
|
||||||
client *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLoader ...
|
|
||||||
func NewLoader(url, secret string) *Loader {
|
|
||||||
return &Loader{
|
|
||||||
url: url,
|
|
||||||
secret: secret,
|
|
||||||
client: &http.Client{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init waits remote server to be ready by testing connections with it
|
|
||||||
func (l *Loader) Init() error {
|
|
||||||
addr := l.url
|
|
||||||
if strings.Contains(addr, "://") {
|
|
||||||
addr = strings.Split(addr, "://")[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(addr, ":") {
|
|
||||||
addr = addr + ":80"
|
|
||||||
}
|
|
||||||
|
|
||||||
return utils.TestTCPConn(addr, 60, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load configurations from remote server
|
|
||||||
func (l *Loader) Load() ([]byte, error) {
|
|
||||||
log.Debug("loading configurations...")
|
|
||||||
req, err := http.NewRequest("GET", l.url+"/api/configurations", nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.AddCookie(&http.Cookie{
|
|
||||||
Name: "secret",
|
|
||||||
Value: l.secret,
|
|
||||||
})
|
|
||||||
|
|
||||||
resp, err := l.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("%d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug("configurations load completed")
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload configurations to remote server
|
|
||||||
func (l *Loader) Upload(b []byte) error {
|
|
||||||
req, err := http.NewRequest("PUT", l.url+"/api/configurations", bytes.NewReader(b))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.AddCookie(&http.Cookie{
|
|
||||||
Name: "secret",
|
|
||||||
Value: l.secret,
|
|
||||||
})
|
|
||||||
|
|
||||||
resp, err := l.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("unexpected http status code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("configurations uploaded")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset sends configurations resetting command to
|
|
||||||
// remote server
|
|
||||||
func (l *Loader) Reset() error {
|
|
||||||
req, err := http.NewRequest("POST", l.url+"/api/configurations/reset", nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.AddCookie(&http.Cookie{
|
|
||||||
Name: "secret",
|
|
||||||
Value: l.secret,
|
|
||||||
})
|
|
||||||
|
|
||||||
resp, err := l.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("unexpected http status code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("configurations resetted")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parser parses configurations
|
|
||||||
type Parser struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses []byte to a map configuration
|
|
||||||
func (p *Parser) Parse(b []byte) (map[string]interface{}, error) {
|
|
||||||
c := map[string]interface{}{}
|
|
||||||
if err := json.Unmarshal(b, &c); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
// const variables
|
||||||
|
const (
|
||||||
|
DBAuth = "db_auth"
|
||||||
|
LDAPAuth = "ldap_auth"
|
||||||
|
ProCrtRestrEveryone = "everyone"
|
||||||
|
ProCrtRestrAdmOnly = "adminonly"
|
||||||
|
LDAPScopeBase = "1"
|
||||||
|
LDAPScopeOnelevel = "2"
|
||||||
|
LDAPScopeSubtree = "3"
|
||||||
|
|
||||||
|
ExtEndpoint = "ext_endpoint"
|
||||||
|
AUTHMode = "auth_mode"
|
||||||
|
DatabaseType = "database_type"
|
||||||
|
MySQLHost = "mysql_host"
|
||||||
|
MySQLPort = "mysql_port"
|
||||||
|
MySQLUsername = "mysql_username"
|
||||||
|
MySQLPassword = "mysql_password"
|
||||||
|
MySQLDatabase = "mysql_database"
|
||||||
|
SQLiteFile = "sqlite_file"
|
||||||
|
SelfRegistration = "self_registration"
|
||||||
|
LDAPURL = "ldap_url"
|
||||||
|
LDAPSearchDN = "ldap_search_dn"
|
||||||
|
LDAPSearchPwd = "ldap_search_password"
|
||||||
|
LDAPBaseDN = "ldap_base_dn"
|
||||||
|
LDAPUID = "ldap_uid"
|
||||||
|
LDAPFilter = "ldap_filter"
|
||||||
|
LDAPScope = "ldap_scope"
|
||||||
|
LDAPTimeout = "ldap_timeout"
|
||||||
|
TokenServiceURL = "token_service_url"
|
||||||
|
RegistryURL = "registry_url"
|
||||||
|
EmailHost = "email_host"
|
||||||
|
EmailPort = "email_port"
|
||||||
|
EmailUsername = "email_username"
|
||||||
|
EmailPassword = "email_password"
|
||||||
|
EmailFrom = "email_from"
|
||||||
|
EmailSSL = "email_ssl"
|
||||||
|
EmailIdentity = "email_identity"
|
||||||
|
ProjectCreationRestriction = "project_creation_restriction"
|
||||||
|
VerifyRemoteCert = "verify_remote_cert"
|
||||||
|
MaxJobWorkers = "max_job_workers"
|
||||||
|
TokenExpiration = "token_expiration"
|
||||||
|
CfgExpiration = "cfg_expiration"
|
||||||
|
JobLogDir = "job_log_dir"
|
||||||
|
UseCompressedJS = "use_compressed_js"
|
||||||
|
AdminInitialPassword = "admin_initial_password"
|
||||||
|
AdmiralEndpoint = "admiral_url"
|
||||||
|
WithNotary = "with_notary"
|
||||||
|
)
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/config"
|
"github.com/vmware/harbor/src/common"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
@ -30,24 +30,24 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var adminServerLdapTestConfig = map[string]interface{}{
|
var adminServerLdapTestConfig = map[string]interface{}{
|
||||||
config.ExtEndpoint: "host01.com",
|
common.ExtEndpoint: "host01.com",
|
||||||
config.AUTHMode: "ldap_auth",
|
common.AUTHMode: "ldap_auth",
|
||||||
config.DatabaseType: "mysql",
|
common.DatabaseType: "mysql",
|
||||||
config.MySQLHost: "127.0.0.1",
|
common.MySQLHost: "127.0.0.1",
|
||||||
config.MySQLPort: 3306,
|
common.MySQLPort: 3306,
|
||||||
config.MySQLUsername: "root",
|
common.MySQLUsername: "root",
|
||||||
config.MySQLPassword: "root123",
|
common.MySQLPassword: "root123",
|
||||||
config.MySQLDatabase: "registry",
|
common.MySQLDatabase: "registry",
|
||||||
config.SQLiteFile: "/tmp/registry.db",
|
common.SQLiteFile: "/tmp/registry.db",
|
||||||
//config.SelfRegistration: true,
|
//config.SelfRegistration: true,
|
||||||
config.LDAPURL: "ldap://127.0.0.1",
|
common.LDAPURL: "ldap://127.0.0.1",
|
||||||
config.LDAPSearchDN: "cn=admin,dc=example,dc=com",
|
common.LDAPSearchDN: "cn=admin,dc=example,dc=com",
|
||||||
config.LDAPSearchPwd: "admin",
|
common.LDAPSearchPwd: "admin",
|
||||||
config.LDAPBaseDN: "dc=example,dc=com",
|
common.LDAPBaseDN: "dc=example,dc=com",
|
||||||
config.LDAPUID: "uid",
|
common.LDAPUID: "uid",
|
||||||
config.LDAPFilter: "",
|
common.LDAPFilter: "",
|
||||||
config.LDAPScope: 3,
|
common.LDAPScope: 3,
|
||||||
config.LDAPTimeout: 30,
|
common.LDAPTimeout: 30,
|
||||||
// config.TokenServiceURL: "",
|
// config.TokenServiceURL: "",
|
||||||
// config.RegistryURL: "",
|
// config.RegistryURL: "",
|
||||||
// config.EmailHost: "",
|
// config.EmailHost: "",
|
||||||
|
@ -61,10 +61,10 @@ var adminServerLdapTestConfig = map[string]interface{}{
|
||||||
// config.VerifyRemoteCert: false,
|
// config.VerifyRemoteCert: false,
|
||||||
// config.MaxJobWorkers: 3,
|
// config.MaxJobWorkers: 3,
|
||||||
// config.TokenExpiration: 30,
|
// config.TokenExpiration: 30,
|
||||||
config.CfgExpiration: 5,
|
common.CfgExpiration: 5,
|
||||||
// config.JobLogDir: "/var/log/jobs",
|
// config.JobLogDir: "/var/log/jobs",
|
||||||
// config.UseCompressedJS: true,
|
// config.UseCompressedJS: true,
|
||||||
config.AdminInitialPassword: "password",
|
common.AdminInitialPassword: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(t *testing.T) {
|
||||||
|
|
|
@ -20,46 +20,47 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/config"
|
"github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage"
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
var adminServerDefaultConfig = map[string]interface{}{
|
var adminServerDefaultConfig = map[string]interface{}{
|
||||||
config.ExtEndpoint: "https://host01.com",
|
common.ExtEndpoint: "https://host01.com",
|
||||||
config.AUTHMode: config.DBAuth,
|
common.AUTHMode: common.DBAuth,
|
||||||
config.DatabaseType: "mysql",
|
common.DatabaseType: "mysql",
|
||||||
config.MySQLHost: "127.0.0.1",
|
common.MySQLHost: "127.0.0.1",
|
||||||
config.MySQLPort: 3306,
|
common.MySQLPort: 3306,
|
||||||
config.MySQLUsername: "user01",
|
common.MySQLUsername: "user01",
|
||||||
config.MySQLPassword: "password",
|
common.MySQLPassword: "password",
|
||||||
config.MySQLDatabase: "registry",
|
common.MySQLDatabase: "registry",
|
||||||
config.SQLiteFile: "/tmp/registry.db",
|
common.SQLiteFile: "/tmp/registry.db",
|
||||||
config.SelfRegistration: true,
|
common.SelfRegistration: true,
|
||||||
config.LDAPURL: "ldap://127.0.0.1",
|
common.LDAPURL: "ldap://127.0.0.1",
|
||||||
config.LDAPSearchDN: "uid=searchuser,ou=people,dc=mydomain,dc=com",
|
common.LDAPSearchDN: "uid=searchuser,ou=people,dc=mydomain,dc=com",
|
||||||
config.LDAPSearchPwd: "password",
|
common.LDAPSearchPwd: "password",
|
||||||
config.LDAPBaseDN: "ou=people,dc=mydomain,dc=com",
|
common.LDAPBaseDN: "ou=people,dc=mydomain,dc=com",
|
||||||
config.LDAPUID: "uid",
|
common.LDAPUID: "uid",
|
||||||
config.LDAPFilter: "",
|
common.LDAPFilter: "",
|
||||||
config.LDAPScope: 3,
|
common.LDAPScope: 3,
|
||||||
config.LDAPTimeout: 30,
|
common.LDAPTimeout: 30,
|
||||||
config.TokenServiceURL: "http://token_service",
|
common.TokenServiceURL: "http://token_service",
|
||||||
config.RegistryURL: "http://registry",
|
common.RegistryURL: "http://registry",
|
||||||
config.EmailHost: "127.0.0.1",
|
common.EmailHost: "127.0.0.1",
|
||||||
config.EmailPort: 25,
|
common.EmailPort: 25,
|
||||||
config.EmailUsername: "user01",
|
common.EmailUsername: "user01",
|
||||||
config.EmailPassword: "password",
|
common.EmailPassword: "password",
|
||||||
config.EmailFrom: "from",
|
common.EmailFrom: "from",
|
||||||
config.EmailSSL: true,
|
common.EmailSSL: true,
|
||||||
config.EmailIdentity: "",
|
common.EmailIdentity: "",
|
||||||
config.ProjectCreationRestriction: config.ProCrtRestrAdmOnly,
|
common.ProjectCreationRestriction: common.ProCrtRestrAdmOnly,
|
||||||
config.VerifyRemoteCert: false,
|
common.VerifyRemoteCert: false,
|
||||||
config.MaxJobWorkers: 3,
|
common.MaxJobWorkers: 3,
|
||||||
config.TokenExpiration: 30,
|
common.TokenExpiration: 30,
|
||||||
config.CfgExpiration: 5,
|
common.CfgExpiration: 5,
|
||||||
config.UseCompressedJS: true,
|
common.UseCompressedJS: true,
|
||||||
config.AdminInitialPassword: "password",
|
common.AdminInitialPassword: "password",
|
||||||
config.AdmiralEndpoint: "http://www.vmware.com",
|
common.AdmiralEndpoint: "http://www.vmware.com",
|
||||||
config.WithNotary: false,
|
common.WithNotary: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdminserver returns a mock admin server
|
// NewAdminserver returns a mock admin server
|
||||||
|
@ -101,5 +102,32 @@ func NewAdminserver(config map[string]interface{}) (*httptest.Server, error) {
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
capacityHandler, err := NewCapacityHandle()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m = append(m, &RequestHandlerMapping{
|
||||||
|
Method: "GET",
|
||||||
|
Pattern: "/api/systeminfo/capacity",
|
||||||
|
Handler: capacityHandler,
|
||||||
|
})
|
||||||
|
|
||||||
return NewServer(m...), nil
|
return NewServer(m...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCapacityHandle ...
|
||||||
|
func NewCapacityHandle() (func(http.ResponseWriter, *http.Request), error) {
|
||||||
|
capacity := imagestorage.Capacity{
|
||||||
|
Total: 100,
|
||||||
|
Free: 90,
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(capacity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := &Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: b,
|
||||||
|
}
|
||||||
|
return Handler(resp), nil
|
||||||
|
}
|
||||||
|
|
|
@ -16,21 +16,28 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/adminserver/client"
|
||||||
|
"github.com/vmware/harbor/src/adminserver/client/auth"
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
comcfg "github.com/vmware/harbor/src/common/config"
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultKeyPath string = "/etc/jobservice/key"
|
defaultKeyPath string = "/etc/jobservice/key"
|
||||||
defaultLogDir string = "/var/log/jobs"
|
defaultLogDir string = "/var/log/jobs"
|
||||||
|
secretCookieName string = "secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mg *comcfg.Manager
|
// AdminserverClient is a client for adminserver
|
||||||
keyProvider comcfg.KeyProvider
|
AdminserverClient client.Client
|
||||||
|
mg *comcfg.Manager
|
||||||
|
keyProvider comcfg.KeyProvider
|
||||||
)
|
)
|
||||||
|
|
||||||
// Init configurations
|
// Init configurations
|
||||||
|
@ -42,12 +49,15 @@ func Init() error {
|
||||||
if len(adminServerURL) == 0 {
|
if len(adminServerURL) == 0 {
|
||||||
adminServerURL = "http://adminserver"
|
adminServerURL = "http://adminserver"
|
||||||
}
|
}
|
||||||
mg = comcfg.NewManager(adminServerURL, JobserviceSecret(), true)
|
log.Infof("initializing client for adminserver %s ...", adminServerURL)
|
||||||
|
authorizer := auth.NewSecretAuthorizer(secretCookieName, UISecret())
|
||||||
if err := mg.Init(); err != nil {
|
AdminserverClient = client.NewClient(adminServerURL, authorizer)
|
||||||
return err
|
if err := AdminserverClient.Ping(); err != nil {
|
||||||
|
return fmt.Errorf("failed to ping adminserver: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mg = comcfg.NewManager(AdminserverClient, true)
|
||||||
|
|
||||||
if _, err := mg.Load(); err != nil {
|
if _, err := mg.Load(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -71,7 +81,7 @@ func VerifyRemoteCert() (bool, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
return cfg[comcfg.VerifyRemoteCert].(bool), nil
|
return cfg[common.VerifyRemoteCert].(bool), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database ...
|
// Database ...
|
||||||
|
@ -81,16 +91,16 @@ func Database() (*models.Database, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
database := &models.Database{}
|
database := &models.Database{}
|
||||||
database.Type = cfg[comcfg.DatabaseType].(string)
|
database.Type = cfg[common.DatabaseType].(string)
|
||||||
mysql := &models.MySQL{}
|
mysql := &models.MySQL{}
|
||||||
mysql.Host = cfg[comcfg.MySQLHost].(string)
|
mysql.Host = cfg[common.MySQLHost].(string)
|
||||||
mysql.Port = int(cfg[comcfg.MySQLPort].(float64))
|
mysql.Port = int(cfg[common.MySQLPort].(float64))
|
||||||
mysql.Username = cfg[comcfg.MySQLUsername].(string)
|
mysql.Username = cfg[common.MySQLUsername].(string)
|
||||||
mysql.Password = cfg[comcfg.MySQLPassword].(string)
|
mysql.Password = cfg[common.MySQLPassword].(string)
|
||||||
mysql.Database = cfg[comcfg.MySQLDatabase].(string)
|
mysql.Database = cfg[common.MySQLDatabase].(string)
|
||||||
database.MySQL = mysql
|
database.MySQL = mysql
|
||||||
sqlite := &models.SQLite{}
|
sqlite := &models.SQLite{}
|
||||||
sqlite.File = cfg[comcfg.SQLiteFile].(string)
|
sqlite.File = cfg[common.SQLiteFile].(string)
|
||||||
database.SQLite = sqlite
|
database.SQLite = sqlite
|
||||||
|
|
||||||
return database, nil
|
return database, nil
|
||||||
|
@ -102,7 +112,7 @@ func MaxJobWorkers() (int, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return int(cfg[comcfg.MaxJobWorkers].(float64)), nil
|
return int(cfg[common.MaxJobWorkers].(float64)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalUIURL returns the local ui url, job service will use this URL to call API hosted on ui process
|
// LocalUIURL returns the local ui url, job service will use this URL to call API hosted on ui process
|
||||||
|
@ -116,7 +126,7 @@ func LocalRegURL() (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cfg[comcfg.RegistryURL].(string), nil
|
return cfg[common.RegistryURL].(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogDir returns the absolute path to which the log file will be written
|
// LogDir returns the absolute path to which the log file will be written
|
||||||
|
@ -151,7 +161,7 @@ func ExtEndpoint() (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cfg[comcfg.ExtEndpoint].(string), nil
|
return cfg[common.ExtEndpoint].(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalTokenServiceEndpoint ...
|
// InternalTokenServiceEndpoint ...
|
||||||
|
|
|
@ -20,8 +20,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
"github.com/vmware/harbor/src/common/api"
|
"github.com/vmware/harbor/src/common/api"
|
||||||
comcfg "github.com/vmware/harbor/src/common/config"
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
|
@ -30,65 +30,65 @@ import (
|
||||||
var (
|
var (
|
||||||
// valid keys of configurations which user can modify
|
// valid keys of configurations which user can modify
|
||||||
validKeys = []string{
|
validKeys = []string{
|
||||||
comcfg.ExtEndpoint,
|
common.ExtEndpoint,
|
||||||
comcfg.AUTHMode,
|
common.AUTHMode,
|
||||||
comcfg.DatabaseType,
|
common.DatabaseType,
|
||||||
comcfg.MySQLHost,
|
common.MySQLHost,
|
||||||
comcfg.MySQLPort,
|
common.MySQLPort,
|
||||||
comcfg.MySQLUsername,
|
common.MySQLUsername,
|
||||||
comcfg.MySQLPassword,
|
common.MySQLPassword,
|
||||||
comcfg.MySQLDatabase,
|
common.MySQLDatabase,
|
||||||
comcfg.SQLiteFile,
|
common.SQLiteFile,
|
||||||
comcfg.SelfRegistration,
|
common.SelfRegistration,
|
||||||
comcfg.LDAPURL,
|
common.LDAPURL,
|
||||||
comcfg.LDAPSearchDN,
|
common.LDAPSearchDN,
|
||||||
comcfg.LDAPSearchPwd,
|
common.LDAPSearchPwd,
|
||||||
comcfg.LDAPBaseDN,
|
common.LDAPBaseDN,
|
||||||
comcfg.LDAPUID,
|
common.LDAPUID,
|
||||||
comcfg.LDAPFilter,
|
common.LDAPFilter,
|
||||||
comcfg.LDAPScope,
|
common.LDAPScope,
|
||||||
comcfg.LDAPTimeout,
|
common.LDAPTimeout,
|
||||||
comcfg.TokenServiceURL,
|
common.TokenServiceURL,
|
||||||
comcfg.RegistryURL,
|
common.RegistryURL,
|
||||||
comcfg.EmailHost,
|
common.EmailHost,
|
||||||
comcfg.EmailPort,
|
common.EmailPort,
|
||||||
comcfg.EmailUsername,
|
common.EmailUsername,
|
||||||
comcfg.EmailPassword,
|
common.EmailPassword,
|
||||||
comcfg.EmailFrom,
|
common.EmailFrom,
|
||||||
comcfg.EmailSSL,
|
common.EmailSSL,
|
||||||
comcfg.EmailIdentity,
|
common.EmailIdentity,
|
||||||
comcfg.ProjectCreationRestriction,
|
common.ProjectCreationRestriction,
|
||||||
comcfg.VerifyRemoteCert,
|
common.VerifyRemoteCert,
|
||||||
comcfg.MaxJobWorkers,
|
common.MaxJobWorkers,
|
||||||
comcfg.TokenExpiration,
|
common.TokenExpiration,
|
||||||
comcfg.CfgExpiration,
|
common.CfgExpiration,
|
||||||
comcfg.JobLogDir,
|
common.JobLogDir,
|
||||||
comcfg.UseCompressedJS,
|
common.UseCompressedJS,
|
||||||
comcfg.AdminInitialPassword,
|
common.AdminInitialPassword,
|
||||||
}
|
}
|
||||||
|
|
||||||
numKeys = []string{
|
numKeys = []string{
|
||||||
comcfg.EmailPort,
|
common.EmailPort,
|
||||||
comcfg.LDAPScope,
|
common.LDAPScope,
|
||||||
comcfg.LDAPTimeout,
|
common.LDAPTimeout,
|
||||||
comcfg.MySQLPort,
|
common.MySQLPort,
|
||||||
comcfg.MaxJobWorkers,
|
common.MaxJobWorkers,
|
||||||
comcfg.TokenExpiration,
|
common.TokenExpiration,
|
||||||
comcfg.CfgExpiration,
|
common.CfgExpiration,
|
||||||
}
|
}
|
||||||
|
|
||||||
boolKeys = []string{
|
boolKeys = []string{
|
||||||
comcfg.EmailSSL,
|
common.EmailSSL,
|
||||||
comcfg.SelfRegistration,
|
common.SelfRegistration,
|
||||||
comcfg.VerifyRemoteCert,
|
common.VerifyRemoteCert,
|
||||||
comcfg.UseCompressedJS,
|
common.UseCompressedJS,
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordKeys = []string{
|
passwordKeys = []string{
|
||||||
comcfg.AdminInitialPassword,
|
common.AdminInitialPassword,
|
||||||
comcfg.EmailPassword,
|
common.EmailPassword,
|
||||||
comcfg.LDAPSearchPwd,
|
common.LDAPSearchPwd,
|
||||||
comcfg.MySQLPassword,
|
common.MySQLPassword,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ func (c *ConfigAPI) Put() {
|
||||||
c.CustomAbort(http.StatusBadRequest, err.Error())
|
c.CustomAbort(http.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if value, ok := cfg[comcfg.AUTHMode]; ok {
|
if value, ok := cfg[common.AUTHMode]; ok {
|
||||||
mode, err := config.AuthMode()
|
mode, err := config.AuthMode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get auth mode: %v", err)
|
log.Errorf("failed to get auth mode: %v", err)
|
||||||
|
@ -174,7 +174,7 @@ func (c *ConfigAPI) Put() {
|
||||||
if !flag {
|
if !flag {
|
||||||
c.CustomAbort(http.StatusBadRequest,
|
c.CustomAbort(http.StatusBadRequest,
|
||||||
fmt.Sprintf("%s can not be modified as new users have been inserted into database",
|
fmt.Sprintf("%s can not be modified as new users have been inserted into database",
|
||||||
comcfg.AUTHMode))
|
common.AUTHMode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,14 +213,14 @@ func validateCfg(c map[string]string) (bool, error) {
|
||||||
return isSysErr, err
|
return isSysErr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if value, ok := c[comcfg.AUTHMode]; ok {
|
if value, ok := c[common.AUTHMode]; ok {
|
||||||
if value != comcfg.DBAuth && value != comcfg.LDAPAuth {
|
if value != common.DBAuth && value != common.LDAPAuth {
|
||||||
return isSysErr, fmt.Errorf("invalid %s, shoud be %s or %s", comcfg.AUTHMode, comcfg.DBAuth, comcfg.LDAPAuth)
|
return isSysErr, fmt.Errorf("invalid %s, shoud be %s or %s", common.AUTHMode, common.DBAuth, common.LDAPAuth)
|
||||||
}
|
}
|
||||||
mode = value
|
mode = value
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode == comcfg.LDAPAuth {
|
if mode == common.LDAPAuth {
|
||||||
ldap, err := config.LDAP()
|
ldap, err := config.LDAP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
isSysErr = true
|
isSysErr = true
|
||||||
|
@ -228,46 +228,46 @@ func validateCfg(c map[string]string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ldap.URL) == 0 {
|
if len(ldap.URL) == 0 {
|
||||||
if _, ok := c[comcfg.LDAPURL]; !ok {
|
if _, ok := c[common.LDAPURL]; !ok {
|
||||||
return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPURL)
|
return isSysErr, fmt.Errorf("%s is missing", common.LDAPURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ldap.BaseDN) == 0 {
|
if len(ldap.BaseDN) == 0 {
|
||||||
if _, ok := c[comcfg.LDAPBaseDN]; !ok {
|
if _, ok := c[common.LDAPBaseDN]; !ok {
|
||||||
return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPBaseDN)
|
return isSysErr, fmt.Errorf("%s is missing", common.LDAPBaseDN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(ldap.UID) == 0 {
|
if len(ldap.UID) == 0 {
|
||||||
if _, ok := c[comcfg.LDAPUID]; !ok {
|
if _, ok := c[common.LDAPUID]; !ok {
|
||||||
return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPUID)
|
return isSysErr, fmt.Errorf("%s is missing", common.LDAPUID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ldap.Scope == 0 {
|
if ldap.Scope == 0 {
|
||||||
if _, ok := c[comcfg.LDAPScope]; !ok {
|
if _, ok := c[common.LDAPScope]; !ok {
|
||||||
return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPScope)
|
return isSysErr, fmt.Errorf("%s is missing", common.LDAPScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ldapURL, ok := c[comcfg.LDAPURL]; ok && len(ldapURL) == 0 {
|
if ldapURL, ok := c[common.LDAPURL]; ok && len(ldapURL) == 0 {
|
||||||
return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPURL)
|
return isSysErr, fmt.Errorf("%s is empty", common.LDAPURL)
|
||||||
}
|
}
|
||||||
if baseDN, ok := c[comcfg.LDAPBaseDN]; ok && len(baseDN) == 0 {
|
if baseDN, ok := c[common.LDAPBaseDN]; ok && len(baseDN) == 0 {
|
||||||
return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPBaseDN)
|
return isSysErr, fmt.Errorf("%s is empty", common.LDAPBaseDN)
|
||||||
}
|
}
|
||||||
if uID, ok := c[comcfg.LDAPUID]; ok && len(uID) == 0 {
|
if uID, ok := c[common.LDAPUID]; ok && len(uID) == 0 {
|
||||||
return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPUID)
|
return isSysErr, fmt.Errorf("%s is empty", common.LDAPUID)
|
||||||
}
|
}
|
||||||
if scope, ok := c[comcfg.LDAPScope]; ok &&
|
if scope, ok := c[common.LDAPScope]; ok &&
|
||||||
scope != comcfg.LDAPScopeBase &&
|
scope != common.LDAPScopeBase &&
|
||||||
scope != comcfg.LDAPScopeOnelevel &&
|
scope != common.LDAPScopeOnelevel &&
|
||||||
scope != comcfg.LDAPScopeSubtree {
|
scope != common.LDAPScopeSubtree {
|
||||||
return isSysErr, fmt.Errorf("invalid %s, should be %s, %s or %s",
|
return isSysErr, fmt.Errorf("invalid %s, should be %s, %s or %s",
|
||||||
comcfg.LDAPScope,
|
common.LDAPScope,
|
||||||
comcfg.LDAPScopeBase,
|
common.LDAPScopeBase,
|
||||||
comcfg.LDAPScopeOnelevel,
|
common.LDAPScopeOnelevel,
|
||||||
comcfg.LDAPScopeSubtree)
|
common.LDAPScopeSubtree)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, k := range boolKeys {
|
for _, k := range boolKeys {
|
||||||
|
@ -293,19 +293,19 @@ func validateCfg(c map[string]string) (bool, error) {
|
||||||
return isSysErr, fmt.Errorf("invalid %s: %s", k, v)
|
return isSysErr, fmt.Errorf("invalid %s: %s", k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (k == comcfg.EmailPort ||
|
if (k == common.EmailPort ||
|
||||||
k == comcfg.MySQLPort) && n > 65535 {
|
k == common.MySQLPort) && n > 65535 {
|
||||||
return isSysErr, fmt.Errorf("invalid %s: %s", k, v)
|
return isSysErr, fmt.Errorf("invalid %s: %s", k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if crt, ok := c[comcfg.ProjectCreationRestriction]; ok &&
|
if crt, ok := c[common.ProjectCreationRestriction]; ok &&
|
||||||
crt != comcfg.ProCrtRestrEveryone &&
|
crt != common.ProCrtRestrEveryone &&
|
||||||
crt != comcfg.ProCrtRestrAdmOnly {
|
crt != common.ProCrtRestrAdmOnly {
|
||||||
return isSysErr, fmt.Errorf("invalid %s, should be %s or %s",
|
return isSysErr, fmt.Errorf("invalid %s, should be %s or %s",
|
||||||
comcfg.ProjectCreationRestriction,
|
common.ProjectCreationRestriction,
|
||||||
comcfg.ProCrtRestrAdmOnly,
|
common.ProCrtRestrAdmOnly,
|
||||||
comcfg.ProCrtRestrEveryone)
|
common.ProCrtRestrEveryone)
|
||||||
}
|
}
|
||||||
|
|
||||||
return isSysErr, nil
|
return isSysErr, nil
|
||||||
|
@ -363,7 +363,7 @@ func convertForGet(cfg map[string]interface{}) (map[string]*value, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result[comcfg.AUTHMode].Editable = flag
|
result[common.AUTHMode].Editable = flag
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/vmware/harbor/src/common/config"
|
"github.com/vmware/harbor/src/common"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetConfig(t *testing.T) {
|
func TestGetConfig(t *testing.T) {
|
||||||
|
@ -46,8 +47,13 @@ func TestGetConfig(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mode := cfg[config.AUTHMode].Value.(string)
|
mode := cfg[common.AUTHMode].Value.(string)
|
||||||
assert.Equal(config.DBAuth, mode, fmt.Sprintf("the auth mode should be %s", config.DBAuth))
|
assert.Equal(common.DBAuth, mode, fmt.Sprintf("the auth mode should be %s", common.DBAuth))
|
||||||
|
ccc, err := config.GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed to get system configurations: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("%v", ccc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPutConfig(t *testing.T) {
|
func TestPutConfig(t *testing.T) {
|
||||||
|
@ -56,7 +62,7 @@ func TestPutConfig(t *testing.T) {
|
||||||
apiTest := newHarborAPI()
|
apiTest := newHarborAPI()
|
||||||
|
|
||||||
cfg := map[string]string{
|
cfg := map[string]string{
|
||||||
config.VerifyRemoteCert: "0",
|
common.VerifyRemoteCert: "0",
|
||||||
}
|
}
|
||||||
|
|
||||||
code, err := apiTest.PutConfig(*admin, cfg)
|
code, err := apiTest.PutConfig(*admin, cfg)
|
||||||
|
@ -67,6 +73,11 @@ func TestPutConfig(t *testing.T) {
|
||||||
if !assert.Equal(200, code, "the status code of modifying configurations with admin user should be 200") {
|
if !assert.Equal(200, code, "the status code of modifying configurations with admin user should be 200") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ccc, err := config.GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed to get system configurations: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("%v", ccc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResetConfig(t *testing.T) {
|
func TestResetConfig(t *testing.T) {
|
||||||
|
@ -94,11 +105,17 @@ func TestResetConfig(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
value, ok := cfgs[config.VerifyRemoteCert]
|
value, ok := cfgs[common.VerifyRemoteCert]
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("%s not found", config.VerifyRemoteCert)
|
t.Errorf("%s not found", common.VerifyRemoteCert)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(value.Value.(bool), true, "unexpected value")
|
assert.Equal(value.Value.(bool), true, "unexpected value")
|
||||||
|
|
||||||
|
ccc, err := config.GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed to get system configurations: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("%v", ccc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
"github.com/vmware/harbor/src/common/api"
|
"github.com/vmware/harbor/src/common/api"
|
||||||
comcfg "github.com/vmware/harbor/src/common/config"
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
|
@ -21,8 +20,8 @@ type SystemInfoAPI struct {
|
||||||
isAdmin bool
|
isAdmin bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const harborStoragePath = "/harbor_storage"
|
|
||||||
const defaultRootCert = "/harbor_storage/ca_download/ca.crt"
|
const defaultRootCert = "/harbor_storage/ca_download/ca.crt"
|
||||||
|
const harborVersionFile = "/harbor/VERSION"
|
||||||
|
|
||||||
//SystemInfo models for system info.
|
//SystemInfo models for system info.
|
||||||
type SystemInfo struct {
|
type SystemInfo struct {
|
||||||
|
@ -45,6 +44,7 @@ type GeneralInfo struct {
|
||||||
ProjectCreationRestrict string `json:"project_creation_restriction"`
|
ProjectCreationRestrict string `json:"project_creation_restriction"`
|
||||||
SelfRegistration bool `json:"self_registration"`
|
SelfRegistration bool `json:"self_registration"`
|
||||||
HasCARoot bool `json:"has_ca_root"`
|
HasCARoot bool `json:"has_ca_root"`
|
||||||
|
HarborVersion string `json:"harbor_version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate for validating user if an admin.
|
// validate for validating user if an admin.
|
||||||
|
@ -66,18 +66,16 @@ func (sia *SystemInfoAPI) GetVolumeInfo() {
|
||||||
sia.RenderError(http.StatusForbidden, "User does not have admin role.")
|
sia.RenderError(http.StatusForbidden, "User does not have admin role.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var stat syscall.Statfs_t
|
|
||||||
err := syscall.Statfs(filepath.Join("/", harborStoragePath), &stat)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error occurred in syscall.Statfs: %v", err)
|
|
||||||
sia.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
capacity, err := config.AdminserverClient.Capacity()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get capacity: %v", err)
|
||||||
|
sia.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
systemInfo := SystemInfo{
|
systemInfo := SystemInfo{
|
||||||
HarborStorage: Storage{
|
HarborStorage: Storage{
|
||||||
Total: stat.Blocks * uint64(stat.Bsize),
|
Total: capacity.Total,
|
||||||
Free: stat.Bavail * uint64(stat.Bsize),
|
Free: capacity.Free,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,22 +110,34 @@ func (sia *SystemInfoAPI) GetGeneralInfo() {
|
||||||
sia.CustomAbort(http.StatusInternalServerError, "Unexpected error")
|
sia.CustomAbort(http.StatusInternalServerError, "Unexpected error")
|
||||||
}
|
}
|
||||||
var registryURL string
|
var registryURL string
|
||||||
if l := strings.Split(cfg[comcfg.ExtEndpoint].(string), "://"); len(l) > 1 {
|
if l := strings.Split(cfg[common.ExtEndpoint].(string), "://"); len(l) > 1 {
|
||||||
registryURL = l[1]
|
registryURL = l[1]
|
||||||
} else {
|
} else {
|
||||||
registryURL = l[0]
|
registryURL = l[0]
|
||||||
}
|
}
|
||||||
_, caStatErr := os.Stat(defaultRootCert)
|
_, caStatErr := os.Stat(defaultRootCert)
|
||||||
|
harborVersion := sia.getVersion()
|
||||||
info := GeneralInfo{
|
info := GeneralInfo{
|
||||||
AdmiralEndpoint: cfg[comcfg.AdmiralEndpoint].(string),
|
AdmiralEndpoint: cfg[common.AdmiralEndpoint].(string),
|
||||||
WithAdmiral: config.WithAdmiral(),
|
WithAdmiral: config.WithAdmiral(),
|
||||||
WithNotary: config.WithNotary(),
|
WithNotary: config.WithNotary(),
|
||||||
AuthMode: cfg[comcfg.AUTHMode].(string),
|
AuthMode: cfg[common.AUTHMode].(string),
|
||||||
ProjectCreationRestrict: cfg[comcfg.ProjectCreationRestriction].(string),
|
ProjectCreationRestrict: cfg[common.ProjectCreationRestriction].(string),
|
||||||
SelfRegistration: cfg[comcfg.SelfRegistration].(bool),
|
SelfRegistration: cfg[common.SelfRegistration].(bool),
|
||||||
RegistryURL: registryURL,
|
RegistryURL: registryURL,
|
||||||
HasCARoot: caStatErr == nil,
|
HasCARoot: caStatErr == nil,
|
||||||
|
HarborVersion: harborVersion,
|
||||||
}
|
}
|
||||||
sia.Data["json"] = info
|
sia.Data["json"] = info
|
||||||
sia.ServeJSON()
|
sia.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetVersion gets harbor version.
|
||||||
|
func (sia *SystemInfoAPI) getVersion() string {
|
||||||
|
version, err := ioutil.ReadFile(harborVersionFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error occured getting harbor version: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(version[:])
|
||||||
|
}
|
||||||
|
|
|
@ -3,8 +3,9 @@ package api
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetVolumeInfo(t *testing.T) {
|
func TestGetVolumeInfo(t *testing.T) {
|
||||||
|
@ -49,6 +50,7 @@ func TestGetGeneralInfo(t *testing.T) {
|
||||||
assert.Nil(err, fmt.Sprintf("Unexpected Error: %v", err))
|
assert.Nil(err, fmt.Sprintf("Unexpected Error: %v", err))
|
||||||
assert.Equal(false, g.WithNotary, "with notary should be false")
|
assert.Equal(false, g.WithNotary, "with notary should be false")
|
||||||
assert.Equal(true, g.HasCARoot, "has ca root should be true")
|
assert.Equal(true, g.HasCARoot, "has ca root should be true")
|
||||||
|
assert.NotEmpty(g.HarborVersion, "harbor version should not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCert(t *testing.T) {
|
func TestGetCert(t *testing.T) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/config"
|
"github.com/vmware/harbor/src/common"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
@ -15,24 +15,24 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var adminServerLdapTestConfig = map[string]interface{}{
|
var adminServerLdapTestConfig = map[string]interface{}{
|
||||||
config.ExtEndpoint: "host01.com",
|
common.ExtEndpoint: "host01.com",
|
||||||
config.AUTHMode: "ldap_auth",
|
common.AUTHMode: "ldap_auth",
|
||||||
config.DatabaseType: "mysql",
|
common.DatabaseType: "mysql",
|
||||||
config.MySQLHost: "127.0.0.1",
|
common.MySQLHost: "127.0.0.1",
|
||||||
config.MySQLPort: 3306,
|
common.MySQLPort: 3306,
|
||||||
config.MySQLUsername: "root",
|
common.MySQLUsername: "root",
|
||||||
config.MySQLPassword: "root123",
|
common.MySQLPassword: "root123",
|
||||||
config.MySQLDatabase: "registry",
|
common.MySQLDatabase: "registry",
|
||||||
config.SQLiteFile: "/tmp/registry.db",
|
common.SQLiteFile: "/tmp/registry.db",
|
||||||
//config.SelfRegistration: true,
|
//config.SelfRegistration: true,
|
||||||
config.LDAPURL: "ldap://127.0.0.1",
|
common.LDAPURL: "ldap://127.0.0.1",
|
||||||
config.LDAPSearchDN: "cn=admin,dc=example,dc=com",
|
common.LDAPSearchDN: "cn=admin,dc=example,dc=com",
|
||||||
config.LDAPSearchPwd: "admin",
|
common.LDAPSearchPwd: "admin",
|
||||||
config.LDAPBaseDN: "dc=example,dc=com",
|
common.LDAPBaseDN: "dc=example,dc=com",
|
||||||
config.LDAPUID: "uid",
|
common.LDAPUID: "uid",
|
||||||
config.LDAPFilter: "",
|
common.LDAPFilter: "",
|
||||||
config.LDAPScope: 3,
|
common.LDAPScope: 3,
|
||||||
config.LDAPTimeout: 30,
|
common.LDAPTimeout: 30,
|
||||||
// config.TokenServiceURL: "",
|
// config.TokenServiceURL: "",
|
||||||
// config.RegistryURL: "",
|
// config.RegistryURL: "",
|
||||||
// config.EmailHost: "",
|
// config.EmailHost: "",
|
||||||
|
@ -46,10 +46,10 @@ var adminServerLdapTestConfig = map[string]interface{}{
|
||||||
// config.VerifyRemoteCert: false,
|
// config.VerifyRemoteCert: false,
|
||||||
// config.MaxJobWorkers: 3,
|
// config.MaxJobWorkers: 3,
|
||||||
// config.TokenExpiration: 30,
|
// config.TokenExpiration: 30,
|
||||||
config.CfgExpiration: 5,
|
common.CfgExpiration: 5,
|
||||||
// config.JobLogDir: "/var/log/jobs",
|
// config.JobLogDir: "/var/log/jobs",
|
||||||
// config.UseCompressedJS: true,
|
// config.UseCompressedJS: true,
|
||||||
config.AdminInitialPassword: "password",
|
common.AdminInitialPassword: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(t *testing.T) {
|
||||||
|
|
|
@ -16,20 +16,28 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/adminserver/client"
|
||||||
|
"github.com/vmware/harbor/src/adminserver/client/auth"
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
comcfg "github.com/vmware/harbor/src/common/config"
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultKeyPath string = "/etc/ui/key"
|
const (
|
||||||
|
defaultKeyPath string = "/etc/ui/key"
|
||||||
|
secretCookieName string = "secret"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mg *comcfg.Manager
|
// AdminserverClient is a client for adminserver
|
||||||
keyProvider comcfg.KeyProvider
|
AdminserverClient client.Client
|
||||||
|
mg *comcfg.Manager
|
||||||
|
keyProvider comcfg.KeyProvider
|
||||||
)
|
)
|
||||||
|
|
||||||
// Init configurations
|
// Init configurations
|
||||||
|
@ -41,14 +49,17 @@ func Init() error {
|
||||||
if len(adminServerURL) == 0 {
|
if len(adminServerURL) == 0 {
|
||||||
adminServerURL = "http://adminserver"
|
adminServerURL = "http://adminserver"
|
||||||
}
|
}
|
||||||
log.Debugf("admin server URL: %s", adminServerURL)
|
|
||||||
mg = comcfg.NewManager(adminServerURL, UISecret(), true)
|
|
||||||
|
|
||||||
if err := mg.Init(); err != nil {
|
log.Infof("initializing client for adminserver %s ...", adminServerURL)
|
||||||
return err
|
authorizer := auth.NewSecretAuthorizer(secretCookieName, UISecret())
|
||||||
|
AdminserverClient = client.NewClient(adminServerURL, authorizer)
|
||||||
|
if err := AdminserverClient.Ping(); err != nil {
|
||||||
|
return fmt.Errorf("failed to ping adminserver: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := mg.Load(); err != nil {
|
mg = comcfg.NewManager(AdminserverClient, true)
|
||||||
|
|
||||||
|
if err := Load(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,26 +89,12 @@ func Reset() error {
|
||||||
|
|
||||||
// Upload uploads all system configutations to admin server
|
// Upload uploads all system configutations to admin server
|
||||||
func Upload(cfg map[string]interface{}) error {
|
func Upload(cfg map[string]interface{}) error {
|
||||||
b, err := json.Marshal(cfg)
|
return mg.Upload(cfg)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return mg.Upload(b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSystemCfg returns the system configurations
|
// GetSystemCfg returns the system configurations
|
||||||
func GetSystemCfg() (map[string]interface{}, error) {
|
func GetSystemCfg() (map[string]interface{}, error) {
|
||||||
raw, err := mg.Loader.Load()
|
return mg.Load()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := mg.Parser.Parse(raw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthMode ...
|
// AuthMode ...
|
||||||
|
@ -106,7 +103,7 @@ func AuthMode() (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cfg[comcfg.AUTHMode].(string), nil
|
return cfg[common.AUTHMode].(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LDAP returns the setting of ldap server
|
// LDAP returns the setting of ldap server
|
||||||
|
@ -117,14 +114,14 @@ func LDAP() (*models.LDAP, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ldap := &models.LDAP{}
|
ldap := &models.LDAP{}
|
||||||
ldap.URL = cfg[comcfg.LDAPURL].(string)
|
ldap.URL = cfg[common.LDAPURL].(string)
|
||||||
ldap.SearchDN = cfg[comcfg.LDAPSearchDN].(string)
|
ldap.SearchDN = cfg[common.LDAPSearchDN].(string)
|
||||||
ldap.SearchPassword = cfg[comcfg.LDAPSearchPwd].(string)
|
ldap.SearchPassword = cfg[common.LDAPSearchPwd].(string)
|
||||||
ldap.BaseDN = cfg[comcfg.LDAPBaseDN].(string)
|
ldap.BaseDN = cfg[common.LDAPBaseDN].(string)
|
||||||
ldap.UID = cfg[comcfg.LDAPUID].(string)
|
ldap.UID = cfg[common.LDAPUID].(string)
|
||||||
ldap.Filter = cfg[comcfg.LDAPFilter].(string)
|
ldap.Filter = cfg[common.LDAPFilter].(string)
|
||||||
ldap.Scope = int(cfg[comcfg.LDAPScope].(float64))
|
ldap.Scope = int(cfg[common.LDAPScope].(float64))
|
||||||
ldap.Timeout = int(cfg[comcfg.LDAPTimeout].(float64))
|
ldap.Timeout = int(cfg[common.LDAPTimeout].(float64))
|
||||||
|
|
||||||
return ldap, nil
|
return ldap, nil
|
||||||
}
|
}
|
||||||
|
@ -135,7 +132,7 @@ func TokenExpiration() (int, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return int(cfg[comcfg.TokenExpiration].(float64)), nil
|
return int(cfg[common.TokenExpiration].(float64)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtEndpoint returns the external URL of Harbor: protocol://host:port
|
// ExtEndpoint returns the external URL of Harbor: protocol://host:port
|
||||||
|
@ -144,7 +141,7 @@ func ExtEndpoint() (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cfg[comcfg.ExtEndpoint].(string), nil
|
return cfg[common.ExtEndpoint].(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtURL returns the external URL: host:port
|
// ExtURL returns the external URL: host:port
|
||||||
|
@ -171,7 +168,7 @@ func SelfRegistration() (bool, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return cfg[comcfg.SelfRegistration].(bool), nil
|
return cfg[common.SelfRegistration].(bool), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegistryURL ...
|
// RegistryURL ...
|
||||||
|
@ -180,7 +177,7 @@ func RegistryURL() (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cfg[comcfg.RegistryURL].(string), nil
|
return cfg[common.RegistryURL].(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalJobServiceURL returns jobservice URL for internal communication between Harbor containers
|
// InternalJobServiceURL returns jobservice URL for internal communication between Harbor containers
|
||||||
|
@ -205,7 +202,7 @@ func InitialAdminPassword() (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cfg[comcfg.AdminInitialPassword].(string), nil
|
return cfg[common.AdminInitialPassword].(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnlyAdminCreateProject returns the flag to restrict that only sys admin can create project
|
// OnlyAdminCreateProject returns the flag to restrict that only sys admin can create project
|
||||||
|
@ -214,7 +211,7 @@ func OnlyAdminCreateProject() (bool, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
return cfg[comcfg.ProjectCreationRestriction].(string) == comcfg.ProCrtRestrAdmOnly, nil
|
return cfg[common.ProjectCreationRestriction].(string) == common.ProCrtRestrAdmOnly, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyRemoteCert returns bool value.
|
// VerifyRemoteCert returns bool value.
|
||||||
|
@ -223,7 +220,7 @@ func VerifyRemoteCert() (bool, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
return cfg[comcfg.VerifyRemoteCert].(bool), nil
|
return cfg[common.VerifyRemoteCert].(bool), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Email returns email server settings
|
// Email returns email server settings
|
||||||
|
@ -234,13 +231,13 @@ func Email() (*models.Email, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
email := &models.Email{}
|
email := &models.Email{}
|
||||||
email.Host = cfg[comcfg.EmailHost].(string)
|
email.Host = cfg[common.EmailHost].(string)
|
||||||
email.Port = int(cfg[comcfg.EmailPort].(float64))
|
email.Port = int(cfg[common.EmailPort].(float64))
|
||||||
email.Username = cfg[comcfg.EmailUsername].(string)
|
email.Username = cfg[common.EmailUsername].(string)
|
||||||
email.Password = cfg[comcfg.EmailPassword].(string)
|
email.Password = cfg[common.EmailPassword].(string)
|
||||||
email.SSL = cfg[comcfg.EmailSSL].(bool)
|
email.SSL = cfg[common.EmailSSL].(bool)
|
||||||
email.From = cfg[comcfg.EmailFrom].(string)
|
email.From = cfg[common.EmailFrom].(string)
|
||||||
email.Identity = cfg[comcfg.EmailIdentity].(string)
|
email.Identity = cfg[common.EmailIdentity].(string)
|
||||||
|
|
||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
@ -252,16 +249,16 @@ func Database() (*models.Database, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
database := &models.Database{}
|
database := &models.Database{}
|
||||||
database.Type = cfg[comcfg.DatabaseType].(string)
|
database.Type = cfg[common.DatabaseType].(string)
|
||||||
mysql := &models.MySQL{}
|
mysql := &models.MySQL{}
|
||||||
mysql.Host = cfg[comcfg.MySQLHost].(string)
|
mysql.Host = cfg[common.MySQLHost].(string)
|
||||||
mysql.Port = int(cfg[comcfg.MySQLPort].(float64))
|
mysql.Port = int(cfg[common.MySQLPort].(float64))
|
||||||
mysql.Username = cfg[comcfg.MySQLUsername].(string)
|
mysql.Username = cfg[common.MySQLUsername].(string)
|
||||||
mysql.Password = cfg[comcfg.MySQLPassword].(string)
|
mysql.Password = cfg[common.MySQLPassword].(string)
|
||||||
mysql.Database = cfg[comcfg.MySQLDatabase].(string)
|
mysql.Database = cfg[common.MySQLDatabase].(string)
|
||||||
database.MySQL = mysql
|
database.MySQL = mysql
|
||||||
sqlite := &models.SQLite{}
|
sqlite := &models.SQLite{}
|
||||||
sqlite.File = cfg[comcfg.SQLiteFile].(string)
|
sqlite.File = cfg[common.SQLiteFile].(string)
|
||||||
database.SQLite = sqlite
|
database.SQLite = sqlite
|
||||||
|
|
||||||
return database, nil
|
return database, nil
|
||||||
|
@ -286,7 +283,7 @@ func WithNotary() bool {
|
||||||
log.Errorf("Failed to get configuration, will return WithNotary == false")
|
log.Errorf("Failed to get configuration, will return WithNotary == false")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return cfg[comcfg.WithNotary].(bool)
|
return cfg[common.WithNotary].(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdmiralEndpoint returns the URL of admiral, if Harbor is not deployed with admiral it should return an empty string.
|
// AdmiralEndpoint returns the URL of admiral, if Harbor is not deployed with admiral it should return an empty string.
|
||||||
|
@ -297,10 +294,10 @@ func AdmiralEndpoint() string {
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if e, ok := cfg[comcfg.AdmiralEndpoint].(string); !ok || e == "NA" {
|
if e, ok := cfg[common.AdmiralEndpoint].(string); !ok || e == "NA" {
|
||||||
cfg[comcfg.AdmiralEndpoint] = ""
|
cfg[common.AdmiralEndpoint] = ""
|
||||||
}
|
}
|
||||||
return cfg[comcfg.AdmiralEndpoint].(string)
|
return cfg[common.AdmiralEndpoint].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithAdmiral returns a bool to indicate if Harbor's deployed with admiral.
|
// WithAdmiral returns a bool to indicate if Harbor's deployed with admiral.
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/beego/i18n"
|
"github.com/beego/i18n"
|
||||||
"github.com/vmware/harbor/src/common/config"
|
"github.com/vmware/harbor/src/common"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
|
@ -113,7 +113,7 @@ func (cc *CommonController) SendEmail() {
|
||||||
|
|
||||||
message := new(bytes.Buffer)
|
message := new(bytes.Buffer)
|
||||||
|
|
||||||
harborURL := config.ExtEndpoint
|
harborURL := common.ExtEndpoint
|
||||||
if harborURL == "" {
|
if harborURL == "" {
|
||||||
harborURL = "localhost"
|
harborURL = "localhost"
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ func initRouters() {
|
||||||
//API:
|
//API:
|
||||||
beego.Router("/api/search", &api.SearchAPI{})
|
beego.Router("/api/search", &api.SearchAPI{})
|
||||||
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{})
|
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{})
|
||||||
beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List;post:Post")
|
beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List;post:Post;head:Head")
|
||||||
beego.Router("/api/projects/:id", &api.ProjectAPI{})
|
beego.Router("/api/projects/:id", &api.ProjectAPI{})
|
||||||
beego.Router("/api/projects/:id/publicity", &api.ProjectAPI{}, "put:ToggleProjectPublic")
|
beego.Router("/api/projects/:id/publicity", &api.ProjectAPI{}, "put:ToggleProjectPublic")
|
||||||
beego.Router("/api/statistics", &api.StatisticAPI{})
|
beego.Router("/api/statistics", &api.StatisticAPI{})
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
<div class="form-group form-group-override">
|
<div class="form-group form-group-override">
|
||||||
<label for="oldPassword" class="required form-group-label-override">{{'CHANGE_PWD.CURRENT_PWD' | translate}}</label>
|
<label for="oldPassword" class="required form-group-label-override">{{'CHANGE_PWD.CURRENT_PWD' | translate}}</label>
|
||||||
<label for="oldPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]="oldPassInput.invalid && (oldPassInput.dirty || oldPassInput.touched)">
|
<label for="oldPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]="oldPassInput.invalid && (oldPassInput.dirty || oldPassInput.touched)">
|
||||||
<input type="password" id="oldPassword" placeholder='{{"PLACEHOLDER.CURRENT_PWD" | translate}}'
|
<input type="password" id="oldPassword"
|
||||||
required
|
required
|
||||||
name="oldPassword"
|
name="oldPassword"
|
||||||
[(ngModel)]="oldPwd"
|
[(ngModel)]="oldPwd"
|
||||||
#oldPassInput="ngModel" size="30">
|
#oldPassInput="ngModel" size="42">
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
{{'TOOLTIP.CURRENT_PWD' | translate}}
|
{{'TOOLTIP.CURRENT_PWD' | translate}}
|
||||||
</span>
|
</span>
|
||||||
|
@ -19,27 +19,32 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-group-override">
|
<div class="form-group form-group-override">
|
||||||
<label for="newPassword" class="required form-group-label-override">{{'CHANGE_PWD.NEW_PWD' | translate}}</label>
|
<label for="newPassword" class="required form-group-label-override">{{'CHANGE_PWD.NEW_PWD' | translate}}</label>
|
||||||
<label for="newPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="newPassInput.invalid && (newPassInput.dirty || newPassInput.touched)">
|
<label for="newPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]='!getValidationState("newPassword")'>
|
||||||
<input type="password" id="newPassword" placeholder='{{"PLACEHOLDER.NEW_PWD" | translate}}'
|
<input type="password" id="newPassword"
|
||||||
required
|
required
|
||||||
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{7,}$"
|
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$"
|
||||||
name="newPassword"
|
name="newPassword"
|
||||||
[(ngModel)]="newPwd"
|
[(ngModel)]="newPwd"
|
||||||
#newPassInput="ngModel" size="30">
|
#newPassInput="ngModel" size="42"
|
||||||
|
(input)='handleValidation("newPassword", false)'
|
||||||
|
(focusout)='handleValidation("newPassword", true)'>
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
{{'TOOLTIP.PASSWORD' | translate}}
|
{{'TOOLTIP.PASSWORD' | translate}}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="sub-label-for-input">{{'CHANGE_PWD.PASS_TIPS' | translate}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-group-override">
|
<div class="form-group form-group-override">
|
||||||
<label for="reNewPassword" class="required form-group-label-override">{{'CHANGE_PWD.CONFIRM_PWD' | translate}}</label>
|
<label for="reNewPassword" class="required form-group-label-override">{{'CHANGE_PWD.CONFIRM_PWD' | translate}}</label>
|
||||||
<label for="reNewPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="(reNewPassInput.invalid && (reNewPassInput.dirty || reNewPassInput.touched)) || (!newPassInput.invalid && reNewPassInput.value != newPassInput.value)">
|
<label for="reNewPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]='!getValidationState("reNewPassword")'>
|
||||||
<input type="password" id="reNewPassword" placeholder='{{"PLACEHOLDER.CONFIRM_PWD" | translate}}'
|
<input type="password" id="reNewPassword"
|
||||||
required
|
required
|
||||||
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{7,}$"
|
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$"
|
||||||
name="reNewPassword"
|
name="reNewPassword"
|
||||||
[(ngModel)]="reNewPwd"
|
[(ngModel)]="reNewPwd"
|
||||||
#reNewPassInput="ngModel" size="30">
|
#reNewPassInput="ngModel" size="42"
|
||||||
|
(input)='handleValidation("reNewPassword", false)'
|
||||||
|
(focusout)='handleValidation("reNewPassword", true)'>
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
{{'TOOLTIP.CONFIRM_PWD' | translate}}
|
{{'TOOLTIP.CONFIRM_PWD' | translate}}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -23,6 +23,10 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
||||||
|
|
||||||
private formValueChanged: boolean = false;
|
private formValueChanged: boolean = false;
|
||||||
private onCalling: boolean = false;
|
private onCalling: boolean = false;
|
||||||
|
private validationStateMap: any = {
|
||||||
|
"newPassword": true,
|
||||||
|
"reNewPassword": true
|
||||||
|
};
|
||||||
|
|
||||||
pwdFormRef: NgForm;
|
pwdFormRef: NgForm;
|
||||||
@ViewChild("changepwdForm") pwdForm: NgForm;
|
@ViewChild("changepwdForm") pwdForm: NgForm;
|
||||||
|
@ -52,6 +56,29 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
||||||
return this.onCalling;
|
return this.onCalling;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getValidationState(key: string): boolean {
|
||||||
|
return this.validationStateMap[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleValidation(key: string, flag: boolean): void {
|
||||||
|
if (flag) {
|
||||||
|
//Checking
|
||||||
|
let cont = this.pwdForm.controls[key];
|
||||||
|
if (cont) {
|
||||||
|
this.validationStateMap[key] = cont.valid;
|
||||||
|
if(key === "reNewPassword" && cont.valid){
|
||||||
|
let compareCont = this.pwdForm.controls["newPassword"];
|
||||||
|
if(compareCont){
|
||||||
|
this.validationStateMap[key]= cont.value === compareCont.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Reset
|
||||||
|
this.validationStateMap[key] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngAfterViewChecked() {
|
ngAfterViewChecked() {
|
||||||
if (this.pwdFormRef != this.pwdForm) {
|
if (this.pwdFormRef != this.pwdForm) {
|
||||||
this.pwdFormRef = this.pwdForm;
|
this.pwdFormRef = this.pwdForm;
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { CookieService } from 'angular2-cookie/core';
|
||||||
import { CookieKeyOfAdmiral, HarborQueryParamKey } from './shared/shared.const';
|
import { CookieKeyOfAdmiral, HarborQueryParamKey } from './shared/shared.const';
|
||||||
import { maintainUrlQueryParmas } from './shared/shared.utils';
|
import { maintainUrlQueryParmas } from './shared/shared.utils';
|
||||||
|
|
||||||
|
|
||||||
export const systemInfoEndpoint = "/api/systeminfo";
|
export const systemInfoEndpoint = "/api/systeminfo";
|
||||||
/**
|
/**
|
||||||
* Declare service to handle the bootstrap options
|
* Declare service to handle the bootstrap options
|
||||||
|
@ -50,7 +49,6 @@ export class AppConfigService {
|
||||||
//Catch the error
|
//Catch the error
|
||||||
console.error("Failed to load bootstrap options with error: ", error);
|
console.error("Failed to load bootstrap options with error: ", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getConfig(): AppConfig {
|
public getConfig(): AppConfig {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { modalEvents } from './modal-events.const'
|
|
||||||
|
|
||||||
//Define a object to store the modal event
|
//Define a object to store the modal event
|
||||||
export class ModalEvent {
|
export class ModalEvent {
|
||||||
modalName: modalEvents;
|
modalName: string;
|
||||||
modalFlag: boolean; //true for open, false for close
|
modalFlag: boolean; //true for open, false for close
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="header-nav">
|
<div class="header-nav">
|
||||||
<a href="{{admiralLink}}" class="nav-link" *ngIf="isIntegrationMode"><span class="nav-text">Management</span></a>
|
<a href="{{admiralLink}}" class="nav-link" *ngIf="isIntegrationMode"><span class="nav-text">Management</span></a>
|
||||||
<a href="javascript:void(0)" routerLink="/harbor" class="active nav-link" *ngIf="isIntegrationMode"><span class="nav-text">Registry</span></a>
|
<a href="javascript:void(0)" (click)="registryAction()" routerLink="/harbor" class="active nav-link" *ngIf="isIntegrationMode"><span class="nav-text">Registry</span></a>
|
||||||
</div>
|
</div>
|
||||||
<global-search></global-search>
|
<global-search></global-search>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
|
|
|
@ -9,9 +9,11 @@ import { SessionUser } from '../../shared/session-user';
|
||||||
import { SessionService } from '../../shared/session.service';
|
import { SessionService } from '../../shared/session.service';
|
||||||
import { CookieService } from 'angular2-cookie/core';
|
import { CookieService } from 'angular2-cookie/core';
|
||||||
|
|
||||||
import { supportedLangs, enLang, languageNames, CommonRoutes } from '../../shared/shared.const';
|
import { supportedLangs, enLang, languageNames, CommonRoutes, AlertType } from '../../shared/shared.const';
|
||||||
|
import { errorHandler } from '../../shared/shared.utils';
|
||||||
import { AppConfigService } from '../../app-config.service';
|
import { AppConfigService } from '../../app-config.service';
|
||||||
|
import { SearchTriggerService } from '../global-search/search-trigger.service';
|
||||||
|
import { MessageService } from '../../global-message/message.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'navigator',
|
selector: 'navigator',
|
||||||
|
@ -31,7 +33,9 @@ export class NavigatorComponent implements OnInit {
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private cookie: CookieService,
|
private cookie: CookieService,
|
||||||
private appConfigService: AppConfigService) { }
|
private appConfigService: AppConfigService,
|
||||||
|
private msgService: MessageService,
|
||||||
|
private searchTrigger: SearchTriggerService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.selectedLang = this.translate.currentLang;
|
this.selectedLang = this.translate.currentLang;
|
||||||
|
@ -98,8 +102,10 @@ export class NavigatorComponent implements OnInit {
|
||||||
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN]);
|
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN]);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error("Log out with error: ", error);
|
this.msgService.announceMessage(error.status | 500, errorHandler(error), AlertType.WARNING);
|
||||||
});
|
});
|
||||||
|
//Confirm search result panel is close
|
||||||
|
this.searchTrigger.closeSearch(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Switch languages
|
//Switch languages
|
||||||
|
@ -124,5 +130,12 @@ export class NavigatorComponent implements OnInit {
|
||||||
//Naviagte to signin page
|
//Naviagte to signin page
|
||||||
this.router.navigate([CommonRoutes.HARBOR_ROOT]);
|
this.router.navigate([CommonRoutes.HARBOR_ROOT]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Confirm search result panel is close
|
||||||
|
this.searchTrigger.closeSearch(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
registryAction(): void {
|
||||||
|
this.searchTrigger.closeSearch(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,4 +5,12 @@
|
||||||
.form-group-label-override {
|
.form-group-label-override {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-label-for-input {
|
||||||
|
position: absolute;
|
||||||
|
top: 26px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 12px;
|
||||||
}
|
}
|
|
@ -1,23 +1,26 @@
|
||||||
<clr-modal [(clrModalOpen)]="createProjectOpened">
|
<clr-modal [(clrModalOpen)]="createProjectOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||||
<h3 class="modal-title">{{'PROJECT.NEW_PROJECT' | translate}}</h3>
|
<h3 class="modal-title">{{'PROJECT.NEW_PROJECT' | translate}}</h3>
|
||||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form #projectForm="ngForm">
|
<form #projectForm="ngForm">
|
||||||
<section class="form-block">
|
<section class="form-block">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="create_project_name" class="col-md-4">{{'PROJECT.NAME' | translate}}</label>
|
<label for="create_project_name" class="col-md-4 form-group-label-override">{{'PROJECT.NAME' | translate}}</label>
|
||||||
<label for="create_project_name" aria-haspopup="true" role="tooltip" [class.invalid]="projectName.invalid && (projectName.dirty || projectName.touched)" [class.valid]="projectName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
<label for="create_project_name" aria-haspopup="true" role="tooltip" [class.invalid]="projectName.invalid && (projectName.dirty || projectName.touched)" [class.valid]="projectName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||||
<input type="text" id="create_project_name" [(ngModel)]="project.name" name="name" size="20" required minlength="2" #projectName="ngModel">
|
<input type="text" id="create_project_name" [(ngModel)]="project.name" name="name" size="20" required minlength="2" #projectName="ngModel" targetExists="PROJECT_NAME">
|
||||||
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.required && (projectName.dirty || projectName.touched)">
|
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.required && (projectName.dirty || projectName.touched)">
|
||||||
{{'PROJECT.NAME_IS_REQUIRED' | translate}}
|
{{'PROJECT.NAME_IS_REQUIRED' | translate}}
|
||||||
</span>
|
</span>
|
||||||
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.minlength && (projectName.dirty || projectName.touched)">
|
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.minlength && (projectName.dirty || projectName.touched)">
|
||||||
{{'PROJECT.NAME_MINIMUM_LENGTH' | translate}}
|
{{'PROJECT.NAME_MINIMUM_LENGTH' | translate}}
|
||||||
</span>
|
</span>
|
||||||
|
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.targetExists && (projectName.dirty || projectName.touched)">
|
||||||
|
{{'PROJECT.NAME_ALREADY_EXISTS' | translate}}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-4">{{'PROJECT.PUBLIC_OR_PRIVATE' | translate}}</label>
|
<label class="col-md-4 form-group-label-override">{{'PROJECT.PUBLIC_OR_PRIVATE' | translate}}</label>
|
||||||
<div class="checkbox-inline">
|
<div class="checkbox-inline">
|
||||||
<input type="checkbox" id="create_project_public" [(ngModel)]="project.public" name="public">
|
<input type="checkbox" id="create_project_public" [(ngModel)]="project.public" name="public">
|
||||||
<label for="create_project_public"></label>
|
<label for="create_project_public"></label>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Component, EventEmitter, Output, ViewChild, AfterViewChecked } from '@angular/core';
|
import { Component, EventEmitter, Output, ViewChild, AfterViewChecked, HostBinding } from '@angular/core';
|
||||||
|
|
||||||
import { Response } from '@angular/http';
|
import { Response } from '@angular/http';
|
||||||
import { NgForm } from '@angular/forms';
|
import { NgForm } from '@angular/forms';
|
||||||
|
|
||||||
|
@ -14,6 +13,7 @@ import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.com
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'create-project',
|
selector: 'create-project',
|
||||||
templateUrl: 'create-project.component.html',
|
templateUrl: 'create-project.component.html',
|
||||||
|
@ -27,12 +27,15 @@ export class CreateProjectComponent implements AfterViewChecked {
|
||||||
currentForm: NgForm;
|
currentForm: NgForm;
|
||||||
|
|
||||||
project: Project = new Project();
|
project: Project = new Project();
|
||||||
|
initVal: Project = new Project();
|
||||||
|
|
||||||
createProjectOpened: boolean;
|
createProjectOpened: boolean;
|
||||||
|
|
||||||
|
|
||||||
hasChanged: boolean;
|
hasChanged: boolean;
|
||||||
|
|
||||||
|
staticBackdrop: boolean = true;
|
||||||
|
closable: boolean = false;
|
||||||
|
|
||||||
@Output() create = new EventEmitter<boolean>();
|
@Output() create = new EventEmitter<boolean>();
|
||||||
@ViewChild(InlineAlertComponent)
|
@ViewChild(InlineAlertComponent)
|
||||||
private inlineAlert: InlineAlertComponent;
|
private inlineAlert: InlineAlertComponent;
|
||||||
|
@ -75,7 +78,9 @@ export class CreateProjectComponent implements AfterViewChecked {
|
||||||
this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'});
|
this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'});
|
||||||
} else {
|
} else {
|
||||||
this.createProjectOpened = false;
|
this.createProjectOpened = false;
|
||||||
|
this.projectForm.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewChecked(): void {
|
ngAfterViewChecked(): void {
|
||||||
|
@ -83,17 +88,14 @@ export class CreateProjectComponent implements AfterViewChecked {
|
||||||
if(this.projectForm) {
|
if(this.projectForm) {
|
||||||
this.projectForm.valueChanges.subscribe(data=>{
|
this.projectForm.valueChanges.subscribe(data=>{
|
||||||
for(let i in data) {
|
for(let i in data) {
|
||||||
let item = data[i];
|
let origin = this.initVal[i];
|
||||||
if(typeof item === 'string' && (<string>item).trim().length !== 0) {
|
let current = data[i];
|
||||||
this.hasChanged = true;
|
if(current && current !== origin) {
|
||||||
break;
|
|
||||||
} else if (typeof item === 'boolean' && (<boolean>item)) {
|
|
||||||
this.hasChanged = true;
|
this.hasChanged = true;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
this.hasChanged = false;
|
this.hasChanged = false;
|
||||||
this.inlineAlert.close();
|
this.inlineAlert.close();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -109,7 +111,7 @@ export class CreateProjectComponent implements AfterViewChecked {
|
||||||
confirmCancel(event: boolean): void {
|
confirmCancel(event: boolean): void {
|
||||||
this.createProjectOpened = false;
|
this.createProjectOpened = false;
|
||||||
this.inlineAlert.close();
|
this.inlineAlert.close();
|
||||||
|
this.projectForm.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<clr-datagrid (clrDgRefresh)="refresh($event)" >
|
<clr-datagrid (clrDgRefresh)="refresh($event)">
|
||||||
<clr-dg-column>{{'PROJECT.NAME' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'PROJECT.NAME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'PROJECT.PUBLIC_OR_PRIVATE' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'PROJECT.PUBLIC_OR_PRIVATE' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'PROJECT.REPO_COUNT'| translate}}</clr-dg-column>
|
<clr-dg-column>{{'PROJECT.REPO_COUNT'| translate}}</clr-dg-column>
|
||||||
|
@ -19,5 +19,5 @@
|
||||||
<clr-dg-footer>
|
<clr-dg-footer>
|
||||||
{{totalRecordCount || (projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}}
|
{{totalRecordCount || (projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}}
|
||||||
<clr-dg-pagination [clrDgPageSize]="pageOffset" [clrDgTotalItems]="totalPage"></clr-dg-pagination>
|
<clr-dg-pagination [clrDgPageSize]="pageOffset" [clrDgTotalItems]="totalPage"></clr-dg-pagination>
|
||||||
</clr-dg-footer>
|
</clr-dg-footer>
|
||||||
</clr-datagrid>
|
</clr-datagrid>
|
|
@ -1,27 +1,23 @@
|
||||||
<clr-modal [(clrModalOpen)]="addMemberOpened">
|
<clr-modal [(clrModalOpen)]="addMemberOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||||
<h3 class="modal-title">{{'MEMBER.NEW_MEMBER' | translate}}</h3>
|
<h3 class="modal-title">{{'MEMBER.NEW_MEMBER' | translate}}</h3>
|
||||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form #memberForm="ngForm">
|
<form #memberForm="ngForm">
|
||||||
<section class="form-block">
|
<section class="form-block">
|
||||||
<clr-alert [clrAlertType]="'alert-danger'" [(clrAlertClosed)]="!errorMessageOpened" (clrAlertClosedChange)="onErrorMessageClose()">
|
|
||||||
<div class="alert-item">
|
|
||||||
<span class="alert-text">
|
|
||||||
{{errorMessage}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</clr-alert>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="member_name" class="col-md-4">{{'MEMBER.NAME' | translate}}</label>
|
<label for="member_name" class="col-md-4 form-group-label-override">{{'MEMBER.NAME' | translate}}</label>
|
||||||
<label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="memberName.invalid && (memberName.dirty || memberName.touched)" [class.valid]="memberName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
<label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="memberName.invalid && (memberName.dirty || memberName.touched)" [class.valid]="memberName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||||
<input type="text" id="member_name" [(ngModel)]="member.username" name="name" size="20" #memberName="ngModel" required>
|
<input type="text" id="member_name" [(ngModel)]="member.username" name="name" size="20" #memberName="ngModel" required targetExists="MEMBER_NAME" [projectId]="projectId">
|
||||||
<span class="tooltip-content" *ngIf="memberName.errors && memberName.errors.required && (memberName.dirty || memberName.touched)">
|
<span class="tooltip-content" *ngIf="memberName.errors && memberName.errors.required && (memberName.dirty || memberName.touched)">
|
||||||
Username is required.
|
{{ 'MEMBER.USERNAME_IS_REQUIRED' | translate }}
|
||||||
|
</span>
|
||||||
|
<span class="tooltip-content" *ngIf="memberName.errors && memberName.errors.targetExists && (memberName.dirty || memberName.touched)">
|
||||||
|
{{ 'MEMBER.USERNAME_ALREADY_EXISTS' | translate }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-4">{{'MEMBER.ROLE' | translate}}</label>
|
<label class="col-md-4 form-group-label-override">{{'MEMBER.ROLE' | translate}}</label>
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
<input type="radio" name="roleRadios" id="checkrads_project_admin" value="1" [(ngModel)]="member.role_id">
|
<input type="radio" name="roleRadios" id="checkrads_project_admin" value="1" [(ngModel)]="member.role_id">
|
||||||
<label for="checkrads_project_admin">{{'MEMBER.PROJECT_ADMIN' | translate}}</label>
|
<label for="checkrads_project_admin">{{'MEMBER.PROJECT_ADMIN' | translate}}</label>
|
||||||
|
|
|
@ -20,10 +20,15 @@ import { Member } from '../member';
|
||||||
export class AddMemberComponent implements AfterViewChecked {
|
export class AddMemberComponent implements AfterViewChecked {
|
||||||
|
|
||||||
member: Member = new Member();
|
member: Member = new Member();
|
||||||
|
initVal: Member = new Member();
|
||||||
|
|
||||||
addMemberOpened: boolean;
|
addMemberOpened: boolean;
|
||||||
|
|
||||||
memberForm: NgForm;
|
memberForm: NgForm;
|
||||||
|
|
||||||
|
staticBackdrop: boolean = true;
|
||||||
|
closable: boolean = false;
|
||||||
|
|
||||||
@ViewChild('memberForm')
|
@ViewChild('memberForm')
|
||||||
currentForm: NgForm;
|
currentForm: NgForm;
|
||||||
|
|
||||||
|
@ -40,6 +45,7 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||||
private translateService: TranslateService) {}
|
private translateService: TranslateService) {}
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
|
if(!this.member.username || this.member.username.length === 0) { return; }
|
||||||
console.log('Adding member:' + JSON.stringify(this.member));
|
console.log('Adding member:' + JSON.stringify(this.member));
|
||||||
this.memberService
|
this.memberService
|
||||||
.addMember(this.projectId, this.member.username, +this.member.role_id)
|
.addMember(this.projectId, this.member.username, +this.member.role_id)
|
||||||
|
@ -76,6 +82,7 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||||
this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'});
|
this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'});
|
||||||
} else {
|
} else {
|
||||||
this.addMemberOpened = false;
|
this.addMemberOpened = false;
|
||||||
|
this.memberForm.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,21 +90,15 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||||
this.memberForm = this.currentForm;
|
this.memberForm = this.currentForm;
|
||||||
if(this.memberForm) {
|
if(this.memberForm) {
|
||||||
this.memberForm.valueChanges.subscribe(data=>{
|
this.memberForm.valueChanges.subscribe(data=>{
|
||||||
for(let i in data) {
|
for(let i in data) {
|
||||||
let item = data[i];
|
let origin = this.initVal[i];
|
||||||
if(typeof item === 'string' && (<string>item).trim().length !== 0) {
|
let current = data[i];
|
||||||
this.hasChanged = true;
|
if(current && current !== origin) {
|
||||||
break;
|
|
||||||
} else if (typeof item === 'boolean' && (<boolean>item)) {
|
|
||||||
this.hasChanged = true;
|
|
||||||
break;
|
|
||||||
} else if (typeof item === 'number' && (<number>item) !== 0) {
|
|
||||||
this.hasChanged = true;
|
this.hasChanged = true;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
this.hasChanged = false;
|
this.hasChanged = false;
|
||||||
this.inlineAlert.close();
|
this.inlineAlert.close();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -107,6 +108,7 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||||
confirmCancel(confirmed: boolean) {
|
confirmCancel(confirmed: boolean) {
|
||||||
this.addMemberOpened = false;
|
this.addMemberOpened = false;
|
||||||
this.inlineAlert.close();
|
this.inlineAlert.close();
|
||||||
|
this.memberForm.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
openAddMemberModal(): void {
|
openAddMemberModal(): void {
|
||||||
|
|
|
@ -5,15 +5,15 @@
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" routerLink="repository" routerLinkActive="active">{{'PROJECT_DETAIL.REPOSITORIES' | translate}}</a>
|
<a class="nav-link" routerLink="repository" routerLinkActive="active">{{'PROJECT_DETAIL.REPOSITORIES' | translate}}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" *ngIf="isSessionValid && isSystemAdmin">
|
|
||||||
<a class="nav-link" routerLink="replication" routerLinkActive="active">{{'PROJECT_DETAIL.REPLICATION' | translate}}</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" *ngIf="isSessionValid">
|
<li class="nav-item" *ngIf="isSessionValid">
|
||||||
<a class="nav-link" routerLink="member" routerLinkActive="active">{{'PROJECT_DETAIL.USERS' | translate}}</a>
|
<a class="nav-link" routerLink="member" routerLinkActive="active">{{'PROJECT_DETAIL.USERS' | translate}}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" *ngIf="isSessionValid">
|
<li class="nav-item" *ngIf="isSessionValid">
|
||||||
<a class="nav-link" routerLink="log" routerLinkActive="active">{{'PROJECT_DETAIL.LOGS' | translate}}</a>
|
<a class="nav-link" routerLink="log" routerLinkActive="active">{{'PROJECT_DETAIL.LOGS' | translate}}</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" *ngIf="isSessionValid && isSystemAdmin">
|
||||||
|
<a class="nav-link" routerLink="replication" routerLinkActive="active">{{'PROJECT_DETAIL.REPLICATION' | translate}}</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
|
@ -51,7 +51,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||||
isPublic: number;
|
isPublic: number;
|
||||||
|
|
||||||
page: number = 1;
|
page: number = 1;
|
||||||
pageSize: number = 3;
|
pageSize: number = 15;
|
||||||
|
|
||||||
totalPage: number;
|
totalPage: number;
|
||||||
totalRecordCount: number;
|
totalRecordCount: number;
|
||||||
|
|
|
@ -18,6 +18,8 @@ import { ProjectService } from './project.service';
|
||||||
import { MemberService } from './member/member.service';
|
import { MemberService } from './member/member.service';
|
||||||
import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
||||||
|
|
||||||
|
import { TargetExistsValidatorDirective } from '../shared/target-exists-directive';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
@ -32,7 +34,8 @@ import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
||||||
ListProjectComponent,
|
ListProjectComponent,
|
||||||
ProjectDetailComponent,
|
ProjectDetailComponent,
|
||||||
MemberComponent,
|
MemberComponent,
|
||||||
AddMemberComponent
|
AddMemberComponent,
|
||||||
|
TargetExistsValidatorDirective
|
||||||
],
|
],
|
||||||
exports: [ProjectComponent, ListProjectComponent],
|
exports: [ProjectComponent, ListProjectComponent],
|
||||||
providers: [ProjectRoutingResolver, ProjectService, MemberService]
|
providers: [ProjectRoutingResolver, ProjectService, MemberService]
|
||||||
|
|
|
@ -62,4 +62,12 @@ export class ProjectService {
|
||||||
.map(response=>response.status)
|
.map(response=>response.status)
|
||||||
.catch(error=>Observable.throw(error));
|
.catch(error=>Observable.throw(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkProjectExists(projectName: string): Observable<any> {
|
||||||
|
return this.http
|
||||||
|
.head(`/api/projects/?project_name=${projectName}`)
|
||||||
|
.map(response=>response.status)
|
||||||
|
.catch(error=>Observable.throw(error));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,18 +1,11 @@
|
||||||
<clr-modal [(clrModalOpen)]="createEditDestinationOpened">
|
<clr-modal [(clrModalOpen)]="createEditDestinationOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||||
<h3 class="modal-title">{{modalTitle}}</h3>
|
<h3 class="modal-title">{{modalTitle}}</h3>
|
||||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form #targetForm="ngForm">
|
<form #targetForm="ngForm">
|
||||||
<section class="form-block">
|
<section class="form-block">
|
||||||
<clr-alert [clrAlertType]="'alert-danger'" [(clrAlertClosed)]="!errorMessageOpened" (clrAlertClosedChange)="onErrorMessageClose()">
|
|
||||||
<div class="alert-item">
|
|
||||||
<span class="alert-text">
|
|
||||||
{{errorMessage}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</clr-alert>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="destination_name" class="col-md-4">{{ 'DESTINATION.NAME' | translate }}<span style="color: red">*</span></label>
|
<label for="destination_name" class="col-md-4 form-group-label-override">{{ 'DESTINATION.NAME' | translate }}<span style="color: red">*</span></label>
|
||||||
<label class="col-md-8" for="destination_name" aria-haspopup="true" role="tooltip" [class.invalid]="targetName.errors && (targetName.dirty || targetName.touched)" [class.valid]="targetName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
<label class="col-md-8" for="destination_name" aria-haspopup="true" role="tooltip" [class.invalid]="targetName.errors && (targetName.dirty || targetName.touched)" [class.valid]="targetName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||||
<input type="text" id="destination_name" [disabled]="testOngoing" [(ngModel)]="target.name" name="targetName" size="20" #targetName="ngModel" value="" required>
|
<input type="text" id="destination_name" [disabled]="testOngoing" [(ngModel)]="target.name" name="targetName" size="20" #targetName="ngModel" value="" required>
|
||||||
<span class="tooltip-content" *ngIf="targetName.errors && targetName.errors.required && (targetName.dirty || targetName.touched)">
|
<span class="tooltip-content" *ngIf="targetName.errors && targetName.errors.required && (targetName.dirty || targetName.touched)">
|
||||||
|
@ -21,7 +14,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="destination_url" class="col-md-4">{{ 'DESTINATION.URL' | translate }}<span style="color: red">*</span></label>
|
<label for="destination_url" class="col-md-4 form-group-label-override">{{ 'DESTINATION.URL' | translate }}<span style="color: red">*</span></label>
|
||||||
<label class="col-md-8" for="destination_url" aria-haspopup="true" role="tooltip" [class.invalid]="targetEndpoint.errors && (targetEndpoint.dirty || targetEndpoint.touched)" [class.valid]="targetEndpoint.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
<label class="col-md-8" for="destination_url" aria-haspopup="true" role="tooltip" [class.invalid]="targetEndpoint.errors && (targetEndpoint.dirty || targetEndpoint.touched)" [class.valid]="targetEndpoint.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||||
<input type="text" id="destination_url" [disabled]="testOngoing" [(ngModel)]="target.endpoint" size="20" name="endpointUrl" #targetEndpoint="ngModel" required>
|
<input type="text" id="destination_url" [disabled]="testOngoing" [(ngModel)]="target.endpoint" size="20" name="endpointUrl" #targetEndpoint="ngModel" required>
|
||||||
<span class="tooltip-content" *ngIf="targetEndpoint.errors && targetEndpoint.errors.required && (targetEndpoint.dirty || targetEndpoint.touched)">
|
<span class="tooltip-content" *ngIf="targetEndpoint.errors && targetEndpoint.errors.required && (targetEndpoint.dirty || targetEndpoint.touched)">
|
||||||
|
@ -30,17 +23,17 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="destination_username" class="col-md-4">{{ 'DESTINATION.USERNAME' | translate }}</label>
|
<label for="destination_username" class="col-md-4 form-group-label-override">{{ 'DESTINATION.USERNAME' | translate }}</label>
|
||||||
<input type="text" class="col-md-8" id="destination_username" [disabled]="testOngoing" [(ngModel)]="target.username" size="20" name="username" #username="ngModel">
|
<input type="text" class="col-md-8" id="destination_username" [disabled]="testOngoing" [(ngModel)]="target.username" size="20" name="username" #username="ngModel">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="destination_password" class="col-md-4">{{ 'DESTINATION.PASSWORD' | translate }}</label>
|
<label for="destination_password" class="col-md-4 form-group-label-override">{{ 'DESTINATION.PASSWORD' | translate }}</label>
|
||||||
<input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing" [(ngModel)]="target.password" size="20" name="password" #password="ngModel">
|
<input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing" [(ngModel)]="target.password" size="20" name="password" #password="ngModel">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="spin" class="col-md-4"></label>
|
<label for="spin" class="col-md-4"></label>
|
||||||
<span class="col-md-8 spinner spinner-inline" [hidden]="!testOngoing"></span>
|
<span class="col-md-8 spinner spinner-inline" [hidden]="!testOngoing"></span>
|
||||||
<span [style.color]="!pingStatus ? 'red': ''">{{ pingTestMessage }}</span>
|
<span [style.color]="!pingStatus ? 'red': ''" class="form-group-label-override">{{ pingTestMessage }}</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||||
selector: 'create-edit-destination',
|
selector: 'create-edit-destination',
|
||||||
templateUrl: './create-edit-destination.component.html'
|
templateUrl: './create-edit-destination.component.html'
|
||||||
})
|
})
|
||||||
export class CreateEditDestinationComponent {
|
export class CreateEditDestinationComponent implements AfterViewChecked {
|
||||||
|
|
||||||
modalTitle: string;
|
modalTitle: string;
|
||||||
createEditDestinationOpened: boolean;
|
createEditDestinationOpened: boolean;
|
||||||
|
@ -27,9 +27,13 @@ export class CreateEditDestinationComponent {
|
||||||
actionType: ActionType;
|
actionType: ActionType;
|
||||||
|
|
||||||
target: Target = new Target();
|
target: Target = new Target();
|
||||||
|
initVal: Target = new Target();
|
||||||
|
|
||||||
targetForm: NgForm;
|
targetForm: NgForm;
|
||||||
|
|
||||||
|
staticBackdrop: boolean = true;
|
||||||
|
closable: boolean = false;
|
||||||
|
|
||||||
@ViewChild('targetForm')
|
@ViewChild('targetForm')
|
||||||
currentForm: NgForm;
|
currentForm: NgForm;
|
||||||
|
|
||||||
|
@ -47,7 +51,6 @@ export class CreateEditDestinationComponent {
|
||||||
|
|
||||||
openCreateEditTarget(targetId?: number) {
|
openCreateEditTarget(targetId?: number) {
|
||||||
this.target = new Target();
|
this.target = new Target();
|
||||||
|
|
||||||
this.createEditDestinationOpened = true;
|
this.createEditDestinationOpened = true;
|
||||||
|
|
||||||
this.hasChanged = false;
|
this.hasChanged = false;
|
||||||
|
@ -62,7 +65,13 @@ export class CreateEditDestinationComponent {
|
||||||
this.replicationService
|
this.replicationService
|
||||||
.getTarget(targetId)
|
.getTarget(targetId)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
target=>this.target=target,
|
target=>{
|
||||||
|
this.target = target;
|
||||||
|
this.initVal.name = this.target.name;
|
||||||
|
this.initVal.endpoint = this.target.endpoint;
|
||||||
|
this.initVal.username = this.target.username;
|
||||||
|
this.initVal.password = this.target.password;
|
||||||
|
},
|
||||||
error=>this.messageService
|
error=>this.messageService
|
||||||
.announceMessage(error.status, 'DESTINATION.FAILED_TO_GET_TARGET', AlertType.DANGER)
|
.announceMessage(error.status, 'DESTINATION.FAILED_TO_GET_TARGET', AlertType.DANGER)
|
||||||
);
|
);
|
||||||
|
@ -171,22 +180,26 @@ export class CreateEditDestinationComponent {
|
||||||
this.inlineAlert.close();
|
this.inlineAlert.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mappedName: {} = {
|
||||||
|
'targetName': 'name',
|
||||||
|
'endpointUrl': 'endpoint',
|
||||||
|
'username': 'username',
|
||||||
|
'password': 'password'
|
||||||
|
};
|
||||||
|
|
||||||
ngAfterViewChecked(): void {
|
ngAfterViewChecked(): void {
|
||||||
this.targetForm = this.currentForm;
|
this.targetForm = this.currentForm;
|
||||||
if(this.targetForm) {
|
if(this.targetForm) {
|
||||||
this.targetForm.valueChanges.subscribe(data=>{
|
this.targetForm.valueChanges.subscribe(data=>{
|
||||||
for(let i in data) {
|
for(let i in data) {
|
||||||
let item = data[i];
|
let current = data[i];
|
||||||
if(typeof item === 'string' && (<string>item).trim().length !== 0) {
|
let origin = this.initVal[this.mappedName[i]];
|
||||||
this.hasChanged = true;
|
if(current && current !== origin) {
|
||||||
break;
|
|
||||||
} else if (typeof item === 'boolean' && (<boolean>item)) {
|
|
||||||
this.hasChanged = true;
|
this.hasChanged = true;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
this.hasChanged = false;
|
this.hasChanged = false;
|
||||||
this.inlineAlert.close();
|
this.inlineAlert.close();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
<clr-dg-cell>{{t.tag}}</clr-dg-cell>
|
<clr-dg-cell>{{t.tag}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{t.pullCommand}}</clr-dg-cell>
|
<clr-dg-cell>{{t.pullCommand}}</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<clr-icon shape="check" *ngIf="t.verified" style="color: #1D5100;"></clr-icon>
|
<clr-icon shape="check" *ngIf="t.signed" style="color: #1D5100;"></clr-icon>
|
||||||
<clr-icon shape="close" *ngIf="!t.verified" style="color: #C92100;"></clr-icon>
|
<clr-icon shape="close" *ngIf="!t.signed" style="color: #C92100;"></clr-icon>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
<clr-dg-cell>{{t.author}}</clr-dg-cell>
|
<clr-dg-cell>{{t.author}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{t.created | date: 'yyyy/MM/dd'}}</clr-dg-cell>
|
<clr-dg-cell>{{t.created | date: 'yyyy/MM/dd'}}</clr-dg-cell>
|
||||||
|
|
|
@ -26,6 +26,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||||
repoName: string;
|
repoName: string;
|
||||||
|
|
||||||
tags: TagView[];
|
tags: TagView[];
|
||||||
|
registryUrl: string;
|
||||||
|
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
|
||||||
|
@ -66,6 +67,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||||
this.projectId = this.route.snapshot.params['id'];
|
this.projectId = this.route.snapshot.params['id'];
|
||||||
this.repoName = this.route.snapshot.params['repo'];
|
this.repoName = this.route.snapshot.params['repo'];
|
||||||
this.tags = [];
|
this.tags = [];
|
||||||
|
this.registryUrl = this.appConfigService.getConfig().registry_url;
|
||||||
this.retrieve();
|
this.retrieve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,10 +89,10 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||||
let data = JSON.parse(t.manifest.history[0].v1Compatibility);
|
let data = JSON.parse(t.manifest.history[0].v1Compatibility);
|
||||||
tag.architecture = data['architecture'];
|
tag.architecture = data['architecture'];
|
||||||
tag.author = data['author'];
|
tag.author = data['author'];
|
||||||
tag.verified = t.signed;
|
tag.signed = t.signed;
|
||||||
tag.created = data['created'];
|
tag.created = data['created'];
|
||||||
tag.dockerVersion = data['docker_version'];
|
tag.dockerVersion = data['docker_version'];
|
||||||
tag.pullCommand = 'docker pull ' + t.manifest.name + ':' + t.tag;
|
tag.pullCommand = 'docker pull ' + this.registryUrl + '/' + t.manifest.name + ':' + t.tag;
|
||||||
tag.os = data['os'];
|
tag.os = data['os'];
|
||||||
this.tags.push(tag);
|
this.tags.push(tag);
|
||||||
});
|
});
|
||||||
|
@ -100,18 +102,20 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
deleteTag(tag: TagView) {
|
deleteTag(tag: TagView) {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
let titleKey: string, summaryKey: string;
|
let titleKey: string, summaryKey: string, content: string;
|
||||||
if (tag.verified) {
|
if (tag.signed) {
|
||||||
titleKey = 'REPOSITORY.DELETION_TITLE_TAG_DENIED';
|
titleKey = 'REPOSITORY.DELETION_TITLE_TAG_DENIED';
|
||||||
summaryKey = 'REPOSITORY.DELETION_SUMMARY_TAG_DENIED';
|
summaryKey = 'REPOSITORY.DELETION_SUMMARY_TAG_DENIED';
|
||||||
|
content = 'notary -s https://' + this.registryUrl + ' -d ~/.docker/trust remove -p ' + this.registryUrl + '/' + this.repoName + ':' + tag.tag;
|
||||||
} else {
|
} else {
|
||||||
titleKey = 'REPOSITORY.DELETION_TITLE_TAG';
|
titleKey = 'REPOSITORY.DELETION_TITLE_TAG';
|
||||||
summaryKey = 'REPOSITORY.DELETION_SUMMARY_TAG';
|
summaryKey = 'REPOSITORY.DELETION_SUMMARY_TAG';
|
||||||
|
content = tag.tag;
|
||||||
}
|
}
|
||||||
let message = new ConfirmationMessage(
|
let message = new ConfirmationMessage(
|
||||||
titleKey,
|
titleKey,
|
||||||
summaryKey,
|
summaryKey,
|
||||||
tag.tag,
|
content,
|
||||||
tag,
|
tag,
|
||||||
ConfirmationTargets.TAG);
|
ConfirmationTargets.TAG);
|
||||||
this.deletionDialogService.openComfirmDialog(message);
|
this.deletionDialogService.openComfirmDialog(message);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export class TagView {
|
export class TagView {
|
||||||
tag: string;
|
tag: string;
|
||||||
pullCommand: string;
|
pullCommand: string;
|
||||||
verified: boolean;
|
signed: boolean;
|
||||||
author: string;
|
author: string;
|
||||||
created: Date;
|
created: Date;
|
||||||
dockerVersion: string;
|
dockerVersion: string;
|
||||||
|
|
|
@ -1,75 +1,68 @@
|
||||||
<clr-modal [(clrModalOpen)]="createEditPolicyOpened">
|
<clr-modal [(clrModalOpen)]="createEditPolicyOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||||
<h3 class="modal-title">{{modalTitle}}</h3>
|
<h3 class="modal-title">{{modalTitle}}</h3>
|
||||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form #policyForm="ngForm">
|
<form #policyForm="ngForm">
|
||||||
<section class="form-block">
|
<section class="form-block">
|
||||||
<clr-alert [clrAlertType]="'alert-danger'" [(clrAlertClosed)]="!errorMessageOpened" (clrAlertClosedChange)="onErrorMessageClose()">
|
|
||||||
<div class="alert-item">
|
|
||||||
<span class="alert-text">
|
|
||||||
{{errorMessage}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</clr-alert>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="policy_name" class="col-md-4">{{'REPLICATION.NAME' | translate}}<span style="color: red">*</span></label>
|
<label for="policy_name" class="col-md-4 form-group-label-override">{{'REPLICATION.NAME' | translate}}<span style="color: red">*</span></label>
|
||||||
<label for="policy_name" class="col-md-8" aria-haspopup="true" role="tooltip" [class.invalid]="name.errors && (name.dirty || name.touched)" [class.valid]="name.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
<label for="policy_name" class="col-md-8" aria-haspopup="true" role="tooltip" [class.invalid]="name.errors && (name.dirty || name.touched)" [class.valid]="name.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||||
<input type="text" id="policy_name" [(ngModel)]="createEditPolicy.name" name="name" #name="ngModel" required>
|
<input type="text" id="policy_name" [(ngModel)]="createEditPolicy.name" name="name" #name="ngModel" required [disabled]="readonly">
|
||||||
<span class="tooltip-content" *ngIf="name.errors && name.errors.required && (name.dirty || name.touched)">
|
<span class="tooltip-content" *ngIf="name.errors && name.errors.required && (name.dirty || name.touched)">
|
||||||
{{'REPLICATION.NAME_IS_REQUIRED'}}
|
{{'REPLICATION.NAME_IS_REQUIRED' | translate}}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="policy_description" class="col-md-4">{{'REPLICATION.DESCRIPTION' | translate}}</label>
|
<label for="policy_description" class="col-md-4 form-group-label-override">{{'REPLICATION.DESCRIPTION' | translate}}</label>
|
||||||
<input type="text" class="col-md-8" id="policy_description" [(ngModel)]="createEditPolicy.description" name="description" size="20" #description="ngModel">
|
<input type="text" class="col-md-8" id="policy_description" [(ngModel)]="createEditPolicy.description" name="description" size="20" #description="ngModel" [disabled]="readonly">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-4">{{'REPLICATION.ENABLE' | translate}}</label>
|
<label class="col-md-4">{{'REPLICATION.ENABLE' | translate}}</label>
|
||||||
<div class="checkbox-inline">
|
<div class="checkbox-inline">
|
||||||
<input type="checkbox" id="policy_enable" [(ngModel)]="createEditPolicy.enable" name="enable" #enable="ngModel">
|
<input type="checkbox" id="policy_enable" [(ngModel)]="createEditPolicy.enable" name="enable" #enable="ngModel" [disabled]="untoggleable">
|
||||||
<label for="policy_enable"></label>
|
<label for="policy_enable"></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="destination_name" class="col-md-4">{{'REPLICATION.DESTINATION_NAME' | translate}}<span style="color: red">*</span></label>
|
<label for="destination_name" class="col-md-4 form-group-label-override">{{'REPLICATION.DESTINATION_NAME' | translate}}<span style="color: red">*</span></label>
|
||||||
<div class="select" *ngIf="!isCreateDestination">
|
<div class="select" *ngIf="!isCreateDestination">
|
||||||
<select id="destination_name" [(ngModel)]="createEditPolicy.targetId" name="targetId" (change)="selectTarget()" [disabled]="testOngoing">
|
<select id="destination_name" [(ngModel)]="createEditPolicy.targetId" name="targetId" (change)="selectTarget()" [disabled]="testOngoing || readonly">
|
||||||
<option *ngFor="let t of targets" [value]="t.id" [selected]="t.id == createEditPolicy.targetId">{{t.name}}</option>
|
<option *ngFor="let t of targets" [value]="t.id" [selected]="t.id == createEditPolicy.targetId">{{t.name}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<label class="col-md-8" *ngIf="isCreateDestination" for="destination_name" aria-haspopup="true" role="tooltip" [class.invalid]="targetName.errors && (targetName.dirty || targetName.touched)" [class.valid]="targetName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
<label class="col-md-8" *ngIf="isCreateDestination" for="destination_name" aria-haspopup="true" role="tooltip" [class.invalid]="targetName.errors && (targetName.dirty || targetName.touched)" [class.valid]="targetName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||||
<input type="text" id="destination_name" [(ngModel)]="createEditPolicy.targetName" name="targetName" size="20" #targetName="ngModel" value="" required>
|
<input type="text" id="destination_name" [(ngModel)]="createEditPolicy.targetName" name="targetName" size="8" #targetName="ngModel" value="" required>
|
||||||
<span class="tooltip-content" *ngIf="targetName.errors && targetName.errors.required && (targetName.dirty || targetName.touched)">
|
<span class="tooltip-content" *ngIf="targetName.errors && targetName.errors.required && (targetName.dirty || targetName.touched)">
|
||||||
{{'REPLICATION.DESTINATION_NAME_IS_REQUIRED' | translate}}
|
{{'REPLICATION.DESTINATION_NAME_IS_REQUIRED' | translate}}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="checkbox-inline">
|
<div class="checkbox-inline" *ngIf="showNewDestination">
|
||||||
<input type="checkbox" id="check_new" (click)="newDestination(checkedAddNew.checked)" #checkedAddNew [checked]="isCreateDestination" [disabled]="testOngoing">
|
<input type="checkbox" id="check_new" (click)="newDestination(checkedAddNew.checked)" #checkedAddNew [checked]="isCreateDestination" [disabled]="testOngoing || readonly">
|
||||||
<label for="check_new">{{'REPLICATION.NEW_DESTINATION' | translate}}</label>
|
<label for="check_new">{{'REPLICATION.NEW_DESTINATION' | translate}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="destination_url" class="col-md-4">{{'REPLICATION.DESTINATION_URL' | translate}}<span style="color: red">*</span></label>
|
<label for="destination_url" class="col-md-4 form-group-label-override">{{'REPLICATION.DESTINATION_URL' | translate}}<span style="color: red">*</span></label>
|
||||||
<label for="destination_url" class="col-md-8" aria-haspopup="true" role="tooltip" [class.invalid]="endpointUrl.errors && (endpointUrl.dirty || endpointUrl.touched)" [class.valid]="endpointUrl.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
<label for="destination_url" class="col-md-8" aria-haspopup="true" role="tooltip" [class.invalid]="endpointUrl.errors && (endpointUrl.dirty || endpointUrl.touched)" [class.valid]="endpointUrl.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||||
<input type="text" id="destination_url" [disabled]="testOngoing" [(ngModel)]="createEditPolicy.endpointUrl" size="20" name="endpointUrl" required #endpointUrl="ngModel">
|
<input type="text" id="destination_url" [disabled]="testOngoing || readonly" [(ngModel)]="createEditPolicy.endpointUrl" size="20" name="endpointUrl" required #endpointUrl="ngModel">
|
||||||
<span class="tooltip-content" *ngIf="endpointUrl.errors && endpointUrl.errors.required && (endpointUrl.dirty || endpointUrl.touched)">
|
<span class="tooltip-content" *ngIf="endpointUrl.errors && endpointUrl.errors.required && (endpointUrl.dirty || endpointUrl.touched)">
|
||||||
{{'REPLICATION.DESTINATION_URL_IS_REQUIRED' | translate}}
|
{{'REPLICATION.DESTINATION_URL_IS_REQUIRED' | translate}}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="destination_username" class="col-md-4">{{'REPLICATION.DESTINATION_USERNAME' | translate}}</label>
|
<label for="destination_username" class="col-md-4 form-group-label-override">{{'REPLICATION.DESTINATION_USERNAME' | translate}}</label>
|
||||||
<input type="text" class="col-md-8" id="destination_username" [disabled]="testOngoing" [(ngModel)]="createEditPolicy.username" size="20" name="username" #username="ngModel">
|
<input type="text" class="col-md-8" id="destination_username" [disabled]="testOngoing || readonly" [(ngModel)]="createEditPolicy.username" size="20" name="username" #username="ngModel">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="destination_password" class="col-md-4">{{'REPLICATION.DESTINATION_PASSWORD' | translate}}</label>
|
<label for="destination_password" class="col-md-4 form-group-label-override">{{'REPLICATION.DESTINATION_PASSWORD' | translate}}</label>
|
||||||
<input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing" [(ngModel)]="createEditPolicy.password" size="20" name="password" #password="ngModel">
|
<input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing || readonly" [(ngModel)]="createEditPolicy.password" size="20" name="password" #password="ngModel">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="spin" class="col-md-4"></label>
|
<label for="spin" class="col-md-4"></label>
|
||||||
<span class="col-md-8 spinner spinner-inline" [hidden]="!testOngoing"></span>
|
<span class="col-md-8 spinner spinner-inline" [hidden]="!testOngoing"></span>
|
||||||
<span [style.color]="!pingStatus ? 'red': ''">{{ pingTestMessage }}</span>
|
<span [style.color]="!pingStatus ? 'red': ''" class="form-group-label-override">{{ pingTestMessage }}</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -24,6 +24,7 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||||
modalTitle: string;
|
modalTitle: string;
|
||||||
createEditPolicyOpened: boolean;
|
createEditPolicyOpened: boolean;
|
||||||
createEditPolicy: CreateEditPolicy = new CreateEditPolicy();
|
createEditPolicy: CreateEditPolicy = new CreateEditPolicy();
|
||||||
|
initVal: CreateEditPolicy = new CreateEditPolicy();
|
||||||
|
|
||||||
actionType: ActionType;
|
actionType: ActionType;
|
||||||
|
|
||||||
|
@ -40,6 +41,9 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||||
|
|
||||||
policyForm: NgForm;
|
policyForm: NgForm;
|
||||||
|
|
||||||
|
staticBackdrop: boolean = true;
|
||||||
|
closable: boolean = false;
|
||||||
|
|
||||||
@ViewChild('policyForm')
|
@ViewChild('policyForm')
|
||||||
currentForm: NgForm;
|
currentForm: NgForm;
|
||||||
|
|
||||||
|
@ -48,6 +52,18 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||||
@ViewChild(InlineAlertComponent)
|
@ViewChild(InlineAlertComponent)
|
||||||
inlineAlert: InlineAlertComponent;
|
inlineAlert: InlineAlertComponent;
|
||||||
|
|
||||||
|
get readonly(): boolean {
|
||||||
|
return this.actionType === ActionType.EDIT && this.createEditPolicy.enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
get untoggleable(): boolean {
|
||||||
|
return this.actionType === ActionType.EDIT && this.initVal.enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
get showNewDestination(): boolean {
|
||||||
|
return this.actionType === ActionType.ADD_NEW || !this.createEditPolicy.enable;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private replicationService: ReplicationService,
|
private replicationService: ReplicationService,
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
|
@ -67,6 +83,11 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||||
this.createEditPolicy.endpointUrl = initialTarget.endpoint;
|
this.createEditPolicy.endpointUrl = initialTarget.endpoint;
|
||||||
this.createEditPolicy.username = initialTarget.username;
|
this.createEditPolicy.username = initialTarget.username;
|
||||||
this.createEditPolicy.password = initialTarget.password;
|
this.createEditPolicy.password = initialTarget.password;
|
||||||
|
|
||||||
|
this.initVal.targetId = this.createEditPolicy.targetId;
|
||||||
|
this.initVal.endpointUrl = this.createEditPolicy.endpointUrl;
|
||||||
|
this.initVal.username = this.createEditPolicy.username;
|
||||||
|
this.initVal.password = this.createEditPolicy.password;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error=>this.messageService.announceMessage(error.status, 'Error occurred while get targets.', AlertType.DANGER)
|
error=>this.messageService.announceMessage(error.status, 'Error occurred while get targets.', AlertType.DANGER)
|
||||||
|
@ -78,6 +99,7 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||||
openCreateEditPolicy(policyId?: number): void {
|
openCreateEditPolicy(policyId?: number): void {
|
||||||
this.createEditPolicyOpened = true;
|
this.createEditPolicyOpened = true;
|
||||||
this.createEditPolicy = new CreateEditPolicy();
|
this.createEditPolicy = new CreateEditPolicy();
|
||||||
|
|
||||||
this.isCreateDestination = false;
|
this.isCreateDestination = false;
|
||||||
|
|
||||||
this.hasChanged = false;
|
this.hasChanged = false;
|
||||||
|
@ -97,7 +119,11 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||||
this.createEditPolicy.name = policy.name;
|
this.createEditPolicy.name = policy.name;
|
||||||
this.createEditPolicy.description = policy.description;
|
this.createEditPolicy.description = policy.description;
|
||||||
this.createEditPolicy.enable = policy.enabled === 1? true : false;
|
this.createEditPolicy.enable = policy.enabled === 1? true : false;
|
||||||
this.prepareTargets(policy.target_id);
|
this.prepareTargets(policy.target_id);
|
||||||
|
|
||||||
|
this.initVal.name = this.createEditPolicy.name;
|
||||||
|
this.initVal.description = this.createEditPolicy.description;
|
||||||
|
this.initVal.enable = this.createEditPolicy.enable;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -218,12 +244,14 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||||
this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'});
|
this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'});
|
||||||
} else {
|
} else {
|
||||||
this.createEditPolicyOpened = false;
|
this.createEditPolicyOpened = false;
|
||||||
|
this.policyForm.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmCancel(confirmed: boolean) {
|
confirmCancel(confirmed: boolean) {
|
||||||
this.createEditPolicyOpened = false;
|
this.createEditPolicyOpened = false;
|
||||||
this.inlineAlert.close();
|
this.inlineAlert.close();
|
||||||
|
this.policyForm.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewChecked(): void {
|
ngAfterViewChecked(): void {
|
||||||
|
@ -231,24 +259,20 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||||
if(this.policyForm) {
|
if(this.policyForm) {
|
||||||
this.policyForm.valueChanges.subscribe(data=>{
|
this.policyForm.valueChanges.subscribe(data=>{
|
||||||
for(let i in data) {
|
for(let i in data) {
|
||||||
let item = data[i];
|
let origin = this.initVal[i];
|
||||||
if(typeof item === 'string' && (<string>item).trim().length !== 0) {
|
let current = data[i];
|
||||||
this.hasChanged = true;
|
if(current && current !== origin) {
|
||||||
break;
|
|
||||||
} else if (typeof item === 'boolean' && (<boolean>item)) {
|
|
||||||
this.hasChanged = true;
|
this.hasChanged = true;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
this.hasChanged = false;
|
this.hasChanged = false;
|
||||||
this.inlineAlert.close();
|
this.inlineAlert.close();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
testConnection() {
|
testConnection() {
|
||||||
this.pingStatus = true;
|
this.pingStatus = true;
|
||||||
this.translateService.get('REPLICATION.TESTING_CONNECTION').subscribe(res=>this.pingTestMessage=res);
|
this.translateService.get('REPLICATION.TESTING_CONNECTION').subscribe(res=>this.pingTestMessage=res);
|
||||||
|
|
|
@ -8,10 +8,17 @@
|
||||||
<clr-dg-row *ngFor="let p of policies;let i = index;" [clrDgItem]="p" (click)="selectPolicy(p)" [style.backgroundColor]="(!projectless && selectedId === p.id) ? '#eee' : ''">
|
<clr-dg-row *ngFor="let p of policies;let i = index;" [clrDgItem]="p" (click)="selectPolicy(p)" [style.backgroundColor]="(!projectless && selectedId === p.id) ? '#eee' : ''">
|
||||||
<clr-dg-action-overflow>
|
<clr-dg-action-overflow>
|
||||||
<button class="action-item" (click)="editPolicy(p)">{{'REPLICATION.EDIT_POLICY' | translate}}</button>
|
<button class="action-item" (click)="editPolicy(p)">{{'REPLICATION.EDIT_POLICY' | translate}}</button>
|
||||||
<button class="action-item" (click)="togglePolicy(p)">{{ (p.enabled === 0 ? 'REPLICATION.ENABLE' : 'REPLICATION.DISABLE') | translate}}</button>
|
<button class="action-item" (click)="togglePolicy(p)">{{ (p.enabled === 0 ? 'REPLICATION.TOGGLE_ENABLE_TITLE' : 'REPLICATION.TOGGLE_DISABLE_TITLE') | translate}}</button>
|
||||||
<button class="action-item" (click)="deletePolicy(p)">{{'REPLICATION.DELETE_POLICY' | translate}}</button>
|
<button class="action-item" (click)="deletePolicy(p)">{{'REPLICATION.DELETE_POLICY' | translate}}</button>
|
||||||
</clr-dg-action-overflow>
|
</clr-dg-action-overflow>
|
||||||
<clr-dg-cell>{{p.name}}</clr-dg-cell>
|
<clr-dg-cell>
|
||||||
|
<template [ngIf]="projectless">
|
||||||
|
<a href="javascript:void(0)" [routerLink]="['/harbor', 'projects', p.project_id, 'replication']">{{p.name}}</a>
|
||||||
|
</template>
|
||||||
|
<template [ngIf]="!projectless">
|
||||||
|
{{p.name}}
|
||||||
|
</template>
|
||||||
|
</clr-dg-cell>
|
||||||
<clr-dg-cell *ngIf="projectless">{{p.project_name}}</clr-dg-cell>
|
<clr-dg-cell *ngIf="projectless">{{p.project_name}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{p.description}}</clr-dg-cell>
|
<clr-dg-cell>{{p.description}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{p.target_name}}</clr-dg-cell>
|
<clr-dg-cell>{{p.target_name}}</clr-dg-cell>
|
||||||
|
|
|
@ -28,31 +28,53 @@ export class ListPolicyComponent implements OnDestroy {
|
||||||
@Output() editOne = new EventEmitter<number>();
|
@Output() editOne = new EventEmitter<number>();
|
||||||
@Output() toggleOne = new EventEmitter<Policy>();
|
@Output() toggleOne = new EventEmitter<Policy>();
|
||||||
|
|
||||||
|
toggleSubscription: Subscription;
|
||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private replicationService: ReplicationService,
|
private replicationService: ReplicationService,
|
||||||
|
private toggleConfirmDialogService: ConfirmationDialogService,
|
||||||
private deletionDialogService: ConfirmationDialogService,
|
private deletionDialogService: ConfirmationDialogService,
|
||||||
private messageService: MessageService) {
|
private messageService: MessageService) {
|
||||||
|
|
||||||
this.subscription = this.subscription = this.deletionDialogService
|
this.toggleSubscription = this.toggleConfirmDialogService
|
||||||
|
.confirmationConfirm$
|
||||||
|
.subscribe(
|
||||||
|
message=> {
|
||||||
|
if(message &&
|
||||||
|
message.source === ConfirmationTargets.TOGGLE_CONFIRM &&
|
||||||
|
message.state === ConfirmationState.CONFIRMED) {
|
||||||
|
let policy: Policy = message.data;
|
||||||
|
policy.enabled = policy.enabled === 0 ? 1 : 0;
|
||||||
|
console.log('Enable policy ID:' + policy.id + ' with activation status ' + policy.enabled);
|
||||||
|
this.replicationService
|
||||||
|
.enablePolicy(policy.id, policy.enabled)
|
||||||
|
.subscribe(
|
||||||
|
res => console.log('Successful toggled policy status'),
|
||||||
|
error => this.messageService.announceMessage(error.status, "Failed to toggle policy status.", AlertType.DANGER)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.subscription = this.deletionDialogService
|
||||||
.confirmationConfirm$
|
.confirmationConfirm$
|
||||||
.subscribe(
|
.subscribe(
|
||||||
message => {
|
message => {
|
||||||
if (message &&
|
if (message &&
|
||||||
message.source === ConfirmationTargets.POLICY &&
|
message.source === ConfirmationTargets.POLICY &&
|
||||||
message.state === ConfirmationState.CONFIRMED) {
|
message.state === ConfirmationState.CONFIRMED) {
|
||||||
this.replicationService
|
this.replicationService
|
||||||
.deletePolicy(message.data)
|
.deletePolicy(message.data)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response => {
|
response => {
|
||||||
console.log('Successful delete policy with ID:' + message.data);
|
console.log('Successful delete policy with ID:' + message.data);
|
||||||
this.reload.emit(true);
|
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, 'Failed to delete policy with ID:' + message.data, AlertType.DANGER)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +82,9 @@ export class ListPolicyComponent implements OnDestroy {
|
||||||
if (this.subscription) {
|
if (this.subscription) {
|
||||||
this.subscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
if(this.toggleSubscription) {
|
||||||
|
this.toggleSubscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectPolicy(policy: Policy): void {
|
selectPolicy(policy: Policy): void {
|
||||||
|
@ -74,13 +99,14 @@ export class ListPolicyComponent implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePolicy(policy: Policy) {
|
togglePolicy(policy: Policy) {
|
||||||
policy.enabled = policy.enabled === 0 ? 1 : 0;
|
let toggleConfirmMessage: ConfirmationMessage = new ConfirmationMessage(
|
||||||
console.log('Enable policy ID:' + policy.id + ' with activation status ' + policy.enabled);
|
policy.enabled === 1 ? 'REPLICATION.TOGGLE_DISABLE_TITLE' : 'REPLICATION.TOGGLE_ENABLE_TITLE',
|
||||||
this.replicationService.enablePolicy(policy.id, policy.enabled)
|
policy.enabled === 1 ? 'REPLICATION.CONFIRM_TOGGLE_DISABLE_POLICY': 'REPLICATION.CONFIRM_TOGGLE_ENABLE_POLICY',
|
||||||
.subscribe(
|
policy.name,
|
||||||
res => console.log('Successful toggled policy status'),
|
policy,
|
||||||
error => this.messageService.announceMessage(error.status, "Failed to toggle policy status.", AlertType.DANGER)
|
ConfirmationTargets.TOGGLE_CONFIRM
|
||||||
);
|
);
|
||||||
|
this.toggleConfirmDialogService.openComfirmDialog(toggleConfirmMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
deletePolicy(policy: Policy) {
|
deletePolicy(policy: Policy) {
|
||||||
|
|
|
@ -2,4 +2,9 @@
|
||||||
margin: 0px !important;
|
margin: 0px !important;
|
||||||
padding: 0px !important;
|
padding: 0px !important;
|
||||||
margin-top: -5px !important;
|
margin-top: -5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-pos {
|
||||||
|
margin-right: 0px !important;
|
||||||
|
top: 2px;
|
||||||
}
|
}
|
|
@ -4,82 +4,66 @@
|
||||||
<div class="form-group form-group-override">
|
<div class="form-group form-group-override">
|
||||||
<label for="username" class="required form-group-label-override">{{'PROFILE.USER_NAME' | translate}}</label>
|
<label for="username" class="required form-group-label-override">{{'PROFILE.USER_NAME' | translate}}</label>
|
||||||
<label for="username" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("username")'>
|
<label for="username" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("username")'>
|
||||||
<input type="text" placeholder='{{"PLACEHOLDER.USER_NAME" | translate}}' required pattern='[^"~#$%]+' maxLengthExt="20" #usernameInput="ngModel" name="username" [(ngModel)]="newUser.username" id="username" size="28"
|
<input type="text" required pattern='[^"~#$%]+' maxLengthExt="20" #usernameInput="ngModel" name="username" [(ngModel)]="newUser.username" id="username" size="40"
|
||||||
(input)='handleValidation("username", false)'
|
(input)='handleValidation("username", false)'
|
||||||
(focusout)='handleValidation("username", true)'>
|
(focusout)='handleValidation("username", true)'>
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
{{usernameTooltip | translate}}
|
{{usernameTooltip | translate}}
|
||||||
</span>
|
</span>
|
||||||
</label><span class="spinner spinner-inline" [hidden]='isChecking("username")'></span>
|
</label><span class="spinner spinner-inline spinner-pos" [hidden]='isChecking("username")'></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-group-override">
|
<div class="form-group form-group-override">
|
||||||
<label for="email" class="required form-group-label-override">{{'PROFILE.EMAIL' | translate}}</label>
|
<label for="email" class="required form-group-label-override">{{'PROFILE.EMAIL' | translate}}</label>
|
||||||
<label for="email" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("email")'>
|
<label for="email" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("email")'>
|
||||||
<input name="email" type="text" #eamilInput="ngModel" [(ngModel)]="newUser.email"
|
<input name="email" type="text" #eamilInput="ngModel" [(ngModel)]="newUser.email"
|
||||||
placeholder='{{"PLACEHOLDER.MAIL" | translate}}'
|
|
||||||
required
|
required
|
||||||
pattern='^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$' id="email" size="28"
|
pattern='^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$' id="email" size="40"
|
||||||
(input)='handleValidation("email", false)'
|
(input)='handleValidation("email", false)'
|
||||||
(focusout)='handleValidation("email", true)'>
|
(focusout)='handleValidation("email", true)'>
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
{{emailTooltip | translate}}
|
{{emailTooltip | translate}}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label *ngIf="isSelfRegistration" role="tooltip" aria-haspopup="true" class="tooltip tooltip-bottom-left">
|
<span class="spinner spinner-inline spinner-pos" [hidden]='isChecking("email")'></span>
|
||||||
<clr-icon shape="info" class="is-info" size="24"></clr-icon>
|
<label class="sub-label-for-input" *ngIf="isSelfRegistration">{{'TOOLTIP.SIGN_UP_MAIL' | translate}}</label>
|
||||||
<span class="tooltip-content">
|
|
||||||
{{'TOOLTIP.SIGN_UP_MAIL' | translate}}
|
|
||||||
</span>
|
|
||||||
</label><span class="spinner spinner-inline" [hidden]='isChecking("email")'></span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-group-override">
|
<div class="form-group form-group-override">
|
||||||
<label for="realname" class="required form-group-label-override">{{'PROFILE.FULL_NAME' | translate}}</label>
|
<label for="realname" class="required form-group-label-override">{{'PROFILE.FULL_NAME' | translate}}</label>
|
||||||
<label for="realname" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("realname")'>
|
<label for="realname" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("realname")'>
|
||||||
<input type="text" placeholder='{{"PLACEHOLDER.FULL_NAME" | translate}}' name="realname" #fullNameInput="ngModel" [(ngModel)]="newUser.realname" required maxLengthExt="20" id="realname" size="28"
|
<input type="text" name="realname" #fullNameInput="ngModel" [(ngModel)]="newUser.realname" required maxLengthExt="20" id="realname" size="40"
|
||||||
(input)='handleValidation("realname", false)'
|
(input)='handleValidation("realname", false)'
|
||||||
(focusout)='handleValidation("realname", true)'>
|
(focusout)='handleValidation("realname", true)'>
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
{{'TOOLTIP.FULL_NAME' | translate}}
|
{{'TOOLTIP.FULL_NAME' | translate}}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label *ngIf="isSelfRegistration" role="tooltip" aria-haspopup="true" class="tooltip tooltip-bottom-left">
|
|
||||||
<clr-icon shape="info" class="is-info" size="24"></clr-icon>
|
|
||||||
<span class="tooltip-content">
|
|
||||||
{{'TOOLTIP.SIGN_UP_REAL_NAME' | translate}}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-group-override">
|
<div class="form-group form-group-override">
|
||||||
<label for="newPassword" class="required form-group-label-override">{{'PROFILE.PASSWORD' | translate}}</label>
|
<label for="newPassword" class="required form-group-label-override">{{'PROFILE.PASSWORD' | translate}}</label>
|
||||||
<label for="newPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("newPassword")'>
|
<label for="newPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("newPassword")'>
|
||||||
<input type="password" id="newPassword" placeholder='{{"PLACEHOLDER.NEW_PWD" | translate}}'
|
<input type="password" id="newPassword"
|
||||||
required
|
required
|
||||||
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{7,}$"
|
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$"
|
||||||
name="newPassword"
|
name="newPassword"
|
||||||
[(ngModel)]="newUser.password"
|
[(ngModel)]="newUser.password"
|
||||||
#newPassInput="ngModel" size="28"
|
#newPassInput="ngModel" size="40"
|
||||||
(input)='handleValidation("newPassword", false)'
|
(input)='handleValidation("newPassword", false)'
|
||||||
(focusout)='handleValidation("newPassword", true)'>
|
(focusout)='handleValidation("newPassword", true)'>
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
{{'TOOLTIP.PASSWORD' | translate}}
|
{{'TOOLTIP.PASSWORD' | translate}}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label *ngIf="isSelfRegistration" role="tooltip" aria-haspopup="true" class="tooltip tooltip-bottom-left">
|
<label class="sub-label-for-input" *ngIf="isSelfRegistration">{{'CHANGE_PWD.PASS_TIPS' | translate}}</label>
|
||||||
<clr-icon shape="info" class="is-info" size="24"></clr-icon>
|
|
||||||
<span class="tooltip-content">
|
|
||||||
{{'TOOLTIP.PASSWORD' | translate}}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-group-override">
|
<div class="form-group form-group-override">
|
||||||
<label for="confirmPassword" class="required form-group-label-override">{{'CHANGE_PWD.CONFIRM_PWD' | translate}}</label>
|
<label for="confirmPassword" class="required form-group-label-override">{{'CHANGE_PWD.CONFIRM_PWD' | translate}}</label>
|
||||||
<label for="confirmPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("confirmPassword")'>
|
<label for="confirmPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("confirmPassword")'>
|
||||||
<input type="password" id="confirmPassword" placeholder='{{"PLACEHOLDER.CONFIRM_PWD" | translate}}'
|
<input type="password" id="confirmPassword"
|
||||||
required
|
required
|
||||||
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{7,}$"
|
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$"
|
||||||
name="confirmPassword"
|
name="confirmPassword"
|
||||||
[(ngModel)]="confirmedPwd"
|
[(ngModel)]="confirmedPwd"
|
||||||
#confirmPassInput="ngModel" size="28"
|
#confirmPassInput="ngModel" size="40"
|
||||||
(input)='handleValidation("confirmPassword", false)'
|
(input)='handleValidation("confirmPassword", false)'
|
||||||
(focusout)='handleValidation("confirmPassword", true)'>
|
(focusout)='handleValidation("confirmPassword", true)'>
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
|
@ -90,7 +74,7 @@
|
||||||
<div class="form-group form-group-override">
|
<div class="form-group form-group-override">
|
||||||
<label for="comment" class="form-group-label-override">{{'PROFILE.COMMENT' | translate}}</label>
|
<label for="comment" class="form-group-label-override">{{'PROFILE.COMMENT' | translate}}</label>
|
||||||
<label for="comment" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("comment")'>
|
<label for="comment" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("comment")'>
|
||||||
<input type="text" #commentInput="ngModel" name="comment" [(ngModel)]="newUser.comment" maxLengthExt="20" id="comment" size="28"
|
<input type="text" #commentInput="ngModel" name="comment" [(ngModel)]="newUser.comment" maxLengthExt="20" id="comment" size="40"
|
||||||
(input)='handleValidation("comment", false)'
|
(input)='handleValidation("comment", false)'
|
||||||
(focusout)='handleValidation("comment", true)'>
|
(focusout)='handleValidation("comment", true)'>
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
|
@ -100,5 +84,4 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div style="height: 15px;"></div>
|
|
|
@ -6,13 +6,11 @@ import {
|
||||||
CanActivateChild,
|
CanActivateChild,
|
||||||
NavigationExtras
|
NavigationExtras
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
import { SessionService } from '../../shared/session.service';
|
import { SessionService } from '../../shared/session.service';
|
||||||
import { CommonRoutes, AdmiralQueryParamKey } from '../../shared/shared.const';
|
import { CommonRoutes, AdmiralQueryParamKey } from '../../shared/shared.const';
|
||||||
import { AppConfigService } from '../../app-config.service';
|
import { AppConfigService } from '../../app-config.service';
|
||||||
import { maintainUrlQueryParmas } from '../../shared/shared.utils';
|
import { maintainUrlQueryParmas } from '../../shared/shared.utils';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthCheckGuard implements CanActivate, CanActivateChild {
|
export class AuthCheckGuard implements CanActivate, CanActivateChild {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -50,7 +48,6 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild {
|
||||||
|
|
||||||
this.router.navigateByUrl(keyRemovedUrl);
|
this.router.navigateByUrl(keyRemovedUrl);
|
||||||
return resolve(false);
|
return resolve(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ export const enum ConfirmationTargets {
|
||||||
PROJECT_MEMBER,
|
PROJECT_MEMBER,
|
||||||
USER,
|
USER,
|
||||||
POLICY,
|
POLICY,
|
||||||
|
TOGGLE_CONFIRM,
|
||||||
TARGET,
|
TARGET,
|
||||||
REPOSITORY,
|
REPOSITORY,
|
||||||
TAG,
|
TAG,
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { Directive, OnChanges, Input, SimpleChanges } from '@angular/core';
|
||||||
|
import { NG_ASYNC_VALIDATORS, Validator, Validators, ValidatorFn, AbstractControl } from '@angular/forms';
|
||||||
|
|
||||||
|
import { ProjectService} from '../project/project.service';
|
||||||
|
|
||||||
|
import { MemberService } from '../project/member/member.service';
|
||||||
|
import { Member } from '../project/member/member';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[targetExists]',
|
||||||
|
providers: [
|
||||||
|
ProjectService, MemberService,
|
||||||
|
{ provide: NG_ASYNC_VALIDATORS, useExisting: TargetExistsValidatorDirective, multi: true},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class TargetExistsValidatorDirective implements Validator, OnChanges {
|
||||||
|
@Input() targetExists: string;
|
||||||
|
@Input() projectId: number;
|
||||||
|
|
||||||
|
private valFn = Validators.nullValidator;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private projectService: ProjectService,
|
||||||
|
private memberService: MemberService) {}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
const change = changes['targetExists'];
|
||||||
|
if (change) {
|
||||||
|
const target: string = change.currentValue;
|
||||||
|
this.valFn = this.targetExistsValidator(target);
|
||||||
|
} else {
|
||||||
|
this.valFn = Validators.nullValidator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validate(control: AbstractControl): {[key: string]: any} {
|
||||||
|
return this.valFn(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
targetExistsValidator(target: string): ValidatorFn {
|
||||||
|
return (control: AbstractControl): {[key: string]: any} => {
|
||||||
|
console.log('Target:' + target + ', validate value:' + control.value);
|
||||||
|
switch(target) {
|
||||||
|
case 'PROJECT_NAME':
|
||||||
|
return new Promise(resolve=>{
|
||||||
|
this.projectService
|
||||||
|
.checkProjectExists(control.value)
|
||||||
|
.subscribe(res=>resolve({'targetExists': true}),error=>resolve(null));
|
||||||
|
});
|
||||||
|
case 'MEMBER_NAME':
|
||||||
|
return new Promise(resolve=>{
|
||||||
|
this.memberService
|
||||||
|
.listMembers(this.projectId, control.value)
|
||||||
|
.subscribe((members: Member[])=>{
|
||||||
|
return members.filter(m=>{
|
||||||
|
if(m.username === control.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}).length > 0 ?
|
||||||
|
resolve({'targetExists': true}) : resolve(null);
|
||||||
|
},error=>resolve(null));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,7 +56,7 @@
|
||||||
"TITLE": "User Profile",
|
"TITLE": "User Profile",
|
||||||
"USER_NAME": "Username",
|
"USER_NAME": "Username",
|
||||||
"EMAIL": "Email",
|
"EMAIL": "Email",
|
||||||
"FULL_NAME": "Full name",
|
"FULL_NAME": "First and last name",
|
||||||
"COMMENT": "Comments",
|
"COMMENT": "Comments",
|
||||||
"PASSWORD": "Password",
|
"PASSWORD": "Password",
|
||||||
"SAVE_SUCCESS": "User profile saved successfully"
|
"SAVE_SUCCESS": "User profile saved successfully"
|
||||||
|
@ -66,7 +66,8 @@
|
||||||
"CURRENT_PWD": "Current Password",
|
"CURRENT_PWD": "Current Password",
|
||||||
"NEW_PWD": "New Password",
|
"NEW_PWD": "New Password",
|
||||||
"CONFIRM_PWD": "Confirm Password",
|
"CONFIRM_PWD": "Confirm Password",
|
||||||
"SAVE_SUCCESS": "User password changed successfully"
|
"SAVE_SUCCESS": "User password changed successfully",
|
||||||
|
"PASS_TIPS": "At least 8 chars with 1 uppercase, 1 lowercase and 1 number"
|
||||||
},
|
},
|
||||||
"ACCOUNT_SETTINGS": {
|
"ACCOUNT_SETTINGS": {
|
||||||
"PROFILE": "User Profile",
|
"PROFILE": "User Profile",
|
||||||
|
@ -152,6 +153,7 @@
|
||||||
"DELETE": "Delete",
|
"DELETE": "Delete",
|
||||||
"ITEMS": "item(s)",
|
"ITEMS": "item(s)",
|
||||||
"ACTIONS": "Actions",
|
"ACTIONS": "Actions",
|
||||||
|
"USERNAME_IS_REQUIRED": "Username is required",
|
||||||
"USERNAME_DOES_NOT_EXISTS": "Username does not exist.",
|
"USERNAME_DOES_NOT_EXISTS": "Username does not exist.",
|
||||||
"USERNAME_ALREADY_EXISTS": "Username already exists.",
|
"USERNAME_ALREADY_EXISTS": "Username already exists.",
|
||||||
"UNKNOWN_ERROR": "Unknown error occurred while adding member.",
|
"UNKNOWN_ERROR": "Unknown error occurred while adding member.",
|
||||||
|
@ -229,7 +231,11 @@
|
||||||
"CREATION_TIME": "Start Time",
|
"CREATION_TIME": "Start Time",
|
||||||
"END_TIME": "End Time",
|
"END_TIME": "End Time",
|
||||||
"LOGS": "Logs",
|
"LOGS": "Logs",
|
||||||
"ITEMS": "item(s)"
|
"ITEMS": "item(s)",
|
||||||
|
"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."
|
||||||
},
|
},
|
||||||
"DESTINATION": {
|
"DESTINATION": {
|
||||||
"NEW_ENDPOINT": "New Endpoint",
|
"NEW_ENDPOINT": "New Endpoint",
|
||||||
|
@ -268,7 +274,7 @@
|
||||||
"DELETION_TITLE_TAG": "Confirm Tag Deletion",
|
"DELETION_TITLE_TAG": "Confirm Tag Deletion",
|
||||||
"DELETION_SUMMARY_TAG": "Do you want to delete tag {{param}}?",
|
"DELETION_SUMMARY_TAG": "Do you want to delete tag {{param}}?",
|
||||||
"DELETION_TITLE_TAG_DENIED": "Signed Tag can't be deleted",
|
"DELETION_TITLE_TAG_DENIED": "Signed Tag can't be deleted",
|
||||||
"DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted.",
|
"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 for repositories",
|
||||||
"TAG": "Tag",
|
"TAG": "Tag",
|
||||||
"SIGNED": "Signed",
|
"SIGNED": "Signed",
|
||||||
|
|
|
@ -66,7 +66,8 @@
|
||||||
"CURRENT_PWD": "当前密码",
|
"CURRENT_PWD": "当前密码",
|
||||||
"NEW_PWD": "新密码",
|
"NEW_PWD": "新密码",
|
||||||
"CONFIRM_PWD": "确认密码",
|
"CONFIRM_PWD": "确认密码",
|
||||||
"SAVE_SUCCESS": "更改用户密码成功"
|
"SAVE_SUCCESS": "更改用户密码成功",
|
||||||
|
"PASS_TIPS": "至少8个字符且需包含至少一个大写字符、小写字符或者数字"
|
||||||
},
|
},
|
||||||
"ACCOUNT_SETTINGS": {
|
"ACCOUNT_SETTINGS": {
|
||||||
"PROFILE": "用户设置",
|
"PROFILE": "用户设置",
|
||||||
|
@ -152,8 +153,9 @@
|
||||||
"DELETE": "删除",
|
"DELETE": "删除",
|
||||||
"ITEMS": "条记录",
|
"ITEMS": "条记录",
|
||||||
"ACTIONS": "操作",
|
"ACTIONS": "操作",
|
||||||
"USERNAME_DOES_NOT_EXISTS": "用户名不存在",
|
"USERNAME_IS_REQUIRED": "用户名为必填项。",
|
||||||
"USERNAME_ALREADY_EXISTS": "用户名已存在",
|
"USERNAME_DOES_NOT_EXISTS": "用户名不存在。",
|
||||||
|
"USERNAME_ALREADY_EXISTS": "用户名已存在。",
|
||||||
"UNKNOWN_ERROR": "添加成员时发生未知错误。",
|
"UNKNOWN_ERROR": "添加成员时发生未知错误。",
|
||||||
"FILTER_PLACEHOLDER": "过滤成员",
|
"FILTER_PLACEHOLDER": "过滤成员",
|
||||||
"DELETION_TITLE": "删除项目成员确认",
|
"DELETION_TITLE": "删除项目成员确认",
|
||||||
|
@ -229,7 +231,11 @@
|
||||||
"CREATION_TIME": "创建时间",
|
"CREATION_TIME": "创建时间",
|
||||||
"END_TIME": "结束时间",
|
"END_TIME": "结束时间",
|
||||||
"LOGS": "日志",
|
"LOGS": "日志",
|
||||||
"ITEMS": "条记录"
|
"ITEMS": "条记录",
|
||||||
|
"TOGGLE_ENABLE_TITLE": "启用策略",
|
||||||
|
"CONFIRM_TOGGLE_ENABLE_POLICY": "启用策略后,该项目下的所有镜像仓库将复制到目标实例。请确认继续。",
|
||||||
|
"TOGGLE_DISABLE_TITLE": "停用策略",
|
||||||
|
"CONFIRM_TOGGLE_DISABLE_POLICY": "停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。"
|
||||||
},
|
},
|
||||||
"DESTINATION": {
|
"DESTINATION": {
|
||||||
"NEW_ENDPOINT": "新建目标",
|
"NEW_ENDPOINT": "新建目标",
|
||||||
|
@ -268,7 +274,7 @@
|
||||||
"DELETION_TITLE_TAG": "删除镜像标签确认",
|
"DELETION_TITLE_TAG": "删除镜像标签确认",
|
||||||
"DELETION_SUMMARY_TAG": "确认删除镜像标签 {{param}}?",
|
"DELETION_SUMMARY_TAG": "确认删除镜像标签 {{param}}?",
|
||||||
"DELETION_TITLE_TAG_DENIED": "已签名的镜像不能被删除",
|
"DELETION_TITLE_TAG_DENIED": "已签名的镜像不能被删除",
|
||||||
"DELETION_SUMMARY_TAG_DENIED": "要删除此镜像标签必须首先从Notary中删除。",
|
"DELETION_SUMMARY_TAG_DENIED": "要删除此镜像标签必须首先从Notary中删除。{{param}}",
|
||||||
"FILTER_FOR_REPOSITORIES": "过滤镜像仓库",
|
"FILTER_FOR_REPOSITORIES": "过滤镜像仓库",
|
||||||
"TAG": "标签",
|
"TAG": "标签",
|
||||||
"SIGNED": "已签名",
|
"SIGNED": "已签名",
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
.datagrid-content-wrapper {
|
.datagrid-content-wrapper {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group-label-override {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
|
@ -31,5 +31,6 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- /data/config/:/etc/adminserver/
|
- /data/config/:/etc/adminserver/
|
||||||
- /data/secretkey:/etc/adminserver/key
|
- /data/secretkey:/etc/adminserver/key
|
||||||
|
- /data/:/data/
|
||||||
ports:
|
ports:
|
||||||
- 8888:80
|
- 8888:80
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TIMEOUT=10
|
||||||
|
while [ $TIMEOUT -gt 0 ]; do
|
||||||
|
STATUS=$(curl -s -o /dev/null -w '%{http_code}' https://127.0.0.1/notary/v2/ -kv)
|
||||||
|
if [ $STATUS -eq 401 ]; then
|
||||||
|
echo "Notary is running success."
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
TIMEOUT=$(($TIMEOUT - 1))
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $TIMEOUT -eq 0 ]; then
|
||||||
|
echo "Notary is running fail."
|
||||||
|
exit 1
|
||||||
|
fi
|
Loading…
Reference in New Issue