mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-14 03:31:27 +01:00
Merge branch 'goharbor:master' into fix-basedn-empty
This commit is contained in:
commit
2ce814329b
20
.github/workflows/CI.yml
vendored
20
.github/workflows/CI.yml
vendored
@ -29,10 +29,10 @@ jobs:
|
||||
- ubuntu-latest
|
||||
timeout-minutes: 100
|
||||
steps:
|
||||
- name: Set up Go 1.15
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.15.12
|
||||
go-version: 1.16.5
|
||||
id: go
|
||||
- name: setup Docker
|
||||
uses: docker-practice/actions-setup-docker@0.0.1
|
||||
@ -95,10 +95,10 @@ jobs:
|
||||
- ubuntu-latest
|
||||
timeout-minutes: 100
|
||||
steps:
|
||||
- name: Set up Go 1.15
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.15.12
|
||||
go-version: 1.16.5
|
||||
id: go
|
||||
- name: setup Docker
|
||||
uses: docker-practice/actions-setup-docker@0.0.1
|
||||
@ -155,10 +155,10 @@ jobs:
|
||||
- ubuntu-latest
|
||||
timeout-minutes: 100
|
||||
steps:
|
||||
- name: Set up Go 1.15
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.15.12
|
||||
go-version: 1.16.5
|
||||
id: go
|
||||
- name: setup Docker
|
||||
uses: docker-practice/actions-setup-docker@0.0.1
|
||||
@ -215,10 +215,10 @@ jobs:
|
||||
- ubuntu-latest
|
||||
timeout-minutes: 100
|
||||
steps:
|
||||
- name: Set up Go 1.15
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.15.12
|
||||
go-version: 1.16.5
|
||||
id: go
|
||||
- name: setup Docker
|
||||
uses: docker-practice/actions-setup-docker@0.0.1
|
||||
@ -273,10 +273,10 @@ jobs:
|
||||
- ubuntu-latest
|
||||
timeout-minutes: 100
|
||||
steps:
|
||||
- name: Set up Go 1.15
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.15.12
|
||||
go-version: 1.16.5
|
||||
id: go
|
||||
- name: setup Docker
|
||||
uses: docker-practice/actions-setup-docker@0.0.1
|
||||
|
4
.github/workflows/build-package.yml
vendored
4
.github/workflows/build-package.yml
vendored
@ -26,10 +26,10 @@ jobs:
|
||||
service_account_key: ${{ secrets.GCP_SA_KEY }}
|
||||
export_default_credentials: true
|
||||
- run: gcloud info
|
||||
- name: Set up Go 1.15
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.15.12
|
||||
go-version: 1.16.5
|
||||
id: go
|
||||
- name: setup Docker
|
||||
uses: docker-practice/actions-setup-docker@0.0.1
|
||||
|
4
.github/workflows/conformance_test.yml
vendored
4
.github/workflows/conformance_test.yml
vendored
@ -26,10 +26,10 @@ jobs:
|
||||
service_account_key: ${{ secrets.GCP_SA_KEY }}
|
||||
export_default_credentials: true
|
||||
- run: gcloud info
|
||||
- name: Set up Go 1.15
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.15.12
|
||||
go-version: 1.16.5
|
||||
id: go
|
||||
- name: setup Docker
|
||||
uses: docker-practice/actions-setup-docker@0.0.1
|
||||
|
@ -158,9 +158,10 @@ Harbor backend is written in [Go](http://golang.org/). If you don't have a Harbo
|
||||
| 1.9 | 1.12.12 |
|
||||
| 1.10 | 1.12.12 |
|
||||
| 2.0 | 1.13.15 |
|
||||
| 2.1 | 1.14.13 |
|
||||
| 2.1 | 1.14.13 |
|
||||
| 2.2 | 1.15.6 |
|
||||
| 2.3 | 1.15.12 |
|
||||
| 2.3 | 1.15.12 |
|
||||
| 2.4 | 1.16.5 |
|
||||
|
||||
Ensure your GOPATH and PATH have been configured in accordance with the Go environment instructions.
|
||||
|
||||
|
4
Makefile
4
Makefile
@ -9,7 +9,7 @@
|
||||
# compile_golangimage:
|
||||
# compile from golang image
|
||||
# for example: make compile_golangimage -e GOBUILDIMAGE= \
|
||||
# golang:1.15.12
|
||||
# golang:1.16.5
|
||||
# compile_core, compile_jobservice: compile specific binary
|
||||
#
|
||||
# build: build Harbor docker images from photon baseimage
|
||||
@ -156,7 +156,7 @@ GOINSTALL=$(GOCMD) install
|
||||
GOTEST=$(GOCMD) test
|
||||
GODEP=$(GOTEST) -i
|
||||
GOFMT=gofmt -w
|
||||
GOBUILDIMAGE=golang:1.15.12
|
||||
GOBUILDIMAGE=golang:1.16.5
|
||||
GOBUILDPATHINCONTAINER=/harbor
|
||||
|
||||
# go build
|
||||
|
@ -2697,6 +2697,11 @@ paths:
|
||||
- usergroup
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- name: ldap_group_dn
|
||||
in: query
|
||||
type: string
|
||||
required: false
|
||||
description: search with ldap group DN
|
||||
responses:
|
||||
'200':
|
||||
description: Get user group successfully.
|
||||
@ -8272,27 +8277,33 @@ definitions:
|
||||
properties:
|
||||
value:
|
||||
type: string
|
||||
x-omitempty: false
|
||||
description: The string value of current config item
|
||||
editable:
|
||||
type: boolean
|
||||
x-omitempty: false
|
||||
description: The configure item can be updated or not
|
||||
BoolConfigItem:
|
||||
type: object
|
||||
properties:
|
||||
value:
|
||||
type: boolean
|
||||
x-omitempty: false
|
||||
description: The boolean value of current config item
|
||||
editable:
|
||||
type: boolean
|
||||
x-omitempty: false
|
||||
description: The configure item can be updated or not
|
||||
IntegerConfigItem:
|
||||
type: object
|
||||
properties:
|
||||
value:
|
||||
type: integer
|
||||
x-omitempty: false
|
||||
description: The integer value of current config item
|
||||
editable:
|
||||
type: boolean
|
||||
x-omitempty: false
|
||||
description: The configure item can be updated or not
|
||||
ProjectMemberEntity:
|
||||
type: object
|
||||
|
@ -4,7 +4,7 @@ set +e
|
||||
|
||||
usage(){
|
||||
echo "Usage: builder <golang image:version> <code path> <code release tag> <main.go path> <binary name>"
|
||||
echo "e.g: builder golang:1.15.12 github.com/helm/chartmuseum v0.12.0 cmd/chartmuseum chartm"
|
||||
echo "e.g: builder golang:1.16.5 github.com/helm/chartmuseum v0.12.0 cmd/chartmuseum chartm"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM golang:1.15.12
|
||||
FROM golang:1.14.15
|
||||
|
||||
ARG NOTARY_VERSION
|
||||
ARG MIGRATE_VERSION
|
||||
|
@ -1,7 +1,8 @@
|
||||
FROM golang:1.15.12
|
||||
FROM golang:1.16.5
|
||||
|
||||
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
|
||||
ENV BUILDTAGS include_oss include_gcs
|
||||
ENV GO111MODULE auto
|
||||
|
||||
WORKDIR $DISTRIBUTION_DIR
|
||||
COPY . $DISTRIBUTION_DIR
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM golang:1.15.12
|
||||
FROM golang:1.16.5
|
||||
|
||||
ADD . /go/src/github.com/aquasecurity/harbor-scanner-trivy/
|
||||
WORKDIR /go/src/github.com/aquasecurity/harbor-scanner-trivy/
|
||||
|
@ -19,7 +19,7 @@ TEMP=$(mktemp -d ${TMPDIR-/tmp}/trivy-adapter.XXXXXX)
|
||||
git clone https://github.com/aquasecurity/harbor-scanner-trivy.git $TEMP
|
||||
cd $TEMP; git checkout $VERSION; cd -
|
||||
|
||||
echo "Building Trivy adapter binary based on golang:1.15.12..."
|
||||
echo "Building Trivy adapter binary based on golang:1.16.5..."
|
||||
cp Dockerfile.binary $TEMP
|
||||
docker build -f $TEMP/Dockerfile.binary -t trivy-adapter-golang $TEMP
|
||||
|
||||
|
@ -1,40 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CreateOrUpdateJobLog ...
|
||||
func CreateOrUpdateJobLog(log *models.JobLog) (int64, error) {
|
||||
o := GetOrmer()
|
||||
count, err := o.InsertOrUpdate(log, "job_uuid")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// GetJobLog ...
|
||||
func GetJobLog(uuid string) (*models.JobLog, error) {
|
||||
o := GetOrmer()
|
||||
jl := models.JobLog{UUID: uuid}
|
||||
err := o.Read(&jl, "UUID")
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
return &jl, nil
|
||||
}
|
||||
|
||||
// DeleteJobLogsBefore ...
|
||||
func DeleteJobLogsBefore(t time.Time) (int64, error) {
|
||||
o := GetOrmer()
|
||||
sql := `delete from job_log where creation_time < ?`
|
||||
res, err := o.Raw(sql, t).Exec()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.RowsAffected()
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMethodsOfJobLog(t *testing.T) {
|
||||
uuid := "uuid_for_unit_test"
|
||||
now := time.Now()
|
||||
content := "content for unit text"
|
||||
jobLog := &models.JobLog{
|
||||
UUID: uuid,
|
||||
CreationTime: now,
|
||||
Content: content,
|
||||
}
|
||||
|
||||
// create
|
||||
_, err := CreateOrUpdateJobLog(jobLog)
|
||||
require.Nil(t, err)
|
||||
|
||||
// update
|
||||
updateContent := "content for unit text update"
|
||||
jobLog.Content = updateContent
|
||||
_, err = CreateOrUpdateJobLog(jobLog)
|
||||
require.Nil(t, err)
|
||||
|
||||
// get
|
||||
log, err := GetJobLog(uuid)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, now.Second(), log.CreationTime.Second())
|
||||
assert.Equal(t, updateContent, log.Content)
|
||||
assert.Equal(t, jobLog.LogID, log.LogID)
|
||||
|
||||
// delete
|
||||
count, err := DeleteJobLogsBefore(time.Now().Add(time.Duration(time.Minute)))
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, int64(1), count)
|
||||
}
|
@ -23,7 +23,6 @@ func init() {
|
||||
new(User),
|
||||
new(Role),
|
||||
new(ResourceLabel),
|
||||
new(JobLog),
|
||||
new(OIDCUser),
|
||||
)
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package error
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrDupProject is the error returned when creating a duplicate project
|
||||
var ErrDupProject = errors.New("duplicate project")
|
@ -1,94 +0,0 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Link : HTTP link header
|
||||
type Link struct {
|
||||
// URL : url part of header
|
||||
URL string
|
||||
// Rel : prev or next
|
||||
Rel string
|
||||
}
|
||||
|
||||
// Links : multiple link
|
||||
type Links []*Link
|
||||
|
||||
// Prev returns the URL indicated by "prev" rel.
|
||||
func (l Links) Prev() string {
|
||||
prev := ""
|
||||
for _, link := range l {
|
||||
if strings.ToLower(link.Rel) == "prev" {
|
||||
prev = link.URL
|
||||
break
|
||||
}
|
||||
}
|
||||
return prev
|
||||
}
|
||||
|
||||
// Next returns the URL indicated by "next" rel.
|
||||
func (l Links) Next() string {
|
||||
next := ""
|
||||
for _, link := range l {
|
||||
if link.Rel == "next" {
|
||||
next = link.URL
|
||||
break
|
||||
}
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
// ParseLink parses the raw link header to Links
|
||||
func ParseLink(raw string) Links {
|
||||
links := Links{}
|
||||
|
||||
for _, l := range strings.Split(raw, ",") {
|
||||
link := parseSingleLink(l)
|
||||
if link != nil {
|
||||
links = append(links, link)
|
||||
}
|
||||
}
|
||||
|
||||
return links
|
||||
}
|
||||
|
||||
func parseSingleLink(raw string) *Link {
|
||||
link := &Link{}
|
||||
|
||||
for _, str := range strings.Split(raw, ";") {
|
||||
str = strings.TrimSpace(str)
|
||||
if strings.HasPrefix(str, "<") && strings.HasSuffix(str, ">") {
|
||||
str = strings.Trim(str, "<>")
|
||||
link.URL = str
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(str, "=", 2)
|
||||
if len(parts) != 2 || strings.ToLower(parts[0]) != "rel" {
|
||||
continue
|
||||
}
|
||||
|
||||
link.Rel = strings.ToLower(strings.Trim(parts[1], "\""))
|
||||
}
|
||||
|
||||
if len(link.URL) == 0 || len(link.Rel) == 0 {
|
||||
link = nil
|
||||
}
|
||||
|
||||
return link
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
scanAllMarker *TimeMarker
|
||||
scanOverviewMarker = &TimeMarker{
|
||||
interval: 15 * time.Second,
|
||||
}
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// TimeMarker is used to control an action not to be taken frequently within the interval
|
||||
type TimeMarker struct {
|
||||
sync.RWMutex
|
||||
next time.Time
|
||||
interval time.Duration
|
||||
}
|
||||
|
||||
// Mark tries to mark a future time, which is after the duration of interval from the time it's called.
|
||||
func (t *TimeMarker) Mark() {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
t.next = time.Now().Add(t.interval)
|
||||
}
|
||||
|
||||
// Check returns true if the current time is after the mark by this marker, and the caction the mark guards and be taken.
|
||||
func (t *TimeMarker) Check() bool {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return time.Now().After(t.next)
|
||||
}
|
||||
|
||||
// Next returns the time of the next mark.
|
||||
func (t *TimeMarker) Next() time.Time {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.next
|
||||
}
|
||||
|
||||
// ScanOverviewMarker ...
|
||||
func ScanOverviewMarker() *TimeMarker {
|
||||
return scanOverviewMarker
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTimeMarker(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
m := &TimeMarker{
|
||||
interval: 1 * time.Second,
|
||||
}
|
||||
r1 := m.Check()
|
||||
assert.True(r1)
|
||||
m.Mark()
|
||||
r2 := m.Check()
|
||||
assert.False(r2)
|
||||
t.Log("Sleep for 2 seconds...")
|
||||
time.Sleep(2 * time.Second)
|
||||
r3 := m.Check()
|
||||
assert.True(r3)
|
||||
}
|
@ -209,16 +209,6 @@ func SafeCastFloat64(value interface{}) float64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// ParseOfftime ...
|
||||
func ParseOfftime(offtime int64) (hour, minite, second int) {
|
||||
offtime = offtime % (3600 * 24)
|
||||
hour = int(offtime / 3600)
|
||||
offtime = offtime % 3600
|
||||
minite = int(offtime / 60)
|
||||
second = int(offtime % 60)
|
||||
return
|
||||
}
|
||||
|
||||
// TrimLower ...
|
||||
func TrimLower(str string) string {
|
||||
return strings.TrimSpace(strings.ToLower(str))
|
||||
@ -268,11 +258,6 @@ func IsContainIllegalChar(s string, illegalChar []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDigest A sha256 is a string with 64 characters.
|
||||
func IsDigest(ref string) bool {
|
||||
return strings.HasPrefix(ref, "sha256:") && len(ref) == 71
|
||||
}
|
||||
|
||||
// ParseJSONInt ...
|
||||
func ParseJSONInt(value interface{}) (int, bool) {
|
||||
switch value.(type) {
|
||||
@ -295,13 +280,3 @@ func FindNamedMatches(regex *regexp.Regexp, str string) map[string]string {
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// ParamPlaceholderForIn returns a string that contains placeholders for sql keyword "in"
|
||||
// e.g. n=3, returns "?,?,?"
|
||||
func ParamPlaceholderForIn(n int) string {
|
||||
placeholders := []string{}
|
||||
for i := 0; i < n; i++ {
|
||||
placeholders = append(placeholders, "?")
|
||||
}
|
||||
return strings.Join(placeholders, ",")
|
||||
}
|
||||
|
@ -160,43 +160,6 @@ func TestGenerateRandomStringWithLen(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLink(t *testing.T) {
|
||||
raw := ""
|
||||
links := ParseLink(raw)
|
||||
if len(links) != 0 {
|
||||
t.Errorf("unexpected length: %d != %d", len(links), 0)
|
||||
}
|
||||
raw = "a;b,c"
|
||||
links = ParseLink(raw)
|
||||
if len(links) != 0 {
|
||||
t.Errorf("unexpected length: %d != %d", len(links), 0)
|
||||
}
|
||||
|
||||
raw = `</api/users?page=1&page_size=100>; rel="prev"`
|
||||
links = ParseLink(raw)
|
||||
if len(links) != 1 {
|
||||
t.Errorf("unexpected length: %d != %d", len(links), 1)
|
||||
}
|
||||
prev := `/api/users?page=1&page_size=100`
|
||||
if links.Prev() != prev {
|
||||
t.Errorf("unexpected prev: %s != %s", links.Prev(), prev)
|
||||
}
|
||||
|
||||
raw = `</api/users?page=1&page_size=100>; rel="prev", </api/users?page=3&page_size=100>; rel="next"`
|
||||
links = ParseLink(raw)
|
||||
if len(links) != 2 {
|
||||
t.Errorf("unexpected length: %d != %d", len(links), 2)
|
||||
}
|
||||
prev = `/api/users?page=1&page_size=100`
|
||||
if links.Prev() != prev {
|
||||
t.Errorf("unexpected prev: %s != %s", links.Prev(), prev)
|
||||
}
|
||||
next := `/api/users?page=3&page_size=100`
|
||||
if links.Next() != next {
|
||||
t.Errorf("unexpected prev: %s != %s", links.Next(), next)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestTCPConn(t *testing.T) {
|
||||
server := httptest.NewServer(nil)
|
||||
defer server.Close()
|
||||
@ -354,29 +317,6 @@ func TestSafeCastFloat64(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseOfftime(t *testing.T) {
|
||||
cases := []struct {
|
||||
offtime int64
|
||||
hour int
|
||||
minite int
|
||||
second int
|
||||
}{
|
||||
{0, 0, 0, 0},
|
||||
{1, 0, 0, 1},
|
||||
{60, 0, 1, 0},
|
||||
{3600, 1, 0, 0},
|
||||
{3661, 1, 1, 1},
|
||||
{3600*24 + 60, 0, 1, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
h, m, s := ParseOfftime(c.offtime)
|
||||
assert.Equal(t, c.hour, h)
|
||||
assert.Equal(t, c.minite, m)
|
||||
assert.Equal(t, c.second, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrimLower(t *testing.T) {
|
||||
type args struct {
|
||||
str string
|
||||
@ -426,9 +366,3 @@ func TestGetStrValueOfAnyType(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDigest(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.False(IsDigest("latest"))
|
||||
assert.True(IsDigest("sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a7"))
|
||||
}
|
||||
|
@ -1,24 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const nameComponent = `[a-z0-9]+((?:[._]|__|[-]*)[a-z0-9]+)*`
|
||||
|
||||
// TagRegexp is regular expression to match image tags, for example, 'v1.0'
|
||||
var TagRegexp = regexp.MustCompile(`^[\w][\w.-]{0,127}$`)
|
||||
|
||||
// RepoRegexp is regular expression to match repo name, for example, 'busybox', 'stage/busybox'
|
||||
var RepoRegexp = regexp.MustCompile(fmt.Sprintf("^%s(/%s)*$", nameComponent, nameComponent))
|
||||
|
||||
// ValidateTag validates whether a tag is valid.
|
||||
func ValidateTag(tag string) bool {
|
||||
return TagRegexp.MatchString(tag)
|
||||
}
|
||||
|
||||
// ValidateRepo validates whether a repo name is valid.
|
||||
func ValidateRepo(repo string) bool {
|
||||
return RepoRegexp.MatchString(repo)
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateTag(t *testing.T) {
|
||||
cases := []struct {
|
||||
tag string
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
"v1.0",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"1.0.0",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"v1.0-alpha.0",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"1__",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"__v1.0",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"_...",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"_-_",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"--v1.0",
|
||||
false,
|
||||
},
|
||||
{
|
||||
".0.1",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"-0.1",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"0.1.*",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"0.1.?",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
if c.valid {
|
||||
assert.True(t, ValidateTag(c.tag))
|
||||
} else {
|
||||
assert.False(t, ValidateTag(c.tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRepo(t *testing.T) {
|
||||
cases := []struct {
|
||||
repo string
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
"a",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"a_a",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"a__a",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"a-a",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"a--a",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"a---a",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"a.a",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"a/b.b",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"a_a/b-b",
|
||||
true,
|
||||
},
|
||||
{
|
||||
".a",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"_a",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"-a",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"a.",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"a_",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"a-",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"a..a",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"a___a",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"a.-a",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"a_-a",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"a*",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"A/_a",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"A/.a",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Aaaa",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"aaaA",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
if c.valid {
|
||||
assert.True(t, ValidateRepo(c.repo))
|
||||
} else {
|
||||
assert.False(t, ValidateRepo(c.repo))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
module github.com/goharbor/harbor/src
|
||||
|
||||
go 1.15
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go v37.2.0+incompatible // indirect
|
||||
|
@ -15,6 +15,7 @@
|
||||
package gc
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@ -270,9 +271,13 @@ func (gc *GarbageCollector) sweep(ctx job.Context) error {
|
||||
}
|
||||
// for manifest, it has to delete the revisions folder of each repository
|
||||
gc.logger.Infof("delete manifest from storage: %s", blob.Digest)
|
||||
if err := ignoreNotFound(func() error {
|
||||
return gc.registryCtlClient.DeleteManifest(art.RepositoryName, blob.Digest)
|
||||
}); err != nil {
|
||||
if err := lib.RetryUntil(func() error {
|
||||
return ignoreNotFound(func() error {
|
||||
return gc.registryCtlClient.DeleteManifest(art.RepositoryName, blob.Digest)
|
||||
})
|
||||
}, lib.RetryCallback(func(err error, sleep time.Duration) {
|
||||
gc.logger.Infof("failed to exec DeleteManifest retry after %s : %v", sleep, err)
|
||||
})); err != nil {
|
||||
if err := ignoreNotFound(func() error {
|
||||
return gc.markDeleteFailed(ctx, blob)
|
||||
}); err != nil {
|
||||
@ -294,9 +299,13 @@ func (gc *GarbageCollector) sweep(ctx job.Context) error {
|
||||
// for the foreign layer, as it's not stored in the storage, no need to call the delete api and count size, but still have to delete the DB record.
|
||||
if !blob.IsForeignLayer() {
|
||||
gc.logger.Infof("delete blob from storage: %s", blob.Digest)
|
||||
if err := ignoreNotFound(func() error {
|
||||
return gc.registryCtlClient.DeleteBlob(blob.Digest)
|
||||
}); err != nil {
|
||||
if err := lib.RetryUntil(func() error {
|
||||
return ignoreNotFound(func() error {
|
||||
return gc.registryCtlClient.DeleteBlob(blob.Digest)
|
||||
})
|
||||
}, lib.RetryCallback(func(err error, sleep time.Duration) {
|
||||
gc.logger.Infof("failed to exec DeleteBlob retry after %s : %v", sleep, err)
|
||||
})); err != nil {
|
||||
if err := ignoreNotFound(func() error {
|
||||
return gc.markDeleteFailed(ctx, blob)
|
||||
}); err != nil {
|
||||
|
@ -2,9 +2,11 @@ package gc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/pkg/registry"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"time"
|
||||
)
|
||||
|
||||
// delKeys ...
|
||||
@ -50,10 +52,11 @@ func v2DeleteManifest(repository, digest string) error {
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
if err := registry.Cli.DeleteManifest(repository, digest); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return lib.RetryUntil(func() error {
|
||||
return registry.Cli.DeleteManifest(repository, digest)
|
||||
}, lib.RetryCallback(func(err error, sleep time.Duration) {
|
||||
fmt.Printf("failed to exec DeleteManifest retry after %s : %v\n", sleep, err)
|
||||
}))
|
||||
}
|
||||
|
||||
// ignoreNotFound ignores the NotFoundErr error
|
||||
|
@ -3,9 +3,10 @@ package backend
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/joblog"
|
||||
"github.com/goharbor/harbor/src/pkg/joblog/models"
|
||||
)
|
||||
|
||||
// DBLogger is an implementation of logger.Interface.
|
||||
@ -47,7 +48,7 @@ func (dbl *DBLogger) Close() error {
|
||||
Content: dbl.buffer.String(),
|
||||
}
|
||||
|
||||
_, err = dao.CreateOrUpdateJobLog(&jobLog)
|
||||
_, err = joblog.Mgr.Create(orm.Context(), &jobLog)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -3,8 +3,9 @@ package getter
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/joblog"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/jobservice/errs"
|
||||
)
|
||||
|
||||
@ -23,7 +24,7 @@ func (dbg *DBGetter) Retrieve(logID string) ([]byte, error) {
|
||||
return nil, errors.New("empty log identify")
|
||||
}
|
||||
|
||||
jobLog, err := dao.GetJobLog(logID)
|
||||
jobLog, err := joblog.Mgr.Get(orm.Context(), logID)
|
||||
if err != nil {
|
||||
// Other errors have been ignored by GetJobLog()
|
||||
return nil, errs.NoObjectFoundError(fmt.Sprintf("log entity: %s", logID))
|
||||
|
@ -2,7 +2,8 @@ package sweeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/joblog"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -28,7 +29,7 @@ func (dbs *DBSweeper) Sweep() (int, error) {
|
||||
|
||||
// Start to sweep logs
|
||||
before := time.Now().Add(time.Duration(dbs.duration) * oneDay * -1)
|
||||
count, err := dao.DeleteJobLogsBefore(before)
|
||||
count, err := joblog.Mgr.DeleteBefore(orm.Context(), before)
|
||||
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("sweep logs in DB failed before %s with error: %s", before, err)
|
||||
|
@ -19,7 +19,7 @@ const (
|
||||
|
||||
var (
|
||||
// V2ManifestURLRe is the regular expression for matching request v2 handler to view/delete manifest
|
||||
V2ManifestURLRe = regexp.MustCompile(fmt.Sprintf(`^/v2/(?P<%s>%s)/manifests/(?P<%s>%s|%s)$`, RepositorySubexp, reference.NameRegexp.String(), ReferenceSubexp, reference.TagRegexp.String(), digest.DigestRegexp.String()))
|
||||
V2ManifestURLRe = regexp.MustCompile(fmt.Sprintf(`^/v2/(?P<%s>%s)/manifests/(?P<%s>.*)$`, RepositorySubexp, reference.NameRegexp.String(), ReferenceSubexp))
|
||||
// V2TagListURLRe is the regular expression for matching request to v2 handler to list tags
|
||||
V2TagListURLRe = regexp.MustCompile(fmt.Sprintf(`^/v2/(?P<%s>%s)/tags/list`, RepositorySubexp, reference.NameRegexp.String()))
|
||||
// V2BlobURLRe is the regular expression for matching request to v2 handler to retrieve head/delete a blob
|
||||
|
@ -21,12 +21,18 @@ import (
|
||||
)
|
||||
|
||||
func TestMatchManifestURLPattern(t *testing.T) {
|
||||
_, _, ok := MatchManifestURLPattern("")
|
||||
assert.False(t, ok)
|
||||
_, _, ok := MatchManifestURLPattern("/v2/library/hello-world/manifests/.Invalid")
|
||||
assert.True(t, ok)
|
||||
|
||||
_, _, ok = MatchManifestURLPattern("/v2/")
|
||||
assert.False(t, ok)
|
||||
|
||||
_, _, ok = MatchManifestURLPattern("/v2/library/hello-world/manifests//")
|
||||
assert.True(t, ok)
|
||||
|
||||
_, _, ok = MatchManifestURLPattern("/v2/library/hello-world/manifests/###")
|
||||
assert.True(t, ok)
|
||||
|
||||
repository, reference, ok := MatchManifestURLPattern("/v2/library/hello-world/manifests/latest")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "library/hello-world", repository)
|
||||
|
69
src/pkg/joblog/dao/dao.go
Normal file
69
src/pkg/joblog/dao/dao.go
Normal file
@ -0,0 +1,69 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/joblog/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DAO is the data access object for job log
|
||||
type DAO interface {
|
||||
// Create the job log
|
||||
Create(ctx context.Context, jobLog *models.JobLog) (id int64, err error)
|
||||
// Get the job log specified by UUID
|
||||
Get(ctx context.Context, uuid string) (jobLog *models.JobLog, err error)
|
||||
// DeleteBefore the job log specified by time
|
||||
DeleteBefore(ctx context.Context, t time.Time) (id int64, err error)
|
||||
}
|
||||
|
||||
// New returns an instance of the default DAO
|
||||
func New() DAO {
|
||||
return &dao{}
|
||||
}
|
||||
|
||||
type dao struct{}
|
||||
|
||||
// Create ...
|
||||
func (d *dao) Create(ctx context.Context, jobLog *models.JobLog) (int64, error) {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count, err := ormer.InsertOrUpdate(jobLog, "job_uuid")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// Get ...
|
||||
func (d *dao) Get(ctx context.Context, uuid string) (jobLog *models.JobLog, err error) {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jl := models.JobLog{UUID: uuid}
|
||||
err = ormer.Read(&jl, "UUID")
|
||||
if e := orm.AsNotFoundError(err, "no job log founded"); e != nil {
|
||||
log.Warningf("no job log founded. Query condition, uuid: %s, err: %v", uuid, e)
|
||||
return nil, err
|
||||
}
|
||||
return &jl, nil
|
||||
}
|
||||
|
||||
// DeleteBefore ...
|
||||
func (d *dao) DeleteBefore(ctx context.Context, t time.Time) (id int64, err error) {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sql := `delete from job_log where creation_time < ?`
|
||||
res, err := ormer.Raw(sql, t).Exec()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.RowsAffected()
|
||||
}
|
53
src/pkg/joblog/dao/dao_test.go
Normal file
53
src/pkg/joblog/dao/dao_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/pkg/joblog/models"
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DaoTestSuite struct {
|
||||
htesting.Suite
|
||||
dao DAO
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) SetupSuite() {
|
||||
suite.Suite.SetupSuite()
|
||||
suite.Suite.ClearTables = []string{"job_log"}
|
||||
suite.dao = New()
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestMethodsOfJobLog() {
|
||||
ctx := suite.Context()
|
||||
|
||||
uuid := "uuid_for_unit_test"
|
||||
now := time.Now()
|
||||
content := "content for unit text"
|
||||
jobLog := &models.JobLog{
|
||||
UUID: uuid,
|
||||
CreationTime: now,
|
||||
Content: content,
|
||||
}
|
||||
|
||||
// create
|
||||
_, err := suite.dao.Create(ctx, jobLog)
|
||||
suite.Nil(err)
|
||||
|
||||
// update
|
||||
updateContent := "content for unit text update"
|
||||
jobLog.Content = updateContent
|
||||
_, err = suite.dao.Create(ctx, jobLog)
|
||||
suite.Nil(err)
|
||||
|
||||
// get
|
||||
log, err := suite.dao.Get(ctx, uuid)
|
||||
suite.Nil(err)
|
||||
suite.Equal(now.Second(), log.CreationTime.Second())
|
||||
suite.Equal(updateContent, log.Content)
|
||||
suite.Equal(jobLog.LogID, log.LogID)
|
||||
|
||||
// delete
|
||||
count, err := suite.dao.DeleteBefore(ctx, time.Now().Add(time.Duration(time.Minute)))
|
||||
suite.Nil(err)
|
||||
suite.Equal(int64(1), count)
|
||||
}
|
47
src/pkg/joblog/manager.go
Normal file
47
src/pkg/joblog/manager.go
Normal file
@ -0,0 +1,47 @@
|
||||
package joblog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/pkg/joblog/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/joblog/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Mgr is the global job log manager instance
|
||||
var Mgr = New()
|
||||
|
||||
// Manager is used for job log management
|
||||
type Manager interface {
|
||||
// Get the job log specified by ID
|
||||
Get(ctx context.Context, uuid string) (jobLog *models.JobLog, err error)
|
||||
// Create the job log
|
||||
Create(ctx context.Context, jobLog *models.JobLog) (id int64, err error)
|
||||
// DeleteBefore the job log specified by time
|
||||
DeleteBefore(ctx context.Context, t time.Time) (id int64, err error)
|
||||
}
|
||||
|
||||
// New returns a default implementation of Manager
|
||||
func New() Manager {
|
||||
return &manager{
|
||||
dao: dao.New(),
|
||||
}
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
dao dao.DAO
|
||||
}
|
||||
|
||||
// Get ...
|
||||
func (m *manager) Get(ctx context.Context, uuid string) (jobLog *models.JobLog, err error) {
|
||||
return m.dao.Get(ctx, uuid)
|
||||
}
|
||||
|
||||
// Create ...
|
||||
func (m *manager) Create(ctx context.Context, jobLog *models.JobLog) (id int64, err error) {
|
||||
return m.dao.Create(ctx, jobLog)
|
||||
}
|
||||
|
||||
// DeleteJobLogsBefore ...
|
||||
func (m *manager) DeleteBefore(ctx context.Context, t time.Time) (id int64, err error) {
|
||||
return m.dao.DeleteBefore(ctx, t)
|
||||
}
|
76
src/pkg/joblog/managet_test.go
Normal file
76
src/pkg/joblog/managet_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
package joblog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/pkg/joblog/models"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
"github.com/goharbor/harbor/src/testing/pkg/joblog/dao"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type managerTestingSuite struct {
|
||||
suite.Suite
|
||||
t *testing.T
|
||||
assert *assert.Assertions
|
||||
require *require.Assertions
|
||||
mockJobLogDao *dao.DAO
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) SetupSuite() {
|
||||
m.t = m.T()
|
||||
m.assert = assert.New(m.t)
|
||||
m.require = require.New(m.t)
|
||||
|
||||
err := os.Setenv("RUN_MODE", "TEST")
|
||||
m.require.Nil(err)
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) TearDownSuite() {
|
||||
err := os.Unsetenv("RUN_MODE")
|
||||
m.require.Nil(err)
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) SetupTest() {
|
||||
m.mockJobLogDao = &dao.DAO{}
|
||||
Mgr = &manager{
|
||||
dao: m.mockJobLogDao,
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerTestingSuite(t *testing.T) {
|
||||
suite.Run(t, &managerTestingSuite{})
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) TestCreate() {
|
||||
m.mockJobLogDao.On("Create", mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
id, err := Mgr.Create(context.Background(), &models.JobLog{})
|
||||
m.mockJobLogDao.AssertCalled(m.t, "Create", mock.Anything, mock.Anything)
|
||||
m.require.Nil(err)
|
||||
m.assert.Equal(int64(1), id)
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) TestGet() {
|
||||
m.mockJobLogDao.On("Get", mock.Anything, mock.Anything).Return(&models.JobLog{
|
||||
LogID: 1,
|
||||
UUID: "1234",
|
||||
Content: "test get",
|
||||
}, nil)
|
||||
ir, err := Mgr.Get(context.Background(), "1234")
|
||||
m.mockJobLogDao.AssertCalled(m.t, "Get", mock.Anything, mock.Anything)
|
||||
m.require.Nil(err)
|
||||
m.require.NotNil(ir)
|
||||
m.assert.Equal(1, ir.LogID)
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) TestDeleteBefore() {
|
||||
m.mockJobLogDao.On("DeleteBefore", mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
_, err := Mgr.DeleteBefore(context.Background(), time.Now())
|
||||
m.mockJobLogDao.AssertCalled(m.t, "DeleteBefore", mock.Anything, mock.Anything)
|
||||
m.require.Nil(err)
|
||||
}
|
@ -1,9 +1,14 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/orm"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
orm.RegisterModel(&JobLog{})
|
||||
}
|
||||
|
||||
// JobLogTable is the name of the table that record the job execution result.
|
||||
const JobLogTable = "job_log"
|
||||
|
@ -30,7 +30,7 @@ func init() {
|
||||
)
|
||||
}
|
||||
|
||||
// RepoRecord holds the record of an repository in DB, all the infors are from the registry notification event.
|
||||
// RepoRecord holds the record of an repository in DB, all the infos are from the registry notification event.
|
||||
type RepoRecord struct {
|
||||
RepositoryID int64 `orm:"pk;auto;column(repository_id)" json:"repository_id"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
@ -57,7 +57,7 @@ func (r *RepoRecord) FilterByBlobDigest(ctx context.Context, qs orm.QuerySeter,
|
||||
return qs.FilterRaw("repository_id", fmt.Sprintf("in (%s)", sql))
|
||||
}
|
||||
|
||||
// TableName is required by by beego orm to map RepoRecord to table repository
|
||||
// TableName is required by beego orm to map RepoRecord to table repository
|
||||
func (r *RepoRecord) TableName() string {
|
||||
return "repository"
|
||||
}
|
||||
|
@ -2,55 +2,80 @@
|
||||
<global-message [isAppLevel]="true"></global-message>
|
||||
<navigator (showDialogModalAction)="openModal($event)"></navigator>
|
||||
<search-result></search-result>
|
||||
<div class="login-wrapper" [ngStyle]="{'background-image': customLoginBgImg? 'url(/images/' + customLoginBgImg + ')': ''}">
|
||||
<div class="login-wrapper"
|
||||
[ngStyle]="{'background-image': customLoginBgImg? 'url(/images/' + customLoginBgImg + ')': ''}">
|
||||
<form #signInForm="ngForm" class="login">
|
||||
<label class="title"> {{customAppTitle? customAppTitle:(appTitle | translate)}}
|
||||
<span *ngIf="isOidcLoginMode && steps===2" (click)="steps=1" class="back-icon">
|
||||
<clr-icon shape="angle left"></clr-icon>
|
||||
<div class="margin-left-3">{{'SEARCH.BACK' | translate }}</div>
|
||||
</span>
|
||||
<label class="title"> {{customAppTitle ? customAppTitle : (appTitle | translate)}}
|
||||
</label>
|
||||
<a href="/c/oidc/login" class="login-oidc" *ngIf="isOidcLoginMode">
|
||||
<button type="button" id="log_oidc" class="btn btn-primary btn-block">
|
||||
<span>{{'BUTTON.LOG_IN_OIDC' | translate }}</span>
|
||||
</button>
|
||||
</a>
|
||||
<div class="login-group">
|
||||
<clr-input-container>
|
||||
<input clrInput class="username" type="text" required [(ngModel)]="signInCredential.principal" name="login_username" id="login_username"
|
||||
placeholder='{{"PLACEHOLDER.SIGN_IN_NAME" | translate}}' #userNameInput='ngModel'>
|
||||
<clr-control-error>{{ 'TOOLTIP.SIGN_IN_USERNAME' | translate }}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<div class="clr-form-control">
|
||||
<div class="clr-control-container" [class.clr-error] ="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="clr-input pwd-input" [type]="showPwd?'text':'password'" required [(ngModel)]="signInCredential.password" name="login_password" id="login_password"
|
||||
placeholder='{{"PLACEHOLDER.SIGN_IN_PWD" | translate}}' #passwordInput="ngModel">
|
||||
<clr-icon *ngIf="!showPwd" shape="eye" class="pw-eye" (click)="showPwd =!showPwd"></clr-icon>
|
||||
<clr-icon *ngIf="showPwd" shape="eye-hide" class="pw-eye" (click)="showPwd =!showPwd"></clr-icon>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error *ngIf="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||
{{ 'TOOLTIP.SIGN_IN_PWD' | translate }}
|
||||
</clr-control-error>
|
||||
<ng-container *ngIf="isOidcLoginMode && steps ===1">
|
||||
<a href="/c/oidc/login">
|
||||
<button type="button" id="log_oidc" class="btn btn-primary btn-block">
|
||||
<span>{{'BUTTON.LOG_IN_OIDC' | translate }}</span>
|
||||
</button>
|
||||
</a>
|
||||
<div class="divider-container mt-1 mb-1">
|
||||
<hr class="divider" size="1" noshade/>
|
||||
<h4 class="m-0"><span>{{'SIGN_IN.OR' | translate }}</span></h4>
|
||||
<hr class="divider" size="1" noshade/>
|
||||
</div>
|
||||
<a href="javascript:void(0)" (click)="steps=2">
|
||||
<button type="button" id="login-db" class="btn btn-primary btn-block mt-0">
|
||||
<span>{{'SIGN_IN.VIA_LOCAL_DB' | translate }}</span>
|
||||
</button>
|
||||
</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="(!isOidcLoginMode && steps === 1) || (isOidcLoginMode && steps ===2)">
|
||||
<clr-input-container class="mt-3">
|
||||
<input clrInput class="username" type="text" required [(ngModel)]="signInCredential.principal"
|
||||
name="login_username" id="login_username"
|
||||
placeholder='{{"PLACEHOLDER.SIGN_IN_NAME" | translate}}' #userNameInput='ngModel'>
|
||||
<clr-control-error>{{ 'TOOLTIP.SIGN_IN_USERNAME' | translate }}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<div class="clr-form-control">
|
||||
<div class="clr-control-container"
|
||||
[class.clr-error]="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="clr-input pwd-input" [type]="showPwd?'text':'password'" required
|
||||
[(ngModel)]="signInCredential.password" name="login_password" id="login_password"
|
||||
placeholder='{{"PLACEHOLDER.SIGN_IN_PWD" | translate}}' #passwordInput="ngModel">
|
||||
<clr-icon *ngIf="!showPwd" shape="eye" class="pw-eye"
|
||||
(click)="showPwd =!showPwd"></clr-icon>
|
||||
<clr-icon *ngIf="showPwd" shape="eye-hide" class="pw-eye"
|
||||
(click)="showPwd =!showPwd"></clr-icon>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error
|
||||
*ngIf="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||
{{ 'TOOLTIP.SIGN_IN_PWD' | translate }}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<div class="display-flex">
|
||||
<clr-checkbox-wrapper>
|
||||
<input clrCheckbox type="checkbox" id="rememberme" #rememberMeBox
|
||||
(click)="clickRememberMe($event)" [checked]="rememberMe">
|
||||
<label class="reset-label" for="rememberme">{{ 'SIGN_IN.REMEMBER' | translate }}</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</div>
|
||||
<div *ngIf="isError" class="error active">
|
||||
<span *ngIf="isCoreServiceAvailable">{{ 'SIGN_IN.INVALID_MSG' | translate }}</span>
|
||||
<span *ngIf="!isCoreServiceAvailable">{{ 'SIGN_IN.CORE_SERVICE_NOT_AVAILABLE' | translate }}</span>
|
||||
</div>
|
||||
<button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary mt-2" (click)="signIn()"
|
||||
id="log_in">{{ 'BUTTON.LOG_IN' | translate }}</button>
|
||||
<a href="javascript:void(0)" class="signup" (click)="signUp()"
|
||||
*ngIf="selfSignUp">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a>
|
||||
</ng-container>
|
||||
<div class="more-info" [ngClass]="{'pt-1': (!isOidcLoginMode && steps === 1) || (isOidcLoginMode && steps ===2)}">
|
||||
<a href="https://github.com/goharbor/harbor" target="_blank">{{ 'BUTTON.MORE_INFO' | translate }}</a>
|
||||
</div>
|
||||
<div class="display-flex">
|
||||
<clr-checkbox-wrapper>
|
||||
<input clrCheckbox type="checkbox" id="rememberme" #rememberMeBox (click)="clickRememberMe($event)" [checked]="rememberMe">
|
||||
<label class="reset-label" for="rememberme">{{ 'SIGN_IN.REMEMBER' | translate }}</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</div>
|
||||
<div [class.visibility-hidden]="!isError" class="error active">
|
||||
<span *ngIf="isCoreServiceAvailable">{{ 'SIGN_IN.INVALID_MSG' | translate }}</span>
|
||||
<span *ngIf="!isCoreServiceAvailable">{{ 'SIGN_IN.CORE_SERVICE_NOT_AVAILABLE' | translate }}</span>
|
||||
</div>
|
||||
<button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary" (click)="signIn()" id="log_in">{{ 'BUTTON.LOG_IN' | translate }}</button>
|
||||
<a href="javascript:void(0)" class="signup" (click)="signUp()" *ngIf="selfSignUp">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://github.com/goharbor/harbor" target="_blank" class="more-info-link">{{ 'BUTTON.MORE_INFO' | translate }}</a>
|
||||
</div>
|
||||
</form>
|
||||
<div *ngIf="appConfig.show_popular_repo" id="pop_repo" class="popular-repo-wrapper">
|
||||
<top-repo class="repo-container"></top-repo>
|
||||
</div>
|
||||
</div>
|
||||
</clr-main-container>
|
||||
<sign-up #signupDialog (userCreation)="handleUserCreation($event)"></sign-up>
|
||||
|
@ -14,31 +14,6 @@
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
.popular-repo-wrapper {
|
||||
background-color: white;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
margin-top: 24px;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
position: relative;
|
||||
border-left: 1px solid #eee;
|
||||
}
|
||||
|
||||
.repo-container {
|
||||
width: 100%;
|
||||
margin-top: -210px;
|
||||
}
|
||||
|
||||
.more-info-link {
|
||||
position: relative;
|
||||
top: 80px;
|
||||
left: 294px;
|
||||
padding-right: 36px;
|
||||
}
|
||||
|
||||
.tm-font {
|
||||
font-size: 14px !important;
|
||||
position: relative;
|
||||
@ -59,10 +34,6 @@
|
||||
background:transparent;
|
||||
}
|
||||
}
|
||||
.login-oidc{
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.reset-label {
|
||||
font-size:inherit;
|
||||
color:inherit;
|
||||
@ -81,4 +52,42 @@
|
||||
}
|
||||
.pwd-input {
|
||||
padding-right: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.divider-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.divider {
|
||||
margin: 0;
|
||||
width: 8.5rem;
|
||||
}
|
||||
.login {
|
||||
padding-bottom: 180px;
|
||||
}
|
||||
.back-icon {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #007cbb;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.margin-left-3 {
|
||||
margin-left: 3px;
|
||||
}
|
||||
.title {
|
||||
position: absolute;
|
||||
top: 10rem
|
||||
}
|
||||
.login-group {
|
||||
width: 70%;
|
||||
position: absolute;
|
||||
top: 10rem
|
||||
}
|
||||
.more-info {
|
||||
text-align: right;
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import { AboutDialogComponent } from "../../shared/components/about-dialog/about
|
||||
import { CommonRoutes, CONFIG_AUTH_MODE } from "../../shared/entities/shared.const";
|
||||
import { SignInCredential } from "./sign-in-credential";
|
||||
import { UserPermissionService } from "../../shared/services";
|
||||
import {finalize} from "rxjs/operators";
|
||||
|
||||
// Define status flags for signing in states
|
||||
export const signInStatusNormal = 0;
|
||||
@ -45,7 +46,6 @@ const expireDays = 10;
|
||||
export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
showPwd: boolean = false;
|
||||
redirectUrl: string = "";
|
||||
appConfig: AppConfig = new AppConfig();
|
||||
// Remeber me indicator
|
||||
rememberMe: boolean = false;
|
||||
rememberedName: string = "";
|
||||
@ -68,6 +68,8 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
password: ""
|
||||
};
|
||||
isCoreServiceAvailable: boolean = true;
|
||||
steps: number = 1;
|
||||
hasLoadedAppConfig: boolean = false;
|
||||
constructor(
|
||||
private router: Router,
|
||||
private session: SessionService,
|
||||
@ -88,15 +90,6 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
this.customAppTitle = customSkinObj.loginTitle;
|
||||
}
|
||||
}
|
||||
|
||||
// Before login: Make sure the updated configuration can be loaded
|
||||
this.appConfigService.load()
|
||||
.subscribe(updatedConfig => this.appConfig = updatedConfig
|
||||
, error => {
|
||||
// Catch the error
|
||||
console.error("Failed to load bootstrap options with error: ", error);
|
||||
});
|
||||
|
||||
this.route.queryParams
|
||||
.subscribe(params => {
|
||||
this.redirectUrl = params["redirect_url"] || "";
|
||||
@ -117,7 +110,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
|
||||
// App title
|
||||
public get appTitle(): string {
|
||||
if (this.appConfig && this.appConfig.with_admiral) {
|
||||
if (this.appConfigService.getConfig() && this.appConfigService.getConfig().with_admiral) {
|
||||
return "APP_TITLE.VIC";
|
||||
}
|
||||
|
||||
@ -140,11 +133,11 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
|
||||
// Whether show the 'sign up' link
|
||||
public get selfSignUp(): boolean {
|
||||
return this.appConfig.auth_mode === CONFIG_AUTH_MODE.DB_AUTH
|
||||
&& this.appConfig.self_registration;
|
||||
return this.appConfigService.getConfig() && this.appConfigService.getConfig().auth_mode === CONFIG_AUTH_MODE.DB_AUTH
|
||||
&& this.appConfigService.getConfig().self_registration;
|
||||
}
|
||||
public get isOidcLoginMode(): boolean {
|
||||
return this.appConfig.auth_mode === CONFIG_AUTH_MODE.OIDC_AUTH;
|
||||
return this.appConfigService.getConfig() && this.appConfigService.getConfig().auth_mode === CONFIG_AUTH_MODE.OIDC_AUTH;
|
||||
}
|
||||
clickRememberMe($event: any): void {
|
||||
if ($event && $event.target) {
|
||||
@ -261,11 +254,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
|
||||
// after login successfully: Make sure the updated configuration can be loaded
|
||||
this.appConfigService.load()
|
||||
.subscribe(updatedConfig => this.appConfig = updatedConfig
|
||||
, error => {
|
||||
// Catch the error
|
||||
console.error("Failed to load bootstrap options with error: ", error);
|
||||
});
|
||||
.subscribe();
|
||||
}, error => {
|
||||
// 403 oidc login no body;
|
||||
if (this.isOidcLoginMode && error && error.status === 403) {
|
||||
|
@ -73,36 +73,25 @@ export class AppComponent {
|
||||
});
|
||||
}
|
||||
initLanguage() {
|
||||
/**
|
||||
* due to the bug(https://github.com/ngx-translate/core/issues/1258) of translate module
|
||||
* we have to call use method for all supported languages
|
||||
* use method will load related language json from backend server
|
||||
*/
|
||||
const usedLangs: Array<Observable<any>> = [];
|
||||
supportedLangs.forEach(lang => {
|
||||
usedLangs.push(this.translate.use(lang));
|
||||
});
|
||||
forkJoin(usedLangs).subscribe(() => { // use target lang after all langs json loaded
|
||||
this.translate.addLangs(supportedLangs);
|
||||
this.translate.setDefaultLang(DeFaultLang);
|
||||
let selectedLang: string = DeFaultLang;
|
||||
if (localStorage && localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY)) {// If user has selected lang, then directly use it
|
||||
selectedLang = localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY);
|
||||
} else {// If user has not selected lang, then use browser language(if contained in supportedLangs)
|
||||
const browserCultureLang: string = this.translate
|
||||
.getBrowserCultureLang()
|
||||
.toLowerCase();
|
||||
if (browserCultureLang && browserCultureLang.trim() !== "") {
|
||||
if (supportedLangs && supportedLangs.length > 0) {
|
||||
if (supportedLangs.find(lang => lang === browserCultureLang)) {
|
||||
selectedLang = browserCultureLang;
|
||||
}
|
||||
this.translate.addLangs(supportedLangs);
|
||||
this.translate.setDefaultLang(DeFaultLang);
|
||||
let selectedLang: string = DeFaultLang;
|
||||
if (localStorage && localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY)) {// If user has selected lang, then directly use it
|
||||
selectedLang = localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY);
|
||||
} else {// If user has not selected lang, then use browser language(if contained in supportedLangs)
|
||||
const browserCultureLang: string = this.translate
|
||||
.getBrowserCultureLang()
|
||||
.toLowerCase();
|
||||
if (browserCultureLang && browserCultureLang.trim() !== "") {
|
||||
if (supportedLangs && supportedLangs.length > 0) {
|
||||
if (supportedLangs.find(lang => lang === browserCultureLang)) {
|
||||
selectedLang = browserCultureLang;
|
||||
}
|
||||
}
|
||||
}
|
||||
localStorage.setItem(DEFAULT_LANG_LOCALSTORAGE_KEY, selectedLang);
|
||||
this.translate.use(selectedLang);
|
||||
}
|
||||
);
|
||||
}
|
||||
localStorage.setItem(DEFAULT_LANG_LOCALSTORAGE_KEY, selectedLang);
|
||||
// use method will load related language json from backend server
|
||||
this.translate.use(selectedLang);
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +149,9 @@ export class NavigatorComponent implements OnInit {
|
||||
switchLanguage(lang: string): void {
|
||||
this.selectedLang = lang;
|
||||
localStorage.setItem(DEFAULT_LANG_LOCALSTORAGE_KEY, lang);
|
||||
this.translate.use(lang);
|
||||
// due to the bug(https://github.com/ngx-translate/core/issues/1258) of translate module
|
||||
// have to reload
|
||||
this.translate.use(lang).subscribe(() => window.location.reload());
|
||||
}
|
||||
|
||||
// Handle the home action
|
||||
|
@ -13,6 +13,12 @@ export class OperateInfo {
|
||||
}
|
||||
}
|
||||
|
||||
export interface OperateInfosLocalstorage {
|
||||
updated: number; // millisecond
|
||||
data: OperateInfo[];
|
||||
newMessageCount: number;
|
||||
}
|
||||
|
||||
export function operateChanges(list: OperateInfo, state?: string, errorInfo?: string, timeStamp?: 0) {
|
||||
list.state = state;
|
||||
list.data.errorInf = errorInfo;
|
||||
|
@ -65,9 +65,26 @@ describe('OperationComponent', () => {
|
||||
expect(errorSpan.style.display).toEqual('none');
|
||||
});
|
||||
it('check calculateTime function', () => {
|
||||
expect(component.calculateTime(1000)).toEqual('less than 1 minute');
|
||||
expect(component.calculateTime(61000)).toEqual('1 minute(s) ago');
|
||||
expect(component.calculateTime(3601000)).toEqual('1 hour(s) ago');
|
||||
expect(component.calculateTime(24 * 3601000)).toEqual('1 day(s) ago');
|
||||
expect(component.calculateTime(
|
||||
1000,
|
||||
'less than 1 minute',
|
||||
' minute(s) ago',
|
||||
' hour(s) ago',
|
||||
' day(s) ago')).toEqual('less than 1 minute');
|
||||
expect(component.calculateTime(61000,
|
||||
'less than 1 minute',
|
||||
' minute(s) ago',
|
||||
' hour(s) ago',
|
||||
' day(s) ago')).toEqual('1 minute(s) ago');
|
||||
expect(component.calculateTime(3601000,
|
||||
'less than 1 minute',
|
||||
' minute(s) ago',
|
||||
' hour(s) ago',
|
||||
' day(s) ago')).toEqual('1 hour(s) ago');
|
||||
expect(component.calculateTime(24 * 3601000,
|
||||
'less than 1 minute',
|
||||
' minute(s) ago',
|
||||
' hour(s) ago',
|
||||
' day(s) ago')).toEqual('1 day(s) ago');
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, OnInit, OnDestroy, HostListener } from '@angular/core';
|
||||
import { OperationService } from "./operation.service";
|
||||
import { Subscription } from "rxjs";
|
||||
import { OperateInfo, OperationState } from "./operate";
|
||||
import { forkJoin, Subscription } from "rxjs";
|
||||
import { OperateInfo, OperateInfosLocalstorage, OperationState } from "./operate";
|
||||
import { SlideInOutAnimation } from "../../_animations/slide-in-out.animation";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { SessionService } from "../../services/session.service";
|
||||
@ -26,12 +26,11 @@ export class OperationComponent implements OnInit, OnDestroy {
|
||||
@HostListener('window:beforeunload', ['$event'])
|
||||
beforeUnloadHander(event) {
|
||||
if (this.session.getCurrentUser()) {
|
||||
// storage to localStorage
|
||||
const timp = new Date().getTime();
|
||||
// store into localStorage
|
||||
// group by user id
|
||||
localStorage.setItem(`${OPERATION_KEY}-${this.session.getCurrentUser().user_id}`,
|
||||
JSON.stringify({
|
||||
timp: timp,
|
||||
updated: new Date().getTime(),
|
||||
data: this.resultLists,
|
||||
newMessageCount: this._newMessageCount
|
||||
}));
|
||||
@ -107,28 +106,27 @@ export class OperationComponent implements OnInit, OnDestroy {
|
||||
|
||||
init() {
|
||||
if (this.session.getCurrentUser()) {
|
||||
let requestCookie = localStorage.getItem(`${OPERATION_KEY}-${this.session.getCurrentUser().user_id}`);
|
||||
if (requestCookie) {
|
||||
let operInfors: any = JSON.parse(requestCookie);
|
||||
if (operInfors) {
|
||||
if (operInfors.newMessageCount) {
|
||||
this._newMessageCount = operInfors.newMessageCount;
|
||||
const operationInfosString: string = localStorage.getItem(`${OPERATION_KEY}-${this.session.getCurrentUser().user_id}`);
|
||||
if (operationInfosString) {
|
||||
const operationInfos: OperateInfosLocalstorage = JSON.parse(operationInfosString);
|
||||
if (operationInfos) {
|
||||
if (operationInfos.newMessageCount) {
|
||||
this._newMessageCount = operationInfos.newMessageCount;
|
||||
}
|
||||
if ((new Date().getTime() - operInfors.timp) > MAX_SAVING_TIME) {
|
||||
localStorage.removeItem(`${OPERATION_KEY}-${this.session.getCurrentUser().user_id}`);
|
||||
} else {
|
||||
if (operInfors.data) {
|
||||
operInfors.data.forEach(operInfo => {
|
||||
if (operInfo.state === OperationState.progressing) {
|
||||
operInfo.state = OperationState.interrupt;
|
||||
operInfo.data.errorInf = 'operation been interrupted';
|
||||
}
|
||||
});
|
||||
this.resultLists = operInfors.data;
|
||||
}
|
||||
if (operationInfos.data && operationInfos.data.length) {
|
||||
// remove expired items
|
||||
operationInfos.data = operationInfos.data.filter(item => {
|
||||
return (new Date().getTime() - item.timeStamp) < MAX_SAVING_TIME;
|
||||
});
|
||||
operationInfos.data.forEach(operInfo => {
|
||||
if (operInfo.state === OperationState.progressing) {
|
||||
operInfo.state = OperationState.interrupt;
|
||||
operInfo.data.errorInf = 'operation been interrupted';
|
||||
}
|
||||
});
|
||||
this.resultLists = operationInfos.data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -167,29 +165,31 @@ export class OperationComponent implements OnInit, OnDestroy {
|
||||
|
||||
|
||||
TabEvent(): void {
|
||||
let timp: any;
|
||||
let secondsAgo: string, minutesAgo: string, hoursAgo: string, daysAgo: string;
|
||||
forkJoin([
|
||||
this.translate.get("OPERATION.SECOND_AGO"),
|
||||
this.translate.get("OPERATION.MINUTE_AGO"),
|
||||
this.translate.get("OPERATION.HOUR_AGO"),
|
||||
this.translate.get("OPERATION.DAY_AGO"),
|
||||
]).subscribe(res => {
|
||||
[secondsAgo, minutesAgo, hoursAgo, daysAgo] = res;
|
||||
});
|
||||
this.resultLists.forEach(data => {
|
||||
timp = new Date().getTime() - +data.timeStamp;
|
||||
data.timeDiff = this.calculateTime(timp);
|
||||
const timeDiff: number = new Date().getTime() - +data.timeStamp;
|
||||
data.timeDiff = this.calculateTime(timeDiff, secondsAgo, minutesAgo, hoursAgo, daysAgo);
|
||||
});
|
||||
}
|
||||
|
||||
calculateTime(timp: number) {
|
||||
let dist = Math.floor(timp / 1000 / 60); // change to minute;
|
||||
calculateTime(timeDiff: number, s: string, m: string, h: string, d: string) {
|
||||
const dist = Math.floor(timeDiff / 1000 / 60); // change to minute;
|
||||
if (dist > 0 && dist < 60) {
|
||||
return Math.floor(dist) + ' minute(s) ago';
|
||||
return Math.floor(dist) + m;
|
||||
} else if (dist >= 60 && Math.floor(dist / 60) < 24) {
|
||||
return Math.floor(dist / 60) + ' hour(s) ago';
|
||||
return Math.floor(dist / 60) + h;
|
||||
} else if (Math.floor(dist / 60) >= 24) {
|
||||
return Math.floor(dist / 60 / 24) + ' day(s) ago';
|
||||
return Math.floor(dist / 60 / 24) + d;
|
||||
} else {
|
||||
return 'less than 1 minute';
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
translateTime(tim: string, param?: number) {
|
||||
this.translate.get(tim, {'param': param}).subscribe((res: string) => {
|
||||
return res;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -87,23 +87,23 @@ export const QuotaUnits = [
|
||||
UNIT: "Byte",
|
||||
},
|
||||
{
|
||||
UNIT: "KB",
|
||||
UNIT: "KiB",
|
||||
},
|
||||
{
|
||||
UNIT: "MB",
|
||||
UNIT: "MiB",
|
||||
},
|
||||
{
|
||||
UNIT: "GB",
|
||||
UNIT: "GiB",
|
||||
},
|
||||
{
|
||||
UNIT: "TB",
|
||||
UNIT: "TiB",
|
||||
},
|
||||
];
|
||||
export const QuotaUnlimited = -1;
|
||||
export const StorageMultipleConstant = 1024;
|
||||
export const LimitCount = 100000000;
|
||||
export enum QuotaUnit {
|
||||
TB = "TB", GB = "GB", MB = "MB", KB = "KB", BIT = "Byte"
|
||||
TB = "TiB", GB = "GiB", MB = "MiB", KB = "KiB", BIT = "Byte"
|
||||
}
|
||||
export enum QuotaProgress {
|
||||
COUNT_USED = "COUNT_USED", COUNT_HARD = "COUNT_HARD", STROAGE_USED = "STORAGE_USED", STORAGE_HARD = "STORAGE_HARD"
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { delUrlParam, getQueryString, getSizeNumber, getSizeUnit, getSortingString, isSameArrayValue, isSameObject } from "./utils";
|
||||
import { ClrDatagridStateInterface } from "@clr/angular";
|
||||
import {QuotaUnit} from "../entities/shared.const";
|
||||
|
||||
describe('functions in utils.ts should work', () => {
|
||||
it('function isSameArrayValue() should work', () => {
|
||||
@ -60,13 +61,15 @@ describe('functions in utils.ts should work', () => {
|
||||
expect(getSizeNumber(10)).toEqual(10);
|
||||
expect(getSizeNumber(456400)).toEqual('445.70');
|
||||
expect(getSizeNumber(45640000)).toEqual('43.53');
|
||||
expect(getSizeNumber(4564000000000)).toEqual('4.15');
|
||||
});
|
||||
|
||||
it('function getSizeUnit() should work', () => {
|
||||
expect(getSizeUnit).toBeTruthy();
|
||||
expect(getSizeUnit(4564)).toEqual('KB');
|
||||
expect(getSizeUnit(10)).toEqual('Byte');
|
||||
expect(getSizeUnit(4564000)).toEqual('MB');
|
||||
expect(getSizeUnit(4564000000)).toEqual('GB');
|
||||
expect(getSizeUnit(4564)).toEqual(QuotaUnit.KB);
|
||||
expect(getSizeUnit(10)).toEqual(QuotaUnit.BIT);
|
||||
expect(getSizeUnit(4564000)).toEqual(QuotaUnit.MB);
|
||||
expect(getSizeUnit(4564000000)).toEqual(QuotaUnit.GB);
|
||||
expect(getSizeUnit(4564000000000)).toEqual(QuotaUnit.TB);
|
||||
});
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ import { HttpHeaders } from '@angular/common/http';
|
||||
import { RequestQueryParams } from '../services';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { Comparator, State, HttpOptionInterface, HttpOptionTextInterface, QuotaUnitInterface } from '../services';
|
||||
import { QuotaUnits, StorageMultipleConstant } from '../entities/shared.const';
|
||||
import {QuotaUnit, QuotaUnits, StorageMultipleConstant} from '../entities/shared.const';
|
||||
import { AbstractControl } from "@angular/forms";
|
||||
import { isValidCron } from 'cron-validator';
|
||||
import { ClrDatagridStateInterface } from "@clr/angular";
|
||||
@ -567,13 +567,15 @@ export const validateLimit = unitContrl => {
|
||||
};
|
||||
|
||||
export function formatSize(tagSize: string): string {
|
||||
let size: number = Number.parseInt(tagSize);
|
||||
const size: number = Number.parseInt(tagSize);
|
||||
if (Math.pow(1024, 1) <= size && size < Math.pow(1024, 2)) {
|
||||
return (size / Math.pow(1024, 1)).toFixed(2) + "KB";
|
||||
return (size / Math.pow(1024, 1)).toFixed(2) + "KiB";
|
||||
} else if (Math.pow(1024, 2) <= size && size < Math.pow(1024, 3)) {
|
||||
return (size / Math.pow(1024, 2)).toFixed(2) + "MB";
|
||||
return (size / Math.pow(1024, 2)).toFixed(2) + "MiB";
|
||||
} else if (Math.pow(1024, 3) <= size && size < Math.pow(1024, 4)) {
|
||||
return (size / Math.pow(1024, 3)).toFixed(2) + "GB";
|
||||
return (size / Math.pow(1024, 3)).toFixed(2) + "GiB";
|
||||
} else if (Math.pow(1024, 4) <= size) {
|
||||
return (size / Math.pow(1024, 4)).toFixed(2) + "TiB";
|
||||
} else {
|
||||
return size + "B";
|
||||
}
|
||||
@ -590,6 +592,8 @@ export function getSizeNumber(size: number): string | number {
|
||||
return (size / Math.pow(1024, 2)).toFixed(2);
|
||||
} else if (Math.pow(1024, 3) <= size && size < Math.pow(1024, 4)) {
|
||||
return (size / Math.pow(1024, 3)).toFixed(2);
|
||||
} else if (Math.pow(1024, 4) <= size) {
|
||||
return (size / Math.pow(1024, 4)).toFixed(2);
|
||||
} else {
|
||||
return size;
|
||||
}
|
||||
@ -601,13 +605,15 @@ export function getSizeNumber(size: number): string | number {
|
||||
*/
|
||||
export function getSizeUnit(size: number): string {
|
||||
if (Math.pow(1024, 1) <= size && size < Math.pow(1024, 2)) {
|
||||
return "KB";
|
||||
return QuotaUnit.KB;
|
||||
} else if (Math.pow(1024, 2) <= size && size < Math.pow(1024, 3)) {
|
||||
return "MB";
|
||||
return QuotaUnit.MB;
|
||||
} else if (Math.pow(1024, 3) <= size && size < Math.pow(1024, 4)) {
|
||||
return "GB";
|
||||
return QuotaUnit.GB;
|
||||
} else if (Math.pow(1024, 4) <= size) {
|
||||
return QuotaUnit.TB;
|
||||
} else {
|
||||
return "Byte";
|
||||
return QuotaUnit.BIT;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,8 @@
|
||||
"INVALID_MSG": "Falscher Nutzername oder Passwort.",
|
||||
"FORGOT_PWD": "Passwort vergessen",
|
||||
"HEADER_LINK": "Einloggen",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core Service ist nicht verfügbar"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "Invalid user name or password.",
|
||||
"FORGOT_PWD": "Forgot password",
|
||||
"HEADER_LINK": "Sign In",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "Sign Up"
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "Nombre o contraseña no válidos.",
|
||||
"FORGOT_PWD": "Olvidé mi contraseña",
|
||||
"HEADER_LINK": "Identificarse",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "Registrarse"
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "Nom d'utilisateur ou mot de passe invalide.",
|
||||
"FORGOT_PWD": "Mot de passe oublié",
|
||||
"HEADER_LINK": "S'identifier",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "S'inscrire"
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "Usuário ou senha inválidos",
|
||||
"FORGOT_PWD": "Esqueci a senha",
|
||||
"HEADER_LINK": "Logar-se",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "Registrar-se"
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "Geçersiz kullanıcı adı veya şifre.",
|
||||
"FORGOT_PWD": "Parolamı Unuttum",
|
||||
"HEADER_LINK": "Oturum aç",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "Kayıt ol"
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "用户名或者密码不正确。",
|
||||
"FORGOT_PWD": "忘记密码",
|
||||
"HEADER_LINK": "登录",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "核心服务不可用。"
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "核心服务不可用。",
|
||||
"OR": "或",
|
||||
"VIA_LOCAL_DB": "通过本地数据库登录"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "注册"
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "用戶名或者密碼不正確。",
|
||||
"FORGOT_PWD": "忘記密碼",
|
||||
"HEADER_LINK": "登錄",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "註冊"
|
||||
|
@ -16,6 +16,7 @@ package artifactinfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/docker/distribution/reference"
|
||||
lib_http "github.com/goharbor/harbor/src/lib/http"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -50,7 +51,11 @@ func Middleware() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
log.Debugf("In artifact info middleware, url: %s", req.URL.String())
|
||||
m, ok := parse(req.URL)
|
||||
m, ok, err := parse(req.URL)
|
||||
if err != nil {
|
||||
lib_http.SendError(rw, err)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
next.ServeHTTP(rw, req)
|
||||
return
|
||||
@ -100,7 +105,7 @@ func projectNameFromRepo(repo string) (string, error) {
|
||||
return components[0], nil
|
||||
}
|
||||
|
||||
func parse(url *url.URL) (map[string]string, bool) {
|
||||
func parse(url *url.URL) (map[string]string, bool, error) {
|
||||
path := url.Path
|
||||
query := url.Query()
|
||||
m := make(map[string]string)
|
||||
@ -119,10 +124,15 @@ func parse(url *url.URL) (map[string]string, bool) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if digest.DigestRegexp.MatchString(m[lib.ReferenceSubexp]) {
|
||||
m[lib.DigestSubexp] = m[lib.ReferenceSubexp]
|
||||
} else if ref, ok := m[lib.ReferenceSubexp]; ok {
|
||||
m[tag] = ref
|
||||
// parse reference, for invalid reference format, just give 404.
|
||||
if m[lib.ReferenceSubexp] != "" {
|
||||
if digest.DigestRegexp.MatchString(m[lib.ReferenceSubexp]) {
|
||||
m[lib.DigestSubexp] = m[lib.ReferenceSubexp]
|
||||
} else if reference.TagRegexp.MatchString(m[lib.ReferenceSubexp]) {
|
||||
m[tag] = m[lib.ReferenceSubexp]
|
||||
} else {
|
||||
return m, match, errors.New("invalid reference format").WithCode(errors.NotFoundCode)
|
||||
}
|
||||
}
|
||||
return m, match
|
||||
return m, match, nil
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ package artifactinfo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@ -30,6 +31,7 @@ func TestParseURL(t *testing.T) {
|
||||
input string
|
||||
expect map[string]string
|
||||
match bool
|
||||
rc string
|
||||
}{
|
||||
{
|
||||
input: "/api/projects",
|
||||
@ -60,8 +62,12 @@ func TestParseURL(t *testing.T) {
|
||||
{
|
||||
input: "/v2/development/golang/manifests/shaxxx:**********************************************************************************************************************************",
|
||||
|
||||
expect: map[string]string{},
|
||||
match: false,
|
||||
expect: map[string]string{
|
||||
lib.RepositorySubexp: "development/golang",
|
||||
lib.ReferenceSubexp: "shaxxx:**********************************************************************************************************************************",
|
||||
"tag": "shaxxx:**********************************************************************************************************************************",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "/v2/multi/sector/repository/blobs/sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
@ -99,6 +105,12 @@ func TestParseURL(t *testing.T) {
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "/v2/library/centos/manifest/.Invalid",
|
||||
expect: map[string]string{},
|
||||
match: false,
|
||||
rc: errors.NotFoundCode,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
@ -106,7 +118,10 @@ func TestParseURL(t *testing.T) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
e, m := parse(url)
|
||||
e, m, err := parse(url)
|
||||
if err != nil {
|
||||
assert.True(t, errors.IsErr(err, c.rc))
|
||||
}
|
||||
assert.Equal(t, c.match, m)
|
||||
assert.Equal(t, c.expect, e)
|
||||
}
|
||||
|
@ -237,6 +237,13 @@ func (api *preheatAPI) CreatePolicy(ctx context.Context, params operation.Create
|
||||
return api.SendError(ctx, err)
|
||||
}
|
||||
|
||||
project, err := api.projectCtl.GetByName(ctx, params.ProjectName)
|
||||
if err != nil {
|
||||
return api.SendError(ctx, err)
|
||||
}
|
||||
// override project ID
|
||||
policy.ProjectID = project.ProjectID
|
||||
|
||||
_, err = api.preheatCtl.CreatePolicy(ctx, policy)
|
||||
if err != nil {
|
||||
return api.SendError(ctx, err)
|
||||
@ -658,16 +665,16 @@ func convertTaskToPayload(model *task.Task) (*models.Task, error) {
|
||||
}
|
||||
|
||||
return &models.Task{
|
||||
CreationTime: model.CreationTime.String(),
|
||||
EndTime: model.EndTime.String(),
|
||||
CreationTime: model.CreationTime.Format(time.RFC3339),
|
||||
EndTime: model.EndTime.Format(time.RFC3339),
|
||||
ExecutionID: model.ExecutionID,
|
||||
ExtraAttrs: model.ExtraAttrs,
|
||||
ID: model.ID,
|
||||
RunCount: model.RunCount,
|
||||
StartTime: model.StartTime.String(),
|
||||
StartTime: model.StartTime.Format(time.RFC3339),
|
||||
Status: model.Status,
|
||||
StatusMessage: model.StatusMessage,
|
||||
UpdateTime: model.UpdateTime.String(),
|
||||
UpdateTime: model.UpdateTime.Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -375,16 +375,16 @@ func Test_convertTaskToPayload(t *testing.T) {
|
||||
EndTime: time.Time{},
|
||||
},
|
||||
expect: &models.Task{
|
||||
CreationTime: "0001-01-01 00:00:00 +0000 UTC",
|
||||
EndTime: "0001-01-01 00:00:00 +0000 UTC",
|
||||
CreationTime: "0001-01-01T00:00:00Z",
|
||||
EndTime: "0001-01-01T00:00:00Z",
|
||||
ExecutionID: 0,
|
||||
ExtraAttrs: nil,
|
||||
ID: 0,
|
||||
RunCount: 0,
|
||||
StartTime: "0001-01-01 00:00:00 +0000 UTC",
|
||||
StartTime: "0001-01-01T00:00:00Z",
|
||||
Status: "",
|
||||
StatusMessage: "",
|
||||
UpdateTime: "0001-01-01 00:00:00 +0000 UTC",
|
||||
UpdateTime: "0001-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -109,6 +109,9 @@ func (u *userGroupAPI) ListUserGroups(ctx context.Context, params operation.List
|
||||
switch authMode {
|
||||
case common.LDAPAuth:
|
||||
query.GroupType = common.LDAPGroupType
|
||||
if params.LdapGroupDn != nil && len(*params.LdapGroupDn) > 0 {
|
||||
query.LdapGroupDN = *params.LdapGroupDn
|
||||
}
|
||||
case common.HTTPAuth:
|
||||
query.GroupType = common.HTTPGroupType
|
||||
}
|
||||
|
83
src/testing/pkg/joblog/dao/dao.go
Normal file
83
src/testing/pkg/joblog/dao/dao.go
Normal file
@ -0,0 +1,83 @@
|
||||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
models "github.com/goharbor/harbor/src/pkg/joblog/models"
|
||||
|
||||
time "time"
|
||||
)
|
||||
|
||||
// DAO is an autogenerated mock type for the DAO type
|
||||
type DAO struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, jobLog
|
||||
func (_m *DAO) Create(ctx context.Context, jobLog *models.JobLog) (int64, error) {
|
||||
ret := _m.Called(ctx, jobLog)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.JobLog) int64); ok {
|
||||
r0 = rf(ctx, jobLog)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *models.JobLog) error); ok {
|
||||
r1 = rf(ctx, jobLog)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteBefore provides a mock function with given fields: ctx, t
|
||||
func (_m *DAO) DeleteBefore(ctx context.Context, t time.Time) (int64, error) {
|
||||
ret := _m.Called(ctx, t)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, time.Time) int64); ok {
|
||||
r0 = rf(ctx, t)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, time.Time) error); ok {
|
||||
r1 = rf(ctx, t)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, uuid
|
||||
func (_m *DAO) Get(ctx context.Context, uuid string) (*models.JobLog, error) {
|
||||
ret := _m.Called(ctx, uuid)
|
||||
|
||||
var r0 *models.JobLog
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *models.JobLog); ok {
|
||||
r0 = rf(ctx, uuid)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.JobLog)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = rf(ctx, uuid)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
83
src/testing/pkg/joblog/manager.go
Normal file
83
src/testing/pkg/joblog/manager.go
Normal file
@ -0,0 +1,83 @@
|
||||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package joblog
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
models "github.com/goharbor/harbor/src/pkg/joblog/models"
|
||||
|
||||
time "time"
|
||||
)
|
||||
|
||||
// Manager is an autogenerated mock type for the Manager type
|
||||
type Manager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, jobLog
|
||||
func (_m *Manager) Create(ctx context.Context, jobLog *models.JobLog) (int64, error) {
|
||||
ret := _m.Called(ctx, jobLog)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.JobLog) int64); ok {
|
||||
r0 = rf(ctx, jobLog)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *models.JobLog) error); ok {
|
||||
r1 = rf(ctx, jobLog)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteBefore provides a mock function with given fields: ctx, t
|
||||
func (_m *Manager) DeleteBefore(ctx context.Context, t time.Time) (int64, error) {
|
||||
ret := _m.Called(ctx, t)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, time.Time) int64); ok {
|
||||
r0 = rf(ctx, t)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, time.Time) error); ok {
|
||||
r1 = rf(ctx, t)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, uuid
|
||||
func (_m *Manager) Get(ctx context.Context, uuid string) (*models.JobLog, error) {
|
||||
ret := _m.Called(ctx, uuid)
|
||||
|
||||
var r0 *models.JobLog
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *models.JobLog); ok {
|
||||
r0 = rf(ctx, uuid)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.JobLog)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = rf(ctx, uuid)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
@ -51,3 +51,5 @@ package pkg
|
||||
//go:generate mockery --case snake --dir ../../pkg/replication --name Manager --output ./replication --outpkg manager
|
||||
//go:generate mockery --case snake --dir ../../pkg/label --name Manager --output ./label --outpkg label
|
||||
//go:generate mockery --case snake --dir ../../pkg/label/dao --name DAO --output ./label/dao --outpkg dao
|
||||
//go:generate mockery --case snake --dir ../../pkg/joblog --name Manager --output ./joblog --outpkg joblog
|
||||
//go:generate mockery --case snake --dir ../../pkg/joblog/dao --name DAO --output ./joblog/dao --outpkg dao
|
||||
|
@ -3,11 +3,17 @@ set -e
|
||||
echo "get the conformance testing code..."
|
||||
git clone https://github.com/opencontainers/distribution-spec.git
|
||||
|
||||
echo "create testing project"
|
||||
STATUS=$(curl -w '%{http_code}' -H 'Content-Type: application/json' -H 'Accept: application/json' -X POST -u "admin:Harbor12345" -s --insecure "https://$IP/api/v2.0/projects" --data '{"project_name":"conformance","metadata":{"public":"false"},"storage_limit":-1}')
|
||||
if [ $STATUS -ne 201 ]; then
|
||||
exit 1
|
||||
fi
|
||||
function createPro {
|
||||
echo "create testing project: $2"
|
||||
STATUS=$(curl -w '%{http_code}' -H 'Content-Type: application/json' -H 'Accept: application/json' -X POST -u "admin:Harbor12345" -s --insecure "https://$1/api/v2.0/projects" --data "{\"project_name\":\"$2\",\"metadata\":{\"public\":\"false\"},\"storage_limit\":-1}")
|
||||
if [ $STATUS -ne 201 ]; then
|
||||
echo "fail to create project: $2, rc: $STATUS"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
createPro $1 conformance
|
||||
createPro $1 crossmount
|
||||
|
||||
echo "run conformance test..."
|
||||
export OCI_ROOT_URL="https://$1"
|
||||
@ -20,6 +26,7 @@ export OCI_TEST_PUSH=1
|
||||
export OCI_TEST_PULL=1
|
||||
export OCI_TEST_CONTENT_DISCOVERY=1
|
||||
export OCI_TEST_CONTENT_MANAGEMENT=1
|
||||
export OCI_CROSSMOUNT_NAMESPACE="crossmount/testrepo"
|
||||
|
||||
cd ./distribution-spec/conformance
|
||||
go test .
|
@ -3,5 +3,5 @@ set -x
|
||||
|
||||
set -e
|
||||
|
||||
sudo make package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-travis PKGVERSIONTAG=dev-travis UIVERSIONTAG=dev-travis GOBUILDIMAGE=golang:1.15.12 COMPILETAG=compile_golangimage BUILDBIN=true NOTARYFLAG=true CHARTFLAG=true TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false
|
||||
sudo make package_offline GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-travis PKGVERSIONTAG=dev-travis UIVERSIONTAG=dev-travis GOBUILDIMAGE=golang:1.15.12 COMPILETAG=compile_golangimage BUILDBIN=true NOTARYFLAG=true CHARTFLAG=true TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false
|
||||
sudo make package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-travis PKGVERSIONTAG=dev-travis UIVERSIONTAG=dev-travis GOBUILDIMAGE=golang:1.16.5 COMPILETAG=compile_golangimage BUILDBIN=true NOTARYFLAG=true CHARTFLAG=true TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false
|
||||
sudo make package_offline GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-travis PKGVERSIONTAG=dev-travis UIVERSIONTAG=dev-travis GOBUILDIMAGE=golang:1.16.5 COMPILETAG=compile_golangimage BUILDBIN=true NOTARYFLAG=true CHARTFLAG=true TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false
|
||||
|
@ -6,6 +6,7 @@ set -e
|
||||
export POSTGRESQL_HOST=$1
|
||||
export REGISTRY_URL=http://$1:5000
|
||||
export CHROME_BIN=chromium-browser
|
||||
export GO111MODULE=auto
|
||||
#export DISPLAY=:99.0
|
||||
#sh -e /etc/init.d/xvfb start
|
||||
|
||||
|
@ -20,9 +20,10 @@ Resource ../../resources/Util.robot
|
||||
|
||||
*** Keywords ***
|
||||
Sign In Harbor
|
||||
[Arguments] ${url} ${user} ${pw}
|
||||
Go To ${url}
|
||||
[Arguments] ${url} ${user} ${pw} ${is_oidc}=${false}
|
||||
Go To ${url}
|
||||
Retry Wait Element ${harbor_span_title}
|
||||
Run Keyword If ${is_oidc}==${true} Retry Element Click ${login_with_db_btn}
|
||||
Retry Wait Element ${login_name}
|
||||
Retry Wait Element ${login_pwd}
|
||||
Input Text ${login_name} ${user}
|
||||
|
@ -19,6 +19,7 @@ Documentation This resource provides any keywords related to the Harbor private
|
||||
${log_oidc_provider_btn} //*[@id='log_oidc']
|
||||
${login_with_email_btn} //span[contains(., 'Log in with Email')]
|
||||
${login_with_ldap_btn} //span[contains(., 'Log in with LDAP')]
|
||||
${login_with_db_btn} //*[@id='login-db']
|
||||
${dex_login_btn} //*[@id='login']
|
||||
${dex_pwd_btn} //*[@id='password']
|
||||
${submit_login_btn} //*[@id='submit-login']
|
||||
|
@ -115,7 +115,7 @@ Test Case - Repo Size
|
||||
Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} library alpine 2.6 2.6
|
||||
Go Into Project library
|
||||
Go Into Repo alpine
|
||||
Wait Until Page Contains 1.92MB
|
||||
Wait Until Page Contains 1.92MiB
|
||||
Close Browser
|
||||
|
||||
Test Case - Staticsinfo
|
||||
@ -517,7 +517,7 @@ Test Case - Create An New Project With Quotas Set
|
||||
Init Chrome Driver
|
||||
${d}= Get Current Date result_format=%m%s
|
||||
${storage_quota}= Set Variable 600
|
||||
${storage_quota_unit}= Set Variable GB
|
||||
${storage_quota_unit}= Set Variable GiB
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
Create An New Project And Go Into Project project${d} storage_quota=${storage_quota} storage_quota_unit=${storage_quota_unit}
|
||||
${storage_quota_ret}= Get Project Storage Quota Text From Project Quotas List project${d}
|
||||
@ -528,11 +528,11 @@ Test Case - Project Storage Quotas Dispaly And Control
|
||||
Init Chrome Driver
|
||||
${d}= Get Current Date result_format=%m%s
|
||||
${storage_quota}= Set Variable 350
|
||||
${storage_quota_unit}= Set Variable MB
|
||||
${storage_quota_unit}= Set Variable MiB
|
||||
${image_a}= Set Variable one_layer
|
||||
${image_b}= Set Variable redis
|
||||
${image_a_size}= Set Variable 330.83MB
|
||||
${image_b_size}= Set Variable 34.1\\dMB
|
||||
${image_a_size}= Set Variable 330.83MiB
|
||||
${image_b_size}= Set Variable 34.1\\dMiB
|
||||
${image_a_ver}= Set Variable 1.0
|
||||
${image_b_ver}= Set Variable donotremove5.0
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
@ -561,7 +561,7 @@ Test Case - Project Quotas Control Under Copy
|
||||
${image_a_ver}= Set Variable donotremove5.0
|
||||
${image_b_ver}= Set Variable do_not_remove_6.8.3
|
||||
${storage_quota}= Set Variable 330
|
||||
${storage_quota_unit}= Set Variable MB
|
||||
${storage_quota_unit}= Set Variable MiB
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
Create An New Project And Go Into Project project_a_${d}
|
||||
Create An New Project And Go Into Project project_b_${d} storage_quota=${storage_quota} storage_quota_unit=${storage_quota_unit}
|
||||
|
@ -74,9 +74,9 @@ Test Case - Project Quotas Control Under GC
|
||||
Init Chrome Driver
|
||||
${d}= Get Current Date result_format=%m%s
|
||||
${storage_quota}= Set Variable 200
|
||||
${storage_quota_unit}= Set Variable MB
|
||||
${storage_quota_unit}= Set Variable MiB
|
||||
${image_a}= Set Variable logstash
|
||||
${image_a_size}= Set Variable 321.03MB
|
||||
${image_a_size}= Set Variable 321.03MiB
|
||||
${image_a_ver}= Set Variable 6.8.3
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
Create An New Project And Go Into Project project${d} storage_quota=${storage_quota} storage_quota_unit=${storage_quota_unit}
|
||||
|
@ -90,17 +90,17 @@ Test Case - Helm CLI Push
|
||||
|
||||
Test Case - Onboard OIDC User Sign In
|
||||
Init Chrome Driver
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} is_oidc=${true}
|
||||
Check Automatic Onboarding And Save
|
||||
Logout Harbor
|
||||
Sign In Harbor With OIDC User ${HARBOR_URL} test8 is_onboard=${true}
|
||||
Logout Harbor
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} is_oidc=${true}
|
||||
Set User Name Claim And Save email
|
||||
Logout Harbor
|
||||
Sign In Harbor With OIDC User ${HARBOR_URL} test9 is_onboard=${true} username_claim=email
|
||||
Logout Harbor
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} is_oidc=${true}
|
||||
Set User Name Claim And Save ${null}
|
||||
Sleep 2
|
||||
Close Browser
|
||||
|
@ -5,6 +5,7 @@ ARG MOCKERY_VERSION
|
||||
|
||||
# https://github.com/docker-library/golang/issues/225
|
||||
ENV XDG_CACHE_HOME /tmp
|
||||
ENV GO111MODULE auto
|
||||
|
||||
RUN mkdir -p /tmp/mockery-${MOCKERY_VERSION} && \
|
||||
curl -fsSL https://github.com/vektra/mockery/releases/download/${MOCKERY_VERSION}/mockery_${MOCKERY_VERSION#v}_Linux_x86_64.tar.gz | tar -xz -C /tmp/mockery-${MOCKERY_VERSION} && \
|
||||
|
Loading…
Reference in New Issue
Block a user