From 0925fd35f109831a3fad80c81e6e3ba07fef4701 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Tue, 6 Jun 2017 17:51:40 +0800 Subject: [PATCH 1/2] implement project manager based on PMS --- .travis.yml | 1 + src/common/const.go | 3 - src/common/models/project.go | 13 +- src/ui/config/config.go | 9 +- src/ui/filter/security.go | 11 +- src/ui/projectmanager/pms/pm.go | 410 ++++++++++++++++++++++++++ src/ui/projectmanager/pms/pm_test.go | 424 +++++++++++++++++++++++++++ tests/admiral.sh | 7 + 8 files changed, 856 insertions(+), 22 deletions(-) create mode 100644 src/ui/projectmanager/pms/pm.go create mode 100644 src/ui/projectmanager/pms/pm_test.go create mode 100755 tests/admiral.sh diff --git a/.travis.yml b/.travis.yml index 9070921e2..589e0aaab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -91,6 +91,7 @@ script: - ./tests/pushimage.sh - cd tests - sudo ./ldapprepare.sh + - sudo ./admiral.sh - cd .. - go test -i ./src/ui ./src/adminserver ./src/jobservice - sudo -E env "PATH=$PATH" ./tests/coverage4gotest.sh diff --git a/src/common/const.go b/src/common/const.go index 0598dc2a3..1c4282946 100644 --- a/src/common/const.go +++ b/src/common/const.go @@ -28,9 +28,6 @@ const ( RoleDeveloper = 2 RoleGuest = 3 - DeployModeStandAlone = "standalone" - DeployModeIntegration = "integration" - ExtEndpoint = "ext_endpoint" AUTHMode = "auth_mode" DatabaseType = "database_type" diff --git a/src/common/models/project.go b/src/common/models/project.go index db7f62d5f..883f32ce3 100644 --- a/src/common/models/project.go +++ b/src/common/models/project.go @@ -31,11 +31,14 @@ type Project struct { OwnerName string `orm:"-" json:"owner_name"` Public int `orm:"column(public)" json:"public"` //This field does not have correspondent column in DB, this is just for UI to disable button - Togglable bool `orm:"-"` - - UpdateTime time.Time `orm:"update_time" json:"update_time"` - Role int `orm:"-" json:"current_user_role_id"` - RepoCount int `orm:"-" json:"repo_count"` + Togglable bool `orm:"-"` + UpdateTime time.Time `orm:"update_time" json:"update_time"` + Role int `orm:"-" json:"current_user_role_id"` + RepoCount int `orm:"-" json:"repo_count"` + EnableContentTrust bool `orm:"-" json:"enable_content_trust"` + PreventVulnerableImagesFromRunning bool `orm:"-" json:"prevent_vulnerable_images_from_running"` + PreventVulnerableImagesFromRunningSeverity string `orm:"-" json:"prevent_vulnerable_images_from_running_severity"` + AutomaticallyScanImagesOnPush bool `orm:"-" json:"automatically_scan_images_on_push"` } // ProjectSorter holds an array of projects diff --git a/src/ui/config/config.go b/src/ui/config/config.go index 50e5add79..206e3fcc6 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -95,8 +95,7 @@ func initSecretStore() { } func initProjectManager() { - if len(DeployMode()) == 0 || - DeployMode() == common.DeployModeStandAlone { + if !WithAdmiral() { log.Info("initializing the project manager based on database...") GlobalProjectMgr = &db.ProjectManager{} } @@ -332,9 +331,3 @@ func AdmiralEndpoint() string { func WithAdmiral() bool { return len(AdmiralEndpoint()) > 0 } - -// DeployMode returns the deploy mode -// TODO read from adminserver -func DeployMode() string { - return common.DeployModeStandAlone -} diff --git a/src/ui/filter/security.go b/src/ui/filter/security.go index b50803d99..4735f4c33 100644 --- a/src/ui/filter/security.go +++ b/src/ui/filter/security.go @@ -21,7 +21,6 @@ import ( "strings" beegoctx "github.com/astaxie/beego/context" - "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/security" "github.com/vmware/harbor/src/common/security/rbac" @@ -30,6 +29,7 @@ import ( "github.com/vmware/harbor/src/ui/auth" "github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/projectmanager" + "github.com/vmware/harbor/src/ui/projectmanager/pms" ) type key string @@ -133,15 +133,14 @@ func fillContext(ctx *beegoctx.Context) { } func getProjectManager(ctx *beegoctx.Context) projectmanager.ProjectManager { - if len(config.DeployMode()) == 0 || - config.DeployMode() == common.DeployModeStandAlone { + if !config.WithAdmiral() { log.Info("filling a project manager based on database...") return config.GlobalProjectMgr } - // TODO create project manager based on pms - log.Info("filling a project manager based on pms...") - return nil + log.Info("filling a project manager based on PMS...") + // TODO pass the token to the function + return pms.NewProjectManager(config.AdmiralEndpoint(), "") } // GetSecurityContext tries to get security context from request and returns it diff --git a/src/ui/projectmanager/pms/pm.go b/src/ui/projectmanager/pms/pm.go new file mode 100644 index 000000000..78dd16154 --- /dev/null +++ b/src/ui/projectmanager/pms/pm.go @@ -0,0 +1,410 @@ +// Copyright (c) 2017 VMware, Inc. All Rights Reserved. +// +// 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 pms + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "strconv" + "strings" + "time" + + "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/models" + "github.com/vmware/harbor/src/common/utils/log" + er "github.com/vmware/harbor/src/common/utils/registry/error" +) + +var transport = &http.Transport{} + +// ProjectManager implements projectmanager.ProjecdtManager interface +// base on project management service +type ProjectManager struct { + endpoint string + token string + client *http.Client +} + +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:"guests"` // TODO the json name needs to be modified according to the API +} + +// NewProjectManager returns an instance of ProjectManager +func NewProjectManager(endpoint, token string) *ProjectManager { + return &ProjectManager{ + endpoint: strings.TrimRight(endpoint, "/"), + token: token, + client: &http.Client{ + Transport: transport, + }, + } +} + +// Get ... +func (p *ProjectManager) Get(projectIDOrName interface{}) (*models.Project, error) { + project, err := p.get(projectIDOrName) + if err != nil { + return nil, err + } + return convert(project) +} + +func (p *ProjectManager) get(projectIDOrName interface{}) (*project, error) { + var key, value interface{} + if id, ok := projectIDOrName.(int64); ok { + key = "customProperties.__harborId" + value = id + } else if name, ok := projectIDOrName.(string); ok { + key = "name" + value = name + } else { + return nil, fmt.Errorf("unsupported type: %v", projectIDOrName) + } + + path := fmt.Sprintf("/projects?$filter=%s eq '%v'", key, value) + data, err := p.send(http.MethodGet, path, nil) + if err != nil { + return nil, err + } + + projects, err := parse(data) + if err != nil { + return nil, err + } + + if len(projects) == 0 { + return nil, nil + } + + if len(projects) != 1 { + return nil, fmt.Errorf("unexpected size of project list: %d != 1", len(projects)) + } + + return projects[0], nil +} + +// parse the response of GET /projects?xxx to project list +func parse(b []byte) ([]*project, error) { + documents := &struct { + //TotalCount int64 `json:"totalCount"` + //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.TrimLeft(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.Public = 1 + } + + value := p.CustomProperties["__harborId"] + if len(value) == 0 { + return nil, fmt.Errorf("property __harborId is null") + } + + id, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse __harborId %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.EnableContentTrust = 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.PreventVulnerableImagesFromRunning = prevent + } + + value = p.CustomProperties["__preventVulnerableImagesFromRunningSeverity"] + if len(value) != 0 { + project.PreventVulnerableImagesFromRunningSeverity = 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.AutomaticallyScanImagesOnPush = scan + } + + return project, nil +} + +// IsPublic ... +func (p *ProjectManager) IsPublic(projectIDOrName interface{}) (bool, error) { + project, err := p.get(projectIDOrName) + if err != nil { + return false, err + } + if project == nil { + return false, nil + } + + return project.Public, nil +} + +// Exist ... +func (p *ProjectManager) Exist(projectIDOrName interface{}) (bool, error) { + project, err := p.get(projectIDOrName) + if err != nil { + return false, err + } + + return project != nil, nil +} + +// GetRoles ... +func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{}) ([]int, error) { + if len(username) == 0 || projectIDOrName == nil { + return nil, nil + } + + id, err := p.getIDbyHarborIDOrName(projectIDOrName) + if err != nil { + return nil, err + } + + // get expanded project which contains role info by GET /projects/id?expand=true + path := fmt.Sprintf("/projects/%s?expand=true", id) + data, err := p.send(http.MethodGet, path, nil) + if err != nil { + return nil, err + } + + pro := &project{} + if err = json.Unmarshal(data, pro); err != nil { + return nil, err + } + + roles := []int{} + + for _, user := range pro.Administrators { + if user.Email == username { + roles = append(roles, common.RoleProjectAdmin) + break + } + } + + for _, user := range pro.Developers { + if user.Email == username { + roles = append(roles, common.RoleDeveloper) + break + } + } + + for _, user := range pro.Guests { + if user.Email == username { + roles = append(roles, common.RoleGuest) + break + } + } + + return roles, nil +} + +func (p *ProjectManager) getIDbyHarborIDOrName(projectIDOrName interface{}) (string, error) { + pro, err := p.get(projectIDOrName) + if err != nil { + return "", err + } + + if pro == nil { + return "", fmt.Errorf("project %v not found", projectIDOrName) + } + + return pro.ID, nil +} + +// GetPublic ... +func (p *ProjectManager) GetPublic() ([]*models.Project, error) { + path := "/projects?$filter=isPublic eq 'true'" + data, err := p.send(http.MethodGet, path, nil) + if err != nil { + return nil, err + } + + projects, err := parse(data) + 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 list, nil +} + +// GetByMember ... +func (p *ProjectManager) GetByMember(username string) ([]*models.Project, error) { + // TODO add implement + return nil, nil +} + +// Create ... +func (p *ProjectManager) Create(pro *models.Project) (int64, error) { + proj := &project{ + CustomProperties: make(map[string]string), + } + proj.Name = pro.Name + proj.Public = pro.Public == 1 + proj.CustomProperties["__enableContentTrust"] = strconv.FormatBool(pro.EnableContentTrust) + proj.CustomProperties["__preventVulnerableImagesFromRunning"] = strconv.FormatBool(pro.PreventVulnerableImagesFromRunning) + proj.CustomProperties["__preventVulnerableImagesFromRunningSeverity"] = pro.PreventVulnerableImagesFromRunningSeverity + proj.CustomProperties["__automaticallyScanImagesOnPush"] = strconv.FormatBool(pro.AutomaticallyScanImagesOnPush) + + // TODO remove the logic if Admiral generates the harborId + proj.CustomProperties["__harborId"] = strconv.FormatInt(time.Now().UnixNano(), 10) + + data, err := json.Marshal(proj) + if err != nil { + return 0, err + } + + b, err := p.send(http.MethodPost, "/projects", bytes.NewBuffer(data)) + if err != nil { + 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 (p *ProjectManager) Delete(projectIDOrName interface{}) error { + id, err := p.getIDbyHarborIDOrName(projectIDOrName) + if err != nil { + return err + } + + _, err = p.send(http.MethodDelete, fmt.Sprintf("/projects/%s", id), nil) + return err +} + +// Update ... +func (p *ProjectManager) Update(projectIDOrName interface{}, project *models.Project) error { + return errors.New("project update is unsupported") +} + +// GetAll ... +func (p *ProjectManager) GetAll(query *models.ProjectQueryParam) ([]*models.Project, error) { + return nil, errors.New("get all projects is unsupported") +} + +// GetTotal ... +func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam) (int64, error) { + return 0, errors.New("get total of projects is unsupported") +} + +// GetHasReadPerm ... +func (p *ProjectManager) GetHasReadPerm(username ...string) ([]*models.Project, error) { + // TODO add implement + return nil, nil +} + +func (p *ProjectManager) send(method, path string, body io.Reader) ([]byte, error) { + req, err := http.NewRequest(method, p.endpoint+path, body) + if err != nil { + return nil, err + } + + req.Header.Add("x-xenon-auth-token", p.token) + + req.URL.RawQuery = req.URL.Query().Encode() + url := req.URL.String() + + resp, err := p.client.Do(req) + if err != nil { + log.Debugf("\"%s %s\" %d", req.Method, url, 0) + 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, &er.Error{ + StatusCode: resp.StatusCode, + Detail: string(b), + } + } + + return b, nil +} diff --git a/src/ui/projectmanager/pms/pm_test.go b/src/ui/projectmanager/pms/pm_test.go new file mode 100644 index 000000000..467fd547b --- /dev/null +++ b/src/ui/projectmanager/pms/pm_test.go @@ -0,0 +1,424 @@ +// Copyright (c) 2017 VMware, Inc. All Rights Reserved. +// +// 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 pms + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/vmware/harbor/src/common/models" +) + +var ( + endpoint = "http://127.0.0.1:8282" + token = "" +) + +func TestConvert(t *testing.T) { + //nil project + pro, err := convert(nil) + assert.Nil(t, err) + assert.Nil(t, pro) + + //project without property __harborId + p := &project{} + pro, err = convert(p) + assert.NotNil(t, err) + assert.Nil(t, pro) + + //project with invalid __harborId + p = &project{ + CustomProperties: map[string]string{ + "__harborId": "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{ + "__harborId": "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.Equal(t, 1, pro.Public) + assert.Equal(t, int64(1), pro.ProjectID) + assert.True(t, pro.EnableContentTrust) + assert.True(t, pro.PreventVulnerableImagesFromRunning) + assert.Equal(t, "medium", pro.PreventVulnerableImagesFromRunningSeverity) + assert.True(t, pro.AutomaticallyScanImagesOnPush) +} + +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": { + "__harborId": "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": { + "__harborId": "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) { + pm := NewProjectManager(endpoint, token) + name := "project_for_pm_based_on_pms" + id, err := pm.Create(&models.Project{ + Name: name, + }) + require.Nil(t, err) + defer pm.Delete(id) + + // get by invalid input type + _, err = pm.Get([]string{}) + assert.NotNil(t, err) + + // get by invalid ID + project, err := pm.Get(int64(0)) + assert.Nil(t, err) + assert.Nil(t, project) + + // get by invalid name + project, err = pm.Get("invalid_name") + assert.Nil(t, err) + assert.Nil(t, project) + + // get by valid ID + project, err = pm.Get(id) + assert.Nil(t, err) + assert.Equal(t, id, project.ProjectID) + + // get by valid name + project, err = pm.Get(name) + assert.Nil(t, err) + assert.Equal(t, id, project.ProjectID) +} + +func TestIsPublic(t *testing.T) { + pm := NewProjectManager(endpoint, token) + + // invalid input type + public, err := pm.IsPublic([]string{}) + assert.NotNil(t, err) + assert.False(t, public) + + // non-exist project + public, err = pm.IsPublic(int64(0)) + assert.Nil(t, err) + assert.False(t, public) + + // public project + name := "project_for_pm_based_on_pms_public" + id, err := pm.Create(&models.Project{ + Name: name, + Public: 1, + }) + require.Nil(t, err) + defer pm.Delete(id) + + public, err = pm.IsPublic(id) + assert.Nil(t, err) + assert.True(t, public) + + public, err = pm.IsPublic(name) + assert.Nil(t, err) + assert.True(t, public) + + // private project + name = "project_for_pm_based_on_pms_private" + id, err = pm.Create(&models.Project{ + Name: name, + Public: 0, + }) + require.Nil(t, err) + defer pm.Delete(id) + + public, err = pm.IsPublic(id) + assert.Nil(t, err) + assert.False(t, public) + + public, err = pm.IsPublic(name) + assert.Nil(t, err) + assert.False(t, public) +} + +func TestExist(t *testing.T) { + pm := NewProjectManager(endpoint, token) + + // invalid input type + exist, err := pm.Exist([]string{}) + assert.NotNil(t, err) + assert.False(t, exist) + + // non-exist project + exist, err = pm.Exist(int64(0)) + assert.Nil(t, err) + assert.False(t, exist) + + // exist project + name := "project_for_pm_based_on_pms" + id, err := pm.Create(&models.Project{ + Name: name, + }) + require.Nil(t, err) + defer pm.Delete(id) + + exist, err = pm.Exist(id) + assert.Nil(t, err) + assert.True(t, exist) + + exist, err = pm.Exist(name) + assert.Nil(t, err) + assert.True(t, exist) +} + +func TestGetRoles(t *testing.T) { + pm := NewProjectManager(endpoint, token) + + // nil username, nil project + roles, err := pm.GetRoles("", nil) + assert.Nil(t, err) + assert.Zero(t, len(roles)) + + // non-exist project + _, err = pm.GetRoles("user01", "non_exist_project") + assert.NotNil(t, err) + + // exist project + name := "project_for_pm_based_on_pms" + id, err := pm.Create(&models.Project{ + Name: name, + }) + require.Nil(t, err) + defer pm.Delete(id) + + roles, err = pm.GetRoles("user01", id) + assert.Nil(t, err) + assert.Zero(t, len(roles)) + + // TODO add test cases for real role of user +} + +func TestGetPublic(t *testing.T) { + pm := NewProjectManager(endpoint, token) + + projects, err := pm.GetPublic() + assert.Nil(t, nil) + size := len(projects) + + name := "project_for_pm_based_on_pms" + id, err := pm.Create(&models.Project{ + Name: name, + Public: 1, + }) + require.Nil(t, err) + defer pm.Delete(id) + + projects, err = pm.GetPublic() + assert.Nil(t, nil) + assert.Equal(t, size+1, len(projects)) + + found := false + for _, project := range projects { + if project.ProjectID == id { + found = true + break + } + } + assert.True(t, found) +} + +// TODO add test case +func TestGetByMember(t *testing.T) { + +} + +func TestCreate(t *testing.T) { + pm := NewProjectManager(endpoint, token) + + name := "project_for_pm_based_on_pms" + id, err := pm.Create(&models.Project{ + Name: name, + Public: 1, + EnableContentTrust: true, + PreventVulnerableImagesFromRunning: true, + PreventVulnerableImagesFromRunningSeverity: "medium", + AutomaticallyScanImagesOnPush: true, + }) + require.Nil(t, err) + defer pm.Delete(id) + + project, err := pm.Get(id) + assert.Nil(t, err) + assert.Equal(t, name, project.Name) + assert.Equal(t, 1, project.Public) + assert.True(t, project.EnableContentTrust) + assert.True(t, project.PreventVulnerableImagesFromRunning) + assert.Equal(t, "medium", project.PreventVulnerableImagesFromRunningSeverity) + assert.True(t, project.AutomaticallyScanImagesOnPush) +} + +func TestDelete(t *testing.T) { + pm := NewProjectManager(endpoint, token) + + // non-exist project + err := pm.Delete(int64(0)) + assert.NotNil(t, err) + + // delete by ID + name := "project_for_pm_based_on_pms_id" + id, err := pm.Create(&models.Project{ + Name: name, + }) + require.Nil(t, err) + err = pm.Delete(id) + assert.Nil(t, err) + + // delete by name + name = "project_for_pm_based_on_pms_name" + id, err = pm.Create(&models.Project{ + Name: name, + }) + require.Nil(t, err) + err = pm.Delete(name) + assert.Nil(t, err) +} + +func TestUpdate(t *testing.T) { + pm := NewProjectManager(endpoint, token) + err := pm.Update(nil, nil) + assert.NotNil(t, err) +} + +func TestGetAll(t *testing.T) { + pm := NewProjectManager(endpoint, token) + _, err := pm.GetAll(nil) + assert.NotNil(t, err) +} + +func TestGetTotal(t *testing.T) { + pm := NewProjectManager(endpoint, token) + _, err := pm.GetTotal(nil) + assert.NotNil(t, err) +} + +// TODO add test case +func TestGetHasReadPerm(t *testing.T) { + +} diff --git a/tests/admiral.sh b/tests/admiral.sh new file mode 100755 index 000000000..62269b739 --- /dev/null +++ b/tests/admiral.sh @@ -0,0 +1,7 @@ +#!/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:dev \ No newline at end of file From 5a254450968048d111d6932bd7143a9aaf8563a6 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Tue, 13 Jun 2017 15:15:09 +0800 Subject: [PATCH 2/2] update according to the comments --- .../utils/{registry => }/error/error.go | 0 .../utils/{registry => }/error/error_test.go | 0 .../utils/registry/auth/tokenauthorizer.go | 2 +- src/common/utils/registry/registry.go | 2 +- src/common/utils/registry/repository.go | 2 +- src/common/utils/registry/repository_test.go | 2 +- src/ui/api/repository.go | 2 +- src/ui/api/target.go | 2 +- src/ui/api/utils.go | 2 +- src/ui/projectmanager/pms/pm.go | 54 +++++++++++-------- 10 files changed, 40 insertions(+), 28 deletions(-) rename src/common/utils/{registry => }/error/error.go (100%) rename src/common/utils/{registry => }/error/error_test.go (100%) diff --git a/src/common/utils/registry/error/error.go b/src/common/utils/error/error.go similarity index 100% rename from src/common/utils/registry/error/error.go rename to src/common/utils/error/error.go diff --git a/src/common/utils/registry/error/error_test.go b/src/common/utils/error/error_test.go similarity index 100% rename from src/common/utils/registry/error/error_test.go rename to src/common/utils/error/error_test.go diff --git a/src/common/utils/registry/auth/tokenauthorizer.go b/src/common/utils/registry/auth/tokenauthorizer.go index d2e8025d8..2ea1f0cb0 100644 --- a/src/common/utils/registry/auth/tokenauthorizer.go +++ b/src/common/utils/registry/auth/tokenauthorizer.go @@ -27,7 +27,7 @@ import ( //"github.com/vmware/harbor/src/common/config" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/registry" - registry_error "github.com/vmware/harbor/src/common/utils/registry/error" + registry_error "github.com/vmware/harbor/src/common/utils/error" token_util "github.com/vmware/harbor/src/ui/service/token" ) diff --git a/src/common/utils/registry/registry.go b/src/common/utils/registry/registry.go index 9fcbc2110..19e5638e5 100644 --- a/src/common/utils/registry/registry.go +++ b/src/common/utils/registry/registry.go @@ -24,7 +24,7 @@ import ( // "time" "github.com/vmware/harbor/src/common/utils" - registry_error "github.com/vmware/harbor/src/common/utils/registry/error" + registry_error "github.com/vmware/harbor/src/common/utils/error" ) // Registry holds information of a registry entity diff --git a/src/common/utils/registry/repository.go b/src/common/utils/registry/repository.go index 17df82721..d431657e9 100644 --- a/src/common/utils/registry/repository.go +++ b/src/common/utils/registry/repository.go @@ -30,7 +30,7 @@ import ( "github.com/docker/distribution/manifest/schema2" "github.com/vmware/harbor/src/common/utils" - registry_error "github.com/vmware/harbor/src/common/utils/registry/error" + registry_error "github.com/vmware/harbor/src/common/utils/error" ) // Repository holds information of a repository entity diff --git a/src/common/utils/registry/repository_test.go b/src/common/utils/registry/repository_test.go index 03ed940aa..22c7fd62d 100644 --- a/src/common/utils/registry/repository_test.go +++ b/src/common/utils/registry/repository_test.go @@ -25,7 +25,7 @@ import ( "testing" "github.com/docker/distribution/manifest/schema2" - registry_error "github.com/vmware/harbor/src/common/utils/registry/error" + registry_error "github.com/vmware/harbor/src/common/utils/error" "github.com/vmware/harbor/src/common/utils/test" ) diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 0782ca04e..0c5aa8244 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -30,7 +30,7 @@ import ( "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/notary" "github.com/vmware/harbor/src/common/utils/registry" - registry_error "github.com/vmware/harbor/src/common/utils/registry/error" + registry_error "github.com/vmware/harbor/src/common/utils/error" "github.com/vmware/harbor/src/ui/config" ) diff --git a/src/ui/api/target.go b/src/ui/api/target.go index 4984bce3a..216453158 100644 --- a/src/ui/api/target.go +++ b/src/ui/api/target.go @@ -27,7 +27,7 @@ import ( "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry/auth" - registry_error "github.com/vmware/harbor/src/common/utils/registry/error" + registry_error "github.com/vmware/harbor/src/common/utils/error" "github.com/vmware/harbor/src/ui/config" ) diff --git a/src/ui/api/utils.go b/src/ui/api/utils.go index a4b4974e0..6351daa32 100644 --- a/src/ui/api/utils.go +++ b/src/ui/api/utils.go @@ -29,7 +29,7 @@ import ( "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry/auth" - registry_error "github.com/vmware/harbor/src/common/utils/registry/error" + registry_error "github.com/vmware/harbor/src/common/utils/error" "github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/projectmanager" ) diff --git a/src/ui/projectmanager/pms/pm.go b/src/ui/projectmanager/pms/pm.go index 78dd16154..d5db15496 100644 --- a/src/ui/projectmanager/pms/pm.go +++ b/src/ui/projectmanager/pms/pm.go @@ -28,8 +28,8 @@ import ( "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/models" + er "github.com/vmware/harbor/src/common/utils/error" "github.com/vmware/harbor/src/common/utils/log" - er "github.com/vmware/harbor/src/common/utils/registry/error" ) var transport = &http.Transport{} @@ -78,24 +78,16 @@ func (p *ProjectManager) Get(projectIDOrName interface{}) (*models.Project, erro } func (p *ProjectManager) get(projectIDOrName interface{}) (*project, error) { - var key, value interface{} + m := map[string]string{} if id, ok := projectIDOrName.(int64); ok { - key = "customProperties.__harborId" - value = id + m["customProperties.__harborId"] = strconv.FormatInt(id, 10) } else if name, ok := projectIDOrName.(string); ok { - key = "name" - value = name + m["name"] = name } else { return nil, fmt.Errorf("unsupported type: %v", projectIDOrName) } - path := fmt.Sprintf("/projects?$filter=%s eq '%v'", key, value) - data, err := p.send(http.MethodGet, path, nil) - if err != nil { - return nil, err - } - - projects, err := parse(data) + projects, err := p.filter(m) if err != nil { return nil, err } @@ -111,6 +103,26 @@ func (p *ProjectManager) get(projectIDOrName interface{}) (*project, error) { return projects[0], nil } +func (p *ProjectManager) filter(m map[string]string) ([]*project, error) { + query := "" + for k, v := range m { + if len(query) == 0 { + query += "?" + } else { + query += "&" + } + query += fmt.Sprintf("$filter=%s eq '%s'", k, v) + } + + path := "/projects" + query + data, err := p.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 { @@ -213,6 +225,7 @@ func (p *ProjectManager) Exist(projectIDOrName interface{}) (bool, error) { } // GetRoles ... +// TODO empty this method after implementing security context with auth context func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{}) ([]int, error) { if len(username) == 0 || projectIDOrName == nil { return nil, nil @@ -276,13 +289,11 @@ func (p *ProjectManager) getIDbyHarborIDOrName(projectIDOrName interface{}) (str // GetPublic ... func (p *ProjectManager) GetPublic() ([]*models.Project, error) { - path := "/projects?$filter=isPublic eq 'true'" - data, err := p.send(http.MethodGet, path, nil) - if err != nil { - return nil, err + m := map[string]string{ + "isPublic": "true", } - projects, err := parse(data) + projects, err := p.filter(m) if err != nil { return nil, err } @@ -369,7 +380,8 @@ func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam) (int64, error return 0, errors.New("get total of projects is unsupported") } -// GetHasReadPerm ... +// GetHasReadPerm returns all projects that user has read perm to +// TODO maybe can be removed as search isn't implemented in integration mode func (p *ProjectManager) GetHasReadPerm(username ...string) ([]*models.Project, error) { // TODO add implement return nil, nil @@ -383,12 +395,12 @@ func (p *ProjectManager) send(method, path string, body io.Reader) ([]byte, erro req.Header.Add("x-xenon-auth-token", p.token) - req.URL.RawQuery = req.URL.Query().Encode() url := req.URL.String() + req.URL.RawQuery = req.URL.Query().Encode() resp, err := p.client.Do(req) if err != nil { - log.Debugf("\"%s %s\" %d", req.Method, url, 0) + log.Debugf("\"%s %s\" failed", req.Method, url) return nil, err } defer resp.Body.Close()