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
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.
![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.
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
# run logrotate hourly, disable imklog model, provides TCP/UDP syslog reception
RUN tdnf install -y cronie rsyslog logrotate shadow\
&& mv /etc/cron.daily/logrotate /etc/cron.hourly/ \
RUN tdnf install -y cronie rsyslog shadow tar gzip \
&& mkdir /etc/rsyslog.d/ \
&& mkdir /var/spool/rsyslog \
&& groupadd syslog \
@ -10,15 +9,13 @@ RUN tdnf install -y cronie rsyslog logrotate shadow\
ADD make/common/log/rsyslog.conf /etc/rsyslog.conf
COPY make/photon/log/logrotate.conf.photon /etc/logrotate.conf
# logrotate configuration file for docker
ADD make/common/log/logrotate_docker.conf /etc/logrotate.d/
# rotate logs weekly
# 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
ADD make/common/log/rsyslog_docker.conf /etc/rsyslog.d/
VOLUME /var/log/docker/
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
# run logrotate hourly, disable imklog model, provides TCP/UDP syslog reception
RUN mv /etc/cron.daily/logrotate /etc/cron.hourly/ \
&& rm /etc/rsyslog.d/* \
&& rm /etc/rsyslog.conf
RUN rm /etc/rsyslog.d/* && rm /etc/rsyslog.conf
ADD make/common/log/rsyslog.conf /etc/rsyslog.conf
# logrotate configuration file for docker
ADD make/common/log/logrotate_docker.conf /etc/logrotate.d/
# rotate logs weekly
# 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
ADD make/common/log/rsyslog_docker.conf /etc/rsyslog.d/

View File

@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http/httptest"
"path/filepath"
"runtime"
@ -90,6 +91,11 @@ func init() {
_ = updateInitPassword(1, "Harbor12345")
//syncRegistry
if err := SyncRegistry(); err != nil {
log.Fatalf("failed to sync repositories from registry: %v", err)
}
//Init user Info
admin = &usrInfo{adminName, adminPwd}
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.
//@param q Search parameter for project and repository name.
//@return []Search
//func (a testapi) SearchGet (q string) (apilib.Search, error) {
func (a testapi) SearchGet(q string) (apilib.Search, error) {
func (a testapi) SearchGet(q string, authInfo ...usrInfo) (int, apilib.Search, error) {
var httpCode int
var body []byte
var err error
_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})
_, 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)
err = json.Unmarshal(body, &successPayload)
return *successPayload, err
return httpCode, *successPayload, err
}
//Create a new project.

View File

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

View File

@ -16,13 +16,14 @@
package api
import (
"fmt"
"net/http"
"strconv"
"github.com/vmware/harbor/src/common/api"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/api"
)
// ProjectMemberAPI handles request to /api/projects/{}/members/{}
@ -98,6 +99,11 @@ func (pma *ProjectMemberAPI) Get() {
log.Errorf("Error occurred in GetUserProjectRoles, error: %v", err)
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
result := make(map[string]interface{})
user, err := dao.GetUser(models.User{UserID: pma.memberID})

View File

@ -4,9 +4,10 @@ import (
"fmt"
"testing"
"strconv"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/tests/apitests/apilib"
"strconv"
)
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")
}
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"
"regexp"
"github.com/vmware/harbor/src/common/api"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/api"
"strconv"
"time"
@ -192,7 +192,8 @@ func (p *ProjectAPI) Delete() {
if err := dao.AddAccessLog(models.AccessLog{
UserID: userID,
ProjectID: p.projectID,
RepoName: p.projectName,
RepoName: p.projectName + "/",
RepoTag: "N/A",
Operation: "delete",
}); err != nil {
log.Errorf("failed to add access log: %v", err)

View File

@ -14,19 +14,29 @@ func TestSearch(t *testing.T) {
apiTest := newHarborAPI()
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 {
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)
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(int64(1), result.Projects[0].Id, "Project id should be equal")
assert.Equal("library", result.Projects[0].Name, "Project name should be library")
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) {
if err := SyncRegistry(); err != nil {
t.Fatalf("failed to sync repositories from registry: %v", err)
}
fmt.Println("Testing Statistic API")
assert := assert.New(t)

View File

@ -1,9 +1,5 @@
package controllers
import (
"net/http"
)
// AccountSettingController handles request to /account_setting
type AccountSettingController struct {
BaseController
@ -11,8 +7,14 @@ type AccountSettingController struct {
// Get renders the account settings page
func (asc *AccountSettingController) Get() {
if asc.AuthMode != "db_auth" {
asc.CustomAbort(http.StatusForbidden, "")
var isAdminForLdap bool
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
import (
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/utils/log"
)
// AdminOptionController handles requests to /admin_option
type AdminOptionController struct {
BaseController
@ -7,5 +12,16 @@ type AdminOptionController struct {
// Get renders the admin options page
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/beego/i18n"
"github.com/vmware/harbor/src/ui/auth"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"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.

View File

@ -1,18 +1,20 @@
package controllers
import (
"net/http"
)
// ChangePasswordController handles request to /change_password
type ChangePasswordController struct {
BaseController
}
// Get renders the change password page
func (asc *ChangePasswordController) Get() {
if asc.AuthMode != "db_auth" {
asc.CustomAbort(http.StatusForbidden, "")
func (cpc *ChangePasswordController) Get() {
var isAdminForLdap bool
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()
beego.BeeApp.Handlers.ServeHTTP(w, r)
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)
w = httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
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)
w = httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
assert.Equal(int(200), w.Code, "'/admin_option' httpStatusCode should be 200")
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>'")
assert.Equal(int(302), w.Code, "'/admin_option' httpStatusCode should be 302")
r, _ = http.NewRequest("GET", "/forgot_password", nil)
w = httptest.NewRecorder()

View File

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

View File

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

View File

@ -19,9 +19,9 @@
.module('harbor.repository')
.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;
@ -104,7 +104,23 @@
}
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() {

View File

@ -32,7 +32,7 @@
</tr>
<tr ng-if="vm.integratedLogs.length > 0" ng-repeat="t in vm.integratedLogs">
<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="14%">//t.operation//</td>
<td width="25%">//t.op_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>

View File

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

View File

@ -31,7 +31,7 @@
<th width="20%">// 'email' | tr //</th>
<th width="35%">// 'registration_time' | 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>
</table>
</div>
@ -46,7 +46,7 @@
<td width="15%">
<toggle-admin current-user="vm.currentUser" has-admin-role="u.has_admin_role" user-id="//u.user_id//"></toggle-admin>
</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>
</td>
</tr>

View File

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

View File

@ -233,6 +233,7 @@ var locale_messages = {
'failed_to_get_project_member': 'Failed to get current project member.',
'failed_to_delete_repo': 'Failed to delete repository. ',
'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_log': 'Failed to get logs.',
'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_toggle_publicity_insuffient_permissions': 'Failed to toggle project publicity, insuffient permissions.',
'failed_to_toggle_publicity': 'Failed to toggle project publicity.',
'project_does_not_exist': 'Project does not exist.',
'project_admin': 'Project Admin',
'developer': 'Developer',
'guest': 'Guest',

View File

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

View File

@ -24,7 +24,7 @@
<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>
</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>
</div>
</div>

View File

@ -47,8 +47,8 @@
<th width="15%">// 'repositories' | tr //</th>
<th width="15%" ng-if="!vm.isPublic">// 'role' | tr //</th>
<th width="20%">// 'creation_time' | tr //</th>
<th width="15%">// 'publicity' | tr //</th>
<th width="10%">// 'operation' | tr //</th>
<th width="15%" ng-if="!vm.isPublic">// 'publicity' | tr //</th>
<th width="10%" ng-if="!vm.isPublic">// 'operation' | tr //</th>
</thead>
</table>
</div>
@ -63,8 +63,8 @@
<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="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="10%">
<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%" 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>
</td>
</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 "===================================================="