mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-12 19:00:50 +01:00
Merge pull request #3822 from ywk253100/171219_merge
Sync with master branch
This commit is contained in:
commit
fe9c423ca7
@ -70,7 +70,7 @@ install:
|
|||||||
before_script:
|
before_script:
|
||||||
# create tables and load data
|
# create tables and load data
|
||||||
# - mysql < ./make/db/registry.sql -uroot --verbose
|
# - mysql < ./make/db/registry.sql -uroot --verbose
|
||||||
- sudo sqlite3 /tmp/registry.db < make/common/db/registry_sqlite.sql
|
- sudo sqlite3 /tmp/registry.db < make/photon/db/registry_sqlite.sql
|
||||||
- sudo chmod 777 /tmp/registry.db
|
- sudo chmod 777 /tmp/registry.db
|
||||||
|
|
||||||
script:
|
script:
|
||||||
@ -82,6 +82,12 @@ script:
|
|||||||
- sudo make run_clarity_ut CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.2.7
|
- sudo make run_clarity_ut CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.2.7
|
||||||
- cat ./src/ui_ng/lib/npm-ut-test-results
|
- cat ./src/ui_ng/lib/npm-ut-test-results
|
||||||
- sudo ./tests/testprepare.sh
|
- sudo ./tests/testprepare.sh
|
||||||
|
- sudo make -f make/photon/Makefile -e MARIADBVERSION=10.2.10 -e VERSIONTAG=dev
|
||||||
|
- sudo make -f make/photon/Makefile _build_registry -e REGISTRYVERSION=v2.6.2 -e VERSIONTAG=dev
|
||||||
|
- sudo sed -i 's/__reg_version__/v2.6.2-dev/g' ./make/docker-compose.test.yml
|
||||||
|
- sudo sed -i 's/__version__/dev/g' ./make/docker-compose.test.yml
|
||||||
|
- sudo mkdir -p ./make/common/config/registry/
|
||||||
|
- sudo mv ./tests/reg_config.yml ./make/common/config/registry/config.yml
|
||||||
- sudo docker-compose -f ./make/docker-compose.test.yml up -d
|
- sudo docker-compose -f ./make/docker-compose.test.yml up -d
|
||||||
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 fgt golint
|
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 fgt golint
|
||||||
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 go vet
|
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 go vet
|
||||||
|
162
Makefile
162
Makefile
@ -12,12 +12,8 @@
|
|||||||
# 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 (default: build_photon)
|
# build: build Harbor docker images from photon baseimage
|
||||||
# for example: make build -e BASEIMAGE=photon
|
|
||||||
# build_photon: build Harbor docker images from photon baseimage
|
|
||||||
#
|
#
|
||||||
# build_postgresql: build postgresql images basaed on photon os
|
|
||||||
# make build -e BASEIMAGE=postgresql
|
|
||||||
# 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
|
||||||
#
|
#
|
||||||
@ -75,26 +71,40 @@ 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
|
|
||||||
COMPILETAG=compile_normal
|
# parameters
|
||||||
REGISTRYSERVER=
|
REGISTRYSERVER=
|
||||||
REGISTRYPROJECTNAME=vmware
|
REGISTRYPROJECTNAME=vmware
|
||||||
DEVFLAG=true
|
DEVFLAG=true
|
||||||
NOTARYFLAG=false
|
NOTARYFLAG=false
|
||||||
REGISTRYVERSION=2.6.2-photon
|
CLAIRFLAG=false
|
||||||
NGINXVERSION=1.11.13
|
|
||||||
PHOTONVERSION=1.0
|
|
||||||
NOTARYVERSION=server-0.5.1
|
|
||||||
NOTARYSIGNERVERSION=signer-0.5.1
|
|
||||||
MARIADBVERSION=10.2.10
|
|
||||||
HTTPPROXY=
|
HTTPPROXY=
|
||||||
REBUILDCLARITYFLAG=false
|
REBUILDCLARITYFLAG=false
|
||||||
NEWCLARITYVERSION=
|
NEWCLARITYVERSION=
|
||||||
|
BUILDBIN=false
|
||||||
|
MIGRATORFLAG=false
|
||||||
|
|
||||||
#clair parameters
|
# version prepare
|
||||||
CLAIRVERSION=v2.0.1-photon
|
VERSIONFILEPATH=$(CURDIR)
|
||||||
CLAIRFLAG=false
|
VERSIONFILENAME=VERSION
|
||||||
CLAIRDBVERSION=9.6.5-photon
|
GITCMD=$(shell which git)
|
||||||
|
GITTAG=$(GITCMD) describe --tags
|
||||||
|
GITTAGVERSION=$(shell git describe --tags || echo UNKNOWN)
|
||||||
|
ifeq ($(DEVFLAG), true)
|
||||||
|
VERSIONTAG=dev
|
||||||
|
else
|
||||||
|
VERSIONTAG=$(GITTAGVERSION)
|
||||||
|
endif
|
||||||
|
|
||||||
|
#versions
|
||||||
|
REGISTRYVERSION=v2.6.2
|
||||||
|
NGINXVERSION=$(VERSIONTAG)
|
||||||
|
PHOTONVERSION=1.0
|
||||||
|
NOTARYVERSION=v0.5.1
|
||||||
|
MARIADBVERSION=$(VERSIONTAG)
|
||||||
|
CLAIRVERSION=v2.0.1
|
||||||
|
CLAIRDBVERSION=$(VERSIONTAG)
|
||||||
|
MIGRATORVERSION=1.3
|
||||||
|
|
||||||
#clarity parameters
|
#clarity parameters
|
||||||
CLARITYIMAGE=vmware/harbor-clarity-ui-builder[:tag]
|
CLARITYIMAGE=vmware/harbor-clarity-ui-builder[:tag]
|
||||||
@ -134,13 +144,10 @@ GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice
|
|||||||
GOLANGDOCKERFILENAME=Dockerfile.golang
|
GOLANGDOCKERFILENAME=Dockerfile.golang
|
||||||
|
|
||||||
# binary
|
# binary
|
||||||
ADMINSERVERSOURCECODE=$(SRCPATH)/adminserver
|
|
||||||
ADMINSERVERBINARYPATH=$(MAKEDEVPATH)/adminserver
|
ADMINSERVERBINARYPATH=$(MAKEDEVPATH)/adminserver
|
||||||
ADMINSERVERBINARYNAME=harbor_adminserver
|
ADMINSERVERBINARYNAME=harbor_adminserver
|
||||||
UISOURCECODE=$(SRCPATH)/ui
|
|
||||||
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
||||||
UIBINARYNAME=harbor_ui
|
UIBINARYNAME=harbor_ui
|
||||||
JOBSERVICESOURCECODE=$(SRCPATH)/jobservice
|
|
||||||
JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice
|
JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice
|
||||||
JOBSERVICEBINARYNAME=harbor_jobservice
|
JOBSERVICEBINARYNAME=harbor_jobservice
|
||||||
|
|
||||||
@ -164,14 +171,8 @@ MAKEFILEPATH_PHOTON=$(MAKEPATH)/photon
|
|||||||
|
|
||||||
# common dockerfile
|
# common dockerfile
|
||||||
DOCKERFILEPATH_COMMON=$(MAKEPATH)/common
|
DOCKERFILEPATH_COMMON=$(MAKEPATH)/common
|
||||||
DOCKERFILEPATH_DB=$(DOCKERFILEPATH_COMMON)/db
|
|
||||||
DOCKERFILENAME_DB=Dockerfile
|
|
||||||
DOCKERFILE_CLARITY=$(MAKEPATH)/dev/nodeclarity/Dockerfile
|
DOCKERFILE_CLARITY=$(MAKEPATH)/dev/nodeclarity/Dockerfile
|
||||||
|
|
||||||
DOCKERFILEPATH_POSTGRESQL=$(DOCKERFILEPATH_COMMON)/postgresql
|
|
||||||
DOCKERFILENAME_POSTGRESQL=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
|
||||||
@ -179,26 +180,16 @@ 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
|
||||||
DOCKERIMAGENAME_CLARITY=vmware/harbor-clarity-ui-builder
|
DOCKERIMAGENAME_CLARITY=vmware/harbor-clarity-ui-builder
|
||||||
DOCKERIMAGENAME_POSTGRESQL=vmware/postgresql
|
|
||||||
# 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
|
||||||
|
DOCKERCOMPOSENOTARYTPLFILENAME=docker-compose.notary.tpl
|
||||||
DOCKERCOMPOSENOTARYFILENAME=docker-compose.notary.yml
|
DOCKERCOMPOSENOTARYFILENAME=docker-compose.notary.yml
|
||||||
|
DOCKERCOMPOSECLAIRTPLFILENAME=docker-compose.clair.tpl
|
||||||
DOCKERCOMPOSECLAIRFILENAME=docker-compose.clair.yml
|
DOCKERCOMPOSECLAIRFILENAME=docker-compose.clair.yml
|
||||||
|
|
||||||
# version prepare
|
|
||||||
VERSIONFILEPATH=$(CURDIR)
|
|
||||||
VERSIONFILENAME=VERSION
|
|
||||||
GITCMD=$(shell which git)
|
|
||||||
GITTAG=$(GITCMD) describe --tags
|
|
||||||
GITTAGVERSION=$(shell git describe --tags || echo UNKNOWN)
|
|
||||||
ifeq ($(DEVFLAG), true)
|
|
||||||
VERSIONTAG=dev
|
|
||||||
else
|
|
||||||
VERSIONTAG=$(GITTAGVERSION)
|
|
||||||
endif
|
|
||||||
|
|
||||||
SEDCMD=$(shell which sed)
|
SEDCMD=$(shell which sed)
|
||||||
|
|
||||||
# package
|
# package
|
||||||
@ -213,42 +204,36 @@ PUSHSCRIPTNAME=pushimage.sh
|
|||||||
REGISTRYUSER=user
|
REGISTRYUSER=user
|
||||||
REGISTRYPASSWORD=default
|
REGISTRYPASSWORD=default
|
||||||
|
|
||||||
# migrator
|
|
||||||
MIGRATORVERSION=1.3
|
|
||||||
MIGRATORFLAG=false
|
|
||||||
|
|
||||||
# cmds
|
# cmds
|
||||||
DOCKERSAVE_PARA=$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
DOCKERSAVE_PARA=$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||||
vmware/nginx-photon:$(NGINXVERSION) vmware/registry:$(REGISTRYVERSION) \
|
vmware/nginx-photon:$(NGINXVERSION)-$(VERSIONTAG) vmware/registry-photon:$(REGISTRYVERSION)-$(VERSIONTAG) \
|
||||||
vmware/photon:$(PHOTONVERSION)
|
vmware/photon:$(PHOTONVERSION)
|
||||||
PACKAGE_OFFLINE_PARA=-zcvf harbor-offline-installer-$(GITTAGVERSION).tgz \
|
PACKAGE_OFFLINE_PARA=-zcvf harbor-offline-installer-$(GITTAGVERSION).tgz \
|
||||||
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tar.gz \
|
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tar.gz \
|
||||||
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
||||||
$(HARBORPKG)/upgrade $(HARBORPKG)/harbor_1_1_0_template \
|
|
||||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
||||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||||
$(HARBORPKG)/ha
|
$(HARBORPKG)/ha
|
||||||
PACKAGE_ONLINE_PARA=-zcvf harbor-online-installer-$(GITTAGVERSION).tgz \
|
PACKAGE_ONLINE_PARA=-zcvf harbor-online-installer-$(GITTAGVERSION).tgz \
|
||||||
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
||||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
||||||
$(HARBORPKG)/upgrade $(HARBORPKG)/harbor_1_1_0_template \
|
|
||||||
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/ha
|
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/ha
|
||||||
DOCKERCOMPOSE_LIST=-f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
DOCKERCOMPOSE_LIST=-f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||||
|
|
||||||
ifeq ($(NOTARYFLAG), true)
|
ifeq ($(NOTARYFLAG), true)
|
||||||
DOCKERSAVE_PARA+= vmware/notary-photon:$(NOTARYVERSION) vmware/notary-photon:$(NOTARYSIGNERVERSION) \
|
DOCKERSAVE_PARA+= vmware/notary-server-photon:$(NOTARYVERSION)-$(VERSIONTAG) vmware/notary-signer-photon:$(NOTARYVERSION)-$(VERSIONTAG) \
|
||||||
vmware/mariadb-photon:$(MARIADBVERSION)
|
vmware/mariadb-photon:$(MARIADBVERSION)-$(VERSIONTAG)
|
||||||
PACKAGE_OFFLINE_PARA+= $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME)
|
PACKAGE_OFFLINE_PARA+= $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME)
|
||||||
PACKAGE_ONLINE_PARA+= $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME)
|
PACKAGE_ONLINE_PARA+= $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME)
|
||||||
DOCKERCOMPOSE_LIST+= -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME)
|
DOCKERCOMPOSE_LIST+= -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME)
|
||||||
endif
|
endif
|
||||||
ifeq ($(CLAIRFLAG), true)
|
ifeq ($(CLAIRFLAG), true)
|
||||||
DOCKERSAVE_PARA+= vmware/clair:$(CLAIRVERSION) vmware/postgresql:$(CLAIRDBVERSION)
|
DOCKERSAVE_PARA+= vmware/clair-photon:$(CLAIRVERSION)-$(VERSIONTAG) vmware/postgresql-photon:$(CLAIRDBVERSION)-$(VERSIONTAG)
|
||||||
PACKAGE_OFFLINE_PARA+= $(HARBORPKG)/$(DOCKERCOMPOSECLAIRFILENAME)
|
PACKAGE_OFFLINE_PARA+= $(HARBORPKG)/$(DOCKERCOMPOSECLAIRFILENAME)
|
||||||
PACKAGE_ONLINE_PARA+= $(HARBORPKG)/$(DOCKERCOMPOSECLAIRFILENAME)
|
PACKAGE_ONLINE_PARA+= $(HARBORPKG)/$(DOCKERCOMPOSECLAIRFILENAME)
|
||||||
DOCKERCOMPOSE_LIST+= -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSECLAIRFILENAME)
|
DOCKERCOMPOSE_LIST+= -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSECLAIRFILENAME)
|
||||||
@ -263,21 +248,6 @@ version:
|
|||||||
check_environment:
|
check_environment:
|
||||||
@$(MAKEPATH)/$(CHECKENVCMD)
|
@$(MAKEPATH)/$(CHECKENVCMD)
|
||||||
|
|
||||||
compile_adminserver:
|
|
||||||
@echo "compiling binary for adminserver..."
|
|
||||||
@$(GOBUILD) -o $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) $(ADMINSERVERSOURCECODE)
|
|
||||||
@echo "Done."
|
|
||||||
|
|
||||||
compile_ui:
|
|
||||||
@echo "compiling binary for ui..."
|
|
||||||
@$(GOBUILD) -o $(UIBINARYPATH)/$(UIBINARYNAME) $(UISOURCECODE)
|
|
||||||
@echo "Done."
|
|
||||||
|
|
||||||
compile_jobservice:
|
|
||||||
@echo "compiling binary for jobservice..."
|
|
||||||
@$(GOBUILD) -o $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) $(JOBSERVICESOURCECODE)
|
|
||||||
@echo "Done."
|
|
||||||
|
|
||||||
compile_clarity:
|
compile_clarity:
|
||||||
@echo "compiling binary for clarity ui..."
|
@echo "compiling binary for clarity ui..."
|
||||||
@if [ "$(HTTPPROXY)" != "" ] ; then \
|
@if [ "$(HTTPPROXY)" != "" ] ; then \
|
||||||
@ -287,8 +257,6 @@ compile_clarity:
|
|||||||
fi
|
fi
|
||||||
@echo "Done."
|
@echo "Done."
|
||||||
|
|
||||||
compile_normal: compile_clarity compile_adminserver compile_ui compile_jobservice
|
|
||||||
|
|
||||||
compile_golangimage: compile_clarity
|
compile_golangimage: compile_clarity
|
||||||
@echo "compiling binary for adminserver (golang image)..."
|
@echo "compiling binary for adminserver (golang image)..."
|
||||||
@echo $(GOBASEPATH)
|
@echo $(GOBASEPATH)
|
||||||
@ -306,31 +274,38 @@ compile_golangimage: compile_clarity
|
|||||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_JOBSERVICE) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_JOBSERVICE)/$(JOBSERVICEBINARYNAME)
|
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_JOBSERVICE) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_JOBSERVICE)/$(JOBSERVICEBINARYNAME)
|
||||||
@echo "Done."
|
@echo "Done."
|
||||||
|
|
||||||
compile:check_environment $(COMPILETAG)
|
compile:check_environment compile_golangimage
|
||||||
|
|
||||||
prepare:
|
prepare:
|
||||||
@echo "preparing..."
|
@echo "preparing..."
|
||||||
@$(MAKEPATH)/$(PREPARECMD) $(PREPARECMD_PARA)
|
@$(MAKEPATH)/$(PREPARECMD) $(PREPARECMD_PARA)
|
||||||
|
|
||||||
build_common: version
|
build:
|
||||||
@echo "buildging db container for photon..."
|
make -f $(MAKEFILEPATH_PHOTON)/Makefile build -e DEVFLAG=$(DEVFLAG) -e MARIADBVERSION=$(MARIADBVERSION) \
|
||||||
@cd $(DOCKERFILEPATH_DB) && $(DOCKERBUILD) --pull -f $(DOCKERFILENAME_DB) -t $(DOCKERIMAGENAME_DB):$(VERSIONTAG) .
|
-e REGISTRYVERSION=$(REGISTRYVERSION) -e NGINXVERSION=$(NGINXVERSION) -e NOTARYVERSION=$(NOTARYVERSION) \
|
||||||
@echo "Done."
|
-e CLAIRVERSION=$(CLAIRVERSION) -e CLAIRDBVERSION=$(CLAIRDBVERSION) -e VERSIONTAG=$(VERSIONTAG) \
|
||||||
|
-e BUILDBIN=$(BUILDBIN)
|
||||||
|
|
||||||
build_photon: build_common
|
modify_composefile: modify_composefile_notary modify_composefile_clair
|
||||||
make -f $(MAKEFILEPATH_PHOTON)/Makefile build -e DEVFLAG=$(DEVFLAG)
|
|
||||||
build_postgresql:
|
|
||||||
@echo "buildging postgresql container for photon..."
|
|
||||||
@cd $(DOCKERFILEPATH_POSTGRESQL) && $(DOCKERBUILD) -f $(DOCKERFILENAME_POSTGRESQL) -t $(DOCKERIMAGENAME_POSTGRESQL):$(CLAIRDBVERSION) .
|
|
||||||
@echo "Done."
|
|
||||||
build: build_$(BASEIMAGE)
|
|
||||||
|
|
||||||
modify_composefile:
|
|
||||||
@echo "preparing docker-compose file..."
|
@echo "preparing docker-compose file..."
|
||||||
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSETPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSETPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||||
@cp $(DOCKERCOMPOSEFILEPATH)/ha/$(DOCKERCOMPOSETPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/ha/$(DOCKERCOMPOSEFILENAME)
|
@cp $(DOCKERCOMPOSEFILEPATH)/ha/$(DOCKERCOMPOSETPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/ha/$(DOCKERCOMPOSEFILENAME)
|
||||||
@$(SEDCMD) -i 's/__version__/$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
|
||||||
@$(SEDCMD) -i 's/__version__/$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/ha/$(DOCKERCOMPOSEFILENAME)
|
@$(SEDCMD) -i 's/__version__/$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/ha/$(DOCKERCOMPOSEFILENAME)
|
||||||
|
@$(SEDCMD) -i 's/__reg_version__/$(REGISTRYVERSION)-$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||||
|
@$(SEDCMD) -i 's/__version__/$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||||
|
@$(SEDCMD) -i 's/__nginx_version__/$(NGINXVERSION)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||||
|
|
||||||
|
modify_composefile_notary:
|
||||||
|
@echo "preparing docker-compose notary file..."
|
||||||
|
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYTPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME)
|
||||||
|
@$(SEDCMD) -i 's/__notary_version__/$(NOTARYVERSION)-$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME)
|
||||||
|
@$(SEDCMD) -i 's/__mariadb_version__/$(MARIADBVERSION)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME)
|
||||||
|
|
||||||
|
modify_composefile_clair:
|
||||||
|
@echo "preparing docker-compose clair file..."
|
||||||
|
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSECLAIRTPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSECLAIRFILENAME)
|
||||||
|
@$(SEDCMD) -i 's/__postgresql_version__/$(CLAIRDBVERSION)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSECLAIRFILENAME)
|
||||||
|
@$(SEDCMD) -i 's/__clair_version__/$(CLAIRVERSION)-$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSECLAIRFILENAME)
|
||||||
|
|
||||||
modify_sourcefiles:
|
modify_sourcefiles:
|
||||||
@echo "change mode of source files."
|
@echo "change mode of source files."
|
||||||
@ -340,7 +315,7 @@ modify_sourcefiles:
|
|||||||
@chmod 600 $(MAKEPATH)/common/templates/ui/private_key.pem
|
@chmod 600 $(MAKEPATH)/common/templates/ui/private_key.pem
|
||||||
@chmod 600 $(MAKEPATH)/common/templates/registry/root.crt
|
@chmod 600 $(MAKEPATH)/common/templates/registry/root.crt
|
||||||
|
|
||||||
install: compile build modify_sourcefiles prepare modify_composefile start
|
install: compile version build modify_sourcefiles prepare modify_composefile start
|
||||||
|
|
||||||
package_online: modify_composefile
|
package_online: modify_composefile
|
||||||
@echo "packing online package ..."
|
@echo "packing online package ..."
|
||||||
@ -353,37 +328,18 @@ package_online: modify_composefile
|
|||||||
fi
|
fi
|
||||||
@cp LICENSE $(HARBORPKG)/LICENSE
|
@cp LICENSE $(HARBORPKG)/LICENSE
|
||||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||||
@cp tools/migration/migration_cfg/upgrade $(HARBORPKG)/upgrade
|
|
||||||
@cp tools/migration/migration_cfg/harbor_1_1_0_template $(HARBORPKG)/harbor_1_1_0_template
|
|
||||||
|
|
||||||
@$(TARCMD) $(PACKAGE_ONLINE_PARA)
|
@$(TARCMD) $(PACKAGE_ONLINE_PARA)
|
||||||
@rm -rf $(HARBORPKG)
|
@rm -rf $(HARBORPKG)
|
||||||
@echo "Done."
|
@echo "Done."
|
||||||
|
|
||||||
package_offline: compile build modify_sourcefiles modify_composefile
|
package_offline: compile version build modify_sourcefiles modify_composefile
|
||||||
@echo "packing offline package ..."
|
@echo "packing offline package ..."
|
||||||
@cp -r make $(HARBORPKG)
|
@cp -r make $(HARBORPKG)
|
||||||
|
|
||||||
@cp LICENSE $(HARBORPKG)/LICENSE
|
@cp LICENSE $(HARBORPKG)/LICENSE
|
||||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||||
@cp tools/migration/migration_cfg/upgrade $(HARBORPKG)/upgrade
|
|
||||||
@cp tools/migration/migration_cfg/harbor_1_1_0_template $(HARBORPKG)/harbor_1_1_0_template
|
|
||||||
@cp $(HARBORPKG)/common/db/registry.sql $(HARBORPKG)/ha/
|
@cp $(HARBORPKG)/common/db/registry.sql $(HARBORPKG)/ha/
|
||||||
|
|
||||||
@echo "pulling nginx and registry..."
|
|
||||||
@$(DOCKERPULL) vmware/registry:$(REGISTRYVERSION)
|
|
||||||
@$(DOCKERPULL) vmware/nginx-photon:$(NGINXVERSION)
|
|
||||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
|
||||||
echo "pulling notary and harbor-notary-db..."; \
|
|
||||||
$(DOCKERPULL) vmware/notary-photon:$(NOTARYVERSION); \
|
|
||||||
$(DOCKERPULL) vmware/notary-photon:$(NOTARYSIGNERVERSION); \
|
|
||||||
$(DOCKERPULL) vmware/mariadb-photon:$(MARIADBVERSION); \
|
|
||||||
fi
|
|
||||||
@if [ "$(CLAIRFLAG)" = "true" ] ; then \
|
|
||||||
echo "pulling claiy and postgres..."; \
|
|
||||||
$(DOCKERPULL) vmware/clair:$(CLAIRVERSION); \
|
|
||||||
$(DOCKERPULL) vmware/postgresql:$(CLAIRDBVERSION); \
|
|
||||||
fi
|
|
||||||
@if [ "$(MIGRATORFLAG)" = "true" ] ; then \
|
@if [ "$(MIGRATORFLAG)" = "true" ] ; then \
|
||||||
echo "pulling DB migrator..."; \
|
echo "pulling DB migrator..."; \
|
||||||
$(DOCKERPULL) vmware/harbor-db-migrator:$(MIGRATORVERSION); \
|
$(DOCKERPULL) vmware/harbor-db-migrator:$(MIGRATORVERSION); \
|
||||||
@ -474,8 +430,6 @@ cleanimage:
|
|||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||||
# - $(DOCKERRMIMAGE) -f registry:$(REGISTRYVERSION)
|
|
||||||
# - $(DOCKERRMIMAGE) -f nginx:1.11.5
|
|
||||||
|
|
||||||
cleandockercomposefile:
|
cleandockercomposefile:
|
||||||
@echo "cleaning $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml"
|
@echo "cleaning $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml"
|
||||||
|
@ -17,7 +17,7 @@ services:
|
|||||||
aliases:
|
aliases:
|
||||||
- postgres
|
- postgres
|
||||||
container_name: clair-db
|
container_name: clair-db
|
||||||
image: vmware/postgresql:9.6.5-photon
|
image: vmware/postgresql-photon:__postgresql_version__
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
- log
|
- log
|
||||||
@ -35,7 +35,7 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- harbor-clair
|
- harbor-clair
|
||||||
container_name: clair
|
container_name: clair
|
||||||
image: vmware/clair:v2.0.1-photon
|
image: vmware/clair-photon:__clair_version__
|
||||||
restart: always
|
restart: always
|
||||||
cpu_quota: 150000
|
cpu_quota: 150000
|
||||||
depends_on:
|
depends_on:
|
@ -7,7 +7,7 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- harbor-notary
|
- harbor-notary
|
||||||
notary-server:
|
notary-server:
|
||||||
image: vmware/notary-photon:server-0.5.1
|
image: vmware/notary-server-photon:__notary_version__
|
||||||
container_name: notary-server
|
container_name: notary-server
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
@ -25,7 +25,7 @@ services:
|
|||||||
syslog-address: "tcp://127.0.0.1:1514"
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
tag: "notary-server"
|
tag: "notary-server"
|
||||||
notary-signer:
|
notary-signer:
|
||||||
image: vmware/notary-photon:signer-0.5.1
|
image: vmware/notary-signer-photon:__notary_version__
|
||||||
container_name: notary-signer
|
container_name: notary-signer
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
@ -45,7 +45,7 @@ services:
|
|||||||
syslog-address: "tcp://127.0.0.1:1514"
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
tag: "notary-signer"
|
tag: "notary-signer"
|
||||||
notary-db:
|
notary-db:
|
||||||
image: vmware/mariadb-photon:10.2.10
|
image: vmware/mariadb-photon:__mariadb_version__
|
||||||
container_name: notary-db
|
container_name: notary-db
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
@ -12,7 +12,7 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- harbor
|
- harbor
|
||||||
registry:
|
registry:
|
||||||
image: vmware/registry:2.6.2-photon
|
image: vmware/registry-photon:__reg_version__
|
||||||
container_name: registry
|
container_name: registry
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
@ -112,7 +112,7 @@ services:
|
|||||||
syslog-address: "tcp://127.0.0.1:1514"
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
tag: "jobservice"
|
tag: "jobservice"
|
||||||
proxy:
|
proxy:
|
||||||
image: vmware/nginx-photon:1.11.13
|
image: vmware/nginx-photon:__nginx_version__
|
||||||
container_name: nginx
|
container_name: nginx
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -11,9 +11,8 @@ BUILDPATH=$(CURDIR)
|
|||||||
MAKEPATH=$(BUILDPATH)/make
|
MAKEPATH=$(BUILDPATH)/make
|
||||||
MAKEDEVPATH=$(MAKEPATH)/dev
|
MAKEDEVPATH=$(MAKEPATH)/dev
|
||||||
SRCPATH=./src
|
SRCPATH=./src
|
||||||
TOOLSPATH=$(BUILDPATH)/tools
|
SEDCMD=$(shell which sed)
|
||||||
CHECKENVCMD=checkenv.sh
|
WGET=$(shell which wget)
|
||||||
DEVFLAG=true
|
|
||||||
|
|
||||||
# docker parameters
|
# docker parameters
|
||||||
DOCKERCMD=$(shell which docker)
|
DOCKERCMD=$(shell which docker)
|
||||||
@ -34,50 +33,146 @@ JOBSERVICEBINARYNAME=harbor_jobservice
|
|||||||
|
|
||||||
# photon dockerfile
|
# photon dockerfile
|
||||||
DOCKERFILEPATH=$(MAKEPATH)/photon
|
DOCKERFILEPATH=$(MAKEPATH)/photon
|
||||||
|
|
||||||
DOCKERFILEPATH_ADMINSERVER=$(DOCKERFILEPATH)/adminserver
|
DOCKERFILEPATH_ADMINSERVER=$(DOCKERFILEPATH)/adminserver
|
||||||
DOCKERFILENAME_ADMINSERVER=Dockerfile
|
DOCKERFILENAME_ADMINSERVER=Dockerfile
|
||||||
DOCKERIMAGENAME_ADMINSERVER=vmware/harbor-adminserver
|
DOCKERIMAGENAME_ADMINSERVER=vmware/harbor-adminserver
|
||||||
|
|
||||||
DOCKERFILEPATH_UI=$(DOCKERFILEPATH)/ui
|
DOCKERFILEPATH_UI=$(DOCKERFILEPATH)/ui
|
||||||
DOCKERFILENAME_UI=Dockerfile
|
DOCKERFILENAME_UI=Dockerfile
|
||||||
DOCKERIMAGENAME_UI=vmware/harbor-ui
|
DOCKERIMAGENAME_UI=vmware/harbor-ui
|
||||||
|
|
||||||
DOCKERFILEPATH_JOBSERVICE=$(DOCKERFILEPATH)/jobservice
|
DOCKERFILEPATH_JOBSERVICE=$(DOCKERFILEPATH)/jobservice
|
||||||
DOCKERFILENAME_JOBSERVICE=Dockerfile
|
DOCKERFILENAME_JOBSERVICE=Dockerfile
|
||||||
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
||||||
|
|
||||||
DOCKERFILEPATH_LOG=$(DOCKERFILEPATH)/log
|
DOCKERFILEPATH_LOG=$(DOCKERFILEPATH)/log
|
||||||
DOCKERFILENAME_LOG=Dockerfile
|
DOCKERFILENAME_LOG=Dockerfile
|
||||||
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
||||||
|
|
||||||
# version prepare
|
DOCKERFILEPATH_DB=$(DOCKERFILEPATH)/db
|
||||||
VERSIONFILEPATH=$(SRCPATH)/views/sections
|
DOCKERFILENAME_DB=Dockerfile
|
||||||
VERSIONFILENAME=header-content.htm
|
DOCKERIMAGENAME_DB=vmware/harbor-db
|
||||||
GITCMD=$(shell which git)
|
|
||||||
GITTAG=$(GITCMD) describe --tags
|
|
||||||
ifeq ($(DEVFLAG), true)
|
|
||||||
VERSIONTAG=dev
|
|
||||||
else
|
|
||||||
VERSIONTAG=$(shell $(GITTAG))
|
|
||||||
endif
|
|
||||||
|
|
||||||
check_environment:
|
DOCKERFILEPATH_POSTGRESQL=$(DOCKERFILEPATH)/postgresql
|
||||||
@$(MAKEPATH)/$(CHECKENVCMD)
|
DOCKERFILENAME_POSTGRESQL=Dockerfile
|
||||||
|
DOCKERIMAGENAME_POSTGRESQL=vmware/postgresql-photon
|
||||||
|
|
||||||
build:
|
DOCKERFILEPATH_CLAIR=$(DOCKERFILEPATH)/clair
|
||||||
|
DOCKERFILENAME_CLAIR=Dockerfile
|
||||||
|
DOCKERIMAGENAME_CLAIR=vmware/clair-photon
|
||||||
|
|
||||||
|
DOCKERFILEPATH_NGINX=$(DOCKERFILEPATH)/nginx
|
||||||
|
DOCKERFILENAME_NGINX=Dockerfile
|
||||||
|
DOCKERIMAGENAME_NGINX=vmware/nginx-photon
|
||||||
|
|
||||||
|
DOCKERFILEPATH_REG=$(DOCKERFILEPATH)/registry
|
||||||
|
DOCKERFILENAME_REG=Dockerfile
|
||||||
|
DOCKERIMAGENAME_REG=vmware/registry-photon
|
||||||
|
|
||||||
|
DOCKERFILEPATH_MARIADB=$(DOCKERFILEPATH)/mariadb
|
||||||
|
DOCKERFILENAME_MARIADB=Dockerfile
|
||||||
|
DOCKERIMAGENAME_MARIADB=vmware/mariadb-photon
|
||||||
|
|
||||||
|
DOCKERFILEPATH_NOTARY=$(DOCKERFILEPATH)/notary
|
||||||
|
DOCKERFILENAME_NOTARYSIGNER=signer.Dockerfile
|
||||||
|
DOCKERIMAGENAME_NOTARYSIGNER=vmware/notary-signer-photon
|
||||||
|
DOCKERFILENAME_NOTARYSERVER=server.Dockerfile
|
||||||
|
DOCKERIMAGENAME_NOTARYSERVER=vmware/notary-server-photon
|
||||||
|
|
||||||
|
_build_db: _build_mariadb
|
||||||
|
@echo "modify the db dockerfile..."
|
||||||
|
@$(SEDCMD) -i 's/__version__/$(MARIADBVERSION)/g' $(DOCKERFILEPATH_DB)/$(DOCKERFILENAME_DB)
|
||||||
|
@echo "building db container for photon..."
|
||||||
|
@cd $(DOCKERFILEPATH_DB) && $(DOCKERBUILD) -f $(DOCKERFILEPATH_DB)/$(DOCKERFILENAME_DB) -t $(DOCKERIMAGENAME_DB):$(VERSIONTAG) .
|
||||||
|
@echo "Done."
|
||||||
|
@$(SEDCMD) -i 's/$(MARIADBVERSION)/__version__/g' $(DOCKERFILEPATH_DB)/$(DOCKERFILENAME_DB)
|
||||||
|
|
||||||
|
_build_adminiserver:
|
||||||
@echo "building adminserver container for photon..."
|
@echo "building adminserver container for photon..."
|
||||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_ADMINSERVER)/$(DOCKERFILENAME_ADMINSERVER) -t $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) .
|
$(DOCKERBUILD) -f $(DOCKERFILEPATH_ADMINSERVER)/$(DOCKERFILENAME_ADMINSERVER) -t $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) .
|
||||||
@echo "Done."
|
@echo "Done."
|
||||||
|
|
||||||
|
_build_ui:
|
||||||
@echo "building ui container for photon..."
|
@echo "building ui container for photon..."
|
||||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_UI)/$(DOCKERFILENAME_UI) -t $(DOCKERIMAGENAME_UI):$(VERSIONTAG) .
|
$(DOCKERBUILD) -f $(DOCKERFILEPATH_UI)/$(DOCKERFILENAME_UI) -t $(DOCKERIMAGENAME_UI):$(VERSIONTAG) .
|
||||||
@echo "Done."
|
@echo "Done."
|
||||||
|
|
||||||
|
_build_jobservice:
|
||||||
@echo "building jobservice container for photon..."
|
@echo "building jobservice container for photon..."
|
||||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_JOBSERVICE)/$(DOCKERFILENAME_JOBSERVICE) -t $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) .
|
$(DOCKERBUILD) -f $(DOCKERFILEPATH_JOBSERVICE)/$(DOCKERFILENAME_JOBSERVICE) -t $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) .
|
||||||
@echo "Done."
|
@echo "Done."
|
||||||
|
|
||||||
|
_build_log:
|
||||||
@echo "building log container for photon..."
|
@echo "building log container for photon..."
|
||||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_LOG)/$(DOCKERFILENAME_LOG) -t $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) $(DOCKERFILEPATH_LOG)
|
$(DOCKERBUILD) -f $(DOCKERFILEPATH_LOG)/$(DOCKERFILENAME_LOG) -t $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) $(DOCKERFILEPATH_LOG)
|
||||||
@echo "Done."
|
@echo "Done."
|
||||||
|
|
||||||
|
_build_postgresql:
|
||||||
|
@echo "building postgresql container for photon..."
|
||||||
|
@cd $(DOCKERFILEPATH_POSTGRESQL) && $(DOCKERBUILD) -f $(DOCKERFILEPATH_POSTGRESQL)/$(DOCKERFILENAME_POSTGRESQL) -t $(DOCKERIMAGENAME_POSTGRESQL):$(CLAIRDBVERSION) .
|
||||||
|
@echo "Done."
|
||||||
|
|
||||||
|
_build_clair:
|
||||||
|
@if [ "$(CLAIRFLAG)" = "true" ] ; then \
|
||||||
|
if [ "$(BUILDBIN)" != "true" ] ; then \
|
||||||
|
rm -rf $(DOCKERFILEPATH_CLAIR)/binary && mkdir -p $(DOCKERFILEPATH_CLAIR)/binary && \
|
||||||
|
$(call _get_binary, https://storage.googleapis.com/harbor-builds/bin/clair, $(DOCKERFILEPATH_CLAIR)/binary/clair); \
|
||||||
|
else \
|
||||||
|
cd $(DOCKERFILEPATH_CLAIR) && $(DOCKERFILEPATH_CLAIR)/builder $(CLAIRVERSION); \
|
||||||
|
fi ; \
|
||||||
|
echo "building clair container for photon..." ; \
|
||||||
|
cd $(DOCKERFILEPATH_CLAIR) && $(DOCKERBUILD) -f $(DOCKERFILEPATH_CLAIR)/$(DOCKERFILENAME_CLAIR) -t $(DOCKERIMAGENAME_CLAIR):$(CLAIRVERSION)-$(VERSIONTAG) . ; \
|
||||||
|
rm -rf $(DOCKERFILEPATH_CLAIR)/binary; \
|
||||||
|
echo "Done." ; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
_build_nginx:
|
||||||
|
@echo "building nginx container for photon..."
|
||||||
|
@$(DOCKERBUILD) -f $(DOCKERFILEPATH_NGINX)/$(DOCKERFILENAME_NGINX) -t $(DOCKERIMAGENAME_NGINX):$(NGINXVERSION) .
|
||||||
|
@echo "Done."
|
||||||
|
|
||||||
|
_build_notary:
|
||||||
|
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||||
|
if [ "$(BUILDBIN)" != "true" ] ; then \
|
||||||
|
rm -rf $(DOCKERFILEPATH_NOTARY)/binary && mkdir -p $(DOCKERFILEPATH_NOTARY)/binary && \
|
||||||
|
$(call _get_binary, https://storage.googleapis.com/harbor-builds/bin/notary/notary-signer, $(DOCKERFILEPATH_NOTARY)/binary/notary-signer) && \
|
||||||
|
$(call _get_binary, https://storage.googleapis.com/harbor-builds/bin/notary/notary-server, $(DOCKERFILEPATH_NOTARY)/binary/notary-server) && \
|
||||||
|
$(call _get_binary, https://storage.googleapis.com/harbor-builds/bin/notary/notary-migrate.tgz, $(DOCKERFILEPATH_NOTARY)/binary/notary-migrate.tgz); \
|
||||||
|
cd $(DOCKERFILEPATH_NOTARY)/binary && tar -zvxf notary-migrate.tgz; \
|
||||||
|
else \
|
||||||
|
cd $(DOCKERFILEPATH_NOTARY) && $(DOCKERFILEPATH_NOTARY)/builder_public $(NOTARYVERSION); \
|
||||||
|
fi ; \
|
||||||
|
echo "building notary container for photon..."; \
|
||||||
|
cd $(DOCKERFILEPATH_NOTARY) && chmod 655 $(DOCKERFILEPATH_NOTARY)/binary/notary-signer && $(DOCKERBUILD) -f $(DOCKERFILEPATH_NOTARY)/$(DOCKERFILENAME_NOTARYSIGNER) -t $(DOCKERIMAGENAME_NOTARYSIGNER):$(NOTARYVERSION)-$(VERSIONTAG) . ; \
|
||||||
|
cd $(DOCKERFILEPATH_NOTARY) && chmod 655 $(DOCKERFILEPATH_NOTARY)/binary/notary-server && $(DOCKERBUILD) -f $(DOCKERFILEPATH_NOTARY)/$(DOCKERFILENAME_NOTARYSERVER) -t $(DOCKERIMAGENAME_NOTARYSERVER):$(NOTARYVERSION)-$(VERSIONTAG) . ; \
|
||||||
|
rm -rf $(DOCKERFILEPATH_NOTARY)/binary; \
|
||||||
|
echo "Done."; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
_build_registry:
|
||||||
|
@if [ "$(BUILDBIN)" != "true" ] ; then \
|
||||||
|
rm -rf $(DOCKERFILEPATH_REG)/binary && mkdir -p $(DOCKERFILEPATH_REG)/binary && \
|
||||||
|
$(call _get_binary, https://storage.googleapis.com/harbor-builds/bin/registry, $(DOCKERFILEPATH_REG)/binary/registry); \
|
||||||
|
else \
|
||||||
|
cd $(DOCKERFILEPATH_REG) && $(DOCKERFILEPATH_REG)/builder $(REGISTRYVERSION); \
|
||||||
|
fi
|
||||||
|
@echo "building registry container for photon..."
|
||||||
|
@cd $(DOCKERFILEPATH_REG) && chmod 655 $(DOCKERFILEPATH_REG)/binary/registry && $(DOCKERBUILD) -f $(DOCKERFILEPATH_REG)/$(DOCKERFILENAME_REG) -t $(DOCKERIMAGENAME_REG):$(REGISTRYVERSION)-$(VERSIONTAG) .
|
||||||
|
@rm -rf $(DOCKERFILEPATH_REG)/binary
|
||||||
|
@echo "Done."
|
||||||
|
|
||||||
|
_build_mariadb:
|
||||||
|
@echo "building mariadb container for photon..."
|
||||||
|
@cd $(DOCKERFILEPATH_MARIADB) && $(DOCKERBUILD) -f $(DOCKERFILEPATH_MARIADB)/$(DOCKERFILENAME_MARIADB) -t $(DOCKERIMAGENAME_MARIADB):$(MARIADBVERSION) .
|
||||||
|
@echo "Done."
|
||||||
|
|
||||||
|
define _get_binary
|
||||||
|
$(WGET) --timeout 30 --no-check-certificate $1 -O $2
|
||||||
|
endef
|
||||||
|
|
||||||
|
build: _build_postgresql _build_db _build_adminiserver _build_ui _build_jobservice _build_log _build_nginx _build_registry _build_notary _build_clair
|
||||||
|
|
||||||
cleanimage:
|
cleanimage:
|
||||||
@echo "cleaning image for photon..."
|
@echo "cleaning image for photon..."
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
FROM vmware/photon:1.0
|
FROM vmware/photon:1.0
|
||||||
|
|
||||||
RUN tdnf erase vim -y \
|
RUN tdnf erase vim -y \
|
||||||
&& tdnf distro-sync -y \
|
&& tdnf distro-sync -y || echo \
|
||||||
&& tdnf install -y sudo \
|
&& tdnf install -y sudo \
|
||||||
&& tdnf clean all \
|
&& tdnf clean all \
|
||||||
&& groupadd -r -g 10000 harbor && useradd --no-log-init -r -g 10000 -u 10000 harbor \
|
&& groupadd -r -g 10000 harbor && useradd --no-log-init -r -g 10000 -u 10000 harbor \
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
FROM vmware/photon:1.0
|
FROM vmware/photon:1.0
|
||||||
|
|
||||||
|
|
||||||
RUN tdnf distro-sync -y \
|
RUN tdnf distro-sync -y || echo \
|
||||||
&& tdnf erase vim -y \
|
&& tdnf erase vim -y \
|
||||||
&& tdnf install -y git shadow sudo bzr rpm xz python-xml \
|
&& tdnf install -y git shadow sudo bzr rpm xz python-xml \
|
||||||
&& tdnf clean all \
|
&& tdnf clean all \
|
||||||
&& mkdir /clair2.0.1/ \
|
&& mkdir /clair2.0.1/ \
|
||||||
&& groupadd -r -g 10000 clair \
|
&& groupadd -r -g 10000 clair \
|
||||||
&& useradd --no-log-init -m -r -g 10000 -u 10000 clair
|
&& useradd --no-log-init -m -r -g 10000 -u 10000 clair
|
||||||
COPY clair /clair2.0.1/
|
COPY ./binary/clair /clair2.0.1/
|
||||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
COPY dumb-init /dumb-init
|
COPY dumb-init /dumb-init
|
||||||
|
|
||||||
|
6
make/photon/clair/Dockerfile.binary
Normal file
6
make/photon/clair/Dockerfile.binary
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
FROM golang:1.7.3
|
||||||
|
|
||||||
|
ADD . /go/src/github.com/coreos/clair/
|
||||||
|
WORKDIR /go/src/github.com/coreos/clair/
|
||||||
|
|
||||||
|
RUN go install -v github.com/coreos/clair/cmd/clair
|
38
make/photon/clair/builder
Executable file
38
make/photon/clair/builder
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set +e
|
||||||
|
|
||||||
|
if [ -z $1 ]; then
|
||||||
|
error "Please set the 'version' variable"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="$1"
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# the temp folder to store binary file...
|
||||||
|
mkdir -p binary
|
||||||
|
rm -rf binary/clair || true
|
||||||
|
|
||||||
|
cd `dirname $0`
|
||||||
|
cur=$PWD
|
||||||
|
|
||||||
|
# the temp folder to store distribution source code...
|
||||||
|
TEMP=`mktemp -d /$TMPDIR/clair.XXXXXX`
|
||||||
|
git clone -b $VERSION https://github.com/coreos/clair.git $TEMP
|
||||||
|
|
||||||
|
echo 'build the clair binary bases on the golang:1.7.3...'
|
||||||
|
cp Dockerfile.binary $TEMP
|
||||||
|
docker build -f $TEMP/Dockerfile.binary -t clair-golang $TEMP
|
||||||
|
|
||||||
|
echo 'copy the clair binary to local...'
|
||||||
|
ID=$(docker create clair-golang)
|
||||||
|
docker cp $ID:/go/bin/clair binary
|
||||||
|
|
||||||
|
docker rm -f $ID
|
||||||
|
docker rmi -f clair-golang
|
||||||
|
|
||||||
|
echo "Build clair binary success, then to build photon image..."
|
||||||
|
cd $cur
|
||||||
|
rm -rf $TEMP
|
@ -1,4 +1,4 @@
|
|||||||
FROM vmware/mariadb-photon:10.2.10
|
FROM vmware/mariadb-photon:__version__
|
||||||
|
|
||||||
HEALTHCHECK CMD mysqladmin -uroot -p$MYSQL_ROOT_PASSWORD ping
|
HEALTHCHECK CMD mysqladmin -uroot -p$MYSQL_ROOT_PASSWORD ping
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
FROM vmware/photon:1.0
|
FROM vmware/photon:1.0
|
||||||
|
|
||||||
RUN mkdir /harbor/ \
|
RUN mkdir /harbor/ \
|
||||||
&& tdnf distro-sync -y \
|
&& tdnf distro-sync -y || echo \
|
||||||
&& tdnf install sudo -y \
|
&& tdnf install sudo -y \
|
||||||
&& tdnf clean all \
|
&& tdnf clean all \
|
||||||
&& groupadd -r -g 10000 harbor && useradd --no-log-init -r -g 10000 -u 10000 harbor
|
&& groupadd -r -g 10000 harbor && useradd --no-log-init -r -g 10000 -u 10000 harbor
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
FROM vmware/photon:1.0
|
FROM vmware/photon:1.0
|
||||||
|
|
||||||
RUN tdnf distro-sync -y \
|
RUN tdnf distro-sync -y || echo \
|
||||||
&& tdnf install -y cronie rsyslog logrotate shadow tar gzip sudo net-tools\
|
&& tdnf install -y cronie rsyslog logrotate shadow tar gzip sudo net-tools\
|
||||||
&& mkdir /etc/rsyslog.d/ \
|
&& mkdir /etc/rsyslog.d/ \
|
||||||
&& mkdir /var/spool/rsyslog \
|
&& mkdir /var/spool/rsyslog \
|
||||||
|
@ -2,7 +2,7 @@ FROM vmware/photon:1.0
|
|||||||
|
|
||||||
#The Docker Daemon has to be running with storage backend btrfs when building the image
|
#The Docker Daemon has to be running with storage backend btrfs when building the image
|
||||||
|
|
||||||
RUN tdnf distro-sync -y \
|
RUN tdnf distro-sync -y || echo \
|
||||||
&& tdnf install -y sed shadow procps-ng gawk gzip sudo net-tools \
|
&& tdnf install -y sed shadow procps-ng gawk gzip sudo net-tools \
|
||||||
&& groupadd -r -g 10000 mysql && useradd --no-log-init -r -g 10000 -u 10000 mysql \
|
&& groupadd -r -g 10000 mysql && useradd --no-log-init -r -g 10000 -u 10000 mysql \
|
||||||
&& tdnf install -y mariadb-server mariadb \
|
&& tdnf install -y mariadb-server mariadb \
|
@ -1,6 +1,6 @@
|
|||||||
FROM vmware/photon:1.0
|
FROM vmware/photon:1.0
|
||||||
|
|
||||||
RUN tdnf distro-sync -y \
|
RUN tdnf distro-sync -y || echo \
|
||||||
&& tdnf install -y nginx \
|
&& tdnf install -y nginx \
|
||||||
&& ln -sf /dev/stdout /var/log/nginx/access.log \
|
&& ln -sf /dev/stdout /var/log/nginx/access.log \
|
||||||
&& ln -sf /dev/stderr /var/log/nginx/error.log \
|
&& ln -sf /dev/stderr /var/log/nginx/error.log \
|
@ -1,13 +1,12 @@
|
|||||||
FROM golang:1.7.3
|
FROM golang:1.7.3
|
||||||
|
|
||||||
ENV NOTARY_DIR /go/src/github.com/docker/notary
|
ENV NOTARYPKG github.com/theupdateframework/notary
|
||||||
ENV NOTARYPKG github.com/docker/notary
|
|
||||||
|
|
||||||
COPY . /go/src/${NOTARYPKG}
|
COPY . /go/src/${NOTARYPKG}
|
||||||
WORKDIR /go/src/${NOTARYPKG}
|
WORKDIR /go/src/${NOTARYPKG}
|
||||||
|
|
||||||
RUN go build -tags pkcs11 \
|
RUN go install -tags pkcs11 \
|
||||||
-ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" $NOTARYPKG/cmd/notary-server
|
-ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" ${NOTARYPKG}/cmd/notary-server
|
||||||
|
|
||||||
RUN go build -tags pkcs11 \
|
RUN go install -tags pkcs11 \
|
||||||
-ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" $NOTARYPKG/cmd/notary-signer
|
-ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" ${NOTARYPKG}/cmd/notary-signer
|
||||||
|
@ -2,26 +2,12 @@
|
|||||||
|
|
||||||
set +e
|
set +e
|
||||||
|
|
||||||
echo "Usage: #./builder [notary version] [registry username] [registry password]"
|
|
||||||
if [ -z $1 ]; then
|
if [ -z $1 ]; then
|
||||||
error "Please set the 'version' variable"
|
error "Please set the 'version' variable"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ -z $2 ]; then
|
|
||||||
error "Please set the 'photonversion' variable"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z $3 ]; then
|
|
||||||
error "Please set the 'username' variable"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
VERSION="$1"
|
VERSION="$1"
|
||||||
USERNAME="$2"
|
|
||||||
PASSWORD="$3"
|
|
||||||
SIGNER_PHOTONIMAGE=vmware/notary-photon:signer-$VERSION
|
|
||||||
SERVER_PHOTONIMAGE=vmware/notary-photon:server-$VERSION
|
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@ -41,19 +27,18 @@ echo 'build the notary binary bases on the golang:1.7.3...'
|
|||||||
cp binary.Dockerfile $TEMP
|
cp binary.Dockerfile $TEMP
|
||||||
cd $TEMP
|
cd $TEMP
|
||||||
docker build -f binary.Dockerfile -t notary-golang $TEMP
|
docker build -f binary.Dockerfile -t notary-golang $TEMP
|
||||||
|
cp -r $TEMP/migrations binary
|
||||||
|
|
||||||
echo 'copy the notary binary to local...'
|
echo 'copy the notary binary to local...'
|
||||||
ID=$(docker create notary-golang)
|
ID=$(docker create notary-golang)
|
||||||
echo $ID
|
echo $ID
|
||||||
cd $cur
|
cd $cur
|
||||||
docker cp $ID:/go/src/github.com/docker/notary/notary-server binary
|
docker cp $ID:/go/src/github.com/theupdateframework/notary/notary-server binary
|
||||||
docker cp $ID:/go/src/github.com/docker/notary/notary-signer binary
|
docker cp $ID:/go/src/github.com/theupdateframework/notary/notary-signer binary
|
||||||
|
|
||||||
docker rm -f $ID
|
docker rm -f $ID
|
||||||
docker rmi -f notary-golang
|
docker rmi -f notary-golang
|
||||||
|
|
||||||
docker build -f server.Dockerfile -t $SERVER_PHOTONIMAGE .
|
rm -rf $TEMP
|
||||||
docker build -f signer.Dockerfile -t $SIGNER_PHOTONIMAGE .
|
|
||||||
|
|
||||||
echo 'Push image to docker hub.'
|
|
||||||
../../pushimage.sh $PHOTONIMAGE $USERNAME $PASSWORD
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
FROM vmware/photon:1.0
|
FROM vmware/photon:1.0
|
||||||
|
|
||||||
RUN tdnf distro-sync -y \
|
RUN tdnf distro-sync -y || echo \
|
||||||
&& tdnf erase vim -y \
|
&& tdnf erase vim -y \
|
||||||
&& tdnf install -y shadow sudo \
|
&& tdnf install -y shadow sudo \
|
||||||
&& tdnf clean all \
|
&& tdnf clean all \
|
||||||
@ -8,8 +8,8 @@ RUN tdnf distro-sync -y \
|
|||||||
&& useradd --no-log-init -r -g 10000 -u 10000 notary
|
&& useradd --no-log-init -r -g 10000 -u 10000 notary
|
||||||
|
|
||||||
COPY ./binary/notary-server /bin/notary-server
|
COPY ./binary/notary-server /bin/notary-server
|
||||||
COPY ./migrate /bin/migrate
|
COPY ./binary/migrate /bin/migrate
|
||||||
COPY ./migrations/ /migrations/
|
COPY ./binary/migrations/ /migrations/
|
||||||
COPY ./server-start.sh /bin/server-start.sh
|
COPY ./server-start.sh /bin/server-start.sh
|
||||||
RUN chmod u+x /bin/notary-server /migrations/migrate.sh /bin/migrate /bin/server-start.sh
|
RUN chmod u+x /bin/notary-server /migrations/migrate.sh /bin/migrate /bin/server-start.sh
|
||||||
ENV SERVICE_NAME=notary_server
|
ENV SERVICE_NAME=notary_server
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
FROM vmware/photon:1.0
|
FROM vmware/photon:1.0
|
||||||
|
|
||||||
RUN tdnf distro-sync -y \
|
RUN tdnf distro-sync -y || echo \
|
||||||
&& tdnf erase vim -y \
|
&& tdnf erase vim -y \
|
||||||
&& tdnf install -y shadow sudo \
|
&& tdnf install -y shadow sudo \
|
||||||
&& tdnf clean all \
|
&& tdnf clean all \
|
||||||
&& groupadd -r -g 10000 notary \
|
&& groupadd -r -g 10000 notary \
|
||||||
&& useradd --no-log-init -r -g 10000 -u 10000 notary
|
&& useradd --no-log-init -r -g 10000 -u 10000 notary
|
||||||
COPY ./binary/notary-signer /bin/notary-signer
|
COPY ./binary/notary-signer /bin/notary-signer
|
||||||
COPY ./migrate /bin/migrate
|
COPY ./binary/migrate /bin/migrate
|
||||||
COPY ./migrations/ /migrations/
|
COPY ./binary/migrations/ /migrations/
|
||||||
COPY ./signer-start.sh /bin/signer-start.sh
|
COPY ./signer-start.sh /bin/signer-start.sh
|
||||||
|
|
||||||
RUN chmod u+x /bin/notary-signer /migrations/migrate.sh /bin/migrate /bin/signer-start.sh
|
RUN chmod u+x /bin/notary-signer /migrations/migrate.sh /bin/migrate /bin/signer-start.sh
|
||||||
|
@ -3,7 +3,7 @@ FROM vmware/photon:1.0
|
|||||||
ENV PGDATA /var/lib/postgresql/data
|
ENV PGDATA /var/lib/postgresql/data
|
||||||
|
|
||||||
RUN touch /etc/localtime.bak \
|
RUN touch /etc/localtime.bak \
|
||||||
&& tdnf distro-sync -y \
|
&& tdnf distro-sync -y || echo \
|
||||||
&& tdnf install -y sed shadow gzip postgresql\
|
&& tdnf install -y sed shadow gzip postgresql\
|
||||||
&& groupadd -r postgres --gid=999 \
|
&& groupadd -r postgres --gid=999 \
|
||||||
&& useradd -r -g postgres --uid=999 postgres \
|
&& useradd -r -g postgres --uid=999 postgres \
|
@ -3,7 +3,7 @@ FROM vmware/photon:1.0
|
|||||||
MAINTAINER wangyan@vmware.com
|
MAINTAINER wangyan@vmware.com
|
||||||
|
|
||||||
# The original script in the docker offical registry image.
|
# The original script in the docker offical registry image.
|
||||||
RUN tdnf distro-sync -y \
|
RUN tdnf distro-sync -y || echo \
|
||||||
&& tdnf erase vim -y \
|
&& tdnf erase vim -y \
|
||||||
&& tdnf install sudo -y \
|
&& tdnf install sudo -y \
|
||||||
&& tdnf clean all \
|
&& tdnf clean all \
|
||||||
@ -13,7 +13,6 @@ COPY entrypoint.sh /
|
|||||||
RUN chmod u+x /entrypoint.sh
|
RUN chmod u+x /entrypoint.sh
|
||||||
|
|
||||||
RUN mkdir -p /etc/registry
|
RUN mkdir -p /etc/registry
|
||||||
COPY config.yml /etc/registry/config.yml
|
|
||||||
|
|
||||||
COPY binary/registry /usr/bin
|
COPY binary/registry /usr/bin
|
||||||
RUN chmod u+x /usr/bin/registry
|
RUN chmod u+x /usr/bin/registry
|
||||||
|
@ -2,29 +2,12 @@
|
|||||||
|
|
||||||
set +e
|
set +e
|
||||||
|
|
||||||
echo "Usage: #./builder [registry version] [photon tag] [registry username] [registry password]"
|
|
||||||
if [ -z $1 ]; then
|
if [ -z $1 ]; then
|
||||||
error "Please set the 'version' variable"
|
error "Please set the 'version' variable"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ -z $2 ]; then
|
|
||||||
error "Please set the 'photonversion' variable"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z $3 ]; then
|
|
||||||
error "Please set the 'username' variable"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z $4 ]; then
|
|
||||||
error "Please set the 'password' variable"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
VERSION="$1"
|
VERSION="$1"
|
||||||
PHOTONVERSION="$2"
|
|
||||||
USERNAME="$3"
|
|
||||||
PASSWORD="$4"
|
|
||||||
PHOTONIMAGE=vmware/registry:$PHOTONVERSION
|
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@ -52,13 +35,5 @@ docker rmi -f registry-golang
|
|||||||
|
|
||||||
echo "Build registry binary success, then to build photon image..."
|
echo "Build registry binary success, then to build photon image..."
|
||||||
cd $cur
|
cd $cur
|
||||||
echo $PHOTONIMAGE
|
|
||||||
cp $TEMP/cmd/registry/config-example.yml config.yml
|
cp $TEMP/cmd/registry/config-example.yml config.yml
|
||||||
docker build -f Dockerfile -t $PHOTONIMAGE .
|
|
||||||
|
|
||||||
rm -rf $TEMP
|
rm -rf $TEMP
|
||||||
rm -rf binary
|
|
||||||
rm -rf config.yml
|
|
||||||
|
|
||||||
echo 'Push image to docker hub.'
|
|
||||||
../../pushimage.sh $PHOTONIMAGE $USERNAME $PASSWORD
|
|
@ -1,6 +1,6 @@
|
|||||||
FROM vmware/photon:1.0
|
FROM vmware/photon:1.0
|
||||||
|
|
||||||
RUN tdnf distro-sync -y \
|
RUN tdnf distro-sync -y || echo \
|
||||||
&& tdnf erase vim -y \
|
&& tdnf erase vim -y \
|
||||||
&& tdnf install sudo -y \
|
&& tdnf install sudo -y \
|
||||||
&& tdnf clean all \
|
&& tdnf clean all \
|
||||||
|
@ -16,8 +16,8 @@ package dao
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,12 +35,10 @@ func SetClairVulnTimestamp(namespace string, timestamp time.Time) error {
|
|||||||
if !created {
|
if !created {
|
||||||
rec.LastUpdate = timestamp
|
rec.LastUpdate = timestamp
|
||||||
n, err := o.Update(rec)
|
n, err := o.Update(rec)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return fmt.Errorf("No record is updated, record: %v", *rec)
|
log.Warningf("no records are updated for %v", *rec)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
"github.com/vmware/harbor/src/common"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
"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"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
@ -305,6 +305,7 @@ var currentUser *models.User
|
|||||||
func TestGetUser(t *testing.T) {
|
func TestGetUser(t *testing.T) {
|
||||||
queryUser := models.User{
|
queryUser := models.User{
|
||||||
Username: username,
|
Username: username,
|
||||||
|
Email: "tester01@vmware.com",
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
currentUser, err = GetUser(queryUser)
|
currentUser, err = GetUser(queryUser)
|
||||||
@ -1531,8 +1532,6 @@ func TestUpdateScanJobStatus(t *testing.T) {
|
|||||||
j, err := GetScanJob(id)
|
j, err := GetScanJob(id)
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
assert.Equal("newstatus", j.Status)
|
assert.Equal("newstatus", j.Status)
|
||||||
err = UpdateScanJobStatus(id+9, "newstatus")
|
|
||||||
assert.NotNil(err)
|
|
||||||
err = ClearTable(models.ScanJobTable)
|
err = ClearTable(models.ScanJobTable)
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
}
|
}
|
||||||
@ -1632,7 +1631,6 @@ func TestGetScanJobsByStatus(t *testing.T) {
|
|||||||
assert.Equal(sj1.Repository, r2[0].Repository)
|
assert.Equal(sj1.Repository, r2[0].Repository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestSaveConfigEntries(t *testing.T) {
|
func TestSaveConfigEntries(t *testing.T) {
|
||||||
configEntries := []models.ConfigEntry{
|
configEntries := []models.ConfigEntry{
|
||||||
{
|
{
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddRepTarget ...
|
// AddRepTarget ...
|
||||||
@ -396,9 +397,9 @@ func UpdateRepJobStatus(id int64, status string) error {
|
|||||||
Status: status,
|
Status: status,
|
||||||
UpdateTime: time.Now(),
|
UpdateTime: time.Now(),
|
||||||
}
|
}
|
||||||
num, err := o.Update(&j, "Status", "UpdateTime")
|
n, err := o.Update(&j, "Status", "UpdateTime")
|
||||||
if num == 0 {
|
if n == 0 {
|
||||||
err = fmt.Errorf("Failed to update replication job with id: %d %s", id, err.Error())
|
log.Warningf("no records are updated when updating replication job %d", id)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package dao
|
|||||||
import (
|
import (
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -78,7 +79,7 @@ func UpdateScanJobStatus(id int64, status string) error {
|
|||||||
}
|
}
|
||||||
n, err := o.Update(&sj, "Status", "UpdateTime")
|
n, err := o.Update(&sj, "Status", "UpdateTime")
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return fmt.Errorf("Failed to update scan job with id: %d, error: %v", id, err)
|
log.Warningf("no records are updated when updating scan job %d", id)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -109,8 +110,9 @@ func SetScanJobForImg(digest string, jobID int64) error {
|
|||||||
rec.UpdateTime = time.Now()
|
rec.UpdateTime = time.Now()
|
||||||
n, err := o.Update(rec, "JobID", "UpdateTime")
|
n, err := o.Update(rec, "JobID", "UpdateTime")
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return fmt.Errorf("Failed to set scan job for image with digest: %s, error: %v", digest, err)
|
log.Warningf("no records are updated when setting scan job for image with digest %s", digest)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,11 @@ func GetUser(query models.User) (*models.User, error) {
|
|||||||
queryParam = append(queryParam, query.ResetUUID)
|
queryParam = append(queryParam, query.ResetUUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Email != "" {
|
||||||
|
sql += ` and email = ? `
|
||||||
|
queryParam = append(queryParam, query.Email)
|
||||||
|
}
|
||||||
|
|
||||||
var u []models.User
|
var u []models.User
|
||||||
n, err := o.Raw(sql, queryParam).QueryRows(&u)
|
n, err := o.Raw(sql, queryParam).QueryRows(&u)
|
||||||
|
|
||||||
|
@ -17,6 +17,5 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,15 +15,13 @@
|
|||||||
package ldap
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"crypto/tls"
|
|
||||||
|
|
||||||
"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"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
@ -31,77 +29,68 @@ import (
|
|||||||
goldap "gopkg.in/ldap.v2"
|
goldap "gopkg.in/ldap.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetSystemLdapConf ...
|
//Session - define a LDAP session
|
||||||
func GetSystemLdapConf() (models.LdapConf, error) {
|
type Session struct {
|
||||||
var err error
|
ldapConfig models.LdapConf
|
||||||
var ldapConfs models.LdapConf
|
ldapConn *goldap.Conn
|
||||||
var authMode string
|
}
|
||||||
|
|
||||||
authMode, err = config.AuthMode()
|
//LoadSystemLdapConfig - load LDAP configure from adminserver
|
||||||
|
func LoadSystemLdapConfig() (*Session, error) {
|
||||||
|
var session Session
|
||||||
|
|
||||||
|
authMode, err := config.AuthMode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("can't load auth mode from system, error: %v", err)
|
log.Errorf("can't load auth mode from system, error: %v", err)
|
||||||
return ldapConfs, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if authMode != "ldap_auth" {
|
if authMode != "ldap_auth" {
|
||||||
return ldapConfs, fmt.Errorf("system auth_mode isn't ldap_auth, please check configuration")
|
return nil, fmt.Errorf("system auth_mode isn't ldap_auth, please check configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
ldap, err := config.LDAP()
|
ldap, err := config.LDAP()
|
||||||
if err != nil {
|
|
||||||
return ldapConfs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ldapConfs.LdapURL = ldap.URL
|
|
||||||
ldapConfs.LdapSearchDn = ldap.SearchDN
|
|
||||||
ldapConfs.LdapSearchPassword = ldap.SearchPassword
|
|
||||||
ldapConfs.LdapBaseDn = ldap.BaseDN
|
|
||||||
ldapConfs.LdapFilter = ldap.Filter
|
|
||||||
ldapConfs.LdapUID = ldap.UID
|
|
||||||
ldapConfs.LdapScope = ldap.Scope
|
|
||||||
ldapConfs.LdapConnectionTimeout = ldap.Timeout
|
|
||||||
ldapConfs.LdapVerifyCert = ldap.VerifyCert
|
|
||||||
|
|
||||||
// ldapConfs = config.LDAP().URL
|
|
||||||
// ldapConfs.LdapSearchDn = config.LDAP().SearchDn
|
|
||||||
// ldapConfs.LdapSearchPassword = config.LDAP().SearchPwd
|
|
||||||
// ldapConfs.LdapBaseDn = config.LDAP().BaseDn
|
|
||||||
// ldapConfs.LdapFilter = config.LDAP().Filter
|
|
||||||
// ldapConfs.LdapUID = config.LDAP().UID
|
|
||||||
// ldapConfs.LdapScope, err = strconv.Atoi(config.LDAP().Scope)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Errorf("invalid LdapScope format from system, error: %v", err)
|
|
||||||
// return ldapConfs, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ldapConfs.LdapConnectionTimeout, err = strconv.Atoi(config.LDAP().ConnectTimeout)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Errorf("invalid LdapConnectionTimeout format from system, error: %v", err)
|
|
||||||
// return ldapConfs, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
return ldapConfs, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateLdapConf ...
|
|
||||||
func ValidateLdapConf(ldapConfs models.LdapConf) (models.LdapConf, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if ldapConfs.LdapURL == "" {
|
|
||||||
return ldapConfs, fmt.Errorf("can not get any available LDAP_URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
ldapConfs.LdapURL, err = formatLdapURL(ldapConfs.LdapURL)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("invalid LdapURL format, error: %v", err)
|
return nil, err
|
||||||
return ldapConfs, err
|
}
|
||||||
|
if ldap.URL == "" {
|
||||||
|
return nil, fmt.Errorf("can not get any available LDAP_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compatible with legacy codes
|
ldapURL, err := formatURL(ldap.URL)
|
||||||
// in previous harbor.cfg:
|
if err != nil {
|
||||||
// the scope to search for users, 1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
session.ldapConfig.LdapURL = ldapURL
|
||||||
|
session.ldapConfig.LdapSearchDn = ldap.SearchDN
|
||||||
|
session.ldapConfig.LdapSearchPassword = ldap.SearchPassword
|
||||||
|
session.ldapConfig.LdapBaseDn = ldap.BaseDN
|
||||||
|
session.ldapConfig.LdapFilter = ldap.Filter
|
||||||
|
session.ldapConfig.LdapUID = ldap.UID
|
||||||
|
session.ldapConfig.LdapConnectionTimeout = ldap.Timeout
|
||||||
|
session.ldapConfig.LdapVerifyCert = ldap.VerifyCert
|
||||||
|
log.Debugf("Load system configuration: %v", ldap)
|
||||||
|
|
||||||
|
switch ldap.Scope {
|
||||||
|
case 1:
|
||||||
|
session.ldapConfig.LdapScope = goldap.ScopeBaseObject
|
||||||
|
case 2:
|
||||||
|
session.ldapConfig.LdapScope = goldap.ScopeSingleLevel
|
||||||
|
case 3:
|
||||||
|
session.ldapConfig.LdapScope = goldap.ScopeWholeSubtree
|
||||||
|
default:
|
||||||
|
log.Errorf("invalid ldap search scope %v", ldap.Scope)
|
||||||
|
return nil, fmt.Errorf("invalid ldap search scope")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateWithUIConfig - create a Session with config from UI
|
||||||
|
func CreateWithUIConfig(ldapConfs models.LdapConf) (*Session, error) {
|
||||||
|
|
||||||
switch ldapConfs.LdapScope {
|
switch ldapConfs.LdapScope {
|
||||||
case 1:
|
case 1:
|
||||||
ldapConfs.LdapScope = goldap.ScopeBaseObject
|
ldapConfs.LdapScope = goldap.ScopeBaseObject
|
||||||
@ -110,131 +99,44 @@ func ValidateLdapConf(ldapConfs models.LdapConf) (models.LdapConf, error) {
|
|||||||
case 3:
|
case 3:
|
||||||
ldapConfs.LdapScope = goldap.ScopeWholeSubtree
|
ldapConfs.LdapScope = goldap.ScopeWholeSubtree
|
||||||
default:
|
default:
|
||||||
return ldapConfs, fmt.Errorf("invalid ldap search scope")
|
return nil, fmt.Errorf("invalid ldap search scope")
|
||||||
}
|
}
|
||||||
|
|
||||||
// value := reflect.ValueOf(ldapConfs)
|
return createWithInternalConfig(ldapConfs)
|
||||||
// lType := reflect.TypeOf(ldapConfs)
|
|
||||||
// for i := 0; i < value.NumField(); i++ {
|
|
||||||
// fmt.Printf("Field %d: %v %v\n", i, value.Field(i), lType.Field(i).Name)
|
|
||||||
// }
|
|
||||||
|
|
||||||
return ldapConfs, nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeFilter ...
|
// createWithInternalConfig - create a Session with internal config
|
||||||
func MakeFilter(username string, ldapFilter string, ldapUID string) string {
|
func createWithInternalConfig(ldapConfs models.LdapConf) (*Session, error) {
|
||||||
|
|
||||||
var filterTag string
|
var session Session
|
||||||
|
|
||||||
if username == "" {
|
if ldapConfs.LdapURL == "" {
|
||||||
filterTag = "*"
|
return nil, fmt.Errorf("can not get any available LDAP_URL")
|
||||||
} else {
|
|
||||||
filterTag = username
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ldapFilter == "" {
|
ldapURL, err := formatURL(ldapConfs.LdapURL)
|
||||||
ldapFilter = "(" + ldapUID + "=" + filterTag + ")"
|
|
||||||
} else {
|
|
||||||
if !strings.Contains(ldapFilter, ldapUID+"=") {
|
|
||||||
ldapFilter = "(&" + ldapFilter + "(" + ldapUID + "=" + filterTag + "))"
|
|
||||||
} else {
|
|
||||||
ldapFilter = strings.Replace(ldapFilter, ldapUID+"=*", ldapUID+"="+filterTag, -1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("one or more ldapFilter: ", ldapFilter)
|
|
||||||
|
|
||||||
return ldapFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectTest ...
|
|
||||||
func ConnectTest(ldapConfs models.LdapConf) error {
|
|
||||||
|
|
||||||
var ldapConn *goldap.Conn
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ldapConn, err = dialLDAP(ldapConfs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer ldapConn.Close()
|
|
||||||
|
|
||||||
if ldapConfs.LdapSearchDn != "" {
|
|
||||||
err = bindLDAPSearchDN(ldapConfs, ldapConn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchUser ...
|
|
||||||
func SearchUser(ldapConfs models.LdapConf) ([]models.LdapUser, error) {
|
|
||||||
var ldapUsers []models.LdapUser
|
|
||||||
var ldapConn *goldap.Conn
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ldapConn, err = dialLDAP(ldapConfs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer ldapConn.Close()
|
|
||||||
|
|
||||||
if ldapConfs.LdapSearchDn != "" {
|
|
||||||
err = bindLDAPSearchDN(ldapConfs, ldapConn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ldapConfs.LdapBaseDn == "" {
|
|
||||||
return nil, fmt.Errorf("can not get any available LDAP_BASE_DN")
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := searchLDAP(ldapConfs, ldapConn)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ldapEntry := range result.Entries {
|
session.ldapConfig.LdapURL = ldapURL
|
||||||
var u models.LdapUser
|
session.ldapConfig.LdapSearchDn = ldapConfs.LdapSearchDn
|
||||||
for _, attr := range ldapEntry.Attributes {
|
session.ldapConfig.LdapSearchPassword = ldapConfs.LdapSearchPassword
|
||||||
//OpenLDAP sometimes contains leading space in username
|
session.ldapConfig.LdapBaseDn = ldapConfs.LdapBaseDn
|
||||||
val := strings.TrimSpace(attr.Values[0])
|
session.ldapConfig.LdapFilter = ldapConfs.LdapFilter
|
||||||
log.Debugf("Current ldap entry attr name: %s\n", attr.Name)
|
session.ldapConfig.LdapUID = ldapConfs.LdapUID
|
||||||
switch strings.ToLower(attr.Name) {
|
session.ldapConfig.LdapConnectionTimeout = ldapConfs.LdapConnectionTimeout
|
||||||
case strings.ToLower(ldapConfs.LdapUID):
|
session.ldapConfig.LdapVerifyCert = ldapConfs.LdapVerifyCert
|
||||||
u.Username = val
|
session.ldapConfig.LdapScope = ldapConfs.LdapScope
|
||||||
case "uid":
|
return &session, nil
|
||||||
u.Realname = val
|
|
||||||
case "cn":
|
|
||||||
u.Realname = val
|
|
||||||
case "mail":
|
|
||||||
u.Email = val
|
|
||||||
case "email":
|
|
||||||
u.Email = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
u.DN = ldapEntry.DN
|
|
||||||
ldapUsers = append(ldapUsers, u)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ldapUsers, nil
|
func formatURL(ldapURL string) (string, error) {
|
||||||
}
|
|
||||||
|
|
||||||
func formatLdapURL(ldapURL string) (string, error) {
|
|
||||||
|
|
||||||
var protocol, hostport string
|
var protocol, hostport string
|
||||||
var err error
|
|
||||||
|
|
||||||
_, err = url.Parse(ldapURL)
|
_, err := url.Parse(ldapURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("parse Ldap Host ERR: %s", err)
|
return "", fmt.Errorf("parse Ldap Host ERR: %s", err)
|
||||||
}
|
}
|
||||||
@ -243,7 +145,7 @@ func formatLdapURL(ldapURL string) (string, error) {
|
|||||||
splitLdapURL := strings.Split(ldapURL, "://")
|
splitLdapURL := strings.Split(ldapURL, "://")
|
||||||
protocol, hostport = splitLdapURL[0], splitLdapURL[1]
|
protocol, hostport = splitLdapURL[0], splitLdapURL[1]
|
||||||
if !((protocol == "ldap") || (protocol == "ldaps")) {
|
if !((protocol == "ldap") || (protocol == "ldaps")) {
|
||||||
return "", fmt.Errorf("unknown ldap protocl")
|
return "", fmt.Errorf("unknown ldap protocol")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hostport = ldapURL
|
hostport = ldapURL
|
||||||
@ -275,128 +177,165 @@ func formatLdapURL(ldapURL string) (string, error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportUser ...
|
//ConnectionTest - test ldap session connection with system default setting
|
||||||
func ImportUser(user models.LdapUser) (int64, error) {
|
func (session *Session) ConnectionTest() error {
|
||||||
var u models.User
|
session, err := LoadSystemLdapConfig()
|
||||||
u.Username = user.Username
|
|
||||||
u.Email = user.Email
|
|
||||||
u.Realname = user.Realname
|
|
||||||
|
|
||||||
log.Debug("username:", u.Username, ",email:", u.Email)
|
|
||||||
exist, err := dao.UserExists(u, "username")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("system checking user %s failed, error: %v", user.Username, err)
|
return fmt.Errorf("Failed to load system ldap config")
|
||||||
return 0, fmt.Errorf("internal_error")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if exist {
|
return ConnectionTestWithConfig(session.ldapConfig)
|
||||||
return 0, fmt.Errorf("duplicate_username")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exist, err = dao.UserExists(u, "email")
|
//ConnectionTestWithConfig - test ldap session connection, out of the scope of normal session create/close
|
||||||
|
func ConnectionTestWithConfig(ldapConfig models.LdapConf) error {
|
||||||
|
|
||||||
|
authMode, err := config.AuthMode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("system checking %s mailbox failed, error: %v", user.Username, err)
|
log.Errorf("Connection test failed %v", err)
|
||||||
return 0, fmt.Errorf("internal_error")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if exist {
|
//If no password present, use the system default password
|
||||||
return 0, fmt.Errorf("duplicate_mailbox")
|
if ldapConfig.LdapSearchPassword == "" && authMode == "ldap_auth" {
|
||||||
}
|
|
||||||
|
|
||||||
u.Password = "12345678AbC"
|
session, err := LoadSystemLdapConfig()
|
||||||
u.Comment = "from LDAP."
|
|
||||||
if u.Email == "" {
|
|
||||||
u.Email = u.Username + "@placeholder.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
UserID, err := dao.Register(u)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("system register user %s failed, error: %v", user.Username, err)
|
return fmt.Errorf("Failed to load system ldap config")
|
||||||
return 0, fmt.Errorf("registe_user_error")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserID, nil
|
ldapConfig.LdapSearchPassword = session.ldapConfig.LdapSearchPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind establish a connection to ldap based on ldapConfs and bind the user with given parameters.
|
testSession, err := createWithInternalConfig(ldapConfig)
|
||||||
func Bind(ldapConfs models.LdapConf, dn string, password string) error {
|
|
||||||
conn, err := dialLDAP(ldapConfs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
err = testSession.Open()
|
||||||
if ldapConfs.LdapSearchDn != "" {
|
|
||||||
if err := bindLDAPSearchDN(ldapConfs, conn); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return conn.Bind(dn, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dialLDAP(ldapConfs models.LdapConf) (*goldap.Conn, error) {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var ldap *goldap.Conn
|
|
||||||
splitLdapURL := strings.Split(ldapConfs.LdapURL, "://")
|
|
||||||
protocol, hostport := splitLdapURL[0], splitLdapURL[1]
|
|
||||||
host := strings.Split(hostport, ":")[0]
|
|
||||||
|
|
||||||
// Sets a Dial Timeout for LDAP
|
|
||||||
connectionTimeout := ldapConfs.LdapConnectionTimeout
|
|
||||||
goldap.DefaultTimeout = time.Duration(connectionTimeout) * time.Second
|
|
||||||
|
|
||||||
switch protocol {
|
|
||||||
case "ldap":
|
|
||||||
ldap, err = goldap.Dial("tcp", hostport)
|
|
||||||
case "ldaps":
|
|
||||||
log.Debug("Start to dial ldaps")
|
|
||||||
ldap, err = goldap.DialTLS("tcp", hostport, &tls.Config{ServerName: host, InsecureSkipVerify: !ldapConfs.LdapVerifyCert})
|
|
||||||
}
|
|
||||||
|
|
||||||
return ldap, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindLDAPSearchDN(ldapConfs models.LdapConf, ldap *goldap.Conn) error {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ldapSearchDn := ldapConfs.LdapSearchDn
|
|
||||||
ldapSearchPassword := ldapConfs.LdapSearchPassword
|
|
||||||
|
|
||||||
err = ldap.Bind(ldapSearchDn, ldapSearchPassword)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Bind search dn error", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer testSession.Close()
|
||||||
|
|
||||||
|
if testSession.ldapConfig.LdapSearchDn != "" {
|
||||||
|
err = testSession.Bind(testSession.ldapConfig.LdapSearchDn, testSession.ldapConfig.LdapSearchPassword)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchLDAP(ldapConfs models.LdapConf, ldap *goldap.Conn) (*goldap.SearchResult, error) {
|
//SearchUser - search LDAP user by name
|
||||||
|
func (session *Session) SearchUser(username string) ([]models.LdapUser, error) {
|
||||||
|
var ldapUsers []models.LdapUser
|
||||||
|
ldapFilter := session.createUserFilter(username)
|
||||||
|
result, err := session.SearchLdap(ldapFilter)
|
||||||
|
|
||||||
var err error
|
if err != nil {
|
||||||
ldapBaseDn := ldapConfs.LdapBaseDn
|
return nil, err
|
||||||
ldapScope := ldapConfs.LdapScope
|
}
|
||||||
ldapFilter := ldapConfs.LdapFilter
|
|
||||||
|
for _, ldapEntry := range result.Entries {
|
||||||
|
var u models.LdapUser
|
||||||
|
for _, attr := range ldapEntry.Attributes {
|
||||||
|
//OpenLdap sometimes contain leading space in useranme
|
||||||
|
val := strings.TrimSpace(attr.Values[0])
|
||||||
|
log.Debugf("Current ldap entry attr name: %s\n", attr.Name)
|
||||||
|
switch strings.ToLower(attr.Name) {
|
||||||
|
case strings.ToLower(session.ldapConfig.LdapUID):
|
||||||
|
u.Username = val
|
||||||
|
case "uid":
|
||||||
|
u.Realname = val
|
||||||
|
case "cn":
|
||||||
|
u.Realname = val
|
||||||
|
case "mail":
|
||||||
|
u.Email = val
|
||||||
|
case "email":
|
||||||
|
u.Email = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.DN = ldapEntry.DN
|
||||||
|
ldapUsers = append(ldapUsers, u)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return ldapUsers, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind with specified DN and password, used in authentication
|
||||||
|
func (session *Session) Bind(dn string, password string) error {
|
||||||
|
return session.ldapConn.Bind(dn, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Open - open Session
|
||||||
|
func (session *Session) Open() error {
|
||||||
|
|
||||||
|
splitLdapURL := strings.Split(session.ldapConfig.LdapURL, "://")
|
||||||
|
protocol, hostport := splitLdapURL[0], splitLdapURL[1]
|
||||||
|
host := strings.Split(hostport, ":")[0]
|
||||||
|
|
||||||
|
connectionTimeout := session.ldapConfig.LdapConnectionTimeout
|
||||||
|
goldap.DefaultTimeout = time.Duration(connectionTimeout) * time.Second
|
||||||
|
|
||||||
|
switch protocol {
|
||||||
|
case "ldap":
|
||||||
|
ldap, err := goldap.Dial("tcp", hostport)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
session.ldapConn = ldap
|
||||||
|
case "ldaps":
|
||||||
|
log.Debug("Start to dial ldaps")
|
||||||
|
ldap, err := goldap.DialTLS("tcp", hostport, &tls.Config{ServerName: host, InsecureSkipVerify: !session.ldapConfig.LdapVerifyCert})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
session.ldapConn = ldap
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchLdap to search ldap with the provide filter
|
||||||
|
func (session *Session) SearchLdap(filter string) (*goldap.SearchResult, error) {
|
||||||
|
|
||||||
|
if err := session.Bind(session.ldapConfig.LdapSearchDn, session.ldapConfig.LdapSearchPassword); err != nil {
|
||||||
|
return nil, fmt.Errorf("Can not bind search dn, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
attributes := []string{"uid", "cn", "mail", "email"}
|
attributes := []string{"uid", "cn", "mail", "email"}
|
||||||
lowerUID := strings.ToLower(ldapConfs.LdapUID)
|
lowerUID := strings.ToLower(session.ldapConfig.LdapUID)
|
||||||
|
|
||||||
if lowerUID != "uid" && lowerUID != "cn" && lowerUID != "mail" && lowerUID != "email" {
|
if lowerUID != "uid" && lowerUID != "cn" && lowerUID != "mail" && lowerUID != "email" {
|
||||||
attributes = append(attributes, ldapConfs.LdapUID)
|
attributes = append(attributes, session.ldapConfig.LdapUID)
|
||||||
}
|
}
|
||||||
|
log.Debugf("Search ldap with filter:%v", filter)
|
||||||
searchRequest := goldap.NewSearchRequest(
|
searchRequest := goldap.NewSearchRequest(
|
||||||
ldapBaseDn,
|
session.ldapConfig.LdapBaseDn,
|
||||||
ldapScope,
|
session.ldapConfig.LdapScope,
|
||||||
goldap.NeverDerefAliases,
|
goldap.NeverDerefAliases,
|
||||||
0, // Unlimited results.
|
0, //Unlimited results
|
||||||
0, // Search Timeout.
|
0, //Search Timeout
|
||||||
false, // Types Only
|
false, //Types only
|
||||||
ldapFilter,
|
filter,
|
||||||
attributes,
|
attributes,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
result, err := ldap.Search(searchRequest)
|
result, err := session.ldapConn.Search(searchRequest)
|
||||||
|
if result != nil {
|
||||||
|
log.Debugf("Found entries:%v\n", len(result.Entries))
|
||||||
|
} else {
|
||||||
|
log.Debugf("No entries")
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("LDAP search error", err)
|
log.Debug("LDAP search error", err)
|
||||||
@ -404,39 +343,36 @@ func searchLDAP(ldapConfs models.LdapConf, ldap *goldap.Conn) (*goldap.SearchRes
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchAndImportUser - Search user in LDAP, if this user exist, import it to database
|
//CreateUserFilter - create filter to search user with specified username
|
||||||
func SearchAndImportUser(username string) (int64, error) {
|
func (session *Session) createUserFilter(username string) string {
|
||||||
var err error
|
var filterTag string
|
||||||
var userID int64
|
|
||||||
|
|
||||||
ldapConfs, err := GetSystemLdapConf()
|
if username == "" {
|
||||||
if err != nil {
|
filterTag = "*"
|
||||||
log.Errorf("Can not get ldap configuration, error %v", err)
|
} else {
|
||||||
return 0, err
|
filterTag = username
|
||||||
}
|
|
||||||
ldapConfs, err = ValidateLdapConf(ldapConfs)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Invalid ldap request, error: %v", err)
|
|
||||||
return 0, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapConfs.LdapFilter = MakeFilter(username, ldapConfs.LdapFilter, ldapConfs.LdapUID)
|
ldapFilter := session.ldapConfig.LdapFilter
|
||||||
log.Debugf("Search with LDAP with filter %s", ldapConfs.LdapFilter)
|
ldapUID := session.ldapConfig.LdapUID
|
||||||
|
|
||||||
ldapUsers, err := SearchUser(ldapConfs)
|
if ldapFilter == "" {
|
||||||
if err != nil {
|
ldapFilter = "(" + ldapUID + "=" + filterTag + ")"
|
||||||
log.Errorf("Can not search ldap, error %v, filter: %s", err, ldapConfs.LdapFilter)
|
} else {
|
||||||
return 0, err
|
ldapFilter = "(&" + ldapFilter + "(" + ldapUID + "=" + filterTag + "))"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ldapUsers) > 0 {
|
log.Debug("ldap filter :", ldapFilter)
|
||||||
log.Debugf("Importing user %s to local database", ldapUsers[0].Username)
|
|
||||||
if userID, err = ImportUser(ldapUsers[0]); err != nil {
|
return ldapFilter
|
||||||
log.Errorf("Can not import ldap user to local db, error %v", err)
|
}
|
||||||
return 0, err
|
|
||||||
|
//Close - close current session
|
||||||
|
func (session *Session) Close() {
|
||||||
|
if session.ldapConn != nil {
|
||||||
|
session.ldapConn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return userID, err
|
|
||||||
}
|
|
||||||
|
@ -11,19 +11,16 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package ldap
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
//"fmt"
|
|
||||||
//"strings"
|
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common"
|
"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/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/common/utils/test"
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
uiConfig "github.com/vmware/harbor/src/ui/config"
|
uiConfig "github.com/vmware/harbor/src/ui/config"
|
||||||
@ -48,21 +45,7 @@ var adminServerLdapTestConfig = map[string]interface{}{
|
|||||||
common.LDAPFilter: "",
|
common.LDAPFilter: "",
|
||||||
common.LDAPScope: 3,
|
common.LDAPScope: 3,
|
||||||
common.LDAPTimeout: 30,
|
common.LDAPTimeout: 30,
|
||||||
// config.TokenServiceURL: "",
|
|
||||||
// config.RegistryURL: "",
|
|
||||||
// config.EmailHost: "",
|
|
||||||
// config.EmailPort: 25,
|
|
||||||
// config.EmailUsername: "",
|
|
||||||
// config.EmailPassword: "password",
|
|
||||||
// config.EmailFrom: "from",
|
|
||||||
// config.EmailSSL: true,
|
|
||||||
// config.EmailIdentity: "",
|
|
||||||
// config.ProjectCreationRestriction: config.ProCrtRestrAdmOnly,
|
|
||||||
// config.VerifyRemoteCert: false,
|
|
||||||
// config.MaxJobWorkers: 3,
|
|
||||||
// config.TokenExpiration: 30,
|
|
||||||
common.CfgExpiration: 5,
|
common.CfgExpiration: 5,
|
||||||
// config.JobLogDir: "/var/log/jobs",
|
|
||||||
common.AdminInitialPassword: "password",
|
common.AdminInitialPassword: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,42 +88,33 @@ var adminServerDefaultConfigWithVerifyCert = map[string]interface{}{
|
|||||||
common.WithClair: false,
|
common.WithClair: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(m *testing.M) {
|
||||||
server, err := test.NewAdminserver(adminServerLdapTestConfig)
|
server, err := test.NewAdminserver(adminServerLdapTestConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create a mock admin server: %v", err)
|
log.Fatalf("failed to create a mock admin server: %v", err)
|
||||||
}
|
}
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
if err := os.Setenv("ADMINSERVER_URL", server.URL); err != nil {
|
if err := os.Setenv("ADMINSERVER_URL", server.URL); err != nil {
|
||||||
t.Fatalf("failed to set env %s: %v", "ADMINSERVER_URL", err)
|
log.Fatalf("failed to set env %s: %v", "ADMINSERVER_URL", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secretKeyPath := "/tmp/secretkey"
|
secretKeyPath := "/tmp/secretkey"
|
||||||
_, err = test.GenerateKey(secretKeyPath)
|
_, err = test.GenerateKey(secretKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to generate secret key: %v", err)
|
log.Errorf("failed to generate secret key: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer os.Remove(secretKeyPath)
|
defer os.Remove(secretKeyPath)
|
||||||
|
|
||||||
if err := os.Setenv("KEY_PATH", secretKeyPath); err != nil {
|
if err := os.Setenv("KEY_PATH", secretKeyPath); err != nil {
|
||||||
t.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
|
log.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := uiConfig.Init(); err != nil {
|
if err := uiConfig.Init(); err != nil {
|
||||||
t.Fatalf("failed to initialize configurations: %v", err)
|
log.Fatalf("failed to initialize configurations: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if err := uiConfig.Load(); err != nil {
|
|
||||||
// t.Fatalf("failed to load configurations: %v", err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// mode, err := uiConfig.AuthMode()
|
|
||||||
// if err != nil {
|
|
||||||
// t.Fatalf("failed to get auth mode: %v", err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
database, err := uiConfig.Database()
|
database, err := uiConfig.Database()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to get database configuration: %v", err)
|
log.Fatalf("failed to get database configuration: %v", err)
|
||||||
@ -149,173 +123,141 @@ func TestMain(t *testing.T) {
|
|||||||
if err := dao.InitDatabase(database); err != nil {
|
if err := dao.InitDatabase(database); err != nil {
|
||||||
log.Fatalf("failed to initialize database: %v", err)
|
log.Fatalf("failed to initialize database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSystemLdapConf(t *testing.T) {
|
func TestLoadSystemLdapConfig(t *testing.T) {
|
||||||
|
session, err := LoadSystemLdapConfig()
|
||||||
testLdapConfig, err := GetSystemLdapConf()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to get system ldap config %v", err)
|
t.Fatalf("failed to get system ldap config %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if testLdapConfig.LdapURL != "ldap://127.0.0.1" {
|
if session.ldapConfig.LdapURL != "ldap://127.0.0.1:389" {
|
||||||
t.Errorf("unexpected LdapURL: %s != %s", testLdapConfig.LdapURL, "ldap://test.ldap.com")
|
t.Errorf("unexpected LdapURL: %s != %s", session.ldapConfig.LdapURL, "ldap://127.0.0.1:389")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateLdapConf(t *testing.T) {
|
if session.ldapConfig.LdapScope != 2 {
|
||||||
|
t.Errorf("unexpected LdapScope: %d != %d", session.ldapConfig.LdapScope, 2)
|
||||||
testLdapConfig, err := GetSystemLdapConf()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to get system ldap config %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testLdapConfig, err = ValidateLdapConf(testLdapConfig)
|
|
||||||
|
|
||||||
if testLdapConfig.LdapScope != 2 {
|
|
||||||
t.Errorf("unexpected LdapScope: %d != %d", testLdapConfig.LdapScope, 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeFilter(t *testing.T) {
|
|
||||||
|
|
||||||
testLdapConfig, err := GetSystemLdapConf()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to get system ldap config %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testLdapConfig.LdapFilter = "(ou=people)"
|
|
||||||
tempUsername := ""
|
|
||||||
|
|
||||||
tempFilter := MakeFilter(tempUsername, testLdapConfig.LdapFilter, testLdapConfig.LdapUID)
|
|
||||||
if tempFilter != "(&(ou=people)(uid=*))" {
|
|
||||||
t.Errorf("unexpected tempFilter: %s != %s", tempFilter, "(&(ou=people)(uid=*))")
|
|
||||||
}
|
|
||||||
|
|
||||||
tempUsername = "user0001"
|
|
||||||
tempFilter = MakeFilter(tempUsername, testLdapConfig.LdapFilter, testLdapConfig.LdapUID)
|
|
||||||
if tempFilter != "(&(ou=people)(uid=user0001))" {
|
|
||||||
t.Errorf("unexpected tempFilter: %s != %s", tempFilter, "(&(ou=people)(uid=user0001)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatLdapURL(t *testing.T) {
|
|
||||||
testLdapConfig, err := GetSystemLdapConf()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to get system ldap config %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testLdapConfig.LdapURL = "test.ldap.com"
|
|
||||||
tempLdapURL, err := formatLdapURL(testLdapConfig.LdapURL)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to format Ldap URL %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tempLdapURL != "ldap://test.ldap.com:389" {
|
|
||||||
t.Errorf("unexpected tempLdapURL: %s != %s", tempLdapURL, "ldap://test.ldap.com:389")
|
|
||||||
}
|
|
||||||
|
|
||||||
testLdapConfig.LdapURL = "ldaps://test.ldap.com"
|
|
||||||
tempLdapURL, err = formatLdapURL(testLdapConfig.LdapURL)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to format Ldap URL %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tempLdapURL != "ldaps://test.ldap.com:636" {
|
|
||||||
t.Errorf("unexpected tempLdapURL: %s != %s", tempLdapURL, "ldap://test.ldap.com:636")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImportUser(t *testing.T) {
|
|
||||||
var u models.LdapUser
|
|
||||||
var user models.User
|
|
||||||
u.Username = "ldapUser0001"
|
|
||||||
u.Realname = "ldapUser"
|
|
||||||
_, err := ImportUser(u)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to add Ldap user: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
user.Username = "ldapUser0001"
|
|
||||||
user.Email = "ldapUser0001@placeholder.com"
|
|
||||||
|
|
||||||
exist, err := dao.UserExists(user, "username")
|
|
||||||
if !exist {
|
|
||||||
t.Errorf("failed to add Ldap username: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
exist, err = dao.UserExists(user, "email")
|
|
||||||
if !exist {
|
|
||||||
t.Errorf("failed to add Ldap user email: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = ImportUser(u)
|
|
||||||
if err.Error() != "duplicate_username" {
|
|
||||||
t.Fatalf("failed to checking duplicate user: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConnectTest(t *testing.T) {
|
func TestConnectTest(t *testing.T) {
|
||||||
|
session, err := LoadSystemLdapConfig()
|
||||||
testLdapConfig, err := GetSystemLdapConf()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to get system ldap config %v", err)
|
t.Errorf("failed to load system ldap config")
|
||||||
|
}
|
||||||
|
err = session.ConnectionTest()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected ldap connect fail: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
testLdapConfig.LdapURL = "ldap://localhost:389"
|
|
||||||
|
|
||||||
err = ConnectTest(testLdapConfig)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected ldap connect fail: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateUIConfig(t *testing.T) {
|
||||||
|
var testConfigs = []struct {
|
||||||
|
config models.LdapConf
|
||||||
|
internalValue int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
models.LdapConf{
|
||||||
|
LdapScope: 3,
|
||||||
|
LdapURL: "ldaps://127.0.0.1",
|
||||||
|
}, 2},
|
||||||
|
{
|
||||||
|
models.LdapConf{
|
||||||
|
LdapScope: 2,
|
||||||
|
LdapURL: "ldaps://127.0.0.1",
|
||||||
|
}, 1},
|
||||||
|
{
|
||||||
|
models.LdapConf{
|
||||||
|
LdapScope: 1,
|
||||||
|
LdapURL: "ldaps://127.0.0.1",
|
||||||
|
}, 0},
|
||||||
|
{
|
||||||
|
models.LdapConf{
|
||||||
|
LdapScope: 1,
|
||||||
|
LdapURL: "ldaps://127.0.0.1:abc",
|
||||||
|
}, -1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, val := range testConfigs {
|
||||||
|
session, err := CreateWithUIConfig(val.config)
|
||||||
|
if val.internalValue < 0 {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Should have error with url :%v", val.config)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Can not create with ui config, err:%v", err)
|
||||||
|
}
|
||||||
|
if session.ldapConfig.LdapScope != val.internalValue {
|
||||||
|
t.Fatalf("Test failed expected %v, actual %v", val.internalValue, session.ldapConfig.LdapScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchUser(t *testing.T) {
|
func TestSearchUser(t *testing.T) {
|
||||||
|
|
||||||
testLdapConfig, err := GetSystemLdapConf()
|
session, err := LoadSystemLdapConfig()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to get system ldap config %v", err)
|
t.Fatalf("Can not load system ldap config")
|
||||||
}
|
}
|
||||||
testLdapConfig.LdapURL = "ldap://localhost:389"
|
err = session.Open()
|
||||||
testLdapConfig.LdapFilter = MakeFilter("", testLdapConfig.LdapFilter, testLdapConfig.LdapUID)
|
|
||||||
|
|
||||||
ldapUsers, err := SearchUser(testLdapConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected ldap search fail: %v", err)
|
t.Fatalf("failed to create ldap session %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ldapUsers[0].Username != "test" {
|
err = session.Bind(session.ldapConfig.LdapSearchDn, session.ldapConfig.LdapSearchPassword)
|
||||||
t.Errorf("unexpected ldap user search result: %s = %s", "ldapUsers[0].Username", ldapUsers[0].Username)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSearchAndImportUser(t *testing.T) {
|
|
||||||
|
|
||||||
userID, err := SearchAndImportUser("test")
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed on error : %v ", err)
|
t.Fatalf("failed to bind search dn")
|
||||||
}
|
}
|
||||||
|
|
||||||
if userID <= 0 {
|
defer session.Close()
|
||||||
t.Fatalf("userID= %v", userID)
|
|
||||||
}
|
result, err := session.SearchUser("test")
|
||||||
|
if err != nil || len(result) == 0 {
|
||||||
|
t.Fatalf("failed to search user test!")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchAndImportUserNotExist(t *testing.T) {
|
}
|
||||||
|
|
||||||
userID, _ := SearchAndImportUser("notexist")
|
func TestFormatURL(t *testing.T) {
|
||||||
|
|
||||||
if userID > 0 {
|
var invalidURL = "http://localhost:389"
|
||||||
t.Fatal("Can not import a non exist ldap user!")
|
_, err := formatURL(invalidURL)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Should failed on invalid URL %v", invalidURL)
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var urls = []struct {
|
||||||
|
rawURL string
|
||||||
|
goodURL string
|
||||||
|
}{
|
||||||
|
{"ldaps://127.0.0.1", "ldaps://127.0.0.1:636"},
|
||||||
|
{"ldap://9.123.102.33", "ldap://9.123.102.33:389"},
|
||||||
|
{"ldaps://127.0.0.1:389", "ldaps://127.0.0.1:389"},
|
||||||
|
{"ldap://127.0.0.1:636", "ldaps://127.0.0.1:636"},
|
||||||
|
{"112.122.122.122", "ldap://112.122.122.122:389"},
|
||||||
|
{"ldap:\\wrong url", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, u := range urls {
|
||||||
|
goodURL, err := formatURL(u.rawURL)
|
||||||
|
if u.goodURL == "" {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Should failed on wrong url, %v", u.rawURL)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil || goodURL != u.goodURL {
|
||||||
|
t.Fatalf("Faild on URL: raw=%v, expected:%v, actual:%v", u.rawURL, u.goodURL, goodURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,5 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ package job
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
@ -80,7 +81,7 @@ func (sm *SM) Start(s string) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
sm.Logger.Errorf("Panic: %v, entering error state", r)
|
sm.Logger.Errorf("Panic: %v, entering error state", r)
|
||||||
log.Warningf("Panic when handling job: %v, panic: %v, entering error state", sm.CurrentJob, r)
|
log.Warningf("Panic when handling job: %v, panic: %v \n %s \n, entering error state", sm.CurrentJob, r, debug.Stack())
|
||||||
sm.EnterState(models.JobError)
|
sm.EnterState(models.JobError)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -17,6 +17,5 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,5 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@ -23,6 +22,7 @@ import (
|
|||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
|
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LdapAPI handles requesst to /api/ldap/ping /api/ldap/user/search /api/ldap/user/import
|
// LdapAPI handles requesst to /api/ldap/ping /api/ldap/user/search /api/ldap/user/import
|
||||||
@ -47,47 +47,27 @@ func (l *LdapAPI) Prepare() {
|
|||||||
|
|
||||||
// Ping ...
|
// Ping ...
|
||||||
func (l *LdapAPI) Ping() {
|
func (l *LdapAPI) Ping() {
|
||||||
var err error
|
|
||||||
var ldapConfs models.LdapConf
|
var ldapConfs models.LdapConf
|
||||||
|
var err error
|
||||||
|
var ldapSession *ldapUtils.Session
|
||||||
|
|
||||||
l.Ctx.Input.CopyBody(1 << 32)
|
l.Ctx.Input.CopyBody(1 << 32)
|
||||||
|
|
||||||
if string(l.Ctx.Input.RequestBody) == "" {
|
if string(l.Ctx.Input.RequestBody) == "" {
|
||||||
ldapConfs, err = ldapUtils.GetSystemLdapConf()
|
ldapSession, err = ldapUtils.LoadSystemLdapConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Can't load system configuration, error: %v", err)
|
log.Errorf("Can't load system configuration, error: %v", err)
|
||||||
l.RenderError(http.StatusInternalServerError, fmt.Sprintf("can't load system configuration: %v", err))
|
l.RenderError(http.StatusInternalServerError, fmt.Sprintf("can't load system configuration: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = ldapSession.ConnectionTest()
|
||||||
} else {
|
} else {
|
||||||
l.DecodeJSONReqAndValidate(&ldapConfs)
|
l.DecodeJSONReqAndValidate(&ldapConfs)
|
||||||
v := map[string]interface{}{}
|
err = ldapUtils.ConnectionTestWithConfig(ldapConfs)
|
||||||
if err := json.Unmarshal(l.Ctx.Input.RequestBody,
|
|
||||||
&v); err != nil {
|
|
||||||
log.Errorf("failed to unmarshal LDAP server settings: %v", err)
|
|
||||||
l.RenderError(http.StatusInternalServerError, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, ok := v["ldap_search_password"]; !ok {
|
|
||||||
settings, err := ldapUtils.GetSystemLdapConf()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Can't load system configuration, error: %v", err)
|
|
||||||
l.RenderError(http.StatusInternalServerError, fmt.Sprintf("can't load system configuration: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ldapConfs.LdapSearchPassword = settings.LdapSearchPassword
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapConfs, err = ldapUtils.ValidateLdapConf(ldapConfs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Invalid ldap request, error: %v", err)
|
log.Errorf("ldap connect fail, error: %v", err)
|
||||||
l.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid ldap request: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ldapUtils.ConnectTest(ldapConfs)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Ldap connect fail, error: %v", err)
|
|
||||||
l.RenderError(http.StatusBadRequest, fmt.Sprintf("ldap connect fail: %v", err))
|
l.RenderError(http.StatusBadRequest, fmt.Sprintf("ldap connect fail: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -98,26 +78,26 @@ func (l *LdapAPI) Search() {
|
|||||||
var err error
|
var err error
|
||||||
var ldapUsers []models.LdapUser
|
var ldapUsers []models.LdapUser
|
||||||
var ldapConfs models.LdapConf
|
var ldapConfs models.LdapConf
|
||||||
|
var ldapSession *ldapUtils.Session
|
||||||
l.Ctx.Input.CopyBody(1 << 32)
|
l.Ctx.Input.CopyBody(1 << 32)
|
||||||
if string(l.Ctx.Input.RequestBody) == "" {
|
if string(l.Ctx.Input.RequestBody) == "" {
|
||||||
ldapConfs, err = ldapUtils.GetSystemLdapConf()
|
ldapSession, err = ldapUtils.LoadSystemLdapConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Can't load system configuration, error: %v", err)
|
log.Errorf("can't load system configuration, error: %v", err)
|
||||||
l.RenderError(http.StatusInternalServerError, fmt.Sprintf("can't load system configuration: %v", err))
|
l.RenderError(http.StatusInternalServerError, fmt.Sprintf("can't load system configuration: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
l.DecodeJSONReqAndValidate(&ldapConfs)
|
l.DecodeJSONReqAndValidate(&ldapConfs)
|
||||||
|
ldapSession, err = ldapUtils.CreateWithUIConfig(ldapConfs)
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapConfs, err = ldapUtils.ValidateLdapConf(ldapConfs)
|
if err = ldapSession.Open(); err != nil {
|
||||||
|
log.Errorf("can't Open ldap session, error: %v", err)
|
||||||
if err != nil {
|
l.RenderError(http.StatusInternalServerError, fmt.Sprintf("can't open ldap session: %v", err))
|
||||||
log.Errorf("Invalid ldap request, error: %v", err)
|
|
||||||
l.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid ldap request: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer ldapSession.Close()
|
||||||
|
|
||||||
searchName := l.GetString("username")
|
searchName := l.GetString("username")
|
||||||
|
|
||||||
@ -131,9 +111,7 @@ func (l *LdapAPI) Search() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapConfs.LdapFilter = ldapUtils.MakeFilter(searchName, ldapConfs.LdapFilter, ldapConfs.LdapUID)
|
ldapUsers, err = ldapSession.SearchUser(searchName)
|
||||||
|
|
||||||
ldapUsers, err = ldapUtils.SearchUser(ldapConfs)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Ldap search fail, error: %v", err)
|
log.Errorf("Ldap search fail, error: %v", err)
|
||||||
@ -152,23 +130,9 @@ func (l *LdapAPI) ImportUser() {
|
|||||||
var ldapFailedImportUsers []models.LdapFailedImportUser
|
var ldapFailedImportUsers []models.LdapFailedImportUser
|
||||||
var ldapConfs models.LdapConf
|
var ldapConfs models.LdapConf
|
||||||
|
|
||||||
ldapConfs, err := ldapUtils.GetSystemLdapConf()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Can't load system configuration, error: %v", err)
|
|
||||||
l.RenderError(http.StatusInternalServerError, fmt.Sprintf("can't load system configuration: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.DecodeJSONReqAndValidate(&ldapImportUsers)
|
l.DecodeJSONReqAndValidate(&ldapImportUsers)
|
||||||
|
|
||||||
ldapConfs, err = ldapUtils.ValidateLdapConf(ldapConfs)
|
ldapFailedImportUsers, err := importUsers(ldapConfs, ldapImportUsers.LdapUIDList)
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Invalid ldap request, error: %v", err)
|
|
||||||
l.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid ldap request: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ldapFailedImportUsers, err = importUsers(ldapConfs, ldapImportUsers.LdapUIDList)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Ldap import user fail, error: %v", err)
|
log.Errorf("Ldap import user fail, error: %v", err)
|
||||||
@ -190,7 +154,16 @@ func importUsers(ldapConfs models.LdapConf, ldapImportUsers []string) ([]models.
|
|||||||
var failedImportUser []models.LdapFailedImportUser
|
var failedImportUser []models.LdapFailedImportUser
|
||||||
var u models.LdapFailedImportUser
|
var u models.LdapFailedImportUser
|
||||||
|
|
||||||
tempFilter := ldapConfs.LdapFilter
|
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("can't load system configuration, error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ldapSession.Open(); err != nil {
|
||||||
|
log.Errorf("Can't connect to ldap, error: %v", err)
|
||||||
|
}
|
||||||
|
defer ldapSession.Close()
|
||||||
|
|
||||||
for _, tempUID := range ldapImportUsers {
|
for _, tempUID := range ldapImportUsers {
|
||||||
u.UID = tempUID
|
u.UID = tempUID
|
||||||
@ -214,9 +187,7 @@ func importUsers(ldapConfs models.LdapConf, ldapImportUsers []string) ([]models.
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapConfs.LdapFilter = ldapUtils.MakeFilter(u.UID, tempFilter, ldapConfs.LdapUID)
|
ldapUsers, err := ldapSession.SearchUser(u.UID)
|
||||||
|
|
||||||
ldapUsers, err := ldapUtils.SearchUser(ldapConfs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.UID = tempUID
|
u.UID = tempUID
|
||||||
u.Error = "failed_search_user"
|
u.Error = "failed_search_user"
|
||||||
@ -225,16 +196,21 @@ func importUsers(ldapConfs models.LdapConf, ldapImportUsers []string) ([]models.
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ldapUsers == nil {
|
if ldapUsers == nil || len(ldapUsers) <= 0 {
|
||||||
u.UID = tempUID
|
u.UID = tempUID
|
||||||
u.Error = "unknown_user"
|
u.Error = "unknown_user"
|
||||||
failedImportUser = append(failedImportUser, u)
|
failedImportUser = append(failedImportUser, u)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = ldapUtils.ImportUser(ldapUsers[0])
|
var user models.User
|
||||||
|
|
||||||
if err != nil {
|
user.Username = ldapUsers[0].Username
|
||||||
|
user.Realname = ldapUsers[0].Realname
|
||||||
|
user.Email = ldapUsers[0].Email
|
||||||
|
err = auth.OnBoardUser(&user)
|
||||||
|
|
||||||
|
if err != nil || user.UserID <= 0 {
|
||||||
u.UID = tempUID
|
u.UID = tempUID
|
||||||
u.Error = err.Error()
|
u.Error = err.Error()
|
||||||
failedImportUser = append(failedImportUser, u)
|
failedImportUser = append(failedImportUser, u)
|
||||||
|
@ -21,9 +21,8 @@ import (
|
|||||||
|
|
||||||
"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"
|
||||||
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
|
|
||||||
"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/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProjectMemberAPI handles request to /api/projects/{}/members/{}
|
// ProjectMemberAPI handles request to /api/projects/{}/members/{}
|
||||||
@ -164,35 +163,35 @@ func (pma *ProjectMemberAPI) Post() {
|
|||||||
username := strings.TrimSpace(req.Username)
|
username := strings.TrimSpace(req.Username)
|
||||||
userID := checkUserExists(username)
|
userID := checkUserExists(username)
|
||||||
if userID <= 0 {
|
if userID <= 0 {
|
||||||
//check current authorization mode
|
|
||||||
authMode, err := config.AuthMode()
|
user, err := auth.SearchUser(username)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed the retrieve auth_mode, error: %s", err)
|
log.Errorf("Failed the search user, error: %v", err)
|
||||||
pma.RenderError(http.StatusInternalServerError, "Failed to retrieve auth_mode")
|
pma.RenderError(http.StatusInternalServerError, "Failed to search user")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if authMode != "ldap_auth" {
|
if user == nil {
|
||||||
log.Errorf("User does not exist, user name: %s", username)
|
log.Errorf("Current user doesn't exist: %v", username)
|
||||||
pma.RenderError(http.StatusNotFound, "User does not exist")
|
pma.RenderError(http.StatusNotFound, "Failed to search user: "+username)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//search and import user
|
err = auth.OnBoardUser(user)
|
||||||
newUserID, err := ldapUtils.SearchAndImportUser(username)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Search and import user failed, error: %v ", err)
|
log.Errorf("Failed the onboard user, error: %s", err)
|
||||||
pma.RenderError(http.StatusInternalServerError, "Failed to search and import user")
|
pma.RenderError(http.StatusInternalServerError, "Failed to onboard user")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if user.UserID <= 0 {
|
||||||
if newUserID <= 0 {
|
log.Error("Failed the onboard user, UserId <=0")
|
||||||
log.Error("Failed to create user")
|
pma.RenderError(http.StatusInternalServerError, "Failed to onboard user")
|
||||||
pma.RenderError(http.StatusNotFound, "Failed to create user")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
userID = user.UserID
|
||||||
|
|
||||||
userID = int(newUserID)
|
|
||||||
}
|
}
|
||||||
rolelist, err := dao.GetUserProjectRoles(userID, projectID)
|
rolelist, err := dao.GetUserProjectRoles(userID, projectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"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"
|
||||||
@ -160,16 +161,9 @@ func (ua *UserAPI) List() {
|
|||||||
|
|
||||||
// Put ...
|
// Put ...
|
||||||
func (ua *UserAPI) Put() {
|
func (ua *UserAPI) Put() {
|
||||||
ldapAdminUser := (ua.AuthMode == "ldap_auth" && ua.userID == 1 && ua.userID == ua.currentUserID)
|
if !ua.modifiable() {
|
||||||
|
ua.RenderError(http.StatusForbidden, fmt.Sprintf("User with ID %d cannot be modified", ua.userID))
|
||||||
if !(ua.AuthMode == "db_auth" || ldapAdminUser) {
|
return
|
||||||
ua.CustomAbort(http.StatusForbidden, "")
|
|
||||||
}
|
|
||||||
if !ua.IsAdmin {
|
|
||||||
if ua.userID != ua.currentUserID {
|
|
||||||
log.Warning("Guests can only change their own account.")
|
|
||||||
ua.CustomAbort(http.StatusForbidden, "Guests can only change their own account.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
user := models.User{UserID: ua.userID}
|
user := models.User{UserID: ua.userID}
|
||||||
ua.DecodeJSONReq(&user)
|
ua.DecodeJSONReq(&user)
|
||||||
@ -210,7 +204,7 @@ func (ua *UserAPI) Put() {
|
|||||||
// Post ...
|
// Post ...
|
||||||
func (ua *UserAPI) Post() {
|
func (ua *UserAPI) Post() {
|
||||||
|
|
||||||
if !(ua.AuthMode == "db_auth") {
|
if !(ua.AuthMode == common.DBAuth) {
|
||||||
ua.CustomAbort(http.StatusForbidden, "")
|
ua.CustomAbort(http.StatusForbidden, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,22 +252,8 @@ func (ua *UserAPI) Post() {
|
|||||||
|
|
||||||
// Delete ...
|
// Delete ...
|
||||||
func (ua *UserAPI) Delete() {
|
func (ua *UserAPI) Delete() {
|
||||||
if !ua.IsAdmin {
|
if !ua.IsAdmin || ua.AuthMode != common.DBAuth || ua.userID == 1 || ua.currentUserID == ua.userID {
|
||||||
log.Warningf("current user, id: %d does not have admin role, can not remove user", ua.currentUserID)
|
ua.RenderError(http.StatusForbidden, fmt.Sprintf("User with ID: %d cannot be removed, auth mode: %s, current user ID: %d", ua.userID, ua.AuthMode, ua.currentUserID))
|
||||||
ua.RenderError(http.StatusForbidden, "User does not have admin role")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ua.AuthMode == "ldap_auth" {
|
|
||||||
ua.CustomAbort(http.StatusForbidden, "user can not be deleted in LDAP authentication mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ua.currentUserID == ua.userID {
|
|
||||||
ua.CustomAbort(http.StatusForbidden, "can not delete yourself")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ua.userID == 1 {
|
|
||||||
ua.HandleForbidden(ua.SecurityCtx.GetUsername())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,17 +268,9 @@ func (ua *UserAPI) Delete() {
|
|||||||
|
|
||||||
// ChangePassword handles PUT to /api/users/{}/password
|
// ChangePassword handles PUT to /api/users/{}/password
|
||||||
func (ua *UserAPI) ChangePassword() {
|
func (ua *UserAPI) ChangePassword() {
|
||||||
ldapAdminUser := (ua.AuthMode == "ldap_auth" && ua.userID == 1 && ua.userID == ua.currentUserID)
|
if !ua.modifiable() {
|
||||||
|
ua.RenderError(http.StatusForbidden, fmt.Sprintf("User with ID: %d is not modifiable", ua.userID))
|
||||||
if !(ua.AuthMode == "db_auth" || ldapAdminUser) {
|
return
|
||||||
ua.CustomAbort(http.StatusForbidden, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ua.IsAdmin {
|
|
||||||
if ua.userID != ua.currentUserID {
|
|
||||||
log.Error("Guests can only change their own account.")
|
|
||||||
ua.CustomAbort(http.StatusForbidden, "Guests can only change their own account.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var req passwordReq
|
var req passwordReq
|
||||||
@ -345,6 +317,18 @@ func (ua *UserAPI) ToggleUserAdminRole() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modifiable returns whether the modify is allowed based on current auth mode and context
|
||||||
|
func (ua *UserAPI) modifiable() bool {
|
||||||
|
if ua.AuthMode == common.DBAuth {
|
||||||
|
//When the auth mode is local DB, admin can modify anyone, non-admin can modify himself.
|
||||||
|
return ua.IsAdmin || ua.userID == ua.currentUserID
|
||||||
|
}
|
||||||
|
//When the auth mode is external IDM backend, only the super user can modify himself,
|
||||||
|
//because he's the only one whose information is stored in local DB.
|
||||||
|
return ua.userID == 1 && ua.userID == ua.currentUserID
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// validate only validate when user register
|
// validate only validate when user register
|
||||||
func validate(user models.User) error {
|
func validate(user models.User) error {
|
||||||
|
|
||||||
|
@ -16,8 +16,11 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/vmware/harbor/src/common/api"
|
||||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testUser0002ID, testUser0003ID int
|
var testUser0002ID, testUser0003ID int
|
||||||
@ -430,3 +433,51 @@ func TestUsersDelete(t *testing.T) {
|
|||||||
assert.Equal(200, code, "Delete test user status should be 200")
|
assert.Equal(200, code, "Delete test user status should be 200")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestModifiable(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
base := BaseController{
|
||||||
|
api.BaseAPI{
|
||||||
|
beego.Controller{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
ua1 := &UserAPI{
|
||||||
|
base,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"db_auth",
|
||||||
|
}
|
||||||
|
assert.False(ua1.modifiable())
|
||||||
|
ua2 := &UserAPI{
|
||||||
|
base,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
"db_auth",
|
||||||
|
}
|
||||||
|
assert.True(ua2.modifiable())
|
||||||
|
ua3 := &UserAPI{
|
||||||
|
base,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
"ldap_auth",
|
||||||
|
}
|
||||||
|
assert.False(ua3.modifiable())
|
||||||
|
ua4 := &UserAPI{
|
||||||
|
base,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
"ldap_auth",
|
||||||
|
}
|
||||||
|
assert.True(ua4.modifiable())
|
||||||
|
}
|
||||||
|
@ -16,10 +16,34 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
var l = NewUserLock(2 * time.Second)
|
var l = NewUserLock(2 * time.Second)
|
||||||
|
|
||||||
|
var adminServerLdapTestConfig = map[string]interface{}{
|
||||||
|
common.ExtEndpoint: "host01.com",
|
||||||
|
common.AUTHMode: "ldap_auth",
|
||||||
|
common.DatabaseType: "mysql",
|
||||||
|
common.MySQLHost: "127.0.0.1",
|
||||||
|
common.MySQLPort: 3306,
|
||||||
|
common.MySQLUsername: "root",
|
||||||
|
common.MySQLPassword: "root123",
|
||||||
|
common.MySQLDatabase: "registry",
|
||||||
|
common.SQLiteFile: "/tmp/registry.db",
|
||||||
|
common.LDAPURL: "ldap://127.0.0.1",
|
||||||
|
common.LDAPSearchDN: "cn=admin,dc=example,dc=com",
|
||||||
|
common.LDAPSearchPwd: "admin",
|
||||||
|
common.LDAPBaseDN: "dc=example,dc=com",
|
||||||
|
common.LDAPUID: "uid",
|
||||||
|
common.LDAPFilter: "",
|
||||||
|
common.LDAPScope: 3,
|
||||||
|
common.LDAPTimeout: 30,
|
||||||
|
common.CfgExpiration: 5,
|
||||||
|
common.AdminInitialPassword: "password",
|
||||||
|
}
|
||||||
|
|
||||||
func TestLock(t *testing.T) {
|
func TestLock(t *testing.T) {
|
||||||
t.Log("Locking john")
|
t.Log("Locking john")
|
||||||
l.Lock("john")
|
l.Lock("john")
|
||||||
|
@ -26,33 +26,30 @@ import (
|
|||||||
// 1.5 seconds
|
// 1.5 seconds
|
||||||
const frozenTime time.Duration = 1500 * time.Millisecond
|
const frozenTime time.Duration = 1500 * time.Millisecond
|
||||||
|
|
||||||
const (
|
|
||||||
// DBAuth is the database auth mode.
|
|
||||||
DBAuth = "db_auth"
|
|
||||||
// LDAPAuth is the ldap auth mode.
|
|
||||||
LDAPAuth = "ldap_auth"
|
|
||||||
// UAAAuth is the uaa auth mode.
|
|
||||||
UAAAuth = "uaa_auth"
|
|
||||||
)
|
|
||||||
|
|
||||||
var lock = NewUserLock(frozenTime)
|
var lock = NewUserLock(frozenTime)
|
||||||
|
|
||||||
// Authenticator provides interface to authenticate user credentials.
|
// AuthenticateHelper provides interface for user management in different auth modes.
|
||||||
type Authenticator interface {
|
type AuthenticateHelper interface {
|
||||||
|
|
||||||
// Authenticate ...
|
// Authenticate ...
|
||||||
Authenticate(m models.AuthModel) (*models.User, error)
|
Authenticate(m models.AuthModel) (*models.User, error)
|
||||||
|
// OnBoardUser will check if a user exists in user table, if not insert the user and
|
||||||
|
// put the id in the pointer of user model, if it does exist, fill in the user model based
|
||||||
|
// on the data record of the user
|
||||||
|
OnBoardUser(u *models.User) error
|
||||||
|
// Get user information from account repository
|
||||||
|
SearchUser(username string) (*models.User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var registry = make(map[string]Authenticator)
|
var registry = make(map[string]AuthenticateHelper)
|
||||||
|
|
||||||
// Register add different authenticators to registry map.
|
// Register add different authenticators to registry map.
|
||||||
func Register(name string, authenticator Authenticator) {
|
func Register(name string, h AuthenticateHelper) {
|
||||||
if _, dup := registry[name]; dup {
|
if _, dup := registry[name]; dup {
|
||||||
log.Infof("authenticator: %s has been registered", name)
|
log.Infof("authenticator: %s has been registered", name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry[name] = authenticator
|
registry[name] = h
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login authenticates user credentials based on setting.
|
// Login authenticates user credentials based on setting.
|
||||||
@ -83,3 +80,34 @@ func Login(m models.AuthModel) (*models.User, error) {
|
|||||||
}
|
}
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getHelper() (AuthenticateHelper, error) {
|
||||||
|
authMode, err := config.AuthMode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
AuthenticateHelper, ok := registry[authMode]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Can not get authenticator, authmode: %s", authMode)
|
||||||
|
}
|
||||||
|
return AuthenticateHelper, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnBoardUser will check if a user exists in user table, if not insert the user and
|
||||||
|
// put the id in the pointer of user model, if it does exist, return the user's profile.
|
||||||
|
func OnBoardUser(user *models.User) error {
|
||||||
|
helper, err := getHelper()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return helper.OnBoardUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchUser --
|
||||||
|
func SearchUser(username string) (*models.User, error) {
|
||||||
|
helper, err := getHelper()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return helper.SearchUser(username)
|
||||||
|
}
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/vmware/harbor/src/ui/auth"
|
|
||||||
"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/ui/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Auth implements Authenticator interface to authenticate user against DB.
|
// Auth implements Authenticator interface to authenticate user against DB.
|
||||||
@ -32,6 +32,21 @@ func (d *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
|||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnBoardUser - Dummy implementation when auth_mod is db_auth
|
||||||
|
func (d *Auth) OnBoardUser(user *models.User) error {
|
||||||
|
//No need to create user in local database
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchUser - Check if user exist in local db
|
||||||
|
func (d *Auth) SearchUser(username string) (*models.User, error) {
|
||||||
|
var queryCondition = models.User{
|
||||||
|
Username: username,
|
||||||
|
}
|
||||||
|
|
||||||
|
return dao.GetUser(queryCondition)
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
auth.Register("db_auth", &Auth{})
|
auth.Register("db_auth", &Auth{})
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,159 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/ldap"
|
||||||
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
|
uiConfig "github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
var adminServerTestConfig = map[string]interface{}{
|
||||||
|
common.ExtEndpoint: "host01.com",
|
||||||
|
common.AUTHMode: "db_auth",
|
||||||
|
common.DatabaseType: "mysql",
|
||||||
|
common.MySQLHost: "127.0.0.1",
|
||||||
|
common.MySQLPort: 3306,
|
||||||
|
common.MySQLUsername: "root",
|
||||||
|
common.MySQLPassword: "root123",
|
||||||
|
common.MySQLDatabase: "registry",
|
||||||
|
common.SQLiteFile: "/tmp/registry.db",
|
||||||
|
//config.SelfRegistration: true,
|
||||||
|
common.LDAPURL: "ldap://127.0.0.1",
|
||||||
|
common.LDAPSearchDN: "cn=admin,dc=example,dc=com",
|
||||||
|
common.LDAPSearchPwd: "admin",
|
||||||
|
common.LDAPBaseDN: "dc=example,dc=com",
|
||||||
|
common.LDAPUID: "uid",
|
||||||
|
common.LDAPFilter: "",
|
||||||
|
common.LDAPScope: 3,
|
||||||
|
common.LDAPTimeout: 30,
|
||||||
|
// config.TokenServiceURL: "",
|
||||||
|
// config.RegistryURL: "",
|
||||||
|
// config.EmailHost: "",
|
||||||
|
// config.EmailPort: 25,
|
||||||
|
// config.EmailUsername: "",
|
||||||
|
// config.EmailPassword: "password",
|
||||||
|
// config.EmailFrom: "from",
|
||||||
|
// config.EmailSSL: true,
|
||||||
|
// config.EmailIdentity: "",
|
||||||
|
// config.ProjectCreationRestriction: config.ProCrtRestrAdmOnly,
|
||||||
|
// config.VerifyRemoteCert: false,
|
||||||
|
// config.MaxJobWorkers: 3,
|
||||||
|
// config.TokenExpiration: 30,
|
||||||
|
common.CfgExpiration: 5,
|
||||||
|
// config.JobLogDir: "/var/log/jobs",
|
||||||
|
common.AdminInitialPassword: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
server, err := test.NewAdminserver(adminServerTestConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create a mock admin server: %v", err)
|
||||||
|
}
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
if err := os.Setenv("ADMINSERVER_URL", server.URL); err != nil {
|
||||||
|
log.Fatalf("failed to set env %s: %v", "ADMINSERVER_URL", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secretKeyPath := "/tmp/secretkey"
|
||||||
|
_, err = test.GenerateKey(secretKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to generate secret key: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(secretKeyPath)
|
||||||
|
|
||||||
|
if err := os.Setenv("KEY_PATH", secretKeyPath); err != nil {
|
||||||
|
log.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := uiConfig.Init(); err != nil {
|
||||||
|
log.Fatalf("failed to initialize configurations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
database, err := uiConfig.Database()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to get database configuration: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dao.InitDatabase(database); err != nil {
|
||||||
|
log.Fatalf("failed to initialize database: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchUser(t *testing.T) {
|
||||||
|
//insert user first
|
||||||
|
user := &models.User{
|
||||||
|
Username: "existuser",
|
||||||
|
Email: "existuser@placeholder.com",
|
||||||
|
Realname: "Existing user",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dao.OnBoardUser(user)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to OnBoardUser %v", user)
|
||||||
|
}
|
||||||
|
|
||||||
|
var auth *Auth
|
||||||
|
newUser, err := auth.SearchUser("existuser")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to search user, error %v", err)
|
||||||
|
}
|
||||||
|
if newUser == nil {
|
||||||
|
t.Fatalf("Failed to search user %v", newUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticateHelperOnboardUser(t *testing.T) {
|
||||||
|
user := models.User{
|
||||||
|
Username: "test01",
|
||||||
|
Realname: "test01",
|
||||||
|
Email: "test01@example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := auth.OnBoardUser(&user)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to onboard user error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticateHelperSearchUser(t *testing.T) {
|
||||||
|
|
||||||
|
user, err := auth.SearchUser("admin")
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Failed to search user, admin")
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
t.Error("Failed to search user admin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLdapConnectionTest(t *testing.T) {
|
||||||
|
var ldapConfig = models.LdapConf{
|
||||||
|
LdapURL: "ldap://127.0.0.1",
|
||||||
|
LdapSearchDn: "cn=admin,dc=example,dc=com",
|
||||||
|
LdapSearchPassword: "admin",
|
||||||
|
LdapBaseDn: "dc=example,dc=com",
|
||||||
|
LdapFilter: "",
|
||||||
|
LdapUID: "cn",
|
||||||
|
LdapScope: 3,
|
||||||
|
LdapConnectionTimeout: 10,
|
||||||
|
LdapVerifyCert: false,
|
||||||
|
}
|
||||||
|
//Test ldap connection under auth_mod is db_auth
|
||||||
|
err := ldap.ConnectionTestWithConfig(ldapConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to test ldap server! error %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BIN
src/ui/auth/db/debug.test
Executable file
BIN
src/ui/auth/db/debug.test
Executable file
Binary file not shown.
@ -25,7 +25,7 @@ import (
|
|||||||
"github.com/vmware/harbor/src/ui/auth"
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Auth implements Authenticator interface to authenticate against LDAP
|
// Auth implements AuthenticateHelper interface to authenticate against LDAP
|
||||||
type Auth struct{}
|
type Auth struct{}
|
||||||
|
|
||||||
const metaChars = "&|!=~*<>()"
|
const metaChars = "&|!=~*<>()"
|
||||||
@ -46,21 +46,19 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapConfs, err := ldapUtils.GetSystemLdapConf()
|
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't load system configuration: %v", err)
|
return nil, fmt.Errorf("can not load system ldap config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapConfs, err = ldapUtils.ValidateLdapConf(ldapConfs)
|
if err = ldapSession.Open(); err != nil {
|
||||||
|
log.Warningf("ldap connection fail: %v", err)
|
||||||
if err != nil {
|
return nil, nil
|
||||||
return nil, fmt.Errorf("invalid ldap request: %v", err)
|
|
||||||
}
|
}
|
||||||
|
defer ldapSession.Close()
|
||||||
|
|
||||||
ldapConfs.LdapFilter = ldapUtils.MakeFilter(p, ldapConfs.LdapFilter, ldapConfs.LdapUID)
|
ldapUsers, err := ldapSession.SearchUser(p)
|
||||||
|
|
||||||
ldapUsers, err := ldapUtils.SearchUser(ldapConfs)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("ldap search fail: %v", err)
|
log.Warningf("ldap search fail: %v", err)
|
||||||
@ -83,7 +81,7 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
|||||||
dn := ldapUsers[0].DN
|
dn := ldapUsers[0].DN
|
||||||
|
|
||||||
log.Debugf("username: %s, dn: %s", u.Username, dn)
|
log.Debugf("username: %s, dn: %s", u.Username, dn)
|
||||||
if err := ldapUtils.Bind(ldapConfs, dn, m.Password); err != nil {
|
if err = ldapSession.Bind(dn, m.Password); err != nil {
|
||||||
log.Warningf("Failed to bind user, username: %s, dn: %s, error: %v", u.Username, dn, err)
|
log.Warningf("Failed to bind user, username: %s, dn: %s, error: %v", u.Username, dn, err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -100,18 +98,68 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
|||||||
u.UserID = currentUser.UserID
|
u.UserID = currentUser.UserID
|
||||||
u.HasAdminRole = currentUser.HasAdminRole
|
u.HasAdminRole = currentUser.HasAdminRole
|
||||||
} else {
|
} else {
|
||||||
userID, err := ldapUtils.ImportUser(ldapUsers[0])
|
var user models.User
|
||||||
if err != nil {
|
user.Username = ldapUsers[0].Username
|
||||||
|
user.Email = ldapUsers[0].Email
|
||||||
|
user.Realname = ldapUsers[0].Realname
|
||||||
|
|
||||||
|
err = auth.OnBoardUser(&user)
|
||||||
|
if err != nil || user.UserID <= 0 {
|
||||||
log.Errorf("Can't import user %s, error: %v", ldapUsers[0].Username, err)
|
log.Errorf("Can't import user %s, error: %v", ldapUsers[0].Username, err)
|
||||||
return nil, fmt.Errorf("can't import user %s, error: %v", ldapUsers[0].Username, err)
|
return nil, fmt.Errorf("can't import user %s, error: %v", ldapUsers[0].Username, err)
|
||||||
}
|
}
|
||||||
u.UserID = int(userID)
|
u.UserID = user.UserID
|
||||||
}
|
}
|
||||||
|
|
||||||
return &u, nil
|
return &u, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnBoardUser will check if a user exists in user table, if not insert the user and
|
||||||
|
// put the id in the pointer of user model, if it does exist, return the user's profile.
|
||||||
|
func (l *Auth) OnBoardUser(u *models.User) error {
|
||||||
|
if u.Email == "" {
|
||||||
|
if strings.Contains(u.Username, "@") {
|
||||||
|
u.Email = u.Username
|
||||||
|
} else {
|
||||||
|
u.Email = u.Username + "@placeholder.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.Password = "12345678AbC" //Password is not kept in local db
|
||||||
|
u.Comment = "from LDAP." //Source is from LDAP
|
||||||
|
|
||||||
|
return dao.OnBoardUser(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
//SearchUser -- Search user in ldap
|
||||||
|
func (l *Auth) SearchUser(username string) (*models.User, error) {
|
||||||
|
var user models.User
|
||||||
|
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
|
||||||
|
if err = ldapSession.Open(); err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to load system ldap config, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ldapUsers, err := ldapSession.SearchUser(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to search user in ldap")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ldapUsers) > 1 {
|
||||||
|
log.Warningf("There are more than one user found, return the first user")
|
||||||
|
}
|
||||||
|
if len(ldapUsers) > 0 {
|
||||||
|
|
||||||
|
user.Username = strings.TrimSpace(ldapUsers[0].Username)
|
||||||
|
user.Realname = strings.TrimSpace(ldapUsers[0].Realname)
|
||||||
|
|
||||||
|
log.Debugf("Found ldap user %v", user)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("No user found, %v", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
auth.Register("ldap_auth", &Auth{})
|
auth.Register("ldap_auth", &Auth{})
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"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"
|
||||||
"github.com/vmware/harbor/src/common/utils/test"
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
uiConfig "github.com/vmware/harbor/src/ui/config"
|
uiConfig "github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -64,42 +65,33 @@ var adminServerLdapTestConfig = map[string]interface{}{
|
|||||||
common.AdminInitialPassword: "password",
|
common.AdminInitialPassword: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(m *testing.M) {
|
||||||
server, err := test.NewAdminserver(adminServerLdapTestConfig)
|
server, err := test.NewAdminserver(adminServerLdapTestConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create a mock admin server: %v", err)
|
log.Fatalf("failed to create a mock admin server: %v", err)
|
||||||
}
|
}
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
if err := os.Setenv("ADMINSERVER_URL", server.URL); err != nil {
|
if err := os.Setenv("ADMINSERVER_URL", server.URL); err != nil {
|
||||||
t.Fatalf("failed to set env %s: %v", "ADMINSERVER_URL", err)
|
log.Fatalf("failed to set env %s: %v", "ADMINSERVER_URL", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secretKeyPath := "/tmp/secretkey"
|
secretKeyPath := "/tmp/secretkey"
|
||||||
_, err = test.GenerateKey(secretKeyPath)
|
_, err = test.GenerateKey(secretKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to generate secret key: %v", err)
|
log.Errorf("failed to generate secret key: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer os.Remove(secretKeyPath)
|
defer os.Remove(secretKeyPath)
|
||||||
|
|
||||||
if err := os.Setenv("KEY_PATH", secretKeyPath); err != nil {
|
if err := os.Setenv("KEY_PATH", secretKeyPath); err != nil {
|
||||||
t.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
|
log.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := uiConfig.Init(); err != nil {
|
if err := uiConfig.Init(); err != nil {
|
||||||
t.Fatalf("failed to initialize configurations: %v", err)
|
log.Fatalf("failed to initialize configurations: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if err := uiConfig.Load(); err != nil {
|
|
||||||
// t.Fatalf("failed to load configurations: %v", err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// mode, err := uiConfig.AuthMode()
|
|
||||||
// if err != nil {
|
|
||||||
// t.Fatalf("failed to get auth mode: %v", err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
database, err := uiConfig.Database()
|
database, err := uiConfig.Database()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to get database configuration: %v", err)
|
log.Fatalf("failed to get database configuration: %v", err)
|
||||||
@ -108,6 +100,9 @@ func TestMain(t *testing.T) {
|
|||||||
if err := dao.InitDatabase(database); err != nil {
|
if err := dao.InitDatabase(database); err != nil {
|
||||||
log.Fatalf("failed to initialize database: %v", err)
|
log.Fatalf("failed to initialize database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retCode := m.Run()
|
||||||
|
os.Exit(retCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthenticate(t *testing.T) {
|
func TestAuthenticate(t *testing.T) {
|
||||||
@ -141,3 +136,71 @@ func TestAuthenticate(t *testing.T) {
|
|||||||
t.Errorf("Nil user for empty credentials")
|
t.Errorf("Nil user for empty credentials")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchUser(t *testing.T) {
|
||||||
|
var username = "test"
|
||||||
|
var auth *Auth
|
||||||
|
user, err := auth.SearchUser(username)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Search user failed %v", err)
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
t.Errorf("Search user failed %v", user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestSearchUser_02(t *testing.T) {
|
||||||
|
var username = "nonexist"
|
||||||
|
var auth *Auth
|
||||||
|
user, _ := auth.SearchUser(username)
|
||||||
|
if user != nil {
|
||||||
|
t.Errorf("Should failed to search nonexist user")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnboardUser(t *testing.T) {
|
||||||
|
user := &models.User{
|
||||||
|
Username: "sample",
|
||||||
|
Email: "sample@example.com",
|
||||||
|
Realname: "Sample",
|
||||||
|
}
|
||||||
|
|
||||||
|
var auth *Auth
|
||||||
|
err := auth.OnBoardUser(user)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to onboard user")
|
||||||
|
}
|
||||||
|
if user.UserID <= 0 {
|
||||||
|
t.Errorf("Failed to onboard user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticateHelperOnboardUser(t *testing.T) {
|
||||||
|
user := models.User{
|
||||||
|
Username: "test01",
|
||||||
|
Realname: "test01",
|
||||||
|
Email: "test01@example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := auth.OnBoardUser(&user)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to onboard user error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.UserID <= 0 {
|
||||||
|
t.Errorf("Failed to onboard user, userid: %v", user.UserID)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticateHelperSearchUser(t *testing.T) {
|
||||||
|
|
||||||
|
user, err := auth.SearchUser("test")
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Failed to search user, test")
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
t.Error("Failed to search user test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"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/uaa"
|
"github.com/vmware/harbor/src/common/utils/uaa"
|
||||||
"github.com/vmware/harbor/src/ui/auth"
|
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,7 +65,7 @@ func doAuth(username, password string, client uaa.Client) (*models.User, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth is the implementation of Authenticator to access uaa for authentication.
|
// Auth is the implementation of AuthenticateHelper to access uaa for authentication.
|
||||||
type Auth struct{}
|
type Auth struct{}
|
||||||
|
|
||||||
//Authenticate ...
|
//Authenticate ...
|
||||||
@ -78,6 +77,17 @@ func (u *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
|||||||
return doAuth(m.Principal, m.Password, client)
|
return doAuth(m.Principal, m.Password, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
// OnBoardUser will check if a user exists in user table, if not insert the user and
|
||||||
auth.Register(auth.UAAAuth, &Auth{})
|
// put the id in the pointer of user model, if it does exist, return the user's profile.
|
||||||
}
|
// func (u *Auth) OnBoardUser(user *models.User) error {
|
||||||
|
// panic("not implemented")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // SearchUser - search user on uaa server
|
||||||
|
// func (u *Auth) SearchUser(username string) (*models.User, error) {
|
||||||
|
// panic("not implemented")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func init() {
|
||||||
|
// auth.Register(auth.UAAAuth, &Auth{})
|
||||||
|
// }
|
||||||
|
@ -61,12 +61,17 @@ var (
|
|||||||
func Init() error {
|
func Init() error {
|
||||||
//init key provider
|
//init key provider
|
||||||
initKeyProvider()
|
initKeyProvider()
|
||||||
|
|
||||||
adminServerURL := os.Getenv("ADMINSERVER_URL")
|
adminServerURL := os.Getenv("ADMINSERVER_URL")
|
||||||
if len(adminServerURL) == 0 {
|
if len(adminServerURL) == 0 {
|
||||||
adminServerURL = "http://adminserver"
|
adminServerURL = "http://adminserver"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return InitByURL(adminServerURL)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitByURL Init configurations with given url
|
||||||
|
func InitByURL(adminServerURL string) error {
|
||||||
log.Infof("initializing client for adminserver %s ...", adminServerURL)
|
log.Infof("initializing client for adminserver %s ...", adminServerURL)
|
||||||
authorizer := auth.NewSecretAuthorizer(secretCookieName, UISecret())
|
authorizer := auth.NewSecretAuthorizer(secretCookieName, UISecret())
|
||||||
AdminserverClient = client.NewClient(adminServerURL, authorizer)
|
AdminserverClient = client.NewClient(adminServerURL, authorizer)
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/beego/i18n"
|
"github.com/beego/i18n"
|
||||||
|
"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"
|
||||||
@ -101,8 +102,8 @@ func (cc *CommonController) UserExists() {
|
|||||||
cc.ServeJSON()
|
cc.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendEmail verifies the Email address and contact SMTP server to send reset password Email.
|
// SendResetEmail verifies the Email address and contact SMTP server to send reset password Email.
|
||||||
func (cc *CommonController) SendEmail() {
|
func (cc *CommonController) SendResetEmail() {
|
||||||
|
|
||||||
email := cc.GetString("email")
|
email := cc.GetString("email")
|
||||||
|
|
||||||
@ -117,16 +118,21 @@ func (cc *CommonController) SendEmail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
queryUser := models.User{Email: email}
|
queryUser := models.User{Email: email}
|
||||||
exist, err := dao.UserExists(queryUser, "email")
|
u, err := dao.GetUser(queryUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error occurred in UserExists: %v", err)
|
log.Errorf("Error occurred in GetUser: %v", err)
|
||||||
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||||
}
|
}
|
||||||
if !exist {
|
if u == nil {
|
||||||
log.Debugf("email %s not found", email)
|
log.Debugf("email %s not found", email)
|
||||||
cc.CustomAbort(http.StatusNotFound, "email_does_not_exist")
|
cc.CustomAbort(http.StatusNotFound, "email_does_not_exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isUserResetable(u) {
|
||||||
|
log.Errorf("Resetting password for user with ID: %d is not allowed", u.UserID)
|
||||||
|
cc.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
|
||||||
|
}
|
||||||
|
|
||||||
uuid := utils.GenerateRandomString()
|
uuid := utils.GenerateRandomString()
|
||||||
user := models.User{ResetUUID: uuid, Email: email}
|
user := models.User{ResetUUID: uuid, Email: email}
|
||||||
if err = dao.UpdateUserResetUUID(user); err != nil {
|
if err = dao.UpdateUserResetUUID(user); err != nil {
|
||||||
@ -192,6 +198,7 @@ func (cc *CommonController) ResetPassword() {
|
|||||||
|
|
||||||
queryUser := models.User{ResetUUID: resetUUID}
|
queryUser := models.User{ResetUUID: resetUUID}
|
||||||
user, err := dao.GetUser(queryUser)
|
user, err := dao.GetUser(queryUser)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error occurred in GetUser: %v", err)
|
log.Errorf("Error occurred in GetUser: %v", err)
|
||||||
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||||
@ -201,6 +208,11 @@ func (cc *CommonController) ResetPassword() {
|
|||||||
cc.CustomAbort(http.StatusBadRequest, "User does not exist")
|
cc.CustomAbort(http.StatusBadRequest, "User does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isUserResetable(user) {
|
||||||
|
log.Errorf("Resetting password for user with ID: %d is not allowed", user.UserID)
|
||||||
|
cc.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
|
||||||
|
}
|
||||||
|
|
||||||
password := cc.GetString("password")
|
password := cc.GetString("password")
|
||||||
|
|
||||||
if password != "" {
|
if password != "" {
|
||||||
@ -215,6 +227,21 @@ func (cc *CommonController) ResetPassword() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isUserResetable(u *models.User) bool {
|
||||||
|
if u == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mode, err := config.AuthMode()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get the auth mode, error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if mode == common.DBAuth {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return u.UserID == 1
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
//conf/app.conf -> os.Getenv("config_path")
|
//conf/app.conf -> os.Getenv("config_path")
|
||||||
configPath := os.Getenv("CONFIG_PATH")
|
configPath := os.Getenv("CONFIG_PATH")
|
||||||
|
@ -22,11 +22,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
"github.com/vmware/harbor/src/ui/proxy"
|
"github.com/vmware/harbor/src/ui/proxy"
|
||||||
)
|
)
|
||||||
@ -44,14 +47,6 @@ import (
|
|||||||
//var admin *usrInfo
|
//var admin *usrInfo
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := config.Init(); err != nil {
|
|
||||||
log.Fatalf("failed to initialize configurations: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := proxy.Init(); err != nil {
|
|
||||||
log.Fatalf("Failed to initialize the proxy: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, file, _, _ := runtime.Caller(1)
|
_, file, _, _ := runtime.Caller(1)
|
||||||
apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".."+string(filepath.Separator))))
|
apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".."+string(filepath.Separator))))
|
||||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||||
@ -64,15 +59,74 @@ func init() {
|
|||||||
beego.Router("/log_out", &CommonController{}, "get:LogOut")
|
beego.Router("/log_out", &CommonController{}, "get:LogOut")
|
||||||
beego.Router("/reset", &CommonController{}, "post:ResetPassword")
|
beego.Router("/reset", &CommonController{}, "post:ResetPassword")
|
||||||
beego.Router("/userExists", &CommonController{}, "post:UserExists")
|
beego.Router("/userExists", &CommonController{}, "post:UserExists")
|
||||||
beego.Router("/sendEmail", &CommonController{}, "get:SendEmail")
|
beego.Router("/sendEmail", &CommonController{}, "get:SendResetEmail")
|
||||||
beego.Router("/registryproxy/*", &RegistryProxy{}, "*:Handle")
|
beego.Router("/registryproxy/*", &RegistryProxy{}, "*:Handle")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
|
||||||
|
rc := m.Run()
|
||||||
|
if rc != 0 {
|
||||||
|
os.Exit(rc)
|
||||||
|
}
|
||||||
//Init user Info
|
//Init user Info
|
||||||
//admin = &usrInfo{adminName, adminPwd}
|
//admin = &usrInfo{adminName, adminPwd}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestUserResettable
|
||||||
|
func TestUserResettable(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
DBAuthConfig := map[string]interface{}{
|
||||||
|
common.AUTHMode: common.DBAuth,
|
||||||
|
common.CfgExpiration: 5,
|
||||||
|
common.TokenExpiration: 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
LDAPAuthConfig := map[string]interface{}{
|
||||||
|
common.AUTHMode: common.LDAPAuth,
|
||||||
|
common.CfgExpiration: 5,
|
||||||
|
common.TokenExpiration: 30,
|
||||||
|
}
|
||||||
|
DBAuthAdminsvr, err := test.NewAdminserver(DBAuthConfig)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
LDAPAuthAdminsvr, err := test.NewAdminserver(LDAPAuthConfig)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer DBAuthAdminsvr.Close()
|
||||||
|
defer LDAPAuthAdminsvr.Close()
|
||||||
|
if err := config.InitByURL(LDAPAuthAdminsvr.URL); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
u1 := &models.User{
|
||||||
|
UserID: 3,
|
||||||
|
Username: "daniel",
|
||||||
|
Email: "daniel@test.com",
|
||||||
|
}
|
||||||
|
u2 := &models.User{
|
||||||
|
UserID: 1,
|
||||||
|
Username: "jack",
|
||||||
|
Email: "jack@test.com",
|
||||||
|
}
|
||||||
|
assert.False(isUserResetable(u1))
|
||||||
|
assert.True(isUserResetable(u2))
|
||||||
|
if err := config.InitByURL(DBAuthAdminsvr.URL); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
assert.True(isUserResetable(u1))
|
||||||
|
}
|
||||||
|
|
||||||
// TestMain is a sample to run an endpoint test
|
// TestMain is a sample to run an endpoint test
|
||||||
func TestMain(t *testing.T) {
|
func TestAll(t *testing.T) {
|
||||||
|
if err := config.Init(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := proxy.Init(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
// v := url.Values{}
|
// v := url.Values{}
|
||||||
|
@ -65,7 +65,7 @@ func initRouters() {
|
|||||||
beego.Router("/log_out", &controllers.CommonController{}, "get:LogOut")
|
beego.Router("/log_out", &controllers.CommonController{}, "get:LogOut")
|
||||||
beego.Router("/reset", &controllers.CommonController{}, "post:ResetPassword")
|
beego.Router("/reset", &controllers.CommonController{}, "post:ResetPassword")
|
||||||
beego.Router("/userExists", &controllers.CommonController{}, "post:UserExists")
|
beego.Router("/userExists", &controllers.CommonController{}, "post:UserExists")
|
||||||
beego.Router("/sendEmail", &controllers.CommonController{}, "get:SendEmail")
|
beego.Router("/sendEmail", &controllers.CommonController{}, "get:SendResetEmail")
|
||||||
|
|
||||||
//API:
|
//API:
|
||||||
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{})
|
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{})
|
||||||
|
@ -17,6 +17,5 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,13 +12,3 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
/*
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
@ -17,6 +17,5 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "harbor-ui",
|
"name": "harbor-ui",
|
||||||
"version": "0.5.0",
|
"version": "0.6.0",
|
||||||
"description": "Harbor shared UI components based on Clarity and Angular4",
|
"description": "Harbor shared UI components based on Clarity and Angular4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ng serve --host 0.0.0.0 --port 4500 --proxy-config proxy.config.json",
|
"start": "ng serve --host 0.0.0.0 --port 4500 --proxy-config proxy.config.json",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "harbor-ui",
|
"name": "harbor-ui",
|
||||||
"version": "0.5.13",
|
"version": "0.6.0",
|
||||||
"description": "Harbor shared UI components based on Clarity and Angular4",
|
"description": "Harbor shared UI components based on Clarity and Angular4",
|
||||||
"author": "VMware",
|
"author": "VMware",
|
||||||
"module": "index.js",
|
"module": "index.js",
|
||||||
|
@ -6,7 +6,7 @@ import { ENDPOINT_DIRECTIVES } from './endpoint/index';
|
|||||||
import { REPOSITORY_DIRECTIVES } from './repository/index';
|
import { REPOSITORY_DIRECTIVES } from './repository/index';
|
||||||
import { REPOSITORY_STACKVIEW_DIRECTIVES } from './repository-stackview/index';
|
import { REPOSITORY_STACKVIEW_DIRECTIVES } from './repository-stackview/index';
|
||||||
|
|
||||||
import { LIST_REPOSITORY_DIRECTIVES } from './list-repository/index';
|
import { REPOSITORY_LISTVIEW_DIRECTIVES } from './repository-listview/index';
|
||||||
import { TAG_DIRECTIVES } from './tag/index';
|
import { TAG_DIRECTIVES } from './tag/index';
|
||||||
|
|
||||||
import { REPLICATION_DIRECTIVES } from './replication/index';
|
import { REPLICATION_DIRECTIVES } from './replication/index';
|
||||||
@ -157,7 +157,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
|
|||||||
ENDPOINT_DIRECTIVES,
|
ENDPOINT_DIRECTIVES,
|
||||||
REPOSITORY_DIRECTIVES,
|
REPOSITORY_DIRECTIVES,
|
||||||
REPOSITORY_STACKVIEW_DIRECTIVES,
|
REPOSITORY_STACKVIEW_DIRECTIVES,
|
||||||
LIST_REPOSITORY_DIRECTIVES,
|
REPOSITORY_LISTVIEW_DIRECTIVES,
|
||||||
TAG_DIRECTIVES,
|
TAG_DIRECTIVES,
|
||||||
CREATE_EDIT_ENDPOINT_DIRECTIVES,
|
CREATE_EDIT_ENDPOINT_DIRECTIVES,
|
||||||
CONFIRMATION_DIALOG_DIRECTIVES,
|
CONFIRMATION_DIALOG_DIRECTIVES,
|
||||||
@ -178,7 +178,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
|
|||||||
ENDPOINT_DIRECTIVES,
|
ENDPOINT_DIRECTIVES,
|
||||||
REPOSITORY_DIRECTIVES,
|
REPOSITORY_DIRECTIVES,
|
||||||
REPOSITORY_STACKVIEW_DIRECTIVES,
|
REPOSITORY_STACKVIEW_DIRECTIVES,
|
||||||
LIST_REPOSITORY_DIRECTIVES,
|
REPOSITORY_LISTVIEW_DIRECTIVES,
|
||||||
TAG_DIRECTIVES,
|
TAG_DIRECTIVES,
|
||||||
CREATE_EDIT_ENDPOINT_DIRECTIVES,
|
CREATE_EDIT_ENDPOINT_DIRECTIVES,
|
||||||
CONFIRMATION_DIALOG_DIRECTIVES,
|
CONFIRMATION_DIALOG_DIRECTIVES,
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { Type } from '@angular/core';
|
|
||||||
import { ListRepositoryComponent } from './list-repository.component';
|
|
||||||
|
|
||||||
|
|
||||||
export const LIST_REPOSITORY_DIRECTIVES: Type<any>[] = [
|
|
||||||
ListRepositoryComponent
|
|
||||||
];
|
|
@ -1 +0,0 @@
|
|||||||
export const LIST_REPOSITORY_STYLE = ``;
|
|
@ -1,20 +0,0 @@
|
|||||||
export const LIST_REPOSITORY_TEMPLATE = `
|
|
||||||
<clr-datagrid (clrDgRefresh)="refresh($event)">
|
|
||||||
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgSortBy]="tagsCountComparator">{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgSortBy]="pullCountComparator">{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-placeholder>{{'REPOSITORY.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
|
||||||
<clr-dg-row *clrDgItems="let r of repositories" [clrDgItem]='r'>
|
|
||||||
<clr-dg-action-overflow [hidden]="!hasProjectAdminRole">
|
|
||||||
<button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button>
|
|
||||||
</clr-dg-action-overflow>
|
|
||||||
<clr-dg-cell><a href="javascript:void(0)" (click)="gotoLink(projectId || r.project_id, r.name || r.repository_name)">{{r.name}}</a></clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
|
|
||||||
</clr-dg-row>
|
|
||||||
<clr-dg-footer>
|
|
||||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
|
|
||||||
{{pagination.totalItems}}{{'REPOSITORY.ITEMS' | translate}}
|
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
|
|
||||||
</clr-dg-footer>
|
|
||||||
</clr-datagrid>`;
|
|
@ -1,78 +0,0 @@
|
|||||||
|
|
||||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
|
||||||
import { By } from '@angular/platform-browser';
|
|
||||||
import { DebugElement } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { SharedModule } from '../shared/shared.module';
|
|
||||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
|
||||||
import { ListRepositoryComponent } from './list-repository.component';
|
|
||||||
import { Repository, RepositoryItem } from '../service/interface';
|
|
||||||
|
|
||||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
|
||||||
|
|
||||||
class RouterStub {
|
|
||||||
navigateByUrl(url: string) { return url; }
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('ListRepositoryComponent (inline template)', () => {
|
|
||||||
let comp: ListRepositoryComponent;
|
|
||||||
let fixture: ComponentFixture<ListRepositoryComponent>;
|
|
||||||
|
|
||||||
let mockData: RepositoryItem[] = [
|
|
||||||
{
|
|
||||||
"id": 11,
|
|
||||||
"name": "library/busybox",
|
|
||||||
"project_id": 1,
|
|
||||||
"description": "",
|
|
||||||
"pull_count": 0,
|
|
||||||
"star_count": 0,
|
|
||||||
"tags_count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 12,
|
|
||||||
"name": "library/nginx",
|
|
||||||
"project_id": 1,
|
|
||||||
"description": "",
|
|
||||||
"pull_count": 0,
|
|
||||||
"star_count": 0,
|
|
||||||
"tags_count": 1
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [
|
|
||||||
SharedModule
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
ListRepositoryComponent,
|
|
||||||
ConfirmationDialogComponent
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
{ provide: Router, useClass: RouterStub },
|
|
||||||
{ provide: SERVICE_CONFIG, useValue: {} }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(ListRepositoryComponent);
|
|
||||||
comp = fixture.componentInstance;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should load and render data', async(() => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
comp.repositories = mockData;
|
|
||||||
fixture.whenStable().then(() => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(comp.repositories).toBeTruthy();
|
|
||||||
let de: DebugElement = fixture.debugElement.query(By.css('datagrid-cell'));
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(de).toBeTruthy();
|
|
||||||
let el: HTMLElement = de.nativeElement;
|
|
||||||
expect(el).toBeTruthy();
|
|
||||||
expect(el.textContent).toEqual('library/busybox');
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
|
@ -1,56 +0,0 @@
|
|||||||
import { Component, Input, Output, EventEmitter, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { State, Comparator } from 'clarity-angular';
|
|
||||||
import { RepositoryItem } from '../service/interface';
|
|
||||||
|
|
||||||
import { LIST_REPOSITORY_TEMPLATE } from './list-repository.component.html';
|
|
||||||
|
|
||||||
import { CustomComparator } from '../utils';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'hbr-list-repository',
|
|
||||||
template: LIST_REPOSITORY_TEMPLATE,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class ListRepositoryComponent {
|
|
||||||
|
|
||||||
@Input() urlPrefix: string;
|
|
||||||
@Input() projectId: number;
|
|
||||||
@Input() repositories: RepositoryItem[];
|
|
||||||
|
|
||||||
@Output() delete = new EventEmitter<string>();
|
|
||||||
@Output() paginate = new EventEmitter<State>();
|
|
||||||
|
|
||||||
@Input() hasProjectAdminRole: boolean;
|
|
||||||
|
|
||||||
pageOffset: number = 1;
|
|
||||||
|
|
||||||
pullCountComparator: Comparator<RepositoryItem> = new CustomComparator<RepositoryItem>('pull_count', 'number');
|
|
||||||
|
|
||||||
tagsCountComparator: Comparator<RepositoryItem> = new CustomComparator<RepositoryItem>('tags_count', 'number');
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private router: Router,
|
|
||||||
private ref: ChangeDetectorRef) {
|
|
||||||
let hnd = setInterval(()=>ref.markForCheck(), 100);
|
|
||||||
setTimeout(()=>clearInterval(hnd), 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() { }
|
|
||||||
|
|
||||||
deleteRepo(repoName: string) {
|
|
||||||
this.delete.emit(repoName);
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh(state: State) {
|
|
||||||
if (this.repositories) {
|
|
||||||
this.paginate.emit(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public gotoLink(projectId: number, repoName: string): void {
|
|
||||||
let linkUrl = [this.urlPrefix, 'tags', projectId, repoName];
|
|
||||||
this.router.navigate(linkUrl);
|
|
||||||
}
|
|
||||||
}
|
|
6
src/ui_ng/lib/src/repository-listview/index.ts
Normal file
6
src/ui_ng/lib/src/repository-listview/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { Type } from '@angular/core';
|
||||||
|
import { RepositoryListviewComponent } from './repository-listview.component';
|
||||||
|
|
||||||
|
export const REPOSITORY_LISTVIEW_DIRECTIVES: Type<any>[] = [
|
||||||
|
RepositoryListviewComponent
|
||||||
|
];
|
@ -0,0 +1,2 @@
|
|||||||
|
export const REPOSITORY_LISTVIEW_STYLE = `
|
||||||
|
`;
|
@ -0,0 +1,41 @@
|
|||||||
|
export const REPOSITORY_LISTVIEW_TEMPLATE = `
|
||||||
|
<div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
|
<div class="row flex-items-xs-right option-right">
|
||||||
|
<div class="flex-xs-middle">
|
||||||
|
<hbr-push-image-button style="display: inline-block;" [registryUrl]="registryUrl" [projectName]="projectName"></hbr-push-image-button>
|
||||||
|
<hbr-filter [withDivider]="true" filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" (filter)="doSearchRepoNames($event)" [currentValue]="lastFilteredRepoName"></hbr-filter>
|
||||||
|
<span class="refresh-btn" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
|
<clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading">
|
||||||
|
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgSortBy]="tagsCountComparator">{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgSortBy]="pullCountComparator">{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-placeholder>{{'REPOSITORY.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||||
|
<clr-dg-row *ngFor="let r of repositories">
|
||||||
|
<clr-dg-action-overflow [hidden]="!hasProjectAdminRole">
|
||||||
|
<button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||||
|
</clr-dg-action-overflow>
|
||||||
|
<clr-dg-cell><a href="javascript:void(0)" (click)="gotoLink(projectId || r.project_id, r.name || r.repository_name)">{{r.name}}</a></clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
<clr-dg-footer>
|
||||||
|
<span *ngIf="showDBStatusWarning" class="db-status-warning">
|
||||||
|
<clr-icon shape="warning" class="is-warning" size="24"></clr-icon>
|
||||||
|
{{'CONFIG.SCANNING.DB_NOT_READY' | translate }}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
|
||||||
|
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
|
||||||
|
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -0,0 +1,170 @@
|
|||||||
|
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
|
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||||
|
import { RepositoryListviewComponent } from './repository-listview.component';
|
||||||
|
import { TagComponent } from '../tag/tag.component';
|
||||||
|
import { FilterComponent } from '../filter/filter.component';
|
||||||
|
|
||||||
|
import { ErrorHandler } from '../error-handler/error-handler';
|
||||||
|
import { Repository, RepositoryItem, Tag, SystemInfo } from '../service/interface';
|
||||||
|
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||||
|
import { RepositoryService, RepositoryDefaultService } from '../service/repository.service';
|
||||||
|
import { TagService, TagDefaultService } from '../service/tag.service';
|
||||||
|
import { SystemInfoService, SystemInfoDefaultService } from '../service/system-info.service';
|
||||||
|
import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index';
|
||||||
|
import { PUSH_IMAGE_BUTTON_DIRECTIVES } from '../push-image/index';
|
||||||
|
import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index';
|
||||||
|
import { JobLogViewerComponent } from '../job-log-viewer/index';
|
||||||
|
|
||||||
|
import { click } from '../utils';
|
||||||
|
|
||||||
|
describe('RepositoryComponentListview (inline template)', () => {
|
||||||
|
|
||||||
|
let compRepo: RepositoryListviewComponent;
|
||||||
|
let fixtureRepo: ComponentFixture<RepositoryListviewComponent>;
|
||||||
|
let repositoryService: RepositoryService;
|
||||||
|
let tagService: TagService;
|
||||||
|
let systemInfoService: SystemInfoService;
|
||||||
|
|
||||||
|
let spyRepos: jasmine.Spy;
|
||||||
|
let spySystemInfo: jasmine.Spy;
|
||||||
|
|
||||||
|
let mockSystemInfo: SystemInfo = {
|
||||||
|
"with_notary": true,
|
||||||
|
"with_admiral": false,
|
||||||
|
"admiral_endpoint": "NA",
|
||||||
|
"auth_mode": "db_auth",
|
||||||
|
"registry_url": "10.112.122.56",
|
||||||
|
"project_creation_restriction": "everyone",
|
||||||
|
"self_registration": true,
|
||||||
|
"has_ca_root": false,
|
||||||
|
"harbor_version": "v1.1.1-rc1-160-g565110d"
|
||||||
|
};
|
||||||
|
|
||||||
|
let mockRepoData: RepositoryItem[] = [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "library/busybox",
|
||||||
|
"project_id": 1,
|
||||||
|
"description": "asdfsadf",
|
||||||
|
"pull_count": 0,
|
||||||
|
"star_count": 0,
|
||||||
|
"tags_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "library/nginx",
|
||||||
|
"project_id": 1,
|
||||||
|
"description": "asdf",
|
||||||
|
"pull_count": 0,
|
||||||
|
"star_count": 0,
|
||||||
|
"tags_count": 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let mockRepo: Repository = {
|
||||||
|
metadata: {xTotalCount: 2},
|
||||||
|
data: mockRepoData
|
||||||
|
};
|
||||||
|
|
||||||
|
let mockTagData: Tag[] = [
|
||||||
|
{
|
||||||
|
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||||
|
"name": "1.11.5",
|
||||||
|
"size": "2049",
|
||||||
|
"architecture": "amd64",
|
||||||
|
"os": "linux",
|
||||||
|
"docker_version": "1.12.3",
|
||||||
|
"author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"",
|
||||||
|
"created": new Date("2016-11-08T22:41:15.912313785Z"),
|
||||||
|
"signature": null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let config: IServiceConfig = {
|
||||||
|
repositoryBaseEndpoint: '/api/repository/testing',
|
||||||
|
systemInfoEndpoint: '/api/systeminfo/testing',
|
||||||
|
targetBaseEndpoint: '/api/tag/testing'
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
SharedModule,
|
||||||
|
RouterTestingModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
RepositoryListviewComponent,
|
||||||
|
TagComponent,
|
||||||
|
ConfirmationDialogComponent,
|
||||||
|
FilterComponent,
|
||||||
|
VULNERABILITY_DIRECTIVES,
|
||||||
|
PUSH_IMAGE_BUTTON_DIRECTIVES,
|
||||||
|
INLINE_ALERT_DIRECTIVES,
|
||||||
|
JobLogViewerComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ErrorHandler,
|
||||||
|
{ provide: SERVICE_CONFIG, useValue: config },
|
||||||
|
{ provide: RepositoryService, useClass: RepositoryDefaultService },
|
||||||
|
{ provide: TagService, useClass: TagDefaultService },
|
||||||
|
{ provide: SystemInfoService, useClass: SystemInfoDefaultService }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixtureRepo = TestBed.createComponent(RepositoryListviewComponent);
|
||||||
|
compRepo = fixtureRepo.componentInstance;
|
||||||
|
compRepo.projectId = 1;
|
||||||
|
compRepo.hasProjectAdminRole = true;
|
||||||
|
|
||||||
|
repositoryService = fixtureRepo.debugElement.injector.get(RepositoryService);
|
||||||
|
systemInfoService = fixtureRepo.debugElement.injector.get(SystemInfoService);
|
||||||
|
|
||||||
|
spyRepos = spyOn(repositoryService, 'getRepositories').and.returnValues(Promise.resolve(mockRepo));
|
||||||
|
spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValues(Promise.resolve(mockSystemInfo));
|
||||||
|
fixtureRepo.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(compRepo).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load and render data', async(() => {
|
||||||
|
fixtureRepo.detectChanges();
|
||||||
|
|
||||||
|
fixtureRepo.whenStable().then(() => {
|
||||||
|
fixtureRepo.detectChanges();
|
||||||
|
|
||||||
|
let deRepo: DebugElement = fixtureRepo.debugElement.query(By.css('datagrid-cell'));
|
||||||
|
expect(deRepo).toBeTruthy();
|
||||||
|
let elRepo: HTMLElement = deRepo.nativeElement;
|
||||||
|
expect(elRepo).toBeTruthy();
|
||||||
|
expect(elRepo.textContent).toEqual('library/busybox');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should filter data by keyword', async(() => {
|
||||||
|
fixtureRepo.detectChanges();
|
||||||
|
|
||||||
|
fixtureRepo.whenStable().then(() => {
|
||||||
|
fixtureRepo.detectChanges();
|
||||||
|
|
||||||
|
compRepo.doSearchRepoNames('nginx');
|
||||||
|
fixtureRepo.detectChanges();
|
||||||
|
let de: DebugElement[] = fixtureRepo.debugElement.queryAll(By.css('datagrid-cell'));
|
||||||
|
expect(de).toBeTruthy();
|
||||||
|
expect(de.length).toEqual(1);
|
||||||
|
let el: HTMLElement = de[0].nativeElement;
|
||||||
|
expect(el).toBeTruthy();
|
||||||
|
expect(el.textContent).toEqual('library/nginx');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,303 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
OnInit,
|
||||||
|
ViewChild,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
EventEmitter, OnChanges, SimpleChanges
|
||||||
|
} from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Comparator } from 'clarity-angular';
|
||||||
|
|
||||||
|
import { REPOSITORY_LISTVIEW_TEMPLATE } from './repository-listview.component.html';
|
||||||
|
import { REPOSITORY_LISTVIEW_STYLE } from './repository-listview.component.css';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Repository,
|
||||||
|
SystemInfo,
|
||||||
|
SystemInfoService,
|
||||||
|
RepositoryService,
|
||||||
|
RequestQueryParams,
|
||||||
|
RepositoryItem,
|
||||||
|
TagService
|
||||||
|
} from '../service/index';
|
||||||
|
import { ErrorHandler } from '../error-handler/error-handler';
|
||||||
|
|
||||||
|
import { toPromise, CustomComparator } from '../utils';
|
||||||
|
|
||||||
|
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const';
|
||||||
|
|
||||||
|
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||||
|
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
|
||||||
|
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message';
|
||||||
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
|
import { Tag, TagClickEvent } from '../service/interface';
|
||||||
|
|
||||||
|
import { State } from "clarity-angular";
|
||||||
|
import {
|
||||||
|
DEFAULT_PAGE_SIZE,
|
||||||
|
calculatePage,
|
||||||
|
doFiltering,
|
||||||
|
doSorting
|
||||||
|
} from '../utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'hbr-repository-listview',
|
||||||
|
template: REPOSITORY_LISTVIEW_TEMPLATE,
|
||||||
|
styles: [REPOSITORY_LISTVIEW_STYLE],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class RepositoryListviewComponent implements OnChanges, OnInit {
|
||||||
|
signedCon: {[key: string]: any | string[]} = {};
|
||||||
|
@Input() projectId: number;
|
||||||
|
@Input() projectName = 'unknown';
|
||||||
|
@Input() urlPrefix: string;
|
||||||
|
|
||||||
|
@Input() hasSignedIn: boolean;
|
||||||
|
@Input() hasProjectAdminRole: boolean;
|
||||||
|
@Output() tagClickEvent = new EventEmitter<TagClickEvent>();
|
||||||
|
|
||||||
|
lastFilteredRepoName: string;
|
||||||
|
repositories: RepositoryItem[];
|
||||||
|
systemInfo: SystemInfo;
|
||||||
|
|
||||||
|
loading = true;
|
||||||
|
|
||||||
|
@ViewChild('confirmationDialog')
|
||||||
|
confirmationDialog: ConfirmationDialogComponent;
|
||||||
|
|
||||||
|
pullCountComparator: Comparator<RepositoryItem> = new CustomComparator<RepositoryItem>('pull_count', 'number');
|
||||||
|
|
||||||
|
tagsCountComparator: Comparator<RepositoryItem> = new CustomComparator<RepositoryItem>('tags_count', 'number');
|
||||||
|
|
||||||
|
pageSize: number = DEFAULT_PAGE_SIZE;
|
||||||
|
currentPage = 1;
|
||||||
|
totalCount = 0;
|
||||||
|
currentState: State;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private errorHandler: ErrorHandler,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private repositoryService: RepositoryService,
|
||||||
|
private systemInfoService: SystemInfoService,
|
||||||
|
private translate: TranslateService,
|
||||||
|
private tagService: TagService,
|
||||||
|
private ref: ChangeDetectorRef,
|
||||||
|
private router: Router) { }
|
||||||
|
|
||||||
|
public get registryUrl(): string {
|
||||||
|
return this.systemInfo ? this.systemInfo.registry_url : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public get withNotary(): boolean {
|
||||||
|
return this.systemInfo ? this.systemInfo.with_notary : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get withClair(): boolean {
|
||||||
|
return this.systemInfo ? this.systemInfo.with_clair : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isClairDBReady(): boolean {
|
||||||
|
return this.systemInfo &&
|
||||||
|
this.systemInfo.clair_vulnerability_status &&
|
||||||
|
this.systemInfo.clair_vulnerability_status.overall_last_update > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get showDBStatusWarning(): boolean {
|
||||||
|
return this.withClair && !this.isClairDBReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmDeletion(message: ConfirmationAcknowledgement) {
|
||||||
|
if (message &&
|
||||||
|
message.source === ConfirmationTargets.REPOSITORY &&
|
||||||
|
message.state === ConfirmationState.CONFIRMED) {
|
||||||
|
let repoName = message.data;
|
||||||
|
toPromise<number>(this.repositoryService
|
||||||
|
.deleteRepository(repoName))
|
||||||
|
.then(
|
||||||
|
response => {
|
||||||
|
this.refresh();
|
||||||
|
let st: State = this.getStateAfterDeletion();
|
||||||
|
if (!st) {
|
||||||
|
this.refresh();
|
||||||
|
} else {
|
||||||
|
this.clrLoad(st);
|
||||||
|
}
|
||||||
|
this.translateService.get('REPOSITORY.DELETED_REPO_SUCCESS')
|
||||||
|
.subscribe(res => this.errorHandler.info(res));
|
||||||
|
}).catch(error => {
|
||||||
|
if (error.status === '412') {
|
||||||
|
this.translateService.get('REPOSITORY.TAGS_SIGNED')
|
||||||
|
.subscribe(res => this.errorHandler.info(res));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.errorHandler.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes['projectId'] && changes['projectId'].currentValue) {
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Get system info for tag views
|
||||||
|
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
|
||||||
|
.then(systemInfo => this.systemInfo = systemInfo)
|
||||||
|
.catch(error => this.errorHandler.error(error));
|
||||||
|
|
||||||
|
this.lastFilteredRepoName = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
doSearchRepoNames(repoName: string) {
|
||||||
|
this.lastFilteredRepoName = repoName;
|
||||||
|
this.currentPage = 1;
|
||||||
|
|
||||||
|
let st: State = this.currentState;
|
||||||
|
if (!st) {
|
||||||
|
st = { page: {} };
|
||||||
|
}
|
||||||
|
st.page.size = this.pageSize;
|
||||||
|
st.page.from = 0;
|
||||||
|
st.page.to = this.pageSize - 1;
|
||||||
|
this.clrLoad(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSignatures(event: {[key: string]: string[]}): void {
|
||||||
|
Object.assign(this.signedCon, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRepo(repoName: string) {
|
||||||
|
if (this.signedCon[repoName]) {
|
||||||
|
this.signedDataSet(repoName);
|
||||||
|
} else {
|
||||||
|
this.getTagInfo(repoName).then(() => {
|
||||||
|
this.signedDataSet(repoName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getTagInfo(repoName: string): Promise<void> {
|
||||||
|
// this.signedNameArr = [];
|
||||||
|
this.signedCon[repoName] = [];
|
||||||
|
return toPromise<Tag[]>(this.tagService
|
||||||
|
.getTags(repoName))
|
||||||
|
.then(items => {
|
||||||
|
items.forEach((t: Tag) => {
|
||||||
|
if (t.signature !== null) {
|
||||||
|
this.signedCon[repoName].push(t.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => this.errorHandler.error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
signedDataSet(repoName: string): void {
|
||||||
|
let signature = '';
|
||||||
|
if (this.signedCon[repoName].length === 0) {
|
||||||
|
this.confirmationDialogSet('REPOSITORY.DELETION_TITLE_REPO', signature, repoName, 'REPOSITORY.DELETION_SUMMARY_REPO', ConfirmationButtons.DELETE_CANCEL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
signature = this.signedCon[repoName].join(',');
|
||||||
|
this.confirmationDialogSet('REPOSITORY.DELETION_TITLE_REPO_SIGNED', signature, repoName, 'REPOSITORY.DELETION_SUMMARY_REPO_SIGNED', ConfirmationButtons.CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmationDialogSet(summaryTitle: string, signature: string, repoName: string, summaryKey: string, button: ConfirmationButtons): void {
|
||||||
|
this.translate.get(summaryKey,
|
||||||
|
{
|
||||||
|
repoName: repoName,
|
||||||
|
signedImages: signature,
|
||||||
|
})
|
||||||
|
.subscribe((res: string) => {
|
||||||
|
summaryKey = res;
|
||||||
|
let message = new ConfirmationMessage(
|
||||||
|
summaryTitle,
|
||||||
|
summaryKey,
|
||||||
|
repoName,
|
||||||
|
repoName,
|
||||||
|
ConfirmationTargets.REPOSITORY,
|
||||||
|
button);
|
||||||
|
this.confirmationDialog.open(message);
|
||||||
|
|
||||||
|
let hnd = setInterval(() => this.ref.markForCheck(), 100);
|
||||||
|
setTimeout(() => clearInterval(hnd), 5000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.doSearchRepoNames('');
|
||||||
|
}
|
||||||
|
|
||||||
|
watchTagClickEvt(tagClickEvt: TagClickEvent): void {
|
||||||
|
this.tagClickEvent.emit(tagClickEvt);
|
||||||
|
}
|
||||||
|
|
||||||
|
clrLoad(state: State): void {
|
||||||
|
//Keep it for future filtering and sorting
|
||||||
|
this.currentState = state;
|
||||||
|
|
||||||
|
let pageNumber: number = calculatePage(state);
|
||||||
|
if (pageNumber <= 0) { pageNumber = 1; }
|
||||||
|
|
||||||
|
//Pagination
|
||||||
|
let params: RequestQueryParams = new RequestQueryParams();
|
||||||
|
params.set("page", '' + pageNumber);
|
||||||
|
params.set("page_size", '' + this.pageSize);
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
toPromise<Repository>(this.repositoryService.getRepositories(
|
||||||
|
this.projectId,
|
||||||
|
this.lastFilteredRepoName,
|
||||||
|
params))
|
||||||
|
.then((repo: Repository) => {
|
||||||
|
this.totalCount = repo.metadata.xTotalCount;
|
||||||
|
this.repositories = repo.data;
|
||||||
|
|
||||||
|
this.signedCon = {};
|
||||||
|
//Do filtering and sorting
|
||||||
|
this.repositories = doFiltering<RepositoryItem>(this.repositories, state);
|
||||||
|
this.repositories = doSorting<RepositoryItem>(this.repositories, state);
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.loading = false;
|
||||||
|
this.errorHandler.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Force refresh view
|
||||||
|
let hnd = setInterval(() => this.ref.markForCheck(), 100);
|
||||||
|
setTimeout(() => clearInterval(hnd), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStateAfterDeletion(): State {
|
||||||
|
let total: number = this.totalCount - 1;
|
||||||
|
if (total <= 0) { return null; }
|
||||||
|
|
||||||
|
let totalPages: number = Math.ceil(total / this.pageSize);
|
||||||
|
let targetPageNumber: number = this.currentPage;
|
||||||
|
|
||||||
|
if (this.currentPage > totalPages) {
|
||||||
|
targetPageNumber = totalPages;//Should == currentPage -1
|
||||||
|
}
|
||||||
|
|
||||||
|
let st: State = this.currentState;
|
||||||
|
if (!st) {
|
||||||
|
st = { page: {} };
|
||||||
|
}
|
||||||
|
st.page.size = this.pageSize;
|
||||||
|
st.page.from = (targetPageNumber - 1) * this.pageSize;
|
||||||
|
st.page.to = targetPageNumber * this.pageSize - 1;
|
||||||
|
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
public gotoLink(projectId: number, repoName: string): void {
|
||||||
|
let linkUrl = [this.router.url, repoName];
|
||||||
|
this.router.navigate(linkUrl);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import { Type } from '@angular/core';
|
import { Type } from '@angular/core';
|
||||||
import { RepositoryComponent } from './repository.component';
|
import { RepositoryComponent } from './repository.component';
|
||||||
|
|
||||||
|
|
||||||
export const REPOSITORY_DIRECTIVES: Type<any>[] = [
|
export const REPOSITORY_DIRECTIVES: Type<any>[] = [
|
||||||
RepositoryComponent
|
RepositoryComponent
|
||||||
];
|
];
|
||||||
|
@ -1,4 +1,46 @@
|
|||||||
export const REPOSITORY_STYLE = `.option-right {
|
export const REPOSITORY_STYLE = `.option-right {
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}`;
|
}
|
||||||
|
|
||||||
|
.arrow-back {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-block {
|
||||||
|
border-right: 2px solid #cccccc;
|
||||||
|
margin-right: 6px;
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 6px 6px 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-block {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-wrapper {
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
.tag-name {
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info-edit-button {
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#images-container {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
harbor-tag {
|
||||||
|
position: relative;
|
||||||
|
top: 24px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -1,15 +1,48 @@
|
|||||||
export const REPOSITORY_TEMPLATE = `
|
export const REPOSITORY_TEMPLATE = `
|
||||||
<confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
|
<section class="overview-section">
|
||||||
<div class="row">
|
<div class="title-wrapper">
|
||||||
|
<div class="title-block arrow-block">
|
||||||
|
<clr-icon class="rotate-90 arrow-back" shape="arrow" size="36" (click)="goBack()"></clr-icon>
|
||||||
|
</div>
|
||||||
|
<div class="title-block">
|
||||||
|
<h2 sub-header-title>{{repoName}}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="detail-section">
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<div class="row flex-items-xs-right option-right">
|
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||||
<div class="flex-xs-middle">
|
<ul id="configTabs" class="nav" role="tablist">
|
||||||
<hbr-filter filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" (filter)="doSearchRepoNames($event)"></hbr-filter>
|
<li role="presentation" class="nav-item">
|
||||||
<a href="javascript:void(0)" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></a>
|
<button id="repo-info" class="btn btn-link nav-link" aria-controls="info" [class.active]='isCurrentTabLink("repo-info")' type="button" (click)='tabLinkClick("repo-info")'>{{'REPOSITORY.INFO' | translate}}</button>
|
||||||
|
</li>
|
||||||
|
<li role="presentation" class="nav-item">
|
||||||
|
<button id="repo-image" class="btn btn-link nav-link active" aria-controls="image" [class.active]='isCurrentTabLink("repo-image")' type="button" (click)='tabLinkClick("repo-image")'>{{'REPOSITORY.IMAGE' | translate}}</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<section id="info" role="tabpanel" aria-labelledby="repo-info" [hidden]='!isCurrentTabContent("info")'>
|
||||||
|
<form #repoInfoForm="ngForm">
|
||||||
|
<div id="info-edit-button">
|
||||||
|
<button class="btn btn-sm" [disabled]="editing" (click)="editInfo()" >{{'BUTTON.EDIT' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 *ngIf="!editing && !hasInfo()" >{{'REPOSITORY.NO_INFO' | translate }}</h3>
|
||||||
|
<pre *ngIf="!editing && hasInfo()" ><code>{{ imageInfo }}</code></pre>
|
||||||
|
<textarea *ngIf="editing" name="info-edit-textarea" [(ngModel)]="imageInfo"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="btn-sm" *ngIf="editing">
|
||||||
|
<button class="btn btn-primary" [disabled]="!hasChanges()" (click)="saveInfo()" >{{'BUTTON.SAVE' | translate}}</button>
|
||||||
|
<button class="btn" (click)="cancelInfo()" >{{'BUTTON.CANCEL' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<confirmation-dialog #confirmationDialog (confirmAction)="confirmCancel($event)"></confirmation-dialog>
|
||||||
<hbr-list-repository [urlPrefix]="urlPrefix" [projectId]="projectId" [repositories]="changedRepositories" (delete)="deleteRepo($event)" [hasProjectAdminRole]="hasProjectAdminRole" (paginate)="retrieve($event)"></hbr-list-repository>
|
</form>
|
||||||
|
</section>
|
||||||
|
<section id="image" role="tabpanel" aria-labelledby="repo-image" [hidden]='!isCurrentTabContent("image")'>
|
||||||
|
<div id=images-container>
|
||||||
|
<hbr-tag ngProjectAs="clr-dg-row-detail" (tagClickEvent)="watchTagClickEvt($event)" (signatureOutput)="saveSignatures($event)" class="sub-grid-custom" [repoName]="repoName" [registryUrl]="registryUrl" [withNotary]="withNotary" [withClair]="withClair" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId"></hbr-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`;
|
@ -1,19 +1,27 @@
|
|||||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, async, } from '@angular/core/testing';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { DebugElement } from '@angular/core';
|
import { DebugElement } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||||
import { RepositoryComponent } from './repository.component';
|
import { RepositoryComponent } from './repository.component';
|
||||||
import { ListRepositoryComponent } from '../list-repository/list-repository.component';
|
import { RepositoryListviewComponent } from '../repository-listview/repository-listview.component';
|
||||||
import { FilterComponent } from '../filter/filter.component';
|
import { FilterComponent } from '../filter/filter.component';
|
||||||
|
import { TagComponent } from '../tag/tag.component';
|
||||||
|
import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index';
|
||||||
|
import { PUSH_IMAGE_BUTTON_DIRECTIVES } from '../push-image/index';
|
||||||
|
import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index';
|
||||||
|
import { JobLogViewerComponent } from '../job-log-viewer/index';
|
||||||
|
|
||||||
|
|
||||||
import { ErrorHandler } from '../error-handler/error-handler';
|
import { ErrorHandler } from '../error-handler/error-handler';
|
||||||
import { Repository, RepositoryItem } from '../service/interface';
|
import { Repository, RepositoryItem, Tag, SystemInfo } from '../service/interface';
|
||||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||||
import { RepositoryService, RepositoryDefaultService } from '../service/repository.service';
|
import { RepositoryService, RepositoryDefaultService } from '../service/repository.service';
|
||||||
import { SystemInfoService, SystemInfoDefaultService } from '../service/system-info.service';
|
import { SystemInfoService, SystemInfoDefaultService } from '../service/system-info.service';
|
||||||
|
import { TagService, TagDefaultService } from '../service/tag.service';
|
||||||
|
import { ChannelService } from '../channel/index';
|
||||||
|
|
||||||
class RouterStub {
|
class RouterStub {
|
||||||
navigateByUrl(url: string) { return url; }
|
navigateByUrl(url: string) { return url; }
|
||||||
@ -21,72 +29,123 @@ class RouterStub {
|
|||||||
|
|
||||||
describe('RepositoryComponent (inline template)', () => {
|
describe('RepositoryComponent (inline template)', () => {
|
||||||
|
|
||||||
let comp: RepositoryComponent;
|
let compRepo: RepositoryComponent;
|
||||||
let fixture: ComponentFixture<RepositoryComponent>;
|
let fixture: ComponentFixture<RepositoryComponent>;
|
||||||
let repositoryService: RepositoryService;
|
let repositoryService: RepositoryService;
|
||||||
let spy: jasmine.Spy;
|
let systemInfoService: SystemInfoService;
|
||||||
|
let tagService: TagService;
|
||||||
|
|
||||||
let mockData: RepositoryItem[] = [
|
let spyRepos: jasmine.Spy;
|
||||||
{
|
let spyTags: jasmine.Spy;
|
||||||
"id": 11,
|
let spySystemInfo: jasmine.Spy;
|
||||||
"name": "library/busybox",
|
|
||||||
"project_id": 1,
|
let mockSystemInfo: SystemInfo = {
|
||||||
"description": "",
|
'with_notary': true,
|
||||||
"pull_count": 0,
|
'with_admiral': false,
|
||||||
"star_count": 0,
|
'admiral_endpoint': 'NA',
|
||||||
"tags_count": 1
|
'auth_mode': 'db_auth',
|
||||||
},
|
'registry_url': '10.112.122.56',
|
||||||
{
|
'project_creation_restriction': 'everyone',
|
||||||
"id": 12,
|
'self_registration': true,
|
||||||
"name": "library/nginx",
|
'has_ca_root': false,
|
||||||
"project_id": 1,
|
'harbor_version': 'v1.1.1-rc1-160-g565110d'
|
||||||
"description": "",
|
|
||||||
"pull_count": 0,
|
|
||||||
"star_count": 0,
|
|
||||||
"tags_count": 1
|
|
||||||
}
|
|
||||||
];
|
|
||||||
let mockRepo: Repository = {
|
|
||||||
metadata: { xTotalCount: 2 },
|
|
||||||
data: mockData
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mockRepoData: RepositoryItem[] = [
|
||||||
|
{
|
||||||
|
'id': 1,
|
||||||
|
'name': 'library/busybox',
|
||||||
|
'project_id': 1,
|
||||||
|
'description': 'asdfsadf',
|
||||||
|
'pull_count': 0,
|
||||||
|
'star_count': 0,
|
||||||
|
'tags_count': 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 2,
|
||||||
|
'name': 'library/nginx',
|
||||||
|
'project_id': 1,
|
||||||
|
'description': 'asdf',
|
||||||
|
'pull_count': 0,
|
||||||
|
'star_count': 0,
|
||||||
|
'tags_count': 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let mockRepo: Repository = {
|
||||||
|
metadata: {xTotalCount: 2},
|
||||||
|
data: mockRepoData
|
||||||
|
};
|
||||||
|
|
||||||
|
let mockTagData: Tag[] = [
|
||||||
|
{
|
||||||
|
'digest': 'sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55',
|
||||||
|
'name': '1.11.5',
|
||||||
|
'size': '2049',
|
||||||
|
'architecture': 'amd64',
|
||||||
|
'os': 'linux',
|
||||||
|
'docker_version': '1.12.3',
|
||||||
|
'author': 'NGINX Docker Maintainers \"docker-maint@nginx.com\"',
|
||||||
|
'created': new Date('2016-11-08T22:41:15.912313785Z'),
|
||||||
|
'signature': null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
let config: IServiceConfig = {
|
let config: IServiceConfig = {
|
||||||
repositoryBaseEndpoint: '/api/repository/testing'
|
repositoryBaseEndpoint: '/api/repository/testing',
|
||||||
|
systemInfoEndpoint: '/api/systeminfo/testing',
|
||||||
|
targetBaseEndpoint: '/api/tag/testing'
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
SharedModule
|
SharedModule,
|
||||||
|
RouterTestingModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
RepositoryComponent,
|
RepositoryComponent,
|
||||||
ListRepositoryComponent,
|
RepositoryListviewComponent,
|
||||||
ConfirmationDialogComponent,
|
ConfirmationDialogComponent,
|
||||||
FilterComponent
|
FilterComponent,
|
||||||
|
TagComponent,
|
||||||
|
VULNERABILITY_DIRECTIVES,
|
||||||
|
PUSH_IMAGE_BUTTON_DIRECTIVES,
|
||||||
|
INLINE_ALERT_DIRECTIVES,
|
||||||
|
JobLogViewerComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ErrorHandler,
|
ErrorHandler,
|
||||||
{ provide: SERVICE_CONFIG, useValue: config },
|
{ provide: SERVICE_CONFIG, useValue: config },
|
||||||
{ provide: RepositoryService, useClass: RepositoryDefaultService },
|
{ provide: RepositoryService, useClass: RepositoryDefaultService },
|
||||||
{ provide: SystemInfoService, useClass: SystemInfoDefaultService },
|
{ provide: SystemInfoService, useClass: SystemInfoDefaultService },
|
||||||
{ provide: Router, useClass: RouterStub }
|
{ provide: TagService, useClass: TagDefaultService },
|
||||||
|
{ provide: ChannelService},
|
||||||
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(RepositoryComponent);
|
fixture = TestBed.createComponent(RepositoryComponent);
|
||||||
comp = fixture.componentInstance;
|
compRepo = fixture.componentInstance;
|
||||||
comp.projectId = 1;
|
compRepo.projectId = 1;
|
||||||
comp.hasProjectAdminRole = true;
|
compRepo.hasProjectAdminRole = true;
|
||||||
|
compRepo.repoName = 'library/nginx';
|
||||||
repositoryService = fixture.debugElement.injector.get(RepositoryService);
|
repositoryService = fixture.debugElement.injector.get(RepositoryService);
|
||||||
|
systemInfoService = fixture.debugElement.injector.get(SystemInfoService);
|
||||||
|
tagService = fixture.debugElement.injector.get(TagService);
|
||||||
|
|
||||||
spy = spyOn(repositoryService, 'getRepositories').and.returnValues(Promise.resolve(mockRepo));
|
spyRepos = spyOn(repositoryService, 'getRepositories').and.returnValues(Promise.resolve(mockRepo));
|
||||||
|
spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValues(Promise.resolve(mockSystemInfo));
|
||||||
|
spyTags = spyOn(tagService, 'getTags').and.returnValues(Promise.resolve(mockTagData));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(compRepo).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it('should load and render data', async(() => {
|
it('should load and render data', async(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
@ -99,22 +158,4 @@ describe('RepositoryComponent (inline template)', () => {
|
|||||||
expect(el.textContent).toEqual('library/busybox');
|
expect(el.textContent).toEqual('library/busybox');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should filter data by keyword', async(() => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
fixture.whenStable().then(() => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
comp.doSearchRepoNames('nginx');
|
|
||||||
fixture.detectChanges();
|
|
||||||
let de: DebugElement[] = fixture.debugElement.queryAll(By.css('datagrid-cell'));
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(de).toBeTruthy();
|
|
||||||
expect(de.length).toEqual(1);
|
|
||||||
let el: HTMLElement = de[0].nativeElement;
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(el).toBeTruthy();
|
|
||||||
expect(el.textContent).toEqual('library/nginx');
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
});
|
});
|
@ -11,20 +11,21 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
import { Component, OnInit, ViewChild, Input } from '@angular/core';
|
import { Component, OnInit, ViewChild, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
|
||||||
import { RepositoryService } from '../service/repository.service';
|
import { RepositoryService } from '../service/repository.service';
|
||||||
import { Repository, RepositoryItem } from '../service/interface';
|
|
||||||
|
import { Repository, RepositoryItem, Tag, TagClickEvent,
|
||||||
|
SystemInfo, SystemInfoService, TagService } from '../service/index';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { ErrorHandler } from '../error-handler/error-handler';
|
import { ErrorHandler } from '../error-handler/error-handler';
|
||||||
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const';
|
import { ConfirmationState, ConfirmationTargets } from '../shared/shared.const';
|
||||||
|
|
||||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||||
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
|
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
|
||||||
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message';
|
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
|
||||||
|
|
||||||
import { State } from 'clarity-angular';
|
import { State } from 'clarity-angular';
|
||||||
|
|
||||||
@ -33,43 +34,54 @@ import { toPromise } from '../utils';
|
|||||||
import { REPOSITORY_TEMPLATE } from './repository.component.html';
|
import { REPOSITORY_TEMPLATE } from './repository.component.html';
|
||||||
import { REPOSITORY_STYLE } from './repository.component.css';
|
import { REPOSITORY_STYLE } from './repository.component.css';
|
||||||
|
|
||||||
|
const TabLinkContentMap: {[index: string]: string} = {
|
||||||
|
'repo-info': 'info',
|
||||||
|
'repo-image': 'image'
|
||||||
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'hbr-repository',
|
selector: 'hbr-repository',
|
||||||
template: REPOSITORY_TEMPLATE,
|
template: REPOSITORY_TEMPLATE,
|
||||||
styles: [REPOSITORY_STYLE]
|
styles: [REPOSITORY_STYLE]
|
||||||
})
|
})
|
||||||
export class RepositoryComponent implements OnInit {
|
export class RepositoryComponent implements OnInit {
|
||||||
changedRepositories: RepositoryItem[];
|
signedCon: {[key: string]: any | string[]} = {};
|
||||||
|
|
||||||
@Input() projectId: number;
|
@Input() projectId: number;
|
||||||
@Input() urlPrefix: string;
|
@Input() projectName: string;
|
||||||
|
@Input() repoName: string;
|
||||||
|
@Input() hasSignedIn: boolean;
|
||||||
@Input() hasProjectAdminRole: boolean;
|
@Input() hasProjectAdminRole: boolean;
|
||||||
|
|
||||||
lastFilteredRepoName: string;
|
@Output() tagClickEvent = new EventEmitter<TagClickEvent>();
|
||||||
|
@Output() backEvt: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
|
onGoing = false;
|
||||||
|
withNotary = false;
|
||||||
|
withClair = true;
|
||||||
|
editing = false;
|
||||||
|
inProgress = true;
|
||||||
|
currentTabID = 'repo-image';
|
||||||
|
changedRepositories: RepositoryItem[];
|
||||||
|
systemInfo: SystemInfo;
|
||||||
|
|
||||||
|
imageInfo: string;
|
||||||
|
orgImageInfo: string;
|
||||||
|
|
||||||
|
timerHandler: any;
|
||||||
|
|
||||||
@ViewChild('confirmationDialog')
|
@ViewChild('confirmationDialog')
|
||||||
confirmationDialog: ConfirmationDialogComponent;
|
confirmationDlg: ConfirmationDialogComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private errorHandler: ErrorHandler,
|
private errorHandler: ErrorHandler,
|
||||||
private repositoryService: RepositoryService,
|
private repositoryService: RepositoryService,
|
||||||
private translateService: TranslateService
|
private systemInfoService: SystemInfoService,
|
||||||
|
private tagService: TagService,
|
||||||
|
private translate: TranslateService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
confirmDeletion(message: ConfirmationAcknowledgement) {
|
public get registryUrl(): string {
|
||||||
if (message &&
|
return this.systemInfo ? this.systemInfo.registry_url : '';
|
||||||
message.source === ConfirmationTargets.REPOSITORY &&
|
|
||||||
message.state === ConfirmationState.CONFIRMED) {
|
|
||||||
let repoName = message.data;
|
|
||||||
toPromise<number>(this.repositoryService
|
|
||||||
.deleteRepository(repoName))
|
|
||||||
.then(
|
|
||||||
response => {
|
|
||||||
this.refresh();
|
|
||||||
this.translateService.get('REPOSITORY.DELETED_REPO_SUCCESS')
|
|
||||||
.subscribe(res=>this.errorHandler.info(res));
|
|
||||||
}).catch(error => this.errorHandler.error(error));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -77,37 +89,117 @@ export class RepositoryComponent implements OnInit {
|
|||||||
this.errorHandler.error('Project ID cannot be unset.');
|
this.errorHandler.error('Project ID cannot be unset.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.lastFilteredRepoName = '';
|
|
||||||
this.retrieve();
|
this.retrieve();
|
||||||
|
this.inProgress = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieve(state?: State) {
|
retrieve(state?: State) {
|
||||||
toPromise<Repository>(this.repositoryService
|
toPromise<Repository>(this.repositoryService.getRepositories(this.projectId, this.repoName))
|
||||||
.getRepositories(this.projectId, this.lastFilteredRepoName))
|
|
||||||
.then(
|
.then(
|
||||||
response => {
|
response => {
|
||||||
this.changedRepositories = response.data;
|
if (response.metadata.xTotalCount > 0) {
|
||||||
},
|
this.orgImageInfo = response.data[0].description;
|
||||||
error => this.errorHandler.error(error));
|
this.imageInfo = response.data[0].description;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => this.errorHandler.error(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
doSearchRepoNames(repoName: string) {
|
saveSignatures(event: {[key: string]: string[]}): void {
|
||||||
this.lastFilteredRepoName = repoName;
|
Object.assign(this.signedCon, event);
|
||||||
this.retrieve();
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteRepo(repoName: string) {
|
|
||||||
let message = new ConfirmationMessage(
|
|
||||||
'REPOSITORY.DELETION_TITLE_REPO',
|
|
||||||
'REPOSITORY.DELETION_SUMMARY_REPO',
|
|
||||||
repoName,
|
|
||||||
repoName,
|
|
||||||
ConfirmationTargets.REPOSITORY,
|
|
||||||
ConfirmationButtons.DELETE_CANCEL);
|
|
||||||
this.confirmationDialog.open(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this.retrieve();
|
this.retrieve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watchTagClickEvt(tagClickEvt: TagClickEvent): void {
|
||||||
|
this.tagClickEvent.emit(tagClickEvt);
|
||||||
|
}
|
||||||
|
|
||||||
|
isCurrentTabLink(tabID: string): boolean {
|
||||||
|
return this.currentTabID === tabID;
|
||||||
|
}
|
||||||
|
|
||||||
|
isCurrentTabContent(ContentID: string): boolean {
|
||||||
|
return TabLinkContentMap[this.currentTabID] === ContentID;
|
||||||
|
}
|
||||||
|
|
||||||
|
tabLinkClick(tabID: string) {
|
||||||
|
this.currentTabID = tabID;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTagInfo(repoName: string): Promise<void> {
|
||||||
|
// this.signedNameArr = [];
|
||||||
|
this.signedCon[repoName] = [];
|
||||||
|
return toPromise<Tag[]>(this.tagService
|
||||||
|
.getTags(repoName))
|
||||||
|
.then(items => {
|
||||||
|
items.forEach((t: Tag) => {
|
||||||
|
if (t.signature !== null) {
|
||||||
|
this.signedCon[repoName].push(t.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => this.errorHandler.error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack() {
|
||||||
|
this.backEvt.emit(this.projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasChanges() {
|
||||||
|
return this.imageInfo !== this.orgImageInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.imageInfo = this.orgImageInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasInfo() {
|
||||||
|
return this.imageInfo && this.imageInfo.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
editInfo() {
|
||||||
|
this.editing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveInfo() {
|
||||||
|
if (!this.hasChanges()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onGoing = true;
|
||||||
|
toPromise<any>(this.repositoryService.updateRepositoryDescription(this.repoName, this.imageInfo))
|
||||||
|
.then(() => {
|
||||||
|
this.onGoing = false;
|
||||||
|
this.translate.get('CONFIG.SAVE_SUCCESS').subscribe((res: string) => {
|
||||||
|
this.errorHandler.info(res);
|
||||||
|
});
|
||||||
|
this.editing = false;
|
||||||
|
this.refresh();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.onGoing = false;
|
||||||
|
this.errorHandler.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelInfo() {
|
||||||
|
let msg = new ConfirmationMessage(
|
||||||
|
'CONFIG.CONFIRM_TITLE',
|
||||||
|
'CONFIG.CONFIRM_SUMMARY',
|
||||||
|
'',
|
||||||
|
{},
|
||||||
|
ConfirmationTargets.CONFIG
|
||||||
|
);
|
||||||
|
this.confirmationDlg.open(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmCancel(ack: ConfirmationAcknowledgement): void {
|
||||||
|
this.editing = false;
|
||||||
|
if (ack && ack.source === ConfirmationTargets.CONFIG &&
|
||||||
|
ack.state === ConfirmationState.CONFIRMED) {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { RequestQueryParams } from './RequestQueryParams';
|
import { RequestQueryParams } from './RequestQueryParams';
|
||||||
import { Repository, RepositoryItem } from './interface';
|
import { Repository, RepositoryItem } from './interface';
|
||||||
import { Injectable, Inject } from "@angular/core";
|
import { Injectable, Inject } from '@angular/core';
|
||||||
import 'rxjs/add/observable/of';
|
import 'rxjs/add/observable/of';
|
||||||
import { Http } from '@angular/http';
|
import { Http } from '@angular/http';
|
||||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||||
@ -31,7 +31,20 @@ export abstract class RepositoryService {
|
|||||||
*
|
*
|
||||||
* @memberOf RepositoryService
|
* @memberOf RepositoryService
|
||||||
*/
|
*/
|
||||||
abstract getRepositories(projectId: number | string, repositoryName?: string, queryParams?: RequestQueryParams): Observable<Repository> | Promise<Repository> | Repository;
|
abstract getRepositories(projectId: number | string, repositoryName?: string, queryParams?: RequestQueryParams):
|
||||||
|
Observable<Repository> | Promise<Repository> | Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update description of specified repository.
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
* @param {number | string} projectId
|
||||||
|
* @param {string} repoName
|
||||||
|
* @returns {(Observable<Repository> | Promise<Repository> | Repository)}
|
||||||
|
*
|
||||||
|
* @memberOf RepositoryService
|
||||||
|
*/
|
||||||
|
abstract updateRepositoryDescription(repoName: string, description: string): Observable<any> | Promise<any> | any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE the specified repository.
|
* DELETE the specified repository.
|
||||||
@ -61,21 +74,22 @@ export class RepositoryDefaultService extends RepositoryService {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRepositories(projectId: number | string, repositoryName?: string, queryParams?: RequestQueryParams): Observable<Repository> | Promise<Repository> | Repository {
|
public getRepositories(projectId: number | string, repositoryName?: string, queryParams?: RequestQueryParams):
|
||||||
|
Observable<Repository> | Promise<Repository> | Repository {
|
||||||
if (!projectId) {
|
if (!projectId) {
|
||||||
return Promise.reject("Bad argument");
|
return Promise.reject('Bad argument');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!queryParams) {
|
if (!queryParams) {
|
||||||
queryParams = new RequestQueryParams();
|
queryParams = new RequestQueryParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
queryParams.set('project_id', "" + projectId);
|
queryParams.set('project_id', '' + projectId);
|
||||||
if (repositoryName && repositoryName.trim() !== '') {
|
if (repositoryName && repositoryName.trim() !== '') {
|
||||||
queryParams.set('q', repositoryName);
|
queryParams.set('q', repositoryName);
|
||||||
}
|
}
|
||||||
|
|
||||||
let url: string = this.config.repositoryBaseEndpoint ? this.config.repositoryBaseEndpoint : "/api/repositories";
|
let url: string = this.config.repositoryBaseEndpoint ? this.config.repositoryBaseEndpoint : '/api/repositories';
|
||||||
return this.http.get(url, buildHttpRequestOptions(queryParams)).toPromise()
|
return this.http.get(url, buildHttpRequestOptions(queryParams)).toPromise()
|
||||||
.then(response => {
|
.then(response => {
|
||||||
let result: Repository = {
|
let result: Repository = {
|
||||||
@ -84,7 +98,7 @@ export class RepositoryDefaultService extends RepositoryService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (response && response.headers) {
|
if (response && response.headers) {
|
||||||
let xHeader: string = response.headers.get("X-Total-Count");
|
let xHeader: string = response.headers.get('X-Total-Count');
|
||||||
if (xHeader) {
|
if (xHeader) {
|
||||||
result.metadata.xTotalCount = parseInt(xHeader, 0);
|
result.metadata.xTotalCount = parseInt(xHeader, 0);
|
||||||
}
|
}
|
||||||
@ -103,6 +117,20 @@ export class RepositoryDefaultService extends RepositoryService {
|
|||||||
.catch(error => Promise.reject(error));
|
.catch(error => Promise.reject(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updateRepositoryDescription(repositoryName: string, description: string,
|
||||||
|
queryParams?: RequestQueryParams): Observable<any> | Promise<any> | any {
|
||||||
|
|
||||||
|
if (!queryParams) {
|
||||||
|
queryParams = new RequestQueryParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseUrl: string = this.config.repositoryBaseEndpoint ? this.config.repositoryBaseEndpoint : '/api/repositories';
|
||||||
|
let url = `${baseUrl}/${repositoryName}`;
|
||||||
|
return this.http.put(url, {'description': description }, HTTP_JSON_OPTIONS).toPromise()
|
||||||
|
.then(response => response)
|
||||||
|
.catch(error => Promise.reject(error));
|
||||||
|
}
|
||||||
|
|
||||||
public deleteRepository(repositoryName: string): Observable<any> | Promise<any> | any {
|
public deleteRepository(repositoryName: string): Observable<any> | Promise<any> | any {
|
||||||
if (!repositoryName) {
|
if (!repositoryName) {
|
||||||
return Promise.reject('Bad argument');
|
return Promise.reject('Bad argument');
|
||||||
@ -112,6 +140,6 @@ export class RepositoryDefaultService extends RepositoryService {
|
|||||||
|
|
||||||
return this.http.delete(url, HTTP_JSON_OPTIONS).toPromise()
|
return this.http.delete(url, HTTP_JSON_OPTIONS).toPromise()
|
||||||
.then(response => response)
|
.then(response => response)
|
||||||
.catch(error => { Promise.reject(error) });
|
.catch(error => { Promise.reject(error); });
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -62,7 +62,8 @@ export const CommonRoutes = {
|
|||||||
|
|
||||||
export const enum ConfirmationState {
|
export const enum ConfirmationState {
|
||||||
NA, CONFIRMED, CANCEL
|
NA, CONFIRMED, CANCEL
|
||||||
}
|
};
|
||||||
|
|
||||||
export const enum ConfirmationButtons {
|
export const enum ConfirmationButtons {
|
||||||
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE
|
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE
|
||||||
}
|
};
|
||||||
|
@ -7,7 +7,7 @@ export const TAG_DETAIL_HTML: string = `
|
|||||||
</div>
|
</div>
|
||||||
<div class="title-block">
|
<div class="title-block">
|
||||||
<div class="tag-name">
|
<div class="tag-name">
|
||||||
{{tagDetails.name}}
|
<h1>{{tagDetails.name}}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="tag-timestamp">
|
<div class="tag-timestamp">
|
||||||
{{'TAG.CREATION_TIME_PREFIX' | translate }} {{tagDetails.created | date }} {{'TAG.CREATOR_PREFIX' | translate }} {{author | translate}}
|
{{'TAG.CREATION_TIME_PREFIX' | translate }} {{tagDetails.created | date }} {{'TAG.CREATOR_PREFIX' | translate }} {{author | translate}}
|
||||||
|
@ -1,4 +1,17 @@
|
|||||||
export const TAG_STYLE = `
|
export const TAG_STYLE = `
|
||||||
|
.option-right {
|
||||||
|
padding-right: 18px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn:hover {
|
||||||
|
color: #007CBB;
|
||||||
|
}
|
||||||
|
|
||||||
.sub-header-title {
|
.sub-header-title {
|
||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
}
|
}
|
||||||
@ -20,18 +33,6 @@ export const TAG_STYLE = `
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host >>> .datagrid .datagrid-body {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host >>> .datagrid .datagrid-head .datagrid-row {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host >>> .datagrid .datagrid-body .datagrid-row-master {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.truncated {
|
.truncated {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -12,8 +12,16 @@ export const TAG_TEMPLATE = `
|
|||||||
<button type="button" class="btn btn-primary" [ngxClipboard]="digestTarget" (cbOnSuccess)="onSuccess($event)" (cbOnError)="onError($event)">{{'BUTTON.COPY' | translate}}</button>
|
<button type="button" class="btn btn-primary" [ngxClipboard]="digestTarget" (cbOnSuccess)="onSuccess($event)" (cbOnError)="onError($event)">{{'BUTTON.COPY' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
|
<div class="row">
|
||||||
<h2 *ngIf="!isEmbedded" class="sub-header-title">{{repoName}}</h2>
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
|
<div class="row flex-items-xs-right option-right">
|
||||||
|
<div class="flex-xs-middle">
|
||||||
|
<hbr-filter [withDivider]="true" filterPlaceholder="{{'TAG.FILTER_FOR_TAGS' | translate}}" (filter)="doSearchTagNames($event)" [currentValue]="lastFilteredTagName"></hbr-filter>
|
||||||
|
<span class="refresh-btn" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<clr-datagrid [clrDgLoading]="loading" [class.embeded-datagrid]="isEmbedded">
|
<clr-datagrid [clrDgLoading]="loading" [class.embeded-datagrid]="isEmbedded">
|
||||||
<clr-dg-column style="min-width: 160px;" [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
<clr-dg-column style="min-width: 160px;" [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column style="width: 90px;" [clrDgField]="'size'">{{'REPOSITORY.SIZE' | translate}}</clr-dg-column>
|
<clr-dg-column style="width: 90px;" [clrDgField]="'size'">{{'REPOSITORY.SIZE' | translate}}</clr-dg-column>
|
||||||
@ -58,4 +66,6 @@ export const TAG_TEMPLATE = `
|
|||||||
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
|
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="10"></clr-dg-pagination>
|
<clr-dg-pagination #pagination [clrDgPageSize]="10"></clr-dg-pagination>
|
||||||
</clr-dg-footer>
|
</clr-dg-footer>
|
||||||
</clr-datagrid>`;
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
ElementRef
|
ElementRef
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import { TagService, VulnerabilitySeverity } from '../service/index';
|
import { TagService, VulnerabilitySeverity, RequestQueryParams } from '../service/index';
|
||||||
import { ErrorHandler } from '../error-handler/error-handler';
|
import { ErrorHandler } from '../error-handler/error-handler';
|
||||||
import { ChannelService } from '../channel/index';
|
import { ChannelService } from '../channel/index';
|
||||||
import {
|
import {
|
||||||
@ -44,13 +44,17 @@ import { TAG_STYLE } from './tag.component.css';
|
|||||||
import {
|
import {
|
||||||
toPromise,
|
toPromise,
|
||||||
CustomComparator,
|
CustomComparator,
|
||||||
VULNERABILITY_SCAN_STATUS
|
calculatePage,
|
||||||
|
doFiltering,
|
||||||
|
doSorting,
|
||||||
|
VULNERABILITY_SCAN_STATUS,
|
||||||
|
DEFAULT_PAGE_SIZE
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { State, Comparator } from 'clarity-angular';
|
import { State, Comparator } from 'clarity-angular';
|
||||||
import {CopyInputComponent} from "../push-image/copy-input.component";
|
import {CopyInputComponent} from '../push-image/copy-input.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'hbr-tag',
|
selector: 'hbr-tag',
|
||||||
@ -60,6 +64,7 @@ import {CopyInputComponent} from "../push-image/copy-input.component";
|
|||||||
})
|
})
|
||||||
export class TagComponent implements OnInit {
|
export class TagComponent implements OnInit {
|
||||||
|
|
||||||
|
signedCon: {[key: string]: any | string[]} = {};
|
||||||
@Input() projectId: number;
|
@Input() projectId: number;
|
||||||
@Input() repoName: string;
|
@Input() repoName: string;
|
||||||
@Input() isEmbedded: boolean;
|
@Input() isEmbedded: boolean;
|
||||||
@ -82,6 +87,7 @@ export class TagComponent implements OnInit {
|
|||||||
digestId: string;
|
digestId: string;
|
||||||
staticBackdrop: boolean = true;
|
staticBackdrop: boolean = true;
|
||||||
closable: boolean = false;
|
closable: boolean = false;
|
||||||
|
lastFilteredTagName: string;
|
||||||
|
|
||||||
createdComparator: Comparator<Tag> = new CustomComparator<Tag>('created', 'date');
|
createdComparator: Comparator<Tag> = new CustomComparator<Tag>('created', 'date');
|
||||||
|
|
||||||
@ -94,6 +100,10 @@ export class TagComponent implements OnInit {
|
|||||||
@ViewChild('digestTarget') textInput: ElementRef;
|
@ViewChild('digestTarget') textInput: ElementRef;
|
||||||
@ViewChild('copyInput') copyInput: CopyInputComponent;
|
@ViewChild('copyInput') copyInput: CopyInputComponent;
|
||||||
|
|
||||||
|
pageSize: number = DEFAULT_PAGE_SIZE;
|
||||||
|
currentPage = 1;
|
||||||
|
totalCount = 0;
|
||||||
|
currentState: State;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private errorHandler: ErrorHandler,
|
private errorHandler: ErrorHandler,
|
||||||
@ -136,6 +146,61 @@ export class TagComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.retrieve();
|
this.retrieve();
|
||||||
|
this.lastFilteredTagName = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
doSearchTagNames(tagName: string) {
|
||||||
|
this.lastFilteredTagName = tagName;
|
||||||
|
this.currentPage = 1;
|
||||||
|
|
||||||
|
let st: State = this.currentState;
|
||||||
|
if (!st) {
|
||||||
|
st = { page: {} };
|
||||||
|
}
|
||||||
|
st.page.size = this.pageSize;
|
||||||
|
st.page.from = 0;
|
||||||
|
st.page.to = this.pageSize - 1;
|
||||||
|
st.filters = [{property: 'name', value: this.lastFilteredTagName}];
|
||||||
|
this.clrLoad(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
clrLoad(state: State): void {
|
||||||
|
// Keep it for future filtering and sorting
|
||||||
|
this.currentState = state;
|
||||||
|
|
||||||
|
let pageNumber: number = calculatePage(state);
|
||||||
|
if (pageNumber <= 0) { pageNumber = 1; }
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
let params: RequestQueryParams = new RequestQueryParams();
|
||||||
|
params.set('page', '' + pageNumber);
|
||||||
|
params.set('page_size', '' + this.pageSize);
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
toPromise<Tag[]>(this.tagService.getTags(
|
||||||
|
this.repoName,
|
||||||
|
params))
|
||||||
|
.then((tags: Tag[]) => {
|
||||||
|
this.signedCon = {};
|
||||||
|
// Do filtering and sorting
|
||||||
|
this.tags = doFiltering<Tag>(tags, state);
|
||||||
|
this.tags = doSorting<Tag>(this.tags, state);
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.loading = false;
|
||||||
|
this.errorHandler.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Force refresh view
|
||||||
|
let hnd = setInterval(() => this.ref.markForCheck(), 100);
|
||||||
|
setTimeout(() => clearInterval(hnd), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.doSearchTagNames('');
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieve() {
|
retrieve() {
|
||||||
@ -285,7 +350,7 @@ export class TagComponent implements OnInit {
|
|||||||
// Trigger scan
|
// Trigger scan
|
||||||
scanNow(tagId: string): void {
|
scanNow(tagId: string): void {
|
||||||
if (tagId) {
|
if (tagId) {
|
||||||
this.channel.publishScanEvent(this.repoName + "/" + tagId);
|
this.channel.publishScanEvent(this.repoName + '/' + tagId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
"clarity-icons": "^0.9.8",
|
"clarity-icons": "^0.9.8",
|
||||||
"clarity-ui": "^0.9.8",
|
"clarity-ui": "^0.9.8",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"harbor-ui": "0.5.27",
|
"harbor-ui": "0.6.0",
|
||||||
"intl": "^1.2.5",
|
"intl": "^1.2.5",
|
||||||
"mutationobserver-shim": "^0.3.2",
|
"mutationobserver-shim": "^0.3.2",
|
||||||
"ngx-cookie": "^1.0.0",
|
"ngx-cookie": "^1.0.0",
|
||||||
|
@ -106,6 +106,14 @@ const harborRoutes: Routes = [
|
|||||||
projectResolver: ProjectRoutingResolver
|
projectResolver: ProjectRoutingResolver
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'projects/:id/repositories/:repo',
|
||||||
|
component: TagRepositoryComponent,
|
||||||
|
canActivate: [MemberGuard],
|
||||||
|
resolve: {
|
||||||
|
projectResolver: ProjectRoutingResolver
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'projects/:id',
|
path: 'projects/:id',
|
||||||
component: ProjectDetailComponent,
|
component: ProjectDetailComponent,
|
||||||
@ -118,6 +126,10 @@ const harborRoutes: Routes = [
|
|||||||
path: 'repositories',
|
path: 'repositories',
|
||||||
component: RepositoryPageComponent
|
component: RepositoryPageComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'repositories/:repo/tags',
|
||||||
|
component: TagRepositoryComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'repositories/:repo/tags/:tag',
|
path: 'repositories/:repo/tags/:tag',
|
||||||
component: TagDetailPageComponent
|
component: TagDetailPageComponent
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<div style="margin-top: 24px;">
|
<div style="margin-top: 24px;">
|
||||||
<hbr-repository-stackview [projectId]="projectId" [projectName]="projectName" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" (tagClickEvent)="watchTagClickEvent($event)"></hbr-repository-stackview>
|
<hbr-repository-listview [projectId]="projectId" [projectName]="projectName" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" (tagClickEvent)="watchTagClickEvent($event)"></hbr-repository-listview>
|
||||||
</div>
|
</div>
|
@ -48,7 +48,7 @@ export class RepositoryPageComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watchTagClickEvent(tagEvt: TagClickEvent): void {
|
watchTagClickEvent(tagEvt: TagClickEvent): void {
|
||||||
let linkUrl = ['harbor', 'projects', tagEvt.project_id, 'repositories', tagEvt.repository_name, 'tags', tagEvt.tag_name];
|
let linkUrl = ['harbor', 'projects', tagEvt.project_id, 'repositories', tagEvt.repository_name];
|
||||||
this.router.navigate(linkUrl);
|
this.router.navigate(linkUrl);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
<div>
|
<div>
|
||||||
<a *ngIf="hasSignedIn" [routerLink]="['/harbor', 'projects', projectId, 'repositories']">< {{'REPOSITORY.REPOSITORIES' | translate}}</a>
|
<hbr-repository (tagClickEvent)="watchTagClickEvt($event)" (backEvt)="goBack($event)" [repoName]="repoName" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId"></hbr-repository>
|
||||||
<a *ngIf="!hasSignedIn" [routerLink]="['/harbor', 'sign-in']">< {{'SEARCH.BACK' | translate}}</a>
|
|
||||||
<hbr-tag (tagClickEvent)="watchTagClickEvt($event)" [repoName]="repoName" [registryUrl]="registryUrl" [withNotary]="withNotary" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId" [isEmbedded]="false" [withClair]="withClair"></hbr-tag>
|
|
||||||
</div>
|
</div>
|
@ -38,11 +38,16 @@ export class TagRepositoryComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.projectId = this.route.snapshot.params['id'];
|
||||||
|
if (!this.projectId) {
|
||||||
|
this.projectId = this.route.snapshot.parent.params['id'];
|
||||||
|
};
|
||||||
|
// let resolverData = this.route.snapshot.parent.data;
|
||||||
let resolverData = this.route.snapshot.data;
|
let resolverData = this.route.snapshot.data;
|
||||||
|
|
||||||
if (resolverData) {
|
if (resolverData) {
|
||||||
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
||||||
}
|
}
|
||||||
this.projectId = this.route.snapshot.params['id'];
|
|
||||||
this.repoName = this.route.snapshot.params['repo'];
|
this.repoName = this.route.snapshot.params['repo'];
|
||||||
|
|
||||||
this.registryUrl = this.appConfigService.getConfig().registry_url;
|
this.registryUrl = this.appConfigService.getConfig().registry_url;
|
||||||
@ -64,4 +69,8 @@ export class TagRepositoryComponent implements OnInit {
|
|||||||
let linkUrl = ['harbor', 'projects', tagEvt.project_id, 'repositories', tagEvt.repository_name, 'tags', tagEvt.tag_name];
|
let linkUrl = ['harbor', 'projects', tagEvt.project_id, 'repositories', tagEvt.repository_name, 'tags', tagEvt.tag_name];
|
||||||
this.router.navigate(linkUrl);
|
this.router.navigate(linkUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
goBack(tag: string): void {
|
||||||
|
this.router.navigate(["harbor", "projects", this.projectId, "repositories"]);
|
||||||
|
}
|
||||||
}
|
}
|
@ -32,7 +32,8 @@
|
|||||||
"YES": "YES",
|
"YES": "YES",
|
||||||
"NO": "NO",
|
"NO": "NO",
|
||||||
"NEGATIVE": "NEGATIVE",
|
"NEGATIVE": "NEGATIVE",
|
||||||
"COPY": "COPY"
|
"COPY": "COPY",
|
||||||
|
"EDIT": "EDIT"
|
||||||
},
|
},
|
||||||
"TOOLTIP": {
|
"TOOLTIP": {
|
||||||
"EMAIL": "Email should be a valid email address like name@example.com.",
|
"EMAIL": "Email should be a valid email address like name@example.com.",
|
||||||
@ -364,7 +365,10 @@
|
|||||||
"DELETED_TAG_SUCCESS": "Deleted tag successfully.",
|
"DELETED_TAG_SUCCESS": "Deleted tag successfully.",
|
||||||
"COPY": "Copy",
|
"COPY": "Copy",
|
||||||
"NOTARY_IS_UNDETERMINED": "Cannot determine the signature of this tag.",
|
"NOTARY_IS_UNDETERMINED": "Cannot determine the signature of this tag.",
|
||||||
"PLACEHOLDER": "We couldn't find any repositories!"
|
"PLACEHOLDER": "We couldn't find any repositories!",
|
||||||
|
"INFO": "Info",
|
||||||
|
"NO_INFO": "No description info for this repository",
|
||||||
|
"IMAGE": "Images"
|
||||||
},
|
},
|
||||||
"ALERT": {
|
"ALERT": {
|
||||||
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet. Do you want to cancel?"
|
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet. Do you want to cancel?"
|
||||||
@ -554,7 +558,8 @@
|
|||||||
"SCAN_COMPLETION_TIME": "Scan Completed",
|
"SCAN_COMPLETION_TIME": "Scan Completed",
|
||||||
"IMAGE_VULNERABILITIES": "Image Vulnerabilities",
|
"IMAGE_VULNERABILITIES": "Image Vulnerabilities",
|
||||||
"PLACEHOLDER": "We couldn't find any tags!",
|
"PLACEHOLDER": "We couldn't find any tags!",
|
||||||
"COPY_ERROR": "Copy failed, please try to manually copy."
|
"COPY_ERROR": "Copy failed, please try to manually copy.",
|
||||||
|
"FILTER_FOR_TAGS": "Filter Tags"
|
||||||
},
|
},
|
||||||
"UNKNOWN_ERROR": "Unknown errors have occurred. Please try again later.",
|
"UNKNOWN_ERROR": "Unknown errors have occurred. Please try again later.",
|
||||||
"UNAUTHORIZED_ERROR": "Your session is invalid or has expired. You need to sign in to continue your action.",
|
"UNAUTHORIZED_ERROR": "Your session is invalid or has expired. You need to sign in to continue your action.",
|
||||||
|
@ -32,7 +32,8 @@
|
|||||||
"YES": "SI",
|
"YES": "SI",
|
||||||
"NO": "NO",
|
"NO": "NO",
|
||||||
"NEGATIVE": "NEGATIVO",
|
"NEGATIVE": "NEGATIVO",
|
||||||
"COPY": "COPY"
|
"COPY": "COPY",
|
||||||
|
"EDIT": "EDITAR"
|
||||||
},
|
},
|
||||||
"TOOLTIP": {
|
"TOOLTIP": {
|
||||||
"EMAIL": "El email debe ser una dirección válida como nombre@ejemplo.com.",
|
"EMAIL": "El email debe ser una dirección válida como nombre@ejemplo.com.",
|
||||||
@ -365,7 +366,10 @@
|
|||||||
"DELETED_TAG_SUCCESS": "Etiqueta eliminada satisfactoriamente.",
|
"DELETED_TAG_SUCCESS": "Etiqueta eliminada satisfactoriamente.",
|
||||||
"COPY": "Copiar",
|
"COPY": "Copiar",
|
||||||
"NOTARY_IS_UNDETERMINED": "Cannot determine the signature of this tag.",
|
"NOTARY_IS_UNDETERMINED": "Cannot determine the signature of this tag.",
|
||||||
"PLACEHOLDER": "We couldn't find any repositories!"
|
"PLACEHOLDER": "We couldn't find any repositories!",
|
||||||
|
"INFO": "Información",
|
||||||
|
"NO_INFO": "Sin información de descripción para este repositorio",
|
||||||
|
"IMAGE": "Imágenes"
|
||||||
},
|
},
|
||||||
"ALERT": {
|
"ALERT": {
|
||||||
"FORM_CHANGE_CONFIRMATION": "Algunos cambios no se han guardado aún. ¿Quiere cancelar?"
|
"FORM_CHANGE_CONFIRMATION": "Algunos cambios no se han guardado aún. ¿Quiere cancelar?"
|
||||||
@ -553,7 +557,8 @@
|
|||||||
"SCAN_COMPLETION_TIME": "Scan Completed",
|
"SCAN_COMPLETION_TIME": "Scan Completed",
|
||||||
"IMAGE_VULNERABILITIES": "Image Vulnerabilities",
|
"IMAGE_VULNERABILITIES": "Image Vulnerabilities",
|
||||||
"PLACEHOLDER": "We couldn't find any tags!",
|
"PLACEHOLDER": "We couldn't find any tags!",
|
||||||
"COPY_ERROR": "Copy failed, please try to manually copy."
|
"COPY_ERROR": "Copy failed, please try to manually copy.",
|
||||||
|
"FILTER_FOR_TAGS": "Etiquetas de filtro"
|
||||||
},
|
},
|
||||||
"UNKNOWN_ERROR": "Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo más tarde.",
|
"UNKNOWN_ERROR": "Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo más tarde.",
|
||||||
"UNAUTHORIZED_ERROR": "La sesión no es válida o ha caducado. Necesita identificarse de nuevo para llevar a cabo esa acción.",
|
"UNAUTHORIZED_ERROR": "La sesión no es válida o ha caducado. Necesita identificarse de nuevo para llevar a cabo esa acción.",
|
||||||
|
@ -32,7 +32,8 @@
|
|||||||
"YES": "是",
|
"YES": "是",
|
||||||
"NO": "否",
|
"NO": "否",
|
||||||
"NEGATIVE": "否",
|
"NEGATIVE": "否",
|
||||||
"COPY": "拷贝"
|
"COPY": "拷贝",
|
||||||
|
"EDIT": "编辑"
|
||||||
},
|
},
|
||||||
"TOOLTIP": {
|
"TOOLTIP": {
|
||||||
"EMAIL": "请使用正确的邮箱地址,比如name@example.com。",
|
"EMAIL": "请使用正确的邮箱地址,比如name@example.com。",
|
||||||
@ -364,7 +365,10 @@
|
|||||||
"DELETED_TAG_SUCCESS": "成功删除镜像标签。",
|
"DELETED_TAG_SUCCESS": "成功删除镜像标签。",
|
||||||
"COPY": "复制",
|
"COPY": "复制",
|
||||||
"NOTARY_IS_UNDETERMINED": "无法确定镜像标签签名。",
|
"NOTARY_IS_UNDETERMINED": "无法确定镜像标签签名。",
|
||||||
"PLACEHOLDER": "未发现任何镜像库!"
|
"PLACEHOLDER": "未发现任何镜像库!",
|
||||||
|
"INFO": "描述信息",
|
||||||
|
"NO_INFO": "此镜像仓库没有描述信息",
|
||||||
|
"IMAGE": "镜像"
|
||||||
},
|
},
|
||||||
"ALERT": {
|
"ALERT": {
|
||||||
"FORM_CHANGE_CONFIRMATION": "表单内容改变,确认是否取消?"
|
"FORM_CHANGE_CONFIRMATION": "表单内容改变,确认是否取消?"
|
||||||
@ -425,7 +429,7 @@
|
|||||||
"TOKEN_EXPIRATION": "由令牌服务创建的令牌的过期时间(分钟),默认为30分钟。",
|
"TOKEN_EXPIRATION": "由令牌服务创建的令牌的过期时间(分钟),默认为30分钟。",
|
||||||
"PRO_CREATION_RESTRICTION": "用来确定哪些用户有权限创建项目,默认为’所有人‘,设置为’仅管理员‘则只有管理员可以创建项目。",
|
"PRO_CREATION_RESTRICTION": "用来确定哪些用户有权限创建项目,默认为’所有人‘,设置为’仅管理员‘则只有管理员可以创建项目。",
|
||||||
"ROOT_CERT_DOWNLOAD": "下载镜像库根证书.",
|
"ROOT_CERT_DOWNLOAD": "下载镜像库根证书.",
|
||||||
"SCANNING_POLICY": "基于不同需求设置镜像扫描策略。‘无’:不设置任何策略;‘每日定时’:每天在设置的时间定时执行扫描。".
|
"SCANNING_POLICY": "基于不同需求设置镜像扫描策略。‘无’:不设置任何策略;‘每日定时’:每天在设置的时间定时执行扫描。",
|
||||||
"VERIFY_CERT": "检查来自LDAP服务端的证书"
|
"VERIFY_CERT": "检查来自LDAP服务端的证书"
|
||||||
},
|
},
|
||||||
"LDAP": {
|
"LDAP": {
|
||||||
@ -521,7 +525,7 @@
|
|||||||
"SEVERITY": {
|
"SEVERITY": {
|
||||||
"HIGH": "严重",
|
"HIGH": "严重",
|
||||||
"MEDIUM": "中等",
|
"MEDIUM": "中等",
|
||||||
"LOW": "一般",
|
"LOW": "较低",
|
||||||
"NEGLIGIBLE": "可忽略",
|
"NEGLIGIBLE": "可忽略",
|
||||||
"UNKNOWN": "未知",
|
"UNKNOWN": "未知",
|
||||||
"NONE": "无"
|
"NONE": "无"
|
||||||
@ -554,7 +558,8 @@
|
|||||||
"SCAN_COMPLETION_TIME": "扫描完成时间",
|
"SCAN_COMPLETION_TIME": "扫描完成时间",
|
||||||
"IMAGE_VULNERABILITIES": "镜像缺陷",
|
"IMAGE_VULNERABILITIES": "镜像缺陷",
|
||||||
"PLACEHOLDER": "未发现任何标签!",
|
"PLACEHOLDER": "未发现任何标签!",
|
||||||
"COPY_ERROR": "拷贝失败,请尝试手动拷贝。"
|
"COPY_ERROR": "拷贝失败,请尝试手动拷贝。",
|
||||||
|
"FILTER_FOR_TAGS": "过滤项目"
|
||||||
},
|
},
|
||||||
"UNKNOWN_ERROR": "发生未知错误,请稍后再试。",
|
"UNKNOWN_ERROR": "发生未知错误,请稍后再试。",
|
||||||
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续。",
|
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续。",
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ type APIClientConfig struct {
|
|||||||
CaFile string
|
CaFile string
|
||||||
CertFile string
|
CertFile string
|
||||||
KeyFile string
|
KeyFile string
|
||||||
|
Proxy string
|
||||||
}
|
}
|
||||||
|
|
||||||
//APIClient provided the http client for trigger http requests
|
//APIClient provided the http client for trigger http requests
|
||||||
@ -60,6 +62,13 @@ func NewAPIClient(config APIClientConfig) (*APIClient, error) {
|
|||||||
TLSClientConfig: tlsConfig,
|
TLSClientConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//If proxy should be set
|
||||||
|
if len(strings.TrimSpace(config.Proxy)) > 0 {
|
||||||
|
if proxyURL, err := url.Parse(config.Proxy); err == nil {
|
||||||
|
transport.Proxy = http.ProxyURL(proxyURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
}
|
}
|
||||||
|
17
tests/apitests/api-testing/envs/concourse_ci_ldap.go
Normal file
17
tests/apitests/api-testing/envs/concourse_ci_ldap.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package envs
|
||||||
|
|
||||||
|
//ConcourseCILdapEnv : Ldap env for concourse pipeline
|
||||||
|
var ConcourseCILdapEnv = Environment{
|
||||||
|
Protocol: "https",
|
||||||
|
TestingProject: "concoursecitesting01",
|
||||||
|
ImageName: "busybox",
|
||||||
|
ImageTag: "latest",
|
||||||
|
CAFile: "../../../ca.crt",
|
||||||
|
KeyFile: "../../../key.crt",
|
||||||
|
CertFile: "../../../cert.crt",
|
||||||
|
Account: "mike",
|
||||||
|
Password: "zhu88jie",
|
||||||
|
Admin: "admin",
|
||||||
|
AdminPass: "pksxgxmifc0cnwa5px9h",
|
||||||
|
Hostname: "30.0.0.3",
|
||||||
|
}
|
@ -22,6 +22,7 @@ type Environment struct {
|
|||||||
CAFile string //env var: CA_FILE_PATH
|
CAFile string //env var: CA_FILE_PATH
|
||||||
CertFile string //env var: CERT_FILE_PATH
|
CertFile string //env var: CERT_FILE_PATH
|
||||||
KeyFile string //env var: KEY_FILE_PATH
|
KeyFile string //env var: KEY_FILE_PATH
|
||||||
|
ProxyURL string //env var: http_proxy, https_proxy, HTTP_PROXY, HTTPS_PROXY
|
||||||
|
|
||||||
//API client
|
//API client
|
||||||
HTTPClient *client.APIClient
|
HTTPClient *client.APIClient
|
||||||
@ -95,6 +96,18 @@ func (env *Environment) Load() error {
|
|||||||
env.CertFile = certFile
|
env.CertFile = certFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxyEnvVar := "https_proxy"
|
||||||
|
if env.Protocol == "http" {
|
||||||
|
proxyEnvVar = "http_proxy"
|
||||||
|
}
|
||||||
|
proxyURL := os.Getenv(proxyEnvVar)
|
||||||
|
if !isNotEmpty(proxyURL) {
|
||||||
|
proxyURL = os.Getenv(strings.ToUpper(proxyEnvVar))
|
||||||
|
}
|
||||||
|
if isNotEmpty(proxyURL) {
|
||||||
|
env.ProxyURL = proxyURL
|
||||||
|
}
|
||||||
|
|
||||||
if !env.loaded {
|
if !env.loaded {
|
||||||
cfg := client.APIClientConfig{
|
cfg := client.APIClientConfig{
|
||||||
Username: env.Admin,
|
Username: env.Admin,
|
||||||
@ -102,6 +115,7 @@ func (env *Environment) Load() error {
|
|||||||
CaFile: env.CAFile,
|
CaFile: env.CAFile,
|
||||||
CertFile: env.CertFile,
|
CertFile: env.CertFile,
|
||||||
KeyFile: env.KeyFile,
|
KeyFile: env.KeyFile,
|
||||||
|
Proxy: env.ProxyURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient, err := client.NewAPIClient(cfg)
|
httpClient, err := client.NewAPIClient(cfg)
|
||||||
|
74
tests/apitests/api-testing/tests/suites/base/suite.go
Normal file
74
tests/apitests/api-testing/tests/suites/base/suite.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/tests/apitests/api-testing/envs"
|
||||||
|
"github.com/vmware/harbor/tests/apitests/api-testing/lib"
|
||||||
|
)
|
||||||
|
|
||||||
|
//ConcourseCiSuite : Provides some base cases
|
||||||
|
type ConcourseCiSuite struct{}
|
||||||
|
|
||||||
|
//Run cases
|
||||||
|
//Not implemented
|
||||||
|
func (ccs *ConcourseCiSuite) Run(onEnvironment *envs.Environment) *lib.Report {
|
||||||
|
return &lib.Report{}
|
||||||
|
}
|
||||||
|
|
||||||
|
//PushImage : Push image to the registry
|
||||||
|
func (ccs *ConcourseCiSuite) PushImage(onEnvironment *envs.Environment) error {
|
||||||
|
docker := onEnvironment.DockerClient
|
||||||
|
if err := docker.Status(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imagePulling := fmt.Sprintf("%s:%s", onEnvironment.ImageName, onEnvironment.ImageTag)
|
||||||
|
if err := docker.Pull(imagePulling); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := docker.Login(onEnvironment.Account, onEnvironment.Password, onEnvironment.Hostname); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imagePushing := fmt.Sprintf("%s/%s/%s:%s",
|
||||||
|
onEnvironment.Hostname,
|
||||||
|
onEnvironment.TestingProject,
|
||||||
|
onEnvironment.ImageName,
|
||||||
|
onEnvironment.ImageTag)
|
||||||
|
|
||||||
|
if err := docker.Tag(imagePulling, imagePushing); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := docker.Push(imagePushing); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//PullImage : Pull image from registry
|
||||||
|
func (ccs *ConcourseCiSuite) PullImage(onEnvironment *envs.Environment) error {
|
||||||
|
docker := onEnvironment.DockerClient
|
||||||
|
if err := docker.Status(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := docker.Login(onEnvironment.Account, onEnvironment.Password, onEnvironment.Hostname); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imagePulling := fmt.Sprintf("%s/%s/%s:%s",
|
||||||
|
onEnvironment.Hostname,
|
||||||
|
onEnvironment.TestingProject,
|
||||||
|
onEnvironment.ImageName,
|
||||||
|
onEnvironment.ImageTag)
|
||||||
|
|
||||||
|
if err := docker.Pull(imagePulling); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/vmware/harbor/tests/apitests/api-testing/envs"
|
"github.com/vmware/harbor/tests/apitests/api-testing/envs"
|
||||||
"github.com/vmware/harbor/tests/apitests/api-testing/lib"
|
"github.com/vmware/harbor/tests/apitests/api-testing/lib"
|
||||||
|
"github.com/vmware/harbor/tests/apitests/api-testing/tests/suites/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Steps of suite01:
|
//Steps of suite01:
|
||||||
@ -22,7 +23,9 @@ import (
|
|||||||
// s11: delete user
|
// s11: delete user
|
||||||
|
|
||||||
//ConcourseCiSuite01 : For harbor journey in concourse pipeline
|
//ConcourseCiSuite01 : For harbor journey in concourse pipeline
|
||||||
type ConcourseCiSuite01 struct{}
|
type ConcourseCiSuite01 struct {
|
||||||
|
base.ConcourseCiSuite
|
||||||
|
}
|
||||||
|
|
||||||
//Run : Run a group of cases
|
//Run : Run a group of cases
|
||||||
func (ccs *ConcourseCiSuite01) Run(onEnvironment *envs.Environment) *lib.Report {
|
func (ccs *ConcourseCiSuite01) Run(onEnvironment *envs.Environment) *lib.Report {
|
||||||
@ -60,7 +63,7 @@ func (ccs *ConcourseCiSuite01) Run(onEnvironment *envs.Environment) *lib.Report
|
|||||||
}
|
}
|
||||||
|
|
||||||
//s4
|
//s4
|
||||||
if err := ccs.pushImage(onEnvironment); err != nil {
|
if err := ccs.PushImage(onEnvironment); err != nil {
|
||||||
report.Failed("pushImage", err)
|
report.Failed("pushImage", err)
|
||||||
} else {
|
} else {
|
||||||
report.Passed("pushImage")
|
report.Passed("pushImage")
|
||||||
@ -76,7 +79,7 @@ func (ccs *ConcourseCiSuite01) Run(onEnvironment *envs.Environment) *lib.Report
|
|||||||
}
|
}
|
||||||
|
|
||||||
//s6
|
//s6
|
||||||
if err := ccs.pullImage(onEnvironment); err != nil {
|
if err := ccs.PullImage(onEnvironment); err != nil {
|
||||||
report.Failed("pullImage[1]", err)
|
report.Failed("pullImage[1]", err)
|
||||||
} else {
|
} else {
|
||||||
report.Passed("pullImage[1]")
|
report.Passed("pullImage[1]")
|
||||||
@ -91,7 +94,7 @@ func (ccs *ConcourseCiSuite01) Run(onEnvironment *envs.Environment) *lib.Report
|
|||||||
}
|
}
|
||||||
|
|
||||||
//s8
|
//s8
|
||||||
if err := ccs.pullImage(onEnvironment); err == nil {
|
if err := ccs.PullImage(onEnvironment); err == nil {
|
||||||
report.Failed("pullImage[2]", err)
|
report.Failed("pullImage[2]", err)
|
||||||
} else {
|
} else {
|
||||||
report.Passed("pullImage[2]")
|
report.Passed("pullImage[2]")
|
||||||
@ -120,58 +123,3 @@ func (ccs *ConcourseCiSuite01) Run(onEnvironment *envs.Environment) *lib.Report
|
|||||||
|
|
||||||
return report
|
return report
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ccs *ConcourseCiSuite01) pushImage(onEnvironment *envs.Environment) error {
|
|
||||||
docker := onEnvironment.DockerClient
|
|
||||||
if err := docker.Status(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
imagePulling := fmt.Sprintf("%s:%s", onEnvironment.ImageName, onEnvironment.ImageTag)
|
|
||||||
if err := docker.Pull(imagePulling); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := docker.Login(onEnvironment.Account, onEnvironment.Password, onEnvironment.Hostname); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
imagePushing := fmt.Sprintf("%s/%s/%s:%s",
|
|
||||||
onEnvironment.Hostname,
|
|
||||||
onEnvironment.TestingProject,
|
|
||||||
onEnvironment.ImageName,
|
|
||||||
onEnvironment.ImageTag)
|
|
||||||
|
|
||||||
if err := docker.Tag(imagePulling, imagePushing); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := docker.Push(imagePushing); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ccs *ConcourseCiSuite01) pullImage(onEnvironment *envs.Environment) error {
|
|
||||||
docker := onEnvironment.DockerClient
|
|
||||||
if err := docker.Status(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := docker.Login(onEnvironment.Account, onEnvironment.Password, onEnvironment.Hostname); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
imagePulling := fmt.Sprintf("%s/%s/%s:%s",
|
|
||||||
onEnvironment.Hostname,
|
|
||||||
onEnvironment.TestingProject,
|
|
||||||
onEnvironment.ImageName,
|
|
||||||
onEnvironment.ImageTag)
|
|
||||||
|
|
||||||
if err := docker.Pull(imagePulling); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
22
tests/apitests/api-testing/tests/suites/suite02/run_test.go
Normal file
22
tests/apitests/api-testing/tests/suites/suite02/run_test.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package suite02
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/tests/apitests/api-testing/envs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//TestRun : Start to run the case
|
||||||
|
func TestRun(t *testing.T) {
|
||||||
|
//Initialize env
|
||||||
|
if err := envs.ConcourseCILdapEnv.Load(); err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
suite := ConcourseCiSuite02{}
|
||||||
|
report := suite.Run(&envs.ConcourseCILdapEnv)
|
||||||
|
report.Print()
|
||||||
|
if report.IsFail() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user