8
.gitignore
vendored
@ -1,11 +1,5 @@
|
||||
harbor
|
||||
make/common/config/registry/config.yml
|
||||
make/common/config/ui/env
|
||||
make/common/config/ui/app.conf
|
||||
make/common/config/db/env
|
||||
make/common/config/jobservice/env
|
||||
make/common/config/nginx/nginx.conf
|
||||
make/common/config/nginx/cert/*
|
||||
make/common/config/*
|
||||
make/dev/ui/harbor_ui
|
||||
make/dev/jobservice/harbor_jobservice
|
||||
src/ui/ui
|
||||
|
64
Makefile
@ -5,10 +5,14 @@
|
||||
# all: prepare env, compile binarys, build images and install images
|
||||
# prepare: prepare env
|
||||
# compile: compile ui and jobservice code
|
||||
# compile_buildgolangimage:
|
||||
# compile local building golang image
|
||||
# forexample : make compile_buildgolangimage -e \
|
||||
# GOBUILDIMAGE=harborgo:1.6.2
|
||||
# compile_golangimage:
|
||||
# compile from golang image
|
||||
# for example: make compile_golangimage -e GOBUILDIMAGE= \
|
||||
# reg-bj.eng.vmware.com/harborrelease/harborgo:1.6.2
|
||||
# harborgo:1.6.2
|
||||
# compile_ui, compile_jobservice: compile specific binary
|
||||
#
|
||||
# build: build Harbor docker images (defuault: build_photon)
|
||||
@ -52,8 +56,6 @@
|
||||
# cleanversiontag:
|
||||
# cleanpackageremove specific version tag
|
||||
# cleanpackage: remove online/offline install package
|
||||
#
|
||||
# all: install
|
||||
#
|
||||
# other example:
|
||||
# clean specific version binarys and images:
|
||||
@ -106,6 +108,7 @@ GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/src/jobservice
|
||||
GOBUILDMAKEPATH=$(GOBUILDPATH)/make
|
||||
GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/dev/ui
|
||||
GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice
|
||||
GOLANGDOCKERFILENAME=Dockerfile.golang
|
||||
|
||||
# binary
|
||||
UISOURCECODE=$(SRCPATH)/ui
|
||||
@ -170,7 +173,7 @@ REGISTRYUSER=user
|
||||
REGISTRYPASSWORD=default
|
||||
|
||||
version:
|
||||
if [ "$(DEVFLAG)" = "false" ] ; then \
|
||||
@if [ "$(DEVFLAG)" = "false" ] ; then \
|
||||
$(SEDCMD) -i 's/version=\"{{.Version}}\"/version=\"$(VERSIONTAG)\"/' -i $(VERSIONFILEPATH)/$(VERSIONFILENAME) ; \
|
||||
fi
|
||||
|
||||
@ -179,40 +182,41 @@ check_environment:
|
||||
|
||||
compile_ui:
|
||||
@echo "compiling binary for ui..."
|
||||
$(GOBUILD) -o $(UIBINARYPATH)/$(UIBINARYNAME) $(UISOURCECODE)
|
||||
@$(GOBUILD) -o $(UIBINARYPATH)/$(UIBINARYNAME) $(UISOURCECODE)
|
||||
@echo "Done."
|
||||
|
||||
compile_jobservice:
|
||||
@echo "compiling binary for jobservice..."
|
||||
$(GOBUILD) -o $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) $(JOBSERVICESOURCECODE)
|
||||
@$(GOBUILD) -o $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) $(JOBSERVICESOURCECODE)
|
||||
@echo "Done."
|
||||
|
||||
compile_normal: compile_ui compile_jobservice
|
||||
|
||||
compile_golangimage:
|
||||
@echo "pulling golang build base image"
|
||||
$(DOCKERPULL) $(GOBUILDIMAGE)
|
||||
compile_buildgolangimage:
|
||||
@echo "compiling golang image for harbor ..."
|
||||
@$(DOCKERBUILD) -t $(GOBUILDIMAGE) -f $(TOOLSPATH)/$(GOLANGDOCKERFILENAME) .
|
||||
@echo "Done."
|
||||
|
||||
compile_golangimage:
|
||||
@echo "compiling binary for ui (golang image)..."
|
||||
@echo $(GOBASEPATH)
|
||||
@echo $(GOBUILDPATH)
|
||||
$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_UI) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_UI)/$(UIBINARYNAME)
|
||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_UI) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_UI)/$(UIBINARYNAME)
|
||||
@echo "Done."
|
||||
|
||||
@echo "compiling binary for jobservice (golang image)..."
|
||||
$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_JOBSERVICE) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_JOBSERVICE)/$(JOBSERVICEBINARYNAME)
|
||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_JOBSERVICE) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_JOBSERVICE)/$(JOBSERVICEBINARYNAME)
|
||||
@echo "Done."
|
||||
|
||||
compile:check_environment $(COMPILETAG)
|
||||
|
||||
prepare:
|
||||
@echo "preparing..."
|
||||
$(MAKEPATH)/$(PREPARECMD) -conf $(CONFIGPATH)/$(CONFIGFILE)
|
||||
@$(MAKEPATH)/$(PREPARECMD) -conf $(CONFIGPATH)/$(CONFIGFILE)
|
||||
|
||||
build_common: version
|
||||
@echo "buildging db container for photon..."
|
||||
cd $(DOCKERFILEPATH_DB) && $(DOCKERBUILD) -f $(DOCKERFILENAME_DB) -t $(DOCKERIMAGENAME_DB):$(VERSIONTAG) .
|
||||
@cd $(DOCKERFILEPATH_DB) && $(DOCKERBUILD) -f $(DOCKERFILENAME_DB) -t $(DOCKERIMAGENAME_DB):$(VERSIONTAG) .
|
||||
@echo "Done."
|
||||
|
||||
build_photon: build_common
|
||||
@ -224,13 +228,13 @@ build_ubuntu: build_common
|
||||
build: build_$(BASEIMAGE)
|
||||
|
||||
modify_composefile:
|
||||
@echo "preparing tag:$(VERSIONTAG) docker-compose file..."
|
||||
@echo "preparing docker-compose file..."
|
||||
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSETPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||
@$(SEDCMD) -i 's/image\: vmware.*/&:$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||
|
||||
install: compile build prepare modify_composefile
|
||||
@echo "loading harbor images..."
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) up -d
|
||||
@$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) up -d
|
||||
@echo "Install complete. You can visit harbor now."
|
||||
|
||||
package_online: modify_composefile
|
||||
@ -238,12 +242,13 @@ package_online: modify_composefile
|
||||
@cp -r make $(HARBORPKG)
|
||||
@if [ -n "$(REGISTRYSERVER)" ] ; then \
|
||||
$(SEDCMD) -i 's/image\: vmware/image\: $(REGISTRYSERVER)\/$(REGISTRYPROJECTNAME)/' \
|
||||
$(HARBORPKG)/docker-compose.$(VERSIONTAG).yml ; \
|
||||
$(HARBORPKG)/docker-compose.yml ; \
|
||||
fi
|
||||
@cp LICENSE $(HARBORPKG)/LICENSE
|
||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||
@$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
||||
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/ubuntu \
|
||||
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/common/config\
|
||||
--exclude=$(HARBORPKG)/common/log --exclude=$(HARBORPKG)/ubuntu \
|
||||
--exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \
|
||||
--exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \
|
||||
--exclude=$(HARBORPKG)/checkenv.sh \
|
||||
@ -262,11 +267,11 @@ package_offline: compile build modify_composefile
|
||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||
|
||||
@echo "pulling nginx and registry..."
|
||||
$(DOCKERPULL) registry:2.5.0
|
||||
$(DOCKERPULL) nginx:1.11.5
|
||||
@$(DOCKERPULL) registry:2.5.0
|
||||
@$(DOCKERPULL) nginx:1.11.5
|
||||
|
||||
@echo "saving harbor docker image"
|
||||
$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||
@$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||
@ -274,7 +279,8 @@ package_offline: compile build modify_composefile
|
||||
nginx:1.11.5 registry:2.5.0
|
||||
|
||||
@$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
||||
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/ubuntu \
|
||||
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/common/config\
|
||||
--exclude=$(HARBORPKG)/common/log --exclude=$(HARBORPKG)/ubuntu \
|
||||
--exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \
|
||||
--exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \
|
||||
--exclude=$(HARBORPKG)/checkenv.sh \
|
||||
@ -287,10 +293,10 @@ package_offline: compile build modify_composefile
|
||||
|
||||
pushimage:
|
||||
@echo "pushing harbor images ..."
|
||||
$(DOCKERTAG) $(DOCKERIMAGENAME_UI):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_UI):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||
@ -309,7 +315,7 @@ pushimage:
|
||||
|
||||
start:
|
||||
@echo "loading harbor images..."
|
||||
@$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.$(VERSIONTAG).yml up -d
|
||||
@$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml up -d
|
||||
@echo "Start complete. You can visit harbor now."
|
||||
|
||||
down:
|
||||
@ -328,12 +334,12 @@ cleanimage:
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||
#- $(DOCKERRMIMAGE) -f registry:2.5.0
|
||||
#- $(DOCKERRMIMAGE) -f nginx:1.11.5
|
||||
# - $(DOCKERRMIMAGE) -f registry:2.5.0
|
||||
# - $(DOCKERRMIMAGE) -f nginx:1.11.5
|
||||
|
||||
cleandockercomposefile:
|
||||
@echo "cleaning $(DOCKERCOMPOSEFILEPATH)/docker-compose.$(VERSIONTAG).yml"
|
||||
@if [ -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.$(VERSIONTAG).yml ] ; then rm $(DOCKERCOMPOSEFILEPATH)/docker-compose.$(VERSIONTAG).yml ; fi
|
||||
@echo "cleaning $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml"
|
||||
@if [ -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ] ; then rm $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ; fi
|
||||
|
||||
cleanversiontag:
|
||||
@echo "cleaning version TAG"
|
||||
|
209
docs/compile_guide.md
Normal file
@ -0,0 +1,209 @@
|
||||
## Introduction
|
||||
|
||||
This guide provides instructions for developers to build and run Harbor from source code.
|
||||
|
||||
## Step 1: Prepare for a build environment for Harbor
|
||||
|
||||
Harbor is deployed as several Docker containers and most of the code is written in Go language. The build host requires Python, Docker, Docker Compose and golang development environment. Please install the below prerequisites:
|
||||
|
||||
|
||||
Software | Required Version
|
||||
----------------------|--------------------------
|
||||
docker | 1.10.0 +
|
||||
docker-compose | 1.7.1 +
|
||||
python | 2.7 +
|
||||
git | 1.9.1 +
|
||||
make | 3.81 +
|
||||
golang* | 1.6.0 +
|
||||
*optional
|
||||
|
||||
|
||||
## Step 2: Getting the source code
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/vmware/harbor
|
||||
```
|
||||
|
||||
## Step 3: Resolving dependencies of Go language
|
||||
You can compile the source code by using a Golang dev image. In this case, you can skip this step.
|
||||
|
||||
If you are building Harbor using your own Go compiling environment. You need to install LDAP packages manually.
|
||||
|
||||
For PhotonOS:
|
||||
|
||||
```sh
|
||||
$ tdnf install -y sed apr-util-ldap
|
||||
```
|
||||
|
||||
For Ubuntu:
|
||||
|
||||
```sh
|
||||
$ apt-get update && apt-get install -y libldap2-dev
|
||||
```
|
||||
|
||||
For other platforms, please consult the relevant documentation of installing LDAP package.
|
||||
|
||||
## Step 4: Building and installing Harbor
|
||||
|
||||
### Configuration
|
||||
|
||||
Edit the file **make/harbor.cfg** and make necessary configuration changes such as hostname, admin password and mail server. Refer to **[Installation and Configuration Guide](installation_guide.md#configuring-harbor)** for more info.
|
||||
|
||||
```sh
|
||||
$ cd harbor
|
||||
$ vi make/harbor.cfg
|
||||
```
|
||||
|
||||
### Compiling and Running
|
||||
|
||||
You can compile the code by one of the three approaches:
|
||||
|
||||
#### I. Create a Golang dev image, then build Harbor
|
||||
|
||||
* Build Golang dev image:
|
||||
|
||||
```sh
|
||||
$ make compile_buildgolangimage -e GOBUILDIMAGE=harborgo:1.6.2
|
||||
```
|
||||
|
||||
* Build, install and bring up Harbor:
|
||||
|
||||
```sh
|
||||
$ make install -e GOBUILDIMAGE=harborgo:1.6.2 COMPILETAG=compile_golangimage
|
||||
```
|
||||
|
||||
#### II. Compile code with your own Golang environment, then build Harbor
|
||||
|
||||
* Move source code to $GOPATH
|
||||
|
||||
```sh
|
||||
$ mkdir $GOPATH/src/github.com/vmware/
|
||||
$ cd ..
|
||||
$ mv harbor $GOPATH/src/github.com/vmware/.
|
||||
```
|
||||
|
||||
* Build, install and run Harbor
|
||||
|
||||
```sh
|
||||
$ cd $GOPATH/src/github.com/vmware/harbor
|
||||
$ make install
|
||||
```
|
||||
|
||||
#### III. Manual build process (compatible with previous versions)
|
||||
|
||||
```sh
|
||||
$ cd make
|
||||
|
||||
$ ./prepare
|
||||
Generated configuration file: ./config/ui/env
|
||||
Generated configuration file: ./config/ui/app.conf
|
||||
Generated configuration file: ./config/registry/config.yml
|
||||
Generated configuration file: ./config/db/env
|
||||
...
|
||||
|
||||
$ cd dev
|
||||
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
### Verify your installation
|
||||
|
||||
If everyting worked properly, you can get the below message:
|
||||
|
||||
```sh
|
||||
...
|
||||
----Harbor has been installed and started successfully.----
|
||||
|
||||
Now you should be able to visit the admin portal at http://$YOURIP.
|
||||
For more details, please visit https://github.com/vmware/harbor .
|
||||
```
|
||||
|
||||
Refer to [Installation and Configuration Guide](installation_guide.md#managing-harbors-lifecycle) for more information about managing your Harbor instance.
|
||||
|
||||
## Appendix
|
||||
* Using the Makefile
|
||||
|
||||
The `Makefile` contains these configurable parameters:
|
||||
|
||||
Variable | Description
|
||||
-------------------|-------------
|
||||
BASEIMAGE | Container base image, default: photon
|
||||
DEVFLAG | Build model flag, default: dev
|
||||
COMPILETAG | Compile model flag, default: compile_normal (local golang build)
|
||||
REGISTRYSERVER | Remote registry server IP address
|
||||
REGISTRYUSER | Remote registry server user name
|
||||
REGISTRYPASSWORD | Remote registry server user password
|
||||
REGISTRYPROJECTNAME| Project name on remote registry server
|
||||
|
||||
* Predefined targets:
|
||||
|
||||
Target | Description
|
||||
--------------------|-------------
|
||||
all | prepare env, compile binaries, build images and install images
|
||||
prepare | prepare env
|
||||
compile | compile ui and jobservice code
|
||||
compile_golangimage | compile local golang image
|
||||
compile_ui | compile ui binary
|
||||
compile_jobservice | compile jobservice binary
|
||||
build | build Harbor docker images (default: using build_photon)
|
||||
build_photon | build Harbor docker images from Photon OS base image
|
||||
build_ubuntu | build Harbor docker images from Ubuntu base image
|
||||
install | compile binaries, build images, prepare specific version of compose file and startup Harbor instance
|
||||
start | startup Harbor instance
|
||||
down | shutdown Harbor instance
|
||||
package_online | prepare online install package
|
||||
package_offline | prepare offline install package
|
||||
pushimage | push Harbor images to specific registry server
|
||||
clean all | remove binary, Harbor images, specific version docker-compose file, specific version tag and online/offline install package
|
||||
cleanbinary | remove ui and jobservice binary
|
||||
cleanimage | remove Harbor images
|
||||
cleandockercomposefile | remove specific version docker-compose
|
||||
cleanversiontag | remove specific version tag
|
||||
cleanpackage | remove online/offline install package
|
||||
|
||||
#### EXAMPLE:
|
||||
|
||||
#### Build a golang dev image (for building Harbor):
|
||||
|
||||
```sh
|
||||
$ make compile_golangimage -e GOBUILDIMAGE= [$YOURIMAGE]
|
||||
|
||||
```
|
||||
|
||||
#### Build Harbor images based on Ubuntu
|
||||
|
||||
```sh
|
||||
$ make build -e BASEIMAGE=ubuntu
|
||||
|
||||
```
|
||||
|
||||
#### Push Harbor images to specific registry server
|
||||
|
||||
```sh
|
||||
$ make pushimage -e DEVFLAG=false REGISTRYSERVER=[$SERVERADDRESS] REGISTRYUSER=[$USERNAME] REGISTRYPASSWORD=[$PASSWORD] REGISTRYPROJECTNAME=[$PROJECTNAME]
|
||||
|
||||
```
|
||||
|
||||
**Note**: need add "/" on end of REGISTRYSERVER. If REGISTRYSERVER is not set, images will be pushed directly to Docker Hub.
|
||||
|
||||
|
||||
```sh
|
||||
$ make pushimage -e DEVFLAG=false REGISTRYUSER=[$USERNAME] REGISTRYPASSWORD=[$PASSWORD] REGISTRYPROJECTNAME=[$PROJECTNAME]
|
||||
|
||||
```
|
||||
|
||||
#### Clean up binaries and images of a specific version
|
||||
|
||||
```sh
|
||||
$ make clean -e VERSIONTAG=[TAG]
|
||||
|
||||
```
|
||||
**Note**: If new code had been added to Github, the git commit TAG will change. Better use this command to clean up images and files of previous TAG.
|
||||
|
||||
#### By default, the make process create a development build. To create a release build of Harbor, set the below flag to false.
|
||||
|
||||
```sh
|
||||
$ make XXXX -e DEVFLAG=false
|
||||
|
||||
```
|
||||
|
@ -27,26 +27,18 @@ Otherwise, if you use IP address to connect your registry host, CN can be anythi
|
||||
```
|
||||
3) Generate the certificate of your registry host:
|
||||
|
||||
On Ubuntu, the config file of openssl locates at **/etc/ssl/openssl.cnf**. Refer to openssl document for more information. The default CA directory of openssl is called demoCA. Let's create necessary directories and files:
|
||||
|
||||
```
|
||||
mkdir demoCA
|
||||
cd demoCA
|
||||
touch index.txt
|
||||
echo '01' > serial
|
||||
cd ..
|
||||
```
|
||||
If you're using FQDN like **reg.yourdomain.com** to connect your registry host, then run this command to generate the certificate of your registry host:
|
||||
|
||||
```
|
||||
openssl ca -in yourdomain.com.csr -out yourdomain.com.crt -cert ca.crt -keyfile ca.key -outdir .
|
||||
openssl x509 -req -days 365 -in yourdomain.com.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out yourdomain.com.crt
|
||||
```
|
||||
If you're using **IP**, say **192.168.1.101** to connect your registry host, you may instead run the command below:
|
||||
|
||||
```
|
||||
echo subjectAltName = IP:192.168.1.101 > extfile.cnf
|
||||
|
||||
openssl ca -in yourdomain.com.csr -out yourdomain.com.crt -cert ca.crt -keyfile ca.key -extfile extfile.cnf -outdir .
|
||||
openssl x509 -req -days 365 -in yourdomain.com.csr -CA ca.crt -CAkey ca.key -CAcreateserial -extfile extfile.cnf -out yourdomain.com
|
||||
.crt
|
||||
```
|
||||
##Configuration and Installation
|
||||
After obtaining the **yourdomain.com.crt** and **yourdomain.com.key** files,
|
||||
@ -124,3 +116,4 @@ If you've mapped nginx 443 port to another, you need to add the port to login, l
|
||||
update-ca-trust
|
||||
```
|
||||
|
||||
|
||||
|
@ -36,7 +36,7 @@ From time to time, you may need to mannually test Harbor REST API. You can deplo
|
||||
```
|
||||
* Change the directory to _make_
|
||||
```sh
|
||||
cd ../make
|
||||
cd ../make/dev
|
||||
```
|
||||
* Edit the _docker-compose.yml_ file.
|
||||
```sh
|
||||
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 110 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 44 KiB |
BIN
docs/img/ova/ova09.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
docs/img/ova/ova_edit_settings.png
Normal file
After Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 37 KiB |
@ -22,43 +22,55 @@ This guide walks you through the steps about installing and configuring Harbor o
|
||||
|
||||
![ova](img/ova/ova03.png)
|
||||
|
||||
5. Specify a name and a location for the virtual appliance.
|
||||
5. Accept the end user license agreements and click "Next".
|
||||
|
||||
![ova](img/ova/ova04.png)
|
||||
|
||||
6. Select the datastore and virtual disk format, click "Next".
|
||||
6. Specify a name and a location for the virtual appliance.
|
||||
|
||||
![ova](img/ova/ova05.png)
|
||||
|
||||
7. Configure the network(s) the virtual appliance should be connected to.
|
||||
7. Select the datastore and virtual disk format, click "Next".
|
||||
|
||||
![ova](img/ova/ova06.png)
|
||||
|
||||
8. Customize the properties of Harbor. The properties are described below. Note that at the very least, you just need to set the **Root Password**, **Harbor Admin Password** and **Database Password** properties.
|
||||
8. Configure the network(s) the virtual appliance should be connected to.
|
||||
|
||||
![ova](img/ova/ova07.png)
|
||||
|
||||
* Harbor
|
||||
* **Root Password**: The password of the root user.
|
||||
9. Customize the properties of Harbor. The properties are described below. Note that at the very least, you just need to set the **Root Password**, **Harbor Admin Password** and **Database Password** properties.
|
||||
|
||||
![ova](img/ova/ova08.png)
|
||||
|
||||
* System
|
||||
* **Root Password**: The initial password of the root user. Subsequent changes of password should be performed in operating system. (8-128 characters)
|
||||
* **Harbor Admin Password**: The initial password of Harbor admin. It only works for the first time when Harbor starts. It has no effect after the first launch of Harbor. Change the admin password from UI after launching Harbor.
|
||||
* **Database Password**: The password of the root user of MySQL database.
|
||||
* **Authentication Mode**: The default authentication mode is db_auth, i.e. the credentials are stored in a local database. Set it to ldap_auth if you want to verify the user's credential against an LDAP/AD server.
|
||||
* **Database Password**: The initial password of the root user of MySQL database. Subsequent changes of password should be performed in operating system. (8-128 characters)
|
||||
* **Permit Root Login**: Specifies whether root use can log in using SSH.
|
||||
* **Self Registration**: 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.
|
||||
* **Garbage Collection**: When setting this to true, Harbor performs garbage collection everytime it boots up. The first time setting this flag to true needs to power off the VM and power it on again.
|
||||
|
||||
* Authentication
|
||||
* **Authentication Mode**: 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.
|
||||
* **LDAP URL**: The URL of an LDAP/AD server.
|
||||
* **LDAP Search DN**: A user's DN who has the permission to search the LDAP/AD server. If your LDAP/AD server does not support anonymous search, you should configure this DN and LDAP Seach Password.
|
||||
* **LDAP Search Password**: The password of the user for LDAP search.
|
||||
* **LDAP Base DN**: The base DN from which to look up a user in LDAP/AD.
|
||||
* **LDAP UID**: 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.
|
||||
|
||||
* Security
|
||||
* **Protocol**: The protocol for accessing Harbor. Warning: setting it to http makes the communication insecure.
|
||||
* **SSL Cert**: Paste in the content of a certificate file. Leave blank for a generated self-signed certificate.
|
||||
* **SSL Cert Key**: Paste in the content of certificate key file. Leave blank for a generated key.
|
||||
* **Verify Remote Cert**: Determine whether the image replication should verify the certificate when it connects to a remote registry via TLS. Set this flag to off when the remote registry uses a self-signed or untrusted certificate.
|
||||
|
||||
* Email Settings
|
||||
* **Email Server**: The mail server to send out emails to reset password.
|
||||
* **Email Server Port**: The port of mail server.
|
||||
* **Email Username**: The user from whom the password reset email is sent.
|
||||
* **Email Password**: The password of the user from whom the password reset email is sent.
|
||||
* **Email From**: The name of the email sender.
|
||||
* **Email SSL**: Whether to enabled secure mail transmission.
|
||||
* **SSL Cert**: Paste in the content of a certificate file. If SSL Cert and SSL Cert Key are both set, HTTPS will be used.
|
||||
* **SSL Cert Key**: Paste in the content of certificate key file. If SSL Cert and SSL Cert Key are both set, HTTPS will be used.
|
||||
* **Self Registration**: 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.
|
||||
* **Verify Remote Cert**: Determine whether the image replication should verify the certificate when it connects to a remote registry via TLS. Set this flag to off when the remote registry uses a self-signed or untrusted certificate.
|
||||
* **Garbage Collection**: When setting this to true, Harbor performs garbage collection everytime it boots up. The first time setting this flag to true needs to power off the VM and power it on again.
|
||||
|
||||
* Networking properties
|
||||
* **Default Gateway**: The default gateway address for this VM. Leave blank if DHCP is desired.
|
||||
@ -68,19 +80,19 @@ This guide walks you through the steps about installing and configuring Harbor o
|
||||
* **Network 1 IP Adress**: The IP address of this interface. Leave blank if DHCP is desired.
|
||||
* **Network 1 Netmask**: The netmask or prefix for this interface. Leave blank if DHCP is desired.
|
||||
|
||||
**Notes:** If you want to enable HTTPS with a self-signed certificate, refer to the "Getting a certificate" part of this [guide](https://github.com/vmware/harbor/blob/master/docs/configure_https.md#getting-a-certificate) for generating a certificate.
|
||||
**Notes:** If you want to enable HTTPS with a self-signed certificate created manually, refer to the "Getting a certificate" part of this [guide](https://github.com/vmware/harbor/blob/master/docs/configure_https.md#getting-a-certificate) for generating a certificate.
|
||||
|
||||
After you complete the properties, click "Next".
|
||||
|
||||
9. Review your settings and click "Finish" to complete the deployment.
|
||||
10. Review your settings and click "Finish" to complete the deployment.
|
||||
|
||||
![ova](img/ova/ova08.png)
|
||||
![ova](img/ova/ova09.png)
|
||||
|
||||
10. Power on the virtual appliance. It may take a few minutes for the first bootup. The virtual appliance needs to initialize itself for configuration like netowrk address and password.
|
||||
11. Power on the virtual appliance. It may take a few minutes for the first bootup. The virtual appliance needs to initialize itself for configuration like netowrk address and password.
|
||||
|
||||
11. When the appliance is ready, check from vSphere Web Client for its IP address. Open a browser and type in the URL `http(s)://harbor_ip_address` or `http(s)://harbor_host_name`. Log in as the admin user and verify Harbor has been successfully installed.
|
||||
12. When the appliance is ready, check from vSphere Web Client for its IP address. Open a browser and type in the URL `http(s)://harbor_ip_address` or `http(s)://harbor_host_name`. Log in as the admin user and verify Harbor has been successfully installed.
|
||||
|
||||
12. For information on how to use Harbor, please refer to [User Guide of Harbor](user_guide.md).
|
||||
13. For information on how to use Harbor, please refer to [User Guide of Harbor](user_guide.md).
|
||||
|
||||
## Reconfiguration
|
||||
If you want to change the properties of Harbor, follow the below steps:
|
||||
@ -96,4 +108,10 @@ If you want to change the properties of Harbor, follow the below steps:
|
||||
|
||||
4. **Power on** the VM.
|
||||
|
||||
**Note:** The initial admin password, root password of the virtual appliance, MySql root password, and all networking properties can not be modified using this method after Harbor's first launch. The password of the admin user should be changed in the admin portal. The root password of virtual appliance, as well as the networking settings, can be changed by logging in the virtural appliance and doing it in the Linux operating system.
|
||||
**Notes:**
|
||||
1. The authentication mode can only be set once on firtst boot. So subsequent modification of this option will have no effect.
|
||||
2. The initial admin password, root password of the virtual appliance, MySQL root password, and all networking properties can not be modified using this method after Harbor's first launch. Modify them by the following steps:
|
||||
* Harbor Admin Password: Change it in Harbor admin portal.
|
||||
* Root Password of Virtual Appliance: Change it by logging in the virtual appliance and doing it in the Linux operating system.
|
||||
* MySQL Root Password: Change it by logging in the virtual appliance and doing it in the Linux operating system.
|
||||
* Networking Properties: Visit `https://harbor_ip_address:5480`, login with root/password of your virtual appliance and modify networking properties. Reboot the system after you changing them.
|
@ -27,35 +27,30 @@ When upgrading your existing Habor instance to a newer version, you may need to
|
||||
git clone https://github.com/vmware/harbor
|
||||
```
|
||||
|
||||
4. Before upgrading Harbor, perform database migration first.
|
||||
The directory **tools/migration/** contains the tool for migration. The first step is to update values of `db_username`, `db_password`, `db_port`, `db_name` in **migration.cfg** so that they match your system's configuration.
|
||||
|
||||
5. The migration tool is delivered as a container, so you should build the image from its Dockerfile:
|
||||
```
|
||||
cd tools/migration/
|
||||
|
||||
docker build -t migrate-tool .
|
||||
```
|
||||
|
||||
6. Back up database to a directory such as `/path/to/backup`. You need to create the directory if it does not exist.
|
||||
4. Before upgrading Harbor, perform database migration first. The migration tool is delivered as a docker image, so you should pull the image from docker hub:
|
||||
|
||||
```
|
||||
docker run -ti --rm -v /data/database:/var/lib/mysql -v /path/to/backup:/harbor-migration/backup migrate-tool backup
|
||||
docker pull vmware/harbor-db-migrator
|
||||
```
|
||||
|
||||
7. Upgrade database schema and migrate data:
|
||||
5. Back up database to a directory such as `/path/to/backup`. You need to create the directory if it does not exist. Also, note that the username and password to access the db are provided via environment variable "DB_USR" and "DB_PWD"
|
||||
|
||||
```
|
||||
docker run -ti --rm -v /data/database:/var/lib/mysql migrate-tool up head
|
||||
docker run -ti --rm -e DB_USR=root -e DB_PWD=xxxx -v /data/database:/var/lib/mysql -v /path/to/backup:/harbor-migration/backup vmware/harbor-db-migrator backup
|
||||
```
|
||||
|
||||
8. Change to `make/` directory, configure Harbor by modifying the file `harbor.cfg`, you may need to refer to the configuration files you've backed up during step 2. Refer to [Installation & Configuration Guide ](../docs/installation_guide.md) for more info.
|
||||
6. Upgrade database schema and migrate data:
|
||||
|
||||
9. If HTTPS has been enabled for Harbor before, restore the `nginx.conf` and key/certificate files from the backup files in Step 2. Refer to [Configuring Harbor with HTTPS Access](../docs/configure_https.md) for more info.
|
||||
```
|
||||
docker run -ti --rm -e DB_USR=root -e DB_PWD=xxxx -v /data/database:/var/lib/mysql vmware/harbor-db-migrator up head
|
||||
```
|
||||
|
||||
10. Under the directory `make/`, run the `./prepare` script to generate necessary config files.
|
||||
7. Change to `make/` directory, configure Harbor by modifying the file `harbor.cfg`, you may need to refer to the configuration files you've backed up during step 2. Refer to [Installation & Configuration Guide ](../docs/installation_guide.md) for more info.
|
||||
|
||||
|
||||
8. Under the directory `make/`, run the `./prepare` script to generate necessary config files.
|
||||
|
||||
11. Rebuild Harbor and restart the registry service
|
||||
9. Rebuild Harbor and restart the registry service
|
||||
|
||||
```
|
||||
docker-compose up --build -d
|
||||
@ -73,7 +68,7 @@ For any reason, if you want to roll back to the previous version of Harbor, foll
|
||||
2. Restore database from backup file in `/path/to/backup` .
|
||||
|
||||
```
|
||||
docker run -ti --rm -v /data/database:/var/lib/mysql -v /path/to/backup:/harbor-migration/backup migrate-tool restore
|
||||
docker run -ti --rm -e DB_USR=root -e DB_PWD=xxxx -v /data/database:/var/lib/mysql -v /path/to/backup:/harbor-migration/backup vmware/harbor-db-migrator restore
|
||||
```
|
||||
|
||||
3. Remove current source code of Harbor.
|
||||
@ -95,9 +90,9 @@ For any reason, if you want to roll back to the previous version of Harbor, foll
|
||||
### Migration tool reference
|
||||
- Use `help` command to show instructions of the migration tool:
|
||||
|
||||
```docker run --rm migrate-tool help```
|
||||
```docker run --rm -e DB_USR=root -e DB_PWD=xxxx vmware/harbor-db-migrator help```
|
||||
|
||||
- Use `test` command to test mysql connection:
|
||||
|
||||
```docker run --rm -v /data/database:/var/lib/mysql migrate-tool test```
|
||||
```docker run --rm -e DB_USR=root -e DB_PWD=xxxx -v /data/database:/var/lib/mysql vmware/harbor-db-migrator test```
|
||||
|
||||
|
@ -507,6 +507,21 @@ paths:
|
||||
description: User registration can only be used by admin role user when self-registration is off.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/users/current:
|
||||
get:
|
||||
summary: Get current user info.
|
||||
description: |
|
||||
This endpoint is to get the current user infomation.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Get current user information successfully.
|
||||
in: body
|
||||
schema:
|
||||
$ref: '#/definitions/User'
|
||||
401:
|
||||
description: User need to log in first.
|
||||
/users/{user_id}:
|
||||
put:
|
||||
summary: Update a registered user to change his profile.
|
||||
|
@ -1,75 +0,0 @@
|
||||
worker_processes auto;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
use epoll;
|
||||
multi_accept on;
|
||||
}
|
||||
|
||||
http {
|
||||
tcp_nodelay on;
|
||||
|
||||
# this is necessary for us to be able to disable request buffering in all cases
|
||||
proxy_http_version 1.1;
|
||||
|
||||
|
||||
upstream registry {
|
||||
server registry:5000;
|
||||
}
|
||||
|
||||
upstream ui {
|
||||
server ui:80;
|
||||
}
|
||||
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
# disable any limits to avoid HTTP 413 for large image uploads
|
||||
client_max_body_size 0;
|
||||
|
||||
location / {
|
||||
proxy_pass http://ui/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# When setting up Harbor behind other proxy, such as an Nginx instance, remove the below line if the proxy already has similar settings.
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
|
||||
location /v1/ {
|
||||
return 404;
|
||||
}
|
||||
|
||||
location /v2/ {
|
||||
proxy_pass http://registry/v2/;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# When setting up Harbor behind other proxy, such as an Nginx instance, remove the below line if the proxy already has similar settings.
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
|
||||
}
|
||||
|
||||
location /service/ {
|
||||
proxy_pass http://ui/service/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# When setting up Harbor behind other proxy, such as an Nginx instance, remove the below line if the proxy already has similar settings.
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
}
|
||||
}
|
@ -12,4 +12,4 @@ LOG_LEVEL=debug
|
||||
LOG_DIR=/var/log/jobs
|
||||
GODEBUG=netdns=cgo
|
||||
EXT_ENDPOINT=$ui_url
|
||||
TOKEN_URL=http://ui
|
||||
TOKEN_ENDPOINT=http://ui
|
||||
|
@ -51,6 +51,9 @@ http {
|
||||
# When setting up Harbor behind other proxy, such as an Nginx instance, remove the below line if the proxy already has similar settings.
|
||||
proxy_set_header X-Forwarded-Proto $$scheme;
|
||||
|
||||
# Add Secure flag when serving HTTPS
|
||||
proxy_cookie_path / "/; secure";
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ names = en-US|zh-CN
|
||||
httpport = 80
|
||||
|
||||
[mail]
|
||||
identity = $email_identity
|
||||
host = $email_server
|
||||
port = $email_server_port
|
||||
username = $email_username
|
||||
|
@ -3,11 +3,11 @@ 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
|
||||
HARBOR_REG_URL=$hostname
|
||||
EXT_REG_URL=$hostname
|
||||
HARBOR_ADMIN_PASSWORD=$harbor_admin_password
|
||||
HARBOR_URL=$ui_url
|
||||
AUTH_MODE=$auth_mode
|
||||
LDAP_URL=$ldap_url
|
||||
LDAP_SEARCH_DN=$ldap_searchdn
|
||||
@ -23,6 +23,7 @@ USE_COMPRESSED_JS=$use_compressed_js
|
||||
LOG_LEVEL=debug
|
||||
GODEBUG=netdns=cgo
|
||||
EXT_ENDPOINT=$ui_url
|
||||
TOKEN_URL=http://ui
|
||||
TOKEN_ENDPOINT=http://ui
|
||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||
TOKEN_EXPIRATION=$token_expiration
|
||||
PROJECT_CREATION_RESTRICTION=$project_creation_restriction
|
||||
|
@ -23,7 +23,9 @@ COPY make/jsminify.sh /tmp/jsminify.sh
|
||||
RUN chmod u+x /go/bin/harbor_ui \
|
||||
&& sed -i 's/TLS_CACERT/#TLS_CAERT/g' /etc/ldap/ldap.conf \
|
||||
&& sed -i '$a\TLS_REQCERT allow' /etc/ldap/ldap.conf \
|
||||
&& /tmp/jsminify.sh /go/bin/views/sections/script-include.htm /go/bin/static/resources/js/harbor.app.min.js /go/bin/
|
||||
&& timestamp=`date '+%s'` \
|
||||
&& /tmp/jsminify.sh /go/bin/views/sections/script-include.htm /go/bin/static/resources/js/harbor.app.min.$timestamp.js /go/bin/ \
|
||||
&& sed -i "s/harbor\.app\.min\.js/harbor\.app\.min\.$timestamp\.js/g" /go/bin/views/sections/script-min-include.htm
|
||||
|
||||
WORKDIR /go/bin/
|
||||
ENTRYPOINT ["/go/bin/harbor_ui"]
|
||||
|
@ -50,6 +50,7 @@ services:
|
||||
volumes:
|
||||
- ./common/config/ui/app.conf:/etc/ui/app.conf
|
||||
- ./common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
||||
- /data:/harbor_storage
|
||||
depends_on:
|
||||
- log
|
||||
logging:
|
||||
|
@ -9,6 +9,7 @@ hostname = reg.mydomain.com
|
||||
ui_url_protocol = http
|
||||
|
||||
#Email account settings for sending out password resetting emails.
|
||||
email_identity = Mail Config
|
||||
email_server = smtp.mydomain.com
|
||||
email_server_port = 25
|
||||
email_username = sample_admin@mydomain.com
|
||||
@ -82,8 +83,11 @@ crt_organizationalunit = organizational unit
|
||||
crt_commonname = example.com
|
||||
crt_email = example@example.com
|
||||
|
||||
#The flag to control what users have permission to create projects
|
||||
#Be default everyone can create a project, set to "adminonly" such that only admin can create project.
|
||||
project_creation_restriction = everyone
|
||||
|
||||
#The path of cert and key files for nginx, they are applied only the protocol is set to https
|
||||
ssl_cert = /path/to/server.crt
|
||||
ssl_cert_key = /path/to/server.key
|
||||
ssl_cert = /data/cert/server.crt
|
||||
ssl_cert_key = /data/cert/server.key
|
||||
#############
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM library/photon:latest
|
||||
FROM library/photon:1.0
|
||||
|
||||
RUN mkdir /harbor/
|
||||
COPY ./make/dev/jobservice/harbor_jobservice /harbor/
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM library/photon:latest
|
||||
FROM library/photon:1.0
|
||||
|
||||
# run logrotate hourly, disable imklog model, provides TCP/UDP syslog reception
|
||||
RUN tdnf install -y cronie rsyslog shadow tar gzip \
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM library/photon:latest
|
||||
FROM library/photon:1.0
|
||||
|
||||
RUN mkdir /harbor/
|
||||
RUN tdnf install -y sed apr-util-ldap
|
||||
@ -11,7 +11,10 @@ COPY ./src/favicon.ico /harbor/favicon.ico
|
||||
COPY ./make/jsminify.sh /tmp/jsminify.sh
|
||||
|
||||
RUN chmod u+x /harbor/harbor_ui \
|
||||
&& tmp/jsminify.sh /harbor/views/sections/script-include.htm /harbor/static/resources/js/harbor.app.min.js /harbor/ \
|
||||
&& timestamp=`date '+%s'` \
|
||||
&& /tmp/jsminify.sh /harbor/views/sections/script-include.htm /harbor/static/resources/js/harbor.app.min.$timestamp.js /harbor/ \
|
||||
&& sed -i "s/harbor\.app\.min\.js/harbor\.app\.min\.$timestamp\.js/g" /harbor/views/sections/script-min-include.htm \
|
||||
&& echo "TLS_REQCERT allow" >> /etc/openldap/ldap.conf
|
||||
|
||||
WORKDIR /harbor/
|
||||
ENTRYPOINT ["/harbor/harbor_ui"]
|
||||
|
53
make/prepare
@ -32,6 +32,10 @@ def validate(conf):
|
||||
cert_key_path = rcp.get("configuration", "ssl_cert_key")
|
||||
if not os.path.isfile(cert_key_path):
|
||||
raise Exception("Error: The path for certificate key: %s is invalid" % cert_key_path)
|
||||
project_creation = rcp.get("configuration", "project_creation_restriction")
|
||||
|
||||
if project_creation != "everyone" and project_creation != "adminonly":
|
||||
raise Exception("Error invalid value for project_creation_restriction: %s" % project_creation)
|
||||
|
||||
def get_secret_key(path):
|
||||
key_file = os.path.join(path, "secretkey")
|
||||
@ -73,6 +77,7 @@ validate(rcp)
|
||||
hostname = rcp.get("configuration", "hostname")
|
||||
protocol = rcp.get("configuration", "ui_url_protocol")
|
||||
ui_url = protocol + "://" + hostname
|
||||
email_identity = rcp.get("configuration", "email_identity")
|
||||
email_server = rcp.get("configuration", "email_server")
|
||||
email_server_port = rcp.get("configuration", "email_server_port")
|
||||
email_username = rcp.get("configuration", "email_username")
|
||||
@ -114,6 +119,7 @@ crt_email = rcp.get("configuration", "crt_email")
|
||||
max_job_workers = rcp.get("configuration", "max_job_workers")
|
||||
token_expiration = rcp.get("configuration", "token_expiration")
|
||||
verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
|
||||
proj_cre_restriction = rcp.get("configuration", "project_creation_restriction")
|
||||
#secret_key = rcp.get("configuration", "secret_key")
|
||||
secret_key = get_secret_key(args.data_volume)
|
||||
########
|
||||
@ -132,6 +138,14 @@ job_config_dir = os.path.join(config_dir, "jobservice")
|
||||
if not os.path.exists(job_config_dir):
|
||||
os.makedirs(job_config_dir)
|
||||
|
||||
registry_config_dir = os.path.join(config_dir, "registry")
|
||||
if not os.path.exists(registry_config_dir):
|
||||
os.makedirs(registry_config_dir)
|
||||
|
||||
nginx_config_dir = os.path.join(config_dir, "nginx")
|
||||
if not os.path.exists(nginx_config_dir):
|
||||
os.makedirs(nginx_config_dir)
|
||||
|
||||
def render(src, dest, **kw):
|
||||
t = Template(open(src, 'r').read())
|
||||
with open(dest, 'w') as f:
|
||||
@ -140,23 +154,29 @@ def render(src, dest, **kw):
|
||||
|
||||
ui_conf_env = os.path.join(config_dir, "ui", "env")
|
||||
ui_conf = os.path.join(config_dir, "ui", "app.conf")
|
||||
jobservice_conf = os.path.join(config_dir, "jobservice", "app.conf")
|
||||
registry_conf = os.path.join(config_dir, "registry", "config.yml")
|
||||
db_conf_env = os.path.join(config_dir, "db", "env")
|
||||
job_conf_env = os.path.join(config_dir, "jobservice", "env")
|
||||
nginx_conf = os.path.join(config_dir, "nginx", "nginx.conf")
|
||||
cert_dir = os.path.join(config_dir, "nginx", "cert")
|
||||
conf_files = [ ui_conf, ui_conf_env, registry_conf, db_conf_env, job_conf_env, nginx_conf, cert_dir ]
|
||||
def rmdir(cf):
|
||||
for f in cf:
|
||||
if os.path.isdir(f):
|
||||
rmdir(map(lambda x: os.path.join(f,x), os.listdir(f)))
|
||||
elif os.path.exists(f) and os.path.basename(f) != ".gitignore":
|
||||
print("Clearing the configuration file: %s" % f)
|
||||
os.remove(f)
|
||||
rmdir(conf_files)
|
||||
def delfile(src):
|
||||
if os.path.isfile(src):
|
||||
try:
|
||||
os.remove(src)
|
||||
print("Clearing the configuration file: %s" % src)
|
||||
except:
|
||||
pass
|
||||
elif os.path.isdir(src):
|
||||
for item in os.listdir(src):
|
||||
itemsrc=os.path.join(src,item)
|
||||
delfile(itemsrc)
|
||||
delfile(config_dir)
|
||||
|
||||
if protocol == "https":
|
||||
target_cert_path = os.path.join(cert_dir, os.path.basename(cert_path))
|
||||
if not os.path.exists(cert_dir):
|
||||
os.makedirs(cert_dir)
|
||||
shutil.copy2(cert_path,target_cert_path)
|
||||
target_cert_key_path = os.path.join(cert_dir, os.path.basename(cert_key_path))
|
||||
shutil.copy2(cert_key_path,target_cert_key_path)
|
||||
@ -187,10 +207,12 @@ render(os.path.join(templates_dir, "ui", "env"),
|
||||
ui_secret=ui_secret,
|
||||
secret_key=secret_key,
|
||||
verify_remote_cert=verify_remote_cert,
|
||||
project_creation_restriction=proj_cre_restriction,
|
||||
token_expiration=token_expiration)
|
||||
|
||||
render(os.path.join(templates_dir, "ui", "app.conf"),
|
||||
ui_conf,
|
||||
email_identity=email_identity,
|
||||
email_server=email_server,
|
||||
email_server_port=email_server_port,
|
||||
email_username=email_username,
|
||||
@ -215,6 +237,9 @@ render(os.path.join(templates_dir, "jobservice", "env"),
|
||||
secret_key=secret_key,
|
||||
ui_url=ui_url,
|
||||
verify_remote_cert=verify_remote_cert)
|
||||
|
||||
print("Generated configuration file: %s" % jobservice_conf)
|
||||
shutil.copyfile(os.path.join(templates_dir, "jobservice", "app.conf"), jobservice_conf)
|
||||
|
||||
def validate_crt_subj(dirty_subj):
|
||||
subj_list = [item for item in dirty_subj.strip().split("/") \
|
||||
@ -262,11 +287,15 @@ if customize_crt == 'on':
|
||||
if openssl_is_installed(shell_stat):
|
||||
private_key_pem = os.path.join(config_dir, "ui", "private_key.pem")
|
||||
root_crt = os.path.join(config_dir, "registry", "root.crt")
|
||||
crt_conf_files = [ private_key_pem, root_crt ]
|
||||
rmdir(crt_conf_files)
|
||||
|
||||
check_private_key_stat(path=private_key_pem)
|
||||
check_certificate_stat(path=root_crt)
|
||||
|
||||
|
||||
else:
|
||||
print("Generated configuration file: %s" % ui_config_dir + "private_key.pem")
|
||||
shutil.copyfile(os.path.join(templates_dir, "ui", "private_key.pem"), os.path.join(ui_config_dir, "private_key.pem"))
|
||||
print("Generated configuration file: %s" % registry_config_dir + "root.crt")
|
||||
shutil.copyfile(os.path.join(templates_dir, "registry", "root.crt"), os.path.join(registry_config_dir, "root.crt"))
|
||||
|
||||
FNULL.close()
|
||||
print("The configuration files are ready, please use docker-compose to start the service.")
|
||||
|
@ -20,7 +20,9 @@ COPY ./make/jsminify.sh /tmp/jsminify.sh
|
||||
RUN chmod u+x /harbor/harbor_ui \
|
||||
&& sed -i 's/TLS_CACERT/#TLS_CAERT/g' /etc/ldap/ldap.conf \
|
||||
&& sed -i '$a\TLS_REQCERT allow' /etc/ldap/ldap.conf \
|
||||
&& /tmp/jsminify.sh /harbor/views/sections/script-include.htm /harbor/static/resources/js/harbor.app.min.js /harbor/
|
||||
&& 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"]
|
||||
|
@ -19,14 +19,14 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/astaxie/beego/validation"
|
||||
"github.com/vmware/harbor/src/ui/auth"
|
||||
"github.com/vmware/harbor/src/common/config"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/auth"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
@ -213,12 +213,5 @@ func (b *BaseAPI) GetPaginationParams() (page, pageSize int64) {
|
||||
|
||||
// GetIsInsecure ...
|
||||
func GetIsInsecure() bool {
|
||||
insecure := false
|
||||
|
||||
verifyRemoteCert := os.Getenv("VERIFY_REMOTE_CERT")
|
||||
if verifyRemoteCert == "off" {
|
||||
insecure = true
|
||||
}
|
||||
|
||||
return insecure
|
||||
return !config.VerifyRemoteCert()
|
||||
}
|
||||
|
33
src/common/api/base_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 (
|
||||
"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")
|
||||
}
|
178
src/common/config/config.go
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
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 provide methods to get the configurations reqruied by code in src/common
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ConfLoader is the interface to load configurations
|
||||
type ConfLoader interface {
|
||||
// Load will load configuration from different source into a string map, the values in the map will be parsed in to configurations.
|
||||
Load() (map[string]string, error)
|
||||
}
|
||||
|
||||
// EnvConfigLoader loads the config from env vars.
|
||||
type EnvConfigLoader struct {
|
||||
Keys []string
|
||||
}
|
||||
|
||||
// Load ...
|
||||
func (ec *EnvConfigLoader) Load() (map[string]string, error) {
|
||||
m := make(map[string]string)
|
||||
for _, k := range ec.Keys {
|
||||
m[k] = os.Getenv(k)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ConfParser ...
|
||||
type ConfParser interface {
|
||||
|
||||
//Parse parse the input raw map into a config map
|
||||
Parse(raw map[string]string, config map[string]interface{}) error
|
||||
}
|
||||
|
||||
// Config wraps a map for the processed configuration values,
|
||||
// and loader parser to read configuration from external source and process the values.
|
||||
type Config struct {
|
||||
Config map[string]interface{}
|
||||
Loader ConfLoader
|
||||
Parser ConfParser
|
||||
}
|
||||
|
||||
// Load reload the configurations
|
||||
func (conf *Config) Load() error {
|
||||
rawMap, err := conf.Loader.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = conf.Parser.Parse(rawMap, conf.Config)
|
||||
return err
|
||||
}
|
||||
|
||||
// MySQLSetting wraps the settings of a MySQL DB
|
||||
type MySQLSetting struct {
|
||||
Database string
|
||||
User string
|
||||
Password string
|
||||
Host string
|
||||
Port string
|
||||
}
|
||||
|
||||
// SQLiteSetting wraps the settings of a SQLite DB
|
||||
type SQLiteSetting struct {
|
||||
FilePath string
|
||||
}
|
||||
|
||||
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
|
||||
config["verify_remote_cert"] = raw["VERIFY_REMOTE_CERT"] != "off"
|
||||
|
||||
config["ext_endpoint"] = raw["EXT_ENDPOINT"]
|
||||
config["token_endpoint"] = raw["TOKEN_ENDPOINT"]
|
||||
config["log_level"] = raw["LOG_LEVEL"]
|
||||
return nil
|
||||
}
|
||||
|
||||
var commonConfig *Config
|
||||
|
||||
func init() {
|
||||
commonKeys := []string{"DATABASE", "MYSQL_DATABASE", "MYSQL_USR", "MYSQL_PWD", "MYSQL_HOST", "MYSQL_PORT", "SQLITE_FILE", "VERIFY_REMOTE_CERT", "EXT_ENDPOINT", "TOKEN_ENDPOINT", "LOG_LEVEL"}
|
||||
commonConfig = &Config{
|
||||
Config: make(map[string]interface{}),
|
||||
Loader: &EnvConfigLoader{Keys: commonKeys},
|
||||
Parser: &commonParser{},
|
||||
}
|
||||
if err := commonConfig.Load(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
111
src/common/config/config_test.go
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
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 (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnvConfLoader(t *testing.T) {
|
||||
os.Unsetenv("KEY2")
|
||||
os.Setenv("KEY1", "V1")
|
||||
os.Setenv("KEY3", "V3")
|
||||
keys := []string{"KEY1", "KEY2"}
|
||||
ecl := EnvConfigLoader{
|
||||
keys,
|
||||
}
|
||||
m, err := ecl.Load()
|
||||
if err != nil {
|
||||
t.Errorf("Error loading the configuration via env: %v", err)
|
||||
}
|
||||
if m["KEY1"] != "V1" {
|
||||
t.Errorf("The value for key KEY1 should be V1, but infact: %s", m["KEY1"])
|
||||
}
|
||||
if len(m["KEY2"]) > 0 {
|
||||
t.Errorf("The value for key KEY2 should be emptye, but infact: %s", m["KEY2"])
|
||||
}
|
||||
if _, ok := m["KEY3"]; ok {
|
||||
t.Errorf("The KEY3 should not be in result as it's not in the initial key list")
|
||||
}
|
||||
os.Unsetenv("KEY1")
|
||||
os.Unsetenv("KEY3")
|
||||
}
|
||||
|
||||
func TestCommonConfig(t *testing.T) {
|
||||
|
||||
mysql := MySQLSetting{"registry", "root", "password", "127.0.0.1", "3306"}
|
||||
sqlite := SQLiteSetting{"file.db"}
|
||||
verify := "off"
|
||||
ext := "http://harbor"
|
||||
token := "http://token"
|
||||
loglevel := "info"
|
||||
|
||||
os.Setenv("DATABASE", "")
|
||||
os.Setenv("MYSQL_DATABASE", mysql.Database)
|
||||
os.Setenv("MYSQL_USR", mysql.User)
|
||||
os.Setenv("MYSQL_PWD", mysql.Password)
|
||||
os.Setenv("MYSQL_HOST", mysql.Host)
|
||||
os.Setenv("MYSQL_PORT", mysql.Port)
|
||||
os.Setenv("SQLITE_FILE", sqlite.FilePath)
|
||||
os.Setenv("VERIFY_REMOTE_CERT", verify)
|
||||
os.Setenv("EXT_ENDPOINT", ext)
|
||||
os.Setenv("TOKEN_ENDPOINT", token)
|
||||
os.Setenv("LOG_LEVEL", loglevel)
|
||||
|
||||
err := Reload()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when loading the configurations, error: %v", err)
|
||||
}
|
||||
if Database() != "mysql" {
|
||||
t.Errorf("Expected Database value: mysql, fact: %s", mysql)
|
||||
}
|
||||
if MySQL() != mysql {
|
||||
t.Errorf("Expected MySQL setting: %+v, fact: %+v", mysql, MySQL())
|
||||
}
|
||||
if VerifyRemoteCert() {
|
||||
t.Errorf("Expected VerifyRemoteCert: false, env var: %s, fact: %v", verify, VerifyRemoteCert())
|
||||
}
|
||||
if ExtEndpoint() != ext {
|
||||
t.Errorf("Expected ExtEndpoint: %s, fact: %s", ext, ExtEndpoint())
|
||||
}
|
||||
if TokenEndpoint() != token {
|
||||
t.Errorf("Expected TokenEndpoint: %s, fact: %s", token, TokenEndpoint())
|
||||
}
|
||||
if LogLevel() != loglevel {
|
||||
t.Errorf("Expected LogLevel: %s, fact: %s", loglevel, LogLevel())
|
||||
}
|
||||
os.Setenv("DATABASE", "sqlite")
|
||||
err = Reload()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when loading the configurations, error: %v", err)
|
||||
}
|
||||
if SQLite() != sqlite {
|
||||
t.Errorf("Expected SQLite setting: %+v, fact %+v", sqlite, SQLite())
|
||||
}
|
||||
|
||||
os.Unsetenv("DATABASE")
|
||||
os.Unsetenv("MYSQL_DATABASE")
|
||||
os.Unsetenv("MYSQL_USR")
|
||||
os.Unsetenv("MYSQL_PWD")
|
||||
os.Unsetenv("MYSQL_HOST")
|
||||
os.Unsetenv("MYSQL_PORT")
|
||||
os.Unsetenv("SQLITE_FILE")
|
||||
os.Unsetenv("VERIFY_REMOTE_CERT")
|
||||
os.Unsetenv("EXT_ENDPOINT")
|
||||
os.Unsetenv("TOKEN_ENDPOINT")
|
||||
os.Unsetenv("LOG_LEVEL")
|
||||
|
||||
}
|
@ -55,7 +55,7 @@ func GetTotalOfAccessLogs(query models.AccessLog) (int64, error) {
|
||||
left join user u
|
||||
on al.user_id = u.user_id
|
||||
where al.project_id = ? and u.username like ? `
|
||||
queryParam = append(queryParam, "%"+query.Username+"%")
|
||||
queryParam = append(queryParam, "%"+escape(query.Username)+"%")
|
||||
}
|
||||
|
||||
sql += genFilterClauses(query, &queryParam)
|
||||
@ -82,7 +82,7 @@ func GetAccessLogs(query models.AccessLog, limit, offset int64) ([]models.Access
|
||||
|
||||
if query.Username != "" {
|
||||
sql += ` and u.username like ? `
|
||||
queryParam = append(queryParam, "%"+query.Username+"%")
|
||||
queryParam = append(queryParam, "%"+escape(query.Username)+"%")
|
||||
}
|
||||
|
||||
sql += genFilterClauses(query, &queryParam)
|
||||
|
@ -17,11 +17,11 @@ package dao
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/vmware/harbor/src/common/config"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
@ -52,42 +52,18 @@ func InitDatabase() {
|
||||
}
|
||||
|
||||
func getDatabase() (db Database, err error) {
|
||||
switch strings.ToLower(os.Getenv("DATABASE")) {
|
||||
switch config.Database() {
|
||||
case "", "mysql":
|
||||
host, port, usr, pwd, database := getMySQLConnInfo()
|
||||
db = NewMySQL(host, port, usr, pwd, database)
|
||||
db = NewMySQL(config.MySQL().Host, config.MySQL().Port, config.MySQL().User,
|
||||
config.MySQL().Password, config.MySQL().Database)
|
||||
case "sqlite":
|
||||
file := getSQLiteConnInfo()
|
||||
db = NewSQLite(file)
|
||||
db = NewSQLite(config.SQLite().FilePath)
|
||||
default:
|
||||
err = fmt.Errorf("invalid database: %s", os.Getenv("DATABASE"))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO read from config
|
||||
func getMySQLConnInfo() (host, port, username, password, database string) {
|
||||
host = os.Getenv("MYSQL_HOST")
|
||||
port = os.Getenv("MYSQL_PORT")
|
||||
username = os.Getenv("MYSQL_USR")
|
||||
password = os.Getenv("MYSQL_PWD")
|
||||
database = os.Getenv("MYSQL_DATABASE")
|
||||
if len(database) == 0 {
|
||||
database = "registry"
|
||||
err = fmt.Errorf("invalid database: %s", config.Database())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TODO read from config
|
||||
func getSQLiteConnInfo() string {
|
||||
file := os.Getenv("SQLITE_FILE")
|
||||
if len(file) == 0 {
|
||||
file = "registry.db"
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
var globalOrm orm.Ormer
|
||||
var once sync.Once
|
||||
|
||||
@ -102,3 +78,9 @@ func GetOrmer() orm.Ormer {
|
||||
func paginateForRawSQL(sql string, limit, offset int64) string {
|
||||
return fmt.Sprintf("%s limit %d offset %d", sql, limit, offset)
|
||||
}
|
||||
|
||||
func escape(str string) string {
|
||||
str = strings.Replace(str, `%`, `\%`, -1)
|
||||
str = strings.Replace(str, `_`, `\_`, -1)
|
||||
return str
|
||||
}
|
||||
|
@ -705,7 +705,7 @@ func TestGetProjectById(t *testing.T) {
|
||||
func TestGetUserByProject(t *testing.T) {
|
||||
pid := currentProject.ProjectID
|
||||
u1 := models.User{
|
||||
Username: "%%Tester%%",
|
||||
Username: "Tester",
|
||||
}
|
||||
u2 := models.User{
|
||||
Username: "nononono",
|
||||
|
@ -195,7 +195,7 @@ func GetTotalOfUserRelevantProjects(userID int, projectName string) (int64, erro
|
||||
queryParam = append(queryParam, userID)
|
||||
if projectName != "" {
|
||||
sql += " and p.name like ? "
|
||||
queryParam = append(queryParam, "%"+projectName+"%")
|
||||
queryParam = append(queryParam, "%"+escape(projectName)+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
@ -254,7 +254,7 @@ func getProjects(userID int, name string, args ...int64) ([]models.Project, erro
|
||||
|
||||
if name != "" {
|
||||
sql += ` and p.name like ? `
|
||||
queryParam = append(queryParam, "%"+name+"%")
|
||||
queryParam = append(queryParam, "%"+escape(name)+"%")
|
||||
}
|
||||
|
||||
switch len(args) {
|
||||
|
@ -71,7 +71,7 @@ func GetUserByProject(projectID int64, queryUser models.User) ([]models.User, er
|
||||
|
||||
if queryUser.Username != "" {
|
||||
sql += " and u.username like ? "
|
||||
queryParam = append(queryParam, queryUser.Username)
|
||||
queryParam = append(queryParam, "%"+escape(queryUser.Username)+"%")
|
||||
}
|
||||
sql += ` order by u.user_id `
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&u)
|
||||
|
@ -52,7 +52,7 @@ func Register(user models.User) (int64, error) {
|
||||
func UserExists(user models.User, target string) (bool, error) {
|
||||
|
||||
if user.Username == "" && user.Email == "" {
|
||||
return false, errors.New("User name and email are blank.")
|
||||
return false, errors.New("user name and email are blank")
|
||||
}
|
||||
|
||||
o := GetOrmer()
|
||||
|
@ -90,7 +90,7 @@ func FilterRepTargets(name string) ([]*models.RepTarget, error) {
|
||||
sql := `select * from replication_target `
|
||||
if len(name) != 0 {
|
||||
sql += `where name like ? `
|
||||
args = append(args, "%"+name+"%")
|
||||
args = append(args, "%"+escape(name)+"%")
|
||||
}
|
||||
sql += `order by creation_time`
|
||||
|
||||
@ -166,11 +166,11 @@ func FilterRepPolicies(name string, projectID int64) ([]*models.RepPolicy, error
|
||||
|
||||
if len(name) != 0 && projectID != 0 {
|
||||
sql += `and rp.name like ? and rp.project_id = ? `
|
||||
args = append(args, "%"+name+"%")
|
||||
args = append(args, "%"+escape(name)+"%")
|
||||
args = append(args, projectID)
|
||||
} else if len(name) != 0 {
|
||||
sql += `and rp.name like ? `
|
||||
args = append(args, "%"+name+"%")
|
||||
args = append(args, "%"+escape(name)+"%")
|
||||
} else if projectID != 0 {
|
||||
sql += `and rp.project_id = ? `
|
||||
args = append(args, projectID)
|
||||
|
@ -138,7 +138,7 @@ func GetTotalOfPublicRepositories(name string) (int64, error) {
|
||||
on r.project_id = p.project_id and p.public = 1 `
|
||||
if len(name) != 0 {
|
||||
sql += ` where r.name like ?`
|
||||
params = append(params, "%"+name+"%")
|
||||
params = append(params, "%"+escape(name)+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
@ -162,7 +162,7 @@ func GetTotalOfUserRelevantRepositories(userID int, name string) (int64, error)
|
||||
params = append(params, userID)
|
||||
if len(name) != 0 {
|
||||
sql += ` where r.name like ?`
|
||||
params = append(params, "%"+name+"%")
|
||||
params = append(params, "%"+escape(name)+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
|
@ -101,7 +101,7 @@ func ListUsers(query models.User) ([]models.User, error) {
|
||||
queryParam := make([]interface{}, 1)
|
||||
if query.Username != "" {
|
||||
sql += ` and username like ? `
|
||||
queryParam = append(queryParam, query.Username)
|
||||
queryParam = append(queryParam, "%"+escape(query.Username)+"%")
|
||||
}
|
||||
sql += ` order by user_id desc `
|
||||
|
||||
@ -131,7 +131,7 @@ func ToggleUserAdminRole(userID, hasAdmin int) error {
|
||||
// ChangeUserPassword ...
|
||||
func ChangeUserPassword(u models.User, oldPassword ...string) (err error) {
|
||||
if len(oldPassword) > 1 {
|
||||
return errors.New("Wrong numbers of params.")
|
||||
return errors.New("wrong numbers of params")
|
||||
}
|
||||
|
||||
o := GetOrmer()
|
||||
@ -153,7 +153,7 @@ func ChangeUserPassword(u models.User, oldPassword ...string) (err error) {
|
||||
return err
|
||||
}
|
||||
if c == 0 {
|
||||
return errors.New("No record has been modified, change password failed.")
|
||||
return errors.New("no record has been modified, change password failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -171,7 +171,7 @@ func ResetUserPassword(u models.User) error {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return errors.New("No record be changed, reset password failed.")
|
||||
return errors.New("no record be changed, reset password failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/src/common/config"
|
||||
)
|
||||
|
||||
var logger = New(os.Stdout, NewTextFormatter(), WarningLevel)
|
||||
@ -29,8 +31,7 @@ var logger = New(os.Stdout, NewTextFormatter(), WarningLevel)
|
||||
func init() {
|
||||
logger.callDepth = 4
|
||||
|
||||
// TODO add item in configuaration file
|
||||
lvl := os.Getenv("LOG_LEVEL")
|
||||
lvl := config.LogLevel()
|
||||
if len(lvl) == 0 {
|
||||
logger.SetLevel(InfoLevel)
|
||||
return
|
||||
|
@ -134,7 +134,7 @@ func loadConfig() {
|
||||
useTLS = true
|
||||
}
|
||||
mc = MailConfig{
|
||||
Identity: "Mail Config",
|
||||
Identity: config["identity"],
|
||||
Host: config["host"],
|
||||
Port: config["port"],
|
||||
Username: config["username"],
|
||||
|
@ -21,15 +21,15 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
token_util "github.com/vmware/harbor/src/ui/service/token"
|
||||
"github.com/vmware/harbor/src/common/config"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/utils/registry"
|
||||
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
||||
token_util "github.com/vmware/harbor/src/ui/service/token"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -234,11 +234,11 @@ func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []
|
||||
// 2. the realm field returned by registry is an IP which can not reachable
|
||||
// inside Harbor
|
||||
func tokenURL(realm string) string {
|
||||
extEndpoint := os.Getenv("EXT_ENDPOINT")
|
||||
tokenURL := os.Getenv("TOKEN_URL")
|
||||
if len(extEndpoint) != 0 && len(tokenURL) != 0 &&
|
||||
extEndpoint := config.ExtEndpoint()
|
||||
tokenEndpoint := config.TokenEndpoint()
|
||||
if len(extEndpoint) != 0 && len(tokenEndpoint) != 0 &&
|
||||
strings.Contains(realm, extEndpoint) {
|
||||
realm = strings.TrimRight(tokenURL, "/") + "/service/token"
|
||||
realm = strings.TrimRight(tokenEndpoint, "/") + "/service/token"
|
||||
}
|
||||
return realm
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ func (sm *SM) EnterState(s string) (string, error) {
|
||||
_, exist := targets[s]
|
||||
_, isForced := sm.ForcedStates[s]
|
||||
if !exist && !isForced {
|
||||
return "", fmt.Errorf("Job id: %d, transition from %s to %s does not exist!", sm.JobID, sm.CurrentState, s)
|
||||
return "", fmt.Errorf("job id: %d, transition from %s to %s does not exist", sm.JobID, sm.CurrentState, s)
|
||||
}
|
||||
exitHandler, ok := sm.Handlers[sm.CurrentState]
|
||||
if ok {
|
||||
|
@ -1,9 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
@ -16,18 +16,22 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
//Prepare Test info
|
||||
TestUserName = "testUser0001"
|
||||
TestUserPwd = "testUser0001"
|
||||
TestUserEmail = "testUser0001@mydomain.com"
|
||||
TestProName = "testProject0001"
|
||||
TestTargetName = "testTarget0001"
|
||||
TestUserName = "testUser0001"
|
||||
TestUserPwd = "testUser0001"
|
||||
TestUserEmail = "testUser0001@mydomain.com"
|
||||
TestProName = "testProject0001"
|
||||
TestTargetName = "testTarget0001"
|
||||
TestRepoName = "testRepo0001"
|
||||
AdminName = "admin"
|
||||
DefaultProjectName = "library"
|
||||
)
|
||||
|
||||
func CommonAddUser() {
|
||||
@ -104,3 +108,20 @@ func CommonDelTarget() {
|
||||
func CommonPolicyEabled(policyID int, enabled int) {
|
||||
_ = dao.UpdateRepPolicyEnablement(int64(policyID), enabled)
|
||||
}
|
||||
|
||||
func CommonAddRepository() {
|
||||
commonRepository := &models.RepoRecord{
|
||||
RepositoryID: "1",
|
||||
Name: TestRepoName,
|
||||
OwnerName: AdminName,
|
||||
OwnerID: 1,
|
||||
ProjectName: DefaultProjectName,
|
||||
ProjectID: 1,
|
||||
PullCount: 1,
|
||||
}
|
||||
_ = dao.AddRepository(*commonRepository)
|
||||
}
|
||||
|
||||
func CommonDelRepository() {
|
||||
_ = dao.DeleteRepository(TestRepoName)
|
||||
}
|
||||
|
@ -174,19 +174,20 @@ func (a testapi) ProjectsPost(prjUsr usrInfo, project apilib.ProjectReq) (int, e
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
func (a testapi) StatisticGet(user usrInfo) (apilib.StatisticMap, error) {
|
||||
func (a testapi) StatisticGet(user usrInfo) (int, apilib.StatisticMap, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
// create path and map variables
|
||||
path := "/api/statistics/"
|
||||
fmt.Printf("project statistic path: %s\n", path)
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
var successPayload = new(apilib.StatisticMap)
|
||||
code, body, err := request(_sling, jsonAcceptHeader, user)
|
||||
if 200 == code && nil == err {
|
||||
var successPayload apilib.StatisticMap
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, user)
|
||||
|
||||
if err == nil && httpStatusCode == 200 {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
return *successPayload, err
|
||||
return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
func (a testapi) LogGet(user usrInfo, startTime, endTime, lines string) (int, []apilib.AccessLog, error) {
|
||||
@ -857,7 +858,7 @@ func updateInitPassword(userID int, password string) error {
|
||||
return fmt.Errorf("Failed to get user, userID: %d %v", userID, err)
|
||||
}
|
||||
if user == nil {
|
||||
return fmt.Errorf("User id: %d does not exist.", userID)
|
||||
return fmt.Errorf("user id: %d does not exist", userID)
|
||||
}
|
||||
if user.Salt == "" {
|
||||
user.Salt = utils.GenerateRandomString()
|
||||
|
@ -85,7 +85,7 @@ func (pma *ProjectMemberAPI) Get() {
|
||||
}
|
||||
if pma.memberID == 0 { //member id not set return list of the members
|
||||
username := pma.GetString("username")
|
||||
queryUser := models.User{Username: "%" + username + "%"}
|
||||
queryUser := models.User{Username: username}
|
||||
userList, err := dao.GetUserByProject(pid, queryUser)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to query database for member list, error: %v", err)
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
|
||||
"strconv"
|
||||
"time"
|
||||
@ -72,11 +73,19 @@ func (p *ProjectAPI) Prepare() {
|
||||
// Post ...
|
||||
func (p *ProjectAPI) Post() {
|
||||
p.userID = p.ValidateUser()
|
||||
|
||||
isSysAdmin, err := dao.IsAdminRole(p.userID)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to check admin role: %v", err)
|
||||
}
|
||||
if !isSysAdmin && config.OnlyAdminCreateProject() {
|
||||
log.Errorf("Only sys admin can create project")
|
||||
p.RenderError(http.StatusForbidden, "Only system admin can create project")
|
||||
return
|
||||
}
|
||||
var req projectReq
|
||||
p.DecodeJSONReq(&req)
|
||||
public := req.Public
|
||||
err := validateProjectReq(req)
|
||||
err = validateProjectReq(req)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid project request, error: %v", err)
|
||||
p.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid request: %v", err))
|
||||
@ -413,7 +422,7 @@ func validateProjectReq(req projectReq) error {
|
||||
validProjectName := regexp.MustCompile(`^[a-z0-9](?:-*[a-z0-9])*(?:[._][a-z0-9](?:-*[a-z0-9])*)*$`)
|
||||
legal := validProjectName.MatchString(pn)
|
||||
if !legal {
|
||||
return fmt.Errorf("Project name is not in lower case or contains illegal characters!")
|
||||
return fmt.Errorf("project name is not in lower case or contains illegal characters")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -19,23 +19,23 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
"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"
|
||||
"github.com/vmware/harbor/src/common/utils/registry"
|
||||
"github.com/vmware/harbor/src/ui/service/cache"
|
||||
svc_utils "github.com/vmware/harbor/src/ui/service/utils"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/api"
|
||||
"github.com/vmware/harbor/src/common/utils/registry"
|
||||
|
||||
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
||||
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
// RepositoryAPI handles request to /api/repositories /api/repositories/tags /api/repositories/manifests, the parm has to be put
|
||||
@ -361,7 +361,7 @@ func (ra *RepositoryAPI) GetManifests() {
|
||||
}
|
||||
|
||||
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {
|
||||
endpoint := os.Getenv("REGISTRY_URL")
|
||||
endpoint := config.InternalRegistryURL()
|
||||
|
||||
username, password, ok := ra.Ctx.Request.BasicAuth()
|
||||
if ok {
|
||||
|
@ -2,14 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
//"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
)
|
||||
|
||||
func TestStatisticGet(t *testing.T) {
|
||||
|
||||
fmt.Println("Testing Statistic API")
|
||||
assert := assert.New(t)
|
||||
|
||||
@ -17,69 +17,60 @@ func TestStatisticGet(t *testing.T) {
|
||||
|
||||
//prepare for test
|
||||
|
||||
var myProCount, pubProCount, totalProCount int32
|
||||
result, err := apiTest.StatisticGet(*admin)
|
||||
var priMyProjectCount, priMyRepoCount int32
|
||||
var priPublicProjectCount, priPublicRepoCount int32
|
||||
var priTotalProjectCount, priTotalRepoCount int32
|
||||
|
||||
//case 1: case 1: user not login, expect fail to get status info.
|
||||
fmt.Println("case 1: user not login, expect fail to get status info.")
|
||||
httpStatusCode, result, err := apiTest.StatisticGet(*unknownUsr)
|
||||
if err != nil {
|
||||
t.Error("Error get statistic info.", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(httpStatusCode, int(401), "Case 1: Get status info without login. (401)")
|
||||
}
|
||||
|
||||
//case 2: admin successful login, expect get status info successful.
|
||||
fmt.Println("case 2: admin successful login, expect get status info successful.")
|
||||
httpStatusCode, result, err = apiTest.StatisticGet(*admin)
|
||||
if err != nil {
|
||||
t.Error("Error get statistic info.", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(httpStatusCode, int(200), "Case 2: Get status info with admin login. (200)")
|
||||
//fmt.Println("pri status data %+v", result)
|
||||
priMyProjectCount = result.MyProjectCount
|
||||
priMyRepoCount = result.MyRepoCount
|
||||
priPublicProjectCount = result.PublicProjectCount
|
||||
priPublicRepoCount = result.PublicRepoCount
|
||||
priTotalProjectCount = result.TotalProjectCount
|
||||
priTotalRepoCount = result.TotalRepoCount
|
||||
}
|
||||
|
||||
//case 3: status info increased after add more project and repo.
|
||||
fmt.Println("case 3: status info increased after add more project and repo.")
|
||||
|
||||
CommonAddProject()
|
||||
CommonAddRepository()
|
||||
|
||||
httpStatusCode, result, err = apiTest.StatisticGet(*admin)
|
||||
//fmt.Println("new status data %+v", result)
|
||||
|
||||
if err != nil {
|
||||
t.Error("Error while get statistic information", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
myProCount = result.MyProjectCount
|
||||
pubProCount = result.PublicProjectCount
|
||||
totalProCount = result.TotalProjectCount
|
||||
}
|
||||
//post project
|
||||
var project apilib.ProjectReq
|
||||
project.ProjectName = "statistic_project"
|
||||
project.Public = 1
|
||||
|
||||
//case 2: admin successful login, expect project creation success.
|
||||
fmt.Println("case 2: admin successful login, expect project creation success.")
|
||||
reply, err := apiTest.ProjectsPost(*admin, project)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(reply, int(201), "Case 2: Project creation status should be 201")
|
||||
}
|
||||
|
||||
//get and compare
|
||||
result, err = apiTest.StatisticGet(*admin)
|
||||
if err != nil {
|
||||
t.Error("Error while get statistic information", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(myProCount+1, result.MyProjectCount, "MyProjectCount should be equal")
|
||||
assert.Equal(int32(2), result.MyRepoCount, "MyRepoCount should be equal")
|
||||
assert.Equal(pubProCount+1, result.PublicProjectCount, "PublicProjectCount should be equal")
|
||||
assert.Equal(int32(2), result.PublicRepoCount, "PublicRepoCount should be equal")
|
||||
assert.Equal(totalProCount+1, result.TotalProjectCount, "TotalProCount should be equal")
|
||||
assert.Equal(int32(2), result.TotalRepoCount, "TotalRepoCount should be equal")
|
||||
assert.Equal(priMyProjectCount+1, result.MyProjectCount, "MyProjectCount should be +1")
|
||||
assert.Equal(priMyRepoCount+1, result.MyRepoCount, "MyRepoCount should be +1")
|
||||
assert.Equal(priPublicProjectCount, result.PublicProjectCount, "PublicProjectCount should be equal")
|
||||
assert.Equal(priPublicRepoCount+1, result.PublicRepoCount, "PublicRepoCount should be +1")
|
||||
assert.Equal(priTotalProjectCount+1, result.TotalProjectCount, "TotalProCount should be +1")
|
||||
assert.Equal(priTotalRepoCount+1, result.TotalRepoCount, "TotalRepoCount should be +1")
|
||||
|
||||
}
|
||||
|
||||
//get the project
|
||||
var projects []apilib.Project
|
||||
var addProjectID int32
|
||||
httpStatusCode, projects, err := apiTest.ProjectsGet(project.ProjectName, 1)
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proName and isPublic", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
addProjectID = projects[0].ProjectId
|
||||
}
|
||||
|
||||
//delete the project
|
||||
projectID := strconv.Itoa(int(addProjectID))
|
||||
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "Case 1: Project creation status should be 200")
|
||||
//t.Log(result)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
//delete the project and repo
|
||||
CommonDelProject()
|
||||
CommonDelRepository()
|
||||
}
|
||||
|
84
src/ui/api/systeminfo.go
Normal file
@ -0,0 +1,84 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/vmware/harbor/src/common/api"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
//SystemInfoAPI handle requests for getting system info /api/systeminfo
|
||||
type SystemInfoAPI struct {
|
||||
api.BaseAPI
|
||||
currentUserID int
|
||||
isAdmin bool
|
||||
}
|
||||
|
||||
const harborStoragePath = "/harbor_storage"
|
||||
const defaultRootCert = "/harbor_storage/ca_download/ca.crt"
|
||||
|
||||
//SystemInfo models for system info.
|
||||
type SystemInfo struct {
|
||||
HarborStorage Storage `json:"storage"`
|
||||
}
|
||||
|
||||
//Storage models for storage.
|
||||
type Storage struct {
|
||||
Total uint64 `json:"total"`
|
||||
Free uint64 `json:"free"`
|
||||
}
|
||||
|
||||
// Prepare for validating user if an admin.
|
||||
func (sia *SystemInfoAPI) Prepare() {
|
||||
sia.currentUserID = sia.ValidateUser()
|
||||
|
||||
var err error
|
||||
sia.isAdmin, err = dao.IsAdminRole(sia.currentUserID)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in IsAdminRole:%v", err)
|
||||
sia.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
}
|
||||
|
||||
// GetVolumeInfo gets specific volume storage info.
|
||||
func (sia *SystemInfoAPI) GetVolumeInfo() {
|
||||
if !sia.isAdmin {
|
||||
sia.RenderError(http.StatusForbidden, "User does not have admin role.")
|
||||
return
|
||||
}
|
||||
var stat syscall.Statfs_t
|
||||
err := syscall.Statfs(filepath.Join("/", harborStoragePath), &stat)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in syscall.Statfs: %v", err)
|
||||
sia.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
return
|
||||
}
|
||||
|
||||
systemInfo := SystemInfo{
|
||||
HarborStorage: Storage{
|
||||
Total: stat.Blocks * uint64(stat.Bsize),
|
||||
Free: stat.Bavail * uint64(stat.Bsize),
|
||||
},
|
||||
}
|
||||
|
||||
sia.Data["json"] = systemInfo
|
||||
sia.ServeJSON()
|
||||
}
|
||||
|
||||
//GetCert gets default self-signed certificate.
|
||||
func (sia *SystemInfoAPI) GetCert() {
|
||||
if sia.isAdmin {
|
||||
if _, err := os.Stat(defaultRootCert); !os.IsNotExist(err) {
|
||||
sia.Ctx.Output.Header("Content-Disposition", "attachment; filename=ca.crt")
|
||||
http.ServeFile(sia.Ctx.ResponseWriter, sia.Ctx.Request, defaultRootCert)
|
||||
} else {
|
||||
log.Error("No certificate found.")
|
||||
sia.CustomAbort(http.StatusNotFound, "No certificate found.")
|
||||
}
|
||||
}
|
||||
sia.CustomAbort(http.StatusUnauthorized, "")
|
||||
}
|
@ -20,17 +20,17 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"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"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/api"
|
||||
"github.com/vmware/harbor/src/common/utils/registry"
|
||||
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
||||
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
// TargetAPI handles request to /api/targets/ping /api/targets/{}
|
||||
@ -41,8 +41,7 @@ type TargetAPI struct {
|
||||
|
||||
// Prepare validates the user
|
||||
func (t *TargetAPI) Prepare() {
|
||||
//TODO:move to config
|
||||
t.secretKey = os.Getenv("SECRET_KEY")
|
||||
t.secretKey = config.SecretKey()
|
||||
|
||||
userID := t.ValidateUser()
|
||||
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||
|
@ -18,7 +18,6 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -27,6 +26,7 @@ import (
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
// UserAPI handles request to /api/users/{}
|
||||
@ -47,16 +47,9 @@ type passwordReq struct {
|
||||
// Prepare validates the URL and parms
|
||||
func (ua *UserAPI) Prepare() {
|
||||
|
||||
authMode := strings.ToLower(os.Getenv("AUTH_MODE"))
|
||||
if authMode == "" {
|
||||
authMode = "db_auth"
|
||||
}
|
||||
ua.AuthMode = authMode
|
||||
ua.AuthMode = config.AuthMode()
|
||||
|
||||
selfRegistration := strings.ToLower(os.Getenv("SELF_REGISTRATION"))
|
||||
if selfRegistration == "on" {
|
||||
ua.SelfRegistration = true
|
||||
}
|
||||
ua.SelfRegistration = config.SelfRegistration()
|
||||
|
||||
if ua.Ctx.Input.IsPost() {
|
||||
sessionUserID := ua.GetSession("userId")
|
||||
@ -109,7 +102,7 @@ func (ua *UserAPI) Get() {
|
||||
username := ua.GetString("username")
|
||||
userQuery := models.User{}
|
||||
if len(username) > 0 {
|
||||
userQuery.Username = "%" + username + "%"
|
||||
userQuery.Username = username
|
||||
}
|
||||
userList, err := dao.ListUsers(userQuery)
|
||||
if err != nil {
|
||||
@ -241,9 +234,7 @@ func (ua *UserAPI) Delete() {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO read from conifg
|
||||
authMode := os.Getenv("AUTH_MODE")
|
||||
if authMode == "ldap_auth" {
|
||||
if config.AuthMode() == "ldap_auth" {
|
||||
ua.CustomAbort(http.StatusForbidden, "user can not be deleted in LDAP authentication mode")
|
||||
}
|
||||
|
||||
@ -323,13 +314,13 @@ func (ua *UserAPI) ToggleUserAdminRole() {
|
||||
func validate(user models.User) error {
|
||||
|
||||
if isIllegalLength(user.Username, 1, 20) {
|
||||
return fmt.Errorf("Username with illegal length.")
|
||||
return fmt.Errorf("username with illegal length")
|
||||
}
|
||||
if isContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
|
||||
return fmt.Errorf("Username contains illegal characters.")
|
||||
return fmt.Errorf("username contains illegal characters")
|
||||
}
|
||||
if isIllegalLength(user.Password, 8, 20) {
|
||||
return fmt.Errorf("Password with illegal length.")
|
||||
return fmt.Errorf("password with illegal length")
|
||||
}
|
||||
if err := commonValidate(user); err != nil {
|
||||
return err
|
||||
@ -342,21 +333,21 @@ func commonValidate(user models.User) error {
|
||||
|
||||
if len(user.Email) > 0 {
|
||||
if m, _ := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, user.Email); !m {
|
||||
return fmt.Errorf("Email with illegal format.")
|
||||
return fmt.Errorf("email with illegal format")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Email can't be empty")
|
||||
}
|
||||
|
||||
if isIllegalLength(user.Realname, 0, 20) {
|
||||
return fmt.Errorf("Realname with illegal length.")
|
||||
return fmt.Errorf("realname with illegal length")
|
||||
}
|
||||
|
||||
if isContainIllegalChar(user.Realname, []string{",", "~", "#", "$", "%"}) {
|
||||
return fmt.Errorf("Realname contains illegal characters.")
|
||||
return fmt.Errorf("realname contains illegal characters")
|
||||
}
|
||||
if isIllegalLength(user.Comment, -1, 30) {
|
||||
return fmt.Errorf("Comment with illegal length.")
|
||||
return fmt.Errorf("comment with illegal length")
|
||||
}
|
||||
return nil
|
||||
|
||||
|
@ -22,18 +22,18 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/ui/service/cache"
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/utils/registry"
|
||||
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
"github.com/vmware/harbor/src/ui/service/cache"
|
||||
)
|
||||
|
||||
func checkProjectPermission(userID int, projectID int64) bool {
|
||||
@ -233,9 +233,8 @@ func postReplicationAction(policyID int64, acton string) error {
|
||||
func addAuthentication(req *http.Request) {
|
||||
if req != nil {
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: models.UISecretCookie,
|
||||
// TODO read secret from config
|
||||
Value: os.Getenv("UI_SECRET"),
|
||||
Name: models.UISecretCookie,
|
||||
Value: config.UISecret(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -351,8 +350,7 @@ func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string
|
||||
}
|
||||
|
||||
// TODO remove the workaround when the bug of registry is fixed
|
||||
// TODO read it from config
|
||||
endpoint := os.Getenv("REGISTRY_URL")
|
||||
endpoint := config.InternalRegistryURL()
|
||||
client, err := cache.NewRepositoryClient(endpoint, true,
|
||||
"admin", repoInR, "repository", repoInR)
|
||||
if err != nil {
|
||||
@ -374,8 +372,7 @@ func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string
|
||||
j++
|
||||
} else {
|
||||
// TODO remove the workaround when the bug of registry is fixed
|
||||
// TODO read it from config
|
||||
endpoint := os.Getenv("REGISTRY_URL")
|
||||
endpoint := config.InternalRegistryURL()
|
||||
client, err := cache.NewRepositoryClient(endpoint, true,
|
||||
"admin", repoInR, "repository", repoInR)
|
||||
if err != nil {
|
||||
@ -425,7 +422,7 @@ func projectExists(repository string) (bool, error) {
|
||||
}
|
||||
|
||||
func initRegistryClient() (r *registry.Registry, err error) {
|
||||
endpoint := os.Getenv("REGISTRY_URL")
|
||||
endpoint := config.InternalRegistryURL()
|
||||
|
||||
addr := endpoint
|
||||
if strings.Contains(endpoint, "/") {
|
||||
@ -462,32 +459,20 @@ func initRegistryClient() (r *registry.Registry, err error) {
|
||||
}
|
||||
|
||||
func buildReplicationURL() string {
|
||||
url := getJobServiceURL()
|
||||
url := config.InternalJobServiceURL()
|
||||
return fmt.Sprintf("%s/api/jobs/replication", url)
|
||||
}
|
||||
|
||||
func buildJobLogURL(jobID string) string {
|
||||
url := getJobServiceURL()
|
||||
url := config.InternalJobServiceURL()
|
||||
return fmt.Sprintf("%s/api/jobs/replication/%s/log", url, jobID)
|
||||
}
|
||||
|
||||
func buildReplicationActionURL() string {
|
||||
url := getJobServiceURL()
|
||||
url := config.InternalJobServiceURL()
|
||||
return fmt.Sprintf("%s/api/jobs/replication/actions", url)
|
||||
}
|
||||
|
||||
func getJobServiceURL() string {
|
||||
url := os.Getenv("JOB_SERVICE_URL")
|
||||
url = strings.TrimSpace(url)
|
||||
url = strings.TrimRight(url, "/")
|
||||
|
||||
if len(url) == 0 {
|
||||
url = "http://jobservice"
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func getReposByProject(name string, keyword ...string) ([]string, error) {
|
||||
repositories := []string{}
|
||||
|
||||
|
@ -17,11 +17,11 @@ package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
// 1.5 seconds
|
||||
@ -50,7 +50,7 @@ func Register(name string, authenticator Authenticator) {
|
||||
// Login authenticates user credentials based on setting.
|
||||
func Login(m models.AuthModel) (*models.User, error) {
|
||||
|
||||
var authMode = os.Getenv("AUTH_MODE")
|
||||
var authMode = config.AuthMode()
|
||||
if authMode == "" || m.Principal == "admin" {
|
||||
authMode = "db_auth"
|
||||
}
|
||||
|
@ -18,14 +18,14 @@ package ldap
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
|
||||
"github.com/vmware/harbor/src/ui/auth"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/ui/auth"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
|
||||
"github.com/mqu/openldap"
|
||||
)
|
||||
@ -46,9 +46,9 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||
return nil, fmt.Errorf("the principal contains meta char: %q", c)
|
||||
}
|
||||
}
|
||||
ldapURL := os.Getenv("LDAP_URL")
|
||||
ldapURL := config.LDAP().URL
|
||||
if ldapURL == "" {
|
||||
return nil, errors.New("Can not get any available LDAP_URL.")
|
||||
return nil, errors.New("can not get any available LDAP_URL")
|
||||
}
|
||||
log.Debug("ldapURL:", ldapURL)
|
||||
ldap, err := openldap.Initialize(ldapURL)
|
||||
@ -57,16 +57,16 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||
}
|
||||
ldap.SetOption(openldap.LDAP_OPT_PROTOCOL_VERSION, openldap.LDAP_VERSION3)
|
||||
|
||||
ldapBaseDn := os.Getenv("LDAP_BASE_DN")
|
||||
ldapBaseDn := config.LDAP().BaseDn
|
||||
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)
|
||||
|
||||
ldapSearchDn := os.Getenv("LDAP_SEARCH_DN")
|
||||
ldapSearchDn := config.LDAP().SearchDn
|
||||
if ldapSearchDn != "" {
|
||||
log.Debug("Search DN: ", ldapSearchDn)
|
||||
ldapSearchPwd := os.Getenv("LDAP_SEARCH_PWD")
|
||||
ldapSearchPwd := config.LDAP().SearchPwd
|
||||
err = ldap.Bind(ldapSearchDn, ldapSearchPwd)
|
||||
if err != nil {
|
||||
log.Debug("Bind search dn error", err)
|
||||
@ -74,8 +74,8 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||
}
|
||||
}
|
||||
|
||||
attrName := os.Getenv("LDAP_UID")
|
||||
filter := os.Getenv("LDAP_FILTER")
|
||||
attrName := config.LDAP().UID
|
||||
filter := config.LDAP().Filter
|
||||
if filter != "" {
|
||||
filter = "(&" + filter + "(" + attrName + "=" + m.Principal + "))"
|
||||
} else {
|
||||
@ -83,7 +83,7 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||
}
|
||||
log.Debug("one or more filter", filter)
|
||||
|
||||
ldapScope := os.Getenv("LDAP_SCOPE")
|
||||
ldapScope := config.LDAP().Scope
|
||||
var scope int
|
||||
if ldapScope == "1" {
|
||||
scope = openldap.LDAP_SCOPE_BASE
|
||||
|
155
src/ui/config/config.go
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
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 provides methods to get configurations required by code in src/ui
|
||||
package config
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
commonConfig "github.com/vmware/harbor/src/common/config"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
// LDAPSetting wraps the setting of an LDAP server
|
||||
type LDAPSetting struct {
|
||||
URL string
|
||||
BaseDn string
|
||||
SearchDn string
|
||||
SearchPwd string
|
||||
UID string
|
||||
Filter string
|
||||
Scope string
|
||||
}
|
||||
|
||||
type uiParser struct{}
|
||||
|
||||
// Parse parses the auth settings url settings and other configuration consumed by code under src/ui
|
||||
func (up *uiParser) Parse(raw map[string]string, config map[string]interface{}) error {
|
||||
mode := raw["AUTH_MODE"]
|
||||
if mode == "ldap_auth" {
|
||||
setting := LDAPSetting{
|
||||
URL: raw["LDAP_URL"],
|
||||
BaseDn: raw["LDAP_BASE_DN"],
|
||||
SearchDn: raw["LDAP_SEARCH_DN"],
|
||||
SearchPwd: raw["LDAP_SEARCH_PWD"],
|
||||
UID: raw["LDAP_UID"],
|
||||
Filter: raw["LDAP_FILTER"],
|
||||
Scope: raw["LDAP_SCOPE"],
|
||||
}
|
||||
config["ldap"] = setting
|
||||
}
|
||||
config["auth_mode"] = mode
|
||||
var tokenExpiration = 30 //minutes
|
||||
if len(raw["TOKEN_EXPIRATION"]) > 0 {
|
||||
i, err := strconv.Atoi(raw["TOKEN_EXPIRATION"])
|
||||
if err != nil {
|
||||
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"]
|
||||
config["ext_reg_url"] = raw["EXT_REG_URL"]
|
||||
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
|
||||
}
|
||||
|
||||
var uiConfig *commonConfig.Config
|
||||
|
||||
func init() {
|
||||
uiKeys := []string{"AUTH_MODE", "LDAP_URL", "LDAP_BASE_DN", "LDAP_SEARCH_DN", "LDAP_SEARCH_PWD", "LDAP_UID", "LDAP_FILTER", "LDAP_SCOPE", "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 ...
|
||||
func Reload() error {
|
||||
return uiConfig.Load()
|
||||
}
|
||||
|
||||
// AuthMode ...
|
||||
func AuthMode() string {
|
||||
return uiConfig.Config["auth_mode"].(string)
|
||||
}
|
||||
|
||||
// LDAP returns the setting of ldap server
|
||||
func LDAP() LDAPSetting {
|
||||
return uiConfig.Config["ldap"].(LDAPSetting)
|
||||
}
|
||||
|
||||
// TokenExpiration returns the token expiration time (in minute)
|
||||
func TokenExpiration() int {
|
||||
return uiConfig.Config["token_exp"].(int)
|
||||
}
|
||||
|
||||
// ExtRegistryURL returns the registry URL to exposed to external client
|
||||
func ExtRegistryURL() string {
|
||||
return uiConfig.Config["ext_reg_url"].(string)
|
||||
}
|
||||
|
||||
// UISecret returns the value of UI secret cookie, used for communication between UI and JobService
|
||||
func UISecret() string {
|
||||
return uiConfig.Config["ui_secret"].(string)
|
||||
}
|
||||
|
||||
// SecretKey returns the secret key to encrypt the password of target
|
||||
func SecretKey() string {
|
||||
return uiConfig.Config["secret_key"].(string)
|
||||
}
|
||||
|
||||
// SelfRegistration returns the enablement of self registration
|
||||
func SelfRegistration() bool {
|
||||
return uiConfig.Config["self_registration"].(bool)
|
||||
}
|
||||
|
||||
// InternalRegistryURL returns registry URL for internal communication between Harbor containers
|
||||
func InternalRegistryURL() string {
|
||||
return uiConfig.Config["internal_registry_url"].(string)
|
||||
}
|
||||
|
||||
// InternalJobServiceURL returns jobservice URL for internal communication between Harbor containers
|
||||
func InternalJobServiceURL() string {
|
||||
return uiConfig.Config["internal_jobservice_url"].(string)
|
||||
}
|
||||
|
||||
// InitialAdminPassword returns the initial password for administrator
|
||||
func InitialAdminPassword() string {
|
||||
return uiConfig.Config["admin_password"].(string)
|
||||
}
|
||||
|
||||
// OnlyAdminCreateProject returns the flag to restrict that only sys admin can create project
|
||||
func OnlyAdminCreateProject() bool {
|
||||
return uiConfig.Config["admin_create_project"].(bool)
|
||||
}
|
144
src/ui/config/config_test.go
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
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 (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
auth = "ldap_auth"
|
||||
ldap = LDAPSetting{
|
||||
"ldap://test.ldap.com",
|
||||
"ou=people",
|
||||
"dc=whatever,dc=org",
|
||||
"1234567",
|
||||
"cn",
|
||||
"uid",
|
||||
"2",
|
||||
}
|
||||
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("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 {
|
||||
panic(err)
|
||||
}
|
||||
rc := m.Run()
|
||||
|
||||
os.Unsetenv("AUTH_MODE")
|
||||
os.Unsetenv("LDAP_URL")
|
||||
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("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())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenExpiration(t *testing.T) {
|
||||
if TokenExpiration() != tokenExpRes {
|
||||
t.Errorf("Expected token expiration: %d, in fact: %d", tokenExpRes, TokenExpiration())
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLs(t *testing.T) {
|
||||
if InternalRegistryURL() != internalRegistryURL {
|
||||
t.Errorf("Expected internal Registry URL: %s, in fact: %s", internalRegistryURL, InternalRegistryURL())
|
||||
}
|
||||
if InternalJobServiceURL() != jobServiceURL {
|
||||
t.Errorf("Expected internal jobservice URL: %s, in fact: %s", jobServiceURL, InternalJobServiceURL())
|
||||
}
|
||||
if ExtRegistryURL() != externalRegURL {
|
||||
t.Errorf("Expected External Registry URL: %s, in fact: %s", externalRegURL, ExtRegistryURL())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelfRegistration(t *testing.T) {
|
||||
if SelfRegistration() {
|
||||
t.Errorf("Expected Self Registration to be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecrets(t *testing.T) {
|
||||
if SecretKey() != secretKey {
|
||||
t.Errorf("Expected Secrect Key :%s, in fact: %s", secretKey, SecretKey())
|
||||
}
|
||||
if UISecret() != uiSecret {
|
||||
t.Errorf("Expected UI Secret: %s, in fact: %s", uiSecret, UISecret())
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectCreationRestrict(t *testing.T) {
|
||||
if !OnlyAdminCreateProject() {
|
||||
t.Errorf("Expected OnlyAdminCreateProject to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitAdminPassword(t *testing.T) {
|
||||
if InitialAdminPassword() != adminPassword {
|
||||
t.Errorf("Expected adminPassword: %s, in fact: %s", adminPassword, InitialAdminPassword())
|
||||
}
|
||||
}
|
@ -9,6 +9,9 @@ type AccountSettingController struct {
|
||||
func (asc *AccountSettingController) Get() {
|
||||
var isAdminForLdap bool
|
||||
sessionUserID, ok := asc.GetSession("userId").(int)
|
||||
if !ok {
|
||||
asc.Redirect("/", 302)
|
||||
}
|
||||
if ok && sessionUserID == 1 {
|
||||
isAdminForLdap = true
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"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/config"
|
||||
)
|
||||
|
||||
// BaseController wraps common methods such as i18n support, forward, which can be leveraged by other UI render controllers.
|
||||
@ -30,9 +31,10 @@ type langType struct {
|
||||
}
|
||||
|
||||
const (
|
||||
viewPath = "sections"
|
||||
prefixNg = ""
|
||||
defaultLang = "en-US"
|
||||
viewPath = "sections"
|
||||
prefixNg = ""
|
||||
defaultLang = "en-US"
|
||||
defaultRootCert = "/harbor_storage/ca_download/ca.crt"
|
||||
)
|
||||
|
||||
var supportLanguages map[string]langType
|
||||
@ -42,19 +44,21 @@ var mappingLangNames map[string]string
|
||||
func (b *BaseController) Prepare() {
|
||||
|
||||
var lang string
|
||||
var langHasChanged bool
|
||||
|
||||
langCookie, err := b.Ctx.Request.Cookie("language")
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in Request.Cookie: %v", err)
|
||||
}
|
||||
if langCookie != nil {
|
||||
lang = langCookie.Value
|
||||
}
|
||||
if len(lang) == 0 {
|
||||
sessionLang := b.GetSession("lang")
|
||||
if sessionLang != nil {
|
||||
b.SetSession("Lang", lang)
|
||||
lang = sessionLang.(string)
|
||||
var showDownloadCert bool
|
||||
|
||||
langRequest := b.GetString("lang")
|
||||
if langRequest != "" {
|
||||
lang = langRequest
|
||||
langHasChanged = true
|
||||
} else {
|
||||
langCookie, err := b.Ctx.Request.Cookie("language")
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in Request.Cookie: %v", err)
|
||||
}
|
||||
if langCookie != nil {
|
||||
lang = langCookie.Value
|
||||
} else {
|
||||
al := b.Ctx.Request.Header.Get("Accept-Language")
|
||||
if len(al) > 4 {
|
||||
@ -63,16 +67,23 @@ func (b *BaseController) Prepare() {
|
||||
lang = al
|
||||
}
|
||||
}
|
||||
langHasChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
if _, exist := supportLanguages[lang]; !exist { //Check if support the request language.
|
||||
lang = defaultLang //Set default language if not supported.
|
||||
if langHasChanged {
|
||||
if _, exist := supportLanguages[lang]; !exist { //Check if support the request language.
|
||||
lang = defaultLang //Set default language if not supported.
|
||||
}
|
||||
cookies := &http.Cookie{
|
||||
Name: "language",
|
||||
Value: lang,
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
}
|
||||
http.SetCookie(b.Ctx.ResponseWriter, cookies)
|
||||
}
|
||||
|
||||
b.Ctx.SetCookie("language", lang, 0, "/")
|
||||
b.SetSession("Lang", lang)
|
||||
|
||||
curLang := langType{
|
||||
Lang: lang,
|
||||
}
|
||||
@ -92,7 +103,7 @@ func (b *BaseController) Prepare() {
|
||||
b.Data["CurLang"] = curLang.Name
|
||||
b.Data["RestLangs"] = restLangs
|
||||
|
||||
authMode := strings.ToLower(os.Getenv("AUTH_MODE"))
|
||||
authMode := config.AuthMode()
|
||||
if authMode == "" {
|
||||
authMode = "db_auth"
|
||||
}
|
||||
@ -104,15 +115,28 @@ func (b *BaseController) Prepare() {
|
||||
b.UseCompressedJS = true
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join("static", "resources", "js", "harbor.app.min.js")); os.IsNotExist(err) {
|
||||
m, err := filepath.Glob(filepath.Join("static", "resources", "js", "harbor.app.min.*.js"))
|
||||
if err != nil || len(m) == 0 {
|
||||
b.UseCompressedJS = false
|
||||
}
|
||||
|
||||
selfRegistration := strings.ToLower(os.Getenv("SELF_REGISTRATION"))
|
||||
if selfRegistration == "on" {
|
||||
b.SelfRegistration = true
|
||||
b.SelfRegistration = config.SelfRegistration()
|
||||
|
||||
b.Data["SelfRegistration"] = config.SelfRegistration()
|
||||
|
||||
sessionUserID := b.GetSession("userId")
|
||||
if sessionUserID != nil {
|
||||
isAdmin, err := dao.IsAdminRole(sessionUserID.(int))
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in IsAdminRole: %v", err)
|
||||
}
|
||||
if isAdmin {
|
||||
if _, err := os.Stat(defaultRootCert); !os.IsNotExist(err) {
|
||||
showDownloadCert = true
|
||||
}
|
||||
}
|
||||
}
|
||||
b.Data["SelfRegistration"] = b.SelfRegistration
|
||||
b.Data["ShowDownloadCert"] = showDownloadCert
|
||||
}
|
||||
|
||||
// Forward to setup layout and template for content for a page.
|
||||
|
@ -9,6 +9,9 @@ type ChangePasswordController struct {
|
||||
func (cpc *ChangePasswordController) Get() {
|
||||
var isAdminForLdap bool
|
||||
sessionUserID, ok := cpc.GetSession("userId").(int)
|
||||
if !ok {
|
||||
cpc.Redirect("/", 302)
|
||||
}
|
||||
if ok && sessionUserID == 1 {
|
||||
isAdminForLdap = true
|
||||
}
|
||||
|
@ -112,12 +112,12 @@ func TestMain(t *testing.T) {
|
||||
r, _ = http.NewRequest("GET", "/account_setting", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(200), w.Code, "'/account_setting' httpStatusCode should be 200")
|
||||
assert.Equal(int(302), w.Code, "'/account_setting' httpStatusCode should be 302")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/change_password", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(200), w.Code, "'/change_password' httpStatusCode should be 200")
|
||||
assert.Equal(int(302), w.Code, "'/change_password' httpStatusCode should be 302")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/admin_option", nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
@ -3,11 +3,11 @@ package controllers
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"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/models"
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
@ -49,7 +49,7 @@ func (cc *CommonController) SendEmail() {
|
||||
|
||||
message := new(bytes.Buffer)
|
||||
|
||||
harborURL := os.Getenv("HARBOR_URL")
|
||||
harborURL := config.ExtEndpoint()
|
||||
if harborURL == "" {
|
||||
harborURL = "localhost"
|
||||
}
|
||||
|
@ -1,5 +1,11 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
// ProjectController handles requests to /project
|
||||
type ProjectController struct {
|
||||
BaseController
|
||||
@ -7,5 +13,16 @@ type ProjectController struct {
|
||||
|
||||
// Get renders project page
|
||||
func (pc *ProjectController) Get() {
|
||||
var err error
|
||||
isSysAdmin := false
|
||||
uid := pc.GetSession("userId")
|
||||
if uid != nil {
|
||||
isSysAdmin, err = dao.IsAdminRole(uid)
|
||||
if err != nil {
|
||||
log.Warningf("Error in checking Admin Role for user, id: %d, error: %v", uid, err)
|
||||
isSysAdmin = false
|
||||
}
|
||||
}
|
||||
pc.Data["CanCreate"] = !config.OnlyAdminCreateProject() || isSysAdmin
|
||||
pc.Forward("page_title_project", "project.htm")
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package controllers
|
||||
|
||||
import "os"
|
||||
import (
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
// RepositoryController handles request to /repository
|
||||
type RepositoryController struct {
|
||||
@ -9,6 +11,6 @@ type RepositoryController struct {
|
||||
|
||||
// Get renders repository page
|
||||
func (rc *RepositoryController) Get() {
|
||||
rc.Data["HarborRegUrl"] = os.Getenv("HARBOR_REG_URL")
|
||||
rc.Data["HarborRegUrl"] = config.ExtRegistryURL()
|
||||
rc.Forward("page_title_repository", "repository.htm")
|
||||
}
|
||||
|
@ -25,11 +25,12 @@ import (
|
||||
"github.com/astaxie/beego"
|
||||
_ "github.com/astaxie/beego/session/redis"
|
||||
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/ui/api"
|
||||
_ "github.com/vmware/harbor/src/ui/auth/db"
|
||||
_ "github.com/vmware/harbor/src/ui/auth/ldap"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -43,7 +44,7 @@ func updateInitPassword(userID int, password string) error {
|
||||
return fmt.Errorf("Failed to get user, userID: %d %v", userID, err)
|
||||
}
|
||||
if user == nil {
|
||||
return fmt.Errorf("User id: %d does not exist.", userID)
|
||||
return fmt.Errorf("user id: %d does not exist", userID)
|
||||
}
|
||||
if user.Salt == "" {
|
||||
salt := utils.GenerateRandomString()
|
||||
@ -76,7 +77,7 @@ func main() {
|
||||
|
||||
dao.InitDatabase()
|
||||
|
||||
if err := updateInitPassword(adminUserID, os.Getenv("HARBOR_ADMIN_PASSWORD")); err != nil {
|
||||
if err := updateInitPassword(adminUserID, config.InitialAdminPassword()); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
initRouters()
|
||||
|
@ -84,6 +84,9 @@ func initRouters() {
|
||||
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
||||
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
||||
beego.Router("/api/logs", &api.LogAPI{})
|
||||
|
||||
beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
|
||||
beego.Router("/api/systeminfo/getcert", &api.SystemInfoAPI{}, "get:GetCert")
|
||||
//external service that hosted on harbor process:
|
||||
beego.Router("/service/notifications", &service.NotificationHandler{})
|
||||
beego.Router("/service/token", &token.Handler{})
|
||||
|
@ -21,13 +21,12 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
"github.com/docker/libtrust"
|
||||
@ -38,27 +37,10 @@ const (
|
||||
privateKey = "/etc/ui/private_key.pem"
|
||||
)
|
||||
|
||||
var (
|
||||
expiration = 30 //minutes
|
||||
)
|
||||
var expiration int //minutes
|
||||
|
||||
func init() {
|
||||
// TODO read it from config
|
||||
expi := os.Getenv("TOKEN_EXPIRATION")
|
||||
if len(expi) != 0 {
|
||||
i, err := strconv.Atoi(expi)
|
||||
if err != nil {
|
||||
log.Errorf("failed to parse token expiration: %v, using default value: %d minutes", err, expiration)
|
||||
return
|
||||
}
|
||||
|
||||
if i <= 0 {
|
||||
log.Warningf("invalid token expiration, using default value: %d minutes", expiration)
|
||||
return
|
||||
}
|
||||
|
||||
expiration = i
|
||||
}
|
||||
expiration = config.TokenExpiration()
|
||||
log.Infof("token expiration: %d minutes", expiration)
|
||||
}
|
||||
|
||||
@ -105,8 +87,17 @@ func FilterAccess(username string, a *token.ResourceActions) {
|
||||
//clear action list to assign to new acess element after perm check.
|
||||
a.Actions = []string{}
|
||||
if a.Type == "repository" {
|
||||
if strings.Contains(a.Name, "/") { //Only check the permission when the requested image has a namespace, i.e. project
|
||||
projectName := a.Name[0:strings.LastIndex(a.Name, "/")]
|
||||
repoSplit := strings.Split(a.Name, "/")
|
||||
repoLength := len(repoSplit)
|
||||
if repoLength > 1 { //Only check the permission when the requested image has a namespace, i.e. project
|
||||
var projectName string
|
||||
registryURL := config.ExtRegistryURL()
|
||||
if repoSplit[0] == registryURL {
|
||||
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)
|
||||
} else {
|
||||
projectName = repoSplit[0]
|
||||
}
|
||||
var permission string
|
||||
if len(username) > 0 {
|
||||
isAdmin, err := dao.IsAdminRole(username)
|
||||
|
@ -18,14 +18,14 @@ package utils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"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.
|
||||
func VerifySecret(r *http.Request) bool {
|
||||
secret := os.Getenv("UI_SECRET")
|
||||
secret := config.UISecret()
|
||||
c, err := r.Cookie("uisecret")
|
||||
if err != nil {
|
||||
log.Warningf("Failed to get secret cookie, error: %v", err)
|
||||
|
@ -48,8 +48,6 @@
|
||||
element.find('.section').css({'height': (h - scope.subsHeight - scope.subsSection) + 'px'});
|
||||
element.find('.sub-pane').css({'height': (h - scope.subsHeight - scope.subsSubPane) + 'px'});
|
||||
element.find('.tab-pane').css({'height': (h - scope.subsHeight - scope.subsSubPane - scope.subsSection -100) + 'px'});
|
||||
// var subPaneHeight = element.find('.sub-pane').height();
|
||||
// element.find('.table-body-container').css({'height': (subPaneHeight - scope.subsTblBody) + 'px'});
|
||||
}
|
||||
}, true);
|
||||
|
||||
|
@ -20,9 +20,9 @@
|
||||
.module('harbor.log')
|
||||
.directive('advancedSearch', advancedSearch);
|
||||
|
||||
AdvancedSearchController.$inject = ['$scope', 'ListLogService'];
|
||||
AdvancedSearchController.$inject = ['$scope', 'ListLogService', '$filter', 'trFilter'];
|
||||
|
||||
function AdvancedSearchController($scope, ListLogService) {
|
||||
function AdvancedSearchController($scope, ListLogService, $filter, trFilter) {
|
||||
var vm = this;
|
||||
|
||||
vm.checkOperation = checkOperation;
|
||||
@ -121,8 +121,21 @@
|
||||
if(vm.opOthers && $.trim(vm.others) !== '') {
|
||||
e.op.push(vm.others);
|
||||
}
|
||||
if(vm.fromDate && vm.toDate && (getDateValue(vm.fromDate) > getDateValue(vm.toDate))) {
|
||||
$scope.$emit('modalTitle', $filter('tr')('error'));
|
||||
$scope.$emit('modalMessage', $filter('tr')('begin_date_is_later_than_end_date'));
|
||||
$scope.$emit('raiseError', true);
|
||||
return
|
||||
}
|
||||
vm.search(e);
|
||||
}
|
||||
|
||||
function getDateValue(date) {
|
||||
if(date) {
|
||||
return new Date(date);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function advancedSearch(I18nService) {
|
||||
|
@ -143,7 +143,9 @@
|
||||
|
||||
}
|
||||
|
||||
function listLog() {
|
||||
listLog.$inject = ['$timeout'];
|
||||
|
||||
function listLog($timeout) {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/log/list-log.directive.html',
|
||||
@ -162,6 +164,12 @@
|
||||
element.find('#txtSearchInput').on('keydown', function(e) {
|
||||
if($(this).is(':focus') && e.keyCode === 13) {
|
||||
ctrl.search({'op': ctrl.op, 'username': ctrl.username});
|
||||
} else {
|
||||
$timeout(function() {
|
||||
if(ctrl.username.length === 0) {
|
||||
ctrl.search({'op': ctrl.op, 'username': ctrl.username});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -20,26 +20,24 @@
|
||||
.module('harbor.optional.menu')
|
||||
.directive('optionalMenu', optionalMenu);
|
||||
|
||||
OptionalMenuController.$inject = ['$scope', '$window', 'I18nService', 'LogOutService', 'currentUser', '$timeout', 'trFilter', '$filter'];
|
||||
OptionalMenuController.$inject = ['$scope', '$window', 'I18nService', 'LogOutService', 'currentUser', '$timeout', 'trFilter', '$filter', 'GetVolumeInfoService'];
|
||||
|
||||
function OptionalMenuController($scope, $window, I18nService, LogOutService, currentUser, $timeoutm, trFilter, $filter) {
|
||||
function OptionalMenuController($scope, $window, I18nService, LogOutService, currentUser, $timeoutm, trFilter, $filter, GetVolumeInfoService) {
|
||||
var vm = this;
|
||||
|
||||
vm.currentLanguage = I18nService().getCurrentLanguage();
|
||||
vm.languageName = I18nService().getLanguageName(vm.currentLanguage);
|
||||
|
||||
I18nService().setCurrentLanguage(vm.currentLanguage);
|
||||
|
||||
var i18n = I18nService();
|
||||
i18n.setCurrentLanguage(vm.language);
|
||||
vm.languageName = i18n.getLanguageName(vm.language);
|
||||
console.log('current language:' + vm.languageName);
|
||||
|
||||
vm.supportLanguages = I18nService().getSupportLanguages();
|
||||
|
||||
vm.supportLanguages = i18n.getSupportLanguages();
|
||||
vm.user = currentUser.get();
|
||||
vm.setLanguage = setLanguage;
|
||||
vm.logOut = logOut;
|
||||
vm.about = about;
|
||||
|
||||
|
||||
function setLanguage(language) {
|
||||
I18nService().setCurrentLanguage(language);
|
||||
vm.languageName = i18n.getLanguageName(vm.language);
|
||||
var hash = $window.location.hash;
|
||||
$window.location.href = '/language?lang=' + language + '&hash=' + encodeURIComponent(hash);
|
||||
}
|
||||
@ -55,16 +53,43 @@
|
||||
function logOutFailed(data, status) {
|
||||
console.log('Failed to log out:' + data);
|
||||
}
|
||||
|
||||
var raiseInfo = {
|
||||
'confirmOnly': true,
|
||||
'contentType': 'text/html',
|
||||
'action': function() {}
|
||||
};
|
||||
|
||||
function about() {
|
||||
$scope.$emit('modalTitle', $filter('tr')('about_harbor'));
|
||||
$scope.$emit('modalMessage', $filter('tr')('current_version', [vm.version || 'Unknown']));
|
||||
var raiseInfo = {
|
||||
'confirmOnly': true,
|
||||
'contentType': 'text/html',
|
||||
'action': function() {}
|
||||
};
|
||||
vm.modalMessage = $filter('tr')('current_version', [vm.version || 'Unknown']);
|
||||
if(vm.showDownloadCert === 'true') {
|
||||
appendDownloadCertLink();
|
||||
}
|
||||
GetVolumeInfoService("data")
|
||||
.then(getVolumeInfoSuccess, getVolumeInfoFailed);
|
||||
}
|
||||
function getVolumeInfoSuccess(response) {
|
||||
var storage = response.data;
|
||||
vm.modalMessage += '<br/>' + $filter('tr')('current_storage',
|
||||
[toGigaBytes(storage['storage']['free']), toGigaBytes(storage['storage']['total'])]);
|
||||
$scope.$emit('modalMessage', vm.modalMessage);
|
||||
$scope.$emit('raiseInfo', raiseInfo);
|
||||
|
||||
}
|
||||
function getVolumeInfoFailed(response) {
|
||||
$scope.$emit('modalMessage', vm.modalMessage);
|
||||
$scope.$emit('raiseInfo', raiseInfo);
|
||||
}
|
||||
|
||||
function toGigaBytes(val) {
|
||||
return Math.round(val / (1024 * 1024 * 1024));
|
||||
}
|
||||
|
||||
function appendDownloadCertLink() {
|
||||
vm.modalMessage += '<br/>' + $filter('tr')('default_root_cert', ['/api/systeminfo/getcert', $filter('tr')('download')]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function optionalMenu() {
|
||||
@ -72,7 +97,9 @@
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/optional_menu?timestamp=' + new Date().getTime(),
|
||||
'scope': {
|
||||
'version': '@'
|
||||
'version': '@',
|
||||
'language': '@',
|
||||
'showDownloadCert': '@'
|
||||
},
|
||||
'controller': OptionalMenuController,
|
||||
'controllerAs': 'vm',
|
||||
|
@ -1,14 +1,25 @@
|
||||
<nav aria-label="Page navigation" class="pull-left">
|
||||
<ul class="pagination" style="margin: 0;">
|
||||
<li>
|
||||
<a href="javascript:void(0);" ng-click="vm.previous()" aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
<ul class="pagination" style="margin: 0 0 0 10px;">
|
||||
<li ng-class="vm.disabledFirst" ng-show="vm.visible">
|
||||
<a href="javascript:void(0);" ng-click="vm.gotoFirst()" aria-label="Previous">
|
||||
<span aria-hidden="true"><<</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<li ng-class="vm.disabledPrevious" ng-show="vm.visible">
|
||||
<a href="javascript:void(0);" ng-click="vm.previous()" aria-label="Previous">
|
||||
<span aria-hidden="true"><</span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="vm.disabledNext" ng-show="vm.visible">
|
||||
<a href="javascript:void(0);" ng-click="vm.next()" aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
<span aria-hidden="true">></span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="vm.disabledLast" ng-show="vm.visible">
|
||||
<a href="javascript:void(0);" ng-click="vm.gotoLast()" aria-label="Next">
|
||||
<span aria-hidden="true">>></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<p class="pull-right" style="margin-right: 15%; margin-top: 5px;">// 'total' | tr // // vm.totalCount // // 'items' | tr //</p>
|
@ -32,7 +32,6 @@
|
||||
return directive;
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
|
||||
scope.$watch('vm.page', function(current) {
|
||||
if(current) {
|
||||
ctrl.page = current;
|
||||
@ -45,34 +44,43 @@
|
||||
scope.$watch('vm.totalCount', function(current) {
|
||||
if(current) {
|
||||
var totalCount = current;
|
||||
|
||||
element.find('ul li:first a').off('click');
|
||||
element.find('ul li:last a').off('click');
|
||||
|
||||
|
||||
tc = new TimeCounter();
|
||||
|
||||
console.log('Total Count:' + totalCount + ', Page Size:' + ctrl.pageSize + ', Display Count:' + ctrl.displayCount + ', Page:' + ctrl.page);
|
||||
|
||||
ctrl.buttonCount = Math.ceil(totalCount / ctrl.pageSize);
|
||||
|
||||
|
||||
if(ctrl.buttonCount <= ctrl.displayCount) {
|
||||
tc.setMaximum(1);
|
||||
ctrl.visible = false;
|
||||
}else{
|
||||
tc.setMaximum(Math.ceil(ctrl.buttonCount / ctrl.displayCount));
|
||||
ctrl.visible = true;
|
||||
}
|
||||
|
||||
element.find('ul li:first a').on('click', previous);
|
||||
element.find('ul li:last a').on('click', next);
|
||||
|
||||
ctrl.gotoFirst = gotoFirst;
|
||||
ctrl.gotoLast = gotoLast;
|
||||
|
||||
if(ctrl.buttonCount < ctrl.page) {
|
||||
ctrl.page = ctrl.buttonCount;
|
||||
}
|
||||
|
||||
ctrl.previous = previous;
|
||||
ctrl.next = next;
|
||||
|
||||
drawButtons(tc.getTime());
|
||||
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
|
||||
toggleFirst();
|
||||
toggleLast();
|
||||
|
||||
togglePageButton();
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var TimeCounter = function() {
|
||||
this.time = 0;
|
||||
@ -84,6 +92,10 @@
|
||||
this.maximum = maximum;
|
||||
};
|
||||
|
||||
TimeCounter.prototype.getMaximum = function() {
|
||||
return this.maximum;
|
||||
};
|
||||
|
||||
TimeCounter.prototype.increment = function() {
|
||||
if(this.time < this.maximum) {
|
||||
++this.time;
|
||||
@ -92,7 +104,6 @@
|
||||
}
|
||||
++ctrl.page;
|
||||
}
|
||||
scope.$apply();
|
||||
};
|
||||
|
||||
TimeCounter.prototype.canIncrement = function() {
|
||||
@ -106,13 +117,11 @@
|
||||
if(this.time > this.minimum) {
|
||||
if(this.time === 0) {
|
||||
ctrl.page = ctrl.displayCount;
|
||||
}else if((ctrl.page % ctrl.displayCount) != 0) {
|
||||
}else{
|
||||
ctrl.page = this.time * ctrl.displayCount;
|
||||
}
|
||||
--this.time;
|
||||
--ctrl.page;
|
||||
}
|
||||
scope.$apply();
|
||||
};
|
||||
|
||||
TimeCounter.prototype.canDecrement = function() {
|
||||
@ -125,6 +134,10 @@
|
||||
TimeCounter.prototype.getTime = function() {
|
||||
return this.time;
|
||||
};
|
||||
|
||||
TimeCounter.prototype.setTime = function(time) {
|
||||
this.time = time;
|
||||
};
|
||||
|
||||
function drawButtons(time) {
|
||||
element.find('li[tag="pagination-button"]').remove();
|
||||
@ -135,32 +148,38 @@
|
||||
buttons.push('<li tag="pagination-button"><a href="javascript:void(0)" page="' + displayNumber + '">' + displayNumber + '<span class="sr-only"></span></a></li>');
|
||||
}
|
||||
}
|
||||
|
||||
$(buttons.join(''))
|
||||
.insertAfter(element.find('ul li:eq(0)')).end()
|
||||
.insertAfter(element.find('ul li:eq(' + (ctrl.visible ? 1 : 0) + ')')).end()
|
||||
.on('click', buttonClickHandler);
|
||||
}
|
||||
|
||||
function togglePrevious(status) {
|
||||
if(status){
|
||||
element.find('ul li:first').removeClass('disabled');
|
||||
}else{
|
||||
element.find('ul li:first').addClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
ctrl.disabledPrevious = status ? '' : 'disabled';
|
||||
toggleFirst();
|
||||
toggleLast();
|
||||
}
|
||||
|
||||
function toggleNext(status) {
|
||||
if(status) {
|
||||
element.find('ul li:last').removeClass('disabled');
|
||||
}else{
|
||||
element.find('ul li:last').addClass('disabled');
|
||||
}
|
||||
ctrl.disabledNext = status ? '' : 'disabled';
|
||||
toggleFirst();
|
||||
toggleLast();
|
||||
}
|
||||
|
||||
function toggleFirst() {
|
||||
ctrl.disabledFirst = (ctrl.page > 1) ? '' : 'disabled';
|
||||
}
|
||||
|
||||
function toggleLast() {
|
||||
ctrl.disabledLast = (ctrl.page < ctrl.buttonCount) ? '' : 'disabled';
|
||||
}
|
||||
|
||||
function buttonClickHandler(e) {
|
||||
ctrl.page = $(e.target).attr('page');
|
||||
ctrl.page = $(e.target).attr('page');
|
||||
togglePageButton();
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
|
||||
scope.$apply();
|
||||
}
|
||||
|
||||
@ -177,8 +196,232 @@
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
}
|
||||
scope.$apply();
|
||||
}
|
||||
|
||||
function gotoFirst() {
|
||||
ctrl.page = 1;
|
||||
tc.setTime(0);
|
||||
drawButtons(0);
|
||||
|
||||
toggleFirst();
|
||||
toggleLast();
|
||||
|
||||
togglePageButton();
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
}
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('harbor.paginator')
|
||||
.directive('paginator', paginator);
|
||||
|
||||
PaginatorController.$inject = [];
|
||||
|
||||
function PaginatorController() {
|
||||
var vm = this;
|
||||
}
|
||||
|
||||
paginator.$inject = [];
|
||||
|
||||
function paginator() {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/paginator/paginator.directive.html',
|
||||
'scope': {
|
||||
'totalCount': '@',
|
||||
'pageSize': '@',
|
||||
'page': '=',
|
||||
'displayCount': '@'
|
||||
},
|
||||
'link': link,
|
||||
'controller': PaginatorController,
|
||||
'controllerAs': 'vm',
|
||||
'bindToController': true
|
||||
};
|
||||
return directive;
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
scope.$watch('vm.page', function(current) {
|
||||
if(current) {
|
||||
ctrl.page = current;
|
||||
togglePageButton();
|
||||
}
|
||||
});
|
||||
|
||||
var tc;
|
||||
|
||||
scope.$watch('vm.totalCount', function(current) {
|
||||
if(current) {
|
||||
var totalCount = current;
|
||||
|
||||
tc = new TimeCounter();
|
||||
|
||||
console.log('Total Count:' + totalCount + ', Page Size:' + ctrl.pageSize + ', Display Count:' + ctrl.displayCount + ', Page:' + ctrl.page);
|
||||
|
||||
ctrl.buttonCount = Math.ceil(totalCount / ctrl.pageSize);
|
||||
|
||||
if(ctrl.buttonCount <= ctrl.displayCount) {
|
||||
tc.setMaximum(1);
|
||||
ctrl.visible = false;
|
||||
}else{
|
||||
tc.setMaximum(Math.ceil(ctrl.buttonCount / ctrl.displayCount));
|
||||
ctrl.visible = true;
|
||||
}
|
||||
|
||||
ctrl.gotoFirst = gotoFirst;
|
||||
ctrl.gotoLast = gotoLast;
|
||||
|
||||
if(ctrl.buttonCount < ctrl.page) {
|
||||
ctrl.page = ctrl.buttonCount;
|
||||
}
|
||||
|
||||
ctrl.previous = previous;
|
||||
ctrl.next = next;
|
||||
|
||||
drawButtons(tc.getTime());
|
||||
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
|
||||
toggleFirst();
|
||||
toggleLast();
|
||||
|
||||
togglePageButton();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
var TimeCounter = function() {
|
||||
this.time = 0;
|
||||
this.minimum = 0;
|
||||
this.maximum = 0;
|
||||
};
|
||||
|
||||
TimeCounter.prototype.setMaximum = function(maximum) {
|
||||
this.maximum = maximum;
|
||||
};
|
||||
|
||||
TimeCounter.prototype.getMaximum = function() {
|
||||
return this.maximum;
|
||||
};
|
||||
|
||||
TimeCounter.prototype.increment = function() {
|
||||
if(this.time < this.maximum) {
|
||||
++this.time;
|
||||
if((ctrl.page % ctrl.displayCount) != 0) {
|
||||
ctrl.page = this.time * ctrl.displayCount;
|
||||
}
|
||||
++ctrl.page;
|
||||
}
|
||||
};
|
||||
|
||||
TimeCounter.prototype.canIncrement = function() {
|
||||
if(this.time + 1 < this.maximum) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
TimeCounter.prototype.decrement = function() {
|
||||
if(this.time > this.minimum) {
|
||||
if(this.time === 0) {
|
||||
ctrl.page = ctrl.displayCount;
|
||||
}else{
|
||||
ctrl.page = this.time * ctrl.displayCount;
|
||||
}
|
||||
--this.time;
|
||||
}
|
||||
};
|
||||
|
||||
TimeCounter.prototype.canDecrement = function() {
|
||||
if(this.time > this.minimum) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
TimeCounter.prototype.getTime = function() {
|
||||
return this.time;
|
||||
};
|
||||
|
||||
TimeCounter.prototype.setTime = function(time) {
|
||||
this.time = time;
|
||||
};
|
||||
|
||||
function drawButtons(time) {
|
||||
element.find('li[tag="pagination-button"]').remove();
|
||||
var buttons = [];
|
||||
for(var i = 1; i <= ctrl.displayCount; i++) {
|
||||
var displayNumber = ctrl.displayCount * time + i;
|
||||
if(displayNumber <= ctrl.buttonCount) {
|
||||
buttons.push('<li tag="pagination-button"><a href="javascript:void(0)" page="' + displayNumber + '">' + displayNumber + '<span class="sr-only"></span></a></li>');
|
||||
}
|
||||
}
|
||||
|
||||
$(buttons.join(''))
|
||||
.insertAfter(element.find('ul li:eq(' + (ctrl.visible ? 1 : 0) + ')')).end()
|
||||
.on('click', buttonClickHandler);
|
||||
}
|
||||
|
||||
function togglePrevious(status) {
|
||||
ctrl.disabledPrevious = status ? '' : 'disabled';
|
||||
toggleFirst();
|
||||
toggleLast();
|
||||
}
|
||||
|
||||
function toggleNext(status) {
|
||||
ctrl.disabledNext = status ? '' : 'disabled';
|
||||
toggleFirst();
|
||||
toggleLast();
|
||||
}
|
||||
|
||||
function toggleFirst() {
|
||||
ctrl.disabledFirst = (ctrl.page > 1) ? '' : 'disabled';
|
||||
}
|
||||
|
||||
function toggleLast() {
|
||||
ctrl.disabledLast = (ctrl.page < ctrl.buttonCount) ? '' : 'disabled';
|
||||
}
|
||||
|
||||
function buttonClickHandler(e) {
|
||||
ctrl.page = $(e.target).attr('page');
|
||||
togglePageButton();
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
|
||||
scope.$apply();
|
||||
}
|
||||
|
||||
function togglePageButton() {
|
||||
element.find('li[tag="pagination-button"]').removeClass('active');
|
||||
element.find('li[tag="pagination-button"] a[page="' + ctrl.page + '"]').parent().addClass('active');
|
||||
}
|
||||
|
||||
function previous() {
|
||||
if(tc.canDecrement()) {
|
||||
tc.decrement();
|
||||
drawButtons(tc.getTime());
|
||||
togglePageButton();
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
}
|
||||
}
|
||||
|
||||
function gotoFirst() {
|
||||
ctrl.page = 1;
|
||||
tc.setTime(0);
|
||||
drawButtons(0);
|
||||
|
||||
toggleFirst();
|
||||
toggleLast();
|
||||
|
||||
togglePageButton();
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
}
|
||||
|
||||
function next() {
|
||||
if(tc.canIncrement()) {
|
||||
@ -188,7 +431,45 @@
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
}
|
||||
scope.$apply();
|
||||
}
|
||||
|
||||
function gotoLast() {
|
||||
ctrl.page = ctrl.buttonCount;
|
||||
tc.setTime(Math.ceil(ctrl.buttonCount / ctrl.displayCount) - 1);
|
||||
drawButtons(tc.getTime());
|
||||
|
||||
toggleFirst();
|
||||
toggleLast();
|
||||
|
||||
togglePageButton();
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
function next() {
|
||||
if(tc.canIncrement()) {
|
||||
tc.increment();
|
||||
drawButtons(tc.getTime());
|
||||
togglePageButton();
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
}
|
||||
}
|
||||
|
||||
function gotoLast() {
|
||||
ctrl.page = ctrl.buttonCount;
|
||||
tc.setTime(Math.ceil(ctrl.buttonCount / ctrl.displayCount) - 1);
|
||||
drawButtons(tc.getTime());
|
||||
|
||||
toggleFirst();
|
||||
toggleLast();
|
||||
|
||||
togglePageButton();
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<div class="well panel-group well-custom" style="margin-top: 10px; position: absolute; width: 98%;">
|
||||
<div class="row">
|
||||
<form name="form" class="css-form form-custom" novalidate>
|
||||
<form name="form" class="css-form form-custom" novalidate autocomplete="off">
|
||||
<div class="col-xs-10 col-md-10">
|
||||
<div class="form-group col-md-6">
|
||||
<input type="text" class="form-control" id="addUsername" placeholder="// 'username' | tr //" ng-model="pm.username" name="uUsername" ng-model-options="{ debounce: 250 }" ng-change="vm.reset()" required>
|
||||
|
@ -88,8 +88,10 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
addProjectMember.$inject = ['$timeout'];
|
||||
|
||||
function addProjectMember() {
|
||||
function addProjectMember($timeout) {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/project-member/add-project-member.directive.html',
|
||||
@ -109,6 +111,13 @@
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
scope.form.$setPristine();
|
||||
scope.form.$setUntouched();
|
||||
scope.$watch('vm.isOpen', function(current) {
|
||||
if(current) {
|
||||
$timeout(function() {
|
||||
element.find('[name=uUsername]:input').focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,9 @@
|
||||
|
||||
}
|
||||
|
||||
function listProjectMember() {
|
||||
listProjectMember.$inject = ['$timeout'];
|
||||
|
||||
function listProjectMember($timeout) {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/project-member/list-project-member.directive.html',
|
||||
@ -116,6 +118,12 @@
|
||||
element.find('#txtSearchInput').on('keydown', function(e) {
|
||||
if($(this).is(':focus') && e.keyCode === 13) {
|
||||
ctrl.retrieve();
|
||||
} else {
|
||||
$timeout(function() {
|
||||
if(ctrl.username.length === 0) {
|
||||
ctrl.retrieve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -12,8 +12,8 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<div class="well well-custom" style="position: absolute; margin-left: 10px; margin-top: 5px; width: 96%;">
|
||||
<form name="form" class="css-form form-custom" novalidate>
|
||||
<div class="well well-custom" style="position: absolute; margin-left: 10px; margin-top: 5px; width: 94%;">
|
||||
<form name="form" class="css-form form-custom" novalidate autocomplete="off">
|
||||
<div class="row">
|
||||
<div class="col-xs-10 col-md-10">
|
||||
<div class="form-group col-md-7">
|
||||
|
@ -97,18 +97,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addProject() {
|
||||
addProject.$inject = ['$timeout'];
|
||||
|
||||
function addProject($timeout) {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/project/add-project.directive.html',
|
||||
'controller': AddProjectController,
|
||||
'link': link,
|
||||
'scope' : {
|
||||
'isOpen': '='
|
||||
},
|
||||
'controllerAs': 'vm',
|
||||
'bindToController': true
|
||||
};
|
||||
return directive;
|
||||
return directive;
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
scope.$watch('vm.isOpen', function(current) {
|
||||
if(current) {
|
||||
$timeout(function() {
|
||||
element.find(':input[name=uProjectName]').focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<div class="modal fade" data-backdrop="static" id="createPolicyModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<form name="form" class="form-horizontal css-form" novalidate>
|
||||
<form name="form" class="form-horizontal css-form" novalidate autocomplete="off">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
@ -38,7 +38,7 @@
|
||||
<label for="name" class="col-md-3 control-label">// 'name' | tr //:</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control form-control-custom" id="name" ng-model="replication.policy.name" name="uName" required maxlength="20" ng-disabled="!vm.targetEditable">
|
||||
<div ng-messages="form.$submitted && form.uName.$error">
|
||||
<div class="error-message" ng-messages="form.$submitted && form.uName.$error">
|
||||
<span ng-message="required">// 'name_is_required' | tr //</span>
|
||||
<span ng-message="maxlength">// 'name_is_too_long' | tr //</span>
|
||||
</div>
|
||||
@ -48,7 +48,7 @@
|
||||
<label for="description" class="col-md-3 control-label">// 'description' | tr //:</label>
|
||||
<div class="col-md-9">
|
||||
<textarea class="form-control form-control-custom" id="description" ng-model="replication.policy.description" name="uDescription" ng-disabled="!vm.targetEditable"></textarea>
|
||||
<div ng-messages="form.$submitted && form.uDescription.$error">
|
||||
<div class="error-message" ng-messages="form.$submitted && form.uDescription.$error">
|
||||
<span ng-message="maxlength">// 'description_is_too_long' | tr //</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -79,7 +79,7 @@
|
||||
<label for="endpoint" class="col-md-3 control-label">// 'endpoint' | tr //:</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control form-control-custom" id="endpoint" ng-model="replication.destination.endpoint" name="uEndpoint" ng-value="vm.endpoint" placeholder="http://ip_address" required ng-disabled="!vm.targetEditable || !vm.checkedAddTarget">
|
||||
<div ng-messages="form.$submitted && form.uEndpoint.$error">
|
||||
<div class="error-message" ng-messages="form.$submitted && form.uEndpoint.$error">
|
||||
<span ng-message="required">// 'endpoint_is_required' | tr //</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -105,7 +105,7 @@
|
||||
<div class="form-group col-md-12 form-group-custom">
|
||||
<div class="col-md-3"></div>
|
||||
<div class="col-md-9">
|
||||
<span>// vm.pingMessage //</span>
|
||||
<span class="error-message" >// vm.pingMessage //</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -105,6 +105,12 @@
|
||||
}
|
||||
|
||||
function refreshReplicationJob() {
|
||||
if(vm.fromDate && vm.toDate && (getDateValue(vm.fromDate) > getDateValue(vm.toDate))) {
|
||||
$scope.$emit('modalTitle', $filter('tr')('error'));
|
||||
$scope.$emit('modalMessage', $filter('tr')('begin_date_is_later_than_end_date'));
|
||||
$scope.$emit('raiseError', true);
|
||||
return;
|
||||
}
|
||||
if(vm.lastPolicyId !== -1) {
|
||||
vm.refreshJobTIP = true;
|
||||
vm.retrieveJob(vm.lastPolicyId, vm.page, vm.pageSize);
|
||||
@ -288,8 +294,17 @@
|
||||
return t.getTime() / 1000;
|
||||
}
|
||||
|
||||
function getDateValue(date) {
|
||||
if(date) {
|
||||
return new Date(date);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
listReplication.inject = ['$timeout', 'I18nService'];
|
||||
|
||||
function listReplication($timeout, I18nService) {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
@ -398,12 +413,24 @@
|
||||
element.find('#txtSearchPolicyInput').on('keydown', function(e) {
|
||||
if($(this).is(':focus') && e.keyCode === 13) {
|
||||
ctrl.searchReplicationPolicy();
|
||||
} else {
|
||||
$timeout(function() {
|
||||
if(ctrl.replicationPolicyName.length === 0) {
|
||||
ctrl.searchReplicationPolicy();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
element.find('#txtSearchJobInput').on('keydown', function(e) {
|
||||
if($(this).is(':focus') && e.keyCode === 13) {
|
||||
ctrl.searchReplicationJob();
|
||||
} else {
|
||||
$timeout(function() {
|
||||
if(ctrl.replicationJobName.length === 0) {
|
||||
ctrl.searchReplicationJob();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -192,7 +192,9 @@
|
||||
|
||||
}
|
||||
|
||||
function listRepository() {
|
||||
listRepository.$inject = ['$timeout'];
|
||||
|
||||
function listRepository($timeout) {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/repository/list-repository.directive.html',
|
||||
@ -212,6 +214,12 @@
|
||||
element.find('#txtSearchInput').on('keydown', function(e) {
|
||||
if($(this).is(':focus') && e.keyCode === 13) {
|
||||
ctrl.retrieve();
|
||||
} else {
|
||||
$timeout(function() {
|
||||
if(ctrl.filterInput.length === 0) {
|
||||
ctrl.retrieve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -37,19 +37,26 @@
|
||||
|
||||
vm.signInTIP = false;
|
||||
|
||||
$scope.user = {};
|
||||
|
||||
function reset() {
|
||||
vm.hasError = false;
|
||||
vm.errorMessage = '';
|
||||
}
|
||||
|
||||
function doSignIn(user) {
|
||||
if(user && angular.isDefined(user.principal) && angular.isDefined(user.password)) {
|
||||
}
|
||||
|
||||
function doSignIn(user) {
|
||||
if(!$scope.user.principal || !$scope.user.password ||
|
||||
$scope.user.principal.length === 0 || $scope.user.password.length === 0) {
|
||||
vm.hasError = true;
|
||||
vm.errorMessage = 'username_and_password_are_required';
|
||||
}
|
||||
if(user.principal && user.password) {
|
||||
vm.lastUrl = getParameterByName('last_url', $location.absUrl());
|
||||
vm.signInTIP = true;
|
||||
SignInService(user.principal, user.password)
|
||||
.success(signedInSuccess)
|
||||
.error(signedInFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function signedInSuccess(data, status) {
|
||||
|
@ -12,7 +12,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<form name="form" class="form-horizontal" ng-submit="form.$valid && vm.changeSettings(system)" >
|
||||
<form name="form" class="form-horizontal" ng-submit="form.$valid && vm.changeSettings(system)" autocomplete="off">
|
||||
<div class="col-md-12">
|
||||
<h5>System Settings</h5>
|
||||
<hr/>
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<div class="modal fade" data-backdrop="static" id="createDestinationModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<form name="form" class="form-horizontal css-form" novalidate>
|
||||
<form name="form" class="form-horizontal css-form" novalidate autocomplete="off">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
@ -34,7 +34,7 @@
|
||||
<label for="name" class="col-md-3 control-label">// 'name' | tr //:</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control form-control-custom" id="name" ng-model="destination.name" name="uName" required ng-disabled="!vm.editable">
|
||||
<div ng-messages="form.$submitted && form.uName.$error">
|
||||
<div class="error-message" ng-messages="form.$submitted && form.uName.$error">
|
||||
<span ng-message="required">// 'name_is_required' | tr //</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -43,7 +43,7 @@
|
||||
<label for="description" class="col-md-3 control-label">// 'endpoint' | tr //:</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control form-control-custom" id="endpoint" ng-model="destination.endpoint" name="uEndpoint" required ng-disabled="!vm.editable">
|
||||
<div ng-messages="form.$submitted && form.uEndpoint.$error">
|
||||
<div class="error-message" ng-messages="form.$submitted && form.uEndpoint.$error">
|
||||
<span ng-message="required">// 'endpoint_is_required' | tr //</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -69,7 +69,7 @@
|
||||
<div class="form-group col-md-12 form-group-custom">
|
||||
<div class="col-md-3"></div>
|
||||
<div class="col-md-9">
|
||||
<span>// vm.pingMessage //</span>
|
||||
<span class="error-message">// vm.pingMessage //</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -103,7 +103,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
function destination() {
|
||||
destination.$inject = ['$timeout'];
|
||||
|
||||
function destination($timeout) {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/system-management/destination.directive.html',
|
||||
@ -119,6 +121,12 @@
|
||||
element.find('#txtSearchInput').on('keydown', function(e) {
|
||||
if($(this).is(':focus') && e.keyCode === 13) {
|
||||
ctrl.retrieve();
|
||||
} else {
|
||||
$timeout(function() {
|
||||
if(ctrl.destinationName.length === 0) {
|
||||
ctrl.retrieve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -106,7 +106,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
function replication() {
|
||||
replication.$inject = ['$timeout'];
|
||||
|
||||
function replication($timeout) {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/system-management/replication.directive.html',
|
||||
@ -122,6 +124,12 @@
|
||||
element.find('#txtSearchInput').on('keydown', function(e) {
|
||||
if($(this).is(':focus') && e.keyCode === 13) {
|
||||
ctrl.retrieve();
|
||||
} else {
|
||||
$timeout(function() {
|
||||
if(ctrl.replicationName.length === 0) {
|
||||
ctrl.retrieve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -55,7 +55,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-4 col-md-12 well well-sm well-custom">
|
||||
<div class="col-md-offset-10">//vm.users ? vm.users.length : 0// items</div>
|
||||
<div class="col-md-offset-10">//vm.users ? vm.users.length : 0// // 'items' | tr //</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -32,9 +32,7 @@
|
||||
vm.searchUser = searchUser;
|
||||
vm.deleteUser = deleteUser;
|
||||
vm.confirmToDelete = confirmToDelete;
|
||||
vm.retrieve = retrieve;
|
||||
|
||||
vm.currentUser = currentUser.get();
|
||||
vm.retrieve = retrieve;
|
||||
|
||||
vm.retrieve();
|
||||
|
||||
@ -82,6 +80,7 @@
|
||||
}
|
||||
|
||||
function listUserSuccess(data, status) {
|
||||
vm.currentUser = currentUser.get();
|
||||
vm.users = data;
|
||||
}
|
||||
|
||||
@ -93,7 +92,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
function listUser() {
|
||||
listUser.$inject = ['$timeout'];
|
||||
|
||||
function listUser($timeout) {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/user/list-user.directive.html',
|
||||
@ -111,6 +112,12 @@
|
||||
element.find('#txtSearchInput').on('keydown', function(e) {
|
||||
if($(this).is(':focus') && e.keyCode === 13) {
|
||||
ctrl.retrieve();
|
||||
} else {
|
||||
$timeout(function() {
|
||||
if(ctrl.username.length === 0) {
|
||||
ctrl.retrieve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|