50
.travis.yml
@ -9,24 +9,26 @@ go_import_path: github.com/vmware/harbor
|
||||
|
||||
services:
|
||||
- docker
|
||||
- mysql
|
||||
|
||||
dist: trusty
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- mysql-server-5.6
|
||||
- mysql-client-core-5.6
|
||||
- mysql-client-5.6
|
||||
|
||||
env:
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_PORT: 3306
|
||||
DB_USR: root
|
||||
DB_PWD:
|
||||
DB_PWD: root123
|
||||
MYSQL_HOST: localhost
|
||||
MYSQL_PORT: 3306
|
||||
MYSQL_USR: root
|
||||
MYSQL_PWD: root123
|
||||
DOCKER_COMPOSE_VERSION: 1.7.1
|
||||
HARBOR_ADMIN: admin
|
||||
HARBOR_ADMIN_PASSWD: Harbor12345
|
||||
UI_SECRET: tempString
|
||||
MAX_JOB_WORKERS: 3
|
||||
SECRET_KEY: 1234567890123456
|
||||
AUTH_MODE: db_auth
|
||||
SELF_REGISTRATION: "on"
|
||||
|
||||
before_install:
|
||||
- sudo ./tests/hostcfg.sh
|
||||
@ -53,32 +55,42 @@ install:
|
||||
- go get -d github.com/go-sql-driver/mysql
|
||||
- go get github.com/golang/lint/golint
|
||||
- go get github.com/GeertJohan/fgt
|
||||
# - sudo apt-get install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" docker-engine=1.11.1-0~trusty
|
||||
|
||||
# - sudo rm /usr/local/bin/docker-compose
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||
- chmod +x docker-compose
|
||||
- sudo mv docker-compose /usr/local/bin
|
||||
- sudo sed -i '$a DOCKER_OPTS=\"$DOCKER_OPTS --insecure-registry 127.0.0.1\"' /etc/default/docker
|
||||
- IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
|
||||
- sudo sed -i '$a DOCKER_OPTS=\"--insecure-registry '$IP':5000\"' /etc/default/docker
|
||||
- sudo service docker restart
|
||||
- go get github.com/dghubble/sling
|
||||
- go get github.com/stretchr/testify
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
|
||||
before_script:
|
||||
# create tables and load data
|
||||
- mysql < ./Deploy/db/registry.sql -uroot --verbose
|
||||
# - mysql < ./Deploy/db/registry.sql -uroot --verbose
|
||||
|
||||
script:
|
||||
- go list ./... | grep -v 'tests' | grep -v /vendor/ | xargs -L1 fgt golint
|
||||
- go list ./... | grep -v 'tests' | grep -v 'vendor' | xargs -L1 go vet
|
||||
- go list ./... | grep -v 'tests' | grep -v 'vendor' | xargs -L1 go test -v
|
||||
script:
|
||||
- sudo ./tests/testprepare.sh
|
||||
- docker-compose -f Deploy/docker-compose.test.yml up -d
|
||||
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 fgt golint
|
||||
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 go vet
|
||||
- export MYSQL_HOST=$IP
|
||||
- export REGISTRY_URL=$IP:5000
|
||||
- echo $REGISTRY_URL
|
||||
- ./tests/pushimage.sh
|
||||
- ./Deploy/coverage4gotest.sh
|
||||
- goveralls -coverprofile=profile.cov -service=travis-ci
|
||||
|
||||
- docker-compose -f Deploy/docker-compose.test.yml down
|
||||
|
||||
- docker-compose -f Deploy/docker-compose.yml up -d
|
||||
|
||||
- docker ps
|
||||
- go run tests/startuptest.go http://localhost/
|
||||
- go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD}
|
||||
|
||||
|
||||
# test for API
|
||||
- sudo ./tests/testprepare.sh
|
||||
- go test -v ./tests/apitests
|
||||
# - sudo ./tests/testprepare.sh
|
||||
# - go test -v ./tests/apitests
|
||||
|
50
CHANGELOG.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Changelog
|
||||
|
||||
## v0.4.0 (2016-09-23)
|
||||
|
||||
- Database schema changed, data migration/upgrade is needed for previous version.
|
||||
- A project can be deleted when no images and policies are under it.
|
||||
- Deleted users can be recreated.
|
||||
- Replication policy can be deleted.
|
||||
- Enhanced LDAP authentication, allowing multiple uid attributes.
|
||||
- Pagination in UI.
|
||||
- Improved authentication for remote image replication.
|
||||
- Display release version in UI
|
||||
- Offline installer.
|
||||
- Various bug fixes.
|
||||
|
||||
## v0.3.5 (2016-08-13)
|
||||
|
||||
- Vendoring all dependencies and remove go get from dockerfile
|
||||
- Installer using Docker Hub to download images
|
||||
- Harbor base images moved to Photon OS (except for official images from third party)
|
||||
- New Harbor logo
|
||||
- Various bug fixes
|
||||
|
||||
## v0.3.0 (2016-07-15)
|
||||
|
||||
- Database schema changed, data migration/upgrade is needed for previous version.
|
||||
- New UI
|
||||
- Image replication across multiple registry instances
|
||||
- Integration with registry v2.4.0 to support image deletion and garbage collection
|
||||
- Database migration tool
|
||||
- Bug fixes
|
||||
|
||||
|
||||
## v0.1.1 (2016-04-08)
|
||||
|
||||
- Refactored database schema
|
||||
- Migrate to docker-compose v2 template
|
||||
- Update token service to support layer mount
|
||||
- Various bug fixes
|
||||
|
||||
## v0.1.0 (2016-03-11)
|
||||
|
||||
Initial release, key features include
|
||||
|
||||
- Role based access control (RBAC)
|
||||
- LDAP / AD integration
|
||||
- Graphical user interface (GUI)
|
||||
- Auditting and logging
|
||||
- RESTful API
|
||||
- Internationalization
|
35
Deploy/coverage4gotest.sh
Executable file
@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
echo "mode: set" >>profile.cov
|
||||
|
||||
deps=""
|
||||
|
||||
# listDeps lists packages referenced by package in $1,
|
||||
# excluding golang standard library and packages in
|
||||
# direcotry vendor
|
||||
function listDeps(){
|
||||
pkg=$1
|
||||
deps=$pkg
|
||||
ds=$(echo $(go list -f '{{.Imports}}' $pkg) | sed 's/[][]//g')
|
||||
for d in $ds
|
||||
do
|
||||
if echo $d | grep -q "github.com/vmware/harbor" && echo $d | grep -qv "vendor"
|
||||
then
|
||||
deps="$deps,$d"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
packages=$(go list ./... | grep -v -E 'vendor|tests')
|
||||
|
||||
for package in $packages
|
||||
do
|
||||
listDeps $package
|
||||
|
||||
go test -cover -coverprofile=profile.tmp -coverpkg "$deps" $package
|
||||
if [ -f profile.tmp ]
|
||||
then
|
||||
cat profile.tmp | tail -n +2 >> profile.cov
|
||||
rm profile.tmp
|
||||
fi
|
||||
done
|
@ -38,8 +38,14 @@ insert into role (role_code, name) values
|
||||
|
||||
create table user (
|
||||
user_id int NOT NULL AUTO_INCREMENT,
|
||||
username varchar(15),
|
||||
email varchar(128),
|
||||
# The max length of username controlled by API is 20,
|
||||
# and 11 is reserved for marking the deleted users.
|
||||
# The mark of deleted user is "#user_id".
|
||||
# The 11 consist of 10 for the max value of user_id(4294967295)
|
||||
# in MySQL and 1 of '#'.
|
||||
username varchar(32),
|
||||
# 11 bytes is reserved for marking the deleted users.
|
||||
email varchar(255),
|
||||
password varchar(40) NOT NULL,
|
||||
realname varchar (20) NOT NULL,
|
||||
comment varchar (30),
|
||||
@ -61,7 +67,9 @@ insert into user (username, email, password, realname, comment, deleted, sysadmi
|
||||
create table project (
|
||||
project_id int NOT NULL AUTO_INCREMENT,
|
||||
owner_id int NOT NULL,
|
||||
name varchar (30) NOT NULL,
|
||||
# The max length of name controlled by API is 30,
|
||||
# and 11 is reserved for marking the deleted project.
|
||||
name varchar (41) NOT NULL,
|
||||
creation_time timestamp,
|
||||
update_time timestamp,
|
||||
deleted tinyint (1) DEFAULT 0 NOT NULL,
|
||||
@ -99,10 +107,25 @@ create table access_log (
|
||||
operation varchar(20) NOT NULL,
|
||||
op_time timestamp,
|
||||
primary key (log_id),
|
||||
INDEX pid_optime (project_id, op_time),
|
||||
FOREIGN KEY (user_id) REFERENCES user(user_id),
|
||||
FOREIGN KEY (project_id) REFERENCES project (project_id)
|
||||
);
|
||||
|
||||
create table repository (
|
||||
repository_id int NOT NULL AUTO_INCREMENT,
|
||||
name varchar(255) NOT NULL,
|
||||
project_id int NOT NULL,
|
||||
owner_id int NOT NULL,
|
||||
description text,
|
||||
pull_count int DEFAULT 0 NOT NULL,
|
||||
star_count int DEFAULT 0 NOT NULL,
|
||||
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||
update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
|
||||
primary key (repository_id),
|
||||
UNIQUE (name)
|
||||
);
|
||||
|
||||
create table replication_policy (
|
||||
id int NOT NULL AUTO_INCREMENT,
|
||||
name varchar(256),
|
||||
@ -110,6 +133,7 @@ create table replication_policy (
|
||||
target_id int NOT NULL,
|
||||
enabled tinyint(1) NOT NULL DEFAULT 1,
|
||||
description text,
|
||||
deleted tinyint (1) DEFAULT 0 NOT NULL,
|
||||
cron_str varchar(256),
|
||||
start_time timestamp NULL,
|
||||
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||
@ -122,7 +146,7 @@ create table replication_target (
|
||||
name varchar(64),
|
||||
url varchar(64),
|
||||
username varchar(40),
|
||||
password varchar(40),
|
||||
password varchar(128),
|
||||
/*
|
||||
target_type indicates the type of target registry,
|
||||
0 means it's a harbor instance,
|
||||
@ -144,7 +168,8 @@ create table replication_job (
|
||||
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||
update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
INDEX policy (policy_id)
|
||||
INDEX policy (policy_id),
|
||||
INDEX poid_uptime (policy_id, update_time)
|
||||
);
|
||||
|
||||
create table properties (
|
||||
@ -157,4 +182,4 @@ CREATE TABLE IF NOT EXISTS `alembic_version` (
|
||||
`version_num` varchar(32) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
insert into alembic_version values ('0.3.0');
|
||||
insert into alembic_version values ('0.4.0');
|
||||
|
@ -16,7 +16,9 @@ email_password = abc
|
||||
email_from = admin <sample_admin@mydomain.com>
|
||||
email_ssl = false
|
||||
|
||||
##The password of Harbor admin, change this before any production use.
|
||||
##The initial password of Harbor admin, 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.
|
||||
harbor_admin_password = Harbor12345
|
||||
|
||||
##By default the auth mode is db_auth, i.e. the credentials are stored in a local database.
|
||||
@ -26,10 +28,24 @@ auth_mode = db_auth
|
||||
#The url for an ldap endpoint.
|
||||
ldap_url = ldaps://ldap.mydomain.com
|
||||
|
||||
#The basedn template to look up a user in LDAP and verify the user's password.
|
||||
#For AD server, uses this template:
|
||||
#ldap_basedn = CN=%s,OU=Dept1,DC=mydomain,DC=com
|
||||
ldap_basedn = uid=%s,ou=people,dc=mydomain,dc=com
|
||||
#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_search_pwd.
|
||||
#ldap_searchdn = uid=searchuser,ou=people,dc=mydomain,dc=com
|
||||
|
||||
#the password of the ldap_searchdn
|
||||
#ldap_search_pwd = password
|
||||
|
||||
#The base DN from which to look up a user in LDAP/AD
|
||||
ldap_basedn = ou=people,dc=mydomain,dc=com
|
||||
|
||||
#Search filter for LDAP/AD, make sure the syntax of the filter is correct.
|
||||
#ldap_filter = (objectClass=person)
|
||||
|
||||
# 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
|
||||
ldap_uid = uid
|
||||
|
||||
#the scope to search for users, 1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE
|
||||
ldap_scope = 3
|
||||
|
||||
#The password for the root user of mysql db, change this before any production use.
|
||||
db_password = root123
|
||||
@ -44,7 +60,12 @@ use_compressed_js = on
|
||||
#Maximum number of job workers in job service
|
||||
max_job_workers = 3
|
||||
|
||||
#The expiration of token used by token service, default is 30 minutes
|
||||
#Secret key for encryption/decryption of password of remote registry, its length has to be 16 chars
|
||||
#**NOTE** if this changes, previously encrypted password will not be decrypted!
|
||||
#Change this key before any production use.
|
||||
secret_key = secretkey1234567
|
||||
|
||||
#The expiration time (in minute) of token created by token service, default is 30 minutes
|
||||
token_expiration = 30
|
||||
|
||||
#Determine whether the job service should verify the ssl cert when it connects to a remote registry.
|
||||
|
@ -1,12 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
echo "This shell will minify the Javascript in Harbor project."
|
||||
echo "Usage: #jsminify [src] [dest]"
|
||||
echo "Usage: #jsminify [src] [dest] [basedir]"
|
||||
|
||||
#prepare workspace
|
||||
rm -rf $2 /tmp/harbor.app.temp.js
|
||||
|
||||
BASEPATH=/go/bin
|
||||
if [ -z $3 ]
|
||||
then
|
||||
BASEPATH=/go/bin
|
||||
else
|
||||
BASEPATH=$3
|
||||
fi
|
||||
|
||||
#concat the js files from js include file
|
||||
echo "Concat js files..."
|
||||
|
||||
@ -20,6 +26,12 @@ do
|
||||
fi
|
||||
done
|
||||
|
||||
# If you want run this script on Mac OS X,
|
||||
# I suggest you install gnu-sed (whth --with-default-names option).
|
||||
# $ brew install gnu-sed --with-default-names
|
||||
# Reference:
|
||||
# http://stackoverflow.com/a/27834828/3167471
|
||||
|
||||
#remove space
|
||||
echo "Remove space.."
|
||||
sed 's/ \+/ /g' -i /tmp/harbor.app.temp.js
|
||||
|
@ -7,7 +7,7 @@ import string
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import commands
|
||||
import subprocess
|
||||
from io import open
|
||||
|
||||
if sys.version_info[:3][0] == 2:
|
||||
@ -18,6 +18,10 @@ if sys.version_info[:3][0] == 3:
|
||||
import configparser as ConfigParser
|
||||
import io as StringIO
|
||||
|
||||
def validate(conf):
|
||||
if len(conf.get("configuration", "secret_key")) != 16:
|
||||
raise Exception("Error: The length of secret key has to be 16 characters!")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-conf', dest='cfgfile', default = 'harbor.cfg',type=str,help="the path of Harbor configuration file")
|
||||
args = parser.parse_args()
|
||||
@ -30,6 +34,8 @@ conf.seek(0, os.SEEK_SET)
|
||||
rcp = ConfigParser.RawConfigParser()
|
||||
rcp.readfp(conf)
|
||||
|
||||
validate(rcp)
|
||||
|
||||
hostname = rcp.get("configuration", "hostname")
|
||||
ui_url = rcp.get("configuration", "ui_url_protocol") + "://" + hostname
|
||||
email_server = rcp.get("configuration", "email_server")
|
||||
@ -41,7 +47,21 @@ email_ssl = rcp.get("configuration", "email_ssl")
|
||||
harbor_admin_password = rcp.get("configuration", "harbor_admin_password")
|
||||
auth_mode = rcp.get("configuration", "auth_mode")
|
||||
ldap_url = rcp.get("configuration", "ldap_url")
|
||||
# this two options are either both set or unset
|
||||
if rcp.has_option("configuration", "ldap_searchdn"):
|
||||
ldap_searchdn = rcp.get("configuration", "ldap_searchdn")
|
||||
ldap_search_pwd = rcp.get("configuration", "ldap_search_pwd")
|
||||
else:
|
||||
ldap_searchdn = ""
|
||||
ldap_search_pwd = ""
|
||||
ldap_basedn = rcp.get("configuration", "ldap_basedn")
|
||||
# ldap_filter is null by default
|
||||
if rcp.has_option("configuration", "ldap_filter"):
|
||||
ldap_filter = rcp.get("configuration", "ldap_filter")
|
||||
else:
|
||||
ldap_filter = ""
|
||||
ldap_uid = rcp.get("configuration", "ldap_uid")
|
||||
ldap_scope = rcp.get("configuration", "ldap_scope")
|
||||
db_password = rcp.get("configuration", "db_password")
|
||||
self_registration = rcp.get("configuration", "self_registration")
|
||||
use_compressed_js = rcp.get("configuration", "use_compressed_js")
|
||||
@ -56,16 +76,9 @@ 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")
|
||||
secret_key = rcp.get("configuration", "secret_key")
|
||||
########
|
||||
|
||||
#Read version form .git
|
||||
status, output = commands.getstatusoutput('git describe --tags')
|
||||
if status == 0:
|
||||
version = output
|
||||
else:
|
||||
version = 'UNKNOWN'
|
||||
#######
|
||||
|
||||
ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
||||
|
||||
base_dir = os.path.dirname(__file__)
|
||||
@ -112,13 +125,18 @@ render(os.path.join(templates_dir, "ui", "env"),
|
||||
auth_mode=auth_mode,
|
||||
harbor_admin_password=harbor_admin_password,
|
||||
ldap_url=ldap_url,
|
||||
ldap_searchdn =ldap_searchdn,
|
||||
ldap_search_pwd =ldap_search_pwd,
|
||||
ldap_basedn=ldap_basedn,
|
||||
ldap_filter=ldap_filter,
|
||||
ldap_uid=ldap_uid,
|
||||
ldap_scope=ldap_scope,
|
||||
self_registration=self_registration,
|
||||
use_compressed_js=use_compressed_js,
|
||||
ui_secret=ui_secret,
|
||||
secret_key=secret_key,
|
||||
verify_remote_cert=verify_remote_cert,
|
||||
token_expiration=token_expiration,
|
||||
version=version)
|
||||
token_expiration=token_expiration)
|
||||
|
||||
render(os.path.join(templates_dir, "ui", "app.conf"),
|
||||
ui_conf,
|
||||
@ -143,6 +161,7 @@ render(os.path.join(templates_dir, "jobservice", "env"),
|
||||
db_password=db_password,
|
||||
ui_secret=ui_secret,
|
||||
max_job_workers=max_job_workers,
|
||||
secret_key=secret_key,
|
||||
ui_url=ui_url,
|
||||
verify_remote_cert=verify_remote_cert)
|
||||
|
||||
@ -188,7 +207,6 @@ def openssl_is_installed(stat):
|
||||
return False
|
||||
|
||||
if customize_crt == 'on':
|
||||
import subprocess
|
||||
shell_stat = subprocess.check_call(["which", "openssl"], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
if openssl_is_installed(shell_stat):
|
||||
private_key_pem = os.path.join(config_dir, "ui", "private_key.pem")
|
||||
|
@ -3,6 +3,7 @@ MYSQL_PORT=3306
|
||||
MYSQL_USR=root
|
||||
MYSQL_PWD=$db_password
|
||||
UI_SECRET=$ui_secret
|
||||
SECRET_KEY=$secret_key
|
||||
CONFIG_PATH=/etc/jobservice/app.conf
|
||||
REGISTRY_URL=http://registry:5000
|
||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||
|
@ -10,8 +10,14 @@ HARBOR_ADMIN_PASSWORD=$harbor_admin_password
|
||||
HARBOR_URL=$ui_url
|
||||
AUTH_MODE=$auth_mode
|
||||
LDAP_URL=$ldap_url
|
||||
LDAP_SEARCH_DN=$ldap_searchdn
|
||||
LDAP_SEARCH_PWD=$ldap_search_pwd
|
||||
LDAP_BASE_DN=$ldap_basedn
|
||||
LDAP_FILTER=$ldap_filter
|
||||
LDAP_UID=$ldap_uid
|
||||
LDAP_SCOPE=$ldap_scope
|
||||
UI_SECRET=$ui_secret
|
||||
SECRET_KEY=$secret_key
|
||||
SELF_REGISTRATION=$self_registration
|
||||
USE_COMPRESSED_JS=$use_compressed_js
|
||||
LOG_LEVEL=debug
|
||||
@ -20,4 +26,3 @@ EXT_ENDPOINT=$ui_url
|
||||
TOKEN_URL=http://ui
|
||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||
TOKEN_EXPIRATION=$token_expiration
|
||||
VERSION=$version
|
||||
|
21
NOTICE
@ -1,11 +1,10 @@
|
||||
Harbor 0.1.0 Beta
|
||||
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||
You may not use this product except in compliance with the License.
|
||||
|
||||
This product may include a number of subcomponents with
|
||||
separate copyright notices and license terms. Your use of the source
|
||||
code for the these subcomponents is subject to the terms and
|
||||
conditions of the subcomponent's license, as noted in the LICENSE file.
|
||||
NOTICE
|
||||
|
||||
Harbor version 0.4.0 Beta
|
||||
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0 (the "License"). You may not use this product except in compliance with the License.
|
||||
|
||||
This product may include a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file.
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Harbor
|
||||
|
||||
[![Build Status](https://travis-ci.org/vmware/harbor.svg?branch=master)](https://travis-ci.org/vmware/harbor)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/vmware/harbor/badge.svg?branch=dev)](https://coveralls.io/github/vmware/harbor?branch=dev)
|
||||
|
||||
<img alt="Harbor" src="docs/img/harbor_logo.png">
|
||||
|
||||
|
9
api/api_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
59
api/base.go
@ -31,6 +31,11 @@ import (
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPageSize int64 = 10
|
||||
maxPageSize int64 = 100
|
||||
)
|
||||
|
||||
// BaseAPI wraps common methods for controllers to host API
|
||||
type BaseAPI struct {
|
||||
beego.Controller
|
||||
@ -151,6 +156,60 @@ func (b *BaseAPI) GetIDFromURL() int64 {
|
||||
return id
|
||||
}
|
||||
|
||||
// set "Link" and "X-Total-Count" header for pagination request
|
||||
func (b *BaseAPI) setPaginationHeader(total, page, pageSize int64) {
|
||||
b.Ctx.ResponseWriter.Header().Set("X-Total-Count", strconv.FormatInt(total, 10))
|
||||
|
||||
link := ""
|
||||
|
||||
// set previous link
|
||||
if page > 1 && (page-1)*pageSize <= total {
|
||||
u := *(b.Ctx.Request.URL)
|
||||
q := u.Query()
|
||||
q.Set("page", strconv.FormatInt(page-1, 10))
|
||||
u.RawQuery = q.Encode()
|
||||
if len(link) != 0 {
|
||||
link += ", "
|
||||
}
|
||||
link += fmt.Sprintf("<%s>; rel=\"prev\"", u.String())
|
||||
}
|
||||
|
||||
// set next link
|
||||
if pageSize*page < total {
|
||||
u := *(b.Ctx.Request.URL)
|
||||
q := u.Query()
|
||||
q.Set("page", strconv.FormatInt(page+1, 10))
|
||||
u.RawQuery = q.Encode()
|
||||
if len(link) != 0 {
|
||||
link += ", "
|
||||
}
|
||||
link += fmt.Sprintf("<%s>; rel=\"next\"", u.String())
|
||||
}
|
||||
|
||||
if len(link) != 0 {
|
||||
b.Ctx.ResponseWriter.Header().Set("Link", link)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BaseAPI) getPaginationParams() (page, pageSize int64) {
|
||||
page, err := b.GetInt64("page", 1)
|
||||
if err != nil || page <= 0 {
|
||||
b.CustomAbort(http.StatusBadRequest, "invalid page")
|
||||
}
|
||||
|
||||
pageSize, err = b.GetInt64("page_size", defaultPageSize)
|
||||
if err != nil || pageSize <= 0 {
|
||||
b.CustomAbort(http.StatusBadRequest, "invalid page_size")
|
||||
}
|
||||
|
||||
if pageSize > maxPageSize {
|
||||
pageSize = maxPageSize
|
||||
log.Debugf("the parameter page_size %d exceeds the max %d, set it to max", pageSize, maxPageSize)
|
||||
}
|
||||
|
||||
return page, pageSize
|
||||
}
|
||||
|
||||
func getIsInsecure() bool {
|
||||
insecure := false
|
||||
|
||||
|
106
api/dataprepare_test.go
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
//Prepare Test info
|
||||
TestUserName = "testUser0001"
|
||||
TestUserPwd = "testUser0001"
|
||||
TestUserEmail = "testUser0001@mydomain.com"
|
||||
TestProName = "testProject0001"
|
||||
TestTargetName = "testTarget0001"
|
||||
)
|
||||
|
||||
func CommonAddUser() {
|
||||
|
||||
commonUser := models.User{
|
||||
Username: TestUserName,
|
||||
Email: TestUserPwd,
|
||||
Password: TestUserEmail,
|
||||
}
|
||||
|
||||
_, _ = dao.Register(commonUser)
|
||||
|
||||
}
|
||||
|
||||
func CommonGetUserID() int {
|
||||
queryUser := &models.User{
|
||||
Username: TestUserName,
|
||||
}
|
||||
commonUser, _ := dao.GetUser(*queryUser)
|
||||
return commonUser.UserID
|
||||
}
|
||||
|
||||
func CommonDelUser() {
|
||||
queryUser := &models.User{
|
||||
Username: TestUserName,
|
||||
}
|
||||
commonUser, _ := dao.GetUser(*queryUser)
|
||||
_ = dao.DeleteUser(commonUser.UserID)
|
||||
|
||||
}
|
||||
|
||||
func CommonAddProject() {
|
||||
|
||||
queryUser := &models.User{
|
||||
Username: "admin",
|
||||
}
|
||||
adminUser, _ := dao.GetUser(*queryUser)
|
||||
commonProject := &models.Project{
|
||||
Name: TestProName,
|
||||
OwnerID: adminUser.UserID,
|
||||
}
|
||||
|
||||
_, _ = dao.AddProject(*commonProject)
|
||||
|
||||
}
|
||||
|
||||
func CommonDelProject() {
|
||||
commonProject, _ := dao.GetProjectByName(TestProName)
|
||||
|
||||
_ = dao.DeleteProject(commonProject.ProjectID)
|
||||
}
|
||||
|
||||
func CommonAddTarget() {
|
||||
endPoint := os.Getenv("REGISTRY_URL")
|
||||
commonTarget := &models.RepTarget{
|
||||
URL: endPoint,
|
||||
Name: TestTargetName,
|
||||
Username: adminName,
|
||||
Password: adminPwd,
|
||||
}
|
||||
_, _ = dao.AddRepTarget(*commonTarget)
|
||||
}
|
||||
|
||||
func CommonGetTarget() int {
|
||||
target, _ := dao.GetRepTargetByName(TestTargetName)
|
||||
return int(target.ID)
|
||||
}
|
||||
|
||||
func CommonDelTarget() {
|
||||
target, _ := dao.GetRepTargetByName(TestTargetName)
|
||||
_ = dao.DeleteRepTarget(target.ID)
|
||||
}
|
||||
|
||||
func CommonPolicyEabled(policyID int, enabled int) {
|
||||
_ = dao.UpdateRepPolicyEnablement(int64(policyID), enabled)
|
||||
}
|
853
api/harborapi_test.go
Normal file
@ -0,0 +1,853 @@
|
||||
//These APIs provide services for manipulating Harbor project.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
"io/ioutil"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/dghubble/sling"
|
||||
|
||||
//for test env prepare
|
||||
_ "github.com/vmware/harbor/auth/db"
|
||||
_ "github.com/vmware/harbor/auth/ldap"
|
||||
)
|
||||
|
||||
const (
|
||||
jsonAcceptHeader = "application/json"
|
||||
testAcceptHeader = "text/plain"
|
||||
adminName = "admin"
|
||||
adminPwd = "Harbor12345"
|
||||
)
|
||||
|
||||
var admin, unknownUsr, testUser *usrInfo
|
||||
|
||||
type api struct {
|
||||
basePath string
|
||||
}
|
||||
|
||||
func newHarborAPI() *api {
|
||||
return &api{
|
||||
basePath: "",
|
||||
}
|
||||
}
|
||||
|
||||
func newHarborAPIWithBasePath(basePath string) *api {
|
||||
return &api{
|
||||
basePath: basePath,
|
||||
}
|
||||
}
|
||||
|
||||
type usrInfo struct {
|
||||
Name string
|
||||
Passwd string
|
||||
}
|
||||
|
||||
func init() {
|
||||
dao.InitDB()
|
||||
_, file, _, _ := runtime.Caller(1)
|
||||
apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".."+string(filepath.Separator))))
|
||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||
beego.TestBeegoInit(apppath)
|
||||
|
||||
beego.Router("/api/search/", &SearchAPI{})
|
||||
beego.Router("/api/projects/", &ProjectAPI{}, "get:List;post:Post;head:Head")
|
||||
beego.Router("/api/projects/:id", &ProjectAPI{}, "delete:Delete;get:Get")
|
||||
beego.Router("/api/users/?:id", &UserAPI{})
|
||||
beego.Router("/api/users/:id([0-9]+)/password", &UserAPI{}, "put:ChangePassword")
|
||||
beego.Router("/api/users/:id/sysadmin", &UserAPI{}, "put:ToggleUserAdminRole")
|
||||
beego.Router("/api/projects/:id/publicity", &ProjectAPI{}, "put:ToggleProjectPublic")
|
||||
beego.Router("/api/projects/:id([0-9]+)/logs/filter", &ProjectAPI{}, "post:FilterAccessLog")
|
||||
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &ProjectMemberAPI{}, "get:Get;post:Post;delete:Delete;put:Put")
|
||||
beego.Router("/api/statistics", &StatisticAPI{})
|
||||
beego.Router("/api/users/?:id", &UserAPI{})
|
||||
beego.Router("/api/logs", &LogAPI{})
|
||||
beego.Router("/api/repositories", &RepositoryAPI{})
|
||||
beego.Router("/api/repositories/tags", &RepositoryAPI{}, "get:GetTags")
|
||||
beego.Router("/api/repositories/manifests", &RepositoryAPI{}, "get:GetManifests")
|
||||
beego.Router("/api/repositories/top", &RepositoryAPI{}, "get:GetTopRepos")
|
||||
beego.Router("/api/targets/", &TargetAPI{}, "get:List")
|
||||
beego.Router("/api/targets/", &TargetAPI{}, "post:Post")
|
||||
beego.Router("/api/targets/:id([0-9]+)", &TargetAPI{})
|
||||
beego.Router("/api/targets/:id([0-9]+)/policies/", &TargetAPI{}, "get:ListPolicies")
|
||||
beego.Router("/api/targets/ping", &TargetAPI{}, "post:Ping")
|
||||
beego.Router("/api/policies/replication/:id([0-9]+)", &RepPolicyAPI{})
|
||||
beego.Router("/api/policies/replication", &RepPolicyAPI{}, "get:List")
|
||||
beego.Router("/api/policies/replication", &RepPolicyAPI{}, "post:Post;delete:Delete")
|
||||
beego.Router("/api/policies/replication/:id([0-9]+)/enablement", &RepPolicyAPI{}, "put:UpdateEnablement")
|
||||
|
||||
_ = updateInitPassword(1, "Harbor12345")
|
||||
|
||||
//Init user Info
|
||||
admin = &usrInfo{adminName, adminPwd}
|
||||
unknownUsr = &usrInfo{"unknown", "unknown"}
|
||||
testUser = &usrInfo{TestUserName, TestUserPwd}
|
||||
|
||||
}
|
||||
|
||||
func request(_sling *sling.Sling, acceptHeader string, authInfo ...usrInfo) (int, []byte, error) {
|
||||
_sling = _sling.Set("Accept", acceptHeader)
|
||||
req, err := _sling.Request()
|
||||
if err != nil {
|
||||
return 400, nil, err
|
||||
}
|
||||
if len(authInfo) > 0 {
|
||||
req.SetBasicAuth(authInfo[0].Name, authInfo[0].Passwd)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, req)
|
||||
body, err := ioutil.ReadAll(w.Body)
|
||||
return w.Code, body, err
|
||||
}
|
||||
|
||||
//Search for projects and repositories
|
||||
//Implementation Notes
|
||||
//The Search endpoint returns information about the projects and repositories
|
||||
//offered at public status or related to the current logged in user.
|
||||
//The response includes the project and repository list in a proper display order.
|
||||
//@param q Search parameter for project and repository name.
|
||||
//@return []Search
|
||||
//func (a api) SearchGet (q string) (apilib.Search, error) {
|
||||
func (a api) SearchGet(q string) (apilib.Search, error) {
|
||||
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
// create path and map variables
|
||||
path := "/api/search"
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
type QueryParams struct {
|
||||
Query string `url:"q,omitempty"`
|
||||
}
|
||||
|
||||
_sling = _sling.QueryStruct(&QueryParams{Query: q})
|
||||
|
||||
_, body, err := request(_sling, jsonAcceptHeader)
|
||||
var successPayload = new(apilib.Search)
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
return *successPayload, err
|
||||
}
|
||||
|
||||
//Create a new project.
|
||||
//Implementation Notes
|
||||
//This endpoint is for user to create a new project.
|
||||
//@param project New created project.
|
||||
//@return void
|
||||
//func (a api) ProjectsPost (prjUsr usrInfo, project apilib.Project) (int, error) {
|
||||
func (a api) ProjectsPost(prjUsr usrInfo, project apilib.ProjectReq) (int, error) {
|
||||
|
||||
_sling := sling.New().Post(a.basePath)
|
||||
|
||||
// create path and map variables
|
||||
path := "/api/projects/"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
// body params
|
||||
_sling = _sling.BodyJSON(project)
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
func (a api) StatisticGet(user usrInfo) (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 {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
return *successPayload, err
|
||||
}
|
||||
|
||||
func (a api) LogGet(user usrInfo, startTime, endTime, lines string) (int, []apilib.AccessLog, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
// create path and map variables
|
||||
path := "/api/logs/"
|
||||
fmt.Printf("logs path: %s\n", path)
|
||||
_sling = _sling.Path(path)
|
||||
type QueryParams struct {
|
||||
StartTime string `url:"start_time,omitempty"`
|
||||
EndTime string `url:"end_time,omitempty"`
|
||||
Lines string `url:"lines,omitempty"`
|
||||
}
|
||||
|
||||
_sling = _sling.QueryStruct(&QueryParams{StartTime: startTime, EndTime: endTime, Lines: lines})
|
||||
var successPayload []apilib.AccessLog
|
||||
code, body, err := request(_sling, jsonAcceptHeader, user)
|
||||
if 200 == code && nil == err {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
return code, successPayload, err
|
||||
}
|
||||
|
||||
////Delete a repository or a tag in a repository.
|
||||
////Delete a repository or a tag in a repository.
|
||||
////This endpoint let user delete repositories and tags with repo name and tag.\n
|
||||
////@param repoName The name of repository which will be deleted.
|
||||
////@param tag Tag of a repository.
|
||||
////@return void
|
||||
////func (a api) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) {
|
||||
//func (a api) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) {
|
||||
// _sling := sling.New().Delete(a.basePath)
|
||||
|
||||
// // create path and map variables
|
||||
// path := "/api/repositories"
|
||||
|
||||
// _sling = _sling.Path(path)
|
||||
|
||||
// type QueryParams struct {
|
||||
// RepoName string `url:"repo_name,omitempty"`
|
||||
// Tag string `url:"tag,omitempty"`
|
||||
// }
|
||||
|
||||
// _sling = _sling.QueryStruct(&QueryParams{RepoName: repoName, Tag: tag})
|
||||
// // accept header
|
||||
// accepts := []string{"application/json", "text/plain"}
|
||||
// for key := range accepts {
|
||||
// _sling = _sling.Set("Accept", accepts[key])
|
||||
// break // only use the first Accept
|
||||
// }
|
||||
|
||||
// req, err := _sling.Request()
|
||||
// req.SetBasicAuth(prjUsr.Name, prjUsr.Passwd)
|
||||
// //fmt.Printf("request %+v", req)
|
||||
|
||||
// client := &http.Client{}
|
||||
// httpResponse, err := client.Do(req)
|
||||
// defer httpResponse.Body.Close()
|
||||
|
||||
// if err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
// return httpResponse.StatusCode, err
|
||||
//}
|
||||
|
||||
//Delete project by projectID
|
||||
func (a api) ProjectsDelete(prjUsr usrInfo, projectID string) (int, error) {
|
||||
_sling := sling.New().Delete(a.basePath)
|
||||
|
||||
//create api path
|
||||
path := "api/projects/" + projectID
|
||||
_sling = _sling.Path(path)
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Check if the project name user provided already exists
|
||||
func (a api) ProjectsHead(prjUsr usrInfo, projectName string) (int, error) {
|
||||
_sling := sling.New().Head(a.basePath)
|
||||
|
||||
//create api path
|
||||
path := "api/projects"
|
||||
_sling = _sling.Path(path)
|
||||
type QueryParams struct {
|
||||
ProjectName string `url:"project_name,omitempty"`
|
||||
}
|
||||
_sling = _sling.QueryStruct(&QueryParams{ProjectName: projectName})
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Return specific project detail infomation
|
||||
func (a api) ProjectsGetByPID(projectID string) (int, apilib.Project, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
//create api path
|
||||
path := "api/projects/" + projectID
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
var successPayload apilib.Project
|
||||
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader)
|
||||
if err == nil && httpStatusCode == 200 {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
//Search projects by projectName and isPublic
|
||||
func (a api) ProjectsGet(projectName string, isPublic int32) (int, []apilib.Project, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
//create api path
|
||||
path := "api/projects"
|
||||
_sling = _sling.Path(path)
|
||||
type QueryParams struct {
|
||||
ProjectName string `url:"project_name,omitempty"`
|
||||
IsPubilc int32 `url:"is_public,omitempty"`
|
||||
}
|
||||
_sling = _sling.QueryStruct(&QueryParams{ProjectName: projectName, IsPubilc: isPublic})
|
||||
|
||||
var successPayload []apilib.Project
|
||||
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader)
|
||||
if err == nil && httpStatusCode == 200 {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
|
||||
return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
//Update properties for a selected project.
|
||||
func (a api) ToggleProjectPublicity(prjUsr usrInfo, projectID string, ispublic int32) (int, error) {
|
||||
// create path and map variables
|
||||
path := "/api/projects/" + projectID + "/publicity/"
|
||||
_sling := sling.New().Put(a.basePath)
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
type QueryParams struct {
|
||||
Public int32 `json:"public,omitempty"`
|
||||
}
|
||||
|
||||
_sling = _sling.BodyJSON(&QueryParams{Public: ispublic})
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
return httpStatusCode, err
|
||||
|
||||
}
|
||||
|
||||
//Get access logs accompany with a relevant project.
|
||||
func (a api) ProjectLogsFilter(prjUsr usrInfo, projectID string, accessLog apilib.AccessLogFilter) (int, []byte, error) {
|
||||
//func (a api) ProjectLogsFilter(prjUsr usrInfo, projectID string, accessLog apilib.AccessLog) (int, apilib.AccessLog, error) {
|
||||
_sling := sling.New().Post(a.basePath)
|
||||
|
||||
path := "/api/projects/" + projectID + "/logs/filter"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
// body params
|
||||
_sling = _sling.BodyJSON(accessLog)
|
||||
|
||||
//var successPayload []apilib.AccessLog
|
||||
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
/*
|
||||
if err == nil && httpStatusCode == 200 {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
*/
|
||||
return httpStatusCode, body, err
|
||||
// return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
//-------------------------Member Test---------------------------------------//
|
||||
|
||||
//Return relevant role members of projectID
|
||||
func (a api) GetProjectMembersByProID(prjUsr usrInfo, projectID string) (int, []apilib.User, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
path := "/api/projects/" + projectID + "/members/"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
var successPayload []apilib.User
|
||||
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
if err == nil && httpStatusCode == 200 {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
//Add project role member accompany with projectID
|
||||
func (a api) AddProjectMember(prjUsr usrInfo, projectID string, roles apilib.RoleParam) (int, error) {
|
||||
_sling := sling.New().Post(a.basePath)
|
||||
|
||||
path := "/api/projects/" + projectID + "/members/"
|
||||
_sling = _sling.Path(path)
|
||||
_sling = _sling.BodyJSON(roles)
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
return httpStatusCode, err
|
||||
|
||||
}
|
||||
|
||||
//Delete project role member accompany with projectID
|
||||
func (a api) DeleteProjectMember(authInfo usrInfo, projectID string, userID string) (int, error) {
|
||||
_sling := sling.New().Delete(a.basePath)
|
||||
|
||||
path := "/api/projects/" + projectID + "/members/" + userID
|
||||
_sling = _sling.Path(path)
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
|
||||
}
|
||||
|
||||
//Get role memberInfo by projectId and UserId
|
||||
func (a api) GetMemByPIDUID(authInfo usrInfo, projectID string, userID string) (int, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
path := "/api/projects/" + projectID + "/members/" + userID
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Put:update current project role members accompany with relevant project and user
|
||||
func (a api) PutProjectMember(authInfo usrInfo, projectID string, userID string, roles apilib.RoleParam) (int, error) {
|
||||
_sling := sling.New().Put(a.basePath)
|
||||
path := "/api/projects/" + projectID + "/members/" + userID
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
_sling = _sling.BodyJSON(roles)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//-------------------------Repositories Test---------------------------------------//
|
||||
//Return relevant repos of projectID
|
||||
func (a api) GetRepos(authInfo usrInfo, projectID string) (int, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
path := "/api/repositories/"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
type QueryParams struct {
|
||||
ProjectID string `url:"project_id"`
|
||||
}
|
||||
|
||||
_sling = _sling.QueryStruct(&QueryParams{ProjectID: projectID})
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Get tags of a relevant repository
|
||||
func (a api) GetReposTags(authInfo usrInfo, repoName string) (int, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
path := "/api/repositories/tags"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
type QueryParams struct {
|
||||
RepoName string `url:"repo_name"`
|
||||
}
|
||||
|
||||
_sling = _sling.QueryStruct(&QueryParams{RepoName: repoName})
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Get manifests of a relevant repository
|
||||
func (a api) GetReposManifests(authInfo usrInfo, repoName string, tag string) (int, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
path := "/api/repositories/manifests"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
type QueryParams struct {
|
||||
RepoName string `url:"repo_name"`
|
||||
Tag string `url:"tag"`
|
||||
}
|
||||
|
||||
_sling = _sling.QueryStruct(&QueryParams{RepoName: repoName, Tag: tag})
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Get public repositories which are accessed most
|
||||
func (a api) GetReposTop(authInfo usrInfo, count string) (int, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
path := "/api/repositories/top"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
type QueryParams struct {
|
||||
Count string `url:"count"`
|
||||
}
|
||||
|
||||
_sling = _sling.QueryStruct(&QueryParams{Count: count})
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//-------------------------Targets Test---------------------------------------//
|
||||
//Create a new replication target
|
||||
func (a api) AddTargets(authInfo usrInfo, repTarget apilib.RepTargetPost) (int, error) {
|
||||
_sling := sling.New().Post(a.basePath)
|
||||
|
||||
path := "/api/targets"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
_sling = _sling.BodyJSON(repTarget)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//List filters targets by name
|
||||
func (a api) ListTargets(authInfo usrInfo, targetName string) (int, []apilib.RepTarget, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
path := "/api/targets?name=" + targetName
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
var successPayload []apilib.RepTarget
|
||||
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
if err == nil && httpStatusCode == 200 {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
|
||||
return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
//Ping target by targetID
|
||||
func (a api) PingTargetsByID(authInfo usrInfo, targetID string) (int, error) {
|
||||
_sling := sling.New().Post(a.basePath)
|
||||
|
||||
path := "/api/targets/ping?id=" + targetID
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Get target by targetID
|
||||
func (a api) GetTargetByID(authInfo usrInfo, targetID string) (int, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
path := "/api/targets/" + targetID
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Update target by targetID
|
||||
func (a api) PutTargetByID(authInfo usrInfo, targetID string, repTarget apilib.RepTargetPost) (int, error) {
|
||||
_sling := sling.New().Put(a.basePath)
|
||||
|
||||
path := "/api/targets/" + targetID
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
_sling = _sling.BodyJSON(repTarget)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//List the target relevant policies by targetID
|
||||
func (a api) GetTargetPoliciesByID(authInfo usrInfo, targetID string) (int, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
path := "/api/targets/" + targetID + "/policies/"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Delete target by targetID
|
||||
func (a api) DeleteTargetsByID(authInfo usrInfo, targetID string) (int, error) {
|
||||
_sling := sling.New().Delete(a.basePath)
|
||||
|
||||
path := "/api/targets/" + targetID
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//--------------------Replication_Policy Test--------------------------------//
|
||||
|
||||
//Create a new replication policy
|
||||
func (a api) AddPolicy(authInfo usrInfo, repPolicy apilib.RepPolicyPost) (int, error) {
|
||||
_sling := sling.New().Post(a.basePath)
|
||||
|
||||
path := "/api/policies/replication/"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
_sling = _sling.BodyJSON(repPolicy)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//List policies by policyName and projectID
|
||||
func (a api) ListPolicies(authInfo usrInfo, policyName string, proID string) (int, []apilib.RepPolicy, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
path := "/api/policies/replication/"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
type QueryParams struct {
|
||||
PolicyName string `url:"name"`
|
||||
ProjectID string `url:"project_id"`
|
||||
}
|
||||
_sling = _sling.QueryStruct(&QueryParams{PolicyName: policyName, ProjectID: proID})
|
||||
|
||||
var successPayload []apilib.RepPolicy
|
||||
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
|
||||
if err == nil && httpStatusCode == 200 {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
//Get replication policy by policyID
|
||||
func (a api) GetPolicyByID(authInfo usrInfo, policyID string) (int, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
path := "/api/policies/replication/" + policyID
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Update policyInfo by policyID
|
||||
func (a api) PutPolicyInfoByID(authInfo usrInfo, policyID string, policyUpdate apilib.RepPolicyUpdate) (int, error) {
|
||||
_sling := sling.New().Put(a.basePath)
|
||||
|
||||
path := "/api/policies/replication/" + policyID
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
_sling = _sling.BodyJSON(policyUpdate)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Update policy enablement flag by policyID
|
||||
func (a api) PutPolicyEnableByID(authInfo usrInfo, policyID string, policyEnable apilib.RepPolicyEnablementReq) (int, error) {
|
||||
_sling := sling.New().Put(a.basePath)
|
||||
|
||||
path := "/api/policies/replication/" + policyID + "/enablement"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
_sling = _sling.BodyJSON(policyEnable)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Delete policy by policyID
|
||||
func (a api) DeletePolicyByID(authInfo usrInfo, policyID string) (int, error) {
|
||||
_sling := sling.New().Delete(a.basePath)
|
||||
|
||||
path := "/api/policies/replication/" + policyID
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Return projects created by Harbor
|
||||
//func (a HarborApi) ProjectsGet (projectName string, isPublic int32) ([]Project, error) {
|
||||
// }
|
||||
|
||||
//Check if the project name user provided already exists.
|
||||
//func (a HarborApi) ProjectsHead (projectName string) (error) {
|
||||
//}
|
||||
|
||||
//Get access logs accompany with a relevant project.
|
||||
//func (a HarborApi) ProjectsProjectIdLogsFilterPost (projectID int32, accessLog AccessLog) ([]AccessLog, error) {
|
||||
//}
|
||||
|
||||
//Return a project's relevant role members.
|
||||
//func (a HarborApi) ProjectsProjectIdMembersGet (projectID int32) ([]Role, error) {
|
||||
//}
|
||||
|
||||
//Add project role member accompany with relevant project and user.
|
||||
//func (a HarborApi) ProjectsProjectIdMembersPost (projectID int32, roles RoleParam) (error) {
|
||||
//}
|
||||
|
||||
//Delete project role members accompany with relevant project and user.
|
||||
//func (a HarborApi) ProjectsProjectIdMembersUserIdDelete (projectID int32, userId int32) (error) {
|
||||
//}
|
||||
|
||||
//Return role members accompany with relevant project and user.
|
||||
//func (a HarborApi) ProjectsProjectIdMembersUserIdGet (projectID int32, userId int32) ([]Role, error) {
|
||||
//}
|
||||
|
||||
//Update project role members accompany with relevant project and user.
|
||||
//func (a HarborApi) ProjectsProjectIdMembersUserIdPut (projectID int32, userId int32, roles RoleParam) (error) {
|
||||
//}
|
||||
|
||||
//Update properties for a selected project.
|
||||
//func (a HarborApi) ProjectsProjectIdPut (projectID int32, project Project) (error) {
|
||||
//}
|
||||
|
||||
//Get repositories accompany with relevant project and repo name.
|
||||
//func (a HarborApi) RepositoriesGet (projectID int32, q string) ([]Repository, error) {
|
||||
//}
|
||||
|
||||
//Get manifests of a relevant repository.
|
||||
//func (a HarborApi) RepositoriesManifestGet (repoName string, tag string) (error) {
|
||||
//}
|
||||
|
||||
//Get tags of a relevant repository.
|
||||
//func (a HarborApi) RepositoriesTagsGet (repoName string) (error) {
|
||||
//}
|
||||
|
||||
//Get registered users of Harbor.
|
||||
func (a api) UsersGet(userName string, authInfo usrInfo) (int, []apilib.User, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
// create path and map variables
|
||||
path := "/api/users/"
|
||||
_sling = _sling.Path(path)
|
||||
// body params
|
||||
type QueryParams struct {
|
||||
UserName string `url:"username, omitempty"`
|
||||
}
|
||||
_sling = _sling.QueryStruct(&QueryParams{UserName: userName})
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
var successPayLoad []apilib.User
|
||||
if 200 == httpStatusCode && nil == err {
|
||||
err = json.Unmarshal(body, &successPayLoad)
|
||||
}
|
||||
return httpStatusCode, successPayLoad, err
|
||||
}
|
||||
|
||||
//Get registered users by userid.
|
||||
func (a api) UsersGetByID(userName string, authInfo usrInfo, userID int) (int, apilib.User, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
// create path and map variables
|
||||
path := "/api/users/" + fmt.Sprintf("%d", userID)
|
||||
_sling = _sling.Path(path)
|
||||
// body params
|
||||
type QueryParams struct {
|
||||
UserName string `url:"username, omitempty"`
|
||||
}
|
||||
_sling = _sling.QueryStruct(&QueryParams{UserName: userName})
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
var successPayLoad apilib.User
|
||||
if 200 == httpStatusCode && nil == err {
|
||||
err = json.Unmarshal(body, &successPayLoad)
|
||||
}
|
||||
return httpStatusCode, successPayLoad, err
|
||||
}
|
||||
|
||||
//Creates a new user account.
|
||||
func (a api) UsersPost(user apilib.User, authInfo ...usrInfo) (int, error) {
|
||||
_sling := sling.New().Post(a.basePath)
|
||||
|
||||
// create path and map variables
|
||||
path := "/api/users/"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
// body params
|
||||
_sling = _sling.BodyJSON(user)
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
if len(authInfo) > 0 {
|
||||
httpStatusCode, _, err = request(_sling, jsonAcceptHeader, authInfo[0])
|
||||
} else {
|
||||
httpStatusCode, _, err = request(_sling, jsonAcceptHeader)
|
||||
}
|
||||
return httpStatusCode, err
|
||||
|
||||
}
|
||||
|
||||
//Update a registered user to change profile.
|
||||
func (a api) UsersPut(userID int, profile apilib.UserProfile, authInfo usrInfo) (int, error) {
|
||||
_sling := sling.New().Put(a.basePath)
|
||||
// create path and map variables
|
||||
path := "/api/users/" + fmt.Sprintf("%d", userID)
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
// body params
|
||||
_sling = _sling.BodyJSON(profile)
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Update a registered user to be an administrator of Harbor.
|
||||
func (a api) UsersToggleAdminRole(userID int, authInfo usrInfo, hasAdminRole int32) (int, error) {
|
||||
_sling := sling.New().Put(a.basePath)
|
||||
// create path and map variables
|
||||
path := "/api/users/" + fmt.Sprintf("%d", userID) + "/sysadmin"
|
||||
_sling = _sling.Path(path)
|
||||
type QueryParams struct {
|
||||
HasAdminRole int32 `json:"has_admin_role,omitempty"`
|
||||
}
|
||||
|
||||
_sling = _sling.BodyJSON(&QueryParams{HasAdminRole: hasAdminRole})
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Update password of a registered user.
|
||||
func (a api) UsersUpdatePassword(userID int, password apilib.Password, authInfo usrInfo) (int, error) {
|
||||
_sling := sling.New().Put(a.basePath)
|
||||
// create path and map variables
|
||||
path := "/api/users/" + fmt.Sprintf("%d", userID) + "/password"
|
||||
_sling = _sling.Path(path)
|
||||
// body params
|
||||
_sling = _sling.BodyJSON(password)
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Mark a registered user as be removed.
|
||||
func (a api) UsersDelete(userID int, authInfo usrInfo) (int, error) {
|
||||
_sling := sling.New().Delete(a.basePath)
|
||||
// create path and map variables
|
||||
path := "/api/users/" + fmt.Sprintf("%d", userID)
|
||||
_sling = _sling.Path(path)
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
func updateInitPassword(userID int, password string) error {
|
||||
queryUser := models.User{UserID: userID}
|
||||
user, err := dao.GetUser(queryUser)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
if user.Salt == "" {
|
||||
salt, err := dao.GenerateRandomString()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to generate salt for encrypting password, %v", err)
|
||||
}
|
||||
|
||||
user.Salt = salt
|
||||
user.Password = password
|
||||
err = dao.ChangeUserPassword(*user)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to update user encrypted password, userID: %d, err: %v", userID, err)
|
||||
}
|
||||
|
||||
} else {
|
||||
}
|
||||
return nil
|
||||
}
|
51
api/internal.go
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
)
|
||||
|
||||
// InternalAPI handles request of harbor admin...
|
||||
type InternalAPI struct {
|
||||
BaseAPI
|
||||
}
|
||||
|
||||
// Prepare validates the URL and parms
|
||||
func (ia *InternalAPI) Prepare() {
|
||||
var currentUserID int
|
||||
currentUserID = ia.ValidateUser()
|
||||
isAdmin, err := dao.IsAdminRole(currentUserID)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in IsAdminRole:%v", err)
|
||||
ia.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if !isAdmin {
|
||||
log.Error("Guests doesn't have the permisson to request harbor internal API.")
|
||||
ia.CustomAbort(http.StatusForbidden, "Guests doesn't have the permisson to request harbor internal API.")
|
||||
}
|
||||
}
|
||||
|
||||
// SyncRegistry ...
|
||||
func (ia *InternalAPI) SyncRegistry() {
|
||||
err := SyncRegistry()
|
||||
if err != nil {
|
||||
ia.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
}
|
||||
}
|
9
api/jobs/job_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
@ -46,6 +46,27 @@ type ReplicationReq struct {
|
||||
TagList []string `json:"tags"`
|
||||
}
|
||||
|
||||
// Prepare ...
|
||||
func (rj *ReplicationJob) Prepare() {
|
||||
rj.authenticate()
|
||||
}
|
||||
|
||||
func (rj *ReplicationJob) authenticate() {
|
||||
cookie, err := rj.Ctx.Request.Cookie(models.UISecretCookie)
|
||||
if err != nil && err != http.ErrNoCookie {
|
||||
log.Errorf("failed to get cookie %s: %v", models.UISecretCookie, err)
|
||||
rj.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
if err == http.ErrNoCookie {
|
||||
rj.CustomAbort(http.StatusUnauthorized, "")
|
||||
}
|
||||
|
||||
if cookie.Value != config.UISecret() {
|
||||
rj.CustomAbort(http.StatusForbidden, "")
|
||||
}
|
||||
}
|
||||
|
||||
// Post creates replication jobs according to the policy.
|
||||
func (rj *ReplicationJob) Post() {
|
||||
var data ReplicationReq
|
||||
|
124
api/log_test.go
Normal file
@ -0,0 +1,124 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLogGet(t *testing.T) {
|
||||
|
||||
fmt.Println("Testing Log API")
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//prepare for test
|
||||
|
||||
var project apilib.ProjectReq
|
||||
project.ProjectName = "my_project"
|
||||
project.Public = 1
|
||||
|
||||
//add the project first.
|
||||
fmt.Println("add the project first.")
|
||||
reply, err := apiTest.ProjectsPost(*admin, project)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(201), reply, "Case 2: Project creation status should be 201")
|
||||
}
|
||||
//case 1: right parameters, expect the right output
|
||||
now := fmt.Sprintf("%v", time.Now().Unix())
|
||||
statusCode, result, err := apiTest.LogGet(*admin, "0", now, "3")
|
||||
if err != nil {
|
||||
t.Error("Error while get log information", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(1, len(result), "lines of logs should be equal")
|
||||
assert.Equal(int32(1), result[0].LogId, "LogId should be equal")
|
||||
assert.Equal("my_project/", result[0].RepoName, "RepoName should be equal")
|
||||
assert.Equal("N/A", result[0].RepoTag, "RepoTag should be equal")
|
||||
assert.Equal("create", result[0].Operation, "Operation should be equal")
|
||||
}
|
||||
|
||||
//case 2: wrong format of start_time parameter, expect the wrong output
|
||||
now = fmt.Sprintf("%v", time.Now().Unix())
|
||||
statusCode, result, err = apiTest.LogGet(*admin, "ss", now, "3")
|
||||
if err != nil {
|
||||
t.Error("Error occured while get log information since the format of start_time parameter is not right.", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), statusCode, "Http status code should be 400")
|
||||
}
|
||||
|
||||
//case 3: wrong format of end_time parameter, expect the wrong output
|
||||
statusCode, result, err = apiTest.LogGet(*admin, "0", "cc", "3")
|
||||
if err != nil {
|
||||
t.Error("Error occured while get log information since the format of end_time parameter is not right.", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), statusCode, "Http status code should be 400")
|
||||
}
|
||||
|
||||
//case 4: wrong format of lines parameter, expect the wrong output
|
||||
now = fmt.Sprintf("%v", time.Now().Unix())
|
||||
statusCode, result, err = apiTest.LogGet(*admin, "0", now, "s")
|
||||
if err != nil {
|
||||
t.Error("Error occured while get log information since the format of lines parameter is not right.", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), statusCode, "Http status code should be 400")
|
||||
}
|
||||
|
||||
//case 5: wrong format of lines parameter, expect the wrong output
|
||||
now = fmt.Sprintf("%v", time.Now().Unix())
|
||||
statusCode, result, err = apiTest.LogGet(*admin, "0", now, "-5")
|
||||
if err != nil {
|
||||
t.Error("Error occured while get log information since the format of lines parameter is not right.", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), statusCode, "Http status code should be 400")
|
||||
}
|
||||
|
||||
//case 6: all parameters are null, expect the right output
|
||||
statusCode, result, err = apiTest.LogGet(*admin, "", "", "")
|
||||
if err != nil {
|
||||
t.Error("Error while get log information", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(1, len(result), "lines of logs should be equal")
|
||||
assert.Equal(int32(1), result[0].LogId, "LogId should be equal")
|
||||
assert.Equal("my_project/", result[0].RepoName, "RepoName should be equal")
|
||||
assert.Equal("N/A", result[0].RepoTag, "RepoTag should be equal")
|
||||
assert.Equal("create", result[0].Operation, "Operation should be equal")
|
||||
}
|
||||
|
||||
//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")
|
||||
|
||||
}
|
@ -142,13 +142,22 @@ func (pma *ProjectMemberAPI) Post() {
|
||||
return
|
||||
}
|
||||
|
||||
for _, rid := range req.Roles {
|
||||
err = dao.AddProjectMember(projectID, userID, int(rid))
|
||||
if err != nil {
|
||||
log.Errorf("Failed to update DB to add project user role, project id: %d, user id: %d, role id: %d", projectID, userID, rid)
|
||||
pma.RenderError(http.StatusInternalServerError, "Failed to update data in database")
|
||||
return
|
||||
}
|
||||
if len(req.Roles) <= 0 || len(req.Roles) > 1 {
|
||||
pma.CustomAbort(http.StatusBadRequest, "only one role is supported")
|
||||
}
|
||||
|
||||
rid := req.Roles[0]
|
||||
if !(rid == models.PROJECTADMIN ||
|
||||
rid == models.DEVELOPER ||
|
||||
rid == models.GUEST) {
|
||||
pma.CustomAbort(http.StatusBadRequest, "invalid role")
|
||||
}
|
||||
|
||||
err = dao.AddProjectMember(projectID, userID, rid)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to update DB to add project user role, project id: %d, user id: %d, role id: %d", projectID, userID, rid)
|
||||
pma.RenderError(http.StatusInternalServerError, "Failed to update data in database")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
189
api/member_test.go
Normal file
@ -0,0 +1,189 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func TestMemGet(t *testing.T) {
|
||||
var result []apilib.User
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
projectID := "1"
|
||||
|
||||
fmt.Println("Testing Member Get API")
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
httpStatusCode, result, err = apiTest.GetProjectMembersByProID(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get members by projectID", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
assert.Equal(int(1), result[0].UserId, "User Id should be 1")
|
||||
assert.Equal("admin", result[0].Username, "User name should be admin")
|
||||
}
|
||||
|
||||
//---------case 2: Response Code=401,User need to log in first.----------//
|
||||
fmt.Println("case 2: Response Code=401,User need to log in first.")
|
||||
httpStatusCode, result, err = apiTest.GetProjectMembersByProID(*unknownUsr, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while get members by projectID", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(401), httpStatusCode, "Case 2: Project creation status should be 401")
|
||||
}
|
||||
|
||||
//------------case 3: Response Code=404,Project does not exist-----------//
|
||||
fmt.Println("case 3: Response Code=404,Project does not exist")
|
||||
projectID = "11"
|
||||
httpStatusCode, result, err = apiTest.GetProjectMembersByProID(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while get members by projectID", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "Case 3: Project creation status should be 404")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add project role member accompany with projectID
|
||||
* role_id = 1 : ProjectAdmin
|
||||
* role_id = 2 : Developer
|
||||
* role_id = 3 : Guest
|
||||
*/
|
||||
|
||||
func TestMemPost(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
projectID := "1"
|
||||
CommonAddUser()
|
||||
roles := &apilib.RoleParam{[]int32{1}, TestUserName}
|
||||
fmt.Printf("Add User \"%s\" successfully!\n", TestUserName)
|
||||
|
||||
fmt.Println("Testing Member Post API")
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1: response code = 200")
|
||||
httpStatusCode, err = apiTest.AddProjectMember(*admin, projectID, *roles)
|
||||
if err != nil {
|
||||
t.Error("Error whihle add project role member", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
//---------case 2: Response Code=409,User is ready in project.----------//
|
||||
fmt.Println("case 2: Response Code=409,User is ready in project.")
|
||||
httpStatusCode, err = apiTest.AddProjectMember(*admin, projectID, *roles)
|
||||
if err != nil {
|
||||
t.Error("Error while add project role member", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(409), httpStatusCode, "Case 2: httpStatusCode should be 409")
|
||||
}
|
||||
|
||||
//---------case 3: Response Code=404,User does not exist.----------//
|
||||
fmt.Println("case 3: Response Code=404,User does not exist.")
|
||||
|
||||
errorRoles := &apilib.RoleParam{[]int32{1}, "T"}
|
||||
httpStatusCode, err = apiTest.AddProjectMember(*admin, projectID, *errorRoles)
|
||||
if err != nil {
|
||||
t.Error("Error while add project role member", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "Case 3: httpStatusCode status should be 404")
|
||||
}
|
||||
/*
|
||||
//---------case 4: Response Code=403,User in session does not have permission to the project..----------//
|
||||
fmt.Println("case 4:User in session does not have permission to the project.")
|
||||
|
||||
httpStatusCode, err = apiTest.AddProjectMember(*testUser, projectID, *roles)
|
||||
if err != nil {
|
||||
t.Error("Error while add project role member", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(403), httpStatusCode, "Case 3: httpStatusCode status should be 403")
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
func TestGetMemByPIDUID(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
projectID := "1"
|
||||
userID := strconv.Itoa(CommonGetUserID())
|
||||
fmt.Println("Testing Member Get API by PID and UID")
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1: response code = 200")
|
||||
httpStatusCode, err = apiTest.GetMemByPIDUID(*admin, projectID, userID)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get project role member", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPutMem(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
projectID := "1"
|
||||
userID := strconv.Itoa(CommonGetUserID())
|
||||
|
||||
roles := &apilib.RoleParam{[]int32{3}, TestUserName}
|
||||
fmt.Println("Testing Member Put API")
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1: response code = 200")
|
||||
httpStatusCode, err = apiTest.PutProjectMember(*admin, projectID, userID, *roles)
|
||||
if err != nil {
|
||||
t.Error("Error whihle put project role member", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDeleteMemUser(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
projectID := "1"
|
||||
|
||||
fmt.Println("Testing Member Delete API")
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1: response code = 200")
|
||||
|
||||
id := strconv.Itoa(CommonGetUserID())
|
||||
|
||||
httpStatusCode, err = apiTest.DeleteProjectMember(*admin, projectID, id)
|
||||
if err != nil {
|
||||
t.Error("Error whihle add project role member", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
CommonDelUser()
|
||||
}
|
195
api/project.go
@ -31,13 +31,14 @@ import (
|
||||
// ProjectAPI handles request to /api/projects/{} /api/projects/{}/logs
|
||||
type ProjectAPI struct {
|
||||
BaseAPI
|
||||
userID int
|
||||
projectID int64
|
||||
userID int
|
||||
projectID int64
|
||||
projectName string
|
||||
}
|
||||
|
||||
type projectReq struct {
|
||||
ProjectName string `json:"project_name"`
|
||||
Public bool `json:"public"`
|
||||
Public int `json:"public"`
|
||||
}
|
||||
|
||||
const projectNameMaxLen int = 30
|
||||
@ -54,14 +55,16 @@ func (p *ProjectAPI) Prepare() {
|
||||
log.Errorf("Error parsing project id: %s, error: %v", idStr, err)
|
||||
p.CustomAbort(http.StatusBadRequest, "invalid project id")
|
||||
}
|
||||
exist, err := dao.ProjectExists(p.projectID)
|
||||
|
||||
project, err := dao.GetProjectByID(p.projectID)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in ProjectExists, error: %v", err)
|
||||
log.Errorf("failed to get project %d: %v", p.projectID, err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if !exist {
|
||||
if project == nil {
|
||||
p.CustomAbort(http.StatusNotFound, fmt.Sprintf("project does not exist, id: %v", p.projectID))
|
||||
}
|
||||
p.projectName = project.Name
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,11 +73,8 @@ func (p *ProjectAPI) Post() {
|
||||
p.userID = p.ValidateUser()
|
||||
|
||||
var req projectReq
|
||||
var public int
|
||||
p.DecodeJSONReq(&req)
|
||||
if req.Public {
|
||||
public = 1
|
||||
}
|
||||
public := req.Public
|
||||
err := validateProjectReq(req)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid project request, error: %v", err)
|
||||
@ -152,15 +152,82 @@ func (p *ProjectAPI) Get() {
|
||||
p.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete ...
|
||||
func (p *ProjectAPI) Delete() {
|
||||
if p.projectID == 0 {
|
||||
p.CustomAbort(http.StatusBadRequest, "project ID is required")
|
||||
}
|
||||
|
||||
userID := p.ValidateUser()
|
||||
|
||||
if !hasProjectAdminRole(userID, p.projectID) {
|
||||
p.CustomAbort(http.StatusForbidden, "")
|
||||
}
|
||||
|
||||
contains, err := projectContainsRepo(p.projectName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check whether project %s contains any repository: %v", p.projectName, err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
if contains {
|
||||
p.CustomAbort(http.StatusPreconditionFailed, "project contains repositores, can not be deleted")
|
||||
}
|
||||
|
||||
contains, err = projectContainsPolicy(p.projectID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check whether project %s contains any policy: %v", p.projectName, err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
if contains {
|
||||
p.CustomAbort(http.StatusPreconditionFailed, "project contains policies, can not be deleted")
|
||||
}
|
||||
|
||||
if err = dao.DeleteProject(p.projectID); err != nil {
|
||||
log.Errorf("failed to delete project %d: %v", p.projectID, err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := dao.AddAccessLog(models.AccessLog{
|
||||
UserID: userID,
|
||||
ProjectID: p.projectID,
|
||||
RepoName: p.projectName,
|
||||
Operation: "delete",
|
||||
}); err != nil {
|
||||
log.Errorf("failed to add access log: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func projectContainsRepo(name string) (bool, error) {
|
||||
repositories, err := getReposByProject(name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(repositories) > 0, nil
|
||||
}
|
||||
|
||||
func projectContainsPolicy(id int64) (bool, error) {
|
||||
policies, err := dao.GetRepPolicyByProject(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(policies) > 0, nil
|
||||
}
|
||||
|
||||
// List ...
|
||||
func (p *ProjectAPI) List() {
|
||||
var projectList []models.Project
|
||||
projectName := p.GetString("project_name")
|
||||
if len(projectName) > 0 {
|
||||
projectName = "%" + projectName + "%"
|
||||
}
|
||||
var total int64
|
||||
var public int
|
||||
var err error
|
||||
|
||||
page, pageSize := p.getPaginationParams()
|
||||
|
||||
var projectList []models.Project
|
||||
projectName := p.GetString("project_name")
|
||||
|
||||
isPublic := p.GetString("is_public")
|
||||
if len(isPublic) > 0 {
|
||||
public, err = strconv.Atoi(isPublic)
|
||||
@ -171,7 +238,16 @@ func (p *ProjectAPI) List() {
|
||||
}
|
||||
isAdmin := false
|
||||
if public == 1 {
|
||||
projectList, err = dao.GetPublicProjects(projectName)
|
||||
total, err = dao.GetTotalOfProjects(projectName, 1)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of projects: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
projectList, err = dao.GetProjects(projectName, 1, pageSize, pageSize*(page-1))
|
||||
if err != nil {
|
||||
log.Errorf("failed to get projects: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
} else {
|
||||
//if the request is not for public projects, user must login or provide credential
|
||||
p.userID = p.ValidateUser()
|
||||
@ -181,15 +257,30 @@ func (p *ProjectAPI) List() {
|
||||
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if isAdmin {
|
||||
projectList, err = dao.GetAllProjects(projectName)
|
||||
total, err = dao.GetTotalOfProjects(projectName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of projects: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
projectList, err = dao.GetProjects(projectName, pageSize, pageSize*(page-1))
|
||||
if err != nil {
|
||||
log.Errorf("failed to get projects: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
} else {
|
||||
projectList, err = dao.GetUserRelevantProjects(p.userID, projectName)
|
||||
total, err = dao.GetTotalOfUserRelevantProjects(p.userID, projectName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of projects: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
projectList, err = dao.GetUserRelevantProjects(p.userID, projectName, pageSize, pageSize*(page-1))
|
||||
if err != nil {
|
||||
log.Errorf("failed to get projects: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Error occured in get projects info, error: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
|
||||
for i := 0; i < len(projectList); i++ {
|
||||
if public != 1 {
|
||||
if isAdmin {
|
||||
@ -199,8 +290,17 @@ func (p *ProjectAPI) List() {
|
||||
projectList[i].Togglable = true
|
||||
}
|
||||
}
|
||||
projectList[i].RepoCount = getRepoCountByProject(projectList[i].Name)
|
||||
|
||||
repos, err := dao.GetRepositoryByProjectName(projectList[i].Name)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get repositories of project %s: %v", projectList[i].Name, err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
projectList[i].RepoCount = len(repos)
|
||||
}
|
||||
|
||||
p.setPaginationHeader(total, page, pageSize)
|
||||
p.Data["json"] = projectList
|
||||
p.ServeJSON()
|
||||
}
|
||||
@ -209,7 +309,6 @@ func (p *ProjectAPI) List() {
|
||||
func (p *ProjectAPI) ToggleProjectPublic() {
|
||||
p.userID = p.ValidateUser()
|
||||
var req projectReq
|
||||
var public int
|
||||
|
||||
projectID, err := strconv.ParseInt(p.Ctx.Input.Param(":id"), 10, 64)
|
||||
if err != nil {
|
||||
@ -219,9 +318,7 @@ func (p *ProjectAPI) ToggleProjectPublic() {
|
||||
}
|
||||
|
||||
p.DecodeJSONReq(&req)
|
||||
if req.Public {
|
||||
public = 1
|
||||
}
|
||||
public := req.Public
|
||||
if !isProjectAdmin(p.userID, projectID) {
|
||||
log.Warningf("Current user, id: %d does not have project admin role for project, id: %d", p.userID, projectID)
|
||||
p.RenderError(http.StatusForbidden, "")
|
||||
@ -238,25 +335,35 @@ func (p *ProjectAPI) ToggleProjectPublic() {
|
||||
func (p *ProjectAPI) FilterAccessLog() {
|
||||
p.userID = p.ValidateUser()
|
||||
|
||||
var filter models.AccessLog
|
||||
p.DecodeJSONReq(&filter)
|
||||
var query models.AccessLog
|
||||
p.DecodeJSONReq(&query)
|
||||
|
||||
username := filter.Username
|
||||
keywords := filter.Keywords
|
||||
|
||||
beginTime := time.Unix(filter.BeginTimestamp, 0)
|
||||
endTime := time.Unix(filter.EndTimestamp, 0)
|
||||
|
||||
query := models.AccessLog{ProjectID: p.projectID, Username: "%" + username + "%", Keywords: keywords, BeginTime: beginTime, BeginTimestamp: filter.BeginTimestamp, EndTime: endTime, EndTimestamp: filter.EndTimestamp}
|
||||
|
||||
log.Infof("Query AccessLog: begin: %v, end: %v, keywords: %s", query.BeginTime, query.EndTime, query.Keywords)
|
||||
|
||||
accessLogList, err := dao.GetAccessLogs(query)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in GetAccessLogs, error: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
if !checkProjectPermission(p.userID, p.projectID) {
|
||||
log.Warningf("Current user, user id: %d does not have permission to read accesslog of project, id: %d", p.userID, p.projectID)
|
||||
p.RenderError(http.StatusForbidden, "")
|
||||
return
|
||||
}
|
||||
p.Data["json"] = accessLogList
|
||||
query.ProjectID = p.projectID
|
||||
query.BeginTime = time.Unix(query.BeginTimestamp, 0)
|
||||
query.EndTime = time.Unix(query.EndTimestamp, 0)
|
||||
|
||||
page, pageSize := p.getPaginationParams()
|
||||
|
||||
total, err := dao.GetTotalOfAccessLogs(query)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of access log: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
logs, err := dao.GetAccessLogs(query, pageSize, pageSize*(page-1))
|
||||
if err != nil {
|
||||
log.Errorf("failed to get access log: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
p.setPaginationHeader(total, page, pageSize)
|
||||
|
||||
p.Data["json"] = logs
|
||||
|
||||
p.ServeJSON()
|
||||
}
|
||||
|
317
api/project_test.go
Normal file
@ -0,0 +1,317 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var addProject *apilib.ProjectReq
|
||||
var addPID int
|
||||
|
||||
func InitAddPro() {
|
||||
addProject = &apilib.ProjectReq{"test_project", 1}
|
||||
}
|
||||
|
||||
func TestAddProject(t *testing.T) {
|
||||
|
||||
fmt.Println("\nTesting Add Project(ProjectsPost) API")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//prepare for test
|
||||
InitAddPro()
|
||||
|
||||
//case 1: admin not login, expect project creation fail.
|
||||
|
||||
result, err := apiTest.ProjectsPost(*unknownUsr, *addProject)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(401), result, "Case 1: Project creation status should be 401")
|
||||
//t.Log(result)
|
||||
}
|
||||
|
||||
//case 2: admin successful login, expect project creation success.
|
||||
fmt.Println("case 2: admin successful login, expect project creation success.")
|
||||
|
||||
result, err = apiTest.ProjectsPost(*admin, *addProject)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(201), result, "Case 2: Project creation status should be 201")
|
||||
//t.Log(result)
|
||||
}
|
||||
|
||||
//case 3: duplicate project name, create project fail
|
||||
fmt.Println("case 3: duplicate project name, create project fail")
|
||||
|
||||
result, err = apiTest.ProjectsPost(*admin, *addProject)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(409), result, "Case 3: Project creation status should be 409")
|
||||
//t.Log(result)
|
||||
}
|
||||
|
||||
//case 4: reponse code = 400 : Project name is illegal in length
|
||||
fmt.Println("case 4 : reponse code = 400 : Project name is illegal in length ")
|
||||
|
||||
result, err = apiTest.ProjectsPost(*admin, apilib.ProjectReq{"t", 1})
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), result, "case 4 : reponse code = 400 : Project name is illegal in length ")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
}
|
||||
|
||||
//Get project by proName
|
||||
func TestProGetByName(t *testing.T) {
|
||||
fmt.Println("\nTest for Project GET API by project name")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
var result []apilib.Project
|
||||
|
||||
//----------------------------case 1 : Response Code=200----------------------------//
|
||||
fmt.Println("case 1: respose code:200")
|
||||
httpStatusCode, result, err := apiTest.ProjectsGet(addProject.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")
|
||||
assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong")
|
||||
assert.Equal(int32(1), result[0].Public, "Public is wrong")
|
||||
//find add projectID
|
||||
addPID = int(result[0].ProjectId)
|
||||
}
|
||||
//----------------------------case 2 : Response Code=401:is_public=0----------------------------//
|
||||
fmt.Println("case 2: respose code:401,isPublic = 0")
|
||||
httpStatusCode, result, err = apiTest.ProjectsGet("library", 0)
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proName and isPublic", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
//Get project by proID
|
||||
func TestProGetByID(t *testing.T) {
|
||||
fmt.Println("\nTest for Project GET API by project id")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
var result apilib.Project
|
||||
projectID := strconv.Itoa(addPID)
|
||||
|
||||
//----------------------------case 1 : Response Code=200----------------------------//
|
||||
fmt.Println("case 1: respose code:200")
|
||||
httpStatusCode, result, err := apiTest.ProjectsGetByPID(projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proID", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
assert.Equal(addProject.ProjectName, result.ProjectName, "ProjectName is wrong")
|
||||
assert.Equal(int32(1), result.Public, "Public is wrong")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
func TestDeleteProject(t *testing.T) {
|
||||
|
||||
fmt.Println("\nTesting Delete Project(ProjectsPost) API")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
projectID := strconv.Itoa(addPID)
|
||||
|
||||
//--------------------------case 1: Response Code=401,User need to log in first.-----------------------//
|
||||
fmt.Println("case 1: Response Code=401,User need to log in first.")
|
||||
httpStatusCode, err := apiTest.ProjectsDelete(*unknownUsr, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(401), httpStatusCode, "Case 1: Project creation status should be 401")
|
||||
}
|
||||
|
||||
//--------------------------case 2: Response Code=200---------------------------------//
|
||||
fmt.Println("case2: respose code:200")
|
||||
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 2: Project creation status should be 200")
|
||||
}
|
||||
|
||||
//--------------------------case 3: Response Code=404,Project does not exist---------------------------------//
|
||||
fmt.Println("case 3: Response Code=404,Project does not exist")
|
||||
projectID = "11"
|
||||
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "Case 3: Project creation status should be 404")
|
||||
}
|
||||
|
||||
//--------------------------case 4: Response Code=400,Invalid project id.---------------------------------//
|
||||
fmt.Println("case 4: Response Code=400,Invalid project id.")
|
||||
projectID = "cc"
|
||||
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "Case 4: Project creation status should be 400")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
||||
}
|
||||
func TestProHead(t *testing.T) {
|
||||
fmt.Println("\nTest for Project HEAD API")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//----------------------------case 1 : Response Code=200----------------------------//
|
||||
fmt.Println("case 1: respose code:200")
|
||||
httpStatusCode, err := apiTest.ProjectsHead(*admin, "library")
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proName", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
//----------------------------case 2 : Response Code=404:Project name does not exist.----------------------------//
|
||||
fmt.Println("case 2: respose code:404,Project name does not exist.")
|
||||
httpStatusCode, err = apiTest.ProjectsHead(*admin, "libra")
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proName", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
//----------------------------case 3 : Response Code=401:User need to log in first..----------------------------//
|
||||
fmt.Println("case 3: respose code:401,User need to log in first..")
|
||||
httpStatusCode, err = apiTest.ProjectsHead(*unknownUsr, "libra")
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proName", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 401")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
}
|
||||
|
||||
func TestToggleProjectPublicity(t *testing.T) {
|
||||
fmt.Println("\nTest for Project PUT API: Update properties for a selected project")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//-------------------case1: Response Code=200------------------------------//
|
||||
fmt.Println("case 1: respose code:200")
|
||||
httpStatusCode, err := apiTest.ToggleProjectPublicity(*admin, "1", 1)
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proId", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
//-------------------case2: Response Code=401 User need to log in first. ------------------------------//
|
||||
fmt.Println("case 2: respose code:401, User need to log in first.")
|
||||
httpStatusCode, err = apiTest.ToggleProjectPublicity(*unknownUsr, "1", 1)
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proId", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 401")
|
||||
}
|
||||
//-------------------case3: Response Code=400 Invalid project id------------------------------//
|
||||
fmt.Println("case 3: respose code:400, Invalid project id")
|
||||
httpStatusCode, err = apiTest.ToggleProjectPublicity(*admin, "cc", 1)
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proId", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
|
||||
}
|
||||
//-------------------case4: Response Code=404 Not found the project------------------------------//
|
||||
fmt.Println("case 4: respose code:404, Not found the project")
|
||||
httpStatusCode, err = apiTest.ToggleProjectPublicity(*admin, "0", 1)
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proId", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
func TestProjectLogsFilter(t *testing.T) {
|
||||
fmt.Println("\nTest for search access logs filtered by operations and date time ranges..")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
endTimestamp := time.Now().Unix()
|
||||
startTimestamp := endTimestamp - 3600
|
||||
accessLog := &apilib.AccessLogFilter{
|
||||
Username: "admin",
|
||||
Keywords: "",
|
||||
BeginTimestamp: startTimestamp,
|
||||
EndTimestamp: endTimestamp,
|
||||
}
|
||||
|
||||
//-------------------case1: Response Code=200------------------------------//
|
||||
fmt.Println("case 1: respose code:200")
|
||||
projectID := "1"
|
||||
httpStatusCode, _, err := apiTest.ProjectLogsFilter(*admin, projectID, *accessLog)
|
||||
if err != nil {
|
||||
t.Error("Error while search access logs")
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
//-------------------case2: Response Code=401:User need to log in first.------------------------------//
|
||||
fmt.Println("case 2: respose code:401:User need to log in first.")
|
||||
projectID = "1"
|
||||
httpStatusCode, _, err = apiTest.ProjectLogsFilter(*unknownUsr, projectID, *accessLog)
|
||||
if err != nil {
|
||||
t.Error("Error while search access logs")
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 401")
|
||||
}
|
||||
//-------------------case3: Response Code=404:Project does not exist.-------------------------//
|
||||
fmt.Println("case 3: respose code:404:Illegal format of provided ID value.")
|
||||
projectID = "11111"
|
||||
httpStatusCode, _, err = apiTest.ProjectLogsFilter(*admin, projectID, *accessLog)
|
||||
if err != nil {
|
||||
t.Error("Error while search access logs")
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
@ -56,43 +56,28 @@ func (ra *RepJobAPI) Prepare() {
|
||||
|
||||
}
|
||||
|
||||
// List filters jobs according to the policy and repository
|
||||
// List filters jobs according to the parameters
|
||||
func (ra *RepJobAPI) List() {
|
||||
var policyID int64
|
||||
var repository, status string
|
||||
var startTime, endTime *time.Time
|
||||
var num int
|
||||
var err error
|
||||
|
||||
policyIDStr := ra.GetString("policy_id")
|
||||
if len(policyIDStr) != 0 {
|
||||
policyID, err = strconv.ParseInt(policyIDStr, 10, 64)
|
||||
if err != nil || policyID <= 0 {
|
||||
ra.CustomAbort(http.StatusBadRequest, fmt.Sprintf("invalid policy ID: %s", policyIDStr))
|
||||
}
|
||||
policyID, err := ra.GetInt64("policy_id")
|
||||
if err != nil || policyID <= 0 {
|
||||
ra.CustomAbort(http.StatusBadRequest, "invalid policy_id")
|
||||
}
|
||||
|
||||
numStr := ra.GetString("num")
|
||||
if len(numStr) != 0 {
|
||||
num, err = strconv.Atoi(numStr)
|
||||
if err != nil {
|
||||
ra.CustomAbort(http.StatusBadRequest, fmt.Sprintf("invalid num: %s", numStr))
|
||||
}
|
||||
}
|
||||
if num <= 0 {
|
||||
num = 200
|
||||
policy, err := dao.GetRepPolicy(policyID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get policy %d: %v", policyID, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
endTimeStr := ra.GetString("end_time")
|
||||
if len(endTimeStr) != 0 {
|
||||
i, err := strconv.ParseInt(endTimeStr, 10, 64)
|
||||
if err != nil {
|
||||
ra.CustomAbort(http.StatusBadRequest, "invalid end_time")
|
||||
}
|
||||
t := time.Unix(i, 0)
|
||||
endTime = &t
|
||||
if policy == nil {
|
||||
ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("policy %d not found", policyID))
|
||||
}
|
||||
|
||||
repository := ra.GetString("repository")
|
||||
status := ra.GetString("status")
|
||||
|
||||
var startTime *time.Time
|
||||
startTimeStr := ra.GetString("start_time")
|
||||
if len(startTimeStr) != 0 {
|
||||
i, err := strconv.ParseInt(startTimeStr, 10, 64)
|
||||
@ -103,21 +88,29 @@ func (ra *RepJobAPI) List() {
|
||||
startTime = &t
|
||||
}
|
||||
|
||||
if startTime == nil && endTime == nil {
|
||||
// if start_time and end_time are both null, list jobs of last 10 days
|
||||
t := time.Now().UTC().AddDate(0, 0, -10)
|
||||
startTime = &t
|
||||
var endTime *time.Time
|
||||
endTimeStr := ra.GetString("end_time")
|
||||
if len(endTimeStr) != 0 {
|
||||
i, err := strconv.ParseInt(endTimeStr, 10, 64)
|
||||
if err != nil {
|
||||
ra.CustomAbort(http.StatusBadRequest, "invalid end_time")
|
||||
}
|
||||
t := time.Unix(i, 0)
|
||||
endTime = &t
|
||||
}
|
||||
|
||||
repository = ra.GetString("repository")
|
||||
status = ra.GetString("status")
|
||||
page, pageSize := ra.getPaginationParams()
|
||||
|
||||
jobs, err := dao.FilterRepJobs(policyID, repository, status, startTime, endTime, num)
|
||||
jobs, total, err := dao.FilterRepJobs(policyID, repository, status,
|
||||
startTime, endTime, pageSize, pageSize*(page-1))
|
||||
if err != nil {
|
||||
log.Errorf("failed to filter jobs according policy ID %d, repository %s, status %s: %v", policyID, repository, status, err)
|
||||
ra.RenderError(http.StatusInternalServerError, "Failed to query job")
|
||||
return
|
||||
log.Errorf("failed to filter jobs according policy ID %d, repository %s, status %s, start time %v, end time %v: %v",
|
||||
policyID, repository, status, startTime, endTime, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
ra.setPaginationHeader(total, page, pageSize)
|
||||
|
||||
ra.Data["json"] = jobs
|
||||
ra.ServeJSON()
|
||||
}
|
||||
@ -154,7 +147,14 @@ func (ra *RepJobAPI) GetLog() {
|
||||
ra.CustomAbort(http.StatusBadRequest, "id is nil")
|
||||
}
|
||||
|
||||
resp, err := http.Get(buildJobLogURL(strconv.FormatInt(ra.jobID, 10)))
|
||||
req, err := http.NewRequest("GET", buildJobLogURL(strconv.FormatInt(ra.jobID, 10)), nil)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create a request: %v", err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
addAuthentication(req)
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get log for job %d: %v", ra.jobID, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
|
@ -91,15 +91,17 @@ func (pa *RepPolicyAPI) Post() {
|
||||
policy := &models.RepPolicy{}
|
||||
pa.DecodeJSONReqAndValidate(policy)
|
||||
|
||||
po, err := dao.GetRepPolicyByName(policy.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get policy %s: %v", policy.Name, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
/*
|
||||
po, err := dao.GetRepPolicyByName(policy.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get policy %s: %v", policy.Name, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
if po != nil {
|
||||
pa.CustomAbort(http.StatusConflict, "name is already used")
|
||||
}
|
||||
if po != nil {
|
||||
pa.CustomAbort(http.StatusConflict, "name is already used")
|
||||
}
|
||||
*/
|
||||
|
||||
project, err := dao.GetProjectByID(policy.ProjectID)
|
||||
if err != nil {
|
||||
@ -169,18 +171,20 @@ func (pa *RepPolicyAPI) Put() {
|
||||
policy.ProjectID = originalPolicy.ProjectID
|
||||
pa.Validate(policy)
|
||||
|
||||
// check duplicate name
|
||||
if policy.Name != originalPolicy.Name {
|
||||
po, err := dao.GetRepPolicyByName(policy.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get policy %s: %v", policy.Name, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
/*
|
||||
// check duplicate name
|
||||
if policy.Name != originalPolicy.Name {
|
||||
po, err := dao.GetRepPolicyByName(policy.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get policy %s: %v", policy.Name, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
if po != nil {
|
||||
pa.CustomAbort(http.StatusConflict, "name is already used")
|
||||
if po != nil {
|
||||
pa.CustomAbort(http.StatusConflict, "name is already used")
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if policy.TargetID != originalPolicy.TargetID {
|
||||
//target of policy can not be modified when the policy is enabled
|
||||
@ -349,3 +353,41 @@ func (pa *RepPolicyAPI) UpdateEnablement() {
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Delete : policies which are disabled and have no running jobs
|
||||
// can be deleted
|
||||
func (pa *RepPolicyAPI) Delete() {
|
||||
id := pa.GetIDFromURL()
|
||||
policy, err := dao.GetRepPolicy(id)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get policy %d: %v", id, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
if policy == nil || policy.Deleted == 1 {
|
||||
pa.CustomAbort(http.StatusNotFound, "")
|
||||
}
|
||||
|
||||
if policy.Enabled == 1 {
|
||||
pa.CustomAbort(http.StatusPreconditionFailed, "plicy is enabled, can not be deleted")
|
||||
}
|
||||
|
||||
jobs, err := dao.GetRepJobByPolicy(id)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get jobs of policy %d: %v", id, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
if job.Status == models.JobRunning ||
|
||||
job.Status == models.JobRetrying ||
|
||||
job.Status == models.JobPending {
|
||||
pa.CustomAbort(http.StatusPreconditionFailed, "policy has running/retrying/pending jobs, can not be deleted")
|
||||
}
|
||||
}
|
||||
|
||||
if err = dao.DeleteRepPolicy(id); err != nil {
|
||||
log.Errorf("failed to delete policy %d: %v", id, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
}
|
||||
|
267
api/replication_policy_test.go
Normal file
@ -0,0 +1,267 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
addPolicyName = "testPolicy"
|
||||
)
|
||||
|
||||
var addPolicyID int
|
||||
|
||||
func TestPoliciesPost(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//add target
|
||||
CommonAddTarget()
|
||||
targetID := int64(CommonGetTarget())
|
||||
repPolicy := &apilib.RepPolicyPost{int64(1), targetID, addPolicyName}
|
||||
|
||||
fmt.Println("Testing Policies Post API")
|
||||
|
||||
//-------------------case 1 : response code = 201------------------------//
|
||||
fmt.Println("case 1 : response code = 201")
|
||||
httpStatusCode, err = apiTest.AddPolicy(*admin, *repPolicy)
|
||||
if err != nil {
|
||||
t.Error("Error while add policy", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(201), httpStatusCode, "httpStatusCode should be 201")
|
||||
}
|
||||
|
||||
//-------------------case 2 : response code = 409------------------------//
|
||||
fmt.Println("case 1 : response code = 409:policy already exists")
|
||||
httpStatusCode, err = apiTest.AddPolicy(*admin, *repPolicy)
|
||||
if err != nil {
|
||||
t.Error("Error while add policy", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(409), httpStatusCode, "httpStatusCode should be 409")
|
||||
}
|
||||
|
||||
//-------------------case 3 : response code = 401------------------------//
|
||||
fmt.Println("case 3 : response code = 401: User need to log in first.")
|
||||
httpStatusCode, err = apiTest.AddPolicy(*unknownUsr, *repPolicy)
|
||||
if err != nil {
|
||||
t.Error("Error while add policy", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 401")
|
||||
}
|
||||
|
||||
//-------------------case 4 : response code = 400------------------------//
|
||||
fmt.Println("case 4 : response code = 400:project_id invalid.")
|
||||
|
||||
repPolicy = &apilib.RepPolicyPost{TargetId: targetID, Name: addPolicyName}
|
||||
httpStatusCode, err = apiTest.AddPolicy(*admin, *repPolicy)
|
||||
if err != nil {
|
||||
t.Error("Error while add policy", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
|
||||
}
|
||||
|
||||
//-------------------case 5 : response code = 400------------------------//
|
||||
fmt.Println("case 5 : response code = 400:project_id does not exist.")
|
||||
|
||||
repPolicy.ProjectId = int64(1111)
|
||||
httpStatusCode, err = apiTest.AddPolicy(*admin, *repPolicy)
|
||||
if err != nil {
|
||||
t.Error("Error while add policy", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
|
||||
}
|
||||
|
||||
//-------------------case 6 : response code = 400------------------------//
|
||||
fmt.Println("case 6 : response code = 400:target_id invalid.")
|
||||
|
||||
repPolicy = &apilib.RepPolicyPost{ProjectId: int64(1), Name: addPolicyName}
|
||||
httpStatusCode, err = apiTest.AddPolicy(*admin, *repPolicy)
|
||||
if err != nil {
|
||||
t.Error("Error while add policy", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
|
||||
}
|
||||
|
||||
//-------------------case 7 : response code = 400------------------------//
|
||||
fmt.Println("case 6 : response code = 400:target_id does not exist.")
|
||||
|
||||
repPolicy.TargetId = int64(1111)
|
||||
httpStatusCode, err = apiTest.AddPolicy(*admin, *repPolicy)
|
||||
if err != nil {
|
||||
t.Error("Error while add policy", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPoliciesList(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
var reslut []apilib.RepPolicy
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
fmt.Println("Testing Policies Get/List API")
|
||||
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
projectID := "1"
|
||||
httpStatusCode, reslut, err = apiTest.ListPolicies(*admin, addPolicyName, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while get policies", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
addPolicyID = int(reslut[0].Id)
|
||||
}
|
||||
|
||||
//-------------------case 2 : response code = 400------------------------//
|
||||
fmt.Println("case 2 : response code = 400:invalid projectID")
|
||||
projectID = "cc"
|
||||
httpStatusCode, reslut, err = apiTest.ListPolicies(*admin, addPolicyName, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while get policies", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPolicyGet(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
fmt.Println("Testing Policy Get API by PolicyID")
|
||||
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
|
||||
policyID := strconv.Itoa(addPolicyID)
|
||||
httpStatusCode, err = apiTest.GetPolicyByID(*admin, policyID)
|
||||
if err != nil {
|
||||
t.Error("Error while get policy", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPolicyUpdateInfo(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
targetID := int64(CommonGetTarget())
|
||||
policyInfo := &apilib.RepPolicyUpdate{TargetId: targetID, Name: "testNewName"}
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
fmt.Println("Testing Policy PUT API to update policyInfo")
|
||||
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
|
||||
policyID := strconv.Itoa(addPolicyID)
|
||||
httpStatusCode, err = apiTest.PutPolicyInfoByID(*admin, policyID, *policyInfo)
|
||||
if err != nil {
|
||||
t.Error("Error while update policyInfo", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPolicyUpdateEnablement(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
enablement := &apilib.RepPolicyEnablementReq{int32(0)}
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
fmt.Println("Testing Policy PUT API to update policy enablement")
|
||||
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
|
||||
policyID := strconv.Itoa(addPolicyID)
|
||||
httpStatusCode, err = apiTest.PutPolicyEnableByID(*admin, policyID, *enablement)
|
||||
if err != nil {
|
||||
t.Error("Error while put policy enablement", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
//-------------------case 2 : response code = 404------------------------//
|
||||
fmt.Println("case 2 : response code = 404,Not Found")
|
||||
|
||||
policyID = "111"
|
||||
httpStatusCode, err = apiTest.PutPolicyEnableByID(*admin, policyID, *enablement)
|
||||
if err != nil {
|
||||
t.Error("Error while put policy enablement", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPolicyDelete(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
fmt.Println("Testing Policy Delete API")
|
||||
|
||||
//-------------------case 1 : response code = 412------------------------//
|
||||
fmt.Println("case 1 : response code = 412:policy is enabled, can not be deleted")
|
||||
|
||||
CommonPolicyEabled(addPolicyID, 1)
|
||||
policyID := strconv.Itoa(addPolicyID)
|
||||
|
||||
httpStatusCode, err = apiTest.DeletePolicyByID(*admin, policyID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete policy", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(412), httpStatusCode, "httpStatusCode should be 412")
|
||||
}
|
||||
|
||||
//-------------------case 2 : response code = 200------------------------//
|
||||
fmt.Println("case 2 : response code = 200")
|
||||
|
||||
CommonPolicyEabled(addPolicyID, 0)
|
||||
policyID = strconv.Itoa(addPolicyID)
|
||||
|
||||
httpStatusCode, err = apiTest.DeletePolicyByID(*admin, policyID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete policy", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
CommonDelTarget()
|
||||
}
|
@ -16,16 +16,14 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
"github.com/vmware/harbor/service/cache"
|
||||
@ -35,6 +33,7 @@ import (
|
||||
|
||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||
|
||||
"github.com/vmware/harbor/utils"
|
||||
"github.com/vmware/harbor/utils/registry/auth"
|
||||
)
|
||||
|
||||
@ -47,23 +46,23 @@ type RepositoryAPI struct {
|
||||
// Get ...
|
||||
func (ra *RepositoryAPI) Get() {
|
||||
projectID, err := ra.GetInt64("project_id")
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get project id, error: %v", err)
|
||||
ra.RenderError(http.StatusBadRequest, "Invalid project id")
|
||||
return
|
||||
}
|
||||
p, err := dao.GetProjectByID(projectID)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in GetProjectById, error: %v", err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if p == nil {
|
||||
log.Warningf("Project with Id: %d does not exist", projectID)
|
||||
ra.RenderError(http.StatusNotFound, "")
|
||||
return
|
||||
if err != nil || projectID <= 0 {
|
||||
ra.CustomAbort(http.StatusBadRequest, "invalid project_id")
|
||||
}
|
||||
|
||||
if p.Public == 0 {
|
||||
page, pageSize := ra.getPaginationParams()
|
||||
|
||||
project, err := dao.GetProjectByID(projectID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project %d: %v", projectID, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
if project == nil {
|
||||
ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("project %d not found", projectID))
|
||||
}
|
||||
|
||||
if project.Public == 0 {
|
||||
var userID int
|
||||
|
||||
if svc_utils.VerifySecret(ra.Ctx.Request) {
|
||||
@ -73,37 +72,31 @@ func (ra *RepositoryAPI) Get() {
|
||||
}
|
||||
|
||||
if !checkProjectPermission(userID, projectID) {
|
||||
ra.RenderError(http.StatusForbidden, "")
|
||||
return
|
||||
ra.CustomAbort(http.StatusForbidden, "")
|
||||
}
|
||||
}
|
||||
|
||||
repoList, err := cache.GetRepoFromCache()
|
||||
repositories, err := getReposByProject(project.Name, ra.GetString("q"))
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get repo from cache, error: %v", err)
|
||||
ra.RenderError(http.StatusInternalServerError, "internal sever error")
|
||||
log.Errorf("failed to get repository: %v", err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
projectName := p.Name
|
||||
q := ra.GetString("q")
|
||||
var resp []string
|
||||
if len(q) > 0 {
|
||||
for _, r := range repoList {
|
||||
if strings.Contains(r, "/") && strings.Contains(r[strings.LastIndex(r, "/")+1:], q) && r[0:strings.LastIndex(r, "/")] == projectName {
|
||||
resp = append(resp, r)
|
||||
}
|
||||
}
|
||||
ra.Data["json"] = resp
|
||||
} else if len(projectName) > 0 {
|
||||
for _, r := range repoList {
|
||||
if strings.Contains(r, "/") && r[0:strings.LastIndex(r, "/")] == projectName {
|
||||
resp = append(resp, r)
|
||||
}
|
||||
}
|
||||
ra.Data["json"] = resp
|
||||
total := int64(len(repositories))
|
||||
|
||||
if (page-1)*pageSize > total {
|
||||
repositories = []string{}
|
||||
} else {
|
||||
ra.Data["json"] = repoList
|
||||
repositories = repositories[(page-1)*pageSize:]
|
||||
}
|
||||
|
||||
if page*pageSize <= total {
|
||||
repositories = repositories[:pageSize]
|
||||
}
|
||||
|
||||
ra.setPaginationHeader(total, page, pageSize)
|
||||
|
||||
ra.Data["json"] = repositories
|
||||
ra.ServeJSON()
|
||||
}
|
||||
|
||||
@ -114,7 +107,7 @@ func (ra *RepositoryAPI) Delete() {
|
||||
ra.CustomAbort(http.StatusBadRequest, "repo_name is nil")
|
||||
}
|
||||
|
||||
projectName := getProjectName(repoName)
|
||||
projectName, _ := utils.ParseRepository(repoName)
|
||||
project, err := dao.GetProjectByName(projectName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project %s: %v", projectName, err)
|
||||
@ -172,13 +165,15 @@ func (ra *RepositoryAPI) Delete() {
|
||||
for _, t := range tags {
|
||||
if err := rc.DeleteTag(t); err != nil {
|
||||
if regErr, ok := err.(*registry_error.Error); ok {
|
||||
ra.CustomAbort(regErr.StatusCode, regErr.Detail)
|
||||
if regErr.StatusCode != http.StatusNotFound {
|
||||
ra.CustomAbort(regErr.StatusCode, regErr.Detail)
|
||||
}
|
||||
} else {
|
||||
log.Errorf("error occurred while deleting tag %s:%s: %v", repoName, t, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
}
|
||||
|
||||
log.Errorf("error occurred while deleting tags of %s: %v", repoName, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
}
|
||||
log.Infof("delete tag: %s %s", repoName, t)
|
||||
log.Infof("delete tag: %s:%s", repoName, t)
|
||||
go TriggerReplicationByRepository(repoName, []string{t}, models.RepOpDelete)
|
||||
|
||||
go func(tag string) {
|
||||
@ -188,6 +183,18 @@ func (ra *RepositoryAPI) Delete() {
|
||||
}(t)
|
||||
}
|
||||
|
||||
exist, err := repositoryExist(repoName, rc)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check the existence of repository %s: %v", repoName, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
if !exist {
|
||||
if err = dao.DeleteRepository(repoName); err != nil {
|
||||
log.Errorf("failed to delete repository %s: %v", repoName, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Debug("refreshing catalog cache")
|
||||
if err := cache.RefreshCatalogCache(); err != nil {
|
||||
@ -208,7 +215,7 @@ func (ra *RepositoryAPI) GetTags() {
|
||||
ra.CustomAbort(http.StatusBadRequest, "repo_name is nil")
|
||||
}
|
||||
|
||||
projectName := getProjectName(repoName)
|
||||
projectName, _ := utils.ParseRepository(repoName)
|
||||
project, err := dao.GetProjectByName(projectName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project %s: %v", projectName, err)
|
||||
@ -267,7 +274,16 @@ func (ra *RepositoryAPI) GetManifests() {
|
||||
ra.CustomAbort(http.StatusBadRequest, "repo_name or tag is nil")
|
||||
}
|
||||
|
||||
projectName := getProjectName(repoName)
|
||||
version := ra.GetString("version")
|
||||
if len(version) == 0 {
|
||||
version = "v2"
|
||||
}
|
||||
|
||||
if version != "v1" && version != "v2" {
|
||||
ra.CustomAbort(http.StatusBadRequest, "version should be v1 or v2")
|
||||
}
|
||||
|
||||
projectName, _ := utils.ParseRepository(repoName)
|
||||
project, err := dao.GetProjectByName(projectName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project %s: %v", projectName, err)
|
||||
@ -291,10 +307,20 @@ func (ra *RepositoryAPI) GetManifests() {
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
}
|
||||
|
||||
item := models.RepoItem{}
|
||||
result := struct {
|
||||
Manifest interface{} `json:"manifest"`
|
||||
Config interface{} `json:"config,omitempty" `
|
||||
}{}
|
||||
|
||||
mediaTypes := []string{schema1.MediaTypeManifest}
|
||||
_, _, payload, err := rc.PullManifest(tag, mediaTypes)
|
||||
mediaTypes := []string{}
|
||||
switch version {
|
||||
case "v1":
|
||||
mediaTypes = append(mediaTypes, schema1.MediaTypeManifest)
|
||||
case "v2":
|
||||
mediaTypes = append(mediaTypes, schema2.MediaTypeManifest)
|
||||
}
|
||||
|
||||
_, mediaType, payload, err := rc.PullManifest(tag, mediaTypes)
|
||||
if err != nil {
|
||||
if regErr, ok := err.(*registry_error.Error); ok {
|
||||
ra.CustomAbort(regErr.StatusCode, regErr.Detail)
|
||||
@ -303,24 +329,33 @@ func (ra *RepositoryAPI) GetManifests() {
|
||||
log.Errorf("error occurred while getting manifest of %s:%s: %v", repoName, tag, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
}
|
||||
mani := models.Manifest{}
|
||||
err = json.Unmarshal(payload, &mani)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to decode json from response for manifests, repo name: %s, tag: %s, error: %v", repoName, tag, err)
|
||||
ra.RenderError(http.StatusInternalServerError, "Internal Server Error")
|
||||
return
|
||||
}
|
||||
v1Compatibility := mani.History[0].V1Compatibility
|
||||
|
||||
err = json.Unmarshal([]byte(v1Compatibility), &item)
|
||||
manifest, _, err := registry.UnMarshal(mediaType, payload)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to decode V1 field for repo, repo name: %s, tag: %s, error: %v", repoName, tag, err)
|
||||
ra.RenderError(http.StatusInternalServerError, "Internal Server Error")
|
||||
return
|
||||
log.Errorf("an error occurred while parsing manifest of %s:%s: %v", repoName, tag, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
item.DurationDays = strconv.Itoa(int(time.Since(item.Created).Hours()/24)) + " days"
|
||||
|
||||
ra.Data["json"] = item
|
||||
result.Manifest = manifest
|
||||
|
||||
deserializedmanifest, ok := manifest.(*schema2.DeserializedManifest)
|
||||
if ok {
|
||||
_, data, err := rc.PullBlob(deserializedmanifest.Target().Digest.String())
|
||||
if err != nil {
|
||||
log.Errorf("failed to get config of manifest %s:%s: %v", repoName, tag, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(data)
|
||||
if err != nil {
|
||||
log.Errorf("failed to read config of manifest %s:%s: %v", repoName, tag, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
result.Config = string(b)
|
||||
}
|
||||
|
||||
ra.Data["json"] = result
|
||||
ra.ServeJSON()
|
||||
}
|
||||
|
||||
@ -375,25 +410,14 @@ func (ra *RepositoryAPI) getUsername() (string, error) {
|
||||
|
||||
//GetTopRepos handles request GET /api/repositories/top
|
||||
func (ra *RepositoryAPI) GetTopRepos() {
|
||||
var err error
|
||||
var countNum int
|
||||
count := ra.GetString("count")
|
||||
if len(count) == 0 {
|
||||
countNum = 10
|
||||
} else {
|
||||
countNum, err = strconv.Atoi(count)
|
||||
if err != nil {
|
||||
log.Errorf("Get parameters error--count, err: %v", err)
|
||||
ra.CustomAbort(http.StatusBadRequest, "bad request of count")
|
||||
}
|
||||
if countNum <= 0 {
|
||||
log.Warning("count must be a positive integer")
|
||||
ra.CustomAbort(http.StatusBadRequest, "count is 0 or negative")
|
||||
}
|
||||
count, err := ra.GetInt("count", 10)
|
||||
if err != nil || count <= 0 {
|
||||
ra.CustomAbort(http.StatusBadRequest, "invalid count")
|
||||
}
|
||||
repos, err := dao.GetTopRepos(countNum)
|
||||
|
||||
repos, err := dao.GetTopRepos(count)
|
||||
if err != nil {
|
||||
log.Errorf("error occured in get top 10 repos: %v", err)
|
||||
log.Errorf("failed to get top repos: %v", err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
||||
}
|
||||
ra.Data["json"] = repos
|
||||
@ -417,11 +441,3 @@ func newRepositoryClient(endpoint string, insecure bool, username, password, rep
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func getProjectName(repository string) string {
|
||||
project := ""
|
||||
if strings.Contains(repository, "/") {
|
||||
project = repository[0:strings.LastIndex(repository, "/")]
|
||||
}
|
||||
return project
|
||||
}
|
||||
|
185
api/repository_test.go
Normal file
@ -0,0 +1,185 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
// "github.com/vmware/harbor/tests/apitests/apilib"
|
||||
// "strconv"
|
||||
)
|
||||
|
||||
func TestGetRepos(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
projectID := "1"
|
||||
|
||||
fmt.Println("Testing Repos Get API")
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
httpStatusCode, err = apiTest.GetRepos(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get repos by projectID", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
//-------------------case 2 : response code = 400------------------------//
|
||||
fmt.Println("case 2 : response code = 409,invalid project_id")
|
||||
projectID = "ccc"
|
||||
httpStatusCode, err = apiTest.GetRepos(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get repos by projectID", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
|
||||
}
|
||||
//-------------------case 3 : response code = 404------------------------//
|
||||
fmt.Println("case 3 : response code = 404:project not found")
|
||||
projectID = "111"
|
||||
httpStatusCode, err = apiTest.GetRepos(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get repos by projectID", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
func TestGetReposTags(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
var repoName string
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
fmt.Println("Testing ReposTags Get API")
|
||||
//-------------------case 1 : response code = 400------------------------//
|
||||
fmt.Println("case 1 : response code = 400,repo_name is nil")
|
||||
repoName = ""
|
||||
httpStatusCode, err = apiTest.GetReposTags(*admin, repoName)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get reposTags by repoName", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
|
||||
}
|
||||
//-------------------case 2 : response code = 404------------------------//
|
||||
fmt.Println("case 2 : response code = 404,repo not found")
|
||||
repoName = "errorRepos"
|
||||
httpStatusCode, err = apiTest.GetReposTags(*admin, repoName)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get reposTags by repoName", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
//-------------------case 3 : response code = 200------------------------//
|
||||
fmt.Println("case 3 : response code = 200")
|
||||
repoName = "library/hello-world"
|
||||
httpStatusCode, err = apiTest.GetReposTags(*admin, repoName)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get reposTags by repoName", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
func TestGetReposManifests(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
var repoName string
|
||||
var tag string
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
fmt.Println("Testing ReposManifests Get API")
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
repoName = "library/hello-world"
|
||||
tag = "latest"
|
||||
httpStatusCode, err = apiTest.GetReposManifests(*admin, repoName, tag)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get reposManifests by repoName and tag", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
//-------------------case 2 : response code = 404------------------------//
|
||||
fmt.Println("case 2 : response code = 404:tags error,manifest unknown")
|
||||
tag = "l"
|
||||
httpStatusCode, err = apiTest.GetReposManifests(*admin, repoName, tag)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get reposManifests by repoName and tag", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
|
||||
//-------------------case 3 : response code = 400------------------------//
|
||||
fmt.Println("case 3 : response code = 400,repo_name or is nil")
|
||||
repoName = ""
|
||||
httpStatusCode, err = apiTest.GetReposManifests(*admin, repoName, tag)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get reposManifests by repoName and tag", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
|
||||
}
|
||||
//-------------------case 4 : response code = 404------------------------//
|
||||
fmt.Println("case 4 : response code = 404,repo not found")
|
||||
repoName = "111"
|
||||
httpStatusCode, err = apiTest.GetReposManifests(*admin, repoName, tag)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get reposManifests by repoName and tag", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
func TestGetReposTop(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
var count string
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
fmt.Println("Testing ReposTop Get API")
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
count = "1"
|
||||
httpStatusCode, err = apiTest.GetReposTop(*admin, count)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get reposTop to show the most popular public repositories ", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
//-------------------case 2 : response code = 400------------------------//
|
||||
fmt.Println("case 2 : response code = 400,invalid count")
|
||||
count = "cc"
|
||||
httpStatusCode, err = apiTest.GetReposTop(*admin, count)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get reposTop to show the most popular public repositories ", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
@ -55,7 +55,7 @@ func (s *SearchAPI) Get() {
|
||||
var projects []models.Project
|
||||
|
||||
if isSysAdmin {
|
||||
projects, err = dao.GetAllProjects("")
|
||||
projects, err = dao.GetProjects("")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get all projects: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
@ -85,11 +85,12 @@ func (s *SearchAPI) Get() {
|
||||
}
|
||||
}
|
||||
|
||||
repositories, err2 := cache.GetRepoFromCache()
|
||||
if err2 != nil {
|
||||
log.Errorf("Failed to get repos from cache, error: %v", err2)
|
||||
s.CustomAbort(http.StatusInternalServerError, "Failed to get repositories search result")
|
||||
repositories, err := cache.GetRepoFromCache()
|
||||
if err != nil {
|
||||
log.Errorf("failed to list repositories: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
sort.Strings(repositories)
|
||||
repositoryResult := filterRepositories(repositories, projects, keyword)
|
||||
result := &searchResult{Project: projectResult, Repository: repositoryResult}
|
||||
@ -101,18 +102,19 @@ func filterRepositories(repositories []string, projects []models.Project, keywor
|
||||
i, j := 0, 0
|
||||
result := []map[string]interface{}{}
|
||||
for i < len(repositories) && j < len(projects) {
|
||||
r := &utils.Repository{Name: repositories[i]}
|
||||
d := strings.Compare(r.GetProject(), projects[j].Name)
|
||||
r := repositories[i]
|
||||
p, _ := utils.ParseRepository(r)
|
||||
d := strings.Compare(p, projects[j].Name)
|
||||
if d < 0 {
|
||||
i++
|
||||
continue
|
||||
} else if d == 0 {
|
||||
i++
|
||||
if len(keyword) != 0 && !strings.Contains(r.Name, keyword) {
|
||||
if len(keyword) != 0 && !strings.Contains(r, keyword) {
|
||||
continue
|
||||
}
|
||||
entry := make(map[string]interface{})
|
||||
entry["repository_name"] = r.Name
|
||||
entry["repository_name"] = r
|
||||
entry["project_name"] = projects[j].Name
|
||||
entry["project_id"] = projects[j].ProjectID
|
||||
entry["project_public"] = projects[j].Public
|
||||
|
32
api/search_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
)
|
||||
|
||||
func TestSearch(t *testing.T) {
|
||||
fmt.Println("Testing Search(SearchGet) API")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
var result apilib.Search
|
||||
result, err := apiTest.SearchGet("library")
|
||||
//fmt.Printf("%+v\n", result)
|
||||
if err != nil {
|
||||
t.Error("Error while search project or repository", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(result.Projects[0].Id, int64(1), "Project id should be equal")
|
||||
assert.Equal(result.Projects[0].Name, "library", "Project name should be library")
|
||||
assert.Equal(result.Projects[0].Public, int32(1), "Project public status should be 1 (true)")
|
||||
//t.Log(result)
|
||||
}
|
||||
//if result.Response.StatusCode != 200 {
|
||||
// t.Log(result.Response)
|
||||
//}
|
||||
|
||||
}
|
128
api/statistic.go
@ -17,14 +17,26 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
"github.com/vmware/harbor/service/cache"
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// MPC : count of my projects
|
||||
MPC = "my_project_count"
|
||||
// MRC : count of my repositories
|
||||
MRC = "my_repo_count"
|
||||
// PPC : count of public projects
|
||||
PPC = "public_project_count"
|
||||
// PRC : count of public repositories
|
||||
PRC = "public_repo_count"
|
||||
// TPC : total count of projects
|
||||
TPC = "total_project_count"
|
||||
// TRC : total count of repositories
|
||||
TRC = "total_repo_count"
|
||||
)
|
||||
|
||||
// StatisticAPI handles request to /api/statistics/
|
||||
type StatisticAPI struct {
|
||||
BaseAPI
|
||||
@ -38,80 +50,60 @@ func (s *StatisticAPI) Prepare() {
|
||||
|
||||
// Get total projects and repos of the user
|
||||
func (s *StatisticAPI) Get() {
|
||||
statistic := map[string]int64{}
|
||||
|
||||
n, err := dao.GetTotalOfProjects("", 1)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of public projects: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
statistic[PPC] = n
|
||||
|
||||
n, err = dao.GetTotalOfPublicRepositories("")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of public repositories: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
statistic[PRC] = n
|
||||
|
||||
isAdmin, err := dao.IsAdminRole(s.userID)
|
||||
if err != nil {
|
||||
log.Errorf("Error occured in check admin, error: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
var projectList []models.Project
|
||||
|
||||
if isAdmin {
|
||||
projectList, err = dao.GetAllProjects("")
|
||||
n, err := dao.GetTotalOfProjects("")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of projects: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
statistic[MPC] = n
|
||||
statistic[TPC] = n
|
||||
|
||||
n, err = dao.GetTotalOfRepositories("")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of repositories: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
statistic[MRC] = n
|
||||
statistic[TRC] = n
|
||||
} else {
|
||||
projectList, err = dao.GetUserRelevantProjects(s.userID, "")
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Error occured in QueryProject, error: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
proMap := map[string]int{}
|
||||
proMap["my_project_count"] = 0
|
||||
proMap["my_repo_count"] = 0
|
||||
proMap["public_project_count"] = 0
|
||||
proMap["public_repo_count"] = 0
|
||||
var publicProjects []models.Project
|
||||
publicProjects, err = dao.GetPublicProjects("")
|
||||
if err != nil {
|
||||
log.Errorf("Error occured in QueryPublicProject, error: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
proMap["public_project_count"] = len(publicProjects)
|
||||
for i := 0; i < len(publicProjects); i++ {
|
||||
proMap["public_repo_count"] += getRepoCountByProject(publicProjects[i].Name)
|
||||
}
|
||||
if isAdmin {
|
||||
proMap["total_project_count"] = len(projectList)
|
||||
proMap["total_repo_count"] = getTotalRepoCount()
|
||||
}
|
||||
for i := 0; i < len(projectList); i++ {
|
||||
if isAdmin {
|
||||
projectList[i].Role = models.PROJECTADMIN
|
||||
n, err := dao.GetTotalOfUserRelevantProjects(s.userID, "")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of projects for user %d: %v", s.userID, err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
if projectList[i].Role == models.PROJECTADMIN || projectList[i].Role == models.DEVELOPER ||
|
||||
projectList[i].Role == models.GUEST {
|
||||
proMap["my_project_count"]++
|
||||
proMap["my_repo_count"] += getRepoCountByProject(projectList[i].Name)
|
||||
statistic[MPC] = n
|
||||
|
||||
n, err = dao.GetTotalOfUserRelevantRepositories(s.userID, "")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of repositories for user %d: %v", s.userID, err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
statistic[MRC] = n
|
||||
}
|
||||
s.Data["json"] = proMap
|
||||
|
||||
s.Data["json"] = statistic
|
||||
s.ServeJSON()
|
||||
}
|
||||
|
||||
//getReposByProject returns repo numbers of specified project
|
||||
func getRepoCountByProject(projectName string) int {
|
||||
repoList, err := cache.GetRepoFromCache()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get repo from cache, error: %v", err)
|
||||
return 0
|
||||
}
|
||||
var resp int
|
||||
if len(projectName) > 0 {
|
||||
for _, r := range repoList {
|
||||
if strings.Contains(r, "/") && r[0:strings.LastIndex(r, "/")] == projectName {
|
||||
resp++
|
||||
}
|
||||
}
|
||||
return resp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
//getTotalRepoCount returns total repo count
|
||||
func getTotalRepoCount() int {
|
||||
repoList, err := cache.GetRepoFromCache()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get repo from cache, error: %v", err)
|
||||
return 0
|
||||
}
|
||||
return len(repoList)
|
||||
|
||||
}
|
||||
|
89
api/statistic_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
)
|
||||
|
||||
func TestStatisticGet(t *testing.T) {
|
||||
if err := SyncRegistry(); err != nil {
|
||||
t.Fatalf("failed to sync repositories from registry: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("Testing Statistic API")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//prepare for test
|
||||
|
||||
var myProCount, pubProCount, totalProCount int32
|
||||
result, err := apiTest.StatisticGet(*admin)
|
||||
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")
|
||||
|
||||
}
|
||||
|
||||
//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")
|
||||
|
||||
}
|
@ -20,6 +20,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
@ -34,10 +35,14 @@ import (
|
||||
// TargetAPI handles request to /api/targets/ping /api/targets/{}
|
||||
type TargetAPI struct {
|
||||
BaseAPI
|
||||
secretKey string
|
||||
}
|
||||
|
||||
// Prepare validates the user
|
||||
func (t *TargetAPI) Prepare() {
|
||||
//TODO:move to config
|
||||
t.secretKey = os.Getenv("SECRET_KEY")
|
||||
|
||||
userID := t.ValidateUser()
|
||||
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||
if err != nil {
|
||||
@ -76,7 +81,7 @@ func (t *TargetAPI) Ping() {
|
||||
password = target.Password
|
||||
|
||||
if len(password) != 0 {
|
||||
password, err = utils.ReversibleDecrypt(password)
|
||||
password, err = utils.ReversibleDecrypt(password, t.secretKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt password: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
@ -136,7 +141,7 @@ func (t *TargetAPI) Get() {
|
||||
// modify other fields of target he does not need to input the password again.
|
||||
// The security issue can be fixed by enable https.
|
||||
if len(target.Password) != 0 {
|
||||
pwd, err := utils.ReversibleDecrypt(target.Password)
|
||||
pwd, err := utils.ReversibleDecrypt(target.Password, t.secretKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt password: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
@ -162,7 +167,7 @@ func (t *TargetAPI) List() {
|
||||
continue
|
||||
}
|
||||
|
||||
str, err := utils.ReversibleDecrypt(target.Password)
|
||||
str, err := utils.ReversibleDecrypt(target.Password, t.secretKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt password: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
@ -201,7 +206,11 @@ func (t *TargetAPI) Post() {
|
||||
}
|
||||
|
||||
if len(target.Password) != 0 {
|
||||
target.Password = utils.ReversibleEncrypt(target.Password)
|
||||
target.Password, err = utils.ReversibleEncrypt(target.Password, t.secretKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to encrypt password: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
}
|
||||
|
||||
id, err := dao.AddRepTarget(*target)
|
||||
@ -275,7 +284,11 @@ func (t *TargetAPI) Put() {
|
||||
target.ID = id
|
||||
|
||||
if len(target.Password) != 0 {
|
||||
target.Password = utils.ReversibleEncrypt(target.Password)
|
||||
target.Password, err = utils.ReversibleEncrypt(target.Password, t.secretKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to encrypt password: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
}
|
||||
|
||||
if err := dao.UpdateRepTarget(*target); err != nil {
|
||||
|
274
api/target_test.go
Normal file
@ -0,0 +1,274 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
)
|
||||
|
||||
const (
|
||||
addTargetName = "testTargets"
|
||||
)
|
||||
|
||||
var addTargetID int
|
||||
|
||||
func TestTargetsPost(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
endPoint := os.Getenv("REGISTRY_URL")
|
||||
repTargets := &apilib.RepTargetPost{endPoint, addTargetName, adminName, adminPwd}
|
||||
|
||||
fmt.Println("Testing Targets Post API")
|
||||
|
||||
//-------------------case 1 : response code = 201------------------------//
|
||||
fmt.Println("case 1 : response code = 201")
|
||||
httpStatusCode, err = apiTest.AddTargets(*admin, *repTargets)
|
||||
if err != nil {
|
||||
t.Error("Error whihle add targets", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(201), httpStatusCode, "httpStatusCode should be 201")
|
||||
}
|
||||
|
||||
//-----------case 2 : response code = 409,name is already used-----------//
|
||||
fmt.Println("case 2 : response code = 409,name is already used")
|
||||
httpStatusCode, err = apiTest.AddTargets(*admin, *repTargets)
|
||||
if err != nil {
|
||||
t.Error("Error whihle add targets", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(409), httpStatusCode, "httpStatusCode should be 409")
|
||||
}
|
||||
|
||||
//-----------case 3 : response code = 409,name is already used-----------//
|
||||
fmt.Println("case 3 : response code = 409,endPoint is already used")
|
||||
repTargets.Username = "errName"
|
||||
httpStatusCode, err = apiTest.AddTargets(*admin, *repTargets)
|
||||
if err != nil {
|
||||
t.Error("Error whihle add targets", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(409), httpStatusCode, "httpStatusCode should be 409")
|
||||
}
|
||||
|
||||
//--------case 4 : response code = 401,User need to log in first.--------//
|
||||
fmt.Println("case 4 : response code = 401,User need to log in first.")
|
||||
httpStatusCode, err = apiTest.AddTargets(*unknownUsr, *repTargets)
|
||||
if err != nil {
|
||||
t.Error("Error whihle add targets", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 401")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
}
|
||||
|
||||
func TestTargetsGet(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
var reslut []apilib.RepTarget
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
fmt.Println("Testing Targets Get API")
|
||||
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
httpStatusCode, reslut, err = apiTest.ListTargets(*admin, addTargetName)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get targets", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
addTargetID = int(reslut[0].Id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTargetPing(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
fmt.Println("Testing Targets Ping Post API")
|
||||
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
id := strconv.Itoa(addTargetID)
|
||||
httpStatusCode, err = apiTest.PingTargetsByID(*admin, id)
|
||||
if err != nil {
|
||||
t.Error("Error whihle ping target", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
//--------------case 2 : response code = 404,target not found------------//
|
||||
fmt.Println("case 2 : response code = 404,target not found")
|
||||
id = "1111"
|
||||
httpStatusCode, err = apiTest.PingTargetsByID(*admin, id)
|
||||
if err != nil {
|
||||
t.Error("Error whihle ping target", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
|
||||
//------------case 3 : response code = 400,targetID is invalid-----------//
|
||||
fmt.Println("case 2 : response code = 400,target not found")
|
||||
id = "cc"
|
||||
httpStatusCode, err = apiTest.PingTargetsByID(*admin, id)
|
||||
if err != nil {
|
||||
t.Error("Error whihle ping target", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTargetGetByID(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
fmt.Println("Testing Targets Get API by Id")
|
||||
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
id := strconv.Itoa(addTargetID)
|
||||
httpStatusCode, err = apiTest.GetTargetByID(*admin, id)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get target by id", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
//--------------case 2 : response code = 404,target not found------------//
|
||||
fmt.Println("case 2 : response code = 404,target not found")
|
||||
id = "1111"
|
||||
httpStatusCode, err = apiTest.GetTargetByID(*admin, id)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get target by id", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTargetsPut(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
endPoint := "1.1.1.1"
|
||||
updateRepTargets := &apilib.RepTargetPost{endPoint, addTargetName, adminName, adminPwd}
|
||||
id := strconv.Itoa(addTargetID)
|
||||
|
||||
fmt.Println("Testing Target Put API")
|
||||
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
httpStatusCode, err = apiTest.PutTargetByID(*admin, id, *updateRepTargets)
|
||||
if err != nil {
|
||||
t.Error("Error whihle update target", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
//--------------case 2 : response code = 404,target not found------------//
|
||||
id = "111"
|
||||
fmt.Println("case 2 : response code = 404,target not found")
|
||||
httpStatusCode, err = apiTest.PutTargetByID(*admin, id, *updateRepTargets)
|
||||
if err != nil {
|
||||
t.Error("Error whihle update target", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
|
||||
}
|
||||
func TestTargetGetPolicies(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
fmt.Println("Testing Targets Get API to list policies")
|
||||
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
id := strconv.Itoa(addTargetID)
|
||||
httpStatusCode, err = apiTest.GetTargetPoliciesByID(*admin, id)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get target by id", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
//--------------case 2 : response code = 404,target not found------------//
|
||||
fmt.Println("case 2 : response code = 404,target not found")
|
||||
id = "1111"
|
||||
httpStatusCode, err = apiTest.GetTargetPoliciesByID(*admin, id)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get target by id", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTargetsDelete(t *testing.T) {
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
id := strconv.Itoa(addTargetID)
|
||||
fmt.Println("Testing Targets Delete API")
|
||||
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
fmt.Println("case 1 : response code = 200")
|
||||
httpStatusCode, err = apiTest.DeleteTargetsByID(*admin, id)
|
||||
if err != nil {
|
||||
t.Error("Error whihle delete targets", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
//--------------case 2 : response code = 404,target not found------------//
|
||||
fmt.Println("case 2 : response code = 404,target not found")
|
||||
id = "1111"
|
||||
httpStatusCode, err = apiTest.DeleteTargetsByID(*admin, id)
|
||||
if err != nil {
|
||||
t.Error("Error whihle delete targets", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||
}
|
||||
|
||||
}
|
@ -315,13 +315,13 @@ func (ua *UserAPI) ToggleUserAdminRole() {
|
||||
// validate only validate when user register
|
||||
func validate(user models.User) error {
|
||||
|
||||
if isIllegalLength(user.Username, 0, 20) {
|
||||
if isIllegalLength(user.Username, 1, 20) {
|
||||
return fmt.Errorf("Username with illegal length.")
|
||||
}
|
||||
if isContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
|
||||
return fmt.Errorf("Username contains illegal characters.")
|
||||
}
|
||||
if isIllegalLength(user.Password, 0, 20) {
|
||||
if isIllegalLength(user.Password, 7, 20) {
|
||||
return fmt.Errorf("Password with illegal length.")
|
||||
}
|
||||
if err := commonValidate(user); err != nil {
|
||||
|
398
api/user_test.go
Normal file
@ -0,0 +1,398 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testUser0002ID, testUser0003ID int
|
||||
var testUser0002, testUser0003 apilib.User
|
||||
var testUser0002Auth, testUser0003Auth *usrInfo
|
||||
|
||||
func TestUsersPost(t *testing.T) {
|
||||
|
||||
fmt.Println("Testing User Add")
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//case 1: register a new user without admin auth, expect 400, because self registration is on
|
||||
fmt.Println("Register user without admin auth")
|
||||
code, err := apiTest.UsersPost(testUser0002)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a test User", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(400, code, "Add user status should be 400")
|
||||
}
|
||||
|
||||
//case 2: register a new user with admin auth, but username is empty, expect 400
|
||||
fmt.Println("Register user with admin auth, but username is empty")
|
||||
code, err = apiTest.UsersPost(testUser0002, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a user", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(400, code, "Add user status should be 400")
|
||||
}
|
||||
|
||||
//case 3: register a new user with admin auth, but bad username format, expect 400
|
||||
testUser0002.Username = "test@$"
|
||||
fmt.Println("Register user with admin auth, but bad username format")
|
||||
code, err = apiTest.UsersPost(testUser0002, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a user", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(400, code, "Add user status should be 400")
|
||||
}
|
||||
|
||||
//case 4: register a new user with admin auth, but bad userpassword format, expect 400
|
||||
testUser0002.Username = "testUser0002"
|
||||
fmt.Println("Register user with admin auth, but empty password.")
|
||||
code, err = apiTest.UsersPost(testUser0002, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a user", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(400, code, "Add user status should be 400")
|
||||
}
|
||||
|
||||
//case 5: register a new user with admin auth, but email is empty, expect 400
|
||||
testUser0002.Password = "testUser0002"
|
||||
fmt.Println("Register user with admin auth, but email is empty")
|
||||
code, err = apiTest.UsersPost(testUser0002, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a user", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(400, code, "Add user status should be 400")
|
||||
}
|
||||
|
||||
//case 6: register a new user with admin auth, but bad email format, expect 400
|
||||
testUser0002.Email = "test..."
|
||||
fmt.Println("Register user with admin auth, but bad email format")
|
||||
code, err = apiTest.UsersPost(testUser0002, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a user", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(400, code, "Add user status should be 400")
|
||||
}
|
||||
|
||||
//case 7: register a new user with admin auth, but userrealname is empty, expect 400
|
||||
/*
|
||||
testUser0002.Email = "testUser0002@mydomain.com"
|
||||
fmt.Println("Register user with admin auth, but user realname is empty")
|
||||
code, err = apiTest.UsersPost(testUser0002, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a user", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(400, code, "Add user status should be 400")
|
||||
}
|
||||
*/
|
||||
//case 8: register a new user with admin auth, but bad userrealname format, expect 400
|
||||
testUser0002.Email = "testUser0002@mydomain.com"
|
||||
testUser0002.Realname = "test$com"
|
||||
fmt.Println("Register user with admin auth, but bad user realname format")
|
||||
code, err = apiTest.UsersPost(testUser0002, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a user", err.Error())
|
||||
t.Log(err)
|
||||
|
||||
} else {
|
||||
assert.Equal(400, code, "Add user status should be 400")
|
||||
}
|
||||
|
||||
//case 9: register a new user with admin auth, but bad user comment, expect 400
|
||||
testUser0002.Realname = "testUser0002"
|
||||
testUser0002.Comment = "vmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"
|
||||
fmt.Println("Register user with admin auth, but bad user comment format")
|
||||
code, err = apiTest.UsersPost(testUser0002, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a user", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(400, code, "Add user status should be 400")
|
||||
}
|
||||
|
||||
//case 10: register a new user with admin auth, expect 201
|
||||
fmt.Println("Register user with admin auth, right parameters")
|
||||
testUser0002.Comment = "test user"
|
||||
code, err = apiTest.UsersPost(testUser0002, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a user", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(201, code, "Add user status should be 201")
|
||||
}
|
||||
|
||||
//case 11: register duplicate user with admin auth, expect 409
|
||||
fmt.Println("Register duplicate user with admin auth")
|
||||
code, err = apiTest.UsersPost(testUser0002, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a user", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(409, code, "Add user status should be 409")
|
||||
}
|
||||
|
||||
//case 12: register a new user with admin auth, but duplicate email, expect 409
|
||||
fmt.Println("Register user with admin auth, but duplicate email")
|
||||
testUser0002.Username = "testUsertest"
|
||||
testUser0002.Email = "testUser0002@mydomain.com"
|
||||
code, err = apiTest.UsersPost(testUser0002, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a user", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(409, code, "Add user status should be 409")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsersGet(t *testing.T) {
|
||||
|
||||
fmt.Println("Testing User Get")
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
testUser0002.Username = "testUser0002"
|
||||
//case 1: Get user2 with common auth, but no userid in path, expect 403
|
||||
|
||||
testUser0002Auth = &usrInfo{"testUser0002", "testUser0002"}
|
||||
code, users, err := apiTest.UsersGet(testUser0002.Username, *testUser0002Auth)
|
||||
if err != nil {
|
||||
t.Error("Error occured while get users", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(403, code, "Get users status should be 403")
|
||||
}
|
||||
//case 2: Get user2 with admin auth, expect 200
|
||||
code, users, err = apiTest.UsersGet(testUser0002.Username, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while get users", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(200, code, "Get users status should be 200")
|
||||
assert.Equal(1, len(users), "Get users record should be 1 ")
|
||||
testUser0002ID = users[0].UserId
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsersGetByID(t *testing.T) {
|
||||
|
||||
fmt.Println("Testing User GetByID")
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//case 1: Get user2 with userID and his own auth, expect 200
|
||||
code, user, err := apiTest.UsersGetByID(testUser0002.Username, *testUser0002Auth, testUser0002ID)
|
||||
if err != nil {
|
||||
t.Error("Error occured while get users", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(200, code, "Get users status should be 200")
|
||||
assert.Equal(testUser0002.Username, user.Username, "Get users username should be equal")
|
||||
assert.Equal(testUser0002.Email, user.Email, "Get users email should be equal")
|
||||
}
|
||||
//case 2: Get user2 with user3 auth, expect 403
|
||||
testUser0003.Username = "testUser0003"
|
||||
testUser0003.Email = "testUser0003@mydomain.com"
|
||||
testUser0003.Password = "testUser0003"
|
||||
testUser0003.Realname = "testUser0003"
|
||||
code, err = apiTest.UsersPost(testUser0003, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while add a user", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(201, code, "Add user status should be 201")
|
||||
}
|
||||
|
||||
testUser0003Auth = &usrInfo{"testUser0003", "testUser0003"}
|
||||
code, user, err = apiTest.UsersGetByID(testUser0002.Username, *testUser0003Auth, testUser0002ID)
|
||||
if err != nil {
|
||||
t.Error("Error occured while get users", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(403, code, "Get users status should be 403")
|
||||
}
|
||||
//case 3: Get user that does not exist with user2 auth, expect 404 not found.
|
||||
code, user, err = apiTest.UsersGetByID(testUser0002.Username, *testUser0002Auth, 1000)
|
||||
if err != nil {
|
||||
t.Error("Error occured while change user profile", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(404, code, "Get users status should be 404")
|
||||
}
|
||||
// Get user3ID in order to delete at the last of the test
|
||||
code, users, err := apiTest.UsersGet(testUser0003.Username, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while change user profile", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(200, code, "Get users status should be 200")
|
||||
assert.Equal(1, len(users), "Get users record should be 1")
|
||||
testUser0003ID = users[0].UserId
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsersPut(t *testing.T) {
|
||||
fmt.Println("Testing User Put")
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
var profile apilib.UserProfile
|
||||
//case 1: change user2 profile with user3 auth
|
||||
code, err := apiTest.UsersPut(testUser0002ID, profile, *testUser0003Auth)
|
||||
if err != nil {
|
||||
t.Error("Error occured while change user profile", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(403, code, "Get users status should be 403")
|
||||
}
|
||||
//case 2: change user2 profile with user2 auth, but bad parameters format.
|
||||
code, err = apiTest.UsersPut(testUser0002ID, profile, *testUser0002Auth)
|
||||
if err != nil {
|
||||
t.Error("Error occured while change user profile", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(400, code, "Get users status should be 400")
|
||||
}
|
||||
//case 3: change user2 profile with user2 auth, but duplicate email.
|
||||
profile.Realname = "test user"
|
||||
profile.Email = "testUser0003@mydomain.com"
|
||||
profile.Comment = "change profile"
|
||||
code, err = apiTest.UsersPut(testUser0002ID, profile, *testUser0002Auth)
|
||||
if err != nil {
|
||||
t.Error("Error occured while change user profile", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(409, code, "Get users status should be 409")
|
||||
}
|
||||
//case 4: change user2 profile with user2 auth, right parameters format.
|
||||
profile.Realname = "test user"
|
||||
profile.Email = "testUser0002@vmware.com"
|
||||
profile.Comment = "change profile"
|
||||
code, err = apiTest.UsersPut(testUser0002ID, profile, *testUser0002Auth)
|
||||
if err != nil {
|
||||
t.Error("Error occured while change user profile", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(200, code, "Get users status should be 200")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsersToggleAdminRole(t *testing.T) {
|
||||
fmt.Println("Testing Toggle User Admin Role")
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
//case 1: toggle user2 admin role without admin auth
|
||||
code, err := apiTest.UsersToggleAdminRole(testUser0002ID, *testUser0002Auth, int32(1))
|
||||
if err != nil {
|
||||
t.Error("Error occured while change user profile", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(403, code, "Get users status should be 403")
|
||||
}
|
||||
//case 2: toggle user2 admin role with admin auth
|
||||
code, err = apiTest.UsersToggleAdminRole(testUser0002ID, *admin, int32(1))
|
||||
if err != nil {
|
||||
t.Error("Error occured while change user profile", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(200, code, "Get users status should be 200")
|
||||
}
|
||||
}
|
||||
func TestUsersUpdatePassword(t *testing.T) {
|
||||
fmt.Println("Testing Update User Password")
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
password := apilib.Password{OldPassword: "", NewPassword: ""}
|
||||
//case 1: update user2 password with user3 auth
|
||||
code, err := apiTest.UsersUpdatePassword(testUser0002ID, password, *testUser0003Auth)
|
||||
if err != nil {
|
||||
t.Error("Error occured while update user password", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(403, code, "Update user password status should be 403")
|
||||
}
|
||||
//case 2: update user2 password with admin auth, but oldpassword is empty
|
||||
code, err = apiTest.UsersUpdatePassword(testUser0002ID, password, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while change user profile", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(400, code, "Get users status should be 400")
|
||||
}
|
||||
//case 3: update user2 password with admin auth, but oldpassword is wrong
|
||||
password.OldPassword = "000"
|
||||
code, err = apiTest.UsersUpdatePassword(testUser0002ID, password, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while change user profile", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(403, code, "Get users status should be 403")
|
||||
}
|
||||
//case 4: update user2 password with admin auth, but newpassword is empty
|
||||
password.OldPassword = "testUser0002"
|
||||
code, err = apiTest.UsersUpdatePassword(testUser0002ID, password, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while change user profile", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(400, code, "Get users status should be 400")
|
||||
}
|
||||
//case 5: update user2 password with admin auth, right parameters
|
||||
password.NewPassword = "TestUser0002"
|
||||
code, err = apiTest.UsersUpdatePassword(testUser0002ID, password, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while change user profile", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(200, code, "Get users status should be 200")
|
||||
testUser0002.Password = password.NewPassword
|
||||
testUser0002Auth.Passwd = password.NewPassword
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsersDelete(t *testing.T) {
|
||||
|
||||
fmt.Println("Testing User Delete")
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//case 1:delete user without admin auth
|
||||
code, err := apiTest.UsersDelete(testUser0002ID, *testUser0003Auth)
|
||||
if err != nil {
|
||||
t.Error("Error occured while delete a testUser", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(403, code, "Delete testUser status should be 403")
|
||||
}
|
||||
//case 2: delete user with admin auth, user2 has already been toggled to admin, but can not delete himself
|
||||
code, err = apiTest.UsersDelete(testUser0002ID, *testUser0002Auth)
|
||||
if err != nil {
|
||||
t.Error("Error occured while delete a testUser", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(403, code, "Delete testUser status should be 403")
|
||||
}
|
||||
//case 3: delete user with admin auth
|
||||
code, err = apiTest.UsersDelete(testUser0002ID, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while delete a testUser", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(200, code, "Delete testUser status should be 200")
|
||||
}
|
||||
//delete user3 with admin auth
|
||||
code, err = apiTest.UsersDelete(testUser0003ID, *admin)
|
||||
if err != nil {
|
||||
t.Error("Error occured while delete a testUser", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(200, code, "Delete testUser status should be 200")
|
||||
}
|
||||
}
|
275
api/utils.go
@ -20,13 +20,20 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
"github.com/vmware/harbor/service/cache"
|
||||
"github.com/vmware/harbor/utils"
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
"github.com/vmware/harbor/utils/registry"
|
||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||
)
|
||||
|
||||
func checkProjectPermission(userID int, projectID int64) bool {
|
||||
@ -115,7 +122,14 @@ func TriggerReplication(policyID int64, repository string,
|
||||
|
||||
url := buildReplicationURL()
|
||||
|
||||
resp, err := http.DefaultClient.Post(url, "application/json", bytes.NewBuffer(b))
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addAuthentication(req)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -188,7 +202,16 @@ func postReplicationAction(policyID int64, acton string) error {
|
||||
|
||||
url := buildReplicationActionURL()
|
||||
|
||||
resp, err := http.DefaultClient.Post(url, "application/json", bytes.NewBuffer(b))
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addAuthentication(req)
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -207,6 +230,217 @@ func postReplicationAction(policyID int64, acton string) error {
|
||||
return fmt.Errorf("%d %s", resp.StatusCode, string(b))
|
||||
}
|
||||
|
||||
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"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SyncRegistry syncs the repositories of registry with database.
|
||||
func SyncRegistry() error {
|
||||
|
||||
log.Debugf("Start syncing repositories from registry to DB... ")
|
||||
|
||||
reposInRegistry, err := catalog()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var repoRecordsInDB []models.RepoRecord
|
||||
repoRecordsInDB, err = dao.GetAllRepositories()
|
||||
if err != nil {
|
||||
log.Errorf("error occurred while getting all registories. %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var reposInDB []string
|
||||
for _, repoRecordInDB := range repoRecordsInDB {
|
||||
reposInDB = append(reposInDB, repoRecordInDB.Name)
|
||||
}
|
||||
|
||||
var reposToAdd []string
|
||||
var reposToDel []string
|
||||
reposToAdd, reposToDel, err = diffRepos(reposInRegistry, reposInDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(reposToAdd) > 0 {
|
||||
log.Debugf("Start adding repositories into DB... ")
|
||||
for _, repoToAdd := range reposToAdd {
|
||||
project, _ := utils.ParseRepository(repoToAdd)
|
||||
user, err := dao.GetAccessLogCreator(repoToAdd)
|
||||
if err != nil {
|
||||
log.Errorf("Error happens when getting the repository owner from access log: %v", err)
|
||||
}
|
||||
if len(user) == 0 {
|
||||
user = "anonymous"
|
||||
}
|
||||
pullCount, err := dao.CountPull(repoToAdd)
|
||||
if err != nil {
|
||||
log.Errorf("Error happens when counting pull count from access log: %v", err)
|
||||
}
|
||||
repoRecord := models.RepoRecord{Name: repoToAdd, OwnerName: user, ProjectName: project, PullCount: pullCount}
|
||||
if err := dao.AddRepository(repoRecord); err != nil {
|
||||
log.Errorf("Error happens when adding the missing repository: %v", err)
|
||||
}
|
||||
log.Debugf("Add repository: %s success.", repoToAdd)
|
||||
}
|
||||
}
|
||||
|
||||
if len(reposToDel) > 0 {
|
||||
log.Debugf("Start deleting repositories from DB... ")
|
||||
for _, repoToDel := range reposToDel {
|
||||
if err := dao.DeleteRepository(repoToDel); err != nil {
|
||||
log.Errorf("Error happens when deleting the repository: %v", err)
|
||||
}
|
||||
log.Debugf("Delete repository: %s success.", repoToDel)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Sync repositories from registry to DB is done.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func catalog() ([]string, error) {
|
||||
repositories := []string{}
|
||||
|
||||
rc, err := initRegistryClient()
|
||||
if err != nil {
|
||||
return repositories, err
|
||||
}
|
||||
|
||||
repositories, err = rc.Catalog()
|
||||
if err != nil {
|
||||
return repositories, err
|
||||
}
|
||||
|
||||
return repositories, nil
|
||||
}
|
||||
|
||||
func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string, error) {
|
||||
var needsAdd []string
|
||||
var needsDel []string
|
||||
|
||||
sort.Strings(reposInRegistry)
|
||||
sort.Strings(reposInDB)
|
||||
|
||||
i, j := 0, 0
|
||||
repoInR, repoInD := "", ""
|
||||
for i < len(reposInRegistry) && j < len(reposInDB) {
|
||||
repoInR = reposInRegistry[i]
|
||||
repoInD = reposInDB[j]
|
||||
d := strings.Compare(repoInR, repoInD)
|
||||
if d < 0 {
|
||||
i++
|
||||
exist, err := projectExists(repoInR)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check the existence of project %s: %v", repoInR, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO remove the workaround when the bug of registry is fixed
|
||||
// TODO read it from config
|
||||
endpoint := os.Getenv("REGISTRY_URL")
|
||||
client, err := cache.NewRepositoryClient(endpoint, true,
|
||||
"admin", repoInR, "repository", repoInR)
|
||||
if err != nil {
|
||||
return needsAdd, needsDel, err
|
||||
}
|
||||
|
||||
exist, err = repositoryExist(repoInR, client)
|
||||
if err != nil {
|
||||
return needsAdd, needsDel, err
|
||||
}
|
||||
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
|
||||
needsAdd = append(needsAdd, repoInR)
|
||||
} else if d > 0 {
|
||||
needsDel = append(needsDel, repoInD)
|
||||
j++
|
||||
} else {
|
||||
i++
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
for i < len(reposInRegistry) {
|
||||
repoInR = reposInRegistry[i]
|
||||
i++
|
||||
exist, err := projectExists(repoInR)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check whether project of %s exists: %v", repoInR, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
needsAdd = append(needsAdd, repoInR)
|
||||
}
|
||||
|
||||
for j < len(reposInDB) {
|
||||
needsDel = append(needsDel, reposInDB[j])
|
||||
j++
|
||||
}
|
||||
|
||||
return needsAdd, needsDel, nil
|
||||
}
|
||||
|
||||
func projectExists(repository string) (bool, error) {
|
||||
project, _ := utils.ParseRepository(repository)
|
||||
return dao.ProjectExists(project)
|
||||
}
|
||||
|
||||
func initRegistryClient() (r *registry.Registry, err error) {
|
||||
endpoint := os.Getenv("REGISTRY_URL")
|
||||
|
||||
addr := endpoint
|
||||
if strings.Contains(endpoint, "/") {
|
||||
addr = endpoint[strings.LastIndex(endpoint, "/")+1:]
|
||||
}
|
||||
|
||||
ch := make(chan int, 1)
|
||||
go func() {
|
||||
var err error
|
||||
var c net.Conn
|
||||
for {
|
||||
c, err = net.DialTimeout("tcp", addr, 20*time.Second)
|
||||
if err == nil {
|
||||
c.Close()
|
||||
ch <- 1
|
||||
} else {
|
||||
log.Errorf("failed to connect to registry client, retry after 2 seconds :%v", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-ch:
|
||||
case <-time.After(60 * time.Second):
|
||||
panic("Failed to connect to registry client after 60 seconds")
|
||||
}
|
||||
|
||||
registryClient, err := cache.NewRegistryClient(endpoint, true, "admin",
|
||||
"registry", "catalog", "*")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return registryClient, nil
|
||||
}
|
||||
|
||||
func buildReplicationURL() string {
|
||||
url := getJobServiceURL()
|
||||
return fmt.Sprintf("%s/api/jobs/replication", url)
|
||||
@ -233,3 +467,40 @@ func getJobServiceURL() string {
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func getReposByProject(name string, keyword ...string) ([]string, error) {
|
||||
repositories := []string{}
|
||||
|
||||
repos, err := dao.GetRepositoryByProjectName(name)
|
||||
if err != nil {
|
||||
return repositories, err
|
||||
}
|
||||
|
||||
needMatchKeyword := len(keyword) > 0 && len(keyword[0]) != 0
|
||||
|
||||
for _, repo := range repos {
|
||||
if needMatchKeyword &&
|
||||
!strings.Contains(repo.Name, keyword[0]) {
|
||||
continue
|
||||
}
|
||||
|
||||
repositories = append(repositories, repo.Name)
|
||||
}
|
||||
|
||||
return repositories, nil
|
||||
}
|
||||
|
||||
func getAllRepos() ([]string, error) {
|
||||
return cache.GetRepoFromCache()
|
||||
}
|
||||
|
||||
func repositoryExist(name string, client *registry.Repository) (bool, error) {
|
||||
tags, err := client.ListTag()
|
||||
if err != nil {
|
||||
if regErr, ok := err.(*registry_error.Error); ok && regErr.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return len(tags) != 0, nil
|
||||
}
|
||||
|
9
auth/auth_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
9
auth/db/db_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
@ -40,62 +40,96 @@ const metaChars = "&|!=~*<>()"
|
||||
// be associated to other entities in the system.
|
||||
func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||
|
||||
ldapURL := os.Getenv("LDAP_URL")
|
||||
if ldapURL == "" {
|
||||
return nil, errors.New("Can not get any available LDAP_URL.")
|
||||
}
|
||||
log.Debug("ldapURL:", ldapURL)
|
||||
|
||||
p := m.Principal
|
||||
for _, c := range metaChars {
|
||||
if strings.ContainsRune(p, c) {
|
||||
return nil, fmt.Errorf("the principal contains meta char: %q", c)
|
||||
}
|
||||
}
|
||||
|
||||
ldapURL := os.Getenv("LDAP_URL")
|
||||
if ldapURL == "" {
|
||||
return nil, errors.New("Can not get any available LDAP_URL.")
|
||||
}
|
||||
log.Debug("ldapURL:", ldapURL)
|
||||
ldap, err := openldap.Initialize(ldapURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ldap.SetOption(openldap.LDAP_OPT_PROTOCOL_VERSION, openldap.LDAP_VERSION3)
|
||||
|
||||
ldapBaseDn := os.Getenv("LDAP_BASE_DN")
|
||||
if ldapBaseDn == "" {
|
||||
return nil, errors.New("Can not get any available LDAP_BASE_DN.")
|
||||
}
|
||||
log.Debug("baseDn:", ldapBaseDn)
|
||||
|
||||
baseDn := fmt.Sprintf(ldapBaseDn, m.Principal)
|
||||
log.Debug("baseDn:", baseDn)
|
||||
ldapSearchDn := os.Getenv("LDAP_SEARCH_DN")
|
||||
if ldapSearchDn != "" {
|
||||
log.Debug("Search DN: ", ldapSearchDn)
|
||||
ldapSearchPwd := os.Getenv("LDAP_SEARCH_PWD")
|
||||
err = ldap.Bind(ldapSearchDn, ldapSearchPwd)
|
||||
if err != nil {
|
||||
log.Debug("Bind search dn error", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = ldap.Bind(baseDn, m.Password)
|
||||
attrName := os.Getenv("LDAP_UID")
|
||||
filter := os.Getenv("LDAP_FILTER")
|
||||
if filter != "" {
|
||||
filter = "(&" + filter + "(" + attrName + "=" + m.Principal + "))"
|
||||
} else {
|
||||
filter = "(" + attrName + "=" + m.Principal + ")"
|
||||
}
|
||||
log.Debug("one or more filter", filter)
|
||||
|
||||
ldapScope := os.Getenv("LDAP_SCOPE")
|
||||
var scope int
|
||||
if ldapScope == "1" {
|
||||
scope = openldap.LDAP_SCOPE_BASE
|
||||
} else if ldapScope == "2" {
|
||||
scope = openldap.LDAP_SCOPE_ONELEVEL
|
||||
} else {
|
||||
scope = openldap.LDAP_SCOPE_SUBTREE
|
||||
}
|
||||
attributes := []string{"uid", "cn", "mail", "email"}
|
||||
result, err := ldap.SearchAll(ldapBaseDn, scope, filter, attributes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(result.Entries()) == 0 {
|
||||
log.Warningf("Not found an entry.")
|
||||
return nil, nil
|
||||
} else if len(result.Entries()) != 1 {
|
||||
log.Warningf("Found more than one entry.")
|
||||
return nil, nil
|
||||
}
|
||||
en := result.Entries()[0]
|
||||
bindDN := en.Dn()
|
||||
log.Debug("found entry:", en)
|
||||
err = ldap.Bind(bindDN, m.Password)
|
||||
if err != nil {
|
||||
log.Debug("Bind user error", err)
|
||||
return nil, err
|
||||
}
|
||||
defer ldap.Close()
|
||||
|
||||
scope := openldap.LDAP_SCOPE_SUBTREE // LDAP_SCOPE_BASE, LDAP_SCOPE_ONELEVEL, LDAP_SCOPE_SUBTREE
|
||||
filter := "objectClass=*"
|
||||
attributes := []string{"mail"}
|
||||
|
||||
result, err := ldap.SearchAll(baseDn, scope, filter, attributes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u := models.User{}
|
||||
if len(result.Entries()) == 1 {
|
||||
en := result.Entries()[0]
|
||||
for _, attr := range en.Attributes() {
|
||||
val := attr.Values()[0]
|
||||
if attr.Name() == "mail" {
|
||||
u.Email = val
|
||||
}
|
||||
for _, attr := range en.Attributes() {
|
||||
val := attr.Values()[0]
|
||||
switch attr.Name() {
|
||||
case "uid":
|
||||
u.Realname = val
|
||||
case "cn":
|
||||
u.Realname = val
|
||||
case "mail":
|
||||
u.Email = val
|
||||
case "email":
|
||||
u.Email = val
|
||||
}
|
||||
}
|
||||
|
||||
u.Username = m.Principal
|
||||
log.Debug("username:", u.Username, ",email:", u.Email)
|
||||
|
||||
exist, err := dao.UserExists(u, "username")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
9
auth/ldap/ldap_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
206
controllers/controllers_test.go
Normal file
@ -0,0 +1,206 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
//"net/url"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
//"github.com/dghubble/sling"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
//const (
|
||||
// adminName = "admin"
|
||||
// adminPwd = "Harbor12345"
|
||||
//)
|
||||
|
||||
//type usrInfo struct {
|
||||
// Name string
|
||||
// Passwd string
|
||||
//}
|
||||
|
||||
//var admin *usrInfo
|
||||
|
||||
func init() {
|
||||
|
||||
_, file, _, _ := runtime.Caller(1)
|
||||
apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".."+string(filepath.Separator))))
|
||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||
beego.TestBeegoInit(apppath)
|
||||
beego.AddTemplateExt("htm")
|
||||
|
||||
beego.Router("/", &IndexController{})
|
||||
beego.Router("/dashboard", &DashboardController{})
|
||||
beego.Router("/project", &ProjectController{})
|
||||
beego.Router("/repository", &RepositoryController{})
|
||||
beego.Router("/sign_up", &SignUpController{})
|
||||
beego.Router("/add_new", &AddNewController{})
|
||||
beego.Router("/account_setting", &AccountSettingController{})
|
||||
beego.Router("/change_password", &ChangePasswordController{})
|
||||
beego.Router("/admin_option", &AdminOptionController{})
|
||||
beego.Router("/forgot_password", &ForgotPasswordController{})
|
||||
beego.Router("/reset_password", &ResetPasswordController{})
|
||||
beego.Router("/search", &SearchController{})
|
||||
|
||||
beego.Router("/login", &CommonController{}, "post:Login")
|
||||
beego.Router("/log_out", &CommonController{}, "get:LogOut")
|
||||
beego.Router("/reset", &CommonController{}, "post:ResetPassword")
|
||||
beego.Router("/userExists", &CommonController{}, "post:UserExists")
|
||||
beego.Router("/sendEmail", &CommonController{}, "get:SendEmail")
|
||||
beego.Router("/language", &CommonController{}, "get:SwitchLanguage")
|
||||
|
||||
beego.Router("/optional_menu", &OptionalMenuController{})
|
||||
beego.Router("/navigation_header", &NavigationHeaderController{})
|
||||
beego.Router("/navigation_detail", &NavigationDetailController{})
|
||||
beego.Router("/sign_in", &SignInController{})
|
||||
|
||||
//Init user Info
|
||||
//admin = &usrInfo{adminName, adminPwd}
|
||||
|
||||
}
|
||||
|
||||
// TestMain is a sample to run an endpoint test
|
||||
func TestMain(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// v := url.Values{}
|
||||
// v.Set("principal", "admin")
|
||||
// v.Add("password", "Harbor12345")
|
||||
|
||||
r, _ := http.NewRequest("GET", "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(200), w.Code, "'/' httpStatusCode should be 200")
|
||||
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_index</title>"), "http respond should have '<title>page_title_index</title>'")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/dashboard", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(200), w.Code, "'/dashboard' httpStatusCode should be 200")
|
||||
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_dashboard</title>"), "http respond should have '<title>page_title_dashboard</title>'")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/project", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(200), w.Code, "'/project' httpStatusCode should be 200")
|
||||
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_project</title>"), "http respond should have '<title>page_title_project</title>'")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/repository", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(200), w.Code, "'/repository' httpStatusCode should be 200")
|
||||
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_repository</title>"), "http respond should have '<title>page_title_repository</title>'")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/sign_up", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(200), w.Code, "'/sign_up' httpStatusCode should be 200")
|
||||
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_sign_up</title>"), "http respond should have '<title>page_title_sign_up</title>'")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/add_new", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(401), w.Code, "'/add_new' httpStatusCode should be 401")
|
||||
|
||||
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(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_account_setting</title>"), "http respond should have '<title>page_title_account_setting</title>'")
|
||||
|
||||
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(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_change_password</title>"), "http respond should have '<title>page_title_change_password</title>'")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/admin_option", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(200), w.Code, "'/admin_option' httpStatusCode should be 200")
|
||||
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_admin_option</title>"), "http respond should have '<title>page_title_admin_option</title>'")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/forgot_password", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(200), w.Code, "'/forgot_password' httpStatusCode should be 200")
|
||||
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_forgot_password</title>"), "http respond should have '<title>page_title_forgot_password</title>'")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/reset_password", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(302), w.Code, "'/reset_password' httpStatusCode should be 302")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/search", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(200), w.Code, "'/search' httpStatusCode should be 200")
|
||||
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_search</title>"), "http respond should have '<title>page_title_searc</title>'")
|
||||
|
||||
r, _ = http.NewRequest("POST", "/login", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(401), w.Code, "'/login' httpStatusCode should be 401")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/log_out", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(200), w.Code, "'/log_out' httpStatusCode should be 200")
|
||||
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), ""), "http respond should be empty")
|
||||
|
||||
r, _ = http.NewRequest("POST", "/reset", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(400), w.Code, "'/reset' httpStatusCode should be 400")
|
||||
|
||||
r, _ = http.NewRequest("POST", "/userExists", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(500), w.Code, "'/userExists' httpStatusCode should be 500")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/sendEmail", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(400), w.Code, "'/sendEmail' httpStatusCode should be 400")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/language", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(302), w.Code, "'/language' httpStatusCode should be 302")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/optional_menu", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
//fmt.Printf("/optional_menu: %s\n", w.Body)
|
||||
assert.Equal(int(200), w.Code, "'/optional_menu' httpStatusCode should be 200")
|
||||
//assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title> </title>"), "http respond should have '<title> </title>'")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/navigation_header", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
//fmt.Printf("/navigation_header: %s\n", w.Body)
|
||||
assert.Equal(int(200), w.Code, "'/navigation_header' httpStatusCode should be 200")
|
||||
//assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title> </title>"), "http respond should have '<title> </title>'")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/navigation_detail", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
//fmt.Printf("/navigation_detail: %s\n", w.Body)
|
||||
assert.Equal(int(200), w.Code, "'/navigation_detail' httpStatusCode should be 200")
|
||||
//assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title> </title>"), "http respond should have '<title> </title>'")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/sign_in", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
//fmt.Printf("/sign_in: %s\n", w.Body)
|
||||
assert.Equal(int(200), w.Code, "'/sign_in' httpStatusCode should be 200")
|
||||
//assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title> </title>"), "http respond should have '<title> </title>'")
|
||||
|
||||
}
|
231
dao/accesslog.go
@ -38,39 +38,84 @@ func AddAccessLog(accessLog models.AccessLog) error {
|
||||
return err
|
||||
}
|
||||
|
||||
//GetAccessLogs gets access logs according to different conditions
|
||||
func GetAccessLogs(accessLog models.AccessLog) ([]models.AccessLog, error) {
|
||||
|
||||
// GetTotalOfAccessLogs ...
|
||||
func GetTotalOfAccessLogs(query models.AccessLog) (int64, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select a.log_id, u.username, a.repo_name, a.repo_tag, a.operation, a.op_time
|
||||
from access_log a left join user u on a.user_id = u.user_id
|
||||
where a.project_id = ? `
|
||||
queryParam := make([]interface{}, 1)
|
||||
queryParam = append(queryParam, accessLog.ProjectID)
|
||||
|
||||
if accessLog.UserID != 0 {
|
||||
sql += ` and a.user_id = ? `
|
||||
queryParam = append(queryParam, accessLog.UserID)
|
||||
queryParam := []interface{}{}
|
||||
|
||||
sql := `select count(*) from access_log al
|
||||
where al.project_id = ?`
|
||||
queryParam = append(queryParam, query.ProjectID)
|
||||
|
||||
if query.Username != "" {
|
||||
sql = `select count(*) from access_log al
|
||||
left join user u
|
||||
on al.user_id = u.user_id
|
||||
where al.project_id = ? and u.username like ? `
|
||||
queryParam = append(queryParam, "%"+query.Username+"%")
|
||||
}
|
||||
if accessLog.Operation != "" {
|
||||
sql += ` and a.operation = ? `
|
||||
queryParam = append(queryParam, accessLog.Operation)
|
||||
|
||||
sql += genFilterClauses(query, &queryParam)
|
||||
|
||||
var total int64
|
||||
if err := o.Raw(sql, queryParam).QueryRow(&total); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if accessLog.Username != "" {
|
||||
return total, nil
|
||||
}
|
||||
|
||||
//GetAccessLogs gets access logs according to different conditions
|
||||
func GetAccessLogs(query models.AccessLog, limit, offset int64) ([]models.AccessLog, error) {
|
||||
o := GetOrmer()
|
||||
|
||||
queryParam := []interface{}{}
|
||||
sql := `select al.log_id, u.username, al.repo_name,
|
||||
al.repo_tag, al.operation, al.op_time
|
||||
from access_log al
|
||||
left join user u
|
||||
on al.user_id = u.user_id
|
||||
where al.project_id = ? `
|
||||
queryParam = append(queryParam, query.ProjectID)
|
||||
|
||||
if query.Username != "" {
|
||||
sql += ` and u.username like ? `
|
||||
queryParam = append(queryParam, accessLog.Username)
|
||||
queryParam = append(queryParam, "%"+query.Username+"%")
|
||||
}
|
||||
if accessLog.RepoName != "" {
|
||||
sql += ` and a.repo_name = ? `
|
||||
queryParam = append(queryParam, accessLog.RepoName)
|
||||
|
||||
sql += genFilterClauses(query, &queryParam)
|
||||
|
||||
sql += ` order by al.op_time desc `
|
||||
|
||||
sql = paginateForRawSQL(sql, limit, offset)
|
||||
|
||||
logs := []models.AccessLog{}
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&logs)
|
||||
if err != nil {
|
||||
return logs, err
|
||||
}
|
||||
if accessLog.RepoTag != "" {
|
||||
sql += ` and a.repo_tag = ? `
|
||||
queryParam = append(queryParam, accessLog.RepoTag)
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func genFilterClauses(query models.AccessLog, queryParam *[]interface{}) string {
|
||||
sql := ""
|
||||
|
||||
if query.Operation != "" {
|
||||
sql += ` and al.operation = ? `
|
||||
*queryParam = append(*queryParam, query.Operation)
|
||||
}
|
||||
if accessLog.Keywords != "" {
|
||||
sql += ` and a.operation in ( `
|
||||
keywordList := strings.Split(accessLog.Keywords, "/")
|
||||
if query.RepoName != "" {
|
||||
sql += ` and al.repo_name = ? `
|
||||
*queryParam = append(*queryParam, query.RepoName)
|
||||
}
|
||||
if query.RepoTag != "" {
|
||||
sql += ` and al.repo_tag = ? `
|
||||
*queryParam = append(*queryParam, query.RepoTag)
|
||||
}
|
||||
if query.Keywords != "" {
|
||||
sql += ` and al.operation in ( `
|
||||
keywordList := strings.Split(query.Keywords, "/")
|
||||
num := len(keywordList)
|
||||
for i := 0; i < num; i++ {
|
||||
if keywordList[i] != "" {
|
||||
@ -79,27 +124,20 @@ func GetAccessLogs(accessLog models.AccessLog) ([]models.AccessLog, error) {
|
||||
} else {
|
||||
sql += `?,`
|
||||
}
|
||||
queryParam = append(queryParam, keywordList[i])
|
||||
*queryParam = append(*queryParam, keywordList[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
if accessLog.BeginTimestamp > 0 {
|
||||
sql += ` and a.op_time >= ? `
|
||||
queryParam = append(queryParam, accessLog.BeginTime)
|
||||
if query.BeginTimestamp > 0 {
|
||||
sql += ` and al.op_time >= ? `
|
||||
*queryParam = append(*queryParam, query.BeginTime)
|
||||
}
|
||||
if accessLog.EndTimestamp > 0 {
|
||||
sql += ` and a.op_time <= ? `
|
||||
queryParam = append(queryParam, accessLog.EndTime)
|
||||
if query.EndTimestamp > 0 {
|
||||
sql += ` and al.op_time <= ? `
|
||||
*queryParam = append(*queryParam, query.EndTime)
|
||||
}
|
||||
|
||||
sql += ` order by a.op_time desc `
|
||||
|
||||
var accessLogList []models.AccessLog
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&accessLogList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return accessLogList, nil
|
||||
return sql
|
||||
}
|
||||
|
||||
// AccessLog ...
|
||||
@ -118,18 +156,48 @@ func AccessLog(username, projectName, repoName, repoTag, action string) error {
|
||||
|
||||
//GetRecentLogs returns recent logs according to parameters
|
||||
func GetRecentLogs(userID, linesNum int, startTime, endTime string) ([]models.AccessLog, error) {
|
||||
var recentLogList []models.AccessLog
|
||||
queryParam := make([]interface{}, 1)
|
||||
logs := []models.AccessLog{}
|
||||
|
||||
isAdmin, err := IsAdminRole(userID)
|
||||
if err != nil {
|
||||
return logs, err
|
||||
}
|
||||
|
||||
queryParam := []interface{}{}
|
||||
sql := `select log_id, access_log.user_id, project_id, repo_name, repo_tag, GUID, operation, op_time, username
|
||||
from access_log
|
||||
join user
|
||||
on access_log.user_id=user.user_id `
|
||||
|
||||
hasWhere := false
|
||||
if !isAdmin {
|
||||
sql += ` where project_id in
|
||||
(select distinct project_id
|
||||
from project_member
|
||||
where user_id = ?) `
|
||||
queryParam = append(queryParam, userID)
|
||||
hasWhere = true
|
||||
}
|
||||
|
||||
sql := "select log_id, access_log.user_id, project_id, repo_name, repo_tag, GUID, operation, op_time, username from access_log left join user on access_log.user_id=user.user_id where project_id in (select distinct project_id from project_member where user_id = ?)"
|
||||
queryParam = append(queryParam, userID)
|
||||
if startTime != "" {
|
||||
sql += " and op_time >= ?"
|
||||
if hasWhere {
|
||||
sql += " and op_time >= ?"
|
||||
} else {
|
||||
sql += " where op_time >= ?"
|
||||
hasWhere = true
|
||||
}
|
||||
|
||||
queryParam = append(queryParam, startTime)
|
||||
}
|
||||
|
||||
if endTime != "" {
|
||||
sql += " and op_time <= ?"
|
||||
if hasWhere {
|
||||
sql += " and op_time <= ?"
|
||||
} else {
|
||||
sql += " where op_time <= ?"
|
||||
hasWhere = true
|
||||
}
|
||||
|
||||
queryParam = append(queryParam, endTime)
|
||||
}
|
||||
|
||||
@ -138,56 +206,39 @@ func GetRecentLogs(userID, linesNum int, startTime, endTime string) ([]models.Ac
|
||||
sql += " limit ?"
|
||||
queryParam = append(queryParam, linesNum)
|
||||
}
|
||||
o := GetOrmer()
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&recentLogList)
|
||||
|
||||
_, err = GetOrmer().Raw(sql, queryParam).QueryRows(&logs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return logs, err
|
||||
}
|
||||
return recentLogList, nil
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
//GetTopRepos return top accessed public repos
|
||||
func GetTopRepos(countNum int) ([]models.TopRepo, error) {
|
||||
|
||||
// GetAccessLogCreator ...
|
||||
func GetAccessLogCreator(repoName string) (string, error) {
|
||||
o := GetOrmer()
|
||||
// hide the where condition: project.public = 1, Can add to the sql when necessary.
|
||||
sql := "select repo_name, COUNT(repo_name) as access_count from access_log left join project on access_log.project_id=project.project_id where access_log.operation = 'pull' group by repo_name order by access_count desc limit ? "
|
||||
queryParam := []interface{}{}
|
||||
queryParam = append(queryParam, countNum)
|
||||
var list []models.TopRepo
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&list)
|
||||
sql := "select * from user where user_id = (select user_id from access_log where operation = 'push' and repo_name = ? order by op_time desc limit 1)"
|
||||
|
||||
var u []models.User
|
||||
n, err := o.Raw(sql, repoName).QueryRows(&u)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return list, nil
|
||||
if n == 0 {
|
||||
return "", nil
|
||||
}
|
||||
placeHolder := make([]string, len(list))
|
||||
repos := make([]string, len(list))
|
||||
for i, v := range list {
|
||||
repos[i] = v.RepoName
|
||||
placeHolder[i] = "?"
|
||||
}
|
||||
placeHolderStr := strings.Join(placeHolder, ",")
|
||||
queryParam = nil
|
||||
queryParam = append(queryParam, repos)
|
||||
var usrnameList []models.TopRepo
|
||||
sql = `select a.username as creator, a.repo_name from (select access_log.repo_name, user.username,
|
||||
access_log.op_time from user left join access_log on user.user_id = access_log.user_id where
|
||||
access_log.operation = 'push' and access_log.repo_name in (######) order by access_log.repo_name,
|
||||
access_log.op_time ASC) a group by a.repo_name`
|
||||
sql = strings.Replace(sql, "######", placeHolderStr, 1)
|
||||
_, err = o.Raw(sql, queryParam).QueryRows(&usrnameList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := 0; i < len(list); i++ {
|
||||
for _, v := range usrnameList {
|
||||
if v.RepoName == list[i].RepoName {
|
||||
// list[i].Creator = v.Creator
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return list, nil
|
||||
|
||||
return u[0].Username, nil
|
||||
}
|
||||
|
||||
// CountPull ...
|
||||
func CountPull(repoName string) (int64, error) {
|
||||
o := GetOrmer()
|
||||
num, err := o.QueryTable("access_log").Filter("repo_name", repoName).Filter("operation", "pull").Count()
|
||||
if err != nil {
|
||||
log.Errorf("error in CountPull: %v ", err)
|
||||
return 0, err
|
||||
}
|
||||
return num, nil
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"os"
|
||||
@ -44,7 +45,7 @@ func GenerateRandomString() (string, error) {
|
||||
|
||||
//InitDB initializes the database
|
||||
func InitDB() {
|
||||
// orm.Debug = true
|
||||
// orm.Debug = true
|
||||
orm.RegisterDriver("mysql", orm.DRMySQL)
|
||||
addr := os.Getenv("MYSQL_HOST")
|
||||
port := os.Getenv("MYSQL_PORT")
|
||||
@ -89,3 +90,7 @@ func GetOrmer() orm.Ormer {
|
||||
})
|
||||
return globalOrm
|
||||
}
|
||||
|
||||
func paginateForRawSQL(sql string, limit, offset int64) string {
|
||||
return fmt.Sprintf("%s limit %d offset %d", sql, limit, offset)
|
||||
}
|
||||
|
331
dao/dao_test.go
@ -16,6 +16,7 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@ -112,7 +113,9 @@ func clearUp(username string) {
|
||||
}
|
||||
|
||||
const username string = "Tester01"
|
||||
const password string = "Abc12345"
|
||||
const projectName string = "test_project"
|
||||
const repositoryName string = "test_repository"
|
||||
const repoTag string = "test1.1"
|
||||
const repoTag2 string = "test1.2"
|
||||
const SysAdmin int = 1
|
||||
@ -157,7 +160,7 @@ func TestRegister(t *testing.T) {
|
||||
user := models.User{
|
||||
Username: username,
|
||||
Email: "tester01@vmware.com",
|
||||
Password: "Abc12345",
|
||||
Password: password,
|
||||
Realname: "tester01",
|
||||
Comment: "register",
|
||||
}
|
||||
@ -184,6 +187,41 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckUserPassword(t *testing.T) {
|
||||
nonExistUser := models.User{
|
||||
Username: "non-exist",
|
||||
}
|
||||
correctUser := models.User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
wrongPwd := models.User{
|
||||
Username: username,
|
||||
Password: "wrong",
|
||||
}
|
||||
u, err := CheckUserPassword(nonExistUser)
|
||||
if err != nil {
|
||||
t.Errorf("Failed in CheckUserPassword: %v", err)
|
||||
}
|
||||
if u != nil {
|
||||
t.Errorf("Expected nil for Non exist user, but actual: %+v", u)
|
||||
}
|
||||
u, err = CheckUserPassword(wrongPwd)
|
||||
if err != nil {
|
||||
t.Errorf("Failed in CheckUserPassword: %v", err)
|
||||
}
|
||||
if u != nil {
|
||||
t.Errorf("Expected nil for user with wrong password, but actual: %+v", u)
|
||||
}
|
||||
u, err = CheckUserPassword(correctUser)
|
||||
if err != nil {
|
||||
t.Errorf("Failed in CheckUserPassword: %v", err)
|
||||
}
|
||||
if u == nil {
|
||||
t.Errorf("User should not be nil for correct user")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserExists(t *testing.T) {
|
||||
var exists bool
|
||||
var err error
|
||||
@ -420,7 +458,7 @@ func TestGetAccessLog(t *testing.T) {
|
||||
UserID: currentUser.UserID,
|
||||
ProjectID: currentProject.ProjectID,
|
||||
}
|
||||
accessLogs, err := GetAccessLogs(queryAccessLog)
|
||||
accessLogs, err := GetAccessLogs(queryAccessLog, 1000, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetAccessLog: %v", err)
|
||||
}
|
||||
@ -432,6 +470,21 @@ func TestGetAccessLog(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTotalOfAccessLogs(t *testing.T) {
|
||||
queryAccessLog := models.AccessLog{
|
||||
UserID: currentUser.UserID,
|
||||
ProjectID: currentProject.ProjectID,
|
||||
}
|
||||
total, err := GetTotalOfAccessLogs(queryAccessLog)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of access log: %v", err)
|
||||
}
|
||||
|
||||
if total != 1 {
|
||||
t.Errorf("unexpected total %d != %d", total, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAccessLog(t *testing.T) {
|
||||
var err error
|
||||
var accessLogList []models.AccessLog
|
||||
@ -448,7 +501,7 @@ func TestAddAccessLog(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AddAccessLog: %v", err)
|
||||
}
|
||||
accessLogList, err = GetAccessLogs(accessLog)
|
||||
accessLogList, err = GetAccessLogs(accessLog, 1000, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetAccessLog: %v", err)
|
||||
}
|
||||
@ -477,7 +530,7 @@ func TestAccessLog(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
accessLogList, err = GetAccessLogs(accessLog)
|
||||
accessLogList, err = GetAccessLogs(accessLog, 1000, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetAccessLog: %v", err)
|
||||
}
|
||||
@ -492,6 +545,50 @@ func TestAccessLog(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAccessLogCreator(t *testing.T) {
|
||||
var err error
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "push")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "push")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
|
||||
user, err := GetAccessLogCreator(currentProject.Name + "/tomcat")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetAccessLogCreator: %v", err)
|
||||
}
|
||||
if user != currentUser.Username {
|
||||
t.Errorf("The access log creator does not match, expected: %s, actual: %s", currentUser.Username, user)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountPull(t *testing.T) {
|
||||
var err error
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "pull")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "pull")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "pull")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
|
||||
pullCount, err := CountPull(currentProject.Name + "/tomcat")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in CountPull: %v", err)
|
||||
}
|
||||
if pullCount != 3 {
|
||||
t.Errorf("The access log pull count does not match, expected: 3, actual: %d", pullCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectExists(t *testing.T) {
|
||||
var exists bool
|
||||
var err error
|
||||
@ -609,6 +706,17 @@ func TestProjectPermission(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTotalOfUserRelevantProjects(t *testing.T) {
|
||||
total, err := GetTotalOfUserRelevantProjects(currentUser.UserID, "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of user relevant projects: %v", err)
|
||||
}
|
||||
|
||||
if total != 1 {
|
||||
t.Errorf("unexpected total: %d != 1", total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserRelevantProjects(t *testing.T) {
|
||||
projects, err := GetUserRelevantProjects(currentUser.UserID, "")
|
||||
if err != nil {
|
||||
@ -622,8 +730,19 @@ func TestGetUserRelevantProjects(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAllProjects(t *testing.T) {
|
||||
projects, err := GetAllProjects("")
|
||||
func TestGetTotalOfProjects(t *testing.T) {
|
||||
total, err := GetTotalOfProjects("")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of projects: %v", err)
|
||||
}
|
||||
|
||||
if total != 2 {
|
||||
t.Errorf("unexpected total: %d != 2", total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProjects(t *testing.T) {
|
||||
projects, err := GetProjects("")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetAllProjects: %v", err)
|
||||
}
|
||||
@ -636,7 +755,7 @@ func TestGetAllProjects(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetPublicProjects(t *testing.T) {
|
||||
projects, err := GetPublicProjects("")
|
||||
projects, err := GetProjects("", 1)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in getProjects: %v", err)
|
||||
}
|
||||
@ -672,6 +791,21 @@ func TestAddProjectMember(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateProjectMember(t *testing.T) {
|
||||
err := UpdateProjectMember(currentProject.ProjectID, 1, models.GUEST)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in UpdateProjectMember: %v", err)
|
||||
}
|
||||
roles, err := GetUserProjectRoles(1, currentProject.ProjectID)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetUserProjectRoles: %v", err)
|
||||
}
|
||||
if roles[0].Name != "guest" {
|
||||
t.Errorf("The user with ID 1 is not guest role after update, the acutal role: %s", roles[0].Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDeleteProjectMember(t *testing.T) {
|
||||
err := DeleteProjectMember(currentProject.ProjectID, 1)
|
||||
if err != nil {
|
||||
@ -688,6 +822,23 @@ func TestDeleteProjectMember(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRoleByID(t *testing.T) {
|
||||
r, err := GetRoleByID(models.PROJECTADMIN)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to call GetRoleByID: %v", err)
|
||||
}
|
||||
if r == nil || r.Name != "projectAdmin" || r.RoleCode != "MDRWS" {
|
||||
t.Errorf("Role does not match for role id: %d, actual: %+v", models.PROJECTADMIN, r)
|
||||
}
|
||||
r, err = GetRoleByID(9999)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to call GetRoleByID: %v", err)
|
||||
}
|
||||
if r != nil {
|
||||
t.Errorf("Role should nil for non-exist id 9999, actual: %+v", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToggleAdminRole(t *testing.T) {
|
||||
err := ToggleUserAdminRole(currentUser.UserID, 1)
|
||||
if err != nil {
|
||||
@ -747,57 +898,9 @@ func TestGetRecentLogs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetTopRepos(t *testing.T) {
|
||||
|
||||
err := ToggleProjectPublicity(currentProject.ProjectID, publicityOn)
|
||||
_, err := GetTopRepos(10)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in ToggleProjectPublicity: %v", err)
|
||||
}
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/ubuntu", repoTag2, "push")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/ubuntu", repoTag2, "pull")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
topRepos, err := GetTopRepos(10)
|
||||
if err != nil {
|
||||
t.Errorf("error occured in getting top repos, error: %v", err)
|
||||
}
|
||||
if topRepos[0].RepoName != currentProject.Name+"/ubuntu" {
|
||||
t.Errorf("error occured in get top reop's name, expected: %v, actual: %v", currentProject.Name+"/ubuntu", topRepos[0].RepoName)
|
||||
}
|
||||
if topRepos[0].AccessCount != 1 {
|
||||
t.Errorf("error occured in get top reop's access count, expected: %v, actual: %v", 1, topRepos[0].AccessCount)
|
||||
}
|
||||
/*
|
||||
if topRepos[0].Creator != currentUser.Username {
|
||||
t.Errorf("error occured in get top reop's creator, expected: %v, actual: %v", currentUser.Username, topRepos[0].Creator)
|
||||
}
|
||||
*/
|
||||
err = ToggleProjectPublicity(currentProject.ProjectID, publicityOff)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in ToggleProjectPublicity: %v", err)
|
||||
}
|
||||
o := GetOrmer()
|
||||
_, err = o.QueryTable("access_log").Filter("operation__in", "push,pull").Delete()
|
||||
if err != nil {
|
||||
t.Errorf("error occurred in deleting access logs, %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
err := DeleteUser(currentUser.UserID)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in DeleteUser: %v", err)
|
||||
}
|
||||
user, err := GetUser(*currentUser)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetUser: %v", err)
|
||||
}
|
||||
if user != nil {
|
||||
t.Errorf("user is not nil after deletion, user: %+v", user)
|
||||
t.Fatalf("error occured in getting top repos, error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1180,7 +1283,7 @@ func TestGetRepJobByPolicy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFilterRepJobs(t *testing.T) {
|
||||
jobs, err := FilterRepJobs(policyID, "", "", nil, nil, 1000)
|
||||
jobs, _, err := FilterRepJobs(policyID, "", "", nil, nil, 1000, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Error occured in FilterRepJobs: %v, policy ID: %d", err, policyID)
|
||||
return
|
||||
@ -1308,7 +1411,7 @@ func TestDeleteRepPolicy(t *testing.T) {
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
t.Errorf("Error occured in GetRepPolicy:%v", err)
|
||||
}
|
||||
if p != nil {
|
||||
if p != nil && p.Deleted != 1 {
|
||||
t.Errorf("Able to find rep policy after deletion, id: %d", policyID)
|
||||
}
|
||||
}
|
||||
@ -1390,3 +1493,113 @@ func TestGetOrmer(t *testing.T) {
|
||||
t.Errorf("Error get ormer.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteProject(t *testing.T) {
|
||||
name := "project_for_test"
|
||||
project := models.Project{
|
||||
OwnerID: currentUser.UserID,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
id, err := AddProject(project)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add project: %v", err)
|
||||
}
|
||||
|
||||
if err = DeleteProject(id); err != nil {
|
||||
t.Fatalf("failed to delete project: %v", err)
|
||||
}
|
||||
|
||||
p := &models.Project{}
|
||||
if err = GetOrmer().Raw(`select * from project where project_id = ?`, id).
|
||||
QueryRow(p); err != nil {
|
||||
t.Fatalf("failed to get project: %v", err)
|
||||
}
|
||||
|
||||
if p.Deleted != 1 {
|
||||
t.Errorf("unexpeced deleted column: %d != %d", p.Deleted, 1)
|
||||
}
|
||||
|
||||
deletedName := fmt.Sprintf("%s#%d", name, id)
|
||||
if p.Name != deletedName {
|
||||
t.Errorf("unexpected name: %s != %s", p.Name, deletedName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAddRepository(t *testing.T) {
|
||||
repoRecord := models.RepoRecord{
|
||||
Name: currentProject.Name + "/" + repositoryName,
|
||||
OwnerName: currentUser.Username,
|
||||
ProjectName: currentProject.Name,
|
||||
Description: "testing repo",
|
||||
PullCount: 0,
|
||||
StarCount: 0,
|
||||
}
|
||||
|
||||
err := AddRepository(repoRecord)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AddRepository: %v", err)
|
||||
}
|
||||
|
||||
newRepoRecord, err := GetRepositoryByName(currentProject.Name + "/" + repositoryName)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetRepositoryByName: %v", err)
|
||||
}
|
||||
if newRepoRecord == nil {
|
||||
t.Errorf("No repository found queried by repository name: %v", currentProject.Name+"/"+repositoryName)
|
||||
}
|
||||
}
|
||||
|
||||
var currentRepository *models.RepoRecord
|
||||
|
||||
func TestGetRepositoryByName(t *testing.T) {
|
||||
var err error
|
||||
currentRepository, err = GetRepositoryByName(currentProject.Name + "/" + repositoryName)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetRepositoryByName: %v", err)
|
||||
}
|
||||
if currentRepository == nil {
|
||||
t.Errorf("No repository found queried by repository name: %v", currentProject.Name+"/"+repositoryName)
|
||||
}
|
||||
if currentRepository.Name != currentProject.Name+"/"+repositoryName {
|
||||
t.Errorf("Repository name does not match, expected: %s, actual: %s", currentProject.Name+"/"+repositoryName, currentProject.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncreasePullCount(t *testing.T) {
|
||||
if err := IncreasePullCount(currentRepository.Name); err != nil {
|
||||
log.Errorf("Error happens when increasing pull count: %v", currentRepository.Name)
|
||||
}
|
||||
|
||||
repository, err := GetRepositoryByName(currentRepository.Name)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetRepositoryByName: %v", err)
|
||||
}
|
||||
|
||||
if repository.PullCount != 1 {
|
||||
t.Errorf("repository pull count is not 1 after IncreasePullCount, expected: 1, actual: %d", repository.PullCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryExists(t *testing.T) {
|
||||
var exists bool
|
||||
exists = RepositoryExists(currentRepository.Name)
|
||||
if !exists {
|
||||
t.Errorf("The repository with name: %d, does not exist", currentRepository.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRepository(t *testing.T) {
|
||||
err := DeleteRepository(currentRepository.Name)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in DeleteRepository: %v", err)
|
||||
}
|
||||
repository, err := GetRepositoryByName(currentRepository.Name)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetRepositoryByName: %v", err)
|
||||
}
|
||||
if repository != nil {
|
||||
t.Errorf("repository is not nil after deletion, repository: %+v", repository)
|
||||
}
|
||||
}
|
||||
|
130
dao/project.go
@ -182,66 +182,106 @@ func SearchProjects(userID int) ([]models.Project, error) {
|
||||
return projects, nil
|
||||
}
|
||||
|
||||
// GetUserRelevantProjects returns the projects of the user which are not deleted and name like projectName
|
||||
func GetUserRelevantProjects(userID int, projectName string) ([]models.Project, error) {
|
||||
//GetTotalOfUserRelevantProjects returns the total count of
|
||||
// user relevant projects
|
||||
func GetTotalOfUserRelevantProjects(userID int, projectName string) (int64, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select distinct
|
||||
p.project_id, p.owner_id, p.name,p.creation_time, p.update_time, p.public, pm.role role
|
||||
from project p
|
||||
left join project_member pm on p.project_id = pm.project_id
|
||||
where p.deleted = 0 and pm.user_id= ?`
|
||||
sql := `select count(*) from project p
|
||||
left join project_member pm
|
||||
on p.project_id = pm.project_id
|
||||
where p.deleted = 0 and pm.user_id= ?`
|
||||
|
||||
queryParam := make([]interface{}, 1)
|
||||
queryParam := []interface{}{}
|
||||
queryParam = append(queryParam, userID)
|
||||
if projectName != "" {
|
||||
sql += " and p.name like ? "
|
||||
queryParam = append(queryParam, projectName)
|
||||
queryParam = append(queryParam, "%"+projectName+"%")
|
||||
}
|
||||
sql += " order by p.name "
|
||||
var r []models.Project
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
|
||||
var total int64
|
||||
err := o.Raw(sql, queryParam).QueryRow(&total)
|
||||
|
||||
return total, err
|
||||
}
|
||||
|
||||
//GetPublicProjects returns all public projects whose name like projectName
|
||||
func GetPublicProjects(projectName string) ([]models.Project, error) {
|
||||
publicProjects, err := getProjects(1, projectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return publicProjects, nil
|
||||
// GetUserRelevantProjects returns the user relevant projects
|
||||
// args[0]: public, args[1]: limit, args[2]: offset
|
||||
func GetUserRelevantProjects(userID int, projectName string, args ...int64) ([]models.Project, error) {
|
||||
return getProjects(userID, projectName, args...)
|
||||
}
|
||||
|
||||
// GetAllProjects returns all projects which are not deleted and name like projectName
|
||||
func GetAllProjects(projectName string) ([]models.Project, error) {
|
||||
allProjects, err := getProjects(0, projectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// GetTotalOfProjects returns the total count of projects
|
||||
func GetTotalOfProjects(name string, public ...int) (int64, error) {
|
||||
qs := GetOrmer().
|
||||
QueryTable(new(models.Project)).
|
||||
Filter("Deleted", 0)
|
||||
|
||||
if len(name) > 0 {
|
||||
qs = qs.Filter("Name__icontains", name)
|
||||
}
|
||||
return allProjects, nil
|
||||
|
||||
if len(public) > 0 {
|
||||
qs = qs.Filter("Public", public[0])
|
||||
}
|
||||
|
||||
return qs.Count()
|
||||
}
|
||||
|
||||
func getProjects(public int, projectName string) ([]models.Project, error) {
|
||||
// GetProjects returns project list
|
||||
// args[0]: public, args[1]: limit, args[2]: offset
|
||||
func GetProjects(name string, args ...int64) ([]models.Project, error) {
|
||||
return getProjects(0, name, args...)
|
||||
}
|
||||
|
||||
func getProjects(userID int, name string, args ...int64) ([]models.Project, error) {
|
||||
projects := []models.Project{}
|
||||
|
||||
o := GetOrmer()
|
||||
sql := `select project_id, owner_id, creation_time, update_time, name, public
|
||||
from project
|
||||
where deleted = 0`
|
||||
queryParam := make([]interface{}, 1)
|
||||
if public == 1 {
|
||||
sql += " and public = ? "
|
||||
queryParam = append(queryParam, public)
|
||||
sql := ""
|
||||
queryParam := []interface{}{}
|
||||
|
||||
if userID != 0 { //get user's projects
|
||||
sql = `select distinct p.project_id, p.owner_id, p.name,
|
||||
p.creation_time, p.update_time, p.public, pm.role role
|
||||
from project p
|
||||
left join project_member pm
|
||||
on p.project_id = pm.project_id
|
||||
where p.deleted = 0 and pm.user_id= ?`
|
||||
queryParam = append(queryParam, userID)
|
||||
} else { // get all projects
|
||||
sql = `select * from project p where p.deleted = 0 `
|
||||
}
|
||||
if len(projectName) > 0 {
|
||||
sql += " and name like ? "
|
||||
queryParam = append(queryParam, projectName)
|
||||
|
||||
if name != "" {
|
||||
sql += ` and p.name like ? `
|
||||
queryParam = append(queryParam, "%"+name+"%")
|
||||
}
|
||||
sql += " order by name "
|
||||
var projects []models.Project
|
||||
if _, err := o.Raw(sql, queryParam).QueryRows(&projects); err != nil {
|
||||
return nil, err
|
||||
|
||||
switch len(args) {
|
||||
case 1:
|
||||
sql += ` and p.public = ?`
|
||||
queryParam = append(queryParam, args[0])
|
||||
sql += ` order by p.name `
|
||||
case 2:
|
||||
sql += ` order by p.name `
|
||||
sql = paginateForRawSQL(sql, args[0], args[1])
|
||||
case 3:
|
||||
sql += ` and p.public = ?`
|
||||
queryParam = append(queryParam, args[0])
|
||||
sql += ` order by p.name `
|
||||
sql = paginateForRawSQL(sql, args[1], args[2])
|
||||
}
|
||||
return projects, nil
|
||||
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&projects)
|
||||
|
||||
return projects, err
|
||||
}
|
||||
|
||||
// DeleteProject ...
|
||||
func DeleteProject(id int64) error {
|
||||
sql := `update project
|
||||
set deleted = 1, name = concat(name,"#",project_id)
|
||||
where project_id = ?`
|
||||
_, err := GetOrmer().Raw(sql, id).Exec()
|
||||
return err
|
||||
}
|
||||
|
@ -155,17 +155,18 @@ func FilterRepPolicies(name string, projectID int64) ([]*models.RepPolicy, error
|
||||
left join project p on rp.project_id=p.project_id
|
||||
left join replication_target rt on rp.target_id=rt.id
|
||||
left join replication_job rj on rp.id=rj.policy_id and (rj.status="error"
|
||||
or rj.status="retrying") `
|
||||
or rj.status="retrying")
|
||||
where rp.deleted = 0 `
|
||||
|
||||
if len(name) != 0 && projectID != 0 {
|
||||
sql += `where rp.name like ? and rp.project_id = ? `
|
||||
sql += `and rp.name like ? and rp.project_id = ? `
|
||||
args = append(args, "%"+name+"%")
|
||||
args = append(args, projectID)
|
||||
} else if len(name) != 0 {
|
||||
sql += `where rp.name like ? `
|
||||
sql += `and rp.name like ? `
|
||||
args = append(args, "%"+name+"%")
|
||||
} else if projectID != 0 {
|
||||
sql += `where rp.project_id = ? `
|
||||
sql += `and rp.project_id = ? `
|
||||
args = append(args, projectID)
|
||||
}
|
||||
|
||||
@ -181,7 +182,7 @@ func FilterRepPolicies(name string, projectID int64) ([]*models.RepPolicy, error
|
||||
// GetRepPolicyByName ...
|
||||
func GetRepPolicyByName(name string) (*models.RepPolicy, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select * from replication_policy where name = ?`
|
||||
sql := `select * from replication_policy where deleted = 0 and name = ?`
|
||||
|
||||
var policy models.RepPolicy
|
||||
|
||||
@ -198,7 +199,7 @@ func GetRepPolicyByName(name string) (*models.RepPolicy, error) {
|
||||
// GetRepPolicyByProject ...
|
||||
func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select * from replication_policy where project_id = ?`
|
||||
sql := `select * from replication_policy where deleted = 0 and project_id = ?`
|
||||
|
||||
var policies []*models.RepPolicy
|
||||
|
||||
@ -212,7 +213,7 @@ func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) {
|
||||
// GetRepPolicyByTarget ...
|
||||
func GetRepPolicyByTarget(targetID int64) ([]*models.RepPolicy, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select * from replication_policy where target_id = ?`
|
||||
sql := `select * from replication_policy where deleted = 0 and target_id = ?`
|
||||
|
||||
var policies []*models.RepPolicy
|
||||
|
||||
@ -226,7 +227,7 @@ func GetRepPolicyByTarget(targetID int64) ([]*models.RepPolicy, error) {
|
||||
// GetRepPolicyByProjectAndTarget ...
|
||||
func GetRepPolicyByProjectAndTarget(projectID, targetID int64) ([]*models.RepPolicy, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select * from replication_policy where project_id = ? and target_id = ?`
|
||||
sql := `select * from replication_policy where deleted = 0 and project_id = ? and target_id = ?`
|
||||
|
||||
var policies []*models.RepPolicy
|
||||
|
||||
@ -247,7 +248,11 @@ func UpdateRepPolicy(policy *models.RepPolicy) error {
|
||||
// DeleteRepPolicy ...
|
||||
func DeleteRepPolicy(id int64) error {
|
||||
o := GetOrmer()
|
||||
_, err := o.Delete(&models.RepPolicy{ID: id})
|
||||
policy := &models.RepPolicy{
|
||||
ID: id,
|
||||
Deleted: 1,
|
||||
}
|
||||
_, err := o.Update(policy, "Deleted")
|
||||
return err
|
||||
}
|
||||
|
||||
@ -312,12 +317,14 @@ func GetRepJobByPolicy(policyID int64) ([]*models.RepJob, error) {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// FilterRepJobs filters jobs by repo and policy ID
|
||||
// FilterRepJobs ...
|
||||
func FilterRepJobs(policyID int64, repository, status string, startTime,
|
||||
endTime *time.Time, limit int) ([]*models.RepJob, error) {
|
||||
o := GetOrmer()
|
||||
endTime *time.Time, limit, offset int64) ([]*models.RepJob, int64, error) {
|
||||
|
||||
jobs := []*models.RepJob{}
|
||||
|
||||
qs := GetOrmer().QueryTable(new(models.RepJob))
|
||||
|
||||
qs := o.QueryTable(new(models.RepJob))
|
||||
if policyID != 0 {
|
||||
qs = qs.Filter("PolicyID", policyID)
|
||||
}
|
||||
@ -327,32 +334,28 @@ func FilterRepJobs(policyID int64, repository, status string, startTime,
|
||||
if len(status) != 0 {
|
||||
qs = qs.Filter("Status__icontains", status)
|
||||
}
|
||||
|
||||
if startTime != nil {
|
||||
fmt.Printf("%v\n", startTime)
|
||||
qs = qs.Filter("CreationTime__gte", startTime)
|
||||
}
|
||||
|
||||
if endTime != nil {
|
||||
fmt.Printf("%v\n", endTime)
|
||||
qs = qs.Filter("CreationTime__lte", endTime)
|
||||
}
|
||||
|
||||
if limit != 0 {
|
||||
qs = qs.Limit(limit)
|
||||
total, err := qs.Count()
|
||||
if err != nil {
|
||||
return jobs, 0, err
|
||||
}
|
||||
|
||||
qs = qs.OrderBy("-UpdateTime")
|
||||
|
||||
var jobs []*models.RepJob
|
||||
_, err := qs.All(&jobs)
|
||||
_, err = qs.Limit(limit).Offset(offset).All(&jobs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return jobs, 0, err
|
||||
}
|
||||
|
||||
genTagListForJob(jobs...)
|
||||
|
||||
return jobs, nil
|
||||
return jobs, total, nil
|
||||
}
|
||||
|
||||
// GetRepJobToStop get jobs that are possibly being handled by workers of a certain policy.
|
||||
|
167
dao/repository.go
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/vmware/harbor/models"
|
||||
)
|
||||
|
||||
// AddRepository adds a repo to the database.
|
||||
func AddRepository(repo models.RepoRecord) error {
|
||||
o := GetOrmer()
|
||||
sql := "insert into repository (owner_id, project_id, name, description, pull_count, star_count, creation_time, update_time) " +
|
||||
"select (select user_id as owner_id from user where username=?), " +
|
||||
"(select project_id as project_id from project where name=?), ?, ?, ?, ?, NOW(), NULL "
|
||||
|
||||
_, err := o.Raw(sql, repo.OwnerName, repo.ProjectName, repo.Name, repo.Description, repo.PullCount, repo.StarCount).Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
// GetRepositoryByName ...
|
||||
func GetRepositoryByName(name string) (*models.RepoRecord, error) {
|
||||
o := GetOrmer()
|
||||
r := models.RepoRecord{Name: name}
|
||||
err := o.Read(&r, "Name")
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return &r, err
|
||||
}
|
||||
|
||||
// GetAllRepositories ...
|
||||
func GetAllRepositories() ([]models.RepoRecord, error) {
|
||||
o := GetOrmer()
|
||||
var repos []models.RepoRecord
|
||||
_, err := o.QueryTable("repository").All(&repos)
|
||||
return repos, err
|
||||
}
|
||||
|
||||
// DeleteRepository ...
|
||||
func DeleteRepository(name string) error {
|
||||
o := GetOrmer()
|
||||
_, err := o.QueryTable("repository").Filter("name", name).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateRepository ...
|
||||
func UpdateRepository(repo models.RepoRecord) error {
|
||||
o := GetOrmer()
|
||||
_, err := o.Update(&repo)
|
||||
return err
|
||||
}
|
||||
|
||||
// IncreasePullCount ...
|
||||
func IncreasePullCount(name string) (err error) {
|
||||
o := GetOrmer()
|
||||
num, err := o.QueryTable("repository").Filter("name", name).Update(
|
||||
orm.Params{
|
||||
"pull_count": orm.ColValue(orm.ColAdd, 1),
|
||||
})
|
||||
if num == 0 {
|
||||
err = fmt.Errorf("Failed to increase repository pull count with name: %s %s", name, err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
//RepositoryExists returns whether the repository exists according to its name.
|
||||
func RepositoryExists(name string) bool {
|
||||
o := GetOrmer()
|
||||
return o.QueryTable("repository").Filter("name", name).Exist()
|
||||
}
|
||||
|
||||
// GetRepositoryByProjectName ...
|
||||
func GetRepositoryByProjectName(name string) ([]*models.RepoRecord, error) {
|
||||
sql := `select * from repository
|
||||
where project_id = (
|
||||
select project_id from project
|
||||
where name = ?
|
||||
)`
|
||||
repos := []*models.RepoRecord{}
|
||||
_, err := GetOrmer().Raw(sql, name).QueryRows(&repos)
|
||||
return repos, err
|
||||
}
|
||||
|
||||
//GetTopRepos returns the most popular repositories
|
||||
func GetTopRepos(count int) ([]models.TopRepo, error) {
|
||||
topRepos := []models.TopRepo{}
|
||||
|
||||
repositories := []*models.RepoRecord{}
|
||||
if _, err := GetOrmer().QueryTable(&models.RepoRecord{}).
|
||||
OrderBy("-PullCount", "Name").Limit(count).All(&repositories); err != nil {
|
||||
return topRepos, err
|
||||
}
|
||||
|
||||
for _, repository := range repositories {
|
||||
topRepos = append(topRepos, models.TopRepo{
|
||||
RepoName: repository.Name,
|
||||
AccessCount: repository.PullCount,
|
||||
})
|
||||
}
|
||||
|
||||
return topRepos, nil
|
||||
}
|
||||
|
||||
// GetTotalOfRepositories ...
|
||||
func GetTotalOfRepositories(name string) (int64, error) {
|
||||
qs := GetOrmer().QueryTable(&models.RepoRecord{})
|
||||
if len(name) != 0 {
|
||||
qs = qs.Filter("Name__contains", name)
|
||||
}
|
||||
return qs.Count()
|
||||
}
|
||||
|
||||
// GetTotalOfPublicRepositories ...
|
||||
func GetTotalOfPublicRepositories(name string) (int64, error) {
|
||||
params := []interface{}{}
|
||||
sql := `select count(*) from repository r
|
||||
join project p
|
||||
on r.project_id = p.project_id and p.public = 1 `
|
||||
if len(name) != 0 {
|
||||
sql += ` where r.name like ?`
|
||||
params = append(params, "%"+name+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
err := GetOrmer().Raw(sql, params).QueryRow(&total)
|
||||
return total, err
|
||||
}
|
||||
|
||||
// GetTotalOfUserRelevantRepositories ...
|
||||
func GetTotalOfUserRelevantRepositories(userID int, name string) (int64, error) {
|
||||
params := []interface{}{}
|
||||
sql := `select count(*)
|
||||
from repository r
|
||||
join (
|
||||
select p.project_id, p.public
|
||||
from project p
|
||||
join project_member pm
|
||||
on p.project_id = pm.project_id
|
||||
where pm.user_id = ?
|
||||
) as pp
|
||||
on r.project_id = pp.project_id `
|
||||
params = append(params, userID)
|
||||
if len(name) != 0 {
|
||||
sql += ` where r.name like ?`
|
||||
params = append(params, "%"+name+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
err := GetOrmer().Raw(sql, params).QueryRow(&total)
|
||||
return total, err
|
||||
}
|
169
dao/repository_test.go
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/harbor/models"
|
||||
)
|
||||
|
||||
var (
|
||||
project = "library"
|
||||
name = "library/repository-test"
|
||||
repository = &models.RepoRecord{
|
||||
Name: name,
|
||||
OwnerName: "admin",
|
||||
ProjectName: project,
|
||||
}
|
||||
)
|
||||
|
||||
func TestGetRepositoryByProjectName(t *testing.T) {
|
||||
if err := addRepository(repository); err != nil {
|
||||
t.Fatalf("failed to add repository %s: %v", name, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := deleteRepository(name); err != nil {
|
||||
t.Fatalf("failed to delete repository %s: %v", name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
repositories, err := GetRepositoryByProjectName(project)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get repositories of project %s: %v",
|
||||
project, err)
|
||||
}
|
||||
|
||||
if len(repositories) == 0 {
|
||||
t.Fatal("unexpected length of repositories: 0, at least 1")
|
||||
}
|
||||
|
||||
exist := false
|
||||
for _, repo := range repositories {
|
||||
if repo.Name == name {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
t.Errorf("there is no repository whose name is %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTotalOfRepositories(t *testing.T) {
|
||||
total, err := GetTotalOfRepositories("")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of repositoreis: %v", err)
|
||||
}
|
||||
|
||||
if err := addRepository(repository); err != nil {
|
||||
t.Fatalf("failed to add repository %s: %v", name, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := deleteRepository(name); err != nil {
|
||||
t.Fatalf("failed to delete repository %s: %v", name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
n, err := GetTotalOfRepositories("")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of repositoreis: %v", err)
|
||||
}
|
||||
|
||||
if n != total+1 {
|
||||
t.Errorf("unexpected total: %d != %d", n, total+1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTotalOfPublicRepositories(t *testing.T) {
|
||||
total, err := GetTotalOfPublicRepositories("")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of public repositoreis: %v", err)
|
||||
}
|
||||
|
||||
if err := addRepository(repository); err != nil {
|
||||
t.Fatalf("failed to add repository %s: %v", name, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := deleteRepository(name); err != nil {
|
||||
t.Fatalf("failed to delete repository %s: %v", name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
n, err := GetTotalOfPublicRepositories("")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of public repositoreis: %v", err)
|
||||
}
|
||||
|
||||
if n != total+1 {
|
||||
t.Errorf("unexpected total: %d != %d", n, total+1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTotalOfUserRelevantRepositories(t *testing.T) {
|
||||
total, err := GetTotalOfUserRelevantRepositories(1, "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of repositoreis for user %d: %v", 1, err)
|
||||
}
|
||||
|
||||
if err := addRepository(repository); err != nil {
|
||||
t.Fatalf("failed to add repository %s: %v", name, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := deleteRepository(name); err != nil {
|
||||
t.Fatalf("failed to delete repository %s: %v", name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
users, err := GetUserByProject(1, models.User{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to list members of project %d: %v", 1, err)
|
||||
}
|
||||
exist := false
|
||||
for _, user := range users {
|
||||
if user.UserID == 1 {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
if err = AddProjectMember(1, 1, models.DEVELOPER); err != nil {
|
||||
t.Fatalf("failed to add user %d to be member of project %d: %v", 1, 1, err)
|
||||
}
|
||||
defer func() {
|
||||
if err = DeleteProjectMember(1, 1); err != nil {
|
||||
t.Fatalf("failed to delete user %d from member of project %d: %v", 1, 1, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
n, err := GetTotalOfUserRelevantRepositories(1, "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of public repositoreis for user %d: %v", 1, err)
|
||||
}
|
||||
|
||||
if n != total+1 {
|
||||
t.Errorf("unexpected total: %d != %d", n, total+1)
|
||||
}
|
||||
}
|
||||
|
||||
func addRepository(repository *models.RepoRecord) error {
|
||||
return AddRepository(*repository)
|
||||
}
|
||||
|
||||
func deleteRepository(name string) error {
|
||||
return DeleteRepository(name)
|
||||
}
|
26
dao/user.go
@ -111,7 +111,7 @@ func ListUsers(query models.User) ([]models.User, error) {
|
||||
// ToggleUserAdminRole gives a user admin role.
|
||||
func ToggleUserAdminRole(userID, hasAdmin int) error {
|
||||
o := GetOrmer()
|
||||
queryParams := make([]interface{}, 1)
|
||||
queryParams := make([]interface{}, 1)
|
||||
sql := `update user set sysadmin_flag = ? where user_id = ?`
|
||||
queryParams = append(queryParams, hasAdmin)
|
||||
queryParams = append(queryParams, userID)
|
||||
@ -185,37 +185,24 @@ func UpdateUserResetUUID(u models.User) error {
|
||||
func CheckUserPassword(query models.User) (*models.User, error) {
|
||||
|
||||
currentUser, err := GetUser(query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if currentUser == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sql := `select user_id, username, salt from user where deleted = 0`
|
||||
|
||||
sql := `select user_id, username, salt from user where deleted = 0 and username = ? and password = ?`
|
||||
queryParam := make([]interface{}, 1)
|
||||
|
||||
if query.UserID != 0 {
|
||||
sql += ` and password = ? and user_id = ?`
|
||||
queryParam = append(queryParam, utils.Encrypt(query.Password, currentUser.Salt))
|
||||
queryParam = append(queryParam, query.UserID)
|
||||
} else {
|
||||
sql += ` and username = ? and password = ?`
|
||||
queryParam = append(queryParam, currentUser.Username)
|
||||
queryParam = append(queryParam, utils.Encrypt(query.Password, currentUser.Salt))
|
||||
}
|
||||
queryParam = append(queryParam, currentUser.Username)
|
||||
queryParam = append(queryParam, utils.Encrypt(query.Password, currentUser.Salt))
|
||||
o := GetOrmer()
|
||||
var user []models.User
|
||||
|
||||
n, err := o.Raw(sql, queryParam).QueryRows(&user)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
log.Warning("User principal does not match password. Current:", currentUser)
|
||||
return nil, nil
|
||||
@ -227,7 +214,10 @@ func CheckUserPassword(query models.User) (*models.User, error) {
|
||||
// DeleteUser ...
|
||||
func DeleteUser(userID int) error {
|
||||
o := GetOrmer()
|
||||
_, err := o.Raw(`update user set deleted = 1 where user_id = ?`, userID).Exec()
|
||||
_, err := o.Raw(`update user
|
||||
set deleted = 1, username = concat(username, "#", user_id),
|
||||
email = concat(email, "#", user_id)
|
||||
where user_id = ?`, userID).Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
|
69
dao/user_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/harbor/models"
|
||||
)
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
username := "user_for_test"
|
||||
email := "user_for_test@vmware.com"
|
||||
password := "P@ssword"
|
||||
realname := "user_for_test"
|
||||
|
||||
u := models.User{
|
||||
Username: username,
|
||||
Email: email,
|
||||
Password: password,
|
||||
Realname: realname,
|
||||
}
|
||||
id, err := Register(u)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to register user: %v", err)
|
||||
}
|
||||
|
||||
err = DeleteUser(int(id))
|
||||
if err != nil {
|
||||
t.Fatalf("Error occurred in DeleteUser: %v", err)
|
||||
}
|
||||
|
||||
user := &models.User{}
|
||||
sql := "select * from user where user_id = ?"
|
||||
if err = GetOrmer().Raw(sql, id).
|
||||
QueryRow(user); err != nil {
|
||||
t.Fatalf("failed to query user: %v", err)
|
||||
}
|
||||
|
||||
if user.Deleted != 1 {
|
||||
t.Error("user is not deleted")
|
||||
}
|
||||
|
||||
expected := fmt.Sprintf("%s#%d", u.Username, id)
|
||||
if user.Username != expected {
|
||||
t.Errorf("unexpected username: %s != %s", user.Username,
|
||||
expected)
|
||||
}
|
||||
|
||||
expected = fmt.Sprintf("%s#%d", u.Email, id)
|
||||
if user.Email != expected {
|
||||
t.Errorf("unexpected email: %s != %s", user.Email,
|
||||
expected)
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 23 KiB |
@ -1,36 +1,42 @@
|
||||
# Installation and Configuration Guide
|
||||
Harbor can be installed in one of two ways:
|
||||
Harbor can be installed by one of two installers:
|
||||
|
||||
1. From source code - This goes through a full build process, _and requires an Internet connection_.
|
||||
2. Pre-built installation package - This can save time (no building necessary!) as well as allows for installation on a host that is _not_ connected to the Internet.
|
||||
- **Online installer:** The installer downloads Harbor's images from Docker hub. For this reason, the installer is very small in size.
|
||||
|
||||
This guide describes both of these approaches.
|
||||
- **Offline installer:** Use this installer when the host does not have Internet connection. The installer contains pre-built images so its size is larger.
|
||||
|
||||
Both installers can be downloaded from the [release page](https://github.com/vmware/harbor/releases). The installation process of both installers are the same, this guide describes the steps to install and confiugure Harbor.
|
||||
|
||||
In addition, the deployment instructions on Kubernetes has been created by the community. Refer to [Deploy Harbor on Kubernetes](kubernetes_deployment.md) for details.
|
||||
|
||||
## Prerequisites for the target host
|
||||
Harbor is deployed as several Docker containers, and, therefore, can be deployed on any Linux distribution that supports Docker.
|
||||
The target host requires Python, Docker, and Docker Compose to be installed.
|
||||
Harbor is deployed as several Docker containers, and, therefore, can be deployed on any Linux distribution that supports Docker. The target host requires Python, Docker, and Docker Compose to be installed.
|
||||
* Python should be version 2.7 or higher. Note that you may have to install Python on Linux distributions (Gentoo, Arch) that do not come with a Python interpreter installed by default
|
||||
* Docker engine should be version 1.10 or higher. For installation instructions, please refer to: https://docs.docker.com/engine/installation/
|
||||
* Docker Compose needs to be version 1.6.0 or higher. For installation instructions, please refer to: https://docs.docker.com/compose/install/
|
||||
|
||||
## Installation from source code
|
||||
## Installation Steps
|
||||
|
||||
_Note: To install from source, the target host must be connected to the Internet!_
|
||||
The steps boil down to the following
|
||||
The installation steps boil down to the following
|
||||
|
||||
1. Get the source code
|
||||
2. Configure **harbor.cfg**
|
||||
3. **prepare** the configuration files
|
||||
4. Start Harbor with Docker Compose
|
||||
1. Download the installer;
|
||||
2. Configure **harbor.cfg**;
|
||||
3. Run **install.sh** to install and start Harbor;
|
||||
|
||||
#### Getting the source code:
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/vmware/harbor
|
||||
#### Downloading the installer:
|
||||
|
||||
The binary of the installer can be downloaded from the [release](https://github.com/vmware/harbor/releases) page. Choose either online or offline installer. Use *tar* command to extract the package.
|
||||
|
||||
Online installer:
|
||||
```
|
||||
|
||||
$ tar xvf harbor-online-installer-<version>.tgz
|
||||
```
|
||||
Offline installer:
|
||||
```
|
||||
$ tar xvf harbor-offline-installer-<version>.tgz
|
||||
```
|
||||
|
||||
#### Configuring Harbor
|
||||
Configuration parameters are located in the file **harbor.cfg**.
|
||||
The parameters are described below - note that at the very least, you will need to change the **hostname** attribute.
|
||||
@ -45,22 +51,32 @@ The parameters are described below - note that at the very least, you will need
|
||||
* email_from = admin <sample_admin@mydomain.com>
|
||||
* email_ssl = false
|
||||
|
||||
* **harbor_admin_password**: The adminstrator's password. _Note that the default username/password are **admin/Harbor12345** ._
|
||||
* **harbor_admin_password**: The adminstrator's initial password. This password only takes effect for the first time Harbor launches. After that, this setting is ignored and the adminstrator's password should be set in the UI. _Note that the default username/password are **admin/Harbor12345** ._
|
||||
* **auth_mode**: The type of authentication that is used. By default it is **db_auth**, i.e. the credentials are stored in a database. For LDAP authentication, set this to **ldap_auth**.
|
||||
* **ldap_url**: The LDAP endpoint URL (e.g. `ldaps://ldap.mydomain.com`). _Only used when **auth_mode** is set to *ldap_auth* ._
|
||||
* **ldap_basedn**: The basedn template for verifying the user's credential against an LDAP (e.g. `uid=%s,ou=people,dc=mydomain,dc=com` ) or an AD (e.g. `CN=%s,OU=Dept1,DC=mydomain,DC=com`) server. _Only used when **auth_mode** is set to *ldap_auth* ._
|
||||
* **ldap_searchdn**: The DN of a user who has the permission to search an LDAP/AD server (e.g. `uid=admin,ou=people,dc=mydomain,dc=com`).
|
||||
* **ldap_search_pwd**: The password of the user specified by *ldap_searchdn*.
|
||||
* **ldap_basedn**: The base DN to look up a user, e.g. `ou=people,dc=mydomain,dc=com`. _Only used when **auth_mode** is set to *ldap_auth* ._
|
||||
* **ldap_filter**:The search filter for looking up a user, e.g. `(objectClass=person)`.
|
||||
* **ldap_uid**: The attribute used to match a user during a ldap search, it could be uid, cn, email or other attributes.
|
||||
* **ldap_scope**: The scope to search for a user, 1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE. Default is 3.
|
||||
* **db_password**: The root password for the mySQL database used for **db_auth**. _Change this password for any production use!_
|
||||
* **self_registration**: (**on** or **off**. Default is **on**) Enable / Disable the ability for a user to register themselves. When disabled, new users can only be created by the Admin user, only an admin user can create new users in Harbor. _NOTE: When **auth_mode** is set to **ldap_auth**, self-registration feature is **always** disabled, and this flag is ignored._
|
||||
* **use_compressed_js**: (**on** or **off**. Default is **on**) For production use, turn this flag to **on**. In development mode, set it to **off** so that js files can be modified separately.
|
||||
* **max_job_workers**: (default value is **3**) The maximum number of replication workers in job service. For each image replication job, a worker synchronizes all tags of a repository to the remote destination. Increasing this number allows more concurrent replication jobs in the system. However, since each worker consumes a certain amount of network/CPU/IO resources, please carefully pick the value of this attribute based on the hardware resource of the host.
|
||||
* **verify_remote_cert**: (**on** or **off**. Default is **on**) This flag determines whether or not to verify SSL/TLS certificate when Harbor communicates with a remote registry instance. Setting this attribute to **off** will bypass the SSL/TLS verification, which is often used when the remote instance has a self-signed or untrusted certificate.
|
||||
* **customize_crt**: (**on** or **off**. Default is **on**) When this attribute is **on**, the prepare script creates private key and root certificate for the generation/verification of the regitry's token. The following attributes:**crt_country**, **crt_state**, **crt_location**, **crt_organization**, **crt_organizationalunit**, **crt_commonname**, **crt_email** are used as parameters for generating the keys. Set this attribute to **off** when the key and root certificate are supplied by external sources. Refer to [Customize Key and Certificate of Harbor Token Service](customize_token_service.md) for more info.
|
||||
* **secret_key**: The key to encrypt or decrypt the password of a remote registry in a replication policy, its length has to be 16 characters. Change this key before any production use. *NOTE: After changing this key, previously encrypted password of a policy can not be decrypted.*
|
||||
|
||||
* **token_expiration**: The expiration time (in minute) of a token created by token service, default is 30 minutes.
|
||||
|
||||
* **verify_remote_cert**: (**on** or **off**. Default is **on**) This flag determines whether or not to verify SSL/TLS certificate when Harbor communicates with a remote registry instance. Setting this attribute to **off** bypasses the SSL/TLS verification, which is often used when the remote instance has a self-signed or untrusted certificate.
|
||||
* **customize_crt**: (**on** or **off**. Default is **on**) When this attribute is **on**, the prepare script creates private key and root certificate for the generation/verification of the regitry's token.
|
||||
* The following attributes:**crt_country**, **crt_state**, **crt_location**, **crt_organization**, **crt_organizationalunit**, **crt_commonname**, **crt_email** are used as parameters for generating the keys. Set this attribute to **off** when the key and root certificate are supplied by external sources. Refer to [Customize Key and Certificate of Harbor Token Service](customize_token_service.md) for more info.
|
||||
|
||||
#### Configuring storage backend (optional)
|
||||
|
||||
By default, Harbor stores images on your local filesystem. In a production environment, you may consider
|
||||
using other storage backend instead of the local filesystem, like S3, Openstack Swift, Ceph, etc.
|
||||
What you need to update is the section of `storage` in the file `Deploy/templates/registry/config.yml`.
|
||||
What you need to update is the section of `storage` in the file `templates/registry/config.yml`.
|
||||
For example, if you use Openstack Swift as your storage backend, the section may look like this:
|
||||
|
||||
```
|
||||
@ -68,7 +84,7 @@ storage:
|
||||
swift:
|
||||
username: admin
|
||||
password: ADMIN_PASS
|
||||
authurl: http://keystone_addr:35357/v3
|
||||
authurl: http://keystone_addr:35357/v3/auth
|
||||
tenant: admin
|
||||
domain: default
|
||||
region: regionOne
|
||||
@ -78,35 +94,21 @@ storage:
|
||||
_NOTE: For detailed information on storage backend of a registry, refer to [Registry Configuration Reference](https://docs.docker.com/registry/configuration/) ._
|
||||
|
||||
|
||||
#### Building and starting Harbor
|
||||
Once **harbord.cfg** and storage backend (optional) are configured, build and start Harbor as follows. Note that the docker-compose process can take a while.
|
||||
#### Installing and starting Harbor
|
||||
Once **harbord.cfg** and storage backend (optional) are configured, install and start Harbor using the ```install.sh script```. Note that it may take some time for the online installer to download Harbor images from Docker hub.
|
||||
|
||||
```sh
|
||||
$ cd Deploy
|
||||
|
||||
$ ./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
|
||||
Generated configuration file: ./config/jobservice/env
|
||||
Clearing the configuration file: ./config/ui/private_key.pem
|
||||
Clearing the configuration file: ./config/registry/root.crt
|
||||
Generated configuration file: ./config/ui/private_key.pem
|
||||
Generated configuration file: ./config/registry/root.crt
|
||||
The configuration files are ready, please use docker-compose to start the service.
|
||||
|
||||
$ sudo docker-compose up -d
|
||||
$ sudo ./install.sh
|
||||
```
|
||||
|
||||
_If everything worked properly, you should be able to open a browser to visit the admin portal at http://reg.yourdomain.com . Note that the default administrator username/password are admin/Harbor12345 ._
|
||||
If everything worked properly, you should be able to open a browser to visit the admin portal at **http://reg.yourdomain.com** (change *reg.yourdomain.com* to the hostname configured in your harbor.cfg). Note that the default administrator username/password are admin/Harbor12345 .
|
||||
|
||||
Log in to the admin portal and create a new project, e.g. `myproject`. You can then use docker commands to login and push images (By default, the registry server listens on port 80):
|
||||
```sh
|
||||
$ docker login reg.yourdomain.com
|
||||
$ docker push reg.yourdomain.com/myproject/myrepo
|
||||
$ docker push reg.yourdomain.com/myproject/myrepo:mytag
|
||||
```
|
||||
**NOTE:** The default installation of Harbor uses _HTTP_ - as such, you will need to add the option `--insecure-registry` to your client's Docker daemon and restart the Docker service.
|
||||
**IMPORTANT:** The default installation of Harbor uses _HTTP_ - as such, you will need to add the option `--insecure-registry` to your client's Docker daemon and restart the Docker service.
|
||||
|
||||
For information on how to use Harbor, please refer to [User Guide of Harbor](user_guide.md) .
|
||||
|
||||
@ -114,125 +116,10 @@ For information on how to use Harbor, please refer to [User Guide of Harbor](use
|
||||
Harbor does not ship with any certificates, and, by default, uses HTTP to serve requests. While this makes it relatively simple to set up and run - especially for a development or testing environment - it is **not** recommended for a production environment. To enable HTTPS, please refer to [Configuring Harbor with HTTPS Access](configure_https.md).
|
||||
|
||||
|
||||
## Installation from a pre-built package
|
||||
|
||||
Pre-built installation packages of each release are available at [release page](https://github.com/vmware/harbor/releases).
|
||||
Download the package file **harbor-<version>.tgz** , and then extract the files.
|
||||
```
|
||||
$ tar -xzvf harbor-0.3.0.tgz
|
||||
$ cd harbor
|
||||
```
|
||||
|
||||
Next, configure Harbor as described earlier in [Configuring Harbor](#configuring-harbor).
|
||||
|
||||
Finally, run the **prepare** script to generate config files, and use docker compose to build and start Harbor.
|
||||
|
||||
|
||||
```
|
||||
$ ./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
|
||||
Generated configuration file: ./config/jobservice/env
|
||||
Clearing the configuration file: ./config/ui/private_key.pem
|
||||
Clearing the configuration file: ./config/registry/root.crt
|
||||
Generated configuration file: ./config/ui/private_key.pem
|
||||
Generated configuration file: ./config/registry/root.crt
|
||||
The configuration files are ready, please use docker-compose to start the service.
|
||||
|
||||
$ sudo docker-compose up -d
|
||||
......
|
||||
```
|
||||
|
||||
### Deploying Harbor on a host which does not have Internet access
|
||||
*docker-compose up* pulls the base images from Docker Hub and builds new images for the containers, which, necessarily, requires Internet access. To deploy Harbor on a host that is not connected to the Internet:
|
||||
|
||||
1. Prepare Harbor on a machine that has access to the Internet.
|
||||
2. Export the images as tgz files
|
||||
3. Transfer them to the target host.
|
||||
4. Load the tgz file into Docker's local image repo on the host.
|
||||
|
||||
These steps are detailed below:
|
||||
|
||||
#### Building and saving images for offline installation
|
||||
On a machine that is connected to the Internet,
|
||||
|
||||
1. Extract the files from the pre-built installation package.
|
||||
2. Then, run `docker-compose build` to build the images.
|
||||
3. Use the script `save_image.sh` to export these images as tar files. Note that the tar files will be stored in the `images/` directory.
|
||||
4. Package everything in the directory `harbor/` into a tgz file
|
||||
5. Transfer this tgz file to the target machine.
|
||||
|
||||
The commands, in detail, are as follows:
|
||||
|
||||
```
|
||||
$ cd harbor
|
||||
$ sudo docker-compose build
|
||||
......
|
||||
$ sudo ./save_image.sh
|
||||
saving the image of harbor_ui
|
||||
finished saving the image of harbor_ui
|
||||
saving the image of harbor_log
|
||||
finished saving the image of harbor_log
|
||||
saving the image of harbor_mysql
|
||||
finished saving the image of harbor_mysql
|
||||
saving the image of nginx
|
||||
finished saving the image of nginx
|
||||
saving the image of registry
|
||||
finished saving the image of registry
|
||||
saving the image of harbor_jobservice
|
||||
finished saving the image of harbor_jobservice
|
||||
$ cd ../
|
||||
$ tar -cvzf harbor_offline-0.3.0.tgz harbor
|
||||
```
|
||||
|
||||
The file `harbor_offline-0.3.0.tgz` contains the images and other files required to start Harbor. You can use tools such as `rsync` or `scp` to transfer this file to the target host.
|
||||
On the target host, execute the following commands to start Harbor. _Note that before running the **prepare** script, you **must** update **harbor.cfg** to reflect the right configuration of the target machine!_ (Refer to Section [Configuring Harbor](#configuring-harbor)).
|
||||
|
||||
```
|
||||
$ tar -xzvf harbor_offline-0.3.0.tgz
|
||||
$ cd harbor
|
||||
|
||||
# load images save by excute ./save_image.sh
|
||||
$ ./load_image.sh
|
||||
loading the image of harbor_ui
|
||||
finish loaded the image of harbor_ui
|
||||
loading the image of harbor_mysql
|
||||
finished loading the image of harbor_mysql
|
||||
loading the image of nginx
|
||||
finished loading the image of nginx
|
||||
loading the image of registry
|
||||
finished loading the image of registry
|
||||
loading the image of harbor_jobservice
|
||||
finished loading the image of harbor_jobservice
|
||||
|
||||
# Make update to the parameters in ./harbor.cfg
|
||||
$ ./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
|
||||
The configuration files are ready, please use docker-compose to start the service.
|
||||
|
||||
# Build the images and then start the services
|
||||
$ sudo docker-compose up -d
|
||||
```
|
||||
|
||||
### Managing Harbor's lifecycle
|
||||
You can use docker-compose to manage the lifecycle of the containers. A few useful commands are listed below:
|
||||
You can use docker-compose to manage the lifecycle of Harbor. Some useful commands are listed as follows (must run in the same directory as *docker-compose.yml*).
|
||||
|
||||
*Build and start Harbor:*
|
||||
```
|
||||
$ sudo docker-compose up -d
|
||||
Creating harbor_log_1
|
||||
Creating harbor_mysql_1
|
||||
Creating harbor_registry_1
|
||||
Creating harbor_ui_1
|
||||
Creating harbor_proxy_1
|
||||
Creating harbor_jobservice_1
|
||||
```
|
||||
*Stop Harbor:*
|
||||
Stop Harbor:
|
||||
```
|
||||
$ sudo docker-compose stop
|
||||
Stopping harbor_proxy_1 ... done
|
||||
@ -242,7 +129,7 @@ Stopping harbor_mysql_1 ... done
|
||||
Stopping harbor_log_1 ... done
|
||||
Stopping harbor_jobservice_1 ... done
|
||||
```
|
||||
*Restart Harbor after stopping:*
|
||||
Restart Harbor after stopping:
|
||||
```
|
||||
$ sudo docker-compose start
|
||||
Starting harbor_log_1
|
||||
@ -252,7 +139,17 @@ Starting harbor_ui_1
|
||||
Starting harbor_proxy_1
|
||||
Starting harbor_jobservice_1
|
||||
```
|
||||
*Remove Harbor's containers while keeping the image data and Harbor's database files on the file system:*
|
||||
|
||||
To change Harbor's confiugration, first stop existing Harbor instance, update harbor.cfg, and then run install.sh again:
|
||||
```
|
||||
$ sudo docker-compose down
|
||||
|
||||
$ vim harbor.cfg
|
||||
|
||||
$ sudo install.sh
|
||||
```
|
||||
|
||||
Remove Harbor's containers while keeping the image data and Harbor's database files on the file system:
|
||||
```
|
||||
$ sudo docker-compose rm
|
||||
Going to remove harbor_proxy_1, harbor_ui_1, harbor_registry_1, harbor_mysql_1, harbor_log_1, harbor_jobservice_1
|
||||
@ -265,7 +162,7 @@ Removing harbor_log_1 ... done
|
||||
Removing harbor_jobservice_1 ... done
|
||||
```
|
||||
|
||||
*Remove Harbor's database and image data (for a clean re-installation):*
|
||||
Remove Harbor's database and image data (for a clean re-installation):
|
||||
```sh
|
||||
$ rm -r /data/database
|
||||
$ rm -r /data/registry
|
||||
@ -274,15 +171,16 @@ $ rm -r /data/registry
|
||||
Please check the [Docker Compose command-line reference](https://docs.docker.com/compose/reference/) for more on docker-compose.
|
||||
|
||||
### Persistent data and log files
|
||||
By default, registry data is persisted in the target host's `/data/` directory. This data remains unchanged even when Harbor's containers are removed and/or recreated.
|
||||
In addition, Harbor uses `rsyslog` to collect the logs of each container. By default, these log files are stored in the directory `/var/log/harbor/` on the target host.
|
||||
By default, registry data is persisted in the target host's `/data/` directory. This data remains unchanged even when Harbor's containers are removed and/or recreated.
|
||||
|
||||
In addition, Harbor uses *rsyslog* to collect the logs of each container. By default, these log files are stored in the directory `/var/log/harbor/` on the target host for troubleshooting.
|
||||
|
||||
## Configuring Harbor listening on a customized port
|
||||
By default, Harbor listens on port 80(HTTP) and 443(HTTPS, if configured) for both admin portal and docker commands, you can configure it with a customized one.
|
||||
|
||||
### For HTTP protocol
|
||||
|
||||
1.Modify Deploy/docker-compose.yml
|
||||
1.Modify docker-compose.yml
|
||||
Replace the first "80" to a customized port, e.g. 8888:80.
|
||||
|
||||
```
|
||||
@ -306,7 +204,7 @@ proxy:
|
||||
tag: "proxy"
|
||||
```
|
||||
|
||||
2.Modify Deploy/templates/registry/config.yml
|
||||
2.Modify templates/registry/config.yml
|
||||
Add the customized port, e.g. ":8888", after "$ui_url".
|
||||
|
||||
```
|
||||
@ -318,17 +216,14 @@ auth:
|
||||
service: token-service
|
||||
```
|
||||
|
||||
3.Execute Deploy/prepare script and start/restart Harbor.
|
||||
3.Run install.sh to update and start Harbor.
|
||||
```sh
|
||||
$ cd Deploy
|
||||
$ ./prepare
|
||||
# If Harbor has already been installed, shutdown it first:
|
||||
$ docker-compose down
|
||||
$ docker-compose up -d
|
||||
$ sudo docker-compose down
|
||||
$ sudo install.sh
|
||||
```
|
||||
### For HTTPS protocol
|
||||
1.Enable HTTPS in Harbor by following this [guide](https://github.com/vmware/harbor/blob/master/docs/configure_https.md).
|
||||
2.Modify Deploy/docker-compose.yml
|
||||
2.Modify docker-compose.yml
|
||||
Replace the first "443" to a customized port, e.g. 4443:443.
|
||||
|
||||
```
|
||||
@ -352,7 +247,7 @@ proxy:
|
||||
tag: "proxy"
|
||||
```
|
||||
|
||||
3.Modify Deploy/templates/registry/config.yml
|
||||
3.Modify templates/registry/config.yml
|
||||
Add the customized port, e.g. ":4443", after "$ui_url".
|
||||
|
||||
```
|
||||
@ -364,17 +259,29 @@ auth:
|
||||
service: token-service
|
||||
```
|
||||
|
||||
4.Execute Deploy/prepare script and start/restart Harbor.
|
||||
4.Run install.sh to update and start Harbor.
|
||||
```sh
|
||||
$ cd Deploy
|
||||
$ ./prepare
|
||||
# If Harbor has already been installed, shutdown it first:
|
||||
$ docker-compose down
|
||||
$ docker-compose up -d
|
||||
$ sudo docker-compose down
|
||||
$ sudo install.sh
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
1.When setting up Harbor behind an nginx proxy or elastic load balancing, look for the line below, in `Deploy/config/nginx/nginx.conf` and remove it from the sections if the proxy already has similar settings: `location /`, `location /v2/` and `location /service/`.
|
||||
1. When Harbor does not work properly, run the below commands to find out if all containers of Harbor are in **UP** status:
|
||||
```
|
||||
$ sudo docker-compose ps
|
||||
Name Command State Ports
|
||||
-----------------------------------------------------------------------------------------------------
|
||||
harbor_jobservice_1 /harbor/harbor_jobservice Up
|
||||
harbor_log_1 /bin/sh -c crond && rsyslo ... Up 0.0.0.0:1514->514/tcp
|
||||
harbor_mysql_1 /entrypoint.sh mysqld Up 3306/tcp
|
||||
harbor_proxy_1 nginx -g daemon off; Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
|
||||
harbor_registry_1 /entrypoint.sh serve /etc/ ... Up 5000/tcp
|
||||
harbor_ui_1 /harbor/harbor_ui Up
|
||||
```
|
||||
If a container is not in **UP** state, check the log file of that container in directory ```/var/log/harbor```. For example, if the container ```harbor_ui_1``` is not running, you should look at the log file ```docker_ui.log```.
|
||||
|
||||
|
||||
2.When setting up Harbor behind an nginx proxy or elastic load balancing, look for the line below, in `Deploy/config/nginx/nginx.conf` and remove it from the sections if the proxy already has similar settings: `location /`, `location /v2/` and `location /service/`.
|
||||
```
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Harbor upgrade and database migration guide
|
||||
|
||||
When upgrading your existing Habor instance to a newer version, you may need to migrate the data in your database. Refer to [change log](changelog.md) to find out whether there is any change in the database. If there is, you should go through the database migration process. Since the migration may alter the database schema, you should **always** back up your data before any migration.
|
||||
When upgrading your existing Habor instance to a newer version, you may need to migrate the data in your database. Refer to [change log](../migration/changelog.md) to find out whether there is any change in the database. If there is, you should go through the database migration process. Since the migration may alter the database schema, you should **always** back up your data before any migration.
|
||||
|
||||
*If your install Harbor for the first time, or the database version is the same as that of the lastest version, you do not need any database migration.*
|
||||
|
||||
|
@ -60,6 +60,18 @@ paths:
|
||||
required: false
|
||||
type: integer
|
||||
format: int32
|
||||
- name: page
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The page nubmer, default is 1.
|
||||
- name: page_size
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The size of per page, default is 10, maximum is 100.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
@ -69,6 +81,13 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Project'
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of projects
|
||||
type: integer
|
||||
Link:
|
||||
description: Link refers to the previous page and next page
|
||||
type: string
|
||||
401:
|
||||
description: User need to log in first.
|
||||
500:
|
||||
@ -104,7 +123,7 @@ paths:
|
||||
description: New created project.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Project'
|
||||
$ref: '#/definitions/ProjectReq'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
@ -136,13 +155,37 @@ paths:
|
||||
200:
|
||||
description: Return matched project information.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Project'
|
||||
$ref: '#/definitions/Project'
|
||||
401:
|
||||
description: User need to log in first.
|
||||
500:
|
||||
description: Internal errors.
|
||||
delete:
|
||||
summary: Delete project by projectID
|
||||
description: |
|
||||
This endpoint is aimed to delete project by project ID.
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
description: Project ID of project which will be deleted.
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Project is deleted successfully.
|
||||
400:
|
||||
description: Invalid project id.
|
||||
403:
|
||||
description: User need to log in first.
|
||||
404:
|
||||
description: Project does not exist.
|
||||
412:
|
||||
description: Project contains policies, can not be deleted.
|
||||
500:
|
||||
description: Internal errors.
|
||||
/projects/{project_id}/publicity:
|
||||
put:
|
||||
summary: Update properties for a selected project.
|
||||
@ -193,6 +236,18 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/AccessLogFilter'
|
||||
description: Search results of access logs.
|
||||
- name: page
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The page nubmer, default is 1.
|
||||
- name: page_size
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The size of per page, default is 10, maximum is 100.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
@ -202,6 +257,13 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/AccessLog'
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of access logs
|
||||
type: integer
|
||||
Link:
|
||||
description: Link refers to the previous page and next page
|
||||
type: string
|
||||
400:
|
||||
description: Illegal format of provided ID value.
|
||||
401:
|
||||
@ -252,7 +314,7 @@ paths:
|
||||
description: Relevant project ID.
|
||||
- name: roles
|
||||
in: body
|
||||
description: Role members for adding to relevant project.
|
||||
description: Role members for adding to relevant project. Only one role is supported in the role list.
|
||||
schema:
|
||||
$ref: '#/definitions/RoleParam'
|
||||
tags:
|
||||
@ -462,7 +524,7 @@ paths:
|
||||
description: Only email, realname and comment can be modified.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/User'
|
||||
$ref: '#/definitions/UserProfile'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
@ -549,6 +611,12 @@ paths:
|
||||
format: int
|
||||
required: true
|
||||
description: Registered user ID
|
||||
- name: has_admin_role
|
||||
in: body
|
||||
description: Toggle a user to admin or not.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/HasAdminRole'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
@ -581,6 +649,18 @@ paths:
|
||||
type: string
|
||||
required: false
|
||||
description: Repo name for filtering results.
|
||||
- name: page
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The page nubmer, default is 1.
|
||||
- name: page_size
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The size of per page, default is 10, maximum is 100.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
@ -590,6 +670,13 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of repositories
|
||||
type: integer
|
||||
Link:
|
||||
description: Link refers to the previous page and next page
|
||||
type: string
|
||||
400:
|
||||
description: Invalid project ID.
|
||||
403:
|
||||
@ -625,7 +712,7 @@ paths:
|
||||
404:
|
||||
description: Repository or tag not found.
|
||||
403:
|
||||
description: Forbidden.
|
||||
description: Forbidden.
|
||||
/repositories/tags:
|
||||
get:
|
||||
summary: Get tags of a relevant repository.
|
||||
@ -660,6 +747,11 @@ paths:
|
||||
type: string
|
||||
required: true
|
||||
description: Tag name
|
||||
- name: version
|
||||
in: query
|
||||
type: string
|
||||
required: false
|
||||
description: The version of manifest, valid value are "v1" and "v2", default is "v2"
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
@ -777,13 +869,32 @@ paths:
|
||||
type: string
|
||||
required: false
|
||||
description: The respond jobs list filter by status.
|
||||
- name: page
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The page nubmer, default is 1.
|
||||
- name: page_size
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The size of per page, default is 10, maximum is 100.
|
||||
responses:
|
||||
200:
|
||||
description: Get the required logs successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/JobStatus'
|
||||
$ref: '#/definitions/RepPolicy'
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of jobs
|
||||
type: integer
|
||||
Link:
|
||||
description: Link refers to the previous page and next page
|
||||
type: string
|
||||
400:
|
||||
description: Bad request because of invalid parameters.
|
||||
401:
|
||||
@ -1106,9 +1217,7 @@ paths:
|
||||
200:
|
||||
description: Get replication's target successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/RepTarget'
|
||||
$ref: '#/definitions/RepTarget'
|
||||
401:
|
||||
description: User need to log in first.
|
||||
404:
|
||||
@ -1197,18 +1306,28 @@ definitions:
|
||||
SearchRepository:
|
||||
type: object
|
||||
properties:
|
||||
repository_name:
|
||||
type: string
|
||||
description: The name of the repository
|
||||
project_name:
|
||||
type: string
|
||||
description: The name of the project that the repository belongs to
|
||||
project_id:
|
||||
type: integer
|
||||
description: The ID of the project that the repository belongs to
|
||||
project_name:
|
||||
type: string
|
||||
description: The name of the project that the repository belongs to
|
||||
project_public:
|
||||
type: integer
|
||||
description: The flag to indicate the publicity of the project that the repository belongs to (1 is public, 0 is not)
|
||||
repository_name:
|
||||
type: string
|
||||
description: The name of the repository
|
||||
ProjectReq:
|
||||
type: object
|
||||
properties:
|
||||
project_name:
|
||||
type: string
|
||||
description: The name of the project.
|
||||
public:
|
||||
type: integer
|
||||
format: int
|
||||
description: The public status of the project.
|
||||
Project:
|
||||
type: object
|
||||
properties:
|
||||
@ -1220,7 +1339,7 @@ definitions:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The owner ID of the project always means the creator of the project.
|
||||
project_name:
|
||||
name:
|
||||
type: string
|
||||
description: The name of the project.
|
||||
creation_time:
|
||||
@ -1233,18 +1352,14 @@ definitions:
|
||||
type: integer
|
||||
format: int32
|
||||
description: A deletion mark of the project (1 means it's deleted, 0 is not)
|
||||
user_id:
|
||||
type: integer
|
||||
format: int32
|
||||
description: A relation field to the user table.
|
||||
owner_name:
|
||||
type: string
|
||||
description: The owner name of the project.
|
||||
description: The owner name of the project.
|
||||
public:
|
||||
type: boolean
|
||||
format: boolean
|
||||
type: integer
|
||||
format: int
|
||||
description: The public status of the project.
|
||||
togglable:
|
||||
Togglable:
|
||||
type: boolean
|
||||
description: Correspond to the UI about whether the project's publicity is updatable (for UI)
|
||||
current_user_role_id:
|
||||
@ -1256,50 +1371,48 @@ definitions:
|
||||
Repository:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
manifest:
|
||||
type: object
|
||||
description: The detail of manifest.
|
||||
config:
|
||||
type: string
|
||||
description: Repository ID
|
||||
parent:
|
||||
type: string
|
||||
description: Parent of the image.
|
||||
created:
|
||||
type: string
|
||||
description: Repository create time.
|
||||
duration_days:
|
||||
type: string
|
||||
description: Duration days of the image.
|
||||
author:
|
||||
type: string
|
||||
description: Author of the image.
|
||||
architecture:
|
||||
type: string
|
||||
description: Architecture of the image.
|
||||
docker_version:
|
||||
type: string
|
||||
description: Docker version of the image.
|
||||
os:
|
||||
type: string
|
||||
description: OS of the image.
|
||||
description: The config of the repository.
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
user_id:
|
||||
type: integer
|
||||
format: int32
|
||||
format: int
|
||||
description: The ID of the user.
|
||||
username:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
password:
|
||||
type: string
|
||||
realname:
|
||||
realname:
|
||||
type: string
|
||||
comment:
|
||||
type: string
|
||||
deleted:
|
||||
type: integer
|
||||
format: int32
|
||||
role_name:
|
||||
type: string
|
||||
role_id:
|
||||
type: integer
|
||||
format: int
|
||||
has_admin_role:
|
||||
type: integer
|
||||
format: int
|
||||
reset_uuid:
|
||||
type: string
|
||||
Salt:
|
||||
type: string
|
||||
creation_time:
|
||||
type: string
|
||||
update_time:
|
||||
type: string
|
||||
Password:
|
||||
type: object
|
||||
properties:
|
||||
@ -1308,7 +1421,7 @@ definitions:
|
||||
description: The user's existing password.
|
||||
new_password:
|
||||
type: string
|
||||
description: New password for marking as to be updated.
|
||||
description: New password for marking as to be updated.
|
||||
AccessLogFilter:
|
||||
type: object
|
||||
properties:
|
||||
@ -1357,6 +1470,8 @@ definitions:
|
||||
role_name:
|
||||
type: string
|
||||
description: Name the the role.
|
||||
role_mask:
|
||||
type: string
|
||||
RoleParam:
|
||||
type: object
|
||||
properties:
|
||||
@ -1375,7 +1490,7 @@ definitions:
|
||||
repo_name:
|
||||
type: string
|
||||
description: The name of the repo
|
||||
access_count:
|
||||
count:
|
||||
type: integer
|
||||
format: int
|
||||
description: The access count of the repo
|
||||
@ -1461,9 +1576,6 @@ definitions:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The target ID.
|
||||
target_name:
|
||||
type: string
|
||||
description: The target name.
|
||||
name:
|
||||
type: string
|
||||
description: The policy name.
|
||||
@ -1489,6 +1601,8 @@ definitions:
|
||||
error_job_count:
|
||||
format: int
|
||||
description: The error job count number for the policy.
|
||||
deleted:
|
||||
type: integer
|
||||
RepPolicyPost:
|
||||
type: object
|
||||
properties:
|
||||
@ -1574,3 +1688,21 @@ definitions:
|
||||
password:
|
||||
type: string
|
||||
description: The target server password.
|
||||
HasAdminRole:
|
||||
type: object
|
||||
properties:
|
||||
has_admin_role:
|
||||
type: integer
|
||||
description: 1-has admin, 0-not.
|
||||
UserProfile:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
description: The new email.
|
||||
realname:
|
||||
type: string
|
||||
description: The new realname.
|
||||
comment:
|
||||
type: string
|
||||
description: The new comment.
|
||||
|
@ -31,6 +31,7 @@ var localUIURL string
|
||||
var localRegURL string
|
||||
var logDir string
|
||||
var uiSecret string
|
||||
var secretKey string
|
||||
var verifyRemoteCert string
|
||||
|
||||
func init() {
|
||||
@ -86,6 +87,11 @@ func init() {
|
||||
beego.LoadAppConfig("ini", configPath)
|
||||
}
|
||||
|
||||
secretKey = os.Getenv("SECRET_KEY")
|
||||
if len(secretKey) != 16 {
|
||||
panic("The length of secretkey has to be 16 characters!")
|
||||
}
|
||||
|
||||
log.Debugf("config: maxJobWorkers: %d", maxJobWorkers)
|
||||
log.Debugf("config: localUIURL: %s", localUIURL)
|
||||
log.Debugf("config: localRegURL: %s", localRegURL)
|
||||
@ -119,6 +125,11 @@ func UISecret() string {
|
||||
return uiSecret
|
||||
}
|
||||
|
||||
// SecretKey will return the secret key for encryption/decryption password in target.
|
||||
func SecretKey() string {
|
||||
return secretKey
|
||||
}
|
||||
|
||||
// VerifyRemoteCert return the flag to tell jobservice whether or not verify the cert of remote registry
|
||||
func VerifyRemoteCert() bool {
|
||||
return verifyRemoteCert != "off"
|
||||
|
9
job/config/config_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
9
job/job_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
9
job/replication/replication_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package replication
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
@ -192,7 +192,7 @@ func (c *Checker) enter() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = c.createProject(project.Public == 1)
|
||||
err = c.createProject(project.Public)
|
||||
if err == nil {
|
||||
c.logger.Infof("project %s is created on %s with user %s", c.project, c.dstURL, c.dstUsr)
|
||||
return StatePullManifest, nil
|
||||
@ -211,13 +211,13 @@ func (c *Checker) enter() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
func (c *Checker) createProject(isPublic bool) error {
|
||||
func (c *Checker) createProject(public int) error {
|
||||
project := struct {
|
||||
ProjectName string `json:"project_name"`
|
||||
Public bool `json:"public"`
|
||||
Public int `json:"public"`
|
||||
}{
|
||||
ProjectName: c.project,
|
||||
Public: isPublic,
|
||||
Public: public,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(project)
|
||||
|
@ -231,7 +231,7 @@ func (sm *SM) Reset(jid int64) error {
|
||||
pwd := target.Password
|
||||
|
||||
if len(pwd) != 0 {
|
||||
pwd, err = uti.ReversibleDecrypt(pwd)
|
||||
pwd, err = uti.ReversibleDecrypt(pwd, config.SecretKey())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt password: %v", err)
|
||||
}
|
||||
|
9
job/utils/utils_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
9
jobservice/jobservice_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ Changelog for harbor database schema
|
||||
- delete data `AMDRWS` from table `role`
|
||||
- delete data `A` from table `access`
|
||||
|
||||
## 0.2.0
|
||||
## 0.3.0
|
||||
|
||||
- create table `replication_policy`
|
||||
- create table `replication_target`
|
||||
@ -25,3 +25,15 @@ Changelog for harbor database schema
|
||||
- add column `repo_tag` to table `access_log`
|
||||
- alter column `repo_name` on table `access_log`
|
||||
- alter column `email` on table `user`
|
||||
|
||||
## 0.4.0
|
||||
|
||||
- add index `pid_optime (project_id, op_time)` on table `access_log`
|
||||
- add index `poid_uptime (policy_id, update_time)` on table `replication_job`
|
||||
- add column `deleted` to table `replication_policy`
|
||||
- alter column `username` on table `user`: varchar(15)->varchar(32)
|
||||
- alter column `password` on table `replication_target`: varchar(40)->varchar(128)
|
||||
- alter column `email` on table `user`: varchar(128)->varchar(255)
|
||||
- alter column `name` on table `project`: varchar(30)->varchar(41)
|
||||
- create table `repository`
|
||||
- alter column `password` on table `replication_target`: varchar(40)->varchar(128)
|
||||
|
@ -125,3 +125,18 @@ class ReplicationJob(Base):
|
||||
update_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
|
||||
|
||||
__table_args__ = (sa.Index('policy', "policy_id"),)
|
||||
|
||||
class Repository(Base):
|
||||
__tablename__ = "repository"
|
||||
|
||||
repository_id = sa.Column(sa.Integer, primary_key=True)
|
||||
name = sa.Column(sa.String(255), nullable=False, unique=True)
|
||||
project_id = sa.Column(sa.Integer, nullable=False)
|
||||
owner_id = sa.Column(sa.Integer, nullable=False)
|
||||
description = sa.Column(sa.Text)
|
||||
pull_count = sa.Column(sa.Integer,server_default=sa.text("'0'"), nullable=False)
|
||||
star_count = sa.Column(sa.Integer,server_default=sa.text("'0'"), nullable=False)
|
||||
creation_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP"))
|
||||
update_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
|
||||
|
||||
|
||||
|
54
migration/migration_harbor/versions/0_4_0.py
Normal file
@ -0,0 +1,54 @@
|
||||
# Copyright (c) 2008-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.
|
||||
|
||||
"""0.3.0 to 0.4.0
|
||||
|
||||
Revision ID: 0.3.0
|
||||
Revises:
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0.4.0'
|
||||
down_revision = '0.3.0'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
from alembic import op
|
||||
from db_meta import *
|
||||
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
def upgrade():
|
||||
"""
|
||||
update schema&data
|
||||
"""
|
||||
bind = op.get_bind()
|
||||
#alter column user.username, alter column user.email, project.name and add column replication_policy.deleted
|
||||
op.alter_column('user', 'username', type_=sa.String(32), existing_type=sa.String(15))
|
||||
op.alter_column('user', 'email', type_=sa.String(255), existing_type=sa.String(128))
|
||||
op.alter_column('project', 'name', type_=sa.String(41), existing_type=sa.String(30), nullable=False)
|
||||
op.alter_column('replication_target', 'password', type_=sa.String(128), existing_type=sa.String(40))
|
||||
op.add_column('replication_policy', sa.Column('deleted', mysql.TINYINT(1), nullable=False, server_default=sa.text("'0'")))
|
||||
#create index pid_optime (project_id, op_time) on table access_log, poid_uptime (policy_id, update_time) on table replication_job
|
||||
op.create_index('pid_optime', 'access_log', ['project_id', 'op_time'])
|
||||
op.create_index('poid_uptime', 'replication_job', ['policy_id', 'update_time'])
|
||||
#create tables: repository
|
||||
Repository.__table__.create(bind)
|
||||
|
||||
def downgrade():
|
||||
"""
|
||||
Downgrade has been disabled.
|
||||
"""
|
||||
pass
|
@ -1,16 +1,16 @@
|
||||
/*
|
||||
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.
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package models
|
||||
@ -23,8 +23,9 @@ func init() {
|
||||
orm.RegisterModel(new(RepTarget),
|
||||
new(RepPolicy),
|
||||
new(RepJob),
|
||||
new(User),
|
||||
new(User),
|
||||
new(Project),
|
||||
new(Role),
|
||||
new(AccessLog))
|
||||
new(AccessLog),
|
||||
new(RepoRecord))
|
||||
}
|
||||
|
9
models/models_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
@ -25,17 +25,17 @@ type Project struct {
|
||||
OwnerID int `orm:"column(owner_id)" json:"owner_id"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"`
|
||||
CreationTimeStr string `json:"creation_time_str"`
|
||||
CreationTimeStr string `orm:"-" json:"creation_time_str"`
|
||||
Deleted int `orm:"column(deleted)" json:"deleted"`
|
||||
//UserID int `json:"UserId"`
|
||||
OwnerName string `json:"owner_name"`
|
||||
OwnerName string `orm:"-" json:"owner_name"`
|
||||
Public int `orm:"column(public)" json:"public"`
|
||||
//This field does not have correspondent column in DB, this is just for UI to disable button
|
||||
Togglable bool
|
||||
Togglable bool `orm:"-"`
|
||||
|
||||
UpdateTime time.Time `orm:"update_time" json:"update_time"`
|
||||
Role int `json:"current_user_role_id"`
|
||||
RepoCount int `json:"repo_count"`
|
||||
Role int `orm:"-" json:"current_user_role_id"`
|
||||
RepoCount int `orm:"-" json:"repo_count"`
|
||||
}
|
||||
|
||||
// ProjectSorter holds an array of projects
|
||||
|
@ -63,6 +63,7 @@ type RepPolicy struct {
|
||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||
ErrorJobCount int `json:"error_job_count"`
|
||||
Deleted int `orm:"column(deleted)" json:"deleted"`
|
||||
}
|
||||
|
||||
// Valid ...
|
||||
|
@ -3,9 +3,7 @@
|
||||
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.
|
||||
@ -19,44 +17,22 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Repo holds information about repositories.
|
||||
type Repo struct {
|
||||
Repositories []string `json:"repositories"`
|
||||
// RepoRecord holds the record of an repository in DB, all the infors are from the registry notification event.
|
||||
type RepoRecord struct {
|
||||
RepositoryID string `orm:"column(repository_id);pk" json:"repository_id"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
OwnerName string `orm:"-"`
|
||||
OwnerID int64 `orm:"column(owner_id)" json:"owner_id"`
|
||||
ProjectName string `orm:"-"`
|
||||
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||
Description string `orm:"column(description)" json:"description"`
|
||||
PullCount int64 `orm:"column(pull_count)" json:"pull_count"`
|
||||
StarCount int64 `orm:"column(star_count)" json:"star_count"`
|
||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||
}
|
||||
|
||||
// RepoItem holds manifest of an image.
|
||||
type RepoItem struct {
|
||||
ID string `json:"Id"`
|
||||
Parent string `json:"Parent"`
|
||||
Created time.Time `json:"Created"`
|
||||
DurationDays string `json:"Duration Days"`
|
||||
Author string `json:"Author"`
|
||||
Architecture string `json:"Architecture"`
|
||||
DockerVersion string `json:"Docker Version"`
|
||||
Os string `json:"OS"`
|
||||
//Size int `json:"Size"`
|
||||
}
|
||||
|
||||
// Tag holds information about a tag.
|
||||
type Tag struct {
|
||||
Version string `json:"version"`
|
||||
ImageID string `json:"image_id"`
|
||||
}
|
||||
|
||||
// Manifest ...
|
||||
type Manifest struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
Name string `json:"name"`
|
||||
Tag string `json:"tag"`
|
||||
Architecture string `json:"architecture"`
|
||||
FsLayers []blobSumItem `json:"fsLayers"`
|
||||
History []histroyItem `json:"history"`
|
||||
}
|
||||
|
||||
type histroyItem struct {
|
||||
V1Compatibility string `json:"v1Compatibility"`
|
||||
}
|
||||
|
||||
type blobSumItem struct {
|
||||
BlobSum string `json:"blobSum"`
|
||||
//TableName is required by by beego orm to map RepoRecord to table repository
|
||||
func (rp *RepoRecord) TableName() string {
|
||||
return "repository"
|
||||
}
|
||||
|
57
service/cache/cache.go
vendored
@ -16,9 +16,9 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
"github.com/vmware/harbor/utils/registry"
|
||||
"github.com/vmware/harbor/utils/registry/auth"
|
||||
@ -28,9 +28,7 @@ import (
|
||||
|
||||
var (
|
||||
// Cache is the global cache in system.
|
||||
Cache cache.Cache
|
||||
endpoint string
|
||||
username string
|
||||
Cache cache.Cache
|
||||
)
|
||||
|
||||
const catalogKey string = "catalog"
|
||||
@ -41,52 +39,17 @@ func init() {
|
||||
if err != nil {
|
||||
log.Errorf("Failed to initialize cache, error:%v", err)
|
||||
}
|
||||
|
||||
endpoint = os.Getenv("REGISTRY_URL")
|
||||
username = "admin"
|
||||
}
|
||||
|
||||
// RefreshCatalogCache calls registry's API to get repository list and write it to cache.
|
||||
func RefreshCatalogCache() error {
|
||||
log.Debug("refreshing catalog cache...")
|
||||
|
||||
registryClient, err := NewRegistryClient(endpoint, true, username,
|
||||
"registry", "catalog", "*")
|
||||
repos, err := getAllRepositories()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rs, err := registryClient.Catalog()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
/*
|
||||
repos := []string{}
|
||||
|
||||
for _, repo := range rs {
|
||||
rc, ok := repositoryClients[repo]
|
||||
if !ok {
|
||||
rc, err = registry.NewRepositoryWithUsername(repo, endpoint, username)
|
||||
if err != nil {
|
||||
log.Errorf("error occurred while initializing repository client used by cache: %s %v", repo, err)
|
||||
continue
|
||||
}
|
||||
repositoryClients[repo] = rc
|
||||
}
|
||||
tags, err := rc.ListTag()
|
||||
if err != nil {
|
||||
log.Errorf("error occurred while list tag for %s: %v", repo, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(tags) != 0 {
|
||||
repos = append(repos, repo)
|
||||
log.Debugf("add %s to catalog cache", repo)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Cache.Put(catalogKey, rs, 600*time.Second)
|
||||
Cache.Put(catalogKey, repos, 600*time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -108,6 +71,18 @@ func GetRepoFromCache() ([]string, error) {
|
||||
return result.([]string), nil
|
||||
}
|
||||
|
||||
func getAllRepositories() ([]string, error) {
|
||||
var repos []string
|
||||
rs, err := dao.GetAllRepositories()
|
||||
if err != nil {
|
||||
return repos, err
|
||||
}
|
||||
for _, e := range rs {
|
||||
repos = append(repos, e.Name)
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// NewRegistryClient ...
|
||||
func NewRegistryClient(endpoint string, insecure bool, username, scopeType, scopeName string,
|
||||
scopeActions ...string) (*registry.Registry, error) {
|
||||
|
9
service/cache/cache_test.go
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
"github.com/vmware/harbor/service/cache"
|
||||
"github.com/vmware/harbor/utils"
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
@ -55,11 +56,7 @@ func (n *NotificationHandler) Post() {
|
||||
for _, event := range events {
|
||||
repository := event.Target.Repository
|
||||
|
||||
project := ""
|
||||
if strings.Contains(repository, "/") {
|
||||
project = repository[0:strings.LastIndex(repository, "/")]
|
||||
}
|
||||
|
||||
project, _ := utils.ParseRepository(repository)
|
||||
tag := event.Target.Tag
|
||||
action := event.Action
|
||||
|
||||
@ -80,6 +77,18 @@ func (n *NotificationHandler) Post() {
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
exist := dao.RepositoryExists(repository)
|
||||
if exist {
|
||||
return
|
||||
}
|
||||
log.Debugf("Add repository %s into DB.", repository)
|
||||
repoRecord := models.RepoRecord{Name: repository, OwnerName: user, ProjectName: project}
|
||||
if err := dao.AddRepository(repoRecord); err != nil {
|
||||
log.Errorf("Error happens when adding repository: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
operation := ""
|
||||
if action == "push" {
|
||||
operation = models.RepOpTransfer
|
||||
@ -87,6 +96,14 @@ func (n *NotificationHandler) Post() {
|
||||
|
||||
go api.TriggerReplicationByRepository(repository, []string{tag}, operation)
|
||||
}
|
||||
if action == "pull" {
|
||||
go func() {
|
||||
log.Debugf("Increase the repository %s pull count.", repository)
|
||||
if err := dao.IncreasePullCount(repository); err != nil {
|
||||
log.Errorf("Error happens when increasing pull count: %v", repository)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
9
service/service_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
9
service/token/token_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
@ -17,9 +17,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
)
|
||||
|
||||
// VerifySecret verifies the UI_SECRET cookie in a http request.
|
||||
@ -27,7 +28,7 @@ func VerifySecret(r *http.Request) bool {
|
||||
secret := os.Getenv("UI_SECRET")
|
||||
c, err := r.Cookie("uisecret")
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get secret cookie, error: %v", err)
|
||||
log.Warningf("Failed to get secret cookie, error: %v", err)
|
||||
}
|
||||
return c != nil && c.Value == secret
|
||||
}
|
||||
|
9
service/utils/utils_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
@ -73,13 +73,11 @@
|
||||
}
|
||||
|
||||
.sub-pane {
|
||||
margin: 15px;
|
||||
min-height: 380px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.well-custom {
|
||||
|
||||
width: 100%;
|
||||
background-color: #f5f5f5;
|
||||
background-image: none;
|
||||
|
@ -27,64 +27,55 @@
|
||||
|
||||
vm.projectName = '';
|
||||
vm.isOpen = false;
|
||||
vm.isProjectMember = false;
|
||||
vm.target = $location.path().substr(1) || 'repositories';
|
||||
|
||||
if(getParameterByName('is_public', $location.absUrl())) {
|
||||
vm.isPublic = getParameterByName('is_public', $location.absUrl()) === 'true' ? 1 : 0;
|
||||
vm.publicity = (vm.isPublic === 1) ? true : false;
|
||||
}
|
||||
|
||||
vm.isPublic = Number(getParameterByName('is_public', $location.absUrl()));
|
||||
|
||||
vm.retrieve = retrieve;
|
||||
vm.filterInput = '';
|
||||
vm.selectItem = selectItem;
|
||||
vm.checkProjectMember = checkProjectMember;
|
||||
|
||||
$scope.$watch('vm.selectedProject', function(current, origin) {
|
||||
if(current) {
|
||||
vm.selectedId = current.project_id;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('vm.publicity', function(current, origin) {
|
||||
vm.publicity = current ? true : false;
|
||||
vm.isPublic = vm.publicity ? 1 : 0;
|
||||
vm.projectType = (vm.isPublic === 1) ? 'public_projects' : 'my_projects';
|
||||
vm.retrieve();
|
||||
});
|
||||
|
||||
|
||||
function retrieve() {
|
||||
ListProjectService(vm.projectName, vm.isPublic)
|
||||
.success(getProjectSuccess)
|
||||
.error(getProjectFailed);
|
||||
}
|
||||
|
||||
|
||||
vm.retrieve();
|
||||
|
||||
$scope.$watch('vm.isPublic', function(current) {
|
||||
vm.projectType = vm.isPublic === 0 ? 'my_project_count' : 'public_project_count';
|
||||
});
|
||||
|
||||
$scope.$watch('vm.selectedProject', function(current) {
|
||||
if(current) {
|
||||
vm.selectedId = current.project_id;
|
||||
}
|
||||
});
|
||||
|
||||
function getProjectSuccess(data, status) {
|
||||
vm.projects = data;
|
||||
|
||||
if(vm.projects == null) {
|
||||
vm.isPublic = 1;
|
||||
vm.publicity = true;
|
||||
vm.projectType = 'public_projects';
|
||||
console.log('vm.projects is null, load public projects.');
|
||||
return;
|
||||
vm.projects = data || [];
|
||||
|
||||
if(vm.projects.length == 0 && vm.isPublic === 0){
|
||||
$window.location.href = '/project';
|
||||
}
|
||||
|
||||
if(angular.isArray(vm.projects) && vm.projects.length > 0) {
|
||||
vm.selectedProject = vm.projects[0];
|
||||
}else{
|
||||
$window.location.href = '/project';
|
||||
}
|
||||
|
||||
|
||||
if(getParameterByName('project_id', $location.absUrl())){
|
||||
angular.forEach(vm.projects, function(value, index) {
|
||||
if(value['project_id'] === Number(getParameterByName('project_id', $location.absUrl()))) {
|
||||
vm.selectedProject = value;
|
||||
for(var i in vm.projects) {
|
||||
var project = vm.projects[i];
|
||||
if(project['project_id'] == getParameterByName('project_id', $location.absUrl())) {
|
||||
vm.selectedProject = project;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$location.search('project_id', vm.selectedProject.project_id);
|
||||
|
||||
vm.checkProjectMember(vm.selectedProject.project_id);
|
||||
vm.checkProjectMember(vm.selectedProject.project_id);
|
||||
|
||||
vm.resultCount = vm.projects.length;
|
||||
|
||||
$scope.$watch('vm.filterInput', function(current, origin) {
|
||||
@ -102,11 +93,13 @@
|
||||
function selectItem(item) {
|
||||
vm.selectedProject = item;
|
||||
$location.search('project_id', vm.selectedProject.project_id);
|
||||
$scope.$emit('projectChanged', true);
|
||||
}
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function(e) {
|
||||
var projectId = getParameterByName('project_id', $location.absUrl());
|
||||
vm.isOpen = false;
|
||||
vm.projectId = getParameterByName('project_id', $location.absUrl());
|
||||
vm.isOpen = false;
|
||||
vm.checkProjectMember(vm.selectedProject.project_id);
|
||||
});
|
||||
|
||||
function checkProjectMember(projectId) {
|
||||
@ -121,8 +114,9 @@
|
||||
}
|
||||
|
||||
function getCurrentProjectMemberFailed(data, status) {
|
||||
vm.isProjectMember = false;
|
||||
vm.isProjectMember = false;
|
||||
console.log('Current user has no member for the project:' + status + ', location.url:' + $location.url());
|
||||
vm.target = 'repositories';
|
||||
}
|
||||
|
||||
}
|
||||
@ -132,9 +126,10 @@
|
||||
restrict: 'E',
|
||||
templateUrl: '/static/resources/js/components/details/retrieve-projects.directive.html',
|
||||
scope: {
|
||||
'target': '=',
|
||||
'isOpen': '=',
|
||||
'selectedProject': '=',
|
||||
'publicity': '=',
|
||||
'isPublic': '=',
|
||||
'isProjectMember': '='
|
||||
},
|
||||
link: link,
|
||||
@ -147,7 +142,7 @@
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
$(document).on('click', clickHandler);
|
||||
|
||||
|
||||
function clickHandler(e) {
|
||||
$('[data-toggle="popover"]').each(function () {
|
||||
if (!$(this).is(e.target) &&
|
||||
|
@ -20,6 +20,8 @@
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary" type="button" ng-click="vm.search({op: vm.op, username: vm.username})"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-link" type="button" ng-click="vm.showAdvancedSearch()">// 'advanced_search' | tr //</button>
|
||||
</span>
|
||||
@ -48,7 +50,8 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<paginator ng-if="vm.totalCount > 0" total-count="//vm.totalCount//" page-size="//vm.pageSize//" page="vm.page" display-count="5"></paginator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -35,7 +35,8 @@
|
||||
vm.beginTimestamp = 0;
|
||||
vm.endTimestamp = 0;
|
||||
vm.keywords = '';
|
||||
vm.username = '';
|
||||
|
||||
vm.username = $location.hash() || '';
|
||||
|
||||
vm.op = [];
|
||||
vm.opOthers = true;
|
||||
@ -51,29 +52,36 @@
|
||||
'projectId': vm.projectId,
|
||||
'username' : vm.username
|
||||
};
|
||||
|
||||
retrieve(vm.queryParams);
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function() {
|
||||
|
||||
if(vm.publicity) {
|
||||
vm.target = 'repositories';
|
||||
|
||||
vm.page = 1;
|
||||
vm.pageSize = 15;
|
||||
|
||||
$scope.$watch('vm.page', function(current, origin) {
|
||||
if(current) {
|
||||
vm.page = current;
|
||||
retrieve(vm.queryParams, vm.page, vm.pageSize);
|
||||
}
|
||||
});
|
||||
|
||||
vm.projectId = getParameterByName('project_id', $location.absUrl());
|
||||
vm.queryParams = {
|
||||
'beginTimestamp' : vm.beginTimestamp,
|
||||
'endTimestamp' : vm.endTimestamp,
|
||||
'keywords' : vm.keywords,
|
||||
'projectId': vm.projectId,
|
||||
'username' : vm.username
|
||||
};
|
||||
vm.username = '';
|
||||
retrieve(vm.queryParams);
|
||||
$scope.$on('retrieveData', function(e, val) {
|
||||
if(val) {
|
||||
vm.projectId = getParameterByName('project_id', $location.absUrl());
|
||||
vm.queryParams = {
|
||||
'beginTimestamp' : vm.beginTimestamp,
|
||||
'endTimestamp' : vm.endTimestamp,
|
||||
'keywords' : vm.keywords,
|
||||
'projectId': vm.projectId,
|
||||
'username' : vm.username
|
||||
};
|
||||
vm.username = '';
|
||||
retrieve(vm.queryParams, vm.page, vm.pageSize);
|
||||
}
|
||||
});
|
||||
|
||||
function search(e) {
|
||||
|
||||
|
||||
vm.page = 1;
|
||||
|
||||
if(e.op[0] === 'all') {
|
||||
e.op = ['create', 'pull', 'push', 'delete'];
|
||||
}
|
||||
@ -83,11 +91,12 @@
|
||||
|
||||
vm.queryParams.keywords = e.op.join('/');
|
||||
vm.queryParams.username = e.username;
|
||||
|
||||
|
||||
vm.queryParams.beginTimestamp = toUTCSeconds(vm.fromDate, 0, 0, 0);
|
||||
vm.queryParams.endTimestamp = toUTCSeconds(vm.toDate, 23, 59, 59);
|
||||
|
||||
retrieve(vm.queryParams);
|
||||
|
||||
retrieve(vm.queryParams, vm.page, vm.pageSize);
|
||||
|
||||
}
|
||||
|
||||
function showAdvancedSearch() {
|
||||
@ -98,27 +107,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
function retrieve(queryParams) {
|
||||
ListLogService(queryParams)
|
||||
function retrieve(queryParams, page, pageSize) {
|
||||
ListLogService(queryParams, page, pageSize)
|
||||
.then(listLogComplete)
|
||||
.catch(listLogFailed);
|
||||
}
|
||||
|
||||
function listLogComplete(response) {
|
||||
vm.logs = response.data;
|
||||
vm.totalCount = response.headers('X-Total-Count');
|
||||
|
||||
console.log('Total Count in logs:' + vm.totalCount + ', page:' + vm.page);
|
||||
|
||||
vm.queryParams = {
|
||||
'beginTimestamp' : 0,
|
||||
'endTimestamp' : 0,
|
||||
'keywords' : '',
|
||||
'projectId': vm.projectId,
|
||||
'username' : ''
|
||||
};
|
||||
vm.op = ['all'];
|
||||
vm.fromDate = '';
|
||||
vm.toDate = '';
|
||||
vm.others = '';
|
||||
vm.opOthers = true;
|
||||
vm.isOpen = false;
|
||||
}
|
||||
function listLogFailed(response){
|
||||
@ -148,9 +148,7 @@
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/log/list-log.directive.html',
|
||||
'scope': {
|
||||
'sectionHeight': '=',
|
||||
'target': '=',
|
||||
'publicity': '='
|
||||
'sectionHeight': '='
|
||||
},
|
||||
'link': link,
|
||||
'controller': ListLogController,
|
||||
|
@ -20,9 +20,9 @@
|
||||
.module('harbor.optional.menu')
|
||||
.directive('optionalMenu', optionalMenu);
|
||||
|
||||
OptionalMenuController.$inject = ['$window', 'I18nService', 'LogOutService', 'currentUser', '$timeout'];
|
||||
OptionalMenuController.$inject = ['$scope', '$window', 'I18nService', 'LogOutService', 'currentUser', '$timeout', 'trFilter', '$filter'];
|
||||
|
||||
function OptionalMenuController($window, I18nService, LogOutService, currentUser, $timeout) {
|
||||
function OptionalMenuController($scope, $window, I18nService, LogOutService, currentUser, $timeoutm, trFilter, $filter) {
|
||||
var vm = this;
|
||||
|
||||
vm.currentLanguage = I18nService().getCurrentLanguage();
|
||||
@ -36,6 +36,7 @@
|
||||
vm.user = currentUser.get();
|
||||
vm.setLanguage = setLanguage;
|
||||
vm.logOut = logOut;
|
||||
vm.about = about;
|
||||
|
||||
function setLanguage(language) {
|
||||
I18nService().setCurrentLanguage(language);
|
||||
@ -54,13 +55,25 @@
|
||||
function logOutFailed(data, status) {
|
||||
console.log('Failed to log out:' + data);
|
||||
}
|
||||
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() {}
|
||||
};
|
||||
$scope.$emit('raiseInfo', raiseInfo);
|
||||
}
|
||||
}
|
||||
|
||||
function optionalMenu() {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/optional_menu?timestamp=' + new Date().getTime(),
|
||||
'scope': true,
|
||||
'scope': {
|
||||
'version': '@'
|
||||
},
|
||||
'controller': OptionalMenuController,
|
||||
'controllerAs': 'vm',
|
||||
'bindToController': true
|
||||
|
@ -0,0 +1,14 @@
|
||||
<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>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:void(0);" ng-click="vm.next()" aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
196
static/resources/js/components/paginator/paginator.directive.js
Normal file
@ -0,0 +1,196 @@
|
||||
(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;
|
||||
|
||||
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);
|
||||
}else{
|
||||
tc.setMaximum(Math.ceil(ctrl.buttonCount / ctrl.displayCount));
|
||||
}
|
||||
|
||||
element.find('ul li:first a').on('click', previous);
|
||||
element.find('ul li:last a').on('click', next);
|
||||
|
||||
drawButtons(tc.getTime());
|
||||
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
|
||||
togglePageButton();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
var TimeCounter = function() {
|
||||
this.time = 0;
|
||||
this.minimum = 0;
|
||||
this.maximum = 0;
|
||||
};
|
||||
|
||||
TimeCounter.prototype.setMaximum = function(maximum) {
|
||||
this.maximum = 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;
|
||||
}
|
||||
scope.$apply();
|
||||
};
|
||||
|
||||
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 if((ctrl.page % ctrl.displayCount) != 0) {
|
||||
ctrl.page = this.time * ctrl.displayCount;
|
||||
}
|
||||
--this.time;
|
||||
--ctrl.page;
|
||||
}
|
||||
scope.$apply();
|
||||
};
|
||||
|
||||
TimeCounter.prototype.canDecrement = function() {
|
||||
if(this.time > this.minimum) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
TimeCounter.prototype.getTime = function() {
|
||||
return this.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(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');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleNext(status) {
|
||||
if(status) {
|
||||
element.find('ul li:last').removeClass('disabled');
|
||||
}else{
|
||||
element.find('ul li:last').addClass('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());
|
||||
}
|
||||
scope.$apply();
|
||||
}
|
||||
|
||||
function next() {
|
||||
if(tc.canIncrement()) {
|
||||
tc.increment();
|
||||
drawButtons(tc.getTime());
|
||||
togglePageButton();
|
||||
togglePrevious(tc.canDecrement());
|
||||
toggleNext(tc.canIncrement());
|
||||
}
|
||||
scope.$apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
@ -0,0 +1,8 @@
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('harbor.paginator', []);
|
||||
|
||||
})();
|
@ -40,10 +40,13 @@
|
||||
vm.projectId = getParameterByName('project_id', $location.absUrl());
|
||||
vm.retrieve();
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function() {
|
||||
vm.projectId = getParameterByName('project_id', $location.absUrl());
|
||||
vm.username = '';
|
||||
vm.retrieve();
|
||||
$scope.$on('retrieveData', function(e, val) {
|
||||
if(val) {
|
||||
console.log('received retrieve data:' + val);
|
||||
vm.projectId = getParameterByName('project_id', $location.absUrl());
|
||||
vm.username = '';
|
||||
vm.retrieve();
|
||||
}
|
||||
});
|
||||
|
||||
function search(e) {
|
||||
@ -91,8 +94,7 @@
|
||||
|
||||
function getProjectMemberFailed(response) {
|
||||
console.log('Failed to get project members:' + response);
|
||||
vm.projectMembers = [];
|
||||
vm.target = 'repositories';
|
||||
vm.projectMembers = [];
|
||||
$location.url('repositories').search('project_id', vm.projectId);
|
||||
}
|
||||
|
||||
@ -103,8 +105,7 @@
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/project-member/list-project-member.directive.html',
|
||||
'scope': {
|
||||
'sectionHeight': '=',
|
||||
'target': '='
|
||||
'sectionHeight': '='
|
||||
},
|
||||
'link': link,
|
||||
'controller': ListProjectMemberController,
|
||||
|
@ -28,7 +28,7 @@
|
||||
$scope.p = {};
|
||||
var vm0 = $scope.p;
|
||||
vm0.projectName = '';
|
||||
vm.isPublic = false;
|
||||
vm.isPublic = 0;
|
||||
|
||||
vm.addProject = addProject;
|
||||
vm.cancel = cancel;
|
||||
@ -37,9 +37,20 @@
|
||||
|
||||
vm.hasError = false;
|
||||
vm.errorMessage = '';
|
||||
|
||||
$scope.$watch('vm.isOpen', function(current) {
|
||||
if(current) {
|
||||
$scope.form.$setPristine();
|
||||
$scope.form.$setUntouched();
|
||||
vm0.projectName = '';
|
||||
vm.isPublic = 0;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function addProject(p) {
|
||||
if(p && angular.isDefined(p.projectName)) {
|
||||
vm.isPublic = vm.isPublic ? 1 : 0;
|
||||
AddProjectService(p.projectName, vm.isPublic)
|
||||
.success(addProjectSuccess)
|
||||
.error(addProjectFailed);
|
||||
@ -74,9 +85,9 @@
|
||||
}
|
||||
vm.isOpen = false;
|
||||
vm0.projectName = '';
|
||||
vm.isPublic = false;
|
||||
vm.isPublic = 0;
|
||||
|
||||
vm.hasError = false; vm.close = close;
|
||||
vm.hasError = false;
|
||||
vm.errorMessage = '';
|
||||
}
|
||||
|
||||
@ -94,16 +105,10 @@
|
||||
'scope' : {
|
||||
'isOpen': '='
|
||||
},
|
||||
'link': link,
|
||||
'controllerAs': 'vm',
|
||||
'bindToController': true
|
||||
};
|
||||
return directive;
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
scope.form.$setPristine();
|
||||
scope.form.$setUntouched();
|
||||
}
|
||||
return directive;
|
||||
}
|
||||
|
||||
})();
|
||||
|