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

This commit is contained in:
Tan Jiang 2016-10-28 22:44:59 +08:00
commit 7f55520b27
36 changed files with 464 additions and 124 deletions

View File

@ -67,6 +67,9 @@ You can update or remove a member by clicking the icon on the right.
##Replicating images ##Replicating images
If you are a system administrator, you can replicate images to a remote registry, which is called destination in Harbor. Only Harbor instance is supported as a destination for now. If you are a system administrator, you can replicate images to a remote registry, which is called destination in Harbor. Only Harbor instance is supported as a destination for now.
**Note:** The replication feature is incompatible between Harbor instance before version 0.3.5(included) and after version 0.3.5.
Click "Add New Policy" on the "Replication" tab, fill the necessary fields and click "OK", a policy for this project will be created. If "Enable" is chosen, the project will be replicated to the remote immediately, and when a new repository is pushed to this project or an existing repository is deleted from this project, the same operation will also be replicated to the destination. Click "Add New Policy" on the "Replication" tab, fill the necessary fields and click "OK", a policy for this project will be created. If "Enable" is chosen, the project will be replicated to the remote immediately, and when a new repository is pushed to this project or an existing repository is deleted from this project, the same operation will also be replicated to the destination.
![browse project](img/new_create_policy.png) ![browse project](img/new_create_policy.png)
@ -169,4 +172,4 @@ $ docker-compose start
Option "--dry-run" will print the progress without removing any data. Option "--dry-run" will print the progress without removing any data.
About the details of GC, please see [GC](https://github.com/docker/distribution/blob/master/docs/garbage-collection.md). About the details of GC, please see [GC](https://github.com/docker/docker.github.io/blob/master/registry/garbage-collection.md).

View File

@ -1,7 +0,0 @@
# Logrotate configuartion file for docker.
/var/log/docker/*/*.log {
rotate 100
size 10M
copytruncate
}

26
make/common/log/rotate.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
set -e
echo "Log rotate starting..."
#The logs n days before will be compressed.
n=14
path=/var/log/docker
list=""
n_days_before=$(($(date +%s) - 3600*24*$n))
for dir in $(ls $path | grep -v "tar.gz");
do
if [ $(date --date=$dir +%s) -lt $n_days_before ]
then
echo "$dir will be compressed"
list="$list $dir"
fi
done
if [ -n "$list" ]
then
cd $path
tar --remove-files -zcvf $(date -d @$n_days_before +%F)-.tar.gz $list
fi
echo "Log rotate finished."

View File

@ -1,8 +1,7 @@
FROM library/photon:latest FROM library/photon:latest
# run logrotate hourly, disable imklog model, provides TCP/UDP syslog reception # run logrotate hourly, disable imklog model, provides TCP/UDP syslog reception
RUN tdnf install -y cronie rsyslog logrotate shadow\ RUN tdnf install -y cronie rsyslog shadow tar gzip \
&& mv /etc/cron.daily/logrotate /etc/cron.hourly/ \
&& mkdir /etc/rsyslog.d/ \ && mkdir /etc/rsyslog.d/ \
&& mkdir /var/spool/rsyslog \ && mkdir /var/spool/rsyslog \
&& groupadd syslog \ && groupadd syslog \
@ -10,15 +9,13 @@ RUN tdnf install -y cronie rsyslog logrotate shadow\
ADD make/common/log/rsyslog.conf /etc/rsyslog.conf ADD make/common/log/rsyslog.conf /etc/rsyslog.conf
COPY make/photon/log/logrotate.conf.photon /etc/logrotate.conf # rotate logs weekly
# notes: file name cannot contain dot, or the script will not run
# logrotate configuration file for docker ADD make/common/log/rotate.sh /etc/cron.weekly/rotate
ADD make/common/log/logrotate_docker.conf /etc/logrotate.d/
# rsyslog configuration file for docker # rsyslog configuration file for docker
ADD make/common/log/rsyslog_docker.conf /etc/rsyslog.d/ ADD make/common/log/rsyslog_docker.conf /etc/rsyslog.d/
VOLUME /var/log/docker/ VOLUME /var/log/docker/
EXPOSE 514 EXPOSE 514

View File

@ -1,35 +0,0 @@
# see "man logrotate" for details
# rotate log files weekly
weekly
# keep 4 weeks worth of backlogs
rotate 4
# create new (empty) log files after rotating old ones
create
# use date as a suffix of the rotated file
dateext
# uncomment this if you want your log files compressed
#compress
# RPM packages drop log rotation information into this directory
include /etc/logrotate.d
# no packages own wtmp and btmp -- we'll rotate them here
#/var/log/wtmp {
# monthly
# create 0664 root utmp
# minsize 1M
# rotate 1
#}
/var/log/btmp {
missingok
monthly
create 0600 root utmp
rotate 1
}
# system-specific logs may be also be configured here.

View File

@ -1,13 +1,12 @@
FROM library/ubuntu:14.04 FROM library/ubuntu:14.04
# run logrotate hourly, disable imklog model, provides TCP/UDP syslog reception RUN rm /etc/rsyslog.d/* && rm /etc/rsyslog.conf
RUN mv /etc/cron.daily/logrotate /etc/cron.hourly/ \
&& rm /etc/rsyslog.d/* \
&& rm /etc/rsyslog.conf
ADD make/common/log/rsyslog.conf /etc/rsyslog.conf ADD make/common/log/rsyslog.conf /etc/rsyslog.conf
# logrotate configuration file for docker # rotate logs weekly
ADD make/common/log/logrotate_docker.conf /etc/logrotate.d/ # notes: file name cannot contain dot, or the script will not run
ADD make/common/log/rotate.sh /etc/cron.weekly/rotate
# rsyslog configuration file for docker # rsyslog configuration file for docker
ADD make/common/log/rsyslog_docker.conf /etc/rsyslog.d/ ADD make/common/log/rsyslog_docker.conf /etc/rsyslog.d/

View File

@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net/http/httptest" "net/http/httptest"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -90,6 +91,11 @@ func init() {
_ = updateInitPassword(1, "Harbor12345") _ = updateInitPassword(1, "Harbor12345")
//syncRegistry
if err := SyncRegistry(); err != nil {
log.Fatalf("failed to sync repositories from registry: %v", err)
}
//Init user Info //Init user Info
admin = &usrInfo{adminName, adminPwd} admin = &usrInfo{adminName, adminPwd}
unknownUsr = &usrInfo{"unknown", "unknown"} unknownUsr = &usrInfo{"unknown", "unknown"}
@ -119,8 +125,10 @@ func request(_sling *sling.Sling, acceptHeader string, authInfo ...usrInfo) (int
//The response includes the project and repository list in a proper display order. //The response includes the project and repository list in a proper display order.
//@param q Search parameter for project and repository name. //@param q Search parameter for project and repository name.
//@return []Search //@return []Search
//func (a testapi) SearchGet (q string) (apilib.Search, error) { func (a testapi) SearchGet(q string, authInfo ...usrInfo) (int, apilib.Search, error) {
func (a testapi) SearchGet(q string) (apilib.Search, error) { var httpCode int
var body []byte
var err error
_sling := sling.New().Get(a.basePath) _sling := sling.New().Get(a.basePath)
@ -134,10 +142,15 @@ func (a testapi) SearchGet(q string) (apilib.Search, error) {
_sling = _sling.QueryStruct(&QueryParams{Query: q}) _sling = _sling.QueryStruct(&QueryParams{Query: q})
_, body, err := request(_sling, jsonAcceptHeader) if len(authInfo) > 0 {
httpCode, body, err = request(_sling, jsonAcceptHeader, authInfo[0])
} else {
httpCode, body, err = request(_sling, jsonAcceptHeader)
}
var successPayload = new(apilib.Search) var successPayload = new(apilib.Search)
err = json.Unmarshal(body, &successPayload) err = json.Unmarshal(body, &successPayload)
return *successPayload, err return httpCode, *successPayload, err
} }
//Create a new project. //Create a new project.

View File

@ -21,7 +21,7 @@ func TestLogGet(t *testing.T) {
project.ProjectName = "my_project" project.ProjectName = "my_project"
project.Public = 1 project.Public = 1
now := fmt.Sprintf("%v", time.Now().Unix()) now := fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err := apiTest.LogGet(*admin, "0", now, "") statusCode, result, err := apiTest.LogGet(*admin, "0", now, "1000")
if err != nil { if err != nil {
t.Error("Error while get log information", err.Error()) t.Error("Error while get log information", err.Error())
t.Log(err) t.Log(err)
@ -41,6 +41,7 @@ func TestLogGet(t *testing.T) {
assert.Equal(int(201), reply, "Case 2: Project creation status should be 201") assert.Equal(int(201), reply, "Case 2: Project creation status should be 201")
} }
//case 1: right parameters, expect the right output //case 1: right parameters, expect the right output
now = fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err = apiTest.LogGet(*admin, "0", now, "1000") statusCode, result, err = apiTest.LogGet(*admin, "0", now, "1000")
if err != nil { if err != nil {
t.Error("Error while get log information", err.Error()) t.Error("Error while get log information", err.Error())
@ -58,7 +59,6 @@ func TestLogGet(t *testing.T) {
} }
fmt.Println("log ", result) fmt.Println("log ", result)
//case 2: wrong format of start_time parameter, expect the wrong output //case 2: wrong format of start_time parameter, expect the wrong output
now = fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err = apiTest.LogGet(*admin, "ss", now, "3") statusCode, result, err = apiTest.LogGet(*admin, "ss", now, "3")
if err != nil { if err != nil {
t.Error("Error occured while get log information since the format of start_time parameter is not right.", err.Error()) t.Error("Error occured while get log information since the format of start_time parameter is not right.", err.Error())
@ -77,7 +77,6 @@ func TestLogGet(t *testing.T) {
} }
//case 4: wrong format of lines parameter, expect the wrong output //case 4: wrong format of lines parameter, expect the wrong output
now = fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err = apiTest.LogGet(*admin, "0", now, "s") statusCode, result, err = apiTest.LogGet(*admin, "0", now, "s")
if err != nil { if err != nil {
t.Error("Error occured while get log information since the format of lines parameter is not right.", err.Error()) t.Error("Error occured while get log information since the format of lines parameter is not right.", err.Error())
@ -87,7 +86,6 @@ func TestLogGet(t *testing.T) {
} }
//case 5: wrong format of lines parameter, expect the wrong output //case 5: wrong format of lines parameter, expect the wrong output
now = fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err = apiTest.LogGet(*admin, "0", now, "-5") statusCode, result, err = apiTest.LogGet(*admin, "0", now, "-5")
if err != nil { if err != nil {
t.Error("Error occured while get log information since the format of lines parameter is not right.", err.Error()) t.Error("Error occured while get log information since the format of lines parameter is not right.", err.Error())
@ -143,7 +141,6 @@ func TestLogGet(t *testing.T) {
} }
fmt.Printf("\n") fmt.Printf("\n")
} }
func getLog(result []apilib.AccessLog) (int, int) { func getLog(result []apilib.AccessLog) (int, int) {

View File

@ -16,13 +16,14 @@
package api package api
import ( import (
"fmt"
"net/http" "net/http"
"strconv" "strconv"
"github.com/vmware/harbor/src/common/api"
"github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/api"
) )
// ProjectMemberAPI handles request to /api/projects/{}/members/{} // ProjectMemberAPI handles request to /api/projects/{}/members/{}
@ -98,6 +99,11 @@ func (pma *ProjectMemberAPI) Get() {
log.Errorf("Error occurred in GetUserProjectRoles, error: %v", err) log.Errorf("Error occurred in GetUserProjectRoles, error: %v", err)
pma.CustomAbort(http.StatusInternalServerError, "Internal error.") pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
} }
if len(roleList) == 0 {
pma.CustomAbort(http.StatusNotFound, fmt.Sprintf("user %d is not a member of the project", pma.memberID))
}
//return empty role list to indicate if a user is not a member //return empty role list to indicate if a user is not a member
result := make(map[string]interface{}) result := make(map[string]interface{})
user, err := dao.GetUser(models.User{UserID: pma.memberID}) user, err := dao.GetUser(models.User{UserID: pma.memberID})

View File

@ -4,9 +4,10 @@ import (
"fmt" "fmt"
"testing" "testing"
"strconv"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/vmware/harbor/tests/apitests/apilib" "github.com/vmware/harbor/tests/apitests/apilib"
"strconv"
) )
func TestMemGet(t *testing.T) { func TestMemGet(t *testing.T) {
@ -51,8 +52,19 @@ func TestMemGet(t *testing.T) {
assert.Equal(int(404), httpStatusCode, "Case 3: Project creation status should be 404") assert.Equal(int(404), httpStatusCode, "Case 3: Project creation status should be 404")
} }
fmt.Printf("\n") //------------case 4: Response Code=404, member does not exist-----------//
fmt.Println("case 4: Response Code=404, member does not exist")
projectID = "1"
memberID := "10000"
httpStatusCode, err = apiTest.GetMemByPIDUID(*admin, projectID, memberID)
if err != nil {
t.Fatalf("failed to get member %s of project %s: %v", memberID, projectID, err)
}
assert.Equal(int(404), httpStatusCode,
fmt.Sprintf("response status code should be 404 other than %d", httpStatusCode))
fmt.Printf("\n")
} }
/** /**

View File

@ -20,10 +20,10 @@ import (
"net/http" "net/http"
"regexp" "regexp"
"github.com/vmware/harbor/src/common/api"
"github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/api"
"strconv" "strconv"
"time" "time"
@ -192,7 +192,8 @@ func (p *ProjectAPI) Delete() {
if err := dao.AddAccessLog(models.AccessLog{ if err := dao.AddAccessLog(models.AccessLog{
UserID: userID, UserID: userID,
ProjectID: p.projectID, ProjectID: p.projectID,
RepoName: p.projectName, RepoName: p.projectName + "/",
RepoTag: "N/A",
Operation: "delete", Operation: "delete",
}); err != nil { }); err != nil {
log.Errorf("failed to add access log: %v", err) log.Errorf("failed to add access log: %v", err)

View File

@ -14,19 +14,29 @@ func TestSearch(t *testing.T) {
apiTest := newHarborAPI() apiTest := newHarborAPI()
var result apilib.Search var result apilib.Search
result, err := apiTest.SearchGet("library")
//fmt.Printf("%+v\n", result) //-------------case 1 : Response Code = 200, Not sysAdmin --------------//
httpStatusCode, result, err := apiTest.SearchGet("library")
if err != nil { if err != nil {
t.Error("Error while search project or repository", err.Error()) t.Error("Error while search project or repository", err.Error())
t.Log(err) t.Log(err)
} else { } else {
assert.Equal(result.Projects[0].Id, int64(1), "Project id should be equal") assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(result.Projects[0].Name, "library", "Project name should be library") assert.Equal(int64(1), result.Projects[0].Id, "Project id should be equal")
assert.Equal(result.Projects[0].Public, int32(1), "Project public status should be 1 (true)") assert.Equal("library", result.Projects[0].Name, "Project name should be library")
//t.Log(result) assert.Equal(int32(1), result.Projects[0].Public, "Project public status should be 1 (true)")
}
//--------case 2 : Response Code = 200, sysAdmin and search repo--------//
httpStatusCode, result, err = apiTest.SearchGet("docker", *admin)
if err != nil {
t.Error("Error while search project or repository", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal("library", result.Repositories[0].ProjectName, "Project name should be library")
assert.Equal("library/docker", result.Repositories[0].RepositoryName, "Repository name should be library/docker")
assert.Equal(int32(1), result.Repositories[0].ProjectPublic, "Project public status should be 1 (true)")
} }
//if result.Response.StatusCode != 200 {
// t.Log(result.Response)
//}
} }

View File

@ -10,10 +10,6 @@ import (
) )
func TestStatisticGet(t *testing.T) { func TestStatisticGet(t *testing.T) {
if err := SyncRegistry(); err != nil {
t.Fatalf("failed to sync repositories from registry: %v", err)
}
fmt.Println("Testing Statistic API") fmt.Println("Testing Statistic API")
assert := assert.New(t) assert := assert.New(t)

View File

@ -1,9 +1,5 @@
package controllers package controllers
import (
"net/http"
)
// AccountSettingController handles request to /account_setting // AccountSettingController handles request to /account_setting
type AccountSettingController struct { type AccountSettingController struct {
BaseController BaseController
@ -11,8 +7,14 @@ type AccountSettingController struct {
// Get renders the account settings page // Get renders the account settings page
func (asc *AccountSettingController) Get() { func (asc *AccountSettingController) Get() {
if asc.AuthMode != "db_auth" { var isAdminForLdap bool
asc.CustomAbort(http.StatusForbidden, "") sessionUserID, ok := asc.GetSession("userId").(int)
if ok && sessionUserID == 1 {
isAdminForLdap = true
}
if asc.AuthMode == "db_auth" || isAdminForLdap {
asc.Forward("page_title_account_setting", "account-settings.htm")
} else {
asc.Redirect("/dashboard", 302)
} }
asc.Forward("page_title_account_setting", "account-settings.htm")
} }

View File

@ -1,5 +1,10 @@
package controllers package controllers
import (
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/utils/log"
)
// AdminOptionController handles requests to /admin_option // AdminOptionController handles requests to /admin_option
type AdminOptionController struct { type AdminOptionController struct {
BaseController BaseController
@ -7,5 +12,16 @@ type AdminOptionController struct {
// Get renders the admin options page // Get renders the admin options page
func (aoc *AdminOptionController) Get() { func (aoc *AdminOptionController) Get() {
aoc.Forward("page_title_admin_option", "admin-options.htm") sessionUserID, ok := aoc.GetSession("userId").(int)
if ok {
isAdmin, err := dao.IsAdminRole(sessionUserID)
if err != nil {
log.Errorf("Error occurred in IsAdminRole: %v", err)
}
if isAdmin {
aoc.Forward("page_title_admin_option", "admin-options.htm")
return
}
}
aoc.Redirect("/dashboard", 302)
} }

View File

@ -8,10 +8,10 @@ import (
"github.com/astaxie/beego" "github.com/astaxie/beego"
"github.com/beego/i18n" "github.com/beego/i18n"
"github.com/vmware/harbor/src/ui/auth"
"github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/auth"
) )
// BaseController wraps common methods such as i18n support, forward, which can be leveraged by other UI render controllers. // BaseController wraps common methods such as i18n support, forward, which can be leveraged by other UI render controllers.

View File

@ -1,18 +1,20 @@
package controllers package controllers
import (
"net/http"
)
// ChangePasswordController handles request to /change_password // ChangePasswordController handles request to /change_password
type ChangePasswordController struct { type ChangePasswordController struct {
BaseController BaseController
} }
// Get renders the change password page // Get renders the change password page
func (asc *ChangePasswordController) Get() { func (cpc *ChangePasswordController) Get() {
if asc.AuthMode != "db_auth" { var isAdminForLdap bool
asc.CustomAbort(http.StatusForbidden, "") sessionUserID, ok := cpc.GetSession("userId").(int)
if ok && sessionUserID == 1 {
isAdminForLdap = true
}
if cpc.AuthMode == "db_auth" || isAdminForLdap {
cpc.Forward("page_title_change_password", "change-password.htm")
} else {
cpc.Redirect("/dashboard", 302)
} }
asc.Forward("page_title_change_password", "change-password.htm")
} }

View File

@ -113,19 +113,16 @@ func TestMain(t *testing.T) {
w = httptest.NewRecorder() w = httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r) beego.BeeApp.Handlers.ServeHTTP(w, r)
assert.Equal(int(200), w.Code, "'/account_setting' httpStatusCode should be 200") assert.Equal(int(200), w.Code, "'/account_setting' httpStatusCode should be 200")
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_account_setting</title>"), "http respond should have '<title>page_title_account_setting</title>'")
r, _ = http.NewRequest("GET", "/change_password", nil) r, _ = http.NewRequest("GET", "/change_password", nil)
w = httptest.NewRecorder() w = httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r) beego.BeeApp.Handlers.ServeHTTP(w, r)
assert.Equal(int(200), w.Code, "'/change_password' httpStatusCode should be 200") assert.Equal(int(200), w.Code, "'/change_password' httpStatusCode should be 200")
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_change_password</title>"), "http respond should have '<title>page_title_change_password</title>'")
r, _ = http.NewRequest("GET", "/admin_option", nil) r, _ = http.NewRequest("GET", "/admin_option", nil)
w = httptest.NewRecorder() w = httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r) beego.BeeApp.Handlers.ServeHTTP(w, r)
assert.Equal(int(200), w.Code, "'/admin_option' httpStatusCode should be 200") assert.Equal(int(302), w.Code, "'/admin_option' httpStatusCode should be 302")
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_admin_option</title>"), "http respond should have '<title>page_title_admin_option</title>'")
r, _ = http.NewRequest("GET", "/forgot_password", nil) r, _ = http.NewRequest("GET", "/forgot_password", nil)
w = httptest.NewRecorder() w = httptest.NewRecorder()

View File

@ -19,6 +19,8 @@ func (omc *OptionalMenuController) Get() {
var hasLoggedIn bool var hasLoggedIn bool
var allowAddNew bool var allowAddNew bool
var isAdminForLdap bool
var allowSettingAccount bool var allowSettingAccount bool
if sessionUserID != nil { if sessionUserID != nil {
@ -35,7 +37,11 @@ func (omc *OptionalMenuController) Get() {
} }
omc.Data["Username"] = u.Username omc.Data["Username"] = u.Username
if omc.AuthMode == "db_auth" { if userID == 1 {
isAdminForLdap = true
}
if omc.AuthMode == "db_auth" || isAdminForLdap {
allowSettingAccount = true allowSettingAccount = true
} }

View File

@ -88,7 +88,6 @@
function getProjectSuccess(response) { function getProjectSuccess(response) {
var partialProjects = response.data || []; var partialProjects = response.data || [];
for(var i in partialProjects) { for(var i in partialProjects) {
vm.projects.push(partialProjects[i]); vm.projects.push(partialProjects[i]);
@ -114,11 +113,13 @@
} }
} }
$location.search('project_id', vm.selectedProject.project_id); if(vm.selectedProject) {
vm.checkProjectMember(vm.selectedProject.project_id); $location.search('project_id', vm.selectedProject.project_id);
vm.checkProjectMember(vm.selectedProject.project_id);
}
vm.resultCount = vm.projects.length; vm.resultCount = vm.projects.length;
$scope.$watch('vm.filterInput', function(current, origin) { $scope.$watch('vm.filterInput', function(current, origin) {
vm.resultCount = $filter('name')(vm.projects, vm.filterInput, 'name').length; vm.resultCount = $filter('name')(vm.projects, vm.filterInput, 'name').length;
}); });

View File

@ -19,9 +19,9 @@
.module('harbor.repository') .module('harbor.repository')
.directive('listRepository', listRepository); .directive('listRepository', listRepository);
ListRepositoryController.$inject = ['$scope', 'ListRepositoryService', 'DeleteRepositoryService', '$filter', 'trFilter', '$location', 'getParameterByName']; ListRepositoryController.$inject = ['$scope', 'ListRepositoryService', 'DeleteRepositoryService', '$filter', 'trFilter', '$location', 'getParameterByName', '$window'];
function ListRepositoryController($scope, ListRepositoryService, DeleteRepositoryService, $filter, trFilter, $location, getParameterByName) { function ListRepositoryController($scope, ListRepositoryService, DeleteRepositoryService, $filter, trFilter, $location, getParameterByName, $window) {
$scope.subsTabPane = 30; $scope.subsTabPane = 30;
@ -104,7 +104,23 @@
} }
function getRepositoryFailed(response) { function getRepositoryFailed(response) {
console.log('Failed to list repositories:' + response); var errorMessage = '';
if(response.status === 404) {
errorMessage = $filter('tr')('project_does_not_exist');
}else{
errorMessage = $filter('tr')('failed_to_get_project');
}
$scope.$emit('modalTitle', $filter('tr')('error'));
$scope.$emit('modalMessage', errorMessage);
var emitInfo = {
'confirmOnly': true,
'contentType': 'text/html',
'action' : function() {
$window.location.href = '/dashboard';
}
};
$scope.$emit('raiseInfo', emitInfo);
console.log('Failed to list repositories:' + response.data);
} }
function searchRepo() { function searchRepo() {

View File

@ -32,7 +32,7 @@
</tr> </tr>
<tr ng-if="vm.integratedLogs.length > 0" ng-repeat="t in vm.integratedLogs"> <tr ng-if="vm.integratedLogs.length > 0" ng-repeat="t in vm.integratedLogs">
<td width="18%">//t.username//</td> <td width="18%">//t.username//</td>
<td width="28%"><a href="javascript:void(0);" ng-click="vm.gotoLog(t.project_id, t.username)">//t.repo_name//</a></td> <td width="28%"><a href="javascript:void(0);" ng-click="vm.gotoRepo(t.project_id, t.repo_name)">//t.repo_name//</a></td>
<td width="15%">//t.repo_tag//</td> <td width="15%">//t.repo_tag//</td>
<td width="14%">//t.operation//</td> <td width="14%">//t.operation//</td>
<td width="25%">//t.op_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td> <td width="25%">//t.op_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>

View File

@ -29,7 +29,7 @@
.success(listIntegratedLogSuccess) .success(listIntegratedLogSuccess)
.error(listIntegratedLogFailed); .error(listIntegratedLogFailed);
vm.gotoLog = gotoLog; vm.gotoRepo = gotoRepo;
function listIntegratedLogSuccess(data) { function listIntegratedLogSuccess(data) {
vm.integratedLogs = data || []; vm.integratedLogs = data || [];
@ -42,8 +42,8 @@
console.log('Failed to get user logs:' + data); console.log('Failed to get user logs:' + data);
} }
function gotoLog(projectId, username) { function gotoRepo(projectId, repoName) {
$window.location.href = '/repository#/logs?project_id=' + projectId + '#' + encodeURIComponent(username); $window.location.href = '/repository#/repositories?project_id=' + projectId + '#' + encodeURIComponent(repoName);
} }
} }

View File

@ -31,7 +31,7 @@
<th width="20%">// 'email' | tr //</th> <th width="20%">// 'email' | tr //</th>
<th width="35%">// 'registration_time' | tr //</th> <th width="35%">// 'registration_time' | tr //</th>
<th width="15%">// 'administrator' | tr //</th> <th width="15%">// 'administrator' | tr //</th>
<th width="20%">// 'operation' | tr //</th> <th width="20%" ng-if="vm.authMode === 'db_auth'">// 'operation' | tr //</th>
</thead> </thead>
</table> </table>
</div> </div>
@ -46,7 +46,7 @@
<td width="15%"> <td width="15%">
<toggle-admin current-user="vm.currentUser" 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>
<td width="20%"> <td width="20%" ng-if="vm.authMode === 'db_auth'">
&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> &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> </td>
</tr> </tr>

View File

@ -98,6 +98,9 @@
'restrict': 'E', 'restrict': 'E',
'templateUrl': '/static/resources/js/components/user/list-user.directive.html', 'templateUrl': '/static/resources/js/components/user/list-user.directive.html',
'link': link, 'link': link,
'scope': {
'authMode': '@'
},
'controller': ListUserController, 'controller': ListUserController,
'controllerAs': 'vm', 'controllerAs': 'vm',
'bindToController': true 'bindToController': true

View File

@ -233,6 +233,7 @@ var locale_messages = {
'failed_to_get_project_member': 'Failed to get current project member.', 'failed_to_get_project_member': 'Failed to get current project member.',
'failed_to_delete_repo': 'Failed to delete repository. ', 'failed_to_delete_repo': 'Failed to delete repository. ',
'failed_to_delete_repo_insuffient_permissions': 'Failed to delete repository, insuffient permissions.', 'failed_to_delete_repo_insuffient_permissions': 'Failed to delete repository, insuffient permissions.',
'failed_to_get_repo': 'Failed to get repositories.',
'failed_to_get_tag': 'Failed to get tag.', 'failed_to_get_tag': 'Failed to get tag.',
'failed_to_get_log': 'Failed to get logs.', 'failed_to_get_log': 'Failed to get logs.',
'failed_to_get_project': 'Failed to get projects.', 'failed_to_get_project': 'Failed to get projects.',
@ -263,6 +264,7 @@ var locale_messages = {
'failed_to_update_destination': 'Failed to update destination.', 'failed_to_update_destination': 'Failed to update destination.',
'failed_to_toggle_publicity_insuffient_permissions': 'Failed to toggle project publicity, insuffient permissions.', 'failed_to_toggle_publicity_insuffient_permissions': 'Failed to toggle project publicity, insuffient permissions.',
'failed_to_toggle_publicity': 'Failed to toggle project publicity.', 'failed_to_toggle_publicity': 'Failed to toggle project publicity.',
'project_does_not_exist': 'Project does not exist.',
'project_admin': 'Project Admin', 'project_admin': 'Project Admin',
'developer': 'Developer', 'developer': 'Developer',
'guest': 'Guest', 'guest': 'Guest',

View File

@ -233,6 +233,7 @@ var locale_messages = {
'failed_to_get_project_member': '无法获取当前项目成员。', 'failed_to_get_project_member': '无法获取当前项目成员。',
'failed_to_delete_repo': '无法删除镜像仓库。', 'failed_to_delete_repo': '无法删除镜像仓库。',
'failed_to_delete_repo_insuffient_permissions': '无法删除镜像仓库,权限不足。', 'failed_to_delete_repo_insuffient_permissions': '无法删除镜像仓库,权限不足。',
'failed_to_get_repo': '获取镜像仓库数据失败。',
'failed_to_get_tag': '获取标签数据失败。', 'failed_to_get_tag': '获取标签数据失败。',
'failed_to_get_log': '获取日志数据失败。', 'failed_to_get_log': '获取日志数据失败。',
'failed_to_get_project': '获取项目数据失败。', 'failed_to_get_project': '获取项目数据失败。',
@ -263,6 +264,7 @@ var locale_messages = {
'failed_to_update_destination': '修改目标失败。', 'failed_to_update_destination': '修改目标失败。',
'failed_to_toggle_publicity_insuffient_permissions': '切换项目公开性失败,权限不足。', 'failed_to_toggle_publicity_insuffient_permissions': '切换项目公开性失败,权限不足。',
'failed_to_toggle_publicity': '切换项目公开性失败。', 'failed_to_toggle_publicity': '切换项目公开性失败。',
'project_does_not_exist': '项目不存在。',
'project_admin': '项目管理员', 'project_admin': '项目管理员',
'developer': '开发人员', 'developer': '开发人员',
'guest': '访客', 'guest': '访客',

View File

@ -24,7 +24,7 @@
<span ng-if="vm.toggle">// 'system_management' | tr //</span> <span ng-if="vm.toggle">// 'system_management' | tr //</span>
<a ng-if="!vm.toggle" href="#/destinations" class="title-color" ng-click="vm.toggleAdminOption({target: 'system_management'})">// 'system_management' | tr //</a> <a ng-if="!vm.toggle" href="#/destinations" class="title-color" ng-click="vm.toggleAdminOption({target: 'system_management'})">// 'system_management' | tr //</a>
</h4> </h4>
<list-user ng-if="vm.target === 'users'"></list-user> <list-user ng-if="vm.target === 'users'" auth-mode="{{ .AuthMode }}"></list-user>
<system-management ng-if="vm.target === 'system_management'"></system-management> <system-management ng-if="vm.target === 'system_management'"></system-management>
</div> </div>
</div> </div>

View File

@ -47,8 +47,8 @@
<th width="15%">// 'repositories' | tr //</th> <th width="15%">// 'repositories' | tr //</th>
<th width="15%" ng-if="!vm.isPublic">// 'role' | tr //</th> <th width="15%" ng-if="!vm.isPublic">// 'role' | tr //</th>
<th width="20%">// 'creation_time' | tr //</th> <th width="20%">// 'creation_time' | tr //</th>
<th width="15%">// 'publicity' | tr //</th> <th width="15%" ng-if="!vm.isPublic">// 'publicity' | tr //</th>
<th width="10%">// 'operation' | tr //</th> <th width="10%" ng-if="!vm.isPublic">// 'operation' | tr //</th>
</thead> </thead>
</table> </table>
</div> </div>
@ -63,8 +63,8 @@
<td width="15%">//p.repo_count//</td> <td width="15%">//p.repo_count//</td>
<td width="15%" ng-if="vm.isPublic === 0">//vm.getProjectRole(p.current_user_role_id) | tr//</td> <td width="15%" ng-if="vm.isPublic === 0">//vm.getProjectRole(p.current_user_role_id) | tr//</td>
<td width="20%">//p.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td> <td width="20%">//p.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="15%"><publicity-button is-public="p.public" project-id="p.project_id" role-id="//p.current_user_role_id//"></publicity-button></td> <td width="15%" ng-if="!vm.isPublic"><publicity-button is-public="p.public" project-id="p.project_id" role-id="//p.current_user_role_id//"></publicity-button></td>
<td width="10%"> <td width="10%" ng-if="!vm.isPublic">
&nbsp;&nbsp;<a ng-if="p.current_user_role_id == 1" href="javascript:void(0)" ng-click="vm.confirmToDelete(p.project_id, p.name)"><span class="glyphicon glyphicon-trash"></span></a> &nbsp;&nbsp;<a ng-if="p.current_user_role_id == 1" href="javascript:void(0)" ng-click="vm.confirmToDelete(p.project_id, p.name)"><span class="glyphicon glyphicon-trash"></span></a>
</td> </td>
</tr> </tr>

View File

@ -0,0 +1,7 @@
#!/bin/bash
echo "docker-compose version 1.7.1"
cd "$( dirname "${BASH_SOURCE[0]}" )"
cp ./docker-compose-Linux-x86_64 /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
tdnf install -y docker
systemctl enable docker.service
mkdir -p /var/log/harbor
echo "Downloading harbor..."
wget -O /ova.tar.gz http://10.117.5.62/ISV/appliancePackages/ova.tar.gz
echo "Downloading notice file..."
wget -O /NOTICE_Harbor_0.4.1_Beta.txt http://10.117.5.62/ISV/appliancePackages/NOTICE_Harbor_0.4.1_Beta.txt
echo "Downloading license file..."
wget -O /LICENSE_Harbor_0.4.1_Beta_100216.txt http://10.117.5.62/ISV/appliancePackages/LICENSE_Harbor_0.4.1_Beta_100216.txt

54
tools/ova/script/common.sh Executable file
View File

@ -0,0 +1,54 @@
#!/bin/bash
#Shut down Harbor
function down {
base_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
docker-compose -f $base_dir/../harbor/docker-compose*.yml down
}
#Start Harbor
function up {
base_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
$base_dir/start_harbor.sh
}
#Configure Harbor
function configure {
base_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
$base_dir/config.sh
}
#Garbage collectoin
function gc {
echo "======================= $(date)====================="
#the registry image
image=$1
base_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
docker run --name gc --rm --volume /data/registry:/storage \
--volume $base_dir/../harbor/common/config/registry/:/etc/registry/ \
$image garbage-collect /etc/registry/config.yml
echo "===================================================="
}
#Add rules to iptables
function addIptableRules {
iptables -A INPUT -p tcp --dport 5480 -j ACCEPT
iptables -A INPUT -p tcp --dport 5488 -j ACCEPT
iptables -A INPUT -p tcp --dport 5489 -j ACCEPT
}
#Install docker-compose
function installDockerCompose {
base_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
$base_dir/../deps/docker-compose-1.7.1/install.sh
}
#Load images
function load {
basedir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
docker load -i $basedir/../harbor/harbor*.tgz
}

88
tools/ova/script/config.sh Executable file
View File

@ -0,0 +1,88 @@
#!/bin/bash
set -e
attrs=(
harbor_admin_password
auth_mode
ldap_url
ldap_searchdn
ldap_search_pwd
ldap_basedn
ldap_uid
email_server
email_server_port
email_username
email_password
email_from
email_ssl
db_password
verify_remote_cert
)
base_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../" && pwd )"
#The location of harbor.cfg
cfg=$base_dir/harbor/harbor.cfg
#Format cert and key files
function format {
file=$1
head=$(sed -rn 's/(-+[A-Za-z ]*-+)([^-]*)(-+[A-Za-z ]*-+)/\1/p' $file)
body=$(sed -rn 's/(-+[A-Za-z ]*-+)([^-]*)(-+[A-Za-z ]*-+)/\2/p' $file)
tail=$(sed -rn 's/(-+[A-Za-z ]*-+)([^-]*)(-+[A-Za-z ]*-+)/\3/p' $file)
echo $head > $file
echo $body | sed 's/\s\+/\n/g' >> $file
echo $tail >> $file
}
#Modify hostname
ip=$(ip addr show eth0|grep "inet "|tr -s ' '|cut -d ' ' -f 3|cut -d '/' -f 1)
if [ -n "$ip" ]
then
echo "Read IP address: [ IP - $ip ]"
sed -i -r s/"hostname = .*"/"hostname = $ip"/ $cfg
else
echo "Failed to get the IP address"
exit 1
fi
#Handle http/https
protocal=http
echo "Read attribute using ovfenv: [ ssl_cert ]"
ssl_cert=$(ovfenv -k ssl_cert)
echo "Read attribute using ovfenv: [ ssl_cert_key ]"
ssl_cert_key=$(ovfenv -k ssl_cert_key)
if [ -n "$ssl_cert" ] && [ -n "$ssl_cert_key" ]
then
echo "ssl_cert and ssl_cert_key are set, using HTTPS protocal"
protocal=https
sed -i -r s%"#?ui_url_protocol = .*"%"ui_url_protocol = $protocal"% $cfg
mkdir -p /path/to
echo $ssl_cert > /path/to/server.crt
format /path/to/server.crt
echo $ssl_cert_key > /path/to/server.key
format /path/to/server.key
else
echo "ssl_cert and ssl_cert_key are not set, using HTTP protocal"
fi
for attr in "${attrs[@]}"
do
echo "Read attribute using ovfenv: [ $attr ]"
value=$(ovfenv -k $attr)
#ldap search password and email password can be null
if [ -n "$value" ] || [ "$attr" = "ldap_search_pwd" ] \
|| [ "$attr" = "email_password" ]
then
if [ "$attr" = ldap_search_pwd ] \
|| [ "$attr" = email_password ] \
|| [ "$attr" = db_password ] \
|| [ "$attr" = harbor_admin_password ]
then
bs=$(echo $value | base64)
#value={base64}$bs
fi
sed -i -r s%"#?$attr = .*"%"$attr = $value"% $cfg
fi
done

43
tools/ova/script/firstboot.sh Executable file
View File

@ -0,0 +1,43 @@
#!/bin/bash
set -e
echo "======================= $(date)====================="
export PATH=$PATH:/usr/local/bin
base_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source $base_dir/common.sh
#Reset root password
value=$(ovfenv -k root_pwd)
if [ -n "$value" ]
then
echo "Resetting root password..."
printf "$value\n$value\n" | passwd root
fi
#echo "Adding rules to iptables..."
#addIptableRules
echo "Installing docker compose..."
installDockerCompose
echo "Starting docker service..."
systemctl start docker
echo "Uncompress Harbor offline instaler tar..."
tar -zxvf $base_dir/../harbor-offline-installer*.tgz -C $base_dir/../
echo "Loading images..."
load
#Configure Harbor
echo "Configuring Harbor..."
chmod 600 $base_dir/../harbor/harbor.cfg
configure
#Start Harbor
echo "Starting Harbor..."
up
echo "===================================================="

View File

@ -0,0 +1,31 @@
#!/bin/bash
set -e
workdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $workdir/../harbor
echo "[Step 1]: preparing environment ..."
./prepare
echo "[Step 2]: starting Harbor ..."
docker-compose -f docker-compose*.yml up -d
protocol=http
hostname=reg.mydomain.com
if [[ $(cat ./harbor.cfg) =~ ui_url_protocol[[:blank:]]*=[[:blank:]]*(https?) ]]
then
protocol=${BASH_REMATCH[1]}
fi
if [[ $(grep 'hostname[[:blank:]]*=' ./harbor.cfg) =~ hostname[[:blank:]]*=[[:blank:]]*(.*) ]]
then
hostname=${BASH_REMATCH[1]}
fi
echo $"
----Harbor has been installed and started successfully.----
Now you should be able to visit the admin portal at ${protocol}://${hostname}.
For more details, please visit https://github.com/vmware/harbor .
"

View File

@ -0,0 +1,37 @@
#!/bin/bash
set -e
echo "======================= $(date)====================="
export PATH=$PATH:/usr/local/bin
base_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source $base_dir/common.sh
#echo "Adding rules to iptables..."
#addIptableRules
#Stop Harbor
echo "Shutting down Harbor..."
down
#Garbage collection
value=$(ovfenv -k gc_enabled)
if [ "$value" = "true" ]
then
echo "GC enabled, starting garbage collection..."
#If the registry contains no images, the gc will fail.
#So append a true to avoid failure.
gc registry:2.5.0 2>&1 >> /var/log/harbor/gc.log || true
else
echo "GC disabled, skip garbage collection"
fi
#Configure Harbor
echo "Configuring Harbor..."
configure
#Start Harbor
echo "Starting Harbor..."
up
echo "===================================================="