mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 12:15:20 +01:00
1. modify registry client 2. add https support
This commit is contained in:
parent
5fc3c7dbeb
commit
c1a8846a68
@ -159,7 +159,7 @@ func (p *ProjectAPI) List() {
|
|||||||
if len(isPublic) > 0 {
|
if len(isPublic) > 0 {
|
||||||
public, err = strconv.Atoi(isPublic)
|
public, err = strconv.Atoi(isPublic)
|
||||||
if err != nil {
|
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")
|
p.CustomAbort(http.StatusBadRequest, "invalid project Id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,11 +255,13 @@ func (ra *RepositoryAPI) GetManifests() {
|
|||||||
|
|
||||||
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {
|
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {
|
||||||
endpoint := os.Getenv("REGISTRY_URL")
|
endpoint := os.Getenv("REGISTRY_URL")
|
||||||
|
// TODO read variable from config file
|
||||||
|
insecure := true
|
||||||
|
|
||||||
username, password, ok := ra.Ctx.Request.BasicAuth()
|
username, password, ok := ra.Ctx.Request.BasicAuth()
|
||||||
if ok {
|
if ok {
|
||||||
credential := auth.NewBasicAuthCredential(username, password)
|
return newRepositoryClient(endpoint, insecure, username, password,
|
||||||
return registry.NewRepositoryWithCredentialForUI(repoName, endpoint, credential)
|
repoName, "repository", repoName, "pull", "push", "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
username, err = ra.getUsername()
|
username, err = ra.getUsername()
|
||||||
@ -267,7 +269,8 @@ func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repo
|
|||||||
return nil, err
|
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) {
|
func (ra *RepositoryAPI) getUsername() (string, error) {
|
||||||
@ -327,3 +330,21 @@ func (ra *RepositoryAPI) GetTopRepos() {
|
|||||||
ra.Data["json"] = repos
|
ra.Data["json"] = repos
|
||||||
ra.ServeJSON()
|
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
|
||||||
|
}
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
"github.com/vmware/harbor/models"
|
"github.com/vmware/harbor/models"
|
||||||
"github.com/vmware/harbor/utils"
|
"github.com/vmware/harbor/utils"
|
||||||
"github.com/vmware/harbor/utils/log"
|
"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"
|
"github.com/vmware/harbor/utils/registry/auth"
|
||||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||||
)
|
)
|
||||||
@ -92,8 +92,10 @@ func (t *TargetAPI) Ping() {
|
|||||||
password = t.GetString("password")
|
password = t.GetString("password")
|
||||||
}
|
}
|
||||||
|
|
||||||
credential := auth.NewBasicAuthCredential(username, password)
|
// TODO read variable from config file
|
||||||
registry, err := registry_util.NewRegistryWithCredential(endpoint, credential)
|
insecure := true
|
||||||
|
registry, err := newRegistryClient(endpoint, insecure, username, password,
|
||||||
|
"", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// timeout, dns resolve error, connection refused, etc.
|
// timeout, dns resolve error, connection refused, etc.
|
||||||
if urlErr, ok := err.(*url.Error); ok {
|
if urlErr, ok := err.(*url.Error); ok {
|
||||||
@ -273,3 +275,20 @@ func (t *TargetAPI) Delete() {
|
|||||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
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
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package replication
|
package replication
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -39,17 +40,20 @@ type Deleter struct {
|
|||||||
dstUsr string // username ...
|
dstUsr string // username ...
|
||||||
dstPwd string // username ...
|
dstPwd string // username ...
|
||||||
|
|
||||||
|
insecure bool
|
||||||
|
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDeleter returns a Deleter
|
// 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{
|
deleter := &Deleter{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
dstURL: dstURL,
|
dstURL: dstURL,
|
||||||
dstUsr: dstUsr,
|
dstUsr: dstUsr,
|
||||||
dstPwd: dstPwd,
|
dstPwd: dstPwd,
|
||||||
|
insecure: insecure,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
deleter.logger.Infof("initialization completed: repository: %s, tags: %v, destination URL: %s, destination user: %s",
|
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
|
// delete repository
|
||||||
if len(d.tags) == 0 {
|
if len(d.tags) == 0 {
|
||||||
u := url + "?repo_name=" + d.repository
|
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)
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
@ -82,7 +86,7 @@ func (d *Deleter) Enter() (string, error) {
|
|||||||
// delele tags
|
// delele tags
|
||||||
for _, tag := range d.tags {
|
for _, tag := range d.tags {
|
||||||
u := url + "?repo_name=" + d.repository + "&tag=" + tag
|
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)
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
@ -93,14 +97,23 @@ func (d *Deleter) Enter() (string, error) {
|
|||||||
return models.JobFinished, nil
|
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)
|
req, err := http.NewRequest("DELETE", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.SetBasicAuth(username, password)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,8 @@ type BaseHandler struct {
|
|||||||
dstUsr string // username ...
|
dstUsr string // username ...
|
||||||
dstPwd string // password ...
|
dstPwd string // password ...
|
||||||
|
|
||||||
|
insecure bool // whether skip secure check when using https
|
||||||
|
|
||||||
srcClient *registry.Repository
|
srcClient *registry.Repository
|
||||||
dstClient *registry.Repository
|
dstClient *registry.Repository
|
||||||
|
|
||||||
@ -75,7 +77,7 @@ type BaseHandler struct {
|
|||||||
// InitBaseHandler initializes a BaseHandler: creating clients for source and destination registry,
|
// InitBaseHandler initializes a BaseHandler: creating clients for source and destination registry,
|
||||||
// listing tags of the repository if parameter tags is nil.
|
// listing tags of the repository if parameter tags is nil.
|
||||||
func InitBaseHandler(repository, srcURL, srcSecret,
|
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",
|
logger.Infof("initializing: repository: %s, tags: %v, source URL: %s, destination URL: %s, destination user: %s",
|
||||||
repository, tags, srcURL, dstURL, dstUsr)
|
repository, tags, srcURL, dstURL, dstUsr)
|
||||||
@ -96,14 +98,16 @@ func InitBaseHandler(repository, srcURL, srcSecret,
|
|||||||
c := &http.Cookie{Name: models.UISecretCookie, Value: srcSecret}
|
c := &http.Cookie{Name: models.UISecretCookie, Value: srcSecret}
|
||||||
srcCred := auth.NewCookieCredential(c)
|
srcCred := auth.NewCookieCredential(c)
|
||||||
// srcCred := auth.NewBasicAuthCredential("admin", "Harbor12345")
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
base.srcClient = srcClient
|
base.srcClient = srcClient
|
||||||
|
|
||||||
dstCred := auth.NewBasicAuthCredential(base.dstUsr, base.dstPwd)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -416,3 +420,34 @@ func (m *ManifestPusher) Enter() (string, error) {
|
|||||||
|
|
||||||
return StatePullManifest, nil
|
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
|
||||||
|
}
|
||||||
|
@ -258,9 +258,12 @@ func (sm *SM) Reset(jid int64) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addImgTransferTransition(sm *SM) 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(),
|
base, err := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(),
|
||||||
sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
|
sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
|
||||||
sm.Parms.Tags, sm.Logger)
|
insecure, sm.Parms.Tags, sm.Logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -274,8 +277,10 @@ func addImgTransferTransition(sm *SM) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addImgDeleteTransition(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,
|
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(models.JobRunning, replication.StateDelete, deleter)
|
||||||
sm.AddTransition(replication.StateDelete, models.JobFinished, &StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobFinished})
|
sm.AddTransition(replication.StateDelete, models.JobFinished, &StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobFinished})
|
||||||
|
63
service/cache/cache.go
vendored
63
service/cache/cache.go
vendored
@ -16,11 +16,13 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/vmware/harbor/utils/log"
|
"github.com/vmware/harbor/utils/log"
|
||||||
"github.com/vmware/harbor/utils/registry"
|
"github.com/vmware/harbor/utils/registry"
|
||||||
|
"github.com/vmware/harbor/utils/registry/auth"
|
||||||
|
|
||||||
"github.com/astaxie/beego/cache"
|
"github.com/astaxie/beego/cache"
|
||||||
)
|
)
|
||||||
@ -29,9 +31,9 @@ var (
|
|||||||
// Cache is the global cache in system.
|
// Cache is the global cache in system.
|
||||||
Cache cache.Cache
|
Cache cache.Cache
|
||||||
endpoint string
|
endpoint string
|
||||||
|
insecure bool
|
||||||
username string
|
username string
|
||||||
registryClient *registry.Registry
|
registryClient *registry.Registry
|
||||||
repositoryClients map[string]*registry.Repository
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const catalogKey string = "catalog"
|
const catalogKey string = "catalog"
|
||||||
@ -44,8 +46,15 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
endpoint = os.Getenv("REGISTRY_URL")
|
endpoint = os.Getenv("REGISTRY_URL")
|
||||||
|
// TODO read variable from config file
|
||||||
|
insecure = true
|
||||||
username = "admin"
|
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.
|
// 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...")
|
log.Debug("refreshing catalog cache...")
|
||||||
|
|
||||||
if registryClient == nil {
|
if registryClient == nil {
|
||||||
var err error
|
return errors.New("registry client is nil")
|
||||||
registryClient, err = registry.NewRegistryWithUsername(endpoint, username)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("error occurred while initializing registry client used by cache: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -70,15 +74,13 @@ func RefreshCatalogCache() error {
|
|||||||
repos := []string{}
|
repos := []string{}
|
||||||
|
|
||||||
for _, repo := range rs {
|
for _, repo := range rs {
|
||||||
rc, ok := repositoryClients[repo]
|
rc, err := NewRepositoryClient(endpoint, insecure, username,
|
||||||
if !ok {
|
repo, "repository", repo, "pull", "push", "*")
|
||||||
rc, err = registry.NewRepositoryWithUsernameForUI(repo, endpoint, username)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error occurred while initializing repository client used by cache: %s %v", repo, err)
|
log.Errorf("error occurred while initializing repository client used by cache: %s %v", repo, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
repositoryClients[repo] = rc
|
|
||||||
}
|
|
||||||
tags, err := rc.ListTag()
|
tags, err := rc.ListTag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error occurred while list tag for %s: %v", repo, err)
|
log.Errorf("error occurred while list tag for %s: %v", repo, err)
|
||||||
@ -112,3 +114,38 @@ func GetRepoFromCache() ([]string, error) {
|
|||||||
}
|
}
|
||||||
return result.([]string), nil
|
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
|
||||||
|
}
|
||||||
|
@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/vmware/harbor/models"
|
"github.com/vmware/harbor/models"
|
||||||
"github.com/vmware/harbor/service/cache"
|
"github.com/vmware/harbor/service/cache"
|
||||||
"github.com/vmware/harbor/utils/log"
|
"github.com/vmware/harbor/utils/log"
|
||||||
"github.com/vmware/harbor/utils/registry"
|
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
)
|
)
|
||||||
@ -57,7 +56,7 @@ func (n *NotificationHandler) Post() {
|
|||||||
matched = false
|
matched = false
|
||||||
}
|
}
|
||||||
if matched && (strings.HasPrefix(e.Request.UserAgent, "docker") ||
|
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
|
username = e.Actor.Name
|
||||||
action = e.Action
|
action = e.Action
|
||||||
repo = e.Target.Repository
|
repo = e.Target.Repository
|
||||||
|
@ -16,40 +16,54 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
au "github.com/docker/distribution/registry/client/auth"
|
au "github.com/docker/distribution/registry/client/auth"
|
||||||
|
"github.com/vmware/harbor/utils/registry/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler authorizes requests according to the schema
|
// Authorizer authorizes requests according to the schema
|
||||||
type Handler interface {
|
type Authorizer interface {
|
||||||
// Scheme : basic, bearer
|
// Scheme : basic, bearer
|
||||||
Scheme() string
|
Scheme() string
|
||||||
//AuthorizeRequest adds basic auth or token auth to the header of request
|
//Authorize adds basic auth or token auth to the header of request
|
||||||
AuthorizeRequest(req *http.Request, params map[string]string) error
|
Authorize(req *http.Request, params map[string]string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestAuthorizer holds a handler list, which will authorize request.
|
// AuthorizerStore holds a authorizer list, which will authorize request.
|
||||||
// Implements interface RequestModifier
|
// And it implements interface Modifier
|
||||||
type RequestAuthorizer struct {
|
type AuthorizerStore struct {
|
||||||
handlers []Handler
|
authorizers []Authorizer
|
||||||
challenges []au.Challenge
|
challenges []au.Challenge
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequestAuthorizer ...
|
// NewAuthorizerStore ...
|
||||||
func NewRequestAuthorizer(handlers []Handler, challenges []au.Challenge) *RequestAuthorizer {
|
func NewAuthorizerStore(endpoint string, authorizers ...Authorizer) (*AuthorizerStore, error) {
|
||||||
return &RequestAuthorizer{
|
endpoint = utils.FormatEndpoint(endpoint)
|
||||||
handlers: handlers,
|
|
||||||
challenges: challenges,
|
resp, err := http.Get(buildPingURL(endpoint))
|
||||||
}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModifyRequest adds authorization to the request
|
challenges := ParseChallengeFromResponse(resp)
|
||||||
func (r *RequestAuthorizer) ModifyRequest(req *http.Request) error {
|
return &AuthorizerStore{
|
||||||
for _, challenge := range r.challenges {
|
authorizers: authorizers,
|
||||||
for _, handler := range r.handlers {
|
challenges: challenges,
|
||||||
if handler.Scheme() == challenge.Scheme {
|
}, nil
|
||||||
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,14 +19,11 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
au "github.com/docker/distribution/registry/client/auth"
|
au "github.com/docker/distribution/registry/client/auth"
|
||||||
"github.com/vmware/harbor/utils/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseChallengeFromResponse ...
|
// ParseChallengeFromResponse ...
|
||||||
func ParseChallengeFromResponse(resp *http.Response) []au.Challenge {
|
func ParseChallengeFromResponse(resp *http.Response) []au.Challenge {
|
||||||
challenges := au.ResponseChallenges(resp)
|
challenges := au.ResponseChallenges(resp)
|
||||||
|
|
||||||
log.Debugf("challenges: %v", challenges)
|
|
||||||
|
|
||||||
return challenges
|
return challenges
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"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)
|
type tokenGenerator func(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error)
|
||||||
|
|
||||||
// Implements interface Handler
|
// Implements interface Authorizer
|
||||||
type tokenHandler struct {
|
type tokenAuthorizer struct {
|
||||||
scope *scope
|
scope *scope
|
||||||
tg tokenGenerator
|
tg tokenGenerator
|
||||||
cache string // cached token
|
cache string // cached token
|
||||||
@ -53,12 +54,12 @@ type tokenHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scheme returns the scheme that the handler can handle
|
// Scheme returns the scheme that the handler can handle
|
||||||
func (t *tokenHandler) Scheme() string {
|
func (t *tokenAuthorizer) Scheme() string {
|
||||||
return "bearer"
|
return "bearer"
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizeRequest will add authorization header which contains a token before the request is sent
|
// 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 scopes []*scope
|
||||||
var token string
|
var token string
|
||||||
|
|
||||||
@ -100,26 +101,23 @@ func (t *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]str
|
|||||||
|
|
||||||
if !hasFrom {
|
if !hasFrom {
|
||||||
t.updateCachedToken(to, expiresIn, issuedAt)
|
t.updateCachedToken(to, expiresIn, issuedAt)
|
||||||
log.Debug("add token to cache")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
token = cachedToken
|
token = cachedToken
|
||||||
log.Debug("get token from cache")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("Bearer %s", token))
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tokenHandler) getCachedToken() (string, int, *time.Time) {
|
func (t *tokenAuthorizer) getCachedToken() (string, int, *time.Time) {
|
||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
return t.cache, t.expiresIn, t.issuedAt
|
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()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
t.cache = token
|
t.cache = token
|
||||||
@ -127,38 +125,43 @@ func (t *tokenHandler) updateCachedToken(token string, expiresIn int, issuedAt *
|
|||||||
t.issuedAt = issuedAt
|
t.issuedAt = issuedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements interface Handler
|
// Implements interface Authorizer
|
||||||
type standardTokenHandler struct {
|
type standardTokenAuthorizer struct {
|
||||||
tokenHandler
|
tokenAuthorizer
|
||||||
client *http.Client
|
client *http.Client
|
||||||
credential Credential
|
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
|
// from token server and add it to the origin request
|
||||||
// TODO deal with https
|
func NewStandardTokenAuthorizer(credential Credential, insecure bool, scopeType, scopeName string, scopeActions ...string) Authorizer {
|
||||||
func NewStandardTokenHandler(credential Credential, scopeType, scopeName string, scopeActions ...string) Handler {
|
t := &http.Transport{
|
||||||
handler := &standardTokenHandler{
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: insecure,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
authorizer := &standardTokenAuthorizer{
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Transport: http.DefaultTransport,
|
Transport: t,
|
||||||
},
|
},
|
||||||
credential: credential,
|
credential: credential,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(scopeType) != 0 || len(scopeName) != 0 {
|
if len(scopeType) != 0 || len(scopeName) != 0 {
|
||||||
handler.scope = &scope{
|
authorizer.scope = &scope{
|
||||||
Type: scopeType,
|
Type: scopeType,
|
||||||
Name: scopeName,
|
Name: scopeName,
|
||||||
Actions: scopeActions,
|
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)
|
u, err := url.Parse(realm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -217,37 +220,34 @@ func (s *standardTokenHandler) generateToken(realm, service string, scopes []str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("get token from token server")
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements interface Handler
|
// Implements interface Handler
|
||||||
type usernameTokenHandler struct {
|
type usernameTokenAuthorizer struct {
|
||||||
tokenHandler
|
tokenAuthorizer
|
||||||
username string
|
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
|
// the user's privileges
|
||||||
func NewUsernameTokenHandler(username string, scopeType, scopeName string, scopeActions ...string) Handler {
|
func NewUsernameTokenAuthorizer(username string, scopeType, scopeName string, scopeActions ...string) Authorizer {
|
||||||
handler := &usernameTokenHandler{
|
authorizer := &usernameTokenAuthorizer{
|
||||||
username: username,
|
username: username,
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.scope = &scope{
|
authorizer.scope = &scope{
|
||||||
Type: scopeType,
|
Type: scopeType,
|
||||||
Name: scopeName,
|
Name: scopeName,
|
||||||
Actions: scopeActions,
|
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)
|
token, expiresIn, issuedAt, err = token_util.GenTokenForUI(u.username, service, scopes)
|
||||||
log.Debug("get token by calling GenTokenForUI directly")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
25
utils/registry/modifier.go
Normal file
25
utils/registry/modifier.go
Normal 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
|
||||||
|
}
|
@ -16,20 +16,15 @@
|
|||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/vmware/harbor/utils/registry/auth"
|
|
||||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||||
)
|
"github.com/vmware/harbor/utils/registry/utils"
|
||||||
|
|
||||||
const (
|
|
||||||
// UserAgent is used to decorate the request so it can be identified by webhook.
|
|
||||||
UserAgent string = "registry-client"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Registry holds information of a registry entity
|
// Registry holds information of a registry entity
|
||||||
@ -40,14 +35,7 @@ type Registry struct {
|
|||||||
|
|
||||||
// NewRegistry returns an instance of registry
|
// NewRegistry returns an instance of registry
|
||||||
func NewRegistry(endpoint string, client *http.Client) (*Registry, error) {
|
func NewRegistry(endpoint string, client *http.Client) (*Registry, error) {
|
||||||
endpoint = strings.TrimSpace(endpoint)
|
u, err := utils.ParseEndpoint(endpoint)
|
||||||
endpoint = strings.TrimRight(endpoint, "/")
|
|
||||||
if !strings.HasPrefix(endpoint, "http://") &&
|
|
||||||
!strings.HasPrefix(endpoint, "https://") {
|
|
||||||
endpoint = "http://" + endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(endpoint)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -60,27 +48,27 @@ func NewRegistry(endpoint string, client *http.Client) (*Registry, error) {
|
|||||||
return registry, nil
|
return registry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRegistryWithUsername returns a Registry instance which will authorize the request
|
// NewRegistryWithModifiers returns an instance of Registry according to the modifiers
|
||||||
// according to the privileges of user
|
func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modifier) (*Registry, error) {
|
||||||
func NewRegistryWithUsername(endpoint, username string) (*Registry, error) {
|
u, err := utils.ParseEndpoint(endpoint)
|
||||||
|
|
||||||
client, err := newClient(endpoint, username, nil, true, "registry", "catalog", "*")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewRegistry(endpoint, client)
|
t := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: insecure,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRegistryWithCredential returns a Registry instance which associate to a crendential.
|
transport := NewTransport(t, modifiers...)
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewRegistry(endpoint, client)
|
return &Registry{
|
||||||
|
Endpoint: u,
|
||||||
|
client: &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catalog ...
|
// Catalog ...
|
||||||
@ -133,16 +121,6 @@ func (r *Registry) Ping() error {
|
|||||||
|
|
||||||
resp, err := r.client.Do(req)
|
resp, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if urlErr, ok := err.(*url.Error); ok {
|
|
||||||
// if regErr, ok := urlErr.Err.(*registry_error.Error); ok {
|
|
||||||
// return ®istry_error.Error{
|
|
||||||
// StatusCode: regErr.StatusCode,
|
|
||||||
// Detail: regErr.Detail,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return urlErr.Err
|
|
||||||
// }
|
|
||||||
|
|
||||||
return parseError(err)
|
return parseError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,44 +144,3 @@ func (r *Registry) Ping() error {
|
|||||||
func buildCatalogURL(endpoint string) string {
|
func buildCatalogURL(endpoint string) string {
|
||||||
return fmt.Sprintf("%s/v2/_catalog", endpoint)
|
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
|
|
||||||
}
|
|
||||||
|
@ -17,6 +17,7 @@ package registry
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -28,8 +29,9 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/manifest/schema2"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
"github.com/vmware/harbor/utils/registry/auth"
|
|
||||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||||
|
"github.com/vmware/harbor/utils/registry/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Repository holds information of a repository entity
|
// Repository holds information of a repository entity
|
||||||
@ -39,20 +41,11 @@ type Repository struct {
|
|||||||
client *http.Client
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add agent to header of request, notifications need it
|
|
||||||
|
|
||||||
// NewRepository returns an instance of Repository
|
// NewRepository returns an instance of Repository
|
||||||
func NewRepository(name, endpoint string, client *http.Client) (*Repository, error) {
|
func NewRepository(name, endpoint string, client *http.Client) (*Repository, error) {
|
||||||
name = strings.TrimSpace(name)
|
name = strings.TrimSpace(name)
|
||||||
|
|
||||||
endpoint = strings.TrimSpace(endpoint)
|
u, err := utils.ParseEndpoint(endpoint)
|
||||||
endpoint = strings.TrimRight(endpoint, "/")
|
|
||||||
if !strings.HasPrefix(endpoint, "http://") &&
|
|
||||||
!strings.HasPrefix(endpoint, "https://") {
|
|
||||||
endpoint = "http://" + endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(endpoint)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -66,48 +59,30 @@ func NewRepository(name, endpoint string, client *http.Client) (*Repository, err
|
|||||||
return repository, nil
|
return repository, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRepositoryWithCredential returns a Repository instance which will authorize the request
|
// NewRepositoryWithModifiers returns an instance of Repository according to the modifiers
|
||||||
// according to the credenttial
|
func NewRepositoryWithModifiers(name, endpoint string, insecure bool, modifiers ...Modifier) (*Repository, error) {
|
||||||
func NewRepositoryWithCredential(name, endpoint string, credential auth.Credential) (*Repository, error) {
|
name = strings.TrimSpace(name)
|
||||||
client, err := newClient(endpoint, "", credential, true, "repository", name, "pull", "push")
|
|
||||||
|
u, err := utils.ParseEndpoint(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewRepository(name, endpoint, client)
|
t := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: insecure,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRepositoryWithCredentialForUI returns a Repository instance for UI
|
transport := NewTransport(t, modifiers...)
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewRepository(name, endpoint, client)
|
return &Repository{
|
||||||
}
|
Name: name,
|
||||||
|
Endpoint: u,
|
||||||
// NewRepositoryWithUsername returns a Repository instance which will authorize the request
|
client: &http.Client{
|
||||||
// according to the privileges of user
|
Transport: transport,
|
||||||
func NewRepositoryWithUsername(name, endpoint, username string) (*Repository, error) {
|
},
|
||||||
client, err := newClient(endpoint, username, nil, true, "repository", name, "pull", "push")
|
}, nil
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseError(err error) error {
|
func parseError(err error) error {
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/vmware/harbor/utils/registry/auth"
|
"github.com/vmware/harbor/utils/registry/auth"
|
||||||
"github.com/vmware/harbor/utils/registry/error"
|
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -139,7 +139,8 @@ func serveToken(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestListTag(t *testing.T) {
|
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 {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -158,13 +159,14 @@ func TestListTag(t *testing.T) {
|
|||||||
|
|
||||||
func TestListTagWithInvalidCredential(t *testing.T) {
|
func TestListTagWithInvalidCredential(t *testing.T) {
|
||||||
credential := auth.NewBasicAuthCredential(username, "wrong_password")
|
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 {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = client.ListTag(); err != nil {
|
if _, err = client.ListTag(); err != nil {
|
||||||
e, ok := err.(*error.Error)
|
e, ok := err.(*registry_error.Error)
|
||||||
if ok && e.StatusCode == http.StatusUnauthorized {
|
if ok && e.StatusCode == http.StatusUnauthorized {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -173,3 +175,20 @@ func TestListTagWithInvalidCredential(t *testing.T) {
|
|||||||
return
|
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
|
||||||
|
}
|
||||||
|
@ -21,39 +21,14 @@ import (
|
|||||||
"github.com/vmware/harbor/utils/log"
|
"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
|
// Transport holds information about base transport and modifiers
|
||||||
type Transport struct {
|
type Transport struct {
|
||||||
transport http.RoundTripper
|
transport http.RoundTripper
|
||||||
modifiers []RequestModifier
|
modifiers []Modifier
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTransport ...
|
// NewTransport ...
|
||||||
func NewTransport(transport http.RoundTripper, modifiers []RequestModifier) *Transport {
|
func NewTransport(transport http.RoundTripper, modifiers ...Modifier) *Transport {
|
||||||
return &Transport{
|
return &Transport{
|
||||||
transport: transport,
|
transport: transport,
|
||||||
modifiers: modifiers,
|
modifiers: modifiers,
|
||||||
@ -63,7 +38,7 @@ func NewTransport(transport http.RoundTripper, modifiers []RequestModifier) *Tra
|
|||||||
// RoundTrip ...
|
// RoundTrip ...
|
||||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
for _, modifier := range t.modifiers {
|
for _, modifier := range t.modifiers {
|
||||||
if err := modifier.ModifyRequest(req); err != nil {
|
if err := modifier.Modify(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
utils/registry/utils/utils.go
Normal file
44
utils/registry/utils/utils.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user