mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 18:25:56 +01:00
Merge pull request #10034 from ywk253100/191128_clean
Clean up admiral-related code
This commit is contained in:
commit
d145f4baf4
@ -5314,12 +5314,6 @@ definitions:
|
|||||||
with_clair:
|
with_clair:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: If the Harbor instance is deployed with nested clair.
|
description: If the Harbor instance is deployed with nested clair.
|
||||||
with_admiral:
|
|
||||||
type: boolean
|
|
||||||
description: If the Harbor instance is deployed with Admiral.
|
|
||||||
admiral_endpoint:
|
|
||||||
type: string
|
|
||||||
description: The url of the endpoint of admiral instance.
|
|
||||||
registry_url:
|
registry_url:
|
||||||
type: string
|
type: string
|
||||||
description: The url of registry against which the docker command should be issued.
|
description: The url of registry against which the docker command should be issued.
|
||||||
|
@ -24,7 +24,6 @@ HARBOR_ADMIN_PASSWORD={{harbor_admin_password}}
|
|||||||
MAX_JOB_WORKERS={{max_job_workers}}
|
MAX_JOB_WORKERS={{max_job_workers}}
|
||||||
CORE_SECRET={{core_secret}}
|
CORE_SECRET={{core_secret}}
|
||||||
JOBSERVICE_SECRET={{jobservice_secret}}
|
JOBSERVICE_SECRET={{jobservice_secret}}
|
||||||
ADMIRAL_URL={{admiral_url}}
|
|
||||||
WITH_NOTARY={{with_notary}}
|
WITH_NOTARY={{with_notary}}
|
||||||
WITH_CLAIR={{with_clair}}
|
WITH_CLAIR={{with_clair}}
|
||||||
CLAIR_DB_PASSWORD={{clair_db_password}}
|
CLAIR_DB_PASSWORD={{clair_db_password}}
|
||||||
|
@ -142,7 +142,6 @@ services:
|
|||||||
- SETUID
|
- SETUID
|
||||||
volumes:
|
volumes:
|
||||||
- {{data_volume}}/ca_download/:/etc/core/ca/:z
|
- {{data_volume}}/ca_download/:/etc/core/ca/:z
|
||||||
- {{data_volume}}/psc/:/etc/core/token/:z
|
|
||||||
- {{data_volume}}/:/data/:z
|
- {{data_volume}}/:/data/:z
|
||||||
- ./common/config/core/certificates/:/etc/core/certificates/:z
|
- ./common/config/core/certificates/:/etc/core/certificates/:z
|
||||||
- type: bind
|
- type: bind
|
||||||
|
@ -318,9 +318,6 @@ def parse_yaml_config(config_file_path, with_notary, with_clair, with_chartmuseu
|
|||||||
# auto generated secret string for core
|
# auto generated secret string for core
|
||||||
config_dict['core_secret'] = generate_random_string(16)
|
config_dict['core_secret'] = generate_random_string(16)
|
||||||
|
|
||||||
# Admiral configs
|
|
||||||
config_dict['admiral_url'] = configs.get("admiral_url") or ""
|
|
||||||
|
|
||||||
# UAA configs
|
# UAA configs
|
||||||
config_dict['uaa'] = configs.get('uaa') or {}
|
config_dict['uaa'] = configs.get('uaa') or {}
|
||||||
|
|
||||||
|
@ -11,11 +11,9 @@ core_conf_template_path = os.path.join(templates_dir, "core", "app.conf.jinja")
|
|||||||
core_conf = os.path.join(config_dir, "core", "app.conf")
|
core_conf = os.path.join(config_dir, "core", "app.conf")
|
||||||
|
|
||||||
ca_download_dir = os.path.join(data_dir, 'ca_download')
|
ca_download_dir = os.path.join(data_dir, 'ca_download')
|
||||||
psc_dir = os.path.join(data_dir, 'psc')
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_core(config_dict, with_notary, with_clair, with_chartmuseum):
|
def prepare_core(config_dict, with_notary, with_clair, with_chartmuseum):
|
||||||
prepare_dir(psc_dir, uid=DEFAULT_UID, gid=DEFAULT_GID)
|
|
||||||
prepare_dir(ca_download_dir, uid=DEFAULT_UID, gid=DEFAULT_GID)
|
prepare_dir(ca_download_dir, uid=DEFAULT_UID, gid=DEFAULT_GID)
|
||||||
prepare_dir(core_config_dir)
|
prepare_dir(core_config_dir)
|
||||||
# Render Core
|
# Render Core
|
||||||
|
@ -15,21 +15,21 @@
|
|||||||
package metadata
|
package metadata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/goharbor/harbor/src/common"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCfgMetaData_InitFromArray(t *testing.T) {
|
func TestCfgMetaData_InitFromArray(t *testing.T) {
|
||||||
testArray := []Item{
|
testArray := []Item{
|
||||||
{Scope: SystemScope, Group: BasicGroup, EnvKey: "HARBOR_ADMIN_PASSWORD", DefaultValue: "", Name: common.AdminInitialPassword, ItemType: &PasswordType{}, Editable: true},
|
{Scope: SystemScope, Group: BasicGroup, EnvKey: "HARBOR_ADMIN_PASSWORD", DefaultValue: "", Name: common.AdminInitialPassword, ItemType: &PasswordType{}, Editable: true},
|
||||||
{Scope: SystemScope, Group: BasicGroup, EnvKey: "ADMIRAL_URL", DefaultValue: "NA", Name: common.AdmiralEndpoint, ItemType: &StringType{}, Editable: false},
|
|
||||||
{Scope: UserScope, Group: BasicGroup, EnvKey: "AUTH_MODE", DefaultValue: "db_auth", Name: common.AUTHMode, ItemType: &StringType{}, Editable: false},
|
{Scope: UserScope, Group: BasicGroup, EnvKey: "AUTH_MODE", DefaultValue: "db_auth", Name: common.AUTHMode, ItemType: &StringType{}, Editable: false},
|
||||||
{Scope: SystemScope, Group: BasicGroup, EnvKey: "CHART_REPOSITORY_URL", DefaultValue: "http://chartmuseum:9999", Name: common.ChartRepoURL, ItemType: &StringType{}, Editable: false},
|
{Scope: SystemScope, Group: BasicGroup, EnvKey: "CHART_REPOSITORY_URL", DefaultValue: "http://chartmuseum:9999", Name: common.ChartRepoURL, ItemType: &StringType{}, Editable: false},
|
||||||
}
|
}
|
||||||
curInst := Instance()
|
curInst := Instance()
|
||||||
curInst.initFromArray(testArray)
|
curInst.initFromArray(testArray)
|
||||||
|
|
||||||
if len(metaDataInstance.metaMap) != 4 {
|
if len(metaDataInstance.metaMap) != 3 {
|
||||||
t.Errorf("Can not initial metadata, size %v", len(metaDataInstance.metaMap))
|
t.Errorf("Can not initial metadata, size %v", len(metaDataInstance.metaMap))
|
||||||
}
|
}
|
||||||
item, ok := curInst.GetByName(common.AdminInitialPassword)
|
item, ok := curInst.GetByName(common.AdminInitialPassword)
|
||||||
|
@ -62,7 +62,6 @@ var (
|
|||||||
ConfigList = []Item{
|
ConfigList = []Item{
|
||||||
|
|
||||||
{Name: common.AdminInitialPassword, Scope: SystemScope, Group: BasicGroup, EnvKey: "HARBOR_ADMIN_PASSWORD", DefaultValue: "", ItemType: &PasswordType{}, Editable: true},
|
{Name: common.AdminInitialPassword, Scope: SystemScope, Group: BasicGroup, EnvKey: "HARBOR_ADMIN_PASSWORD", DefaultValue: "", ItemType: &PasswordType{}, Editable: true},
|
||||||
{Name: common.AdmiralEndpoint, Scope: SystemScope, Group: BasicGroup, EnvKey: "ADMIRAL_URL", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
|
||||||
{Name: common.AUTHMode, Scope: UserScope, Group: BasicGroup, EnvKey: "AUTH_MODE", DefaultValue: "db_auth", ItemType: &AuthModeType{}, Editable: false},
|
{Name: common.AUTHMode, Scope: UserScope, Group: BasicGroup, EnvKey: "AUTH_MODE", DefaultValue: "db_auth", ItemType: &AuthModeType{}, Editable: false},
|
||||||
{Name: common.ChartRepoURL, Scope: SystemScope, Group: BasicGroup, EnvKey: "CHART_REPOSITORY_URL", DefaultValue: "http://chartmuseum:9999", ItemType: &StringType{}, Editable: false},
|
{Name: common.ChartRepoURL, Scope: SystemScope, Group: BasicGroup, EnvKey: "CHART_REPOSITORY_URL", DefaultValue: "http://chartmuseum:9999", ItemType: &StringType{}, Editable: false},
|
||||||
|
|
||||||
|
@ -87,7 +87,6 @@ const (
|
|||||||
MaxJobWorkers = "max_job_workers"
|
MaxJobWorkers = "max_job_workers"
|
||||||
TokenExpiration = "token_expiration"
|
TokenExpiration = "token_expiration"
|
||||||
AdminInitialPassword = "admin_initial_password"
|
AdminInitialPassword = "admin_initial_password"
|
||||||
AdmiralEndpoint = "admiral_url"
|
|
||||||
WithNotary = "with_notary"
|
WithNotary = "with_notary"
|
||||||
WithClair = "with_clair"
|
WithClair = "with_clair"
|
||||||
ScanAllPolicy = "scan_all_policy"
|
ScanAllPolicy = "scan_all_policy"
|
||||||
|
@ -186,8 +186,6 @@ func repositoryQueryConditions(query ...*models.RepositoryQuery) (string, []inte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(q.ProjectName) > 0 {
|
if len(q.ProjectName) > 0 {
|
||||||
// use "like" rather than "table joining" because that
|
|
||||||
// in integration mode the projects are saved in Admiral side
|
|
||||||
sql += `and r.name like ? `
|
sql += `and r.name like ? `
|
||||||
params = append(params, q.ProjectName+"/%")
|
params = append(params, q.ProjectName+"/%")
|
||||||
}
|
}
|
||||||
|
@ -1,211 +0,0 @@
|
|||||||
// Copyright Project Harbor Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package authcontext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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/utils"
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// AuthTokenHeader is the key of auth token header
|
|
||||||
AuthTokenHeader = "x-xenon-auth-token"
|
|
||||||
sysAdminRole = "CLOUD_ADMIN"
|
|
||||||
projectAdminRole = "PROJECT_ADMIN"
|
|
||||||
developerRole = "PROJECT_MEMBER"
|
|
||||||
guestRole = "PROJECT_VIEWER"
|
|
||||||
)
|
|
||||||
|
|
||||||
type project struct {
|
|
||||||
SelfLink string `json:"documentSelfLink"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Roles []string `json:"roles"`
|
|
||||||
Properties map[string]string `json:"customProperties"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthContext ...
|
|
||||||
type AuthContext struct {
|
|
||||||
PrincipalID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Roles []string `json:"roles"`
|
|
||||||
Projects []*project `json:"projects"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSysAdmin ...
|
|
||||||
func (a *AuthContext) IsSysAdmin() bool {
|
|
||||||
for _, role := range a.Roles {
|
|
||||||
if role == sysAdminRole {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProjectRoles ...
|
|
||||||
func (a *AuthContext) GetProjectRoles(projectIDOrName interface{}) []int {
|
|
||||||
id, name, err := utils.ParseProjectIDOrName(projectIDOrName)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to parse project ID or name: %v", err)
|
|
||||||
return []int{}
|
|
||||||
}
|
|
||||||
|
|
||||||
roles := []string{}
|
|
||||||
for _, project := range a.Projects {
|
|
||||||
p := convertProject(project)
|
|
||||||
if id != 0 && p.ProjectID == id || len(name) > 0 && p.Name == name {
|
|
||||||
roles = append(roles, project.Roles...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return convertRoles(roles)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMyProjects returns all projects which the user is a member of
|
|
||||||
func (a *AuthContext) GetMyProjects() []*models.Project {
|
|
||||||
projects := []*models.Project{}
|
|
||||||
for _, project := range a.Projects {
|
|
||||||
projects = append(projects, convertProject(project))
|
|
||||||
}
|
|
||||||
return projects
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert project returned by Admiral to project used in Harbor
|
|
||||||
func convertProject(p *project) *models.Project {
|
|
||||||
project := &models.Project{
|
|
||||||
Name: p.Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
index := ""
|
|
||||||
if p.Properties != nil {
|
|
||||||
index = p.Properties["__projectIndex"]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(index) == 0 {
|
|
||||||
log.Errorf("property __projectIndex not found when parsing project")
|
|
||||||
return project
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.ParseInt(index, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to parse __projectIndex %s: %v", index, err)
|
|
||||||
return project
|
|
||||||
}
|
|
||||||
|
|
||||||
project.ProjectID = id
|
|
||||||
return project
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert roles defined by Admiral to roles used in Harbor
|
|
||||||
func convertRoles(roles []string) []int {
|
|
||||||
list := []int{}
|
|
||||||
for _, role := range roles {
|
|
||||||
switch role {
|
|
||||||
case projectAdminRole:
|
|
||||||
list = append(list, common.RoleProjectAdmin)
|
|
||||||
case developerRole:
|
|
||||||
list = append(list, common.RoleDeveloper)
|
|
||||||
case guestRole:
|
|
||||||
list = append(list, common.RoleGuest)
|
|
||||||
default:
|
|
||||||
log.Warningf("unknow role: %s", role)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAuthCtx returns the auth context of the current user
|
|
||||||
func GetAuthCtx(client *http.Client, url, token string) (*AuthContext, error) {
|
|
||||||
req, err := http.NewRequest(http.MethodGet, buildCurrentUserAuthCtxURL(url), nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add(AuthTokenHeader, token)
|
|
||||||
|
|
||||||
return send(client, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login with credential and returns auth context and error
|
|
||||||
func Login(client *http.Client, url, username, password, token string) (*AuthContext, error) {
|
|
||||||
data, err := json.Marshal(&struct {
|
|
||||||
Password string `json:"password"`
|
|
||||||
}{
|
|
||||||
Password: password,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPost, buildLoginURL(url, username), bytes.NewBuffer(data))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add(AuthTokenHeader, token)
|
|
||||||
|
|
||||||
return send(client, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func send(client *http.Client, req *http.Request) (*AuthContext, error) {
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("\"%s %s\" failed", req.Method, req.URL.String())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
log.Debugf("\"%s %s\" %d", req.Method, req.URL.String(), resp.StatusCode)
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, &commonhttp.Error{
|
|
||||||
Code: resp.StatusCode,
|
|
||||||
Message: string(data),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := &AuthContext{}
|
|
||||||
if err = json.Unmarshal(data, ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildCurrentUserAuthCtxURL(url string) string {
|
|
||||||
return strings.TrimRight(url, "/") + "/auth/session"
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildLoginURL(url, principalID string) string {
|
|
||||||
return fmt.Sprintf("%s/auth/idm/principals/%s/security-context",
|
|
||||||
strings.TrimRight(url, "/"), principalID)
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
// Copyright Project Harbor Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package authcontext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIsSysAdmin(t *testing.T) {
|
|
||||||
// nil roles
|
|
||||||
ctx := &AuthContext{}
|
|
||||||
assert.False(t, ctx.IsSysAdmin())
|
|
||||||
|
|
||||||
// has no admin role
|
|
||||||
ctx = &AuthContext{
|
|
||||||
Roles: []string{projectAdminRole, developerRole, guestRole},
|
|
||||||
}
|
|
||||||
assert.False(t, ctx.IsSysAdmin())
|
|
||||||
|
|
||||||
// has admin role
|
|
||||||
ctx = &AuthContext{
|
|
||||||
Roles: []string{sysAdminRole},
|
|
||||||
}
|
|
||||||
assert.True(t, ctx.IsSysAdmin())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetProjectRoles(t *testing.T) {
|
|
||||||
ctx := &AuthContext{
|
|
||||||
Projects: []*project{
|
|
||||||
{
|
|
||||||
Name: "project",
|
|
||||||
Roles: []string{projectAdminRole, developerRole, guestRole},
|
|
||||||
Properties: map[string]string{"__projectIndex": "9"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// test with name
|
|
||||||
roles := ctx.GetProjectRoles("project")
|
|
||||||
assert.Equal(t, 3, len(roles))
|
|
||||||
|
|
||||||
// test with ID
|
|
||||||
roles = ctx.GetProjectRoles(9)
|
|
||||||
assert.Equal(t, 3, len(roles))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetMyProjects(t *testing.T) {
|
|
||||||
ctx := &AuthContext{
|
|
||||||
Projects: []*project{
|
|
||||||
{
|
|
||||||
Name: "project1",
|
|
||||||
Roles: []string{projectAdminRole},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "project2",
|
|
||||||
Roles: []string{developerRole},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
projects := ctx.GetMyProjects()
|
|
||||||
assert.Equal(t, 2, len(projects))
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
// Copyright Project Harbor Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package admiral
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
|
||||||
"github.com/goharbor/harbor/src/common/rbac/project"
|
|
||||||
"github.com/goharbor/harbor/src/common/security/admiral/authcontext"
|
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SecurityContext implements security.Context interface based on
|
|
||||||
// auth context and project manager
|
|
||||||
type SecurityContext struct {
|
|
||||||
ctx *authcontext.AuthContext
|
|
||||||
pm promgr.ProjectManager
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSecurityContext ...
|
|
||||||
func NewSecurityContext(ctx *authcontext.AuthContext, pm promgr.ProjectManager) *SecurityContext {
|
|
||||||
return &SecurityContext{
|
|
||||||
ctx: ctx,
|
|
||||||
pm: pm,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAuthenticated returns true if the user has been authenticated
|
|
||||||
func (s *SecurityContext) IsAuthenticated() bool {
|
|
||||||
if s.ctx == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return len(s.ctx.PrincipalID) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsername returns the username of the authenticated user
|
|
||||||
// It returns null if the user has not been authenticated
|
|
||||||
func (s *SecurityContext) GetUsername() string {
|
|
||||||
if !s.IsAuthenticated() {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return s.ctx.PrincipalID
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSysAdmin returns whether the authenticated user is system admin
|
|
||||||
// It returns false if the user has not been authenticated
|
|
||||||
func (s *SecurityContext) IsSysAdmin() bool {
|
|
||||||
if !s.IsAuthenticated() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.ctx.IsSysAdmin()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSolutionUser ...
|
|
||||||
func (s *SecurityContext) IsSolutionUser() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can returns whether the user can do action on resource
|
|
||||||
func (s *SecurityContext) Can(action rbac.Action, resource rbac.Resource) bool {
|
|
||||||
ns, err := resource.GetNamespace()
|
|
||||||
if err == nil {
|
|
||||||
switch ns.Kind() {
|
|
||||||
case "project":
|
|
||||||
projectID := ns.Identity().(int64)
|
|
||||||
isPublicProject, _ := s.pm.IsPublic(projectID)
|
|
||||||
projectNamespace := rbac.NewProjectNamespace(projectID, isPublicProject)
|
|
||||||
user := project.NewUser(s, projectNamespace, s.GetProjectRoles(projectID)...)
|
|
||||||
return rbac.HasPermission(user, resource, action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMyProjects ...
|
|
||||||
func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) {
|
|
||||||
return s.ctx.GetMyProjects(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProjectRoles ...
|
|
||||||
func (s *SecurityContext) GetProjectRoles(projectIDOrName interface{}) []int {
|
|
||||||
if !s.IsAuthenticated() || projectIDOrName == nil {
|
|
||||||
return []int{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.ctx.GetProjectRoles(projectIDOrName)
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
// Copyright Project Harbor Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package admiral
|
|
@ -66,7 +66,6 @@ var defaultConfigWithVerifyCert = map[string]interface{}{
|
|||||||
common.MaxJobWorkers: 3,
|
common.MaxJobWorkers: 3,
|
||||||
common.TokenExpiration: 30,
|
common.TokenExpiration: 30,
|
||||||
common.AdminInitialPassword: "password",
|
common.AdminInitialPassword: "password",
|
||||||
common.AdmiralEndpoint: "http://www.vmware.com",
|
|
||||||
common.WithNotary: false,
|
common.WithNotary: false,
|
||||||
common.WithClair: false,
|
common.WithClair: false,
|
||||||
}
|
}
|
||||||
@ -85,9 +84,7 @@ func TestMain(m *testing.M) {
|
|||||||
log.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
|
log.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := uiConfig.Init(); err != nil {
|
uiConfig.Init()
|
||||||
log.Fatalf("failed to initialize configurations: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
uiConfig.Upload(ldapTestConfig)
|
uiConfig.Upload(ldapTestConfig)
|
||||||
|
|
||||||
|
@ -30,7 +30,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common"
|
"github.com/goharbor/harbor/src/common"
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var endpoint = "10.117.4.142"
|
var endpoint = "10.117.4.142"
|
||||||
@ -45,9 +44,7 @@ func TestMain(m *testing.M) {
|
|||||||
common.TokenExpiration: 30,
|
common.TokenExpiration: 30,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := config.Init(); err != nil {
|
config.Init()
|
||||||
log.Fatalf("failed to initialize config: %v", err)
|
|
||||||
}
|
|
||||||
test.InitDatabaseFromEnv()
|
test.InitDatabaseFromEnv()
|
||||||
config.Upload(defaultConfig)
|
config.Upload(defaultConfig)
|
||||||
notaryCachePath = "/tmp/notary"
|
notaryCachePath = "/tmp/notary"
|
||||||
|
@ -54,7 +54,6 @@ var defaultConfig = map[string]interface{}{
|
|||||||
common.MaxJobWorkers: 3,
|
common.MaxJobWorkers: 3,
|
||||||
common.TokenExpiration: 30,
|
common.TokenExpiration: 30,
|
||||||
common.AdminInitialPassword: "password",
|
common.AdminInitialPassword: "password",
|
||||||
common.AdmiralEndpoint: "",
|
|
||||||
common.WithNotary: false,
|
common.WithNotary: false,
|
||||||
common.WithClair: false,
|
common.WithClair: false,
|
||||||
common.ClairDBUsername: "postgres",
|
common.ClairDBUsername: "postgres",
|
||||||
|
@ -98,17 +98,11 @@ func (p *ProjectAPI) Post() {
|
|||||||
p.SendUnAuthorizedError(errors.New("Unauthorized"))
|
p.SendUnAuthorizedError(errors.New("Unauthorized"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var onlyAdmin bool
|
onlyAdmin, err := config.OnlyAdminCreateProject()
|
||||||
var err error
|
if err != nil {
|
||||||
if config.WithAdmiral() {
|
log.Errorf("failed to determine whether only admin can create projects: %v", err)
|
||||||
onlyAdmin = true
|
p.SendInternalServerError(fmt.Errorf("failed to determine whether only admin can create projects: %v", err))
|
||||||
} else {
|
return
|
||||||
onlyAdmin, err = config.OnlyAdminCreateProject()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to determine whether only admin can create projects: %v", err)
|
|
||||||
p.SendInternalServerError(fmt.Errorf("failed to determine whether only admin can create projects: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if onlyAdmin && !(p.SecurityCtx.IsSysAdmin() || p.SecurityCtx.IsSolutionUser()) {
|
if onlyAdmin && !(p.SecurityCtx.IsSysAdmin() || p.SecurityCtx.IsSolutionUser()) {
|
||||||
@ -400,46 +394,43 @@ func (p *ProjectAPI) List() {
|
|||||||
query.Public = &pub
|
query.Public = &pub
|
||||||
}
|
}
|
||||||
|
|
||||||
// standalone, filter projects according to the privilleges of the user first
|
var projects []*models.Project
|
||||||
if !config.WithAdmiral() {
|
if !p.SecurityCtx.IsAuthenticated() {
|
||||||
var projects []*models.Project
|
// not login, only get public projects
|
||||||
if !p.SecurityCtx.IsAuthenticated() {
|
pros, err := p.ProjectMgr.GetPublic()
|
||||||
// not login, only get public projects
|
if err != nil {
|
||||||
|
p.SendInternalServerError(fmt.Errorf("failed to get public projects: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
projects = []*models.Project{}
|
||||||
|
projects = append(projects, pros...)
|
||||||
|
} else {
|
||||||
|
if !(p.SecurityCtx.IsSysAdmin() || p.SecurityCtx.IsSolutionUser()) {
|
||||||
|
projects = []*models.Project{}
|
||||||
|
// login, but not system admin or solution user, get public projects and
|
||||||
|
// projects that the user is member of
|
||||||
pros, err := p.ProjectMgr.GetPublic()
|
pros, err := p.ProjectMgr.GetPublic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.SendInternalServerError(fmt.Errorf("failed to get public projects: %v", err))
|
p.SendInternalServerError(fmt.Errorf("failed to get public projects: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
projects = []*models.Project{}
|
|
||||||
projects = append(projects, pros...)
|
projects = append(projects, pros...)
|
||||||
} else {
|
mps, err := p.SecurityCtx.GetMyProjects()
|
||||||
if !(p.SecurityCtx.IsSysAdmin() || p.SecurityCtx.IsSolutionUser()) {
|
if err != nil {
|
||||||
projects = []*models.Project{}
|
p.SendInternalServerError(fmt.Errorf("failed to list projects: %v", err))
|
||||||
// login, but not system admin or solution user, get public projects and
|
return
|
||||||
// projects that the user is member of
|
|
||||||
pros, err := p.ProjectMgr.GetPublic()
|
|
||||||
if err != nil {
|
|
||||||
p.SendInternalServerError(fmt.Errorf("failed to get public projects: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
projects = append(projects, pros...)
|
|
||||||
mps, err := p.SecurityCtx.GetMyProjects()
|
|
||||||
if err != nil {
|
|
||||||
p.SendInternalServerError(fmt.Errorf("failed to list projects: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
projects = append(projects, mps...)
|
|
||||||
}
|
}
|
||||||
|
projects = append(projects, mps...)
|
||||||
}
|
}
|
||||||
// Query projects by user group
|
}
|
||||||
|
// Query projects by user group
|
||||||
|
|
||||||
if projects != nil {
|
if projects != nil {
|
||||||
projectIDs := []int64{}
|
projectIDs := []int64{}
|
||||||
for _, project := range projects {
|
for _, project := range projects {
|
||||||
projectIDs = append(projectIDs, project.ProjectID)
|
projectIDs = append(projectIDs, project.ProjectID)
|
||||||
}
|
|
||||||
query.ProjectIDs = projectIDs
|
|
||||||
}
|
}
|
||||||
|
query.ProjectIDs = projectIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := p.ProjectMgr.List(query)
|
result, err := p.ProjectMgr.List(query)
|
||||||
|
@ -52,8 +52,6 @@ type Storage struct {
|
|||||||
// GeneralInfo wraps common systeminfo for anonymous request
|
// GeneralInfo wraps common systeminfo for anonymous request
|
||||||
type GeneralInfo struct {
|
type GeneralInfo struct {
|
||||||
WithNotary bool `json:"with_notary"`
|
WithNotary bool `json:"with_notary"`
|
||||||
WithAdmiral bool `json:"with_admiral"`
|
|
||||||
AdmiralEndpoint string `json:"admiral_endpoint"`
|
|
||||||
AuthMode string `json:"auth_mode"`
|
AuthMode string `json:"auth_mode"`
|
||||||
AuthProxySettings *models.HTTPAuthProxy `json:"authproxy_settings,omitempty"`
|
AuthProxySettings *models.HTTPAuthProxy `json:"authproxy_settings,omitempty"`
|
||||||
RegistryURL string `json:"registry_url"`
|
RegistryURL string `json:"registry_url"`
|
||||||
@ -135,8 +133,6 @@ func (sia *SystemInfoAPI) GetGeneralInfo() {
|
|||||||
enableCADownload := caStatErr == nil && strings.HasPrefix(extURL, "https://")
|
enableCADownload := caStatErr == nil && strings.HasPrefix(extURL, "https://")
|
||||||
harborVersion := sia.getVersion()
|
harborVersion := sia.getVersion()
|
||||||
info := GeneralInfo{
|
info := GeneralInfo{
|
||||||
AdmiralEndpoint: utils.SafeCastString(cfg[common.AdmiralEndpoint]),
|
|
||||||
WithAdmiral: config.WithAdmiral(),
|
|
||||||
WithNotary: config.WithNotary(),
|
WithNotary: config.WithNotary(),
|
||||||
AuthMode: utils.SafeCastString(cfg[common.AUTHMode]),
|
AuthMode: utils.SafeCastString(cfg[common.AUTHMode]),
|
||||||
ProjectCreationRestrict: utils.SafeCastString(cfg[common.ProjectCreationRestriction]),
|
ProjectCreationRestrict: utils.SafeCastString(cfg[common.ProjectCreationRestriction]),
|
||||||
|
@ -63,9 +63,7 @@ func TestMain(m *testing.M) {
|
|||||||
log.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
|
log.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := coreConfig.Init(); err != nil {
|
coreConfig.Init()
|
||||||
log.Fatalf("failed to initialize configurations: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Upload(testConfig)
|
config.Upload(testConfig)
|
||||||
retCode := m.Run()
|
retCode := m.Run()
|
||||||
|
@ -28,12 +28,9 @@ import (
|
|||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
test.InitDatabaseFromEnv()
|
test.InitDatabaseFromEnv()
|
||||||
err := config.Init()
|
config.Init()
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dao.ClearTable("project_member")
|
err := dao.ClearTable("project_member")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -33,14 +28,11 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/secret"
|
"github.com/goharbor/harbor/src/common/secret"
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
"github.com/goharbor/harbor/src/core/promgr"
|
||||||
"github.com/goharbor/harbor/src/core/promgr/pmsdriver"
|
|
||||||
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/admiral"
|
|
||||||
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/local"
|
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/local"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultKeyPath = "/etc/core/key"
|
defaultKeyPath = "/etc/core/key"
|
||||||
defaultTokenFilePath = "/etc/core/token/tokens.properties"
|
|
||||||
defaultRegistryTokenPrivateKeyPath = "/etc/core/private_key.pem"
|
defaultRegistryTokenPrivateKeyPath = "/etc/core/private_key.pem"
|
||||||
|
|
||||||
// SessionCookieName is the name of the cookie for session ID
|
// SessionCookieName is the name of the cookie for session ID
|
||||||
@ -53,18 +45,13 @@ var (
|
|||||||
// GlobalProjectMgr is initialized based on the deploy mode
|
// GlobalProjectMgr is initialized based on the deploy mode
|
||||||
GlobalProjectMgr promgr.ProjectManager
|
GlobalProjectMgr promgr.ProjectManager
|
||||||
keyProvider comcfg.KeyProvider
|
keyProvider comcfg.KeyProvider
|
||||||
// AdmiralClient is initialized only under integration deploy mode
|
|
||||||
// and can be passed to project manager as a parameter
|
|
||||||
AdmiralClient *http.Client
|
|
||||||
// TokenReader is used in integration mode to read token
|
|
||||||
TokenReader admiral.TokenReader
|
|
||||||
// defined as a var for testing.
|
// defined as a var for testing.
|
||||||
defaultCACertPath = "/etc/core/ca/ca.crt"
|
defaultCACertPath = "/etc/core/ca/ca.crt"
|
||||||
cfgMgr *comcfg.CfgManager
|
cfgMgr *comcfg.CfgManager
|
||||||
)
|
)
|
||||||
|
|
||||||
// Init configurations
|
// Init configurations
|
||||||
func Init() error {
|
func Init() {
|
||||||
// init key provider
|
// init key provider
|
||||||
initKeyProvider()
|
initKeyProvider()
|
||||||
|
|
||||||
@ -73,13 +60,9 @@ func Init() error {
|
|||||||
log.Info("init secret store")
|
log.Info("init secret store")
|
||||||
// init secret store
|
// init secret store
|
||||||
initSecretStore()
|
initSecretStore()
|
||||||
log.Info("init project manager based on deploy mode")
|
log.Info("init project manager")
|
||||||
// init project manager based on deploy mode
|
// init project manager
|
||||||
if err := initProjectManager(); err != nil {
|
initProjectManager()
|
||||||
log.Errorf("Failed to initialise project manager, error: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitWithSettings init config with predefined configs, and optionally overwrite the keyprovider
|
// InitWithSettings init config with predefined configs, and optionally overwrite the keyprovider
|
||||||
@ -108,46 +91,9 @@ func initSecretStore() {
|
|||||||
SecretStore = secret.NewStore(m)
|
SecretStore = secret.NewStore(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initProjectManager() error {
|
func initProjectManager() {
|
||||||
var driver pmsdriver.PMSDriver
|
log.Info("initializing the project manager based on local database...")
|
||||||
if WithAdmiral() {
|
GlobalProjectMgr = promgr.NewDefaultProjectManager(local.NewDriver(), true)
|
||||||
log.Debugf("Initialising Admiral client with certificate: %s", defaultCACertPath)
|
|
||||||
content, err := ioutil.ReadFile(defaultCACertPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pool := x509.NewCertPool()
|
|
||||||
if ok := pool.AppendCertsFromPEM(content); !ok {
|
|
||||||
return fmt.Errorf("failed to append cert content into cert worker")
|
|
||||||
}
|
|
||||||
AdmiralClient = &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
RootCAs: pool,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// integration with admiral
|
|
||||||
log.Info("initializing the project manager based on PMS...")
|
|
||||||
path := os.Getenv("SERVICE_TOKEN_FILE_PATH")
|
|
||||||
if len(path) == 0 {
|
|
||||||
path = defaultTokenFilePath
|
|
||||||
}
|
|
||||||
log.Infof("service token file path: %s", path)
|
|
||||||
TokenReader = &admiral.FileTokenReader{
|
|
||||||
Path: path,
|
|
||||||
}
|
|
||||||
driver = admiral.NewDriver(AdmiralClient, AdmiralEndpoint(), TokenReader)
|
|
||||||
} else {
|
|
||||||
// standalone
|
|
||||||
log.Info("initializing the project manager based on local database...")
|
|
||||||
driver = local.NewDriver()
|
|
||||||
}
|
|
||||||
GlobalProjectMgr = promgr.NewDefaultProjectManager(driver, true)
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCfgManager return the current config manager
|
// GetCfgManager return the current config manager
|
||||||
@ -393,19 +339,6 @@ func ClairAdapterEndpoint() string {
|
|||||||
return cfgMgr.Get(common.ClairAdapterURL).GetString()
|
return cfgMgr.Get(common.ClairAdapterURL).GetString()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdmiralEndpoint returns the URL of admiral, if Harbor is not deployed with admiral it should return an empty string.
|
|
||||||
func AdmiralEndpoint() string {
|
|
||||||
if cfgMgr.Get(common.AdmiralEndpoint).GetString() == "NA" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return cfgMgr.Get(common.AdmiralEndpoint).GetString()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithAdmiral returns a bool to indicate if Harbor's deployed with admiral.
|
|
||||||
func WithAdmiral() bool {
|
|
||||||
return len(AdmiralEndpoint()) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// UAASettings returns the UAASettings to access UAA service.
|
// UAASettings returns the UAASettings to access UAA service.
|
||||||
func UAASettings() (*models.UAASettings, error) {
|
func UAASettings() (*models.UAASettings, error) {
|
||||||
err := cfgMgr.Load()
|
err := cfgMgr.Load()
|
||||||
|
@ -32,7 +32,6 @@ func TestConfig(t *testing.T) {
|
|||||||
dao.PrepareTestData([]string{"delete from properties where k='scan_all_policy'"}, []string{})
|
dao.PrepareTestData([]string{"delete from properties where k='scan_all_policy'"}, []string{})
|
||||||
defaultCACertPath = path.Join(currPath(), "test", "ca.crt")
|
defaultCACertPath = path.Join(currPath(), "test", "ca.crt")
|
||||||
c := map[string]interface{}{
|
c := map[string]interface{}{
|
||||||
common.AdmiralEndpoint: "https://www.vmware.com",
|
|
||||||
common.WithClair: false,
|
common.WithClair: false,
|
||||||
common.WithChartMuseum: false,
|
common.WithChartMuseum: false,
|
||||||
common.WithNotary: false,
|
common.WithNotary: false,
|
||||||
@ -59,9 +58,7 @@ func TestConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.Setenv("TOKEN_PRIVATE_KEY_PATH", oriKeyPath)
|
defer os.Setenv("TOKEN_PRIVATE_KEY_PATH", oriKeyPath)
|
||||||
|
|
||||||
if err := Init(); err != nil {
|
Init()
|
||||||
t.Fatalf("failed to initialize configurations: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := Load(); err != nil {
|
if err := Load(); err != nil {
|
||||||
t.Fatalf("failed to load configurations: %v", err)
|
t.Fatalf("failed to load configurations: %v", err)
|
||||||
@ -143,7 +140,6 @@ func TestConfig(t *testing.T) {
|
|||||||
t.Fatalf("failed to get clair DB %v", err)
|
t.Fatalf("failed to get clair DB %v", err)
|
||||||
}
|
}
|
||||||
defaultConfig := test.GetDefaultConfigMap()
|
defaultConfig := test.GetDefaultConfigMap()
|
||||||
defaultConfig[common.AdmiralEndpoint] = "http://www.vmware.com"
|
|
||||||
Upload(defaultConfig)
|
Upload(defaultConfig)
|
||||||
assert.Equal(defaultConfig[common.ClairDB], clairDB.Database)
|
assert.Equal(defaultConfig[common.ClairDB], clairDB.Database)
|
||||||
assert.Equal(defaultConfig[common.ClairDBUsername], clairDB.Username)
|
assert.Equal(defaultConfig[common.ClairDBUsername], clairDB.Username)
|
||||||
@ -160,15 +156,9 @@ func TestConfig(t *testing.T) {
|
|||||||
if WithClair() {
|
if WithClair() {
|
||||||
t.Errorf("WithClair should be false")
|
t.Errorf("WithClair should be false")
|
||||||
}
|
}
|
||||||
if !WithAdmiral() {
|
|
||||||
t.Errorf("WithAdmiral should be true")
|
|
||||||
}
|
|
||||||
if ReadOnly() {
|
if ReadOnly() {
|
||||||
t.Errorf("ReadOnly should be false")
|
t.Errorf("ReadOnly should be false")
|
||||||
}
|
}
|
||||||
if AdmiralEndpoint() != "http://www.vmware.com" {
|
|
||||||
t.Errorf("Unexpected admiral endpoint: %s", AdmiralEndpoint())
|
|
||||||
}
|
|
||||||
|
|
||||||
extURL, err := ExtURL()
|
extURL, err := ExtURL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -17,9 +17,8 @@ package filter
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/goharbor/harbor/src/common/utils/oidc"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"strings"
|
||||||
|
|
||||||
beegoctx "github.com/astaxie/beego/context"
|
beegoctx "github.com/astaxie/beego/context"
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
@ -29,18 +28,14 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
secstore "github.com/goharbor/harbor/src/common/secret"
|
secstore "github.com/goharbor/harbor/src/common/secret"
|
||||||
"github.com/goharbor/harbor/src/common/security"
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
admr "github.com/goharbor/harbor/src/common/security/admiral"
|
|
||||||
"github.com/goharbor/harbor/src/common/security/admiral/authcontext"
|
|
||||||
"github.com/goharbor/harbor/src/common/security/local"
|
"github.com/goharbor/harbor/src/common/security/local"
|
||||||
robotCtx "github.com/goharbor/harbor/src/common/security/robot"
|
robotCtx "github.com/goharbor/harbor/src/common/security/robot"
|
||||||
"github.com/goharbor/harbor/src/common/security/secret"
|
"github.com/goharbor/harbor/src/common/security/secret"
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils/oidc"
|
||||||
"github.com/goharbor/harbor/src/core/auth"
|
"github.com/goharbor/harbor/src/core/auth"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
"github.com/goharbor/harbor/src/core/config"
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
"github.com/goharbor/harbor/src/core/promgr"
|
||||||
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/admiral"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/pkg/authproxy"
|
"github.com/goharbor/harbor/src/pkg/authproxy"
|
||||||
"github.com/goharbor/harbor/src/pkg/robot"
|
"github.com/goharbor/harbor/src/pkg/robot"
|
||||||
pkg_token "github.com/goharbor/harbor/src/pkg/token"
|
pkg_token "github.com/goharbor/harbor/src/pkg/token"
|
||||||
@ -95,17 +90,6 @@ var (
|
|||||||
|
|
||||||
// Init ReqCtxMofiers list
|
// Init ReqCtxMofiers list
|
||||||
func Init() {
|
func Init() {
|
||||||
// integration with admiral
|
|
||||||
if config.WithAdmiral() {
|
|
||||||
reqCtxModifiers = []ReqCtxModifier{
|
|
||||||
&secretReqCtxModifier{config.SecretStore},
|
|
||||||
&tokenReqCtxModifier{},
|
|
||||||
&basicAuthReqCtxModifier{},
|
|
||||||
&unauthorizedReqCtxModifier{}}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// standalone
|
|
||||||
reqCtxModifiers = []ReqCtxModifier{
|
reqCtxModifiers = []ReqCtxModifier{
|
||||||
&configCtxModifier{},
|
&configCtxModifier{},
|
||||||
&secretReqCtxModifier{config.SecretStore},
|
&secretReqCtxModifier{config.SecretStore},
|
||||||
@ -375,53 +359,6 @@ func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
|||||||
}
|
}
|
||||||
log.Debug("got user information via basic auth")
|
log.Debug("got user information via basic auth")
|
||||||
|
|
||||||
// integration with admiral
|
|
||||||
if config.WithAdmiral() {
|
|
||||||
// Can't get a token from Admiral's login API, we can only
|
|
||||||
// create a project manager with the token of the solution user.
|
|
||||||
// That way may cause some wrong permission promotion in some API
|
|
||||||
// calls, so we just handle the requests which are necessary
|
|
||||||
match := false
|
|
||||||
var err error
|
|
||||||
path := ctx.Request.URL.Path
|
|
||||||
for _, pattern := range basicAuthReqPatterns {
|
|
||||||
match, err = regexp.MatchString(pattern.path, path)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to match %s with pattern %s", path, pattern)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if match {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
log.Debugf("basic auth is not supported for request %s %s, skip",
|
|
||||||
ctx.Request.Method, ctx.Request.URL.Path)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := config.TokenReader.ReadToken()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to read solution user token: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
authCtx, err := authcontext.Login(config.AdmiralClient,
|
|
||||||
config.AdmiralEndpoint(), username, password, token)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to authenticate %s: %v", username, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("using global project manager...")
|
|
||||||
pm := config.GlobalProjectMgr
|
|
||||||
log.Debug("creating admiral security context...")
|
|
||||||
securCtx := admr.NewSecurityContext(authCtx, pm)
|
|
||||||
|
|
||||||
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// standalone
|
|
||||||
user, err := auth.Login(models.AuthModel{
|
user, err := auth.Login(models.AuthModel{
|
||||||
Principal: username,
|
Principal: username,
|
||||||
Password: password,
|
Password: password,
|
||||||
@ -466,61 +403,15 @@ func (s *sessionReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type tokenReqCtxModifier struct{}
|
|
||||||
|
|
||||||
func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
|
||||||
token := ctx.Request.Header.Get(authcontext.AuthTokenHeader)
|
|
||||||
if len(token) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("got token from request")
|
|
||||||
|
|
||||||
authContext, err := authcontext.GetAuthCtx(config.AdmiralClient,
|
|
||||||
config.AdmiralEndpoint(), token)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to get auth context: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("creating PMS project manager...")
|
|
||||||
driver := admiral.NewDriver(config.AdmiralClient,
|
|
||||||
config.AdmiralEndpoint(), &admiral.RawTokenReader{
|
|
||||||
Token: token,
|
|
||||||
})
|
|
||||||
|
|
||||||
pm := promgr.NewDefaultProjectManager(driver, false)
|
|
||||||
|
|
||||||
log.Debug("creating admiral security context...")
|
|
||||||
securCtx := admr.NewSecurityContext(authContext, pm)
|
|
||||||
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// use this one as the last modifier in the modifier list for unauthorized request
|
// use this one as the last modifier in the modifier list for unauthorized request
|
||||||
type unauthorizedReqCtxModifier struct{}
|
type unauthorizedReqCtxModifier struct{}
|
||||||
|
|
||||||
func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||||
log.Debug("user information is nil")
|
log.Debug("user information is nil")
|
||||||
|
log.Debug("using local database project manager")
|
||||||
var securCtx security.Context
|
pm := config.GlobalProjectMgr
|
||||||
var pm promgr.ProjectManager
|
log.Debug("creating local database security context...")
|
||||||
if config.WithAdmiral() {
|
securCtx := local.NewSecurityContext(nil, pm)
|
||||||
// integration with admiral
|
|
||||||
log.Debug("creating PMS project manager...")
|
|
||||||
driver := admiral.NewDriver(config.AdmiralClient,
|
|
||||||
config.AdmiralEndpoint(), nil)
|
|
||||||
pm = promgr.NewDefaultProjectManager(driver, false)
|
|
||||||
log.Debug("creating admiral security context...")
|
|
||||||
securCtx = admr.NewSecurityContext(nil, pm)
|
|
||||||
} else {
|
|
||||||
// standalone
|
|
||||||
log.Debug("using local database project manager")
|
|
||||||
pm = config.GlobalProjectMgr
|
|
||||||
log.Debug("creating local database security context...")
|
|
||||||
securCtx = local.NewSecurityContext(nil, pm)
|
|
||||||
}
|
|
||||||
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -175,9 +175,7 @@ func main() {
|
|||||||
beego.AddTemplateExt("htm")
|
beego.AddTemplateExt("htm")
|
||||||
|
|
||||||
log.Info("initializing configurations...")
|
log.Info("initializing configurations...")
|
||||||
if err := config.Init(); err != nil {
|
config.Init()
|
||||||
log.Fatalf("failed to initialize configurations: %v", err)
|
|
||||||
}
|
|
||||||
log.Info("configurations initialization completed")
|
log.Info("configurations initialization completed")
|
||||||
token.InitCreators()
|
token.InitCreators()
|
||||||
database, err := config.Database()
|
database, err := config.Database()
|
||||||
|
@ -15,20 +15,19 @@
|
|||||||
package contenttrust
|
package contenttrust
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common"
|
"github.com/goharbor/harbor/src/common"
|
||||||
notarytest "github.com/goharbor/harbor/src/common/utils/notary/test"
|
notarytest "github.com/goharbor/harbor/src/common/utils/notary/test"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
"github.com/goharbor/harbor/src/core/config"
|
||||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"net/http/httptest"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var endpoint = "10.117.4.142"
|
var endpoint = "10.117.4.142"
|
||||||
var notaryServer *httptest.Server
|
var notaryServer *httptest.Server
|
||||||
|
|
||||||
var admiralEndpoint = "http://127.0.0.1:8282"
|
|
||||||
var token = ""
|
var token = ""
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
@ -146,7 +146,7 @@ func TestMatchPushManifest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPMSPolicyChecker(t *testing.T) {
|
func TestPMSPolicyChecker(t *testing.T) {
|
||||||
var defaultConfigAdmiral = map[string]interface{}{
|
var defaultConfig = map[string]interface{}{
|
||||||
common.ExtEndpoint: "https://" + endpoint,
|
common.ExtEndpoint: "https://" + endpoint,
|
||||||
common.WithNotary: true,
|
common.WithNotary: true,
|
||||||
common.TokenExpiration: 30,
|
common.TokenExpiration: 30,
|
||||||
@ -158,11 +158,9 @@ func TestPMSPolicyChecker(t *testing.T) {
|
|||||||
common.PostGreSQLDatabase: "registry",
|
common.PostGreSQLDatabase: "registry",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := config.Init(); err != nil {
|
config.Init()
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Upload(defaultConfigAdmiral)
|
config.Upload(defaultConfig)
|
||||||
|
|
||||||
name := "project_for_test_get_sev_low"
|
name := "project_for_test_get_sev_low"
|
||||||
id, err := config.GlobalProjectMgr.Create(&models.Project{
|
id, err := config.GlobalProjectMgr.Create(&models.Project{
|
||||||
|
@ -1,421 +0,0 @@
|
|||||||
// Copyright 2018 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 admiral
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
|
||||||
"github.com/goharbor/harbor/src/common/utils"
|
|
||||||
er "github.com/goharbor/harbor/src/common/utils/error"
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
|
||||||
"github.com/goharbor/harbor/src/core/promgr/pmsdriver"
|
|
||||||
)
|
|
||||||
|
|
||||||
const dupProjectPattern = `Project name '\w+' is already used`
|
|
||||||
|
|
||||||
type driver struct {
|
|
||||||
client *http.Client
|
|
||||||
endpoint string
|
|
||||||
tokenReader TokenReader
|
|
||||||
}
|
|
||||||
|
|
||||||
type user struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type project struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Public bool `json:"isPublic"`
|
|
||||||
OwnerID string `json:"documentOwner"`
|
|
||||||
CustomProperties map[string]string `json:"customProperties"`
|
|
||||||
Administrators []*user `json:"administrators"`
|
|
||||||
Developers []*user `json:"members"`
|
|
||||||
Guests []*user `json:"viewers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDriver returns an instance of driver
|
|
||||||
func NewDriver(client *http.Client, endpoint string,
|
|
||||||
tokenReader TokenReader) pmsdriver.PMSDriver {
|
|
||||||
return &driver{
|
|
||||||
client: client,
|
|
||||||
endpoint: strings.TrimRight(endpoint, "/"),
|
|
||||||
tokenReader: tokenReader,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get ...
|
|
||||||
func (d *driver) Get(projectIDOrName interface{}) (*models.Project, error) {
|
|
||||||
project, err := d.get(projectIDOrName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return convert(project)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get Admiral project with Harbor project ID or name
|
|
||||||
func (d *driver) get(projectIDOrName interface{}) (*project, error) {
|
|
||||||
// if token is provided, search project from my projects list first
|
|
||||||
if len(d.getToken()) != 0 {
|
|
||||||
project, err := d.getFromMy(projectIDOrName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if project != nil {
|
|
||||||
return project, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to get project from public projects list
|
|
||||||
return d.getFromPublic(projectIDOrName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// call GET /projects?$filter=xxx eq xxx, the API can only filter projects
|
|
||||||
// which the user is a member of
|
|
||||||
func (d *driver) getFromMy(projectIDOrName interface{}) (*project, error) {
|
|
||||||
return d.getAdmiralProject(projectIDOrName, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// call GET /projects?public=true&$filter=xxx eq xxx
|
|
||||||
func (d *driver) getFromPublic(projectIDOrName interface{}) (*project, error) {
|
|
||||||
project, err := d.getAdmiralProject(projectIDOrName, true)
|
|
||||||
if project != nil {
|
|
||||||
// the projects returned by GET /projects?public=true&xxx have no
|
|
||||||
// "public" property, populate it here
|
|
||||||
project.Public = true
|
|
||||||
}
|
|
||||||
return project, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driver) getAdmiralProject(projectIDOrName interface{}, public bool) (*project, error) {
|
|
||||||
m := map[string]string{}
|
|
||||||
|
|
||||||
id, name, err := utils.ParseProjectIDOrName(projectIDOrName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if id > 0 {
|
|
||||||
m["customProperties.__projectIndex"] = strconv.FormatInt(id, 10)
|
|
||||||
} else {
|
|
||||||
m["name"] = name
|
|
||||||
}
|
|
||||||
if public {
|
|
||||||
m["public"] = "true"
|
|
||||||
}
|
|
||||||
|
|
||||||
projects, err := d.filter(m)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(projects) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(projects) != 1 {
|
|
||||||
for _, project := range projects {
|
|
||||||
fmt.Printf("%v", project)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unexpected size of project list: %d != 1", len(projects))
|
|
||||||
}
|
|
||||||
|
|
||||||
return projects[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driver) filter(m map[string]string) ([]*project, error) {
|
|
||||||
query := ""
|
|
||||||
for k, v := range m {
|
|
||||||
if len(query) == 0 {
|
|
||||||
query += "?"
|
|
||||||
} else {
|
|
||||||
query += "&"
|
|
||||||
}
|
|
||||||
if k == "public" {
|
|
||||||
query += fmt.Sprintf("%s=%s", k, v)
|
|
||||||
} else {
|
|
||||||
query += fmt.Sprintf("$filter=%s eq '%s'", k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(query) == 0 {
|
|
||||||
query = "?expand=true"
|
|
||||||
}
|
|
||||||
|
|
||||||
path := "/projects" + query
|
|
||||||
data, err := d.send(http.MethodGet, path, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return parse(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse the response of GET /projects?xxx to project list
|
|
||||||
func parse(b []byte) ([]*project, error) {
|
|
||||||
documents := &struct {
|
|
||||||
// DocumentCount int64 `json:"documentCount"`
|
|
||||||
Projects map[string]*project `json:"documents"`
|
|
||||||
}{}
|
|
||||||
if err := json.Unmarshal(b, documents); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
projects := []*project{}
|
|
||||||
for link, project := range documents.Projects {
|
|
||||||
project.ID = strings.TrimPrefix(link, "/projects/")
|
|
||||||
projects = append(projects, project)
|
|
||||||
}
|
|
||||||
|
|
||||||
return projects, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convert(p *project) (*models.Project, error) {
|
|
||||||
if p == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
project := &models.Project{
|
|
||||||
Name: p.Name,
|
|
||||||
}
|
|
||||||
if p.Public {
|
|
||||||
project.SetMetadata(models.ProMetaPublic, "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
value := p.CustomProperties["__projectIndex"]
|
|
||||||
if len(value) == 0 {
|
|
||||||
return nil, fmt.Errorf("property __projectIndex is null")
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse __projectIndex %s to int64: %v", value, err)
|
|
||||||
}
|
|
||||||
project.ProjectID = id
|
|
||||||
|
|
||||||
value = p.CustomProperties["__enableContentTrust"]
|
|
||||||
if len(value) != 0 {
|
|
||||||
enable, err := strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse __enableContentTrust %s to bool: %v", value, err)
|
|
||||||
}
|
|
||||||
project.SetMetadata(models.ProMetaEnableContentTrust, strconv.FormatBool(enable))
|
|
||||||
}
|
|
||||||
|
|
||||||
value = p.CustomProperties["__preventVulnerableImagesFromRunning"]
|
|
||||||
if len(value) != 0 {
|
|
||||||
prevent, err := strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse __preventVulnerableImagesFromRunning %s to bool: %v", value, err)
|
|
||||||
}
|
|
||||||
project.SetMetadata(models.ProMetaPreventVul, strconv.FormatBool(prevent))
|
|
||||||
}
|
|
||||||
|
|
||||||
value = p.CustomProperties["__preventVulnerableImagesFromRunningSeverity"]
|
|
||||||
if len(value) != 0 {
|
|
||||||
project.SetMetadata(models.ProMetaSeverity, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
value = p.CustomProperties["__automaticallyScanImagesOnPush"]
|
|
||||||
if len(value) != 0 {
|
|
||||||
scan, err := strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse __automaticallyScanImagesOnPush %s to bool: %v", value, err)
|
|
||||||
}
|
|
||||||
project.SetMetadata(models.ProMetaAutoScan, strconv.FormatBool(scan))
|
|
||||||
}
|
|
||||||
|
|
||||||
return project, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driver) getIDbyHarborIDOrName(projectIDOrName interface{}) (string, error) {
|
|
||||||
pro, err := d.get(projectIDOrName)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if pro == nil {
|
|
||||||
return "", fmt.Errorf("project %v not found", projectIDOrName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pro.ID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create ...
|
|
||||||
func (d *driver) Create(pro *models.Project) (int64, error) {
|
|
||||||
proj := &project{
|
|
||||||
CustomProperties: make(map[string]string),
|
|
||||||
}
|
|
||||||
proj.Name = pro.Name
|
|
||||||
proj.Public = pro.IsPublic()
|
|
||||||
proj.CustomProperties["__enableContentTrust"] = strconv.FormatBool(pro.ContentTrustEnabled())
|
|
||||||
proj.CustomProperties["__preventVulnerableImagesFromRunning"] = strconv.FormatBool(pro.VulPrevented())
|
|
||||||
proj.CustomProperties["__preventVulnerableImagesFromRunningSeverity"] = pro.Severity()
|
|
||||||
proj.CustomProperties["__automaticallyScanImagesOnPush"] = strconv.FormatBool(pro.AutoScan())
|
|
||||||
|
|
||||||
data, err := json.Marshal(proj)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := d.send(http.MethodPost, "/projects", bytes.NewBuffer(data))
|
|
||||||
if err != nil {
|
|
||||||
// when creating a project with a duplicate name in Admiral, a 500 error
|
|
||||||
// with a specific message will be returned for now.
|
|
||||||
// Maybe a 409 error will be returned if Admiral team finds the way to
|
|
||||||
// return a specific code in Xenon.
|
|
||||||
// The following codes convert both those two errors to DupProjectErr
|
|
||||||
httpErr, ok := err.(*commonhttp.Error)
|
|
||||||
if !ok {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if httpErr.Code == http.StatusConflict {
|
|
||||||
return 0, er.ErrDupProject
|
|
||||||
}
|
|
||||||
|
|
||||||
if httpErr.Code != http.StatusInternalServerError {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
match, e := regexp.MatchString(dupProjectPattern, httpErr.Message)
|
|
||||||
if e != nil {
|
|
||||||
log.Errorf("failed to match duplicate project mattern: %v", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if match {
|
|
||||||
err = er.ErrDupProject
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
proj = &project{}
|
|
||||||
if err = json.Unmarshal(b, proj); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pp, err := convert(proj)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pp.ProjectID, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete ...
|
|
||||||
func (d *driver) Delete(projectIDOrName interface{}) error {
|
|
||||||
id, err := d.getIDbyHarborIDOrName(projectIDOrName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = d.send(http.MethodDelete, fmt.Sprintf("/projects/%s", id), nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update ...
|
|
||||||
func (d *driver) Update(projectIDOrName interface{}, project *models.Project) error {
|
|
||||||
return errors.New("project update is unsupported")
|
|
||||||
}
|
|
||||||
|
|
||||||
// List ...
|
|
||||||
func (d *driver) List(query *models.ProjectQueryParam) (*models.ProjectQueryResult, error) {
|
|
||||||
m := map[string]string{}
|
|
||||||
if query != nil {
|
|
||||||
if len(query.Name) > 0 {
|
|
||||||
m["name"] = query.Name
|
|
||||||
}
|
|
||||||
if query.Public != nil {
|
|
||||||
m["public"] = strconv.FormatBool(*query.Public)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
projects, err := d.filter(m)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
list := []*models.Project{}
|
|
||||||
for _, p := range projects {
|
|
||||||
project, err := convert(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
list = append(list, project)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &models.ProjectQueryResult{
|
|
||||||
Total: int64(len(list)),
|
|
||||||
Projects: list,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driver) send(method, path string, body io.Reader) ([]byte, error) {
|
|
||||||
req, err := http.NewRequest(method, d.endpoint+path, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("x-xenon-auth-token", d.getToken())
|
|
||||||
|
|
||||||
url := req.URL.String()
|
|
||||||
|
|
||||||
req.URL.RawQuery = req.URL.Query().Encode()
|
|
||||||
resp, err := d.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("\"%s %s\" failed", req.Method, url)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
log.Debugf("\"%s %s\" %d", req.Method, url, resp.StatusCode)
|
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, &commonhttp.Error{
|
|
||||||
Code: resp.StatusCode,
|
|
||||||
Message: string(b),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driver) getToken() string {
|
|
||||||
if d.tokenReader == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := d.tokenReader.ReadToken()
|
|
||||||
if err != nil {
|
|
||||||
token = ""
|
|
||||||
log.Errorf("failed to read token: %v", err)
|
|
||||||
}
|
|
||||||
return token
|
|
||||||
}
|
|
@ -1,354 +0,0 @@
|
|||||||
// Copyright 2018 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 admiral
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
|
||||||
errutil "github.com/goharbor/harbor/src/common/utils/error"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
client = http.DefaultClient
|
|
||||||
endpoint = "http://127.0.0.1:8282"
|
|
||||||
tokenReader = &RawTokenReader{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConvert(t *testing.T) {
|
|
||||||
// nil project
|
|
||||||
pro, err := convert(nil)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Nil(t, pro)
|
|
||||||
|
|
||||||
// project without property __projectIndex
|
|
||||||
p := &project{}
|
|
||||||
pro, err = convert(p)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Nil(t, pro)
|
|
||||||
|
|
||||||
// project with invalid __projectIndex
|
|
||||||
p = &project{
|
|
||||||
CustomProperties: map[string]string{
|
|
||||||
"__projectIndex": "invalid_value",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pro, err = convert(p)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Nil(t, pro)
|
|
||||||
|
|
||||||
// project with invalid __enableContentTrust
|
|
||||||
p = &project{
|
|
||||||
CustomProperties: map[string]string{
|
|
||||||
"__enableContentTrust": "invalid_value",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pro, err = convert(p)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Nil(t, pro)
|
|
||||||
|
|
||||||
// project with invalid __preventVulnerableImagesFromRunning
|
|
||||||
p = &project{
|
|
||||||
CustomProperties: map[string]string{
|
|
||||||
"__preventVulnerableImagesFromRunning": "invalid_value",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pro, err = convert(p)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Nil(t, pro)
|
|
||||||
|
|
||||||
// project with invalid __automaticallyScanImagesOnPush
|
|
||||||
p = &project{
|
|
||||||
CustomProperties: map[string]string{
|
|
||||||
"__automaticallyScanImagesOnPush": "invalid_value",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pro, err = convert(p)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Nil(t, pro)
|
|
||||||
|
|
||||||
// valid project
|
|
||||||
p = &project{
|
|
||||||
Name: "test",
|
|
||||||
Public: true,
|
|
||||||
CustomProperties: map[string]string{
|
|
||||||
"__projectIndex": "1",
|
|
||||||
"__enableContentTrust": "true",
|
|
||||||
"__preventVulnerableImagesFromRunning": "true",
|
|
||||||
"__preventVulnerableImagesFromRunningSeverity": "medium",
|
|
||||||
"__automaticallyScanImagesOnPush": "true",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pro, err = convert(p)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, pro)
|
|
||||||
assert.Equal(t, "test", pro.Name)
|
|
||||||
assert.True(t, pro.IsPublic())
|
|
||||||
assert.Equal(t, int64(1), pro.ProjectID)
|
|
||||||
assert.True(t, pro.ContentTrustEnabled())
|
|
||||||
assert.True(t, pro.VulPrevented())
|
|
||||||
assert.Equal(t, "medium", pro.Severity())
|
|
||||||
assert.True(t, pro.AutoScan())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
|
||||||
data := `{
|
|
||||||
"totalCount": 2,
|
|
||||||
"documentLinks": [
|
|
||||||
"/projects/default-project",
|
|
||||||
"/projects/fc6c6c7ddd430875551449a65e7c8"
|
|
||||||
],
|
|
||||||
"documents": {
|
|
||||||
"/projects/fc6c6c7ddd430875551449a65e7c8": {
|
|
||||||
"isPublic": false,
|
|
||||||
"description": "This is a test project.",
|
|
||||||
"id": "41427587-70e9-4671-9a9e-b9def0a07bb7",
|
|
||||||
"name": "project02",
|
|
||||||
"customProperties": {
|
|
||||||
"__projectIndex": "2",
|
|
||||||
"__enableContentTrust": "true",
|
|
||||||
"__preventVulnerableImagesFromRunning": "true",
|
|
||||||
"__preventVulnerableImagesFromRunningSeverity": "medium",
|
|
||||||
"__automaticallyScanImagesOnPush": "false"
|
|
||||||
},
|
|
||||||
"documentVersion": 0,
|
|
||||||
"documentEpoch": 0,
|
|
||||||
"documentKind": "com:vmware:admiral:auth:project:ProjectService:ProjectState",
|
|
||||||
"documentSelfLink": "/projects/fc6c6c7ddd430875551449a65e7c8",
|
|
||||||
"documentUpdateTimeMicros": 1496729973549001,
|
|
||||||
"documentUpdateAction": "POST",
|
|
||||||
"documentExpirationTimeMicros": 0,
|
|
||||||
"documentOwner": "f65900c4-2b6a-4671-8cf7-c17340dd3d39"
|
|
||||||
},
|
|
||||||
"/projects/default-project": {
|
|
||||||
"isPublic": false,
|
|
||||||
"administratorsUserGroupLink": "/core/authz/user-groups/fc6c6c7ddd43087555143835bcaf8",
|
|
||||||
"membersUserGroupLink": "/core/authz/user-groups/fc6c6c7ddd43087555143835bde80",
|
|
||||||
"id": "default-project",
|
|
||||||
"name": "default-project",
|
|
||||||
"customProperties": {
|
|
||||||
"__projectIndex": "2",
|
|
||||||
"__enableContentTrust": "true",
|
|
||||||
"__preventVulnerableImagesFromRunning": "true",
|
|
||||||
"__preventVulnerableImagesFromRunningSeverity": "medium",
|
|
||||||
"__automaticallyScanImagesOnPush": "false"
|
|
||||||
},
|
|
||||||
"documentVersion": 0,
|
|
||||||
"documentEpoch": 0,
|
|
||||||
"documentKind": "com:vmware:admiral:auth:project:ProjectService:ProjectState",
|
|
||||||
"documentSelfLink": "/projects/default-project",
|
|
||||||
"documentUpdateTimeMicros": 1496725292012001,
|
|
||||||
"documentUpdateAction": "POST",
|
|
||||||
"documentExpirationTimeMicros": 0,
|
|
||||||
"documentOwner": "f65900c4-2b6a-4671-8cf7-c17340dd3d39",
|
|
||||||
"documentAuthPrincipalLink": "/core/authz/system-user"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"documentCount": 2,
|
|
||||||
"queryTimeMicros": 1,
|
|
||||||
"documentVersion": 0,
|
|
||||||
"documentUpdateTimeMicros": 0,
|
|
||||||
"documentExpirationTimeMicros": 0,
|
|
||||||
"documentOwner": "f65900c4-2b6a-4671-8cf7-c17340dd3d39"
|
|
||||||
}`
|
|
||||||
|
|
||||||
projects, err := parse([]byte(data))
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, 2, len(projects))
|
|
||||||
|
|
||||||
ids := []string{projects[0].ID, projects[1].ID}
|
|
||||||
sort.Strings(ids)
|
|
||||||
|
|
||||||
assert.Equal(t, "default-project", ids[0])
|
|
||||||
assert.Equal(t, "fc6c6c7ddd430875551449a65e7c8", ids[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
|
||||||
d := NewDriver(client, endpoint, tokenReader)
|
|
||||||
name := "project_for_test_get"
|
|
||||||
id, err := d.Create(&models.Project{
|
|
||||||
Name: name,
|
|
||||||
})
|
|
||||||
require.Nil(t, err)
|
|
||||||
defer deleteProject(t, id)
|
|
||||||
|
|
||||||
// get by invalid input type
|
|
||||||
_, err = d.Get([]string{})
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
|
|
||||||
// get by invalid ID
|
|
||||||
project, err := d.Get(int64(0))
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Nil(t, project)
|
|
||||||
|
|
||||||
// get by invalid name
|
|
||||||
project, err = d.Get("invalid_name")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Nil(t, project)
|
|
||||||
|
|
||||||
// get by valid ID
|
|
||||||
project, err = d.Get(id)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, id, project.ProjectID)
|
|
||||||
|
|
||||||
// get by valid name
|
|
||||||
project, err = d.Get(name)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, id, project.ProjectID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
|
||||||
d := NewDriver(client, endpoint, tokenReader)
|
|
||||||
|
|
||||||
name := "project_for_test_create"
|
|
||||||
id, err := d.Create(&models.Project{
|
|
||||||
Name: name,
|
|
||||||
Metadata: map[string]string{
|
|
||||||
models.ProMetaPublic: "true",
|
|
||||||
models.ProMetaEnableContentTrust: "true",
|
|
||||||
models.ProMetaPreventVul: "true",
|
|
||||||
models.ProMetaSeverity: "medium",
|
|
||||||
models.ProMetaAutoScan: "true",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
require.Nil(t, err)
|
|
||||||
defer deleteProject(t, id)
|
|
||||||
|
|
||||||
project, err := d.Get(id)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, name, project.Name)
|
|
||||||
assert.True(t, project.IsPublic())
|
|
||||||
assert.True(t, project.ContentTrustEnabled())
|
|
||||||
assert.True(t, project.VulPrevented())
|
|
||||||
assert.Equal(t, "medium", project.Severity())
|
|
||||||
assert.True(t, project.AutoScan())
|
|
||||||
|
|
||||||
// duplicate project name
|
|
||||||
_, err = d.Create(&models.Project{
|
|
||||||
Name: name,
|
|
||||||
})
|
|
||||||
assert.Equal(t, errutil.ErrDupProject, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDelete(t *testing.T) {
|
|
||||||
d := NewDriver(client, endpoint, tokenReader)
|
|
||||||
|
|
||||||
// non-exist project
|
|
||||||
err := d.Delete(int64(0))
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
|
|
||||||
// delete by ID
|
|
||||||
name := "project_for_pm_based_on_pms_id"
|
|
||||||
id, err := d.Create(&models.Project{
|
|
||||||
Name: name,
|
|
||||||
})
|
|
||||||
require.Nil(t, err)
|
|
||||||
err = d.Delete(id)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
// delete by name
|
|
||||||
name = "project_for_pm_based_on_pms_name"
|
|
||||||
id, err = d.Create(&models.Project{
|
|
||||||
Name: name,
|
|
||||||
})
|
|
||||||
require.Nil(t, err)
|
|
||||||
err = d.Delete(name)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
|
||||||
d := NewDriver(client, endpoint, tokenReader)
|
|
||||||
err := d.Update(nil, nil)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
|
||||||
d := NewDriver(client, endpoint, tokenReader)
|
|
||||||
|
|
||||||
name1 := "project_for_test_get_all_01"
|
|
||||||
id1, err := d.Create(&models.Project{
|
|
||||||
Name: name1,
|
|
||||||
})
|
|
||||||
require.Nil(t, err)
|
|
||||||
defer deleteProject(t, id1)
|
|
||||||
|
|
||||||
name2 := "project_for_test_get_all_02"
|
|
||||||
id2, err := d.Create(&models.Project{
|
|
||||||
Name: name2,
|
|
||||||
Metadata: map[string]string{
|
|
||||||
models.ProMetaPublic: "true",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
require.Nil(t, err)
|
|
||||||
defer deleteProject(t, id2)
|
|
||||||
|
|
||||||
// no filter
|
|
||||||
result, err := d.List(nil)
|
|
||||||
require.Nil(t, err)
|
|
||||||
found1 := false
|
|
||||||
found2 := false
|
|
||||||
for _, project := range result.Projects {
|
|
||||||
if project.ProjectID == id1 {
|
|
||||||
found1 = true
|
|
||||||
}
|
|
||||||
if project.ProjectID == id2 {
|
|
||||||
found2 = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert.True(t, found1)
|
|
||||||
assert.True(t, found2)
|
|
||||||
|
|
||||||
// filter by name
|
|
||||||
result, err = d.List(&models.ProjectQueryParam{
|
|
||||||
Name: name1,
|
|
||||||
})
|
|
||||||
require.Nil(t, err)
|
|
||||||
found1 = false
|
|
||||||
for _, project := range result.Projects {
|
|
||||||
if project.ProjectID == id1 {
|
|
||||||
found1 = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert.True(t, found1)
|
|
||||||
|
|
||||||
// filter by public
|
|
||||||
value := true
|
|
||||||
result, err = d.List(&models.ProjectQueryParam{
|
|
||||||
Public: &value,
|
|
||||||
})
|
|
||||||
require.Nil(t, err)
|
|
||||||
found2 = false
|
|
||||||
for _, project := range result.Projects {
|
|
||||||
if project.ProjectID == id2 {
|
|
||||||
found2 = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert.True(t, found2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteProject(t *testing.T, id int64) {
|
|
||||||
d := NewDriver(client, endpoint, tokenReader)
|
|
||||||
if err := d.Delete(id); err != nil {
|
|
||||||
t.Logf("failed to delete project %d: %v", id, err)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
// Copyright Project Harbor Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package admiral
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
key = "access_token"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TokenReader is an interface used to wrap the way how to get token
|
|
||||||
type TokenReader interface {
|
|
||||||
// ReadToken reads token
|
|
||||||
ReadToken() (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RawTokenReader just returns the token contained by field Token
|
|
||||||
type RawTokenReader struct {
|
|
||||||
Token string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadToken ...
|
|
||||||
func (r *RawTokenReader) ReadToken() (string, error) {
|
|
||||||
return r.Token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileTokenReader reads token from file
|
|
||||||
type FileTokenReader struct {
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadToken ...
|
|
||||||
func (f *FileTokenReader) ReadToken() (string, error) {
|
|
||||||
data, err := ioutil.ReadFile(f.Path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return strings.TrimRight(string(data), "\n"), nil
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
// Copyright Project Harbor Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package admiral
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRawTokenReader(t *testing.T) {
|
|
||||||
raw := "token"
|
|
||||||
reader := &RawTokenReader{
|
|
||||||
Token: raw,
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := reader.ReadToken()
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, raw, token)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFileTokenReader(t *testing.T) {
|
|
||||||
// file not exist
|
|
||||||
path := "/tmp/not_exist_file"
|
|
||||||
reader := &FileTokenReader{
|
|
||||||
Path: path,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := reader.ReadToken()
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
|
|
||||||
// file exist
|
|
||||||
path = "/tmp/exist_file"
|
|
||||||
err = ioutil.WriteFile(path, []byte("token"), 0x0766)
|
|
||||||
require.Nil(t, err)
|
|
||||||
defer os.Remove(path)
|
|
||||||
|
|
||||||
reader = &FileTokenReader{
|
|
||||||
Path: path,
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := reader.ReadToken()
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, "token", token)
|
|
||||||
}
|
|
@ -28,40 +28,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func initRouters() {
|
func initRouters() {
|
||||||
|
// Controller API:
|
||||||
|
beego.Router("/c/login", &controllers.CommonController{}, "post:Login")
|
||||||
|
beego.Router("/c/log_out", &controllers.CommonController{}, "get:LogOut")
|
||||||
|
beego.Router("/c/reset", &controllers.CommonController{}, "post:ResetPassword")
|
||||||
|
beego.Router("/c/userExists", &controllers.CommonController{}, "post:UserExists")
|
||||||
|
beego.Router("/c/sendEmail", &controllers.CommonController{}, "get:SendResetEmail")
|
||||||
|
beego.Router(common.OIDCLoginPath, &controllers.OIDCController{}, "get:RedirectLogin")
|
||||||
|
beego.Router("/c/oidc/onboard", &controllers.OIDCController{}, "post:Onboard")
|
||||||
|
beego.Router(common.OIDCCallbackPath, &controllers.OIDCController{}, "get:Callback")
|
||||||
|
|
||||||
// standalone
|
// API:
|
||||||
if !config.WithAdmiral() {
|
beego.Router("/api/projects/:pid([0-9]+)/members/?:pmid([0-9]+)", &api.ProjectMemberAPI{})
|
||||||
// Controller API:
|
beego.Router("/api/projects/", &api.ProjectAPI{}, "head:Head")
|
||||||
beego.Router("/c/login", &controllers.CommonController{}, "post:Login")
|
beego.Router("/api/projects/:id([0-9]+)", &api.ProjectAPI{})
|
||||||
beego.Router("/c/log_out", &controllers.CommonController{}, "get:LogOut")
|
beego.Router("/api/users/:id", &api.UserAPI{}, "get:Get;delete:Delete;put:Put")
|
||||||
beego.Router("/c/reset", &controllers.CommonController{}, "post:ResetPassword")
|
beego.Router("/api/users", &api.UserAPI{}, "get:List;post:Post")
|
||||||
beego.Router("/c/userExists", &controllers.CommonController{}, "post:UserExists")
|
beego.Router("/api/users/search", &api.UserAPI{}, "get:Search")
|
||||||
beego.Router("/c/sendEmail", &controllers.CommonController{}, "get:SendResetEmail")
|
beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
|
||||||
beego.Router(common.OIDCLoginPath, &controllers.OIDCController{}, "get:RedirectLogin")
|
beego.Router("/api/users/:id/permissions", &api.UserAPI{}, "get:ListUserPermissions")
|
||||||
beego.Router("/c/oidc/onboard", &controllers.OIDCController{}, "post:Onboard")
|
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
||||||
beego.Router(common.OIDCCallbackPath, &controllers.OIDCController{}, "get:Callback")
|
beego.Router("/api/users/:id/cli_secret", &api.UserAPI{}, "put:SetCLISecret")
|
||||||
|
beego.Router("/api/usergroups/?:ugid([0-9]+)", &api.UserGroupAPI{})
|
||||||
// API:
|
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
|
||||||
beego.Router("/api/projects/:pid([0-9]+)/members/?:pmid([0-9]+)", &api.ProjectMemberAPI{})
|
beego.Router("/api/ldap/users/search", &api.LdapAPI{}, "get:Search")
|
||||||
beego.Router("/api/projects/", &api.ProjectAPI{}, "head:Head")
|
beego.Router("/api/ldap/groups/search", &api.LdapAPI{}, "get:SearchGroup")
|
||||||
beego.Router("/api/projects/:id([0-9]+)", &api.ProjectAPI{})
|
beego.Router("/api/ldap/users/import", &api.LdapAPI{}, "post:ImportUser")
|
||||||
|
beego.Router("/api/email/ping", &api.EmailAPI{}, "post:Ping")
|
||||||
beego.Router("/api/users/:id", &api.UserAPI{}, "get:Get;delete:Delete;put:Put")
|
|
||||||
beego.Router("/api/users", &api.UserAPI{}, "get:List;post:Post")
|
|
||||||
beego.Router("/api/users/search", &api.UserAPI{}, "get:Search")
|
|
||||||
beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
|
|
||||||
beego.Router("/api/users/:id/permissions", &api.UserAPI{}, "get:ListUserPermissions")
|
|
||||||
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
|
||||||
beego.Router("/api/users/:id/cli_secret", &api.UserAPI{}, "put:SetCLISecret")
|
|
||||||
beego.Router("/api/usergroups/?:ugid([0-9]+)", &api.UserGroupAPI{})
|
|
||||||
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
|
|
||||||
beego.Router("/api/ldap/users/search", &api.LdapAPI{}, "get:Search")
|
|
||||||
beego.Router("/api/ldap/groups/search", &api.LdapAPI{}, "get:SearchGroup")
|
|
||||||
beego.Router("/api/ldap/users/import", &api.LdapAPI{}, "post:ImportUser")
|
|
||||||
beego.Router("/api/email/ping", &api.EmailAPI{}, "post:Ping")
|
|
||||||
}
|
|
||||||
|
|
||||||
// API
|
|
||||||
beego.Router("/api/health", &api.HealthAPI{}, "get:CheckHealth")
|
beego.Router("/api/health", &api.HealthAPI{}, "get:CheckHealth")
|
||||||
beego.Router("/api/ping", &api.SystemInfoAPI{}, "get:Ping")
|
beego.Router("/api/ping", &api.SystemInfoAPI{}, "get:Ping")
|
||||||
beego.Router("/api/search", &api.SearchAPI{})
|
beego.Router("/api/search", &api.SearchAPI{})
|
||||||
@ -71,8 +64,6 @@ func initRouters() {
|
|||||||
beego.Router("/api/projects/:id([0-9]+)/_deletable", &api.ProjectAPI{}, "get:Deletable")
|
beego.Router("/api/projects/:id([0-9]+)/_deletable", &api.ProjectAPI{}, "get:Deletable")
|
||||||
beego.Router("/api/projects/:id([0-9]+)/metadatas/?:name", &api.MetadataAPI{}, "get:Get")
|
beego.Router("/api/projects/:id([0-9]+)/metadatas/?:name", &api.MetadataAPI{}, "get:Get")
|
||||||
beego.Router("/api/projects/:id([0-9]+)/metadatas/", &api.MetadataAPI{}, "post:Post")
|
beego.Router("/api/projects/:id([0-9]+)/metadatas/", &api.MetadataAPI{}, "post:Post")
|
||||||
beego.Router("/api/projects/:id([0-9]+)/metadatas/:name", &api.MetadataAPI{}, "put:Put;delete:Delete")
|
|
||||||
|
|
||||||
beego.Router("/api/projects/:pid([0-9]+)/robots", &api.RobotAPI{}, "post:Post;get:List")
|
beego.Router("/api/projects/:pid([0-9]+)/robots", &api.RobotAPI{}, "post:Post;get:List")
|
||||||
beego.Router("/api/projects/:pid([0-9]+)/robots/:id([0-9]+)", &api.RobotAPI{}, "get:Get;put:Put;delete:Delete")
|
beego.Router("/api/projects/:pid([0-9]+)/robots/:id([0-9]+)", &api.RobotAPI{}, "get:Get;put:Put;delete:Delete")
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
package token
|
package token
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/dgrijalva/jwt-go"
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
"github.com/docker/distribution/registry/auth/token"
|
"github.com/docker/distribution/registry/auth/token"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
@ -35,9 +35,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
if err := config.Init(); err != nil {
|
config.Init()
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
InitCreators()
|
InitCreators()
|
||||||
result := m.Run()
|
result := m.Run()
|
||||||
if result != 0 {
|
if result != 0 {
|
||||||
|
@ -21,10 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
err := config.Init()
|
config.Init()
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
rc := m.Run()
|
rc := m.Run()
|
||||||
os.Exit(rc)
|
os.Exit(rc)
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dgrijalva/jwt-go"
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
"github.com/goharbor/harbor/src/core/config"
|
||||||
robot_claim "github.com/goharbor/harbor/src/pkg/token/claims/robot"
|
robot_claim "github.com/goharbor/harbor/src/pkg/token/claims/robot"
|
||||||
@ -13,9 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
if err := config.Init(); err != nil {
|
config.Init()
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := m.Run()
|
result := m.Run()
|
||||||
if result != 0 {
|
if result != 0 {
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# run admiral for unit test
|
|
||||||
name=admiral
|
|
||||||
port=8282
|
|
||||||
docker rm -f $name 2>/dev/null
|
|
||||||
docker run -d -p $port:8282 --name $name vmware/admiral:v1.2.1
|
|
||||||
|
|
||||||
# solution user token file for test
|
|
||||||
mkdir -p /etc/core/token/
|
|
||||||
echo "token" > /etc/core/token/tokens.properties
|
|
@ -1,105 +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
|
|
||||||
|
|
||||||
*** Settings ***
|
|
||||||
Documentation This resource contains all keywords related to creating, deleting, maintaining an instance of Admiral
|
|
||||||
Library Selenium2Library 5 5
|
|
||||||
Library OperatingSystem
|
|
||||||
|
|
||||||
*** Keywords ***
|
|
||||||
Install Admiral
|
|
||||||
${rc} ${output}= Run And Return Rc And Output docker %{VCH-PARAMS} run -d -p 8282:8282 --name admiral vmware/admiral:vic_v1.1.0
|
|
||||||
Should Be Equal As Integers 0 ${rc}
|
|
||||||
Set Environment Variable ADMIRAL_IP %{VCH-IP}:8282
|
|
||||||
:FOR ${idx} IN RANGE 0 10
|
|
||||||
\ ${out}= Run curl %{ADMIRAL_IP}
|
|
||||||
\ ${status}= Run Keyword And Return Status Should Contain ${out} <body class="admiral-default">
|
|
||||||
\ Return From Keyword If ${status}
|
|
||||||
\ Sleep 5
|
|
||||||
Fail Install Admiral failed: Admiral endpoint failed to respond to curl
|
|
||||||
|
|
||||||
Cleanup Admiral
|
|
||||||
${rc} ${output}= Run And Return Rc And Output docker %{VCH-PARAMS} rm -f admiral
|
|
||||||
Should Be Equal As Integers 0 ${rc}
|
|
||||||
|
|
||||||
Login To Admiral
|
|
||||||
[Arguments] ${url}=localhost:8282 ${browser}=chrome
|
|
||||||
Open Browser ${url} ${browser}
|
|
||||||
Maximize Browser Window
|
|
||||||
Wait Until Page Contains Welcome!
|
|
||||||
Wait Until Page Contains This is the place for you to create, provision, manage and monitor containerized applications.
|
|
||||||
Wait Until Element Is Visible css=button.btn.btn-primary.enter-btn
|
|
||||||
Wait Until Element Is Enabled css=button.btn.btn-primary.enter-btn
|
|
||||||
Click Button css=button.btn.btn-primary.enter-btn
|
|
||||||
Wait Until Element Is Visible css=div.query-search-input-controls.form-control
|
|
||||||
Wait Until Element Is Enabled css=div.query-search-input-controls.form-control
|
|
||||||
Wait Until Element Is Visible css=a.btn.btn-primary.addHost-btn
|
|
||||||
Wait Until Element Is Enabled css=a.btn.btn-primary.addHost-btn
|
|
||||||
|
|
||||||
Add Host To Admiral
|
|
||||||
[Arguments] ${address} ${credentials}=${EMPTY}
|
|
||||||
Wait Until Element Is Visible css=a[data-cmd='navigation-hosts']
|
|
||||||
Wait Until Element Is Enabled css=a[data-cmd='navigation-hosts']
|
|
||||||
Click Element css=a[data-cmd='navigation-hosts']
|
|
||||||
|
|
||||||
Wait Until Element Is Visible css=div.query-search-input-controls.form-control
|
|
||||||
Wait Until Element Is Enabled css=div.query-search-input-controls.form-control
|
|
||||||
Wait Until Element Is Visible css=a.btn.btn-primary.addHost-btn
|
|
||||||
Wait Until Element Is Enabled css=a.btn.btn-primary.addHost-btn
|
|
||||||
Click Element css=a.btn.btn-primary.addHost-btn
|
|
||||||
|
|
||||||
Wait Until Page Contains Add Host
|
|
||||||
Wait Until Element Is Visible address
|
|
||||||
Wait Until Element Is Enabled address
|
|
||||||
Wait Until Element Is Visible credential
|
|
||||||
Wait Until Element Is Enabled credential
|
|
||||||
|
|
||||||
Input Text css=#address > input.form-control ${address}
|
|
||||||
|
|
||||||
Select From List css=#hostType > div.select > select VCH
|
|
||||||
|
|
||||||
Run Keyword If '${credentials}' != '${EMPTY}' Click Element css=#credential > div.form-control > div.dropdown > button.dropdown-toggle
|
|
||||||
Run Keyword If '${credentials}' != '${EMPTY}' Click Element css=a[data-name=${credentials}]
|
|
||||||
|
|
||||||
Wait Until Element Is Visible css=a.btn.verifyHost
|
|
||||||
Wait Until Element Is Enabled css=a.btn.verifyHost
|
|
||||||
Click Element css=a.btn.verifyHost
|
|
||||||
|
|
||||||
Wait Until Page Contains Verified successfully!
|
|
||||||
Wait Until Element Is Visible css=button.btn.btn-primary.saveHost
|
|
||||||
Wait Until Element Is Enabled css=button.btn.btn-primary.saveHost
|
|
||||||
Click Button css=button.btn.btn-primary.saveHost
|
|
||||||
|
|
||||||
Wait Until Element Is Visible css=div.status.ON
|
|
||||||
Wait Until Element Is Enabled css=div.status.ON
|
|
||||||
|
|
||||||
|
|
||||||
Add Project to Admiral
|
|
||||||
[Arguments] ${name}
|
|
||||||
Wait Until Element Is Visible css=a[data-cmd='navigation-placements']
|
|
||||||
Wait Until Element Is Enabled css=a[data-cmd='navigation-placements']
|
|
||||||
Click Element css=a[data-cmd='navigation-placements']
|
|
||||||
|
|
||||||
Wait Until Element Is Visible css=div.right-context-panel > div.toolbar > div:nth-child(2) > a
|
|
||||||
Wait Until Element Is Enabled css=div.right-context-panel > div.toolbar > div:nth-child(2) > a
|
|
||||||
Click Element css=div.right-context-panel > div.toolbar > div:nth-child(2) > a
|
|
||||||
|
|
||||||
Wait Until Page Contains Project
|
|
||||||
Wait Until Element Is Visible css=div.right-context-panel > div.content > div > div.list-holder > div.inline-editable-list-holder > div.inline-editable-list > div.toolbar > a.new-item
|
|
||||||
Wait Until Element Is Enabled css=div.right-context-panel > div.content > div > div.list-holder > div.inline-editable-list-holder > div.inline-editable-list > div.toolbar > a.new-item
|
|
||||||
Click Element css=div.right-context-panel > div.content > div > div.list-holder > div.inline-editable-list-holder > div.inline-editable-list > div.toolbar > a.new-item
|
|
||||||
Input Text css=input.name-input ${name}
|
|
||||||
Click Element css=button.btn.btn-primary.inline-edit-save
|
|
||||||
|
|
||||||
Table Should Contain css=div.right-context-panel > div.content > div > div > div > div > table ${name}
|
|
@ -68,7 +68,6 @@ Resource Harbor-Pages/OIDC_Auth_Elements.robot
|
|||||||
Resource Harbor-Pages/Verify.robot
|
Resource Harbor-Pages/Verify.robot
|
||||||
Resource Docker-Util.robot
|
Resource Docker-Util.robot
|
||||||
Resource Helm-Util.robot
|
Resource Helm-Util.robot
|
||||||
Resource Admiral-Util.robot
|
|
||||||
Resource OVA-Util.robot
|
Resource OVA-Util.robot
|
||||||
Resource Cert-Util.robot
|
Resource Cert-Util.robot
|
||||||
Resource SeleniumUtil.robot
|
Resource SeleniumUtil.robot
|
||||||
|
@ -26,7 +26,7 @@ sudo mkdir -p /etc/core/ca/ && sudo mv ./tests/ca.crt /etc/core/ca/
|
|||||||
sudo mkdir -p /harbor && sudo mv ./VERSION /harbor/UIVERSION
|
sudo mkdir -p /harbor && sudo mv ./VERSION /harbor/UIVERSION
|
||||||
sudo ./tests/testprepare.sh
|
sudo ./tests/testprepare.sh
|
||||||
|
|
||||||
cd tests && sudo ./ldapprepare.sh && sudo ./admiral.sh && cd ..
|
cd tests && sudo ./ldapprepare.sh && cd ..
|
||||||
sudo sed -i 's/__reg_version__/${REG_VERSION}-dev/g' ./make/docker-compose.test.yml
|
sudo sed -i 's/__reg_version__/${REG_VERSION}-dev/g' ./make/docker-compose.test.yml
|
||||||
sudo sed -i 's/__version__/dev/g' ./make/docker-compose.test.yml
|
sudo sed -i 's/__version__/dev/g' ./make/docker-compose.test.yml
|
||||||
sudo mkdir -p ./make/common/config/registry/ && sudo mv ./tests/reg_config.yml ./make/common/config/registry/config.yml
|
sudo mkdir -p ./make/common/config/registry/ && sudo mv ./tests/reg_config.yml ./make/common/config/registry/config.yml
|
||||||
|
Loading…
Reference in New Issue
Block a user