Merge branch 'dev' of https://github.com/vmware/harbor into dev

This commit is contained in:
yhua 2016-09-05 15:46:49 +08:00
commit 285ef73653
48 changed files with 1995 additions and 458 deletions

View File

@ -59,7 +59,8 @@ install:
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
- chmod +x docker-compose
- sudo mv docker-compose /usr/local/bin
- sudo sed -i '$a DOCKER_OPTS=\"$DOCKER_OPTS --insecure-registry 127.0.0.1\"' /etc/default/docker
- IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
- sudo sed -i '$a DOCKER_OPTS=\"--insecure-registry '$IP':5000\"' /etc/default/docker
- sudo service docker restart
- go get github.com/dghubble/sling
- go get github.com/stretchr/testify
@ -75,10 +76,10 @@ script:
- docker-compose -f Deploy/docker-compose.test.yml up -d
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 fgt golint
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 go vet
- IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
- export MYSQL_HOST=$IP
- export REGISTRY_URL=http://$IP:5000
- export REGISTRY_URL=$IP:5000
- echo $REGISTRY_URL
- ./tests/pushimage.sh
- ./Deploy/coverage4gotest.sh
- goveralls -coverprofile=profile.cov -service=travis-ci

View File

@ -38,8 +38,14 @@ insert into role (role_code, name) values
create table user (
user_id int NOT NULL AUTO_INCREMENT,
username varchar(15),
email varchar(128),
# The max length of username controlled by API is 20,
# and 11 is reserved for marking the deleted users.
# The mark of deleted user is "#user_id".
# The 11 consist of 10 for the max value of user_id(4294967295)
# in MySQL and 1 of '#'.
username varchar(32),
# 11 bytes is reserved for marking the deleted users.
email varchar(255),
password varchar(40) NOT NULL,
realname varchar (20) NOT NULL,
comment varchar (30),
@ -62,7 +68,7 @@ create table project (
project_id int NOT NULL AUTO_INCREMENT,
owner_id int NOT NULL,
# The max length of name controlled by API is 30,
# and 11 bytes is reserved for marking the deleted project.
# and 11 is reserved for marking the deleted project.
name varchar (41) NOT NULL,
creation_time timestamp,
update_time timestamp,
@ -101,10 +107,27 @@ create table access_log (
operation varchar(20) NOT NULL,
op_time timestamp,
primary key (log_id),
INDEX pid_optime (project_id, op_time),
FOREIGN KEY (user_id) REFERENCES user(user_id),
FOREIGN KEY (project_id) REFERENCES project (project_id)
);
create table repository (
repository_id int NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
project_id int NOT NULL,
owner_id int NOT NULL,
description text,
pull_count int DEFAULT 0 NOT NULL,
star_count int DEFAULT 0 NOT NULL,
creation_time timestamp default CURRENT_TIMESTAMP,
update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
primary key (repository_id),
FOREIGN KEY (owner_id) REFERENCES user(user_id),
FOREIGN KEY (project_id) REFERENCES project(project_id),
UNIQUE (name)
);
create table replication_policy (
id int NOT NULL AUTO_INCREMENT,
name varchar(256),
@ -147,7 +170,8 @@ create table replication_job (
creation_time timestamp default CURRENT_TIMESTAMP,
update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
PRIMARY KEY (id),
INDEX policy (policy_id)
INDEX policy (policy_id),
INDEX poid_uptime (policy_id, update_time)
);
create table properties (

View File

@ -25,8 +25,12 @@ import (
const (
jsonAcceptHeader = "application/json"
testAcceptHeader = "text/plain"
adminName = "admin"
adminPwd = "Harbor12345"
)
var admin, unknownUsr *usrInfo
type api struct {
basePath string
}
@ -57,13 +61,20 @@ func init() {
beego.Router("/api/search/", &SearchAPI{})
beego.Router("/api/projects/", &ProjectAPI{}, "get:List;post:Post;head:Head")
beego.Router("/api/projects/:id/delete", &ProjectAPI{}, "delete:Delete")
beego.Router("/api/projects/:id", &ProjectAPI{}, "delete:Delete;get:Get")
beego.Router("/api/users/:id([0-9]+)/password", &UserAPI{}, "put:ChangePassword")
beego.Router("/api/projects/:id/publicity", &ProjectAPI{}, "put:ToggleProjectPublic")
beego.Router("/api/projects/:id([0-9]+)/logs/filter", &ProjectAPI{}, "post:FilterAccessLog")
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &ProjectMemberAPI{}, "get:Get")
beego.Router("/api/statistics", &StatisticAPI{})
beego.Router("/api/logs", &LogAPI{})
_ = updateInitPassword(1, "Harbor12345")
//Init user Info
admin = &usrInfo{adminName, adminPwd}
unknownUsr = &usrInfo{"unknown", "unknown"}
}
func request(_sling *sling.Sling, acceptHeader string, authInfo ...usrInfo) (int, []byte, error) {
@ -115,7 +126,7 @@ func (a api) SearchGet(q string) (apilib.Search, error) {
//@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) {
func (a api) ProjectsPost(prjUsr usrInfo, project apilib.ProjectReq) (int, error) {
_sling := sling.New().Post(a.basePath)
@ -152,7 +163,43 @@ func (a api) UsersUserIDPasswordPut(user usrInfo, userID int32, password apilib.
httpStatusCode, _, _ := request(_sling, jsonAcceptHeader, user)
return httpStatusCode
}
func (a api) StatisticGet(user usrInfo) (apilib.StatisticMap, error) {
_sling := sling.New().Get(a.basePath)
// create path and map variables
path := "/api/statistics/"
fmt.Printf("project statistic path: %s\n", path)
_sling = _sling.Path(path)
var successPayload = new(apilib.StatisticMap)
code, body, err := request(_sling, jsonAcceptHeader, user)
if 200 == code && nil == err {
err = json.Unmarshal(body, &successPayload)
}
return *successPayload, err
}
func (a api) LogGet(user usrInfo, startTime, endTime, lines string) (int, []apilib.AccessLog, error) {
_sling := sling.New().Get(a.basePath)
// create path and map variables
path := "/api/logs/"
fmt.Printf("logs path: %s\n", path)
_sling = _sling.Path(path)
type QueryParams struct {
StartTime string `url:"start_time,omitempty"`
EndTime string `url:"end_time,omitempty"`
Lines string `url:"lines,omitempty"`
}
_sling = _sling.QueryStruct(&QueryParams{StartTime: startTime, EndTime: endTime, Lines: lines})
var successPayload []apilib.AccessLog
code, body, err := request(_sling, jsonAcceptHeader, user)
if 200 == code && nil == err {
err = json.Unmarshal(body, &successPayload)
}
return code, successPayload, err
}
////Delete a repository or a tag in a repository.
@ -202,12 +249,9 @@ func (a api) ProjectsDelete(prjUsr usrInfo, projectID string) (int, error) {
_sling := sling.New().Delete(a.basePath)
//create api path
path := "api/projects/" + projectID + "/delete"
path := "api/projects/" + projectID
_sling = _sling.Path(path)
//_sling = _sling.BodyJSON(project)
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr)
fmt.Println(string(body))
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr)
return httpStatusCode, err
}
@ -227,6 +271,23 @@ func (a api) ProjectsHead(prjUsr usrInfo, projectName string) (int, error) {
return httpStatusCode, err
}
//Return specific project detail infomation
func (a api) ProjectsGetByPID(projectID string) (int, apilib.Project, error) {
_sling := sling.New().Get(a.basePath)
//create api path
path := "api/projects/" + projectID
_sling = _sling.Path(path)
var successPayload apilib.Project
httpStatusCode, body, err := request(_sling, jsonAcceptHeader)
if err == nil && httpStatusCode == 200 {
err = json.Unmarshal(body, &successPayload)
}
return httpStatusCode, successPayload, err
}
//Search projects by projectName and isPublic
func (a api) ProjectsGet(projectName string, isPublic int32) (int, []apilib.Project, error) {
_sling := sling.New().Get(a.basePath)
@ -281,7 +342,7 @@ func (a api) ProjectLogsFilter(prjUsr usrInfo, projectID string, accessLog apili
// body params
_sling = _sling.BodyJSON(accessLog)
// var successPayload []apilib.AccessLog
//var successPayload []apilib.AccessLog
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr)
/*
@ -293,17 +354,23 @@ func (a api) ProjectLogsFilter(prjUsr usrInfo, projectID string, accessLog apili
// return httpStatusCode, successPayload, err
}
//-------------------------Member Test---------------------------------------//
//Return relevant role members of projectID
func (a api) GetProjectMembersByProID(prjUsr usrInfo, projectID string) (int, []byte, error) {
_sling := sling.New().Post(a.basePath)
func (a api) GetProjectMembersByProID(prjUsr usrInfo, projectID string) (int, []apilib.User, error) {
_sling := sling.New().Get(a.basePath)
path := "/api/projects/" + projectID + "/members/"
_sling = _sling.Path(path)
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr)
return httpStatusCode, body, err
var successPayload []apilib.User
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr)
if err == nil && httpStatusCode == 200 {
err = json.Unmarshal(body, &successPayload)
}
return httpStatusCode, successPayload, err
}
//Add project role member accompany with projectID

51
api/internal.go Normal file
View File

@ -0,0 +1,51 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"net/http"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/utils/log"
)
// InternalAPI handles request of harbor admin...
type InternalAPI struct {
BaseAPI
}
// Prepare validates the URL and parms
func (ia *InternalAPI) Prepare() {
var currentUserID int
currentUserID = ia.ValidateUser()
isAdmin, err := dao.IsAdminRole(currentUserID)
if err != nil {
log.Errorf("Error occurred in IsAdminRole:%v", err)
ia.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if !isAdmin {
log.Error("Guests doesn't have the permisson to request harbor internal API.")
ia.CustomAbort(http.StatusForbidden, "Guests doesn't have the permisson to request harbor internal API.")
}
}
// SyncRegistry ...
func (ia *InternalAPI) SyncRegistry() {
err := SyncRegistry()
if err != nil {
ia.CustomAbort(http.StatusInternalServerError, "internal error")
}
}

125
api/log_test.go Normal file
View File

@ -0,0 +1,125 @@
package api
import (
"fmt"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/tests/apitests/apilib"
"strconv"
"testing"
"time"
)
func TestLogGet(t *testing.T) {
fmt.Println("Testing Log API")
assert := assert.New(t)
apiTest := newHarborAPI()
//prepare for test
admin := &usrInfo{"admin", "Harbor12345"}
var project apilib.ProjectReq
project.ProjectName = "my_project"
project.Public = 1
//add the project first.
fmt.Println("add the project first.")
reply, err := apiTest.ProjectsPost(*admin, project)
if err != nil {
t.Error("Error while creat project", err.Error())
t.Log(err)
} else {
assert.Equal(int(201), reply, "Case 2: Project creation status should be 201")
}
//case 1: right parameters, expect the right output
now := fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err := apiTest.LogGet(*admin, "0", now, "3")
if err != nil {
t.Error("Error while get log information", err.Error())
t.Log(err)
} else {
assert.Equal(1, len(result), "lines of logs should be equal")
assert.Equal(int32(1), result[0].LogId, "LogId should be equal")
assert.Equal("my_project/", result[0].RepoName, "RepoName should be equal")
assert.Equal("N/A", result[0].RepoTag, "RepoTag should be equal")
assert.Equal("create", result[0].Operation, "Operation should be equal")
}
//case 2: wrong format of start_time parameter, expect the wrong output
now = fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err = apiTest.LogGet(*admin, "ss", now, "3")
if err != nil {
t.Error("Error occured while get log information since the format of start_time parameter is not right.", err.Error())
t.Log(err)
} else {
assert.Equal(int(400), statusCode, "Http status code should be 400")
}
//case 3: wrong format of end_time parameter, expect the wrong output
statusCode, result, err = apiTest.LogGet(*admin, "0", "cc", "3")
if err != nil {
t.Error("Error occured while get log information since the format of end_time parameter is not right.", err.Error())
t.Log(err)
} else {
assert.Equal(int(400), statusCode, "Http status code should be 400")
}
//case 4: wrong format of lines parameter, expect the wrong output
now = fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err = apiTest.LogGet(*admin, "0", now, "s")
if err != nil {
t.Error("Error occured while get log information since the format of lines parameter is not right.", err.Error())
t.Log(err)
} else {
assert.Equal(int(400), statusCode, "Http status code should be 400")
}
//case 5: wrong format of lines parameter, expect the wrong output
now = fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err = apiTest.LogGet(*admin, "0", now, "-5")
if err != nil {
t.Error("Error occured while get log information since the format of lines parameter is not right.", err.Error())
t.Log(err)
} else {
assert.Equal(int(400), statusCode, "Http status code should be 400")
}
//case 6: all parameters are null, expect the right output
statusCode, result, err = apiTest.LogGet(*admin, "", "", "")
if err != nil {
t.Error("Error while get log information", err.Error())
t.Log(err)
} else {
assert.Equal(1, len(result), "lines of logs should be equal")
assert.Equal(int32(1), result[0].LogId, "LogId should be equal")
assert.Equal("my_project/", result[0].RepoName, "RepoName should be equal")
assert.Equal("N/A", result[0].RepoTag, "RepoTag should be equal")
assert.Equal("create", result[0].Operation, "Operation should be equal")
}
//get the project
var projects []apilib.Project
var addProjectID int32
httpStatusCode, projects, err := apiTest.ProjectsGet(project.ProjectName, 1)
if err != nil {
t.Error("Error while search project by proName and isPublic", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
addProjectID = projects[0].ProjectId
}
//delete the project
projectID := strconv.Itoa(int(addProjectID))
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
if err != nil {
t.Error("Error while delete project", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "Case 1: Project creation status should be 200")
//t.Log(result)
}
fmt.Printf("\n")
}

54
api/member_test.go Normal file
View File

@ -0,0 +1,54 @@
package api
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/tests/apitests/apilib"
)
func TestMemGet(t *testing.T) {
var result []apilib.User
var httpStatusCode int
var err error
assert := assert.New(t)
apiTest := newHarborAPI()
projectID := "1"
fmt.Println("Testing Member Get API")
//-------------------case 1 : response code = 200------------------------//
httpStatusCode, result, err = apiTest.GetProjectMembersByProID(*admin, projectID)
if err != nil {
t.Error("Error whihle get members by projectID", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(int(1), result[0].UserId, "User Id should be 1")
assert.Equal("admin", result[0].Username, "User name should be admin")
}
//---------case 2: Response Code=401,User need to log in first.----------//
fmt.Println("case 2: Response Code=401,User need to log in first.")
httpStatusCode, result, err = apiTest.GetProjectMembersByProID(*unknownUsr, projectID)
if err != nil {
t.Error("Error while get members by projectID", err.Error())
t.Log(err)
} else {
assert.Equal(int(401), httpStatusCode, "Case 2: Project creation status should be 401")
}
//------------case 3: Response Code=404,Project does not exist-----------//
fmt.Println("case 3: Response Code=404,Project does not exist")
projectID = "11"
httpStatusCode, result, err = apiTest.GetProjectMembersByProID(*admin, projectID)
if err != nil {
t.Error("Error while get members by projectID", err.Error())
t.Log(err)
} else {
assert.Equal(int(404), httpStatusCode, "Case 3: Project creation status should be 404")
}
fmt.Printf("\n")
}

View File

@ -290,7 +290,14 @@ func (p *ProjectAPI) List() {
projectList[i].Togglable = true
}
}
projectList[i].RepoCount = getRepoCountByProject(projectList[i].Name)
repos, err := dao.GetRepositoryByProjectName(projectList[i].Name)
if err != nil {
log.Errorf("failed to get repositories of project %s: %v", projectList[i].Name, err)
p.CustomAbort(http.StatusInternalServerError, "")
}
projectList[i].RepoCount = len(repos)
}
p.setPaginationHeader(total, page, pageSize)
@ -332,13 +339,18 @@ func (p *ProjectAPI) FilterAccessLog() {
p.DecodeJSONReq(&query)
query.ProjectID = p.projectID
query.Username = "%" + query.Username + "%"
query.BeginTime = time.Unix(query.BeginTimestamp, 0)
query.EndTime = time.Unix(query.EndTimestamp, 0)
page, pageSize := p.getPaginationParams()
logs, total, err := dao.GetAccessLogs(query, pageSize, pageSize*(page-1))
total, err := dao.GetTotalOfAccessLogs(query)
if err != nil {
log.Errorf("failed to get total of access log: %v", err)
p.CustomAbort(http.StatusInternalServerError, "")
}
logs, err := dao.GetAccessLogs(query, pageSize, pageSize*(page-1))
if err != nil {
log.Errorf("failed to get access log: %v", err)
p.CustomAbort(http.StatusInternalServerError, "")

View File

@ -9,15 +9,11 @@ import (
"time"
)
var admin, unknownUsr *usrInfo
var addProject apilib.Project
func Init() {
admin = &usrInfo{"admin", "Harbor12345"}
unknownUsr = &usrInfo{"unknown", "unknown"}
addProject.ProjectName = "test_project"
addProject.Public = 1
var addProject *apilib.ProjectReq
var addPID int
func InitAddPro() {
addProject = &apilib.ProjectReq{"test_project", 1}
}
func TestAddProject(t *testing.T) {
@ -28,11 +24,11 @@ func TestAddProject(t *testing.T) {
apiTest := newHarborAPI()
//prepare for test
Init()
InitAddPro()
//case 1: admin not login, expect project creation fail.
result, err := apiTest.ProjectsPost(*unknownUsr, addProject)
result, err := apiTest.ProjectsPost(*unknownUsr, *addProject)
if err != nil {
t.Error("Error while creat project", err.Error())
t.Log(err)
@ -44,9 +40,7 @@ func TestAddProject(t *testing.T) {
//case 2: admin successful login, expect project creation success.
fmt.Println("case 2: admin successful login, expect project creation success.")
unknownUsr = admin
result, err = apiTest.ProjectsPost(*admin, addProject)
result, err = apiTest.ProjectsPost(*admin, *addProject)
if err != nil {
t.Error("Error while creat project", err.Error())
t.Log(err)
@ -58,7 +52,7 @@ func TestAddProject(t *testing.T) {
//case 3: duplicate project name, create project fail
fmt.Println("case 3: duplicate project name, create project fail")
result, err = apiTest.ProjectsPost(*admin, addProject)
result, err = apiTest.ProjectsPost(*admin, *addProject)
if err != nil {
t.Error("Error while creat project", err.Error())
t.Log(err)
@ -66,12 +60,25 @@ func TestAddProject(t *testing.T) {
assert.Equal(int(409), result, "Case 3: Project creation status should be 409")
//t.Log(result)
}
//case 4: reponse code = 400 : Project name is illegal in length
fmt.Println("case 4 : reponse code = 400 : Project name is illegal in length ")
result, err = apiTest.ProjectsPost(*admin, apilib.ProjectReq{"t", 1})
if err != nil {
t.Error("Error while creat project", err.Error())
t.Log(err)
} else {
assert.Equal(int(400), result, "case 4 : reponse code = 400 : Project name is illegal in length ")
}
fmt.Printf("\n")
}
func TestProGet(t *testing.T) {
fmt.Println("\nTest for Project GET API")
//Get project by proName
func TestProGetByName(t *testing.T) {
fmt.Println("\nTest for Project GET API by project name")
assert := assert.New(t)
apiTest := newHarborAPI()
@ -85,10 +92,11 @@ func TestProGet(t *testing.T) {
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong")
assert.Equal(int32(1), result[0].Public, "Public is wrong")
//find add projectID
addProject.ProjectId = int32(result[0].ProjectId)
addPID = int(result[0].ProjectId)
}
//----------------------------case 2 : Response Code=401:is_public=0----------------------------//
fmt.Println("case 2: respose code:401,isPublic = 0")
httpStatusCode, result, err = apiTest.ProjectsGet("library", 0)
@ -98,10 +106,31 @@ func TestProGet(t *testing.T) {
} else {
assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 200")
}
fmt.Printf("\n")
}
//Get project by proID
func TestProGetByID(t *testing.T) {
fmt.Println("\nTest for Project GET API by project id")
assert := assert.New(t)
apiTest := newHarborAPI()
var result apilib.Project
projectID := strconv.Itoa(addPID)
//----------------------------case 1 : Response Code=200----------------------------//
fmt.Println("case 1: respose code:200")
httpStatusCode, result, err := apiTest.ProjectsGetByPID(projectID)
if err != nil {
t.Error("Error while search project by proID", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(addProject.ProjectName, result.ProjectName, "ProjectName is wrong")
assert.Equal(int32(1), result.Public, "Public is wrong")
}
fmt.Printf("\n")
}
func TestDeleteProject(t *testing.T) {
fmt.Println("\nTesting Delete Project(ProjectsPost) API")
@ -109,23 +138,53 @@ func TestDeleteProject(t *testing.T) {
apiTest := newHarborAPI()
projectID := strconv.Itoa(int(addProject.ProjectId))
//--------------------------case 1: Response Code=200---------------------------------//
projectID := strconv.Itoa(addPID)
httpStatusCode, err := apiTest.ProjectsDelete(*admin, projectID)
//--------------------------case 1: Response Code=401,User need to log in first.-----------------------//
fmt.Println("case 1: Response Code=401,User need to log in first.")
httpStatusCode, err := apiTest.ProjectsDelete(*unknownUsr, projectID)
if err != nil {
t.Error("Error while delete project", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "Case 1: Project creation status should be 200")
//t.Log(result)
assert.Equal(int(401), httpStatusCode, "Case 1: Project creation status should be 401")
}
//--------------------------case 2: Response Code=200---------------------------------//
fmt.Println("case2: respose code:200")
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
if err != nil {
t.Error("Error while delete project", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "Case 2: Project creation status should be 200")
}
//--------------------------case 3: Response Code=404,Project does not exist---------------------------------//
fmt.Println("case 3: Response Code=404,Project does not exist")
projectID = "11"
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
if err != nil {
t.Error("Error while delete project", err.Error())
t.Log(err)
} else {
assert.Equal(int(404), httpStatusCode, "Case 3: Project creation status should be 404")
}
//--------------------------case 4: Response Code=400,Invalid project id.---------------------------------//
fmt.Println("case 4: Response Code=400,Invalid project id.")
projectID = "cc"
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
if err != nil {
t.Error("Error while delete project", err.Error())
t.Log(err)
} else {
assert.Equal(int(400), httpStatusCode, "Case 4: Project creation status should be 400")
}
fmt.Printf("\n")
}
func TestProHead(t *testing.T) {
Init()
fmt.Println("\nTest for Project HEAD API")
assert := assert.New(t)

View File

@ -91,15 +91,17 @@ func (pa *RepPolicyAPI) Post() {
policy := &models.RepPolicy{}
pa.DecodeJSONReqAndValidate(policy)
po, err := dao.GetRepPolicyByName(policy.Name)
if err != nil {
log.Errorf("failed to get policy %s: %v", policy.Name, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
/*
po, err := dao.GetRepPolicyByName(policy.Name)
if err != nil {
log.Errorf("failed to get policy %s: %v", policy.Name, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if po != nil {
pa.CustomAbort(http.StatusConflict, "name is already used")
}
if po != nil {
pa.CustomAbort(http.StatusConflict, "name is already used")
}
*/
project, err := dao.GetProjectByID(policy.ProjectID)
if err != nil {
@ -169,18 +171,20 @@ func (pa *RepPolicyAPI) Put() {
policy.ProjectID = originalPolicy.ProjectID
pa.Validate(policy)
// check duplicate name
if policy.Name != originalPolicy.Name {
po, err := dao.GetRepPolicyByName(policy.Name)
if err != nil {
log.Errorf("failed to get policy %s: %v", policy.Name, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
/*
// check duplicate name
if policy.Name != originalPolicy.Name {
po, err := dao.GetRepPolicyByName(policy.Name)
if err != nil {
log.Errorf("failed to get policy %s: %v", policy.Name, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if po != nil {
pa.CustomAbort(http.StatusConflict, "name is already used")
if po != nil {
pa.CustomAbort(http.StatusConflict, "name is already used")
}
}
}
*/
if policy.TargetID != originalPolicy.TargetID {
//target of policy can not be modified when the policy is enabled

View File

@ -21,8 +21,6 @@ import (
"net/http"
"os"
"sort"
"strconv"
"strings"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
@ -35,6 +33,7 @@ import (
registry_error "github.com/vmware/harbor/utils/registry/error"
"github.com/vmware/harbor/utils"
"github.com/vmware/harbor/utils/registry/auth"
)
@ -108,7 +107,7 @@ func (ra *RepositoryAPI) Delete() {
ra.CustomAbort(http.StatusBadRequest, "repo_name is nil")
}
projectName := getProjectName(repoName)
projectName, _ := utils.ParseRepository(repoName)
project, err := dao.GetProjectByName(projectName)
if err != nil {
log.Errorf("failed to get project %s: %v", projectName, err)
@ -182,6 +181,18 @@ func (ra *RepositoryAPI) Delete() {
}(t)
}
exist, err := repositoryExist(repoName, rc)
if err != nil {
log.Errorf("failed to check the existence of repository %s: %v", repoName, err)
ra.CustomAbort(http.StatusInternalServerError, "")
}
if !exist {
if err = dao.DeleteRepository(repoName); err != nil {
log.Errorf("failed to delete repository %s: %v", repoName, err)
ra.CustomAbort(http.StatusInternalServerError, "")
}
}
go func() {
log.Debug("refreshing catalog cache")
if err := cache.RefreshCatalogCache(); err != nil {
@ -202,7 +213,7 @@ func (ra *RepositoryAPI) GetTags() {
ra.CustomAbort(http.StatusBadRequest, "repo_name is nil")
}
projectName := getProjectName(repoName)
projectName, _ := utils.ParseRepository(repoName)
project, err := dao.GetProjectByName(projectName)
if err != nil {
log.Errorf("failed to get project %s: %v", projectName, err)
@ -270,7 +281,7 @@ func (ra *RepositoryAPI) GetManifests() {
ra.CustomAbort(http.StatusBadRequest, "version should be v1 or v2")
}
projectName := getProjectName(repoName)
projectName, _ := utils.ParseRepository(repoName)
project, err := dao.GetProjectByName(projectName)
if err != nil {
log.Errorf("failed to get project %s: %v", projectName, err)
@ -397,25 +408,14 @@ func (ra *RepositoryAPI) getUsername() (string, error) {
//GetTopRepos handles request GET /api/repositories/top
func (ra *RepositoryAPI) GetTopRepos() {
var err error
var countNum int
count := ra.GetString("count")
if len(count) == 0 {
countNum = 10
} else {
countNum, err = strconv.Atoi(count)
if err != nil {
log.Errorf("Get parameters error--count, err: %v", err)
ra.CustomAbort(http.StatusBadRequest, "bad request of count")
}
if countNum <= 0 {
log.Warning("count must be a positive integer")
ra.CustomAbort(http.StatusBadRequest, "count is 0 or negative")
}
count, err := ra.GetInt("count", 10)
if err != nil || count <= 0 {
ra.CustomAbort(http.StatusBadRequest, "invalid count")
}
repos, err := dao.GetTopRepos(countNum)
repos, err := dao.GetTopRepos(count)
if err != nil {
log.Errorf("error occured in get top 10 repos: %v", err)
log.Errorf("failed to get top repos: %v", err)
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
}
ra.Data["json"] = repos
@ -439,11 +439,3 @@ func newRepositoryClient(endpoint string, insecure bool, username, password, rep
}
return client, nil
}
func getProjectName(repository string) string {
project := ""
if strings.Contains(repository, "/") {
project = repository[0:strings.LastIndex(repository, "/")]
}
return project
}

View File

@ -17,14 +17,26 @@ package api
import (
"net/http"
"strings"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/service/cache"
"github.com/vmware/harbor/utils/log"
)
const (
// MPC : count of my projects
MPC = "my_project_count"
// MRC : count of my repositories
MRC = "my_repo_count"
// PPC : count of public projects
PPC = "public_project_count"
// PRC : count of public repositories
PRC = "public_repo_count"
// TPC : total count of projects
TPC = "total_project_count"
// TRC : total count of repositories
TRC = "total_repo_count"
)
// StatisticAPI handles request to /api/statistics/
type StatisticAPI struct {
BaseAPI
@ -38,80 +50,60 @@ func (s *StatisticAPI) Prepare() {
// Get total projects and repos of the user
func (s *StatisticAPI) Get() {
statistic := map[string]int64{}
n, err := dao.GetTotalOfProjects("", 1)
if err != nil {
log.Errorf("failed to get total of public projects: %v", err)
s.CustomAbort(http.StatusInternalServerError, "")
}
statistic[PPC] = n
n, err = dao.GetTotalOfPublicRepositories("")
if err != nil {
log.Errorf("failed to get total of public repositories: %v", err)
s.CustomAbort(http.StatusInternalServerError, "")
}
statistic[PRC] = n
isAdmin, err := dao.IsAdminRole(s.userID)
if err != nil {
log.Errorf("Error occured in check admin, error: %v", err)
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
var projectList []models.Project
if isAdmin {
projectList, err = dao.GetProjects("")
n, err := dao.GetTotalOfProjects("")
if err != nil {
log.Errorf("failed to get total of projects: %v", err)
s.CustomAbort(http.StatusInternalServerError, "")
}
statistic[MPC] = n
statistic[TPC] = n
n, err = dao.GetTotalOfRepositories("")
if err != nil {
log.Errorf("failed to get total of repositories: %v", err)
s.CustomAbort(http.StatusInternalServerError, "")
}
statistic[MRC] = n
statistic[TRC] = n
} else {
projectList, err = dao.GetUserRelevantProjects(s.userID, "")
}
if err != nil {
log.Errorf("Error occured in QueryProject, error: %v", err)
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
proMap := map[string]int{}
proMap["my_project_count"] = 0
proMap["my_repo_count"] = 0
proMap["public_project_count"] = 0
proMap["public_repo_count"] = 0
var publicProjects []models.Project
publicProjects, err = dao.GetProjects("", 1)
if err != nil {
log.Errorf("Error occured in QueryPublicProject, error: %v", err)
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
proMap["public_project_count"] = len(publicProjects)
for i := 0; i < len(publicProjects); i++ {
proMap["public_repo_count"] += getRepoCountByProject(publicProjects[i].Name)
}
if isAdmin {
proMap["total_project_count"] = len(projectList)
proMap["total_repo_count"] = getTotalRepoCount()
}
for i := 0; i < len(projectList); i++ {
if isAdmin {
projectList[i].Role = models.PROJECTADMIN
n, err := dao.GetTotalOfUserRelevantProjects(s.userID, "")
if err != nil {
log.Errorf("failed to get total of projects for user %d: %v", s.userID, err)
s.CustomAbort(http.StatusInternalServerError, "")
}
if projectList[i].Role == models.PROJECTADMIN || projectList[i].Role == models.DEVELOPER ||
projectList[i].Role == models.GUEST {
proMap["my_project_count"]++
proMap["my_repo_count"] += getRepoCountByProject(projectList[i].Name)
statistic[MPC] = n
n, err = dao.GetTotalOfUserRelevantRepositories(s.userID, "")
if err != nil {
log.Errorf("failed to get total of repositories for user %d: %v", s.userID, err)
s.CustomAbort(http.StatusInternalServerError, "")
}
statistic[MRC] = n
}
s.Data["json"] = proMap
s.Data["json"] = statistic
s.ServeJSON()
}
//getReposByProject returns repo numbers of specified project
func getRepoCountByProject(projectName string) int {
repoList, err := cache.GetRepoFromCache()
if err != nil {
log.Errorf("Failed to get repo from cache, error: %v", err)
return 0
}
var resp int
if len(projectName) > 0 {
for _, r := range repoList {
if strings.Contains(r, "/") && r[0:strings.LastIndex(r, "/")] == projectName {
resp++
}
}
return resp
}
return 0
}
//getTotalRepoCount returns total repo count
func getTotalRepoCount() int {
repoList, err := cache.GetRepoFromCache()
if err != nil {
log.Errorf("Failed to get repo from cache, error: %v", err)
return 0
}
return len(repoList)
}

91
api/statistic_test.go Normal file
View File

@ -0,0 +1,91 @@
package api
import (
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/tests/apitests/apilib"
)
func TestStatisticGet(t *testing.T) {
if err := SyncRegistry(); err != nil {
t.Fatalf("failed to sync repositories from registry: %v", err)
}
fmt.Println("Testing Statistic API")
assert := assert.New(t)
apiTest := newHarborAPI()
//prepare for test
admin := &usrInfo{"admin", "Harbor12345"}
var myProCount, pubProCount, totalProCount int32
result, err := apiTest.StatisticGet(*admin)
if err != nil {
t.Error("Error while get statistic information", err.Error())
t.Log(err)
} else {
myProCount = result.MyProjectCount
pubProCount = result.PublicProjectCount
totalProCount = result.TotalProjectCount
}
//post project
var project apilib.ProjectReq
project.ProjectName = "statistic_project"
project.Public = 1
//case 2: admin successful login, expect project creation success.
fmt.Println("case 2: admin successful login, expect project creation success.")
reply, err := apiTest.ProjectsPost(*admin, project)
if err != nil {
t.Error("Error while creat project", err.Error())
t.Log(err)
} else {
assert.Equal(reply, int(201), "Case 2: Project creation status should be 201")
}
//get and compare
result, err = apiTest.StatisticGet(*admin)
if err != nil {
t.Error("Error while get statistic information", err.Error())
t.Log(err)
} else {
assert.Equal(myProCount+1, result.MyProjectCount, "MyProjectCount should be equal")
assert.Equal(int32(2), result.MyRepoCount, "MyRepoCount should be equal")
assert.Equal(pubProCount+1, result.PublicProjectCount, "PublicProjectCount should be equal")
assert.Equal(int32(2), result.PublicRepoCount, "PublicRepoCount should be equal")
assert.Equal(totalProCount+1, result.TotalProjectCount, "TotalProCount should be equal")
assert.Equal(int32(2), result.TotalRepoCount, "TotalRepoCount should be equal")
}
//get the project
var projects []apilib.Project
var addProjectID int32
httpStatusCode, projects, err := apiTest.ProjectsGet(project.ProjectName, 1)
if err != nil {
t.Error("Error while search project by proName and isPublic", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
addProjectID = projects[0].ProjectId
}
//delete the project
projectID := strconv.Itoa(int(addProjectID))
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
if err != nil {
t.Error("Error while delete project", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "Case 1: Project creation status should be 200")
//t.Log(result)
}
fmt.Printf("\n")
}

View File

@ -20,15 +20,20 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"sort"
"strings"
"time"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/service/cache"
"github.com/vmware/harbor/utils"
"github.com/vmware/harbor/utils/log"
"github.com/vmware/harbor/utils/registry"
registry_error "github.com/vmware/harbor/utils/registry/error"
)
func checkProjectPermission(userID int, projectID int64) bool {
@ -235,6 +240,207 @@ func addAuthentication(req *http.Request) {
}
}
// SyncRegistry syncs the repositories of registry with database.
func SyncRegistry() error {
log.Debugf("Start syncing repositories from registry to DB... ")
reposInRegistry, err := catalog()
if err != nil {
log.Error(err)
return err
}
var repoRecordsInDB []models.RepoRecord
repoRecordsInDB, err = dao.GetAllRepositories()
if err != nil {
log.Errorf("error occurred while getting all registories. %v", err)
return err
}
var reposInDB []string
for _, repoRecordInDB := range repoRecordsInDB {
reposInDB = append(reposInDB, repoRecordInDB.Name)
}
var reposToAdd []string
var reposToDel []string
reposToAdd, reposToDel = diffRepos(reposInRegistry, reposInDB)
if len(reposToAdd) > 0 {
log.Debugf("Start adding repositories into DB... ")
for _, repoToAdd := range reposToAdd {
project, _ := utils.ParseRepository(repoToAdd)
user, err := dao.GetAccessLogCreator(repoToAdd)
if err != nil {
log.Errorf("Error happens when getting the repository owner from access log: %v", err)
}
if len(user) == 0 {
user = "anonymous"
}
pullCount, err := dao.CountPull(repoToAdd)
if err != nil {
log.Errorf("Error happens when counting pull count from access log: %v", err)
}
repoRecord := models.RepoRecord{Name: repoToAdd, OwnerName: user, ProjectName: project, PullCount: pullCount}
if err := dao.AddRepository(repoRecord); err != nil {
log.Errorf("Error happens when adding the missing repository: %v", err)
}
log.Debugf("Add repository: %s success.", repoToAdd)
}
}
if len(reposToDel) > 0 {
log.Debugf("Start deleting repositories from DB... ")
for _, repoToDel := range reposToDel {
if err := dao.DeleteRepository(repoToDel); err != nil {
log.Errorf("Error happens when deleting the repository: %v", err)
}
log.Debugf("Delete repository: %s success.", repoToDel)
}
}
log.Debugf("Sync repositories from registry to DB is done.")
return nil
}
func catalog() ([]string, error) {
repositories := []string{}
rc, err := initRegistryClient()
if err != nil {
return repositories, err
}
repos, err := rc.Catalog()
if err != nil {
return repositories, err
}
for _, repo := range repos {
// TODO remove the workaround when the bug of registry is fixed
// TODO read it from config
endpoint := os.Getenv("REGISTRY_URL")
client, err := cache.NewRepositoryClient(endpoint, true,
"admin", repo, "repository", repo)
if err != nil {
return repositories, err
}
exist, err := repositoryExist(repo, client)
if err != nil {
return repositories, err
}
if !exist {
continue
}
repositories = append(repositories, repo)
}
return repositories, nil
}
func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string) {
var needsAdd []string
var needsDel []string
sort.Strings(reposInRegistry)
sort.Strings(reposInDB)
i, j := 0, 0
repoInR, repoInD := "", ""
for i < len(reposInRegistry) && j < len(reposInDB) {
repoInR = reposInRegistry[i]
repoInD = reposInDB[j]
d := strings.Compare(repoInR, repoInD)
if d < 0 {
i++
exist, err := projectExists(repoInR)
if err != nil {
log.Errorf("failed to check the existence of project %s: %v", repoInR, err)
continue
}
if !exist {
continue
}
needsAdd = append(needsAdd, repoInR)
} else if d > 0 {
needsDel = append(needsDel, repoInD)
j++
} else {
i++
j++
}
}
for i < len(reposInRegistry) {
repoInR = reposInRegistry[i]
i++
exist, err := projectExists(repoInR)
if err != nil {
log.Errorf("failed to check whether project of %s exists: %v", repoInR, err)
continue
}
if !exist {
continue
}
needsAdd = append(needsAdd, repoInR)
}
for j < len(reposInDB) {
needsDel = append(needsDel, reposInDB[j])
j++
}
return needsAdd, needsDel
}
func projectExists(repository string) (bool, error) {
project, _ := utils.ParseRepository(repository)
return dao.ProjectExists(project)
}
func initRegistryClient() (r *registry.Registry, err error) {
endpoint := os.Getenv("REGISTRY_URL")
addr := endpoint
if strings.Contains(endpoint, "/") {
addr = endpoint[strings.LastIndex(endpoint, "/")+1:]
}
ch := make(chan int, 1)
go func() {
var err error
var c net.Conn
for {
c, err = net.DialTimeout("tcp", addr, 20*time.Second)
if err == nil {
c.Close()
ch <- 1
} else {
log.Errorf("failed to connect to registry client, retry after 2 seconds :%v", err)
time.Sleep(2 * time.Second)
}
}
}()
select {
case <-ch:
case <-time.After(60 * time.Second):
panic("Failed to connect to registry client after 60 seconds")
}
registryClient, err := cache.NewRegistryClient(endpoint, true, "admin",
"registry", "catalog", "*")
if err != nil {
return nil, err
}
return registryClient, nil
}
func buildReplicationURL() string {
url := getJobServiceURL()
return fmt.Sprintf("%s/api/jobs/replication", url)
@ -265,25 +471,20 @@ func getJobServiceURL() string {
func getReposByProject(name string, keyword ...string) ([]string, error) {
repositories := []string{}
list, err := getAllRepos()
repos, err := dao.GetRepositoryByProjectName(name)
if err != nil {
return repositories, err
}
project := ""
rest := ""
for _, repository := range list {
project, rest = utils.ParseRepository(repository)
if project != name {
needMatchKeyword := len(keyword) > 0 && len(keyword[0]) != 0
for _, repo := range repos {
if needMatchKeyword &&
strings.Contains(repo.Name, keyword[0]) {
repositories = append(repositories, repo.Name)
continue
}
if len(keyword) > 0 && len(keyword[0]) != 0 &&
!strings.Contains(rest, keyword[0]) {
continue
}
repositories = append(repositories, repository)
repositories = append(repositories, repo.Name)
}
return repositories, nil
@ -292,3 +493,14 @@ func getReposByProject(name string, keyword ...string) ([]string, error) {
func getAllRepos() ([]string, error) {
return cache.GetRepoFromCache()
}
func repositoryExist(name string, client *registry.Repository) (bool, error) {
tags, err := client.ListTag()
if err != nil {
if regErr, ok := err.(*registry_error.Error); ok && regErr.StatusCode == http.StatusNotFound {
return false, nil
}
return false, err
}
return len(tags) != 0, nil
}

View File

@ -38,79 +38,106 @@ func AddAccessLog(accessLog models.AccessLog) error {
return err
}
//GetAccessLogs gets access logs according to different conditions
func GetAccessLogs(query models.AccessLog, limit, offset int64) ([]models.AccessLog, int64, error) {
// GetTotalOfAccessLogs ...
func GetTotalOfAccessLogs(query models.AccessLog) (int64, error) {
o := GetOrmer()
condition := ` from access_log a left join user u on a.user_id = u.user_id
where a.project_id = ? `
queryParam := make([]interface{}, 1)
queryParam := []interface{}{}
sql := `select count(*) from access_log al
where al.project_id = ?`
queryParam = append(queryParam, query.ProjectID)
if query.UserID != 0 {
condition += ` and a.user_id = ? `
queryParam = append(queryParam, query.UserID)
}
if query.Operation != "" {
condition += ` and a.operation = ? `
queryParam = append(queryParam, query.Operation)
}
if query.Username != "" {
condition += ` and u.username like ? `
queryParam = append(queryParam, query.Username)
sql = `select count(*) from access_log al
left join user u
on al.user_id = u.user_id
where al.project_id = ? and u.username like ? `
queryParam = append(queryParam, "%"+query.Username+"%")
}
sql += genFilterClauses(query, &queryParam)
var total int64
if err := o.Raw(sql, queryParam).QueryRow(&total); err != nil {
return 0, err
}
return total, nil
}
//GetAccessLogs gets access logs according to different conditions
func GetAccessLogs(query models.AccessLog, limit, offset int64) ([]models.AccessLog, error) {
o := GetOrmer()
queryParam := []interface{}{}
sql := `select al.log_id, u.username, al.repo_name,
al.repo_tag, al.operation, al.op_time
from access_log al
left join user u
on al.user_id = u.user_id
where al.project_id = ? `
queryParam = append(queryParam, query.ProjectID)
if query.Username != "" {
sql += ` and u.username like ? `
queryParam = append(queryParam, "%"+query.Username+"%")
}
sql += genFilterClauses(query, &queryParam)
sql += ` order by al.op_time desc `
sql = paginateForRawSQL(sql, limit, offset)
logs := []models.AccessLog{}
_, err := o.Raw(sql, queryParam).QueryRows(&logs)
if err != nil {
return logs, err
}
return logs, nil
}
func genFilterClauses(query models.AccessLog, queryParam *[]interface{}) string {
sql := ""
if query.Operation != "" {
sql += ` and al.operation = ? `
*queryParam = append(*queryParam, query.Operation)
}
if query.RepoName != "" {
condition += ` and a.repo_name = ? `
queryParam = append(queryParam, query.RepoName)
sql += ` and al.repo_name = ? `
*queryParam = append(*queryParam, query.RepoName)
}
if query.RepoTag != "" {
condition += ` and a.repo_tag = ? `
queryParam = append(queryParam, query.RepoTag)
sql += ` and al.repo_tag = ? `
*queryParam = append(*queryParam, query.RepoTag)
}
if query.Keywords != "" {
condition += ` and a.operation in ( `
sql += ` and al.operation in ( `
keywordList := strings.Split(query.Keywords, "/")
num := len(keywordList)
for i := 0; i < num; i++ {
if keywordList[i] != "" {
if i == num-1 {
condition += `?)`
sql += `?)`
} else {
condition += `?,`
sql += `?,`
}
queryParam = append(queryParam, keywordList[i])
*queryParam = append(*queryParam, keywordList[i])
}
}
}
if query.BeginTimestamp > 0 {
condition += ` and a.op_time >= ? `
queryParam = append(queryParam, query.BeginTime)
sql += ` and al.op_time >= ? `
*queryParam = append(*queryParam, query.BeginTime)
}
if query.EndTimestamp > 0 {
condition += ` and a.op_time <= ? `
queryParam = append(queryParam, query.EndTime)
sql += ` and al.op_time <= ? `
*queryParam = append(*queryParam, query.EndTime)
}
condition += ` order by a.op_time desc `
totalSQL := `select count(*) ` + condition
logs := []models.AccessLog{}
var total int64
if err := o.Raw(totalSQL, queryParam).QueryRow(&total); err != nil {
return logs, 0, err
}
condition = paginateForRawSQL(condition, limit, offset)
recordsSQL := `select a.log_id, u.username, a.repo_name, a.repo_tag, a.operation, a.op_time ` + condition
_, err := o.Raw(recordsSQL, queryParam).QueryRows(&logs)
if err != nil {
return logs, 0, err
}
return logs, total, nil
return sql
}
// AccessLog ...
@ -157,48 +184,31 @@ func GetRecentLogs(userID, linesNum int, startTime, endTime string) ([]models.Ac
return recentLogList, nil
}
//GetTopRepos return top accessed public repos
func GetTopRepos(countNum int) ([]models.TopRepo, error) {
// GetAccessLogCreator ...
func GetAccessLogCreator(repoName string) (string, error) {
o := GetOrmer()
// hide the where condition: project.public = 1, Can add to the sql when necessary.
sql := "select repo_name, COUNT(repo_name) as access_count from access_log left join project on access_log.project_id=project.project_id where access_log.operation = 'pull' group by repo_name order by access_count desc limit ? "
queryParam := []interface{}{}
queryParam = append(queryParam, countNum)
var list []models.TopRepo
_, err := o.Raw(sql, queryParam).QueryRows(&list)
sql := "select * from user where user_id = (select user_id from access_log where operation = 'push' and repo_name = ? order by op_time desc limit 1)"
var u []models.User
n, err := o.Raw(sql, repoName).QueryRows(&u)
if err != nil {
return nil, err
return "", err
}
if len(list) == 0 {
return list, nil
if n == 0 {
return "", nil
}
placeHolder := make([]string, len(list))
repos := make([]string, len(list))
for i, v := range list {
repos[i] = v.RepoName
placeHolder[i] = "?"
}
placeHolderStr := strings.Join(placeHolder, ",")
queryParam = nil
queryParam = append(queryParam, repos)
var usrnameList []models.TopRepo
sql = `select a.username as creator, a.repo_name from (select access_log.repo_name, user.username,
access_log.op_time from user left join access_log on user.user_id = access_log.user_id where
access_log.operation = 'push' and access_log.repo_name in (######) order by access_log.repo_name,
access_log.op_time ASC) a group by a.repo_name`
sql = strings.Replace(sql, "######", placeHolderStr, 1)
_, err = o.Raw(sql, queryParam).QueryRows(&usrnameList)
if err != nil {
return nil, err
}
for i := 0; i < len(list); i++ {
for _, v := range usrnameList {
if v.RepoName == list[i].RepoName {
// list[i].Creator = v.Creator
break
}
}
}
return list, nil
return u[0].Username, nil
}
// CountPull ...
func CountPull(repoName string) (int64, error) {
o := GetOrmer()
num, err := o.QueryTable("access_log").Filter("repo_name", repoName).Filter("operation", "pull").Count()
if err != nil {
log.Errorf("error in CountPull: %v ", err)
return 0, err
}
return num, nil
}

View File

@ -115,6 +115,7 @@ func clearUp(username string) {
const username string = "Tester01"
const password string = "Abc12345"
const projectName string = "test_project"
const repositoryName string = "test_repository"
const repoTag string = "test1.1"
const repoTag2 string = "test1.2"
const SysAdmin int = 1
@ -457,7 +458,7 @@ func TestGetAccessLog(t *testing.T) {
UserID: currentUser.UserID,
ProjectID: currentProject.ProjectID,
}
accessLogs, _, err := GetAccessLogs(queryAccessLog, 1000, 0)
accessLogs, err := GetAccessLogs(queryAccessLog, 1000, 0)
if err != nil {
t.Errorf("Error occurred in GetAccessLog: %v", err)
}
@ -469,6 +470,21 @@ func TestGetAccessLog(t *testing.T) {
}
}
func TestGetTotalOfAccessLogs(t *testing.T) {
queryAccessLog := models.AccessLog{
UserID: currentUser.UserID,
ProjectID: currentProject.ProjectID,
}
total, err := GetTotalOfAccessLogs(queryAccessLog)
if err != nil {
t.Fatalf("failed to get total of access log: %v", err)
}
if total != 1 {
t.Errorf("unexpected total %d != %d", total, 1)
}
}
func TestAddAccessLog(t *testing.T) {
var err error
var accessLogList []models.AccessLog
@ -485,7 +501,7 @@ func TestAddAccessLog(t *testing.T) {
if err != nil {
t.Errorf("Error occurred in AddAccessLog: %v", err)
}
accessLogList, _, err = GetAccessLogs(accessLog, 1000, 0)
accessLogList, err = GetAccessLogs(accessLog, 1000, 0)
if err != nil {
t.Errorf("Error occurred in GetAccessLog: %v", err)
}
@ -514,7 +530,7 @@ func TestAccessLog(t *testing.T) {
if err != nil {
t.Errorf("Error occurred in AccessLog: %v", err)
}
accessLogList, _, err = GetAccessLogs(accessLog, 1000, 0)
accessLogList, err = GetAccessLogs(accessLog, 1000, 0)
if err != nil {
t.Errorf("Error occurred in GetAccessLog: %v", err)
}
@ -529,6 +545,50 @@ func TestAccessLog(t *testing.T) {
}
}
func TestGetAccessLogCreator(t *testing.T) {
var err error
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "push")
if err != nil {
t.Errorf("Error occurred in AccessLog: %v", err)
}
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "push")
if err != nil {
t.Errorf("Error occurred in AccessLog: %v", err)
}
user, err := GetAccessLogCreator(currentProject.Name + "/tomcat")
if err != nil {
t.Errorf("Error occurred in GetAccessLogCreator: %v", err)
}
if user != currentUser.Username {
t.Errorf("The access log creator does not match, expected: %s, actual: %s", currentUser.Username, user)
}
}
func TestCountPull(t *testing.T) {
var err error
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "pull")
if err != nil {
t.Errorf("Error occurred in AccessLog: %v", err)
}
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "pull")
if err != nil {
t.Errorf("Error occurred in AccessLog: %v", err)
}
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "pull")
if err != nil {
t.Errorf("Error occurred in AccessLog: %v", err)
}
pullCount, err := CountPull(currentProject.Name + "/tomcat")
if err != nil {
t.Errorf("Error occurred in CountPull: %v", err)
}
if pullCount != 3 {
t.Errorf("The access log pull count does not match, expected: 3, actual: %d", pullCount)
}
}
func TestProjectExists(t *testing.T) {
var exists bool
var err error
@ -838,57 +898,9 @@ func TestGetRecentLogs(t *testing.T) {
}
func TestGetTopRepos(t *testing.T) {
err := ToggleProjectPublicity(currentProject.ProjectID, publicityOn)
_, err := GetTopRepos(10)
if err != nil {
t.Errorf("Error occurred in ToggleProjectPublicity: %v", err)
}
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/ubuntu", repoTag2, "push")
if err != nil {
t.Errorf("Error occurred in AccessLog: %v", err)
}
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/ubuntu", repoTag2, "pull")
if err != nil {
t.Errorf("Error occurred in AccessLog: %v", err)
}
topRepos, err := GetTopRepos(10)
if err != nil {
t.Errorf("error occured in getting top repos, error: %v", err)
}
if topRepos[0].RepoName != currentProject.Name+"/ubuntu" {
t.Errorf("error occured in get top reop's name, expected: %v, actual: %v", currentProject.Name+"/ubuntu", topRepos[0].RepoName)
}
if topRepos[0].AccessCount != 1 {
t.Errorf("error occured in get top reop's access count, expected: %v, actual: %v", 1, topRepos[0].AccessCount)
}
/*
if topRepos[0].Creator != currentUser.Username {
t.Errorf("error occured in get top reop's creator, expected: %v, actual: %v", currentUser.Username, topRepos[0].Creator)
}
*/
err = ToggleProjectPublicity(currentProject.ProjectID, publicityOff)
if err != nil {
t.Errorf("Error occurred in ToggleProjectPublicity: %v", err)
}
o := GetOrmer()
_, err = o.QueryTable("access_log").Filter("operation__in", "push,pull").Delete()
if err != nil {
t.Errorf("error occurred in deleting access logs, %v", err)
}
}
func TestDeleteUser(t *testing.T) {
err := DeleteUser(currentUser.UserID)
if err != nil {
t.Errorf("Error occurred in DeleteUser: %v", err)
}
user, err := GetUser(*currentUser)
if err != nil {
t.Errorf("Error occurred in GetUser: %v", err)
}
if user != nil {
t.Errorf("user is not nil after deletion, user: %+v", user)
t.Fatalf("error occured in getting top repos, error: %v", err)
}
}
@ -1514,3 +1526,80 @@ func TestDeleteProject(t *testing.T) {
}
}
func TestAddRepository(t *testing.T) {
repoRecord := models.RepoRecord{
Name: currentProject.Name + "/" + repositoryName,
OwnerName: currentUser.Username,
ProjectName: currentProject.Name,
Description: "testing repo",
PullCount: 0,
StarCount: 0,
}
err := AddRepository(repoRecord)
if err != nil {
t.Errorf("Error occurred in AddRepository: %v", err)
}
newRepoRecord, err := GetRepositoryByName(currentProject.Name + "/" + repositoryName)
if err != nil {
t.Errorf("Error occurred in GetRepositoryByName: %v", err)
}
if newRepoRecord == nil {
t.Errorf("No repository found queried by repository name: %v", currentProject.Name+"/"+repositoryName)
}
}
var currentRepository *models.RepoRecord
func TestGetRepositoryByName(t *testing.T) {
var err error
currentRepository, err = GetRepositoryByName(currentProject.Name + "/" + repositoryName)
if err != nil {
t.Errorf("Error occurred in GetRepositoryByName: %v", err)
}
if currentRepository == nil {
t.Errorf("No repository found queried by repository name: %v", currentProject.Name+"/"+repositoryName)
}
if currentRepository.Name != currentProject.Name+"/"+repositoryName {
t.Errorf("Repository name does not match, expected: %s, actual: %s", currentProject.Name+"/"+repositoryName, currentProject.Name)
}
}
func TestIncreasePullCount(t *testing.T) {
if err := IncreasePullCount(currentRepository.Name); err != nil {
log.Errorf("Error happens when increasing pull count: %v", currentRepository.Name)
}
repository, err := GetRepositoryByName(currentRepository.Name)
if err != nil {
t.Errorf("Error occurred in GetRepositoryByName: %v", err)
}
if repository.PullCount != 1 {
t.Errorf("repository pull count is not 1 after IncreasePullCount, expected: 1, actual: %d", repository.PullCount)
}
}
func TestRepositoryExists(t *testing.T) {
var exists bool
exists = RepositoryExists(currentRepository.Name)
if !exists {
t.Errorf("The repository with name: %d, does not exist", currentRepository.Name)
}
}
func TestDeleteRepository(t *testing.T) {
err := DeleteRepository(currentRepository.Name)
if err != nil {
t.Errorf("Error occurred in DeleteRepository: %v", err)
}
repository, err := GetRepositoryByName(currentRepository.Name)
if err != nil {
t.Errorf("Error occurred in GetRepositoryByName: %v", err)
}
if repository != nil {
t.Errorf("repository is not nil after deletion, repository: %+v", repository)
}
}

View File

@ -184,8 +184,7 @@ func SearchProjects(userID int) ([]models.Project, error) {
//GetTotalOfUserRelevantProjects returns the total count of
// user relevant projects
func GetTotalOfUserRelevantProjects(userID int, projectName string,
public ...int) (int64, error) {
func GetTotalOfUserRelevantProjects(userID int, projectName string) (int64, error) {
o := GetOrmer()
sql := `select count(*) from project p
left join project_member pm

167
dao/repository.go Normal file
View File

@ -0,0 +1,167 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dao
import (
"fmt"
"github.com/astaxie/beego/orm"
"github.com/vmware/harbor/models"
)
// AddRepository adds a repo to the database.
func AddRepository(repo models.RepoRecord) error {
o := GetOrmer()
sql := "insert into repository (owner_id, project_id, name, description, pull_count, star_count, creation_time, update_time) " +
"select (select user_id as owner_id from user where username=?), " +
"(select project_id as project_id from project where name=?), ?, ?, ?, ?, NOW(), NULL "
_, err := o.Raw(sql, repo.OwnerName, repo.ProjectName, repo.Name, repo.Description, repo.PullCount, repo.StarCount).Exec()
return err
}
// GetRepositoryByName ...
func GetRepositoryByName(name string) (*models.RepoRecord, error) {
o := GetOrmer()
r := models.RepoRecord{Name: name}
err := o.Read(&r, "Name")
if err == orm.ErrNoRows {
return nil, nil
}
return &r, err
}
// GetAllRepositories ...
func GetAllRepositories() ([]models.RepoRecord, error) {
o := GetOrmer()
var repos []models.RepoRecord
_, err := o.QueryTable("repository").All(&repos)
return repos, err
}
// DeleteRepository ...
func DeleteRepository(name string) error {
o := GetOrmer()
_, err := o.QueryTable("repository").Filter("name", name).Delete()
return err
}
// UpdateRepository ...
func UpdateRepository(repo models.RepoRecord) error {
o := GetOrmer()
_, err := o.Update(&repo)
return err
}
// IncreasePullCount ...
func IncreasePullCount(name string) (err error) {
o := GetOrmer()
num, err := o.QueryTable("repository").Filter("name", name).Update(
orm.Params{
"pull_count": orm.ColValue(orm.ColAdd, 1),
})
if num == 0 {
err = fmt.Errorf("Failed to increase repository pull count with name: %s %s", name, err.Error())
}
return err
}
//RepositoryExists returns whether the repository exists according to its name.
func RepositoryExists(name string) bool {
o := GetOrmer()
return o.QueryTable("repository").Filter("name", name).Exist()
}
// GetRepositoryByProjectName ...
func GetRepositoryByProjectName(name string) ([]*models.RepoRecord, error) {
sql := `select * from repository
where project_id = (
select project_id from project
where name = ?
)`
repos := []*models.RepoRecord{}
_, err := GetOrmer().Raw(sql, name).QueryRows(&repos)
return repos, err
}
//GetTopRepos returns the most popular repositories
func GetTopRepos(count int) ([]models.TopRepo, error) {
topRepos := []models.TopRepo{}
repositories := []*models.RepoRecord{}
if _, err := GetOrmer().QueryTable(&models.RepoRecord{}).
OrderBy("-PullCount", "Name").Limit(count).All(&repositories); err != nil {
return topRepos, err
}
for _, repository := range repositories {
topRepos = append(topRepos, models.TopRepo{
RepoName: repository.Name,
AccessCount: repository.PullCount,
})
}
return topRepos, nil
}
// GetTotalOfRepositories ...
func GetTotalOfRepositories(name string) (int64, error) {
qs := GetOrmer().QueryTable(&models.RepoRecord{})
if len(name) != 0 {
qs = qs.Filter("Name__contains", name)
}
return qs.Count()
}
// GetTotalOfPublicRepositories ...
func GetTotalOfPublicRepositories(name string) (int64, error) {
params := []interface{}{}
sql := `select count(*) from repository r
join project p
on r.project_id = p.project_id and p.public = 1 `
if len(name) != 0 {
sql += ` where r.name like ?`
params = append(params, "%"+name+"%")
}
var total int64
err := GetOrmer().Raw(sql, params).QueryRow(&total)
return total, err
}
// GetTotalOfUserRelevantRepositories ...
func GetTotalOfUserRelevantRepositories(userID int, name string) (int64, error) {
params := []interface{}{}
sql := `select count(*)
from repository r
join (
select p.project_id, p.public
from project p
join project_member pm
on p.project_id = pm.project_id
where pm.user_id = ?
) as pp
on r.project_id = pp.project_id `
params = append(params, userID)
if len(name) != 0 {
sql += ` where r.name like ?`
params = append(params, "%"+name+"%")
}
var total int64
err := GetOrmer().Raw(sql, params).QueryRow(&total)
return total, err
}

169
dao/repository_test.go Normal file
View File

@ -0,0 +1,169 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dao
import (
"testing"
"github.com/vmware/harbor/models"
)
var (
project = "library"
name = "library/repository-test"
repository = &models.RepoRecord{
Name: name,
OwnerName: "admin",
ProjectName: project,
}
)
func TestGetRepositoryByProjectName(t *testing.T) {
if err := addRepository(repository); err != nil {
t.Fatalf("failed to add repository %s: %v", name, err)
}
defer func() {
if err := deleteRepository(name); err != nil {
t.Fatalf("failed to delete repository %s: %v", name, err)
}
}()
repositories, err := GetRepositoryByProjectName(project)
if err != nil {
t.Fatalf("failed to get repositories of project %s: %v",
project, err)
}
if len(repositories) == 0 {
t.Fatal("unexpected length of repositories: 0, at least 1")
}
exist := false
for _, repo := range repositories {
if repo.Name == name {
exist = true
break
}
}
if !exist {
t.Errorf("there is no repository whose name is %s", name)
}
}
func TestGetTotalOfRepositories(t *testing.T) {
total, err := GetTotalOfRepositories("")
if err != nil {
t.Fatalf("failed to get total of repositoreis: %v", err)
}
if err := addRepository(repository); err != nil {
t.Fatalf("failed to add repository %s: %v", name, err)
}
defer func() {
if err := deleteRepository(name); err != nil {
t.Fatalf("failed to delete repository %s: %v", name, err)
}
}()
n, err := GetTotalOfRepositories("")
if err != nil {
t.Fatalf("failed to get total of repositoreis: %v", err)
}
if n != total+1 {
t.Errorf("unexpected total: %d != %d", n, total+1)
}
}
func TestGetTotalOfPublicRepositories(t *testing.T) {
total, err := GetTotalOfPublicRepositories("")
if err != nil {
t.Fatalf("failed to get total of public repositoreis: %v", err)
}
if err := addRepository(repository); err != nil {
t.Fatalf("failed to add repository %s: %v", name, err)
}
defer func() {
if err := deleteRepository(name); err != nil {
t.Fatalf("failed to delete repository %s: %v", name, err)
}
}()
n, err := GetTotalOfPublicRepositories("")
if err != nil {
t.Fatalf("failed to get total of public repositoreis: %v", err)
}
if n != total+1 {
t.Errorf("unexpected total: %d != %d", n, total+1)
}
}
func TestGetTotalOfUserRelevantRepositories(t *testing.T) {
total, err := GetTotalOfUserRelevantRepositories(1, "")
if err != nil {
t.Fatalf("failed to get total of repositoreis for user %d: %v", 1, err)
}
if err := addRepository(repository); err != nil {
t.Fatalf("failed to add repository %s: %v", name, err)
}
defer func() {
if err := deleteRepository(name); err != nil {
t.Fatalf("failed to delete repository %s: %v", name, err)
}
}()
users, err := GetUserByProject(1, models.User{})
if err != nil {
t.Fatalf("failed to list members of project %d: %v", 1, err)
}
exist := false
for _, user := range users {
if user.UserID == 1 {
exist = true
break
}
}
if !exist {
if err = AddProjectMember(1, 1, models.DEVELOPER); err != nil {
t.Fatalf("failed to add user %d to be member of project %d: %v", 1, 1, err)
}
defer func() {
if err = DeleteProjectMember(1, 1); err != nil {
t.Fatalf("failed to delete user %d from member of project %d: %v", 1, 1, err)
}
}()
}
n, err := GetTotalOfUserRelevantRepositories(1, "")
if err != nil {
t.Fatalf("failed to get total of public repositoreis for user %d: %v", 1, err)
}
if n != total+1 {
t.Errorf("unexpected total: %d != %d", n, total+1)
}
}
func addRepository(repository *models.RepoRecord) error {
return AddRepository(*repository)
}
func deleteRepository(name string) error {
return DeleteRepository(name)
}

View File

@ -214,7 +214,10 @@ func CheckUserPassword(query models.User) (*models.User, error) {
// DeleteUser ...
func DeleteUser(userID int) error {
o := GetOrmer()
_, err := o.Raw(`update user set deleted = 1 where user_id = ?`, userID).Exec()
_, err := o.Raw(`update user
set deleted = 1, username = concat(username, "#", user_id),
email = concat(email, "#", user_id)
where user_id = ?`, userID).Exec()
return err
}

69
dao/user_test.go Normal file
View File

@ -0,0 +1,69 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dao
import (
"fmt"
"testing"
"github.com/vmware/harbor/models"
)
func TestDeleteUser(t *testing.T) {
username := "user_for_test"
email := "user_for_test@vmware.com"
password := "P@ssword"
realname := "user_for_test"
u := models.User{
Username: username,
Email: email,
Password: password,
Realname: realname,
}
id, err := Register(u)
if err != nil {
t.Fatalf("failed to register user: %v", err)
}
err = DeleteUser(int(id))
if err != nil {
t.Fatalf("Error occurred in DeleteUser: %v", err)
}
user := &models.User{}
sql := "select * from user where user_id = ?"
if err = GetOrmer().Raw(sql, id).
QueryRow(user); err != nil {
t.Fatalf("failed to query user: %v", err)
}
if user.Deleted != 1 {
t.Error("user is not deleted")
}
expected := fmt.Sprintf("%s#%d", u.Username, id)
if user.Username != expected {
t.Errorf("unexpected username: %s != %s", user.Username,
expected)
}
expected = fmt.Sprintf("%s#%d", u.Email, id)
if user.Email != expected {
t.Errorf("unexpected email: %s != %s", user.Email,
expected)
}
}

View File

@ -123,7 +123,7 @@ paths:
description: New created project.
required: true
schema:
$ref: '#/definitions/Project'
$ref: '#/definitions/ProjectReq'
tags:
- Products
responses:
@ -155,13 +155,37 @@ paths:
200:
description: Return matched project information.
schema:
type: array
items:
$ref: '#/definitions/Project'
$ref: '#/definitions/Project'
401:
description: User need to log in first.
500:
description: Internal errors.
delete:
summary: Delete project by projectID
description: |
This endpoint is aimed to delete project by project ID.
parameters:
- name: project_id
in: path
description: Project ID of project which will be deleted.
required: true
type: integer
format: int64
tags:
- Products
responses:
200:
description: Project is deleted successfully.
400:
description: Invalid project id.
403:
description: User need to log in first.
404:
description: Project does not exist.
412:
description: Project contains policies, can not be deleted.
500:
description: Internal errors.
/projects/{project_id}/publicity:
put:
summary: Update properties for a selected project.
@ -682,7 +706,7 @@ paths:
404:
description: Repository or tag not found.
403:
description: Forbidden.
description: Forbidden.
/repositories/tags:
get:
summary: Get tags of a relevant repository.
@ -721,7 +745,7 @@ paths:
in: query
type: string
required: false
description: The version of manifest, valid value are "v1" and "v2", default is "v2"
description: The version of manifest, valid value are "v1" and "v2", default is "v2"
tags:
- Products
responses:
@ -1290,6 +1314,16 @@ definitions:
project_public:
type: integer
description: The flag to indicate the publicity of the project that the repository belongs to (1 is public, 0 is not)
ProjectReq:
type: object
properties:
project_name:
type: string
description: The name of the project.
public:
type: integer
format: int
description: The public status of the project.
Project:
type: object
properties:
@ -1301,7 +1335,7 @@ definitions:
type: integer
format: int32
description: The owner ID of the project always means the creator of the project.
project_name:
name:
type: string
description: The name of the project.
creation_time:
@ -1320,10 +1354,10 @@ definitions:
description: A relation field to the user table.
owner_name:
type: string
description: The owner name of the project.
description: The owner name of the project.
public:
type: boolean
format: boolean
type: integer
format: int
description: The public status of the project.
togglable:
type: boolean
@ -1348,21 +1382,37 @@ definitions:
properties:
user_id:
type: integer
format: int32
format: int
description: The ID of the user.
username:
type: string
email:
type: string
password:
password:
type: string
realname:
realname:
type: string
comment:
type: string
deleted:
type: integer
format: int32
role_name:
type: string
role_id:
type: integer
format: int
has_admin_role:
type: integer
format: int
reset_uuid:
type: string
Salt:
type: string
creation_time:
type: string
update_time:
type: string
Password:
type: object
properties:
@ -1371,7 +1421,7 @@ definitions:
description: The user's existing password.
new_password:
type: string
description: New password for marking as to be updated.
description: New password for marking as to be updated.
AccessLogFilter:
type: object
properties:
@ -1637,3 +1687,4 @@ definitions:
password:
type: string
description: The target server password.

View File

@ -1,16 +1,16 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package models
@ -23,8 +23,9 @@ func init() {
orm.RegisterModel(new(RepTarget),
new(RepPolicy),
new(RepJob),
new(User),
new(User),
new(Project),
new(Role),
new(AccessLog))
new(AccessLog),
new(RepoRecord))
}

38
models/repo.go Normal file
View File

@ -0,0 +1,38 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package models
import (
"time"
)
// RepoRecord holds the record of an repository in DB, all the infors are from the registry notification event.
type RepoRecord struct {
RepositoryID string `orm:"column(repository_id);pk" json:"repository_id"`
Name string `orm:"column(name)" json:"name"`
OwnerName string `orm:"-"`
OwnerID int64 `orm:"column(owner_id)" json:"owner_id"`
ProjectName string `orm:"-"`
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
Description string `orm:"column(description)" json:"description"`
PullCount int64 `orm:"column(pull_count)" json:"pull_count"`
StarCount int64 `orm:"column(star_count)" json:"star_count"`
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
}
//TableName is required by by beego orm to map RepoRecord to table repository
func (rp *RepoRecord) TableName() string {
return "repository"
}

View File

@ -24,6 +24,7 @@ import (
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/service/cache"
"github.com/vmware/harbor/utils"
"github.com/vmware/harbor/utils/log"
"github.com/astaxie/beego"
@ -55,11 +56,7 @@ func (n *NotificationHandler) Post() {
for _, event := range events {
repository := event.Target.Repository
project := ""
if strings.Contains(repository, "/") {
project = repository[0:strings.LastIndex(repository, "/")]
}
project, _ := utils.ParseRepository(repository)
tag := event.Target.Tag
action := event.Action
@ -80,6 +77,18 @@ func (n *NotificationHandler) Post() {
}
}()
go func() {
exist := dao.RepositoryExists(repository)
if exist {
return
}
log.Debugf("Add repository %s into DB.", repository)
repoRecord := models.RepoRecord{Name: repository, OwnerName: user, ProjectName: project}
if err := dao.AddRepository(repoRecord); err != nil {
log.Errorf("Error happens when adding repository: %v", err)
}
}()
operation := ""
if action == "push" {
operation = models.RepOpTransfer
@ -87,6 +96,14 @@ func (n *NotificationHandler) Post() {
go api.TriggerReplicationByRepository(repository, []string{tag}, operation)
}
if action == "pull" {
go func() {
log.Debugf("Increase the repository %s pull count.", repository)
if err := dao.IncreasePullCount(repository); err != nil {
log.Errorf("Error happens when increasing pull count: %v", repository)
}
}()
}
}
}

View File

@ -27,12 +27,12 @@
vm.projectName = '';
vm.isOpen = false;
vm.isProjectMember = false;
vm.target = 'repositories';
vm.isPublic = Number(getParameterByName('is_public', $location.absUrl()));
vm.publicity = (vm.isPublic === 1) ? true : false;
if(getParameterByName('is_public', $location.absUrl())) {
vm.isPublic = getParameterByName('is_public', $location.absUrl()) === 'true' ? 1 : 0;
vm.publicity = (vm.isPublic === 1) ? true : false;
}
vm.retrieve = retrieve;
vm.filterInput = '';
vm.selectItem = selectItem;
@ -58,20 +58,20 @@
}
function getProjectSuccess(data, status) {
vm.projects = data;
if(vm.projects == null) {
vm.isPublic = 1;
vm.publicity = true;
vm.projectType = 'public_projects';
console.log('vm.projects is null, load public projects.');
return;
vm.projects = data || [];
if(vm.projects.length == 0){
if(vm.isPublic === 0) {
$window.location.href = '/project';
}else{
vm.publicity = true;
vm.projectType = 'public_projects';
vm.target = 'repositories';
}
}
if(angular.isArray(vm.projects) && vm.projects.length > 0) {
vm.selectedProject = vm.projects[0];
}else{
$window.location.href = '/project';
}
if(getParameterByName('project_id', $location.absUrl())){
@ -81,10 +81,10 @@
}
});
}
$location.search('project_id', vm.selectedProject.project_id);
vm.checkProjectMember(vm.selectedProject.project_id);
vm.checkProjectMember(vm.selectedProject.project_id);
vm.resultCount = vm.projects.length;
$scope.$watch('vm.filterInput', function(current, origin) {
@ -102,11 +102,12 @@
function selectItem(item) {
vm.selectedProject = item;
$location.search('project_id', vm.selectedProject.project_id);
vm.checkProjectMember(vm.selectedProject.project_id);
}
$scope.$on('$locationChangeSuccess', function(e) {
var projectId = getParameterByName('project_id', $location.absUrl());
vm.isOpen = false;
vm.projectId = getParameterByName('project_id', $location.absUrl());
vm.isOpen = false;
});
function checkProjectMember(projectId) {
@ -121,7 +122,7 @@
}
function getCurrentProjectMemberFailed(data, status) {
vm.isProjectMember = false;
vm.isProjectMember = false;
console.log('Current user has no member for the project:' + status + ', location.url:' + $location.url());
}
@ -132,6 +133,7 @@
restrict: 'E',
templateUrl: '/static/resources/js/components/details/retrieve-projects.directive.html',
scope: {
'target': '=',
'isOpen': '=',
'selectedProject': '=',
'publicity': '=',
@ -147,7 +149,7 @@
function link(scope, element, attrs, ctrl) {
$(document).on('click', clickHandler);
function clickHandler(e) {
$('[data-toggle="popover"]').each(function () {
if (!$(this).is(e.target) &&

View File

@ -56,20 +56,14 @@
vm.pageSize = 20;
$scope.$watch('vm.page', function(current, origin) {
if(current !== 1) {
if(current) {
vm.page = current;
retrieve(vm.queryParams, vm.page, vm.pageSize);
}
});
retrieve(vm.queryParams, vm.page, vm.pageSize);
$scope.$on('$locationChangeSuccess', function() {
if(vm.publicity) {
vm.target = 'repositories';
}
vm.projectId = getParameterByName('project_id', $location.absUrl());
vm.queryParams = {
'beginTimestamp' : vm.beginTimestamp,

View File

@ -28,7 +28,7 @@
$scope.p = {};
var vm0 = $scope.p;
vm0.projectName = '';
vm.isPublic = false;
vm.isPublic = 0;
vm.addProject = addProject;
vm.cancel = cancel;
@ -37,9 +37,20 @@
vm.hasError = false;
vm.errorMessage = '';
$scope.$watch('vm.isOpen', function(current) {
if(current) {
$scope.form.$setPristine();
$scope.form.$setUntouched();
vm0.projectName = '';
vm.isPublic = 0;
}
});
function addProject(p) {
if(p && angular.isDefined(p.projectName)) {
vm.isPublic = vm.isPublic ? 1 : 0;
AddProjectService(p.projectName, vm.isPublic)
.success(addProjectSuccess)
.error(addProjectFailed);
@ -74,9 +85,9 @@
}
vm.isOpen = false;
vm0.projectName = '';
vm.isPublic = false;
vm.isPublic = 0;
vm.hasError = false; vm.close = close;
vm.hasError = false;
vm.errorMessage = '';
}
@ -94,16 +105,10 @@
'scope' : {
'isOpen': '='
},
'link': link,
'controllerAs': 'vm',
'bindToController': true
};
return directive;
function link(scope, element, attrs, ctrl) {
scope.form.$setPristine();
scope.form.$setUntouched();
}
return directive;
}
})();

View File

@ -27,11 +27,7 @@
vm.toggle = toggle;
function toggle() {
if(vm.isPublic) {
vm.isPublic = false;
}else{
vm.isPublic = true;
}
vm.isPublic = vm.isPublic ? 0 : 1;
ToggleProjectPublicityService(vm.projectId, vm.isPublic)
.success(toggleProjectPublicitySuccess)
.error(toggleProjectPublicityFailed);
@ -53,12 +49,7 @@
$scope.$emit('modalMessage', message);
$scope.$emit('raiseError', true);
if(vm.isPublic) {
vm.isPublic = false;
}else{
vm.isPublic = true;
}
vm.isPublic = vm.isPublic ? 0 : 1;
console.log('Failed to toggle project publicity:' + e);
}
}
@ -69,7 +60,6 @@
'templateUrl': '/static/resources/js/components/project/publicity-button.directive.html',
'scope': {
'isPublic': '=',
'owned': '=',
'projectId': '='
},
'link': link,

View File

@ -62,7 +62,7 @@
&nbsp;
<a href="javascript:void(0);" data-toggle="modal" data-target="#createPolicyModal" ng-click="vm.editReplication(r.id)" title="// 'edit_policy' | tr //"><span class="glyphicon glyphicon-pencil"></span></a>
&nbsp;
<!--a href="javascript:void(0);"><span class="glyphicon glyphicon-trash"></span></a -->
<a href="javascript:void(0);" ng-click="vm.confirmToDelete(r.id, r.name)" title="// 'delete_policy' | tr //"><span class="glyphicon glyphicon-trash"></span></a>
</td>
<td width="5%">
<a ng-if= "r.error_job_count > 0" title = "// 'found_error_in_replication_job' | tr: [r.error_job_count] //"><span class="glyphicon glyphicon-exclamation-sign color-danger" ></span></a>

View File

@ -37,9 +37,9 @@
};
}
ListReplicationController.$inject = ['$scope', 'getParameterByName', '$location', 'ListReplicationPolicyService', 'ToggleReplicationPolicyService', 'ListReplicationJobService', '$window', '$filter', 'trFilter', 'jobStatus'];
ListReplicationController.$inject = ['$scope', 'getParameterByName', '$location', 'ListReplicationPolicyService', 'ToggleReplicationPolicyService', 'DeleteReplicationPolicyService', 'ListReplicationJobService', '$window', '$filter', 'trFilter', 'jobStatus'];
function ListReplicationController($scope, getParameterByName, $location, ListReplicationPolicyService, ToggleReplicationPolicyService, ListReplicationJobService, $window, $filter, trFilter, jobStatus) {
function ListReplicationController($scope, getParameterByName, $location, ListReplicationPolicyService, ToggleReplicationPolicyService, DeleteReplicationPolicyService, ListReplicationJobService, $window, $filter, trFilter, jobStatus) {
var vm = this;
vm.sectionHeight = {'min-height': '1260px'};
@ -51,6 +51,9 @@
vm.addReplication = addReplication;
vm.editReplication = editReplication;
vm.deleteReplicationPolicy = deleteReplicationPolicy;
vm.confirmToDelete = confirmToDelete;
vm.searchReplicationPolicy = searchReplicationPolicy;
vm.searchReplicationJob = searchReplicationJob;
@ -176,6 +179,40 @@
console.log('Selected policy ID:' + vm.policyId);
}
function deleteReplicationPolicy() {
DeleteReplicationPolicyService(vm.policyId)
.success(deleteReplicationPolicySuccess)
.error(deleteReplicationPolicyFailed);
}
function deleteReplicationPolicySuccess(data, status) {
console.log('Successful delete replication policy.');
vm.retrievePolicy();
}
function deleteReplicationPolicyFailed(data, status) {
$scope.$emit('modalTitle', $filter('tr')('error'));
$scope.$emit('modalMessage', $filter('tr')('failed_to_delete_replication_policy'));
$scope.$emit('raiseError', true);
console.log('Failed to delete replication policy.');
}
function confirmToDelete(policyId, policyName) {
vm.policyId = policyId;
$scope.$emit('modalTitle', $filter('tr')('confirm_delete_policy_title'));
$scope.$emit('modalMessage', $filter('tr')('confirm_delete_policy', [policyName]));
var emitInfo = {
'confirmOnly': false,
'contentType': 'text/plain',
'action': vm.deleteReplicationPolicy
};
$scope.$emit('raiseInfo', emitInfo);
}
function confirmToTogglePolicy(policyId, enabled, name) {
vm.policyId = policyId;
vm.enabled = enabled;

View File

@ -48,7 +48,6 @@
vm.tagCount = {};
vm.projectId = getParameterByName('project_id', $location.absUrl());
vm.retrieve();
$scope.$on('$locationChangeSuccess', function() {
vm.projectId = getParameterByName('project_id', $location.absUrl());
@ -62,11 +61,9 @@
vm.repositories = current || [];
}
});
$scope.$watch('vm.page', function(current) {
if(current !== 1) {
if(current) {
vm.page = current;
vm.retrieve();
}

View File

@ -24,13 +24,14 @@
function NavigationDetailsController($window, $location, $scope, getParameterByName) {
var vm = this;
vm.projectId = getParameterByName('project_id', $location.absUrl());
$scope.$on('$locationChangeSuccess', function() {
vm.projectId = getParameterByName('project_id', $location.absUrl());
});
vm.path = $location.path();
}
@ -51,19 +52,23 @@
return directive;
function link(scope, element, attrs, ctrl) {
var visited = ctrl.path.substring(1);
if(visited.indexOf('?') >= 0) {
visited = ctrl.url.substring(1, ctrl.url.indexOf('?'));
}
if(visited) {
element.find('a[tag="' + visited + '"]').addClass('active');
}else{
element.find('a:first').addClass('active');
}
ctrl.target = visited;
scope.$watch('vm.target', function(current) {
if(current) {
ctrl.target = current;
element.find('a').removeClass('active');
element.find('a[tag="' + ctrl.target + '"]').addClass('active');
}
});
element.find('a').on('click', click);
function click(event) {

View File

@ -20,14 +20,17 @@
.module('harbor.layout.project')
.controller('ProjectController', ProjectController);
ProjectController.$inject = ['$scope', 'ListProjectService', '$timeout', 'currentUser', 'getRole', '$filter', 'trFilter'];
ProjectController.$inject = ['$scope', 'ListProjectService', 'DeleteProjectService', '$timeout', 'currentUser', 'getRole', '$filter', 'trFilter'];
function ProjectController($scope, ListProjectService, $timeout, currentUser, getRole, $filter, trFilter) {
function ProjectController($scope, ListProjectService, DeleteProjectService, $timeout, currentUser, getRole, $filter, trFilter) {
var vm = this;
vm.isOpen = false;
vm.projectName = '';
vm.publicity = 0;
vm.page = 1;
vm.pageSize = 10;
vm.retrieve = retrieve;
vm.showAddProject = showAddProject;
@ -35,10 +38,11 @@
vm.showAddButton = showAddButton;
vm.togglePublicity = togglePublicity;
vm.user = currentUser.get();
vm.retrieve();
vm.getProjectRole = getProjectRole;
vm.searchProjectByKeyPress = searchProjectByKeyPress;
vm.confirmToDelete = confirmToDelete;
vm.deleteProject = deleteProject;
//Error message dialog handler for project.
@ -57,19 +61,42 @@
};
vm.contentType = 'text/plain';
vm.confirmOnly = true;
$timeout(function() {
$scope.$broadcast('showDialog', true);
}, 350);
}
});
$scope.$on('raiseInfo', function(e, val) {
if(val) {
vm.action = function() {
val.action();
$scope.$broadcast('showDialog', false);
};
vm.contentType = val.contentType;
vm.confirmOnly = val.confirmOnly;
$scope.$broadcast('showDialog', true);
}
});
$scope.$watch('vm.page', function(current) {
if(current) {
vm.page = current;
vm.retrieve();
}
});
function retrieve() {
ListProjectService(vm.projectName, vm.publicity)
.success(listProjectSuccess)
.error(listProjectFailed);
ListProjectService(vm.projectName, vm.publicity, vm.page, vm.pageSize)
.then(listProjectSuccess)
.catch(listProjectFailed);
}
function listProjectSuccess(data, status) {
vm.projects = data || [];
function listProjectSuccess(response) {
vm.totalCount = response.headers('X-Total-Count');
vm.projects = response.data || [];
}
function getProjectRole(roleId) {
@ -80,7 +107,7 @@
return '';
}
function listProjectFailed(data, status) {
function listProjectFailed(response) {
$scope.$emit('modalTitle', $filter('tr')('error'));
$scope.$emit('modalMessage', $filter('tr')('failed_to_get_project'));
$scope.$emit('raiseError', true);
@ -92,11 +119,7 @@
});
function showAddProject() {
if(vm.isOpen){
vm.isOpen = false;
}else{
vm.isOpen = true;
}
vm.isOpen = vm.isOpen ? false : true;
}
function searchProject() {
@ -104,16 +127,13 @@
}
function showAddButton() {
if(vm.publicity === 0) {
return true;
}else{
return false;
}
return (vm.publicity === 0);
}
function togglePublicity(e) {
vm.publicity = e.publicity;
vm.isOpen = false;
vm.page = 1;
vm.retrieve();
console.log('vm.publicity:' + vm.publicity);
}
@ -125,6 +145,39 @@
}
}
function confirmToDelete(projectId, projectName) {
vm.selectedProjectId = projectId;
$scope.$emit('modalTitle', $filter('tr')('confirm_delete_project_title'));
$scope.$emit('modalMessage', $filter('tr')('confirm_delete_project', [projectName]));
var emitInfo = {
'confirmOnly': false,
'contentType': 'text/plain',
'action': vm.deleteProject
};
$scope.$emit('raiseInfo', emitInfo);
}
function deleteProject() {
DeleteProjectService(vm.selectedProjectId)
.success(deleteProjectSuccess)
.error(deleteProjectFailed);
}
function deleteProjectSuccess(data, status) {
console.log('Successful delete project.');
vm.retrieve();
}
function deleteProjectFailed(data, status) {
$scope.$emit('modalTitle', $filter('tr')('error'));
$scope.$emit('modalMessage', $filter('tr')('failed_to_delete_project'));
$scope.$emit('raiseError', true);
console.log('Failed to delete project.');
}
}
})();

View File

@ -149,8 +149,13 @@ var locale_messages = {
'failed_to_add_member': 'Project member can not be added, insuffient permissions.',
'failed_to_change_member': 'Project member can not be changed, insuffient permissions.',
'failed_to_delete_member': 'Project member can not be deleted, insuffient permissions.',
'failed_to_delete_project': 'Project can not be deleted, insuffient permissions.',
'confirm_delete_project_title': 'Project Deletion',
'confirm_delete_project': 'Are you sure to delete the project "$0" ?',
'confirm_delete_user_title': 'User Deletion',
'confirm_delete_user': 'Are you sure to delete the user "$0" ?',
'confirm_delete_policy_title': 'Replication Policy Deletion',
'confirm_delete_policy': 'Are you sure to delete the replication policy "$0" ?',
'confirm_delete_destination_title': 'Destination Deletion',
'confirm_delete_destination': 'Are you sure to delete the destination "$0" ?',
'replication': 'Replication',
@ -194,6 +199,7 @@ var locale_messages = {
'successful_signed_up': 'Signed up successfully.',
'add_new_policy': 'Add New Policy',
'edit_policy': 'Edit Policy',
'delete_policy': 'Delete Policy',
'add_new_title': 'Add User',
'add_new': 'Add',
'successful_added': 'New user added successfully.',
@ -246,6 +252,7 @@ var locale_messages = {
'failed_to_get_destination_policies': 'Failed to get destination policies.',
'failed_to_get_replication_policy': 'Failed to get replication policy.',
'failed_to_update_replication_policy': 'Failed to update replication policy.',
'failed_to_delete_replication_policy': 'Failed to delete replication policy.',
'failed_to_delete_destination': 'Failed to delete destination.',
'failed_to_create_destination': 'Failed to create destination.',
'failed_to_update_destination': 'Failed to update destination.',

View File

@ -148,8 +148,13 @@ var locale_messages = {
'failed_to_add_member': '无法添加项目成员,权限不足。',
'failed_to_change_member': '无法修改项目成员,权限不足。',
'failed_to_delete_member': '无法删除项目成员,权限不足。',
'failed_to_delete_project' : '无法删除项目,权限不足。',
'confirm_delete_project_title': '删除项目',
'confirm_delete_project': '确认删除项目 "$0" ?',
'confirm_delete_user_title': '删除用户',
'confirm_delete_user': '确认删除用户 "$0" ?',
'confirm_delete_policy_title': '删除复制策略',
'confirm_delete_policy': '确认删除复制策略 "$0" ?',
'confirm_delete_destination_title': '删除目标',
'confirm_delete_destination': '确认删除目标 "$0"',
'replication': '复制',
@ -194,6 +199,7 @@ var locale_messages = {
'successful_signed_up': '注册成功。',
'add_new_policy': '新增策略',
'edit_policy': '修改策略',
'delete_policy': '删除策略',
'add_new_title': '新增用户',
'add_new': '新增',
'successful_added': '新增用户成功。',
@ -246,6 +252,7 @@ var locale_messages = {
'failed_to_get_destination_policies': '获取目标关联策略数据失败。',
'failed_to_get_replication_policy': '获取复制策略失败。',
'failed_to_update_replication_policy': '修改复制策略失败。',
'failed_to_delete_replication_policy': '删除复制策略失败。',
'failed_to_delete_destination': '删除目标失败。',
'failed_to_create_destination': '创建目标失败。',
'failed_to_update_destination': '修改目标失败。',

View File

@ -0,0 +1,34 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
'use strict';
angular
.module('harbor.services.project')
.factory('DeleteProjectService', DeleteProjectService);
DeleteProjectService.$inject = ['$http', '$log'];
function DeleteProjectService($http, $log) {
return DeleteProject;
function DeleteProject(projectId) {
return $http
.delete('/api/projects/' + projectId);
}
}
})();

View File

@ -25,10 +25,14 @@
return ListProject;
function ListProject(projectName, isPublic) {
function ListProject(projectName, isPublic, page, pageSize) {
$log.info('list project projectName:' + projectName, ', isPublic:' + isPublic);
var urlParams = '';
if(angular.isDefined(page, pageSize)) {
urlParams = '?page=' + page + '&page_size=' + pageSize;
}
return $http
.get('/api/projects', {
.get('/api/projects' + urlParams, {
'params' : {
'is_public': isPublic,
'project_name': projectName

View File

@ -0,0 +1,34 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
'use strict';
angular
.module('harbor.services.replication.policy')
.factory('DeleteReplicationPolicyService', DeleteReplicationPolicyService);
DeleteReplicationPolicyService.$inject = ['$http'];
function DeleteReplicationPolicyService($http) {
return deleteReplicationPolicy;
function deleteReplicationPolicy(policyId) {
return $http
.delete('/api/policies/replication/' + policyId);
}
}
})();

View File

@ -31,7 +31,7 @@ type Project struct {
OwnerId int32 `json:"owner_id,omitempty"`
// The name of the project.
ProjectName string `json:"project_name,omitempty"`
ProjectName string `json:"name,omitempty"`
// The creation time of the project.
CreationTime string `json:"creation_time,omitempty"`

View 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 ProjectReq struct {
// The name of the project.
ProjectName string `json:"project_name,omitempty"`
// The public status of the project.
Public int32 `json:"public,omitempty"`
}

View File

@ -1,10 +1,10 @@
/*
/*
* 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");
@ -25,7 +25,7 @@ package apilib
type User struct {
// The ID of the user.
UserId int32 `json:"user_id,omitempty"`
UserId int `json:"user_id,omitempty"`
Username string `json:"username,omitempty"`
@ -38,4 +38,18 @@ type User struct {
Comment string `json:"comment,omitempty"`
Deleted int32 `json:"deleted,omitempty"`
RoleName string `json:"role_name,omitempty"`
RoleId int32 `json:"role_id,omitempty"`
HasAdminRole int32 `json:"has_admin_role,omitempty"`
ResetUuid string `json:"reset_uuid,omitempty"`
Salt string `json:"Salt,omitempty"`
CreationTime string `json:"creation_time,omitempty"`
UpdateTime string `json:"update_time,omitempty"`
}

View File

@ -11,7 +11,7 @@ services:
ports:
- 5000:5000
command:
["serve", "/etc/registry/config.yml"]
["serve", "/etc/docker/registry/config.yml"]
mysql:
build: ./db/
restart: always

12
tests/pushimage.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
docker pull hello-world
docker pull docker
docker login -u admin -p Harbor12345 $IP:5000
docker tag hello-world $IP:5000/library/hello-world:latest
docker push $IP:5000/library/hello-world:latest
docker tag docker $IP:5000/library/docker:latest
docker push $IP:5000/library/docker:latest

View File

@ -17,11 +17,11 @@ package main
import (
"fmt"
"os"
log "github.com/vmware/harbor/utils/log"
"os"
"github.com/vmware/harbor/api"
_ "github.com/vmware/harbor/auth/db"
_ "github.com/vmware/harbor/auth/ldap"
"github.com/vmware/harbor/dao"
@ -80,5 +80,8 @@ func main() {
log.Error(err)
}
initRouters()
if err := api.SyncRegistry(); err != nil {
log.Error(err)
}
beego.Run()
}

View File

@ -65,6 +65,7 @@ func initRouters() {
beego.Router("/api/projects/:id([0-9]+)/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
beego.Router("/api/users/?:id", &api.UserAPI{})
beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
beego.Router("/api/internal/syncregistry", &api.InternalAPI{}, "post:SyncRegistry")
beego.Router("/api/repositories", &api.RepositoryAPI{})
beego.Router("/api/repositories/tags", &api.RepositoryAPI{}, "get:GetTags")
beego.Router("/api/repositories/manifests", &api.RepositoryAPI{}, "get:GetManifests")

View File

@ -37,16 +37,19 @@
<button ng-if="!vm.isOpen" class="btn btn-success" type="button" ng-show="vm.showAddButton()" ng-click="vm.showAddProject()"><span class="glyphicon glyphicon-plus"></span> // 'new_project' | tr //</button>
</div>
</div>
<add-project ng-show="vm.isOpen" is-open="vm.isOpen"></add-project>
<add-project ng-show="vm.isOpen" is-open="vm.isOpen"></add-project>
<div class="each-tab-pane">
<div class="sub-pane">
<div class="table-head-container">
<table class="table table-pane table-header">
<thead>
<th width="15%">// 'project_name' | tr //</th>
<th width="20%">// 'repositories' | tr //</th>
<th width="20%">// 'project_name' | tr //</th>
<th width="15%">// 'repositories' | tr //</th>
<th width="15%" ng-if="!vm.publicity">// 'role' | tr //</th>
<th width="30%">// 'creation_time' | tr //</th>
<th width="20%">// 'creation_time' | tr //</th>
<th width="15%">// 'publicity' | tr //</th>
<th width="10%">// 'operation' | tr //</th>
</thead>
</table>
</div>
@ -57,17 +60,21 @@
<td colspan="5" height="320px" class="empty-hint" ng-if="vm.projects.length === 0"><h4 class="text-muted">// 'no_projects_add_new_project' | tr //</h4></td>
</tr>
<tr ng-if="vm.projects.length > 0" ng-repeat="p in vm.projects">
<td width="15%"><a href="/repository#/repositories?project_id=//p.project_id//&is_public=//p.public//">//p.name//</a></td>
<td width="20%">//p.repo_count//</td>
<td width="20%"><a href="/repository#/repositories?project_id=//p.project_id//&is_public=//p.public//">//p.name//</a></td>
<td width="15%">//p.repo_count//</td>
<td width="15%" ng-if="!vm.publicity">//vm.getProjectRole(p.current_user_role_id) | tr//</td>
<td width="30%">//p.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="15%"><publicity-button is-public="p.public" owned="p.owner_id == vm.user.user_id" project-id="p.project_id"></publicity-button></td>
<td width="20%">//p.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="15%"><publicity-button is-public="p.public" project-id="p.project_id"></publicity-button></td>
<td width="10%">
&nbsp;&nbsp;<a href="javascript:void(0)" ng-click="vm.confirmToDelete(p.project_id, p.name)"><span class="glyphicon glyphicon-trash"></span></a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col-xs-4 col-md-12 well well-sm well-custom"><div class="col-md-offset-10">//vm.projects ? vm.projects.length : 0// // 'items' | tr //</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>

View File

@ -30,7 +30,7 @@
<navigation-details target="vm.target" ng-show="vm.isProjectMember"></navigation-details>
</span>
</div>
<retrieve-projects is-open="vm.isOpen" selected-project="vm.selectedProject" is-project-member="vm.isProjectMember" publicity="vm.publicity"></retrieve-projects>
<retrieve-projects target="vm.target" is-open="vm.isOpen" selected-project="vm.selectedProject" is-project-member="vm.isProjectMember" publicity="vm.publicity"></retrieve-projects>
<!-- Tab panes -->
<div class="tab-content" ng-click="vm.closeRetrievePane()">
<input type="hidden" id="HarborRegUrl" value="{{.HarborRegUrl}}">

View File

@ -58,6 +58,7 @@
<script src="/static/resources/js/services/project/services.add-project.js"></script>
<script src="/static/resources/js/services/project/services.toggle-project-publicity.js"></script>
<script src="/static/resources/js/services/project/services.stat-project.js"></script>
<script src="/static/resources/js/services/project/services.delete-project.js"></script>
<script src="/static/resources/js/services/user/services.user.module.js"></script>
<script src="/static/resources/js/services/user/services.current-user.js"></script>
@ -96,6 +97,7 @@
<script src="/static/resources/js/services/replication-policy/services.create-replication-policy.js"></script>
<script src="/static/resources/js/services/replication-policy/services.toggle-replication-policy.js"></script>
<script src="/static/resources/js/services/replication-policy/services.update-replication-policy.js"></script>
<script src="/static/resources/js/services/replication-policy/services.delete-replication-policy.js"></script>
<script src="/static/resources/js/services/replication-job/services.replication-job.module.js"></script>
<script src="/static/resources/js/services/replication-job/services.list-replication-job.js"></script>