mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 02:35:17 +01:00
Merge pull request #727 from ywk253100/m_t
Merge lost commits into branch dev
This commit is contained in:
commit
4e065bba95
41
.travis.yml
41
.travis.yml
@ -9,25 +9,25 @@ go_import_path: github.com/vmware/harbor
|
||||
|
||||
services:
|
||||
- docker
|
||||
- mysql
|
||||
|
||||
dist: trusty
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- mysql-server-5.6
|
||||
- mysql-client-core-5.6
|
||||
- mysql-client-5.6
|
||||
|
||||
env:
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_PORT: 3306
|
||||
DB_USR: root
|
||||
DB_PWD:
|
||||
DB_PWD: root123
|
||||
MYSQL_HOST: localhost
|
||||
MYSQL_PORT: 3306
|
||||
MYSQL_USR: root
|
||||
MYSQL_PWD: root123
|
||||
DOCKER_COMPOSE_VERSION: 1.7.1
|
||||
HARBOR_ADMIN: admin
|
||||
HARBOR_ADMIN_PASSWD: Harbor12345
|
||||
UI_SECRET: tempString
|
||||
MAX_JOB_WORKERS: 3
|
||||
SECRET_KEY: 1234567890123456
|
||||
AUTH_MODE: db_auth
|
||||
|
||||
before_install:
|
||||
- sudo ./tests/hostcfg.sh
|
||||
@ -54,7 +54,7 @@ install:
|
||||
- go get -d github.com/go-sql-driver/mysql
|
||||
- go get github.com/golang/lint/golint
|
||||
- go get github.com/GeertJohan/fgt
|
||||
# - sudo apt-get install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" docker-engine=1.11.1-0~trusty
|
||||
|
||||
# - sudo rm /usr/local/bin/docker-compose
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||
- chmod +x docker-compose
|
||||
@ -63,15 +63,26 @@ install:
|
||||
- sudo service docker restart
|
||||
- go get github.com/dghubble/sling
|
||||
- go get github.com/stretchr/testify
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
|
||||
before_script:
|
||||
# create tables and load data
|
||||
- mysql < ./Deploy/db/registry.sql -uroot --verbose
|
||||
# - mysql < ./Deploy/db/registry.sql -uroot --verbose
|
||||
|
||||
script:
|
||||
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 fgt golint
|
||||
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 go vet
|
||||
- sudo ./tests/testprepare.sh
|
||||
- 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
|
||||
- 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
|
||||
|
||||
@ -79,7 +90,5 @@ script:
|
||||
- go run tests/startuptest.go http://localhost/
|
||||
- go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD}
|
||||
|
||||
|
||||
# test for API
|
||||
- sudo ./tests/testprepare.sh
|
||||
- go test -v ./tests/apitests
|
||||
# - sudo ./tests/testprepare.sh
|
||||
# - go test -v ./tests/apitests
|
||||
|
@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
echo "mode: set" >>profile.cov
|
||||
for dir in $(go list ./... | grep -v -E 'vendor|tests')
|
||||
do
|
||||
|
@ -61,7 +61,9 @@ insert into user (username, email, password, realname, comment, deleted, sysadmi
|
||||
create table project (
|
||||
project_id int NOT NULL AUTO_INCREMENT,
|
||||
owner_id int NOT NULL,
|
||||
name varchar (30) NOT NULL,
|
||||
# The max length of name controlled by API is 30,
|
||||
# and 11 bytes is reserved for marking the deleted project.
|
||||
name varchar (41) NOT NULL,
|
||||
creation_time timestamp,
|
||||
update_time timestamp,
|
||||
deleted tinyint (1) DEFAULT 0 NOT NULL,
|
||||
@ -110,6 +112,7 @@ create table replication_policy (
|
||||
target_id int NOT NULL,
|
||||
enabled tinyint(1) NOT NULL DEFAULT 1,
|
||||
description text,
|
||||
deleted tinyint (1) DEFAULT 0 NOT NULL,
|
||||
cron_str varchar(256),
|
||||
start_time timestamp NULL,
|
||||
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||
@ -122,7 +125,7 @@ create table replication_target (
|
||||
name varchar(64),
|
||||
url varchar(64),
|
||||
username varchar(40),
|
||||
password varchar(40),
|
||||
password varchar(128),
|
||||
/*
|
||||
target_type indicates the type of target registry,
|
||||
0 means it's a harbor instance,
|
||||
|
@ -44,6 +44,10 @@ use_compressed_js = on
|
||||
#Maximum number of job workers in job service
|
||||
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
|
||||
token_expiration = 30
|
||||
|
||||
|
@ -1,12 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
echo "This shell will minify the Javascript in Harbor project."
|
||||
echo "Usage: #jsminify [src] [dest]"
|
||||
echo "Usage: #jsminify [src] [dest] [basedir]"
|
||||
|
||||
#prepare workspace
|
||||
rm -rf $2 /tmp/harbor.app.temp.js
|
||||
|
||||
BASEPATH=/go/bin
|
||||
if [ -z $3 ]
|
||||
then
|
||||
BASEPATH=/go/bin
|
||||
else
|
||||
BASEPATH=$3
|
||||
fi
|
||||
|
||||
#concat the js files from js include file
|
||||
echo "Concat js files..."
|
||||
|
||||
@ -20,6 +26,12 @@ do
|
||||
fi
|
||||
done
|
||||
|
||||
# If you want run this script on Mac OS X,
|
||||
# I suggest you install gnu-sed (whth --with-default-names option).
|
||||
# $ brew install gnu-sed --with-default-names
|
||||
# Reference:
|
||||
# http://stackoverflow.com/a/27834828/3167471
|
||||
|
||||
#remove space
|
||||
echo "Remove space.."
|
||||
sed 's/ \+/ /g' -i /tmp/harbor.app.temp.js
|
||||
|
@ -16,6 +16,10 @@ if sys.version_info[:3][0] == 3:
|
||||
import configparser as ConfigParser
|
||||
import io as StringIO
|
||||
|
||||
def validate(conf):
|
||||
if len(conf.get("configuration", "secret_key")) != 16:
|
||||
raise Exception("Error: The length of secret key has to be 16 characters!")
|
||||
|
||||
#Read configurations
|
||||
conf = StringIO.StringIO()
|
||||
conf.write("[configuration]\n")
|
||||
@ -24,6 +28,8 @@ conf.seek(0, os.SEEK_SET)
|
||||
rcp = ConfigParser.RawConfigParser()
|
||||
rcp.readfp(conf)
|
||||
|
||||
validate(rcp)
|
||||
|
||||
hostname = rcp.get("configuration", "hostname")
|
||||
ui_url = rcp.get("configuration", "ui_url_protocol") + "://" + hostname
|
||||
email_server = rcp.get("configuration", "email_server")
|
||||
@ -50,6 +56,7 @@ crt_email = rcp.get("configuration", "crt_email")
|
||||
max_job_workers = rcp.get("configuration", "max_job_workers")
|
||||
token_expiration = rcp.get("configuration", "token_expiration")
|
||||
verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
|
||||
secret_key = rcp.get("configuration", "secret_key")
|
||||
########
|
||||
|
||||
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,
|
||||
use_compressed_js=use_compressed_js,
|
||||
ui_secret=ui_secret,
|
||||
secret_key=secret_key,
|
||||
verify_remote_cert=verify_remote_cert,
|
||||
token_expiration=token_expiration)
|
||||
|
||||
@ -128,6 +136,7 @@ render(os.path.join(templates_dir, "jobservice", "env"),
|
||||
db_password=db_password,
|
||||
ui_secret=ui_secret,
|
||||
max_job_workers=max_job_workers,
|
||||
secret_key=secret_key,
|
||||
ui_url=ui_url,
|
||||
verify_remote_cert=verify_remote_cert)
|
||||
|
||||
|
@ -3,6 +3,7 @@ MYSQL_PORT=3306
|
||||
MYSQL_USR=root
|
||||
MYSQL_PWD=$db_password
|
||||
UI_SECRET=$ui_secret
|
||||
SECRET_KEY=$secret_key
|
||||
CONFIG_PATH=/etc/jobservice/app.conf
|
||||
REGISTRY_URL=http://registry:5000
|
||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||
|
@ -12,6 +12,7 @@ AUTH_MODE=$auth_mode
|
||||
LDAP_URL=$ldap_url
|
||||
LDAP_BASE_DN=$ldap_basedn
|
||||
UI_SECRET=$ui_secret
|
||||
SECRET_KEY=$secret_key
|
||||
SELF_REGISTRATION=$self_registration
|
||||
USE_COMPRESSED_JS=$use_compressed_js
|
||||
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"`
|
||||
}
|
||||
|
||||
// Prepare ...
|
||||
func (rj *ReplicationJob) Prepare() {
|
||||
rj.authenticate()
|
||||
}
|
||||
|
||||
func (rj *ReplicationJob) authenticate() {
|
||||
cookie, err := rj.Ctx.Request.Cookie(models.UISecretCookie)
|
||||
if err != nil && err != http.ErrNoCookie {
|
||||
log.Errorf("failed to get cookie %s: %v", models.UISecretCookie, err)
|
||||
rj.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
if err == http.ErrNoCookie {
|
||||
rj.CustomAbort(http.StatusUnauthorized, "")
|
||||
}
|
||||
|
||||
if cookie.Value != config.UISecret() {
|
||||
rj.CustomAbort(http.StatusForbidden, "")
|
||||
}
|
||||
}
|
||||
|
||||
// Post creates replication jobs according to the policy.
|
||||
func (rj *ReplicationJob) Post() {
|
||||
var data ReplicationReq
|
||||
|
@ -33,6 +33,7 @@ type ProjectAPI struct {
|
||||
BaseAPI
|
||||
userID int
|
||||
projectID int64
|
||||
projectName string
|
||||
}
|
||||
|
||||
type projectReq struct {
|
||||
@ -54,14 +55,16 @@ func (p *ProjectAPI) Prepare() {
|
||||
log.Errorf("Error parsing project id: %s, error: %v", idStr, err)
|
||||
p.CustomAbort(http.StatusBadRequest, "invalid project id")
|
||||
}
|
||||
exist, err := dao.ProjectExists(p.projectID)
|
||||
|
||||
project, err := dao.GetProjectByID(p.projectID)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in ProjectExists, error: %v", err)
|
||||
log.Errorf("failed to get project %d: %v", p.projectID, err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if !exist {
|
||||
if project == nil {
|
||||
p.CustomAbort(http.StatusNotFound, fmt.Sprintf("project does not exist, id: %v", p.projectID))
|
||||
}
|
||||
p.projectName = project.Name
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,6 +155,71 @@ func (p *ProjectAPI) Get() {
|
||||
p.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete ...
|
||||
func (p *ProjectAPI) Delete() {
|
||||
if p.projectID == 0 {
|
||||
p.CustomAbort(http.StatusBadRequest, "project ID is required")
|
||||
}
|
||||
|
||||
userID := p.ValidateUser()
|
||||
|
||||
if !hasProjectAdminRole(userID, p.projectID) {
|
||||
p.CustomAbort(http.StatusForbidden, "")
|
||||
}
|
||||
|
||||
contains, err := projectContainsRepo(p.projectName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check whether project %s contains any repository: %v", p.projectName, err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
if contains {
|
||||
p.CustomAbort(http.StatusPreconditionFailed, "project contains repositores, can not be deleted")
|
||||
}
|
||||
|
||||
contains, err = projectContainsPolicy(p.projectID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check whether project %s contains any policy: %v", p.projectName, err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
if contains {
|
||||
p.CustomAbort(http.StatusPreconditionFailed, "project contains policies, can not be deleted")
|
||||
}
|
||||
|
||||
if err = dao.DeleteProject(p.projectID); err != nil {
|
||||
log.Errorf("failed to delete project %d: %v", p.projectID, err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := dao.AddAccessLog(models.AccessLog{
|
||||
UserID: userID,
|
||||
ProjectID: p.projectID,
|
||||
RepoName: p.projectName,
|
||||
Operation: "delete",
|
||||
}); err != nil {
|
||||
log.Errorf("failed to add access log: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func projectContainsRepo(name string) (bool, error) {
|
||||
repositories, err := getReposByProject(name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(repositories) > 0, nil
|
||||
}
|
||||
|
||||
func projectContainsPolicy(id int64) (bool, error) {
|
||||
policies, err := dao.GetRepPolicyByProject(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(policies) > 0, nil
|
||||
}
|
||||
|
||||
// List ...
|
||||
func (p *ProjectAPI) List() {
|
||||
var 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")
|
||||
}
|
||||
|
||||
resp, err := http.Get(buildJobLogURL(strconv.FormatInt(ra.jobID, 10)))
|
||||
req, err := http.NewRequest("GET", buildJobLogURL(strconv.FormatInt(ra.jobID, 10)), nil)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create a request: %v", err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
addAuthentication(req)
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get log for job %d: %v", ra.jobID, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
|
@ -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"
|
||||
|
||||
"github.com/vmware/harbor/utils"
|
||||
"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 {
|
||||
log.Errorf("failed to get repository from cache: %v", err)
|
||||
log.Errorf("failed to get repository: %v", err)
|
||||
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))
|
||||
|
||||
if (page-1)*pageSize > total {
|
||||
|
@ -101,18 +101,19 @@ func filterRepositories(repositories []string, projects []models.Project, keywor
|
||||
i, j := 0, 0
|
||||
result := []map[string]interface{}{}
|
||||
for i < len(repositories) && j < len(projects) {
|
||||
r := &utils.Repository{Name: repositories[i]}
|
||||
d := strings.Compare(r.GetProject(), projects[j].Name)
|
||||
r := repositories[i]
|
||||
p, _ := utils.ParseRepository(r)
|
||||
d := strings.Compare(p, projects[j].Name)
|
||||
if d < 0 {
|
||||
i++
|
||||
continue
|
||||
} else if d == 0 {
|
||||
i++
|
||||
if len(keyword) != 0 && !strings.Contains(r.Name, keyword) {
|
||||
if len(keyword) != 0 && !strings.Contains(r, keyword) {
|
||||
continue
|
||||
}
|
||||
entry := make(map[string]interface{})
|
||||
entry["repository_name"] = r.Name
|
||||
entry["repository_name"] = r
|
||||
entry["project_name"] = projects[j].Name
|
||||
entry["project_id"] = projects[j].ProjectID
|
||||
entry["project_public"] = projects[j].Public
|
||||
|
32
api/search_test.go
Normal file
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/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
@ -34,10 +35,14 @@ import (
|
||||
// TargetAPI handles request to /api/targets/ping /api/targets/{}
|
||||
type TargetAPI struct {
|
||||
BaseAPI
|
||||
secretKey string
|
||||
}
|
||||
|
||||
// Prepare validates the user
|
||||
func (t *TargetAPI) Prepare() {
|
||||
//TODO:move to config
|
||||
t.secretKey = os.Getenv("SECRET_KEY")
|
||||
|
||||
userID := t.ValidateUser()
|
||||
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||
if err != nil {
|
||||
@ -76,7 +81,7 @@ func (t *TargetAPI) Ping() {
|
||||
password = target.Password
|
||||
|
||||
if len(password) != 0 {
|
||||
password, err = utils.ReversibleDecrypt(password)
|
||||
password, err = utils.ReversibleDecrypt(password, t.secretKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt password: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
@ -136,7 +141,7 @@ func (t *TargetAPI) Get() {
|
||||
// modify other fields of target he does not need to input the password again.
|
||||
// The security issue can be fixed by enable https.
|
||||
if len(target.Password) != 0 {
|
||||
pwd, err := utils.ReversibleDecrypt(target.Password)
|
||||
pwd, err := utils.ReversibleDecrypt(target.Password, t.secretKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt password: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
@ -162,7 +167,7 @@ func (t *TargetAPI) List() {
|
||||
continue
|
||||
}
|
||||
|
||||
str, err := utils.ReversibleDecrypt(target.Password)
|
||||
str, err := utils.ReversibleDecrypt(target.Password, t.secretKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt password: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
@ -201,7 +206,11 @@ func (t *TargetAPI) Post() {
|
||||
}
|
||||
|
||||
if len(target.Password) != 0 {
|
||||
target.Password = utils.ReversibleEncrypt(target.Password)
|
||||
target.Password, err = utils.ReversibleEncrypt(target.Password, t.secretKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to encrypt password: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
}
|
||||
|
||||
id, err := dao.AddRepTarget(*target)
|
||||
@ -275,7 +284,11 @@ func (t *TargetAPI) Put() {
|
||||
target.ID = id
|
||||
|
||||
if len(target.Password) != 0 {
|
||||
target.Password = utils.ReversibleEncrypt(target.Password)
|
||||
target.Password, err = utils.ReversibleEncrypt(target.Password, t.secretKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to encrypt password: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
}
|
||||
|
||||
if err := dao.UpdateRepTarget(*target); err != nil {
|
||||
|
63
api/utils.go
63
api/utils.go
@ -26,6 +26,8 @@ import (
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
"github.com/vmware/harbor/service/cache"
|
||||
"github.com/vmware/harbor/utils"
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
)
|
||||
|
||||
@ -115,7 +117,14 @@ func TriggerReplication(policyID int64, repository string,
|
||||
|
||||
url := buildReplicationURL()
|
||||
|
||||
resp, err := http.DefaultClient.Post(url, "application/json", bytes.NewBuffer(b))
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addAuthentication(req)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -188,7 +197,16 @@ func postReplicationAction(policyID int64, acton string) error {
|
||||
|
||||
url := buildReplicationActionURL()
|
||||
|
||||
resp, err := http.DefaultClient.Post(url, "application/json", bytes.NewBuffer(b))
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addAuthentication(req)
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -207,6 +225,16 @@ func postReplicationAction(policyID int64, acton string) error {
|
||||
return fmt.Errorf("%d %s", resp.StatusCode, string(b))
|
||||
}
|
||||
|
||||
func addAuthentication(req *http.Request) {
|
||||
if req != nil {
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: models.UISecretCookie,
|
||||
// TODO read secret from config
|
||||
Value: os.Getenv("UI_SECRET"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func buildReplicationURL() string {
|
||||
url := getJobServiceURL()
|
||||
return fmt.Sprintf("%s/api/jobs/replication", url)
|
||||
@ -233,3 +261,34 @@ func getJobServiceURL() string {
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@ -112,6 +113,7 @@ func clearUp(username string) {
|
||||
}
|
||||
|
||||
const username string = "Tester01"
|
||||
const password string = "Abc12345"
|
||||
const projectName string = "test_project"
|
||||
const repoTag string = "test1.1"
|
||||
const repoTag2 string = "test1.2"
|
||||
@ -157,7 +159,7 @@ func TestRegister(t *testing.T) {
|
||||
user := models.User{
|
||||
Username: username,
|
||||
Email: "tester01@vmware.com",
|
||||
Password: "Abc12345",
|
||||
Password: password,
|
||||
Realname: "tester01",
|
||||
Comment: "register",
|
||||
}
|
||||
@ -184,6 +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) {
|
||||
var exists bool
|
||||
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) {
|
||||
err := DeleteProjectMember(currentProject.ProjectID, 1)
|
||||
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) {
|
||||
err := ToggleUserAdminRole(currentUser.UserID, 1)
|
||||
if err != nil {
|
||||
@ -1330,7 +1399,7 @@ func TestDeleteRepPolicy(t *testing.T) {
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
t.Errorf("Error occured in GetRepPolicy:%v", err)
|
||||
}
|
||||
if p != nil {
|
||||
if p != nil && p.Deleted != 1 {
|
||||
t.Errorf("Able to find rep policy after deletion, id: %d", policyID)
|
||||
}
|
||||
}
|
||||
@ -1412,3 +1481,36 @@ func TestGetOrmer(t *testing.T) {
|
||||
t.Errorf("Error get ormer.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteProject(t *testing.T) {
|
||||
name := "project_for_test"
|
||||
project := models.Project{
|
||||
OwnerID: currentUser.UserID,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
id, err := AddProject(project)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add project: %v", err)
|
||||
}
|
||||
|
||||
if err = DeleteProject(id); err != nil {
|
||||
t.Fatalf("failed to delete project: %v", err)
|
||||
}
|
||||
|
||||
p := &models.Project{}
|
||||
if err = GetOrmer().Raw(`select * from project where project_id = ?`, id).
|
||||
QueryRow(p); err != nil {
|
||||
t.Fatalf("failed to get project: %v", err)
|
||||
}
|
||||
|
||||
if p.Deleted != 1 {
|
||||
t.Errorf("unexpeced deleted column: %d != %d", p.Deleted, 1)
|
||||
}
|
||||
|
||||
deletedName := fmt.Sprintf("%s#%d", name, id)
|
||||
if p.Name != deletedName {
|
||||
t.Errorf("unexpected name: %s != %s", p.Name, deletedName)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -277,3 +277,12 @@ func getProjects(userID int, name string, args ...int64) ([]models.Project, erro
|
||||
|
||||
return projects, err
|
||||
}
|
||||
|
||||
// DeleteProject ...
|
||||
func DeleteProject(id int64) error {
|
||||
sql := `update project
|
||||
set deleted = 1, name = concat(name,"#",project_id)
|
||||
where project_id = ?`
|
||||
_, err := GetOrmer().Raw(sql, id).Exec()
|
||||
return err
|
||||
}
|
||||
|
@ -155,17 +155,18 @@ func FilterRepPolicies(name string, projectID int64) ([]*models.RepPolicy, error
|
||||
left join project p on rp.project_id=p.project_id
|
||||
left join replication_target rt on rp.target_id=rt.id
|
||||
left join replication_job rj on rp.id=rj.policy_id and (rj.status="error"
|
||||
or rj.status="retrying") `
|
||||
or rj.status="retrying")
|
||||
where rp.deleted = 0 `
|
||||
|
||||
if len(name) != 0 && projectID != 0 {
|
||||
sql += `where rp.name like ? and rp.project_id = ? `
|
||||
sql += `and rp.name like ? and rp.project_id = ? `
|
||||
args = append(args, "%"+name+"%")
|
||||
args = append(args, projectID)
|
||||
} else if len(name) != 0 {
|
||||
sql += `where rp.name like ? `
|
||||
sql += `and rp.name like ? `
|
||||
args = append(args, "%"+name+"%")
|
||||
} else if projectID != 0 {
|
||||
sql += `where rp.project_id = ? `
|
||||
sql += `and rp.project_id = ? `
|
||||
args = append(args, projectID)
|
||||
}
|
||||
|
||||
@ -181,7 +182,7 @@ func FilterRepPolicies(name string, projectID int64) ([]*models.RepPolicy, error
|
||||
// GetRepPolicyByName ...
|
||||
func GetRepPolicyByName(name string) (*models.RepPolicy, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select * from replication_policy where name = ?`
|
||||
sql := `select * from replication_policy where deleted = 0 and name = ?`
|
||||
|
||||
var policy models.RepPolicy
|
||||
|
||||
@ -198,7 +199,7 @@ func GetRepPolicyByName(name string) (*models.RepPolicy, error) {
|
||||
// GetRepPolicyByProject ...
|
||||
func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select * from replication_policy where project_id = ?`
|
||||
sql := `select * from replication_policy where deleted = 0 and project_id = ?`
|
||||
|
||||
var policies []*models.RepPolicy
|
||||
|
||||
@ -212,7 +213,7 @@ func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) {
|
||||
// GetRepPolicyByTarget ...
|
||||
func GetRepPolicyByTarget(targetID int64) ([]*models.RepPolicy, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select * from replication_policy where target_id = ?`
|
||||
sql := `select * from replication_policy where deleted = 0 and target_id = ?`
|
||||
|
||||
var policies []*models.RepPolicy
|
||||
|
||||
@ -226,7 +227,7 @@ func GetRepPolicyByTarget(targetID int64) ([]*models.RepPolicy, error) {
|
||||
// GetRepPolicyByProjectAndTarget ...
|
||||
func GetRepPolicyByProjectAndTarget(projectID, targetID int64) ([]*models.RepPolicy, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select * from replication_policy where project_id = ? and target_id = ?`
|
||||
sql := `select * from replication_policy where deleted = 0 and project_id = ? and target_id = ?`
|
||||
|
||||
var policies []*models.RepPolicy
|
||||
|
||||
@ -247,7 +248,11 @@ func UpdateRepPolicy(policy *models.RepPolicy) error {
|
||||
// DeleteRepPolicy ...
|
||||
func DeleteRepPolicy(id int64) error {
|
||||
o := GetOrmer()
|
||||
_, err := o.Delete(&models.RepPolicy{ID: id})
|
||||
policy := &models.RepPolicy{
|
||||
ID: id,
|
||||
Deleted: 1,
|
||||
}
|
||||
_, err := o.Update(policy, "Deleted")
|
||||
return err
|
||||
}
|
||||
|
||||
|
15
dao/user.go
15
dao/user.go
@ -185,37 +185,24 @@ func UpdateUserResetUUID(u models.User) error {
|
||||
func CheckUserPassword(query models.User) (*models.User, error) {
|
||||
|
||||
currentUser, err := GetUser(query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if currentUser == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sql := `select user_id, username, salt from user where deleted = 0`
|
||||
|
||||
sql := `select user_id, username, salt from user where deleted = 0 and username = ? and password = ?`
|
||||
queryParam := make([]interface{}, 1)
|
||||
|
||||
if query.UserID != 0 {
|
||||
sql += ` and password = ? and user_id = ?`
|
||||
queryParam = append(queryParam, utils.Encrypt(query.Password, currentUser.Salt))
|
||||
queryParam = append(queryParam, query.UserID)
|
||||
} else {
|
||||
sql += ` and username = ? and password = ?`
|
||||
queryParam = append(queryParam, currentUser.Username)
|
||||
queryParam = append(queryParam, utils.Encrypt(query.Password, currentUser.Salt))
|
||||
}
|
||||
o := GetOrmer()
|
||||
var user []models.User
|
||||
|
||||
n, err := o.Raw(sql, queryParam).QueryRows(&user)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
log.Warning("User principal does not match password. Current:", currentUser)
|
||||
return nil, nil
|
||||
|
@ -31,6 +31,7 @@ var localUIURL string
|
||||
var localRegURL string
|
||||
var logDir string
|
||||
var uiSecret string
|
||||
var secretKey string
|
||||
var verifyRemoteCert string
|
||||
|
||||
func init() {
|
||||
@ -86,6 +87,11 @@ func init() {
|
||||
beego.LoadAppConfig("ini", configPath)
|
||||
}
|
||||
|
||||
secretKey = os.Getenv("SECRET_KEY")
|
||||
if len(secretKey) != 16 {
|
||||
panic("The length of secretkey has to be 16 characters!")
|
||||
}
|
||||
|
||||
log.Debugf("config: maxJobWorkers: %d", maxJobWorkers)
|
||||
log.Debugf("config: localUIURL: %s", localUIURL)
|
||||
log.Debugf("config: localRegURL: %s", localRegURL)
|
||||
@ -119,6 +125,11 @@ func UISecret() string {
|
||||
return uiSecret
|
||||
}
|
||||
|
||||
// SecretKey will return the secret key for encryption/decryption password in target.
|
||||
func SecretKey() string {
|
||||
return secretKey
|
||||
}
|
||||
|
||||
// VerifyRemoteCert return the flag to tell jobservice whether or not verify the cert of remote registry
|
||||
func VerifyRemoteCert() bool {
|
||||
return verifyRemoteCert != "off"
|
||||
|
@ -231,7 +231,7 @@ func (sm *SM) Reset(jid int64) error {
|
||||
pwd := target.Password
|
||||
|
||||
if len(pwd) != 0 {
|
||||
pwd, err = uti.ReversibleDecrypt(pwd)
|
||||
pwd, err = uti.ReversibleDecrypt(pwd, config.SecretKey())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt password: %v", err)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ Changelog for harbor database schema
|
||||
- delete data `AMDRWS` from table `role`
|
||||
- delete data `A` from table `access`
|
||||
|
||||
## 0.2.0
|
||||
## 0.3.0
|
||||
|
||||
- create table `replication_policy`
|
||||
- create table `replication_target`
|
||||
@ -25,3 +25,9 @@ Changelog for harbor database schema
|
||||
- add column `repo_tag` to table `access_log`
|
||||
- alter column `repo_name` on table `access_log`
|
||||
- alter column `email` on table `user`
|
||||
|
||||
## 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"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||
ErrorJobCount int `json:"error_job_count"`
|
||||
Deleted int `orm:"column(deleted)" json:"deleted"`
|
||||
}
|
||||
|
||||
// Valid ...
|
||||
|
@ -73,13 +73,11 @@
|
||||
}
|
||||
|
||||
.sub-pane {
|
||||
margin: 15px;
|
||||
min-height: 380px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.well-custom {
|
||||
|
||||
width: 100%;
|
||||
background-color: #f5f5f5;
|
||||
background-image: none;
|
||||
|
@ -49,6 +49,7 @@
|
||||
</table>
|
||||
</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>
|
||||
|
@ -52,7 +52,17 @@
|
||||
'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() {
|
||||
|
||||
@ -69,11 +79,13 @@
|
||||
'username' : vm.username
|
||||
};
|
||||
vm.username = '';
|
||||
retrieve(vm.queryParams);
|
||||
retrieve(vm.queryParams, vm.page, vm.pageSize);
|
||||
});
|
||||
|
||||
function search(e) {
|
||||
|
||||
vm.page = 1;
|
||||
|
||||
if(e.op[0] === 'all') {
|
||||
e.op = ['create', 'pull', 'push', 'delete'];
|
||||
}
|
||||
@ -87,7 +99,8 @@
|
||||
vm.queryParams.beginTimestamp = toUTCSeconds(vm.fromDate, 0, 0, 0);
|
||||
vm.queryParams.endTimestamp = toUTCSeconds(vm.toDate, 23, 59, 59);
|
||||
|
||||
retrieve(vm.queryParams);
|
||||
retrieve(vm.queryParams, vm.page, vm.pageSize);
|
||||
|
||||
}
|
||||
|
||||
function showAdvancedSearch() {
|
||||
@ -98,27 +111,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
function retrieve(queryParams) {
|
||||
ListLogService(queryParams)
|
||||
function retrieve(queryParams, page, pageSize) {
|
||||
ListLogService(queryParams, page, pageSize)
|
||||
.then(listLogComplete)
|
||||
.catch(listLogFailed);
|
||||
}
|
||||
|
||||
function listLogComplete(response) {
|
||||
vm.logs = response.data;
|
||||
vm.totalCount = response.headers('X-Total-Count');
|
||||
|
||||
vm.queryParams = {
|
||||
'beginTimestamp' : 0,
|
||||
'endTimestamp' : 0,
|
||||
'keywords' : '',
|
||||
'projectId': vm.projectId,
|
||||
'username' : ''
|
||||
};
|
||||
vm.op = ['all'];
|
||||
vm.fromDate = '';
|
||||
vm.toDate = '';
|
||||
vm.others = '';
|
||||
vm.opOthers = true;
|
||||
console.log('Total Count in logs:' + vm.totalCount + ', page:' + vm.page);
|
||||
|
||||
// vm.queryParams = {
|
||||
// 'beginTimestamp' : 0,
|
||||
// 'endTimestamp' : 0,
|
||||
// 'keywords' : '',
|
||||
// 'projectId': vm.projectId,
|
||||
// 'username' : ''
|
||||
// };
|
||||
// vm.op = ['all'];
|
||||
// vm.fromDate = '';
|
||||
// vm.toDate = '';
|
||||
// vm.others = '';
|
||||
// vm.opOthers = true;
|
||||
vm.isOpen = false;
|
||||
}
|
||||
function listLogFailed(response){
|
||||
|
@ -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 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>
|
||||
<h4 class="h4-custom-down">// 'replication_jobs' | tr //</h4>
|
||||
<hr class="hr-line"/>
|
||||
@ -147,7 +149,7 @@
|
||||
</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>
|
||||
|
@ -59,6 +59,17 @@
|
||||
vm.retrievePolicy = retrievePolicy;
|
||||
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.togglePolicy = togglePolicy;
|
||||
|
||||
@ -84,14 +95,14 @@
|
||||
function searchReplicationJob() {
|
||||
if(vm.lastPolicyId !== -1) {
|
||||
vm.searchJobTIP = true;
|
||||
vm.retrieveJob(vm.lastPolicyId);
|
||||
vm.retrieveJob(vm.lastPolicyId, vm.page, vm.pageSize);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshReplicationJob() {
|
||||
if(vm.lastPolicyId !== -1) {
|
||||
vm.refreshJobTIP = true;
|
||||
vm.retrieveJob(vm.lastPolicyId);
|
||||
vm.retrieveJob(vm.lastPolicyId, vm.page, vm.pageSize);
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,11 +112,10 @@
|
||||
.error(listReplicationPolicyFailed);
|
||||
}
|
||||
|
||||
function retrieveJob(policyId) {
|
||||
function retrieveJob(policyId, page, pageSize) {
|
||||
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))
|
||||
.success(listReplicationJobSuccess)
|
||||
.error(listReplicationJobFailed);
|
||||
ListReplicationJobService(policyId, vm.replicationJobName, status, toUTCSeconds(vm.fromDate, 0, 0, 0), toUTCSeconds(vm.toDate, 23, 59, 59), page, pageSize)
|
||||
.then(listReplicationJobSuccess, listReplicationJobFailed);
|
||||
}
|
||||
|
||||
function listReplicationPolicySuccess(data, status) {
|
||||
@ -117,8 +127,9 @@
|
||||
console.log('Failed to list replication policy:' + data);
|
||||
}
|
||||
|
||||
function listReplicationJobSuccess(data, status) {
|
||||
vm.replicationJobs = data || [];
|
||||
function listReplicationJobSuccess(response) {
|
||||
vm.replicationJobs = response.data || [];
|
||||
vm.totalCount = response.headers('X-Total-Count');
|
||||
var alertInfo = {
|
||||
'show': false,
|
||||
'message': ''
|
||||
@ -146,8 +157,8 @@
|
||||
vm.refreshJobTIP = false;
|
||||
}
|
||||
|
||||
function listReplicationJobFailed(data, status) {
|
||||
console.log('Failed to list replication job:' + data);
|
||||
function listReplicationJobFailed(response) {
|
||||
console.log('Failed to list replication job:' + response);
|
||||
vm.searchJobTIP = false;
|
||||
vm.refreshJobTIP = false;
|
||||
}
|
||||
@ -259,8 +270,8 @@
|
||||
var uponTableHeight = element.find('#upon-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;
|
||||
|
||||
element.find('.split-handle').on('mousedown', mousedownHandler);
|
||||
@ -328,7 +339,7 @@
|
||||
.css({'color': '#fff'});
|
||||
$('a', this)
|
||||
.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');
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
</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>
|
@ -41,6 +41,8 @@
|
||||
vm.filterInput = hashValue;
|
||||
}
|
||||
}
|
||||
vm.page = 1;
|
||||
vm.pageSize = 8;
|
||||
|
||||
vm.retrieve = retrieve;
|
||||
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) {
|
||||
vm.repoName = val;
|
||||
});
|
||||
@ -82,13 +93,13 @@
|
||||
vm.deleteImage = deleteImage;
|
||||
|
||||
function retrieve(){
|
||||
ListRepositoryService(vm.projectId, vm.filterInput)
|
||||
.success(getRepositoryComplete)
|
||||
.error(getRepositoryFailed);
|
||||
ListRepositoryService(vm.projectId, vm.filterInput, vm.page, vm.pageSize)
|
||||
.then(getRepositoryComplete, getRepositoryFailed);
|
||||
}
|
||||
|
||||
function getRepositoryComplete(data, status) {
|
||||
vm.repositories = data || [];
|
||||
function getRepositoryComplete(response) {
|
||||
vm.repositories = response.data || [];
|
||||
vm.totalCount = response.headers('X-Total-Count');
|
||||
$scope.$broadcast('refreshTags', true);
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,7 @@
|
||||
'harbor.system.management',
|
||||
'harbor.loading.progress',
|
||||
'harbor.inline.help',
|
||||
'harbor.dismissable.alerts'
|
||||
'harbor.dismissable.alerts',
|
||||
'harbor.paginator'
|
||||
]);
|
||||
})();
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
return LogResult;
|
||||
|
||||
function LogResult(queryParams) {
|
||||
function LogResult(queryParams, page, pageSize) {
|
||||
var projectId = queryParams.projectId;
|
||||
var username = queryParams.username;
|
||||
var beginTimestamp = queryParams.beginTimestamp;
|
||||
@ -34,7 +34,7 @@
|
||||
var keywords = queryParams.keywords;
|
||||
|
||||
return $http
|
||||
.post('/api/projects/' + projectId + '/logs/filter', {
|
||||
.post('/api/projects/' + projectId + '/logs/filter?page=' + page + '&page_size=' + pageSize, {
|
||||
'begin_timestamp' : beginTimestamp,
|
||||
'end_timestamp' : endTimestamp,
|
||||
'keywords' : keywords,
|
||||
|
@ -26,9 +26,9 @@
|
||||
|
||||
return listReplicationJob;
|
||||
|
||||
function listReplicationJob(policyId, repository, status, startTime, endTime) {
|
||||
function listReplicationJob(policyId, repository, status, startTime, endTime, page, pageSize) {
|
||||
return $http
|
||||
.get('/api/jobs/replication/', {
|
||||
.get('/api/jobs/replication/?page=' + page + '&page_size=' + pageSize, {
|
||||
'params': {
|
||||
'policy_id': policyId,
|
||||
'repository': repository,
|
||||
|
@ -25,11 +25,11 @@
|
||||
|
||||
return ListRepository;
|
||||
|
||||
function ListRepository(projectId, q) {
|
||||
function ListRepository(projectId, q, page, pageSize) {
|
||||
$log.info('list repositories:' + projectId + ', q:' + q);
|
||||
|
||||
return $http
|
||||
.get('/api/repositories', {
|
||||
.get('/api/repositories?page=' + page + '&page_size=' + pageSize, {
|
||||
'params':{
|
||||
'project_id': projectId,
|
||||
'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.
|
||||
package HarborAPI
|
||||
package apilib
|
||||
|
||||
import (
|
||||
"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
|
||||
package HarborAPI
|
||||
package apilib
|
||||
|
||||
import (
|
||||
"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
|
||||
package HarborAPI
|
||||
package apilib
|
||||
|
||||
import (
|
||||
"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 {
|
||||
ProjectId int32 `json:"id,omitempty"`
|
||||
|
||||
// Project ID
|
||||
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"`
|
||||
|
||||
// The update time of the project.
|
||||
UpdateTime string `json:"update_time,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 (
|
||||
"time"
|
||||
)
|
||||
package apilib
|
||||
|
||||
type Repository struct {
|
||||
|
||||
// Repository ID
|
||||
Id string `json:"id,omitempty"`
|
||||
|
||||
// Parent of the image.
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Created time.Time `json:"created,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 {
|
||||
|
||||
// ID in table.
|
||||
RoleId int32 `json:"role_id,omitempty"`
|
||||
|
||||
// Description of permissions for the role.
|
||||
RoleCode string `json:"role_code,omitempty"`
|
||||
|
||||
// Name the the role.
|
||||
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 {
|
||||
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 {
|
||||
|
||||
// The ID of the user.
|
||||
UserId int32 `json:"user_id,omitempty"`
|
||||
|
||||
Username string `json:"username,omitempty"`
|
||||
|
||||
Email string `json:"email,omitempty"`
|
||||
|
||||
Password string `json:"password,omitempty"`
|
||||
|
||||
Realname string `json:"realname,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
|
||||
docker pull docker
|
||||
docker login -u admin -p Harbor12345 127.0.0.1
|
||||
#!/bin/bash
|
||||
|
||||
docker tag hello-world 127.0.0.1/library/hello-world
|
||||
docker push 127.0.0.1/library/hello-world
|
||||
cp tests/docker-compose.test.yml Deploy/.
|
||||
|
||||
docker tag docker 127.0.0.1/library/docker
|
||||
docker push 127.0.0.1/library/docker
|
||||
mkdir /etc/ui
|
||||
cp Deploy/config/ui/private_key.pem /etc/ui/.
|
||||
|
||||
mkdir conf
|
||||
cp Deploy/config/ui/app.conf conf/.
|
||||
|
@ -16,9 +16,15 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"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))
|
||||
}
|
||||
|
||||
// ReversibleEncrypt encrypts the str with base64
|
||||
func ReversibleEncrypt(str string) string {
|
||||
return base64.StdEncoding.EncodeToString([]byte(str))
|
||||
const (
|
||||
// EncryptHeaderV1 ...
|
||||
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
|
||||
func ReversibleDecrypt(str string) (string, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(str)
|
||||
return string(b), err
|
||||
// ReversibleDecrypt decrypts the str with aes/base64 or base 64 depending on "header"
|
||||
func ReversibleDecrypt(str, key string) (string, error) {
|
||||
if strings.HasPrefix(str, EncryptHeaderV1) {
|
||||
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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Level ...
|
||||
@ -56,7 +57,7 @@ func (l Level) string() (lvl string) {
|
||||
|
||||
func parseLevel(lvl string) (level Level, err error) {
|
||||
|
||||
switch lvl {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "debug":
|
||||
level = DebugLevel
|
||||
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
|
||||
}
|
||||
|
||||
if s.credential != nil {
|
||||
s.credential.AddAuthorization(r)
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(r)
|
||||
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"
|
||||
)
|
||||
|
||||
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
|
||||
func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modifier) (*Registry, error) {
|
||||
u, err := utils.ParseEndpoint(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: insecure,
|
||||
@ -61,12 +56,9 @@ func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modif
|
||||
|
||||
transport := NewTransport(t, modifiers...)
|
||||
|
||||
return &Registry{
|
||||
Endpoint: u,
|
||||
client: &http.Client{
|
||||
return NewRegistry(endpoint, &http.Client{
|
||||
Transport: transport,
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
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{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: insecure,
|
||||
@ -76,13 +69,9 @@ func NewRepositoryWithModifiers(name, endpoint string, insecure bool, modifiers
|
||||
|
||||
transport := NewTransport(t, modifiers...)
|
||||
|
||||
return &Repository{
|
||||
Name: name,
|
||||
Endpoint: u,
|
||||
client: &http.Client{
|
||||
return NewRepository(name, endpoint, &http.Client{
|
||||
Transport: transport,
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func parseError(err error) error {
|
||||
@ -347,7 +336,7 @@ func (r *Repository) PullBlob(digest string) (size int64, data io.ReadCloser, er
|
||||
data = resp.Body
|
||||
return
|
||||
}
|
||||
|
||||
// can not close the connect if the status code is 200
|
||||
defer resp.Body.Close()
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.monolithicBlobUpload(location, digest, size, data)
|
||||
}
|
||||
|
||||
@ -482,5 +470,12 @@ func buildInitiateBlobUploadURL(endpoint, repoName 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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/utils/registry/auth"
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||
"github.com/vmware/harbor/utils/test"
|
||||
)
|
||||
|
||||
var (
|
||||
username = "user"
|
||||
password = "P@ssw0rd"
|
||||
repo = "samalba/my-app"
|
||||
tags = tagResp{Tags: []string{"1.0", "2.0", "3.0"}}
|
||||
validToken = "valid_token"
|
||||
invalidToken = "invalid_token"
|
||||
credential auth.Credential
|
||||
registryServer *httptest.Server
|
||||
tokenServer *httptest.Server
|
||||
repositoryClient *Repository
|
||||
repository = "library/hello-world"
|
||||
tag = "latest"
|
||||
|
||||
mediaType = schema2.MediaTypeManifest
|
||||
manifest = []byte("manifest")
|
||||
|
||||
blob = []byte("blob")
|
||||
|
||||
uuid = "0663ff44-63bb-11e6-8b77-86f30ca893d3"
|
||||
|
||||
digest = "sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b"
|
||||
)
|
||||
|
||||
type tagResp struct {
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
//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 TestNewRepositoryWithModifiers(t *testing.T) {
|
||||
_, err := NewRepositoryWithModifiers("library/ubuntu",
|
||||
"http://registry.org", true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create client for repository: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func serveTaglisting(w http.ResponseWriter, r *http.Request) {
|
||||
if !isTokenValid(r) {
|
||||
challenge(w)
|
||||
func TestBlobExist(t *testing.T) {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
dgt := path[strings.LastIndex(path, "/")+1 : len(path)]
|
||||
if dgt == digest {
|
||||
w.Header().Add(http.CanonicalHeaderKey("Content-Length"), strconv.Itoa(len(blob)))
|
||||
w.Header().Add(http.CanonicalHeaderKey("Docker-Content-Digest"), digest)
|
||||
w.Header().Add(http.CanonicalHeaderKey("Content-Type"), "application/octet-stream")
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(tags); err != nil {
|
||||
w.Write([]byte(err.Error()))
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
server := test.NewServer(
|
||||
&test.RequestHandlerMapping{
|
||||
Method: "HEAD",
|
||||
Pattern: fmt.Sprintf("/v2/%s/blobs/", repository),
|
||||
Handler: handler,
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
client, err := newRepository(server.URL)
|
||||
if err != nil {
|
||||
err = parseError(err)
|
||||
t.Fatalf("failed to create client for repository: %v", err)
|
||||
}
|
||||
|
||||
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 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
|
||||
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))
|
||||
}
|
||||
return valid
|
||||
}
|
||||
|
||||
func challenge(w http.ResponseWriter) {
|
||||
challenge := "Bearer realm=\"" + tokenServer.URL + "/service/token\",service=\"token-service\""
|
||||
w.Header().Set("Www-Authenticate", challenge)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
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 initTokenServer() *httptest.Server {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/service/token", serveToken)
|
||||
func TestDeleteBlob(t *testing.T) {
|
||||
handler := test.Handler(&test.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
})
|
||||
|
||||
return httptest.NewServer(mux)
|
||||
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 serveToken(w http.ResponseWriter, r *http.Request) {
|
||||
u, p, ok := r.BasicAuth()
|
||||
if !ok || u != username || p != password {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
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
|
||||
}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
result["token"] = validToken
|
||||
result["expires_in"] = 300
|
||||
result["issued_at"] = time.Now().Format(time.RFC3339)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(w)
|
||||
if err := encoder.Encode(result); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
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) {
|
||||
client, err := newRepositoryClient(registryServer.URL, true, credential,
|
||||
repo, "repository", repo, "pull", "push", "*")
|
||||
handler := test.Handler(&test.Response{
|
||||
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 {
|
||||
t.Error(err)
|
||||
t.Fatalf("failed to create client for repository: %v", err)
|
||||
}
|
||||
|
||||
list, err := client.ListTag()
|
||||
tags, err := client.ListTag()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if len(list) != len(tags.Tags) {
|
||||
t.Errorf("expected length: %d, actual length: %d", len(tags.Tags), len(list))
|
||||
return
|
||||
t.Fatalf("failed to list tags: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 len(tags) != 1 {
|
||||
t.Fatalf("unexpected length of tags: %d != %d", len(tags), 1)
|
||||
}
|
||||
|
||||
if _, err = client.ListTag(); err != nil {
|
||||
e, ok := err.(*registry_error.Error)
|
||||
if ok && e.StatusCode == http.StatusUnauthorized {
|
||||
return
|
||||
}
|
||||
|
||||
t.Error(err)
|
||||
return
|
||||
if tags[0] != tag {
|
||||
t.Errorf("unexpected tag: %s != %s", tags[0], tag)
|
||||
}
|
||||
}
|
||||
|
||||
func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential, repository, scopeType, scopeName string,
|
||||
scopeActions ...string) (*Repository, error) {
|
||||
|
||||
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
|
||||
|
||||
store, err := auth.NewAuthorizerStore(endpoint, true, authorizer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func TestParseError(t *testing.T) {
|
||||
err := &url.Error{
|
||||
Err: ®istry_error.Error{},
|
||||
}
|
||||
|
||||
client, err := NewRepositoryWithModifiers(repository, endpoint, insecure, store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
e := parseError(err)
|
||||
if _, ok := e.(*registry_error.Error); !ok {
|
||||
t.Errorf("error type does not match registry error")
|
||||
}
|
||||
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"
|
||||
)
|
||||
|
||||
// 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
|
||||
func FormatEndpoint(endpoint string) string {
|
||||
endpoint = strings.TrimSpace(endpoint)
|
||||
|
@ -16,10 +16,41 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"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) {
|
||||
@ -61,3 +92,45 @@ func TestParseRepository(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -192,3 +192,6 @@
|
||||
|
||||
<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/paginator/paginator.module.js"></script>
|
||||
<script src="/static/resources/js/components/paginator/paginator.directive.js"></script>
|
Loading…
Reference in New Issue
Block a user