Merge branch 'master' into inline-css

This commit is contained in:
Fangyuan Cheng 2018-12-19 10:07:21 +08:00 committed by GitHub
commit bb15f67316
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 18310 additions and 31593 deletions

View File

@ -26,8 +26,8 @@ go get github.com/goharbor/harbor
cd $GOPATH/src/github.com/goharbor/harbor cd $GOPATH/src/github.com/goharbor/harbor
#Track repository under your personal account #Track repository under your personal account
git config push.default nothing # Anything to avoid pushing to vmware/harbor by default git config push.default nothing # Anything to avoid pushing to goharbor/harbor by default
git remote rename origin vmware git remote rename origin goharbor
git remote add $USER git@github.com:$USER/harbor.git git remote add $USER git@github.com:$USER/harbor.git
git fetch $USER git fetch $USER
@ -114,6 +114,9 @@ Harbor backend is written in [Go](http://golang.org/). If you don't have a Harbo
| 1.2 | 1.7.3 | | 1.2 | 1.7.3 |
| 1.3 | 1.9.2 | | 1.3 | 1.9.2 |
| 1.4 | 1.9.2 | | 1.4 | 1.9.2 |
| 1.5 | 1.9.2 |
| 1.6 | 1.9.2 |
| 1.6 | tbd |
Ensure your GOPATH and PATH have been configured in accordance with the Go environment instructions. Ensure your GOPATH and PATH have been configured in accordance with the Go environment instructions.
@ -128,9 +131,11 @@ Harbor web UI is built based on [Clarity](https://vmware.github.io/clarity/) and
| 1.1 | 2.4.1 | 0.8.7 | | 1.1 | 2.4.1 | 0.8.7 |
| 1.2 | 4.1.3 | 0.9.8 | | 1.2 | 4.1.3 | 0.9.8 |
| 1.3 | 4.3.0 | 0.10.17 | | 1.3 | 4.3.0 | 0.10.17 |
| 1.4 | | | | 1.4 | 4.3.0 | 0.10.17 |
| 1.5 | 4.3.0 | 0.10.27 |
| 1.6 | 4.3.0 | 0.10.27 |
**Npm Package Dependency:** Run the following commands to restore the package dependencies. **npm Package Dependency:** Run the following commands to restore the package dependencies.
``` ```
#For the web UI #For the web UI
cd $REPO_DIR/src/portal cd $REPO_DIR/src/portal
@ -173,12 +178,12 @@ Both `$working_dir` and `$user` are mentioned in the figure above.
Changes should be made on your own fork in a new branch. The branch should be named `XXX-description` where XXX is the number of the issue. PR should be rebased on top of master without multiple branches mixed into the PR. If your PR do not merge cleanly, use commands listed below to get it up to date. Changes should be made on your own fork in a new branch. The branch should be named `XXX-description` where XXX is the number of the issue. PR should be rebased on top of master without multiple branches mixed into the PR. If your PR do not merge cleanly, use commands listed below to get it up to date.
``` ```
#vmware is the origin upstream #goharbor is the origin upstream
cd $working_dir/kubernetes cd $working_dir/kubernetes
git fetch vmware git fetch goharbor
git checkout master git checkout master
git rebase vmware/master git rebase goharbor/master
``` ```
Branch from the updated `master` branch: Branch from the updated `master` branch:
@ -225,11 +230,12 @@ To build code, please refer to [build](docs/compile_guide.md) guideline.
### Keep sync with upstream ### Keep sync with upstream
Once your branch gets out of sync with the vmware/master branch, use the following commands to update: ```
Once your branch gets out of sync with the goharbor/master branch, use the following commands to update:
``` ```
git checkout my_feature git checkout my_feature
git fetch -a git fetch -a
git rebase vmware/master git rebase goharbor/master
``` ```
@ -237,10 +243,16 @@ Please don't use `git pull` instead of the above `fetch / rebase`. `git pull` do
### Commit ### Commit
As Harbor has integrated the [DCO (Developer Certificate of Origin)](https://probot.github.io/apps/dco/) check tool, contributors are required to sign-off that they adhere to those requirements by adding a `Signed-off-by` line to the commit messages. Git has even provided a `-s` command line option to append that automatically to your commit messages, please use it when you commit your changes.
```bash
$ git commit -s -m 'This is my commit message'
```
Commit your changes if they're ready: Commit your changes if they're ready:
``` ```
#git add -A #git add -A
git commit #-a git commit -s #-a
git push --force-with-lease $user my_feature git push --force-with-lease $user my_feature
``` ```
@ -280,7 +292,7 @@ Commit changes made in response to review comments to the same branch on your fo
It is a great way to contribute to Harbor by reporting an issue. Well-written and complete bug reports are always welcome! Please open an issue on Github and follow the template to fill in required information. It is a great way to contribute to Harbor by reporting an issue. Well-written and complete bug reports are always welcome! Please open an issue on Github and follow the template to fill in required information.
Before opening any issue, please look up the existing [issues](https://github.com/vmware/harbor/issues) to avoid submitting a duplication. Before opening any issue, please look up the existing [issues](https://github.com/goharbor/harbor/issues) to avoid submitting a duplication.
If you find a match, you can "subscribe" to it to get notified on updates. If you have additional helpful information about the issue, please leave a comment. If you find a match, you can "subscribe" to it to get notified on updates. If you have additional helpful information about the issue, please leave a comment.
When reporting issues, always include: When reporting issues, always include:

View File

@ -9,7 +9,7 @@ Harbor is deployed as several Docker containers and most of the code is written
Software | Required Version Software | Required Version
----------------------|-------------------------- ----------------------|--------------------------
docker | 1.12.0 + docker | 17.05 +
docker-compose | 1.11.0 + docker-compose | 1.11.0 +
python | 2.7 + python | 2.7 +
git | 1.9.1 + git | 1.9.1 +

View File

@ -3015,7 +3015,7 @@ paths:
$ref: '#/definitions/InternalChartAPIError' $ref: '#/definitions/InternalChartAPIError'
'507': '507':
$ref: '#/definitions/InsufficientStorageChartAPIError' $ref: '#/definitions/InsufficientStorageChartAPIError'
/chartrepo/:repo/charts/:name/:version/labels: /chartrepo/{repo}/charts/{name}/{version}/labels:
get: get:
summary: Return the attahced labels of chart. summary: Return the attahced labels of chart.
description: Return the attahced labels of the specified chart version. description: Return the attahced labels of the specified chart version.
@ -3094,7 +3094,7 @@ paths:
$ref: '#/definitions/ConflictFormatedError' $ref: '#/definitions/ConflictFormatedError'
'500': '500':
$ref: '#/definitions/InternalChartAPIError' $ref: '#/definitions/InternalChartAPIError'
/chartrepo/:repo/charts/:name/:version/labels/:id: /chartrepo/{repo}/charts/{name}/{version}/labels/{id}:
delete: delete:
summary: Remove label from chart. summary: Remove label from chart.
description: Remove label from the specified chart version. description: Remove label from the specified chart version.

View File

@ -5,6 +5,7 @@ RUN mkdir -p /build_dir
COPY make/photon/portal/entrypoint.sh / COPY make/photon/portal/entrypoint.sh /
COPY src/portal /portal_src COPY src/portal /portal_src
COPY ./docs/swagger.yaml /portal_src
WORKDIR /portal_src WORKDIR /portal_src
@ -24,7 +25,11 @@ RUN tdnf install -y nginx >> /dev/null \
EXPOSE 80 EXPOSE 80
VOLUME /var/cache/nginx /var/log/nginx /run VOLUME /var/cache/nginx /var/log/nginx /run
COPY --from=nodeportal /build_dir/dist /usr/share/nginx/html COPY --from=nodeportal /build_dir/dist /usr/share/nginx/html
COPY --from=nodeportal /build_dir/swagger.yaml /usr/share/nginx/html
COPY --from=nodeportal /build_dir/swagger.json /usr/share/nginx/html
COPY make/photon/portal/nginx.conf /etc/nginx/nginx.conf COPY make/photon/portal/nginx.conf /etc/nginx/nginx.conf
STOPSIGNAL SIGQUIT STOPSIGNAL SIGQUIT

View File

@ -5,6 +5,11 @@ cd /build_dir
cp -r /portal_src/* . cp -r /portal_src/* .
ls -la ls -la
# Update
apt-get update
apt-get install -y ruby
ruby -ryaml -rjson -e 'puts JSON.pretty_generate(YAML.load(ARGF))' swagger.yaml>swagger.json
cat ./package.json cat ./package.json
npm install npm install

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ import (
"strconv" "strconv"
"github.com/astaxie/beego/validation" "github.com/astaxie/beego/validation"
http_error "github.com/goharbor/harbor/src/common/utils/error" commonhttp "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
"github.com/astaxie/beego" "github.com/astaxie/beego"
@ -103,8 +103,8 @@ func (b *BaseAPI) ParseAndHandleError(text string, err error) {
return return
} }
log.Errorf("%s: %v", text, err) log.Errorf("%s: %v", text, err)
if e, ok := err.(*http_error.HTTPError); ok { if e, ok := err.(*commonhttp.Error); ok {
b.RenderError(e.StatusCode, e.Detail) b.RenderError(e.Code, e.Message)
return return
} }
b.RenderError(http.StatusInternalServerError, "") b.RenderError(http.StatusInternalServerError, "")

View File

@ -0,0 +1,83 @@
// 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 encrypt
import (
"os"
"sync"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/log"
)
var (
defaultKeyPath = "/etc/core/key"
)
// Encryptor encrypts or decrypts a strings
type Encryptor interface {
// Encrypt encrypts plaintext
Encrypt(string) (string, error)
// Decrypt decrypts ciphertext
Decrypt(string) (string, error)
}
// AESEncryptor uses AES to encrypt or decrypt string
type AESEncryptor struct {
keyProvider KeyProvider
keyParams map[string]interface{}
}
// NewAESEncryptor returns an instance of an AESEncryptor
func NewAESEncryptor(keyProvider KeyProvider) Encryptor {
return &AESEncryptor{
keyProvider: keyProvider,
}
}
var encryptInstance Encryptor
var encryptOnce sync.Once
// Instance ... Get instance of encryptor
func Instance() Encryptor {
encryptOnce.Do(func() {
kp := os.Getenv("KEY_PATH")
if len(kp) == 0 {
kp = defaultKeyPath
}
log.Infof("the path of key used by key provider: %s", kp)
encryptInstance = NewAESEncryptor(NewFileKeyProvider(kp))
})
return encryptInstance
}
// Encrypt ...
func (a *AESEncryptor) Encrypt(plaintext string) (string, error) {
key, err := a.keyProvider.Get(a.keyParams)
if err != nil {
return "", err
}
return utils.ReversibleEncrypt(plaintext, key)
}
// Decrypt ...
func (a *AESEncryptor) Decrypt(ciphertext string) (string, error) {
key, err := a.keyProvider.Get(a.keyParams)
if err != nil {
return "", err
}
return utils.ReversibleDecrypt(ciphertext, key)
}

View File

@ -0,0 +1,39 @@
package encrypt
import (
"fmt"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"testing"
)
func TestMain(m *testing.M) {
secret := []byte("9TXCcHgNAAp1aSHh")
filename, err := ioutil.TempFile(os.TempDir(), "keyfile")
err = ioutil.WriteFile(filename.Name(), secret, 0644)
if err != nil {
fmt.Printf("failed to create temp key file\n")
}
defer os.Remove(filename.Name())
os.Setenv("KEY_PATH", filename.Name())
ret := m.Run()
os.Exit(ret)
}
func TestEncryptDecrypt(t *testing.T) {
password := "zhu888jie"
encrypted, err := Instance().Encrypt(password)
if err != nil {
t.Errorf("Failed to decrypt password, error %v", err)
}
decrypted, err := Instance().Decrypt(encrypted)
if err != nil {
t.Errorf("Failed to decrypt password, error %v", err)
}
assert.NotEqual(t, password, encrypted)
assert.Equal(t, password, decrypted)
}

View File

@ -0,0 +1,48 @@
// 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 encrypt
import (
"io/ioutil"
)
// KeyProvider provides the key used to encrypt and decrypt attrs
type KeyProvider interface {
// Get returns the key
// params can be used to pass parameters in different implements
Get(params map[string]interface{}) (string, error)
}
// FileKeyProvider reads key from file
type FileKeyProvider struct {
path string
}
// NewFileKeyProvider returns an instance of FileKeyProvider
// path: where the key should be read from
func NewFileKeyProvider(path string) KeyProvider {
return &FileKeyProvider{
path: path,
}
}
// Get returns the key read from file
func (f *FileKeyProvider) Get(params map[string]interface{}) (string, error) {
b, err := ioutil.ReadFile(f.path)
if err != nil {
return "", err
}
return string(b), nil
}

View File

@ -11,20 +11,34 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package error
package encrypt
import ( import (
"io/ioutil"
"os"
"testing" "testing"
) )
func TestError(t *testing.T) { func TestGetOfFileKeyProvider(t *testing.T) {
err := &HTTPError{ path := "/tmp/key"
StatusCode: 404, key := "key_content"
Detail: "not found",
if err := ioutil.WriteFile(path, []byte(key), 0777); err != nil {
t.Errorf("failed to write to file %s: %v", path, err)
return
}
defer os.Remove(path)
provider := NewFileKeyProvider(path)
k, err := provider.Get(nil)
if err != nil {
t.Errorf("failed to get key from the file provider: %v", err)
return
} }
if err.Error() != "404 not found" { if k != key {
t.Fatalf("unexpected content: %s != %s", t.Errorf("unexpected key: %s != %s", k, key)
err.Error(), "404 not found") return
} }
} }

View File

@ -0,0 +1,70 @@
// 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 metadata
import (
"sync"
)
var metaDataOnce sync.Once
var metaDataInstance *CfgMetaData
// Instance - Get Instance, make it singleton because there is only one copy of metadata in an env
func Instance() *CfgMetaData {
metaDataOnce.Do(func() {
metaDataInstance = newCfgMetaData()
metaDataInstance.init()
})
return metaDataInstance
}
func newCfgMetaData() *CfgMetaData {
return &CfgMetaData{metaMap: make(map[string]Item)}
}
// CfgMetaData ...
type CfgMetaData struct {
metaMap map[string]Item
}
// init ...
func (c *CfgMetaData) init() {
c.initFromArray(ConfigList)
}
// initFromArray - Initial metadata from an array
func (c *CfgMetaData) initFromArray(items []Item) {
c.metaMap = make(map[string]Item)
for _, item := range items {
c.metaMap[item.Name] = item
}
}
// GetByName - Get current metadata of current name, if not defined, return false in second params
func (c *CfgMetaData) GetByName(name string) (*Item, bool) {
if item, ok := c.metaMap[name]; ok {
return &item, true
}
return nil, false
}
// GetAll - Get all metadata in current env
func (c *CfgMetaData) GetAll() []Item {
metaDataList := make([]Item, 0)
for _, value := range c.metaMap {
metaDataList = append(metaDataList, value)
}
return metaDataList
}

View File

@ -0,0 +1,51 @@
// 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 metadata
import (
"testing"
)
func TestCfgMetaData_InitFromArray(t *testing.T) {
testArray := []Item{
{Scope: SystemScope, Group: BasicGroup, EnvKey: "HARBOR_ADMIN_PASSWORD", DefaultValue: "", Name: "admin_initial_password", ItemType: &PasswordType{}, Editable: true},
{Scope: SystemScope, Group: BasicGroup, EnvKey: "ADMIRAL_URL", DefaultValue: "NA", Name: "admiral_url", ItemType: &StringType{}, Editable: false},
{Scope: UserScope, Group: BasicGroup, EnvKey: "AUTH_MODE", DefaultValue: "db_auth", Name: "auth_mode", ItemType: &StringType{}, Editable: false},
{Scope: SystemScope, Group: BasicGroup, EnvKey: "CFG_EXPIRATION", DefaultValue: "5", Name: "cfg_expiration", ItemType: &StringType{}, Editable: false},
{Scope: SystemScope, Group: BasicGroup, EnvKey: "CHART_REPOSITORY_URL", DefaultValue: "http://chartmuseum:9999", Name: "chart_repository_url", ItemType: &StringType{}, Editable: false},
}
curInst := Instance()
curInst.initFromArray(testArray)
if len(metaDataInstance.metaMap) != 5 {
t.Errorf("Can not initial metadata, size %v", len(metaDataInstance.metaMap))
}
item, ok := curInst.GetByName("admin_initial_password")
if ok == false {
t.Errorf("Can not get admin_initial_password metadata")
}
if item.Name != "admin_initial_password" {
t.Errorf("Can not get admin_initial_password metadata")
}
}
func TestCfgMetaData_Init(t *testing.T) {
curInst := Instance()
curInst.init()
if len(metaDataInstance.metaMap) < 60 {
t.Errorf("Can not initial metadata, size %v", len(metaDataInstance.metaMap))
}
}

View File

@ -0,0 +1,134 @@
// 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 metadata
import "github.com/goharbor/harbor/src/common"
// Item - Configure item include default value, type, env name
type Item struct {
// The Scope of this configuration item: eg: SystemScope, UserScope
Scope string `json:"scope,omitempty"`
// email, ldapbasic, ldapgroup, uaa settings, used to retieve configure items by group
Group string `json:"group,omitempty"`
// environment key to retrieves this value when initialize, for example: POSTGRESQL_HOST, only used for system settings, for user settings no EnvKey
EnvKey string `json:"environment_key,omitempty"`
// The default string value for this key
DefaultValue string `json:"default_value,omitempty"`
// The key for current configure settings in database or rest api
Name string `json:"name,omitempty"`
// It can be &IntType{}, &StringType{}, &BoolType{}, &PasswordType{}, &MapType{} etc, any type interface implementation
ItemType Type
// Is this settign can be modified after configure
Editable bool `json:"editable,omitempty"`
}
// Constant for configure item
const (
// Scope
UserScope = "user"
SystemScope = "system"
// Group
LdapBasicGroup = "ldapbasic"
LdapGroupGroup = "ldapgroup"
EmailGroup = "email"
UAAGroup = "uaa"
DatabaseGroup = "database"
// Put all config items do not belong a existing group into basic
BasicGroup = "basic"
ClairGroup = "clair"
)
var (
// ConfigList - All configure items used in harbor
// Steps to onboard a new setting
// 1. Add configure item in metadatalist.go
// 2. Get/Set config settings by CfgManager
// 3. CfgManager.Load()/CfgManager.Save() to load/save from configure storage.
ConfigList = []Item{
{Name: "admin_initial_password", Scope: SystemScope, Group: BasicGroup, EnvKey: "HARBOR_ADMIN_PASSWORD", DefaultValue: "", ItemType: &PasswordType{}, Editable: true},
{Name: "admiral_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "ADMIRAL_URL", DefaultValue: "NA", ItemType: &StringType{}, Editable: false},
{Name: "auth_mode", Scope: UserScope, Group: BasicGroup, EnvKey: "AUTH_MODE", DefaultValue: "db_auth", ItemType: &StringType{}, Editable: false},
{Name: "cfg_expiration", Scope: SystemScope, Group: BasicGroup, EnvKey: "CFG_EXPIRATION", DefaultValue: "5", ItemType: &IntType{}, Editable: false},
{Name: "chart_repository_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "CHART_REPOSITORY_URL", DefaultValue: "http://chartmuseum:9999", ItemType: &StringType{}, Editable: false},
{Name: "clair_db", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB", DefaultValue: "postgres", ItemType: &StringType{}, Editable: false},
{Name: "clair_db_host", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_HOST", DefaultValue: "postgresql", ItemType: &StringType{}, Editable: false},
{Name: "clair_db_password", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_PASSWORD", DefaultValue: "root123", ItemType: &PasswordType{}, Editable: false},
{Name: "clair_db_port", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_PORT", DefaultValue: "5432", ItemType: &IntType{}, Editable: false},
{Name: "clair_db_sslmode", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_SSLMODE", DefaultValue: "disable", ItemType: &StringType{}, Editable: false},
{Name: "clair_db_username", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_USERNAME", DefaultValue: "postgres", ItemType: &StringType{}, Editable: false},
{Name: "clair_url", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_URL", DefaultValue: "http://clair:6060", ItemType: &StringType{}, Editable: false},
{Name: "core_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "CORE_URL", DefaultValue: "http://core:8080", ItemType: &StringType{}, Editable: false},
{Name: "database_type", Scope: SystemScope, Group: BasicGroup, EnvKey: "DATABASE_TYPE", DefaultValue: "postgresql", ItemType: &StringType{}, Editable: false},
{Name: "email_from", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_FROM", DefaultValue: "admin <sample_admin@mydomain.com>", ItemType: &StringType{}, Editable: false},
{Name: "email_host", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_HOST", DefaultValue: "smtp.mydomain.com", ItemType: &StringType{}, Editable: false},
{Name: "email_identity", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_IDENTITY", DefaultValue: "", ItemType: &StringType{}, Editable: false},
{Name: "email_insecure", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_INSECURE", DefaultValue: "false", ItemType: &BoolType{}, Editable: false},
{Name: "email_password", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_PWD", DefaultValue: "", ItemType: &PasswordType{}, Editable: false},
{Name: "email_port", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_PORT", DefaultValue: "25", ItemType: &IntType{}, Editable: false},
{Name: "email_ssl", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_SSL", DefaultValue: "false", ItemType: &BoolType{}, Editable: false},
{Name: "email_username", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_USR", DefaultValue: "sample_admin@mydomain.com", ItemType: &StringType{}, Editable: false},
{Name: "ext_endpoint", Scope: SystemScope, Group: BasicGroup, EnvKey: "EXT_ENDPOINT", DefaultValue: "https://host01.com", ItemType: &StringType{}, Editable: false},
{Name: "jobservice_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "JOBSERVICE_URL", DefaultValue: "http://jobservice:8080", ItemType: &StringType{}, Editable: false},
{Name: "ldap_base_dn", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_BASE_DN", DefaultValue: "", ItemType: &StringType{}, Editable: false},
{Name: "ldap_filter", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_FILTER", DefaultValue: "", ItemType: &StringType{}, Editable: false},
{Name: "ldap_group_base_dn", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_BASE_DN", DefaultValue: "", ItemType: &StringType{}, Editable: false},
{Name: "ldap_group_admin_dn", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_ADMIN_DN", DefaultValue: "", ItemType: &StringType{}, Editable: false},
{Name: "ldap_group_attribute_name", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_GID", DefaultValue: "", ItemType: &StringType{}, Editable: false},
{Name: "ldap_group_search_filter", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_FILTER", DefaultValue: "", ItemType: &StringType{}, Editable: false},
{Name: "ldap_group_search_scope", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_SCOPE", DefaultValue: "2", ItemType: &IntType{}, Editable: false},
{Name: "ldap_scope", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_SCOPE", DefaultValue: "2", ItemType: &IntType{}, Editable: true},
{Name: "ldap_search_dn", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_SEARCH_DN", DefaultValue: "", ItemType: &StringType{}, Editable: false},
{Name: "ldap_search_password", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_SEARCH_PWD", DefaultValue: "", ItemType: &PasswordType{}, Editable: false},
{Name: "ldap_timeout", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_TIMEOUT", DefaultValue: "5", ItemType: &IntType{}, Editable: false},
{Name: "ldap_uid", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_UID", DefaultValue: "cn", ItemType: &StringType{}, Editable: true},
{Name: "ldap_url", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_URL", DefaultValue: "", ItemType: &StringType{}, Editable: true},
{Name: "ldap_verify_cert", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_VERIFY_CERT", DefaultValue: "true", ItemType: &BoolType{}, Editable: false},
{Name: "max_job_workers", Scope: SystemScope, Group: BasicGroup, EnvKey: "MAX_JOB_WORKERS", DefaultValue: "10", ItemType: &IntType{}, Editable: false},
{Name: "notary_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "NOTARY_URL", DefaultValue: "http://notary-server:4443", ItemType: &StringType{}, Editable: false},
{Name: "postgresql_database", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_DATABASE", DefaultValue: "registry", ItemType: &StringType{}, Editable: false},
{Name: "postgresql_host", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_HOST", DefaultValue: "postgresql", ItemType: &StringType{}, Editable: false},
{Name: "postgresql_password", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_PASSWORD", DefaultValue: "root123", ItemType: &PasswordType{}, Editable: false},
{Name: "postgresql_port", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_PORT", DefaultValue: "5432", ItemType: &IntType{}, Editable: false},
{Name: "postgresql_sslmode", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_SSLMODE", DefaultValue: "disable", ItemType: &StringType{}, Editable: false},
{Name: "postgresql_username", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_USERNAME", DefaultValue: "postgres", ItemType: &StringType{}, Editable: false},
{Name: "project_creation_restriction", Scope: UserScope, Group: BasicGroup, EnvKey: "PROJECT_CREATION_RESTRICTION", DefaultValue: common.ProCrtRestrEveryone, ItemType: &StringType{}, Editable: false},
{Name: "read_only", Scope: UserScope, Group: BasicGroup, EnvKey: "READ_ONLY", DefaultValue: "false", ItemType: &BoolType{}, Editable: false},
{Name: "registry_storage_provider_name", Scope: SystemScope, Group: BasicGroup, EnvKey: "REGISTRY_STORAGE_PROVIDER_NAME", DefaultValue: "filesystem", ItemType: &StringType{}, Editable: false},
{Name: "registry_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "REGISTRY_URL", DefaultValue: "http://registry:5000", ItemType: &StringType{}, Editable: false},
{Name: "registry_controller_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "REGISTRY_CONTROLLER_URL", DefaultValue: "http://registryctl:8080", ItemType: &StringType{}, Editable: false},
{Name: "self_registration", Scope: UserScope, Group: BasicGroup, EnvKey: "SELF_REGISTRATION", DefaultValue: "true", ItemType: &BoolType{}, Editable: false},
{Name: "token_expiration", Scope: UserScope, Group: BasicGroup, EnvKey: "TOKEN_EXPIRATION", DefaultValue: "30", ItemType: &IntType{}, Editable: false},
{Name: "token_service_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "TOKEN_SERVICE_URL", DefaultValue: "", ItemType: &StringType{}, Editable: false},
{Name: "uaa_client_id", Scope: UserScope, Group: UAAGroup, EnvKey: "UAA_CLIENTID", DefaultValue: "", ItemType: &StringType{}, Editable: false},
{Name: "uaa_client_secret", Scope: UserScope, Group: UAAGroup, EnvKey: "UAA_CLIENTSECRET", DefaultValue: "", ItemType: &StringType{}, Editable: false},
{Name: "uaa_endpoint", Scope: UserScope, Group: UAAGroup, EnvKey: "UAA_ENDPOINT", DefaultValue: "", ItemType: &StringType{}, Editable: false},
{Name: "uaa_verify_cert", Scope: UserScope, Group: UAAGroup, EnvKey: "UAA_VERIFY_CERT", DefaultValue: "false", ItemType: &BoolType{}, Editable: false},
{Name: "with_chartmuseum", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_CHARTMUSEUM", DefaultValue: "false", ItemType: &BoolType{}, Editable: true},
{Name: "with_clair", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_CLAIR", DefaultValue: "true", ItemType: &BoolType{}, Editable: true},
{Name: "with_notary", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_NOTARY", DefaultValue: "false", ItemType: &BoolType{}, Editable: true},
}
)

View File

@ -0,0 +1,122 @@
// 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 metadata
import (
"encoding/json"
"strconv"
)
// Type - Use this interface to define and encapsulate the behavior of validation and transformation
type Type interface {
// validate the configure value
validate(str string) error
// get the real type of current value, if it is int, return int, if it is string return string etc.
get(str string) (interface{}, error)
}
// StringType ...
type StringType struct {
}
func (t *StringType) validate(str string) error {
return nil
}
func (t *StringType) get(str string) (interface{}, error) {
return str, nil
}
// IntType ..
type IntType struct {
}
func (t *IntType) validate(str string) error {
_, err := strconv.Atoi(str)
return err
}
// GetInt ...
func (t *IntType) get(str string) (interface{}, error) {
return strconv.Atoi(str)
}
// LdapScopeType - The LDAP scope is a int type, but its is limit to 0, 1, 2
type LdapScopeType struct {
IntType
}
// Validate - Verify the range is limited
func (t *LdapScopeType) validate(str string) error {
if str == "0" || str == "1" || str == "2" {
return nil
}
return ErrInvalidData
}
// Int64Type ...
type Int64Type struct {
}
func (t *Int64Type) validate(str string) error {
_, err := strconv.ParseInt(str, 10, 64)
return err
}
// GetInt64 ...
func (t *Int64Type) get(str string) (interface{}, error) {
return strconv.ParseInt(str, 10, 64)
}
// BoolType ...
type BoolType struct {
}
func (t *BoolType) validate(str string) error {
_, err := strconv.ParseBool(str)
return err
}
func (t *BoolType) get(str string) (interface{}, error) {
return strconv.ParseBool(str)
}
// PasswordType ...
type PasswordType struct {
}
func (t *PasswordType) validate(str string) error {
return nil
}
func (t *PasswordType) get(str string) (interface{}, error) {
return str, nil
}
// MapType ...
type MapType struct {
}
func (t *MapType) validate(str string) error {
result := map[string]interface{}{}
err := json.Unmarshal([]byte(str), &result)
return err
}
func (t *MapType) get(str string) (interface{}, error) {
result := map[string]string{}
err := json.Unmarshal([]byte(str), &result)
return result, err
}

View File

@ -0,0 +1,98 @@
// 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 metadata
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestIntType_validate(t *testing.T) {
test := &IntType{}
assert.NotNil(t, test.validate("sample"))
assert.Nil(t, test.validate("1000"))
}
func TestIntType_get(t *testing.T) {
test := &IntType{}
result, _ := test.get("1000")
assert.IsType(t, result, 1000)
}
func TestStringType_get(t *testing.T) {
test := &StringType{}
result, _ := test.get("1000")
assert.IsType(t, result, "sample")
}
func TestStringType_validate(t *testing.T) {
test := &StringType{}
assert.Nil(t, test.validate("sample"))
}
func TestLdapScopeType_validate(t *testing.T) {
test := &LdapScopeType{}
assert.NotNil(t, test.validate("3"))
assert.Nil(t, test.validate("2"))
}
func TestInt64Type_validate(t *testing.T) {
test := &Int64Type{}
assert.NotNil(t, test.validate("sample"))
assert.Nil(t, test.validate("1000"))
}
func TestInt64Type_get(t *testing.T) {
test := &Int64Type{}
result, _ := test.get("32")
assert.Equal(t, result, int64(32))
}
func TestBoolType_validate(t *testing.T) {
test := &BoolType{}
assert.NotNil(t, test.validate("sample"))
assert.Nil(t, test.validate("True"))
}
func TestBoolType_get(t *testing.T) {
test := &BoolType{}
result, _ := test.get("true")
assert.Equal(t, result, true)
result, _ = test.get("false")
assert.Equal(t, result, false)
}
func TestPasswordType_validate(t *testing.T) {
test := &PasswordType{}
assert.Nil(t, test.validate("zhu88jie"))
}
func TestPasswordType_get(t *testing.T) {
test := &PasswordType{}
assert.Nil(t, test.validate("zhu88jie"))
}
func TestMapType_validate(t *testing.T) {
test := &MapType{}
assert.Nil(t, test.validate(`{"sample":"abc", "another":"welcome"}`))
assert.NotNil(t, test.validate(`{"sample":"abc", "another":"welcome"`))
}
func TestMapType_get(t *testing.T) {
test := &MapType{}
result, _ := test.get(`{"sample":"abc", "another":"welcome"}`)
assert.Equal(t, result, map[string]string{"sample": "abc", "another": "welcome"})
}

View File

@ -0,0 +1,159 @@
// 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 metadata
import (
"errors"
"github.com/goharbor/harbor/src/common/utils/log"
)
var (
// ErrNotDefined ...
ErrNotDefined = errors.New("configure item is not defined in metadata")
// ErrTypeNotMatch ...
ErrTypeNotMatch = errors.New("the required value doesn't matched with metadata defined")
// ErrInvalidData ...
ErrInvalidData = errors.New("the data provided is invalid")
// ErrValueNotSet ...
ErrValueNotSet = errors.New("the configure value is not set")
)
// ConfigureValue - struct to hold a actual value, also include the name of config metadata.
type ConfigureValue struct {
Name string `json:"name,omitempty"`
Value string `json:"value,omitempty"`
}
// NewConfigureValue ...
func NewConfigureValue(name, value string) *ConfigureValue {
result := &ConfigureValue{}
err := result.Set(name, value)
if err != nil {
log.Errorf("Failed to set name:%v, value:%v, error %v", name, value, err)
result.Name = name // Keep name to trace error
}
return result
}
// GetString - Get the string value of current configure
func (c *ConfigureValue) GetString() string {
// Any type has the string value
if _, ok := Instance().GetByName(c.Name); ok {
return c.Value
}
return ""
}
// GetName ...
func (c *ConfigureValue) GetName() string {
return c.Name
}
// GetInt - return the int value of current value
func (c *ConfigureValue) GetInt() int {
if item, ok := Instance().GetByName(c.Name); ok {
val, err := item.ItemType.get(c.Value)
if err != nil {
log.Errorf("GetInt failed, error: %+v", err)
return 0
}
if intValue, suc := val.(int); suc {
return intValue
}
}
log.Errorf("The current value's metadata is not defined, %+v", c)
return 0
}
// GetInt64 - return the int64 value of current value
func (c *ConfigureValue) GetInt64() int64 {
if item, ok := Instance().GetByName(c.Name); ok {
val, err := item.ItemType.get(c.Value)
if err != nil {
log.Errorf("GetInt64 failed, error: %+v", err)
return 0
}
if int64Value, suc := val.(int64); suc {
return int64Value
}
}
log.Errorf("The current value's metadata is not defined, %+v", c)
return 0
}
// GetBool - return the bool value of current setting
func (c *ConfigureValue) GetBool() bool {
if item, ok := Instance().GetByName(c.Name); ok {
val, err := item.ItemType.get(c.Value)
if err != nil {
log.Errorf("GetBool failed, error: %+v", err)
return false
}
if boolValue, suc := val.(bool); suc {
return boolValue
}
}
log.Errorf("The current value's metadata is not defined, %+v", c)
return false
}
// GetStringToStringMap - return the string to string map of current value
func (c *ConfigureValue) GetStringToStringMap() map[string]string {
result := map[string]string{}
if item, ok := Instance().GetByName(c.Name); ok {
val, err := item.ItemType.get(c.Value)
if err != nil {
log.Errorf("The GetBool failed, error: %+v", err)
return result
}
if mapValue, suc := val.(map[string]string); suc {
return mapValue
}
}
log.Errorf("GetStringToStringMap failed, current value's metadata is not defined, %+v", c)
return result
}
// Validate - to validate configure items, if passed, return nil, else return error
func (c *ConfigureValue) Validate() error {
if item, ok := Instance().GetByName(c.Name); ok {
return item.ItemType.validate(c.Value)
}
return ErrNotDefined
}
// GetPassword ...
func (c *ConfigureValue) GetPassword() string {
if _, ok := Instance().GetByName(c.Name); ok {
return c.Value
}
log.Errorf("GetPassword failed, metadata not defined: %v", c.Name)
return ""
}
// Set - set this configure item to configure store
func (c *ConfigureValue) Set(name, value string) error {
if item, ok := Instance().GetByName(name); ok {
err := item.ItemType.validate(value)
if err == nil {
c.Name = name
c.Value = value
return nil
}
return err
}
return ErrNotDefined
}

View File

@ -0,0 +1,51 @@
// 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 metadata
import (
"github.com/stretchr/testify/assert"
"testing"
)
var testingMetaDataArray = []Item{
{Name: "ldap_search_scope", ItemType: &LdapScopeType{}, Scope: "system", Group: "ldapbasic"},
{Name: "ldap_search_dn", ItemType: &StringType{}, Scope: "user", Group: "ldapbasic"},
{Name: "ulimit", ItemType: &Int64Type{}, Scope: "user", Group: "ldapbasic"},
{Name: "ldap_verify_cert", ItemType: &BoolType{}, Scope: "user", Group: "ldapbasic"},
{Name: "sample_map_setting", ItemType: &MapType{}, Scope: "user", Group: "ldapbasic"},
}
func TestConfigureValue_GetBool(t *testing.T) {
assert.Equal(t, NewConfigureValue("ldap_verify_cert", "true").GetBool(), true)
assert.Equal(t, NewConfigureValue("unknown", "false").GetBool(), false)
}
func TestConfigureValue_GetString(t *testing.T) {
assert.Equal(t, NewConfigureValue("ldap_url", "ldaps://ldap.vmware.com").GetString(), "ldaps://ldap.vmware.com")
}
func TestConfigureValue_GetStringToStringMap(t *testing.T) {
Instance().initFromArray(testingMetaDataArray)
assert.Equal(t, NewConfigureValue("sample_map_setting", `{"sample":"abc"}`).GetStringToStringMap(), map[string]string{"sample": "abc"})
Instance().init()
}
func TestConfigureValue_GetInt(t *testing.T) {
assert.Equal(t, NewConfigureValue("ldap_timeout", "5").GetInt(), 5)
}
func TestConfigureValue_GetInt64(t *testing.T) {
Instance().initFromArray(testingMetaDataArray)
assert.Equal(t, NewConfigureValue("ulimit", "99999").GetInt64(), int64(99999))
}

View File

@ -39,7 +39,11 @@ func NewClient(c *http.Client, modifiers ...modifier.Modifier) *Client {
client: c, client: c,
} }
if client.client == nil { if client.client == nil {
client.client = &http.Client{} client.client = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
}
} }
if len(modifiers) > 0 { if len(modifiers) > 0 {
client.modifiers = modifiers client.modifiers = modifiers

View File

@ -24,9 +24,9 @@ import (
"strings" "strings"
"github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/common"
commonhttp "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/common/utils"
http_error "github.com/goharbor/harbor/src/common/utils/error"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
) )
@ -187,9 +187,9 @@ func send(client *http.Client, req *http.Request) (*AuthContext, error) {
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, &http_error.HTTPError{ return nil, &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(data), Message: string(data),
} }
} }

View File

@ -16,19 +16,7 @@ package error
import ( import (
"errors" "errors"
"fmt"
) )
// ErrDupProject is the error returned when creating a duplicate project // ErrDupProject is the error returned when creating a duplicate project
var ErrDupProject = errors.New("duplicate project") var ErrDupProject = errors.New("duplicate project")
// HTTPError : if response is returned but the status code is not 200, an Error instance will be returned
type HTTPError struct {
StatusCode int
Detail string
}
// Error returns the details as string
func (e *HTTPError) Error() string {
return fmt.Sprintf("%d %s", e.StatusCode, e.Detail)
}

View File

@ -21,8 +21,8 @@ import (
"net/url" "net/url"
"github.com/docker/distribution/registry/auth/token" "github.com/docker/distribution/registry/auth/token"
commonhttp "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
registry_error "github.com/goharbor/harbor/src/common/utils/error"
"github.com/goharbor/harbor/src/common/utils/registry" "github.com/goharbor/harbor/src/common/utils/registry"
) )
@ -73,9 +73,9 @@ func getToken(client *http.Client, credential Credential, realm, service string,
return nil, err return nil, err
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, &registry_error.HTTPError{ return nil, &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(data), Message: string(data),
} }
} }

View File

@ -21,10 +21,11 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
// "time" // "time"
commonhttp "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/common/utils"
registry_error "github.com/goharbor/harbor/src/common/utils/error"
) )
// Registry holds information of a registry entity // Registry holds information of a registry entity
@ -39,11 +40,13 @@ func init() {
defaultHTTPTransport = &http.Transport{} defaultHTTPTransport = &http.Transport{}
secureHTTPTransport = &http.Transport{ secureHTTPTransport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
InsecureSkipVerify: false, InsecureSkipVerify: false,
}, },
} }
insecureHTTPTransport = &http.Transport{ insecureHTTPTransport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: true,
}, },
@ -118,9 +121,9 @@ func (r *Registry) Catalog() ([]string, error) {
suffix = "" suffix = ""
} }
} else { } else {
return repos, &registry_error.HTTPError{ return repos, &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
} }
} }
@ -149,8 +152,8 @@ func (r *Registry) Ping() error {
return err return err
} }
return &registry_error.HTTPError{ return &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
} }

View File

@ -30,8 +30,8 @@ import (
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
commonhttp "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/common/utils"
registry_error "github.com/goharbor/harbor/src/common/utils/error"
) )
// Repository holds information of a repository entity // Repository holds information of a repository entity
@ -61,7 +61,7 @@ func NewRepository(name, endpoint string, client *http.Client) (*Repository, err
func parseError(err error) error { func parseError(err error) error {
if urlErr, ok := err.(*url.Error); ok { if urlErr, ok := err.(*url.Error); ok {
if regErr, ok := urlErr.Err.(*registry_error.HTTPError); ok { if regErr, ok := urlErr.Err.(*commonhttp.Error); ok {
return regErr return regErr
} }
} }
@ -109,9 +109,9 @@ func (r *Repository) ListTag() ([]string, error) {
return tags, nil return tags, nil
} }
return tags, &registry_error.HTTPError{ return tags, &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
} }
@ -149,9 +149,9 @@ func (r *Repository) ManifestExist(reference string) (digest string, exist bool,
return return
} }
err = &registry_error.HTTPError{ err = &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
return return
} }
@ -186,9 +186,9 @@ func (r *Repository) PullManifest(reference string, acceptMediaTypes []string) (
return return
} }
err = &registry_error.HTTPError{ err = &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
return return
@ -221,9 +221,9 @@ func (r *Repository) PushManifest(reference, mediaType string, payload []byte) (
return return
} }
err = &registry_error.HTTPError{ err = &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
return return
@ -252,9 +252,9 @@ func (r *Repository) DeleteManifest(digest string) error {
return err return err
} }
return &registry_error.HTTPError{ return &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
} }
@ -288,8 +288,8 @@ func (r *Repository) DeleteTag(tag string) error {
} }
if !exist { if !exist {
return &registry_error.HTTPError{ return &commonhttp.Error{
StatusCode: http.StatusNotFound, Code: http.StatusNotFound,
} }
} }
@ -323,9 +323,9 @@ func (r *Repository) BlobExist(digest string) (bool, error) {
return false, err return false, err
} }
return false, &registry_error.HTTPError{ return false, &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
} }
@ -359,9 +359,9 @@ func (r *Repository) PullBlob(digest string) (size int64, data io.ReadCloser, er
return return
} }
err = &registry_error.HTTPError{ err = &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
return return
@ -390,9 +390,9 @@ func (r *Repository) initiateBlobUpload(name string) (location, uploadUUID strin
return return
} }
err = &registry_error.HTTPError{ err = &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
return return
@ -424,9 +424,9 @@ func (r *Repository) monolithicBlobUpload(location, digest string, size int64, d
return err return err
} }
return &registry_error.HTTPError{ return &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
} }
@ -462,9 +462,9 @@ func (r *Repository) DeleteBlob(digest string) error {
return err return err
} }
return &registry_error.HTTPError{ return &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
} }

View File

@ -29,7 +29,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
registry_error "github.com/goharbor/harbor/src/common/utils/error" commonhttp "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/utils/test" "github.com/goharbor/harbor/src/common/utils/test"
) )
@ -392,10 +392,10 @@ func TestListTag(t *testing.T) {
func TestParseError(t *testing.T) { func TestParseError(t *testing.T) {
err := &url.Error{ err := &url.Error{
Err: &registry_error.HTTPError{}, Err: &commonhttp.Error{},
} }
e := parseError(err) e := parseError(err)
if _, ok := e.(*registry_error.HTTPError); !ok { if _, ok := e.(*commonhttp.Error); !ok {
t.Errorf("error type does not match registry error") t.Errorf("error type does not match registry error")
} }
} }

View File

@ -41,7 +41,7 @@ const (
UsersURLSuffix = "/Users" UsersURLSuffix = "/Users"
) )
var uaaTransport = &http.Transport{} var uaaTransport = &http.Transport{Proxy: http.ProxyFromEnvironment}
// Client provides funcs to interact with UAA. // Client provides funcs to interact with UAA.
type Client interface { type Client interface {

View File

@ -32,7 +32,6 @@ import (
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/clair" "github.com/goharbor/harbor/src/common/utils/clair"
registry_error "github.com/goharbor/harbor/src/common/utils/error"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/common/utils/notary" "github.com/goharbor/harbor/src/common/utils/notary"
"github.com/goharbor/harbor/src/common/utils/registry" "github.com/goharbor/harbor/src/common/utils/registry"
@ -265,8 +264,8 @@ func (ra *RepositoryAPI) Delete() {
if err != nil { if err != nil {
log.Errorf("error occurred while listing tags of %s: %v", repoName, err) log.Errorf("error occurred while listing tags of %s: %v", repoName, err)
if regErr, ok := err.(*registry_error.HTTPError); ok { if regErr, ok := err.(*commonhttp.Error); ok {
ra.CustomAbort(regErr.StatusCode, regErr.Detail) ra.CustomAbort(regErr.Code, regErr.Message)
} }
ra.CustomAbort(http.StatusInternalServerError, "internal error") ra.CustomAbort(http.StatusInternalServerError, "internal error")
@ -312,12 +311,12 @@ func (ra *RepositoryAPI) Delete() {
return return
} }
if err = rc.DeleteTag(t); err != nil { if err = rc.DeleteTag(t); err != nil {
if regErr, ok := err.(*registry_error.HTTPError); ok { if regErr, ok := err.(*commonhttp.Error); ok {
if regErr.StatusCode == http.StatusNotFound { if regErr.Code == http.StatusNotFound {
continue continue
} }
log.Errorf("failed to delete tag %s: %v", t, err) log.Errorf("failed to delete tag %s: %v", t, err)
ra.CustomAbort(regErr.StatusCode, regErr.Detail) ra.CustomAbort(regErr.Code, regErr.Message)
} }
log.Errorf("error occurred while deleting tag %s:%s: %v", repoName, t, err) log.Errorf("error occurred while deleting tag %s:%s: %v", repoName, t, err)
ra.CustomAbort(http.StatusInternalServerError, "internal error") ra.CustomAbort(http.StatusInternalServerError, "internal error")
@ -751,8 +750,8 @@ func (ra *RepositoryAPI) GetManifests() {
if err != nil { if err != nil {
log.Errorf("error occurred while getting manifest of %s:%s: %v", repoName, tag, err) log.Errorf("error occurred while getting manifest of %s:%s: %v", repoName, tag, err)
if regErr, ok := err.(*registry_error.HTTPError); ok { if regErr, ok := err.(*commonhttp.Error); ok {
ra.CustomAbort(regErr.StatusCode, regErr.Detail) ra.CustomAbort(regErr.Code, regErr.Message)
} }
ra.CustomAbort(http.StatusInternalServerError, "internal error") ra.CustomAbort(http.StatusInternalServerError, "internal error")

View File

@ -21,10 +21,10 @@ import (
"strings" "strings"
"github.com/goharbor/harbor/src/common/dao" "github.com/goharbor/harbor/src/common/dao"
commonhttp "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/clair" "github.com/goharbor/harbor/src/common/utils/clair"
registry_error "github.com/goharbor/harbor/src/common/utils/error"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/common/utils/registry" "github.com/goharbor/harbor/src/common/utils/registry"
"github.com/goharbor/harbor/src/common/utils/registry/auth" "github.com/goharbor/harbor/src/common/utils/registry/auth"
@ -273,7 +273,7 @@ func buildReplicationActionURL() string {
func repositoryExist(name string, client *registry.Repository) (bool, error) { func repositoryExist(name string, client *registry.Repository) (bool, error) {
tags, err := client.ListTag() tags, err := client.ListTag()
if err != nil { if err != nil {
if regErr, ok := err.(*registry_error.HTTPError); ok && regErr.StatusCode == http.StatusNotFound { if regErr, ok := err.(*commonhttp.Error); ok && regErr.Code == http.StatusNotFound {
return false, nil return false, nil
} }
return false, err return false, err

View File

@ -135,6 +135,7 @@ func initProjectManager() error {
} }
AdmiralClient = &http.Client{ AdmiralClient = &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
RootCAs: pool, RootCAs: pool,
}, },

View File

@ -26,6 +26,7 @@ import (
"strconv" "strconv"
"strings" "strings"
commonhttp "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/common/utils"
er "github.com/goharbor/harbor/src/common/utils/error" er "github.com/goharbor/harbor/src/common/utils/error"
@ -286,20 +287,20 @@ func (d *driver) Create(pro *models.Project) (int64, error) {
// Maybe a 409 error will be returned if Admiral team finds the way to // Maybe a 409 error will be returned if Admiral team finds the way to
// return a specific code in Xenon. // return a specific code in Xenon.
// The following codes convert both those two errors to DupProjectErr // The following codes convert both those two errors to DupProjectErr
httpErr, ok := err.(*er.HTTPError) httpErr, ok := err.(*commonhttp.Error)
if !ok { if !ok {
return 0, err return 0, err
} }
if httpErr.StatusCode == http.StatusConflict { if httpErr.Code == http.StatusConflict {
return 0, er.ErrDupProject return 0, er.ErrDupProject
} }
if httpErr.StatusCode != http.StatusInternalServerError { if httpErr.Code != http.StatusInternalServerError {
return 0, err return 0, err
} }
match, e := regexp.MatchString(dupProjectPattern, httpErr.Detail) match, e := regexp.MatchString(dupProjectPattern, httpErr.Message)
if e != nil { if e != nil {
log.Errorf("failed to match duplicate project mattern: %v", e) log.Errorf("failed to match duplicate project mattern: %v", e)
} }
@ -397,9 +398,9 @@ func (d *driver) send(method, path string, body io.Reader) ([]byte, error) {
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, &er.HTTPError{ return nil, &commonhttp.Error{
StatusCode: resp.StatusCode, Code: resp.StatusCode,
Detail: string(b), Message: string(b),
} }
} }

View File

@ -230,6 +230,7 @@ func getDBFromConfig(cfg map[string]interface{}) *models.Database {
postgresql.Username = cfg[common.PostGreSQLUsername].(string) postgresql.Username = cfg[common.PostGreSQLUsername].(string)
postgresql.Password = cfg[common.PostGreSQLPassword].(string) postgresql.Password = cfg[common.PostGreSQLPassword].(string)
postgresql.Database = cfg[common.PostGreSQLDatabase].(string) postgresql.Database = cfg[common.PostGreSQLDatabase].(string)
postgresql.SSLMode = cfg[common.PostGreSQLSSLMode].(string)
database.PostGreSQL = postgresql database.PostGreSQL = postgresql
return database return database

View File

@ -24,6 +24,7 @@
"styles": [ "styles": [
"node_modules/@clr/icons/clr-icons.min.css", "node_modules/@clr/icons/clr-icons.min.css",
"node_modules/@clr/ui/clr-ui.min.css", "node_modules/@clr/ui/clr-ui.min.css",
"node_modules/swagger-ui/dist/swagger-ui.css",
"node_modules/prismjs/themes/prism-solarizedlight.css", "node_modules/prismjs/themes/prism-solarizedlight.css",
"src/styles.css" "src/styles.css"
], ],
@ -35,7 +36,10 @@
"node_modules/web-animations-js/web-animations.min.js", "node_modules/web-animations-js/web-animations.min.js",
"node_modules/marked/lib/marked.js", "node_modules/marked/lib/marked.js",
"node_modules/prismjs/prism.js", "node_modules/prismjs/prism.js",
"node_modules/prismjs/components/prism-yaml.min.js" "node_modules/prismjs/components/prism-yaml.min.js",
"node_modules/jquery/dist/jquery.slim.js",
"node_modules/popper.js/dist/umd/popper.js",
"node_modules/bootstrap/dist/js/bootstrap.js"
] ]
}, },
"configurations": { "configurations": {

View File

@ -1,8 +1,7 @@
<div> <div>
<h2 class="h2-log-override" *ngIf="withTitle">{{'SIDE_NAV.LOGS' | translate}}</h2>
<div class="row flex-items-xs-between flex-items-xs-bottom"> <div class="row flex-items-xs-between flex-items-xs-bottom">
<div></div> <div></div>
<div class="action-head-pos rightPos"> <div class="action-head-pos">
<div class="select filterTag" [hidden]="!isOpenFilterTag"> <div class="select filterTag" [hidden]="!isOpenFilterTag">
<select id="selectKey" (change)="selectFilterKey($event)"> <select id="selectKey" (change)="selectFilterKey($event)">
<option value="username">{{"AUDIT_LOG.USERNAME" | translate | lowercase}}</option> <option value="username">{{"AUDIT_LOG.USERNAME" | translate | lowercase}}</option>

View File

@ -92,7 +92,7 @@ export const LabelColor = [
export const RoleMapping = { 'projectAdmin': 'MEMBER.PROJECT_ADMIN', 'developer': 'MEMBER.DEVELOPER', 'guest': 'MEMBER.GUEST' }; export const RoleMapping = { 'projectAdmin': 'MEMBER.PROJECT_ADMIN', 'developer': 'MEMBER.DEVELOPER', 'guest': 'MEMBER.GUEST' };
export const DefaultHelmIcon = '/images/helm-gray.png'; export const DefaultHelmIcon = '/images/helm-gray.svg';
export enum Roles { export enum Roles {
PROJECT_ADMIN = 1, PROJECT_ADMIN = 1,

View File

@ -75,7 +75,7 @@
</div> </div>
</clr-dropdown-menu> </clr-dropdown-menu>
</clr-dropdown> </clr-dropdown>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length===1 && developerRoleOrAbove)" (click)="retag(selectedRow)"><clr-icon shape="copy" size="16"></clr-icon>&nbsp;{{'REPOSITORY.RETAG' | translate}}</button> <button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length===1 && guestRoleOrAbove)" (click)="retag(selectedRow)"><clr-icon shape="copy" size="16"></clr-icon>&nbsp;{{'REPOSITORY.RETAG' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasProjectAdminRole" (click)="deleteTags(selectedRow)" [disabled]="!selectedRow.length"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.DELETE' | translate}}</button> <button type="button" class="btn btn-sm btn-secondary" *ngIf="hasProjectAdminRole" (click)="deleteTags(selectedRow)" [disabled]="!selectedRow.length"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.DELETE' | translate}}</button>
</clr-dg-action-bar> </clr-dg-action-bar>
<clr-dg-column class="width-130" [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column> <clr-dg-column class="width-130" [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column>

View File

@ -763,4 +763,8 @@ export class TagComponent implements OnInit, AfterViewInit {
public get developerRoleOrAbove(): boolean { public get developerRoleOrAbove(): boolean {
return this.memberRoleID === Roles.DEVELOPER || this.hasProjectAdminRole; return this.memberRoleID === Roles.DEVELOPER || this.hasProjectAdminRole;
} }
public get guestRoleOrAbove(): boolean {
return this.memberRoleID === Roles.GUEST || this.memberRoleID === Roles.DEVELOPER || this.hasProjectAdminRole;
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -32,18 +32,26 @@
"@clr/angular": "^0.12.10", "@clr/angular": "^0.12.10",
"@clr/icons": "^0.12.0", "@clr/icons": "^0.12.0",
"@clr/ui": "^0.12.0", "@clr/ui": "^0.12.0",
"@fortawesome/fontawesome-free": "^5.1.0-4",
"@ng-bootstrap/ng-bootstrap": "^2.0.0",
"@ngx-translate/core": "^10.0.2", "@ngx-translate/core": "^10.0.2",
"@ngx-translate/http-loader": "^3.0.1", "@ngx-translate/http-loader": "^3.0.1",
"@types/jquery": "^2.0.41", "@types/jquery": "^2.0.41",
"@webcomponents/custom-elements": "^1.1.3", "@webcomponents/custom-elements": "^1.1.3",
"bootstrap": "^4.1.1",
"buffer": "^5.2.1",
"core-js": "^2.5.4", "core-js": "^2.5.4",
"intl": "^1.2.5", "intl": "^1.2.5",
"jquery": "^3.3.1",
"mutationobserver-shim": "^0.3.2", "mutationobserver-shim": "^0.3.2",
"ng-packagr": "^4.1.1", "ng-packagr": "^4.1.1",
"ngx-clipboard": "^11.1.1", "ngx-clipboard": "^11.1.1",
"ngx-cookie": "^1.0.0", "ngx-cookie": "^1.0.0",
"ngx-markdown": "^6.2.0", "ngx-markdown": "^6.2.0",
"popper.js": "^1.14.3",
"rxjs": "^6.3.1", "rxjs": "^6.3.1",
"stream": "^0.0.2",
"swagger-ui": "^3.20.2",
"ts-helpers": "^1.1.1", "ts-helpers": "^1.1.1",
"tslib": "^1.9.0", "tslib": "^1.9.0",
"types": "^0.1.1", "types": "^0.1.1",

View File

@ -42,7 +42,7 @@ export class AppConfig {
this.project_creation_restriction = "everyone"; this.project_creation_restriction = "everyone";
this.self_registration = true; this.self_registration = true;
this.has_ca_root = false; this.has_ca_root = false;
this.harbor_version = "1.2.0"; this.harbor_version = "unknown";
this.clair_vulnerability_status = { this.clair_vulnerability_status = {
overall_last_update: 0, overall_last_update: 0,
details: [] details: []

View File

@ -20,6 +20,7 @@ import { HarborRoutingModule } from './harbor-routing.module';
import { SharedModule } from './shared/shared.module'; import { SharedModule } from './shared/shared.module';
import { AccountModule } from './account/account.module'; import { AccountModule } from './account/account.module';
import { ConfigurationModule } from './config/config.module'; import { ConfigurationModule } from './config/config.module';
import { DeveloperCenterModule } from './dev-center/dev-center.module';
import { registerLocaleData } from '@angular/common'; import { registerLocaleData } from '@angular/common';
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from "@ngx-translate/core";
@ -30,6 +31,7 @@ import { ProjectConfigComponent } from './project/project-config/project-config.
import zh from '@angular/common/locales/zh-Hans'; import zh from '@angular/common/locales/zh-Hans';
import es from '@angular/common/locales/es'; import es from '@angular/common/locales/es';
import localeFr from '@angular/common/locales/fr'; import localeFr from '@angular/common/locales/fr';
import { DevCenterComponent } from './dev-center/dev-center.component';
registerLocaleData(zh, 'zh-cn'); registerLocaleData(zh, 'zh-cn');
registerLocaleData(es, 'es-es'); registerLocaleData(es, 'es-es');
registerLocaleData(localeFr, 'fr-fr'); registerLocaleData(localeFr, 'fr-fr');
@ -49,7 +51,7 @@ export function getCurrentLanguage(translateService: TranslateService) {
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
ProjectConfigComponent, ProjectConfigComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -57,7 +59,8 @@ export function getCurrentLanguage(translateService: TranslateService) {
BaseModule, BaseModule,
AccountModule, AccountModule,
HarborRoutingModule, HarborRoutingModule,
ConfigurationModule ConfigurationModule,
DeveloperCenterModule
], ],
exports: [ exports: [
], ],

View File

@ -26,7 +26,6 @@ import { GlobalSearchComponent } from './global-search/global-search.component';
import { FooterComponent } from './footer/footer.component'; import { FooterComponent } from './footer/footer.component';
import { HarborShellComponent } from './harbor-shell/harbor-shell.component'; import { HarborShellComponent } from './harbor-shell/harbor-shell.component';
import { SearchResultComponent } from './global-search/search-result.component'; import { SearchResultComponent } from './global-search/search-result.component';
import { StartPageComponent } from './start-page/start.component';
import { SearchTriggerService } from './global-search/search-trigger.service'; import { SearchTriggerService } from './global-search/search-trigger.service';
@ -46,7 +45,6 @@ import { SearchTriggerService } from './global-search/search-trigger.service';
FooterComponent, FooterComponent,
HarborShellComponent, HarborShellComponent,
SearchResultComponent, SearchResultComponent,
StartPageComponent
], ],
exports: [ HarborShellComponent ], exports: [ HarborShellComponent ],
providers: [SearchTriggerService] providers: [SearchTriggerService]

View File

@ -2,7 +2,8 @@
<global-message [isAppLevel]="true"></global-message> <global-message [isAppLevel]="true"></global-message>
<navigator (showAccountSettingsModal)="openModal($event)" (showPwdChangeModal)="openModal($event)"></navigator> <navigator (showAccountSettingsModal)="openModal($event)" (showPwdChangeModal)="openModal($event)"></navigator>
<div class="content-container"> <div class="content-container">
<div class="content-area" [class.container-override]="showSearch" [class.content-area-override]="!shouldOverrideContent" [class.start-content-padding]="shouldOverrideContent"> <div class="content-area" [class.container-override]="showSearch" [class.content-area-override]="!shouldOverrideContent"
[class.start-content-padding]="shouldOverrideContent">
<global-message [isAppLevel]="false"></global-message> <global-message [isAppLevel]="false"></global-message>
<!-- Only appear when searching --> <!-- Only appear when searching -->
<search-result></search-result> <search-result></search-result>
@ -17,39 +18,32 @@
<clr-icon shape="list" clrVerticalNavIcon></clr-icon> <clr-icon shape="list" clrVerticalNavIcon></clr-icon>
{{'SIDE_NAV.LOGS' | translate}} {{'SIDE_NAV.LOGS' | translate}}
</a> </a>
<a clrVerticalNavLink target="_blank" routerLink="/devcenter" routerLinkActive="active">
<clr-icon shape="terminal" clrVerticalNavIcon></clr-icon>
{{'SIDE_NAV.SYSTEM_MGMT.DEVCENTER' | translate}}
</a>
<clr-vertical-nav-group *ngIf="isSystemAdmin" routerLinkActive="active"> <clr-vertical-nav-group *ngIf="isSystemAdmin" routerLinkActive="active">
<clr-icon shape="administrator" clrVerticalNavIcon></clr-icon> <clr-icon shape="administrator" clrVerticalNavIcon></clr-icon>
{{'SIDE_NAV.SYSTEM_MGMT.NAME' | translate}} {{'SIDE_NAV.SYSTEM_MGMT.NAME' | translate}}
<a routerLink="#" hidden aria-hidden="true"></a> <a routerLink="#" hidden aria-hidden="true"></a>
<clr-vertical-nav-group-children *clrIfExpanded="true"> <clr-vertical-nav-group-children *clrIfExpanded="true">
<a clrVerticalNavLink <a clrVerticalNavLink routerLink="/harbor/users" routerLinkActive="active">
routerLink="/harbor/users"
routerLinkActive="active">
<clr-icon shape="users" clrVerticalNavIcon></clr-icon> <clr-icon shape="users" clrVerticalNavIcon></clr-icon>
{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}} {{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}
</a> </a>
<a *ngIf='isLdapMode' <a *ngIf='isLdapMode' clrVerticalNavLink routerLink="/harbor/groups" routerLinkActive="active">
clrVerticalNavLink
routerLink="/harbor/groups"
routerLinkActive="active">
<clr-icon shape="users" clrVerticalNavIcon></clr-icon> <clr-icon shape="users" clrVerticalNavIcon></clr-icon>
{{'SIDE_NAV.SYSTEM_MGMT.GROUP' | translate}} {{'SIDE_NAV.SYSTEM_MGMT.GROUP' | translate}}
</a> </a>
<a clrVerticalNavLink <a clrVerticalNavLink routerLink="/harbor/registries" routerLinkActive="active">
routerLink="/harbor/registries"
routerLinkActive="active">
<clr-icon shape="block" clrVerticalNavIcon></clr-icon> <clr-icon shape="block" clrVerticalNavIcon></clr-icon>
{{'SIDE_NAV.SYSTEM_MGMT.REGISTRY' | translate}} {{'SIDE_NAV.SYSTEM_MGMT.REGISTRY' | translate}}
</a> </a>
<a clrVerticalNavLink <a clrVerticalNavLink routerLink="/harbor/replications" routerLinkActive="active">
routerLink="/harbor/replications"
routerLinkActive="active">
<clr-icon shape="cloud-traffic" clrVerticalNavIcon></clr-icon> <clr-icon shape="cloud-traffic" clrVerticalNavIcon></clr-icon>
{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}} {{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}
</a> </a>
<a clrVerticalNavLink <a clrVerticalNavLink routerLink="/harbor/configs" routerLinkActive="active">
routerLink="/harbor/configs"
routerLinkActive="active">
<clr-icon shape="cog" clrVerticalNavIcon></clr-icon> <clr-icon shape="cog" clrVerticalNavIcon></clr-icon>
{{'SIDE_NAV.SYSTEM_MGMT.CONFIG' | translate}} {{'SIDE_NAV.SYSTEM_MGMT.CONFIG' | translate}}
</a> </a>

View File

@ -2,7 +2,7 @@
<div class="branding"> <div class="branding">
<a href="javascript:void(0)" class="nav-link" (click)="homeAction()"> <a href="javascript:void(0)" class="nav-link" (click)="homeAction()">
<!-- <clr-icon shape="vm-bug" *ngIf="!customStyle?.headerLogo"></clr-icon> --> <!-- <clr-icon shape="vm-bug" *ngIf="!customStyle?.headerLogo"></clr-icon> -->
<img src="../../../images/harbor-white-logo.svg" class="harbor-logo" /> <img [src]="'images/harbor-logo.svg'" class="harbor-logo" />
<img [attr.src]="'static/images/'+customStyle?.headerLogo" *ngIf="customStyle?.headerLogo" class="headerLogo"> <img [attr.src]="'static/images/'+customStyle?.headerLogo" *ngIf="customStyle?.headerLogo" class="headerLogo">
<span class="title">{{customProjectName?.projectName? customProjectName?.projectName:(appTitle | translate)}}</span> <span class="title">{{customProjectName?.projectName? customProjectName?.projectName:(appTitle | translate)}}</span>
</a> </a>

View File

@ -1,29 +0,0 @@
<!-- Authenticated-->
<div class="row row-fill-height row-margin" *ngIf="isSessionValid">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
<statistics-panel></statistics-panel>
<top-repo></top-repo>
</div>
</div>
<!-- Guest -->
<div class="row row-fill-height" *ngIf="!isSessionValid">
<div class="col-xs-12 col-sm-12 col-md-5 col-lg-5 col-xl-5 column-fill-height">
<div class="start-card">
<div class="card-img my-card-img">
</div>
<div class="card-block">
<h3 class="card-title">Getting Start</h3>
<p class="card-text">
{{'START_PAGE.GETTING_START' | translate}}
</p>
</div>
<div class="card-footer my-card-footer">
<a href="https://goharbor.io" target="_blank" class="btn btn-sm btn-link">Learn More</a>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-7 col-lg-7 col-xl-7">
<top-repo></top-repo>
</div>
</div>

View File

@ -1,30 +0,0 @@
.start-card {
border-right: 1px solid #cccccc;
padding: 24px;
background-color: white;
height: 100%;
}
.row-fill-height {
height: 100%;
}
.row-margin {
margin-left: 24px;
}
.column-fill-height {
height: 100%;
}
.my-card-img {
background-image: url('../../../images/harbor-logo.png');
background-repeat: no-repeat;
background-size: contain;
height: 160px;
}
.my-card-footer {
float: right;
margin-top: 100px;
}

View File

@ -1,33 +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.
import { Component, OnInit } from '@angular/core';
import { SessionService } from '../../shared/session.service';
@Component({
selector: 'start-page',
templateUrl: "start.component.html",
styleUrls: ['start.component.scss']
})
export class StartPageComponent implements OnInit {
isSessionValid: boolean = false;
constructor(
private session: SessionService
) { }
ngOnInit(): void {
this.isSessionValid = this.session.getCurrentUser() != null;
}
}

View File

@ -0,0 +1,2 @@
<div class="swagger-container" style="overflow:scroll;"></div>

View File

@ -0,0 +1,8 @@
.swagger-container {
overflow: auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DevCenterComponent } from './dev-center.component';
describe('DevCenterComponent', () => {
let component: DevCenterComponent;
let fixture: ComponentFixture<DevCenterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DevCenterComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DevCenterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,37 @@
import { AfterViewInit, Component, ElementRef, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import { throwError as observableThrowError, Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
const SwaggerUI = require('swagger-ui');
@Component({
selector: 'dev-center',
templateUrl: 'dev-center.component.html',
styleUrls: ['dev-center.component.scss']
})
export class DevCenterComponent implements AfterViewInit {
private ui: any;
private host: any;
private json: any;
constructor(private el: ElementRef, private http: Http) {
}
ngAfterViewInit() {
this.http.get("/swagger.json")
.pipe(catchError(error => observableThrowError(error)))
.pipe(map(response => response.json())).subscribe(json => {
json.host = window.location.host;
const protocal = window.location.protocol;
json.schemes = [protocal.replace(":", "")];
let ui = SwaggerUI({
spec: json,
domNode: this.el.nativeElement.querySelector('.swagger-container'),
deepLinking: true,
presets: [
SwaggerUI.presets.apis
],
});
});
}
}

View File

@ -0,0 +1,23 @@
import { NgModule } from "@angular/core";
import { RouterModule } from "@angular/router";
import { CommonModule } from "@angular/common";
import { ClarityModule } from '@clr/angular';
import { SharedModule } from '../shared/shared.module';
import { DevCenterComponent } from "./dev-center.component";
@NgModule({
imports: [
CommonModule,
SharedModule,
RouterModule.forChild([{
path: "**",
component: DevCenterComponent,
}]),
ClarityModule.forRoot(),
],
declarations: [
DevCenterComponent,
],
})
export class DeveloperCenterModule {}

View File

@ -22,6 +22,7 @@ import { MemberGuard } from './shared/route/member-guard-activate.service';
import { PageNotFoundComponent } from './shared/not-found/not-found.component'; import { PageNotFoundComponent } from './shared/not-found/not-found.component';
import { HarborShellComponent } from './base/harbor-shell/harbor-shell.component'; import { HarborShellComponent } from './base/harbor-shell/harbor-shell.component';
import { ConfigurationComponent } from './config/config.component'; import { ConfigurationComponent } from './config/config.component';
import { DevCenterComponent } from './dev-center/dev-center.component';
import { UserComponent } from './user/user.component'; import { UserComponent } from './user/user.component';
import { SignInComponent } from './account/sign-in/sign-in.component'; import { SignInComponent } from './account/sign-in/sign-in.component';
@ -53,6 +54,10 @@ import { ChartDetailComponent } from './project/chart-detail/chart-detail.compon
const harborRoutes: Routes = [ const harborRoutes: Routes = [
{ path: '', redirectTo: 'harbor', pathMatch: 'full' }, { path: '', redirectTo: 'harbor', pathMatch: 'full' },
{ path: 'reset_password', component: ResetPasswordComponent }, { path: 'reset_password', component: ResetPasswordComponent },
{
path: 'devcenter',
component: DevCenterComponent
},
{ {
path: 'harbor', path: 'harbor',
component: HarborShellComponent, component: HarborShellComponent,

View File

@ -1,7 +1,7 @@
<clr-modal [(clrModalOpen)]="opened" [clrModalClosable]="false" [clrModalStaticBackdrop]="false"> <clr-modal [(clrModalOpen)]="opened" [clrModalClosable]="false" [clrModalStaticBackdrop]="false">
<div class="modal-body dialog-body"> <div class="modal-body dialog-body">
<div class="harbor-logo-black"> <div class="harbor-logo-black">
<img [src]="'images/harbor-black-logo.png'"> <img [src]="'images/harbor-logo.svg'" class="harbor-icon">
</div> </div>
<div class="content"> <div class="content">
<div>{{customName?.projectName? customName?.projectName : ('APP_TITLE.HARBOR' | translate)}}</div> <div>{{customName?.projectName? customName?.projectName : ('APP_TITLE.HARBOR' | translate)}}</div>

View File

@ -16,3 +16,9 @@
.content { .content {
margin:0 10px 10px 10px; margin:0 10px 10px 10px;
} }
.harbor-icon {
transform: translateX(-100%);
width: 56px;
filter: drop-shadow(rgb(0, 0, 0) 58px 2px);
}

View File

@ -47,7 +47,7 @@ export class AboutDialogComponent implements OnInit {
public get version(): string { public get version(): string {
let appConfig = this.appConfigService.getConfig(); let appConfig = this.appConfigService.getConfig();
return appConfig ? appConfig.harbor_version : "n/a"; return appConfig.harbor_version;
} }
public open(): void { public open(): void {

View File

@ -121,7 +121,8 @@
"GROUP": "Groups", "GROUP": "Groups",
"REGISTRY": "Registries", "REGISTRY": "Registries",
"REPLICATION": "Replications", "REPLICATION": "Replications",
"CONFIG": "Configuration" "CONFIG": "Configuration",
"DEVCENTER": "Developer Center"
}, },
"LOGS": "Logs" "LOGS": "Logs"
}, },
@ -444,7 +445,7 @@
"DELETION_TITLE_TAG": "Confirm Tag Deletion", "DELETION_TITLE_TAG": "Confirm Tag Deletion",
"DELETION_SUMMARY_TAG": "Do you want to delete tag {{param}}?", "DELETION_SUMMARY_TAG": "Do you want to delete tag {{param}}?",
"DELETION_TITLE_TAG_DENIED": "Signed tag cannot be deleted", "DELETION_TITLE_TAG_DENIED": "Signed tag cannot be deleted",
"DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted.\nDelete from Notary via this command:\n{{param}}", "DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted.\nDelete from Notary via this command:\n",
"TAGS_NO_DELETE": "Delete is prohibited in read only mode.", "TAGS_NO_DELETE": "Delete is prohibited in read only mode.",
"FILTER_FOR_REPOSITORIES": "Filter Repositories", "FILTER_FOR_REPOSITORIES": "Filter Repositories",
"TAG": "Tag", "TAG": "Tag",

View File

@ -121,7 +121,8 @@
"REGISTRY": "Registries", "REGISTRY": "Registries",
"GROUP": "Groups", "GROUP": "Groups",
"REPLICATION": "Replicacións", "REPLICATION": "Replicacións",
"CONFIG": "Configuración" "CONFIG": "Configuración",
"DEVCENTER": "Developer Center"
}, },
"LOGS": "Logs" "LOGS": "Logs"
}, },
@ -442,7 +443,7 @@
"DELETION_TITLE_TAG": "Confirmación de Eliminación de Etiqueta", "DELETION_TITLE_TAG": "Confirmación de Eliminación de Etiqueta",
"DELETION_SUMMARY_TAG": "¿Quiere eliminar la etiqueta {{param}}?", "DELETION_SUMMARY_TAG": "¿Quiere eliminar la etiqueta {{param}}?",
"DELETION_TITLE_TAG_DENIED": "La etiqueta firmada no puede ser eliminada", "DELETION_TITLE_TAG_DENIED": "La etiqueta firmada no puede ser eliminada",
"DELETION_SUMMARY_TAG_DENIED": "La etiqueta debe ser eliminada de la Notaría antes de eliminarla.\nEliminarla de la Notaría con este comando:\n{{param}}", "DELETION_SUMMARY_TAG_DENIED": "La etiqueta debe ser eliminada de la Notaría antes de eliminarla.\nEliminarla de la Notaría con este comando:\n",
"TAGS_NO_DELETE": "Delete is prohibited in read only mode.", "TAGS_NO_DELETE": "Delete is prohibited in read only mode.",
"FILTER_FOR_REPOSITORIES": "Filtrar Repositorios", "FILTER_FOR_REPOSITORIES": "Filtrar Repositorios",
"TAG": "Etiqueta", "TAG": "Etiqueta",

View File

@ -107,7 +107,8 @@
"USER": "Utilisateurs", "USER": "Utilisateurs",
"GROUP": "Groups", "GROUP": "Groups",
"REPLICATION": "Réplication", "REPLICATION": "Réplication",
"CONFIG": "Configuration" "CONFIG": "Configuration",
"DEVCENTER": "Developer Center"
}, },
"LOGS": "Logs" "LOGS": "Logs"
}, },
@ -422,7 +423,7 @@
"DELETION_TITLE_TAG": "Confirmer la suppression du Tag", "DELETION_TITLE_TAG": "Confirmer la suppression du Tag",
"DELETION_SUMMARY_TAG": "Voulez-vous supprimer le tag {{param}}?", "DELETION_SUMMARY_TAG": "Voulez-vous supprimer le tag {{param}}?",
"DELETION_TITLE_TAG_DENIED": "Un tag signé ne peut être supprimé", "DELETION_TITLE_TAG_DENIED": "Un tag signé ne peut être supprimé",
"DELETION_SUMMARY_TAG_DENIED": "La balise doit être supprimée du Résumé avant qu'elle ne puisse être supprimée. \nSupprimer du Résumé via cette commande: \n{{param}}", "DELETION_SUMMARY_TAG_DENIED": "La balise doit être supprimée du Résumé avant qu'elle ne puisse être supprimée. \nSupprimer du Résumé via cette commande: \n",
"TAGS_NO_DELETE": "Upload/Delete is prohibited in read only mode.", "TAGS_NO_DELETE": "Upload/Delete is prohibited in read only mode.",
"FILTER_FOR_REPOSITORIES": "Filtrer les Dépôts", "FILTER_FOR_REPOSITORIES": "Filtrer les Dépôts",
"TAG": "Tag", "TAG": "Tag",

View File

@ -119,7 +119,8 @@
"GROUP": "Grupos", "GROUP": "Grupos",
"REGISTRY": "Registros", "REGISTRY": "Registros",
"REPLICATION": "Replicações", "REPLICATION": "Replicações",
"CONFIG": "Configuração" "CONFIG": "Configuração",
"DEVCENTER": "Developer Center"
}, },
"LOGS": "Logs" "LOGS": "Logs"
}, },
@ -442,7 +443,7 @@
"DELETION_TITLE_TAG": "Confirmar remoção de Tag", "DELETION_TITLE_TAG": "Confirmar remoção de Tag",
"DELETION_SUMMARY_TAG": "Você quer remover a Tag {{param}}?", "DELETION_SUMMARY_TAG": "Você quer remover a Tag {{param}}?",
"DELETION_TITLE_TAG_DENIED": "Tags assinadas não podem ser removidas", "DELETION_TITLE_TAG_DENIED": "Tags assinadas não podem ser removidas",
"DELETION_SUMMARY_TAG_DENIED": "A tag deve ser removida do Notary antes de ser apagada.\nRemova do Notary com o seguinte comando:\n{{param}}", "DELETION_SUMMARY_TAG_DENIED": "A tag deve ser removida do Notary antes de ser apagada.\nRemova do Notary com o seguinte comando:\n",
"TAGS_NO_DELETE": "Remover é proibido em modo somente leitura.", "TAGS_NO_DELETE": "Remover é proibido em modo somente leitura.",
"FILTER_FOR_REPOSITORIES": "Filtrar repositórios", "FILTER_FOR_REPOSITORIES": "Filtrar repositórios",
"TAG": "Tag", "TAG": "Tag",

View File

@ -120,7 +120,8 @@
"GROUP": "组管理", "GROUP": "组管理",
"REGISTRY": "仓库管理", "REGISTRY": "仓库管理",
"REPLICATION": "复制管理", "REPLICATION": "复制管理",
"CONFIG": "配置管理" "CONFIG": "配置管理",
"DEVCENTER": "开发者中心"
}, },
"LOGS": "日志" "LOGS": "日志"
}, },
@ -442,7 +443,7 @@
"DELETION_TITLE_TAG": "删除镜像标签确认", "DELETION_TITLE_TAG": "删除镜像标签确认",
"DELETION_SUMMARY_TAG": "确认删除镜像标签 {{param}}?", "DELETION_SUMMARY_TAG": "确认删除镜像标签 {{param}}?",
"DELETION_TITLE_TAG_DENIED": "已签名的镜像不能被删除", "DELETION_TITLE_TAG_DENIED": "已签名的镜像不能被删除",
"DELETION_SUMMARY_TAG_DENIED": "要删除此镜像标签必须首先从Notary中删除。\n请执行如下Notary命令删除:\n{{param}}", "DELETION_SUMMARY_TAG_DENIED": "要删除此镜像标签必须首先从Notary中删除。\n请执行如下Notary命令删除:\n",
"TAGS_NO_DELETE": "在只读模式下删除是被禁止的", "TAGS_NO_DELETE": "在只读模式下删除是被禁止的",
"FILTER_FOR_REPOSITORIES": "过滤镜像仓库", "FILTER_FOR_REPOSITORIES": "过滤镜像仓库",
"TAG": "标签", "TAG": "标签",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 121.02 126.41"><defs><style>.ed7893be-f72e-4d49-983e-3ddcacf196ab{fill:#a2a2a2;}</style></defs><path class="ed7893be-f72e-4d49-983e-3ddcacf196ab" d="M453.41,121.78c-.91-.64-2-2.73-2.61-5.06a37.72,37.72,0,0,1-.38-10.23,12,12,0,0,0,.17-1.89,16.89,16.89,0,0,0-1.89-.29,46.62,46.62,0,0,1-16.5-5.26,13.83,13.83,0,0,0-2-1,5,5,0,0,0-1.12,1.65c-2.2,4.23-6,8.42-8.94,9.79a4.18,4.18,0,0,1-3.14.27c-1-.53-1.28-2.63-.69-5a29.48,29.48,0,0,1,3.35-6.89,38.1,38.1,0,0,1,2.55-3.26l1.5-1.68-.78-.74c-2.06-2-5.92-6.51-5.92-7a61.13,61.13,0,0,1,5.65-3.92c.2-.06.67.41,1.31,1.29a45.53,45.53,0,0,0,7.25,7.17,37.93,37.93,0,0,0,52.57-7,6.94,6.94,0,0,1,1.45-1.56A55.26,55.26,0,0,1,490.88,85c.16.26-2.11,3.22-4.06,5.26a16.35,16.35,0,0,0-1.68,1.94,15.29,15.29,0,0,0,1.62,1.88c2.81,3,5.57,7.59,6.34,10.61.59,2.32.3,4.42-.69,5a3.58,3.58,0,0,1-1.26.2c-3,0-8-4.87-11-10.68a9.12,9.12,0,0,0-1.23-2,6.61,6.61,0,0,0-1.3.72A46.94,46.94,0,0,1,461.11,104a13.84,13.84,0,0,0-2.38.52c-.15.12-.13.68.07,1.94a37.74,37.74,0,0,1-.26,9.73,11,11,0,0,1-2.19,5c-1.07,1.17-1.91,1.34-3,.6ZM393.88,76.39c0-.14-.07-7.52,0-16.4l.06-16.16h8.46l.06,6c0,4.45.11,6,.3,6.1a39,39,0,0,0,4.71.17c3.47,0,4.51-.06,4.68-.27s.24-2.21.28-6.11V43.88h8.47V76.59h-8.47V70.35c0-4.2-.13-6.33-.28-6.51s-1.21-.27-4.71-.27-4.53.06-4.71.27-.24,2.31-.28,6.51v6.24l-4.19.06C394.92,76.64,394,76.59,393.88,76.39Zm34.26,0c0-.14-.07-7.52,0-16.41V43.83h21l.06,3.58V51H436.8l-.06,2.41c0,1.79,0,2.45.2,2.57a47.92,47.92,0,0,0,5.36.17h5.09l-.06,3.58v3.59l-5.24.1-5.24.1v5.65l6.45.1,6.45.1v7.07l-10.74,0c-8.48,0-10.76,0-10.84-.2Zm28.22,0c0-.14-.07-7.52,0-16.41V43.83h8.46l.11,12.72.1,12.72,6.14.1,6.15.1v7.07l-10.43,0c-8.25,0-10.46,0-10.54-.2Zm26.81,0c0-.14-.07-7.52,0-16.4l.06-16.16h4.43a38.16,38.16,0,0,1,4.66.11c.3.2,4.77,12.39,5.76,15.69s.95,3.23,1.18,3a21.38,21.38,0,0,0,1.06-3c1-3.41,5.17-15.23,5.44-15.59.15-.19,1.24-.24,4.65-.2h4.46V76.55l-3.77.06-3.78,0-.13-.53c-.28-1.11.09-12.8.51-16.13.67-5.26.56-5.34-1-.81-1.69,4.89-5,13.89-5.36,14.38S500.9,74,499,74c-1.43,0-2.15-.08-2.29-.26s-4.36-11.51-5.48-14.93c-.8-2.42-1.23-3.29-1.22-2.44,0,.2.19,1.86.42,3.7.41,3.43.78,14.93.51,16l-.13.53H487c-2.8,0-3.76-.06-3.83-.25Zm2.66-37.29a39.91,39.91,0,0,0-4.48-5.64,37.5,37.5,0,0,0-20-11.07c-2.31-.5-2.92-.54-7.16-.55-5,0-6.09.11-10,1.12A37.22,37.22,0,0,0,430,30.22a46.3,46.3,0,0,0-6.64,7c-.86,1.19-1.28,1.64-1.51,1.55a43.94,43.94,0,0,1-5.65-3.8c0-.48,3.32-4.63,5.57-7l2.33-2.43-1.54-1.67c-2.81-3.05-5.51-7.59-6.26-10.54-.59-2.33-.3-4.42.69-5a3.37,3.37,0,0,1,1.26-.21c3,0,8.05,4.87,11,10.68a10.93,10.93,0,0,0,1.2,2,13.21,13.21,0,0,0,1.75-.91,47,47,0,0,1,16.65-5.27,8.92,8.92,0,0,0,1.78-.28c.18-.11.16-.65-.1-2.21a35.31,35.31,0,0,1,.28-10.81c1-3.7,2.55-5.77,4.14-5.47s2.93,2.71,3.68,6.2a40.46,40.46,0,0,1,0,10.77,4.56,4.56,0,0,0-.2,1.56,13,13,0,0,0,2.15.45,45,45,0,0,1,17.27,6.36c.4.24.79.36.85.26s.61-1.19,1.22-2.41c2.36-4.73,6.17-9,9.28-10.48a4.18,4.18,0,0,1,3.14-.27c1,.53,1.28,2.62.69,5a29.92,29.92,0,0,1-3.57,7.21,33.88,33.88,0,0,1-3.13,3.74l-1.87,1.89,1.84,2A59.77,59.77,0,0,1,492,35.46c.27.53.28.67,0,.88a52.14,52.14,0,0,1-5.68,3.34c-.13,0-.4-.27-.6-.6Z" transform="translate(-393.85 4.18)"/></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -37,3 +37,6 @@ import 'intl/locale-data/jsonp/zh';
import 'zone.js/dist/zone'; import 'zone.js/dist/zone';
(window as any).global = window;
// @ts-ignore
window.Buffer = window.Buffer || require('buffer').Buffer;

View File

@ -8,6 +8,10 @@
"es6", "es6",
"dom" "dom"
], ],
"types": [
"node",
"jasmine"
],
"mapRoot": "./", "mapRoot": "./",
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",

View File

@ -7,7 +7,7 @@
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"lib": [ "lib": [
"es2015", "es2017",
"dom" "dom"
], ],
"noImplicitAny": false, "noImplicitAny": false,

View File

@ -34,7 +34,7 @@ type secretHandler struct {
secrets map[string]string secrets map[string]string
} }
// NewSecretHandler creaters a new authentiation handler which adds // NewSecretHandler creates a new authentication handler which adds
// basic authentication credentials to a request. // basic authentication credentials to a request.
func NewSecretHandler(secrets map[string]string) AuthenticationHandler { func NewSecretHandler(secrets map[string]string) AuthenticationHandler {
return &secretHandler{ return &secretHandler{

View File

@ -15,6 +15,7 @@
package auth package auth
import ( import (
"fmt"
"net/http" "net/http"
"testing" "testing"
@ -49,3 +50,45 @@ func TestAuthorizeRequestValid(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
} }
func TestNilRequest(t *testing.T) {
secret := "Correct"
req, err := http.NewRequest("", "", nil)
req = nil
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
_ = commonsecret.AddToRequest(req, secret)
authenticator := NewSecretHandler(map[string]string{"secret1": "correct"})
err = authenticator.AuthorizeRequest(req)
assert.Equal(t, err, ErrNoSecret)
}
func TestNoSecret(t *testing.T) {
secret := ""
req, err := http.NewRequest("", "", nil)
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
_ = commonsecret.AddToRequest(req, secret)
authenticator := NewSecretHandler(map[string]string{})
err = authenticator.AuthorizeRequest(req)
assert.Equal(t, err, ErrNoSecret)
}
func TestIncorrectHarborSecret(t *testing.T) {
secret := "correct"
req, err := http.NewRequest("", "", nil)
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
_ = commonsecret.AddToRequest(req, secret)
// Set req header to an incorrect value to trigger error return
req.Header.Set("Authorization", fmt.Sprintf("%s%s", "WrongPrefix", secret))
authenticator := NewSecretHandler(map[string]string{"secret1": "correct"})
err = authenticator.AuthorizeRequest(req)
assert.Equal(t, err, ErrInvalidCredential)
}

View File

@ -11,12 +11,6 @@ def pull_harbor_image(registry, username, password, image, tag, expected_error_m
time.sleep(2) time.sleep(2)
_docker_api.docker_image_pull(r'{}/{}'.format(registry, image), tag = tag, expected_error_message = expected_error_message) _docker_api.docker_image_pull(r'{}/{}'.format(registry, image), tag = tag, expected_error_message = expected_error_message)
def pull_harbor_image_successfully(registry, username, password, image, tag):
pull_harbor_image(registry, username, password, image, tag)
def pull_harbor_image_unsuccessfully(registry, username, password, image, tag, expected_error_message):
pull_harbor_image(registry, username, password, image, tag, expected_error_message = expected_error_message)
def push_image_to_project(project_name, registry, username, password, image, tag): def push_image_to_project(project_name, registry, username, password, image, tag):
_docker_api = DockerAPI() _docker_api = DockerAPI()
_docker_api.docker_login(registry, username, password) _docker_api.docker_login(registry, username, password)
@ -95,7 +89,7 @@ class Repository(base.Base):
raise Exception("Image should be <Not Scanned> state!") raise Exception("Image should be <Not Scanned> state!")
def check_image_scan_result(self, repo_name, tag, expected_scan_status = "finished", **kwargs): def check_image_scan_result(self, repo_name, tag, expected_scan_status = "finished", **kwargs):
timeout_count = 20 timeout_count = 30
while True: while True:
time.sleep(5) time.sleep(5)
timeout_count = timeout_count - 1 timeout_count = timeout_count - 1

View File

@ -10,8 +10,7 @@ from library.project import Project
from library.user import User from library.user import User
from library.repository import Repository from library.repository import Repository
from library.repository import push_image_to_project from library.repository import push_image_to_project
from library.repository import pull_harbor_image_successfully from library.repository import pull_harbor_image
from library.repository import pull_harbor_image_unsuccessfully
class TestProjects(unittest.TestCase): class TestProjects(unittest.TestCase):
@classmethod @classmethod
@ -77,13 +76,13 @@ class TestProjects(unittest.TestCase):
self.repo.image_should_exist(TestProjects.repo_name, tag, **TestProjects.USER_CONTENT_TRUST_CLIENT) self.repo.image_should_exist(TestProjects.repo_name, tag, **TestProjects.USER_CONTENT_TRUST_CLIENT)
#5. Pull image(IA) successfully; #5. Pull image(IA) successfully;
pull_harbor_image_successfully(harbor_server, admin_name, admin_password, TestProjects.repo_name, tag) pull_harbor_image(harbor_server, admin_name, admin_password, TestProjects.repo_name, tag)
#6. Enable content trust in project(PA) configuration; #6. Enable content trust in project(PA) configuration;
self.project.update_project(TestProjects.project_content_trust_id, metadata = {"enable_content_trust": "true"}, **TestProjects.USER_CONTENT_TRUST_CLIENT) self.project.update_project(TestProjects.project_content_trust_id, metadata = {"enable_content_trust": "true"}, **TestProjects.USER_CONTENT_TRUST_CLIENT)
#7. Pull image(IA) failed and the reason is "The image is not signed in Notary". #7. Pull image(IA) failed and the reason is "The image is not signed in Notary".
pull_harbor_image_unsuccessfully(harbor_server, admin_name, admin_password, TestProjects.repo_name, tag, "The image is not signed in Notary") pull_harbor_image(harbor_server, admin_name, admin_password, TestProjects.repo_name, tag, expected_error_message = "The image is not signed in Notary")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -73,8 +73,8 @@ class TestProjects(unittest.TestCase):
#Note: Please make sure that this Image has never been pulled before by any other cases, #Note: Please make sure that this Image has never been pulled before by any other cases,
# so it is a not-scanned image rigth after rpository creation. # so it is a not-scanned image rigth after rpository creation.
#image = "tomcat" #image = "tomcat"
image = "pypy" image = "docker"
src_tag = "latest" src_tag = "1.13"
#5. Create a new repository(RA) and tag(TA) in project(PA) by user(UA); #5. Create a new repository(RA) and tag(TA) in project(PA) by user(UA);
TestProjects.repo_name, tag = push_image_to_project(project_scan_image_name, harbor_server, user_scan_image_name, user_001_password, image, src_tag) TestProjects.repo_name, tag = push_image_to_project(project_scan_image_name, harbor_server, user_scan_image_name, user_001_password, image, src_tag)

View File

@ -0,0 +1,8 @@
\c notaryserver;
ALTER TABLE tuf_files OWNER TO server;
ALTER SEQUENCE tuf_files_id_seq OWNER TO server;
ALTER TABLE change_category OWNER TO server;
ALTER TABLE changefeed OWNER TO server;
ALTER SEQUENCE changefeed_id_seq OWNER TO server;
ALTER TABLE schema_migrations OWNER TO server;

View File

@ -1,7 +1,7 @@
\c notaryserver; \c notaryserver;
CREATE TABLE "tuf_files" ( CREATE TABLE "tuf_files" (
"id" int PRIMARY KEY, "id" serial PRIMARY KEY,
"created_at" timestamp NULL DEFAULT NULL, "created_at" timestamp NULL DEFAULT NULL,
"updated_at" timestamp NULL DEFAULT NULL, "updated_at" timestamp NULL DEFAULT NULL,
"deleted_at" timestamp NULL DEFAULT NULL, "deleted_at" timestamp NULL DEFAULT NULL,

View File

@ -0,0 +1,5 @@
\c notarysigner;
ALTER TABLE private_keys OWNER TO signer;
ALTER SEQUENCE private_keys_id_seq OWNER TO signer;
ALTER TABLE schema_migrations OWNER TO signer;

View File

@ -1,7 +1,7 @@
\c notarysigner; \c notarysigner;
CREATE TABLE "private_keys" ( CREATE TABLE "private_keys" (
"id" int PRIMARY KEY, "id" serial PRIMARY KEY,
"created_at" timestamp NULL DEFAULT NULL, "created_at" timestamp NULL DEFAULT NULL,
"updated_at" timestamp NULL DEFAULT NULL, "updated_at" timestamp NULL DEFAULT NULL,
"deleted_at" timestamp NULL DEFAULT NULL, "deleted_at" timestamp NULL DEFAULT NULL,

View File

@ -73,8 +73,11 @@ EOF
# launch_pgsql $PGSQL_USR # launch_pgsql $PGSQL_USR
psql -U $1 -f /harbor-migration/db/schema/notaryserver_create_tables.pgsql psql -U $1 -f /harbor-migration/db/schema/notaryserver_create_tables.pgsql
psql -U $1 -f /harbor-migration/db/schema/notaryserver_insert_data.pgsql psql -U $1 -f /harbor-migration/db/schema/notaryserver_insert_data.pgsql
psql -U $1 -f /harbor-migration/db/schema/notaryserver_alter_tables.pgsql
psql -U $1 -f /harbor-migration/db/schema/notarysigner_create_tables.pgsql psql -U $1 -f /harbor-migration/db/schema/notarysigner_create_tables.pgsql
psql -U $1 -f /harbor-migration/db/schema/notarysigner_insert_data.pgsql psql -U $1 -f /harbor-migration/db/schema/notarysigner_insert_data.pgsql
psql -U $1 -f /harbor-migration/db/schema/notarysigner_alter_tables.pgsql
stop_mysql root stop_mysql root
stop_pgsql $1 stop_pgsql $1

View File

@ -95,6 +95,7 @@ def convert_notary_server_db(mysql_dump_file, pgsql_dump_file):
write_database(pgsql_dump, "notaryserver") write_database(pgsql_dump, "notaryserver")
write_insert(pgsql_dump, insert_lines) write_insert(pgsql_dump, insert_lines)
write_sequence(pgsql_dump, "tuf_files", "id") write_sequence(pgsql_dump, "tuf_files", "id")
write_sequence(pgsql_dump, "changefeed", "id")
def convert_notary_signer_db(mysql_dump_file, pgsql_dump_file): def convert_notary_signer_db(mysql_dump_file, pgsql_dump_file):
mysql_dump = open(mysql_dump_file) mysql_dump = open(mysql_dump_file)
@ -144,7 +145,7 @@ def write_alter_table_bool(pgsql_dump, table_name, table_columnn, default_value=
def write_sequence(pgsql_dump, table_name, table_columnn): def write_sequence(pgsql_dump, table_name, table_columnn):
pgsql_dump.write('\n') pgsql_dump.write('\n')
pgsql_dump.write("CREATE SEQUENCE %s_%s_seq;\n" % (table_name, table_columnn)) pgsql_dump.write("CREATE SEQUENCE IF NOT EXISTS %s_%s_seq;\n" % (table_name, table_columnn))
pgsql_dump.write("SELECT setval('%s_%s_seq', max(%s)) FROM %s;\n" % (table_name, table_columnn, table_columnn, table_name)) pgsql_dump.write("SELECT setval('%s_%s_seq', max(%s)) FROM %s;\n" % (table_name, table_columnn, table_columnn, table_name))
pgsql_dump.write("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" SET DEFAULT nextval('%s_%s_seq');\n" % (table_name, table_columnn, table_name, table_columnn)) pgsql_dump.write("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" SET DEFAULT nextval('%s_%s_seq');\n" % (table_name, table_columnn, table_name, table_columnn))