Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
Tan Jiang 2016-08-17 16:17:56 +08:00
commit fd0a6f8b1d
260 changed files with 22604 additions and 5257 deletions

View File

@ -9,27 +9,25 @@ go_import_path: github.com/vmware/harbor
services:
- docker
- mysql
dist: trusty
addons:
apt:
packages:
- mysql-server-5.6
- mysql-client-core-5.6
- mysql-client-5.6
env:
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_USR: root
DB_PWD:
DB_PWD: root123
MYSQL_HOST: localhost
MYSQL_PORT: 3306
MYSQL_USR: root
MYSQL_PWD: root123
DOCKER_COMPOSE_VERSION: 1.7.1
HARBOR_ADMIN: admin
HARBOR_ADMIN_PASSWD: Harbor12345
UI_SECRET: tempString
MAX_JOB_WORKERS: 3
SECRET_KEY: 1234567890123456
SECRET_KEY: 1234567890123456
AUTH_MODE: db_auth
before_install:
- sudo ./tests/hostcfg.sh
@ -56,8 +54,8 @@ install:
- go get -d github.com/go-sql-driver/mysql
- go get github.com/golang/lint/golint
- go get github.com/GeertJohan/fgt
- sudo apt-get install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" docker-engine=1.11.1-0~trusty
- sudo rm /usr/local/bin/docker-compose
# - sudo rm /usr/local/bin/docker-compose
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
- chmod +x docker-compose
- sudo mv docker-compose /usr/local/bin
@ -70,21 +68,27 @@ install:
before_script:
# create tables and load data
- mysql < ./Deploy/db/registry.sql -uroot --verbose
# - mysql < ./Deploy/db/registry.sql -uroot --verbose
script:
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 fgt golint
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 go vet
script:
- sudo ./tests/testprepare.sh
- docker-compose -f Deploy/docker-compose.test.yml up -d
- go list ./... | grep -v -E 'vendor|tests|api' | xargs -L1 fgt golint
- go list ./... | grep -v -E 'vendor|tests|api' | xargs -L1 go vet
- IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
- export MYSQL_HOST=$IP
- export REGISTRY_URL=http://$IP:5000
- echo $REGISTRY_URL
- ./Deploy/coverage4gotest.sh
- goveralls -coverprofile=profile.cov -service=travis-ci
- docker-compose -f Deploy/docker-compose.test.yml down
- docker-compose -f Deploy/docker-compose.yml up -d
- docker ps
- go run tests/startuptest.go http://localhost/
- go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD}
# test for API
- sudo ./tests/testprepare.sh
- go test -v ./tests/apitests
# - sudo ./tests/testprepare.sh
# - go test -v ./tests/apitests

View File

@ -89,6 +89,6 @@ http {
server {
listen 80;
server_name harbordomain.com;
rewrite ^/(.*) https://$server_name/$1 permanent;
rewrite ^/(.*) https://$server_name:443/$1 permanent;
}
}

View File

@ -48,6 +48,9 @@ max_job_workers = 3
#**NOTE** if this changes, previously encrypted password will not be decrypted!
secret_key = secretkey1234567
#The expiration of token used by token service, default is 30 minutes
token_expiration = 30
#Determine whether the job service should verify the ssl cert when it connects to a remote registry.
#Set this flag to off when the remote registry uses a self-signed or untrusted certificate.
verify_remote_cert = on

View File

@ -54,6 +54,7 @@ crt_organizationalunit = rcp.get("configuration", "crt_organizationalunit")
crt_commonname = rcp.get("configuration", "crt_commonname")
crt_email = rcp.get("configuration", "crt_email")
max_job_workers = rcp.get("configuration", "max_job_workers")
token_expiration = rcp.get("configuration", "token_expiration")
verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
secret_key = rcp.get("configuration", "secret_key")
########
@ -109,7 +110,8 @@ render(os.path.join(templates_dir, "ui", "env"),
use_compressed_js=use_compressed_js,
ui_secret=ui_secret,
secret_key=secret_key,
verify_remote_cert=verify_remote_cert)
verify_remote_cert=verify_remote_cert,
token_expiration=token_expiration)
render(os.path.join(templates_dir, "ui", "app.conf"),
ui_conf,

View File

@ -20,3 +20,4 @@ GODEBUG=netdns=cgo
EXT_ENDPOINT=$ui_url
TOKEN_URL=http://ui
VERIFY_REMOTE_CERT=$verify_remote_cert
TOKEN_EXPIRATION=$token_expiration

View File

@ -5,12 +5,11 @@ MAINTAINER jiangd@vmware.com
RUN apt-get update \
&& apt-get install -y libldap2-dev \
&& rm -r /var/lib/apt/lists/*
RUN go get -d github.com/docker/distribution \
&& go get -d github.com/docker/libtrust \
&& go get -d github.com/go-sql-driver/mysql
COPY . /go/src/github.com/vmware/harbor
WORKDIR /go/src/github.com/vmware/harbor/jobservice
RUN go build -v -a -o /go/bin/harbor_jobservice \
&& chmod u+x /go/bin/harbor_jobservice
WORKDIR /go/bin/

View File

@ -7,14 +7,9 @@ RUN apt-get update \
&& rm -r /var/lib/apt/lists/*
COPY . /go/src/github.com/vmware/harbor
#golang.org is blocked in China
COPY ./vendor/golang.org /go/src/golang.org
WORKDIR /go/src/github.com/vmware/harbor/ui
RUN go get -d github.com/docker/distribution \
&& go get -d github.com/docker/libtrust \
&& go get -d github.com/go-sql-driver/mysql \
&& go build -v -a -o /go/bin/harbor_ui
RUN go build -v -a -o /go/bin/harbor_ui
ENV MYSQL_USR root \
MYSQL_PWD root \

View File

@ -66,6 +66,9 @@ If you are upgrading Harbor from an older version with existing data, you need t
### Run
For information on how to use Harbor, please take a look at [User Guide](docs/user_guide.md).
### Community
Get connected with Project Harbor's community and sign up with VMware {code} [https://code.vmware.com/join/](https://code.vmware.com/join/) to get invited to VMware {code} Slack group, Channel: #harbor.
### Contribution
We welcome contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a pull request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq).

View File

@ -86,7 +86,28 @@ func (b *BaseAPI) DecodeJSONReqAndValidate(v interface{}) {
// ValidateUser checks if the request triggered by a valid user
func (b *BaseAPI) ValidateUser() int {
userID, needsCheck, ok := b.GetUserIDForRequest()
if !ok {
log.Warning("No user id in session, canceling request")
b.CustomAbort(http.StatusUnauthorized, "")
}
if needsCheck {
u, err := dao.GetUser(models.User{UserID: userID})
if err != nil {
log.Errorf("Error occurred in GetUser, error: %v", err)
b.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if u == nil {
log.Warningf("User was deleted already, user id: %d, canceling request.", userID)
b.CustomAbort(http.StatusUnauthorized, "")
}
}
return userID
}
// GetUserIDForRequest tries to get user ID from basic auth header and session.
// It returns the user ID, whether need further verification(when the id is from session) and if the action is successful
func (b *BaseAPI) GetUserIDForRequest() (int, bool, bool) {
username, password, ok := b.Ctx.Request.BasicAuth()
if ok {
log.Infof("Requst with Basic Authentication header, username: %s", username)
@ -99,25 +120,17 @@ func (b *BaseAPI) ValidateUser() int {
user = nil
}
if user != nil {
return user.UserID
// User login successfully no further check required.
return user.UserID, false, true
}
}
sessionUserID := b.GetSession("userId")
if sessionUserID == nil {
log.Warning("No user id in session, canceling request")
b.CustomAbort(http.StatusUnauthorized, "")
sessionUserID, ok := b.GetSession("userId").(int)
if ok {
// The ID is from session
return sessionUserID, true, true
}
userID := sessionUserID.(int)
u, err := dao.GetUser(models.User{UserID: userID})
if err != nil {
log.Errorf("Error occurred in GetUser, error: %v", err)
b.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if u == nil {
log.Warningf("User was deleted already, user id: %d, canceling request.", userID)
b.CustomAbort(http.StatusUnauthorized, "")
}
return userID
log.Debug("No valid user id in session.")
return 0, false, false
}
// Redirect does redirection to resource URI with http header status code.

312
api/harborapi_test.go Normal file
View File

@ -0,0 +1,312 @@
//These APIs provide services for manipulating Harbor project.
package api
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http/httptest"
"path/filepath"
"runtime"
"github.com/vmware/harbor/tests/apitests/apilib"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/astaxie/beego"
"github.com/dghubble/sling"
//for test env prepare
_ "github.com/vmware/harbor/auth/db"
_ "github.com/vmware/harbor/auth/ldap"
)
type api struct {
basePath string
}
func newHarborAPI() *api {
return &api{
basePath: "",
}
}
func newHarborAPIWithBasePath(basePath string) *api {
return &api{
basePath: basePath,
}
}
type usrInfo struct {
Name string
Passwd string
}
func init() {
dao.InitDB()
_, file, _, _ := runtime.Caller(1)
apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".."+string(filepath.Separator))))
beego.BConfig.WebConfig.Session.SessionOn = true
beego.TestBeegoInit(apppath)
beego.Router("/api/search/", &SearchAPI{})
beego.Router("/api/projects/", &ProjectAPI{}, "get:List;post:Post")
beego.Router("/api/users/:id([0-9]+)/password", &UserAPI{}, "put:ChangePassword")
_ = updateInitPassword(1, "Harbor12345")
}
//Search for projects and repositories
//Implementation Notes
//The Search endpoint returns information about the projects and repositories
//offered at public status or related to the current logged in user.
//The response includes the project and repository list in a proper display order.
//@param q Search parameter for project and repository name.
//@return []Search
//func (a api) SearchGet (q string) (apilib.Search, error) {
func (a api) SearchGet(q string) (apilib.Search, error) {
_sling := sling.New().Get(a.basePath)
// create path and map variables
path := "/api/search"
_sling = _sling.Path(path)
type QueryParams struct {
Query string `url:"q,omitempty"`
}
_sling = _sling.QueryStruct(&QueryParams{Query: q})
accepts := []string{"application/json", "text/plain"}
for key := range accepts {
_sling = _sling.Set("Accept", accepts[key])
break // only use the first Accept
}
req, err := _sling.Request()
w := httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, req)
body, err := ioutil.ReadAll(w.Body)
if err != nil {
// handle error
}
var successPayload = new(apilib.Search)
err = json.Unmarshal(body, &successPayload)
return *successPayload, err
}
//Create a new project.
//Implementation Notes
//This endpoint is for user to create a new project.
//@param project New created project.
//@return void
//func (a api) ProjectsPost (prjUsr usrInfo, project apilib.Project) (int, error) {
func (a api) ProjectsPost(prjUsr usrInfo, project apilib.Project) (int, error) {
_sling := sling.New().Post(a.basePath)
// create path and map variables
path := "/api/projects/"
_sling = _sling.Path(path)
// accept header
accepts := []string{"application/json", "text/plain"}
for key := range accepts {
_sling = _sling.Set("Accept", accepts[key])
break // only use the first Accept
}
// body params
_sling = _sling.BodyJSON(project)
//fmt.Printf("project post req: %+v\n", _sling)
req, err := _sling.Request()
req.SetBasicAuth(prjUsr.Name, prjUsr.Passwd)
w := httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, req)
return w.Code, err
}
//Change password
//Implementation Notes
//Change the password on a user that already exists.
//@param userID user ID
//@param password user old and new password
//@return error
//func (a api) UsersUserIDPasswordPut (user usrInfo, userID int32, password apilib.Password) int {
func (a api) UsersUserIDPasswordPut(user usrInfo, userID int32, password apilib.Password) int {
_sling := sling.New().Put(a.basePath)
// create path and map variables
path := "/api/users/" + fmt.Sprintf("%d", userID) + "/password"
fmt.Printf("change passwd path: %s\n", path)
fmt.Printf("password %+v\n", password)
_sling = _sling.Path(path)
// accept header
accepts := []string{"application/json", "text/plain"}
for key := range accepts {
_sling = _sling.Set("Accept", accepts[key])
break // only use the first Accept
}
// body params
_sling = _sling.BodyJSON(password)
fmt.Printf("project post req: %+v\n", _sling)
req, err := _sling.Request()
req.SetBasicAuth(user.Name, user.Passwd)
fmt.Printf("project post req: %+v\n", req)
if err != nil {
// handle error
}
w := httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, req)
return w.Code
}
////Delete a repository or a tag in a repository.
////Delete a repository or a tag in a repository.
////This endpoint let user delete repositories and tags with repo name and tag.\n
////@param repoName The name of repository which will be deleted.
////@param tag Tag of a repository.
////@return void
////func (a HarborAPI) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) {
//func (a HarborAPI) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) {
// _sling := sling.New().Delete(a.basePath)
// // create path and map variables
// path := "/api/repositories"
// _sling = _sling.Path(path)
// type QueryParams struct {
// RepoName string `url:"repo_name,omitempty"`
// Tag string `url:"tag,omitempty"`
// }
// _sling = _sling.QueryStruct(&QueryParams{RepoName: repoName, Tag: tag})
// // accept header
// accepts := []string{"application/json", "text/plain"}
// for key := range accepts {
// _sling = _sling.Set("Accept", accepts[key])
// break // only use the first Accept
// }
// req, err := _sling.Request()
// req.SetBasicAuth(prjUsr.Name, prjUsr.Passwd)
// //fmt.Printf("request %+v", req)
// client := &http.Client{}
// httpResponse, err := client.Do(req)
// defer httpResponse.Body.Close()
// if err != nil {
// // handle error
// }
// return httpResponse.StatusCode, err
//}
//Return projects created by Harbor
//func (a HarborApi) ProjectsGet (projectName string, isPublic int32) ([]Project, error) {
// }
//Check if the project name user provided already exists.
//func (a HarborApi) ProjectsHead (projectName string) (error) {
//}
//Get access logs accompany with a relevant project.
//func (a HarborApi) ProjectsProjectIdLogsFilterPost (projectId int32, accessLog AccessLog) ([]AccessLog, error) {
//}
//Return a project&#39;s relevant role members.
//func (a HarborApi) ProjectsProjectIdMembersGet (projectId int32) ([]Role, error) {
//}
//Add project role member accompany with relevant project and user.
//func (a HarborApi) ProjectsProjectIdMembersPost (projectId int32, roles RoleParam) (error) {
//}
//Delete project role members accompany with relevant project and user.
//func (a HarborApi) ProjectsProjectIdMembersUserIdDelete (projectId int32, userId int32) (error) {
//}
//Return role members accompany with relevant project and user.
//func (a HarborApi) ProjectsProjectIdMembersUserIdGet (projectId int32, userId int32) ([]Role, error) {
//}
//Update project role members accompany with relevant project and user.
//func (a HarborApi) ProjectsProjectIdMembersUserIdPut (projectId int32, userId int32, roles RoleParam) (error) {
//}
//Update properties for a selected project.
//func (a HarborApi) ProjectsProjectIdPut (projectId int32, project Project) (error) {
//}
//Get repositories accompany with relevant project and repo name.
//func (a HarborApi) RepositoriesGet (projectId int32, q string) ([]Repository, error) {
//}
//Get manifests of a relevant repository.
//func (a HarborApi) RepositoriesManifestGet (repoName string, tag string) (error) {
//}
//Get tags of a relevant repository.
//func (a HarborApi) RepositoriesTagsGet (repoName string) (error) {
//}
//Get registered users of Harbor.
//func (a HarborApi) UsersGet (userName string) ([]User, error) {
//}
//Creates a new user account.
//func (a HarborApi) UsersPost (user User) (error) {
//}
//Mark a registered user as be removed.
//func (a HarborApi) UsersUserIdDelete (userId int32) (error) {
//}
//Update a registered user to change to be an administrator of Harbor.
//func (a HarborApi) UsersUserIdPut (userId int32) (error) {
//}
func updateInitPassword(userID int, password string) error {
queryUser := models.User{UserID: userID}
user, err := dao.GetUser(queryUser)
if err != nil {
return fmt.Errorf("Failed to get user, userID: %d %v", userID, err)
}
if user == nil {
return fmt.Errorf("User id: %d does not exist.", userID)
}
if user.Salt == "" {
salt, err := dao.GenerateRandomString()
if err != nil {
return fmt.Errorf("Failed to generate salt for encrypting password, %v", err)
}
user.Salt = salt
user.Password = password
err = dao.ChangeUserPassword(*user)
if err != nil {
return fmt.Errorf("Failed to update user encrypted password, userID: %d, err: %v", userID, err)
}
} else {
}
return nil
}

65
api/project_test.go Normal file
View File

@ -0,0 +1,65 @@
package api
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/tests/apitests/apilib"
)
func TestAddProject(t *testing.T) {
fmt.Println("Testing Add Project(ProjectsPost) API")
assert := assert.New(t)
apiTest := newHarborAPI()
//prepare for test
admin := &usrInfo{"admin", "Harbor12345"}
prjUsr := &usrInfo{"unknown", "unknown"}
var project apilib.Project
project.ProjectName = "test_project"
project.Public = true
//case 1: admin not login, expect project creation fail.
result, err := apiTest.ProjectsPost(*prjUsr, project)
if err != nil {
t.Error("Error while creat project", err.Error())
t.Log(err)
} else {
assert.Equal(result, int(401), "Case 1: Project creation status should be 401")
//t.Log(result)
}
//case 2: admin successful login, expect project creation success.
fmt.Println("case 2: admin successful login, expect project creation success.")
prjUsr = admin
result, err = apiTest.ProjectsPost(*prjUsr, project)
if err != nil {
t.Error("Error while creat project", err.Error())
t.Log(err)
} else {
assert.Equal(result, int(201), "Case 2: Project creation status should be 201")
//t.Log(result)
}
//case 3: duplicate project name, create project fail
fmt.Println("case 3: duplicate project name, create project fail")
result, err = apiTest.ProjectsPost(*prjUsr, project)
if err != nil {
t.Error("Error while creat project", err.Error())
t.Log(err)
} else {
assert.Equal(result, int(409), "Case 3: Project creation status should be 409")
//t.Log(result)
}
}

View File

@ -132,6 +132,10 @@ func (ra *RepositoryAPI) Delete() {
ra.CustomAbort(http.StatusInternalServerError, "")
}
if project == nil {
ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("project %s not found", projectName))
}
if project.Public == 0 {
userID := ra.ValidateUser()
if !hasProjectAdminRole(userID, project.ProjectID) {
@ -290,6 +294,10 @@ func (ra *RepositoryAPI) GetManifests() {
ra.CustomAbort(http.StatusInternalServerError, "")
}
if project == nil {
ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("project %s not found", projectName))
}
if project.Public == 0 {
userID := ra.ValidateUser()
if !checkProjectPermission(userID, project.ProjectID) {

View File

@ -39,7 +39,7 @@ type searchResult struct {
// Get ...
func (s *SearchAPI) Get() {
userID, ok := s.GetSession("userId").(int)
userID, _, ok := s.GetUserIDForRequest()
if !ok {
userID = dao.NonExistUserID
}
@ -101,18 +101,19 @@ func filterRepositories(repositories []string, projects []models.Project, keywor
i, j := 0, 0
result := []map[string]interface{}{}
for i < len(repositories) && j < len(projects) {
r := &utils.Repository{Name: repositories[i]}
d := strings.Compare(r.GetProject(), projects[j].Name)
r := repositories[i]
p, _ := utils.ParseRepository(r)
d := strings.Compare(p, projects[j].Name)
if d < 0 {
i++
continue
} else if d == 0 {
i++
if len(keyword) != 0 && !strings.Contains(r.Name, keyword) {
if len(keyword) != 0 && !strings.Contains(r, keyword) {
continue
}
entry := make(map[string]interface{})
entry["repository_name"] = r.Name
entry["repository_name"] = r
entry["project_name"] = projects[j].Name
entry["project_id"] = projects[j].ProjectID
entry["project_public"] = projects[j].Public

32
api/search_test.go Normal file
View File

@ -0,0 +1,32 @@
package api
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/tests/apitests/apilib"
)
func TestSearch(t *testing.T) {
fmt.Println("Testing Search(SearchGet) API")
assert := assert.New(t)
apiTest := newHarborAPI()
var result apilib.Search
result, err := apiTest.SearchGet("library")
//fmt.Printf("%+v\n", result)
if err != nil {
t.Error("Error while search project or repository", err.Error())
t.Log(err)
} else {
assert.Equal(result.Projects[0].Id, int64(1), "Project id should be equal")
assert.Equal(result.Projects[0].Name, "library", "Project name should be library")
assert.Equal(result.Projects[0].Public, int32(1), "Project public status should be 1 (true)")
//t.Log(result)
}
//if result.Response.StatusCode != 200 {
// t.Log(result.Response)
//}
}

View File

@ -239,6 +239,11 @@ func (ua *UserAPI) Delete() {
ua.RenderError(http.StatusForbidden, "User does not have admin role")
return
}
if ua.currentUserID == ua.userID {
ua.CustomAbort(http.StatusForbidden, "can not delete yourself")
}
var err error
err = dao.DeleteUser(ua.userID)
if err != nil {

View File

@ -1 +1,23 @@
docker-compose.sh is used to configure docker-compose.yml to pull images from platform like docker hub, daocloud.io and others. If you don't want to waste time on building images, you can execute this script to pull images from platform you prefer. Currently, we only support daocloud.io and docker hub, the default is docker hub.
## Make use of pre-built images of Harbor
Community members have helped building Harbor's docker images. If you want to save time from building Harbor from source, please follow the below instructions to quickly pull Harbor's pre-built images for installation.
### Steps
Run the command `update_compose.sh` :
```
$ ./update_compose.sh
Please enter the registry service you want to pull the pre-built images from.
Enter 1 for Docker Hub.
Enter 2 for Daocloud.io (recommended for Chinese users).
or enter other registry URL such as https://my_registry/harbor/ .
The default is 1 (Docker Hub):
```
Enter **1** to pull images from Docker Hub,
Enter **2** to pull image from Daocloud.io, recommended for Chinese users.
or Enter other registry URL like `https://my_registry/harbor/` . Do not forget the "/" and the end.
This command backs up and updates the file `Deploy/docker-compose.yml` . Next, just follow the [Harbor Installation Guide](../../docs/installation_guide.md) to install Harbor.

View File

@ -1,5 +1,12 @@
#/bin/bash
read -p "Please input the platform name you want to pull images, for docker hub, enter 1; for daocloud.io, enter 2, otherwise enter the name of the platform, the default is 1:" choice
echo " "
echo "Please enter the registry service you want to pull the pre-built images from."
echo "Enter 1 for Docker Hub."
echo "Enter 2 for Daocloud.io (recommended for Chinese users)."
echo "or enter other registry URL such as https://my_registry/harbor/ ."
read -p "The default is 1 (Docker Hub): " choice
cd ../../Deploy
template_file="docker-compose.yml.template"
yml_file='docker-compose.yml'
@ -29,4 +36,6 @@ sed -i -- '/build: .\/log\//c\ image: '$platform$log$version'' $yml_file
sed -i -- '/build: .\/db\//c\ image: '$platform$db$version'' $yml_file
sed -i -- '/ui:/{n;N;N;d}' $yml_file && sed -i -- '/ui:/a\\ image: '$platform$ui$version'' $yml_file
sed -i -- '/jobservice:/{n;N;N;d}' $yml_file && sed -i -- '/jobservice:/a\\ image: '$platform$job_service$version'' $yml_file
echo "succeed! "
echo "Succeeded! "
echo "Please follow the normal installation process to install Harbor."

View File

@ -14,7 +14,10 @@ In a test or development environment, you may choose to use a self-signed certif
-newkey rsa:4096 -nodes -sha256 -keyout ca.key \
-x509 -days 365 -out ca.crt
```
2) Generate a Certificate Signing Request, be sure to use **reg.yourdomain.com** as the CN (Common Name):
2) Generate a Certificate Signing Request:
If you use FQDN like **reg.yourdomain.com** to connect your registry host, then you must use **reg.yourdomain.com** as CN (Common Name).
Otherwise, if you use IP address to connect your registry host, CN can be anything like your name and so on:
```
openssl req \
-newkey rsa:4096 -nodes -sha256 -keyout yourdomain.com.key \
@ -22,7 +25,7 @@ In a test or development environment, you may choose to use a self-signed certif
```
3) Generate the certificate of your registry host:
You need to configure openssl first. On Ubuntu, the config file locates at **/etc/ssl/openssl.cnf**. Refer to openssl document for more information. The default CA directory of openssl is called demoCA. Let's create necessary directories and files:
On Ubuntu, the config file of openssl locates at **/etc/ssl/openssl.cnf**. Refer to openssl document for more information. The default CA directory of openssl is called demoCA. Let's create necessary directories and files:
```
mkdir demoCA
cd demoCA
@ -30,11 +33,17 @@ You need to configure openssl first. On Ubuntu, the config file locates at **/et
echo '01' > serial
cd ..
```
Then run this command to generate the certificate of your registry host:
If you're using FQDN like **reg.yourdomain.com** to connect your registry host, then run this command to generate the certificate of your registry host:
```
openssl ca -in yourdomain.com.csr -out yourdomain.com.crt -cert ca.crt -keyfile ca.key -outdir .
```
If you're using **IP** to connect your registry host, you may instead run the command below:
```
echo subjectAltName = IP:your registry host IP > extfile.cnf
openssl ca -in yourdomain.com.csr -out yourdomain.com.crt -cert ca.crt -keyfile ca.key -extfile extfile.cnf -outdir .
```
##Configuration of Nginx
After obtaining the **yourdomain.com.crt** and **yourdomain.com.key** files, change the directory to Deploy/config/nginx in Harbor project.
```
@ -54,7 +63,7 @@ Copy the template **nginx.https.conf** as the new configuration file:
```
cp nginx.https.conf nginx.conf
```
Edit the file nginx.conf and replace two occurrences of **harbordomain.com** to your own host name, such as reg.yourdomain.com .
Edit the file nginx.conf and replace two occurrences of **harbordomain.com** to your own host name, such as reg.yourdomain.com . If you use a customized port rather than the default port 443, replace the port "443" in the line "rewrite ^/(.*) https://$server_name:443/$1 permanent;" as well. Please refer to the [installation guide](https://github.com/vmware/harbor/blob/master/docs/installation_guide.md) for other required steps of port customization.
```
server {
listen 443 ssl;
@ -65,7 +74,7 @@ Edit the file nginx.conf and replace two occurrences of **harbordomain.com** to
server {
listen 80;
server_name harbordomain.com;
rewrite ^/(.*) https://$server_name$1 permanent;
rewrite ^/(.*) https://$server_name:443/$1 permanent;
```
Then look for the SSL section to make sure the files of your certificates match the names in the config file. Do not change the path of the files.
```
@ -99,14 +108,22 @@ Finally, restart Harbor:
```
docker-compose up -d
```
After setting up HTTPS for Harbor, you can verify it by the follow steps:
After setting up HTTPS for Harbor, you can verify it by the following steps:
1. Open a browser and enter the address: https://reg.yourdomain.com . It should display the user interface of Harbor.
2. On a machine with Docker daemon, make sure the option "-insecure-registry" does not present, run any docker command to verify the setup, e.g.
2. On a machine with Docker daemon, make sure the option "-insecure-registry" does not present, and you must copy ca.crt generated in the above step to /etc/docker/certs.d/yourdomain.com(or your registry host IP), if the directory does not exist, create it.
If you mapped nginx port 443 to another port, then you should instead create the directory /etc/docker/certs.d/yourdomain.com:port(or your registry host IP:port). Then run any docker command to verify the setup, e.g.
```
docker login reg.yourdomain.com
```
If you've mapped nginx 443 port to another, you need to add the port to login, like below:
```
docker login reg.yourdomain.com:port
```
##Troubleshooting
1. You may get an intermediate certificate from a certificate issuer. In this case, you should merge the intermediate certificate with your own certificate to create a certificate bundle. You can achieve this by the below command:
```
@ -124,4 +141,4 @@ After setting up HTTPS for Harbor, you can verify it by the follow steps:
cp yourdomain.com.crt /etc/pki/ca-trust/source/anchors/reg.yourdomain.com.crt
update-ca-trust
```

View File

@ -277,6 +277,102 @@ Please check the [Docker Compose command-line reference](https://docs.docker.com
By default, registry data is persisted in the target host's `/data/` directory. This data remains unchanged even when Harbor's containers are removed and/or recreated.
In addition, Harbor uses `rsyslog` to collect the logs of each container. By default, these log files are stored in the directory `/var/log/harbor/` on the target host.
## Configuring Harbor listening on a customized port
By default, Harbor listens on port 80(HTTP) and 443(HTTPS, if configured) for both admin portal and docker commands, you can configure it with a customized one.
### For HTTP protocol
1.Modify Deploy/docker-compose.yml
Replace the first "80" to a customized port, e.g. 8888:80.
```
proxy:
image: library/nginx:1.9
restart: always
volumes:
- ./config/nginx:/etc/nginx
ports:
- 8888:80
- 443:443
depends_on:
- mysql
- registry
- ui
- log
logging:
driver: "syslog"
options:
syslog-address: "tcp://127.0.0.1:1514"
tag: "proxy"
```
2.Modify Deploy/templates/registry/config.yml
Add the customized port, e.g. ":8888", after "$ui_url".
```
auth:
token:
issuer: registry-token-issuer
realm: $ui_url:8888/service/token
rootcertbundle: /etc/registry/root.crt
service: token-service
```
3.Execute Deploy/prepare script and start/restart Harbor.
```sh
$ cd Deploy
$ ./prepare
# If Harbor has already been installed, shutdown it first:
$ docker-compose down
$ docker-compose up -d
```
### For HTTPS protocol
1.Enable HTTPS in Harbor by following this [guide](https://github.com/vmware/harbor/blob/master/docs/configure_https.md).
2.Modify Deploy/docker-compose.yml
Replace the first "443" to a customized port, e.g. 4443:443.
```
proxy:
image: library/nginx:1.9
restart: always
volumes:
- ./config/nginx:/etc/nginx
ports:
- 80:80
- 4443:443
depends_on:
- mysql
- registry
- ui
- log
logging:
driver: "syslog"
options:
syslog-address: "tcp://127.0.0.1:1514"
tag: "proxy"
```
3.Modify Deploy/templates/registry/config.yml
Add the customized port, e.g. ":4443", after "$ui_url".
```
auth:
token:
issuer: registry-token-issuer
realm: $ui_url:4443/service/token
rootcertbundle: /etc/registry/root.crt
service: token-service
```
4.Execute Deploy/prepare script and start/restart Harbor.
```sh
$ cd Deploy
$ ./prepare
# If Harbor has already been installed, shutdown it first:
$ docker-compose down
$ docker-compose up -d
```
## Troubleshooting
1.When setting up Harbor behind an nginx proxy or elastic load balancing, look for the line below, in `Deploy/config/nginx/nginx.conf` and remove it from the sections if the proxy already has similar settings: `location /`, `location /v2/` and `location /service/`.
```

View File

@ -32,7 +32,6 @@ paths:
description: Search parameter for project and repository name.
required: true
type: string
format: string
tags:
- Products
responses:
@ -55,7 +54,6 @@ paths:
description: Project name for filtering results.
required: false
type: string
format: string
- name: is_public
in: query
description: Public sign for filtering projects.
@ -71,6 +69,8 @@ paths:
type: array
items:
$ref: '#/definitions/Project'
401:
description: User need to log in first.
500:
description: Internal errors.
head:
@ -83,7 +83,6 @@ paths:
description: Project name for checking exists.
required: true
type: string
format: string
tags:
- Products
responses:
@ -119,6 +118,31 @@ paths:
description: Project name already exists.
500:
description: Unexpected internal errors.
/projects/{project_id}:
get:
summary: Return specific project detail infomation
description: |
This endpoint returns specific project information by project ID.
parameters:
- name: project_id
in: path
description: Project ID for filtering results.
required: true
type: integer
format: int64
tags:
- Products
responses:
200:
description: Return matched project information.
schema:
type: array
items:
$ref: '#/definitions/Project'
401:
description: User need to log in first.
500:
description: Internal errors.
/projects/{project_id}/publicity:
put:
summary: Update properties for a selected project.
@ -128,7 +152,7 @@ paths:
- name: project_id
in: path
type: integer
format: int32
format: int64
required: true
description: Selected project ID.
- name: project
@ -161,7 +185,7 @@ paths:
- name: project_id
in: path
type: integer
format: int32
format: int64
required: true
description: Relevant project ID
- name: access_log
@ -193,7 +217,7 @@ paths:
- name: project_id
in: path
type: integer
format: int32
format: int64
required: true
description: Relevant project ID.
tags:
@ -223,7 +247,7 @@ paths:
- name: project_id
in: path
type: integer
format: int32
format: int64
required: true
description: Relevant project ID.
- name: roles
@ -257,13 +281,13 @@ paths:
- name: project_id
in: path
type: integer
format: int32
format: int64
required: true
description: Relevant project ID
- name: user_id
in: path
type: integer
format: int32
format: int
required: true
description: Relevant user ID
tags:
@ -293,13 +317,13 @@ paths:
- name: project_id
in: path
type: integer
format: int32
format: int64
required: true
description: Relevant project ID.
- name: user_id
in: path
type: integer
format: int32
format: int
required: true
description: Relevant user ID.
- name: roles
@ -330,13 +354,13 @@ paths:
- name: project_id
in: path
type: integer
format: int32
format: int64
required: true
description: Relevant project ID.
- name: user_id
in: path
type: integer
format: int32
format: int
required: true
description: Relevant user ID.
tags:
@ -377,10 +401,9 @@ paths:
description: |
This endpoint is for user to search registered users, support for filtering results with username.Notice, by now this operation is only for administrator.
parameters:
- name: user_name
- name: username
in: query
type: string
format: string
required: false
description: Username for filtering results.
tags:
@ -431,7 +454,7 @@ paths:
- name: user_id
in: path
type: integer
format: int32
format: int
required: true
description: Registered user ID
- name: profile
@ -464,7 +487,7 @@ paths:
- name: user_id
in: path
type: integer
format: int32
format: int
required: true
description: User ID for marking as to be removed.
tags:
@ -491,7 +514,7 @@ paths:
- name: user_id
in: path
type: integer
format: int32
format: int
required: true
description: Registered user ID.
- name: password
@ -508,9 +531,9 @@ paths:
400:
description: Invalid user ID; Old password is blank; New password is blank.
401:
description: Old password is not correct.
description: Don't have authority to change password. Please check login status.
403:
description: Guests can only change their own account.
description: Old password is not correct.
500:
description: Unexpected internal errors.
/users/{user_id}/sysadmin:
@ -523,7 +546,7 @@ paths:
- name: user_id
in: path
type: integer
format: int32
format: int
required: true
description: Registered user ID
tags:
@ -556,7 +579,6 @@ paths:
- name: q
in: query
type: string
format: string
required: false
description: Repo name for filtering results.
tags:
@ -584,13 +606,11 @@ paths:
- name: repo_name
in: query
type: string
format: string
required: true
description: The name of repository which will be deleted.
- name: tag
in: query
type: string
format: string
required: false
description: Tag of a repository.
tags:
@ -645,6 +665,10 @@ paths:
responses:
200:
description: Retrieved manifests from a relevant repository successfully.
schema:
$ref: '#/definitions/Repository'
404:
description: Retrieved manifests from a relevant repository not found.
500:
description: Unexpected internal errors.
/repositories/top:
@ -711,6 +735,437 @@ paths:
description: User need to login first.
500:
description: Unexpected internal errors.
/jobs/replication:
get:
summary: List filters jobs according to the policy and repository
description: |
This endpoint let user list filters jobs according to the policy and repository. (if start_time and end_time are both null, list jobs of last 10 days)
tags:
- Products
parameters:
- name: policy_id
in: query
type: integer
format: int
required: false
description: The ID of the policy that triggered this job.
- name: num
in: query
type: integer
format: int32
required: false
description: The return list length number.
- name: end_time
in: query
type: integer
format: int64
required: false
description: The end time of jobs done. (Timestamp)
- name: start_time
in: query
type: integer
format: int64
required: false
description: The start time of jobs. (Timestamp)
- name: repository
in: query
type: string
required: false
description: The respond jobs list filter by repository name.
- name: status
in: query
type: string
required: false
description: The respond jobs list filter by status.
responses:
200:
description: Get the required logs successfully.
schema:
type: array
items:
$ref: '#/definitions/JobStatus'
400:
description: Bad request because of invalid parameters.
401:
description: User need to login first.
500:
description: Unexpected internal errors.
/jobs/replication/{id}:
delete:
summary: Delete specific ID job.
description: |
This endpoint is aimed to remove specific ID job from jobservice.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: Delete job ID.
tags:
- Products
responses:
200:
description: Job deleted successfully.
400:
description: Job ID is invalid or can't remove this job.
401:
description: Only admin has this authority.
404:
description: Project ID does not exist.
500:
description: Unexpected internal errors.
/jobs/replication/{id}/log:
get:
summary: Get job logs.
description: |
This endpoint let user search job logs filtered by specific ID.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: Relevant repository ID
tags:
- Products
responses:
200:
description: Get job log successfully.
400:
description: Illegal format of provided ID value.
401:
description: User need to log in first.
404:
description: The specific repository ID's log does not exist.
500:
description: Unexpected internal errors.
/policies/replication:
get:
summary: List filters policies by name and project_id
description: |
This endpoint let user list filters policies by name and project_id, if name and project_id are nil, list returns all policies
parameters:
- name: name
in: query
type: string
required: false
description: The replication's policy name.
- name: project_id
in: query
type: integer
format: int64
required: false
description: Relevant project ID.
tags:
- Products
responses:
200:
description: Get policy successfully.
schema:
type: array
items:
$ref: '#/definitions/JobStatus'
400:
description: Invalid project ID.
401:
description: User need to log in first.
500:
description: Unexpected internal errors.
post:
summary: Post creates a policy
description: |
This endpoint let user creates a policy, and if it is enabled, the replication will be triggered right now.
parameters:
- name: policyinfo
in: body
description: Create new policy.
required: true
schema:
$ref: '#/definitions/RepPolicyPost'
tags:
- Products
responses:
201:
description: Create policy successfully.
400:
description: Invalid project ID or target ID.
401:
description: User need to log in first.
409:
description: Policy name already used or policy already exists with the same project and target.
500:
description: Unexpected internal errors.
/policies/replication/{id}:
get:
summary: Get replication policy.
description: |
This endpoint let user search replication policy by specific ID.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: policy ID
tags:
- Products
responses:
200:
description: Get job policy successfully.
schema:
$ref: '#/definitions/RepPolicy'
401:
description: User need to log in first.
404:
description: The specific repository ID's policy does not exist.
500:
description: Unexpected internal errors.
put:
summary: Put modifies name, description, target and enablement of policy.
description: |
This endpoint let user update policy name, description, target and enablement.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: policy ID
- name: policyupdate
in: body
description: Update policy name, description, target and enablement.
required: true
schema:
$ref: '#/definitions/RepPolicyUpdate'
tags:
- Products
responses:
200:
description: Update job policy content successfully.
400:
description: policy is enabled or target does not exist
401:
description: User need to log in first.
404:
description: The specific repository ID's policy does not exist.
409:
description: Policy name already used or policy already exists with the same project and target.
500:
description: Unexpected internal errors.
/policies/replication/{id}/enablement:
put:
summary: Put modifies enablement of the policy.
description: |
This endpoint let user update policy enablement flag.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: policy ID
- name: enabledflag
in: body
description: The policy enablement flag.
required: true
schema:
$ref: '#/definitions/RepPolicyEnablementReq'
tags:
- Products
responses:
200:
description: Update job policy enablement successfully.
400:
description: Invalid enabled value.
401:
description: User need to log in first.
404:
description: The specific repository ID's policy does not exist.
500:
description: Unexpected internal errors.
/targets:
get:
summary: List filters targets by name.
description: |
This endpoint let user list filters targets by name, if name is nil, list returns all targets.
parameters:
- name: name
in: query
type: string
required: false
description: The replication's target name.
tags:
- Products
responses:
200:
description: Get policy successfully.
schema:
type: array
items:
$ref: '#/definitions/RepTarget'
401:
description: User need to log in first.
500:
description: Unexpected internal errors.
post:
summary: Create a new replication target.
description: |
This endpoint is for user to create a new replication target.
parameters:
- name: reptarget
in: body
description: New created replication target.
required: true
schema:
$ref: '#/definitions/RepTargetPost'
tags:
- Products
responses:
201:
description: Replication target created successfully.
400:
description: Unsatisfied with constraints of the target creation.
401:
description: User need to log in first.
409:
description: Replication target name already exists.
500:
description: Unexpected internal errors.
/targets/ping:
post:
summary: Ping validates target.
description: |
This endpoint is for ping validates whether the target is reachable and whether the credential is valid.
parameters:
- name: id
in: query
type: integer
format: int64
required: false
description: The replication's policy ID.
tags:
- Products
responses:
200:
description: Ping target successfully.
400:
description: Target id is invalid/ endpoint is needed/ invaild URL/ network issue.
401:
description: User need to log in first or wrong username/password for remote target.
404:
description: Target not found.
500:
description: Unexpected internal errors.
/targets/{id}:
put:
summary: Update replication's target.
description: |
This endpoint is for update specific replication's target.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: The replication's target ID.
- name: repo_target
in: body
required: true
schema:
$ref: '#/definitions/RepTargetPost'
description: Updates of replication's target.
tags:
- Products
responses:
200:
description: Updated replication's target successfully.
400:
description: The target is associated with policy which is enabled.
401:
description: User need to log in first.
404:
description: Target ID does not exist.
409:
description: Target name or endpoint is already used.
500:
description: Unexpected internal errors.
get:
summary: Get replication's target.
description: This endpoint is for get specific replication's target.
tags:
- Products
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: The replication's target ID.
responses:
200:
description: Get replication's target successfully.
schema:
type: array
items:
$ref: '#/definitions/RepTarget'
401:
description: User need to log in first.
404:
description: Replication's target not found
500:
description: Unexpected internal errors.
delete:
summary: Delete specific replication's target.
description: |
This endpoint is for to delete specific replication's target.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: The replication's target ID.
tags:
- Products
responses:
200:
description: Replication's target deleted successfully.
400:
description: Replication's target ID is invalid or the target is used by policies.
401:
description: Only admin has this authority.
404:
description: Replication's target does not exist.
500:
description: Unexpected internal errors.
/targets/{id}/policies/:
get:
summary: List the target relevant policies.
description: |
This endpoint list policies filter with specific replication's target ID.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: The replication's target ID.
tags:
- Products
responses:
200:
description: Get relevant policies successfully.
schema:
type: array
items:
$ref: '#/definitions/RepPolicy'
401:
description: User need to log in first.
404:
description: Replication's target not found
500:
description: Unexpected internal errors.
definitions:
Search:
type: object
@ -765,7 +1220,7 @@ definitions:
type: integer
format: int32
description: The owner ID of the project always means the creator of the project.
name:
project_name:
type: string
description: The name of the project.
creation_time:
@ -798,6 +1253,33 @@ definitions:
repo_count:
type: integer
description: The number of the repositories under this project.
Repository:
type: object
properties:
id:
type: string
description: Repository ID
parent:
type: string
description: Parent of the image.
created:
type: string
description: Repository create time.
duration_days:
type: string
description: Duration days of the image.
author:
type: string
description: Author of the image.
architecture:
type: string
description: Architecture of the image.
docker_version:
type: string
description: Docker version of the image.
os:
type: string
description: OS of the image.
User:
type: object
properties:
@ -924,4 +1406,171 @@ definitions:
type: integer
format: int32
description: The count of the total repositories, only be seen when the user is admin.
JobStatus:
type: object
properties:
id:
type: integer
format: int64
description: The job ID.
status:
type: string
description: The status of the job.
repository:
type: string
description: The repository handled by the job.
policy_id:
type: integer
format: int64
description: The ID of the policy that triggered this job.
operation:
type: string
description: The operation of the job.
tags:
type: array
description: The repository's used tag list.
items:
$ref: '#/definitions/Tags'
creation_time:
type: string
description: The creation time of the job.
update_time:
type: string
description: The update time of the job.
Tags:
type: object
properties:
tag:
type: string
description: The repository's used tag.
RepPolicy:
type: object
properties:
id:
type: integer
format: int64
description: The policy ID.
project_id:
type: integer
format: int64
description: The project ID.
project_name:
type: string
description: The project name.
target_id:
type: integer
format: int64
description: The target ID.
target_name:
type: string
description: The target name.
name:
type: string
description: The policy name.
enabled:
type: integer
format: int
description: The policy's enabled status.
description:
type: string
description: The description of the policy.
cron_str:
type: string
description: The cron string for schedule job.
start_time:
type: string
description: The start time of the policy.
creation_time:
type: string
description: The create time of the policy.
update_time:
type: string
description: The update time of the policy.
error_job_count:
format: int
description: The error job count number for the policy.
RepPolicyPost:
type: object
properties:
project_id:
type: integer
format: int64
description: The project ID.
target_id:
type: integer
format: int64
description: The target ID.
name:
type: string
description: The policy name.
RepPolicyUpdate:
type: object
properties:
target_id:
type: integer
format: int64
description: The target ID.
name:
type: string
description: The policy name.
enabled:
type: integer
format: int
description: The policy's enabled status.
description:
type: string
description: The description of the policy.
cron_str:
type: string
description: The cron string for schedule job.
RepPolicyEnablementReq:
type: object
properties:
enabled:
type: integer
format: int
description: The policy enablement flag.
RepTarget:
type: object
properties:
id:
type: integer
format: int64
description: The target ID.
endpoint:
type: string
description: The target address URL string.
name:
type: string
description: The target name.
username:
type: string
description: The target server username.
password:
type: string
description: The target server password.
type:
type: integer
format: int
description: Reserved field.
creation_time:
type: string
description: The create time of the policy.
update_time:
type: string
description: The update time of the policy.
RepTargetPost:
type: object
properties:
endpoint:
type: string
description: The target address URL string.
name:
type: string
description: The target name.
username:
type: string
description: The target server username.
password:
type: string
description: The target server password.

View File

@ -21,6 +21,8 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
"time"
@ -34,9 +36,32 @@ import (
const (
issuer = "registry-token-issuer"
privateKey = "/etc/ui/private_key.pem"
expiration = 5 //minute
)
var (
expiration = 30 //minutes
)
func init() {
// TODO read it from config
expi := os.Getenv("TOKEN_EXPIRATION")
if len(expi) != 0 {
i, err := strconv.Atoi(expi)
if err != nil {
log.Errorf("failed to parse token expiration: %v, using default value: %d minutes", err, expiration)
return
}
if i <= 0 {
log.Warningf("invalid token expiration, using default value: %d minutes", expiration)
return
}
expiration = i
}
log.Infof("token expiration: %d minutes", expiration)
}
// GetResourceActions ...
func GetResourceActions(scopes []string) []*token.ResourceActions {
log.Debugf("scopes: %+v", scopes)

View File

@ -73,13 +73,11 @@
}
.sub-pane {
margin: 15px;
min-height: 380px;
overflow-y: auto;
}
.well-custom {
width: 100%;
background-color: #f5f5f5;
background-image: none;

View File

@ -48,7 +48,8 @@
</tbody>
</table>
</div>
</div>
</div>
<paginator ng-if="vm.totalCount > 0" total-count="//vm.totalCount//" page-size="//vm.pageSize//" page="vm.page" display-count="5"></paginator>
</div>
</div>
</div>

View File

@ -51,8 +51,18 @@
'projectId': vm.projectId,
'username' : vm.username
};
retrieve(vm.queryParams);
vm.page = 1;
vm.pageSize = 20;
$scope.$watch('vm.page', function(current, origin) {
if(current !== 1) {
vm.page = current;
retrieve(vm.queryParams, vm.page, vm.pageSize);
}
});
retrieve(vm.queryParams, vm.page, vm.pageSize);
$scope.$on('$locationChangeSuccess', function() {
@ -69,11 +79,13 @@
'username' : vm.username
};
vm.username = '';
retrieve(vm.queryParams);
retrieve(vm.queryParams, vm.page, vm.pageSize);
});
function search(e) {
vm.page = 1;
if(e.op[0] === 'all') {
e.op = ['create', 'pull', 'push', 'delete'];
}
@ -83,11 +95,12 @@
vm.queryParams.keywords = e.op.join('/');
vm.queryParams.username = e.username;
vm.queryParams.beginTimestamp = toUTCSeconds(vm.fromDate, 0, 0, 0);
vm.queryParams.endTimestamp = toUTCSeconds(vm.toDate, 23, 59, 59);
retrieve(vm.queryParams);
retrieve(vm.queryParams, vm.page, vm.pageSize);
}
function showAdvancedSearch() {
@ -98,27 +111,30 @@
}
}
function retrieve(queryParams) {
ListLogService(queryParams)
function retrieve(queryParams, page, pageSize) {
ListLogService(queryParams, page, pageSize)
.then(listLogComplete)
.catch(listLogFailed);
}
function listLogComplete(response) {
vm.logs = response.data;
vm.totalCount = response.headers('X-Total-Count');
vm.queryParams = {
'beginTimestamp' : 0,
'endTimestamp' : 0,
'keywords' : '',
'projectId': vm.projectId,
'username' : ''
};
vm.op = ['all'];
vm.fromDate = '';
vm.toDate = '';
vm.others = '';
vm.opOthers = true;
console.log('Total Count in logs:' + vm.totalCount + ', page:' + vm.page);
// vm.queryParams = {
// 'beginTimestamp' : 0,
// 'endTimestamp' : 0,
// 'keywords' : '',
// 'projectId': vm.projectId,
// 'username' : ''
// };
// vm.op = ['all'];
// vm.fromDate = '';
// vm.toDate = '';
// vm.others = '';
// vm.opOthers = true;
vm.isOpen = false;
}
function listLogFailed(response){

View File

@ -0,0 +1,14 @@
<nav aria-label="Page navigation" class="pull-left">
<ul class="pagination" style="margin: 0;">
<li>
<a href="javascript:void(0);" ng-click="vm.previous()" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li>
<a href="javascript:void(0);" ng-click="vm.next()" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>

View File

@ -0,0 +1,196 @@
(function() {
'use strict';
angular
.module('harbor.paginator')
.directive('paginator', paginator);
PaginatorController.$inject = [];
function PaginatorController() {
var vm = this;
}
paginator.$inject = [];
function paginator() {
var directive = {
'restrict': 'E',
'templateUrl': '/static/resources/js/components/paginator/paginator.directive.html',
'scope': {
'totalCount': '@',
'pageSize': '@',
'page': '=',
'displayCount': '@'
},
'link': link,
'controller': PaginatorController,
'controllerAs': 'vm',
'bindToController': true
};
return directive;
function link(scope, element, attrs, ctrl) {
scope.$watch('vm.page', function(current) {
if(current) {
ctrl.page = current;
togglePageButton();
}
});
var tc;
scope.$watch('vm.totalCount', function(current) {
if(current) {
var totalCount = current;
element.find('ul li:first a').off('click');
element.find('ul li:last a').off('click');
tc = new TimeCounter();
console.log('Total Count:' + totalCount + ', Page Size:' + ctrl.pageSize + ', Display Count:' + ctrl.displayCount + ', Page:' + ctrl.page);
ctrl.buttonCount = Math.ceil(totalCount / ctrl.pageSize);
if(ctrl.buttonCount <= ctrl.displayCount) {
tc.setMaximum(1);
}else{
tc.setMaximum(Math.ceil(ctrl.buttonCount / ctrl.displayCount));
}
element.find('ul li:first a').on('click', previous);
element.find('ul li:last a').on('click', next);
drawButtons(tc.getTime());
togglePrevious(tc.canDecrement());
toggleNext(tc.canIncrement());
togglePageButton();
}
});
var TimeCounter = function() {
this.time = 0;
this.minimum = 0;
this.maximum = 0;
}
TimeCounter.prototype.setMaximum = function(maximum) {
this.maximum = maximum;
};
TimeCounter.prototype.increment = function() {
if(this.time < this.maximum) {
++this.time;
if((ctrl.page % ctrl.displayCount) != 0) {
ctrl.page = this.time * ctrl.displayCount;
}
++ctrl.page;
}
scope.$apply();
};
TimeCounter.prototype.canIncrement = function() {
if(this.time + 1 < this.maximum) {
return true;
}
return false;
};
TimeCounter.prototype.decrement = function() {
if(this.time > this.minimum) {
if(this.time === 0) {
ctrl.page = ctrl.displayCount;
}else if((ctrl.page % ctrl.displayCount) != 0) {
ctrl.page = this.time * ctrl.displayCount;
}
--this.time;
--ctrl.page;
}
scope.$apply();
};
TimeCounter.prototype.canDecrement = function() {
if(this.time > this.minimum) {
return true;
}
return false;
};
TimeCounter.prototype.getTime = function() {
return this.time;
};
function drawButtons(time) {
element.find('li[tag="pagination-button"]').remove();
var buttons = [];
for(var i = 1; i <= ctrl.displayCount; i++) {
var displayNumber = ctrl.displayCount * time + i;
if(displayNumber <= ctrl.buttonCount) {
buttons.push('<li tag="pagination-button"><a href="javascript:void(0)" page="' + displayNumber + '">' + displayNumber + '<span class="sr-only"></span></a></li>');
}
}
$(buttons.join(''))
.insertAfter(element.find('ul li:eq(0)')).end()
.on('click', buttonClickHandler);
}
function togglePrevious(status) {
if(status){
element.find('ul li:first').removeClass('disabled');
}else{
element.find('ul li:first').addClass('disabled');
}
}
function toggleNext(status) {
if(status) {
element.find('ul li:last').removeClass('disabled');
}else{
element.find('ul li:last').addClass('disabled');
}
}
function buttonClickHandler(e) {
ctrl.page = $(e.target).attr('page');
togglePageButton();
togglePrevious(tc.canDecrement());
toggleNext(tc.canIncrement());
scope.$apply();
}
function togglePageButton() {
element.find('li[tag="pagination-button"]').removeClass('active');
element.find('li[tag="pagination-button"] a[page="' + ctrl.page + '"]').parent().addClass('active');
}
function previous() {
if(tc.canDecrement()) {
tc.decrement();
drawButtons(tc.getTime());
togglePageButton();
togglePrevious(tc.canDecrement());
toggleNext(tc.canIncrement());
}
scope.$apply();
}
function next() {
if(tc.canIncrement()) {
tc.increment();
drawButtons(tc.getTime());
togglePageButton();
togglePrevious(tc.canDecrement());
toggleNext(tc.canIncrement());
}
scope.$apply();
}
}
}
})();

View File

@ -0,0 +1,8 @@
(function() {
'use strict';
angular
.module('harbor.paginator', []);
})();

View File

@ -12,5 +12,5 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<button ng-if="vm.isPublic" ng-disabled="!vm.owned" class="btn btn-success" ng-click="vm.toggle()">// 'button_on' | tr //</button>
<button ng-if="!vm.isPublic" ng-disabled="!vm.owned" class="btn btn-danger" ng-click="vm.toggle()">// 'button_off' | tr //</button>
<button ng-if="vm.isPublic" class="btn btn-success" ng-click="vm.toggle()">// 'button_on' | tr //</button>
<button ng-if="!vm.isPublic" class="btn btn-danger" ng-click="vm.toggle()">// 'button_off' | tr //</button>

View File

@ -73,7 +73,9 @@
</div>
</div>
</div>
<div class="col-xs-4 col-md-12 well well-sm well-custom well-split"><div class="col-md-offset-10">//vm.replicationPolicies ? vm.replicationPolicies.length : 0// // 'items' | tr //</div></div>
<div class="col-xs-4 col-md-12 well well-sm well-custom well-split">
<div class="col-md-offset-10">//vm.replicationPolicies ? vm.replicationPolicies.length : 0// // 'items' | tr //</div>
</div>
<p class="split-handle"><span class="glyphicon glyphicon-align-justify"></span></p>
<h4 class="h4-custom-down">// 'replication_jobs' | tr //</h4>
<hr class="hr-line"/>
@ -147,7 +149,7 @@
</div>
</div>
</div>
<div class="col-xs-4 col-md-12 well well-sm well-custom well-split"><div class="col-md-offset-10">//vm.replicationJobs ? vm.replicationJobs.length : 0// // 'items' | tr //</div></div>
<paginator ng-if="vm.totalCount > 0" total-count="//vm.totalCount//" page-size="//vm.pageSize//" display-count="5" page="vm.page"></paginator>
</div>
</div>
</div>

View File

@ -59,6 +59,17 @@
vm.retrievePolicy = retrievePolicy;
vm.retrieveJob = retrieveJob;
vm.pageSize = 20;
vm.page = 1;
$scope.$watch('vm.page', function(current) {
if(current !== 1) {
vm.page = current;
console.log('replication job: vm.page:' + current);
vm.retrieveJob(vm.lastPolicyId, vm.page, vm.pageSize);
}
});
vm.confirmToTogglePolicy = confirmToTogglePolicy;
vm.togglePolicy = togglePolicy;
@ -84,14 +95,14 @@
function searchReplicationJob() {
if(vm.lastPolicyId !== -1) {
vm.searchJobTIP = true;
vm.retrieveJob(vm.lastPolicyId);
vm.retrieveJob(vm.lastPolicyId, vm.page, vm.pageSize);
}
}
function refreshReplicationJob() {
if(vm.lastPolicyId !== -1) {
vm.refreshJobTIP = true;
vm.retrieveJob(vm.lastPolicyId);
vm.retrieveJob(vm.lastPolicyId, vm.page, vm.pageSize);
}
}
@ -101,11 +112,10 @@
.error(listReplicationPolicyFailed);
}
function retrieveJob(policyId) {
function retrieveJob(policyId, page, pageSize) {
var status = (vm.currentStatus.key === 'all' ? '' : vm.currentStatus.key);
ListReplicationJobService(policyId, vm.replicationJobName, status, toUTCSeconds(vm.fromDate, 0, 0, 0), toUTCSeconds(vm.toDate, 23, 59, 59))
.success(listReplicationJobSuccess)
.error(listReplicationJobFailed);
ListReplicationJobService(policyId, vm.replicationJobName, status, toUTCSeconds(vm.fromDate, 0, 0, 0), toUTCSeconds(vm.toDate, 23, 59, 59), page, pageSize)
.then(listReplicationJobSuccess, listReplicationJobFailed);
}
function listReplicationPolicySuccess(data, status) {
@ -117,8 +127,9 @@
console.log('Failed to list replication policy:' + data);
}
function listReplicationJobSuccess(data, status) {
vm.replicationJobs = data || [];
function listReplicationJobSuccess(response) {
vm.replicationJobs = response.data || [];
vm.totalCount = response.headers('X-Total-Count');
var alertInfo = {
'show': false,
'message': ''
@ -146,8 +157,8 @@
vm.refreshJobTIP = false;
}
function listReplicationJobFailed(data, status) {
console.log('Failed to list replication job:' + data);
function listReplicationJobFailed(response) {
console.log('Failed to list replication job:' + response);
vm.searchJobTIP = false;
vm.refreshJobTIP = false;
}
@ -259,8 +270,8 @@
var uponTableHeight = element.find('#upon-pane .table-body-container').height();
var downTableHeight = element.find('#down-pane .table-body-container').height();
var handleHeight = element.find('.split-handle').height() + element.find('.split-handle').offset().top + element.find('.well').height() - 24;
var handleHeight = element.find('.split-handle').height() + element.find('.split-handle').offset().top + element.find('.well').height() - 32;
console.log('handleHeight:' + handleHeight);
var maxDownPaneHeight = 760;
element.find('.split-handle').on('mousedown', mousedownHandler);
@ -328,7 +339,7 @@
.css({'color': '#fff'});
$('a', this)
.css({'color': '#fff'});
ctrl.retrieveJob($(this).attr('policy_id'));
ctrl.retrieveJob($(this).attr('policy_id'), ctrl.page, ctrl.pageSize);
ctrl.lastPolicyId = $(this).attr('policy_id');
}

View File

@ -30,6 +30,7 @@
</div>
</div>
</div>
<paginator ng-if="vm.totalCount > 0" total-count="//vm.totalCount//" page-size="//vm.pageSize//" page="vm.page" display-count="5"></paginator>
</div>
</div>
</div>

View File

@ -41,7 +41,9 @@
vm.filterInput = hashValue;
}
}
vm.page = 1;
vm.pageSize = 8;
vm.retrieve = retrieve;
vm.tagCount = {};
@ -61,6 +63,15 @@
}
});
$scope.$watch('vm.page', function(current) {
if(current !== 1) {
vm.page = current;
vm.retrieve();
}
});
$scope.$on('repoName', function(e, val) {
vm.repoName = val;
});
@ -76,19 +87,19 @@
$scope.$on('tags', function(e, val) {
vm.tags = val;
});
vm.deleteByRepo = deleteByRepo;
vm.deleteByTag = deleteByTag;
vm.deleteImage = deleteImage;
function retrieve(){
ListRepositoryService(vm.projectId, vm.filterInput)
.success(getRepositoryComplete)
.error(getRepositoryFailed);
ListRepositoryService(vm.projectId, vm.filterInput, vm.page, vm.pageSize)
.then(getRepositoryComplete, getRepositoryFailed);
}
function getRepositoryComplete(data, status) {
vm.repositories = data || [];
function getRepositoryComplete(response) {
vm.repositories = response.data || [];
vm.totalCount = response.headers('X-Total-Count');
$scope.$broadcast('refreshTags', true);
}

View File

@ -44,10 +44,10 @@
<td width="20%">//u.email//</td>
<td width="35%">//u.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="15%">
<toggle-admin has-admin-role="u.has_admin_role" user-id="//u.user_id//"></toggle-admin>
<toggle-admin current-user="vm.currentUser" has-admin-role="u.has_admin_role" user-id="//u.user_id//"></toggle-admin>
</td>
<td width="20%">
&nbsp;&nbsp;<a href="javascript:void(0)" ng-click="vm.confirmToDelete(u.user_id, u.username)"><span class="glyphicon glyphicon-trash"></span></a>
&nbsp;&nbsp;<a ng-if="vm.currentUser.user_id != u.user_id" href="javascript:void(0)" ng-click="vm.confirmToDelete(u.user_id, u.username)"><span class="glyphicon glyphicon-trash"></span></a>
</td>
</tr>
</tbody>

View File

@ -20,9 +20,9 @@
.module('harbor.user')
.directive('listUser', listUser);
ListUserController.$inject = ['$scope', 'ListUserService', 'DeleteUserService', '$filter', 'trFilter'];
ListUserController.$inject = ['$scope', 'ListUserService', 'DeleteUserService', 'currentUser', '$filter', 'trFilter'];
function ListUserController($scope, ListUserService, DeleteUserService, $filter, $trFilter) {
function ListUserController($scope, ListUserService, DeleteUserService, currentUser, $filter, $trFilter) {
$scope.subsSubPane = 226;
@ -33,6 +33,8 @@
vm.deleteUser = deleteUser;
vm.confirmToDelete = confirmToDelete;
vm.retrieve = retrieve;
vm.currentUser = currentUser.get();
vm.retrieve();

View File

@ -12,5 +12,5 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<button ng-show="vm.isAdmin" class="btn btn-success" ng-click="vm.toggle()">// 'button_on' | tr //</button>
<button ng-show="vm.isAdmin" ng-disabled="!vm.editable" class="btn btn-success" ng-click="vm.toggle()">// 'button_on' | tr //</button>
<button ng-show="!vm.isAdmin" class="btn btn-danger" ng-click="vm.toggle()">// 'button_off' | tr //</button>

View File

@ -28,6 +28,7 @@
vm.isAdmin = (vm.hasAdminRole === 1);
vm.enabled = vm.isAdmin ? 0 : 1;
vm.toggle = toggle;
vm.editable = (vm.currentUser.user_id !== Number(vm.userId));
function toggle() {
ToggleAdminService(vm.userId, vm.enabled)
@ -63,7 +64,8 @@
'templateUrl': '/static/resources/js/components/user/toggle-admin.directive.html',
'scope': {
'hasAdminRole': '=',
'userId': '@'
'userId': '@',
'currentUser': '='
},
'link': link,
'controller': ToggleAdminController,

View File

@ -60,6 +60,7 @@
'harbor.system.management',
'harbor.loading.progress',
'harbor.inline.help',
'harbor.dismissable.alerts'
'harbor.dismissable.alerts',
'harbor.paginator'
]);
})();

View File

@ -26,7 +26,7 @@
return LogResult;
function LogResult(queryParams) {
function LogResult(queryParams, page, pageSize) {
var projectId = queryParams.projectId;
var username = queryParams.username;
var beginTimestamp = queryParams.beginTimestamp;
@ -34,7 +34,7 @@
var keywords = queryParams.keywords;
return $http
.post('/api/projects/' + projectId + '/logs/filter', {
.post('/api/projects/' + projectId + '/logs/filter?page=' + page + '&page_size=' + pageSize, {
'begin_timestamp' : beginTimestamp,
'end_timestamp' : endTimestamp,
'keywords' : keywords,

View File

@ -26,9 +26,9 @@
return listReplicationJob;
function listReplicationJob(policyId, repository, status, startTime, endTime) {
function listReplicationJob(policyId, repository, status, startTime, endTime, page, pageSize) {
return $http
.get('/api/jobs/replication/', {
.get('/api/jobs/replication/?page=' + page + '&page_size=' + pageSize, {
'params': {
'policy_id': policyId,
'repository': repository,

View File

@ -25,11 +25,11 @@
return ListRepository;
function ListRepository(projectId, q) {
function ListRepository(projectId, q, page, pageSize) {
$log.info('list repositories:' + projectId + ', q:' + q);
return $http
.get('/api/repositories', {
.get('/api/repositories?page=' + page + '&page_size=' + pageSize, {
'params':{
'project_id': projectId,
'q': q

View File

@ -0,0 +1,41 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type AccessLog struct {
// The ID of the log entry.
LogId int32 `json:"log_id,omitempty"`
// Name of the repository in this log entry.
RepoName string `json:"repo_name,omitempty"`
// Tag of the repository in this log entry.
RepoTag string `json:"repo_tag,omitempty"`
// The operation against the repository in this log entry.
Operation string `json:"operation,omitempty"`
// The time when this operation is triggered.
OpTime string `json:"op_time,omitempty"`
}

View File

@ -0,0 +1,38 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type AccessLogFilter struct {
// Relevant user's name that accessed this project.
Username string `json:"username,omitempty"`
// Operation name specified when project created.
Keywords string `json:"keywords,omitempty"`
// Begin timestamp for querying access logs.
BeginTimestamp int64 `json:"begin_timestamp,omitempty"`
// End timestamp for querying accessl logs.
EndTimestamp int64 `json:"end_timestamp,omitempty"`
}

View File

@ -1,23 +0,0 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package HarborAPI
type AccessLog struct {
Username string `json:"username,omitempty"`
Keywords string `json:"keywords,omitempty"`
BeginTimestamp int32 `json:"beginTimestamp,omitempty"`
EndTimestamp int32 `json:"endTimestamp,omitempty"`
}

View File

@ -1,6 +1,6 @@
//Package HarborAPI
//Package apilib
//These APIs provide services for manipulating Harbor project.
package HarborAPI
package apilib
import (
"encoding/json"

View File

@ -0,0 +1,15 @@
// HarborLogout.go
package HarborAPI
import (
"net/http"
)
func (a HarborAPI) HarborLogout() (int, error) {
response, err := http.Get(a.basePath + "/logout")
defer response.Body.Close()
return response.StatusCode, err
}

View File

@ -1,5 +1,5 @@
// HarborLogout.go
package HarborAPI
package apilib
import (
"net/http"

View File

@ -0,0 +1,28 @@
// HarborLogon.go
package HarborAPI
import (
"io/ioutil"
"net/http"
"net/url"
"strings"
)
func (a HarborAPI) HarborLogin(user UsrInfo) (int, error) {
v := url.Values{}
v.Set("principal", user.Name)
v.Set("password", user.Passwd)
body := ioutil.NopCloser(strings.NewReader(v.Encode())) //endode v:[body struce]
client := &http.Client{}
reqest, err := http.NewRequest("POST", a.basePath+"/login", body)
reqest.Header.Set("Content-Type", "application/x-www-form-urlencoded;param=value") //setting post head
resp, err := client.Do(reqest)
defer resp.Body.Close() //close resp.Body
return resp.StatusCode, err
}

View File

@ -1,5 +1,5 @@
// HarborLogon.go
package HarborAPI
package apilib
import (
"io/ioutil"

View File

@ -0,0 +1,50 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type JobStatus struct {
// The job ID.
Id int64 `json:"id,omitempty"`
// The status of the job.
Status string `json:"status,omitempty"`
// The repository handled by the job.
Repository string `json:"repository,omitempty"`
// The ID of the policy that triggered this job.
PolicyId int64 `json:"policy_id,omitempty"`
// The operation of the job.
Operation string `json:"operation,omitempty"`
// The repository's used tag list.
Tags []Tags `json:"tags,omitempty"`
// The creation time of the job.
CreationTime string `json:"creation_time,omitempty"`
// The update time of the job.
UpdateTime string `json:"update_time,omitempty"`
}

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 Password struct {
// The user's existing password.
OldPassword string `json:"old_password,omitempty"`
// New password for marking as to be updated.
NewPassword string `json:"new_password,omitempty"`
}

View File

@ -1,15 +1,62 @@
package HarborAPI
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ()
package apilib
type Project struct {
ProjectId int32 `json:"id,omitempty"`
OwnerId int32 `json:"owner_id,omitempty"`
ProjectName string `json:"project_name,omitempty"`
// Project ID
ProjectId int32 `json:"project_id,omitempty"`
// The owner ID of the project always means the creator of the project.
OwnerId int32 `json:"owner_id,omitempty"`
// The name of the project.
ProjectName string `json:"project_name,omitempty"`
// The creation time of the project.
CreationTime string `json:"creation_time,omitempty"`
Deleted int32 `json:"deleted,omitempty"`
UserId int32 `json:"user_id,omitempty"`
OwnerName string `json:"owner_name,omitempty"`
Public bool `json:"public,omitempty"`
Togglable bool `json:"togglable,omitempty"`
// The update time of the project.
UpdateTime string `json:"update_time,omitempty"`
// A deletion mark of the project (1 means it's deleted, 0 is not)
Deleted int32 `json:"deleted,omitempty"`
// A relation field to the user table.
UserId int32 `json:"user_id,omitempty"`
// The owner name of the project.
OwnerName string `json:"owner_name,omitempty"`
// The public status of the project.
Public bool `json:"public,omitempty"`
// Correspond to the UI about whether the project's publicity is updatable (for UI)
Togglable bool `json:"togglable,omitempty"`
// The role ID of the current user who triggered the API (for UI)
CurrentUserRoleId int32 `json:"current_user_role_id,omitempty"`
// The number of the repositories under this project.
RepoCount int32 `json:"repo_count,omitempty"`
}

View File

@ -1,9 +0,0 @@
package HarborAPI
import ()
type Project4Search struct {
ProjectId int32 `json:"id,omitempty"`
ProjectName string `json:"name,omitempty"`
Public int32 `json:"public,omitempty"`
}

View File

@ -0,0 +1,62 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type RepPolicy struct {
// The policy ID.
Id int64 `json:"id,omitempty"`
// The project ID.
ProjectId int64 `json:"project_id,omitempty"`
// The project name.
ProjectName string `json:"project_name,omitempty"`
// The target ID.
TargetId int64 `json:"target_id,omitempty"`
// The target name.
TargetName string `json:"target_name,omitempty"`
// The policy name.
Name string `json:"name,omitempty"`
// The policy's enabled status.
Enabled int32 `json:"enabled,omitempty"`
// The description of the policy.
Description string `json:"description,omitempty"`
// The cron string for schedule job.
CronStr string `json:"cron_str,omitempty"`
// The start time of the policy.
StartTime string `json:"start_time,omitempty"`
// The create time of the policy.
CreationTime string `json:"creation_time,omitempty"`
// The update time of the policy.
UpdateTime string `json:"update_time,omitempty"`
}

View File

@ -0,0 +1,29 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type RepPolicyEnablementReq struct {
// The policy enablement flag.
Enabled int32 `json:"enabled,omitempty"`
}

View File

@ -0,0 +1,35 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type RepPolicyPost struct {
// The project ID.
ProjectId int64 `json:"project_id,omitempty"`
// The target ID.
TargetId int64 `json:"target_id,omitempty"`
// The policy name.
Name string `json:"name,omitempty"`
}

View File

@ -0,0 +1,41 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type RepPolicyUpdate struct {
// The target ID.
TargetId int64 `json:"target_id,omitempty"`
// The policy name.
Name string `json:"name,omitempty"`
// The policy's enabled status.
Enabled int32 `json:"enabled,omitempty"`
// The description of the policy.
Description string `json:"description,omitempty"`
// The cron string for schedule job.
CronStr string `json:"cron_str,omitempty"`
}

View File

@ -0,0 +1,50 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type RepTarget struct {
// The target ID.
Id int64 `json:"id,omitempty"`
// The target address URL string.
Endpoint string `json:"endpoint,omitempty"`
// The target name.
Name string `json:"name,omitempty"`
// The target server username.
Username string `json:"username,omitempty"`
// The target server password.
Password string `json:"password,omitempty"`
// Reserved field.
Type_ int32 `json:"type,omitempty"`
// The create time of the policy.
CreationTime string `json:"creation_time,omitempty"`
// The update time of the policy.
UpdateTime string `json:"update_time,omitempty"`
}

View File

@ -0,0 +1,38 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type RepTargetPost struct {
// The target address URL string.
Endpoint string `json:"endpoint,omitempty"`
// The target name.
Name string `json:"name,omitempty"`
// The target server username.
Username string `json:"username,omitempty"`
// The target server password.
Password string `json:"password,omitempty"`
}

View File

@ -1,16 +1,50 @@
package HarborAPI
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import (
"time"
)
package apilib
type Repository struct {
Id string `json:"id,omitempty"`
Parent string `json:"parent,omitempty"`
Created time.Time `json:"created,omitempty"`
DurationDays string `json:"duration_days,omitempty"`
Author string `json:"author,omitempty"`
Architecture string `json:"architecture,omitempty"`
DockerVersion string `json:"docker_version,omitempty"`
Os string `json:"os,omitempty"`
// Repository ID
Id string `json:"id,omitempty"`
// Parent of the image.
Parent string `json:"parent,omitempty"`
// Repository create time.
Created string `json:"created,omitempty"`
// Duration days of the image.
DurationDays string `json:"duration_days,omitempty"`
// Author of the image.
Author string `json:"author,omitempty"`
// Architecture of the image.
Architecture string `json:"architecture,omitempty"`
// Docker version of the image.
DockerVersion string `json:"docker_version,omitempty"`
// OS of the image.
Os string `json:"os,omitempty"`
}

View File

@ -1,9 +0,0 @@
package HarborAPI
type Repository4Search struct {
ProjectId int32 `json:"project_id,omitempty"`
ProjectName string `json:"project_name,omitempty"`
ProjectPublic int32 `json:"project_public,omitempty"`
RepoName string `json:"repository_name,omitempty"`
}

View File

@ -1,7 +1,35 @@
package HarborAPI
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type Role struct {
RoleId int32 `json:"role_id,omitempty"`
// ID in table.
RoleId int32 `json:"role_id,omitempty"`
// Description of permissions for the role.
RoleCode string `json:"role_code,omitempty"`
// Name the the role.
RoleName string `json:"role_name,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 RoleParam struct {
// Role ID for updating project role member.
Roles []int32 `json:"roles,omitempty"`
// Username relevant to a project role member.
Username string `json:"username,omitempty"`
}

View File

@ -1,6 +0,0 @@
package HarborAPI
type RoleParam struct {
Roles []int32 `json:"roles,omitempty"`
UserName string `json:"user_name,omitempty"`
}

View File

@ -1,8 +1,34 @@
package HarborAPI
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ()
package apilib
import()
type Search struct {
Projects []Project4Search `json:"project,omitempty"`
Repositories []Repository4Search `json:"repository,omitempty"`
// Search results of the projects that matched the filter keywords.
Projects []SearchProject `json:"project,omitempty"`
// Search results of the repositories that matched the filter keywords.
Repositories []SearchRepository `json:"repository,omitempty"`
}

View File

@ -0,0 +1,35 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type SearchProject struct {
// The ID of project
Id int64 `json:"id,omitempty"`
// The name of the project
Name string `json:"name,omitempty"`
// The flag to indicate the publicity of the project (1 is public, 0 is non-public)
Public int32 `json:"public,omitempty"`
}

View File

@ -0,0 +1,38 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type SearchRepository struct {
// The name of the repository
RepositoryName string `json:"repository_name,omitempty"`
// The name of the project that the repository belongs to
ProjectName string `json:"project_name,omitempty"`
// The ID of the project that the repository belongs to
ProjectId int32 `json:"project_id,omitempty"`
// The flag to indicate the publicity of the project that the repository belongs to (1 is public, 0 is not)
ProjectPublic int32 `json:"project_public,omitempty"`
}

View File

@ -0,0 +1,44 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type StatisticMap struct {
// The count of the projects which the user is a member of.
MyProjectCount int32 `json:"my_project_count,omitempty"`
// The count of the repositories belonging to the projects which the user is a member of.
MyRepoCount int32 `json:"my_repo_count,omitempty"`
// The count of the public projects.
PublicProjectCount int32 `json:"public_project_count,omitempty"`
// The count of the public repositories belonging to the public projects which the user is a member of.
PublicRepoCount int32 `json:"public_repo_count,omitempty"`
// The count of the total projects, only be seen when the is admin.
TotalProjectCount int32 `json:"total_project_count,omitempty"`
// The count of the total repositories, only be seen when the user is admin.
TotalRepoCount int32 `json:"total_repo_count,omitempty"`
}

View File

@ -0,0 +1,29 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type Tags struct {
// The repository's used tag.
Tag string `json:"tag,omitempty"`
}

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 TopRepo struct {
// The name of the repo
RepoName string `json:"repo_name,omitempty"`
// The access count of the repo
AccessCount int32 `json:"access_count,omitempty"`
}

View File

@ -1,11 +1,41 @@
package HarborAPI
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apilib
type User struct {
UserId int32 `json:"user_id,omitempty"`
// The ID of the user.
UserId int32 `json:"user_id,omitempty"`
Username string `json:"username,omitempty"`
Email string `json:"email,omitempty"`
Email string `json:"email,omitempty"`
Password string `json:"password,omitempty"`
Realname string `json:"realname,omitempty"`
Comment string `json:"comment,omitempty"`
Deleted int32 `json:"deleted,omitempty"`
Comment string `json:"comment,omitempty"`
Deleted int32 `json:"deleted,omitempty"`
}

View File

@ -1,95 +0,0 @@
package HarborAPItest
import (
"fmt"
"github.com/stretchr/testify/assert"
"testing"
"github.com/vmware/harbor/tests/apitests/apilib"
)
func TestAddProject(t *testing.T) {
fmt.Println("Test for Project Add (ProjectsPost) API")
assert := assert.New(t)
apiTest := HarborAPI.NewHarborAPI()
//prepare for test
adminEr := &HarborAPI.UsrInfo{"admin", "Harbor1234"}
admin := &HarborAPI.UsrInfo{"admin", "Harbor12345"}
prjUsr := &HarborAPI.UsrInfo{"unknown", "unknown"}
var project HarborAPI.Project
project.ProjectName = "testproject"
project.Public = true
//case 1: admin login fail, expect project creation fail.
fmt.Println("case 1: admin login fail, expect project creation fail.")
resault, err := apiTest.HarborLogin(*adminEr)
if err != nil {
t.Error("Error while admin login", err.Error())
t.Log(err)
} else {
assert.Equal(resault, int(401), "Admin login status should be 401")
//t.Log(resault)
}
resault, err = apiTest.ProjectsPost(*prjUsr, project)
if err != nil {
t.Error("Error while creat project", err.Error())
t.Log(err)
} else {
assert.Equal(resault, int(401), "Case 1: Project creation status should be 401")
//t.Log(resault)
}
//case 2: admin successful login, expect project creation success.
fmt.Println("case 2: admin successful login, expect project creation success.")
resault, err = apiTest.HarborLogin(*admin)
if err != nil {
t.Error("Error while admin login", err.Error())
t.Log(err)
} else {
assert.Equal(resault, int(200), "Admin login status should be 200")
//t.Log(resault)
}
if resault != 200 {
t.Log(resault)
} else {
prjUsr = admin
}
resault, err = apiTest.ProjectsPost(*prjUsr, project)
if err != nil {
t.Error("Error while creat project", err.Error())
t.Log(err)
} else {
assert.Equal(resault, int(201), "Case 2: Project creation status should be 201")
//t.Log(resault)
}
//case 3: duplicate project name, create project fail
fmt.Println("case 3: duplicate project name, create project fail")
resault, err = apiTest.ProjectsPost(*prjUsr, project)
if err != nil {
t.Error("Error while creat project", err.Error())
t.Log(err)
} else {
assert.Equal(resault, int(409), "Case 3: Project creation status should be 409")
//t.Log(resault)
}
//resault1, err := apiTest.HarborLogout()
//if err != nil {
// t.Error("Error while admin logout", err.Error())
// t.Log(err)
//} else {
// assert.Equal(resault1, int(200), "Admin logout status")
// //t.Log(resault)
//}
//if resault1 != 200 {
// t.Log(resault)
//}
}

View File

@ -1,130 +0,0 @@
package HarborAPItest
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/tests/apitests/apilib"
)
func TestRepositoryDelete(t *testing.T) {
fmt.Println("Test for Project Delete (ProjectDelete) API")
assert := assert.New(t)
//prepare for test
adminEr := &HarborAPI.UsrInfo{"admin", "Harbor1234"}
admin := &HarborAPI.UsrInfo{"admin", "Harbor12345"}
prjUsr := &HarborAPI.UsrInfo{"unknown", "unknown"}
fmt.Println("Checking repository status...")
apiTest := HarborAPI.NewHarborAPI()
var searchResault HarborAPI.Search
searchResault, err := apiTest.SearchGet("library")
//fmt.Printf("%+v\n", resault)
if err != nil {
t.Error("Error while search project or repository", err.Error())
t.Log(err)
} else {
//assert.Equal(searchResault.Repositories[0].RepoName, "library/docker", "1st repo name should be")
if !assert.Equal(searchResault.Repositories[0].RepoName, "library/docker", "1st repo name should be") {
t.Error("fail to find repo 'library/docker'", err.Error())
t.Log(err)
} else {
fmt.Println("repo 'library/docker' exit")
}
//assert.Equal(searchResault.Repositories[1].RepoName, "library/hello-world", "2nd repo name should be")
if !assert.Equal(searchResault.Repositories[1].RepoName, "library/hello-world", "2nd repo name should be") {
t.Error("fail to find repo 'library/hello-world'", err.Error())
t.Log(err)
} else {
fmt.Println("repo 'library/hello-world' exit")
}
//t.Log(resault)
}
//case 1: admin login fail, expect repo delete fail.
fmt.Println("case 1: admin login fail, expect repo delete fail.")
resault, err := apiTest.HarborLogin(*adminEr)
if err != nil {
t.Error("Error while admin login", err.Error())
t.Log(err)
} else {
assert.Equal(resault, int(401), "Admin login status should be 401")
//t.Log(resault)
}
if resault != 401 {
t.Log(resault)
} else {
prjUsr = adminEr
}
resault, err = apiTest.RepositoriesDelete(*prjUsr, "library/docker", "")
if err != nil {
t.Error("Error while delete repository", err.Error())
t.Log(err)
} else {
assert.Equal(resault, int(401), "Case 1: Repository delete status should be 401")
//t.Log(resault)
}
//case 2: admin successful login, expect repository delete success.
fmt.Println("case 2: admin successful login, expect repository delete success.")
resault, err = apiTest.HarborLogin(*admin)
if err != nil {
t.Error("Error while admin login", err.Error())
t.Log(err)
} else {
assert.Equal(resault, int(200), "Admin login status should be 200")
//t.Log(resault)
}
if resault != 200 {
t.Log(resault)
} else {
prjUsr = admin
}
resault, err = apiTest.RepositoriesDelete(*prjUsr, "library/docker", "")
if err != nil {
t.Error("Error while delete repository", err.Error())
t.Log(err)
} else {
if assert.Equal(resault, int(200), "Case 2: Repository delete status should be 200") {
fmt.Println("Repository 'library/docker' delete success.")
}
//t.Log(resault)
}
resault, err = apiTest.RepositoriesDelete(*prjUsr, "library/hello-world", "")
if err != nil {
t.Error("Error while delete repository", err.Error())
t.Log(err)
} else {
if assert.Equal(resault, int(200), "Case 2: Repository delete status should be 200") {
fmt.Println("Repository 'hello-world' delete success.")
}
//t.Log(resault)
}
//case 3: delete one repo not exit, expect repo delete fail.
fmt.Println("case 3: delete one repo not exit, expect repo delete fail.")
resault, err = apiTest.RepositoriesDelete(*prjUsr, "library/hello-world", "")
if err != nil {
t.Error("Error while delete repository", err.Error())
t.Log(err)
} else {
if assert.Equal(resault, int(404), "Case 3: Repository delete status should be 404") {
fmt.Println("Repository 'hello-world' not exit.")
}
//t.Log(resault)
}
//if resault.Response.StatusCode != 200 {
// t.Log(resault.Response)
//}
}

View File

@ -1,31 +0,0 @@
package HarborAPItest
import (
"fmt"
"github.com/stretchr/testify/assert"
"testing"
"github.com/vmware/harbor/tests/apitests/apilib"
)
func TestSearch(t *testing.T) {
fmt.Println("Test for Search (SearchGet) API")
assert := assert.New(t)
apiTest := HarborAPI.NewHarborAPI()
var resault HarborAPI.Search
resault, err := apiTest.SearchGet("library")
//fmt.Printf("%+v\n", resault)
if err != nil {
t.Error("Error while search project or repository", err.Error())
t.Log(err)
} else {
assert.Equal(resault.Projects[0].ProjectId, int32(1), "Project id should be equal")
assert.Equal(resault.Projects[0].ProjectName, "library", "Project name should be library")
assert.Equal(resault.Projects[0].Public, int32(1), "Project public status should be 1 (true)")
//t.Log(resault)
}
//if resault.Response.StatusCode != 200 {
// t.Log(resault.Response)
//}
}

View File

@ -0,0 +1,23 @@
version: '2'
services:
registry:
image: library/registry:2.4.0
restart: always
volumes:
- /data/registry:/storage
- ./config/registry/:/etc/registry/
environment:
- GODEBUG=netdns=cgo
ports:
- 5000:5000
command:
["serve", "/etc/registry/config.yml"]
mysql:
build: ./db/
restart: always
volumes:
- /data/database:/var/lib/mysql
env_file:
- ./config/db/env
ports:
- 3306:3306

View File

@ -1,10 +1,9 @@
docker pull hello-world
docker pull docker
docker login -u admin -p Harbor12345 127.0.0.1
#!/bin/bash
docker tag hello-world 127.0.0.1/library/hello-world
docker push 127.0.0.1/library/hello-world
cp tests/docker-compose.test.yml Deploy/.
docker tag docker 127.0.0.1/library/docker
docker push 127.0.0.1/library/docker
mkdir /etc/ui
cp Deploy/config/ui/private_key.pem /etc/ui/.
mkdir conf
cp Deploy/config/ui/app.conf conf/.

View File

@ -1,9 +0,0 @@
package auth
import (
"testing"
)
func TestMain(t *testing.T) {
}

View File

@ -0,0 +1,91 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/docker/distribution/registry/client/auth"
)
func TestNewAuthorizerStore(t *testing.T) {
server := newRegistryServer()
defer server.Close()
_, err := NewAuthorizerStore(server.URL, false, nil)
if err != nil {
t.Fatalf("failed to create authorizer store: %v", err)
}
}
type simpleAuthorizer struct {
}
func (s *simpleAuthorizer) Scheme() string {
return "bearer"
}
func (s *simpleAuthorizer) Authorize(req *http.Request,
params map[string]string) error {
req.Header.Set("Authorization", "Bearer token")
return nil
}
func TestModify(t *testing.T) {
authorizer := &simpleAuthorizer{}
challenge := auth.Challenge{
Scheme: "bearer",
}
as := &AuthorizerStore{
authorizers: []Authorizer{authorizer},
challenges: []auth.Challenge{challenge},
}
req, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
if err = as.Modify(req); err != nil {
t.Fatalf("failed to modify request: %v", err)
}
header := req.Header.Get("Authorization")
if len(header) == 0 {
t.Fatal("\"Authorization\" header not found")
}
if !strings.HasPrefix(header, "Bearer") {
t.Fatal("\"Authorization\" header does not start with \"Bearer\"")
}
}
func newRegistryServer() *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/v2/", handlePing)
return httptest.NewServer(mux)
}
func handlePing(w http.ResponseWriter, r *http.Request) {
challenge := "Bearer realm=\"https://auth.docker.io/token\",service=\"registry.docker.io\""
w.Header().Set("Www-Authenticate", challenge)
w.WriteHeader(http.StatusUnauthorized)
}

View File

@ -0,0 +1,67 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"net/http"
"testing"
)
func TestAddAuthorizationOfBasicAuthCredential(t *testing.T) {
cred := NewBasicAuthCredential("usr", "pwd")
req, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
cred.AddAuthorization(req)
usr, pwd, ok := req.BasicAuth()
if !ok {
t.Fatal("basic auth not found")
}
if usr != "usr" {
t.Errorf("unexpected username: %s != usr", usr)
}
if pwd != "pwd" {
t.Errorf("unexpected password: %s != pwd", pwd)
}
}
func TestAddAuthorizationOfCookieCredential(t *testing.T) {
cookie := &http.Cookie{
Name: "name",
Value: "value",
}
cred := NewCookieCredential(cookie)
req, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
cred.AddAuthorization(req)
ck, err := req.Cookie("name")
if err != nil {
t.Fatalf("failed to get cookie: %v", err)
}
if ck.Value != "value" {
t.Errorf("unexpected value: %s != value", ck.Value)
}
}

View File

@ -180,7 +180,9 @@ func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []
return
}
s.credential.AddAuthorization(r)
if s.credential != nil {
s.credential.AddAuthorization(r)
}
resp, err := s.client.Do(r)
if err != nil {

View File

@ -0,0 +1,81 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
)
var (
token = "token"
)
func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) {
tokenServer := newTokenServer()
defer tokenServer.Close()
authorizer := NewStandardTokenAuthorizer(nil, false, "repository", "library/ubuntu", "pull")
req, err := http.NewRequest("GET", "http://registry", nil)
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
params := map[string]string{
"realm": tokenServer.URL + "/token",
}
if err := authorizer.Authorize(req, params); err != nil {
t.Fatalf("failed to authorize request: %v", err)
}
tk := req.Header.Get("Authorization")
if tk != "Bearer "+token {
t.Errorf("unexpected token: %s != %s", tk, "Bearer "+token)
}
}
func TestSchemeOfStandardTokenAuthorizer(t *testing.T) {
authorizer := &standardTokenAuthorizer{}
if authorizer.Scheme() != "bearer" {
t.Errorf("unexpected scheme: %s != %s", authorizer.Scheme(), "bearer")
}
}
func newTokenServer() *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/token", handleToken)
return httptest.NewServer(mux)
}
func handleToken(w http.ResponseWriter, r *http.Request) {
result := map[string]interface{}{}
result["token"] = token
result["expires_in"] = 300
result["issued_at"] = time.Now().Format(time.RFC3339)
encoder := json.NewEncoder(w)
if err := encoder.Encode(result); err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
}

View File

@ -4,6 +4,14 @@ import (
"testing"
)
func TestMain(t *testing.T) {
}
func TestError(t *testing.T) {
err := &Error{
StatusCode: 404,
Detail: "not found",
}
if err.Error() != "404 not found" {
t.Fatalf("unexpected content: %s != %s",
err.Error(), "404 not found")
}
}

View File

@ -20,17 +20,6 @@ import (
"strings"
)
// Repository holds information about repository
type Repository struct {
Name string
}
// GetProject parses the repository and return the name of project.
func (r *Repository) GetProject() string {
project, _ := ParseRepository(r.Name)
return project
}
// FormatEndpoint formats endpoint
func FormatEndpoint(endpoint string) string {
endpoint = strings.TrimSpace(endpoint)

View File

@ -21,7 +21,36 @@ import (
"testing"
)
func TestMain(t *testing.T) {
func TestParseEndpoint(t *testing.T) {
endpoint := "example.com"
u, err := ParseEndpoint(endpoint)
if err != nil {
t.Fatalf("failed to parse endpoint %s: %v", endpoint, err)
}
if u.String() != "http://example.com" {
t.Errorf("unexpected endpoint: %s != %s", endpoint, "http://example.com")
}
endpoint = "https://example.com"
u, err = ParseEndpoint(endpoint)
if err != nil {
t.Fatalf("failed to parse endpoint %s: %v", endpoint, err)
}
if u.String() != "https://example.com" {
t.Errorf("unexpected endpoint: %s != %s", endpoint, "https://example.com")
}
endpoint = " example.com/ "
u, err = ParseEndpoint(endpoint)
if err != nil {
t.Fatalf("failed to parse endpoint %s: %v", endpoint, err)
}
if u.String() != "http://example.com" {
t.Errorf("unexpected endpoint: %s != %s", endpoint, "http://example.com")
}
}
func TestParseRepository(t *testing.T) {
@ -64,6 +93,16 @@ func TestParseRepository(t *testing.T) {
}
}
func TestEncrypt(t *testing.T) {
content := "content"
salt := "salt"
result := Encrypt(content, salt)
if result != "dc79e76c88415c97eb089d9cc80b4ab0" {
t.Errorf("unexpected result: %s != %s", result, "dc79e76c88415c97eb089d9cc80b4ab0")
}
}
func TestReversibleEncrypt(t *testing.T) {
password := "password"
key := "1234567890123456"

View File

@ -1 +0,0 @@
logrus

View File

@ -1,7 +0,0 @@
language: go
go:
- 1.3
- 1.4
- tip
install:
- go get -t ./...

View File

@ -1,9 +1,21 @@
# 0.9.0 (Unreleased)
# 0.10.0
* feature: Add a test hook (#180)
* feature: `ParseLevel` is now case-insensitive (#326)
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
* performance: avoid re-allocations on `WithFields` (#335)
# 0.9.0
* logrus/text_formatter: don't emit empty msg
* logrus/hooks/airbrake: move out of main repository
* logrus/hooks/sentry: move out of main repository
* logrus/hooks/papertrail: move out of main repository
* logrus/hooks/bugsnag: move out of main repository
* logrus/core: run tests with `-race`
* logrus/core: detect TTY based on `stderr`
* logrus/core: support `WithError` on logger
* logrus/core: Solaris support
# 0.8.7

View File

@ -1,4 +1,4 @@
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus)&nbsp;[![godoc reference](https://godoc.org/github.com/Sirupsen/logrus?status.png)][godoc]
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus)&nbsp;[![GoDoc](https://godoc.org/github.com/Sirupsen/logrus?status.svg)](https://godoc.org/github.com/Sirupsen/logrus)
Logrus is a structured logger for Go (golang), completely API compatible with
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
@ -12,7 +12,7 @@ plain text):
![Colored](http://i.imgur.com/PY7qMwd.png)
With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
or Splunk:
```json
@ -32,7 +32,7 @@ ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
```
With the default `log.Formatter = new(&log.TextFormatter{})` when a TTY is not
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
attached, the output is compatible with the
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
@ -204,7 +204,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
@ -218,7 +218,17 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
| [Influxus] (http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB] (http://influxdata.com/) |
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) |
| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
| [KafkaLogrus](https://github.com/goibibo/KafkaLogrus) | Hook for logging to kafka |
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
#### Level logging
@ -296,15 +306,12 @@ The built-in logging formatters are:
field to `true`. To force no colored output even if there is a TTY set the
`DisableColors` field to `true`
* `logrus.JSONFormatter`. Logs fields as JSON.
* `logrus/formatters/logstash.LogstashFormatter`. Logs fields as [Logstash](http://logstash.net) Events.
```go
logrus.SetFormatter(&logstash.LogstashFormatter{Type: “application_name"})
```
Third party logging formatters:
* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
You can define your formatter by implementing the `Formatter` interface,
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
@ -353,5 +360,43 @@ Log rotation is not provided with Logrus. Log rotation should be done by an
external program (like `logrotate(8)`) that can compress and delete old log
entries. It should not be a feature of the application-level logger.
#### Tools
[godoc]: https://godoc.org/github.com/Sirupsen/logrus
| Tool | Description |
| ---- | ----------- |
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
#### Testing
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just add the `test` hook
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
```go
logger, hook := NewNullLogger()
logger.Error("Hello error")
assert.Equal(1, len(hook.Entries))
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
assert.Equal("Hello error", hook.LastEntry().Message)
hook.Reset()
assert.Nil(hook.LastEntry())
```
#### Fatal handlers
Logrus can register one or more functions that will be called when any `fatal`
level message is logged. The registered handlers will be executed before
logrus performs a `os.Exit(1)`. This behavior may be helpful if callers need
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
```
...
handler := func() {
// gracefully shutdown something...
}
logrus.RegisterExitHandler(handler)
...
```

64
vendor/github.com/Sirupsen/logrus/alt_exit.go generated vendored Normal file
View File

@ -0,0 +1,64 @@
package logrus
// The following code was sourced and modified from the
// https://bitbucket.org/tebeka/atexit package governed by the following license:
//
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import (
"fmt"
"os"
)
var handlers = []func(){}
func runHandler(handler func()) {
defer func() {
if err := recover(); err != nil {
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
}
}()
handler()
}
func runHandlers() {
for _, handler := range handlers {
runHandler(handler)
}
}
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
func Exit(code int) {
runHandlers()
os.Exit(code)
}
// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke
// all handlers. The handlers will also be invoked when any Fatal log entry is
// made.
//
// This method is useful when a caller wishes to use logrus to log a fatal
// message but also needs to gracefully shutdown. An example usecase could be
// closing database connections, or sending a alert that the application is
// closing.
func RegisterExitHandler(handler func()) {
handlers = append(handlers, handler)
}

View File

@ -68,7 +68,7 @@ func (entry *Entry) WithField(key string, value interface{}) *Entry {
// Add a map of fields to the Entry.
func (entry *Entry) WithFields(fields Fields) *Entry {
data := Fields{}
data := make(Fields, len(entry.Data)+len(fields))
for k, v := range entry.Data {
data[k] = v
}
@ -150,7 +150,7 @@ func (entry *Entry) Fatal(args ...interface{}) {
if entry.Logger.Level >= FatalLevel {
entry.log(FatalLevel, fmt.Sprint(args...))
}
os.Exit(1)
Exit(1)
}
func (entry *Entry) Panic(args ...interface{}) {
@ -198,7 +198,7 @@ func (entry *Entry) Fatalf(format string, args ...interface{}) {
if entry.Logger.Level >= FatalLevel {
entry.Fatal(fmt.Sprintf(format, args...))
}
os.Exit(1)
Exit(1)
}
func (entry *Entry) Panicf(format string, args ...interface{}) {
@ -245,7 +245,7 @@ func (entry *Entry) Fatalln(args ...interface{}) {
if entry.Logger.Level >= FatalLevel {
entry.Fatal(entry.sprintlnn(args...))
}
os.Exit(1)
Exit(1)
}
func (entry *Entry) Panicln(args ...interface{}) {

View File

@ -1,77 +0,0 @@
package logrus
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestEntryWithError(t *testing.T) {
assert := assert.New(t)
defer func() {
ErrorKey = "error"
}()
err := fmt.Errorf("kaboom at layer %d", 4711)
assert.Equal(err, WithError(err).Data["error"])
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
assert.Equal(err, entry.WithError(err).Data["error"])
ErrorKey = "err"
assert.Equal(err, entry.WithError(err).Data["err"])
}
func TestEntryPanicln(t *testing.T) {
errBoom := fmt.Errorf("boom time")
defer func() {
p := recover()
assert.NotNil(t, p)
switch pVal := p.(type) {
case *Entry:
assert.Equal(t, "kaboom", pVal.Message)
assert.Equal(t, errBoom, pVal.Data["err"])
default:
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
}
}()
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
entry.WithField("err", errBoom).Panicln("kaboom")
}
func TestEntryPanicf(t *testing.T) {
errBoom := fmt.Errorf("boom again")
defer func() {
p := recover()
assert.NotNil(t, p)
switch pVal := p.(type) {
case *Entry:
assert.Equal(t, "kaboom true", pVal.Message)
assert.Equal(t, errBoom, pVal.Data["err"])
default:
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
}
}()
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
entry.WithField("err", errBoom).Panicf("kaboom %v", true)
}

View File

@ -1,50 +0,0 @@
package main
import (
"github.com/Sirupsen/logrus"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.JSONFormatter)
log.Formatter = new(logrus.TextFormatter) // default
log.Level = logrus.DebugLevel
}
func main() {
defer func() {
err := recover()
if err != nil {
log.WithFields(logrus.Fields{
"omg": true,
"err": err,
"number": 100,
}).Fatal("The ice breaks!")
}
}()
log.WithFields(logrus.Fields{
"animal": "walrus",
"number": 8,
}).Debug("Started observing beach")
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"temperature": -4,
}).Debug("Temperature changes")
log.WithFields(logrus.Fields{
"animal": "orca",
"size": 9009,
}).Panic("It's over 9000!")
}

View File

@ -1,30 +0,0 @@
package main
import (
"github.com/Sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.TextFormatter) // default
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
}
func main() {
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
}

View File

@ -31,18 +31,15 @@ type Formatter interface {
// It's not exported because it's still using Data in an opinionated way. It's to
// avoid code duplication between the two default formatters.
func prefixFieldClashes(data Fields) {
_, ok := data["time"]
if ok {
data["fields.time"] = data["time"]
if t, ok := data["time"]; ok {
data["fields.time"] = t
}
_, ok = data["msg"]
if ok {
data["fields.msg"] = data["msg"]
if m, ok := data["msg"]; ok {
data["fields.msg"] = m
}
_, ok = data["level"]
if ok {
data["fields.level"] = data["level"]
if l, ok := data["level"]; ok {
data["fields.level"] = l
}
}

View File

@ -1,98 +0,0 @@
package logrus
import (
"fmt"
"testing"
"time"
)
// smallFields is a small size data set for benchmarking
var smallFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
}
// largeFields is a large size data set for benchmarking
var largeFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
"five": "six",
"seven": "eight",
"nine": "ten",
"eleven": "twelve",
"thirteen": "fourteen",
"fifteen": "sixteen",
"seventeen": "eighteen",
"nineteen": "twenty",
"a": "b",
"c": "d",
"e": "f",
"g": "h",
"i": "j",
"k": "l",
"m": "n",
"o": "p",
"q": "r",
"s": "t",
"u": "v",
"w": "x",
"y": "z",
"this": "will",
"make": "thirty",
"entries": "yeah",
}
var errorFields = Fields{
"foo": fmt.Errorf("bar"),
"baz": fmt.Errorf("qux"),
}
func BenchmarkErrorTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, errorFields)
}
func BenchmarkSmallTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
}
func BenchmarkLargeTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields)
}
func BenchmarkSmallColoredTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields)
}
func BenchmarkLargeColoredTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields)
}
func BenchmarkSmallJSONFormatter(b *testing.B) {
doBenchmark(b, &JSONFormatter{}, smallFields)
}
func BenchmarkLargeJSONFormatter(b *testing.B) {
doBenchmark(b, &JSONFormatter{}, largeFields)
}
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
entry := &Entry{
Time: time.Time{},
Level: InfoLevel,
Message: "message",
Data: fields,
}
var d []byte
var err error
for i := 0; i < b.N; i++ {
d, err = formatter.Format(entry)
if err != nil {
b.Fatal(err)
}
b.SetBytes(int64(len(d)))
}
}

View File

@ -1,56 +0,0 @@
package logstash
import (
"encoding/json"
"fmt"
"github.com/Sirupsen/logrus"
)
// Formatter generates json in logstash format.
// Logstash site: http://logstash.net/
type LogstashFormatter struct {
Type string // if not empty use for logstash type field.
// TimestampFormat sets the format used for timestamps.
TimestampFormat string
}
func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) {
entry.Data["@version"] = 1
if f.TimestampFormat == "" {
f.TimestampFormat = logrus.DefaultTimestampFormat
}
entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat)
// set message field
v, ok := entry.Data["message"]
if ok {
entry.Data["fields.message"] = v
}
entry.Data["message"] = entry.Message
// set level field
v, ok = entry.Data["level"]
if ok {
entry.Data["fields.level"] = v
}
entry.Data["level"] = entry.Level.String()
// set type field
if f.Type != "" {
v, ok = entry.Data["type"]
if ok {
entry.Data["fields.type"] = v
}
entry.Data["type"] = f.Type
}
serialized, err := json.Marshal(entry.Data)
if err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
}
return append(serialized, '\n'), nil
}

View File

@ -1,52 +0,0 @@
package logstash
import (
"bytes"
"encoding/json"
"github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
"testing"
)
func TestLogstashFormatter(t *testing.T) {
assert := assert.New(t)
lf := LogstashFormatter{Type: "abc"}
fields := logrus.Fields{
"message": "def",
"level": "ijk",
"type": "lmn",
"one": 1,
"pi": 3.14,
"bool": true,
}
entry := logrus.WithFields(fields)
entry.Message = "msg"
entry.Level = logrus.InfoLevel
b, _ := lf.Format(entry)
var data map[string]interface{}
dec := json.NewDecoder(bytes.NewReader(b))
dec.UseNumber()
dec.Decode(&data)
// base fields
assert.Equal(json.Number("1"), data["@version"])
assert.NotEmpty(data["@timestamp"])
assert.Equal("abc", data["type"])
assert.Equal("msg", data["message"])
assert.Equal("info", data["level"])
// substituted fields
assert.Equal("def", data["fields.message"])
assert.Equal("ijk", data["fields.level"])
assert.Equal("lmn", data["fields.type"])
// formats
assert.Equal(json.Number("1"), data["one"])
assert.Equal(json.Number("3.14"), data["pi"])
assert.Equal(true, data["bool"])
}

View File

@ -1,122 +0,0 @@
package logrus
import (
"testing"
"github.com/stretchr/testify/assert"
)
type TestHook struct {
Fired bool
}
func (hook *TestHook) Fire(entry *Entry) error {
hook.Fired = true
return nil
}
func (hook *TestHook) Levels() []Level {
return []Level{
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
FatalLevel,
PanicLevel,
}
}
func TestHookFires(t *testing.T) {
hook := new(TestHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
assert.Equal(t, hook.Fired, false)
log.Print("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, true)
})
}
type ModifyHook struct {
}
func (hook *ModifyHook) Fire(entry *Entry) error {
entry.Data["wow"] = "whale"
return nil
}
func (hook *ModifyHook) Levels() []Level {
return []Level{
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
FatalLevel,
PanicLevel,
}
}
func TestHookCanModifyEntry(t *testing.T) {
hook := new(ModifyHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.WithField("wow", "elephant").Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["wow"], "whale")
})
}
func TestCanFireMultipleHooks(t *testing.T) {
hook1 := new(ModifyHook)
hook2 := new(TestHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook1)
log.Hooks.Add(hook2)
log.WithField("wow", "elephant").Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["wow"], "whale")
assert.Equal(t, hook2.Fired, true)
})
}
type ErrorHook struct {
Fired bool
}
func (hook *ErrorHook) Fire(entry *Entry) error {
hook.Fired = true
return nil
}
func (hook *ErrorHook) Levels() []Level {
return []Level{
ErrorLevel,
}
}
func TestErrorHookShouldntFireOnInfo(t *testing.T) {
hook := new(ErrorHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.Info("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, false)
})
}
func TestErrorHookShouldFireOnError(t *testing.T) {
hook := new(ErrorHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.Error("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, true)
})
}

View File

@ -1,68 +0,0 @@
package logrus_bugsnag
import (
"errors"
"github.com/Sirupsen/logrus"
"github.com/bugsnag/bugsnag-go"
)
type bugsnagHook struct{}
// ErrBugsnagUnconfigured is returned if NewBugsnagHook is called before
// bugsnag.Configure. Bugsnag must be configured before the hook.
var ErrBugsnagUnconfigured = errors.New("bugsnag must be configured before installing this logrus hook")
// ErrBugsnagSendFailed indicates that the hook failed to submit an error to
// bugsnag. The error was successfully generated, but `bugsnag.Notify()`
// failed.
type ErrBugsnagSendFailed struct {
err error
}
func (e ErrBugsnagSendFailed) Error() string {
return "failed to send error to Bugsnag: " + e.err.Error()
}
// NewBugsnagHook initializes a logrus hook which sends exceptions to an
// exception-tracking service compatible with the Bugsnag API. Before using
// this hook, you must call bugsnag.Configure(). The returned object should be
// registered with a log via `AddHook()`
//
// Entries that trigger an Error, Fatal or Panic should now include an "error"
// field to send to Bugsnag.
func NewBugsnagHook() (*bugsnagHook, error) {
if bugsnag.Config.APIKey == "" {
return nil, ErrBugsnagUnconfigured
}
return &bugsnagHook{}, nil
}
// Fire forwards an error to Bugsnag. Given a logrus.Entry, it extracts the
// "error" field (or the Message if the error isn't present) and sends it off.
func (hook *bugsnagHook) Fire(entry *logrus.Entry) error {
var notifyErr error
err, ok := entry.Data["error"].(error)
if ok {
notifyErr = err
} else {
notifyErr = errors.New(entry.Message)
}
bugsnagErr := bugsnag.Notify(notifyErr)
if bugsnagErr != nil {
return ErrBugsnagSendFailed{bugsnagErr}
}
return nil
}
// Levels enumerates the log levels on which the error should be forwarded to
// bugsnag: everything at or above the "Error" level.
func (hook *bugsnagHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.ErrorLevel,
logrus.FatalLevel,
logrus.PanicLevel,
}
}

View File

@ -1,64 +0,0 @@
package logrus_bugsnag
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/Sirupsen/logrus"
"github.com/bugsnag/bugsnag-go"
)
type notice struct {
Events []struct {
Exceptions []struct {
Message string `json:"message"`
} `json:"exceptions"`
} `json:"events"`
}
func TestNoticeReceived(t *testing.T) {
msg := make(chan string, 1)
expectedMsg := "foo"
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var notice notice
data, _ := ioutil.ReadAll(r.Body)
if err := json.Unmarshal(data, &notice); err != nil {
t.Error(err)
}
_ = r.Body.Close()
msg <- notice.Events[0].Exceptions[0].Message
}))
defer ts.Close()
hook := &bugsnagHook{}
bugsnag.Configure(bugsnag.Configuration{
Endpoint: ts.URL,
ReleaseStage: "production",
APIKey: "12345678901234567890123456789012",
Synchronous: true,
})
log := logrus.New()
log.Hooks.Add(hook)
log.WithFields(logrus.Fields{
"error": errors.New(expectedMsg),
}).Error("Bugsnag will not see this string")
select {
case received := <-msg:
if received != expectedMsg {
t.Errorf("Unexpected message received: %s", received)
}
case <-time.After(time.Second):
t.Error("Timed out; no notice received by Bugsnag API")
}
}

View File

@ -1,39 +0,0 @@
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
## Usage
```go
import (
"log/syslog"
"github.com/Sirupsen/logrus"
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
)
func main() {
log := logrus.New()
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err == nil {
log.Hooks.Add(hook)
}
}
```
If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). Just assign empty string to the first two parameters of `NewSyslogHook`. It should look like the following.
```go
import (
"log/syslog"
"github.com/Sirupsen/logrus"
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
)
func main() {
log := logrus.New()
hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
if err == nil {
log.Hooks.Add(hook)
}
}
```

Some files were not shown because too many files have changed in this diff Show More