mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-15 23:05:57 +01:00
Merge pull request #727 from ywk253100/m_t
Merge lost commits into branch dev
This commit is contained in:
commit
4e065bba95
43
.travis.yml
43
.travis.yml
@ -9,25 +9,25 @@ go_import_path: github.com/vmware/harbor
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
- mysql
|
|
||||||
|
|
||||||
dist: trusty
|
dist: trusty
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- mysql-server-5.6
|
|
||||||
- mysql-client-core-5.6
|
|
||||||
- mysql-client-5.6
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DB_HOST: 127.0.0.1
|
DB_HOST: 127.0.0.1
|
||||||
DB_PORT: 3306
|
DB_PORT: 3306
|
||||||
DB_USR: root
|
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
|
DOCKER_COMPOSE_VERSION: 1.7.1
|
||||||
HARBOR_ADMIN: admin
|
HARBOR_ADMIN: admin
|
||||||
HARBOR_ADMIN_PASSWD: Harbor12345
|
HARBOR_ADMIN_PASSWD: Harbor12345
|
||||||
UI_SECRET: tempString
|
UI_SECRET: tempString
|
||||||
|
MAX_JOB_WORKERS: 3
|
||||||
|
SECRET_KEY: 1234567890123456
|
||||||
|
AUTH_MODE: db_auth
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- sudo ./tests/hostcfg.sh
|
- sudo ./tests/hostcfg.sh
|
||||||
@ -54,7 +54,7 @@ install:
|
|||||||
- go get -d github.com/go-sql-driver/mysql
|
- go get -d github.com/go-sql-driver/mysql
|
||||||
- go get github.com/golang/lint/golint
|
- go get github.com/golang/lint/golint
|
||||||
- go get github.com/GeertJohan/fgt
|
- 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
|
# - 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
|
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||||
- chmod +x docker-compose
|
- chmod +x docker-compose
|
||||||
@ -63,23 +63,32 @@ install:
|
|||||||
- sudo service docker restart
|
- sudo service docker restart
|
||||||
- go get github.com/dghubble/sling
|
- go get github.com/dghubble/sling
|
||||||
- go get github.com/stretchr/testify
|
- go get github.com/stretchr/testify
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
# create tables and load data
|
# create tables and load data
|
||||||
- mysql < ./Deploy/db/registry.sql -uroot --verbose
|
# - mysql < ./Deploy/db/registry.sql -uroot --verbose
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 fgt golint
|
- sudo ./tests/testprepare.sh
|
||||||
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 go vet
|
- docker-compose -f Deploy/docker-compose.test.yml up -d
|
||||||
|
- go list ./... | grep -v -E 'vendor|tests|api' | xargs -L1 fgt golint
|
||||||
|
- go list ./... | grep -v -E 'vendor|tests|api' | xargs -L1 go vet
|
||||||
|
- IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
|
||||||
|
- export MYSQL_HOST=$IP
|
||||||
|
- export REGISTRY_URL=http://$IP:5000
|
||||||
|
- echo $REGISTRY_URL
|
||||||
- ./Deploy/coverage4gotest.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-compose -f Deploy/docker-compose.yml up -d
|
||||||
|
|
||||||
- docker ps
|
- docker ps
|
||||||
- go run tests/startuptest.go http://localhost/
|
- go run tests/startuptest.go http://localhost/
|
||||||
- go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD}
|
- go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD}
|
||||||
|
|
||||||
|
# - sudo ./tests/testprepare.sh
|
||||||
# test for API
|
# - go test -v ./tests/apitests
|
||||||
- sudo ./tests/testprepare.sh
|
|
||||||
- go test -v ./tests/apitests
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
echo "mode: set" >>profile.cov
|
echo "mode: set" >>profile.cov
|
||||||
for dir in $(go list ./... | grep -v -E 'vendor|tests')
|
for dir in $(go list ./... | grep -v -E 'vendor|tests')
|
||||||
do
|
do
|
||||||
|
@ -61,7 +61,9 @@ insert into user (username, email, password, realname, comment, deleted, sysadmi
|
|||||||
create table project (
|
create table project (
|
||||||
project_id int NOT NULL AUTO_INCREMENT,
|
project_id int NOT NULL AUTO_INCREMENT,
|
||||||
owner_id int NOT NULL,
|
owner_id int NOT NULL,
|
||||||
name varchar (30) NOT NULL,
|
# The max length of name controlled by API is 30,
|
||||||
|
# and 11 bytes is reserved for marking the deleted project.
|
||||||
|
name varchar (41) NOT NULL,
|
||||||
creation_time timestamp,
|
creation_time timestamp,
|
||||||
update_time timestamp,
|
update_time timestamp,
|
||||||
deleted tinyint (1) DEFAULT 0 NOT NULL,
|
deleted tinyint (1) DEFAULT 0 NOT NULL,
|
||||||
@ -110,6 +112,7 @@ create table replication_policy (
|
|||||||
target_id int NOT NULL,
|
target_id int NOT NULL,
|
||||||
enabled tinyint(1) NOT NULL DEFAULT 1,
|
enabled tinyint(1) NOT NULL DEFAULT 1,
|
||||||
description text,
|
description text,
|
||||||
|
deleted tinyint (1) DEFAULT 0 NOT NULL,
|
||||||
cron_str varchar(256),
|
cron_str varchar(256),
|
||||||
start_time timestamp NULL,
|
start_time timestamp NULL,
|
||||||
creation_time timestamp default CURRENT_TIMESTAMP,
|
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||||
@ -122,7 +125,7 @@ create table replication_target (
|
|||||||
name varchar(64),
|
name varchar(64),
|
||||||
url varchar(64),
|
url varchar(64),
|
||||||
username varchar(40),
|
username varchar(40),
|
||||||
password varchar(40),
|
password varchar(128),
|
||||||
/*
|
/*
|
||||||
target_type indicates the type of target registry,
|
target_type indicates the type of target registry,
|
||||||
0 means it's a harbor instance,
|
0 means it's a harbor instance,
|
||||||
|
@ -44,6 +44,10 @@ use_compressed_js = on
|
|||||||
#Maximum number of job workers in job service
|
#Maximum number of job workers in job service
|
||||||
max_job_workers = 3
|
max_job_workers = 3
|
||||||
|
|
||||||
|
#Secret key for encryption/decryption, its length has to be 16 chars
|
||||||
|
#**NOTE** if this changes, previously encrypted password will not be decrypted!
|
||||||
|
secret_key = secretkey1234567
|
||||||
|
|
||||||
#The expiration of token used by token service, default is 30 minutes
|
#The expiration of token used by token service, default is 30 minutes
|
||||||
token_expiration = 30
|
token_expiration = 30
|
||||||
|
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
echo "This shell will minify the Javascript in Harbor project."
|
echo "This shell will minify the Javascript in Harbor project."
|
||||||
echo "Usage: #jsminify [src] [dest]"
|
echo "Usage: #jsminify [src] [dest] [basedir]"
|
||||||
|
|
||||||
#prepare workspace
|
#prepare workspace
|
||||||
rm -rf $2 /tmp/harbor.app.temp.js
|
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
|
#concat the js files from js include file
|
||||||
echo "Concat js files..."
|
echo "Concat js files..."
|
||||||
|
|
||||||
@ -20,6 +26,12 @@ do
|
|||||||
fi
|
fi
|
||||||
done
|
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
|
#remove space
|
||||||
echo "Remove space.."
|
echo "Remove space.."
|
||||||
sed 's/ \+/ /g' -i /tmp/harbor.app.temp.js
|
sed 's/ \+/ /g' -i /tmp/harbor.app.temp.js
|
||||||
|
@ -16,6 +16,10 @@ if sys.version_info[:3][0] == 3:
|
|||||||
import configparser as ConfigParser
|
import configparser as ConfigParser
|
||||||
import io as StringIO
|
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!")
|
||||||
|
|
||||||
#Read configurations
|
#Read configurations
|
||||||
conf = StringIO.StringIO()
|
conf = StringIO.StringIO()
|
||||||
conf.write("[configuration]\n")
|
conf.write("[configuration]\n")
|
||||||
@ -24,6 +28,8 @@ conf.seek(0, os.SEEK_SET)
|
|||||||
rcp = ConfigParser.RawConfigParser()
|
rcp = ConfigParser.RawConfigParser()
|
||||||
rcp.readfp(conf)
|
rcp.readfp(conf)
|
||||||
|
|
||||||
|
validate(rcp)
|
||||||
|
|
||||||
hostname = rcp.get("configuration", "hostname")
|
hostname = rcp.get("configuration", "hostname")
|
||||||
ui_url = rcp.get("configuration", "ui_url_protocol") + "://" + hostname
|
ui_url = rcp.get("configuration", "ui_url_protocol") + "://" + hostname
|
||||||
email_server = rcp.get("configuration", "email_server")
|
email_server = rcp.get("configuration", "email_server")
|
||||||
@ -50,6 +56,7 @@ crt_email = rcp.get("configuration", "crt_email")
|
|||||||
max_job_workers = rcp.get("configuration", "max_job_workers")
|
max_job_workers = rcp.get("configuration", "max_job_workers")
|
||||||
token_expiration = rcp.get("configuration", "token_expiration")
|
token_expiration = rcp.get("configuration", "token_expiration")
|
||||||
verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
|
verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
|
||||||
|
secret_key = rcp.get("configuration", "secret_key")
|
||||||
########
|
########
|
||||||
|
|
||||||
ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
||||||
@ -102,6 +109,7 @@ render(os.path.join(templates_dir, "ui", "env"),
|
|||||||
self_registration=self_registration,
|
self_registration=self_registration,
|
||||||
use_compressed_js=use_compressed_js,
|
use_compressed_js=use_compressed_js,
|
||||||
ui_secret=ui_secret,
|
ui_secret=ui_secret,
|
||||||
|
secret_key=secret_key,
|
||||||
verify_remote_cert=verify_remote_cert,
|
verify_remote_cert=verify_remote_cert,
|
||||||
token_expiration=token_expiration)
|
token_expiration=token_expiration)
|
||||||
|
|
||||||
@ -128,6 +136,7 @@ render(os.path.join(templates_dir, "jobservice", "env"),
|
|||||||
db_password=db_password,
|
db_password=db_password,
|
||||||
ui_secret=ui_secret,
|
ui_secret=ui_secret,
|
||||||
max_job_workers=max_job_workers,
|
max_job_workers=max_job_workers,
|
||||||
|
secret_key=secret_key,
|
||||||
ui_url=ui_url,
|
ui_url=ui_url,
|
||||||
verify_remote_cert=verify_remote_cert)
|
verify_remote_cert=verify_remote_cert)
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ MYSQL_PORT=3306
|
|||||||
MYSQL_USR=root
|
MYSQL_USR=root
|
||||||
MYSQL_PWD=$db_password
|
MYSQL_PWD=$db_password
|
||||||
UI_SECRET=$ui_secret
|
UI_SECRET=$ui_secret
|
||||||
|
SECRET_KEY=$secret_key
|
||||||
CONFIG_PATH=/etc/jobservice/app.conf
|
CONFIG_PATH=/etc/jobservice/app.conf
|
||||||
REGISTRY_URL=http://registry:5000
|
REGISTRY_URL=http://registry:5000
|
||||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||||
|
@ -12,6 +12,7 @@ AUTH_MODE=$auth_mode
|
|||||||
LDAP_URL=$ldap_url
|
LDAP_URL=$ldap_url
|
||||||
LDAP_BASE_DN=$ldap_basedn
|
LDAP_BASE_DN=$ldap_basedn
|
||||||
UI_SECRET=$ui_secret
|
UI_SECRET=$ui_secret
|
||||||
|
SECRET_KEY=$secret_key
|
||||||
SELF_REGISTRATION=$self_registration
|
SELF_REGISTRATION=$self_registration
|
||||||
USE_COMPRESSED_JS=$use_compressed_js
|
USE_COMPRESSED_JS=$use_compressed_js
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
312
api/harborapi_test.go
Normal file
312
api/harborapi_test.go
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
//These APIs provide services for manipulating Harbor project.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http/httptest"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||||
|
"github.com/vmware/harbor/dao"
|
||||||
|
"github.com/vmware/harbor/models"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/dghubble/sling"
|
||||||
|
|
||||||
|
//for test env prepare
|
||||||
|
_ "github.com/vmware/harbor/auth/db"
|
||||||
|
_ "github.com/vmware/harbor/auth/ldap"
|
||||||
|
)
|
||||||
|
|
||||||
|
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")
|
||||||
|
beego.Router("/api/users/:id([0-9]+)/password", &UserAPI{}, "put:ChangePassword")
|
||||||
|
|
||||||
|
_ = updateInitPassword(1, "Harbor12345")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//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})
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
beego.BeeApp.Handlers.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(w.Body)
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Project) (int, error) {
|
||||||
|
|
||||||
|
_sling := sling.New().Post(a.basePath)
|
||||||
|
|
||||||
|
// create path and map variables
|
||||||
|
path := "/api/projects/"
|
||||||
|
|
||||||
|
_sling = _sling.Path(path)
|
||||||
|
|
||||||
|
// accept header
|
||||||
|
accepts := []string{"application/json", "text/plain"}
|
||||||
|
for key := range accepts {
|
||||||
|
_sling = _sling.Set("Accept", accepts[key])
|
||||||
|
break // only use the first Accept
|
||||||
|
}
|
||||||
|
|
||||||
|
// body params
|
||||||
|
_sling = _sling.BodyJSON(project)
|
||||||
|
//fmt.Printf("project post req: %+v\n", _sling)
|
||||||
|
req, err := _sling.Request()
|
||||||
|
req.SetBasicAuth(prjUsr.Name, prjUsr.Passwd)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
beego.BeeApp.Handlers.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
return w.Code, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Change password
|
||||||
|
//Implementation Notes
|
||||||
|
//Change the password on a user that already exists.
|
||||||
|
//@param userID user ID
|
||||||
|
//@param password user old and new password
|
||||||
|
//@return error
|
||||||
|
//func (a api) UsersUserIDPasswordPut (user usrInfo, userID int32, password apilib.Password) int {
|
||||||
|
func (a api) UsersUserIDPasswordPut(user usrInfo, userID int32, password apilib.Password) int {
|
||||||
|
|
||||||
|
_sling := sling.New().Put(a.basePath)
|
||||||
|
|
||||||
|
// create path and map variables
|
||||||
|
path := "/api/users/" + fmt.Sprintf("%d", userID) + "/password"
|
||||||
|
fmt.Printf("change passwd path: %s\n", path)
|
||||||
|
fmt.Printf("password %+v\n", password)
|
||||||
|
_sling = _sling.Path(path)
|
||||||
|
|
||||||
|
// accept header
|
||||||
|
accepts := []string{"application/json", "text/plain"}
|
||||||
|
for key := range accepts {
|
||||||
|
_sling = _sling.Set("Accept", accepts[key])
|
||||||
|
break // only use the first Accept
|
||||||
|
}
|
||||||
|
|
||||||
|
// body params
|
||||||
|
_sling = _sling.BodyJSON(password)
|
||||||
|
fmt.Printf("project post req: %+v\n", _sling)
|
||||||
|
req, err := _sling.Request()
|
||||||
|
req.SetBasicAuth(user.Name, user.Passwd)
|
||||||
|
fmt.Printf("project post req: %+v\n", req)
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
beego.BeeApp.Handlers.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
return w.Code
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
////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 HarborAPI) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) {
|
||||||
|
//func (a HarborAPI) 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
|
||||||
|
//}
|
||||||
|
|
||||||
|
//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 HarborApi) UsersGet (userName string) ([]User, error) {
|
||||||
|
//}
|
||||||
|
|
||||||
|
//Creates a new user account.
|
||||||
|
//func (a HarborApi) UsersPost (user User) (error) {
|
||||||
|
//}
|
||||||
|
|
||||||
|
//Mark a registered user as be removed.
|
||||||
|
//func (a HarborApi) UsersUserIdDelete (userId int32) (error) {
|
||||||
|
//}
|
||||||
|
|
||||||
|
//Update a registered user to change to be an administrator of Harbor.
|
||||||
|
//func (a HarborApi) UsersUserIdPut (userId int32) (error) {
|
||||||
|
//}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -46,6 +46,27 @@ type ReplicationReq struct {
|
|||||||
TagList []string `json:"tags"`
|
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.
|
// Post creates replication jobs according to the policy.
|
||||||
func (rj *ReplicationJob) Post() {
|
func (rj *ReplicationJob) Post() {
|
||||||
var data ReplicationReq
|
var data ReplicationReq
|
||||||
|
@ -31,8 +31,9 @@ import (
|
|||||||
// ProjectAPI handles request to /api/projects/{} /api/projects/{}/logs
|
// ProjectAPI handles request to /api/projects/{} /api/projects/{}/logs
|
||||||
type ProjectAPI struct {
|
type ProjectAPI struct {
|
||||||
BaseAPI
|
BaseAPI
|
||||||
userID int
|
userID int
|
||||||
projectID int64
|
projectID int64
|
||||||
|
projectName string
|
||||||
}
|
}
|
||||||
|
|
||||||
type projectReq struct {
|
type projectReq struct {
|
||||||
@ -54,14 +55,16 @@ func (p *ProjectAPI) Prepare() {
|
|||||||
log.Errorf("Error parsing project id: %s, error: %v", idStr, err)
|
log.Errorf("Error parsing project id: %s, error: %v", idStr, err)
|
||||||
p.CustomAbort(http.StatusBadRequest, "invalid project id")
|
p.CustomAbort(http.StatusBadRequest, "invalid project id")
|
||||||
}
|
}
|
||||||
exist, err := dao.ProjectExists(p.projectID)
|
|
||||||
|
project, err := dao.GetProjectByID(p.projectID)
|
||||||
if err != nil {
|
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.")
|
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.CustomAbort(http.StatusNotFound, fmt.Sprintf("project does not exist, id: %v", p.projectID))
|
||||||
}
|
}
|
||||||
|
p.projectName = project.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,6 +155,71 @@ func (p *ProjectAPI) Get() {
|
|||||||
p.ServeJSON()
|
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 ...
|
// List ...
|
||||||
func (p *ProjectAPI) List() {
|
func (p *ProjectAPI) List() {
|
||||||
var total int64
|
var total int64
|
||||||
|
65
api/project_test.go
Normal file
65
api/project_test.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddProject(t *testing.T) {
|
||||||
|
|
||||||
|
fmt.Println("Testing Add Project(ProjectsPost) API")
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
apiTest := newHarborAPI()
|
||||||
|
|
||||||
|
//prepare for test
|
||||||
|
|
||||||
|
admin := &usrInfo{"admin", "Harbor12345"}
|
||||||
|
|
||||||
|
prjUsr := &usrInfo{"unknown", "unknown"}
|
||||||
|
|
||||||
|
var project apilib.Project
|
||||||
|
project.ProjectName = "test_project"
|
||||||
|
project.Public = true
|
||||||
|
|
||||||
|
//case 1: admin not login, expect project creation fail.
|
||||||
|
|
||||||
|
result, err := apiTest.ProjectsPost(*prjUsr, project)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error while creat project", err.Error())
|
||||||
|
t.Log(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(result, int(401), "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.")
|
||||||
|
|
||||||
|
prjUsr = admin
|
||||||
|
|
||||||
|
result, err = apiTest.ProjectsPost(*prjUsr, project)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error while creat project", err.Error())
|
||||||
|
t.Log(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(result, int(201), "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(*prjUsr, project)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error while creat project", err.Error())
|
||||||
|
t.Log(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(result, int(409), "Case 3: Project creation status should be 409")
|
||||||
|
//t.Log(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -147,7 +147,14 @@ func (ra *RepJobAPI) GetLog() {
|
|||||||
ra.CustomAbort(http.StatusBadRequest, "id is nil")
|
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 {
|
if err != nil {
|
||||||
log.Errorf("failed to get log for job %d: %v", ra.jobID, err)
|
log.Errorf("failed to get log for job %d: %v", ra.jobID, err)
|
||||||
ra.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
ra.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
@ -349,3 +349,41 @@ func (pa *RepPolicyAPI) UpdateEnablement() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete : policies which are disabled and have no running jobs
|
||||||
|
// can be deleted
|
||||||
|
func (r *RepPolicyAPI) Delete() {
|
||||||
|
id := r.GetIDFromURL()
|
||||||
|
policy, err := dao.GetRepPolicy(id)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get policy %d: %v", id, err)
|
||||||
|
r.CustomAbort(http.StatusInternalServerError, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if policy == nil || policy.Deleted == 1 {
|
||||||
|
r.CustomAbort(http.StatusNotFound, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if policy.Enabled == 1 {
|
||||||
|
r.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)
|
||||||
|
r.CustomAbort(http.StatusInternalServerError, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, job := range jobs {
|
||||||
|
if job.Status == models.JobRunning ||
|
||||||
|
job.Status == models.JobRetrying ||
|
||||||
|
job.Status == models.JobPending {
|
||||||
|
r.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)
|
||||||
|
r.CustomAbort(http.StatusInternalServerError, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -35,7 +35,6 @@ import (
|
|||||||
|
|
||||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||||
|
|
||||||
"github.com/vmware/harbor/utils"
|
|
||||||
"github.com/vmware/harbor/utils/registry/auth"
|
"github.com/vmware/harbor/utils/registry/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -78,28 +77,12 @@ func (ra *RepositoryAPI) Get() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repoList, err := cache.GetRepoFromCache()
|
repositories, err := getReposByProject(project.Name, ra.GetString("q"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get repository from cache: %v", err)
|
log.Errorf("failed to get repository: %v", err)
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories := []string{}
|
|
||||||
|
|
||||||
q := ra.GetString("q")
|
|
||||||
for _, repo := range repoList {
|
|
||||||
pn, rest := utils.ParseRepository(repo)
|
|
||||||
if project.Name != pn {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(q) != 0 && !strings.Contains(rest, q) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories = append(repositories, repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
total := int64(len(repositories))
|
total := int64(len(repositories))
|
||||||
|
|
||||||
if (page-1)*pageSize > total {
|
if (page-1)*pageSize > total {
|
||||||
|
@ -101,18 +101,19 @@ func filterRepositories(repositories []string, projects []models.Project, keywor
|
|||||||
i, j := 0, 0
|
i, j := 0, 0
|
||||||
result := []map[string]interface{}{}
|
result := []map[string]interface{}{}
|
||||||
for i < len(repositories) && j < len(projects) {
|
for i < len(repositories) && j < len(projects) {
|
||||||
r := &utils.Repository{Name: repositories[i]}
|
r := repositories[i]
|
||||||
d := strings.Compare(r.GetProject(), projects[j].Name)
|
p, _ := utils.ParseRepository(r)
|
||||||
|
d := strings.Compare(p, projects[j].Name)
|
||||||
if d < 0 {
|
if d < 0 {
|
||||||
i++
|
i++
|
||||||
continue
|
continue
|
||||||
} else if d == 0 {
|
} else if d == 0 {
|
||||||
i++
|
i++
|
||||||
if len(keyword) != 0 && !strings.Contains(r.Name, keyword) {
|
if len(keyword) != 0 && !strings.Contains(r, keyword) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
entry := make(map[string]interface{})
|
entry := make(map[string]interface{})
|
||||||
entry["repository_name"] = r.Name
|
entry["repository_name"] = r
|
||||||
entry["project_name"] = projects[j].Name
|
entry["project_name"] = projects[j].Name
|
||||||
entry["project_id"] = projects[j].ProjectID
|
entry["project_id"] = projects[j].ProjectID
|
||||||
entry["project_public"] = projects[j].Public
|
entry["project_public"] = projects[j].Public
|
||||||
|
32
api/search_test.go
Normal file
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)
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
@ -20,6 +20,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/vmware/harbor/dao"
|
"github.com/vmware/harbor/dao"
|
||||||
@ -34,10 +35,14 @@ import (
|
|||||||
// TargetAPI handles request to /api/targets/ping /api/targets/{}
|
// TargetAPI handles request to /api/targets/ping /api/targets/{}
|
||||||
type TargetAPI struct {
|
type TargetAPI struct {
|
||||||
BaseAPI
|
BaseAPI
|
||||||
|
secretKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare validates the user
|
// Prepare validates the user
|
||||||
func (t *TargetAPI) Prepare() {
|
func (t *TargetAPI) Prepare() {
|
||||||
|
//TODO:move to config
|
||||||
|
t.secretKey = os.Getenv("SECRET_KEY")
|
||||||
|
|
||||||
userID := t.ValidateUser()
|
userID := t.ValidateUser()
|
||||||
isSysAdmin, err := dao.IsAdminRole(userID)
|
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,7 +81,7 @@ func (t *TargetAPI) Ping() {
|
|||||||
password = target.Password
|
password = target.Password
|
||||||
|
|
||||||
if len(password) != 0 {
|
if len(password) != 0 {
|
||||||
password, err = utils.ReversibleDecrypt(password)
|
password, err = utils.ReversibleDecrypt(password, t.secretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to decrypt password: %v", err)
|
log.Errorf("failed to decrypt password: %v", err)
|
||||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
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.
|
// modify other fields of target he does not need to input the password again.
|
||||||
// The security issue can be fixed by enable https.
|
// The security issue can be fixed by enable https.
|
||||||
if len(target.Password) != 0 {
|
if len(target.Password) != 0 {
|
||||||
pwd, err := utils.ReversibleDecrypt(target.Password)
|
pwd, err := utils.ReversibleDecrypt(target.Password, t.secretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to decrypt password: %v", err)
|
log.Errorf("failed to decrypt password: %v", err)
|
||||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
@ -162,7 +167,7 @@ func (t *TargetAPI) List() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
str, err := utils.ReversibleDecrypt(target.Password)
|
str, err := utils.ReversibleDecrypt(target.Password, t.secretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to decrypt password: %v", err)
|
log.Errorf("failed to decrypt password: %v", err)
|
||||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
@ -201,7 +206,11 @@ func (t *TargetAPI) Post() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(target.Password) != 0 {
|
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)
|
id, err := dao.AddRepTarget(*target)
|
||||||
@ -275,7 +284,11 @@ func (t *TargetAPI) Put() {
|
|||||||
target.ID = id
|
target.ID = id
|
||||||
|
|
||||||
if len(target.Password) != 0 {
|
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 {
|
if err := dao.UpdateRepTarget(*target); err != nil {
|
||||||
|
63
api/utils.go
63
api/utils.go
@ -26,6 +26,8 @@ import (
|
|||||||
|
|
||||||
"github.com/vmware/harbor/dao"
|
"github.com/vmware/harbor/dao"
|
||||||
"github.com/vmware/harbor/models"
|
"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/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -115,7 +117,14 @@ func TriggerReplication(policyID int64, repository string,
|
|||||||
|
|
||||||
url := buildReplicationURL()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -188,7 +197,16 @@ func postReplicationAction(policyID int64, acton string) error {
|
|||||||
|
|
||||||
url := buildReplicationActionURL()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -207,6 +225,16 @@ func postReplicationAction(policyID int64, acton string) error {
|
|||||||
return fmt.Errorf("%d %s", resp.StatusCode, string(b))
|
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"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func buildReplicationURL() string {
|
func buildReplicationURL() string {
|
||||||
url := getJobServiceURL()
|
url := getJobServiceURL()
|
||||||
return fmt.Sprintf("%s/api/jobs/replication", url)
|
return fmt.Sprintf("%s/api/jobs/replication", url)
|
||||||
@ -233,3 +261,34 @@ func getJobServiceURL() string {
|
|||||||
|
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getReposByProject(name string, keyword ...string) ([]string, error) {
|
||||||
|
repositories := []string{}
|
||||||
|
|
||||||
|
list, err := getAllRepos()
|
||||||
|
if err != nil {
|
||||||
|
return repositories, err
|
||||||
|
}
|
||||||
|
|
||||||
|
project := ""
|
||||||
|
rest := ""
|
||||||
|
for _, repository := range list {
|
||||||
|
project, rest = utils.ParseRepository(repository)
|
||||||
|
if project != name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keyword) > 0 && len(keyword[0]) != 0 &&
|
||||||
|
!strings.Contains(rest, keyword[0]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories = append(repositories, repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
return repositories, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAllRepos() ([]string, error) {
|
||||||
|
return cache.GetRepoFromCache()
|
||||||
|
}
|
||||||
|
106
dao/dao_test.go
106
dao/dao_test.go
@ -16,6 +16,7 @@
|
|||||||
package dao
|
package dao
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -112,6 +113,7 @@ func clearUp(username string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const username string = "Tester01"
|
const username string = "Tester01"
|
||||||
|
const password string = "Abc12345"
|
||||||
const projectName string = "test_project"
|
const projectName string = "test_project"
|
||||||
const repoTag string = "test1.1"
|
const repoTag string = "test1.1"
|
||||||
const repoTag2 string = "test1.2"
|
const repoTag2 string = "test1.2"
|
||||||
@ -157,7 +159,7 @@ func TestRegister(t *testing.T) {
|
|||||||
user := models.User{
|
user := models.User{
|
||||||
Username: username,
|
Username: username,
|
||||||
Email: "tester01@vmware.com",
|
Email: "tester01@vmware.com",
|
||||||
Password: "Abc12345",
|
Password: password,
|
||||||
Realname: "tester01",
|
Realname: "tester01",
|
||||||
Comment: "register",
|
Comment: "register",
|
||||||
}
|
}
|
||||||
@ -184,6 +186,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) {
|
func TestUserExists(t *testing.T) {
|
||||||
var exists bool
|
var exists bool
|
||||||
var err error
|
var err error
|
||||||
@ -694,6 +731,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) {
|
func TestDeleteProjectMember(t *testing.T) {
|
||||||
err := DeleteProjectMember(currentProject.ProjectID, 1)
|
err := DeleteProjectMember(currentProject.ProjectID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -710,6 +762,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) {
|
func TestToggleAdminRole(t *testing.T) {
|
||||||
err := ToggleUserAdminRole(currentUser.UserID, 1)
|
err := ToggleUserAdminRole(currentUser.UserID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1330,7 +1399,7 @@ func TestDeleteRepPolicy(t *testing.T) {
|
|||||||
if err != nil && err != orm.ErrNoRows {
|
if err != nil && err != orm.ErrNoRows {
|
||||||
t.Errorf("Error occured in GetRepPolicy:%v", err)
|
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)
|
t.Errorf("Able to find rep policy after deletion, id: %d", policyID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1412,3 +1481,36 @@ func TestGetOrmer(t *testing.T) {
|
|||||||
t.Errorf("Error get ormer.")
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -277,3 +277,12 @@ func getProjects(userID int, name string, args ...int64) ([]models.Project, erro
|
|||||||
|
|
||||||
return projects, err
|
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 project p on rp.project_id=p.project_id
|
||||||
left join replication_target rt on rp.target_id=rt.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"
|
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 {
|
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, "%"+name+"%")
|
||||||
args = append(args, projectID)
|
args = append(args, projectID)
|
||||||
} else if len(name) != 0 {
|
} else if len(name) != 0 {
|
||||||
sql += `where rp.name like ? `
|
sql += `and rp.name like ? `
|
||||||
args = append(args, "%"+name+"%")
|
args = append(args, "%"+name+"%")
|
||||||
} else if projectID != 0 {
|
} else if projectID != 0 {
|
||||||
sql += `where rp.project_id = ? `
|
sql += `and rp.project_id = ? `
|
||||||
args = append(args, projectID)
|
args = append(args, projectID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +182,7 @@ func FilterRepPolicies(name string, projectID int64) ([]*models.RepPolicy, error
|
|||||||
// GetRepPolicyByName ...
|
// GetRepPolicyByName ...
|
||||||
func GetRepPolicyByName(name string) (*models.RepPolicy, error) {
|
func GetRepPolicyByName(name string) (*models.RepPolicy, error) {
|
||||||
o := GetOrmer()
|
o := GetOrmer()
|
||||||
sql := `select * from replication_policy where name = ?`
|
sql := `select * from replication_policy where deleted = 0 and name = ?`
|
||||||
|
|
||||||
var policy models.RepPolicy
|
var policy models.RepPolicy
|
||||||
|
|
||||||
@ -198,7 +199,7 @@ func GetRepPolicyByName(name string) (*models.RepPolicy, error) {
|
|||||||
// GetRepPolicyByProject ...
|
// GetRepPolicyByProject ...
|
||||||
func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) {
|
func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) {
|
||||||
o := GetOrmer()
|
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
|
var policies []*models.RepPolicy
|
||||||
|
|
||||||
@ -212,7 +213,7 @@ func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) {
|
|||||||
// GetRepPolicyByTarget ...
|
// GetRepPolicyByTarget ...
|
||||||
func GetRepPolicyByTarget(targetID int64) ([]*models.RepPolicy, error) {
|
func GetRepPolicyByTarget(targetID int64) ([]*models.RepPolicy, error) {
|
||||||
o := GetOrmer()
|
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
|
var policies []*models.RepPolicy
|
||||||
|
|
||||||
@ -226,7 +227,7 @@ func GetRepPolicyByTarget(targetID int64) ([]*models.RepPolicy, error) {
|
|||||||
// GetRepPolicyByProjectAndTarget ...
|
// GetRepPolicyByProjectAndTarget ...
|
||||||
func GetRepPolicyByProjectAndTarget(projectID, targetID int64) ([]*models.RepPolicy, error) {
|
func GetRepPolicyByProjectAndTarget(projectID, targetID int64) ([]*models.RepPolicy, error) {
|
||||||
o := GetOrmer()
|
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
|
var policies []*models.RepPolicy
|
||||||
|
|
||||||
@ -247,7 +248,11 @@ func UpdateRepPolicy(policy *models.RepPolicy) error {
|
|||||||
// DeleteRepPolicy ...
|
// DeleteRepPolicy ...
|
||||||
func DeleteRepPolicy(id int64) error {
|
func DeleteRepPolicy(id int64) error {
|
||||||
o := GetOrmer()
|
o := GetOrmer()
|
||||||
_, err := o.Delete(&models.RepPolicy{ID: id})
|
policy := &models.RepPolicy{
|
||||||
|
ID: id,
|
||||||
|
Deleted: 1,
|
||||||
|
}
|
||||||
|
_, err := o.Update(policy, "Deleted")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
dao/user.go
21
dao/user.go
@ -111,7 +111,7 @@ func ListUsers(query models.User) ([]models.User, error) {
|
|||||||
// ToggleUserAdminRole gives a user admin role.
|
// ToggleUserAdminRole gives a user admin role.
|
||||||
func ToggleUserAdminRole(userID, hasAdmin int) error {
|
func ToggleUserAdminRole(userID, hasAdmin int) error {
|
||||||
o := GetOrmer()
|
o := GetOrmer()
|
||||||
queryParams := make([]interface{}, 1)
|
queryParams := make([]interface{}, 1)
|
||||||
sql := `update user set sysadmin_flag = ? where user_id = ?`
|
sql := `update user set sysadmin_flag = ? where user_id = ?`
|
||||||
queryParams = append(queryParams, hasAdmin)
|
queryParams = append(queryParams, hasAdmin)
|
||||||
queryParams = append(queryParams, userID)
|
queryParams = append(queryParams, userID)
|
||||||
@ -185,37 +185,24 @@ func UpdateUserResetUUID(u models.User) error {
|
|||||||
func CheckUserPassword(query models.User) (*models.User, error) {
|
func CheckUserPassword(query models.User) (*models.User, error) {
|
||||||
|
|
||||||
currentUser, err := GetUser(query)
|
currentUser, err := GetUser(query)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentUser == nil {
|
if currentUser == nil {
|
||||||
return nil, 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)
|
queryParam := make([]interface{}, 1)
|
||||||
|
queryParam = append(queryParam, currentUser.Username)
|
||||||
if query.UserID != 0 {
|
queryParam = append(queryParam, utils.Encrypt(query.Password, currentUser.Salt))
|
||||||
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))
|
|
||||||
}
|
|
||||||
o := GetOrmer()
|
o := GetOrmer()
|
||||||
var user []models.User
|
var user []models.User
|
||||||
|
|
||||||
n, err := o.Raw(sql, queryParam).QueryRows(&user)
|
n, err := o.Raw(sql, queryParam).QueryRows(&user)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
log.Warning("User principal does not match password. Current:", currentUser)
|
log.Warning("User principal does not match password. Current:", currentUser)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -31,6 +31,7 @@ var localUIURL string
|
|||||||
var localRegURL string
|
var localRegURL string
|
||||||
var logDir string
|
var logDir string
|
||||||
var uiSecret string
|
var uiSecret string
|
||||||
|
var secretKey string
|
||||||
var verifyRemoteCert string
|
var verifyRemoteCert string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -86,6 +87,11 @@ func init() {
|
|||||||
beego.LoadAppConfig("ini", configPath)
|
beego.LoadAppConfig("ini", configPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secretKey = os.Getenv("SECRET_KEY")
|
||||||
|
if len(secretKey) != 16 {
|
||||||
|
panic("The length of secretkey has to be 16 characters!")
|
||||||
|
}
|
||||||
|
|
||||||
log.Debugf("config: maxJobWorkers: %d", maxJobWorkers)
|
log.Debugf("config: maxJobWorkers: %d", maxJobWorkers)
|
||||||
log.Debugf("config: localUIURL: %s", localUIURL)
|
log.Debugf("config: localUIURL: %s", localUIURL)
|
||||||
log.Debugf("config: localRegURL: %s", localRegURL)
|
log.Debugf("config: localRegURL: %s", localRegURL)
|
||||||
@ -119,6 +125,11 @@ func UISecret() string {
|
|||||||
return uiSecret
|
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
|
// VerifyRemoteCert return the flag to tell jobservice whether or not verify the cert of remote registry
|
||||||
func VerifyRemoteCert() bool {
|
func VerifyRemoteCert() bool {
|
||||||
return verifyRemoteCert != "off"
|
return verifyRemoteCert != "off"
|
||||||
|
@ -231,7 +231,7 @@ func (sm *SM) Reset(jid int64) error {
|
|||||||
pwd := target.Password
|
pwd := target.Password
|
||||||
|
|
||||||
if len(pwd) != 0 {
|
if len(pwd) != 0 {
|
||||||
pwd, err = uti.ReversibleDecrypt(pwd)
|
pwd, err = uti.ReversibleDecrypt(pwd, config.SecretKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to decrypt password: %v", err)
|
return fmt.Errorf("failed to decrypt password: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ Changelog for harbor database schema
|
|||||||
- delete data `AMDRWS` from table `role`
|
- delete data `AMDRWS` from table `role`
|
||||||
- delete data `A` from table `access`
|
- delete data `A` from table `access`
|
||||||
|
|
||||||
## 0.2.0
|
## 0.3.0
|
||||||
|
|
||||||
- create table `replication_policy`
|
- create table `replication_policy`
|
||||||
- create table `replication_target`
|
- create table `replication_target`
|
||||||
@ -25,3 +25,9 @@ Changelog for harbor database schema
|
|||||||
- add column `repo_tag` to table `access_log`
|
- add column `repo_tag` to table `access_log`
|
||||||
- alter column `repo_name` on table `access_log`
|
- alter column `repo_name` on table `access_log`
|
||||||
- alter column `email` on table `user`
|
- alter column `email` on table `user`
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
- 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`
|
||||||
|
@ -63,6 +63,7 @@ type RepPolicy struct {
|
|||||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
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"`
|
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||||
ErrorJobCount int `json:"error_job_count"`
|
ErrorJobCount int `json:"error_job_count"`
|
||||||
|
Deleted int `orm:"column(deleted)" json:"deleted"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid ...
|
// Valid ...
|
||||||
|
@ -73,13 +73,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sub-pane {
|
.sub-pane {
|
||||||
margin: 15px;
|
|
||||||
min-height: 380px;
|
min-height: 380px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.well-custom {
|
.well-custom {
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
|
@ -48,7 +48,8 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,8 +51,18 @@
|
|||||||
'projectId': vm.projectId,
|
'projectId': vm.projectId,
|
||||||
'username' : vm.username
|
'username' : vm.username
|
||||||
};
|
};
|
||||||
|
|
||||||
retrieve(vm.queryParams);
|
vm.page = 1;
|
||||||
|
vm.pageSize = 20;
|
||||||
|
|
||||||
|
$scope.$watch('vm.page', function(current, origin) {
|
||||||
|
if(current !== 1) {
|
||||||
|
vm.page = current;
|
||||||
|
retrieve(vm.queryParams, vm.page, vm.pageSize);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
retrieve(vm.queryParams, vm.page, vm.pageSize);
|
||||||
|
|
||||||
$scope.$on('$locationChangeSuccess', function() {
|
$scope.$on('$locationChangeSuccess', function() {
|
||||||
|
|
||||||
@ -69,11 +79,13 @@
|
|||||||
'username' : vm.username
|
'username' : vm.username
|
||||||
};
|
};
|
||||||
vm.username = '';
|
vm.username = '';
|
||||||
retrieve(vm.queryParams);
|
retrieve(vm.queryParams, vm.page, vm.pageSize);
|
||||||
});
|
});
|
||||||
|
|
||||||
function search(e) {
|
function search(e) {
|
||||||
|
|
||||||
|
vm.page = 1;
|
||||||
|
|
||||||
if(e.op[0] === 'all') {
|
if(e.op[0] === 'all') {
|
||||||
e.op = ['create', 'pull', 'push', 'delete'];
|
e.op = ['create', 'pull', 'push', 'delete'];
|
||||||
}
|
}
|
||||||
@ -83,11 +95,12 @@
|
|||||||
|
|
||||||
vm.queryParams.keywords = e.op.join('/');
|
vm.queryParams.keywords = e.op.join('/');
|
||||||
vm.queryParams.username = e.username;
|
vm.queryParams.username = e.username;
|
||||||
|
|
||||||
vm.queryParams.beginTimestamp = toUTCSeconds(vm.fromDate, 0, 0, 0);
|
vm.queryParams.beginTimestamp = toUTCSeconds(vm.fromDate, 0, 0, 0);
|
||||||
vm.queryParams.endTimestamp = toUTCSeconds(vm.toDate, 23, 59, 59);
|
vm.queryParams.endTimestamp = toUTCSeconds(vm.toDate, 23, 59, 59);
|
||||||
|
|
||||||
retrieve(vm.queryParams);
|
retrieve(vm.queryParams, vm.page, vm.pageSize);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showAdvancedSearch() {
|
function showAdvancedSearch() {
|
||||||
@ -98,27 +111,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function retrieve(queryParams) {
|
function retrieve(queryParams, page, pageSize) {
|
||||||
ListLogService(queryParams)
|
ListLogService(queryParams, page, pageSize)
|
||||||
.then(listLogComplete)
|
.then(listLogComplete)
|
||||||
.catch(listLogFailed);
|
.catch(listLogFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
function listLogComplete(response) {
|
function listLogComplete(response) {
|
||||||
vm.logs = response.data;
|
vm.logs = response.data;
|
||||||
|
vm.totalCount = response.headers('X-Total-Count');
|
||||||
|
|
||||||
vm.queryParams = {
|
console.log('Total Count in logs:' + vm.totalCount + ', page:' + vm.page);
|
||||||
'beginTimestamp' : 0,
|
|
||||||
'endTimestamp' : 0,
|
// vm.queryParams = {
|
||||||
'keywords' : '',
|
// 'beginTimestamp' : 0,
|
||||||
'projectId': vm.projectId,
|
// 'endTimestamp' : 0,
|
||||||
'username' : ''
|
// 'keywords' : '',
|
||||||
};
|
// 'projectId': vm.projectId,
|
||||||
vm.op = ['all'];
|
// 'username' : ''
|
||||||
vm.fromDate = '';
|
// };
|
||||||
vm.toDate = '';
|
// vm.op = ['all'];
|
||||||
vm.others = '';
|
// vm.fromDate = '';
|
||||||
vm.opOthers = true;
|
// vm.toDate = '';
|
||||||
|
// vm.others = '';
|
||||||
|
// vm.opOthers = true;
|
||||||
vm.isOpen = false;
|
vm.isOpen = false;
|
||||||
}
|
}
|
||||||
function listLogFailed(response){
|
function listLogFailed(response){
|
||||||
|
@ -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
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', []);
|
||||||
|
|
||||||
|
})();
|
@ -73,7 +73,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-4 col-md-12 well well-sm well-custom well-split"><div class="col-md-offset-10">//vm.replicationPolicies ? vm.replicationPolicies.length : 0// // 'items' | tr //</div></div>
|
<div class="col-xs-4 col-md-12 well well-sm well-custom well-split">
|
||||||
|
<div class="col-md-offset-10">//vm.replicationPolicies ? vm.replicationPolicies.length : 0// // 'items' | tr //</div>
|
||||||
|
</div>
|
||||||
<p class="split-handle"><span class="glyphicon glyphicon-align-justify"></span></p>
|
<p class="split-handle"><span class="glyphicon glyphicon-align-justify"></span></p>
|
||||||
<h4 class="h4-custom-down">// 'replication_jobs' | tr //</h4>
|
<h4 class="h4-custom-down">// 'replication_jobs' | tr //</h4>
|
||||||
<hr class="hr-line"/>
|
<hr class="hr-line"/>
|
||||||
@ -147,7 +149,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-4 col-md-12 well well-sm well-custom well-split"><div class="col-md-offset-10">//vm.replicationJobs ? vm.replicationJobs.length : 0// // 'items' | tr //</div></div>
|
<paginator ng-if="vm.totalCount > 0" total-count="//vm.totalCount//" page-size="//vm.pageSize//" display-count="5" page="vm.page"></paginator>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,6 +59,17 @@
|
|||||||
vm.retrievePolicy = retrievePolicy;
|
vm.retrievePolicy = retrievePolicy;
|
||||||
vm.retrieveJob = retrieveJob;
|
vm.retrieveJob = retrieveJob;
|
||||||
|
|
||||||
|
vm.pageSize = 20;
|
||||||
|
vm.page = 1;
|
||||||
|
|
||||||
|
$scope.$watch('vm.page', function(current) {
|
||||||
|
if(current !== 1) {
|
||||||
|
vm.page = current;
|
||||||
|
console.log('replication job: vm.page:' + current);
|
||||||
|
vm.retrieveJob(vm.lastPolicyId, vm.page, vm.pageSize);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
vm.confirmToTogglePolicy = confirmToTogglePolicy;
|
vm.confirmToTogglePolicy = confirmToTogglePolicy;
|
||||||
vm.togglePolicy = togglePolicy;
|
vm.togglePolicy = togglePolicy;
|
||||||
|
|
||||||
@ -84,14 +95,14 @@
|
|||||||
function searchReplicationJob() {
|
function searchReplicationJob() {
|
||||||
if(vm.lastPolicyId !== -1) {
|
if(vm.lastPolicyId !== -1) {
|
||||||
vm.searchJobTIP = true;
|
vm.searchJobTIP = true;
|
||||||
vm.retrieveJob(vm.lastPolicyId);
|
vm.retrieveJob(vm.lastPolicyId, vm.page, vm.pageSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshReplicationJob() {
|
function refreshReplicationJob() {
|
||||||
if(vm.lastPolicyId !== -1) {
|
if(vm.lastPolicyId !== -1) {
|
||||||
vm.refreshJobTIP = true;
|
vm.refreshJobTIP = true;
|
||||||
vm.retrieveJob(vm.lastPolicyId);
|
vm.retrieveJob(vm.lastPolicyId, vm.page, vm.pageSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,11 +112,10 @@
|
|||||||
.error(listReplicationPolicyFailed);
|
.error(listReplicationPolicyFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
function retrieveJob(policyId) {
|
function retrieveJob(policyId, page, pageSize) {
|
||||||
var status = (vm.currentStatus.key === 'all' ? '' : vm.currentStatus.key);
|
var status = (vm.currentStatus.key === 'all' ? '' : vm.currentStatus.key);
|
||||||
ListReplicationJobService(policyId, vm.replicationJobName, status, toUTCSeconds(vm.fromDate, 0, 0, 0), toUTCSeconds(vm.toDate, 23, 59, 59))
|
ListReplicationJobService(policyId, vm.replicationJobName, status, toUTCSeconds(vm.fromDate, 0, 0, 0), toUTCSeconds(vm.toDate, 23, 59, 59), page, pageSize)
|
||||||
.success(listReplicationJobSuccess)
|
.then(listReplicationJobSuccess, listReplicationJobFailed);
|
||||||
.error(listReplicationJobFailed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function listReplicationPolicySuccess(data, status) {
|
function listReplicationPolicySuccess(data, status) {
|
||||||
@ -117,8 +127,9 @@
|
|||||||
console.log('Failed to list replication policy:' + data);
|
console.log('Failed to list replication policy:' + data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function listReplicationJobSuccess(data, status) {
|
function listReplicationJobSuccess(response) {
|
||||||
vm.replicationJobs = data || [];
|
vm.replicationJobs = response.data || [];
|
||||||
|
vm.totalCount = response.headers('X-Total-Count');
|
||||||
var alertInfo = {
|
var alertInfo = {
|
||||||
'show': false,
|
'show': false,
|
||||||
'message': ''
|
'message': ''
|
||||||
@ -146,8 +157,8 @@
|
|||||||
vm.refreshJobTIP = false;
|
vm.refreshJobTIP = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function listReplicationJobFailed(data, status) {
|
function listReplicationJobFailed(response) {
|
||||||
console.log('Failed to list replication job:' + data);
|
console.log('Failed to list replication job:' + response);
|
||||||
vm.searchJobTIP = false;
|
vm.searchJobTIP = false;
|
||||||
vm.refreshJobTIP = false;
|
vm.refreshJobTIP = false;
|
||||||
}
|
}
|
||||||
@ -259,8 +270,8 @@
|
|||||||
var uponTableHeight = element.find('#upon-pane .table-body-container').height();
|
var uponTableHeight = element.find('#upon-pane .table-body-container').height();
|
||||||
var downTableHeight = element.find('#down-pane .table-body-container').height();
|
var downTableHeight = element.find('#down-pane .table-body-container').height();
|
||||||
|
|
||||||
var handleHeight = element.find('.split-handle').height() + element.find('.split-handle').offset().top + element.find('.well').height() - 24;
|
var handleHeight = element.find('.split-handle').height() + element.find('.split-handle').offset().top + element.find('.well').height() - 32;
|
||||||
|
console.log('handleHeight:' + handleHeight);
|
||||||
var maxDownPaneHeight = 760;
|
var maxDownPaneHeight = 760;
|
||||||
|
|
||||||
element.find('.split-handle').on('mousedown', mousedownHandler);
|
element.find('.split-handle').on('mousedown', mousedownHandler);
|
||||||
@ -328,7 +339,7 @@
|
|||||||
.css({'color': '#fff'});
|
.css({'color': '#fff'});
|
||||||
$('a', this)
|
$('a', this)
|
||||||
.css({'color': '#fff'});
|
.css({'color': '#fff'});
|
||||||
ctrl.retrieveJob($(this).attr('policy_id'));
|
ctrl.retrieveJob($(this).attr('policy_id'), ctrl.page, ctrl.pageSize);
|
||||||
ctrl.lastPolicyId = $(this).attr('policy_id');
|
ctrl.lastPolicyId = $(this).attr('policy_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
@ -41,7 +41,9 @@
|
|||||||
vm.filterInput = hashValue;
|
vm.filterInput = hashValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
vm.page = 1;
|
||||||
|
vm.pageSize = 8;
|
||||||
|
|
||||||
vm.retrieve = retrieve;
|
vm.retrieve = retrieve;
|
||||||
vm.tagCount = {};
|
vm.tagCount = {};
|
||||||
|
|
||||||
@ -61,6 +63,15 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$scope.$watch('vm.page', function(current) {
|
||||||
|
if(current !== 1) {
|
||||||
|
vm.page = current;
|
||||||
|
vm.retrieve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$scope.$on('repoName', function(e, val) {
|
$scope.$on('repoName', function(e, val) {
|
||||||
vm.repoName = val;
|
vm.repoName = val;
|
||||||
});
|
});
|
||||||
@ -76,19 +87,19 @@
|
|||||||
$scope.$on('tags', function(e, val) {
|
$scope.$on('tags', function(e, val) {
|
||||||
vm.tags = val;
|
vm.tags = val;
|
||||||
});
|
});
|
||||||
|
|
||||||
vm.deleteByRepo = deleteByRepo;
|
vm.deleteByRepo = deleteByRepo;
|
||||||
vm.deleteByTag = deleteByTag;
|
vm.deleteByTag = deleteByTag;
|
||||||
vm.deleteImage = deleteImage;
|
vm.deleteImage = deleteImage;
|
||||||
|
|
||||||
function retrieve(){
|
function retrieve(){
|
||||||
ListRepositoryService(vm.projectId, vm.filterInput)
|
ListRepositoryService(vm.projectId, vm.filterInput, vm.page, vm.pageSize)
|
||||||
.success(getRepositoryComplete)
|
.then(getRepositoryComplete, getRepositoryFailed);
|
||||||
.error(getRepositoryFailed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRepositoryComplete(data, status) {
|
function getRepositoryComplete(response) {
|
||||||
vm.repositories = data || [];
|
vm.repositories = response.data || [];
|
||||||
|
vm.totalCount = response.headers('X-Total-Count');
|
||||||
$scope.$broadcast('refreshTags', true);
|
$scope.$broadcast('refreshTags', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
'harbor.system.management',
|
'harbor.system.management',
|
||||||
'harbor.loading.progress',
|
'harbor.loading.progress',
|
||||||
'harbor.inline.help',
|
'harbor.inline.help',
|
||||||
'harbor.dismissable.alerts'
|
'harbor.dismissable.alerts',
|
||||||
|
'harbor.paginator'
|
||||||
]);
|
]);
|
||||||
})();
|
})();
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
return LogResult;
|
return LogResult;
|
||||||
|
|
||||||
function LogResult(queryParams) {
|
function LogResult(queryParams, page, pageSize) {
|
||||||
var projectId = queryParams.projectId;
|
var projectId = queryParams.projectId;
|
||||||
var username = queryParams.username;
|
var username = queryParams.username;
|
||||||
var beginTimestamp = queryParams.beginTimestamp;
|
var beginTimestamp = queryParams.beginTimestamp;
|
||||||
@ -34,7 +34,7 @@
|
|||||||
var keywords = queryParams.keywords;
|
var keywords = queryParams.keywords;
|
||||||
|
|
||||||
return $http
|
return $http
|
||||||
.post('/api/projects/' + projectId + '/logs/filter', {
|
.post('/api/projects/' + projectId + '/logs/filter?page=' + page + '&page_size=' + pageSize, {
|
||||||
'begin_timestamp' : beginTimestamp,
|
'begin_timestamp' : beginTimestamp,
|
||||||
'end_timestamp' : endTimestamp,
|
'end_timestamp' : endTimestamp,
|
||||||
'keywords' : keywords,
|
'keywords' : keywords,
|
||||||
|
@ -26,9 +26,9 @@
|
|||||||
|
|
||||||
return listReplicationJob;
|
return listReplicationJob;
|
||||||
|
|
||||||
function listReplicationJob(policyId, repository, status, startTime, endTime) {
|
function listReplicationJob(policyId, repository, status, startTime, endTime, page, pageSize) {
|
||||||
return $http
|
return $http
|
||||||
.get('/api/jobs/replication/', {
|
.get('/api/jobs/replication/?page=' + page + '&page_size=' + pageSize, {
|
||||||
'params': {
|
'params': {
|
||||||
'policy_id': policyId,
|
'policy_id': policyId,
|
||||||
'repository': repository,
|
'repository': repository,
|
||||||
|
@ -25,11 +25,11 @@
|
|||||||
|
|
||||||
return ListRepository;
|
return ListRepository;
|
||||||
|
|
||||||
function ListRepository(projectId, q) {
|
function ListRepository(projectId, q, page, pageSize) {
|
||||||
$log.info('list repositories:' + projectId + ', q:' + q);
|
$log.info('list repositories:' + projectId + ', q:' + q);
|
||||||
|
|
||||||
return $http
|
return $http
|
||||||
.get('/api/repositories', {
|
.get('/api/repositories?page=' + page + '&page_size=' + pageSize, {
|
||||||
'params':{
|
'params':{
|
||||||
'project_id': projectId,
|
'project_id': projectId,
|
||||||
'q': q
|
'q': q
|
||||||
|
41
tests/apitests/apilib/access_log.go
Normal file
41
tests/apitests/apilib/access_log.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type AccessLog struct {
|
||||||
|
|
||||||
|
// The ID of the log entry.
|
||||||
|
LogId int32 `json:"log_id,omitempty"`
|
||||||
|
|
||||||
|
// Name of the repository in this log entry.
|
||||||
|
RepoName string `json:"repo_name,omitempty"`
|
||||||
|
|
||||||
|
// Tag of the repository in this log entry.
|
||||||
|
RepoTag string `json:"repo_tag,omitempty"`
|
||||||
|
|
||||||
|
// The operation against the repository in this log entry.
|
||||||
|
Operation string `json:"operation,omitempty"`
|
||||||
|
|
||||||
|
// The time when this operation is triggered.
|
||||||
|
OpTime string `json:"op_time,omitempty"`
|
||||||
|
}
|
38
tests/apitests/apilib/access_log_filter.go
Normal file
38
tests/apitests/apilib/access_log_filter.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type AccessLogFilter struct {
|
||||||
|
|
||||||
|
// Relevant user's name that accessed this project.
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
|
||||||
|
// Operation name specified when project created.
|
||||||
|
Keywords string `json:"keywords,omitempty"`
|
||||||
|
|
||||||
|
// Begin timestamp for querying access logs.
|
||||||
|
BeginTimestamp int64 `json:"begin_timestamp,omitempty"`
|
||||||
|
|
||||||
|
// End timestamp for querying accessl logs.
|
||||||
|
EndTimestamp int64 `json:"end_timestamp,omitempty"`
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
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 HarborAPI
|
|
||||||
|
|
||||||
type AccessLog struct {
|
|
||||||
Username string `json:"username,omitempty"`
|
|
||||||
Keywords string `json:"keywords,omitempty"`
|
|
||||||
BeginTimestamp int32 `json:"beginTimestamp,omitempty"`
|
|
||||||
EndTimestamp int32 `json:"endTimestamp,omitempty"`
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
//Package HarborAPI
|
//Package apilib
|
||||||
//These APIs provide services for manipulating Harbor project.
|
//These APIs provide services for manipulating Harbor project.
|
||||||
package HarborAPI
|
package apilib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
15
tests/apitests/apilib/harborlogout.bak
Normal file
15
tests/apitests/apilib/harborlogout.bak
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// HarborLogout.go
|
||||||
|
package HarborAPI
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a HarborAPI) HarborLogout() (int, error) {
|
||||||
|
|
||||||
|
response, err := http.Get(a.basePath + "/logout")
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
return response.StatusCode, err
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
// HarborLogout.go
|
// HarborLogout.go
|
||||||
package HarborAPI
|
package apilib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
28
tests/apitests/apilib/harlogin.bak
Normal file
28
tests/apitests/apilib/harlogin.bak
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// HarborLogon.go
|
||||||
|
package HarborAPI
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a HarborAPI) HarborLogin(user UsrInfo) (int, error) {
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("principal", user.Name)
|
||||||
|
v.Set("password", user.Passwd)
|
||||||
|
|
||||||
|
body := ioutil.NopCloser(strings.NewReader(v.Encode())) //endode v:[body struce]
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
reqest, err := http.NewRequest("POST", a.basePath+"/login", body)
|
||||||
|
|
||||||
|
reqest.Header.Set("Content-Type", "application/x-www-form-urlencoded;param=value") //setting post head
|
||||||
|
|
||||||
|
resp, err := client.Do(reqest)
|
||||||
|
defer resp.Body.Close() //close resp.Body
|
||||||
|
|
||||||
|
return resp.StatusCode, err
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
// HarborLogon.go
|
// HarborLogon.go
|
||||||
package HarborAPI
|
package apilib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
50
tests/apitests/apilib/job_status.go
Normal file
50
tests/apitests/apilib/job_status.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type JobStatus struct {
|
||||||
|
|
||||||
|
// The job ID.
|
||||||
|
Id int64 `json:"id,omitempty"`
|
||||||
|
|
||||||
|
// The status of the job.
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
|
||||||
|
// The repository handled by the job.
|
||||||
|
Repository string `json:"repository,omitempty"`
|
||||||
|
|
||||||
|
// The ID of the policy that triggered this job.
|
||||||
|
PolicyId int64 `json:"policy_id,omitempty"`
|
||||||
|
|
||||||
|
// The operation of the job.
|
||||||
|
Operation string `json:"operation,omitempty"`
|
||||||
|
|
||||||
|
// The repository's used tag list.
|
||||||
|
Tags []Tags `json:"tags,omitempty"`
|
||||||
|
|
||||||
|
// The creation time of the job.
|
||||||
|
CreationTime string `json:"creation_time,omitempty"`
|
||||||
|
|
||||||
|
// The update time of the job.
|
||||||
|
UpdateTime string `json:"update_time,omitempty"`
|
||||||
|
}
|
32
tests/apitests/apilib/password.go
Normal file
32
tests/apitests/apilib/password.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type Password struct {
|
||||||
|
|
||||||
|
// The user's existing password.
|
||||||
|
OldPassword string `json:"old_password,omitempty"`
|
||||||
|
|
||||||
|
// New password for marking as to be updated.
|
||||||
|
NewPassword string `json:"new_password,omitempty"`
|
||||||
|
}
|
@ -1,15 +1,62 @@
|
|||||||
package HarborAPI
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import ()
|
package apilib
|
||||||
|
|
||||||
type Project struct {
|
type Project struct {
|
||||||
ProjectId int32 `json:"id,omitempty"`
|
|
||||||
OwnerId int32 `json:"owner_id,omitempty"`
|
// Project ID
|
||||||
ProjectName string `json:"project_name,omitempty"`
|
ProjectId int32 `json:"project_id,omitempty"`
|
||||||
|
|
||||||
|
// The owner ID of the project always means the creator of the project.
|
||||||
|
OwnerId int32 `json:"owner_id,omitempty"`
|
||||||
|
|
||||||
|
// The name of the project.
|
||||||
|
ProjectName string `json:"project_name,omitempty"`
|
||||||
|
|
||||||
|
// The creation time of the project.
|
||||||
CreationTime string `json:"creation_time,omitempty"`
|
CreationTime string `json:"creation_time,omitempty"`
|
||||||
Deleted int32 `json:"deleted,omitempty"`
|
|
||||||
UserId int32 `json:"user_id,omitempty"`
|
// The update time of the project.
|
||||||
OwnerName string `json:"owner_name,omitempty"`
|
UpdateTime string `json:"update_time,omitempty"`
|
||||||
Public bool `json:"public,omitempty"`
|
|
||||||
Togglable bool `json:"togglable,omitempty"`
|
// A deletion mark of the project (1 means it's deleted, 0 is not)
|
||||||
|
Deleted int32 `json:"deleted,omitempty"`
|
||||||
|
|
||||||
|
// A relation field to the user table.
|
||||||
|
UserId int32 `json:"user_id,omitempty"`
|
||||||
|
|
||||||
|
// The owner name of the project.
|
||||||
|
OwnerName string `json:"owner_name,omitempty"`
|
||||||
|
|
||||||
|
// The public status of the project.
|
||||||
|
Public bool `json:"public,omitempty"`
|
||||||
|
|
||||||
|
// Correspond to the UI about whether the project's publicity is updatable (for UI)
|
||||||
|
Togglable bool `json:"togglable,omitempty"`
|
||||||
|
|
||||||
|
// The role ID of the current user who triggered the API (for UI)
|
||||||
|
CurrentUserRoleId int32 `json:"current_user_role_id,omitempty"`
|
||||||
|
|
||||||
|
// The number of the repositories under this project.
|
||||||
|
RepoCount int32 `json:"repo_count,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package HarborAPI
|
|
||||||
|
|
||||||
import ()
|
|
||||||
|
|
||||||
type Project4Search struct {
|
|
||||||
ProjectId int32 `json:"id,omitempty"`
|
|
||||||
ProjectName string `json:"name,omitempty"`
|
|
||||||
Public int32 `json:"public,omitempty"`
|
|
||||||
}
|
|
62
tests/apitests/apilib/rep_policy.go
Normal file
62
tests/apitests/apilib/rep_policy.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type RepPolicy struct {
|
||||||
|
|
||||||
|
// The policy ID.
|
||||||
|
Id int64 `json:"id,omitempty"`
|
||||||
|
|
||||||
|
// The project ID.
|
||||||
|
ProjectId int64 `json:"project_id,omitempty"`
|
||||||
|
|
||||||
|
// The project name.
|
||||||
|
ProjectName string `json:"project_name,omitempty"`
|
||||||
|
|
||||||
|
// The target ID.
|
||||||
|
TargetId int64 `json:"target_id,omitempty"`
|
||||||
|
|
||||||
|
// The target name.
|
||||||
|
TargetName string `json:"target_name,omitempty"`
|
||||||
|
|
||||||
|
// The policy name.
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// The policy's enabled status.
|
||||||
|
Enabled int32 `json:"enabled,omitempty"`
|
||||||
|
|
||||||
|
// The description of the policy.
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
|
||||||
|
// The cron string for schedule job.
|
||||||
|
CronStr string `json:"cron_str,omitempty"`
|
||||||
|
|
||||||
|
// The start time of the policy.
|
||||||
|
StartTime string `json:"start_time,omitempty"`
|
||||||
|
|
||||||
|
// The create time of the policy.
|
||||||
|
CreationTime string `json:"creation_time,omitempty"`
|
||||||
|
|
||||||
|
// The update time of the policy.
|
||||||
|
UpdateTime string `json:"update_time,omitempty"`
|
||||||
|
}
|
29
tests/apitests/apilib/rep_policy_enablement_req.go
Normal file
29
tests/apitests/apilib/rep_policy_enablement_req.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type RepPolicyEnablementReq struct {
|
||||||
|
|
||||||
|
// The policy enablement flag.
|
||||||
|
Enabled int32 `json:"enabled,omitempty"`
|
||||||
|
}
|
35
tests/apitests/apilib/rep_policy_post.go
Normal file
35
tests/apitests/apilib/rep_policy_post.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type RepPolicyPost struct {
|
||||||
|
|
||||||
|
// The project ID.
|
||||||
|
ProjectId int64 `json:"project_id,omitempty"`
|
||||||
|
|
||||||
|
// The target ID.
|
||||||
|
TargetId int64 `json:"target_id,omitempty"`
|
||||||
|
|
||||||
|
// The policy name.
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
41
tests/apitests/apilib/rep_policy_update.go
Normal file
41
tests/apitests/apilib/rep_policy_update.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type RepPolicyUpdate struct {
|
||||||
|
|
||||||
|
// The target ID.
|
||||||
|
TargetId int64 `json:"target_id,omitempty"`
|
||||||
|
|
||||||
|
// The policy name.
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// The policy's enabled status.
|
||||||
|
Enabled int32 `json:"enabled,omitempty"`
|
||||||
|
|
||||||
|
// The description of the policy.
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
|
||||||
|
// The cron string for schedule job.
|
||||||
|
CronStr string `json:"cron_str,omitempty"`
|
||||||
|
}
|
50
tests/apitests/apilib/rep_target.go
Normal file
50
tests/apitests/apilib/rep_target.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type RepTarget struct {
|
||||||
|
|
||||||
|
// The target ID.
|
||||||
|
Id int64 `json:"id,omitempty"`
|
||||||
|
|
||||||
|
// The target address URL string.
|
||||||
|
Endpoint string `json:"endpoint,omitempty"`
|
||||||
|
|
||||||
|
// The target name.
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// The target server username.
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
|
||||||
|
// The target server password.
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
|
||||||
|
// Reserved field.
|
||||||
|
Type_ int32 `json:"type,omitempty"`
|
||||||
|
|
||||||
|
// The create time of the policy.
|
||||||
|
CreationTime string `json:"creation_time,omitempty"`
|
||||||
|
|
||||||
|
// The update time of the policy.
|
||||||
|
UpdateTime string `json:"update_time,omitempty"`
|
||||||
|
}
|
38
tests/apitests/apilib/rep_target_post.go
Normal file
38
tests/apitests/apilib/rep_target_post.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type RepTargetPost struct {
|
||||||
|
|
||||||
|
// The target address URL string.
|
||||||
|
Endpoint string `json:"endpoint,omitempty"`
|
||||||
|
|
||||||
|
// The target name.
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// The target server username.
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
|
||||||
|
// The target server password.
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
}
|
@ -1,16 +1,50 @@
|
|||||||
package HarborAPI
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import (
|
package apilib
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
Id string `json:"id,omitempty"`
|
|
||||||
Parent string `json:"parent,omitempty"`
|
// Repository ID
|
||||||
Created time.Time `json:"created,omitempty"`
|
Id string `json:"id,omitempty"`
|
||||||
DurationDays string `json:"duration_days,omitempty"`
|
|
||||||
Author string `json:"author,omitempty"`
|
// Parent of the image.
|
||||||
Architecture string `json:"architecture,omitempty"`
|
Parent string `json:"parent,omitempty"`
|
||||||
DockerVersion string `json:"docker_version,omitempty"`
|
|
||||||
Os string `json:"os,omitempty"`
|
// Repository create time.
|
||||||
|
Created string `json:"created,omitempty"`
|
||||||
|
|
||||||
|
// Duration days of the image.
|
||||||
|
DurationDays string `json:"duration_days,omitempty"`
|
||||||
|
|
||||||
|
// Author of the image.
|
||||||
|
Author string `json:"author,omitempty"`
|
||||||
|
|
||||||
|
// Architecture of the image.
|
||||||
|
Architecture string `json:"architecture,omitempty"`
|
||||||
|
|
||||||
|
// Docker version of the image.
|
||||||
|
DockerVersion string `json:"docker_version,omitempty"`
|
||||||
|
|
||||||
|
// OS of the image.
|
||||||
|
Os string `json:"os,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package HarborAPI
|
|
||||||
|
|
||||||
type Repository4Search struct {
|
|
||||||
ProjectId int32 `json:"project_id,omitempty"`
|
|
||||||
ProjectName string `json:"project_name,omitempty"`
|
|
||||||
ProjectPublic int32 `json:"project_public,omitempty"`
|
|
||||||
RepoName string `json:"repository_name,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,35 @@
|
|||||||
package HarborAPI
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
type Role struct {
|
type Role struct {
|
||||||
RoleId int32 `json:"role_id,omitempty"`
|
|
||||||
|
// ID in table.
|
||||||
|
RoleId int32 `json:"role_id,omitempty"`
|
||||||
|
|
||||||
|
// Description of permissions for the role.
|
||||||
RoleCode string `json:"role_code,omitempty"`
|
RoleCode string `json:"role_code,omitempty"`
|
||||||
|
|
||||||
|
// Name the the role.
|
||||||
RoleName string `json:"role_name,omitempty"`
|
RoleName string `json:"role_name,omitempty"`
|
||||||
}
|
}
|
||||||
|
32
tests/apitests/apilib/role_param.go
Normal file
32
tests/apitests/apilib/role_param.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type RoleParam struct {
|
||||||
|
|
||||||
|
// Role ID for updating project role member.
|
||||||
|
Roles []int32 `json:"roles,omitempty"`
|
||||||
|
|
||||||
|
// Username relevant to a project role member.
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
}
|
@ -1,6 +0,0 @@
|
|||||||
package HarborAPI
|
|
||||||
|
|
||||||
type RoleParam struct {
|
|
||||||
Roles []int32 `json:"roles,omitempty"`
|
|
||||||
UserName string `json:"user_name,omitempty"`
|
|
||||||
}
|
|
@ -1,8 +1,34 @@
|
|||||||
package HarborAPI
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import ()
|
package apilib
|
||||||
|
|
||||||
|
import()
|
||||||
|
|
||||||
type Search struct {
|
type Search struct {
|
||||||
Projects []Project4Search `json:"project,omitempty"`
|
|
||||||
Repositories []Repository4Search `json:"repository,omitempty"`
|
// Search results of the projects that matched the filter keywords.
|
||||||
|
Projects []SearchProject `json:"project,omitempty"`
|
||||||
|
|
||||||
|
// Search results of the repositories that matched the filter keywords.
|
||||||
|
Repositories []SearchRepository `json:"repository,omitempty"`
|
||||||
}
|
}
|
||||||
|
35
tests/apitests/apilib/search_project.go
Normal file
35
tests/apitests/apilib/search_project.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type SearchProject struct {
|
||||||
|
|
||||||
|
// The ID of project
|
||||||
|
Id int64 `json:"id,omitempty"`
|
||||||
|
|
||||||
|
// The name of the project
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// The flag to indicate the publicity of the project (1 is public, 0 is non-public)
|
||||||
|
Public int32 `json:"public,omitempty"`
|
||||||
|
}
|
38
tests/apitests/apilib/search_repository.go
Normal file
38
tests/apitests/apilib/search_repository.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type SearchRepository struct {
|
||||||
|
|
||||||
|
// The name of the repository
|
||||||
|
RepositoryName string `json:"repository_name,omitempty"`
|
||||||
|
|
||||||
|
// The name of the project that the repository belongs to
|
||||||
|
ProjectName string `json:"project_name,omitempty"`
|
||||||
|
|
||||||
|
// The ID of the project that the repository belongs to
|
||||||
|
ProjectId int32 `json:"project_id,omitempty"`
|
||||||
|
|
||||||
|
// The flag to indicate the publicity of the project that the repository belongs to (1 is public, 0 is not)
|
||||||
|
ProjectPublic int32 `json:"project_public,omitempty"`
|
||||||
|
}
|
44
tests/apitests/apilib/statistic_map.go
Normal file
44
tests/apitests/apilib/statistic_map.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type StatisticMap struct {
|
||||||
|
|
||||||
|
// The count of the projects which the user is a member of.
|
||||||
|
MyProjectCount int32 `json:"my_project_count,omitempty"`
|
||||||
|
|
||||||
|
// The count of the repositories belonging to the projects which the user is a member of.
|
||||||
|
MyRepoCount int32 `json:"my_repo_count,omitempty"`
|
||||||
|
|
||||||
|
// The count of the public projects.
|
||||||
|
PublicProjectCount int32 `json:"public_project_count,omitempty"`
|
||||||
|
|
||||||
|
// The count of the public repositories belonging to the public projects which the user is a member of.
|
||||||
|
PublicRepoCount int32 `json:"public_repo_count,omitempty"`
|
||||||
|
|
||||||
|
// The count of the total projects, only be seen when the is admin.
|
||||||
|
TotalProjectCount int32 `json:"total_project_count,omitempty"`
|
||||||
|
|
||||||
|
// The count of the total repositories, only be seen when the user is admin.
|
||||||
|
TotalRepoCount int32 `json:"total_repo_count,omitempty"`
|
||||||
|
}
|
29
tests/apitests/apilib/tags.go
Normal file
29
tests/apitests/apilib/tags.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type Tags struct {
|
||||||
|
|
||||||
|
// The repository's used tag.
|
||||||
|
Tag string `json:"tag,omitempty"`
|
||||||
|
}
|
32
tests/apitests/apilib/top_repo.go
Normal file
32
tests/apitests/apilib/top_repo.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
|
type TopRepo struct {
|
||||||
|
|
||||||
|
// The name of the repo
|
||||||
|
RepoName string `json:"repo_name,omitempty"`
|
||||||
|
|
||||||
|
// The access count of the repo
|
||||||
|
AccessCount int32 `json:"access_count,omitempty"`
|
||||||
|
}
|
@ -1,11 +1,41 @@
|
|||||||
package HarborAPI
|
/*
|
||||||
|
* Harbor API
|
||||||
|
*
|
||||||
|
* These APIs provide services for manipulating Harbor project.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*
|
||||||
|
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
*
|
||||||
|
* 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 apilib
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
UserId int32 `json:"user_id,omitempty"`
|
|
||||||
|
// The ID of the user.
|
||||||
|
UserId int32 `json:"user_id,omitempty"`
|
||||||
|
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
Email string `json:"email,omitempty"`
|
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
|
|
||||||
Realname string `json:"realname,omitempty"`
|
Realname string `json:"realname,omitempty"`
|
||||||
Comment string `json:"comment,omitempty"`
|
|
||||||
Deleted int32 `json:"deleted,omitempty"`
|
Comment string `json:"comment,omitempty"`
|
||||||
|
|
||||||
|
Deleted int32 `json:"deleted,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
package HarborAPItest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAddProject(t *testing.T) {
|
|
||||||
|
|
||||||
fmt.Println("Test for Project Add (ProjectsPost) API")
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
apiTest := HarborAPI.NewHarborAPI()
|
|
||||||
|
|
||||||
//prepare for test
|
|
||||||
adminEr := &HarborAPI.UsrInfo{"admin", "Harbor1234"}
|
|
||||||
admin := &HarborAPI.UsrInfo{"admin", "Harbor12345"}
|
|
||||||
|
|
||||||
prjUsr := &HarborAPI.UsrInfo{"unknown", "unknown"}
|
|
||||||
|
|
||||||
var project HarborAPI.Project
|
|
||||||
project.ProjectName = "testproject"
|
|
||||||
project.Public = true
|
|
||||||
|
|
||||||
//case 1: admin login fail, expect project creation fail.
|
|
||||||
fmt.Println("case 1: admin login fail, expect project creation fail.")
|
|
||||||
resault, err := apiTest.HarborLogin(*adminEr)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while admin login", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(resault, int(401), "Admin login status should be 401")
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
|
|
||||||
resault, err = apiTest.ProjectsPost(*prjUsr, project)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while creat project", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(resault, int(401), "Case 1: Project creation status should be 401")
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
|
|
||||||
//case 2: admin successful login, expect project creation success.
|
|
||||||
fmt.Println("case 2: admin successful login, expect project creation success.")
|
|
||||||
resault, err = apiTest.HarborLogin(*admin)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while admin login", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(resault, int(200), "Admin login status should be 200")
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
if resault != 200 {
|
|
||||||
t.Log(resault)
|
|
||||||
} else {
|
|
||||||
prjUsr = admin
|
|
||||||
}
|
|
||||||
|
|
||||||
resault, err = apiTest.ProjectsPost(*prjUsr, project)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while creat project", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(resault, int(201), "Case 2: Project creation status should be 201")
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
|
|
||||||
//case 3: duplicate project name, create project fail
|
|
||||||
fmt.Println("case 3: duplicate project name, create project fail")
|
|
||||||
resault, err = apiTest.ProjectsPost(*prjUsr, project)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while creat project", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(resault, int(409), "Case 3: Project creation status should be 409")
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
|
|
||||||
//resault1, err := apiTest.HarborLogout()
|
|
||||||
//if err != nil {
|
|
||||||
// t.Error("Error while admin logout", err.Error())
|
|
||||||
// t.Log(err)
|
|
||||||
//} else {
|
|
||||||
// assert.Equal(resault1, int(200), "Admin logout status")
|
|
||||||
// //t.Log(resault)
|
|
||||||
//}
|
|
||||||
//if resault1 != 200 {
|
|
||||||
// t.Log(resault)
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
|
@ -1,130 +0,0 @@
|
|||||||
package HarborAPItest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRepositoryDelete(t *testing.T) {
|
|
||||||
fmt.Println("Test for Project Delete (ProjectDelete) API")
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
//prepare for test
|
|
||||||
adminEr := &HarborAPI.UsrInfo{"admin", "Harbor1234"}
|
|
||||||
admin := &HarborAPI.UsrInfo{"admin", "Harbor12345"}
|
|
||||||
prjUsr := &HarborAPI.UsrInfo{"unknown", "unknown"}
|
|
||||||
|
|
||||||
fmt.Println("Checking repository status...")
|
|
||||||
apiTest := HarborAPI.NewHarborAPI()
|
|
||||||
var searchResault HarborAPI.Search
|
|
||||||
searchResault, err := apiTest.SearchGet("library")
|
|
||||||
//fmt.Printf("%+v\n", resault)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while search project or repository", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
//assert.Equal(searchResault.Repositories[0].RepoName, "library/docker", "1st repo name should be")
|
|
||||||
if !assert.Equal(searchResault.Repositories[0].RepoName, "library/docker", "1st repo name should be") {
|
|
||||||
t.Error("fail to find repo 'library/docker'", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
fmt.Println("repo 'library/docker' exit")
|
|
||||||
}
|
|
||||||
//assert.Equal(searchResault.Repositories[1].RepoName, "library/hello-world", "2nd repo name should be")
|
|
||||||
if !assert.Equal(searchResault.Repositories[1].RepoName, "library/hello-world", "2nd repo name should be") {
|
|
||||||
t.Error("fail to find repo 'library/hello-world'", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
fmt.Println("repo 'library/hello-world' exit")
|
|
||||||
}
|
|
||||||
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
|
|
||||||
//case 1: admin login fail, expect repo delete fail.
|
|
||||||
fmt.Println("case 1: admin login fail, expect repo delete fail.")
|
|
||||||
|
|
||||||
resault, err := apiTest.HarborLogin(*adminEr)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while admin login", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(resault, int(401), "Admin login status should be 401")
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
if resault != 401 {
|
|
||||||
t.Log(resault)
|
|
||||||
} else {
|
|
||||||
prjUsr = adminEr
|
|
||||||
}
|
|
||||||
|
|
||||||
resault, err = apiTest.RepositoriesDelete(*prjUsr, "library/docker", "")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while delete repository", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(resault, int(401), "Case 1: Repository delete status should be 401")
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
|
|
||||||
//case 2: admin successful login, expect repository delete success.
|
|
||||||
fmt.Println("case 2: admin successful login, expect repository delete success.")
|
|
||||||
resault, err = apiTest.HarborLogin(*admin)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while admin login", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(resault, int(200), "Admin login status should be 200")
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
if resault != 200 {
|
|
||||||
t.Log(resault)
|
|
||||||
} else {
|
|
||||||
prjUsr = admin
|
|
||||||
}
|
|
||||||
|
|
||||||
resault, err = apiTest.RepositoriesDelete(*prjUsr, "library/docker", "")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while delete repository", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
if assert.Equal(resault, int(200), "Case 2: Repository delete status should be 200") {
|
|
||||||
fmt.Println("Repository 'library/docker' delete success.")
|
|
||||||
}
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
|
|
||||||
resault, err = apiTest.RepositoriesDelete(*prjUsr, "library/hello-world", "")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while delete repository", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
if assert.Equal(resault, int(200), "Case 2: Repository delete status should be 200") {
|
|
||||||
fmt.Println("Repository 'hello-world' delete success.")
|
|
||||||
}
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
|
|
||||||
//case 3: delete one repo not exit, expect repo delete fail.
|
|
||||||
fmt.Println("case 3: delete one repo not exit, expect repo delete fail.")
|
|
||||||
|
|
||||||
resault, err = apiTest.RepositoriesDelete(*prjUsr, "library/hello-world", "")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while delete repository", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
if assert.Equal(resault, int(404), "Case 3: Repository delete status should be 404") {
|
|
||||||
fmt.Println("Repository 'hello-world' not exit.")
|
|
||||||
}
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
|
|
||||||
//if resault.Response.StatusCode != 200 {
|
|
||||||
// t.Log(resault.Response)
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package HarborAPItest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSearch(t *testing.T) {
|
|
||||||
fmt.Println("Test for Search (SearchGet) API")
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
apiTest := HarborAPI.NewHarborAPI()
|
|
||||||
var resault HarborAPI.Search
|
|
||||||
resault, err := apiTest.SearchGet("library")
|
|
||||||
//fmt.Printf("%+v\n", resault)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error while search project or repository", err.Error())
|
|
||||||
t.Log(err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(resault.Projects[0].ProjectId, int32(1), "Project id should be equal")
|
|
||||||
assert.Equal(resault.Projects[0].ProjectName, "library", "Project name should be library")
|
|
||||||
assert.Equal(resault.Projects[0].Public, int32(1), "Project public status should be 1 (true)")
|
|
||||||
//t.Log(resault)
|
|
||||||
}
|
|
||||||
//if resault.Response.StatusCode != 200 {
|
|
||||||
// t.Log(resault.Response)
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
|
23
tests/docker-compose.test.yml
Normal file
23
tests/docker-compose.test.yml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
registry:
|
||||||
|
image: library/registry:2.4.0
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /data/registry:/storage
|
||||||
|
- ./config/registry/:/etc/registry/
|
||||||
|
environment:
|
||||||
|
- GODEBUG=netdns=cgo
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
|
command:
|
||||||
|
["serve", "/etc/registry/config.yml"]
|
||||||
|
mysql:
|
||||||
|
build: ./db/
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /data/database:/var/lib/mysql
|
||||||
|
env_file:
|
||||||
|
- ./config/db/env
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
@ -1,10 +1,9 @@
|
|||||||
docker pull hello-world
|
#!/bin/bash
|
||||||
docker pull docker
|
|
||||||
docker login -u admin -p Harbor12345 127.0.0.1
|
|
||||||
|
|
||||||
docker tag hello-world 127.0.0.1/library/hello-world
|
cp tests/docker-compose.test.yml Deploy/.
|
||||||
docker push 127.0.0.1/library/hello-world
|
|
||||||
|
|
||||||
docker tag docker 127.0.0.1/library/docker
|
mkdir /etc/ui
|
||||||
docker push 127.0.0.1/library/docker
|
cp Deploy/config/ui/private_key.pem /etc/ui/.
|
||||||
|
|
||||||
|
mkdir conf
|
||||||
|
cp Deploy/config/ui/app.conf conf/.
|
||||||
|
@ -16,9 +16,15 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
)
|
)
|
||||||
@ -28,13 +34,67 @@ func Encrypt(content string, salt string) string {
|
|||||||
return fmt.Sprintf("%x", pbkdf2.Key([]byte(content), []byte(salt), 4096, 16, sha1.New))
|
return fmt.Sprintf("%x", pbkdf2.Key([]byte(content), []byte(salt), 4096, 16, sha1.New))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReversibleEncrypt encrypts the str with base64
|
const (
|
||||||
func ReversibleEncrypt(str string) string {
|
// EncryptHeaderV1 ...
|
||||||
return base64.StdEncoding.EncodeToString([]byte(str))
|
EncryptHeaderV1 = "<enc-v1>"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReversibleEncrypt encrypts the str with aes/base64
|
||||||
|
func ReversibleEncrypt(str, key string) (string, error) {
|
||||||
|
keyBytes := []byte(key)
|
||||||
|
var block cipher.Block
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if block, err = aes.NewCipher(keyBytes); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cipherText := make([]byte, aes.BlockSize+len(str))
|
||||||
|
iv := cipherText[:aes.BlockSize]
|
||||||
|
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfb := cipher.NewCFBEncrypter(block, iv)
|
||||||
|
cfb.XORKeyStream(cipherText[aes.BlockSize:], []byte(str))
|
||||||
|
encrypted := EncryptHeaderV1 + base64.StdEncoding.EncodeToString(cipherText)
|
||||||
|
return encrypted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReversibleDecrypt decrypts the str with base64
|
// ReversibleDecrypt decrypts the str with aes/base64 or base 64 depending on "header"
|
||||||
func ReversibleDecrypt(str string) (string, error) {
|
func ReversibleDecrypt(str, key string) (string, error) {
|
||||||
b, err := base64.StdEncoding.DecodeString(str)
|
if strings.HasPrefix(str, EncryptHeaderV1) {
|
||||||
return string(b), err
|
str = str[len(EncryptHeaderV1):]
|
||||||
|
return decryptAES(str, key)
|
||||||
|
}
|
||||||
|
//fallback to base64
|
||||||
|
return decodeB64(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeB64(str string) (string, error) {
|
||||||
|
cipherText, err := base64.StdEncoding.DecodeString(str)
|
||||||
|
return string(cipherText), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptAES(str, key string) (string, error) {
|
||||||
|
keyBytes := []byte(key)
|
||||||
|
var block cipher.Block
|
||||||
|
var cipherText []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if block, err = aes.NewCipher(keyBytes); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if cipherText, err = base64.StdEncoding.DecodeString(str); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(cipherText) < aes.BlockSize {
|
||||||
|
err = errors.New("cipherText too short")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := cipherText[:aes.BlockSize]
|
||||||
|
cipherText = cipherText[aes.BlockSize:]
|
||||||
|
cfb := cipher.NewCFBDecrypter(block, iv)
|
||||||
|
cfb.XORKeyStream(cipherText, cipherText)
|
||||||
|
return string(cipherText), nil
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package log
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Level ...
|
// Level ...
|
||||||
@ -56,7 +57,7 @@ func (l Level) string() (lvl string) {
|
|||||||
|
|
||||||
func parseLevel(lvl string) (level Level, err error) {
|
func parseLevel(lvl string) (level Level, err error) {
|
||||||
|
|
||||||
switch lvl {
|
switch strings.ToLower(lvl) {
|
||||||
case "debug":
|
case "debug":
|
||||||
level = DebugLevel
|
level = DebugLevel
|
||||||
case "info":
|
case "info":
|
||||||
|
61
utils/log/level_test.go
Normal file
61
utils/log/level_test.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
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 log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestString(t *testing.T) {
|
||||||
|
m := map[Level]string{
|
||||||
|
DebugLevel: "DEBUG",
|
||||||
|
InfoLevel: "INFO",
|
||||||
|
WarningLevel: "WARNING",
|
||||||
|
ErrorLevel: "ERROR",
|
||||||
|
FatalLevel: "FATAL",
|
||||||
|
-1: "UNKNOWN",
|
||||||
|
}
|
||||||
|
|
||||||
|
for level, str := range m {
|
||||||
|
if level.string() != str {
|
||||||
|
t.Errorf("unexpected string: %s != %s", level.string(), str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLevel(t *testing.T) {
|
||||||
|
m := map[string]Level{
|
||||||
|
"DEBUG": DebugLevel,
|
||||||
|
"INFO": InfoLevel,
|
||||||
|
"WARNING": WarningLevel,
|
||||||
|
"ERROR": ErrorLevel,
|
||||||
|
"FATAL": FatalLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
for str, level := range m {
|
||||||
|
l, err := parseLevel(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to parse level: %v", err)
|
||||||
|
}
|
||||||
|
if l != level {
|
||||||
|
t.Errorf("unexpected level: %d != %d", l, level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := parseLevel("UNKNOWN"); err == nil {
|
||||||
|
t.Errorf("unexpected behaviour: should be error here")
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
|
||||||
}
|
|
||||||
|
|
156
utils/log/logger_test.go
Normal file
156
utils/log/logger_test.go
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
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 log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
message = "message"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetx(t *testing.T) {
|
||||||
|
logger := New(nil, nil, WarningLevel)
|
||||||
|
logger.SetOutput(os.Stdout)
|
||||||
|
fmt := NewTextFormatter()
|
||||||
|
logger.SetFormatter(fmt)
|
||||||
|
logger.SetLevel(DebugLevel)
|
||||||
|
|
||||||
|
if logger.out != os.Stdout {
|
||||||
|
t.Errorf("unexpected outer: %v != %v", logger.out, os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger.fmtter != fmt {
|
||||||
|
t.Errorf("unexpected formatter: %v != %v", logger.fmtter, fmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger.lvl != DebugLevel {
|
||||||
|
t.Errorf("unexpected log level: %v != %v", logger.lvl, DebugLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebug(t *testing.T) {
|
||||||
|
buf := enter()
|
||||||
|
defer exit()
|
||||||
|
|
||||||
|
Debug(message)
|
||||||
|
|
||||||
|
str := buf.String()
|
||||||
|
if len(str) != 0 {
|
||||||
|
t.Errorf("unexpected message: %s != %s", str, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebugf(t *testing.T) {
|
||||||
|
buf := enter()
|
||||||
|
defer exit()
|
||||||
|
|
||||||
|
Debugf("%s", message)
|
||||||
|
|
||||||
|
str := buf.String()
|
||||||
|
if len(str) != 0 {
|
||||||
|
t.Errorf("unexpected message: %s != %s", str, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfo(t *testing.T) {
|
||||||
|
buf := enter()
|
||||||
|
defer exit()
|
||||||
|
|
||||||
|
Info(message)
|
||||||
|
|
||||||
|
str := buf.String()
|
||||||
|
if strings.HasSuffix(str, "[INFO] message") {
|
||||||
|
t.Errorf("unexpected message: %s != %s", str, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfof(t *testing.T) {
|
||||||
|
buf := enter()
|
||||||
|
defer exit()
|
||||||
|
|
||||||
|
Infof("%s", message)
|
||||||
|
|
||||||
|
str := buf.String()
|
||||||
|
if strings.HasSuffix(str, "[INFO] message") {
|
||||||
|
t.Errorf("unexpected message: %s != %s", str, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarning(t *testing.T) {
|
||||||
|
buf := enter()
|
||||||
|
defer exit()
|
||||||
|
|
||||||
|
Warning(message)
|
||||||
|
|
||||||
|
str := buf.String()
|
||||||
|
if strings.HasSuffix(str, "[WARNING] message") {
|
||||||
|
t.Errorf("unexpected message: %s != %s", str, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarningf(t *testing.T) {
|
||||||
|
buf := enter()
|
||||||
|
defer exit()
|
||||||
|
|
||||||
|
Warningf("%s", message)
|
||||||
|
|
||||||
|
str := buf.String()
|
||||||
|
if strings.HasSuffix(str, "[WARNING] message") {
|
||||||
|
t.Errorf("unexpected message: %s != %s", str, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
buf := enter()
|
||||||
|
defer exit()
|
||||||
|
|
||||||
|
Error(message)
|
||||||
|
|
||||||
|
str := buf.String()
|
||||||
|
if strings.HasSuffix(str, "[ERROR] message") {
|
||||||
|
t.Errorf("unexpected message: %s != %s", str, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorf(t *testing.T) {
|
||||||
|
buf := enter()
|
||||||
|
defer exit()
|
||||||
|
|
||||||
|
Errorf("%s", message)
|
||||||
|
|
||||||
|
str := buf.String()
|
||||||
|
if strings.HasSuffix(str, "[ERROR] message") {
|
||||||
|
t.Errorf("unexpected message: %s != %s", str, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func enter() *bytes.Buffer {
|
||||||
|
b := make([]byte, 0, 32)
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
|
||||||
|
logger.SetOutput(buf)
|
||||||
|
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func exit() {
|
||||||
|
logger.SetOutput(os.Stdout)
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
|
||||||
}
|
|
||||||
|
|
89
utils/registry/auth/authorizer_test.go
Normal file
89
utils/registry/auth/authorizer_test.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/registry/client/auth"
|
||||||
|
"github.com/vmware/harbor/utils/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewAuthorizerStore(t *testing.T) {
|
||||||
|
handler := test.Handler(&test.Response{
|
||||||
|
StatusCode: http.StatusUnauthorized,
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Www-Authenticate": "Bearer realm=\"https://auth.docker.io/token\",service=\"registry.docker.io\"",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
server := test.NewServer(&test.RequestHandlerMapping{
|
||||||
|
Method: "GET",
|
||||||
|
Pattern: "/v2/",
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
_, err := NewAuthorizerStore(server.URL, false, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create authorizer store: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleAuthorizer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *simpleAuthorizer) Scheme() string {
|
||||||
|
return "bearer"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *simpleAuthorizer) Authorize(req *http.Request,
|
||||||
|
params map[string]string) error {
|
||||||
|
req.Header.Set("Authorization", "Bearer token")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModify(t *testing.T) {
|
||||||
|
authorizer := &simpleAuthorizer{}
|
||||||
|
challenge := auth.Challenge{
|
||||||
|
Scheme: "bearer",
|
||||||
|
}
|
||||||
|
|
||||||
|
as := &AuthorizerStore{
|
||||||
|
authorizers: []Authorizer{authorizer},
|
||||||
|
challenges: []auth.Challenge{challenge},
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "http://example.com", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = as.Modify(req); err != nil {
|
||||||
|
t.Fatalf("failed to modify request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
header := req.Header.Get("Authorization")
|
||||||
|
if len(header) == 0 {
|
||||||
|
t.Fatal("\"Authorization\" header not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(header, "Bearer") {
|
||||||
|
t.Fatal("\"Authorization\" header does not start with \"Bearer\"")
|
||||||
|
}
|
||||||
|
}
|
67
utils/registry/auth/credential_test.go
Normal file
67
utils/registry/auth/credential_test.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddAuthorizationOfBasicAuthCredential(t *testing.T) {
|
||||||
|
cred := NewBasicAuthCredential("usr", "pwd")
|
||||||
|
req, err := http.NewRequest("GET", "http://example.com", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cred.AddAuthorization(req)
|
||||||
|
|
||||||
|
usr, pwd, ok := req.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("basic auth not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if usr != "usr" {
|
||||||
|
t.Errorf("unexpected username: %s != usr", usr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pwd != "pwd" {
|
||||||
|
t.Errorf("unexpected password: %s != pwd", pwd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddAuthorizationOfCookieCredential(t *testing.T) {
|
||||||
|
cookie := &http.Cookie{
|
||||||
|
Name: "name",
|
||||||
|
Value: "value",
|
||||||
|
}
|
||||||
|
cred := NewCookieCredential(cookie)
|
||||||
|
req, err := http.NewRequest("GET", "http://example.com", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cred.AddAuthorization(req)
|
||||||
|
|
||||||
|
ck, err := req.Cookie("name")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get cookie: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ck.Value != "value" {
|
||||||
|
t.Errorf("unexpected value: %s != value", ck.Value)
|
||||||
|
}
|
||||||
|
}
|
@ -180,7 +180,9 @@ func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.credential.AddAuthorization(r)
|
if s.credential != nil {
|
||||||
|
s.credential.AddAuthorization(r)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := s.client.Do(r)
|
resp, err := s.client.Do(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
69
utils/registry/auth/tokenauthorizer_test.go
Normal file
69
utils/registry/auth/tokenauthorizer_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 auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/utils/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) {
|
||||||
|
handler := test.Handler(&test.Response{
|
||||||
|
Body: []byte(`
|
||||||
|
{
|
||||||
|
"token":"token",
|
||||||
|
"expires_in":300,
|
||||||
|
"issued_at":"2016-08-17T23:17:58+08:00"
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
})
|
||||||
|
|
||||||
|
server := test.NewServer(&test.RequestHandlerMapping{
|
||||||
|
Method: "GET",
|
||||||
|
Pattern: "/token",
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
authorizer := NewStandardTokenAuthorizer(nil, false, "repository", "library/ubuntu", "pull")
|
||||||
|
req, err := http.NewRequest("GET", "http://registry", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"realm": server.URL + "/token",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := authorizer.Authorize(req, params); err != nil {
|
||||||
|
t.Fatalf("failed to authorize request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tk := req.Header.Get("Authorization")
|
||||||
|
if tk != "Bearer token" {
|
||||||
|
t.Errorf("unexpected token: %s != %s", tk, "Bearer token")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSchemeOfStandardTokenAuthorizer(t *testing.T) {
|
||||||
|
authorizer := &standardTokenAuthorizer{}
|
||||||
|
if authorizer.Scheme() != "bearer" {
|
||||||
|
t.Errorf("unexpected scheme: %s != %s", authorizer.Scheme(), "bearer")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,6 +4,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestError(t *testing.T) {
|
||||||
}
|
err := &Error{
|
||||||
|
StatusCode: 404,
|
||||||
|
Detail: "not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err.Error() != "404 not found" {
|
||||||
|
t.Fatalf("unexpected content: %s != %s",
|
||||||
|
err.Error(), "404 not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
56
utils/registry/manifest_test.go
Normal file
56
utils/registry/manifest_test.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
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 registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnMarshal(t *testing.T) {
|
||||||
|
b := []byte(`{
|
||||||
|
"schemaVersion":2,
|
||||||
|
"mediaType":"application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"config":{
|
||||||
|
"mediaType":"application/vnd.docker.container.image.v1+json",
|
||||||
|
"size":1473,
|
||||||
|
"digest":"sha256:c54a2cc56cbb2f04003c1cd4507e118af7c0d340fe7e2720f70976c4b75237dc"
|
||||||
|
},
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
|
"size":974,
|
||||||
|
"digest":"sha256:c04b14da8d1441880ed3fe6106fb2cc6fa1c9661846ac0266b8a5ec8edf37b7c"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
manifest, _, err := UnMarshal(schema2.MediaTypeManifest, b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
refs := manifest.References()
|
||||||
|
if len(refs) != 1 {
|
||||||
|
t.Fatalf("unexpected length of reference: %d != %d", len(refs), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := "sha256:c04b14da8d1441880ed3fe6106fb2cc6fa1c9661846ac0266b8a5ec8edf37b7c"
|
||||||
|
if refs[0].Digest.String() != digest {
|
||||||
|
t.Errorf("unexpected digest: %s != %s", refs[0].Digest.String(), digest)
|
||||||
|
}
|
||||||
|
}
|
@ -48,11 +48,6 @@ func NewRegistry(endpoint string, client *http.Client) (*Registry, error) {
|
|||||||
|
|
||||||
// NewRegistryWithModifiers returns an instance of Registry according to the modifiers
|
// NewRegistryWithModifiers returns an instance of Registry according to the modifiers
|
||||||
func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modifier) (*Registry, error) {
|
func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modifier) (*Registry, error) {
|
||||||
u, err := utils.ParseEndpoint(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t := &http.Transport{
|
t := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
InsecureSkipVerify: insecure,
|
InsecureSkipVerify: insecure,
|
||||||
@ -61,12 +56,9 @@ func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modif
|
|||||||
|
|
||||||
transport := NewTransport(t, modifiers...)
|
transport := NewTransport(t, modifiers...)
|
||||||
|
|
||||||
return &Registry{
|
return NewRegistry(endpoint, &http.Client{
|
||||||
Endpoint: u,
|
Transport: transport,
|
||||||
client: &http.Client{
|
})
|
||||||
Transport: transport,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catalog ...
|
// Catalog ...
|
||||||
|
150
utils/registry/registry_test.go
Normal file
150
utils/registry/registry_test.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
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 registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/utils/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewRegistryWithModifiers(t *testing.T) {
|
||||||
|
_, err := NewRegistryWithModifiers("http://registry.org", false, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("fail to crearte client of registry: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPing(t *testing.T) {
|
||||||
|
server := test.NewServer(
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "GET",
|
||||||
|
Pattern: "/v2/",
|
||||||
|
Handler: test.Handler(nil),
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := newRegistryClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client for registry: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = client.Ping(); err != nil {
|
||||||
|
t.Errorf("failed to ping registry: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCatalog(t *testing.T) {
|
||||||
|
repositories := make([]string, 0, 1001)
|
||||||
|
for i := 0; i < 1001; i++ {
|
||||||
|
repositories = append(repositories, strconv.Itoa(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
q := r.URL.Query()
|
||||||
|
last := q.Get("last")
|
||||||
|
n, err := strconv.Atoi(q.Get("n"))
|
||||||
|
if err != nil || n <= 0 {
|
||||||
|
n = 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
length := len(repositories)
|
||||||
|
|
||||||
|
begin := length
|
||||||
|
if len(last) == 0 {
|
||||||
|
begin = 0
|
||||||
|
} else {
|
||||||
|
for i, repository := range repositories {
|
||||||
|
if repository == last {
|
||||||
|
begin = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end := begin + n
|
||||||
|
if end > length {
|
||||||
|
end = length
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set(http.CanonicalHeaderKey("Content-Type"), "application/json")
|
||||||
|
if end < length {
|
||||||
|
u, err := url.Parse("/v2/_catalog")
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
values := u.Query()
|
||||||
|
values.Add("last", repositories[end-1])
|
||||||
|
values.Add("n", strconv.Itoa(n))
|
||||||
|
|
||||||
|
u.RawQuery = values.Encode()
|
||||||
|
|
||||||
|
link := fmt.Sprintf("<%s>; rel=\"next\"", u.String())
|
||||||
|
w.Header().Set(http.CanonicalHeaderKey("link"), link)
|
||||||
|
}
|
||||||
|
|
||||||
|
repos := struct {
|
||||||
|
Repositories []string `json:"repositories"`
|
||||||
|
}{
|
||||||
|
Repositories: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if begin < length {
|
||||||
|
repos.Repositories = repositories[begin:end]
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(repos)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(b)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
server := test.NewServer(
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "GET",
|
||||||
|
Pattern: "/v2/_catalog",
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := newRegistryClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client for registry: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
repos, err := client.Catalog()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to catalog repositories: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(repos) != len(repositories) {
|
||||||
|
t.Errorf("unexpected length of repositories: %d != %d", len(repos), len(repositories))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRegistryClient(url string) (*Registry, error) {
|
||||||
|
return NewRegistry(url, &http.Client{})
|
||||||
|
}
|
@ -61,13 +61,6 @@ func NewRepository(name, endpoint string, client *http.Client) (*Repository, err
|
|||||||
|
|
||||||
// NewRepositoryWithModifiers returns an instance of Repository according to the modifiers
|
// NewRepositoryWithModifiers returns an instance of Repository according to the modifiers
|
||||||
func NewRepositoryWithModifiers(name, endpoint string, insecure bool, modifiers ...Modifier) (*Repository, error) {
|
func NewRepositoryWithModifiers(name, endpoint string, insecure bool, modifiers ...Modifier) (*Repository, error) {
|
||||||
name = strings.TrimSpace(name)
|
|
||||||
|
|
||||||
u, err := utils.ParseEndpoint(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t := &http.Transport{
|
t := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
InsecureSkipVerify: insecure,
|
InsecureSkipVerify: insecure,
|
||||||
@ -76,13 +69,9 @@ func NewRepositoryWithModifiers(name, endpoint string, insecure bool, modifiers
|
|||||||
|
|
||||||
transport := NewTransport(t, modifiers...)
|
transport := NewTransport(t, modifiers...)
|
||||||
|
|
||||||
return &Repository{
|
return NewRepository(name, endpoint, &http.Client{
|
||||||
Name: name,
|
Transport: transport,
|
||||||
Endpoint: u,
|
})
|
||||||
client: &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseError(err error) error {
|
func parseError(err error) error {
|
||||||
@ -347,7 +336,7 @@ func (r *Repository) PullBlob(digest string) (size int64, data io.ReadCloser, er
|
|||||||
data = resp.Body
|
data = resp.Body
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// can not close the connect if the status code is 200
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
@ -428,7 +417,6 @@ func (r *Repository) PushBlob(digest string, size int64, data io.Reader) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.monolithicBlobUpload(location, digest, size, data)
|
return r.monolithicBlobUpload(location, digest, size, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,5 +470,12 @@ func buildInitiateBlobUploadURL(endpoint, repoName string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildMonolithicBlobUploadURL(location, digest string) string {
|
func buildMonolithicBlobUploadURL(location, digest string) string {
|
||||||
return fmt.Sprintf("%s&digest=%s", location, digest)
|
query := ""
|
||||||
|
if strings.ContainsRune(location, '?') {
|
||||||
|
query = "&"
|
||||||
|
} else {
|
||||||
|
query = "?"
|
||||||
|
}
|
||||||
|
query += fmt.Sprintf("digest=%s", digest)
|
||||||
|
return fmt.Sprintf("%s%s", location, query)
|
||||||
}
|
}
|
||||||
|
@ -16,179 +16,395 @@
|
|||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/url"
|
||||||
"os"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/vmware/harbor/utils/registry/auth"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||||
|
"github.com/vmware/harbor/utils/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
username = "user"
|
repository = "library/hello-world"
|
||||||
password = "P@ssw0rd"
|
tag = "latest"
|
||||||
repo = "samalba/my-app"
|
|
||||||
tags = tagResp{Tags: []string{"1.0", "2.0", "3.0"}}
|
mediaType = schema2.MediaTypeManifest
|
||||||
validToken = "valid_token"
|
manifest = []byte("manifest")
|
||||||
invalidToken = "invalid_token"
|
|
||||||
credential auth.Credential
|
blob = []byte("blob")
|
||||||
registryServer *httptest.Server
|
|
||||||
tokenServer *httptest.Server
|
uuid = "0663ff44-63bb-11e6-8b77-86f30ca893d3"
|
||||||
repositoryClient *Repository
|
|
||||||
|
digest = "sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tagResp struct {
|
func TestNewRepositoryWithModifiers(t *testing.T) {
|
||||||
Tags []string `json:"tags"`
|
_, err := NewRepositoryWithModifiers("library/ubuntu",
|
||||||
}
|
"http://registry.org", true, nil)
|
||||||
|
if err != nil {
|
||||||
func TestMain(m *testing.M) {
|
t.Fatalf("failed to create client for repository: %v", err)
|
||||||
//log.SetLevel(log.DebugLevel)
|
|
||||||
credential = auth.NewBasicAuthCredential(username, password)
|
|
||||||
|
|
||||||
tokenServer = initTokenServer()
|
|
||||||
defer tokenServer.Close()
|
|
||||||
|
|
||||||
registryServer = initRegistryServer()
|
|
||||||
defer registryServer.Close()
|
|
||||||
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
|
|
||||||
func initRegistryServer() *httptest.Server {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/v2/", servePing)
|
|
||||||
mux.HandleFunc(fmt.Sprintf("/v2/%s/tags/list", repo), serveTaglisting)
|
|
||||||
|
|
||||||
return httptest.NewServer(mux)
|
|
||||||
}
|
|
||||||
|
|
||||||
//response ping request: http://registry/v2
|
|
||||||
func servePing(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if !isTokenValid(r) {
|
|
||||||
challenge(w)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveTaglisting(w http.ResponseWriter, r *http.Request) {
|
func TestBlobExist(t *testing.T) {
|
||||||
if !isTokenValid(r) {
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
challenge(w)
|
path := r.URL.Path
|
||||||
return
|
dgt := path[strings.LastIndex(path, "/")+1 : len(path)]
|
||||||
}
|
if dgt == digest {
|
||||||
|
w.Header().Add(http.CanonicalHeaderKey("Content-Length"), strconv.Itoa(len(blob)))
|
||||||
if err := json.NewEncoder(w).Encode(tags); err != nil {
|
w.Header().Add(http.CanonicalHeaderKey("Docker-Content-Digest"), digest)
|
||||||
w.Write([]byte(err.Error()))
|
w.Header().Add(http.CanonicalHeaderKey("Content-Type"), "application/octet-stream")
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func isTokenValid(r *http.Request) bool {
|
|
||||||
valid := false
|
|
||||||
auth := r.Header.Get(http.CanonicalHeaderKey("Authorization"))
|
|
||||||
if len(auth) != 0 {
|
|
||||||
auth = strings.TrimSpace(auth)
|
|
||||||
index := strings.Index(auth, "Bearer")
|
|
||||||
token := auth[index+6:]
|
|
||||||
token = strings.TrimSpace(token)
|
|
||||||
if token == validToken {
|
|
||||||
valid = true
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return valid
|
|
||||||
}
|
|
||||||
|
|
||||||
func challenge(w http.ResponseWriter) {
|
w.WriteHeader(http.StatusNotFound)
|
||||||
challenge := "Bearer realm=\"" + tokenServer.URL + "/service/token\",service=\"token-service\""
|
|
||||||
w.Header().Set("Www-Authenticate", challenge)
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func initTokenServer() *httptest.Server {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/service/token", serveToken)
|
|
||||||
|
|
||||||
return httptest.NewServer(mux)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveToken(w http.ResponseWriter, r *http.Request) {
|
|
||||||
u, p, ok := r.BasicAuth()
|
|
||||||
if !ok || u != username || p != password {
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make(map[string]interface{})
|
server := test.NewServer(
|
||||||
result["token"] = validToken
|
&test.RequestHandlerMapping{
|
||||||
result["expires_in"] = 300
|
Method: "HEAD",
|
||||||
result["issued_at"] = time.Now().Format(time.RFC3339)
|
Pattern: fmt.Sprintf("/v2/%s/blobs/", repository),
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
encoder := json.NewEncoder(w)
|
client, err := newRepository(server.URL)
|
||||||
if err := encoder.Encode(result); err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
err = parseError(err)
|
||||||
w.Write([]byte(err.Error()))
|
t.Fatalf("failed to create client for repository: %v", err)
|
||||||
return
|
}
|
||||||
|
|
||||||
|
exist, err := client.BlobExist(digest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to check the existence of blob: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exist {
|
||||||
|
t.Errorf("blob should exist on registry, but it does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
exist, err = client.BlobExist("invalid_digest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to check the existence of blob: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exist {
|
||||||
|
t.Errorf("blob should not exist on registry, but it exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPullBlob(t *testing.T) {
|
||||||
|
handler := test.Handler(&test.Response{
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Length": strconv.Itoa(len(blob)),
|
||||||
|
"Docker-Content-Digest": digest,
|
||||||
|
"Content-Type": "application/octet-stream",
|
||||||
|
},
|
||||||
|
Body: blob,
|
||||||
|
})
|
||||||
|
|
||||||
|
server := test.NewServer(
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "GET",
|
||||||
|
Pattern: fmt.Sprintf("/v2/%s/blobs/%s", repository, digest),
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := newRepository(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client for repository: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
size, reader, err := client.PullBlob(digest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to pull blob: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if size != int64(len(blob)) {
|
||||||
|
t.Errorf("unexpected size of blob: %d != %d", size, len(blob))
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read from reader: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.Compare(b, blob) != 0 {
|
||||||
|
t.Errorf("unexpected blob: %s != %s", string(b), string(blob))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPushBlob(t *testing.T) {
|
||||||
|
location := ""
|
||||||
|
initUploadHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add(http.CanonicalHeaderKey("Content-Length"), "0")
|
||||||
|
w.Header().Add(http.CanonicalHeaderKey("Location"), location)
|
||||||
|
w.Header().Add(http.CanonicalHeaderKey("Range"), "0-0")
|
||||||
|
w.Header().Add(http.CanonicalHeaderKey("Docker-Upload-UUID"), uuid)
|
||||||
|
w.WriteHeader(http.StatusAccepted)
|
||||||
|
}
|
||||||
|
|
||||||
|
monolithicUploadHandler := test.Handler(&test.Response{
|
||||||
|
StatusCode: http.StatusCreated,
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Length": "0",
|
||||||
|
"Location": fmt.Sprintf("/v2/%s/blobs/%s", repository, digest),
|
||||||
|
"Docker-Content-Digest": digest,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
server := test.NewServer(
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "POST",
|
||||||
|
Pattern: fmt.Sprintf("/v2/%s/blobs/uploads/", repository),
|
||||||
|
Handler: initUploadHandler,
|
||||||
|
},
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "PUT",
|
||||||
|
Pattern: fmt.Sprintf("/v2/%s/blobs/uploads/%s", repository, uuid),
|
||||||
|
Handler: monolithicUploadHandler,
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
location = fmt.Sprintf("%s/v2/%s/blobs/uploads/%s", server.URL, repository, uuid)
|
||||||
|
|
||||||
|
client, err := newRepository(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client for repository: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = client.PushBlob(digest, int64(len(blob)), bytes.NewReader(blob)); err != nil {
|
||||||
|
t.Fatalf("failed to push blob: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteBlob(t *testing.T) {
|
||||||
|
handler := test.Handler(&test.Response{
|
||||||
|
StatusCode: http.StatusAccepted,
|
||||||
|
})
|
||||||
|
|
||||||
|
server := test.NewServer(
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "DELETE",
|
||||||
|
Pattern: fmt.Sprintf("/v2/%s/blobs/%s", repository, digest),
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := newRepository(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client for repository: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = client.DeleteBlob(digest); err != nil {
|
||||||
|
t.Fatalf("failed to delete blob: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManifestExist(t *testing.T) {
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
path := r.URL.Path
|
||||||
|
tg := path[strings.LastIndex(path, "/")+1 : len(path)]
|
||||||
|
if tg == tag {
|
||||||
|
w.Header().Add(http.CanonicalHeaderKey("Docker-Content-Digest"), digest)
|
||||||
|
w.Header().Add(http.CanonicalHeaderKey("Content-Type"), mediaType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := test.NewServer(
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "HEAD",
|
||||||
|
Pattern: fmt.Sprintf("/v2/%s/manifests/%s", repository, tag),
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := newRepository(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client for repository: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d, exist, err := client.ManifestExist(tag)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to check the existence of manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exist || d != digest {
|
||||||
|
t.Errorf("manifest should exist on registry, but it does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exist, err = client.ManifestExist("invalid_tag")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to check the existence of manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exist {
|
||||||
|
t.Errorf("manifest should not exist on registry, but it exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPullManifest(t *testing.T) {
|
||||||
|
handler := test.Handler(&test.Response{
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Docker-Content-Digest": digest,
|
||||||
|
"Content-Type": mediaType,
|
||||||
|
},
|
||||||
|
Body: manifest,
|
||||||
|
})
|
||||||
|
|
||||||
|
server := test.NewServer(
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "GET",
|
||||||
|
Pattern: fmt.Sprintf("/v2/%s/manifests/%s", repository, tag),
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := newRepository(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client for repository: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d, md, payload, err := client.PullManifest(tag, []string{mediaType})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to pull manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d != digest {
|
||||||
|
t.Errorf("unexpected digest of manifest: %s != %s", d, digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if md != mediaType {
|
||||||
|
t.Errorf("unexpected media type of manifest: %s != %s", md, mediaType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.Compare(payload, manifest) != 0 {
|
||||||
|
t.Errorf("unexpected manifest: %s != %s", string(payload), string(manifest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPushManifest(t *testing.T) {
|
||||||
|
handler := test.Handler(&test.Response{
|
||||||
|
StatusCode: http.StatusCreated,
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Length": "0",
|
||||||
|
"Docker-Content-Digest": digest,
|
||||||
|
"Location": "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
server := test.NewServer(
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "PUT",
|
||||||
|
Pattern: fmt.Sprintf("/v2/%s/manifests/%s", repository, tag),
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := newRepository(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client for repository: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := client.PushManifest(tag, mediaType, manifest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to pull manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d != digest {
|
||||||
|
t.Errorf("unexpected digest of manifest: %s != %s", d, digest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteTag(t *testing.T) {
|
||||||
|
manifestExistHandler := test.Handler(&test.Response{
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Docker-Content-Digest": digest,
|
||||||
|
"Content-Type": mediaType,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
deleteManifestandler := test.Handler(&test.Response{
|
||||||
|
StatusCode: http.StatusAccepted,
|
||||||
|
})
|
||||||
|
|
||||||
|
server := test.NewServer(
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "HEAD",
|
||||||
|
Pattern: fmt.Sprintf("/v2/%s/manifests/", repository),
|
||||||
|
Handler: manifestExistHandler,
|
||||||
|
},
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "DELETE",
|
||||||
|
Pattern: fmt.Sprintf("/v2/%s/manifests/%s", repository, digest),
|
||||||
|
Handler: deleteManifestandler,
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := newRepository(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client for repository: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = client.DeleteTag(tag); err != nil {
|
||||||
|
t.Fatalf("failed to delete tag: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListTag(t *testing.T) {
|
func TestListTag(t *testing.T) {
|
||||||
client, err := newRepositoryClient(registryServer.URL, true, credential,
|
handler := test.Handler(&test.Response{
|
||||||
repo, "repository", repo, "pull", "push", "*")
|
Headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
Body: []byte(fmt.Sprintf("{\"name\": \"%s\",\"tags\": [\"%s\"]}", repository, tag)),
|
||||||
|
})
|
||||||
|
|
||||||
|
server := test.NewServer(
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "GET",
|
||||||
|
Pattern: fmt.Sprintf("/v2/%s/tags/list", repository),
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := newRepository(server.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Fatalf("failed to create client for repository: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
list, err := client.ListTag()
|
tags, err := client.ListTag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Fatalf("failed to list tags: %v", err)
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(list) != len(tags.Tags) {
|
|
||||||
t.Errorf("expected length: %d, actual length: %d", len(tags.Tags), len(list))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
if len(tags) != 1 {
|
||||||
|
t.Fatalf("unexpected length of tags: %d != %d", len(tags), 1)
|
||||||
func TestListTagWithInvalidCredential(t *testing.T) {
|
|
||||||
credential := auth.NewBasicAuthCredential(username, "wrong_password")
|
|
||||||
client, err := newRepositoryClient(registryServer.URL, true, credential,
|
|
||||||
repo, "repository", repo, "pull", "push", "*")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = client.ListTag(); err != nil {
|
if tags[0] != tag {
|
||||||
e, ok := err.(*registry_error.Error)
|
t.Errorf("unexpected tag: %s != %s", tags[0], tag)
|
||||||
if ok && e.StatusCode == http.StatusUnauthorized {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential, repository, scopeType, scopeName string,
|
func TestParseError(t *testing.T) {
|
||||||
scopeActions ...string) (*Repository, error) {
|
err := &url.Error{
|
||||||
|
Err: ®istry_error.Error{},
|
||||||
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
|
|
||||||
|
|
||||||
store, err := auth.NewAuthorizerStore(endpoint, true, authorizer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
e := parseError(err)
|
||||||
client, err := NewRepositoryWithModifiers(repository, endpoint, insecure, store)
|
if _, ok := e.(*registry_error.Error); !ok {
|
||||||
if err != nil {
|
t.Errorf("error type does not match registry error")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return client, nil
|
}
|
||||||
|
|
||||||
|
func newRepository(endpoint string) (*Repository, error) {
|
||||||
|
return NewRepository(repository, endpoint, &http.Client{})
|
||||||
}
|
}
|
||||||
|
60
utils/registry/transport_test.go
Normal file
60
utils/registry/transport_test.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/utils/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
type simpleModifier struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *simpleModifier) Modify(req *http.Request) error {
|
||||||
|
req.Header.Set("Authorization", "token")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoundTrip(t *testing.T) {
|
||||||
|
server := test.NewServer(
|
||||||
|
&test.RequestHandlerMapping{
|
||||||
|
Method: "GET",
|
||||||
|
Pattern: "/",
|
||||||
|
Handler: test.Handler(nil),
|
||||||
|
})
|
||||||
|
transport := NewTransport(&http.Transport{}, &simpleModifier{})
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", fmt.Sprintf("%s/", server.URL), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := client.Do(req); err != nil {
|
||||||
|
t.Fatalf("failed to send request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
header := req.Header.Get("Authorization")
|
||||||
|
if header != "token" {
|
||||||
|
t.Errorf("unexpected header: %s != %s", header, "token")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
88
utils/test/test.go
Normal file
88
utils/test/test.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestHandlerMapping is a mapping between request and its handler
|
||||||
|
type RequestHandlerMapping struct {
|
||||||
|
// Method is the method the request used
|
||||||
|
Method string
|
||||||
|
// Pattern is the pattern the request must match
|
||||||
|
Pattern string
|
||||||
|
// Handler is the handler which handles the request
|
||||||
|
Handler func(http.ResponseWriter, *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP ...
|
||||||
|
func (rhm *RequestHandlerMapping) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if len(rhm.Method) != 0 && r.Method != strings.ToUpper(rhm.Method) {
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rhm.Handler(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response is a response used for unit test
|
||||||
|
type Response struct {
|
||||||
|
// StatusCode is the status code of the response
|
||||||
|
StatusCode int
|
||||||
|
// Headers are the headers of the response
|
||||||
|
Headers map[string]string
|
||||||
|
// Boby is the body of the response
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler returns a handler function which handle requst according to
|
||||||
|
// the response provided
|
||||||
|
func Handler(resp *Response) func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if resp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range resp.Headers {
|
||||||
|
w.Header().Add(http.CanonicalHeaderKey(k), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == 0 {
|
||||||
|
resp.StatusCode = http.StatusOK
|
||||||
|
}
|
||||||
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
|
||||||
|
if len(resp.Body) != 0 {
|
||||||
|
io.Copy(w, bytes.NewReader(resp.Body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer creates a HTTP server for unit test
|
||||||
|
func NewServer(mappings ...*RequestHandlerMapping) *httptest.Server {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
for _, mapping := range mappings {
|
||||||
|
mux.Handle(mapping.Pattern, mapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
return httptest.NewServer(mux)
|
||||||
|
}
|
@ -20,17 +20,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Repository holds information about repository
|
|
||||||
type Repository struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProject parses the repository and return the name of project.
|
|
||||||
func (r *Repository) GetProject() string {
|
|
||||||
project, _ := ParseRepository(r.Name)
|
|
||||||
return project
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatEndpoint formats endpoint
|
// FormatEndpoint formats endpoint
|
||||||
func FormatEndpoint(endpoint string) string {
|
func FormatEndpoint(endpoint string) string {
|
||||||
endpoint = strings.TrimSpace(endpoint)
|
endpoint = strings.TrimSpace(endpoint)
|
||||||
|
@ -16,10 +16,41 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestParseEndpoint(t *testing.T) {
|
||||||
|
endpoint := "example.com"
|
||||||
|
u, err := ParseEndpoint(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse endpoint %s: %v", endpoint, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.String() != "http://example.com" {
|
||||||
|
t.Errorf("unexpected endpoint: %s != %s", endpoint, "http://example.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint = "https://example.com"
|
||||||
|
u, err = ParseEndpoint(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse endpoint %s: %v", endpoint, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.String() != "https://example.com" {
|
||||||
|
t.Errorf("unexpected endpoint: %s != %s", endpoint, "https://example.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint = " example.com/ "
|
||||||
|
u, err = ParseEndpoint(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse endpoint %s: %v", endpoint, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.String() != "http://example.com" {
|
||||||
|
t.Errorf("unexpected endpoint: %s != %s", endpoint, "http://example.com")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseRepository(t *testing.T) {
|
func TestParseRepository(t *testing.T) {
|
||||||
@ -61,3 +92,45 @@ func TestParseRepository(t *testing.T) {
|
|||||||
t.Errorf("unexpected rest: [%s] != [%s]", rest, "")
|
t.Errorf("unexpected rest: [%s] != [%s]", rest, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEncrypt(t *testing.T) {
|
||||||
|
content := "content"
|
||||||
|
salt := "salt"
|
||||||
|
result := Encrypt(content, salt)
|
||||||
|
|
||||||
|
if result != "dc79e76c88415c97eb089d9cc80b4ab0" {
|
||||||
|
t.Errorf("unexpected result: %s != %s", result, "dc79e76c88415c97eb089d9cc80b4ab0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReversibleEncrypt(t *testing.T) {
|
||||||
|
password := "password"
|
||||||
|
key := "1234567890123456"
|
||||||
|
encrypted, err := ReversibleEncrypt(password, key)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to encrypt: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("Encrypted password: %s", encrypted)
|
||||||
|
if encrypted == password {
|
||||||
|
t.Errorf("Encrypted password is identical to the original")
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(encrypted, EncryptHeaderV1) {
|
||||||
|
t.Errorf("Encrypted password does not have v1 header")
|
||||||
|
}
|
||||||
|
decrypted, err := ReversibleDecrypt(encrypted, key)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to decrypt: %v", err)
|
||||||
|
}
|
||||||
|
if decrypted != password {
|
||||||
|
t.Errorf("decrypted password: %s, is not identical to original", decrypted)
|
||||||
|
}
|
||||||
|
//Test b64 for backward compatibility
|
||||||
|
b64password := base64.StdEncoding.EncodeToString([]byte(password))
|
||||||
|
decrypted, err = ReversibleDecrypt(b64password, key)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to decrypt: %v", err)
|
||||||
|
}
|
||||||
|
if decrypted != password {
|
||||||
|
t.Errorf("decrypted password: %s, is not identical to original", decrypted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -191,4 +191,7 @@
|
|||||||
<script src="/static/resources/js/components/inline-help/inline-help.directive.js"></script>
|
<script src="/static/resources/js/components/inline-help/inline-help.directive.js"></script>
|
||||||
|
|
||||||
<script src="/static/resources/js/components/dismissable-alerts/dismissable-alerts.module.js"></script>
|
<script src="/static/resources/js/components/dismissable-alerts/dismissable-alerts.module.js"></script>
|
||||||
<script src="/static/resources/js/components/dismissable-alerts/dismissable-alerts.directive.js"></script>
|
<script src="/static/resources/js/components/dismissable-alerts/dismissable-alerts.directive.js"></script>
|
||||||
|
|
||||||
|
<script src="/static/resources/js/components/paginator/paginator.module.js"></script>
|
||||||
|
<script src="/static/resources/js/components/paginator/paginator.directive.js"></script>
|
Loading…
Reference in New Issue
Block a user