Merge pull request #10034 from ywk253100/191128_clean

Clean up admiral-related code
This commit is contained in:
Wenkai Yin(尹文开) 2019-12-04 17:33:31 +08:00 committed by GitHub
commit d145f4baf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 94 additions and 1753 deletions

View File

@ -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.

View File

@ -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}}

View File

@ -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

View File

@ -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 {}

View File

@ -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

View File

@ -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)

View File

@ -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},

View File

@ -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"

View File

@ -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+"/%")
} }

View File

@ -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)
}

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)

View File

@ -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"

View File

@ -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",

View File

@ -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)

View File

@ -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]),

View File

@ -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()

View File

@ -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)
} }

View File

@ -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()

View File

@ -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 {

View File

@ -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
} }

View File

@ -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()

View File

@ -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) {

View File

@ -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{

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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")

View File

@ -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 {

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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