diff --git a/docs/user_guide.md b/docs/user_guide.md
index 084da17c3..b304399c7 100644
--- a/docs/user_guide.md
+++ b/docs/user_guide.md
@@ -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).
\ No newline at end of file
+About the details of GC, please see [GC](https://github.com/docker/docker.github.io/blob/master/registry/garbage-collection.md).
\ No newline at end of file
diff --git a/make/common/log/logrotate_docker.conf b/make/common/log/logrotate_docker.conf
deleted file mode 100644
index 6a953d4c3..000000000
--- a/make/common/log/logrotate_docker.conf
+++ /dev/null
@@ -1,7 +0,0 @@
-# Logrotate configuartion file for docker.
-
-/var/log/docker/*/*.log {
- rotate 100
- size 10M
- copytruncate
-}
diff --git a/make/common/log/rotate.sh b/make/common/log/rotate.sh
new file mode 100755
index 000000000..3f96df9bc
--- /dev/null
+++ b/make/common/log/rotate.sh
@@ -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."
diff --git a/make/photon/log/Dockerfile b/make/photon/log/Dockerfile
index 8ae03e95c..ea0759427 100644
--- a/make/photon/log/Dockerfile
+++ b/make/photon/log/Dockerfile
@@ -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
diff --git a/make/photon/log/logrotate.conf.photon b/make/photon/log/logrotate.conf.photon
deleted file mode 100644
index c28d55b62..000000000
--- a/make/photon/log/logrotate.conf.photon
+++ /dev/null
@@ -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.
diff --git a/make/ubuntu/log/Dockerfile b/make/ubuntu/log/Dockerfile
index f91da3469..3e1c7520e 100644
--- a/make/ubuntu/log/Dockerfile
+++ b/make/ubuntu/log/Dockerfile
@@ -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/
diff --git a/src/ui/api/harborapi_test.go b/src/ui/api/harborapi_test.go
index 5bfb200fb..3169a6ed9 100644
--- a/src/ui/api/harborapi_test.go
+++ b/src/ui/api/harborapi_test.go
@@ -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.
diff --git a/src/ui/api/log_test.go b/src/ui/api/log_test.go
index a7b027a75..bda4d9279 100644
--- a/src/ui/api/log_test.go
+++ b/src/ui/api/log_test.go
@@ -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) {
diff --git a/src/ui/api/member.go b/src/ui/api/member.go
index 63cdc7cde..b528872c5 100644
--- a/src/ui/api/member.go
+++ b/src/ui/api/member.go
@@ -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})
diff --git a/src/ui/api/member_test.go b/src/ui/api/member_test.go
index 4b6618e9b..8dbeae8fe 100644
--- a/src/ui/api/member_test.go
+++ b/src/ui/api/member_test.go
@@ -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")
}
/**
diff --git a/src/ui/api/project.go b/src/ui/api/project.go
index e8828e0e3..9905630ec 100644
--- a/src/ui/api/project.go
+++ b/src/ui/api/project.go
@@ -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)
diff --git a/src/ui/api/search_test.go b/src/ui/api/search_test.go
index 1eecd1009..ac05f74f2 100644
--- a/src/ui/api/search_test.go
+++ b/src/ui/api/search_test.go
@@ -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)
- //}
}
diff --git a/src/ui/api/statistic_test.go b/src/ui/api/statistic_test.go
index 17e2b9f5a..37400bea4 100644
--- a/src/ui/api/statistic_test.go
+++ b/src/ui/api/statistic_test.go
@@ -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)
diff --git a/src/ui/controllers/accountsetting.go b/src/ui/controllers/accountsetting.go
index 516547b98..612015455 100644
--- a/src/ui/controllers/accountsetting.go
+++ b/src/ui/controllers/accountsetting.go
@@ -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")
}
diff --git a/src/ui/controllers/adminoption.go b/src/ui/controllers/adminoption.go
index d0a10c1fa..69cc48828 100644
--- a/src/ui/controllers/adminoption.go
+++ b/src/ui/controllers/adminoption.go
@@ -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)
}
diff --git a/src/ui/controllers/base.go b/src/ui/controllers/base.go
index 5bbba0557..c8d2c7a39 100644
--- a/src/ui/controllers/base.go
+++ b/src/ui/controllers/base.go
@@ -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.
diff --git a/src/ui/controllers/changepassword.go b/src/ui/controllers/changepassword.go
index 842cbe2f2..0873db73b 100644
--- a/src/ui/controllers/changepassword.go
+++ b/src/ui/controllers/changepassword.go
@@ -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")
}
diff --git a/src/ui/controllers/controllers_test.go b/src/ui/controllers/controllers_test.go
index b4cb4fe2c..46e239be6 100644
--- a/src/ui/controllers/controllers_test.go
+++ b/src/ui/controllers/controllers_test.go
@@ -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), "
page_title_account_setting"), "http respond should have 'page_title_account_setting'")
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), "page_title_change_password"), "http respond should have 'page_title_change_password'")
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), "page_title_admin_option"), "http respond should have 'page_title_admin_option'")
+ assert.Equal(int(302), w.Code, "'/admin_option' httpStatusCode should be 302")
r, _ = http.NewRequest("GET", "/forgot_password", nil)
w = httptest.NewRecorder()
diff --git a/src/ui/controllers/optionalmenu.go b/src/ui/controllers/optionalmenu.go
index 96efcac68..fe0bb2b78 100644
--- a/src/ui/controllers/optionalmenu.go
+++ b/src/ui/controllers/optionalmenu.go
@@ -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
}
diff --git a/src/ui/static/resources/js/components/details/retrieve-projects.directive.js b/src/ui/static/resources/js/components/details/retrieve-projects.directive.js
index 3533f2c42..4101898a6 100644
--- a/src/ui/static/resources/js/components/details/retrieve-projects.directive.js
+++ b/src/ui/static/resources/js/components/details/retrieve-projects.directive.js
@@ -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;
});
diff --git a/src/ui/static/resources/js/components/repository/list-repository.directive.js b/src/ui/static/resources/js/components/repository/list-repository.directive.js
index 0a5fd627b..918f80eac 100644
--- a/src/ui/static/resources/js/components/repository/list-repository.directive.js
+++ b/src/ui/static/resources/js/components/repository/list-repository.directive.js
@@ -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() {
diff --git a/src/ui/static/resources/js/components/user-log/user-log.directive.html b/src/ui/static/resources/js/components/user-log/user-log.directive.html
index 4fd762d5e..9764fc7af 100644
--- a/src/ui/static/resources/js/components/user-log/user-log.directive.html
+++ b/src/ui/static/resources/js/components/user-log/user-log.directive.html
@@ -32,7 +32,7 @@
//t.username// |
- //t.repo_name// |
+ //t.repo_name// |
//t.repo_tag// |
//t.operation// |
//t.op_time | dateL : 'YYYY-MM-DD HH:mm:ss'// |
diff --git a/src/ui/static/resources/js/components/user-log/user-log.directive.js b/src/ui/static/resources/js/components/user-log/user-log.directive.js
index ceb86a778..0a462c466 100644
--- a/src/ui/static/resources/js/components/user-log/user-log.directive.js
+++ b/src/ui/static/resources/js/components/user-log/user-log.directive.js
@@ -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);
}
}
diff --git a/src/ui/static/resources/js/components/user/list-user.directive.html b/src/ui/static/resources/js/components/user/list-user.directive.html
index 31e540295..10147219e 100644
--- a/src/ui/static/resources/js/components/user/list-user.directive.html
+++ b/src/ui/static/resources/js/components/user/list-user.directive.html
@@ -31,7 +31,7 @@
// 'email' | tr // |
// 'registration_time' | tr // |
// 'administrator' | tr // |
- // 'operation' | tr // |
+ // 'operation' | tr // |
@@ -46,7 +46,7 @@
|
-
+ |
|
diff --git a/src/ui/static/resources/js/components/user/list-user.directive.js b/src/ui/static/resources/js/components/user/list-user.directive.js
index 18cd60a2c..77b1903db 100644
--- a/src/ui/static/resources/js/components/user/list-user.directive.js
+++ b/src/ui/static/resources/js/components/user/list-user.directive.js
@@ -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
diff --git a/src/ui/static/resources/js/services/i18n/locale_messages_en-US.js b/src/ui/static/resources/js/services/i18n/locale_messages_en-US.js
index 0ac5025c5..39de11118 100644
--- a/src/ui/static/resources/js/services/i18n/locale_messages_en-US.js
+++ b/src/ui/static/resources/js/services/i18n/locale_messages_en-US.js
@@ -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',
diff --git a/src/ui/static/resources/js/services/i18n/locale_messages_zh-CN.js b/src/ui/static/resources/js/services/i18n/locale_messages_zh-CN.js
index a896ba848..b57d3c42a 100644
--- a/src/ui/static/resources/js/services/i18n/locale_messages_zh-CN.js
+++ b/src/ui/static/resources/js/services/i18n/locale_messages_zh-CN.js
@@ -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': '访客',
diff --git a/src/ui/views/admin-options.htm b/src/ui/views/admin-options.htm
index 68c225e0b..8e57a8ff0 100644
--- a/src/ui/views/admin-options.htm
+++ b/src/ui/views/admin-options.htm
@@ -24,7 +24,7 @@
// 'system_management' | tr //
// 'system_management' | tr //
-
+
diff --git a/src/ui/views/project.htm b/src/ui/views/project.htm
index 4c0360068..7be0c4593 100644
--- a/src/ui/views/project.htm
+++ b/src/ui/views/project.htm
@@ -47,8 +47,8 @@
// 'repositories' | tr // |
// 'role' | tr // |
// 'creation_time' | tr // |
- // 'publicity' | tr // |
- // 'operation' | tr // |
+ // 'publicity' | tr // |
+ // 'operation' | tr // |
@@ -63,8 +63,8 @@
//p.repo_count// |
//vm.getProjectRole(p.current_user_role_id) | tr// |
//p.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'// |
- |
-
+ | |
+
|
diff --git a/tools/ova/deps/docker-compose-1.7.1/install.sh b/tools/ova/deps/docker-compose-1.7.1/install.sh
new file mode 100755
index 000000000..810b24630
--- /dev/null
+++ b/tools/ova/deps/docker-compose-1.7.1/install.sh
@@ -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
+
diff --git a/tools/ova/script/app_post_install.sh b/tools/ova/script/app_post_install.sh
new file mode 100755
index 000000000..aeda8239a
--- /dev/null
+++ b/tools/ova/script/app_post_install.sh
@@ -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
\ No newline at end of file
diff --git a/tools/ova/script/common.sh b/tools/ova/script/common.sh
new file mode 100755
index 000000000..88b268a78
--- /dev/null
+++ b/tools/ova/script/common.sh
@@ -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
+}
\ No newline at end of file
diff --git a/tools/ova/script/config.sh b/tools/ova/script/config.sh
new file mode 100755
index 000000000..43a238d59
--- /dev/null
+++ b/tools/ova/script/config.sh
@@ -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
\ No newline at end of file
diff --git a/tools/ova/script/firstboot.sh b/tools/ova/script/firstboot.sh
new file mode 100755
index 000000000..dde6da80e
--- /dev/null
+++ b/tools/ova/script/firstboot.sh
@@ -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 "===================================================="
\ No newline at end of file
diff --git a/tools/ova/script/start_harbor.sh b/tools/ova/script/start_harbor.sh
new file mode 100755
index 000000000..9dc967ede
--- /dev/null
+++ b/tools/ova/script/start_harbor.sh
@@ -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 .
+"
diff --git a/tools/ova/script/subsequentboot.sh b/tools/ova/script/subsequentboot.sh
new file mode 100755
index 000000000..ad09d8743
--- /dev/null
+++ b/tools/ova/script/subsequentboot.sh
@@ -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 "===================================================="
\ No newline at end of file