mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-29 13:57:33 +02:00
Merge pull request #1449 from ywk253100/170224_merge_config
Merge configuration to dev
This commit is contained in:
commit
01e105090e
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,8 +1,11 @@
|
|||||||
harbor
|
harbor
|
||||||
make/common/config/*
|
make/common/config/*
|
||||||
|
make/dev/adminserver/harbor_adminserver
|
||||||
make/dev/ui/harbor_ui
|
make/dev/ui/harbor_ui
|
||||||
make/dev/jobservice/harbor_jobservice
|
make/dev/jobservice/harbor_jobservice
|
||||||
|
src/adminserver/adminserver
|
||||||
src/ui/ui
|
src/ui/ui
|
||||||
src/jobservice/jobservice
|
src/jobservice/jobservice
|
||||||
|
src/common/dao/dao.test
|
||||||
*.pyc
|
*.pyc
|
||||||
jobservice/test
|
jobservice/test
|
||||||
|
17
.travis.yml
17
.travis.yml
@ -13,22 +13,21 @@ services:
|
|||||||
dist: trusty
|
dist: trusty
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DB_HOST: 127.0.0.1
|
|
||||||
DB_PORT: 3306
|
|
||||||
DB_USR: root
|
|
||||||
DB_PWD: root123
|
|
||||||
MYSQL_HOST: localhost
|
MYSQL_HOST: localhost
|
||||||
MYSQL_PORT: 3306
|
MYSQL_PORT: 3306
|
||||||
MYSQL_USR: root
|
MYSQL_USR: root
|
||||||
MYSQL_PWD: root123
|
MYSQL_PWD: root123
|
||||||
|
MYSQL_DATABASE: registry
|
||||||
|
SQLITE_FILE: /tmp/registry.db
|
||||||
|
ADMIN_SERVER_URL: http://127.0.0.1:8888
|
||||||
DOCKER_COMPOSE_VERSION: 1.7.1
|
DOCKER_COMPOSE_VERSION: 1.7.1
|
||||||
HARBOR_ADMIN: admin
|
HARBOR_ADMIN: admin
|
||||||
HARBOR_ADMIN_PASSWD: Harbor12345
|
HARBOR_ADMIN_PASSWD: Harbor12345
|
||||||
UI_SECRET: tempString
|
UI_SECRET: tempString
|
||||||
MAX_JOB_WORKERS: 3
|
MAX_JOB_WORKERS: 3
|
||||||
SECRET_KEY: 1234567890123456
|
|
||||||
AUTH_MODE: db_auth
|
AUTH_MODE: db_auth
|
||||||
SELF_REGISTRATION: "on"
|
SELF_REGISTRATION: on
|
||||||
|
KEY_PATH: /data/secretkey
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- sudo ./tests/hostcfg.sh
|
- sudo ./tests/hostcfg.sh
|
||||||
@ -70,7 +69,8 @@ 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 /registry.db < make/common/db/registry_sqlite.sql
|
- sudo sqlite3 /tmp/registry.db < make/common/db/registry_sqlite.sql
|
||||||
|
- sudo chmod 777 /tmp/registry.db
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- sudo mkdir -p /harbor_storage/ca_download
|
- sudo mkdir -p /harbor_storage/ca_download
|
||||||
@ -88,7 +88,8 @@ script:
|
|||||||
- goveralls -coverprofile=profile.cov -service=travis-ci
|
- goveralls -coverprofile=profile.cov -service=travis-ci
|
||||||
|
|
||||||
- docker-compose -f make/docker-compose.test.yml down
|
- docker-compose -f make/docker-compose.test.yml down
|
||||||
|
- sudo make/prepare
|
||||||
|
- sudo rm -rf /data/config/*
|
||||||
- docker-compose -f make/dev/docker-compose.yml up -d
|
- docker-compose -f make/dev/docker-compose.yml up -d
|
||||||
|
|
||||||
- docker ps
|
- docker ps
|
||||||
|
42
Makefile
42
Makefile
@ -4,18 +4,17 @@
|
|||||||
#
|
#
|
||||||
# all: prepare env, compile binarys, build images and install images
|
# all: prepare env, compile binarys, build images and install images
|
||||||
# prepare: prepare env
|
# prepare: prepare env
|
||||||
# compile: compile ui and jobservice code
|
# compile: compile adminserver, ui and jobservice code
|
||||||
#
|
#
|
||||||
# compile_golangimage:
|
# compile_golangimage:
|
||||||
# compile from golang image
|
# compile from golang image
|
||||||
# for example: make compile_golangimage -e GOBUILDIMAGE= \
|
# for example: make compile_golangimage -e GOBUILDIMAGE= \
|
||||||
# golang:1.7.3
|
# golang:1.7.3
|
||||||
# compile_ui, compile_jobservice: compile specific binary
|
# compile_adminserver, compile_ui, compile_jobservice: compile specific binary
|
||||||
#
|
#
|
||||||
# build: build Harbor docker images (defuault: build_photon)
|
# build: build Harbor docker images (defuault: build_photon)
|
||||||
# for example: make build -e BASEIMAGE=photon
|
# for example: make build -e BASEIMAGE=photon
|
||||||
# build_photon: build Harbor docker images from photon bsaeimage
|
# build_photon: build Harbor docker images from photon baseimage
|
||||||
# build_ubuntu: build Harbor docker images from ubuntu baseimage
|
|
||||||
#
|
#
|
||||||
# install: include compile binarys, build images, prepare specific \
|
# install: include compile binarys, build images, prepare specific \
|
||||||
# version composefile and startup Harbor instance
|
# version composefile and startup Harbor instance
|
||||||
@ -46,7 +45,7 @@
|
|||||||
#
|
#
|
||||||
# clean: remove binary, Harbor images, specific version docker-compose \
|
# clean: remove binary, Harbor images, specific version docker-compose \
|
||||||
# file, specific version tag and online/offline install package
|
# file, specific version tag and online/offline install package
|
||||||
# cleanbinary: remove ui and jobservice binary
|
# cleanbinary: remove adminserver, ui and jobservice binary
|
||||||
# cleanimage: remove Harbor images
|
# cleanimage: remove Harbor images
|
||||||
# cleandockercomposefile:
|
# cleandockercomposefile:
|
||||||
# remove specific version docker-compose
|
# remove specific version docker-compose
|
||||||
@ -100,14 +99,19 @@ GOBUILDIMAGE=reg.mydomain.com/library/harborgo[:tag]
|
|||||||
GOBUILDPATH=$(GOBASEPATH)/harbor
|
GOBUILDPATH=$(GOBASEPATH)/harbor
|
||||||
GOIMAGEBUILDCMD=/usr/local/go/bin/go
|
GOIMAGEBUILDCMD=/usr/local/go/bin/go
|
||||||
GOIMAGEBUILD=$(GOIMAGEBUILDCMD) build
|
GOIMAGEBUILD=$(GOIMAGEBUILDCMD) build
|
||||||
|
GOBUILDPATH_ADMINSERVER=$(GOBUILDPATH)/src/adminserver
|
||||||
GOBUILDPATH_UI=$(GOBUILDPATH)/src/ui
|
GOBUILDPATH_UI=$(GOBUILDPATH)/src/ui
|
||||||
GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/src/jobservice
|
GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/src/jobservice
|
||||||
GOBUILDMAKEPATH=$(GOBUILDPATH)/make
|
GOBUILDMAKEPATH=$(GOBUILDPATH)/make
|
||||||
|
GOBUILDMAKEPATH_ADMINSERVER=$(GOBUILDMAKEPATH)/dev/adminserver
|
||||||
GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/dev/ui
|
GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/dev/ui
|
||||||
GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice
|
GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice
|
||||||
GOLANGDOCKERFILENAME=Dockerfile.golang
|
GOLANGDOCKERFILENAME=Dockerfile.golang
|
||||||
|
|
||||||
# binary
|
# binary
|
||||||
|
ADMINSERVERSOURCECODE=$(SRCPATH)/adminserver
|
||||||
|
ADMINSERVERBINARYPATH=$(MAKEDEVPATH)/adminserver
|
||||||
|
ADMINSERVERBINARYNAME=harbor_adminserver
|
||||||
UISOURCECODE=$(SRCPATH)/ui
|
UISOURCECODE=$(SRCPATH)/ui
|
||||||
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
||||||
UIBINARYNAME=harbor_ui
|
UIBINARYNAME=harbor_ui
|
||||||
@ -125,7 +129,6 @@ CONFIGFILE=harbor.cfg
|
|||||||
|
|
||||||
# makefile
|
# makefile
|
||||||
MAKEFILEPATH_PHOTON=$(MAKEPATH)/photon
|
MAKEFILEPATH_PHOTON=$(MAKEPATH)/photon
|
||||||
MAKEFILEPATH_UBUNTU=$(MAKEPATH)/ubuntu
|
|
||||||
|
|
||||||
# common dockerfile
|
# common dockerfile
|
||||||
DOCKERFILEPATH_COMMON=$(MAKEPATH)/common
|
DOCKERFILEPATH_COMMON=$(MAKEPATH)/common
|
||||||
@ -133,6 +136,7 @@ DOCKERFILEPATH_DB=$(DOCKERFILEPATH_COMMON)/db
|
|||||||
DOCKERFILENAME_DB=Dockerfile
|
DOCKERFILENAME_DB=Dockerfile
|
||||||
|
|
||||||
# docker image name
|
# docker image name
|
||||||
|
DOCKERIMAGENAME_ADMINSERVER=vmware/harbor-adminserver
|
||||||
DOCKERIMAGENAME_UI=vmware/harbor-ui
|
DOCKERIMAGENAME_UI=vmware/harbor-ui
|
||||||
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
||||||
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
||||||
@ -177,6 +181,11 @@ 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:
|
compile_ui:
|
||||||
@echo "compiling binary for ui..."
|
@echo "compiling binary for ui..."
|
||||||
@$(GOBUILD) -o $(UIBINARYPATH)/$(UIBINARYNAME) $(UISOURCECODE)
|
@$(GOBUILD) -o $(UIBINARYPATH)/$(UIBINARYNAME) $(UISOURCECODE)
|
||||||
@ -187,9 +196,15 @@ compile_jobservice:
|
|||||||
@$(GOBUILD) -o $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) $(JOBSERVICESOURCECODE)
|
@$(GOBUILD) -o $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) $(JOBSERVICESOURCECODE)
|
||||||
@echo "Done."
|
@echo "Done."
|
||||||
|
|
||||||
compile_normal: compile_ui compile_jobservice
|
compile_normal: compile_adminserver compile_ui compile_jobservice
|
||||||
|
|
||||||
compile_golangimage:
|
compile_golangimage:
|
||||||
|
@echo "compiling binary for adminserver (golang image)..."
|
||||||
|
@echo $(GOBASEPATH)
|
||||||
|
@echo $(GOBUILDPATH)
|
||||||
|
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_ADMINSERVER) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_ADMINSERVER)/$(ADMINSERVERBINARYNAME)
|
||||||
|
@echo "Done."
|
||||||
|
|
||||||
@echo "compiling binary for ui (golang image)..."
|
@echo "compiling binary for ui (golang image)..."
|
||||||
@echo $(GOBASEPATH)
|
@echo $(GOBASEPATH)
|
||||||
@echo $(GOBUILDPATH)
|
@echo $(GOBUILDPATH)
|
||||||
@ -214,9 +229,6 @@ build_common: version
|
|||||||
build_photon: build_common
|
build_photon: build_common
|
||||||
make -f $(MAKEFILEPATH_PHOTON)/Makefile build -e DEVFLAG=$(DEVFLAG)
|
make -f $(MAKEFILEPATH_PHOTON)/Makefile build -e DEVFLAG=$(DEVFLAG)
|
||||||
|
|
||||||
build_ubuntu: build_common
|
|
||||||
make -f $(MAKEFILEPATH_UBUNTU)/Makefile build -e DEVFLAG=$(DEVFLAG)
|
|
||||||
|
|
||||||
build: build_$(BASEIMAGE)
|
build: build_$(BASEIMAGE)
|
||||||
|
|
||||||
modify_composefile:
|
modify_composefile:
|
||||||
@ -240,7 +252,6 @@ package_online: modify_composefile
|
|||||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||||
@$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
@$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
||||||
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/common/config\
|
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/common/config\
|
||||||
--exclude=$(HARBORPKG)/common/log --exclude=$(HARBORPKG)/ubuntu \
|
|
||||||
--exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \
|
--exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \
|
||||||
--exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \
|
--exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \
|
||||||
--exclude=$(HARBORPKG)/checkenv.sh \
|
--exclude=$(HARBORPKG)/checkenv.sh \
|
||||||
@ -264,6 +275,7 @@ package_offline: compile build modify_composefile
|
|||||||
|
|
||||||
@echo "saving harbor docker image"
|
@echo "saving harbor docker image"
|
||||||
@$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
@$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||||
|
$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||||
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||||
@ -272,7 +284,6 @@ package_offline: compile build modify_composefile
|
|||||||
|
|
||||||
@$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
@$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
||||||
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/common/config\
|
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/common/config\
|
||||||
--exclude=$(HARBORPKG)/common/log --exclude=$(HARBORPKG)/ubuntu \
|
|
||||||
--exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \
|
--exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \
|
||||||
--exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \
|
--exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \
|
||||||
--exclude=$(HARBORPKG)/checkenv.sh \
|
--exclude=$(HARBORPKG)/checkenv.sh \
|
||||||
@ -285,6 +296,11 @@ package_offline: compile build modify_composefile
|
|||||||
|
|
||||||
pushimage:
|
pushimage:
|
||||||
@echo "pushing harbor images ..."
|
@echo "pushing harbor images ..."
|
||||||
|
@$(DOCKERTAG) $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||||
|
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||||
|
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||||
|
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||||
|
|
||||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_UI):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
@$(DOCKERTAG) $(DOCKERIMAGENAME_UI):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||||
@ -317,11 +333,13 @@ down:
|
|||||||
|
|
||||||
cleanbinary:
|
cleanbinary:
|
||||||
@echo "cleaning binary..."
|
@echo "cleaning binary..."
|
||||||
|
@if [ -f $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ] ; then rm $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ; fi
|
||||||
@if [ -f $(UIBINARYPATH)/$(UIBINARYNAME) ] ; then rm $(UIBINARYPATH)/$(UIBINARYNAME) ; fi
|
@if [ -f $(UIBINARYPATH)/$(UIBINARYNAME) ] ; then rm $(UIBINARYPATH)/$(UIBINARYNAME) ; fi
|
||||||
@if [ -f $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ] ; then rm $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ; fi
|
@if [ -f $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ] ; then rm $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ; fi
|
||||||
|
|
||||||
cleanimage:
|
cleanimage:
|
||||||
@echo "cleaning image for photon..."
|
@echo "cleaning image for photon..."
|
||||||
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||||
|
@ -127,7 +127,6 @@ compile_ui | compile ui binary
|
|||||||
compile_jobservice | compile jobservice binary
|
compile_jobservice | compile jobservice binary
|
||||||
build | build Harbor docker images (default: using build_photon)
|
build | build Harbor docker images (default: using build_photon)
|
||||||
build_photon | build Harbor docker images from Photon OS base image
|
build_photon | build Harbor docker images from Photon OS base image
|
||||||
build_ubuntu | build Harbor docker images from Ubuntu base image
|
|
||||||
install | compile binaries, build images, prepare specific version of compose file and startup Harbor instance
|
install | compile binaries, build images, prepare specific version of compose file and startup Harbor instance
|
||||||
start | startup Harbor instance
|
start | startup Harbor instance
|
||||||
down | shutdown Harbor instance
|
down | shutdown Harbor instance
|
||||||
@ -143,13 +142,6 @@ cleanpackage | remove online/offline install package
|
|||||||
|
|
||||||
#### EXAMPLE:
|
#### EXAMPLE:
|
||||||
|
|
||||||
#### Build Harbor images based on Ubuntu
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ make build -e BASEIMAGE=ubuntu
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Push Harbor images to specific registry server
|
#### Push Harbor images to specific registry server
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
@ -1347,7 +1347,70 @@ paths:
|
|||||||
404:
|
404:
|
||||||
description: Not found the default root certificate.
|
description: Not found the default root certificate.
|
||||||
500:
|
500:
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
|
/ldap/ping:
|
||||||
|
post:
|
||||||
|
summary: Ping available ldap service.
|
||||||
|
description: |
|
||||||
|
This endpoint ping the available ldap service for test related configuration parameters.
|
||||||
|
parameters:
|
||||||
|
- name: ldapconf
|
||||||
|
in: body
|
||||||
|
description: ldap configuration.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/LdapConf'
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Ping ldap service successfully.
|
||||||
|
401:
|
||||||
|
description: Only admin has this authority.
|
||||||
|
403:
|
||||||
|
description: Inviald ldap configuration parameters.
|
||||||
|
500:
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
/configurations:
|
||||||
|
get:
|
||||||
|
summary: Get system configurations.
|
||||||
|
description: |
|
||||||
|
This endpoint is for retrieving system configurations that only provides for admin user.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Get system configurations successfully. The response body is a map.
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
401:
|
||||||
|
description: User need to log in first.
|
||||||
|
403:
|
||||||
|
description: User does not have permission of admin role.
|
||||||
|
500:
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
put:
|
||||||
|
summary: Modify system configurations.
|
||||||
|
description: |
|
||||||
|
This endpoint is for modifying system configurations that only provides for admin user.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
parameters:
|
||||||
|
- name: configurations
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
description: The configurations map need to be modified, the following are keys "auth_mode", "email_from", "email_host", "email_identity", "email_password", "email_port", "email_ssl", "email_username", "ldap_base_dn", "ldap_filter", "ldap_scope", "ldap_search_dn", "ldap_search_password", "ldap_timeout", "ldap_uid", "ldap_url", "project_creation_restriction", "self_registration", "verify_remote_cert".
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Modify system configurations successfully.
|
||||||
|
401:
|
||||||
|
description: User need to log in first.
|
||||||
|
403:
|
||||||
|
description: User does not have permission of admin role.
|
||||||
|
500:
|
||||||
|
description: Unexpected internal errors.
|
||||||
definitions:
|
definitions:
|
||||||
Search:
|
Search:
|
||||||
type: object
|
type: object
|
||||||
@ -1798,3 +1861,32 @@ definitions:
|
|||||||
description: The storage of system.
|
description: The storage of system.
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/Storage'
|
$ref: '#/definitions/Storage'
|
||||||
|
LdapConf:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ldap_url:
|
||||||
|
type: string
|
||||||
|
description: The url of ldap service.
|
||||||
|
ldap_search_dn:
|
||||||
|
type: string
|
||||||
|
description: The search dn of ldap service.
|
||||||
|
ldap_search_password:
|
||||||
|
type: string
|
||||||
|
description: The search password of ldap service.
|
||||||
|
ldap_base_dn:
|
||||||
|
type: string
|
||||||
|
description: The base dn of ldap service.
|
||||||
|
ldap_filter:
|
||||||
|
type: string
|
||||||
|
description: The serach filter of ldap service.
|
||||||
|
ldap_uid:
|
||||||
|
type: string
|
||||||
|
description: The serach uid of ldap service.
|
||||||
|
ldap_scope:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The serach scope of ldap service.
|
||||||
|
ldap_connection_timeout:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The connect timeout of ldap service(second).
|
||||||
|
38
make/common/templates/adminserver/env
Normal file
38
make/common/templates/adminserver/env
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
LOG_LEVEL=debug
|
||||||
|
EXT_ENDPOINT=$ui_url
|
||||||
|
AUTH_MODE=$auth_mode
|
||||||
|
SELF_REGISTRATION=$self_registration
|
||||||
|
LDAP_URL=$ldap_url
|
||||||
|
LDAP_SEARCH_DN=$ldap_searchdn
|
||||||
|
LDAP_SEARCH_PWD=$ldap_search_pwd
|
||||||
|
LDAP_BASE_DN=$ldap_basedn
|
||||||
|
LDAP_FILTER=$ldap_filter
|
||||||
|
LDAP_UID=$ldap_uid
|
||||||
|
LDAP_SCOPE=$ldap_scope
|
||||||
|
LDAP_TIMEOUT=$ldap_timeout
|
||||||
|
DATABASE_TYPE=mysql
|
||||||
|
MYSQL_HOST=mysql
|
||||||
|
MYSQL_PORT=3306
|
||||||
|
MYSQL_USR=root
|
||||||
|
MYSQL_PWD=$db_password
|
||||||
|
MYSQL_DATABASE=registry
|
||||||
|
REGISTRY_URL=http://registry:5000
|
||||||
|
TOKEN_SERVICE_URL=http://ui/service/token
|
||||||
|
EMAIL_HOST=$email_host
|
||||||
|
EMAIL_PORT=$email_port
|
||||||
|
EMAIL_USR=$email_usr
|
||||||
|
EMAIL_PWD=$email_pwd
|
||||||
|
EMAIL_SSL=$email_ssl
|
||||||
|
EMAIL_FROM=$email_from
|
||||||
|
EMAIL_IDENTITY=$email_identity
|
||||||
|
HARBOR_ADMIN_PASSWORD=$harbor_admin_password
|
||||||
|
PROJECT_CREATION_RESTRICTION=$project_creation_restriction
|
||||||
|
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||||
|
MAX_JOB_WORKERS=$max_job_workers
|
||||||
|
LOG_DIR=/var/log/jobs
|
||||||
|
UI_SECRET=$ui_secret
|
||||||
|
JOBSERVICE_SECRET=$jobservice_secret
|
||||||
|
TOKEN_EXPIRATION=$token_expiration
|
||||||
|
CFG_EXPIRATION=5
|
||||||
|
USE_COMPRESSED_JS=$use_compressed_js
|
||||||
|
GODEBUG=netdns=cgo
|
@ -1,15 +1,5 @@
|
|||||||
MYSQL_HOST=mysql
|
|
||||||
MYSQL_PORT=3306
|
|
||||||
MYSQL_USR=root
|
|
||||||
MYSQL_PWD=$db_password
|
|
||||||
UI_SECRET=$ui_secret
|
|
||||||
SECRET_KEY=$secret_key
|
|
||||||
CONFIG_PATH=/etc/jobservice/app.conf
|
|
||||||
REGISTRY_URL=http://registry:5000
|
|
||||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
|
||||||
MAX_JOB_WORKERS=$max_job_workers
|
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
LOG_DIR=/var/log/jobs
|
CONFIG_PATH=/etc/jobservice/app.conf
|
||||||
|
UI_SECRET=$ui_secret
|
||||||
|
JOBSERVICE_SECRET=$jobservice_secret
|
||||||
GODEBUG=netdns=cgo
|
GODEBUG=netdns=cgo
|
||||||
EXT_ENDPOINT=$ui_url
|
|
||||||
TOKEN_ENDPOINT=http://ui
|
|
||||||
|
@ -6,13 +6,4 @@ types = en-US|zh-CN
|
|||||||
names = en-US|zh-CN
|
names = en-US|zh-CN
|
||||||
|
|
||||||
[dev]
|
[dev]
|
||||||
httpport = 80
|
httpport = 80
|
||||||
|
|
||||||
[mail]
|
|
||||||
identity = $email_identity
|
|
||||||
host = $email_server
|
|
||||||
port = $email_server_port
|
|
||||||
username = $email_username
|
|
||||||
password = $email_password
|
|
||||||
from = $email_from
|
|
||||||
ssl = $email_ssl
|
|
@ -1,30 +1,5 @@
|
|||||||
MYSQL_HOST=mysql
|
|
||||||
MYSQL_PORT=3306
|
|
||||||
MYSQL_USR=root
|
|
||||||
MYSQL_PWD=$db_password
|
|
||||||
REGISTRY_URL=http://registry:5000
|
|
||||||
JOB_SERVICE_URL=http://jobservice
|
|
||||||
UI_URL=http://ui
|
|
||||||
CONFIG_PATH=/etc/ui/app.conf
|
|
||||||
EXT_REG_URL=$hostname
|
|
||||||
HARBOR_ADMIN_PASSWORD=$harbor_admin_password
|
|
||||||
AUTH_MODE=$auth_mode
|
|
||||||
LDAP_URL=$ldap_url
|
|
||||||
LDAP_SEARCH_DN=$ldap_searchdn
|
|
||||||
LDAP_SEARCH_PWD=$ldap_search_pwd
|
|
||||||
LDAP_BASE_DN=$ldap_basedn
|
|
||||||
LDAP_FILTER=$ldap_filter
|
|
||||||
LDAP_UID=$ldap_uid
|
|
||||||
LDAP_SCOPE=$ldap_scope
|
|
||||||
LDAP_CONNECT_TIMEOUT=$ldap_connect_timeout
|
|
||||||
UI_SECRET=$ui_secret
|
|
||||||
SECRET_KEY=$secret_key
|
|
||||||
SELF_REGISTRATION=$self_registration
|
|
||||||
USE_COMPRESSED_JS=$use_compressed_js
|
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
CONFIG_PATH=/etc/ui/app.conf
|
||||||
|
UI_SECRET=$ui_secret
|
||||||
|
JOBSERVICE_SECRET=$jobservice_secret
|
||||||
GODEBUG=netdns=cgo
|
GODEBUG=netdns=cgo
|
||||||
EXT_ENDPOINT=$ui_url
|
|
||||||
TOKEN_ENDPOINT=http://ui
|
|
||||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
|
||||||
TOKEN_EXPIRATION=$token_expiration
|
|
||||||
PROJECT_CREATION_RESTRICTION=$project_creation_restriction
|
|
||||||
|
12
make/dev/adminserver/Dockerfile
Normal file
12
make/dev/adminserver/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM golang:1.7.3
|
||||||
|
|
||||||
|
MAINTAINER yinw@vmware.com
|
||||||
|
|
||||||
|
COPY . /go/src/github.com/vmware/harbor
|
||||||
|
|
||||||
|
WORKDIR /go/src/github.com/vmware/harbor/src/adminserver
|
||||||
|
|
||||||
|
RUN go build -v -a -o /go/bin/harbor_adminserver \
|
||||||
|
&& chmod u+x /go/bin/harbor_adminserver
|
||||||
|
WORKDIR /go/bin/
|
||||||
|
ENTRYPOINT ["/go/bin/harbor_adminserver"]
|
@ -3,7 +3,7 @@ services:
|
|||||||
log:
|
log:
|
||||||
build:
|
build:
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: make/ubuntu/log/Dockerfile
|
dockerfile: make/photon/log/Dockerfile
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- /var/log/harbor/:/var/log/docker/
|
- /var/log/harbor/:/var/log/docker/
|
||||||
@ -40,6 +40,23 @@ services:
|
|||||||
options:
|
options:
|
||||||
syslog-address: "tcp://127.0.0.1:1514"
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
tag: "mysql"
|
tag: "mysql"
|
||||||
|
adminserver:
|
||||||
|
build:
|
||||||
|
context: ../../
|
||||||
|
dockerfile: make/dev/adminserver/Dockerfile
|
||||||
|
env_file:
|
||||||
|
- ../common/config/adminserver/env
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /data/config/:/etc/adminserver/
|
||||||
|
- /data/secretkey:/etc/adminserver/key
|
||||||
|
depends_on:
|
||||||
|
- log
|
||||||
|
logging:
|
||||||
|
driver: "syslog"
|
||||||
|
options:
|
||||||
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
|
tag: "adminserver"
|
||||||
ui:
|
ui:
|
||||||
build:
|
build:
|
||||||
context: ../../
|
context: ../../
|
||||||
@ -50,8 +67,11 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ../common/config/ui/app.conf:/etc/ui/app.conf
|
- ../common/config/ui/app.conf:/etc/ui/app.conf
|
||||||
- ../common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
- ../common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
||||||
|
- /data/secretkey:/etc/ui/key
|
||||||
depends_on:
|
depends_on:
|
||||||
- log
|
- log
|
||||||
|
- adminserver
|
||||||
|
- registry
|
||||||
logging:
|
logging:
|
||||||
driver: "syslog"
|
driver: "syslog"
|
||||||
options:
|
options:
|
||||||
@ -67,8 +87,10 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /data/job_logs:/var/log/jobs
|
- /data/job_logs:/var/log/jobs
|
||||||
- ../common/config/jobservice/app.conf:/etc/jobservice/app.conf
|
- ../common/config/jobservice/app.conf:/etc/jobservice/app.conf
|
||||||
|
- /data/secretkey:/etc/jobservice/key
|
||||||
depends_on:
|
depends_on:
|
||||||
- ui
|
- ui
|
||||||
|
- adminserver
|
||||||
logging:
|
logging:
|
||||||
driver: "syslog"
|
driver: "syslog"
|
||||||
options:
|
options:
|
||||||
|
@ -47,6 +47,24 @@ services:
|
|||||||
options:
|
options:
|
||||||
syslog-address: "tcp://127.0.0.1:1514"
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
tag: "mysql"
|
tag: "mysql"
|
||||||
|
adminserver:
|
||||||
|
image: vmware/harbor-adminserver
|
||||||
|
container_name: harbor-adminserver
|
||||||
|
env_file:
|
||||||
|
- ./common/config/adminserver/env
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /data/config/:/etc/adminserver/
|
||||||
|
- /data/secretkey:/etc/adminserver/key
|
||||||
|
networks:
|
||||||
|
- harbor
|
||||||
|
depends_on:
|
||||||
|
- log
|
||||||
|
logging:
|
||||||
|
driver: "syslog"
|
||||||
|
options:
|
||||||
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
|
tag: "adminserver"
|
||||||
ui:
|
ui:
|
||||||
image: vmware/harbor-ui
|
image: vmware/harbor-ui
|
||||||
container_name: harbor-ui
|
container_name: harbor-ui
|
||||||
@ -57,10 +75,13 @@ services:
|
|||||||
- ./common/config/ui/app.conf:/etc/ui/app.conf
|
- ./common/config/ui/app.conf:/etc/ui/app.conf
|
||||||
- ./common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
- ./common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
||||||
- /data:/harbor_storage
|
- /data:/harbor_storage
|
||||||
|
- /data/secretkey:/etc/ui/key
|
||||||
networks:
|
networks:
|
||||||
- harbor
|
- harbor
|
||||||
depends_on:
|
depends_on:
|
||||||
- log
|
- log
|
||||||
|
- adminserver
|
||||||
|
- registry
|
||||||
logging:
|
logging:
|
||||||
driver: "syslog"
|
driver: "syslog"
|
||||||
options:
|
options:
|
||||||
@ -75,10 +96,12 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /data/job_logs:/var/log/jobs
|
- /data/job_logs:/var/log/jobs
|
||||||
- ./common/config/jobservice/app.conf:/etc/jobservice/app.conf
|
- ./common/config/jobservice/app.conf:/etc/jobservice/app.conf
|
||||||
|
- /data/secretkey:/etc/jobservice/key
|
||||||
networks:
|
networks:
|
||||||
- harbor
|
- harbor
|
||||||
depends_on:
|
depends_on:
|
||||||
- ui
|
- ui
|
||||||
|
- adminserver
|
||||||
logging:
|
logging:
|
||||||
driver: "syslog"
|
driver: "syslog"
|
||||||
options:
|
options:
|
||||||
|
@ -53,7 +53,7 @@ ldap_uid = uid
|
|||||||
ldap_scope = 3
|
ldap_scope = 3
|
||||||
|
|
||||||
#Timeout (in seconds) when connecting to an LDAP Server. The default value (and most reasonable) is 5 seconds.
|
#Timeout (in seconds) when connecting to an LDAP Server. The default value (and most reasonable) is 5 seconds.
|
||||||
ldap_connect_timeout = 5
|
ldap_timeout = 5
|
||||||
|
|
||||||
#The password for the root user of mysql db, change this before any production use.
|
#The password for the root user of mysql db, change this before any production use.
|
||||||
db_password = root123
|
db_password = root123
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# Targets:
|
# Targets:
|
||||||
#
|
#
|
||||||
# build: build harbor photon images
|
# build: build harbor photon images
|
||||||
# clean: clean ui and jobservice harbor images
|
# clean: clean adminserver, ui and jobservice harbor images
|
||||||
|
|
||||||
# common
|
# common
|
||||||
SHELL := /bin/bash
|
SHELL := /bin/bash
|
||||||
@ -22,6 +22,9 @@ DOCKERRMIMAGE=$(DOCKERCMD) rmi
|
|||||||
DOCKERIMASES=$(DOCKERCMD) images
|
DOCKERIMASES=$(DOCKERCMD) images
|
||||||
|
|
||||||
# binary
|
# binary
|
||||||
|
ADMINSERVERSOURCECODE=$(SRCPATH)/adminserver
|
||||||
|
ADMINSERVERBINARYPATH=$(MAKEDEVPATH)/adminserver
|
||||||
|
ADMINSERVERBINARYNAME=harbor_adminserver
|
||||||
UISOURCECODE=$(SRCPATH)/ui
|
UISOURCECODE=$(SRCPATH)/ui
|
||||||
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
||||||
UIBINARYNAME=harbor_ui
|
UIBINARYNAME=harbor_ui
|
||||||
@ -31,6 +34,9 @@ JOBSERVICEBINARYNAME=harbor_jobservice
|
|||||||
|
|
||||||
# photon dockerfile
|
# photon dockerfile
|
||||||
DOCKERFILEPATH=$(MAKEPATH)/photon
|
DOCKERFILEPATH=$(MAKEPATH)/photon
|
||||||
|
DOCKERFILEPATH_ADMINSERVER=$(DOCKERFILEPATH)/adminserver
|
||||||
|
DOCKERFILENAME_ADMINSERVER=Dockerfile
|
||||||
|
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
|
||||||
@ -56,6 +62,10 @@ check_environment:
|
|||||||
@$(MAKEPATH)/$(CHECKENVCMD)
|
@$(MAKEPATH)/$(CHECKENVCMD)
|
||||||
|
|
||||||
build:
|
build:
|
||||||
|
@echo "building adminserver container for photon..."
|
||||||
|
$(DOCKERBUILD) -f $(DOCKERFILEPATH_ADMINSERVER)/$(DOCKERFILENAME_ADMINSERVER) -t $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) .
|
||||||
|
@echo "Done."
|
||||||
|
|
||||||
@echo "building ui container for photon..."
|
@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."
|
||||||
@ -70,6 +80,7 @@ build:
|
|||||||
|
|
||||||
cleanimage:
|
cleanimage:
|
||||||
@echo "cleaning image for photon..."
|
@echo "cleaning image for photon..."
|
||||||
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||||
|
8
make/photon/adminserver/Dockerfile
Normal file
8
make/photon/adminserver/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
FROM library/photon:1.0
|
||||||
|
|
||||||
|
RUN mkdir /harbor/
|
||||||
|
COPY ./make/dev/adminserver/harbor_adminserver /harbor/
|
||||||
|
|
||||||
|
RUN chmod u+x /harbor/harbor_adminserver
|
||||||
|
WORKDIR /harbor/
|
||||||
|
ENTRYPOINT ["/harbor/harbor_adminserver"]
|
83
make/prepare
83
make/prepare
@ -103,10 +103,10 @@ hostname = rcp.get("configuration", "hostname")
|
|||||||
protocol = rcp.get("configuration", "ui_url_protocol")
|
protocol = rcp.get("configuration", "ui_url_protocol")
|
||||||
ui_url = protocol + "://" + hostname
|
ui_url = protocol + "://" + hostname
|
||||||
email_identity = rcp.get("configuration", "email_identity")
|
email_identity = rcp.get("configuration", "email_identity")
|
||||||
email_server = rcp.get("configuration", "email_server")
|
email_host = rcp.get("configuration", "email_server")
|
||||||
email_server_port = rcp.get("configuration", "email_server_port")
|
email_port = rcp.get("configuration", "email_server_port")
|
||||||
email_username = rcp.get("configuration", "email_username")
|
email_usr = rcp.get("configuration", "email_username")
|
||||||
email_password = rcp.get("configuration", "email_password")
|
email_pwd = rcp.get("configuration", "email_password")
|
||||||
email_from = rcp.get("configuration", "email_from")
|
email_from = rcp.get("configuration", "email_from")
|
||||||
email_ssl = rcp.get("configuration", "email_ssl")
|
email_ssl = rcp.get("configuration", "email_ssl")
|
||||||
harbor_admin_password = rcp.get("configuration", "harbor_admin_password")
|
harbor_admin_password = rcp.get("configuration", "harbor_admin_password")
|
||||||
@ -127,7 +127,7 @@ else:
|
|||||||
ldap_filter = ""
|
ldap_filter = ""
|
||||||
ldap_uid = rcp.get("configuration", "ldap_uid")
|
ldap_uid = rcp.get("configuration", "ldap_uid")
|
||||||
ldap_scope = rcp.get("configuration", "ldap_scope")
|
ldap_scope = rcp.get("configuration", "ldap_scope")
|
||||||
ldap_connect_timeout = rcp.get("configuration", "ldap_connect_timeout")
|
ldap_timeout = rcp.get("configuration", "ldap_timeout")
|
||||||
db_password = rcp.get("configuration", "db_password")
|
db_password = rcp.get("configuration", "db_password")
|
||||||
self_registration = rcp.get("configuration", "self_registration")
|
self_registration = rcp.get("configuration", "self_registration")
|
||||||
use_compressed_js = rcp.get("configuration", "use_compressed_js")
|
use_compressed_js = rcp.get("configuration", "use_compressed_js")
|
||||||
@ -151,6 +151,11 @@ secret_key = get_secret_key(secretkey_path)
|
|||||||
########
|
########
|
||||||
|
|
||||||
ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
||||||
|
jobservice_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
||||||
|
|
||||||
|
adminserver_config_dir = os.path.join(config_dir,"adminserver")
|
||||||
|
if not os.path.exists(adminserver_config_dir):
|
||||||
|
os.makedirs(os.path.join(config_dir, "adminserver"))
|
||||||
|
|
||||||
ui_config_dir = prep_conf_dir(config_dir,"ui")
|
ui_config_dir = prep_conf_dir(config_dir,"ui")
|
||||||
db_config_dir = prep_conf_dir(config_dir, "db")
|
db_config_dir = prep_conf_dir(config_dir, "db")
|
||||||
@ -159,6 +164,7 @@ registry_config_dir = prep_conf_dir(config_dir, "registry")
|
|||||||
nginx_config_dir = prep_conf_dir (config_dir, "nginx")
|
nginx_config_dir = prep_conf_dir (config_dir, "nginx")
|
||||||
nginx_conf_d = prep_conf_dir(nginx_config_dir, "conf.d")
|
nginx_conf_d = prep_conf_dir(nginx_config_dir, "conf.d")
|
||||||
|
|
||||||
|
adminserver_conf_env = os.path.join(config_dir, "adminserver", "env")
|
||||||
ui_conf_env = os.path.join(config_dir, "ui", "env")
|
ui_conf_env = os.path.join(config_dir, "ui", "env")
|
||||||
ui_conf = os.path.join(config_dir, "ui", "app.conf")
|
ui_conf = os.path.join(config_dir, "ui", "app.conf")
|
||||||
jobservice_conf = os.path.join(config_dir, "jobservice", "app.conf")
|
jobservice_conf = os.path.join(config_dir, "jobservice", "app.conf")
|
||||||
@ -182,14 +188,12 @@ if protocol == "https":
|
|||||||
else:
|
else:
|
||||||
render(os.path.join(templates_dir, "nginx", "nginx.http.conf"),
|
render(os.path.join(templates_dir, "nginx", "nginx.http.conf"),
|
||||||
nginx_conf)
|
nginx_conf)
|
||||||
|
|
||||||
render(os.path.join(templates_dir, "ui", "env"),
|
render(os.path.join(templates_dir, "adminserver", "env"),
|
||||||
ui_conf_env,
|
adminserver_conf_env,
|
||||||
hostname=hostname,
|
ui_url=ui_url,
|
||||||
db_password=db_password,
|
auth_mode=auth_mode,
|
||||||
ui_url=ui_url,
|
self_registration=self_registration,
|
||||||
auth_mode=auth_mode,
|
|
||||||
harbor_admin_password=harbor_admin_password,
|
|
||||||
ldap_url=ldap_url,
|
ldap_url=ldap_url,
|
||||||
ldap_searchdn =ldap_searchdn,
|
ldap_searchdn =ldap_searchdn,
|
||||||
ldap_search_pwd =ldap_search_pwd,
|
ldap_search_pwd =ldap_search_pwd,
|
||||||
@ -197,27 +201,32 @@ render(os.path.join(templates_dir, "ui", "env"),
|
|||||||
ldap_filter=ldap_filter,
|
ldap_filter=ldap_filter,
|
||||||
ldap_uid=ldap_uid,
|
ldap_uid=ldap_uid,
|
||||||
ldap_scope=ldap_scope,
|
ldap_scope=ldap_scope,
|
||||||
ldap_connect_timeout=ldap_connect_timeout,
|
ldap_timeout=ldap_timeout,
|
||||||
self_registration=self_registration,
|
db_password=db_password,
|
||||||
use_compressed_js=use_compressed_js,
|
email_host=email_host,
|
||||||
ui_secret=ui_secret,
|
email_port=email_port,
|
||||||
secret_key=secret_key,
|
email_usr=email_usr,
|
||||||
verify_remote_cert=verify_remote_cert,
|
email_pwd=email_pwd,
|
||||||
project_creation_restriction=proj_cre_restriction,
|
email_ssl=email_ssl,
|
||||||
token_expiration=token_expiration)
|
email_from=email_from,
|
||||||
|
email_identity=email_identity,
|
||||||
|
harbor_admin_password=harbor_admin_password,
|
||||||
|
project_creation_restriction=proj_cre_restriction,
|
||||||
|
verify_remote_cert=verify_remote_cert,
|
||||||
|
max_job_workers=max_job_workers,
|
||||||
|
ui_secret=ui_secret,
|
||||||
|
jobservice_secret=jobservice_secret,
|
||||||
|
token_expiration=token_expiration,
|
||||||
|
use_compressed_js=use_compressed_js
|
||||||
|
)
|
||||||
|
|
||||||
render(os.path.join(templates_dir, "ui", "app.conf"),
|
render(os.path.join(templates_dir, "ui", "env"),
|
||||||
ui_conf,
|
ui_conf_env,
|
||||||
email_identity=email_identity,
|
ui_secret=ui_secret,
|
||||||
email_server=email_server,
|
jobservice_secret=jobservice_secret,)
|
||||||
email_server_port=email_server_port,
|
|
||||||
email_username=email_username,
|
|
||||||
email_password=email_password,
|
|
||||||
email_from=email_from,
|
|
||||||
email_ssl=email_ssl,
|
|
||||||
ui_url=ui_url)
|
|
||||||
|
|
||||||
render(os.path.join(templates_dir, "registry", "config.yml"),
|
render(os.path.join(templates_dir, "registry",
|
||||||
|
"config.yml"),
|
||||||
registry_conf,
|
registry_conf,
|
||||||
ui_url=ui_url)
|
ui_url=ui_url)
|
||||||
|
|
||||||
@ -227,16 +236,16 @@ render(os.path.join(templates_dir, "db", "env"),
|
|||||||
|
|
||||||
render(os.path.join(templates_dir, "jobservice", "env"),
|
render(os.path.join(templates_dir, "jobservice", "env"),
|
||||||
job_conf_env,
|
job_conf_env,
|
||||||
db_password=db_password,
|
|
||||||
ui_secret=ui_secret,
|
ui_secret=ui_secret,
|
||||||
max_job_workers=max_job_workers,
|
jobservice_secret=jobservice_secret)
|
||||||
secret_key=secret_key,
|
|
||||||
ui_url=ui_url,
|
|
||||||
verify_remote_cert=verify_remote_cert)
|
|
||||||
|
|
||||||
print("Generated configuration file: %s" % jobservice_conf)
|
print("Generated configuration file: %s" % jobservice_conf)
|
||||||
shutil.copyfile(os.path.join(templates_dir, "jobservice", "app.conf"), jobservice_conf)
|
shutil.copyfile(os.path.join(templates_dir, "jobservice", "app.conf"), jobservice_conf)
|
||||||
|
|
||||||
|
print("Generated configuration file: %s" % ui_conf)
|
||||||
|
shutil.copyfile(os.path.join(templates_dir, "ui", "app.conf"), ui_conf)
|
||||||
|
|
||||||
|
|
||||||
def validate_crt_subj(dirty_subj):
|
def validate_crt_subj(dirty_subj):
|
||||||
subj_list = [item for item in dirty_subj.strip().split("/") \
|
subj_list = [item for item in dirty_subj.strip().split("/") \
|
||||||
if len(item.split("=")) == 2 and len(item.split("=")[1]) > 0]
|
if len(item.split("=")) == 2 and len(item.split("=")[1]) > 0]
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
# Makefile for a harbor project
|
|
||||||
#
|
|
||||||
# Targets:
|
|
||||||
#
|
|
||||||
# build: build harbor ubuntu images
|
|
||||||
# clean: clean ui and jobservice harbor images
|
|
||||||
|
|
||||||
# common
|
|
||||||
SHELL := /bin/bash
|
|
||||||
BUILDPATH=$(CURDIR)
|
|
||||||
MAKEPATH=$(BUILDPATH)/make
|
|
||||||
MAKEDEVPATH=$(MAKEPATH)/dev
|
|
||||||
SRCPATH=./src
|
|
||||||
TOOLSPATH=$(BUILDPATH)/tools
|
|
||||||
CHECKENVCMD=checkenv.sh
|
|
||||||
DEVFLAG=true
|
|
||||||
|
|
||||||
# docker parameters
|
|
||||||
DOCKERCMD=$(shell which docker)
|
|
||||||
DOCKERBUILD=$(DOCKERCMD) build
|
|
||||||
DOCKERRMIMAGE=$(DOCKERCMD) rmi
|
|
||||||
DOCKERIMASES=$(DOCKERCMD) images
|
|
||||||
|
|
||||||
# binary
|
|
||||||
UISOURCECODE=$(SRCPATH)/ui
|
|
||||||
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
|
||||||
UIBINARYNAME=harbor_ui
|
|
||||||
JOBSERVICESOURCECODE=$(SRCPATH)/jobservice
|
|
||||||
JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice
|
|
||||||
JOBSERVICEBINARYNAME=harbor_jobservice
|
|
||||||
|
|
||||||
# ubuntu dockerfile
|
|
||||||
DOCKERFILEPATH=$(MAKEPATH)/ubuntu
|
|
||||||
DOCKERFILEPATH_UI=$(DOCKERFILEPATH)/ui
|
|
||||||
DOCKERFILENAME_UI=Dockerfile
|
|
||||||
DOCKERIMAGENAME_UI=vmware/harbor-ui
|
|
||||||
DOCKERFILEPATH_JOBSERVICE=$(DOCKERFILEPATH)/jobservice
|
|
||||||
DOCKERFILENAME_JOBSERVICE=Dockerfile
|
|
||||||
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
|
||||||
DOCKERFILEPATH_LOG=$(DOCKERFILEPATH)/log
|
|
||||||
DOCKERFILENAME_LOG=Dockerfile
|
|
||||||
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
|
||||||
|
|
||||||
# version prepare
|
|
||||||
VERSIONFILEPATH=$(SRCPATH)/views/sections
|
|
||||||
VERSIONFILENAME=header-content.htm
|
|
||||||
GITCMD=$(shell which git)
|
|
||||||
GITTAG=$(GITCMD) describe --tags
|
|
||||||
ifeq ($(DEVFLAG), true)
|
|
||||||
VERSIONTAG=dev
|
|
||||||
else
|
|
||||||
VERSIONTAG=$(shell $(GITTAG))
|
|
||||||
endif
|
|
||||||
|
|
||||||
check_environment:
|
|
||||||
@$(MAKEPATH)/$(CHECKENVCMD)
|
|
||||||
|
|
||||||
build:
|
|
||||||
@echo "building ui container for ubuntu..."
|
|
||||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_UI)/$(DOCKERFILENAME_UI) -t $(DOCKERIMAGENAME_UI):$(VERSIONTAG) .
|
|
||||||
@echo "Done."
|
|
||||||
|
|
||||||
@echo "building jobservice container for ubuntu..."
|
|
||||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_JOBSERVICE)/$(DOCKERFILENAME_JOBSERVICE) -t $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) .
|
|
||||||
@echo "Done."
|
|
||||||
|
|
||||||
@echo "building log container for ubuntu..."
|
|
||||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_LOG)/$(DOCKERFILENAME_LOG) -t $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) .
|
|
||||||
@echo "Done."
|
|
||||||
|
|
||||||
cleanimage:
|
|
||||||
@echo "cleaning image for ubuntu..."
|
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
|
||||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
|
||||||
|
|
||||||
.PHONY: clean
|
|
||||||
clean: cleanimage
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
|||||||
FROM library/ubuntu:14.04
|
|
||||||
|
|
||||||
MAINTAINER jiangd@vmware.com
|
|
||||||
|
|
||||||
RUN mkdir /harbor/
|
|
||||||
COPY ./make/dev/jobservice/harbor_jobservice /harbor/
|
|
||||||
|
|
||||||
RUN chmod u+x /harbor/harbor_jobservice
|
|
||||||
|
|
||||||
WORKDIR /harbor/
|
|
||||||
ENTRYPOINT ["/harbor/harbor_jobservice"]
|
|
@ -1,18 +0,0 @@
|
|||||||
FROM library/ubuntu:14.04
|
|
||||||
|
|
||||||
RUN rm /etc/rsyslog.d/* && rm /etc/rsyslog.conf
|
|
||||||
|
|
||||||
ADD make/common/log/rsyslog.conf /etc/rsyslog.conf
|
|
||||||
|
|
||||||
# rotate logs weekly
|
|
||||||
# notes: file name cannot contain dot, or the script will not run
|
|
||||||
ADD make/common/log/rotate.sh /etc/cron.weekly/rotate
|
|
||||||
|
|
||||||
# rsyslog configuration file for docker
|
|
||||||
ADD make/common/log/rsyslog_docker.conf /etc/rsyslog.d/
|
|
||||||
|
|
||||||
VOLUME /var/log/docker/
|
|
||||||
|
|
||||||
EXPOSE 514
|
|
||||||
|
|
||||||
CMD cron && rsyslogd -n
|
|
@ -1,26 +0,0 @@
|
|||||||
FROM library/ubuntu:14.04
|
|
||||||
|
|
||||||
MAINTAINER jiangd@vmware.com
|
|
||||||
|
|
||||||
ENV MYSQL_USR root \
|
|
||||||
MYSQL_PWD root \
|
|
||||||
REGISTRY_URL localhost:5000
|
|
||||||
|
|
||||||
RUN mkdir /harbor/
|
|
||||||
COPY ./make/dev/ui/harbor_ui /harbor/
|
|
||||||
|
|
||||||
COPY ./src/ui/views /harbor/views
|
|
||||||
COPY ./src/ui/static /harbor/static
|
|
||||||
COPY ./src/favicon.ico /harbor/favicon.ico
|
|
||||||
COPY ./make/jsminify.sh /tmp/jsminify.sh
|
|
||||||
|
|
||||||
RUN chmod u+x /harbor/harbor_ui \
|
|
||||||
&& timestamp=`date '+%s'` \
|
|
||||||
&& /tmp/jsminify.sh /harbor/views/sections/script-include.htm /harbor/static/resources/js/harbor.app.min.$timestamp.js /harbor/ \
|
|
||||||
&& sed -i "s/harbor\.app\.min\.js/harbor\.app\.min\.$timestamp\.js/g" /harbor/views/sections/script-min-include.htm
|
|
||||||
|
|
||||||
WORKDIR /harbor/
|
|
||||||
ENTRYPOINT ["/harbor/harbor_ui"]
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
34
src/adminserver/api/base.go
Normal file
34
src/adminserver/api/base.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleInternalServerError(w http.ResponseWriter) {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError),
|
||||||
|
http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleBadRequestError(w http.ResponseWriter, error string) {
|
||||||
|
http.Error(w, error, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUnauthorized(w http.ResponseWriter) {
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized),
|
||||||
|
http.StatusUnauthorized)
|
||||||
|
}
|
32
src/adminserver/api/base_test.go
Normal file
32
src/adminserver/api/base_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleInternalServerError(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handleInternalServerError(w)
|
||||||
|
|
||||||
|
if w.Code != http.StatusInternalServerError {
|
||||||
|
t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
106
src/adminserver/api/cfg.go
Normal file
106
src/adminserver/api/cfg.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
cfg "github.com/vmware/harbor/src/adminserver/systemcfg"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isAuthenticated(r *http.Request) (bool, error) {
|
||||||
|
uiSecret := os.Getenv("UI_SECRET")
|
||||||
|
jobserviceSecret := os.Getenv("JOBSERVICE_SECRET")
|
||||||
|
c, err := r.Cookie("secret")
|
||||||
|
if err != nil {
|
||||||
|
if err == http.ErrNoCookie {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return c != nil && (c.Value == uiSecret ||
|
||||||
|
c.Value == jobserviceSecret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCfgs lists configurations
|
||||||
|
func ListCfgs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
authenticated, err := isAuthenticated(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to check whether the request is authenticated or not: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !authenticated {
|
||||||
|
handleUnauthorized(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := cfg.GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get system configurations: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.MarshalIndent(cfg, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to marshal configurations: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = w.Write(b); err != nil {
|
||||||
|
log.Errorf("failed to write response: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCfgs updates configurations
|
||||||
|
func UpdateCfgs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
authenticated, err := isAuthenticated(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to check whether the request is authenticated or not: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !authenticated {
|
||||||
|
handleUnauthorized(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to read request body: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]interface{}{}
|
||||||
|
if err = json.Unmarshal(b, &m); err != nil {
|
||||||
|
handleBadRequestError(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cfg.UpdateSystemCfg(m); err != nil {
|
||||||
|
log.Errorf("failed to update system configurations: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
183
src/adminserver/api/cfg_test.go
Normal file
183
src/adminserver/api/cfg_test.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/adminserver/systemcfg"
|
||||||
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigAPI(t *testing.T) {
|
||||||
|
configPath := "/tmp/config.json"
|
||||||
|
secretKeyPath := "/tmp/secretkey"
|
||||||
|
|
||||||
|
_, err := test.GenerateKey(secretKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to generate secret key: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(secretKeyPath)
|
||||||
|
|
||||||
|
secret := "secret"
|
||||||
|
envs := map[string]string{
|
||||||
|
|
||||||
|
"JSON_CFG_STORE_PATH": configPath,
|
||||||
|
"KEY_PATH": secretKeyPath,
|
||||||
|
"UI_SECRET": secret,
|
||||||
|
"MYSQL_PORT": "3306",
|
||||||
|
"TOKEN_EXPIRATION": "30",
|
||||||
|
"CFG_EXPIRATION": "5",
|
||||||
|
"MAX_JOB_WORKERS": "3",
|
||||||
|
"LDAP_SCOPE": "3",
|
||||||
|
"LDAP_TIMEOUT": "30",
|
||||||
|
"EMAIL_PORT": "25",
|
||||||
|
"MYSQL_PWD": "",
|
||||||
|
"LDAP_SEARCH_PWD": "",
|
||||||
|
"EMAIL_PWD": "",
|
||||||
|
"HARBOR_ADMIN_PASSWORD": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range envs {
|
||||||
|
if err := os.Setenv(k, v); err != nil {
|
||||||
|
t.Errorf("failed to set env %s: %v", k, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
if err := systemcfg.Init(); err != nil {
|
||||||
|
t.Errorf("failed to initialize system configurations: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := http.NewRequest("GET", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to create request: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
ListCfgs(w, r)
|
||||||
|
if w.Code != http.StatusUnauthorized {
|
||||||
|
t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.AddCookie(&http.Cookie{
|
||||||
|
Name: "secret",
|
||||||
|
Value: secret,
|
||||||
|
})
|
||||||
|
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
ListCfgs(w, r)
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := parse(w.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to parse response body: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := int(m[comcfg.LDAPScope].(float64))
|
||||||
|
if scope != 3 {
|
||||||
|
t.Errorf("unexpected ldap scope: %d != %d", scope, 3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// modify configurations
|
||||||
|
c := map[string]interface{}{
|
||||||
|
comcfg.AUTHMode: comcfg.LDAPAuth,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to marshal configuartions: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
r, err = http.NewRequest("GET", "", bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to create request: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.AddCookie(&http.Cookie{
|
||||||
|
Name: "secret",
|
||||||
|
Value: secret,
|
||||||
|
})
|
||||||
|
|
||||||
|
UpdateCfgs(w, r)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm the modification is done
|
||||||
|
r, err = http.NewRequest("GET", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to create request: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.AddCookie(&http.Cookie{
|
||||||
|
Name: "secret",
|
||||||
|
Value: secret,
|
||||||
|
})
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
ListCfgs(w, r)
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err = parse(w.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to parse response body: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := m[comcfg.AUTHMode].(string)
|
||||||
|
if mode != comcfg.LDAPAuth {
|
||||||
|
t.Errorf("unexpected ldap scope: %s != %s", mode, comcfg.LDAPAuth)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(reader io.Reader) (map[string]interface{}, error) {
|
||||||
|
b, err := ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]interface{}{}
|
||||||
|
if err := json.Unmarshal(b, &m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
60
src/adminserver/main.go
Normal file
60
src/adminserver/main.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
syscfg "github.com/vmware/harbor/src/adminserver/systemcfg"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server for admin component
|
||||||
|
type Server struct {
|
||||||
|
Port string
|
||||||
|
Handler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve the API
|
||||||
|
func (s *Server) Serve() error {
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: ":" + s.Port,
|
||||||
|
Handler: s.Handler,
|
||||||
|
}
|
||||||
|
|
||||||
|
return server.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Info("initializing system configurations...")
|
||||||
|
if err := syscfg.Init(); err != nil {
|
||||||
|
log.Fatalf("failed to initialize the system: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("system initialization completed")
|
||||||
|
|
||||||
|
port := os.Getenv("PORT")
|
||||||
|
if len(port) == 0 {
|
||||||
|
port = "80"
|
||||||
|
}
|
||||||
|
server := &Server{
|
||||||
|
Port: port,
|
||||||
|
Handler: newHandler(),
|
||||||
|
}
|
||||||
|
if err := server.Serve(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
30
src/adminserver/router.go
Normal file
30
src/adminserver/router.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/vmware/harbor/src/adminserver/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newHandler() http.Handler {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/api/configurations", api.ListCfgs).Methods("GET")
|
||||||
|
r.HandleFunc("/api/configurations", api.UpdateCfgs).Methods("PUT")
|
||||||
|
return r
|
||||||
|
}
|
27
src/adminserver/systemcfg/store/driver.go
Normal file
27
src/adminserver/systemcfg/store/driver.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package store
|
||||||
|
|
||||||
|
// Driver defines methods that a configuration store driver must implement
|
||||||
|
type Driver interface {
|
||||||
|
// Name returns a human-readable name of the driver
|
||||||
|
Name() string
|
||||||
|
// Read reads all the configurations from store
|
||||||
|
Read() (map[string]interface{}, error)
|
||||||
|
// Write writes the configurations to store, the configurations can be
|
||||||
|
// part of all
|
||||||
|
Write(map[string]interface{}) error
|
||||||
|
}
|
124
src/adminserver/systemcfg/store/json/driver_json.go
Normal file
124
src/adminserver/systemcfg/store/json/driver_json.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/adminserver/systemcfg/store"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// the default path of configuration file
|
||||||
|
defaultPath = "/etc/harbor/config.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cfgStore struct {
|
||||||
|
path string // the path of cfg file
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCfgStore returns an instance of cfgStore that stores the configurations
|
||||||
|
// in a json file. The file will be created if it does not exist.
|
||||||
|
func NewCfgStore(path ...string) (store.Driver, error) {
|
||||||
|
p := defaultPath
|
||||||
|
if len(path) > 0 && len(path[0]) > 0 {
|
||||||
|
p = path[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("path of configuration file: %s", p)
|
||||||
|
|
||||||
|
if _, err := os.Stat(p); os.IsNotExist(err) {
|
||||||
|
log.Infof("the configuration file %s does not exist, creating it...", p)
|
||||||
|
if err = os.MkdirAll(filepath.Dir(p), 0600); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = ioutil.WriteFile(p, []byte{}, 0600); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfgStore{
|
||||||
|
path: p,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name ...
|
||||||
|
func (c *cfgStore) Name() string {
|
||||||
|
return "JSON"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read ...
|
||||||
|
func (c *cfgStore) Read() (map[string]interface{}, error) {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
return read(c.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func read(path string) (map[string]interface{}, error) {
|
||||||
|
b, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty file
|
||||||
|
if len(b) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
config := map[string]interface{}{}
|
||||||
|
if err = json.Unmarshal(b, &config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write ...
|
||||||
|
func (c *cfgStore) Write(config map[string]interface{}) error {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
|
||||||
|
cfg, err := read(c.path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg == nil {
|
||||||
|
cfg = config
|
||||||
|
} else {
|
||||||
|
for k, v := range config {
|
||||||
|
cfg[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.MarshalIndent(cfg, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioutil.WriteFile(c.path, b, 0600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
52
src/adminserver/systemcfg/store/json/driver_json_test.go
Normal file
52
src/adminserver/systemcfg/store/json/driver_json_test.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadWrite(t *testing.T) {
|
||||||
|
path := "/tmp/config.json"
|
||||||
|
store, err := NewCfgStore(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create json cfg store: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := os.Remove(path); err != nil {
|
||||||
|
t.Fatalf("failed to remove the json file %s: %v", path, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if store.Name() != "JSON" {
|
||||||
|
t.Errorf("unexpected name: %s != %s", store.Name(), "JSON")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
}
|
||||||
|
if err := store.Write(config); err != nil {
|
||||||
|
t.Errorf("failed to write configurations to json file: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = store.Read(); err != nil {
|
||||||
|
t.Errorf("failed to read configurations from json file: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
298
src/adminserver/systemcfg/systemcfg.go
Normal file
298
src/adminserver/systemcfg/systemcfg.go
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package systemcfg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/adminserver/systemcfg/store"
|
||||||
|
"github.com/vmware/harbor/src/adminserver/systemcfg/store/json"
|
||||||
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultCfgStoreDriver string = "json"
|
||||||
|
defaultJSONCfgStorePath string = "/etc/adminserver/config.json"
|
||||||
|
defaultKeyPath string = "/etc/adminserver/key"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cfgStore store.Driver
|
||||||
|
keyProvider comcfg.KeyProvider
|
||||||
|
|
||||||
|
// attrs need to be encrypted or decrypted
|
||||||
|
attrs = []string{
|
||||||
|
comcfg.EmailPassword,
|
||||||
|
comcfg.LDAPSearchPwd,
|
||||||
|
comcfg.MySQLPassword,
|
||||||
|
comcfg.AdminInitialPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
// envs are configurations need read from environment variables
|
||||||
|
envs = map[string]interface{}{
|
||||||
|
comcfg.ExtEndpoint: "EXT_ENDPOINT",
|
||||||
|
comcfg.AUTHMode: "AUTH_MODE",
|
||||||
|
comcfg.SelfRegistration: &parser{
|
||||||
|
env: "SELF_REGISTRATION",
|
||||||
|
parse: parseStringToBool,
|
||||||
|
},
|
||||||
|
comcfg.DatabaseType: "DATABASE_TYPE",
|
||||||
|
comcfg.MySQLHost: "MYSQL_HOST",
|
||||||
|
comcfg.MySQLPort: &parser{
|
||||||
|
env: "MYSQL_PORT",
|
||||||
|
parse: parseStringToInt,
|
||||||
|
},
|
||||||
|
comcfg.MySQLUsername: "MYSQL_USR",
|
||||||
|
comcfg.MySQLPassword: "MYSQL_PWD",
|
||||||
|
comcfg.MySQLDatabase: "MYSQL_DATABASE",
|
||||||
|
comcfg.SQLiteFile: "SQLITE_FILE",
|
||||||
|
comcfg.LDAPURL: "LDAP_URL",
|
||||||
|
comcfg.LDAPSearchDN: "LDAP_SEARCH_DN",
|
||||||
|
comcfg.LDAPSearchPwd: "LDAP_SEARCH_PWD",
|
||||||
|
comcfg.LDAPBaseDN: "LDAP_BASE_DN",
|
||||||
|
comcfg.LDAPFilter: "LDAP_FILTER",
|
||||||
|
comcfg.LDAPUID: "LDAP_UID",
|
||||||
|
comcfg.LDAPScope: &parser{
|
||||||
|
env: "LDAP_SCOPE",
|
||||||
|
parse: parseStringToInt,
|
||||||
|
},
|
||||||
|
comcfg.LDAPTimeout: &parser{
|
||||||
|
env: "LDAP_TIMEOUT",
|
||||||
|
parse: parseStringToInt,
|
||||||
|
},
|
||||||
|
comcfg.EmailHost: "EMAIL_HOST",
|
||||||
|
comcfg.EmailPort: &parser{
|
||||||
|
env: "EMAIL_PORT",
|
||||||
|
parse: parseStringToInt,
|
||||||
|
},
|
||||||
|
comcfg.EmailUsername: "EMAIL_USR",
|
||||||
|
comcfg.EmailPassword: "EMAIL_PWD",
|
||||||
|
comcfg.EmailSSL: &parser{
|
||||||
|
env: "EMAIL_SSL",
|
||||||
|
parse: parseStringToBool,
|
||||||
|
},
|
||||||
|
comcfg.EmailFrom: "EMAIL_FROM",
|
||||||
|
comcfg.EmailIdentity: "EMAIL_IDENTITY",
|
||||||
|
comcfg.RegistryURL: "REGISTRY_URL",
|
||||||
|
comcfg.TokenExpiration: &parser{
|
||||||
|
env: "TOKEN_EXPIRATION",
|
||||||
|
parse: parseStringToInt,
|
||||||
|
},
|
||||||
|
comcfg.JobLogDir: "LOG_DIR",
|
||||||
|
comcfg.UseCompressedJS: &parser{
|
||||||
|
env: "USE_COMPRESSED_JS",
|
||||||
|
parse: parseStringToBool,
|
||||||
|
},
|
||||||
|
comcfg.CfgExpiration: &parser{
|
||||||
|
env: "CFG_EXPIRATION",
|
||||||
|
parse: parseStringToInt,
|
||||||
|
},
|
||||||
|
comcfg.MaxJobWorkers: &parser{
|
||||||
|
env: "MAX_JOB_WORKERS",
|
||||||
|
parse: parseStringToInt,
|
||||||
|
},
|
||||||
|
comcfg.VerifyRemoteCert: &parser{
|
||||||
|
env: "VERIFY_REMOTE_CERT",
|
||||||
|
parse: parseStringToBool,
|
||||||
|
},
|
||||||
|
comcfg.ProjectCreationRestriction: "PROJECT_CREATION_RESTRICTION",
|
||||||
|
comcfg.AdminInitialPassword: "HARBOR_ADMIN_PASSWORD",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
// the name of env
|
||||||
|
env string
|
||||||
|
// parse the value of env, e.g. parse string to int or
|
||||||
|
// parse string to bool
|
||||||
|
parse func(string) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStringToInt(str string) (interface{}, error) {
|
||||||
|
return strconv.Atoi(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStringToBool(str string) (interface{}, error) {
|
||||||
|
return strings.ToLower(str) == "true" ||
|
||||||
|
strings.ToLower(str) == "on", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init system configurations. Read from config store first,
|
||||||
|
// if null read from env
|
||||||
|
func Init() (err error) {
|
||||||
|
//init configuation store
|
||||||
|
if err = initCfgStore(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//init key provider
|
||||||
|
initKeyProvider()
|
||||||
|
|
||||||
|
cfg, err := GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("configurations read from store driver are null, initializing system from environment variables...")
|
||||||
|
cfg, err = loadFromEnv()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//sync configurations into cfg store
|
||||||
|
return UpdateSystemCfg(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initCfgStore() (err error) {
|
||||||
|
t := os.Getenv("CFG_STORE_DRIVER")
|
||||||
|
if len(t) == 0 {
|
||||||
|
t = defaultCfgStoreDriver
|
||||||
|
}
|
||||||
|
log.Infof("configuration store driver: %s", t)
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case "json":
|
||||||
|
path := os.Getenv("JSON_CFG_STORE_PATH")
|
||||||
|
if len(path) == 0 {
|
||||||
|
path = defaultJSONCfgStorePath
|
||||||
|
}
|
||||||
|
log.Infof("json configuration store path: %s", path)
|
||||||
|
|
||||||
|
cfgStore, err = json.NewCfgStore(path)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unsupported configuration store driver %s", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func initKeyProvider() {
|
||||||
|
path := os.Getenv("KEY_PATH")
|
||||||
|
if len(path) == 0 {
|
||||||
|
path = defaultKeyPath
|
||||||
|
}
|
||||||
|
log.Infof("key path: %s", path)
|
||||||
|
|
||||||
|
keyProvider = comcfg.NewFileKeyProvider(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
//load the configurations from env
|
||||||
|
func loadFromEnv() (map[string]interface{}, error) {
|
||||||
|
cfg := map[string]interface{}{}
|
||||||
|
|
||||||
|
for k, v := range envs {
|
||||||
|
if str, ok := v.(string); ok {
|
||||||
|
cfg[k] = os.Getenv(str)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if parser, ok := v.(*parser); ok {
|
||||||
|
i, err := parser.parse(os.Getenv(parser.env))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg[k] = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("%v is not string or parse type", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemCfg returns the system configurations
|
||||||
|
func GetSystemCfg() (map[string]interface{}, error) {
|
||||||
|
m, err := cfgStore.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := keyProvider.Get(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = decrypt(m, attrs, key); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSystemCfg updates the system configurations
|
||||||
|
func UpdateSystemCfg(cfg map[string]interface{}) error {
|
||||||
|
|
||||||
|
key, err := keyProvider.Get(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := encrypt(cfg, attrs, key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfgStore.Write(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encrypt(m map[string]interface{}, keys []string, secretKey string) error {
|
||||||
|
for _, key := range keys {
|
||||||
|
v, ok := m[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.(string)) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherText, err := utils.ReversibleEncrypt(v.(string), secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m[key] = cipherText
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decrypt(m map[string]interface{}, keys []string, secretKey string) error {
|
||||||
|
for _, key := range keys {
|
||||||
|
v, ok := m[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.(string)) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
text, err := utils.ReversibleDecrypt(v.(string), secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m[key] = text
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
120
src/adminserver/systemcfg/systemcfg_test.go
Normal file
120
src/adminserver/systemcfg/systemcfg_test.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package systemcfg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
// test functions in adminserver/systemcfg/systemcfg.go
|
||||||
|
func TestSystemcfg(t *testing.T) {
|
||||||
|
configPath := "/tmp/config.json"
|
||||||
|
if _, err := os.Stat(configPath); err == nil {
|
||||||
|
if err := os.Remove(configPath); err != nil {
|
||||||
|
t.Errorf("failed to remove %s: %v", configPath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
t.Errorf("failed to check the existence of %s: %v", configPath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Setenv("JSON_CFG_STORE_PATH", configPath); err != nil {
|
||||||
|
t.Errorf("failed to set env: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPath := "/tmp/secretkey"
|
||||||
|
if _, err := test.GenerateKey(keyPath); err != nil {
|
||||||
|
t.Errorf("failed to generate key: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(keyPath)
|
||||||
|
|
||||||
|
if err := os.Setenv("KEY_PATH", keyPath); err != nil {
|
||||||
|
t.Errorf("failed to set env: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]string{
|
||||||
|
"AUTH_MODE": comcfg.DBAuth,
|
||||||
|
"LDAP_SCOPE": "1",
|
||||||
|
"LDAP_TIMEOUT": "30",
|
||||||
|
"MYSQL_PORT": "3306",
|
||||||
|
"MAX_JOB_WORKERS": "3",
|
||||||
|
"TOKEN_EXPIRATION": "30",
|
||||||
|
"CFG_EXPIRATION": "5",
|
||||||
|
"EMAIL_PORT": "25",
|
||||||
|
"MYSQL_PWD": "",
|
||||||
|
"LDAP_SEARCH_PWD": "",
|
||||||
|
"EMAIL_PWD": "",
|
||||||
|
"HARBOR_ADMIN_PASSWORD": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range m {
|
||||||
|
if err := os.Setenv(k, v); err != nil {
|
||||||
|
t.Fatalf("failed to set env %s: %v", k, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Init(); err != nil {
|
||||||
|
t.Errorf("failed to initialize system configurations: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
// run Init again to make sure it works well when the configuration file
|
||||||
|
// already exists
|
||||||
|
if err := Init(); err != nil {
|
||||||
|
t.Errorf("failed to initialize system configurations: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get system configurations: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg[comcfg.AUTHMode] != comcfg.DBAuth {
|
||||||
|
t.Errorf("unexpected auth mode: %s != %s",
|
||||||
|
cfg[comcfg.AUTHMode], comcfg.DBAuth)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg[comcfg.AUTHMode] = comcfg.LDAPAuth
|
||||||
|
|
||||||
|
if err = UpdateSystemCfg(cfg); err != nil {
|
||||||
|
t.Errorf("failed to update system configurations: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err = GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get system configurations: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg[comcfg.AUTHMode] != comcfg.LDAPAuth {
|
||||||
|
t.Errorf("unexpected auth mode: %s != %s",
|
||||||
|
cfg[comcfg.AUTHMode], comcfg.DBAuth)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/astaxie/beego/validation"
|
"github.com/astaxie/beego/validation"
|
||||||
"github.com/vmware/harbor/src/common/config"
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/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"
|
||||||
@ -210,8 +209,3 @@ func (b *BaseAPI) GetPaginationParams() (page, pageSize int64) {
|
|||||||
|
|
||||||
return page, pageSize
|
return page, pageSize
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIsInsecure ...
|
|
||||||
func GetIsInsecure() bool {
|
|
||||||
return !config.VerifyRemoteCert()
|
|
||||||
}
|
|
||||||
|
@ -13,21 +13,3 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/vmware/harbor/src/common/config"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetIsInsecure(t *testing.T) {
|
|
||||||
os.Setenv("VERIFY_REMOTE_CERT", "off")
|
|
||||||
err := config.Reload()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Failed to load config, error: %v", err)
|
|
||||||
}
|
|
||||||
if !GetIsInsecure() {
|
|
||||||
t.Errorf("GetIsInsecure() should be true when VERIFY_REMOTE_CERT is off, in fact: false")
|
|
||||||
}
|
|
||||||
os.Unsetenv("VERIFY_REMOTE_CERT")
|
|
||||||
}
|
|
||||||
|
@ -17,162 +17,249 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfLoader is the interface to load configurations
|
// const variables
|
||||||
type ConfLoader interface {
|
const (
|
||||||
// Load will load configuration from different source into a string map, the values in the map will be parsed in to configurations.
|
DBAuth = "db_auth"
|
||||||
Load() (map[string]string, error)
|
LDAPAuth = "ldap_auth"
|
||||||
|
ProCrtRestrEveryone = "everyone"
|
||||||
|
ProCrtRestrAdmOnly = "adminonly"
|
||||||
|
LDAPScopeBase = "1"
|
||||||
|
LDAPScopeOnelevel = "2"
|
||||||
|
LDAPScopeSubtree = "3"
|
||||||
|
|
||||||
|
ExtEndpoint = "ext_endpoint"
|
||||||
|
AUTHMode = "auth_mode"
|
||||||
|
DatabaseType = "database_type"
|
||||||
|
MySQLHost = "mysql_host"
|
||||||
|
MySQLPort = "mysql_port"
|
||||||
|
MySQLUsername = "mysql_username"
|
||||||
|
MySQLPassword = "mysql_password"
|
||||||
|
MySQLDatabase = "mysql_database"
|
||||||
|
SQLiteFile = "sqlite_file"
|
||||||
|
SelfRegistration = "self_registration"
|
||||||
|
LDAPURL = "ldap_url"
|
||||||
|
LDAPSearchDN = "ldap_search_dn"
|
||||||
|
LDAPSearchPwd = "ldap_search_password"
|
||||||
|
LDAPBaseDN = "ldap_base_dn"
|
||||||
|
LDAPUID = "ldap_uid"
|
||||||
|
LDAPFilter = "ldap_filter"
|
||||||
|
LDAPScope = "ldap_scope"
|
||||||
|
LDAPTimeout = "ldap_timeout"
|
||||||
|
TokenServiceURL = "token_service_url"
|
||||||
|
RegistryURL = "registry_url"
|
||||||
|
EmailHost = "email_host"
|
||||||
|
EmailPort = "email_port"
|
||||||
|
EmailUsername = "email_username"
|
||||||
|
EmailPassword = "email_password"
|
||||||
|
EmailFrom = "email_from"
|
||||||
|
EmailSSL = "email_ssl"
|
||||||
|
EmailIdentity = "email_identity"
|
||||||
|
ProjectCreationRestriction = "project_creation_restriction"
|
||||||
|
VerifyRemoteCert = "verify_remote_cert"
|
||||||
|
MaxJobWorkers = "max_job_workers"
|
||||||
|
TokenExpiration = "token_expiration"
|
||||||
|
CfgExpiration = "cfg_expiration"
|
||||||
|
JobLogDir = "job_log_dir"
|
||||||
|
UseCompressedJS = "use_compressed_js"
|
||||||
|
AdminInitialPassword = "admin_initial_password"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager manages configurations
|
||||||
|
type Manager struct {
|
||||||
|
Loader *Loader
|
||||||
|
Parser *Parser
|
||||||
|
Cache bool
|
||||||
|
cache cache.Cache
|
||||||
|
key string
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnvConfigLoader loads the config from env vars.
|
// NewManager returns an instance of Manager
|
||||||
type EnvConfigLoader struct {
|
// url: the url from which loader loads configurations
|
||||||
Keys []string
|
func NewManager(url, secret string, enableCache bool) *Manager {
|
||||||
}
|
m := &Manager{
|
||||||
|
Loader: NewLoader(url, secret),
|
||||||
// Load ...
|
Parser: &Parser{},
|
||||||
func (ec *EnvConfigLoader) Load() (map[string]string, error) {
|
|
||||||
m := make(map[string]string)
|
|
||||||
for _, k := range ec.Keys {
|
|
||||||
m[k] = os.Getenv(k)
|
|
||||||
}
|
}
|
||||||
return m, nil
|
|
||||||
|
if enableCache {
|
||||||
|
m.Cache = true
|
||||||
|
m.cache = cache.NewMemoryCache()
|
||||||
|
m.key = "cfg"
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfParser ...
|
// Init loader
|
||||||
type ConfParser interface {
|
func (m *Manager) Init() error {
|
||||||
|
return m.Loader.Init()
|
||||||
//Parse parse the input raw map into a config map
|
|
||||||
Parse(raw map[string]string, config map[string]interface{}) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config wraps a map for the processed configuration values,
|
// Load configurations, if cache is enabled, cache the configurations
|
||||||
// and loader parser to read configuration from external source and process the values.
|
func (m *Manager) Load() (map[string]interface{}, error) {
|
||||||
type Config struct {
|
b, err := m.Loader.Load()
|
||||||
Config map[string]interface{}
|
if err != nil {
|
||||||
Loader ConfLoader
|
return nil, err
|
||||||
Parser ConfParser
|
}
|
||||||
|
|
||||||
|
c, err := m.Parser.Parse(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Cache {
|
||||||
|
expi, err := getCfgExpiration(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = m.cache.Put(m.key, c,
|
||||||
|
time.Duration(expi)*time.Second); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load reload the configurations
|
func getCfgExpiration(m map[string]interface{}) (int, error) {
|
||||||
func (conf *Config) Load() error {
|
if m == nil {
|
||||||
rawMap, err := conf.Loader.Load()
|
return 0, fmt.Errorf("can not get cfg expiration as configurations are null")
|
||||||
|
}
|
||||||
|
|
||||||
|
expi, ok := m[CfgExpiration]
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("cfg expiration is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(expi.(float64)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get : if cache is enabled, read configurations from cache,
|
||||||
|
// if cache is null or cache is disabled it loads configurations directly
|
||||||
|
func (m *Manager) Get() (map[string]interface{}, error) {
|
||||||
|
if m.Cache {
|
||||||
|
c := m.cache.Get(m.key)
|
||||||
|
if c != nil {
|
||||||
|
return c.(map[string]interface{}), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload configurations
|
||||||
|
func (m *Manager) Upload(b []byte) error {
|
||||||
|
return m.Loader.Upload(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loader loads and uploads configurations
|
||||||
|
type Loader struct {
|
||||||
|
url string
|
||||||
|
secret string
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLoader ...
|
||||||
|
func NewLoader(url, secret string) *Loader {
|
||||||
|
return &Loader{
|
||||||
|
url: url,
|
||||||
|
secret: secret,
|
||||||
|
client: &http.Client{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init waits remote server to be ready by testing connections with it
|
||||||
|
func (l *Loader) Init() error {
|
||||||
|
addr := l.url
|
||||||
|
if strings.Contains(addr, "://") {
|
||||||
|
addr = strings.Split(addr, "://")[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(addr, ":") {
|
||||||
|
addr = addr + ":80"
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.TestTCPConn(addr, 60, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load configurations from remote server
|
||||||
|
func (l *Loader) Load() ([]byte, error) {
|
||||||
|
log.Debug("loading configurations...")
|
||||||
|
req, err := http.NewRequest("GET", l.url+"/api/configurations", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.AddCookie(&http.Cookie{
|
||||||
|
Name: "secret",
|
||||||
|
Value: l.secret,
|
||||||
|
})
|
||||||
|
|
||||||
|
resp, err := l.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("%d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debug("configurations load completed")
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload configuratons to remote server
|
||||||
|
func (l *Loader) Upload(b []byte) error {
|
||||||
|
req, err := http.NewRequest("PUT", l.url+"/api/configurations", bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = conf.Parser.Parse(rawMap, conf.Config)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MySQLSetting wraps the settings of a MySQL DB
|
req.AddCookie(&http.Cookie{
|
||||||
type MySQLSetting struct {
|
Name: "secret",
|
||||||
Database string
|
Value: l.secret,
|
||||||
User string
|
})
|
||||||
Password string
|
|
||||||
Host string
|
|
||||||
Port string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SQLiteSetting wraps the settings of a SQLite DB
|
resp, err := l.client.Do(req)
|
||||||
type SQLiteSetting struct {
|
if err != nil {
|
||||||
FilePath string
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
type commonParser struct{}
|
|
||||||
|
|
||||||
// Parse parses the db settings, veryfy_remote_cert, ext_endpoint, token_endpoint
|
|
||||||
func (cp *commonParser) Parse(raw map[string]string, config map[string]interface{}) error {
|
|
||||||
db := strings.ToLower(raw["DATABASE"])
|
|
||||||
if db == "mysql" || db == "" {
|
|
||||||
db = "mysql"
|
|
||||||
mySQLDB := raw["MYSQL_DATABASE"]
|
|
||||||
if len(mySQLDB) == 0 {
|
|
||||||
mySQLDB = "registry"
|
|
||||||
}
|
|
||||||
setting := MySQLSetting{
|
|
||||||
mySQLDB,
|
|
||||||
raw["MYSQL_USR"],
|
|
||||||
raw["MYSQL_PWD"],
|
|
||||||
raw["MYSQL_HOST"],
|
|
||||||
raw["MYSQL_PORT"],
|
|
||||||
}
|
|
||||||
config["mysql"] = setting
|
|
||||||
} else if db == "sqlite" {
|
|
||||||
f := raw["SQLITE_FILE"]
|
|
||||||
if len(f) == 0 {
|
|
||||||
f = "registry.db"
|
|
||||||
}
|
|
||||||
setting := SQLiteSetting{
|
|
||||||
f,
|
|
||||||
}
|
|
||||||
config["sqlite"] = setting
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("Invalid DB: %s", db)
|
|
||||||
}
|
}
|
||||||
config["database"] = db
|
|
||||||
|
|
||||||
//By default it's true
|
if resp.StatusCode != http.StatusOK {
|
||||||
config["verify_remote_cert"] = raw["VERIFY_REMOTE_CERT"] != "off"
|
return fmt.Errorf("unexpected http status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("configurations uploaded")
|
||||||
|
|
||||||
config["ext_endpoint"] = raw["EXT_ENDPOINT"]
|
|
||||||
config["token_endpoint"] = raw["TOKEN_ENDPOINT"]
|
|
||||||
config["log_level"] = raw["LOG_LEVEL"]
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var commonConfig *Config
|
// Parser parses configurations
|
||||||
|
type Parser struct {
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
// Parse parses []byte to a map configuration
|
||||||
commonKeys := []string{"DATABASE", "MYSQL_DATABASE", "MYSQL_USR", "MYSQL_PWD", "MYSQL_HOST", "MYSQL_PORT", "SQLITE_FILE", "VERIFY_REMOTE_CERT", "EXT_ENDPOINT", "TOKEN_ENDPOINT", "LOG_LEVEL"}
|
func (p *Parser) Parse(b []byte) (map[string]interface{}, error) {
|
||||||
commonConfig = &Config{
|
c := map[string]interface{}{}
|
||||||
Config: make(map[string]interface{}),
|
if err := json.Unmarshal(b, &c); err != nil {
|
||||||
Loader: &EnvConfigLoader{Keys: commonKeys},
|
return nil, err
|
||||||
Parser: &commonParser{},
|
|
||||||
}
|
|
||||||
if err := commonConfig.Load(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
}
|
return c, nil
|
||||||
|
|
||||||
// Reload will reload the configuration.
|
|
||||||
func Reload() error {
|
|
||||||
return commonConfig.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Database returns the DB type in configuration.
|
|
||||||
func Database() string {
|
|
||||||
return commonConfig.Config["database"].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MySQL returns the mysql setting in configuration.
|
|
||||||
func MySQL() MySQLSetting {
|
|
||||||
return commonConfig.Config["mysql"].(MySQLSetting)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SQLite returns the SQLite setting
|
|
||||||
func SQLite() SQLiteSetting {
|
|
||||||
return commonConfig.Config["sqlite"].(SQLiteSetting)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyRemoteCert returns bool value.
|
|
||||||
func VerifyRemoteCert() bool {
|
|
||||||
return commonConfig.Config["verify_remote_cert"].(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtEndpoint ...
|
|
||||||
func ExtEndpoint() string {
|
|
||||||
return commonConfig.Config["ext_endpoint"].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TokenEndpoint returns the endpoint string of token service, which can be accessed by internal service of Harbor.
|
|
||||||
func TokenEndpoint() string {
|
|
||||||
return commonConfig.Config["token_endpoint"].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogLevel returns the log level in string format.
|
|
||||||
func LogLevel() string {
|
|
||||||
return commonConfig.Config["log_level"].(string)
|
|
||||||
}
|
}
|
||||||
|
@ -14,98 +14,5 @@
|
|||||||
*/
|
*/
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
// the functions in common/config/config.go have been tested
|
||||||
"os"
|
// by cases in UI and Jobservice
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEnvConfLoader(t *testing.T) {
|
|
||||||
os.Unsetenv("KEY2")
|
|
||||||
os.Setenv("KEY1", "V1")
|
|
||||||
os.Setenv("KEY3", "V3")
|
|
||||||
keys := []string{"KEY1", "KEY2"}
|
|
||||||
ecl := EnvConfigLoader{
|
|
||||||
keys,
|
|
||||||
}
|
|
||||||
m, err := ecl.Load()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error loading the configuration via env: %v", err)
|
|
||||||
}
|
|
||||||
if m["KEY1"] != "V1" {
|
|
||||||
t.Errorf("The value for key KEY1 should be V1, but infact: %s", m["KEY1"])
|
|
||||||
}
|
|
||||||
if len(m["KEY2"]) > 0 {
|
|
||||||
t.Errorf("The value for key KEY2 should be emptye, but infact: %s", m["KEY2"])
|
|
||||||
}
|
|
||||||
if _, ok := m["KEY3"]; ok {
|
|
||||||
t.Errorf("The KEY3 should not be in result as it's not in the initial key list")
|
|
||||||
}
|
|
||||||
os.Unsetenv("KEY1")
|
|
||||||
os.Unsetenv("KEY3")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCommonConfig(t *testing.T) {
|
|
||||||
|
|
||||||
mysql := MySQLSetting{"registry", "root", "password", "127.0.0.1", "3306"}
|
|
||||||
sqlite := SQLiteSetting{"file.db"}
|
|
||||||
verify := "off"
|
|
||||||
ext := "http://harbor"
|
|
||||||
token := "http://token"
|
|
||||||
loglevel := "info"
|
|
||||||
|
|
||||||
os.Setenv("DATABASE", "")
|
|
||||||
os.Setenv("MYSQL_DATABASE", mysql.Database)
|
|
||||||
os.Setenv("MYSQL_USR", mysql.User)
|
|
||||||
os.Setenv("MYSQL_PWD", mysql.Password)
|
|
||||||
os.Setenv("MYSQL_HOST", mysql.Host)
|
|
||||||
os.Setenv("MYSQL_PORT", mysql.Port)
|
|
||||||
os.Setenv("SQLITE_FILE", sqlite.FilePath)
|
|
||||||
os.Setenv("VERIFY_REMOTE_CERT", verify)
|
|
||||||
os.Setenv("EXT_ENDPOINT", ext)
|
|
||||||
os.Setenv("TOKEN_ENDPOINT", token)
|
|
||||||
os.Setenv("LOG_LEVEL", loglevel)
|
|
||||||
|
|
||||||
err := Reload()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error when loading the configurations, error: %v", err)
|
|
||||||
}
|
|
||||||
if Database() != "mysql" {
|
|
||||||
t.Errorf("Expected Database value: mysql, fact: %s", mysql)
|
|
||||||
}
|
|
||||||
if MySQL() != mysql {
|
|
||||||
t.Errorf("Expected MySQL setting: %+v, fact: %+v", mysql, MySQL())
|
|
||||||
}
|
|
||||||
if VerifyRemoteCert() {
|
|
||||||
t.Errorf("Expected VerifyRemoteCert: false, env var: %s, fact: %v", verify, VerifyRemoteCert())
|
|
||||||
}
|
|
||||||
if ExtEndpoint() != ext {
|
|
||||||
t.Errorf("Expected ExtEndpoint: %s, fact: %s", ext, ExtEndpoint())
|
|
||||||
}
|
|
||||||
if TokenEndpoint() != token {
|
|
||||||
t.Errorf("Expected TokenEndpoint: %s, fact: %s", token, TokenEndpoint())
|
|
||||||
}
|
|
||||||
if LogLevel() != loglevel {
|
|
||||||
t.Errorf("Expected LogLevel: %s, fact: %s", loglevel, LogLevel())
|
|
||||||
}
|
|
||||||
os.Setenv("DATABASE", "sqlite")
|
|
||||||
err = Reload()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error when loading the configurations, error: %v", err)
|
|
||||||
}
|
|
||||||
if SQLite() != sqlite {
|
|
||||||
t.Errorf("Expected SQLite setting: %+v, fact %+v", sqlite, SQLite())
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Unsetenv("DATABASE")
|
|
||||||
os.Unsetenv("MYSQL_DATABASE")
|
|
||||||
os.Unsetenv("MYSQL_USR")
|
|
||||||
os.Unsetenv("MYSQL_PWD")
|
|
||||||
os.Unsetenv("MYSQL_HOST")
|
|
||||||
os.Unsetenv("MYSQL_PORT")
|
|
||||||
os.Unsetenv("SQLITE_FILE")
|
|
||||||
os.Unsetenv("VERIFY_REMOTE_CERT")
|
|
||||||
os.Unsetenv("EXT_ENDPOINT")
|
|
||||||
os.Unsetenv("TOKEN_ENDPOINT")
|
|
||||||
os.Unsetenv("LOG_LEVEL")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
47
src/common/config/keyprovider.go
Normal file
47
src/common/config/keyprovider.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyProvider provides the key used to encrypt and decrypt attrs
|
||||||
|
type KeyProvider interface {
|
||||||
|
// Get returns the key
|
||||||
|
// params can be used to pass parameters in different implements
|
||||||
|
Get(params map[string]interface{}) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileKeyProvider reads key from file
|
||||||
|
type FileKeyProvider struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileKeyProvider returns an instance of FileKeyProvider
|
||||||
|
// path: where the key should be read from
|
||||||
|
func NewFileKeyProvider(path string) KeyProvider {
|
||||||
|
return &FileKeyProvider{
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the key read from file
|
||||||
|
func (f *FileKeyProvider) Get(params map[string]interface{}) (string, error) {
|
||||||
|
b, err := ioutil.ReadFile(f.path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
43
src/common/config/keyprovider_test.go
Normal file
43
src/common/config/keyprovider_test.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetOfFileKeyProvider(t *testing.T) {
|
||||||
|
path := "/tmp/key"
|
||||||
|
key := "key_content"
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(path, []byte(key), 0777); err != nil {
|
||||||
|
t.Errorf("failed to write to file %s: %v", path, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(path)
|
||||||
|
|
||||||
|
provider := NewFileKeyProvider(path)
|
||||||
|
k, err := provider.Get(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get key from the file provider: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if k != key {
|
||||||
|
t.Errorf("unexpected key: %s != %s", k, key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
@ -17,11 +17,12 @@ package dao
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
"github.com/vmware/harbor/src/common/config"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,27 +40,32 @@ type Database interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitDatabase initializes the database
|
// InitDatabase initializes the database
|
||||||
func InitDatabase() {
|
func InitDatabase(database *models.Database) error {
|
||||||
database, err := getDatabase()
|
db, err := getDatabase(database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("initializing database: %s", database.String())
|
log.Infof("initializing database: %s", db.String())
|
||||||
if err := database.Register(); err != nil {
|
if err := db.Register(); err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
log.Info("initialize database completed")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDatabase() (db Database, err error) {
|
func getDatabase(database *models.Database) (db Database, err error) {
|
||||||
switch config.Database() {
|
switch database.Type {
|
||||||
case "", "mysql":
|
case "", "mysql":
|
||||||
db = NewMySQL(config.MySQL().Host, config.MySQL().Port, config.MySQL().User,
|
db = NewMySQL(database.MySQL.Host,
|
||||||
config.MySQL().Password, config.MySQL().Database)
|
strconv.Itoa(database.MySQL.Port),
|
||||||
|
database.MySQL.Username,
|
||||||
|
database.MySQL.Password,
|
||||||
|
database.MySQL.Database)
|
||||||
case "sqlite":
|
case "sqlite":
|
||||||
db = NewSQLite(config.SQLite().FilePath)
|
db = NewSQLite(database.SQLite.File)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("invalid database: %s", config.Database())
|
err = fmt.Errorf("invalid database: %s", database.Type)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
32
src/common/dao/config.go
Normal file
32
src/common/dao/config.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthModeCanBeModified determines whether auth mode can be
|
||||||
|
// modified or not. Auth mode can modified when there is only admin
|
||||||
|
// user in database.
|
||||||
|
func AuthModeCanBeModified() (bool, error) {
|
||||||
|
c, err := GetOrmer().QueryTable(&models.User{}).Count()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
// admin and anonymous
|
||||||
|
return c == 2, nil
|
||||||
|
}
|
72
src/common/dao/config_test.go
Normal file
72
src/common/dao/config_test.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuthModeCanBeModified(t *testing.T) {
|
||||||
|
c, err := GetOrmer().QueryTable(&models.User{}).Count()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to count users: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == 2 {
|
||||||
|
flag, err := AuthModeCanBeModified()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to determine whether auth mode can be modified: %v", err)
|
||||||
|
}
|
||||||
|
if !flag {
|
||||||
|
t.Errorf("unexpected result: %t != %t", flag, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
user := models.User{
|
||||||
|
Username: "user_for_config_test",
|
||||||
|
Email: "user_for_config_test@vmware.com",
|
||||||
|
Password: "P@ssword",
|
||||||
|
Realname: "user_for_config_test",
|
||||||
|
}
|
||||||
|
id, err := Register(user)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to register user: %v", err)
|
||||||
|
}
|
||||||
|
defer func(id int64) {
|
||||||
|
if err := deleteUser(id); err != nil {
|
||||||
|
t.Fatalf("failed to delete user %d: %v", id, err)
|
||||||
|
}
|
||||||
|
}(id)
|
||||||
|
|
||||||
|
flag, err = AuthModeCanBeModified()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to determine whether auth mode can be modified: %v", err)
|
||||||
|
}
|
||||||
|
if flag {
|
||||||
|
t.Errorf("unexpected result: %t != %t", flag, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
flag, err := AuthModeCanBeModified()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to determine whether auth mode can be modified: %v", err)
|
||||||
|
}
|
||||||
|
if flag {
|
||||||
|
t.Errorf("unexpected result: %t != %t", flag, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,10 +17,12 @@ package dao
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
|
//"github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/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"
|
||||||
@ -42,7 +44,7 @@ func execUpdate(o orm.Ormer, sql string, params ...interface{}) error {
|
|||||||
func clearUp(username string) {
|
func clearUp(username string) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
o.Begin()
|
o.Begin()
|
||||||
|
|
||||||
err = execUpdate(o, `delete
|
err = execUpdate(o, `delete
|
||||||
@ -156,53 +158,63 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testForMySQL(m *testing.M) int {
|
func testForMySQL(m *testing.M) int {
|
||||||
db := os.Getenv("DATABASE")
|
dbHost := os.Getenv("MYSQL_HOST")
|
||||||
defer os.Setenv("DATABASE", db)
|
|
||||||
|
|
||||||
os.Setenv("DATABASE", "mysql")
|
|
||||||
|
|
||||||
dbHost := os.Getenv("DB_HOST")
|
|
||||||
if len(dbHost) == 0 {
|
if len(dbHost) == 0 {
|
||||||
log.Fatalf("environment variable DB_HOST is not set")
|
log.Fatalf("environment variable MYSQL_HOST is not set")
|
||||||
}
|
}
|
||||||
dbUser := os.Getenv("DB_USR")
|
dbUser := os.Getenv("MYSQL_USR")
|
||||||
if len(dbUser) == 0 {
|
if len(dbUser) == 0 {
|
||||||
log.Fatalf("environment variable DB_USR is not set")
|
log.Fatalf("environment variable MYSQL_USR is not set")
|
||||||
}
|
}
|
||||||
dbPort := os.Getenv("DB_PORT")
|
dbPortStr := os.Getenv("MYSQL_PORT")
|
||||||
if len(dbPort) == 0 {
|
if len(dbPortStr) == 0 {
|
||||||
log.Fatalf("environment variable DB_PORT is not set")
|
log.Fatalf("environment variable MYSQL_PORT is not set")
|
||||||
|
}
|
||||||
|
dbPort, err := strconv.Atoi(dbPortStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("invalid MYSQL_PORT: %v", err)
|
||||||
}
|
}
|
||||||
dbPassword := os.Getenv("DB_PWD")
|
|
||||||
|
|
||||||
log.Infof("DB_HOST: %s, DB_USR: %s, DB_PORT: %s, DB_PWD: %s\n", dbHost, dbUser, dbPort, dbPassword)
|
dbPassword := os.Getenv("MYSQL_PWD")
|
||||||
|
dbDatabase := os.Getenv("MYSQL_DATABASE")
|
||||||
|
if len(dbDatabase) == 0 {
|
||||||
|
log.Fatalf("environment variable MYSQL_DATABASE is not set")
|
||||||
|
}
|
||||||
|
|
||||||
os.Setenv("MYSQL_HOST", dbHost)
|
database := &models.Database{
|
||||||
os.Setenv("MYSQL_PORT", dbPort)
|
Type: "mysql",
|
||||||
os.Setenv("MYSQL_USR", dbUser)
|
MySQL: &models.MySQL{
|
||||||
os.Setenv("MYSQL_PWD", dbPassword)
|
Host: dbHost,
|
||||||
|
Port: dbPort,
|
||||||
|
Username: dbUser,
|
||||||
|
Password: dbPassword,
|
||||||
|
Database: dbDatabase,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
return testForAll(m)
|
log.Infof("MYSQL_HOST: %s, MYSQL_USR: %s, MYSQL_PORT: %s, MYSQL_PWD: %s\n", dbHost, dbUser, dbPort, dbPassword)
|
||||||
|
|
||||||
|
return testForAll(m, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testForSQLite(m *testing.M) int {
|
func testForSQLite(m *testing.M) int {
|
||||||
db := os.Getenv("DATABASE")
|
|
||||||
defer os.Setenv("DATABASE", db)
|
|
||||||
|
|
||||||
os.Setenv("DATABASE", "sqlite")
|
|
||||||
|
|
||||||
file := os.Getenv("SQLITE_FILE")
|
file := os.Getenv("SQLITE_FILE")
|
||||||
if len(file) == 0 {
|
if len(file) == 0 {
|
||||||
os.Setenv("SQLITE_FILE", "/registry.db")
|
log.Fatalf("environment variable SQLITE_FILE is not set")
|
||||||
defer os.Setenv("SQLITE_FILE", "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return testForAll(m)
|
database := &models.Database{
|
||||||
|
Type: "sqlite",
|
||||||
|
SQLite: &models.SQLite{
|
||||||
|
File: file,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return testForAll(m, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testForAll(m *testing.M) int {
|
func testForAll(m *testing.M, database *models.Database) int {
|
||||||
os.Setenv("AUTH_MODE", "db_auth")
|
initDatabaseForTest(database)
|
||||||
initDatabaseForTest()
|
|
||||||
clearUp(username)
|
clearUp(username)
|
||||||
|
|
||||||
return m.Run()
|
return m.Run()
|
||||||
@ -210,8 +222,8 @@ func testForAll(m *testing.M) int {
|
|||||||
|
|
||||||
var defaultRegistered = false
|
var defaultRegistered = false
|
||||||
|
|
||||||
func initDatabaseForTest() {
|
func initDatabaseForTest(db *models.Database) {
|
||||||
database, err := getDatabase()
|
database, err := getDatabase(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -226,6 +238,12 @@ func initDatabaseForTest() {
|
|||||||
if err := database.Register(alias); err != nil {
|
if err := database.Register(alias); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if alias != "default" {
|
||||||
|
if err = globalOrm.Using(alias); err != nil {
|
||||||
|
log.Fatalf("failed to create new orm: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegister(t *testing.T) {
|
func TestRegister(t *testing.T) {
|
||||||
|
@ -16,15 +16,11 @@
|
|||||||
package dao
|
package dao
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
_ "github.com/go-sql-driver/mysql" //register mysql driver
|
_ "github.com/go-sql-driver/mysql" //register mysql driver
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mysql struct {
|
type mysql struct {
|
||||||
@ -48,7 +44,8 @@ func NewMySQL(host, port, usr, pwd, database string) Database {
|
|||||||
|
|
||||||
// Register registers MySQL as the underlying database used
|
// Register registers MySQL as the underlying database used
|
||||||
func (m *mysql) Register(alias ...string) error {
|
func (m *mysql) Register(alias ...string) error {
|
||||||
if err := m.testConn(m.host, m.port); err != nil {
|
|
||||||
|
if err := utils.TestTCPConn(m.host+":"+m.port, 60, 2); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,30 +62,6 @@ func (m *mysql) Register(alias ...string) error {
|
|||||||
return orm.RegisterDataBase(an, "mysql", conn)
|
return orm.RegisterDataBase(an, "mysql", conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mysql) testConn(host, port string) error {
|
|
||||||
ch := make(chan int, 1)
|
|
||||||
go func() {
|
|
||||||
var err error
|
|
||||||
var c net.Conn
|
|
||||||
for {
|
|
||||||
c, err = net.DialTimeout("tcp", host+":"+port, 20*time.Second)
|
|
||||||
if err == nil {
|
|
||||||
c.Close()
|
|
||||||
ch <- 1
|
|
||||||
} else {
|
|
||||||
log.Errorf("failed to connect to db, retry after 2 seconds :%v", err)
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-ch:
|
|
||||||
return nil
|
|
||||||
case <-time.After(60 * time.Second):
|
|
||||||
return errors.New("failed to connect to database after 60 seconds")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of MySQL
|
// Name returns the name of MySQL
|
||||||
func (m *mysql) Name() string {
|
func (m *mysql) Name() string {
|
||||||
return "MySQL"
|
return "MySQL"
|
||||||
|
@ -38,6 +38,11 @@ func TestDeleteUser(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to register user: %v", err)
|
t.Fatalf("failed to register user: %v", err)
|
||||||
}
|
}
|
||||||
|
defer func(id int64) {
|
||||||
|
if err := deleteUser(id); err != nil {
|
||||||
|
t.Fatalf("failed to delete user %d: %v", id, err)
|
||||||
|
}
|
||||||
|
}(id)
|
||||||
|
|
||||||
err = DeleteUser(int(id))
|
err = DeleteUser(int(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -67,3 +72,11 @@ func TestDeleteUser(t *testing.T) {
|
|||||||
expected)
|
expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteUser(id int64) error {
|
||||||
|
if _, err := GetOrmer().QueryTable(&models.User{}).
|
||||||
|
Filter("UserID", id).Delete(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
100
src/common/models/config.go
Normal file
100
src/common/models/config.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Authentication ...
|
||||||
|
type Authentication struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
SelfRegistration bool `json:"self_registration"`
|
||||||
|
LDAP *LDAP `json:"ldap,omitempty"`
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// LDAP ...
|
||||||
|
type LDAP struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
SearchDN string `json:"search_dn"`
|
||||||
|
SearchPassword string `json:"search_password"`
|
||||||
|
BaseDN string `json:"base_dn"`
|
||||||
|
Filter string `json:"filter"`
|
||||||
|
UID string `json:"uid"`
|
||||||
|
Scope int `json:"scope"`
|
||||||
|
Timeout int `json:"timeout"` // in second
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database ...
|
||||||
|
type Database struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
MySQL *MySQL `json:"mysql,omitempty"`
|
||||||
|
SQLite *SQLite `json:"sqlite,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQL ...
|
||||||
|
type MySQL struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
Database string `json:"database"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SQLite ...
|
||||||
|
type SQLite struct {
|
||||||
|
File string `json:"file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email ...
|
||||||
|
type Email struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
SSL bool `json:"ssl"`
|
||||||
|
Identity string `json:"identity"`
|
||||||
|
From string `json:"from"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Registry ...
|
||||||
|
type Registry struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenService ...
|
||||||
|
type TokenService struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemCfg holds all configurations of system
|
||||||
|
type SystemCfg struct {
|
||||||
|
DomainName string `json:"domain_name"` // Harbor external URL: protocal://host:port
|
||||||
|
Authentication *Authentication `json:"authentication"`
|
||||||
|
Database *Database `json:"database"`
|
||||||
|
TokenService *TokenService `json:"token_service"`
|
||||||
|
Registry *Registry `json:"registry"`
|
||||||
|
Email *Email `json:"email"`
|
||||||
|
VerifyRemoteCert bool `json:"verify_remote_cert"`
|
||||||
|
ProjectCreationRestriction string `json:"project_creation_restriction"`
|
||||||
|
MaxJobWorkers int `json:"max_job_workers"`
|
||||||
|
JobLogDir string `json:"job_log_dir"`
|
||||||
|
InitialAdminPwd string `json:"initial_admin_pwd,omitempty"`
|
||||||
|
CompressJS bool `json:"compress_js"` //TODO remove
|
||||||
|
TokenExpiration int `json:"token_expiration"` // in minute
|
||||||
|
SecretKey string `json:"secret_key,omitempty"`
|
||||||
|
CfgExpiration int `json:"cfg_expiration"`
|
||||||
|
}
|
||||||
|
*/
|
28
src/common/models/ldap.go
Normal file
28
src/common/models/ldap.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
// LdapConf holds information about repository that accessed most
|
||||||
|
type LdapConf struct {
|
||||||
|
LdapURL string `json:"ldap_url"`
|
||||||
|
LdapSearchDn string `json:"ldap_search_dn"`
|
||||||
|
LdapSearchPassword string `json:"ldap_search_password"`
|
||||||
|
LdapBaseDn string `json:"ldap_base_dn"`
|
||||||
|
LdapFilter string `json:"ldap_filter"`
|
||||||
|
LdapUID string `json:"ldap_uid"`
|
||||||
|
LdapScope int `json:"ldap_scope"`
|
||||||
|
LdapConnectionTimeout int `json:"ldap_connection_timeout"`
|
||||||
|
}
|
@ -44,7 +44,7 @@ const (
|
|||||||
//RepOpDelete represents the operation of a job to remove repository from a remote registry/harbor instance.
|
//RepOpDelete represents the operation of a job to remove repository from a remote registry/harbor instance.
|
||||||
RepOpDelete string = "delete"
|
RepOpDelete string = "delete"
|
||||||
//UISecretCookie is the cookie name to contain the UI secret
|
//UISecretCookie is the cookie name to contain the UI secret
|
||||||
UISecretCookie string = "uisecret"
|
UISecretCookie string = "secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RepPolicy is the model for a replication policy, which associate to a project and a target (destination)
|
// RepPolicy is the model for a replication policy, which associate to a project and a target (destination)
|
||||||
|
@ -13,17 +13,20 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package utils
|
package email
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"strings"
|
"strconv"
|
||||||
|
//"strings"
|
||||||
|
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
//"github.com/astaxie/beego"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mail holds information about content of Email
|
// Mail holds information about content of Email
|
||||||
@ -34,24 +37,15 @@ type Mail struct {
|
|||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailConfig holds information about Email configurations
|
var mc models.Email
|
||||||
type MailConfig struct {
|
|
||||||
Identity string
|
|
||||||
Host string
|
|
||||||
Port string
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
TLS bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var mc MailConfig
|
|
||||||
|
|
||||||
// SendMail sends Email according to the configurations
|
// SendMail sends Email according to the configurations
|
||||||
func (m Mail) SendMail() error {
|
func (m Mail) SendMail() error {
|
||||||
|
mc, err := config.Email()
|
||||||
if mc.Host == "" {
|
if err != nil {
|
||||||
loadConfig()
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mailTemplate, err := template.ParseFiles("views/mail.tpl")
|
mailTemplate, err := template.ParseFiles("views/mail.tpl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -64,7 +58,7 @@ func (m Mail) SendMail() error {
|
|||||||
content := mailContent.Bytes()
|
content := mailContent.Bytes()
|
||||||
|
|
||||||
auth := smtp.PlainAuth(mc.Identity, mc.Username, mc.Password, mc.Host)
|
auth := smtp.PlainAuth(mc.Identity, mc.Username, mc.Password, mc.Host)
|
||||||
if mc.TLS {
|
if mc.SSL {
|
||||||
err = sendMailWithTLS(m, auth, content)
|
err = sendMailWithTLS(m, auth, content)
|
||||||
} else {
|
} else {
|
||||||
err = sendMail(m, auth, content)
|
err = sendMail(m, auth, content)
|
||||||
@ -74,11 +68,11 @@ func (m Mail) SendMail() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendMail(m Mail, auth smtp.Auth, content []byte) error {
|
func sendMail(m Mail, auth smtp.Auth, content []byte) error {
|
||||||
return smtp.SendMail(mc.Host+":"+mc.Port, auth, m.From, m.To, content)
|
return smtp.SendMail(mc.Host+":"+strconv.Itoa(mc.Port), auth, m.From, m.To, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMailWithTLS(m Mail, auth smtp.Auth, content []byte) error {
|
func sendMailWithTLS(m Mail, auth smtp.Auth, content []byte) error {
|
||||||
conn, err := tls.Dial("tcp", mc.Host+":"+mc.Port, nil)
|
conn, err := tls.Dial("tcp", mc.Host+":"+strconv.Itoa(mc.Port), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -123,6 +117,7 @@ func sendMailWithTLS(m Mail, auth smtp.Auth, content []byte) error {
|
|||||||
return client.Quit()
|
return client.Quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func loadConfig() {
|
func loadConfig() {
|
||||||
config, err := beego.AppConfig.GetSection("mail")
|
config, err := beego.AppConfig.GetSection("mail")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -142,3 +137,4 @@ func loadConfig() {
|
|||||||
TLS: useTLS,
|
TLS: useTLS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
@ -22,8 +22,6 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger = New(os.Stdout, NewTextFormatter(), WarningLevel)
|
var logger = New(os.Stdout, NewTextFormatter(), WarningLevel)
|
||||||
@ -31,7 +29,7 @@ var logger = New(os.Stdout, NewTextFormatter(), WarningLevel)
|
|||||||
func init() {
|
func init() {
|
||||||
logger.callDepth = 4
|
logger.callDepth = 4
|
||||||
|
|
||||||
lvl := config.LogLevel()
|
lvl := os.Getenv("LOG_LEVEL")
|
||||||
if len(lvl) == 0 {
|
if len(lvl) == 0 {
|
||||||
logger.SetLevel(InfoLevel)
|
logger.SetLevel(InfoLevel)
|
||||||
return
|
return
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/config"
|
//"github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/common/utils/registry"
|
"github.com/vmware/harbor/src/common/utils/registry"
|
||||||
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
||||||
@ -133,19 +133,24 @@ func (t *tokenAuthorizer) updateCachedToken(token string, expiresIn int) {
|
|||||||
// Implements interface Authorizer
|
// Implements interface Authorizer
|
||||||
type standardTokenAuthorizer struct {
|
type standardTokenAuthorizer struct {
|
||||||
tokenAuthorizer
|
tokenAuthorizer
|
||||||
client *http.Client
|
client *http.Client
|
||||||
credential Credential
|
credential Credential
|
||||||
|
tokenServiceEndpoint string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStandardTokenAuthorizer returns a standard token authorizer. The authorizer will request a token
|
// NewStandardTokenAuthorizer returns a standard token authorizer. The authorizer will request a token
|
||||||
// from token server and add it to the origin request
|
// from token server and add it to the origin request
|
||||||
func NewStandardTokenAuthorizer(credential Credential, insecure bool, scopeType, scopeName string, scopeActions ...string) Authorizer {
|
// If tokenServiceEndpoint is set, the token request will be sent to it instead of the server get from authorizer
|
||||||
|
// The usage please refer to the function tokenURL
|
||||||
|
func NewStandardTokenAuthorizer(credential Credential, insecure bool,
|
||||||
|
tokenServiceEndpoint string, scopeType, scopeName string, scopeActions ...string) Authorizer {
|
||||||
authorizer := &standardTokenAuthorizer{
|
authorizer := &standardTokenAuthorizer{
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Transport: registry.GetHTTPTransport(insecure),
|
Transport: registry.GetHTTPTransport(insecure),
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
},
|
},
|
||||||
credential: credential,
|
credential: credential,
|
||||||
|
tokenServiceEndpoint: tokenServiceEndpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(scopeType) != 0 || len(scopeName) != 0 {
|
if len(scopeType) != 0 || len(scopeName) != 0 {
|
||||||
@ -162,7 +167,7 @@ func NewStandardTokenAuthorizer(credential Credential, insecure bool, scopeType,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
||||||
realm = tokenURL(realm)
|
realm = s.tokenURL(realm)
|
||||||
|
|
||||||
u, err := url.Parse(realm)
|
u, err := url.Parse(realm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -229,16 +234,14 @@ func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []
|
|||||||
|
|
||||||
// when the registry client is used inside Harbor, the token request
|
// when the registry client is used inside Harbor, the token request
|
||||||
// can be posted to token service directly rather than going through nginx.
|
// can be posted to token service directly rather than going through nginx.
|
||||||
// this solution can resolve two problems:
|
// If realm is set as the internal url of token service, this can resolve
|
||||||
|
// two problems:
|
||||||
// 1. performance issue
|
// 1. performance issue
|
||||||
// 2. the realm field returned by registry is an IP which can not reachable
|
// 2. the realm field returned by registry is an IP which can not reachable
|
||||||
// inside Harbor
|
// inside Harbor
|
||||||
func tokenURL(realm string) string {
|
func (s *standardTokenAuthorizer) tokenURL(realm string) string {
|
||||||
extEndpoint := config.ExtEndpoint()
|
if len(s.tokenServiceEndpoint) != 0 {
|
||||||
tokenEndpoint := config.TokenEndpoint()
|
return s.tokenServiceEndpoint
|
||||||
if len(extEndpoint) != 0 && len(tokenEndpoint) != 0 &&
|
|
||||||
strings.Contains(realm, extEndpoint) {
|
|
||||||
realm = strings.TrimRight(tokenEndpoint, "/") + "/service/token"
|
|
||||||
}
|
}
|
||||||
return realm
|
return realm
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) {
|
|||||||
})
|
})
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
authorizer := NewStandardTokenAuthorizer(nil, false, "repository", "library/ubuntu", "pull")
|
authorizer := NewStandardTokenAuthorizer(nil, false, "", "repository", "library/ubuntu", "pull")
|
||||||
req, err := http.NewRequest("GET", "http://registry", nil)
|
req, err := http.NewRequest("GET", "http://registry", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create request: %v", err)
|
t.Fatalf("failed to create request: %v", err)
|
||||||
|
96
src/common/utils/test/adminserver.go
Normal file
96
src/common/utils/test/adminserver.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var adminServerDefaultConfig = map[string]interface{}{
|
||||||
|
config.ExtEndpoint: "host01.com",
|
||||||
|
config.AUTHMode: config.DBAuth,
|
||||||
|
config.DatabaseType: "mysql",
|
||||||
|
config.MySQLHost: "127.0.0.1",
|
||||||
|
config.MySQLPort: 3306,
|
||||||
|
config.MySQLUsername: "user01",
|
||||||
|
config.MySQLPassword: "password",
|
||||||
|
config.MySQLDatabase: "registry",
|
||||||
|
config.SQLiteFile: "/tmp/registry.db",
|
||||||
|
config.SelfRegistration: true,
|
||||||
|
config.LDAPURL: "ldap://127.0.0.1",
|
||||||
|
config.LDAPSearchDN: "uid=searchuser,ou=people,dc=mydomain,dc=com",
|
||||||
|
config.LDAPSearchPwd: "password",
|
||||||
|
config.LDAPBaseDN: "ou=people,dc=mydomain,dc=com",
|
||||||
|
config.LDAPUID: "uid",
|
||||||
|
config.LDAPFilter: "",
|
||||||
|
config.LDAPScope: 3,
|
||||||
|
config.LDAPTimeout: 30,
|
||||||
|
config.TokenServiceURL: "http://token_service",
|
||||||
|
config.RegistryURL: "http://registry",
|
||||||
|
config.EmailHost: "127.0.0.1",
|
||||||
|
config.EmailPort: 25,
|
||||||
|
config.EmailUsername: "user01",
|
||||||
|
config.EmailPassword: "password",
|
||||||
|
config.EmailFrom: "from",
|
||||||
|
config.EmailSSL: true,
|
||||||
|
config.EmailIdentity: "",
|
||||||
|
config.ProjectCreationRestriction: config.ProCrtRestrAdmOnly,
|
||||||
|
config.VerifyRemoteCert: false,
|
||||||
|
config.MaxJobWorkers: 3,
|
||||||
|
config.TokenExpiration: 30,
|
||||||
|
config.CfgExpiration: 5,
|
||||||
|
config.JobLogDir: "/var/log/jobs",
|
||||||
|
config.UseCompressedJS: true,
|
||||||
|
config.AdminInitialPassword: "password",
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAdminserver returns a mock admin server
|
||||||
|
func NewAdminserver(config map[string]interface{}) (*httptest.Server, error) {
|
||||||
|
m := []*RequestHandlerMapping{}
|
||||||
|
if config == nil {
|
||||||
|
config = adminServerDefaultConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: b,
|
||||||
|
}
|
||||||
|
|
||||||
|
m = append(m, &RequestHandlerMapping{
|
||||||
|
Method: "GET",
|
||||||
|
Pattern: "/api/configurations",
|
||||||
|
Handler: Handler(resp),
|
||||||
|
})
|
||||||
|
|
||||||
|
m = append(m, &RequestHandlerMapping{
|
||||||
|
Method: "PUT",
|
||||||
|
Pattern: "/api/configurations",
|
||||||
|
Handler: Handler(&Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
return NewServer(m...), nil
|
||||||
|
}
|
41
src/common/utils/test/key.go
Normal file
41
src/common/utils/test/key.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateKey generates aes key
|
||||||
|
func GenerateKey(path string) (string, error) {
|
||||||
|
data := make([]byte, aes.BlockSize)
|
||||||
|
n, err := rand.Read(data)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to generate random bytes: %v", err)
|
||||||
|
}
|
||||||
|
if n != aes.BlockSize {
|
||||||
|
return "", fmt.Errorf("the length of random bytes %d != %d", n, aes.BlockSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioutil.WriteFile(path, data, 0777); err != nil {
|
||||||
|
return "", fmt.Errorf("failed write secret key to file %s: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(data), nil
|
||||||
|
}
|
@ -21,6 +21,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequestHandlerMapping is a mapping between request and its handler
|
// RequestHandlerMapping is a mapping between request and its handler
|
||||||
@ -78,11 +80,11 @@ func Handler(resp *Response) func(http.ResponseWriter, *http.Request) {
|
|||||||
|
|
||||||
// NewServer creates a HTTP server for unit test
|
// NewServer creates a HTTP server for unit test
|
||||||
func NewServer(mappings ...*RequestHandlerMapping) *httptest.Server {
|
func NewServer(mappings ...*RequestHandlerMapping) *httptest.Server {
|
||||||
mux := http.NewServeMux()
|
r := mux.NewRouter()
|
||||||
|
|
||||||
for _, mapping := range mappings {
|
for _, mapping := range mappings {
|
||||||
mux.Handle(mapping.Pattern, mapping)
|
r.PathPrefix(mapping.Pattern).Handler(mapping).Methods(mapping.Method)
|
||||||
}
|
}
|
||||||
|
|
||||||
return httptest.NewServer(mux)
|
return httptest.NewServer(r)
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,14 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FormatEndpoint formats endpoint
|
// FormatEndpoint formats endpoint
|
||||||
@ -70,3 +74,40 @@ func GenerateRandomString() string {
|
|||||||
}
|
}
|
||||||
return string(result)
|
return string(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTCPConn tests TCP connection
|
||||||
|
// timeout: the total time before returning if something is wrong
|
||||||
|
// with the connection, in second
|
||||||
|
// interval: the interval time for retring after failure, in second
|
||||||
|
func TestTCPConn(addr string, timeout, interval int) error {
|
||||||
|
success := make(chan int)
|
||||||
|
cancel := make(chan int)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-cancel:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
conn, err := net.DialTimeout("tcp", addr, time.Duration(timeout)*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to connect to tcp://%s, retry after %d seconds :%v",
|
||||||
|
addr, interval, err)
|
||||||
|
time.Sleep(time.Duration(interval) * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
success <- 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-success:
|
||||||
|
return nil
|
||||||
|
case <-time.After(time.Duration(timeout) * time.Second):
|
||||||
|
cancel <- 1
|
||||||
|
return fmt.Errorf("failed to connect to tcp:%s after %d seconds", addr, timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -178,3 +179,12 @@ func TestParseLink(t *testing.T) {
|
|||||||
t.Errorf("unexpected prev: %s != %s", links.Next(), next)
|
t.Errorf("unexpected prev: %s != %s", links.Next(), next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTestTCPConn(t *testing.T) {
|
||||||
|
server := httptest.NewServer(nil)
|
||||||
|
defer server.Close()
|
||||||
|
addr := strings.TrimLeft(server.URL, "http://")
|
||||||
|
if err := TestTCPConn(addr, 60, 2); err != nil {
|
||||||
|
t.Fatalf("failed to test tcp connection of %s: %v", addr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,12 +25,12 @@ import (
|
|||||||
|
|
||||||
"github.com/vmware/harbor/src/common/api"
|
"github.com/vmware/harbor/src/common/api"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/jobservice/job"
|
|
||||||
"github.com/vmware/harbor/src/jobservice/config"
|
|
||||||
"github.com/vmware/harbor/src/jobservice/utils"
|
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
u "github.com/vmware/harbor/src/common/utils"
|
u "github.com/vmware/harbor/src/common/utils"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/config"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/job"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReplicationJob handles /api/replicationJobs /api/replicationJobs/:id/log
|
// ReplicationJob handles /api/replicationJobs /api/replicationJobs/:id/log
|
||||||
@ -171,7 +171,13 @@ func (rj *ReplicationJob) GetLog() {
|
|||||||
rj.RenderError(http.StatusBadRequest, "Invalid job id")
|
rj.RenderError(http.StatusBadRequest, "Invalid job id")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logFile := utils.GetJobLogPath(jid)
|
logFile, err := utils.GetJobLogPath(jid)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get log path of job %s: %v", idStr, err)
|
||||||
|
rj.RenderError(http.StatusInternalServerError,
|
||||||
|
http.StatusText(http.StatusInternalServerError))
|
||||||
|
return
|
||||||
|
}
|
||||||
rj.Ctx.Output.Download(logFile)
|
rj.Ctx.Output.Download(logFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +194,7 @@ func getRepoList(projectID int64) ([]string, error) {
|
|||||||
return repositories, err
|
return repositories, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.AddCookie(&http.Cookie{Name: models.UISecretCookie, Value: config.UISecret()})
|
req.AddCookie(&http.Cookie{Name: models.UISecretCookie, Value: config.JobserviceSecret()})
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -16,121 +16,144 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultMaxWorkers int = 10
|
const (
|
||||||
|
defaultKeyPath string = "/etc/jobservice/key"
|
||||||
|
)
|
||||||
|
|
||||||
var maxJobWorkers int
|
var (
|
||||||
var localUIURL string
|
mg *comcfg.Manager
|
||||||
var localRegURL string
|
keyProvider comcfg.KeyProvider
|
||||||
var logDir string
|
)
|
||||||
var uiSecret string
|
|
||||||
var secretKey string
|
|
||||||
var verifyRemoteCert string
|
|
||||||
|
|
||||||
func init() {
|
// Init configurations
|
||||||
maxWorkersEnv := os.Getenv("MAX_JOB_WORKERS")
|
func Init() error {
|
||||||
maxWorkers64, err := strconv.ParseInt(maxWorkersEnv, 10, 32)
|
//init key provider
|
||||||
maxJobWorkers = int(maxWorkers64)
|
initKeyProvider()
|
||||||
|
|
||||||
|
adminServerURL := os.Getenv("ADMIN_SERVER_URL")
|
||||||
|
if len(adminServerURL) == 0 {
|
||||||
|
adminServerURL = "http://adminserver"
|
||||||
|
}
|
||||||
|
mg = comcfg.NewManager(adminServerURL, JobserviceSecret(), true)
|
||||||
|
|
||||||
|
if err := mg.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := mg.Load(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initKeyProvider() {
|
||||||
|
path := os.Getenv("KEY_PATH")
|
||||||
|
if len(path) == 0 {
|
||||||
|
path = defaultKeyPath
|
||||||
|
}
|
||||||
|
log.Infof("key path: %s", path)
|
||||||
|
|
||||||
|
keyProvider = comcfg.NewFileKeyProvider(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyRemoteCert returns bool value.
|
||||||
|
func VerifyRemoteCert() (bool, error) {
|
||||||
|
cfg, err := mg.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("Failed to parse max works setting, error: %v, the default value: %d will be used", err, defaultMaxWorkers)
|
return true, err
|
||||||
maxJobWorkers = defaultMaxWorkers
|
|
||||||
}
|
}
|
||||||
|
return cfg[comcfg.VerifyRemoteCert].(bool), nil
|
||||||
|
}
|
||||||
|
|
||||||
localRegURL = os.Getenv("REGISTRY_URL")
|
// Database ...
|
||||||
if len(localRegURL) == 0 {
|
func Database() (*models.Database, error) {
|
||||||
localRegURL = "http://registry:5000"
|
cfg, err := mg.Get()
|
||||||
}
|
|
||||||
|
|
||||||
localUIURL = os.Getenv("UI_URL")
|
|
||||||
if len(localUIURL) == 0 {
|
|
||||||
localUIURL = "http://ui"
|
|
||||||
}
|
|
||||||
|
|
||||||
logDir = os.Getenv("LOG_DIR")
|
|
||||||
if len(logDir) == 0 {
|
|
||||||
logDir = "/var/log"
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(logDir)
|
|
||||||
defer f.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
|
||||||
finfo, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if !finfo.IsDir() {
|
|
||||||
panic(fmt.Sprintf("%s is not a direcotry", logDir))
|
|
||||||
}
|
}
|
||||||
|
database := &models.Database{}
|
||||||
|
database.Type = cfg[comcfg.DatabaseType].(string)
|
||||||
|
mysql := &models.MySQL{}
|
||||||
|
mysql.Host = cfg[comcfg.MySQLHost].(string)
|
||||||
|
mysql.Port = int(cfg[comcfg.MySQLPort].(float64))
|
||||||
|
mysql.Username = cfg[comcfg.MySQLUsername].(string)
|
||||||
|
mysql.Password = cfg[comcfg.MySQLPassword].(string)
|
||||||
|
mysql.Database = cfg[comcfg.MySQLDatabase].(string)
|
||||||
|
database.MySQL = mysql
|
||||||
|
sqlite := &models.SQLite{}
|
||||||
|
sqlite.File = cfg[comcfg.SQLiteFile].(string)
|
||||||
|
database.SQLite = sqlite
|
||||||
|
|
||||||
uiSecret = os.Getenv("UI_SECRET")
|
return database, nil
|
||||||
if len(uiSecret) == 0 {
|
|
||||||
panic("UI Secret is not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
verifyRemoteCert = os.Getenv("VERIFY_REMOTE_CERT")
|
|
||||||
if len(verifyRemoteCert) == 0 {
|
|
||||||
verifyRemoteCert = "on"
|
|
||||||
}
|
|
||||||
|
|
||||||
configPath := os.Getenv("CONFIG_PATH")
|
|
||||||
if len(configPath) != 0 {
|
|
||||||
log.Infof("Config path: %s", configPath)
|
|
||||||
beego.LoadAppConfig("ini", configPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
secretKey = os.Getenv("SECRET_KEY")
|
|
||||||
if len(secretKey) != 16 {
|
|
||||||
panic("The length of secretkey has to be 16 characters!")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("config: maxJobWorkers: %d", maxJobWorkers)
|
|
||||||
log.Debugf("config: localUIURL: %s", localUIURL)
|
|
||||||
log.Debugf("config: localRegURL: %s", localRegURL)
|
|
||||||
log.Debugf("config: verifyRemoteCert: %s", verifyRemoteCert)
|
|
||||||
log.Debugf("config: logDir: %s", logDir)
|
|
||||||
log.Debugf("config: uiSecret: ******")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxJobWorkers ...
|
// MaxJobWorkers ...
|
||||||
func MaxJobWorkers() int {
|
func MaxJobWorkers() (int, error) {
|
||||||
return maxJobWorkers
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(cfg[comcfg.MaxJobWorkers].(float64)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalUIURL returns the local ui url, job service will use this URL to call API hosted on ui process
|
// LocalUIURL returns the local ui url, job service will use this URL to call API hosted on ui process
|
||||||
func LocalUIURL() string {
|
func LocalUIURL() string {
|
||||||
return localUIURL
|
return "http://ui"
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalRegURL returns the local registry url, job service will use this URL to pull image from the registry
|
// LocalRegURL returns the local registry url, job service will use this URL to pull image from the registry
|
||||||
func LocalRegURL() string {
|
func LocalRegURL() (string, error) {
|
||||||
return localRegURL
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg[comcfg.RegistryURL].(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogDir returns the absolute path to which the log file will be written
|
// LogDir returns the absolute path to which the log file will be written
|
||||||
func LogDir() string {
|
func LogDir() (string, error) {
|
||||||
return logDir
|
cfg, err := mg.Get()
|
||||||
}
|
if err != nil {
|
||||||
|
return "", err
|
||||||
// UISecret will return the value of secret cookie for jobsevice to call UI API.
|
}
|
||||||
func UISecret() string {
|
return cfg[comcfg.JobLogDir].(string), nil
|
||||||
return uiSecret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecretKey will return the secret key for encryption/decryption password in target.
|
// SecretKey will return the secret key for encryption/decryption password in target.
|
||||||
func SecretKey() string {
|
func SecretKey() (string, error) {
|
||||||
return secretKey
|
return keyProvider.Get(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyRemoteCert return the flag to tell jobservice whether or not verify the cert of remote registry
|
// UISecret returns a secret to mark UI when communicate with other
|
||||||
func VerifyRemoteCert() bool {
|
// component
|
||||||
return verifyRemoteCert != "off"
|
func UISecret() string {
|
||||||
|
return os.Getenv("UI_SECRET")
|
||||||
|
}
|
||||||
|
|
||||||
|
// JobserviceSecret returns a secret to mark Jobservice when communicate with
|
||||||
|
// other component
|
||||||
|
func JobserviceSecret() string {
|
||||||
|
return os.Getenv("JOBSERVICE_SECRET")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtEndpoint ...
|
||||||
|
func ExtEndpoint() (string, error) {
|
||||||
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg[comcfg.ExtEndpoint].(string), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalTokenServiceEndpoint ...
|
||||||
|
func InternalTokenServiceEndpoint() string {
|
||||||
|
return "http://ui/service/token"
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,84 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
// test functions under package jobservice/config
|
||||||
}
|
func TestConfig(t *testing.T) {
|
||||||
|
server, err := test.NewAdminserver(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a mock admin server: %v", err)
|
||||||
|
}
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
if err := os.Setenv("ADMIN_SERVER_URL", server.URL); err != nil {
|
||||||
|
t.Fatalf("failed to set env %s: %v", "ADMIN_SERVER_URL", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secretKeyPath := "/tmp/secretkey"
|
||||||
|
_, err = test.GenerateKey(secretKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to generate secret key: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(secretKeyPath)
|
||||||
|
|
||||||
|
if err := os.Setenv("KEY_PATH", secretKeyPath); err != nil {
|
||||||
|
t.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Init(); err != nil {
|
||||||
|
t.Fatalf("failed to initialize configurations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := VerifyRemoteCert(); err != nil {
|
||||||
|
t.Fatalf("failed to get verify remote cert: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Database(); err != nil {
|
||||||
|
t.Fatalf("failed to get database settings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := MaxJobWorkers(); err != nil {
|
||||||
|
t.Fatalf("failed to get max job workers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := LocalRegURL(); err != nil {
|
||||||
|
t.Fatalf("failed to get registry URL: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := LogDir(); err != nil {
|
||||||
|
t.Fatalf("failed to get log directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := SecretKey(); err != nil {
|
||||||
|
t.Fatalf("failed to get secret key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(InternalTokenServiceEndpoint()) == 0 {
|
||||||
|
t.Error("the internal token service endpoint is null")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := ExtEndpoint(); err != nil {
|
||||||
|
t.Fatalf("failed to get ext endpoint: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,12 +20,12 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/jobservice/config"
|
|
||||||
"github.com/vmware/harbor/src/jobservice/replication"
|
|
||||||
"github.com/vmware/harbor/src/jobservice/utils"
|
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
uti "github.com/vmware/harbor/src/common/utils"
|
uti "github.com/vmware/harbor/src/common/utils"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/config"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/replication"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RepJobParm wraps the parm of a job
|
// RepJobParm wraps the parm of a job
|
||||||
@ -184,14 +184,17 @@ func (sm *SM) Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset resets the state machine so it will start handling another job.
|
// Reset resets the state machine so it will start handling another job.
|
||||||
func (sm *SM) Reset(jid int64) error {
|
func (sm *SM) Reset(jid int64) (err error) {
|
||||||
//To ensure the new jobID is visible to the thread to stop the SM
|
//To ensure the new jobID is visible to the thread to stop the SM
|
||||||
sm.lock.Lock()
|
sm.lock.Lock()
|
||||||
sm.JobID = jid
|
sm.JobID = jid
|
||||||
sm.desiredState = ""
|
sm.desiredState = ""
|
||||||
sm.lock.Unlock()
|
sm.lock.Unlock()
|
||||||
|
|
||||||
sm.Logger = utils.NewLogger(sm.JobID)
|
sm.Logger, err = utils.NewLogger(sm.JobID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
//init parms
|
//init parms
|
||||||
job, err := dao.GetRepJob(sm.JobID)
|
job, err := dao.GetRepJob(sm.JobID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -207,13 +210,22 @@ func (sm *SM) Reset(jid int64) error {
|
|||||||
if policy == nil {
|
if policy == nil {
|
||||||
return fmt.Errorf("The policy doesn't exist in DB, policy id:%d", job.PolicyID)
|
return fmt.Errorf("The policy doesn't exist in DB, policy id:%d", job.PolicyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
regURL, err := config.LocalRegURL()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verify, err := config.VerifyRemoteCert()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
sm.Parms = &RepJobParm{
|
sm.Parms = &RepJobParm{
|
||||||
LocalRegURL: config.LocalRegURL(),
|
LocalRegURL: regURL,
|
||||||
Repository: job.Repository,
|
Repository: job.Repository,
|
||||||
Tags: job.TagList,
|
Tags: job.TagList,
|
||||||
Enabled: policy.Enabled,
|
Enabled: policy.Enabled,
|
||||||
Operation: job.Operation,
|
Operation: job.Operation,
|
||||||
Insecure: !config.VerifyRemoteCert(),
|
Insecure: !verify,
|
||||||
}
|
}
|
||||||
if policy.Enabled == 0 {
|
if policy.Enabled == 0 {
|
||||||
//worker will cancel this job
|
//worker will cancel this job
|
||||||
@ -231,7 +243,11 @@ func (sm *SM) Reset(jid int64) error {
|
|||||||
pwd := target.Password
|
pwd := target.Password
|
||||||
|
|
||||||
if len(pwd) != 0 {
|
if len(pwd) != 0 {
|
||||||
pwd, err = uti.ReversibleDecrypt(pwd, config.SecretKey())
|
key, err := config.SecretKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pwd, err = uti.ReversibleDecrypt(pwd, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to decrypt password: %v", err)
|
return fmt.Errorf("failed to decrypt password: %v", err)
|
||||||
}
|
}
|
||||||
@ -269,7 +285,7 @@ func addTestTransition(sm *SM) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addImgTransferTransition(sm *SM) {
|
func addImgTransferTransition(sm *SM) {
|
||||||
base := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(),
|
base := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.JobserviceSecret(),
|
||||||
sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
|
sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
|
||||||
sm.Parms.Insecure, sm.Parms.Tags, sm.Logger)
|
sm.Parms.Insecure, sm.Parms.Tags, sm.Logger)
|
||||||
|
|
||||||
|
@ -17,9 +17,9 @@ package job
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/jobservice/config"
|
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type workerPool struct {
|
type workerPool struct {
|
||||||
@ -111,17 +111,22 @@ func NewWorker(id int) *Worker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitWorkerPool create workers according to configuration.
|
// InitWorkerPool create workers according to configuration.
|
||||||
func InitWorkerPool() {
|
func InitWorkerPool() error {
|
||||||
WorkerPool = &workerPool{
|
n, err := config.MaxJobWorkers()
|
||||||
workerChan: make(chan *Worker, config.MaxJobWorkers()),
|
if err != nil {
|
||||||
workerList: make([]*Worker, 0, config.MaxJobWorkers()),
|
return err
|
||||||
}
|
}
|
||||||
for i := 0; i < config.MaxJobWorkers(); i++ {
|
WorkerPool = &workerPool{
|
||||||
|
workerChan: make(chan *Worker, n),
|
||||||
|
workerList: make([]*Worker, 0, n),
|
||||||
|
}
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
worker := NewWorker(i)
|
worker := NewWorker(i)
|
||||||
WorkerPool.workerList = append(WorkerPool.workerList, worker)
|
WorkerPool.workerList = append(WorkerPool.workerList, worker)
|
||||||
worker.Start()
|
worker.Start()
|
||||||
log.Debugf("worker %d started", worker.ID)
|
log.Debugf("worker %d started", worker.ID)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch will listen to the jobQueue of job service and try to pick a free worker from the worker pool and assign the job to it.
|
// Dispatch will listen to the jobQueue of job service and try to pick a free worker from the worker pool and assign the job to it.
|
||||||
|
@ -16,15 +16,32 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/jobservice/job"
|
|
||||||
"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/jobservice/config"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/job"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
dao.InitDatabase()
|
log.Info("initializing configurations...")
|
||||||
|
if err := config.Init(); err != nil {
|
||||||
|
log.Fatalf("failed to initialize configurations: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("configurations initialization completed")
|
||||||
|
|
||||||
|
database, err := config.Database()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to get database configurations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dao.InitDatabase(database); err != nil {
|
||||||
|
log.Fatalf("failed to initialize database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
initRouters()
|
initRouters()
|
||||||
job.InitWorkerPool()
|
job.InitWorkerPool()
|
||||||
go job.Dispatch()
|
go job.Dispatch()
|
||||||
@ -48,3 +65,11 @@ func resumeJobs() {
|
|||||||
log.Warningf("Failed to jobs to resume, error: %v", err)
|
log.Warningf("Failed to jobs to resume, error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
configPath := os.Getenv("CONFIG_PATH")
|
||||||
|
if len(configPath) != 0 {
|
||||||
|
log.Infof("Config path: %s", configPath)
|
||||||
|
beego.LoadAppConfig("ini", configPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -33,6 +33,7 @@ import (
|
|||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/common/utils/registry"
|
"github.com/vmware/harbor/src/common/utils/registry"
|
||||||
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -137,7 +138,7 @@ func (i *Initializer) enter() (string, error) {
|
|||||||
c := &http.Cookie{Name: models.UISecretCookie, Value: i.srcSecret}
|
c := &http.Cookie{Name: models.UISecretCookie, Value: i.srcSecret}
|
||||||
srcCred := auth.NewCookieCredential(c)
|
srcCred := auth.NewCookieCredential(c)
|
||||||
srcClient, err := newRepositoryClient(i.srcURL, i.insecure, srcCred,
|
srcClient, err := newRepositoryClient(i.srcURL, i.insecure, srcCred,
|
||||||
i.repository, "repository", i.repository, "pull", "push", "*")
|
config.InternalTokenServiceEndpoint(), i.repository, "repository", i.repository, "pull", "push", "*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
i.logger.Errorf("an error occurred while creating source repository client: %v", err)
|
i.logger.Errorf("an error occurred while creating source repository client: %v", err)
|
||||||
return "", err
|
return "", err
|
||||||
@ -146,7 +147,7 @@ func (i *Initializer) enter() (string, error) {
|
|||||||
|
|
||||||
dstCred := auth.NewBasicAuthCredential(i.dstUsr, i.dstPwd)
|
dstCred := auth.NewBasicAuthCredential(i.dstUsr, i.dstPwd)
|
||||||
dstClient, err := newRepositoryClient(i.dstURL, i.insecure, dstCred,
|
dstClient, err := newRepositoryClient(i.dstURL, i.insecure, dstCred,
|
||||||
i.repository, "repository", i.repository, "pull", "push", "*")
|
"", i.repository, "repository", i.repository, "pull", "push", "*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
i.logger.Errorf("an error occurred while creating destination repository client: %v", err)
|
i.logger.Errorf("an error occurred while creating destination repository client: %v", err)
|
||||||
return "", err
|
return "", err
|
||||||
@ -457,10 +458,11 @@ func (m *ManifestPusher) enter() (string, error) {
|
|||||||
return StatePullManifest, nil
|
return StatePullManifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential, repository, scopeType, scopeName string,
|
func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential,
|
||||||
|
tokenServiceEndpoint, repository, scopeType, scopeName string,
|
||||||
scopeActions ...string) (*registry.Repository, error) {
|
scopeActions ...string) (*registry.Repository, error) {
|
||||||
|
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure,
|
||||||
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
|
tokenServiceEndpoint, scopeType, scopeName, scopeActions...)
|
||||||
|
|
||||||
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -18,16 +18,20 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/jobservice/config"
|
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewLogger create a logger for a speicified job
|
// NewLogger create a logger for a speicified job
|
||||||
func NewLogger(jobID int64) *log.Logger {
|
func NewLogger(jobID int64) (*log.Logger, error) {
|
||||||
logFile := GetJobLogPath(jobID)
|
logFile, err := GetJobLogPath(jobID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
d := filepath.Dir(logFile)
|
d := filepath.Dir(logFile)
|
||||||
if _, err := os.Stat(d); os.IsNotExist(err) {
|
if _, err := os.Stat(d); os.IsNotExist(err) {
|
||||||
err := os.MkdirAll(d, 0660)
|
err := os.MkdirAll(d, 0660)
|
||||||
@ -40,11 +44,11 @@ func NewLogger(jobID int64) *log.Logger {
|
|||||||
log.Errorf("Failed to open log file %s, the log of job %d will be printed to standard output, the error: %v", logFile, jobID, err)
|
log.Errorf("Failed to open log file %s, the log of job %d will be printed to standard output, the error: %v", logFile, jobID, err)
|
||||||
f = os.Stdout
|
f = os.Stdout
|
||||||
}
|
}
|
||||||
return log.New(f, log.NewTextFormatter(), log.InfoLevel)
|
return log.New(f, log.NewTextFormatter(), log.InfoLevel), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetJobLogPath returns the absolute path in which the job log file is located.
|
// GetJobLogPath returns the absolute path in which the job log file is located.
|
||||||
func GetJobLogPath(jobID int64) string {
|
func GetJobLogPath(jobID int64) (string, error) {
|
||||||
f := fmt.Sprintf("job_%d.log", jobID)
|
f := fmt.Sprintf("job_%d.log", jobID)
|
||||||
k := jobID / 1000
|
k := jobID / 1000
|
||||||
p := ""
|
p := ""
|
||||||
@ -61,6 +65,10 @@ func GetJobLogPath(jobID int64) string {
|
|||||||
|
|
||||||
p = filepath.Join(d, p)
|
p = filepath.Join(d, p)
|
||||||
}
|
}
|
||||||
p = filepath.Join(config.LogDir(), p, f)
|
base, err := config.LogDir()
|
||||||
return p
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
p = filepath.Join(base, p, f)
|
||||||
|
return p, nil
|
||||||
}
|
}
|
||||||
|
365
src/ui/api/config.go
Normal file
365
src/ui/api/config.go
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/api"
|
||||||
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// valid keys of configurations which user can modify
|
||||||
|
validKeys = []string{
|
||||||
|
comcfg.ExtEndpoint,
|
||||||
|
comcfg.AUTHMode,
|
||||||
|
comcfg.DatabaseType,
|
||||||
|
comcfg.MySQLHost,
|
||||||
|
comcfg.MySQLPort,
|
||||||
|
comcfg.MySQLUsername,
|
||||||
|
comcfg.MySQLPassword,
|
||||||
|
comcfg.MySQLDatabase,
|
||||||
|
comcfg.SQLiteFile,
|
||||||
|
comcfg.SelfRegistration,
|
||||||
|
comcfg.LDAPURL,
|
||||||
|
comcfg.LDAPSearchDN,
|
||||||
|
comcfg.LDAPSearchPwd,
|
||||||
|
comcfg.LDAPBaseDN,
|
||||||
|
comcfg.LDAPUID,
|
||||||
|
comcfg.LDAPFilter,
|
||||||
|
comcfg.LDAPScope,
|
||||||
|
comcfg.LDAPTimeout,
|
||||||
|
comcfg.TokenServiceURL,
|
||||||
|
comcfg.RegistryURL,
|
||||||
|
comcfg.EmailHost,
|
||||||
|
comcfg.EmailPort,
|
||||||
|
comcfg.EmailUsername,
|
||||||
|
comcfg.EmailPassword,
|
||||||
|
comcfg.EmailFrom,
|
||||||
|
comcfg.EmailSSL,
|
||||||
|
comcfg.EmailIdentity,
|
||||||
|
comcfg.ProjectCreationRestriction,
|
||||||
|
comcfg.VerifyRemoteCert,
|
||||||
|
comcfg.MaxJobWorkers,
|
||||||
|
comcfg.TokenExpiration,
|
||||||
|
comcfg.CfgExpiration,
|
||||||
|
comcfg.JobLogDir,
|
||||||
|
comcfg.UseCompressedJS,
|
||||||
|
comcfg.AdminInitialPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
numKeys = []string{
|
||||||
|
comcfg.EmailPort,
|
||||||
|
comcfg.LDAPScope,
|
||||||
|
comcfg.LDAPTimeout,
|
||||||
|
comcfg.MySQLPort,
|
||||||
|
comcfg.MaxJobWorkers,
|
||||||
|
comcfg.TokenExpiration,
|
||||||
|
comcfg.CfgExpiration,
|
||||||
|
}
|
||||||
|
|
||||||
|
boolKeys = []string{
|
||||||
|
comcfg.EmailSSL,
|
||||||
|
comcfg.SelfRegistration,
|
||||||
|
comcfg.VerifyRemoteCert,
|
||||||
|
comcfg.UseCompressedJS,
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordKeys = []string{
|
||||||
|
comcfg.AdminInitialPassword,
|
||||||
|
comcfg.EmailPassword,
|
||||||
|
comcfg.LDAPSearchPwd,
|
||||||
|
comcfg.MySQLPassword,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigAPI ...
|
||||||
|
type ConfigAPI struct {
|
||||||
|
api.BaseAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare validates the user
|
||||||
|
func (c *ConfigAPI) Prepare() {
|
||||||
|
userID := c.ValidateUser()
|
||||||
|
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to check the role of user: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isSysAdmin {
|
||||||
|
c.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type value struct {
|
||||||
|
Value interface{} `json:"value"`
|
||||||
|
Editable bool `json:"editable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns configurations
|
||||||
|
func (c *ConfigAPI) Get() {
|
||||||
|
cfg, err := config.GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get configurations: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := convertForGet(cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to convert configurations: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = m
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put updates configurations
|
||||||
|
func (c *ConfigAPI) Put() {
|
||||||
|
m := map[string]string{}
|
||||||
|
c.DecodeJSONReq(&m)
|
||||||
|
|
||||||
|
cfg := map[string]string{}
|
||||||
|
for _, k := range validKeys {
|
||||||
|
if v, ok := m[k]; ok {
|
||||||
|
cfg[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isSysErr, err := validateCfg(cfg)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if isSysErr {
|
||||||
|
log.Errorf("failed to validate configurations: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.CustomAbort(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := cfg[comcfg.AUTHMode]; ok {
|
||||||
|
mode, err := config.AuthMode()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get auth mode: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode != value {
|
||||||
|
flag, err := authModeCanBeModified()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to determine whether auth mode can be modified: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !flag {
|
||||||
|
c.CustomAbort(http.StatusBadRequest,
|
||||||
|
fmt.Sprintf("%s can not be modified as new users have been inserted into database",
|
||||||
|
comcfg.AUTHMode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := convertForPut(cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to convert configurations: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := config.Upload(result); err != nil {
|
||||||
|
log.Errorf("failed to upload configurations: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := config.Load(); err != nil {
|
||||||
|
log.Errorf("failed to load configurations: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateCfg(c map[string]string) (bool, error) {
|
||||||
|
isSysErr := false
|
||||||
|
|
||||||
|
mode, err := config.AuthMode()
|
||||||
|
if err != nil {
|
||||||
|
isSysErr = true
|
||||||
|
return isSysErr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := c[comcfg.AUTHMode]; ok {
|
||||||
|
if value != comcfg.DBAuth && value != comcfg.LDAPAuth {
|
||||||
|
return isSysErr, fmt.Errorf("invalid %s, shoud be %s or %s", comcfg.AUTHMode, comcfg.DBAuth, comcfg.LDAPAuth)
|
||||||
|
}
|
||||||
|
mode = value
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == comcfg.LDAPAuth {
|
||||||
|
ldap, err := config.LDAP()
|
||||||
|
if err != nil {
|
||||||
|
isSysErr = true
|
||||||
|
return isSysErr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ldap.URL) == 0 {
|
||||||
|
if _, ok := c[comcfg.LDAPURL]; !ok {
|
||||||
|
return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ldap.BaseDN) == 0 {
|
||||||
|
if _, ok := c[comcfg.LDAPBaseDN]; !ok {
|
||||||
|
return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPBaseDN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ldap.UID) == 0 {
|
||||||
|
if _, ok := c[comcfg.LDAPUID]; !ok {
|
||||||
|
return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPUID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ldap.Scope == 0 {
|
||||||
|
if _, ok := c[comcfg.LDAPScope]; !ok {
|
||||||
|
return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ldapURL, ok := c[comcfg.LDAPURL]; ok && len(ldapURL) == 0 {
|
||||||
|
return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPURL)
|
||||||
|
}
|
||||||
|
if baseDN, ok := c[comcfg.LDAPBaseDN]; ok && len(baseDN) == 0 {
|
||||||
|
return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPBaseDN)
|
||||||
|
}
|
||||||
|
if uID, ok := c[comcfg.LDAPUID]; ok && len(uID) == 0 {
|
||||||
|
return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPUID)
|
||||||
|
}
|
||||||
|
if scope, ok := c[comcfg.LDAPScope]; ok &&
|
||||||
|
scope != comcfg.LDAPScopeBase &&
|
||||||
|
scope != comcfg.LDAPScopeOnelevel &&
|
||||||
|
scope != comcfg.LDAPScopeSubtree {
|
||||||
|
return isSysErr, fmt.Errorf("invalid %s, should be %s, %s or %s",
|
||||||
|
comcfg.LDAPScope,
|
||||||
|
comcfg.LDAPScopeBase,
|
||||||
|
comcfg.LDAPScopeOnelevel,
|
||||||
|
comcfg.LDAPScopeSubtree)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range boolKeys {
|
||||||
|
v, ok := c[k]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if v != "0" && v != "1" {
|
||||||
|
return isSysErr, fmt.Errorf("%s should be %s or %s",
|
||||||
|
k, "0", "1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range numKeys {
|
||||||
|
v, ok := c[k]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := strconv.Atoi(v)
|
||||||
|
if err != nil || n < 0 {
|
||||||
|
return isSysErr, fmt.Errorf("invalid %s: %s", k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k == comcfg.EmailPort ||
|
||||||
|
k == comcfg.MySQLPort) && n > 65535 {
|
||||||
|
return isSysErr, fmt.Errorf("invalid %s: %s", k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if crt, ok := c[comcfg.ProjectCreationRestriction]; ok &&
|
||||||
|
crt != comcfg.ProCrtRestrEveryone &&
|
||||||
|
crt != comcfg.ProCrtRestrAdmOnly {
|
||||||
|
return isSysErr, fmt.Errorf("invalid %s, should be %s or %s",
|
||||||
|
comcfg.ProjectCreationRestriction,
|
||||||
|
comcfg.ProCrtRestrAdmOnly,
|
||||||
|
comcfg.ProCrtRestrEveryone)
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSysErr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//convert map[string]string to map[string]interface{}
|
||||||
|
func convertForPut(m map[string]string) (map[string]interface{}, error) {
|
||||||
|
cfg := map[string]interface{}{}
|
||||||
|
|
||||||
|
for k, v := range m {
|
||||||
|
cfg[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range numKeys {
|
||||||
|
if _, ok := cfg[k]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := strconv.Atoi(cfg[k].(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range boolKeys {
|
||||||
|
if _, ok := cfg[k]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg[k] = cfg[k] == "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete sensitive attrs and add editable field to every attr
|
||||||
|
func convertForGet(cfg map[string]interface{}) (map[string]*value, error) {
|
||||||
|
result := map[string]*value{}
|
||||||
|
|
||||||
|
for _, k := range passwordKeys {
|
||||||
|
if _, ok := cfg[k]; ok {
|
||||||
|
delete(cfg, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range cfg {
|
||||||
|
result[k] = &value{
|
||||||
|
Value: v,
|
||||||
|
Editable: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flag, err := authModeCanBeModified()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result[comcfg.AUTHMode].Editable = flag
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func authModeCanBeModified() (bool, error) {
|
||||||
|
return dao.AuthModeCanBeModified()
|
||||||
|
}
|
70
src/ui/api/config_test.go
Normal file
70
src/ui/api/config_test.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/vmware/harbor/src/common/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetConfig(t *testing.T) {
|
||||||
|
fmt.Println("Testing getting configurations")
|
||||||
|
assert := assert.New(t)
|
||||||
|
apiTest := newHarborAPI()
|
||||||
|
|
||||||
|
//case 1: get configurations without admin role
|
||||||
|
code, _, err := apiTest.GetConfig(*testUser)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get configurations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(401, code, "the status code of getting configurations with non-admin user should be 401")
|
||||||
|
|
||||||
|
//case 2: get configurations with admin role
|
||||||
|
code, cfg, err := apiTest.GetConfig(*admin)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get configurations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !assert.Equal(200, code, "the status code of getting configurations with admin user should be 200") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := cfg[config.AUTHMode].Value.(string)
|
||||||
|
assert.Equal(config.DBAuth, mode, fmt.Sprintf("the auth mode should be %s", config.DBAuth))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPutConfig(t *testing.T) {
|
||||||
|
fmt.Println("Testing modifying configurations")
|
||||||
|
assert := assert.New(t)
|
||||||
|
apiTest := newHarborAPI()
|
||||||
|
|
||||||
|
cfg := map[string]string{
|
||||||
|
config.VerifyRemoteCert: "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
code, err := apiTest.PutConfig(*admin, cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get configurations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !assert.Equal(200, code, "the status code of modifying configurations with admin user should be 200") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ 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"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||||
// "strconv"
|
// "strconv"
|
||||||
// "strings"
|
// "strings"
|
||||||
@ -57,7 +58,14 @@ type usrInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
dao.InitDatabase()
|
if err := config.Init(); err != nil {
|
||||||
|
log.Fatalf("failed to initialize configurations: %v", err)
|
||||||
|
}
|
||||||
|
database, err := config.Database()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to get database configurations: %v", err)
|
||||||
|
}
|
||||||
|
dao.InitDatabase(database)
|
||||||
_, 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
|
||||||
@ -90,6 +98,8 @@ func init() {
|
|||||||
beego.Router("/api/policies/replication/:id([0-9]+)/enablement", &RepPolicyAPI{}, "put:UpdateEnablement")
|
beego.Router("/api/policies/replication/:id([0-9]+)/enablement", &RepPolicyAPI{}, "put:UpdateEnablement")
|
||||||
beego.Router("/api/systeminfo/volumes", &SystemInfoAPI{}, "get:GetVolumeInfo")
|
beego.Router("/api/systeminfo/volumes", &SystemInfoAPI{}, "get:GetVolumeInfo")
|
||||||
beego.Router("/api/systeminfo/getcert", &SystemInfoAPI{}, "get:GetCert")
|
beego.Router("/api/systeminfo/getcert", &SystemInfoAPI{}, "get:GetCert")
|
||||||
|
beego.Router("/api/ldap/ping", &LdapAPI{}, "post:Ping")
|
||||||
|
beego.Router("/api/configurations", &ConfigAPI{})
|
||||||
|
|
||||||
_ = updateInitPassword(1, "Harbor12345")
|
_ = updateInitPassword(1, "Harbor12345")
|
||||||
|
|
||||||
@ -512,7 +522,7 @@ func (a testapi) GetReposTop(authInfo usrInfo, count string) (int, error) {
|
|||||||
|
|
||||||
//-------------------------Targets Test---------------------------------------//
|
//-------------------------Targets Test---------------------------------------//
|
||||||
//Create a new replication target
|
//Create a new replication target
|
||||||
func (a testapi) AddTargets(authInfo usrInfo, repTarget apilib.RepTargetPost) (int, error) {
|
func (a testapi) AddTargets(authInfo usrInfo, repTarget apilib.RepTargetPost) (int, string, error) {
|
||||||
_sling := sling.New().Post(a.basePath)
|
_sling := sling.New().Post(a.basePath)
|
||||||
|
|
||||||
path := "/api/targets"
|
path := "/api/targets"
|
||||||
@ -520,8 +530,8 @@ func (a testapi) AddTargets(authInfo usrInfo, repTarget apilib.RepTargetPost) (i
|
|||||||
_sling = _sling.Path(path)
|
_sling = _sling.Path(path)
|
||||||
_sling = _sling.BodyJSON(repTarget)
|
_sling = _sling.BodyJSON(repTarget)
|
||||||
|
|
||||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||||
return httpStatusCode, err
|
return httpStatusCode, string(body), err
|
||||||
}
|
}
|
||||||
|
|
||||||
//List filters targets by name
|
//List filters targets by name
|
||||||
@ -897,3 +907,39 @@ func (a testapi) CertGet(authInfo usrInfo) (int, []byte, error) {
|
|||||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||||
return httpStatusCode, body, err
|
return httpStatusCode, body, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Post ldap test
|
||||||
|
func (a testapi) LdapPost(authInfo usrInfo, ldapConf apilib.LdapConf) (int, error) {
|
||||||
|
|
||||||
|
_sling := sling.New().Post(a.basePath)
|
||||||
|
|
||||||
|
// create path and map variables
|
||||||
|
path := "/api/ldap/ping"
|
||||||
|
|
||||||
|
_sling = _sling.Path(path)
|
||||||
|
|
||||||
|
// body params
|
||||||
|
_sling = _sling.BodyJSON(ldapConf)
|
||||||
|
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||||
|
return httpStatusCode, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a testapi) GetConfig(authInfo usrInfo) (int, map[string]*value, error) {
|
||||||
|
_sling := sling.New().Base(a.basePath).Get("/api/configurations")
|
||||||
|
|
||||||
|
cfg := map[string]*value{}
|
||||||
|
|
||||||
|
code, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||||
|
if err == nil && code == 200 {
|
||||||
|
err = json.Unmarshal(body, &cfg)
|
||||||
|
}
|
||||||
|
return code, cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a testapi) PutConfig(authInfo usrInfo, cfg map[string]string) (int, error) {
|
||||||
|
_sling := sling.New().Base(a.basePath).Put("/api/configurations").BodyJSON(cfg)
|
||||||
|
|
||||||
|
code, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||||
|
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
159
src/ui/api/ldap.go
Normal file
159
src/ui/api/ldap.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"crypto/tls"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/api"
|
||||||
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
|
||||||
|
goldap "gopkg.in/ldap.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LdapAPI handles requesst to /api/ldap/ping /api/ldap/search
|
||||||
|
type LdapAPI struct {
|
||||||
|
api.BaseAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
var ldapConfs models.LdapConf
|
||||||
|
|
||||||
|
// Prepare ...
|
||||||
|
func (l *LdapAPI) Prepare() {
|
||||||
|
|
||||||
|
userID := l.ValidateUser()
|
||||||
|
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error occurred in IsAdminRole: %v", err)
|
||||||
|
l.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isSysAdmin {
|
||||||
|
l.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping ...
|
||||||
|
func (l *LdapAPI) Ping() {
|
||||||
|
l.DecodeJSONReqAndValidate(&ldapConfs)
|
||||||
|
|
||||||
|
err := validateLdapReq(ldapConfs)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Invalid ldap request, error: %v", err)
|
||||||
|
l.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid ldap request: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = connectTest(ldapConfs)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Ldap connect fail, error: %v", err)
|
||||||
|
l.RenderError(http.StatusBadRequest, fmt.Sprintf("ldap connect fail: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateLdapReq(ldapConfs models.LdapConf) error {
|
||||||
|
ldapURL := ldapConfs.LdapURL
|
||||||
|
if ldapURL == "" {
|
||||||
|
return fmt.Errorf("can not get any available LDAP_URL")
|
||||||
|
}
|
||||||
|
log.Debug("ldapURL:", ldapURL)
|
||||||
|
|
||||||
|
ldapConnectionTimeout := ldapConfs.LdapConnectionTimeout
|
||||||
|
log.Debug("ldapConnectionTimeout:", ldapConnectionTimeout)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectTest(ldapConfs models.LdapConf) error {
|
||||||
|
|
||||||
|
var ldap *goldap.Conn
|
||||||
|
var protocol, hostport string
|
||||||
|
var host, port string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ldapURL := ldapConfs.LdapURL
|
||||||
|
|
||||||
|
// This routine keeps compability with the old format used on harbor.cfg
|
||||||
|
|
||||||
|
if strings.Contains(ldapURL, "://") {
|
||||||
|
splitLdapURL := strings.Split(ldapURL, "://")
|
||||||
|
protocol, hostport = splitLdapURL[0], splitLdapURL[1]
|
||||||
|
if !((protocol == "ldap") || (protocol == "ldaps")) {
|
||||||
|
return fmt.Errorf("unknown ldap protocl")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hostport = ldapURL
|
||||||
|
protocol = "ldap"
|
||||||
|
}
|
||||||
|
|
||||||
|
// This tries to detect the used port, if not defined
|
||||||
|
if strings.Contains(hostport, ":") {
|
||||||
|
splitHostPort := strings.Split(hostport, ":")
|
||||||
|
host, port = splitHostPort[0], splitHostPort[1]
|
||||||
|
_, error := strconv.Atoi(splitHostPort[1])
|
||||||
|
if error != nil {
|
||||||
|
return fmt.Errorf("illegal url format")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
host = hostport
|
||||||
|
switch protocol {
|
||||||
|
case "ldap":
|
||||||
|
port = "389"
|
||||||
|
case "ldaps":
|
||||||
|
port = "636"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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", fmt.Sprintf("%s:%s", host, port))
|
||||||
|
case "ldaps":
|
||||||
|
ldap, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%s", host, port), &tls.Config{InsecureSkipVerify: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ldap.Close()
|
||||||
|
|
||||||
|
ldapSearchDn := ldapConfs.LdapSearchDn
|
||||||
|
if ldapSearchDn != "" {
|
||||||
|
log.Debug("Search DN: ", ldapSearchDn)
|
||||||
|
ldapSearchPassword := ldapConfs.LdapSearchPassword
|
||||||
|
err = ldap.Bind(ldapSearchDn, ldapSearchPassword)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Bind search dn error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
95
src/ui/api/ldap_test.go
Normal file
95
src/ui/api/ldap_test.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ldapConf apilib.LdapConf
|
||||||
|
|
||||||
|
func TestLdapPost(t *testing.T) {
|
||||||
|
fmt.Println("Testing ldap post")
|
||||||
|
assert := assert.New(t)
|
||||||
|
apiTest := newHarborAPI()
|
||||||
|
|
||||||
|
//case 1: ping ldap server without admin role
|
||||||
|
CommonAddUser()
|
||||||
|
code, err := apiTest.LdapPost(*testUser, ldapConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error occured while ping ldap server")
|
||||||
|
t.Log(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(403, code, "Ping ldap server status should be 403")
|
||||||
|
}
|
||||||
|
//case 2: ping ldap server with admin role, but empty ldapConf
|
||||||
|
code, err = apiTest.LdapPost(*admin, ldapConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error occured while ping ldap server")
|
||||||
|
t.Log(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(400, code, "Ping ldap server status should be 400")
|
||||||
|
}
|
||||||
|
|
||||||
|
//case 3: ping ldap server with admin role, but bad format of ldapConf
|
||||||
|
ldapConf.LdapURL = "http://127.0.0.1"
|
||||||
|
code, err = apiTest.LdapPost(*admin, ldapConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error occured while ping ldap server")
|
||||||
|
t.Log(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(400, code, "Ping ldap server status should be 400")
|
||||||
|
}
|
||||||
|
//case 4: ping ldap server with admin role, but bad format of ldapConf
|
||||||
|
ldapConf.LdapURL = "127.0.0.1:sss"
|
||||||
|
code, err = apiTest.LdapPost(*admin, ldapConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error occured while ping ldap server")
|
||||||
|
t.Log(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(400, code, "Ping ldap server status should be 400")
|
||||||
|
}
|
||||||
|
//case 5: ping ldap server with admin role, ldap protocol, without port
|
||||||
|
ldapConf.LdapURL = "127.0.0.1"
|
||||||
|
code, err = apiTest.LdapPost(*admin, ldapConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error occured while ping ldap server")
|
||||||
|
t.Log(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(200, code, "Ping ldap server status should be 200")
|
||||||
|
}
|
||||||
|
//not success, will try later
|
||||||
|
/*
|
||||||
|
//case 6: ping ldap server with admin role, ldaps protocol without port
|
||||||
|
ldapConf.LdapURL = "ldaps://127.0.0.1"
|
||||||
|
code, err = apiTest.LdapPost(*admin, ldapConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error occured while ping ldap server")
|
||||||
|
t.Log(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(200, code, "Ping ldap server status should be 200")
|
||||||
|
}*/
|
||||||
|
//case 7: ping ldap server with admin role, ldap protocol, port, ldapSearchDn, but wrong password
|
||||||
|
ldapConf.LdapURL = "ldap://127.0.0.1:389"
|
||||||
|
ldapConf.LdapSearchDn = "cn=admin,dc=example,dc=org"
|
||||||
|
code, err = apiTest.LdapPost(*admin, ldapConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error occured while ping ldap server")
|
||||||
|
t.Log(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(400, code, "Ping ldap server status should be 400")
|
||||||
|
}
|
||||||
|
//case 8: ping ldap server with admin role, ldap protocol, port, ldapSearchDn, right password
|
||||||
|
ldapConf.LdapURL = "ldap://127.0.0.1:389"
|
||||||
|
ldapConf.LdapSearchDn = "cn=admin,dc=example,dc=org"
|
||||||
|
ldapConf.LdapSearchPassword = "admin"
|
||||||
|
code, err = apiTest.LdapPost(*admin, ldapConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error occured while ping ldap server")
|
||||||
|
t.Log(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(200, code, "Ping ldap server status should be 200")
|
||||||
|
}
|
||||||
|
CommonDelUser()
|
||||||
|
}
|
@ -78,7 +78,13 @@ func (p *ProjectAPI) Post() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to check admin role: %v", err)
|
log.Errorf("Failed to check admin role: %v", err)
|
||||||
}
|
}
|
||||||
if !isSysAdmin && config.OnlyAdminCreateProject() {
|
|
||||||
|
onlyAdmin, err := config.OnlyAdminCreateProject()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to determine whether only admin can create projects: %v", err)
|
||||||
|
p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
if !isSysAdmin && onlyAdmin {
|
||||||
log.Errorf("Only sys admin can create project")
|
log.Errorf("Only sys admin can create project")
|
||||||
p.RenderError(http.StatusForbidden, "Only system admin can create project")
|
p.RenderError(http.StatusForbidden, "Only system admin can create project")
|
||||||
return
|
return
|
||||||
|
@ -66,7 +66,7 @@ func (ra *RepositoryAPI) Get() {
|
|||||||
if project.Public == 0 {
|
if project.Public == 0 {
|
||||||
var userID int
|
var userID int
|
||||||
|
|
||||||
if svc_utils.VerifySecret(ra.Ctx.Request) {
|
if svc_utils.VerifySecret(ra.Ctx.Request, config.JobserviceSecret()) {
|
||||||
userID = 1
|
userID = 1
|
||||||
} else {
|
} else {
|
||||||
userID = ra.ValidateUser()
|
userID = ra.ValidateUser()
|
||||||
@ -361,11 +361,19 @@ func (ra *RepositoryAPI) GetManifests() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {
|
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {
|
||||||
endpoint := config.InternalRegistryURL()
|
endpoint, err := config.RegistryURL()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
verify, err := config.VerifyRemoteCert()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
username, password, ok := ra.Ctx.Request.BasicAuth()
|
username, password, ok := ra.Ctx.Request.BasicAuth()
|
||||||
if ok {
|
if ok {
|
||||||
return newRepositoryClient(endpoint, api.GetIsInsecure(), username, password,
|
return newRepositoryClient(endpoint, !verify, username, password,
|
||||||
repoName, "repository", repoName, "pull", "push", "*")
|
repoName, "repository", repoName, "pull", "push", "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +382,7 @@ func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return cache.NewRepositoryClient(endpoint, api.GetIsInsecure(), username, repoName,
|
return cache.NewRepositoryClient(endpoint, !verify, username, repoName,
|
||||||
"repository", repoName, "pull", "push", "*")
|
"repository", repoName, "pull", "push", "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,7 +442,9 @@ func newRepositoryClient(endpoint string, insecure bool, username, password, rep
|
|||||||
scopeActions ...string) (*registry.Repository, error) {
|
scopeActions ...string) (*registry.Repository, error) {
|
||||||
|
|
||||||
credential := auth.NewBasicAuthCredential(username, password)
|
credential := auth.NewBasicAuthCredential(username, password)
|
||||||
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
|
|
||||||
|
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure,
|
||||||
|
config.InternalTokenServiceEndpoint(), scopeType, scopeName, scopeActions...)
|
||||||
|
|
||||||
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -41,7 +41,12 @@ type TargetAPI struct {
|
|||||||
|
|
||||||
// Prepare validates the user
|
// Prepare validates the user
|
||||||
func (t *TargetAPI) Prepare() {
|
func (t *TargetAPI) Prepare() {
|
||||||
t.secretKey = config.SecretKey()
|
var err error
|
||||||
|
t.secretKey, err = config.SecretKey()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get secret key: %v", err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
userID := t.ValidateUser()
|
userID := t.ValidateUser()
|
||||||
isSysAdmin, err := dao.IsAdminRole(userID)
|
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||||
@ -97,7 +102,12 @@ func (t *TargetAPI) Ping() {
|
|||||||
password = t.GetString("password")
|
password = t.GetString("password")
|
||||||
}
|
}
|
||||||
|
|
||||||
registry, err := newRegistryClient(endpoint, api.GetIsInsecure(), username, password,
|
verify, err := config.VerifyRemoteCert()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to check whether insecure or not: %v", err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
registry, err := newRegistryClient(endpoint, !verify, username, password,
|
||||||
"", "", "")
|
"", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// timeout, dns resolve error, connection refused, etc.
|
// timeout, dns resolve error, connection refused, etc.
|
||||||
@ -330,7 +340,9 @@ func (t *TargetAPI) Delete() {
|
|||||||
func newRegistryClient(endpoint string, insecure bool, username, password, scopeType, scopeName string,
|
func newRegistryClient(endpoint string, insecure bool, username, password, scopeType, scopeName string,
|
||||||
scopeActions ...string) (*registry.Registry, error) {
|
scopeActions ...string) (*registry.Registry, error) {
|
||||||
credential := auth.NewBasicAuthCredential(username, password)
|
credential := auth.NewBasicAuthCredential(username, password)
|
||||||
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
|
|
||||||
|
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure,
|
||||||
|
"", scopeType, scopeName, scopeActions...)
|
||||||
|
|
||||||
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -30,17 +30,18 @@ func TestTargetsPost(t *testing.T) {
|
|||||||
|
|
||||||
//-------------------case 1 : response code = 201------------------------//
|
//-------------------case 1 : response code = 201------------------------//
|
||||||
fmt.Println("case 1 : response code = 201")
|
fmt.Println("case 1 : response code = 201")
|
||||||
httpStatusCode, err = apiTest.AddTargets(*admin, *repTargets)
|
httpStatusCode, body, err := apiTest.AddTargets(*admin, *repTargets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error whihle add targets", err.Error())
|
t.Error("Error whihle add targets", err.Error())
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(int(201), httpStatusCode, "httpStatusCode should be 201")
|
assert.Equal(int(201), httpStatusCode, "httpStatusCode should be 201")
|
||||||
|
t.Log(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------case 2 : response code = 409,name is already used-----------//
|
//-----------case 2 : response code = 409,name is already used-----------//
|
||||||
fmt.Println("case 2 : response code = 409,name is already used")
|
fmt.Println("case 2 : response code = 409,name is already used")
|
||||||
httpStatusCode, err = apiTest.AddTargets(*admin, *repTargets)
|
httpStatusCode, _, err = apiTest.AddTargets(*admin, *repTargets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error whihle add targets", err.Error())
|
t.Error("Error whihle add targets", err.Error())
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
@ -51,7 +52,7 @@ func TestTargetsPost(t *testing.T) {
|
|||||||
//-----------case 3 : response code = 409,name is already used-----------//
|
//-----------case 3 : response code = 409,name is already used-----------//
|
||||||
fmt.Println("case 3 : response code = 409,endPoint is already used")
|
fmt.Println("case 3 : response code = 409,endPoint is already used")
|
||||||
repTargets.Username = "errName"
|
repTargets.Username = "errName"
|
||||||
httpStatusCode, err = apiTest.AddTargets(*admin, *repTargets)
|
httpStatusCode, _, err = apiTest.AddTargets(*admin, *repTargets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error whihle add targets", err.Error())
|
t.Error("Error whihle add targets", err.Error())
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
@ -61,7 +62,7 @@ func TestTargetsPost(t *testing.T) {
|
|||||||
|
|
||||||
//--------case 4 : response code = 401,User need to log in first.--------//
|
//--------case 4 : response code = 401,User need to log in first.--------//
|
||||||
fmt.Println("case 4 : response code = 401,User need to log in first.")
|
fmt.Println("case 4 : response code = 401,User need to log in first.")
|
||||||
httpStatusCode, err = apiTest.AddTargets(*unknownUsr, *repTargets)
|
httpStatusCode, _, err = apiTest.AddTargets(*unknownUsr, *repTargets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error whihle add targets", err.Error())
|
t.Error("Error whihle add targets", err.Error())
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
|
@ -46,10 +46,21 @@ type passwordReq struct {
|
|||||||
|
|
||||||
// Prepare validates the URL and parms
|
// Prepare validates the URL and parms
|
||||||
func (ua *UserAPI) Prepare() {
|
func (ua *UserAPI) Prepare() {
|
||||||
|
mode, err := config.AuthMode()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get auth mode: %v", err)
|
||||||
|
ua.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
ua.AuthMode = config.AuthMode()
|
ua.AuthMode = mode
|
||||||
|
|
||||||
ua.SelfRegistration = config.SelfRegistration()
|
self, err := config.SelfRegistration()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get self registration: %v", err)
|
||||||
|
ua.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
ua.SelfRegistration = self
|
||||||
|
|
||||||
if ua.Ctx.Input.IsPost() {
|
if ua.Ctx.Input.IsPost() {
|
||||||
sessionUserID := ua.GetSession("userId")
|
sessionUserID := ua.GetSession("userId")
|
||||||
@ -82,7 +93,6 @@ func (ua *UserAPI) Prepare() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
ua.IsAdmin, err = dao.IsAdminRole(ua.currentUserID)
|
ua.IsAdmin, err = dao.IsAdminRole(ua.currentUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error occurred in IsAdminRole:%v", err)
|
log.Errorf("Error occurred in IsAdminRole:%v", err)
|
||||||
@ -234,7 +244,7 @@ func (ua *UserAPI) Delete() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AuthMode() == "ldap_auth" {
|
if ua.AuthMode == "ldap_auth" {
|
||||||
ua.CustomAbort(http.StatusForbidden, "user can not be deleted in LDAP authentication mode")
|
ua.CustomAbort(http.StatusForbidden, "user can not be deleted in LDAP authentication mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,11 +20,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"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"
|
||||||
@ -242,7 +240,7 @@ func addAuthentication(req *http.Request) {
|
|||||||
// SyncRegistry syncs the repositories of registry with database.
|
// SyncRegistry syncs the repositories of registry with database.
|
||||||
func SyncRegistry() error {
|
func SyncRegistry() error {
|
||||||
|
|
||||||
log.Debugf("Start syncing repositories from registry to DB... ")
|
log.Infof("Start syncing repositories from registry to DB... ")
|
||||||
|
|
||||||
reposInRegistry, err := catalog()
|
reposInRegistry, err := catalog()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -304,7 +302,7 @@ func SyncRegistry() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Sync repositories from registry to DB is done.")
|
log.Infof("Sync repositories from registry to DB is done.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,7 +348,10 @@ func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove the workaround when the bug of registry is fixed
|
// TODO remove the workaround when the bug of registry is fixed
|
||||||
endpoint := config.InternalRegistryURL()
|
endpoint, err := config.RegistryURL()
|
||||||
|
if err != nil {
|
||||||
|
return needsAdd, needsDel, err
|
||||||
|
}
|
||||||
client, err := cache.NewRepositoryClient(endpoint, true,
|
client, err := cache.NewRepositoryClient(endpoint, true,
|
||||||
"admin", repoInR, "repository", repoInR)
|
"admin", repoInR, "repository", repoInR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -372,7 +373,10 @@ func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string
|
|||||||
j++
|
j++
|
||||||
} else {
|
} else {
|
||||||
// TODO remove the workaround when the bug of registry is fixed
|
// TODO remove the workaround when the bug of registry is fixed
|
||||||
endpoint := config.InternalRegistryURL()
|
endpoint, err := config.RegistryURL()
|
||||||
|
if err != nil {
|
||||||
|
return needsAdd, needsDel, err
|
||||||
|
}
|
||||||
client, err := cache.NewRepositoryClient(endpoint, true,
|
client, err := cache.NewRepositoryClient(endpoint, true,
|
||||||
"admin", repoInR, "repository", repoInR)
|
"admin", repoInR, "repository", repoInR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -422,32 +426,18 @@ func projectExists(repository string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initRegistryClient() (r *registry.Registry, err error) {
|
func initRegistryClient() (r *registry.Registry, err error) {
|
||||||
endpoint := config.InternalRegistryURL()
|
endpoint, err := config.RegistryURL()
|
||||||
|
if err != nil {
|
||||||
addr := endpoint
|
return nil, err
|
||||||
if strings.Contains(endpoint, "/") {
|
|
||||||
addr = endpoint[strings.LastIndex(endpoint, "/")+1:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan int, 1)
|
addr := endpoint
|
||||||
go func() {
|
if strings.Contains(endpoint, "://") {
|
||||||
var err error
|
addr = strings.Split(endpoint, "://")[1]
|
||||||
var c net.Conn
|
}
|
||||||
for {
|
|
||||||
c, err = net.DialTimeout("tcp", addr, 20*time.Second)
|
if err := utils.TestTCPConn(addr, 60, 2); err != nil {
|
||||||
if err == nil {
|
return nil, err
|
||||||
c.Close()
|
|
||||||
ch <- 1
|
|
||||||
} else {
|
|
||||||
log.Errorf("failed to connect to registry client, retry after 2 seconds :%v", err)
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-ch:
|
|
||||||
case <-time.After(60 * time.Second):
|
|
||||||
panic("Failed to connect to registry client after 60 seconds")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registryClient, err := cache.NewRegistryClient(endpoint, true, "admin",
|
registryClient, err := cache.NewRegistryClient(endpoint, true, "admin",
|
||||||
|
@ -50,7 +50,10 @@ func Register(name string, authenticator Authenticator) {
|
|||||||
// Login authenticates user credentials based on setting.
|
// Login authenticates user credentials based on setting.
|
||||||
func Login(m models.AuthModel) (*models.User, error) {
|
func Login(m models.AuthModel) (*models.User, error) {
|
||||||
|
|
||||||
var authMode = config.AuthMode()
|
authMode, err := config.AuthMode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if authMode == "" || m.Principal == "admin" {
|
if authMode == "" || m.Principal == "admin" {
|
||||||
authMode = "db_auth"
|
authMode = "db_auth"
|
||||||
}
|
}
|
||||||
|
@ -16,18 +16,15 @@
|
|||||||
package ldap
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"crypto/tls"
|
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
|
||||||
|
|
||||||
"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/ui/auth"
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
|
|
||||||
@ -41,9 +38,8 @@ const metaChars = "&|!=~*<>()"
|
|||||||
|
|
||||||
// Connect checks the LDAP configuration directives, and connects to the LDAP URL
|
// Connect checks the LDAP configuration directives, and connects to the LDAP URL
|
||||||
// Returns an LDAP connection
|
// Returns an LDAP connection
|
||||||
func Connect() (*goldap.Conn, error) {
|
func Connect(settings *models.LDAP) (*goldap.Conn, error) {
|
||||||
|
ldapURL := settings.URL
|
||||||
ldapURL := config.LDAP().URL
|
|
||||||
if ldapURL == "" {
|
if ldapURL == "" {
|
||||||
return nil, errors.New("can not get any available LDAP_URL")
|
return nil, errors.New("can not get any available LDAP_URL")
|
||||||
}
|
}
|
||||||
@ -70,13 +66,10 @@ func Connect() (*goldap.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sets a Dial Timeout for LDAP
|
// Sets a Dial Timeout for LDAP
|
||||||
cTimeout := config.LDAP().ConnectTimeout
|
goldap.DefaultTimeout = time.Duration(settings.Timeout) * time.Second
|
||||||
connectTimeout, _ := strconv.Atoi(cTimeout)
|
|
||||||
goldap.DefaultTimeout = time.Duration(connectTimeout) * time.Second
|
|
||||||
|
|
||||||
var ldap *goldap.Conn
|
var ldap *goldap.Conn
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch protocol {
|
switch protocol {
|
||||||
case "ldap":
|
case "ldap":
|
||||||
ldap, err = goldap.Dial("tcp", fmt.Sprintf("%s:%s", host, port))
|
ldap, err = goldap.Dial("tcp", fmt.Sprintf("%s:%s", host, port))
|
||||||
@ -104,21 +97,26 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ldap, err := Connect()
|
settings, err := config.LDAP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapBaseDn := config.LDAP().BaseDn
|
ldap, err := Connect(settings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ldapBaseDn := settings.BaseDN
|
||||||
if ldapBaseDn == "" {
|
if ldapBaseDn == "" {
|
||||||
return nil, errors.New("can not get any available LDAP_BASE_DN")
|
return nil, errors.New("can not get any available LDAP_BASE_DN")
|
||||||
}
|
}
|
||||||
log.Debug("baseDn:", ldapBaseDn)
|
log.Debug("baseDn:", ldapBaseDn)
|
||||||
|
|
||||||
ldapSearchDn := config.LDAP().SearchDn
|
ldapSearchDn := settings.SearchDN
|
||||||
if ldapSearchDn != "" {
|
if ldapSearchDn != "" {
|
||||||
log.Debug("Search DN: ", ldapSearchDn)
|
log.Debug("Search DN: ", ldapSearchDn)
|
||||||
ldapSearchPwd := config.LDAP().SearchPwd
|
ldapSearchPwd := settings.SearchPassword
|
||||||
err = ldap.Bind(ldapSearchDn, ldapSearchPwd)
|
err = ldap.Bind(ldapSearchDn, ldapSearchPwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Bind search dn error", err)
|
log.Debug("Bind search dn error", err)
|
||||||
@ -126,8 +124,8 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attrName := config.LDAP().UID
|
attrName := settings.UID
|
||||||
filter := config.LDAP().Filter
|
filter := settings.Filter
|
||||||
if filter != "" {
|
if filter != "" {
|
||||||
filter = "(&" + filter + "(" + attrName + "=" + m.Principal + "))"
|
filter = "(&" + filter + "(" + attrName + "=" + m.Principal + "))"
|
||||||
} else {
|
} else {
|
||||||
@ -135,11 +133,11 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
|||||||
}
|
}
|
||||||
log.Debug("one or more filter", filter)
|
log.Debug("one or more filter", filter)
|
||||||
|
|
||||||
ldapScope := config.LDAP().Scope
|
ldapScope := settings.Scope
|
||||||
var scope int
|
var scope int
|
||||||
if ldapScope == "1" {
|
if ldapScope == 1 {
|
||||||
scope = goldap.ScopeBaseObject
|
scope = goldap.ScopeBaseObject
|
||||||
} else if ldapScope == "2" {
|
} else if ldapScope == 2 {
|
||||||
scope = goldap.ScopeSingleLevel
|
scope = goldap.ScopeSingleLevel
|
||||||
} else {
|
} else {
|
||||||
scope = goldap.ScopeWholeSubtree
|
scope = goldap.ScopeWholeSubtree
|
||||||
|
@ -13,145 +13,243 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package config provides methods to get configurations required by code in src/ui
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"encoding/json"
|
||||||
"strings"
|
"os"
|
||||||
|
|
||||||
commonConfig "github.com/vmware/harbor/src/common/config"
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LDAPSetting wraps the setting of an LDAP server
|
const defaultKeyPath string = "/etc/ui/key"
|
||||||
type LDAPSetting struct {
|
|
||||||
URL string
|
|
||||||
BaseDn string
|
|
||||||
SearchDn string
|
|
||||||
SearchPwd string
|
|
||||||
UID string
|
|
||||||
Filter string
|
|
||||||
Scope string
|
|
||||||
ConnectTimeout string
|
|
||||||
}
|
|
||||||
|
|
||||||
type uiParser struct{}
|
var (
|
||||||
|
mg *comcfg.Manager
|
||||||
|
keyProvider comcfg.KeyProvider
|
||||||
|
)
|
||||||
|
|
||||||
// Parse parses the auth settings url settings and other configuration consumed by code under src/ui
|
// Init configurations
|
||||||
func (up *uiParser) Parse(raw map[string]string, config map[string]interface{}) error {
|
func Init() error {
|
||||||
mode := raw["AUTH_MODE"]
|
//init key provider
|
||||||
if mode == "ldap_auth" {
|
initKeyProvider()
|
||||||
setting := LDAPSetting{
|
|
||||||
URL: raw["LDAP_URL"],
|
adminServerURL := os.Getenv("ADMIN_SERVER_URL")
|
||||||
BaseDn: raw["LDAP_BASE_DN"],
|
if len(adminServerURL) == 0 {
|
||||||
SearchDn: raw["LDAP_SEARCH_DN"],
|
adminServerURL = "http://adminserver"
|
||||||
SearchPwd: raw["LDAP_SEARCH_PWD"],
|
|
||||||
UID: raw["LDAP_UID"],
|
|
||||||
Filter: raw["LDAP_FILTER"],
|
|
||||||
Scope: raw["LDAP_SCOPE"],
|
|
||||||
ConnectTimeout: raw["LDAP_CONNECT_TIMEOUT"],
|
|
||||||
}
|
|
||||||
config["ldap"] = setting
|
|
||||||
}
|
}
|
||||||
config["auth_mode"] = mode
|
log.Debugf("admin server URL: %s", adminServerURL)
|
||||||
var tokenExpiration = 30 //minutes
|
mg = comcfg.NewManager(adminServerURL, UISecret(), true)
|
||||||
if len(raw["TOKEN_EXPIRATION"]) > 0 {
|
|
||||||
i, err := strconv.Atoi(raw["TOKEN_EXPIRATION"])
|
if err := mg.Init(); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
log.Warningf("failed to parse token expiration: %v, using default value %d", err, tokenExpiration)
|
|
||||||
} else if i <= 0 {
|
|
||||||
log.Warningf("invalid token expiration, using default value: %d minutes", tokenExpiration)
|
|
||||||
} else {
|
|
||||||
tokenExpiration = i
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
config["token_exp"] = tokenExpiration
|
|
||||||
config["admin_password"] = raw["HARBOR_ADMIN_PASSWORD"]
|
if _, err := mg.Load(); err != nil {
|
||||||
config["ext_reg_url"] = raw["EXT_REG_URL"]
|
return err
|
||||||
config["ui_secret"] = raw["UI_SECRET"]
|
}
|
||||||
config["secret_key"] = raw["SECRET_KEY"]
|
|
||||||
config["self_registration"] = raw["SELF_REGISTRATION"] != "off"
|
|
||||||
config["admin_create_project"] = strings.ToLower(raw["PROJECT_CREATION_RESTRICTION"]) == "adminonly"
|
|
||||||
registryURL := raw["REGISTRY_URL"]
|
|
||||||
registryURL = strings.TrimRight(registryURL, "/")
|
|
||||||
config["internal_registry_url"] = registryURL
|
|
||||||
jobserviceURL := raw["JOB_SERVICE_URL"]
|
|
||||||
jobserviceURL = strings.TrimRight(jobserviceURL, "/")
|
|
||||||
config["internal_jobservice_url"] = jobserviceURL
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var uiConfig *commonConfig.Config
|
func initKeyProvider() {
|
||||||
|
path := os.Getenv("KEY_PATH")
|
||||||
|
if len(path) == 0 {
|
||||||
|
path = defaultKeyPath
|
||||||
|
}
|
||||||
|
log.Infof("key path: %s", path)
|
||||||
|
|
||||||
func init() {
|
keyProvider = comcfg.NewFileKeyProvider(path)
|
||||||
uiKeys := []string{"AUTH_MODE", "LDAP_URL", "LDAP_BASE_DN", "LDAP_SEARCH_DN", "LDAP_SEARCH_PWD", "LDAP_UID", "LDAP_FILTER", "LDAP_SCOPE", "LDAP_CONNECT_TIMEOUT", "TOKEN_EXPIRATION", "HARBOR_ADMIN_PASSWORD", "EXT_REG_URL", "UI_SECRET", "SECRET_KEY", "SELF_REGISTRATION", "PROJECT_CREATION_RESTRICTION", "REGISTRY_URL", "JOB_SERVICE_URL"}
|
|
||||||
uiConfig = &commonConfig.Config{
|
|
||||||
Config: make(map[string]interface{}),
|
|
||||||
Loader: &commonConfig.EnvConfigLoader{Keys: uiKeys},
|
|
||||||
Parser: &uiParser{},
|
|
||||||
}
|
|
||||||
if err := uiConfig.Load(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload ...
|
// Load configurations
|
||||||
func Reload() error {
|
func Load() error {
|
||||||
return uiConfig.Load()
|
_, err := mg.Load()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload uploads all system configutations to admin server
|
||||||
|
func Upload(cfg map[string]interface{}) error {
|
||||||
|
b, err := json.Marshal(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return mg.Upload(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemCfg returns the system configurations
|
||||||
|
func GetSystemCfg() (map[string]interface{}, error) {
|
||||||
|
raw, err := mg.Loader.Load()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := mg.Parser.Parse(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthMode ...
|
// AuthMode ...
|
||||||
func AuthMode() string {
|
func AuthMode() (string, error) {
|
||||||
return uiConfig.Config["auth_mode"].(string)
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg[comcfg.AUTHMode].(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LDAP returns the setting of ldap server
|
// LDAP returns the setting of ldap server
|
||||||
func LDAP() LDAPSetting {
|
func LDAP() (*models.LDAP, error) {
|
||||||
return uiConfig.Config["ldap"].(LDAPSetting)
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ldap := &models.LDAP{}
|
||||||
|
ldap.URL = cfg[comcfg.LDAPURL].(string)
|
||||||
|
ldap.SearchDN = cfg[comcfg.LDAPSearchDN].(string)
|
||||||
|
ldap.SearchPassword = cfg[comcfg.LDAPSearchPwd].(string)
|
||||||
|
ldap.BaseDN = cfg[comcfg.LDAPBaseDN].(string)
|
||||||
|
ldap.UID = cfg[comcfg.LDAPUID].(string)
|
||||||
|
ldap.Filter = cfg[comcfg.LDAPFilter].(string)
|
||||||
|
ldap.Scope = int(cfg[comcfg.LDAPScope].(float64))
|
||||||
|
ldap.Timeout = int(cfg[comcfg.LDAPTimeout].(float64))
|
||||||
|
|
||||||
|
return ldap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenExpiration returns the token expiration time (in minute)
|
// TokenExpiration returns the token expiration time (in minute)
|
||||||
func TokenExpiration() int {
|
func TokenExpiration() (int, error) {
|
||||||
return uiConfig.Config["token_exp"].(int)
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(cfg[comcfg.TokenExpiration].(float64)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtRegistryURL returns the registry URL to exposed to external client
|
// ExtEndpoint returns the external URL of Harbor: protocal://host:port
|
||||||
func ExtRegistryURL() string {
|
func ExtEndpoint() (string, error) {
|
||||||
return uiConfig.Config["ext_reg_url"].(string)
|
cfg, err := mg.Get()
|
||||||
}
|
if err != nil {
|
||||||
|
return "", err
|
||||||
// UISecret returns the value of UI secret cookie, used for communication between UI and JobService
|
}
|
||||||
func UISecret() string {
|
return cfg[comcfg.ExtEndpoint].(string), nil
|
||||||
return uiConfig.Config["ui_secret"].(string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecretKey returns the secret key to encrypt the password of target
|
// SecretKey returns the secret key to encrypt the password of target
|
||||||
func SecretKey() string {
|
func SecretKey() (string, error) {
|
||||||
return uiConfig.Config["secret_key"].(string)
|
return keyProvider.Get(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelfRegistration returns the enablement of self registration
|
// SelfRegistration returns the enablement of self registration
|
||||||
func SelfRegistration() bool {
|
func SelfRegistration() (bool, error) {
|
||||||
return uiConfig.Config["self_registration"].(bool)
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return cfg[comcfg.SelfRegistration].(bool), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalRegistryURL returns registry URL for internal communication between Harbor containers
|
// RegistryURL ...
|
||||||
func InternalRegistryURL() string {
|
func RegistryURL() (string, error) {
|
||||||
return uiConfig.Config["internal_registry_url"].(string)
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg[comcfg.RegistryURL].(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalJobServiceURL returns jobservice URL for internal communication between Harbor containers
|
// InternalJobServiceURL returns jobservice URL for internal communication between Harbor containers
|
||||||
func InternalJobServiceURL() string {
|
func InternalJobServiceURL() string {
|
||||||
return uiConfig.Config["internal_jobservice_url"].(string)
|
return "http://jobservice"
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalTokenServiceEndpoint returns token service endpoint for internal communication between Harbor containers
|
||||||
|
func InternalTokenServiceEndpoint() string {
|
||||||
|
return "http://ui/service/token"
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitialAdminPassword returns the initial password for administrator
|
// InitialAdminPassword returns the initial password for administrator
|
||||||
func InitialAdminPassword() string {
|
func InitialAdminPassword() (string, error) {
|
||||||
return uiConfig.Config["admin_password"].(string)
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg[comcfg.AdminInitialPassword].(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnlyAdminCreateProject returns the flag to restrict that only sys admin can create project
|
// OnlyAdminCreateProject returns the flag to restrict that only sys admin can create project
|
||||||
func OnlyAdminCreateProject() bool {
|
func OnlyAdminCreateProject() (bool, error) {
|
||||||
return uiConfig.Config["admin_create_project"].(bool)
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return cfg[comcfg.ProjectCreationRestriction].(string) == comcfg.ProCrtRestrAdmOnly, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyRemoteCert returns bool value.
|
||||||
|
func VerifyRemoteCert() (bool, error) {
|
||||||
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return cfg[comcfg.VerifyRemoteCert].(bool), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email returns email server settings
|
||||||
|
func Email() (*models.Email, error) {
|
||||||
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
email := &models.Email{}
|
||||||
|
email.Host = cfg[comcfg.EmailHost].(string)
|
||||||
|
email.Port = int(cfg[comcfg.EmailPort].(float64))
|
||||||
|
email.Username = cfg[comcfg.EmailUsername].(string)
|
||||||
|
email.Password = cfg[comcfg.EmailPassword].(string)
|
||||||
|
email.SSL = cfg[comcfg.EmailSSL].(bool)
|
||||||
|
email.From = cfg[comcfg.EmailFrom].(string)
|
||||||
|
email.Identity = cfg[comcfg.EmailIdentity].(string)
|
||||||
|
|
||||||
|
return email, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database returns database settings
|
||||||
|
func Database() (*models.Database, error) {
|
||||||
|
cfg, err := mg.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
database := &models.Database{}
|
||||||
|
database.Type = cfg[comcfg.DatabaseType].(string)
|
||||||
|
mysql := &models.MySQL{}
|
||||||
|
mysql.Host = cfg[comcfg.MySQLHost].(string)
|
||||||
|
mysql.Port = int(cfg[comcfg.MySQLPort].(float64))
|
||||||
|
mysql.Username = cfg[comcfg.MySQLUsername].(string)
|
||||||
|
mysql.Password = cfg[comcfg.MySQLPassword].(string)
|
||||||
|
mysql.Database = cfg[comcfg.MySQLDatabase].(string)
|
||||||
|
database.MySQL = mysql
|
||||||
|
sqlite := &models.SQLite{}
|
||||||
|
sqlite.File = cfg[comcfg.SQLiteFile].(string)
|
||||||
|
database.SQLite = sqlite
|
||||||
|
|
||||||
|
return database, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UISecret returns a secret to mark UI when communicate with
|
||||||
|
// other component
|
||||||
|
func UISecret() string {
|
||||||
|
return os.Getenv("UI_SECRET")
|
||||||
|
}
|
||||||
|
|
||||||
|
// JobserviceSecret returns a secret to mark Jobservice when communicate with
|
||||||
|
// other component
|
||||||
|
func JobserviceSecret() string {
|
||||||
|
return os.Getenv("JOBSERVICE_SECRET")
|
||||||
}
|
}
|
||||||
|
@ -17,131 +17,107 @@ package config
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// test functions under package ui/config
|
||||||
auth = "ldap_auth"
|
func TestConfig(t *testing.T) {
|
||||||
ldap = LDAPSetting{
|
server, err := test.NewAdminserver(nil)
|
||||||
"ldap://test.ldap.com",
|
|
||||||
"ou=people",
|
|
||||||
"dc=whatever,dc=org",
|
|
||||||
"1234567",
|
|
||||||
"cn",
|
|
||||||
"uid",
|
|
||||||
"2",
|
|
||||||
"5",
|
|
||||||
}
|
|
||||||
tokenExp = "3"
|
|
||||||
tokenExpRes = 3
|
|
||||||
adminPassword = "password"
|
|
||||||
externalRegURL = "127.0.0.1"
|
|
||||||
uiSecret = "ffadsdfsdf"
|
|
||||||
secretKey = "keykey"
|
|
||||||
selfRegistration = "off"
|
|
||||||
projectCreationRestriction = "adminonly"
|
|
||||||
internalRegistryURL = "http://registry:5000"
|
|
||||||
jobServiceURL = "http://jobservice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
|
|
||||||
os.Setenv("AUTH_MODE", auth)
|
|
||||||
os.Setenv("LDAP_URL", ldap.URL)
|
|
||||||
os.Setenv("LDAP_BASE_DN", ldap.BaseDn)
|
|
||||||
os.Setenv("LDAP_SEARCH_DN", ldap.SearchDn)
|
|
||||||
os.Setenv("LDAP_SEARCH_PWD", ldap.SearchPwd)
|
|
||||||
os.Setenv("LDAP_UID", ldap.UID)
|
|
||||||
os.Setenv("LDAP_SCOPE", ldap.Scope)
|
|
||||||
os.Setenv("LDAP_FILTER", ldap.Filter)
|
|
||||||
os.Setenv("LDAP_CONNECT_TIMEOUT", ldap.ConnectTimeout)
|
|
||||||
os.Setenv("TOKEN_EXPIRATION", tokenExp)
|
|
||||||
os.Setenv("HARBOR_ADMIN_PASSWORD", adminPassword)
|
|
||||||
os.Setenv("EXT_REG_URL", externalRegURL)
|
|
||||||
os.Setenv("UI_SECRET", uiSecret)
|
|
||||||
os.Setenv("SECRET_KEY", secretKey)
|
|
||||||
os.Setenv("SELF_REGISTRATION", selfRegistration)
|
|
||||||
os.Setenv("PROJECT_CREATION_RESTRICTION", projectCreationRestriction)
|
|
||||||
os.Setenv("REGISTRY_URL", internalRegistryURL)
|
|
||||||
os.Setenv("JOB_SERVICE_URL", jobServiceURL)
|
|
||||||
|
|
||||||
err := Reload()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
t.Fatalf("failed to create a mock admin server: %v", err)
|
||||||
}
|
}
|
||||||
rc := m.Run()
|
defer server.Close()
|
||||||
|
|
||||||
os.Unsetenv("AUTH_MODE")
|
if err := os.Setenv("ADMIN_SERVER_URL", server.URL); err != nil {
|
||||||
os.Unsetenv("LDAP_URL")
|
t.Fatalf("failed to set env %s: %v", "ADMIN_SERVER_URL", err)
|
||||||
os.Unsetenv("LDAP_BASE_DN")
|
|
||||||
os.Unsetenv("LDAP_SEARCH_DN")
|
|
||||||
os.Unsetenv("LDAP_SEARCH_PWD")
|
|
||||||
os.Unsetenv("LDAP_UID")
|
|
||||||
os.Unsetenv("LDAP_SCOPE")
|
|
||||||
os.Unsetenv("LDAP_FILTER")
|
|
||||||
os.Unsetenv("LDAP_CONNECT_TIMEOUT")
|
|
||||||
os.Unsetenv("TOKEN_EXPIRATION")
|
|
||||||
os.Unsetenv("HARBOR_ADMIN_PASSWORD")
|
|
||||||
os.Unsetenv("EXT_REG_URL")
|
|
||||||
os.Unsetenv("UI_SECRET")
|
|
||||||
os.Unsetenv("SECRET_KEY")
|
|
||||||
os.Unsetenv("SELF_REGISTRATION")
|
|
||||||
os.Unsetenv("CREATE_PROJECT_RESTRICTION")
|
|
||||||
os.Unsetenv("REGISTRY_URL")
|
|
||||||
os.Unsetenv("JOB_SERVICE_URL")
|
|
||||||
|
|
||||||
os.Exit(rc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuth(t *testing.T) {
|
|
||||||
if AuthMode() != auth {
|
|
||||||
t.Errorf("Expected auth mode:%s, in fact: %s", auth, AuthMode())
|
|
||||||
}
|
}
|
||||||
if LDAP() != ldap {
|
|
||||||
t.Errorf("Expected ldap setting: %+v, in fact: %+v", ldap, LDAP())
|
secretKeyPath := "/tmp/secretkey"
|
||||||
}
|
_, err = test.GenerateKey(secretKeyPath)
|
||||||
}
|
if err != nil {
|
||||||
|
t.Errorf("failed to generate secret key: %v", err)
|
||||||
func TestTokenExpiration(t *testing.T) {
|
return
|
||||||
if TokenExpiration() != tokenExpRes {
|
}
|
||||||
t.Errorf("Expected token expiration: %d, in fact: %d", tokenExpRes, TokenExpiration())
|
defer os.Remove(secretKeyPath)
|
||||||
}
|
|
||||||
}
|
if err := os.Setenv("KEY_PATH", secretKeyPath); err != nil {
|
||||||
|
t.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
|
||||||
func TestURLs(t *testing.T) {
|
}
|
||||||
if InternalRegistryURL() != internalRegistryURL {
|
|
||||||
t.Errorf("Expected internal Registry URL: %s, in fact: %s", internalRegistryURL, InternalRegistryURL())
|
if err := Init(); err != nil {
|
||||||
}
|
t.Fatalf("failed to initialize configurations: %v", err)
|
||||||
if InternalJobServiceURL() != jobServiceURL {
|
}
|
||||||
t.Errorf("Expected internal jobservice URL: %s, in fact: %s", jobServiceURL, InternalJobServiceURL())
|
|
||||||
}
|
if err := Load(); err != nil {
|
||||||
if ExtRegistryURL() != externalRegURL {
|
t.Fatalf("failed to load configurations: %v", err)
|
||||||
t.Errorf("Expected External Registry URL: %s, in fact: %s", externalRegURL, ExtRegistryURL())
|
}
|
||||||
}
|
|
||||||
}
|
if err := Upload(map[string]interface{}{}); err != nil {
|
||||||
|
t.Fatalf("failed to upload configurations: %v", err)
|
||||||
func TestSelfRegistration(t *testing.T) {
|
}
|
||||||
if SelfRegistration() {
|
|
||||||
t.Errorf("Expected Self Registration to be false")
|
if _, err := GetSystemCfg(); err != nil {
|
||||||
}
|
t.Fatalf("failed to get system configurations: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSecrets(t *testing.T) {
|
mode, err := AuthMode()
|
||||||
if SecretKey() != secretKey {
|
if err != nil {
|
||||||
t.Errorf("Expected Secrect Key :%s, in fact: %s", secretKey, SecretKey())
|
t.Fatalf("failed to get auth mode: %v", err)
|
||||||
}
|
}
|
||||||
if UISecret() != uiSecret {
|
if mode != "db_auth" {
|
||||||
t.Errorf("Expected UI Secret: %s, in fact: %s", uiSecret, UISecret())
|
t.Errorf("unexpected mode: %s != %s", mode, "db_auth")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if _, err := LDAP(); err != nil {
|
||||||
func TestProjectCreationRestrict(t *testing.T) {
|
t.Fatalf("failed to get ldap settings: %v", err)
|
||||||
if !OnlyAdminCreateProject() {
|
}
|
||||||
t.Errorf("Expected OnlyAdminCreateProject to be true")
|
|
||||||
}
|
if _, err := TokenExpiration(); err != nil {
|
||||||
}
|
t.Fatalf("failed to get token expiration: %v", err)
|
||||||
|
}
|
||||||
func TestInitAdminPassword(t *testing.T) {
|
|
||||||
if InitialAdminPassword() != adminPassword {
|
if _, err := ExtEndpoint(); err != nil {
|
||||||
t.Errorf("Expected adminPassword: %s, in fact: %s", adminPassword, InitialAdminPassword())
|
t.Fatalf("failed to get domain name: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := SecretKey(); err != nil {
|
||||||
|
t.Fatalf("failed to get secret key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := SelfRegistration(); err != nil {
|
||||||
|
t.Fatalf("failed to get self registration: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := RegistryURL(); err != nil {
|
||||||
|
t.Fatalf("failed to get registry URL: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(InternalJobServiceURL()) == 0 {
|
||||||
|
t.Error("the internal job service url is null")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(InternalTokenServiceEndpoint()) == 0 {
|
||||||
|
t.Error("the internal token service endpoint is null")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := InitialAdminPassword(); err != nil {
|
||||||
|
t.Fatalf("failed to get initial admin password: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := OnlyAdminCreateProject(); err != nil {
|
||||||
|
t.Fatalf("failed to get onldy admin create project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := VerifyRemoteCert(); err != nil {
|
||||||
|
t.Fatalf("failed to get verify remote cert: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Email(); err != nil {
|
||||||
|
t.Fatalf("failed to get email settings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Database(); err != nil {
|
||||||
|
t.Fatalf("failed to get database: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,12 @@ func (b *BaseController) Prepare() {
|
|||||||
b.Data["CurLang"] = curLang.Name
|
b.Data["CurLang"] = curLang.Name
|
||||||
b.Data["RestLangs"] = restLangs
|
b.Data["RestLangs"] = restLangs
|
||||||
|
|
||||||
authMode := config.AuthMode()
|
authMode, err := config.AuthMode()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get auth mode: %v", err)
|
||||||
|
b.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
if authMode == "" {
|
if authMode == "" {
|
||||||
authMode = "db_auth"
|
authMode = "db_auth"
|
||||||
}
|
}
|
||||||
@ -120,9 +125,13 @@ func (b *BaseController) Prepare() {
|
|||||||
b.UseCompressedJS = false
|
b.UseCompressedJS = false
|
||||||
}
|
}
|
||||||
|
|
||||||
b.SelfRegistration = config.SelfRegistration()
|
b.SelfRegistration, err = config.SelfRegistration()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get self registration: %v", err)
|
||||||
|
b.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
b.Data["SelfRegistration"] = config.SelfRegistration()
|
b.Data["SelfRegistration"] = b.SelfRegistration
|
||||||
|
|
||||||
sessionUserID := b.GetSession("userId")
|
sessionUserID := b.GetSession("userId")
|
||||||
if sessionUserID != nil {
|
if sessionUserID != nil {
|
||||||
@ -235,12 +244,13 @@ func (cc *CommonController) UserExists() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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")
|
||||||
if len(configPath) != 0 {
|
if len(configPath) != 0 {
|
||||||
log.Infof("Config path: %s", configPath)
|
log.Infof("Config path: %s", configPath)
|
||||||
beego.LoadAppConfig("ini", configPath)
|
if err := beego.LoadAppConfig("ini", configPath); err != nil {
|
||||||
|
log.Errorf("failed to load app config: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
beego.AddFuncMap("i18n", i18n.Tr)
|
beego.AddFuncMap("i18n", i18n.Tr)
|
||||||
@ -263,5 +273,4 @@ func init() {
|
|||||||
log.Errorf("Fail to set message file: %s", err.Error())
|
log.Errorf("Fail to set message file: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ import (
|
|||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
//"github.com/dghubble/sling"
|
//"github.com/dghubble/sling"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
//const (
|
//const (
|
||||||
@ -29,6 +31,9 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
_, 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))))
|
||||||
@ -63,7 +68,6 @@ func init() {
|
|||||||
|
|
||||||
//Init user Info
|
//Init user Info
|
||||||
//admin = &usrInfo{adminName, adminPwd}
|
//admin = &usrInfo{adminName, adminPwd}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMain is a sample to run an endpoint test
|
// TestMain is a sample to run an endpoint test
|
||||||
|
@ -6,12 +6,12 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
|
||||||
"github.com/vmware/harbor/src/common/config"
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
|
email_util "github.com/vmware/harbor/src/common/utils/email"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type messageDetail struct {
|
type messageDetail struct {
|
||||||
@ -49,7 +49,11 @@ func (cc *CommonController) SendEmail() {
|
|||||||
|
|
||||||
message := new(bytes.Buffer)
|
message := new(bytes.Buffer)
|
||||||
|
|
||||||
harborURL := config.ExtEndpoint()
|
harborURL, err := config.ExtEndpoint()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get domain name: %v", err)
|
||||||
|
cc.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
if harborURL == "" {
|
if harborURL == "" {
|
||||||
harborURL = "localhost"
|
harborURL = "localhost"
|
||||||
}
|
}
|
||||||
@ -65,14 +69,14 @@ func (cc *CommonController) SendEmail() {
|
|||||||
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := beego.AppConfig.GetSection("mail")
|
emailSettings, err := config.Email()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Can not load app.conf: %v", err)
|
log.Errorf("failed to get email configurations: %v", err)
|
||||||
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
||||||
}
|
}
|
||||||
|
|
||||||
mail := utils.Mail{
|
mail := email_util.Mail{
|
||||||
From: config["from"],
|
From: emailSettings.From,
|
||||||
To: []string{email},
|
To: []string{email},
|
||||||
Subject: cc.Tr("reset_email_subject"),
|
Subject: cc.Tr("reset_email_subject"),
|
||||||
Message: message.String()}
|
Message: message.String()}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
@ -23,6 +25,11 @@ func (pc *ProjectController) Get() {
|
|||||||
isSysAdmin = false
|
isSysAdmin = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pc.Data["CanCreate"] = !config.OnlyAdminCreateProject() || isSysAdmin
|
onlyAdmin, err := config.OnlyAdminCreateProject()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to determine whether only admin can create projects: %v", err)
|
||||||
|
pc.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
pc.Data["CanCreate"] = !onlyAdmin || isSysAdmin
|
||||||
pc.Forward("page_title_project", "project.htm")
|
pc.Forward("page_title_project", "project.htm")
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,6 +15,11 @@ type RepositoryController struct {
|
|||||||
|
|
||||||
// Get renders repository page
|
// Get renders repository page
|
||||||
func (rc *RepositoryController) Get() {
|
func (rc *RepositoryController) Get() {
|
||||||
rc.Data["HarborRegUrl"] = config.ExtRegistryURL()
|
url, err := config.ExtEndpoint()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get domain name: %v", err)
|
||||||
|
rc.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
rc.Data["HarborRegUrl"] = strings.Split(url, "://")[1]
|
||||||
rc.Forward("page_title_repository", "repository.htm")
|
rc.Forward("page_title_repository", "repository.htm")
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,6 @@ func updateInitPassword(userID int, password string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||||
//TODO
|
//TODO
|
||||||
redisURL := os.Getenv("_REDIS_URL")
|
redisURL := os.Getenv("_REDIS_URL")
|
||||||
@ -72,12 +71,28 @@ func main() {
|
|||||||
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
|
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
|
||||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = redisURL
|
beego.BConfig.WebConfig.Session.SessionProviderConfig = redisURL
|
||||||
}
|
}
|
||||||
//
|
|
||||||
beego.AddTemplateExt("htm")
|
beego.AddTemplateExt("htm")
|
||||||
|
|
||||||
dao.InitDatabase()
|
log.Info("initializing configurations...")
|
||||||
|
if err := config.Init(); err != nil {
|
||||||
|
log.Fatalf("failed to initialize configurations: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("configurations initialization completed")
|
||||||
|
|
||||||
if err := updateInitPassword(adminUserID, config.InitialAdminPassword()); err != nil {
|
database, err := config.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
password, err := config.InitialAdminPassword()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to get admin's initia password: %v", err)
|
||||||
|
}
|
||||||
|
if err := updateInitPassword(adminUserID, password); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
initRouters()
|
initRouters()
|
||||||
|
@ -84,9 +84,12 @@ func initRouters() {
|
|||||||
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
||||||
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
||||||
beego.Router("/api/logs", &api.LogAPI{})
|
beego.Router("/api/logs", &api.LogAPI{})
|
||||||
|
beego.Router("/api/configurations", &api.ConfigAPI{})
|
||||||
|
|
||||||
beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
|
beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
|
||||||
beego.Router("/api/systeminfo/getcert", &api.SystemInfoAPI{}, "get:GetCert")
|
beego.Router("/api/systeminfo/getcert", &api.SystemInfoAPI{}, "get:GetCert")
|
||||||
|
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
|
||||||
|
|
||||||
//external service that hosted on harbor process:
|
//external service that hosted on harbor process:
|
||||||
beego.Router("/service/notifications", &service.NotificationHandler{})
|
beego.Router("/service/notifications", &service.NotificationHandler{})
|
||||||
beego.Router("/service/token", &token.Handler{})
|
beego.Router("/service/token", &token.Handler{})
|
||||||
|
@ -36,13 +36,10 @@ const (
|
|||||||
issuer = "registry-token-issuer"
|
issuer = "registry-token-issuer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var expiration int //minutes
|
|
||||||
var privateKey string
|
var privateKey string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
expiration = config.TokenExpiration()
|
|
||||||
privateKey = "/etc/ui/private_key.pem"
|
privateKey = "/etc/ui/private_key.pem"
|
||||||
log.Infof("token expiration: %d minutes", expiration)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetResourceActions ...
|
// GetResourceActions ...
|
||||||
@ -92,7 +89,12 @@ func FilterAccess(username string, a *token.ResourceActions) {
|
|||||||
repoLength := len(repoSplit)
|
repoLength := len(repoSplit)
|
||||||
if repoLength > 1 { //Only check the permission when the requested image has a namespace, i.e. project
|
if repoLength > 1 { //Only check the permission when the requested image has a namespace, i.e. project
|
||||||
var projectName string
|
var projectName string
|
||||||
registryURL := config.ExtRegistryURL()
|
registryURL, err := config.ExtEndpoint()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get domain name: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
registryURL = strings.Split(registryURL, "://")[1]
|
||||||
if repoSplit[0] == registryURL {
|
if repoSplit[0] == registryURL {
|
||||||
projectName = repoSplit[1]
|
projectName = repoSplit[1]
|
||||||
log.Infof("Detected Registry URL in Project Name. Assuming this is a notary request and setting Project Name as %s\n", projectName)
|
log.Infof("Detected Registry URL in Project Name. Assuming this is a notary request and setting Project Name as %s\n", projectName)
|
||||||
@ -154,6 +156,11 @@ func MakeToken(username, service string, access []*token.ResourceActions) (token
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, nil, err
|
return "", 0, nil, err
|
||||||
}
|
}
|
||||||
|
expiration, err := config.TokenExpiration()
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
tk, expiresIn, issuedAt, err := makeTokenCore(issuer, username, service, expiration, access, pk)
|
tk, expiresIn, issuedAt, err := makeTokenCore(issuer, username, service, expiration, access, pk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, nil, err
|
return "", 0, nil, err
|
||||||
|
@ -19,10 +19,11 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/ui/auth"
|
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
svc_utils "github.com/vmware/harbor/src/ui/service/utils"
|
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
|
svc_utils "github.com/vmware/harbor/src/ui/service/utils"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/docker/distribution/registry/auth/token"
|
"github.com/docker/distribution/registry/auth/token"
|
||||||
@ -45,7 +46,7 @@ func (h *Handler) Get() {
|
|||||||
access := GetResourceActions(scopes)
|
access := GetResourceActions(scopes)
|
||||||
log.Infof("request url: %v", request.URL.String())
|
log.Infof("request url: %v", request.URL.String())
|
||||||
|
|
||||||
if svc_utils.VerifySecret(request) {
|
if svc_utils.VerifySecret(request, config.JobserviceSecret()) {
|
||||||
log.Debugf("Will grant all access as this request is from job service with legal secret.")
|
log.Debugf("Will grant all access as this request is from job service with legal secret.")
|
||||||
username = "job-service-user"
|
username = "job-service-user"
|
||||||
} else {
|
} else {
|
||||||
|
@ -14,9 +14,16 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
if err := config.Init(); err != nil {
|
||||||
|
log.Fatalf("failed to initialize configurations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
result := m.Run()
|
result := m.Run()
|
||||||
if result != 0 {
|
if result != 0 {
|
||||||
os.Exit(result)
|
os.Exit(result)
|
||||||
@ -73,7 +80,6 @@ func TestMakeToken(t *testing.T) {
|
|||||||
pk, crt := getKeyAndCertPath()
|
pk, crt := getKeyAndCertPath()
|
||||||
//overwrite the config values for testing.
|
//overwrite the config values for testing.
|
||||||
privateKey = pk
|
privateKey = pk
|
||||||
expiration = 10
|
|
||||||
ra := []*token.ResourceActions{&token.ResourceActions{
|
ra := []*token.ResourceActions{&token.ResourceActions{
|
||||||
Type: "repository",
|
Type: "repository",
|
||||||
Name: "10.117.4.142/notary-test/hello-world-2",
|
Name: "10.117.4.142/notary-test/hello-world-2",
|
||||||
|
@ -20,15 +20,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// VerifySecret verifies the UI_SECRET cookie in a http request.
|
// VerifySecret verifies the UI_SECRET cookie in a http request.
|
||||||
func VerifySecret(r *http.Request) bool {
|
func VerifySecret(r *http.Request, expectedSecret string) bool {
|
||||||
secret := config.UISecret()
|
c, err := r.Cookie("secret")
|
||||||
c, err := r.Cookie("uisecret")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("Failed to get secret cookie, error: %v", err)
|
log.Warningf("Failed to get secret cookie, error: %v", err)
|
||||||
}
|
}
|
||||||
return c != nil && c.Value == secret
|
return c != nil && c.Value == expectedSecret
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
.switch-pane-admin-options {
|
.switch-pane-admin-options {
|
||||||
display: inline;
|
display: inline;
|
||||||
width: 245px;
|
width: 340px;
|
||||||
float: right;
|
float: right;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
@ -28,4 +28,8 @@
|
|||||||
.switch-pane-admin-options li .active {
|
.switch-pane-admin-options li .active {
|
||||||
border-bottom: 2px solid rgb(0, 84, 190);
|
border-bottom: 2px solid rgb(0, 84, 190);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-help-config {
|
||||||
|
padding: 6px;
|
||||||
}
|
}
|
@ -12,65 +12,224 @@
|
|||||||
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.
|
||||||
-->
|
-->
|
||||||
<form name="form" class="form-horizontal" ng-submit="form.$valid && vm.changeSettings(system)" autocomplete="off">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<ul id="ulTabHeader" class="nav nav-pills nav-stacked col-md-2 col-xs-12">
|
||||||
<h5>System Settings</h5>
|
<li role="presentation"><a href="#auth" aria-controls="auth" role="tab" data-toggle="tab">// 'authentication' | tr //</a></li>
|
||||||
<hr/>
|
<li role="presentation"><a href="#email" aria-controls="email" role="tab" data-toggle="tab">// 'email_settings' | tr //</a></li>
|
||||||
</div>
|
<li role="presentation"><a href="#system" aria-controls="system" role="tab" data-toggle="tab">// 'system_settings' | tr //</a></li>
|
||||||
<div class="col-md-12 col-md-off-set-1 main-content">
|
</ul>
|
||||||
<div class="form-group">
|
<!-- Tab panes -->
|
||||||
<label for="hostName" class="col-sm-3 control-label">Host Name:</label>
|
<div id="tabConfigurations" class="tab-content col-md-10 col-xs-12">
|
||||||
<div class="col-sm-7">
|
<div role="tabpanel" class="tab-pane" id="auth">
|
||||||
<input type="text" class="form-control" id="hostName" ng-model="system.hostName" ng-model-options="{ updateOn: 'blur' }" ng-value="vm.system.hostName" name="uHostName" required>
|
<form name="authForm" class="form-horizontal" no-validate autocomplete="off">
|
||||||
<div ng-messages="form.$dirty && form.uHostName.$error">
|
<div class="form-group">
|
||||||
<span ng-message="required">Host name is required.</span>
|
<label for="selAuth" class="col-sm-3 control-label">// 'authentication_mode' | tr //<span ng-if="vm.warning['auth.authMode']">*</span></label>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<select id="selAuth" class="form-control" ng-model="auth.authMode.data" ng-options="r as r.name for r in vm.supportedAuths track by r.value" ng-disabled="!vm.editable['auth.authMode']"></select>
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'authentication_mode' | tr //" content="// 'authentication_mode_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group" ng-if="auth.authMode.data.value !== 'ldap_auth'">
|
||||||
|
<label for="selfRegistration" class="col-sm-3 control-label">// 'self_registration' | tr //<span ng-if="vm.warning['auth.selfRegistration']">*</span></label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<select class="form-control" ng-model="auth.selfRegistration.data" ng-options="r as r.name for r in vm.toggleBooleans track by r.value" ng-disabled="!vm.editable['auth.selfRegistration']"></select>
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'self_registration' | tr //" content="// 'self_registration_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-if="auth.authMode.data.value === 'ldap_auth'">
|
||||||
|
<label for="ldapURL" class="col-sm-3 control-label">// 'ldap_url' | tr //<span ng-if="vm.warning['auth.ldapURL']">*</span></label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<input id="ldapURL" type="text" class="form-control" ng-model="auth.ldapURL.data" ng-disabled="!vm.editable['auth.ldapURL']">
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'ldap_url' | tr //" content="// 'ldap_url_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-if="auth.authMode.data.value === 'ldap_auth'">
|
||||||
|
<label for="ldapSearchDN" class="col-sm-3 control-label">// 'ldap_search_dn' | tr //<span ng-if="vm.warning['auth.ldapSearchDN']">*</span></label>
|
||||||
|
<div class="col-sm-5 clearfix">
|
||||||
|
<input id="ldapSearchDN" type="text" class="form-control" ng-model="auth.ldapSearchDN.data" ng-disabled="!vm.editable['auth.ldapSearchDN']">
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'ldap_search_dn' | tr //" content="// 'ldap_search_dn_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-if="auth.authMode.data.value === 'ldap_auth'">
|
||||||
|
<label for="ldapSearchPassword" class="col-sm-3 control-label">// 'ldap_search_password' | tr //<span ng-if="vm.warning['auth.ldapSearchPassword']">*</span></label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<input id="ldapSearchPassword" type="password" class="form-control" ng-model="auth.ldapSearchPassword.data" ng-click="vm.clearUp(auth.ldapSearchPassword)" ng-change="vm.hasChanged(auth.ldapSearchPassword)" ng-blur="vm.setMaskPassword(auth.ldapSearchPassword)">
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'ldap_search_password' | tr //" content="// 'ldap_search_password_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-if="auth.authMode.data.value === 'ldap_auth'">
|
||||||
|
<label for="ldapBaseDN" class="col-sm-3 control-label">// 'ldap_base_dn' | tr //<span ng-if="vm.warning['auth.ldapBaseDN']">*</span></label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<input id="ldapBaseDN" type="text" class="form-control" ng-model="auth.ldapBaseDN.data" ng-disabled="!vm.editable['auth.ldapBaseDN']">
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'ldap_base_dn' | tr //" content="// 'ldap_base_dn_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-if="auth.authMode.data.value === 'ldap_auth'">
|
||||||
|
<label for="ldapUID" class="col-sm-3 control-label">// 'ldap_uid' | tr //<span ng-if="vm.warning['auth.ldapUID']">*</span></label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<input id="ldapUID" type="text" class="form-control" ng-model="auth.ldapUID.data" ng-disabled="!vm.editable['auth.ldapUID']">
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'ldap_uid' | tr //" content="// 'ldap_uid_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-if="auth.authMode.data.value === 'ldap_auth'">
|
||||||
|
<label for="ldapFilter" class="col-sm-3 control-label">// 'ldap_filter' | tr //<span ng-if="vm.warning['auth.ldapFilter']">*</span></label>
|
||||||
|
<div class="col-sm-5 clearfix">
|
||||||
|
<input id="ldapFilter" type="text" class="form-control" ng-model="auth.ldapFilter.data" ng-disabled="!vm.editable['auth.ldapFilter']">
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'ldap_filter' | tr //" content="// 'ldap_filter_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-if="auth.authMode.data.value === 'ldap_auth'">
|
||||||
|
<label for="ldapTimeout" class="col-sm-3 control-label">// 'ldap_connection_timeout' | tr //<span ng-if="vm.warning['auth.ldapConnectionTimeout']">*</span></label>
|
||||||
|
<div class="col-sm-2 clearfix">
|
||||||
|
<input id="ldapConnectionTimeout" type="number" class="form-control" name="ldapConnectionTimeout" ng-model="auth.ldapConnectionTimeout.data" ng-disabled="!vm.editable['auth.ldapConnectionTimeout']" min="1" max="60" required>
|
||||||
|
<div class="error-message" ng-messages="authForm.ldapConnectionTimeout.$error" >
|
||||||
|
<span ng-message="required">// 'timeout_is_required' | tr //</span>
|
||||||
|
<span ng-message="min">// 'invalid_timeout' | tr //</span>
|
||||||
|
<span ng-message="max">// 'invalid_timeout' | tr //</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-if="auth.authMode.data.value === 'ldap_auth'">
|
||||||
|
<label for="selLDAPScope" class="col-sm-3 control-label">// 'ldap_scope' | tr //<span ng-if="vm.warning['auth.ldapScope']">*</span></label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<select id="selLDAPScope" class="form-control" ng-model="auth.ldapScope.data" ng-options="item for item in [1, 2, 3]" ng-disabled="!vm.editable['auth.ldapScope']"></select>
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'ldap_scope' | tr //" content="// 'ldap_scope_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-if="auth.authMode.data.value === 'ldap_auth'">
|
||||||
|
<div class="col-sm-7 col-sm-offset-3">
|
||||||
|
<span ng-if="vm.isError" class="error-message" >// vm.pingMessage //</span>
|
||||||
|
<span ng-if="!vm.isError">// vm.pingMessage //</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-offset-3 col-sm-5">
|
||||||
|
<div class="pull-right">
|
||||||
|
<button type="submit" class="btn btn-link" ng-if="vm.changed" ng-click="vm.undo()">// 'undo' | tr //</button>
|
||||||
|
<button type="button" class="btn btn-default" ng-if="auth.authMode.data.value === 'ldap_auth'" ng-disabled="authForm.$invalid" ng-click="vm.pingLDAP(auth)" loading-progress hide-target="false" toggle-in-progress="vm.pingTIP">// 'test_connection' | tr //</button>
|
||||||
|
<button type="submit" class="btn btn-success" ng-disabled="authForm.$invalid" ng-click="vm.saveAuthConf(auth)">// 'save' | tr //</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div role="tabpanel" class="tab-pane" id="email">
|
||||||
<label for="urlProtocol" class="col-sm-3 control-label">URL Protocol:</label>
|
<form name="emailForm" class="form-horizontal" novalidate autocomplete="off">
|
||||||
<div class="col-sm-7">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" id="urlProtocol" ng-model="system.urlProtocol" ng-model-options="{ updateOn: 'blur' }" ng-value="vm.system.urlProtocol" name="uUrlProtocol" required>
|
<label for="emailServer" class="col-sm-3 control-label">// 'email_server' | tr //<span ng-if="vm.warning['email.server']">*</span></label>
|
||||||
<div ng-messages="form.$dirty && form.uUrlProtocol.$error">
|
<div class="col-sm-5">
|
||||||
<span ng-message="required">Url protocol is required.</span>
|
<input id="emailServer" type="text" class="form-control" ng-model="email.server.data" ng-disabled="!vm.editable['email.server']">
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'email_server' | tr //" content="// 'email_server_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
</div>
|
<label for="emailServerPort" class="col-sm-3 control-label">// 'email_server_port' | tr //<span ng-if="vm.warning['email.serverPort']">*</span></label>
|
||||||
<div class="form-group">
|
<div class="col-sm-5">
|
||||||
<label for="emailServer" class="col-sm-3 control-label">Email server:</label>
|
<input id="emailServerPort" type="number" class="form-control" ng-model="email.serverPort.data" name="emailServerPort" ng-disabled="!vm.editable['email.serverPort']" min="1" max="65535">
|
||||||
<div class="col-sm-7">
|
<div class="error-message" ng-messages="emailForm.emailServerPort.$error" >
|
||||||
<input type="text" class="form-control" id="emailServer" ng-model="system.emailServer" ng-model-options="{ updateOn: 'blur' }" ng-value="vm.system.emailServer" name="uEmailServer" required>
|
<span ng-message="min">// 'invalid_port_number' | tr //</span>
|
||||||
<div ng-messages="form.$dirty && form.uEmailServer.$error">
|
<span ng-message="max">// 'invalid_port_number' | tr //</span>
|
||||||
<span ng-message="required">Email server is required.</span>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'email_server_port' | tr //" content="// 'email_server_port_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
|
<label for="emailUsername" class="col-sm-3 control-label">// 'email_username' | tr //<span ng-if="vm.warning['email.username']">*</span></label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<input id="emailServerPort" type="text" class="form-control" ng-model="email.username.data" ng-disabled="!vm.editable['email.username']">
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'email_username' | tr //" content="// 'email_username_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="emailPassword" class="col-sm-3 control-label">// 'email_password' | tr //<span ng-if="vm.warning['email.password']">*</span></label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<input id="emailPassword" type="password" class="form-control" ng-model="email.password.data" ng-click="vm.clearUp(email.password)" ng-change="vm.hasChanged(email.password)" ng-blur="vm.setMaskPassword(email.password)">
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'email_password' | tr //" content="// 'email_password_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="emailFrom" class="col-sm-3 control-label">// 'email_from' | tr //<span ng-if="vm.warning['email.from']">*</span></label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<input id="emailFrom" type="text" class="form-control" ng-model="email.from.data" ng-disabled="!vm.editable['email.from']">
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'email_from' | tr //" content="// 'email_from_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="emailSSL" class="col-sm-3 control-label">// 'email_ssl' | tr //<span ng-if="vm.warning['email.SSL']">*</span></label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<select class="form-control" ng-model="email.SSL.data" ng-options="r as r.name for r in vm.toggleBooleans track by r.value" ng-disabled="!vm.editable['email.SSL']"></select>
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'email_ssl' | tr //" content="// 'email_ssl_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-offset-3 col-sm-5">
|
||||||
|
<div class="pull-right">
|
||||||
|
<button type="submit" class="btn btn-link" ng-if="vm.changed" ng-click="vm.undo()">// 'undo' | tr //</button>
|
||||||
|
<button type="submit" class="btn btn-success" ng-disabled="emailForm.$invalid" ng-click="vm.saveEmailConf(email)">// 'save' | tr //</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div role="tabpanel" class="tab-pane" id="system">
|
||||||
<label for="ldapUrl" class="col-sm-3 control-label">LDAP URL:</label>
|
<form name="systemForm" class="form-horizontal" no-validate autocomplete="off">
|
||||||
<div class="col-sm-7">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" id="ldapUrl" ng-model="system.ldapUrl" ng-model-options="{ updateOn: 'blur' }" ng-value="vm.system.ldapUrl" name="uLdapUrl" required>
|
<label for="projectCreationRestriction" class="col-sm-3 control-label">// 'project_creation_restriction' | tr //<span ng-if="vm.warning['system.projectCreationRestriction']">*</span></label>
|
||||||
<div ng-messages="form.$dirty && form.uLdapUrl.$error">
|
<div class="col-sm-5">
|
||||||
<span ng-message="required">LDAP URL is required.</span>
|
<select id="projectCreationRestriction" class="form-control" ng-model="system.projectCreationRestriction.data" ng-options="r as r.name for r in vm.toggleCustoms track by r.value" ng-disabled="!vm.editable['system.projectCreationRestriction']"></select>
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'project_creation_restriction' | tr //" content="// 'project_creation_restriction_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
|
<label for="verifyRemoteCert" class="col-sm-3 control-label">// 'verify_remote_cert' | tr //<span ng-if="vm.warning['system.verifyRemoteCert']">*</span></label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<select id="verifyRemoteCert" class="form-control" ng-model="system.verifyRemoteCert.data" ng-options="r as r.name for r in vm.toggleBooleans track by r.value" ng-disabled="!vm.editable['system.verifyRemoteCert']"></select>
|
||||||
|
</div>
|
||||||
|
<div class="inline-help-config">
|
||||||
|
<inline-help help-title="// 'verify_remote_cert' | tr //" content="// 'verify_remote_cert_desc' | tr //"></inline-help>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-offset-3 col-sm-5">
|
||||||
|
<div class="pull-right">
|
||||||
|
<button type="submit" class="btn btn-link" ng-if="vm.changed" ng-click="vm.undo()">// 'undo' | tr //</button>
|
||||||
|
<button type="submit" class="btn btn-success" ng-disabled="systemForm.$invalid" ng-click="vm.saveSystemConf(system)">// 'save' | tr //</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12">
|
</div>
|
||||||
<h5>Registration</h5>
|
|
||||||
<hr/>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12 col-md-off-set-1 main-content">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="registration" class="col-sm-3 control-label">Registration:</label>
|
|
||||||
<div class="col-sm-7">
|
|
||||||
<select class="form-control" ng-model="vm.currentRegistration" ng-options="r as r.name for r in vm.registrationOptions track by r.value" ng-click="vm.selectRegistration()"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-md-offset-7 col-md-10">
|
|
||||||
<input type="submit" class="btn btn-primary" ng-disabled="form.$invalid" value="Save">
|
|
||||||
<input type="submit" class="btn btn-default" ng-click="vm.cancel()" value="Cancel">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
@ -18,51 +18,365 @@
|
|||||||
|
|
||||||
angular
|
angular
|
||||||
.module('harbor.system.management')
|
.module('harbor.system.management')
|
||||||
|
.constant('defaultPassword', '12345678')
|
||||||
.directive('configuration', configuration);
|
.directive('configuration', configuration);
|
||||||
|
|
||||||
ConfigurationController.$inject = [];
|
ConfigurationController.$inject = ['$scope', 'ConfigurationService', 'defaultPassword', '$filter', 'trFilter'];
|
||||||
|
|
||||||
function ConfigurationController() {
|
function ConfigurationController($scope, ConfigurationService, defaultPassword, $filter, trFilter) {
|
||||||
var vm = this;
|
var vm = this;
|
||||||
|
|
||||||
vm.registrationOptions = [
|
vm.toggleBooleans = [
|
||||||
{
|
{
|
||||||
'name': 'on',
|
'name': 'True',
|
||||||
'value': true
|
'value': true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'off',
|
'name': 'False',
|
||||||
'value': false
|
'value': false
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
vm.currentRegistration = {
|
|
||||||
'name': 'on',
|
vm.toggleCustoms = [
|
||||||
'value': true
|
{
|
||||||
|
'name': 'Admin Only',
|
||||||
|
'value': 'adminonly',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Everyone',
|
||||||
|
'value': 'everyone'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
vm.supportedAuths = [
|
||||||
|
{
|
||||||
|
'name': 'DB auth',
|
||||||
|
'value': 'db_auth'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'LDAP auth',
|
||||||
|
'value': 'ldap_auth'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
var confKeyDefinitions = {
|
||||||
|
'auth_mode': { type: 'auth', attr: 'authMode' },
|
||||||
|
'self_registration': { type: 'auth', attr: 'selfRegistration' },
|
||||||
|
'ldap_url': { type: 'auth', attr: 'ldapURL' },
|
||||||
|
'ldap_search_dn': { type: 'auth', attr: 'ldapSearchDN' },
|
||||||
|
'ldap_search_password': { type: 'auth', attr: 'ldapSearchPassword' },
|
||||||
|
'ldap_base_dn': { type: 'auth', attr: 'ldapBaseDN' },
|
||||||
|
'ldap_uid': { type: 'auth', attr: 'ldapUID' },
|
||||||
|
'ldap_filter': { type: 'auth', attr: 'ldapFilter' },
|
||||||
|
'ldap_timeout': { type: 'auth', attr: 'ldapConnectionTimeout' },
|
||||||
|
'ldap_scope': { type: 'auth', attr: 'ldapScope' },
|
||||||
|
'email_host': { type: 'email', attr: 'server' },
|
||||||
|
'email_port': { type: 'email', attr: 'serverPort' },
|
||||||
|
'email_username': { type: 'email', attr: 'username' },
|
||||||
|
'email_password': { type: 'email', attr: 'password' },
|
||||||
|
'email_from': { type: 'email', attr: 'from' },
|
||||||
|
'email_ssl': { type: 'email', attr: 'SSL' },
|
||||||
|
'project_creation_restriction': { type: 'system', attr: 'projectCreationRestriction' },
|
||||||
|
'verify_remote_cert': { type: 'system', attr: 'verifyRemoteCert' }
|
||||||
};
|
};
|
||||||
|
|
||||||
vm.changeSettings = changeSettings;
|
$scope.auth = {};
|
||||||
|
$scope.email = {};
|
||||||
|
$scope.system = {};
|
||||||
|
|
||||||
vm.selectRegistration = selectRegistration;
|
vm.retrieve = retrieve;
|
||||||
|
|
||||||
function selectRegistration() {
|
vm.saveAuthConf = saveAuthConf;
|
||||||
|
vm.saveEmailConf = saveEmailConf;
|
||||||
|
vm.saveSystemConf = saveSystemConf;
|
||||||
|
|
||||||
|
vm.gatherUpdateItems = gatherUpdateItems;
|
||||||
|
vm.clearUp = clearUp;
|
||||||
|
vm.hasChanged = hasChanged;
|
||||||
|
vm.setMaskPassword = setMaskPassword;
|
||||||
|
vm.undo = undo;
|
||||||
|
|
||||||
|
vm.pingLDAP = pingLDAP;
|
||||||
|
vm.pingTIP = false;
|
||||||
|
vm.isError = false;
|
||||||
|
vm.pingMessage = '';
|
||||||
|
|
||||||
|
vm.retrieve();
|
||||||
|
|
||||||
|
function retrieve() {
|
||||||
|
|
||||||
|
vm.ldapSearchPasswordChanged = false;
|
||||||
|
vm.emailPasswordChanged = false;
|
||||||
|
vm.changedItems = {};
|
||||||
|
vm.updatedItems = {};
|
||||||
|
vm.warning = {};
|
||||||
|
vm.editable = {};
|
||||||
|
|
||||||
|
ConfigurationService
|
||||||
|
.get()
|
||||||
|
.then(getConfigurationSuccess, getConfigurationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfigurationSuccess(response) {
|
||||||
|
var data = response.data || [];
|
||||||
|
for(var key in data) {
|
||||||
|
var mappedDef = keyMapping(key);
|
||||||
|
if(mappedDef) {
|
||||||
|
$scope[mappedDef['type']][mappedDef['attr']] = { 'target': mappedDef['type'] + '.' + mappedDef['attr'], 'data': valueMapping(data[key]['value']) };
|
||||||
|
$scope.$watch(mappedDef['type'] + '.' + mappedDef['attr'], onChangedCallback, true);
|
||||||
|
$scope[mappedDef['type']][mappedDef['attr']]['origin'] = { 'target': mappedDef['type'] + '.' + mappedDef['attr'], 'data': valueMapping(data[key]['value']) };
|
||||||
|
vm.editable[mappedDef['type'] + '.' + mappedDef['attr']] = data[key]['editable'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.auth.ldapSearchPassword = { 'target': 'auth.ldapSearchPassword', 'data': defaultPassword};
|
||||||
|
$scope.email.password = { 'target': 'email.password', 'data': defaultPassword};
|
||||||
|
|
||||||
|
$scope.$watch('auth.ldapSearchPassword', onChangedCallback, true);
|
||||||
|
$scope.$watch('email.password', onChangedCallback, true);
|
||||||
|
|
||||||
|
$scope.auth.ldapSearchPassword.actual = { 'target': 'auth.ldapSearchPassword', 'data': ''};
|
||||||
|
$scope.email.password.actual = { 'target': 'email.password', 'data': ''};
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyMapping(confKey) {
|
||||||
|
for (var key in confKeyDefinitions) {
|
||||||
|
if (confKey === key) {
|
||||||
|
return confKeyDefinitions[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeSettings(system) {
|
function valueMapping(value) {
|
||||||
console.log(system);
|
switch(value) {
|
||||||
|
case true:
|
||||||
|
return vm.toggleBooleans[0];
|
||||||
|
case false:
|
||||||
|
return vm.toggleBooleans[1];
|
||||||
|
case 'db_auth':
|
||||||
|
return vm.supportedAuths[0];
|
||||||
|
case 'ldap_auth':
|
||||||
|
return vm.supportedAuths[1];
|
||||||
|
case 'adminonly':
|
||||||
|
return vm.toggleCustoms[0];
|
||||||
|
case 'everyone':
|
||||||
|
return vm.toggleCustoms[1];
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onChangedCallback(current, previous) {
|
||||||
|
if(!angular.equals(current, previous)) {
|
||||||
|
var compositeKey = current.target.split(".");
|
||||||
|
vm.changed = false;
|
||||||
|
var changedData = {};
|
||||||
|
switch(current.target) {
|
||||||
|
case 'auth.ldapSearchPassword':
|
||||||
|
if(vm.ldapSearchPasswordChanged) {
|
||||||
|
vm.changed = true;
|
||||||
|
changedData = $scope.auth.ldapSearchPassword.actual.data;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'email.password':
|
||||||
|
if(vm.emailPasswordChanged) {
|
||||||
|
vm.changed = true;
|
||||||
|
changedData = $scope.email.password.actual.data;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if(!angular.equals(current.data, $scope[compositeKey[0]][compositeKey[1]]['origin']['data'])) {
|
||||||
|
vm.changed = true;
|
||||||
|
changedData = current.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(vm.changed) {
|
||||||
|
vm.changedItems[current.target] = changedData;
|
||||||
|
vm.warning[current.target] = true;
|
||||||
|
} else {
|
||||||
|
delete vm.changedItems[current.target];
|
||||||
|
vm.warning[current.target] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfigurationFailed(response) {
|
||||||
|
console.log('Failed to get configurations.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateConfigurationSuccess(response) {
|
||||||
|
$scope.$emit('modalTitle', $filter('tr')('update_configuration_title', []));
|
||||||
|
$scope.$emit('modalMessage', $filter('tr')('successful_update_configuration', []));
|
||||||
|
var emitInfo = {
|
||||||
|
'confirmOnly': true,
|
||||||
|
'contentType': 'text/plain',
|
||||||
|
'action' : function() {
|
||||||
|
vm.retrieve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$scope.$emit('raiseInfo', emitInfo);
|
||||||
|
console.log('Updated system configuration successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateConfigurationFailed() {
|
||||||
|
$scope.$emit('modalTitle', $filter('tr')('update_configuration_title', []));
|
||||||
|
$scope.$emit('modalMessage', $filter('tr')('failed_to_update_configuration', []));
|
||||||
|
$scope.$emit('raiseError', true);
|
||||||
|
console.log('Failed to update system configurations.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function gatherUpdateItems() {
|
||||||
|
vm.updatedItems = {};
|
||||||
|
for(var key in confKeyDefinitions) {
|
||||||
|
var value = confKeyDefinitions[key];
|
||||||
|
var compositeKey = value.type + '.' + value.attr;
|
||||||
|
for(var itemKey in vm.changedItems) {
|
||||||
|
var item = vm.changedItems[itemKey];
|
||||||
|
if (compositeKey === itemKey) {
|
||||||
|
(typeof item === 'object' && item) ? vm.updatedItems[key] = ((typeof item.value === 'boolean') ? Number(item.value) + '' : item.value) : vm.updatedItems[key] = String(item) || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAuthConf(auth) {
|
||||||
|
vm.gatherUpdateItems();
|
||||||
|
console.log('auth changed:' + angular.toJson(vm.updatedItems));
|
||||||
|
ConfigurationService
|
||||||
|
.update(vm.updatedItems)
|
||||||
|
.then(updateConfigurationSuccess, updateConfigurationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveEmailConf(email) {
|
||||||
|
vm.gatherUpdateItems();
|
||||||
|
console.log('email changed:' + angular.toJson(vm.updatedItems));
|
||||||
|
ConfigurationService
|
||||||
|
.update(vm.updatedItems)
|
||||||
|
.then(updateConfigurationSuccess, updateConfigurationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSystemConf(system) {
|
||||||
|
vm.gatherUpdateItems();
|
||||||
|
console.log('system changed:' + angular.toJson(vm.updatedItems));
|
||||||
|
ConfigurationService
|
||||||
|
.update(vm.updatedItems)
|
||||||
|
.then(updateConfigurationSuccess, updateConfigurationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearUp(input) {
|
||||||
|
switch(input.target) {
|
||||||
|
case 'auth.ldapSearchPassword':
|
||||||
|
$scope.auth.ldapSearchPassword.data = '';
|
||||||
|
break;
|
||||||
|
case 'email.password':
|
||||||
|
$scope.email.password.data = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasChanged(input) {
|
||||||
|
switch(input.target) {
|
||||||
|
case 'auth.ldapSearchPassword':
|
||||||
|
vm.ldapSearchPasswordChanged = true;
|
||||||
|
$scope.auth.ldapSearchPassword.actual.data = input.data;
|
||||||
|
break;
|
||||||
|
case 'email.password':
|
||||||
|
vm.emailPasswordChanged = true;
|
||||||
|
$scope.email.password.actual.data = input.data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMaskPassword(input) {
|
||||||
|
input.data = defaultPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
function undo() {
|
||||||
|
vm.retrieve();
|
||||||
|
}
|
||||||
|
|
||||||
|
function pingLDAP(auth) {
|
||||||
|
var keyset = [
|
||||||
|
{'name': 'ldapURL' , 'attr': 'ldap_url'},
|
||||||
|
{'name': 'ldapSearchDN', 'attr': 'ldap_search_dn'},
|
||||||
|
{'name': 'ldapSearchPassword' , 'attr': 'ldap_search_password'},
|
||||||
|
{'name': 'ldapConnectionTimeout', 'attr': 'ldap_connection_timeout'}
|
||||||
|
];
|
||||||
|
var ldapConf = {};
|
||||||
|
|
||||||
|
for(var i = 0; i < keyset.length; i++) {
|
||||||
|
var key = keyset[i];
|
||||||
|
var value;
|
||||||
|
if(key.name === 'ldapSearchPassword') {
|
||||||
|
value = auth[key.name]['actual']['data'];
|
||||||
|
}else {
|
||||||
|
value = auth[key.name]['data'];
|
||||||
|
}
|
||||||
|
ldapConf[key.attr] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.pingMessage = $filter('tr')('pinging_target');
|
||||||
|
vm.pingTIP = true;
|
||||||
|
vm.isError = false;
|
||||||
|
|
||||||
|
ConfigurationService
|
||||||
|
.pingLDAP(ldapConf)
|
||||||
|
.then(pingLDAPSuccess, pingLDAPFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pingLDAPSuccess(response) {
|
||||||
|
vm.pingTIP = false;
|
||||||
|
vm.pingMessage = $filter('tr')('successful_ping_target');
|
||||||
|
}
|
||||||
|
|
||||||
|
function pingLDAPFailed(response) {
|
||||||
|
vm.isError = true;
|
||||||
|
vm.pingTIP = false;
|
||||||
|
vm.pingMessage = $filter('tr')('failed_to_ping_target');
|
||||||
|
console.log('Failed to ping LDAP target:' + response.data);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function configuration() {
|
configuration.$inject = ['$filter', 'trFilter'];
|
||||||
|
|
||||||
|
function configuration($filter, trFilter) {
|
||||||
var directive = {
|
var directive = {
|
||||||
'restrict': 'E',
|
'restrict': 'E',
|
||||||
'templateUrl': '/static/resources/js/components/system-management/configuration.directive.html',
|
'templateUrl': '/static/resources/js/components/system-management/configuration.directive.html',
|
||||||
'scope': true,
|
'scope': true,
|
||||||
|
'link': link,
|
||||||
'controller': ConfigurationController,
|
'controller': ConfigurationController,
|
||||||
'controllerAs': 'vm',
|
'controllerAs': 'vm',
|
||||||
'bindToController': true
|
'bindToController': true
|
||||||
};
|
};
|
||||||
return directive;
|
return directive;
|
||||||
|
|
||||||
|
function link(scope, element, attrs, ctrl) {
|
||||||
|
element.find('#ulTabHeader a').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
ctrl.gatherUpdateItems();
|
||||||
|
if(!angular.equals(ctrl.updatedItems, {})) {
|
||||||
|
var emitInfo = {
|
||||||
|
'confirmOnly': true,
|
||||||
|
'contentType': 'text/html',
|
||||||
|
'action' : function() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
scope.$emit('modalTitle', $filter('tr')('caution'));
|
||||||
|
scope.$emit('modalMessage', $filter('tr')('please_save_changes'));
|
||||||
|
scope.$emit('raiseInfo', emitInfo);
|
||||||
|
scope.$apply();
|
||||||
|
e.stopPropagation();
|
||||||
|
}else{
|
||||||
|
$(this).tab('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
element.find('#ulTabHeader a:first').trigger('click');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
@ -29,6 +29,7 @@
|
|||||||
switch(currentTarget) {
|
switch(currentTarget) {
|
||||||
case 'destinations':
|
case 'destinations':
|
||||||
case 'replication':
|
case 'replication':
|
||||||
|
case 'configuration':
|
||||||
$location.path('/' + currentTarget);
|
$location.path('/' + currentTarget);
|
||||||
vm.target = currentTarget;
|
vm.target = currentTarget;
|
||||||
break;
|
break;
|
||||||
|
@ -12,10 +12,8 @@
|
|||||||
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.
|
||||||
-->
|
-->
|
||||||
<ul class="switch-pane-admin-options" role="tablist">
|
<ul class="switch-pane-admin-options" role="tablist" ng-style="vm.customPos">
|
||||||
<li><a tag="destinations" href="#/destinations">// 'destination' | tr //</a><span class="gutter">|</span></li>
|
<li><a tag="destinations" href="#/destinations">// 'destination' | tr //</a><span class="gutter">|</span></li>
|
||||||
<li><a tag="replication" href="#/replication">// 'replication' | tr //</a><span class="gutter">
|
<li><a tag="replication" href="#/replication">// 'replication' | tr //</a><span class="gutter">|</span>
|
||||||
<!--
|
<li><a tag="configuration" href="#/configuration">// 'configuration' | tr //</a></li>
|
||||||
<li><a tag="configuration" href="#/configuration">Configuration</a></li>
|
|
||||||
-->
|
|
||||||
</ul>
|
</ul>
|
@ -27,7 +27,9 @@
|
|||||||
vm.path = $location.path();
|
vm.path = $location.path();
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigationAdminOptions() {
|
navigationAdminOptions.$inject = ['I18nService'];
|
||||||
|
|
||||||
|
function navigationAdminOptions(I18nService) {
|
||||||
var directive = {
|
var directive = {
|
||||||
'restrict': 'E',
|
'restrict': 'E',
|
||||||
'templateUrl': '/static/resources/js/layout/navigation/navigation-admin-options.directive.html',
|
'templateUrl': '/static/resources/js/layout/navigation/navigation-admin-options.directive.html',
|
||||||
@ -44,7 +46,14 @@
|
|||||||
function link(scope, element, attrs, ctrl) {
|
function link(scope, element, attrs, ctrl) {
|
||||||
var visited = ctrl.path.substring(1);
|
var visited = ctrl.path.substring(1);
|
||||||
console.log('visited:' + visited);
|
console.log('visited:' + visited);
|
||||||
|
|
||||||
|
var lang = I18nService().getCurrentLanguage();
|
||||||
|
ctrl.customPos = {};
|
||||||
|
|
||||||
|
if(lang === 'zh-CN') {
|
||||||
|
ctrl.customPos = {'position': 'relative', 'left': '14%'};
|
||||||
|
}
|
||||||
|
|
||||||
if(visited) {
|
if(visited) {
|
||||||
element.find('a[tag="' + visited + '"]').addClass('active');
|
element.find('a[tag="' + visited + '"]').addClass('active');
|
||||||
}else{
|
}else{
|
||||||
|
@ -35,7 +35,9 @@
|
|||||||
vm.path = $location.path();
|
vm.path = $location.path();
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigationDetails() {
|
navigationDetails.$inject = ['I18nService'];
|
||||||
|
|
||||||
|
function navigationDetails(I18nService) {
|
||||||
var directive = {
|
var directive = {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
templateUrl: '/navigation_detail?timestamp=' + new Date().getTime(),
|
templateUrl: '/navigation_detail?timestamp=' + new Date().getTime(),
|
||||||
@ -53,6 +55,13 @@
|
|||||||
|
|
||||||
function link(scope, element, attrs, ctrl) {
|
function link(scope, element, attrs, ctrl) {
|
||||||
|
|
||||||
|
var lang = I18nService().getCurrentLanguage();
|
||||||
|
ctrl.customPos = {};
|
||||||
|
|
||||||
|
if(lang === 'zh-CN') {
|
||||||
|
ctrl.customPos = {'position': 'relative', 'left': '8%'};
|
||||||
|
}
|
||||||
|
|
||||||
var visited = ctrl.path.substring(1);
|
var visited = ctrl.path.substring(1);
|
||||||
|
|
||||||
if(visited) {
|
if(visited) {
|
||||||
|
@ -289,5 +289,57 @@ var locale_messages = {
|
|||||||
'confirm_to_toggle_enabled_policy': 'After enabling the replication policy, all repositories under the project will be replicated to the destination registry. Please confirm to continue.',
|
'confirm_to_toggle_enabled_policy': 'After enabling the replication policy, all repositories under the project will be replicated to the destination registry. Please confirm to continue.',
|
||||||
'confirm_to_toggle_disabled_policy_title': 'Disable Policy',
|
'confirm_to_toggle_disabled_policy_title': 'Disable Policy',
|
||||||
'confirm_to_toggle_disabled_policy': 'After disabling the policy, all unfinished replication jobs of this policy will be stopped and canceled. Please confirm to continue.',
|
'confirm_to_toggle_disabled_policy': 'After disabling the policy, all unfinished replication jobs of this policy will be stopped and canceled. Please confirm to continue.',
|
||||||
'begin_date_is_later_than_end_date': 'Begin date should not be later than end date.'
|
'begin_date_is_later_than_end_date': 'Begin date should not be later than end date.',
|
||||||
|
'configuration': 'Configuration',
|
||||||
|
'authentication': 'Authentication',
|
||||||
|
'email_settings': 'Email Settings',
|
||||||
|
'system_settings': 'System Settings',
|
||||||
|
'authentication_mode': 'Authentication Mode',
|
||||||
|
'authentication_mode_desc': 'The default authentication mode is db_auth. Set it to ldap_auth when users\' credentials are stored in an LDAP or AD server. Note: this option can only be set once.',
|
||||||
|
'self_registration': 'Self Registration',
|
||||||
|
'self_registration_desc': 'Determine whether the self-registration is allowed or not. Set this to off to disable a user\'s self-registration in Harbor. This flag has no effect when users are stored in LDAP or AD.',
|
||||||
|
'ldap_url': 'LDAP URL',
|
||||||
|
'ldap_url_desc': 'The URL of an LDAP/AD server.',
|
||||||
|
'ldap_search_dn': 'LDAP Search DN',
|
||||||
|
'ldap_search_dn_desc': 'A user\'s DN who has the permission to search the LDAP/AD server. Leave blank if your LDAP/AD server supports anonymous search, otherwise you should configure this DN and LDAP Search Password.',
|
||||||
|
'ldap_search_password': 'LDAP Search Password',
|
||||||
|
'ldap_search_password_desc': 'The password of the user for LDAP search. Leave blank if your LDAP/AD server supports anonymous search.',
|
||||||
|
'ldap_base_dn': 'LDAP Base DN',
|
||||||
|
'ldap_base_dn_desc': 'The base DN of a node from which to look up a user for authentication. The search scope includes subtree of the node.',
|
||||||
|
'ldap_uid': 'LDAP UID',
|
||||||
|
'ldap_uid_desc': 'The attribute used in a search to match a user, it could be uid, cn, email, sAMAccountName or other attributes depending on your LDAP/AD server.',
|
||||||
|
'ldap_filter': 'LDAP Filter',
|
||||||
|
'ldap_filter_desc': 'Search filter for LDAP/AD, make sure the syntax of the filter is correct.',
|
||||||
|
'ldap_connection_timeout': 'LDAP Connection Timeout(sec.)',
|
||||||
|
'ldap_scope': 'LDAP Scope',
|
||||||
|
'ldap_scope_desc': 'The scope to search for users.(1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE)',
|
||||||
|
'email_server': 'Email Server',
|
||||||
|
'email_server_desc': 'The mail server to send out emails to reset password.',
|
||||||
|
'email_server_port': 'Email Server Port',
|
||||||
|
'email_server_port_desc': 'The port of mail server.',
|
||||||
|
'email_username': 'Email Username',
|
||||||
|
'email_username_desc': 'The user from whom the password reset email is sent. Usually this is a system email address.',
|
||||||
|
'email_password': 'Email Password',
|
||||||
|
'email_password_desc': 'The password of the user from whom the password reset email is sent.',
|
||||||
|
'email_from': 'Email From',
|
||||||
|
'email_from_desc': 'The name of the email sender.',
|
||||||
|
'email_ssl': 'Email SSL',
|
||||||
|
'email_ssl_desc': 'Whether to enable secure mail transmission.',
|
||||||
|
'project_creation_restriction': 'Project Creation Restriction',
|
||||||
|
'project_creation_restriction_desc': 'The flag to control what users have permission to create projects. Be default everyone can create a project, set to "adminonly" such that only admin can create project.',
|
||||||
|
'verify_remote_cert': 'Verify Remote Cert.',
|
||||||
|
'verify_remote_cert_desc': 'Determine whether the image replication should verify the certificate of a remote Harbor registry. Set this flag to off when the remote registry uses a self-signed or untrusted certificate.',
|
||||||
|
'max_job_workers': 'Max Job Workers',
|
||||||
|
'max_job_workers_desc': 'Maximum number of job workers in job service.',
|
||||||
|
'please_save_changes': 'Please save changes before leaving this page.',
|
||||||
|
'undo': 'Undo',
|
||||||
|
'invalid_port_number': 'Invalid port number.',
|
||||||
|
'max_job_workers_is_required': 'Max job workers number is required.',
|
||||||
|
'timeout_is_required': 'Timeout value is required.',
|
||||||
|
'invalid_timeout': 'Invalid timeout value.',
|
||||||
|
'ldap_scope_is_required': 'Scope is required.',
|
||||||
|
'invalid_ldap_scope': 'Invalid Scope value.',
|
||||||
|
'update_configuration_title': 'Update Configuration(s)',
|
||||||
|
'successful_update_configuration': 'Configuration(s) updated successfully.',
|
||||||
|
'failed_to_update_configuration': 'Failed to update configuration.'
|
||||||
};
|
};
|
@ -289,5 +289,57 @@ var locale_messages = {
|
|||||||
'confirm_to_toggle_enabled_policy': '启用策略后,该项目下的所有镜像仓库将复制到目标实例。请确认继续。',
|
'confirm_to_toggle_enabled_policy': '启用策略后,该项目下的所有镜像仓库将复制到目标实例。请确认继续。',
|
||||||
'confirm_to_toggle_disabled_policy_title': '停用策略',
|
'confirm_to_toggle_disabled_policy_title': '停用策略',
|
||||||
'confirm_to_toggle_disabled_policy': '停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。',
|
'confirm_to_toggle_disabled_policy': '停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。',
|
||||||
'begin_date_is_later_than_end_date': '起始日期不能晚于结束日期。'
|
'begin_date_is_later_than_end_date': '起始日期不能晚于结束日期。',
|
||||||
|
'configuration': '设置',
|
||||||
|
'authentication': '认证设置',
|
||||||
|
'email_settings': '邮箱设置',
|
||||||
|
'system_settings': '系统设置',
|
||||||
|
'authentication_mode': '认证模式',
|
||||||
|
'authentication_mode_desc': '默认的认证模式是: 数据库认证。 当用户信息存储在LDAP或AD服务器时,应设置为LDAP认证模式。 注意:该选项只能被设置一次。',
|
||||||
|
'self_registration': '自注册',
|
||||||
|
'self_registration_desc': '确定是否允许或禁止用户自注册。 关闭该项会禁用Harbor用户注册。 当用户数据存储在LDAP或AD时,该选项无效。 ',
|
||||||
|
'ldap_url': 'LDAP URL',
|
||||||
|
'ldap_url_desc': 'LDAP/AD服务URL。',
|
||||||
|
'ldap_search_dn': 'LDAP Search DN',
|
||||||
|
'ldap_search_dn_desc': '提供一个拥有检索LDAP/AD权限的用户DN。 如果LDAP/AD允许匿名访问该项可以置空, 否则需提供用户DN和密码。',
|
||||||
|
'ldap_search_password': 'LDAP Search 密码',
|
||||||
|
'ldap_search_password_desc': '检索LDAP的用户密码。 如果LDAP/AD允许匿名访问该项可以置空。',
|
||||||
|
'ldap_base_dn': 'LDAP Base DN',
|
||||||
|
'ldap_base_dn_desc': '用于查询认证用户的基本DN节点。 检索范围包含子树节点。',
|
||||||
|
'ldap_uid': 'LDAP UID',
|
||||||
|
'ldap_uid_desc': '该属性用于检索匹配用户, 可以是uid, cn, Email, sAMAccountName或是其他属性, 取决于LDAP/AD服务。',
|
||||||
|
'ldap_filter': 'LDAP 过滤器',
|
||||||
|
'ldap_filter_desc': '用于过滤LDAP/AP检索, 请确保正确语法形式。',
|
||||||
|
'ldap_connection_timeout': 'LDAP连接超时(秒)',
|
||||||
|
'ldap_scope': 'LDAP检索范围',
|
||||||
|
'ldap_scope_desc': '指定用户检索范围。(1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE)',
|
||||||
|
'email_server': '邮箱服务地址',
|
||||||
|
'email_server_desc': '用以发送重置密码的邮箱服务地址。',
|
||||||
|
'email_server_port': '邮箱服务端口号',
|
||||||
|
'email_server_port_desc': '邮箱服务端口号',
|
||||||
|
'email_username': '邮箱用户名',
|
||||||
|
'email_username_desc': '发送重置密码邮箱的用户。 通常是系统邮箱地址。',
|
||||||
|
'email_password': '邮箱密码',
|
||||||
|
'email_password_desc': '发送重置密码邮箱的密码。',
|
||||||
|
'email_from': '寄信人',
|
||||||
|
'email_from_desc': '邮箱寄信人名。',
|
||||||
|
'email_ssl': '邮箱SSL',
|
||||||
|
'email_ssl_desc': '是否启用安全邮箱传输。',
|
||||||
|
'project_creation_restriction': '项目创建约束',
|
||||||
|
'project_creation_restriction_desc': '此标志用于控制用户是否有权限创建项目。 默认允许任何人创建项目, 当设置为"adminonly"只允许管理员创建项目。',
|
||||||
|
'verify_remote_cert': '验证远程服务证书',
|
||||||
|
'verify_remote_cert_desc': '确定镜像复制是否检查远程Harbor服务证书。 当使用非受信或自签名证书时应该关闭该检查。',
|
||||||
|
'max_job_workers': '最大任务调度数',
|
||||||
|
'max_job_workers_desc': '任务调度服务最大调度数。',
|
||||||
|
'please_save_changes': '请在离开此页之前保存。',
|
||||||
|
'undo': '撤销',
|
||||||
|
'invalid_port_number': '无效的端口号。',
|
||||||
|
'max_job_workers_is_required': '最大任务数为必填项。',
|
||||||
|
'timeout_is_required': '超时时间为必填项。',
|
||||||
|
'invalid_timeout': '无效的超时时间。',
|
||||||
|
'ldap_scope_is_required': '范围值为必填项。',
|
||||||
|
'invalid_ldap_scope': '无效的范围值。',
|
||||||
|
'update_configuration_title': '修改设置',
|
||||||
|
'successful_update_configuration': '修改设置成功。',
|
||||||
|
'failed_to_update_configuration': '修改设置失败。'
|
||||||
};
|
};
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
angular.module('harbor.services.system.info')
|
||||||
|
.service('ConfigurationService', ConfigurationService);
|
||||||
|
|
||||||
|
ConfigurationService.$inject = ['$http', '$q', '$timeout'];
|
||||||
|
|
||||||
|
function ConfigurationService($http, $q, $timeout) {
|
||||||
|
this.get = get;
|
||||||
|
this.update = update;
|
||||||
|
this.pingLDAP = pingLDAP;
|
||||||
|
|
||||||
|
function get() {
|
||||||
|
return $http.get('/api/configurations');
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(updates) {
|
||||||
|
return $http.put('/api/configurations', updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pingLDAP(ldapConf) {
|
||||||
|
return $http
|
||||||
|
.post('/api/ldap/ping', ldapConf);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
@ -1,5 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -7,3 +8,4 @@ import (
|
|||||||
func TestMain(t *testing.T) {
|
func TestMain(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user