1. modify registry client 2. add https support

This commit is contained in:
Wenkai Yin 2016-06-21 16:39:03 +08:00
parent 5fc3c7dbeb
commit c1a8846a68
17 changed files with 381 additions and 266 deletions

View File

@ -159,7 +159,7 @@ func (p *ProjectAPI) List() {
if len(isPublic) > 0 {
public, err = strconv.Atoi(isPublic)
if err != nil {
log.Errorf("Error parsing public property: %d, error: %v", isPublic, err)
log.Errorf("Error parsing public property: %v, error: %v", isPublic, err)
p.CustomAbort(http.StatusBadRequest, "invalid project Id")
}
}

View File

@ -255,11 +255,13 @@ func (ra *RepositoryAPI) GetManifests() {
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {
endpoint := os.Getenv("REGISTRY_URL")
// TODO read variable from config file
insecure := true
username, password, ok := ra.Ctx.Request.BasicAuth()
if ok {
credential := auth.NewBasicAuthCredential(username, password)
return registry.NewRepositoryWithCredentialForUI(repoName, endpoint, credential)
return newRepositoryClient(endpoint, insecure, username, password,
repoName, "repository", repoName, "pull", "push", "*")
}
username, err = ra.getUsername()
@ -267,7 +269,8 @@ func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repo
return nil, err
}
return registry.NewRepositoryWithUsernameForUI(repoName, endpoint, username)
return cache.NewRepositoryClient(endpoint, insecure, username, repoName,
"repository", repoName, "pull", "push", "*")
}
func (ra *RepositoryAPI) getUsername() (string, error) {
@ -327,3 +330,21 @@ func (ra *RepositoryAPI) GetTopRepos() {
ra.Data["json"] = repos
ra.ServeJSON()
}
func newRepositoryClient(endpoint string, insecure bool, username, password, repository, scopeType, scopeName string,
scopeActions ...string) (*registry.Repository, error) {
credential := auth.NewBasicAuthCredential(username, password)
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
store, err := auth.NewAuthorizerStore(endpoint, authorizer)
if err != nil {
return nil, err
}
client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store)
if err != nil {
return nil, err
}
return client, nil
}

View File

@ -26,7 +26,7 @@ import (
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils"
"github.com/vmware/harbor/utils/log"
registry_util "github.com/vmware/harbor/utils/registry"
"github.com/vmware/harbor/utils/registry"
"github.com/vmware/harbor/utils/registry/auth"
registry_error "github.com/vmware/harbor/utils/registry/error"
)
@ -92,8 +92,10 @@ func (t *TargetAPI) Ping() {
password = t.GetString("password")
}
credential := auth.NewBasicAuthCredential(username, password)
registry, err := registry_util.NewRegistryWithCredential(endpoint, credential)
// TODO read variable from config file
insecure := true
registry, err := newRegistryClient(endpoint, insecure, username, password,
"", "", "")
if err != nil {
// timeout, dns resolve error, connection refused, etc.
if urlErr, ok := err.(*url.Error); ok {
@ -273,3 +275,20 @@ func (t *TargetAPI) Delete() {
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
}
func newRegistryClient(endpoint string, insecure bool, username, password, scopeType, scopeName string,
scopeActions ...string) (*registry.Registry, error) {
credential := auth.NewBasicAuthCredential(username, password)
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
store, err := auth.NewAuthorizerStore(endpoint, authorizer)
if err != nil {
return nil, err
}
client, err := registry.NewRegistryWithModifiers(endpoint, insecure, store)
if err != nil {
return nil, err
}
return client, nil
}

View File

@ -16,6 +16,7 @@
package replication
import (
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
@ -39,17 +40,20 @@ type Deleter struct {
dstUsr string // username ...
dstPwd string // username ...
insecure bool
logger *log.Logger
}
// NewDeleter returns a Deleter
func NewDeleter(repository string, tags []string, dstURL, dstUsr, dstPwd string, logger *log.Logger) *Deleter {
func NewDeleter(repository string, tags []string, dstURL, dstUsr, dstPwd string, insecure bool, logger *log.Logger) *Deleter {
deleter := &Deleter{
repository: repository,
tags: tags,
dstURL: dstURL,
dstUsr: dstUsr,
dstPwd: dstPwd,
insecure: insecure,
logger: logger,
}
deleter.logger.Infof("initialization completed: repository: %s, tags: %v, destination URL: %s, destination user: %s",
@ -69,7 +73,7 @@ func (d *Deleter) Enter() (string, error) {
// delete repository
if len(d.tags) == 0 {
u := url + "?repo_name=" + d.repository
if err := del(u, d.dstUsr, d.dstPwd); err != nil {
if err := del(u, d.dstUsr, d.dstPwd, d.insecure); err != nil {
d.logger.Errorf("an error occurred while deleting repository %s on %s with user %s: %v", d.repository, d.dstURL, d.dstUsr, err)
return "", err
}
@ -82,7 +86,7 @@ func (d *Deleter) Enter() (string, error) {
// delele tags
for _, tag := range d.tags {
u := url + "?repo_name=" + d.repository + "&tag=" + tag
if err := del(u, d.dstUsr, d.dstPwd); err != nil {
if err := del(u, d.dstUsr, d.dstPwd, d.insecure); err != nil {
d.logger.Errorf("an error occurred while deleting repository %s:%s on %s with user %s: %v", d.repository, tag, d.dstURL, d.dstUsr, err)
return "", err
}
@ -93,14 +97,23 @@ func (d *Deleter) Enter() (string, error) {
return models.JobFinished, nil
}
func del(url, username, password string) error {
func del(url, username, password string, insecure bool) error {
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return err
}
req.SetBasicAuth(username, password)
resp, err := http.DefaultClient.Do(req)
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure,
},
},
}
resp, err := client.Do(req)
if err != nil {
return err
}

View File

@ -61,6 +61,8 @@ type BaseHandler struct {
dstUsr string // username ...
dstPwd string // password ...
insecure bool // whether skip secure check when using https
srcClient *registry.Repository
dstClient *registry.Repository
@ -75,7 +77,7 @@ type BaseHandler struct {
// InitBaseHandler initializes a BaseHandler: creating clients for source and destination registry,
// listing tags of the repository if parameter tags is nil.
func InitBaseHandler(repository, srcURL, srcSecret,
dstURL, dstUsr, dstPwd string, tags []string, logger *log.Logger) (*BaseHandler, error) {
dstURL, dstUsr, dstPwd string, insecure bool, tags []string, logger *log.Logger) (*BaseHandler, error) {
logger.Infof("initializing: repository: %s, tags: %v, source URL: %s, destination URL: %s, destination user: %s",
repository, tags, srcURL, dstURL, dstUsr)
@ -96,14 +98,16 @@ func InitBaseHandler(repository, srcURL, srcSecret,
c := &http.Cookie{Name: models.UISecretCookie, Value: srcSecret}
srcCred := auth.NewCookieCredential(c)
// srcCred := auth.NewBasicAuthCredential("admin", "Harbor12345")
srcClient, err := registry.NewRepositoryWithCredential(base.repository, base.srcURL, srcCred)
srcClient, err := newRepositoryClient(base.srcURL, base.insecure, srcCred,
base.repository, "repository", base.repository, "pull", "push", "*")
if err != nil {
return nil, err
}
base.srcClient = srcClient
dstCred := auth.NewBasicAuthCredential(base.dstUsr, base.dstPwd)
dstClient, err := registry.NewRepositoryWithCredential(base.repository, base.dstURL, dstCred)
dstClient, err := newRepositoryClient(base.dstURL, base.insecure, dstCred,
base.repository, "repository", base.repository, "pull", "push", "*")
if err != nil {
return nil, err
}
@ -416,3 +420,34 @@ func (m *ManifestPusher) Enter() (string, error) {
return StatePullManifest, nil
}
func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential, repository, scopeType, scopeName string,
scopeActions ...string) (*registry.Repository, error) {
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
store, err := auth.NewAuthorizerStore(endpoint, authorizer)
if err != nil {
return nil, err
}
uam := &userAgentModifier{
userAgent: "registry-client",
}
client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store, uam)
if err != nil {
return nil, err
}
return client, nil
}
type userAgentModifier struct {
userAgent string
}
// Modify adds user-agent header to the request
func (u *userAgentModifier) Modify(req *http.Request) error {
req.Header.Set(http.CanonicalHeaderKey("User-Agent"), u.userAgent)
return nil
}

View File

@ -258,9 +258,12 @@ func (sm *SM) Reset(jid int64) error {
}
func addImgTransferTransition(sm *SM) error {
// TODO read variable from config file
insecure := true
base, err := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(),
sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
sm.Parms.Tags, sm.Logger)
insecure, sm.Parms.Tags, sm.Logger)
if err != nil {
return err
}
@ -274,8 +277,10 @@ func addImgTransferTransition(sm *SM) error {
}
func addImgDeleteTransition(sm *SM) error {
// TODO read variable from config file
insecure := true
deleter := replication.NewDeleter(sm.Parms.Repository, sm.Parms.Tags, sm.Parms.TargetURL,
sm.Parms.TargetUsername, sm.Parms.TargetPassword, sm.Logger)
sm.Parms.TargetUsername, sm.Parms.TargetPassword, insecure, sm.Logger)
sm.AddTransition(models.JobRunning, replication.StateDelete, deleter)
sm.AddTransition(replication.StateDelete, models.JobFinished, &StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobFinished})

View File

@ -16,11 +16,13 @@
package cache
import (
"errors"
"os"
"time"
"github.com/vmware/harbor/utils/log"
"github.com/vmware/harbor/utils/registry"
"github.com/vmware/harbor/utils/registry/auth"
"github.com/astaxie/beego/cache"
)
@ -29,9 +31,9 @@ var (
// Cache is the global cache in system.
Cache cache.Cache
endpoint string
insecure bool
username string
registryClient *registry.Registry
repositoryClients map[string]*registry.Repository
)
const catalogKey string = "catalog"
@ -44,8 +46,15 @@ func init() {
}
endpoint = os.Getenv("REGISTRY_URL")
// TODO read variable from config file
insecure = true
username = "admin"
repositoryClients = make(map[string]*registry.Repository, 10)
registryClient, err = NewRegistryClient(endpoint, insecure, username,
"registry", "catalog", "*")
if err != nil {
log.Errorf("failed to create registry client: %v", err)
return
}
}
// RefreshCatalogCache calls registry's API to get repository list and write it to cache.
@ -53,12 +62,7 @@ func RefreshCatalogCache() error {
log.Debug("refreshing catalog cache...")
if registryClient == nil {
var err error
registryClient, err = registry.NewRegistryWithUsername(endpoint, username)
if err != nil {
log.Errorf("error occurred while initializing registry client used by cache: %v", err)
return err
}
return errors.New("registry client is nil")
}
var err error
@ -70,15 +74,13 @@ func RefreshCatalogCache() error {
repos := []string{}
for _, repo := range rs {
rc, ok := repositoryClients[repo]
if !ok {
rc, err = registry.NewRepositoryWithUsernameForUI(repo, endpoint, username)
rc, err := NewRepositoryClient(endpoint, insecure, username,
repo, "repository", repo, "pull", "push", "*")
if err != nil {
log.Errorf("error occurred while initializing repository client used by cache: %s %v", repo, err)
continue
}
repositoryClients[repo] = rc
}
tags, err := rc.ListTag()
if err != nil {
log.Errorf("error occurred while list tag for %s: %v", repo, err)
@ -112,3 +114,38 @@ func GetRepoFromCache() ([]string, error) {
}
return result.([]string), nil
}
// NewRegistryClient ...
func NewRegistryClient(endpoint string, insecure bool, username, scopeType, scopeName string,
scopeActions ...string) (*registry.Registry, error) {
authorizer := auth.NewUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...)
store, err := auth.NewAuthorizerStore(endpoint, authorizer)
if err != nil {
return nil, err
}
client, err := registry.NewRegistryWithModifiers(endpoint, insecure, store)
if err != nil {
return nil, err
}
return client, nil
}
// NewRepositoryClient ...
func NewRepositoryClient(endpoint string, insecure bool, username, repository, scopeType, scopeName string,
scopeActions ...string) (*registry.Repository, error) {
authorizer := auth.NewUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...)
store, err := auth.NewAuthorizerStore(endpoint, authorizer)
if err != nil {
return nil, err
}
client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store)
if err != nil {
return nil, err
}
return client, nil
}

View File

@ -25,7 +25,6 @@ import (
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/service/cache"
"github.com/vmware/harbor/utils/log"
"github.com/vmware/harbor/utils/registry"
"github.com/astaxie/beego"
)
@ -57,7 +56,7 @@ func (n *NotificationHandler) Post() {
matched = false
}
if matched && (strings.HasPrefix(e.Request.UserAgent, "docker") ||
strings.ToLower(strings.TrimSpace(e.Request.UserAgent)) == strings.ToLower(registry.UserAgent)) {
strings.ToLower(strings.TrimSpace(e.Request.UserAgent)) == "registry-client") {
username = e.Actor.Name
action = e.Action
repo = e.Target.Repository

View File

@ -16,40 +16,54 @@
package auth
import (
"fmt"
"net/http"
au "github.com/docker/distribution/registry/client/auth"
"github.com/vmware/harbor/utils/registry/utils"
)
// Handler authorizes requests according to the schema
type Handler interface {
// Authorizer authorizes requests according to the schema
type Authorizer interface {
// Scheme : basic, bearer
Scheme() string
//AuthorizeRequest adds basic auth or token auth to the header of request
AuthorizeRequest(req *http.Request, params map[string]string) error
//Authorize adds basic auth or token auth to the header of request
Authorize(req *http.Request, params map[string]string) error
}
// RequestAuthorizer holds a handler list, which will authorize request.
// Implements interface RequestModifier
type RequestAuthorizer struct {
handlers []Handler
// AuthorizerStore holds a authorizer list, which will authorize request.
// And it implements interface Modifier
type AuthorizerStore struct {
authorizers []Authorizer
challenges []au.Challenge
}
// NewRequestAuthorizer ...
func NewRequestAuthorizer(handlers []Handler, challenges []au.Challenge) *RequestAuthorizer {
return &RequestAuthorizer{
handlers: handlers,
challenges: challenges,
// NewAuthorizerStore ...
func NewAuthorizerStore(endpoint string, authorizers ...Authorizer) (*AuthorizerStore, error) {
endpoint = utils.FormatEndpoint(endpoint)
resp, err := http.Get(buildPingURL(endpoint))
if err != nil {
return nil, err
}
challenges := ParseChallengeFromResponse(resp)
return &AuthorizerStore{
authorizers: authorizers,
challenges: challenges,
}, nil
}
// ModifyRequest adds authorization to the request
func (r *RequestAuthorizer) ModifyRequest(req *http.Request) error {
for _, challenge := range r.challenges {
for _, handler := range r.handlers {
if handler.Scheme() == challenge.Scheme {
if err := handler.AuthorizeRequest(req, challenge.Parameters); err != nil {
func buildPingURL(endpoint string) string {
return fmt.Sprintf("%s/v2/", endpoint)
}
// Modify adds authorization to the request
func (a *AuthorizerStore) Modify(req *http.Request) error {
for _, challenge := range a.challenges {
for _, authorizer := range a.authorizers {
if authorizer.Scheme() == challenge.Scheme {
if err := authorizer.Authorize(req, challenge.Parameters); err != nil {
return err
}
}

View File

@ -19,14 +19,11 @@ import (
"net/http"
au "github.com/docker/distribution/registry/client/auth"
"github.com/vmware/harbor/utils/log"
)
// ParseChallengeFromResponse ...
func ParseChallengeFromResponse(resp *http.Response) []au.Challenge {
challenges := au.ResponseChallenges(resp)
log.Debugf("challenges: %v", challenges)
return challenges
}

View File

@ -16,6 +16,7 @@
package auth
import (
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
@ -42,8 +43,8 @@ func (s *scope) string() string {
type tokenGenerator func(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error)
// Implements interface Handler
type tokenHandler struct {
// Implements interface Authorizer
type tokenAuthorizer struct {
scope *scope
tg tokenGenerator
cache string // cached token
@ -53,12 +54,12 @@ type tokenHandler struct {
}
// Scheme returns the scheme that the handler can handle
func (t *tokenHandler) Scheme() string {
func (t *tokenAuthorizer) Scheme() string {
return "bearer"
}
// AuthorizeRequest will add authorization header which contains a token before the request is sent
func (t *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
func (t *tokenAuthorizer) Authorize(req *http.Request, params map[string]string) error {
var scopes []*scope
var token string
@ -100,26 +101,23 @@ func (t *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]str
if !hasFrom {
t.updateCachedToken(to, expiresIn, issuedAt)
log.Debug("add token to cache")
}
} else {
token = cachedToken
log.Debug("get token from cache")
}
req.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("Bearer %s", token))
log.Debugf("add token to request: %s %s", req.Method, req.URL.String())
return nil
}
func (t *tokenHandler) getCachedToken() (string, int, *time.Time) {
func (t *tokenAuthorizer) getCachedToken() (string, int, *time.Time) {
t.Lock()
defer t.Unlock()
return t.cache, t.expiresIn, t.issuedAt
}
func (t *tokenHandler) updateCachedToken(token string, expiresIn int, issuedAt *time.Time) {
func (t *tokenAuthorizer) updateCachedToken(token string, expiresIn int, issuedAt *time.Time) {
t.Lock()
defer t.Unlock()
t.cache = token
@ -127,38 +125,43 @@ func (t *tokenHandler) updateCachedToken(token string, expiresIn int, issuedAt *
t.issuedAt = issuedAt
}
// Implements interface Handler
type standardTokenHandler struct {
tokenHandler
// Implements interface Authorizer
type standardTokenAuthorizer struct {
tokenAuthorizer
client *http.Client
credential Credential
}
// NewStandardTokenHandler returns a standard token handler. The handler will request a token
// NewStandardTokenAuthorizer returns a standard token authorizer. The authorizer will request a token
// from token server and add it to the origin request
// TODO deal with https
func NewStandardTokenHandler(credential Credential, scopeType, scopeName string, scopeActions ...string) Handler {
handler := &standardTokenHandler{
func NewStandardTokenAuthorizer(credential Credential, insecure bool, scopeType, scopeName string, scopeActions ...string) Authorizer {
t := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure,
},
}
authorizer := &standardTokenAuthorizer{
client: &http.Client{
Transport: http.DefaultTransport,
Transport: t,
},
credential: credential,
}
if len(scopeType) != 0 || len(scopeName) != 0 {
handler.scope = &scope{
authorizer.scope = &scope{
Type: scopeType,
Name: scopeName,
Actions: scopeActions,
}
}
handler.tg = handler.generateToken
authorizer.tg = authorizer.generateToken
return handler
return authorizer
}
func (s *standardTokenHandler) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
u, err := url.Parse(realm)
if err != nil {
return
@ -217,37 +220,34 @@ func (s *standardTokenHandler) generateToken(realm, service string, scopes []str
}
}
log.Debug("get token from token server")
return
}
// Implements interface Handler
type usernameTokenHandler struct {
tokenHandler
type usernameTokenAuthorizer struct {
tokenAuthorizer
username string
}
// NewUsernameTokenHandler returns a handler which will generate a token according to
// NewUsernameTokenAuthorizer returns a authorizer which will generate a token according to
// the user's privileges
func NewUsernameTokenHandler(username string, scopeType, scopeName string, scopeActions ...string) Handler {
handler := &usernameTokenHandler{
func NewUsernameTokenAuthorizer(username string, scopeType, scopeName string, scopeActions ...string) Authorizer {
authorizer := &usernameTokenAuthorizer{
username: username,
}
handler.scope = &scope{
authorizer.scope = &scope{
Type: scopeType,
Name: scopeName,
Actions: scopeActions,
}
handler.tg = handler.generateToken
authorizer.tg = authorizer.generateToken
return handler
return authorizer
}
func (u *usernameTokenHandler) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
func (u *usernameTokenAuthorizer) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
token, expiresIn, issuedAt, err = token_util.GenTokenForUI(u.username, service, scopes)
log.Debug("get token by calling GenTokenForUI directly")
return
}

View File

@ -0,0 +1,25 @@
/*
Copyright (c) 2016 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 registry
import (
"net/http"
)
// Modifier modifies request
type Modifier interface {
Modify(*http.Request) error
}

View File

@ -16,20 +16,15 @@
package registry
import (
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/vmware/harbor/utils/registry/auth"
registry_error "github.com/vmware/harbor/utils/registry/error"
)
const (
// UserAgent is used to decorate the request so it can be identified by webhook.
UserAgent string = "registry-client"
"github.com/vmware/harbor/utils/registry/utils"
)
// Registry holds information of a registry entity
@ -40,14 +35,7 @@ type Registry struct {
// NewRegistry returns an instance of registry
func NewRegistry(endpoint string, client *http.Client) (*Registry, error) {
endpoint = strings.TrimSpace(endpoint)
endpoint = strings.TrimRight(endpoint, "/")
if !strings.HasPrefix(endpoint, "http://") &&
!strings.HasPrefix(endpoint, "https://") {
endpoint = "http://" + endpoint
}
u, err := url.Parse(endpoint)
u, err := utils.ParseEndpoint(endpoint)
if err != nil {
return nil, err
}
@ -60,27 +48,27 @@ func NewRegistry(endpoint string, client *http.Client) (*Registry, error) {
return registry, nil
}
// NewRegistryWithUsername returns a Registry instance which will authorize the request
// according to the privileges of user
func NewRegistryWithUsername(endpoint, username string) (*Registry, error) {
client, err := newClient(endpoint, username, nil, true, "registry", "catalog", "*")
// NewRegistryWithModifiers returns an instance of Registry according to the modifiers
func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modifier) (*Registry, error) {
u, err := utils.ParseEndpoint(endpoint)
if err != nil {
return nil, err
}
return NewRegistry(endpoint, client)
}
// NewRegistryWithCredential returns a Registry instance which associate to a crendential.
// And Credential is essentially a decorator for client to docorate the request before sending it to the registry.
func NewRegistryWithCredential(endpoint string, credential auth.Credential) (*Registry, error) {
client, err := newClient(endpoint, "", credential, true, "", "", "")
if err != nil {
return nil, err
t := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure,
},
}
return NewRegistry(endpoint, client)
transport := NewTransport(t, modifiers...)
return &Registry{
Endpoint: u,
client: &http.Client{
Transport: transport,
},
}, nil
}
// Catalog ...
@ -133,16 +121,6 @@ func (r *Registry) Ping() error {
resp, err := r.client.Do(req)
if err != nil {
// if urlErr, ok := err.(*url.Error); ok {
// if regErr, ok := urlErr.Err.(*registry_error.Error); ok {
// return &registry_error.Error{
// StatusCode: regErr.StatusCode,
// Detail: regErr.Detail,
// }
// }
// return urlErr.Err
// }
return parseError(err)
}
@ -166,44 +144,3 @@ func (r *Registry) Ping() error {
func buildCatalogURL(endpoint string) string {
return fmt.Sprintf("%s/v2/_catalog", endpoint)
}
func newClient(endpoint, username string, credential auth.Credential,
addUserAgent bool, scopeType, scopeName string, scopeActions ...string) (*http.Client, error) {
endpoint = strings.TrimSpace(endpoint)
endpoint = strings.TrimRight(endpoint, "/")
if !strings.HasPrefix(endpoint, "http://") &&
!strings.HasPrefix(endpoint, "https://") {
endpoint = "http://" + endpoint
}
resp, err := http.Get(buildPingURL(endpoint))
if err != nil {
return nil, err
}
var handlers []auth.Handler
var handler auth.Handler
if credential != nil {
handler = auth.NewStandardTokenHandler(credential, scopeType, scopeName, scopeActions...)
} else {
handler = auth.NewUsernameTokenHandler(username, scopeType, scopeName, scopeActions...)
}
handlers = append(handlers, handler)
challenges := auth.ParseChallengeFromResponse(resp)
authorizer := auth.NewRequestAuthorizer(handlers, challenges)
rm := []RequestModifier{}
rm = append(rm, authorizer)
if addUserAgent {
headerModifier := NewHeaderModifier(map[string]string{http.CanonicalHeaderKey("User-Agent"): UserAgent})
rm = append(rm, headerModifier)
}
transport := NewTransport(http.DefaultTransport, rm)
return &http.Client{
Transport: transport,
}, nil
}

View File

@ -17,6 +17,7 @@ package registry
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io"
@ -28,8 +29,9 @@ import (
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
"github.com/vmware/harbor/utils/registry/auth"
registry_error "github.com/vmware/harbor/utils/registry/error"
"github.com/vmware/harbor/utils/registry/utils"
)
// Repository holds information of a repository entity
@ -39,20 +41,11 @@ type Repository struct {
client *http.Client
}
// TODO add agent to header of request, notifications need it
// NewRepository returns an instance of Repository
func NewRepository(name, endpoint string, client *http.Client) (*Repository, error) {
name = strings.TrimSpace(name)
endpoint = strings.TrimSpace(endpoint)
endpoint = strings.TrimRight(endpoint, "/")
if !strings.HasPrefix(endpoint, "http://") &&
!strings.HasPrefix(endpoint, "https://") {
endpoint = "http://" + endpoint
}
u, err := url.Parse(endpoint)
u, err := utils.ParseEndpoint(endpoint)
if err != nil {
return nil, err
}
@ -66,48 +59,30 @@ func NewRepository(name, endpoint string, client *http.Client) (*Repository, err
return repository, nil
}
// NewRepositoryWithCredential returns a Repository instance which will authorize the request
// according to the credenttial
func NewRepositoryWithCredential(name, endpoint string, credential auth.Credential) (*Repository, error) {
client, err := newClient(endpoint, "", credential, true, "repository", name, "pull", "push")
// NewRepositoryWithModifiers returns an instance of Repository according to the modifiers
func NewRepositoryWithModifiers(name, endpoint string, insecure bool, modifiers ...Modifier) (*Repository, error) {
name = strings.TrimSpace(name)
u, err := utils.ParseEndpoint(endpoint)
if err != nil {
return nil, err
}
return NewRepository(name, endpoint, client)
}
// NewRepositoryWithCredentialForUI returns a Repository instance for UI
// The "user-agent" header of this instance does not been set
func NewRepositoryWithCredentialForUI(name, endpoint string, credential auth.Credential) (*Repository, error) {
client, err := newClient(endpoint, "", credential, false, "repository", name, "pull", "push")
if err != nil {
return nil, err
t := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure,
},
}
return NewRepository(name, endpoint, client)
}
transport := NewTransport(t, modifiers...)
// NewRepositoryWithUsername returns a Repository instance which will authorize the request
// according to the privileges of user
func NewRepositoryWithUsername(name, endpoint, username string) (*Repository, error) {
client, err := newClient(endpoint, username, nil, true, "repository", name, "pull", "push")
if err != nil {
return nil, err
}
return NewRepository(name, endpoint, client)
}
// NewRepositoryWithUsernameForUI returns a Repository instance for UI
// The "user-agent" header of this instance does not been set
func NewRepositoryWithUsernameForUI(name, endpoint, username string) (*Repository, error) {
client, err := newClient(endpoint, username, nil, false, "repository", name, "pull", "push")
if err != nil {
return nil, err
}
return NewRepository(name, endpoint, client)
return &Repository{
Name: name,
Endpoint: u,
client: &http.Client{
Transport: transport,
},
}, nil
}
func parseError(err error) error {

View File

@ -26,7 +26,7 @@ import (
"time"
"github.com/vmware/harbor/utils/registry/auth"
"github.com/vmware/harbor/utils/registry/error"
registry_error "github.com/vmware/harbor/utils/registry/error"
)
var (
@ -139,7 +139,8 @@ func serveToken(w http.ResponseWriter, r *http.Request) {
}
func TestListTag(t *testing.T) {
client, err := NewRepositoryWithCredential(repo, registryServer.URL, credential)
client, err := newRepositoryClient(registryServer.URL, true, credential,
repo, "repository", repo, "pull", "push", "*")
if err != nil {
t.Error(err)
}
@ -158,13 +159,14 @@ func TestListTag(t *testing.T) {
func TestListTagWithInvalidCredential(t *testing.T) {
credential := auth.NewBasicAuthCredential(username, "wrong_password")
client, err := NewRepositoryWithCredential(repo, registryServer.URL, credential)
client, err := newRepositoryClient(registryServer.URL, true, credential,
repo, "repository", repo, "pull", "push", "*")
if err != nil {
t.Error(err)
}
if _, err = client.ListTag(); err != nil {
e, ok := err.(*error.Error)
e, ok := err.(*registry_error.Error)
if ok && e.StatusCode == http.StatusUnauthorized {
return
}
@ -173,3 +175,20 @@ func TestListTagWithInvalidCredential(t *testing.T) {
return
}
}
func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential, repository, scopeType, scopeName string,
scopeActions ...string) (*Repository, error) {
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
store, err := auth.NewAuthorizerStore(endpoint, authorizer)
if err != nil {
return nil, err
}
client, err := NewRepositoryWithModifiers(repository, endpoint, insecure, store)
if err != nil {
return nil, err
}
return client, nil
}

View File

@ -21,39 +21,14 @@ import (
"github.com/vmware/harbor/utils/log"
)
// RequestModifier modifies request
type RequestModifier interface {
ModifyRequest(*http.Request) error
}
// HeaderModifier adds headers to request
type HeaderModifier struct {
headers map[string]string
}
// NewHeaderModifier ...
func NewHeaderModifier(headers map[string]string) *HeaderModifier {
return &HeaderModifier{
headers: headers,
}
}
// ModifyRequest adds headers to the request
func (h *HeaderModifier) ModifyRequest(req *http.Request) error {
for key, value := range h.headers {
req.Header.Add(key, value)
}
return nil
}
// Transport holds information about base transport and modifiers
type Transport struct {
transport http.RoundTripper
modifiers []RequestModifier
modifiers []Modifier
}
// NewTransport ...
func NewTransport(transport http.RoundTripper, modifiers []RequestModifier) *Transport {
func NewTransport(transport http.RoundTripper, modifiers ...Modifier) *Transport {
return &Transport{
transport: transport,
modifiers: modifiers,
@ -63,7 +38,7 @@ func NewTransport(transport http.RoundTripper, modifiers []RequestModifier) *Tra
// RoundTrip ...
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
for _, modifier := range t.modifiers {
if err := modifier.ModifyRequest(req); err != nil {
if err := modifier.Modify(req); err != nil {
return nil, err
}
}

View File

@ -0,0 +1,44 @@
/*
Copyright (c) 2016 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 utils
import (
"net/url"
"strings"
)
// FormatEndpoint formats endpoint
func FormatEndpoint(endpoint string) string {
endpoint = strings.TrimSpace(endpoint)
endpoint = strings.TrimRight(endpoint, "/")
if !strings.HasPrefix(endpoint, "http://") &&
!strings.HasPrefix(endpoint, "https://") {
endpoint = "http://" + endpoint
}
return endpoint
}
// ParseEndpoint parses endpoint to a URL
func ParseEndpoint(endpoint string) (*url.URL, error) {
endpoint = FormatEndpoint(endpoint)
u, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
return u, nil
}