Merge pull request #2672 from ywk253100/170628_getpm

Create a global project manager
This commit is contained in:
Wenkai Yin 2017-06-30 16:38:29 +08:00 committed by GitHub
commit 2818c047bf
11 changed files with 259 additions and 46 deletions

View File

@ -83,6 +83,7 @@ services:
- ./common/config/ui/private_key.pem:/etc/ui/private_key.pem:z - ./common/config/ui/private_key.pem:/etc/ui/private_key.pem:z
- /data/secretkey:/etc/ui/key:z - /data/secretkey:/etc/ui/key:z
- /data/ca_download/:/etc/ui/ca/:z - /data/ca_download/:/etc/ui/ca/:z
- /data/service_token:/etc/ui/service_token:z
networks: networks:
- harbor - harbor
depends_on: depends_on:

View File

@ -77,6 +77,7 @@ services:
- ./common/config/ui/private_key.pem:/etc/ui/private_key.pem:z - ./common/config/ui/private_key.pem:/etc/ui/private_key.pem:z
- /data/secretkey:/etc/ui/key:z - /data/secretkey:/etc/ui/key:z
- /data/ca_download/:/etc/ui/ca/:z - /data/ca_download/:/etc/ui/ca/:z
- /data/service_token:/etc/ui/service_token:z
networks: networks:
- harbor - harbor
depends_on: depends_on:

View File

@ -15,8 +15,10 @@
package config package config
import ( import (
"crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"os" "os"
"strings" "strings"
@ -33,8 +35,9 @@ import (
) )
const ( const (
defaultKeyPath string = "/etc/ui/key" defaultKeyPath string = "/etc/ui/key"
secretCookieName string = "secret" defaultTokenFilePath string = "/etc/ui/service_token"
secretCookieName string = "secret"
) )
var ( var (
@ -46,6 +49,9 @@ var (
GlobalProjectMgr projectmanager.ProjectManager GlobalProjectMgr projectmanager.ProjectManager
mg *comcfg.Manager mg *comcfg.Manager
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
) )
// Init configurations // Init configurations
@ -105,9 +111,19 @@ func initProjectManager() {
} }
// integration with admiral // integration with admiral
// TODO create project manager based on pms using service account
log.Info("initializing the project manager based on PMS...") log.Info("initializing the project manager based on PMS...")
GlobalProjectMgr = pms.NewProjectManager(AdmiralEndpoint(), "") // TODO read ca/cert file and pass it to the TLS config
AdminserverClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
GlobalProjectMgr = pms.NewProjectManager(AdminserverClient,
AdmiralEndpoint(), &pms.FileTokenReader{
Path: defaultTokenFilePath,
})
} }
// Load configurations // Load configurations

View File

@ -51,8 +51,8 @@ func Init() {
if config.WithAdmiral() { if config.WithAdmiral() {
reqCtxModifiers = []ReqCtxModifier{ reqCtxModifiers = []ReqCtxModifier{
&secretReqCtxModifier{config.SecretStore}, &secretReqCtxModifier{config.SecretStore},
&basicAuthReqCtxModifier{},
&tokenReqCtxModifier{}, &tokenReqCtxModifier{},
&basicAuthReqCtxModifier{},
&unauthorizedReqCtxModifier{}} &unauthorizedReqCtxModifier{}}
return return
} }
@ -131,15 +131,14 @@ func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
if config.WithAdmiral() { if config.WithAdmiral() {
// integration with admiral // integration with admiral
token, authCtx, err := authcontext.Login(config.AdmiralEndpoint(), username, password) _, authCtx, err := authcontext.Login(config.AdmiralEndpoint(), username, password)
if err != nil { if err != nil {
log.Errorf("failed to authenticate %s: %v", username, err) log.Errorf("failed to authenticate %s: %v", username, err)
return false return false
} }
log.Debug("creating PMS project manager...") log.Debug("using glocal project manager...")
pm = pms.NewProjectManager(config.AdmiralEndpoint(), token) pm = config.GlobalProjectMgr
log.Debug("creating admiral security context...") log.Debug("creating admiral security context...")
securCtx = admiral.NewSecurityContext(authCtx, pm) securCtx = admiral.NewSecurityContext(authCtx, pm)
} else { } else {
@ -211,7 +210,10 @@ func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
} }
log.Debug("creating PMS project manager...") log.Debug("creating PMS project manager...")
pm := pms.NewProjectManager(config.AdmiralEndpoint(), token) pm := pms.NewProjectManager(config.AdmiralClient,
config.AdmiralEndpoint(), &pms.RawTokenReader{
Token: token,
})
log.Debug("creating admiral security context...") log.Debug("creating admiral security context...")
securCtx := admiral.NewSecurityContext(authContext, pm) securCtx := admiral.NewSecurityContext(authContext, pm)
setSecurCtxAndPM(ctx.Request, securCtx, pm) setSecurCtxAndPM(ctx.Request, securCtx, pm)
@ -230,7 +232,8 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
if config.WithAdmiral() { if config.WithAdmiral() {
// integration with admiral // integration with admiral
log.Debug("creating PMS project manager...") log.Debug("creating PMS project manager...")
pm = pms.NewProjectManager(config.AdmiralEndpoint(), "") pm = pms.NewProjectManager(config.AdmiralClient,
config.AdmiralEndpoint(), nil)
log.Debug("creating admiral security context...") log.Debug("creating admiral security context...")
securCtx = admiral.NewSecurityContext(nil, pm) securCtx = admiral.NewSecurityContext(nil, pm)
} else { } else {

View File

@ -32,14 +32,12 @@ import (
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
) )
var transport = &http.Transport{}
// ProjectManager implements projectmanager.ProjecdtManager interface // ProjectManager implements projectmanager.ProjecdtManager interface
// base on project management service // base on project management service
type ProjectManager struct { type ProjectManager struct {
endpoint string client *http.Client
token string endpoint string
client *http.Client tokenReader TokenReader
} }
type user struct { type user struct {
@ -58,13 +56,12 @@ type project struct {
} }
// NewProjectManager returns an instance of ProjectManager // NewProjectManager returns an instance of ProjectManager
func NewProjectManager(endpoint, token string) *ProjectManager { func NewProjectManager(client *http.Client, endpoint string,
tokenReader TokenReader) *ProjectManager {
return &ProjectManager{ return &ProjectManager{
endpoint: strings.TrimRight(endpoint, "/"), client: client,
token: token, endpoint: strings.TrimRight(endpoint, "/"),
client: &http.Client{ tokenReader: tokenReader,
Transport: transport,
},
} }
} }
@ -307,7 +304,7 @@ func (p *ProjectManager) GetPublic() ([]*models.Project, error) {
// GetByMember ... // GetByMember ...
func (p *ProjectManager) GetByMember(username string) ([]*models.Project, error) { func (p *ProjectManager) GetByMember(username string) ([]*models.Project, error) {
projects := []*models.Project{} projects := []*models.Project{}
ctx, err := authcontext.GetAuthCtxOfUser(p.endpoint, p.token, username) ctx, err := authcontext.GetAuthCtxOfUser(p.endpoint, p.getToken(), username)
if err != nil { if err != nil {
return projects, err return projects, err
} }
@ -425,7 +422,7 @@ func (p *ProjectManager) send(method, path string, body io.Reader) ([]byte, erro
return nil, err return nil, err
} }
req.Header.Add("x-xenon-auth-token", p.token) req.Header.Add("x-xenon-auth-token", p.getToken())
url := req.URL.String() url := req.URL.String()
@ -452,3 +449,16 @@ func (p *ProjectManager) send(method, path string, body io.Reader) ([]byte, erro
return b, nil return b, nil
} }
func (p *ProjectManager) getToken() string {
if p.tokenReader == nil {
return ""
}
token, err := p.tokenReader.ReadToken()
if err != nil {
token = ""
log.Errorf("failed to read token: %v", err)
}
return token
}

View File

@ -15,6 +15,7 @@
package pms package pms
import ( import (
"net/http"
"sort" "sort"
"testing" "testing"
@ -24,8 +25,11 @@ import (
) )
var ( var (
endpoint = "http://127.0.0.1:8282" client = http.DefaultClient
token = "" endpoint = "http://127.0.0.1:8282"
tokenReader = &RawTokenReader{
Token: "",
}
) )
func TestConvert(t *testing.T) { func TestConvert(t *testing.T) {
@ -177,7 +181,7 @@ func TestParse(t *testing.T) {
} }
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
pm := NewProjectManager(endpoint, token) pm := NewProjectManager(client, endpoint, tokenReader)
name := "project_for_test_get" name := "project_for_test_get"
id, err := pm.Create(&models.Project{ id, err := pm.Create(&models.Project{
Name: name, Name: name,
@ -211,7 +215,7 @@ func TestGet(t *testing.T) {
} }
func TestIsPublic(t *testing.T) { func TestIsPublic(t *testing.T) {
pm := NewProjectManager(endpoint, token) pm := NewProjectManager(client, endpoint, tokenReader)
// invalid input type // invalid input type
public, err := pm.IsPublic([]string{}) public, err := pm.IsPublic([]string{})
@ -259,7 +263,7 @@ func TestIsPublic(t *testing.T) {
} }
func TestExist(t *testing.T) { func TestExist(t *testing.T) {
pm := NewProjectManager(endpoint, token) pm := NewProjectManager(client, endpoint, tokenReader)
// invalid input type // invalid input type
exist, err := pm.Exist([]string{}) exist, err := pm.Exist([]string{})
@ -289,7 +293,7 @@ func TestExist(t *testing.T) {
} }
func TestGetRoles(t *testing.T) { func TestGetRoles(t *testing.T) {
pm := NewProjectManager(endpoint, token) pm := NewProjectManager(client, endpoint, tokenReader)
// nil username, nil project // nil username, nil project
roles, err := pm.GetRoles("", nil) roles, err := pm.GetRoles("", nil)
@ -316,7 +320,7 @@ func TestGetRoles(t *testing.T) {
} }
func TestGetPublic(t *testing.T) { func TestGetPublic(t *testing.T) {
pm := NewProjectManager(endpoint, token) pm := NewProjectManager(client, endpoint, tokenReader)
projects, err := pm.GetPublic() projects, err := pm.GetPublic()
assert.Nil(t, nil) assert.Nil(t, nil)
@ -350,7 +354,7 @@ func TestGetByMember(t *testing.T) {
} }
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
pm := NewProjectManager(endpoint, token) pm := NewProjectManager(client, endpoint, tokenReader)
name := "project_for_test_create" name := "project_for_test_create"
id, err := pm.Create(&models.Project{ id, err := pm.Create(&models.Project{
@ -375,7 +379,7 @@ func TestCreate(t *testing.T) {
} }
func TestDelete(t *testing.T) { func TestDelete(t *testing.T) {
pm := NewProjectManager(endpoint, token) pm := NewProjectManager(client, endpoint, tokenReader)
// non-exist project // non-exist project
err := pm.Delete(int64(0)) err := pm.Delete(int64(0))
@ -401,13 +405,13 @@ func TestDelete(t *testing.T) {
} }
func TestUpdate(t *testing.T) { func TestUpdate(t *testing.T) {
pm := NewProjectManager(endpoint, token) pm := NewProjectManager(client, endpoint, tokenReader)
err := pm.Update(nil, nil) err := pm.Update(nil, nil)
assert.NotNil(t, err) assert.NotNil(t, err)
} }
func TestGetAll(t *testing.T) { func TestGetAll(t *testing.T) {
pm := NewProjectManager(endpoint, token) pm := NewProjectManager(client, endpoint, tokenReader)
name1 := "project_for_test_get_all_01" name1 := "project_for_test_get_all_01"
id1, err := pm.Create(&models.Project{ id1, err := pm.Create(&models.Project{
@ -471,7 +475,7 @@ func TestGetAll(t *testing.T) {
} }
func TestGetTotal(t *testing.T) { func TestGetTotal(t *testing.T) {
pm := NewProjectManager(endpoint, token) pm := NewProjectManager(client, endpoint, tokenReader)
total1, err := pm.GetTotal(nil) total1, err := pm.GetTotal(nil)
require.Nil(t, err) require.Nil(t, err)
@ -489,13 +493,13 @@ func TestGetTotal(t *testing.T) {
} }
func TestGetHasReadPerm(t *testing.T) { func TestGetHasReadPerm(t *testing.T) {
pm := NewProjectManager(endpoint, token) pm := NewProjectManager(client, endpoint, tokenReader)
_, err := pm.GetHasReadPerm() _, err := pm.GetHasReadPerm()
assert.NotNil(t, err) assert.NotNil(t, err)
} }
func delete(t *testing.T, id int64) { func delete(t *testing.T, id int64) {
pm := NewProjectManager(endpoint, token) pm := NewProjectManager(client, endpoint, tokenReader)
if err := pm.Delete(id); err != nil { if err := pm.Delete(id); err != nil {
t.Logf("failed to delete project %d: %v", id, err) t.Logf("failed to delete project %d: %v", id, err)
} }

View File

@ -0,0 +1,84 @@
// 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 (
"bufio"
"fmt"
"io"
"os"
"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) {
file, err := os.Open(f.Path)
if err != nil {
return "", err
}
defer file.Close()
return readToken(file)
}
func readToken(reader io.Reader) (string, error) {
if reader == nil {
return "", fmt.Errorf("reader is nil")
}
r := bufio.NewReader(reader)
for {
line, _, err := r.ReadLine()
if err != nil {
if err == io.EOF {
err = fmt.Errorf("%s not found", key)
}
return "", err
}
strs := strings.SplitN(string(line), "=", 2)
if len(strs) != 2 {
continue
}
if strs[0] == key {
return strs[1], nil
}
}
}

View File

@ -0,0 +1,98 @@
// 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"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
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 TestReadToken(t *testing.T) {
// nil reader
_, err := readToken(nil)
assert.NotNil(t, err)
// empty
reader := bytes.NewReader([]byte{})
_, err = readToken(reader)
assert.NotNil(t, err)
// contains no "access_token"
content := "key1=value\nkey2=value2"
reader = bytes.NewReader([]byte(content))
_, err = readToken(reader)
assert.NotNil(t, err)
// contains "access_token" but no "="
content = "access_token value\nkey2=value2"
reader = bytes.NewReader([]byte(content))
_, err = readToken(reader)
assert.NotNil(t, err)
// contains "access_token" and "=", but no value
content = "access_token=\nkey2=value2"
reader = bytes.NewReader([]byte(content))
token, err := readToken(reader)
require.Nil(t, err)
assert.Len(t, token, 0)
// valid "access_token"
content = "access_token=token\nkey2=value2"
reader = bytes.NewReader([]byte(content))
token, err = readToken(reader)
require.Nil(t, err)
assert.Equal(t, "token", 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("access_token=token"), 0x0666)
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

@ -112,7 +112,6 @@ func TestEnvPolicyChecker(t *testing.T) {
} }
func TestPMSPolicyChecker(t *testing.T) { func TestPMSPolicyChecker(t *testing.T) {
var defaultConfigAdmiral = map[string]interface{}{ var defaultConfigAdmiral = map[string]interface{}{
common.ExtEndpoint: "https://" + endpoint, common.ExtEndpoint: "https://" + endpoint,
common.WithNotary: true, common.WithNotary: true,
@ -131,7 +130,8 @@ func TestPMSPolicyChecker(t *testing.T) {
panic(err) panic(err)
} }
pm := pms.NewProjectManager(admiralEndpoint, token) pm := pms.NewProjectManager(http.DefaultClient,
admiralEndpoint, nil)
name := "project_for_test_get_sev_low" name := "project_for_test_get_sev_low"
id, err := pm.Create(&models.Project{ id, err := pm.Create(&models.Project{
Name: name, Name: name,

View File

@ -9,7 +9,6 @@ import (
// "github.com/vmware/harbor/src/ui/api" // "github.com/vmware/harbor/src/ui/api"
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/projectmanager" "github.com/vmware/harbor/src/ui/projectmanager"
"github.com/vmware/harbor/src/ui/projectmanager/pms"
"context" "context"
"fmt" "fmt"
@ -102,7 +101,7 @@ func newPMSPolicyChecker(pm projectmanager.ProjectManager) policyChecker {
// TODO: Get project manager with PM factory. // TODO: Get project manager with PM factory.
func getPolicyChecker() policyChecker { func getPolicyChecker() policyChecker {
if config.WithAdmiral() { if config.WithAdmiral() {
return newPMSPolicyChecker(pms.NewProjectManager(config.AdmiralEndpoint(), "")) return newPMSPolicyChecker(config.GlobalProjectMgr)
} }
return EnvChecker return EnvChecker
} }

View File

@ -27,7 +27,6 @@ import (
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/api" "github.com/vmware/harbor/src/ui/api"
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/projectmanager/pms"
uiutils "github.com/vmware/harbor/src/ui/utils" uiutils "github.com/vmware/harbor/src/ui/utils"
) )
@ -161,9 +160,7 @@ func autoScanEnabled(projectName string) bool {
return false return false
} }
if config.WithAdmiral() { if config.WithAdmiral() {
//TODO get a project manager based on service account. p, err := config.GlobalProjectMgr.Get(projectName)
var pm *pms.ProjectManager = pms.NewProjectManager("", "")
p, err := pm.Get(projectName)
if err != nil { if err != nil {
log.Warningf("failed to get project, error: %v", err) log.Warningf("failed to get project, error: %v", err)
return false return false