зеркало из https://github.com/microsoft/docker.git
Merge pull request #13265 from tiborvass/refactor-v1-auth
registry: Refactor requestfactory to use http.RoundTrippers
This commit is contained in:
Коммит
a16b2ab274
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/docker/docker/pkg/progressreader"
|
"github.com/docker/docker/pkg/progressreader"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/docker/docker/pkg/transport"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
@ -55,12 +56,19 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf
|
||||||
defer s.poolRemove("pull", utils.ImageReference(repoInfo.LocalName, tag))
|
defer s.poolRemove("pull", utils.ImageReference(repoInfo.LocalName, tag))
|
||||||
|
|
||||||
logrus.Debugf("pulling image from host %q with remote name %q", repoInfo.Index.Name, repoInfo.RemoteName)
|
logrus.Debugf("pulling image from host %q with remote name %q", repoInfo.Index.Name, repoInfo.RemoteName)
|
||||||
endpoint, err := repoInfo.GetEndpoint()
|
|
||||||
|
endpoint, err := repoInfo.GetEndpoint(imagePullConfig.MetaHeaders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// TODO(tiborvass): reuse client from endpoint?
|
||||||
r, err := registry.NewSession(imagePullConfig.AuthConfig, registry.HTTPRequestFactory(imagePullConfig.MetaHeaders), endpoint, true)
|
// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
|
||||||
|
tr := transport.NewTransport(
|
||||||
|
registry.NewTransport(registry.ReceiveTimeout, endpoint.IsSecure),
|
||||||
|
registry.DockerHeaders(imagePullConfig.MetaHeaders)...,
|
||||||
|
)
|
||||||
|
client := registry.HTTPClient(tr)
|
||||||
|
r, err := registry.NewSession(client, imagePullConfig.AuthConfig, endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -109,7 +117,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("Retrieving the tag list")
|
logrus.Debugf("Retrieving the tag list")
|
||||||
tagsList, err := r.GetRemoteTags(repoData.Endpoints, repoInfo.RemoteName, repoData.Tokens)
|
tagsList, err := r.GetRemoteTags(repoData.Endpoints, repoInfo.RemoteName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("unable to get remote tags: %s", err)
|
logrus.Errorf("unable to get remote tags: %s", err)
|
||||||
return err
|
return err
|
||||||
|
@ -240,7 +248,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *streamformatter.StreamFormatter) (bool, error) {
|
func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *streamformatter.StreamFormatter) (bool, error) {
|
||||||
history, err := r.GetRemoteHistory(imgID, endpoint, token)
|
history, err := r.GetRemoteHistory(imgID, endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -269,7 +277,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint
|
||||||
)
|
)
|
||||||
retries := 5
|
retries := 5
|
||||||
for j := 1; j <= retries; j++ {
|
for j := 1; j <= retries; j++ {
|
||||||
imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token)
|
imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint)
|
||||||
if err != nil && j == retries {
|
if err != nil && j == retries {
|
||||||
out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil))
|
out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil))
|
||||||
return layersDownloaded, err
|
return layersDownloaded, err
|
||||||
|
@ -297,7 +305,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint
|
||||||
status = fmt.Sprintf("Pulling fs layer [retries: %d]", j)
|
status = fmt.Sprintf("Pulling fs layer [retries: %d]", j)
|
||||||
}
|
}
|
||||||
out.Write(sf.FormatProgress(stringid.TruncateID(id), status, nil))
|
out.Write(sf.FormatProgress(stringid.TruncateID(id), status, nil))
|
||||||
layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token, int64(imgSize))
|
layer, err := r.GetRemoteImageLayer(img.ID, endpoint, int64(imgSize))
|
||||||
if uerr, ok := err.(*url.Error); ok {
|
if uerr, ok := err.(*url.Error); ok {
|
||||||
err = uerr.Err
|
err = uerr.Err
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/docker/docker/pkg/progressreader"
|
"github.com/docker/docker/pkg/progressreader"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/docker/docker/pkg/transport"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
|
@ -141,7 +142,7 @@ func lookupImageOnEndpoint(wg *sync.WaitGroup, r *registry.Session, out io.Write
|
||||||
images chan imagePushData, imagesToPush chan string) {
|
images chan imagePushData, imagesToPush chan string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
for image := range images {
|
for image := range images {
|
||||||
if err := r.LookupRemoteImage(image.id, image.endpoint, image.tokens); err != nil {
|
if err := r.LookupRemoteImage(image.id, image.endpoint); err != nil {
|
||||||
logrus.Errorf("Error in LookupRemoteImage: %s", err)
|
logrus.Errorf("Error in LookupRemoteImage: %s", err)
|
||||||
imagesToPush <- image.id
|
imagesToPush <- image.id
|
||||||
continue
|
continue
|
||||||
|
@ -199,7 +200,7 @@ func (s *TagStore) pushImageToEndpoint(endpoint string, out io.Writer, remoteNam
|
||||||
}
|
}
|
||||||
for _, tag := range tags[id] {
|
for _, tag := range tags[id] {
|
||||||
out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(id), endpoint+"repositories/"+remoteName+"/tags/"+tag))
|
out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(id), endpoint+"repositories/"+remoteName+"/tags/"+tag))
|
||||||
if err := r.PushRegistryTag(remoteName, id, tag, endpoint, repo.Tokens); err != nil {
|
if err := r.PushRegistryTag(remoteName, id, tag, endpoint); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -258,7 +259,7 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the json
|
// Send the json
|
||||||
if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
|
if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep); err != nil {
|
||||||
if err == registry.ErrAlreadyExists {
|
if err == registry.ErrAlreadyExists {
|
||||||
out.Write(sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image already pushed, skipping", nil))
|
out.Write(sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image already pushed, skipping", nil))
|
||||||
return "", nil
|
return "", nil
|
||||||
|
@ -284,14 +285,14 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin
|
||||||
NewLines: false,
|
NewLines: false,
|
||||||
ID: stringid.TruncateID(imgData.ID),
|
ID: stringid.TruncateID(imgData.ID),
|
||||||
Action: "Pushing",
|
Action: "Pushing",
|
||||||
}), ep, token, jsonRaw)
|
}), ep, jsonRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
imgData.Checksum = checksum
|
imgData.Checksum = checksum
|
||||||
imgData.ChecksumPayload = checksumPayload
|
imgData.ChecksumPayload = checksumPayload
|
||||||
// Send the checksum
|
// Send the checksum
|
||||||
if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil {
|
if err := r.PushImageChecksumRegistry(imgData, ep); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -509,12 +510,18 @@ func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) erro
|
||||||
}
|
}
|
||||||
defer s.poolRemove("push", repoInfo.LocalName)
|
defer s.poolRemove("push", repoInfo.LocalName)
|
||||||
|
|
||||||
endpoint, err := repoInfo.GetEndpoint()
|
endpoint, err := repoInfo.GetEndpoint(imagePushConfig.MetaHeaders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// TODO(tiborvass): reuse client from endpoint?
|
||||||
r, err := registry.NewSession(imagePushConfig.AuthConfig, registry.HTTPRequestFactory(imagePushConfig.MetaHeaders), endpoint, false)
|
// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
|
||||||
|
tr := transport.NewTransport(
|
||||||
|
registry.NewTransport(registry.NoTimeout, endpoint.IsSecure),
|
||||||
|
registry.DockerHeaders(imagePushConfig.MetaHeaders)...,
|
||||||
|
)
|
||||||
|
client := registry.HTTPClient(tr)
|
||||||
|
r, err := registry.NewSession(client, imagePushConfig.AuthConfig, endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
This package provides helper functions for decorating a request with user agent
|
|
||||||
versions, auth, meta headers.
|
|
|
@ -1,172 +0,0 @@
|
||||||
// Package requestdecorator provides helper functions to decorate a request with
|
|
||||||
// user agent versions, auth, meta headers.
|
|
||||||
package requestdecorator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNilRequest = errors.New("request cannot be nil")
|
|
||||||
)
|
|
||||||
|
|
||||||
// UAVersionInfo is used to model UserAgent versions.
|
|
||||||
type UAVersionInfo struct {
|
|
||||||
Name string
|
|
||||||
Version string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUAVersionInfo(name, version string) UAVersionInfo {
|
|
||||||
return UAVersionInfo{
|
|
||||||
Name: name,
|
|
||||||
Version: version,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vi *UAVersionInfo) isValid() bool {
|
|
||||||
const stopChars = " \t\r\n/"
|
|
||||||
name := vi.Name
|
|
||||||
vers := vi.Version
|
|
||||||
if len(name) == 0 || strings.ContainsAny(name, stopChars) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(vers) == 0 || strings.ContainsAny(vers, stopChars) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert versions to a string and append the string to the string base.
|
|
||||||
//
|
|
||||||
// Each UAVersionInfo will be converted to a string in the format of
|
|
||||||
// "product/version", where the "product" is get from the name field, while
|
|
||||||
// version is get from the version field. Several pieces of verson information
|
|
||||||
// will be concatinated and separated by space.
|
|
||||||
func appendVersions(base string, versions ...UAVersionInfo) string {
|
|
||||||
if len(versions) == 0 {
|
|
||||||
return base
|
|
||||||
}
|
|
||||||
|
|
||||||
verstrs := make([]string, 0, 1+len(versions))
|
|
||||||
if len(base) > 0 {
|
|
||||||
verstrs = append(verstrs, base)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range versions {
|
|
||||||
if !v.isValid() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
verstrs = append(verstrs, v.Name+"/"+v.Version)
|
|
||||||
}
|
|
||||||
return strings.Join(verstrs, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decorator is used to change an instance of
|
|
||||||
// http.Request. It could be used to add more header fields,
|
|
||||||
// change body, etc.
|
|
||||||
type Decorator interface {
|
|
||||||
// ChangeRequest() changes the request accordingly.
|
|
||||||
// The changed request will be returned or err will be non-nil
|
|
||||||
// if an error occur.
|
|
||||||
ChangeRequest(req *http.Request) (newReq *http.Request, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserAgentDecorator appends the product/version to the user agent field
|
|
||||||
// of a request.
|
|
||||||
type UserAgentDecorator struct {
|
|
||||||
Versions []UAVersionInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *UserAgentDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
|
|
||||||
if req == nil {
|
|
||||||
return req, ErrNilRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
userAgent := appendVersions(req.UserAgent(), h.Versions...)
|
|
||||||
if len(userAgent) > 0 {
|
|
||||||
req.Header.Set("User-Agent", userAgent)
|
|
||||||
}
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MetaHeadersDecorator struct {
|
|
||||||
Headers map[string][]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *MetaHeadersDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
|
|
||||||
if h.Headers == nil {
|
|
||||||
return req, ErrNilRequest
|
|
||||||
}
|
|
||||||
for k, v := range h.Headers {
|
|
||||||
req.Header[k] = v
|
|
||||||
}
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthDecorator struct {
|
|
||||||
login string
|
|
||||||
password string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAuthDecorator(login, password string) Decorator {
|
|
||||||
return &AuthDecorator{
|
|
||||||
login: login,
|
|
||||||
password: password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *AuthDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
|
|
||||||
if req == nil {
|
|
||||||
return req, ErrNilRequest
|
|
||||||
}
|
|
||||||
req.SetBasicAuth(self.login, self.password)
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestFactory creates an HTTP request
|
|
||||||
// and applies a list of decorators on the request.
|
|
||||||
type RequestFactory struct {
|
|
||||||
decorators []Decorator
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequestFactory(d ...Decorator) *RequestFactory {
|
|
||||||
return &RequestFactory{
|
|
||||||
decorators: d,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *RequestFactory) AddDecorator(d ...Decorator) {
|
|
||||||
f.decorators = append(f.decorators, d...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *RequestFactory) GetDecorators() []Decorator {
|
|
||||||
return f.decorators
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRequest() creates a new *http.Request,
|
|
||||||
// applies all decorators in the Factory on the request,
|
|
||||||
// then applies decorators provided by d on the request.
|
|
||||||
func (h *RequestFactory) NewRequest(method, urlStr string, body io.Reader, d ...Decorator) (*http.Request, error) {
|
|
||||||
req, err := http.NewRequest(method, urlStr, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default, a nil factory should work.
|
|
||||||
if h == nil {
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
for _, dec := range h.decorators {
|
|
||||||
req, _ = dec.ChangeRequest(req)
|
|
||||||
}
|
|
||||||
for _, dec := range d {
|
|
||||||
req, _ = dec.ChangeRequest(req)
|
|
||||||
}
|
|
||||||
logrus.Debugf("%v -- HEADERS: %v", req.URL, req.Header)
|
|
||||||
return req, err
|
|
||||||
}
|
|
|
@ -1,222 +0,0 @@
|
||||||
package requestdecorator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUAVersionInfo(t *testing.T) {
|
|
||||||
uavi := NewUAVersionInfo("foo", "bar")
|
|
||||||
if !uavi.isValid() {
|
|
||||||
t.Fatalf("UAVersionInfo should be valid")
|
|
||||||
}
|
|
||||||
uavi = NewUAVersionInfo("", "bar")
|
|
||||||
if uavi.isValid() {
|
|
||||||
t.Fatalf("Expected UAVersionInfo to be invalid")
|
|
||||||
}
|
|
||||||
uavi = NewUAVersionInfo("foo", "")
|
|
||||||
if uavi.isValid() {
|
|
||||||
t.Fatalf("Expected UAVersionInfo to be invalid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserAgentDecorator(t *testing.T) {
|
|
||||||
httpVersion := make([]UAVersionInfo, 2)
|
|
||||||
httpVersion = append(httpVersion, NewUAVersionInfo("testname", "testversion"))
|
|
||||||
httpVersion = append(httpVersion, NewUAVersionInfo("name", "version"))
|
|
||||||
uad := &UserAgentDecorator{
|
|
||||||
Versions: httpVersion,
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "/something", strings.NewReader("test"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
reqDecorated, err := uad.ChangeRequest(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if reqDecorated.Header.Get("User-Agent") != "testname/testversion name/version" {
|
|
||||||
t.Fatalf("Request should have User-Agent 'testname/testversion name/version'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserAgentDecoratorErr(t *testing.T) {
|
|
||||||
httpVersion := make([]UAVersionInfo, 0)
|
|
||||||
uad := &UserAgentDecorator{
|
|
||||||
Versions: httpVersion,
|
|
||||||
}
|
|
||||||
|
|
||||||
var req *http.Request
|
|
||||||
_, err := uad.ChangeRequest(req)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected to get ErrNilRequest instead no error was returned")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMetaHeadersDecorator(t *testing.T) {
|
|
||||||
var headers = map[string][]string{
|
|
||||||
"key1": {"value1"},
|
|
||||||
"key2": {"value2"},
|
|
||||||
}
|
|
||||||
mhd := &MetaHeadersDecorator{
|
|
||||||
Headers: headers,
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "/something", strings.NewReader("test"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
reqDecorated, err := mhd.ChangeRequest(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
v, ok := reqDecorated.Header["key1"]
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Expected to have header key1")
|
|
||||||
}
|
|
||||||
if v[0] != "value1" {
|
|
||||||
t.Fatalf("Expected value for key1 isn't value1")
|
|
||||||
}
|
|
||||||
|
|
||||||
v, ok = reqDecorated.Header["key2"]
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Expected to have header key2")
|
|
||||||
}
|
|
||||||
if v[0] != "value2" {
|
|
||||||
t.Fatalf("Expected value for key2 isn't value2")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMetaHeadersDecoratorErr(t *testing.T) {
|
|
||||||
mhd := &MetaHeadersDecorator{}
|
|
||||||
|
|
||||||
var req *http.Request
|
|
||||||
_, err := mhd.ChangeRequest(req)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected to get ErrNilRequest instead no error was returned")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthDecorator(t *testing.T) {
|
|
||||||
ad := NewAuthDecorator("test", "password")
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "/something", strings.NewReader("test"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
reqDecorated, err := ad.ChangeRequest(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
username, password, ok := reqDecorated.BasicAuth()
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Cannot retrieve basic auth info from request")
|
|
||||||
}
|
|
||||||
if username != "test" {
|
|
||||||
t.Fatalf("Expected username to be test, got %s", username)
|
|
||||||
}
|
|
||||||
if password != "password" {
|
|
||||||
t.Fatalf("Expected password to be password, got %s", password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthDecoratorErr(t *testing.T) {
|
|
||||||
ad := &AuthDecorator{}
|
|
||||||
|
|
||||||
var req *http.Request
|
|
||||||
_, err := ad.ChangeRequest(req)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected to get ErrNilRequest instead no error was returned")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestFactory(t *testing.T) {
|
|
||||||
ad := NewAuthDecorator("test", "password")
|
|
||||||
httpVersion := make([]UAVersionInfo, 2)
|
|
||||||
httpVersion = append(httpVersion, NewUAVersionInfo("testname", "testversion"))
|
|
||||||
httpVersion = append(httpVersion, NewUAVersionInfo("name", "version"))
|
|
||||||
uad := &UserAgentDecorator{
|
|
||||||
Versions: httpVersion,
|
|
||||||
}
|
|
||||||
|
|
||||||
requestFactory := NewRequestFactory(ad, uad)
|
|
||||||
|
|
||||||
if l := len(requestFactory.GetDecorators()); l != 2 {
|
|
||||||
t.Fatalf("Expected to have two decorators, got %d", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
username, password, ok := req.BasicAuth()
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Cannot retrieve basic auth info from request")
|
|
||||||
}
|
|
||||||
if username != "test" {
|
|
||||||
t.Fatalf("Expected username to be test, got %s", username)
|
|
||||||
}
|
|
||||||
if password != "password" {
|
|
||||||
t.Fatalf("Expected password to be password, got %s", password)
|
|
||||||
}
|
|
||||||
if req.Header.Get("User-Agent") != "testname/testversion name/version" {
|
|
||||||
t.Fatalf("Request should have User-Agent 'testname/testversion name/version'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestFactoryNewRequestWithDecorators(t *testing.T) {
|
|
||||||
ad := NewAuthDecorator("test", "password")
|
|
||||||
|
|
||||||
requestFactory := NewRequestFactory(ad)
|
|
||||||
|
|
||||||
if l := len(requestFactory.GetDecorators()); l != 1 {
|
|
||||||
t.Fatalf("Expected to have one decorators, got %d", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
ad2 := NewAuthDecorator("test2", "password2")
|
|
||||||
|
|
||||||
req, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test"), ad2)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
username, password, ok := req.BasicAuth()
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Cannot retrieve basic auth info from request")
|
|
||||||
}
|
|
||||||
if username != "test2" {
|
|
||||||
t.Fatalf("Expected username to be test, got %s", username)
|
|
||||||
}
|
|
||||||
if password != "password2" {
|
|
||||||
t.Fatalf("Expected password to be password, got %s", password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestFactoryAddDecorator(t *testing.T) {
|
|
||||||
requestFactory := NewRequestFactory()
|
|
||||||
|
|
||||||
if l := len(requestFactory.GetDecorators()); l != 0 {
|
|
||||||
t.Fatalf("Expected to have zero decorators, got %d", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
ad := NewAuthDecorator("test", "password")
|
|
||||||
requestFactory.AddDecorator(ad)
|
|
||||||
|
|
||||||
if l := len(requestFactory.GetDecorators()); l != 1 {
|
|
||||||
t.Fatalf("Expected to have one decorators, got %d", l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestFactoryNil(t *testing.T) {
|
|
||||||
var requestFactory RequestFactory
|
|
||||||
_, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected not to get and error, got %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009 The oauth2 Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,148 @@
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RequestModifier interface {
|
||||||
|
ModifyRequest(*http.Request) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerModifier http.Header
|
||||||
|
|
||||||
|
// NewHeaderRequestModifier returns a RequestModifier that merges the HTTP headers
|
||||||
|
// passed as an argument, with the HTTP headers of a request.
|
||||||
|
//
|
||||||
|
// If the same key is present in both, the modifying header values for that key,
|
||||||
|
// are appended to the values for that same key in the request header.
|
||||||
|
func NewHeaderRequestModifier(header http.Header) RequestModifier {
|
||||||
|
return headerModifier(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h headerModifier) ModifyRequest(req *http.Request) error {
|
||||||
|
for k, s := range http.Header(h) {
|
||||||
|
req.Header[k] = append(req.Header[k], s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTransport returns an http.RoundTripper that modifies requests according to
|
||||||
|
// the RequestModifiers passed in the arguments, before sending the requests to
|
||||||
|
// the base http.RoundTripper (which, if nil, defaults to http.DefaultTransport).
|
||||||
|
func NewTransport(base http.RoundTripper, modifiers ...RequestModifier) http.RoundTripper {
|
||||||
|
return &transport{
|
||||||
|
Modifiers: modifiers,
|
||||||
|
Base: base,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// transport is an http.RoundTripper that makes HTTP requests after
|
||||||
|
// copying and modifying the request
|
||||||
|
type transport struct {
|
||||||
|
Modifiers []RequestModifier
|
||||||
|
Base http.RoundTripper
|
||||||
|
|
||||||
|
mu sync.Mutex // guards modReq
|
||||||
|
modReq map[*http.Request]*http.Request // original -> modified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
req2 := CloneRequest(req)
|
||||||
|
for _, modifier := range t.Modifiers {
|
||||||
|
if err := modifier.ModifyRequest(req2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.setModReq(req, req2)
|
||||||
|
res, err := t.base().RoundTrip(req2)
|
||||||
|
if err != nil {
|
||||||
|
t.setModReq(req, nil)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res.Body = &OnEOFReader{
|
||||||
|
Rc: res.Body,
|
||||||
|
Fn: func() { t.setModReq(req, nil) },
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelRequest cancels an in-flight request by closing its connection.
|
||||||
|
func (t *transport) CancelRequest(req *http.Request) {
|
||||||
|
type canceler interface {
|
||||||
|
CancelRequest(*http.Request)
|
||||||
|
}
|
||||||
|
if cr, ok := t.base().(canceler); ok {
|
||||||
|
t.mu.Lock()
|
||||||
|
modReq := t.modReq[req]
|
||||||
|
delete(t.modReq, req)
|
||||||
|
t.mu.Unlock()
|
||||||
|
cr.CancelRequest(modReq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transport) base() http.RoundTripper {
|
||||||
|
if t.Base != nil {
|
||||||
|
return t.Base
|
||||||
|
}
|
||||||
|
return http.DefaultTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transport) setModReq(orig, mod *http.Request) {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
if t.modReq == nil {
|
||||||
|
t.modReq = make(map[*http.Request]*http.Request)
|
||||||
|
}
|
||||||
|
if mod == nil {
|
||||||
|
delete(t.modReq, orig)
|
||||||
|
} else {
|
||||||
|
t.modReq[orig] = mod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneRequest returns a clone of the provided *http.Request.
|
||||||
|
// The clone is a shallow copy of the struct and its Header map.
|
||||||
|
func CloneRequest(r *http.Request) *http.Request {
|
||||||
|
// shallow copy of the struct
|
||||||
|
r2 := new(http.Request)
|
||||||
|
*r2 = *r
|
||||||
|
// deep copy of the Header
|
||||||
|
r2.Header = make(http.Header, len(r.Header))
|
||||||
|
for k, s := range r.Header {
|
||||||
|
r2.Header[k] = append([]string(nil), s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r2
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnEOFReader ensures a callback function is called
|
||||||
|
// on Close() and when the underlying Reader returns an io.EOF error
|
||||||
|
type OnEOFReader struct {
|
||||||
|
Rc io.ReadCloser
|
||||||
|
Fn func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OnEOFReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = r.Rc.Read(p)
|
||||||
|
if err == io.EOF {
|
||||||
|
r.runFunc()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OnEOFReader) Close() error {
|
||||||
|
err := r.Rc.Close()
|
||||||
|
r.runFunc()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OnEOFReader) runFunc() {
|
||||||
|
if fn := r.Fn; fn != nil {
|
||||||
|
fn()
|
||||||
|
r.Fn = nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
This package provides helper functions to pack version information into a single User-Agent header.
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Package useragent provides helper functions to pack
|
||||||
|
// version information into a single User-Agent header.
|
||||||
|
package useragent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNilRequest = errors.New("request cannot be nil")
|
||||||
|
)
|
||||||
|
|
||||||
|
// VersionInfo is used to model UserAgent versions.
|
||||||
|
type VersionInfo struct {
|
||||||
|
Name string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vi *VersionInfo) isValid() bool {
|
||||||
|
const stopChars = " \t\r\n/"
|
||||||
|
name := vi.Name
|
||||||
|
vers := vi.Version
|
||||||
|
if len(name) == 0 || strings.ContainsAny(name, stopChars) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(vers) == 0 || strings.ContainsAny(vers, stopChars) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert versions to a string and append the string to the string base.
|
||||||
|
//
|
||||||
|
// Each VersionInfo will be converted to a string in the format of
|
||||||
|
// "product/version", where the "product" is get from the name field, while
|
||||||
|
// version is get from the version field. Several pieces of verson information
|
||||||
|
// will be concatinated and separated by space.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// AppendVersions("base", VersionInfo{"foo", "1.0"}, VersionInfo{"bar", "2.0"})
|
||||||
|
// results in "base foo/1.0 bar/2.0".
|
||||||
|
func AppendVersions(base string, versions ...VersionInfo) string {
|
||||||
|
if len(versions) == 0 {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
verstrs := make([]string, 0, 1+len(versions))
|
||||||
|
if len(base) > 0 {
|
||||||
|
verstrs = append(verstrs, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range versions {
|
||||||
|
if !v.isValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
verstrs = append(verstrs, v.Name+"/"+v.Version)
|
||||||
|
}
|
||||||
|
return strings.Join(verstrs, " ")
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package useragent
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestVersionInfo(t *testing.T) {
|
||||||
|
vi := VersionInfo{"foo", "bar"}
|
||||||
|
if !vi.isValid() {
|
||||||
|
t.Fatalf("VersionInfo should be valid")
|
||||||
|
}
|
||||||
|
vi = VersionInfo{"", "bar"}
|
||||||
|
if vi.isValid() {
|
||||||
|
t.Fatalf("Expected VersionInfo to be invalid")
|
||||||
|
}
|
||||||
|
vi = VersionInfo{"foo", ""}
|
||||||
|
if vi.isValid() {
|
||||||
|
t.Fatalf("Expected VersionInfo to be invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendVersions(t *testing.T) {
|
||||||
|
vis := []VersionInfo{
|
||||||
|
{"foo", "1.0"},
|
||||||
|
{"bar", "0.1"},
|
||||||
|
{"pi", "3.1.4"},
|
||||||
|
}
|
||||||
|
v := AppendVersions("base", vis...)
|
||||||
|
expect := "base foo/1.0 bar/0.1 pi/3.1.4"
|
||||||
|
if v != expect {
|
||||||
|
t.Fatalf("expected %q, got %q", expect, v)
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,6 @@ import (
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/cliconfig"
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/pkg/requestdecorator"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RequestAuthorization struct {
|
type RequestAuthorization struct {
|
||||||
|
@ -45,9 +44,6 @@ func (auth *RequestAuthorization) getToken() (string, error) {
|
||||||
return auth.tokenCache, nil
|
return auth.tokenCache, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
client := auth.registryEndpoint.HTTPClient()
|
|
||||||
factory := HTTPRequestFactory(nil)
|
|
||||||
|
|
||||||
for _, challenge := range auth.registryEndpoint.AuthChallenges {
|
for _, challenge := range auth.registryEndpoint.AuthChallenges {
|
||||||
switch strings.ToLower(challenge.Scheme) {
|
switch strings.ToLower(challenge.Scheme) {
|
||||||
case "basic":
|
case "basic":
|
||||||
|
@ -59,7 +55,7 @@ func (auth *RequestAuthorization) getToken() (string, error) {
|
||||||
params[k] = v
|
params[k] = v
|
||||||
}
|
}
|
||||||
params["scope"] = fmt.Sprintf("%s:%s:%s", auth.resource, auth.scope, strings.Join(auth.actions, ","))
|
params["scope"] = fmt.Sprintf("%s:%s:%s", auth.resource, auth.scope, strings.Join(auth.actions, ","))
|
||||||
token, err := getToken(auth.authConfig.Username, auth.authConfig.Password, params, auth.registryEndpoint, client, factory)
|
token, err := getToken(auth.authConfig.Username, auth.authConfig.Password, params, auth.registryEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -92,21 +88,20 @@ func (auth *RequestAuthorization) Authorize(req *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login tries to register/login to the registry server.
|
// Login tries to register/login to the registry server.
|
||||||
func Login(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) {
|
func Login(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
|
||||||
// Separates the v2 registry login logic from the v1 logic.
|
// Separates the v2 registry login logic from the v1 logic.
|
||||||
if registryEndpoint.Version == APIVersion2 {
|
if registryEndpoint.Version == APIVersion2 {
|
||||||
return loginV2(authConfig, registryEndpoint, factory)
|
return loginV2(authConfig, registryEndpoint)
|
||||||
}
|
}
|
||||||
return loginV1(authConfig, registryEndpoint, factory)
|
return loginV1(authConfig, registryEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loginV1 tries to register/login to the v1 registry server.
|
// loginV1 tries to register/login to the v1 registry server.
|
||||||
func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) {
|
func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
|
||||||
var (
|
var (
|
||||||
status string
|
status string
|
||||||
reqBody []byte
|
reqBody []byte
|
||||||
err error
|
err error
|
||||||
client = registryEndpoint.HTTPClient()
|
|
||||||
reqStatusCode = 0
|
reqStatusCode = 0
|
||||||
serverAddress = authConfig.ServerAddress
|
serverAddress = authConfig.ServerAddress
|
||||||
)
|
)
|
||||||
|
@ -130,7 +125,7 @@ func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, facto
|
||||||
|
|
||||||
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
|
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
|
||||||
b := strings.NewReader(string(jsonBody))
|
b := strings.NewReader(string(jsonBody))
|
||||||
req1, err := client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
|
req1, err := registryEndpoint.client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Server Error: %s", err)
|
return "", fmt.Errorf("Server Error: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -151,9 +146,9 @@ func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, facto
|
||||||
}
|
}
|
||||||
} else if reqStatusCode == 400 {
|
} else if reqStatusCode == 400 {
|
||||||
if string(reqBody) == "\"Username or email already exists\"" {
|
if string(reqBody) == "\"Username or email already exists\"" {
|
||||||
req, err := factory.NewRequest("GET", serverAddress+"users/", nil)
|
req, err := http.NewRequest("GET", serverAddress+"users/", nil)
|
||||||
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||||
resp, err := client.Do(req)
|
resp, err := registryEndpoint.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -180,9 +175,9 @@ func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, facto
|
||||||
} else if reqStatusCode == 401 {
|
} else if reqStatusCode == 401 {
|
||||||
// This case would happen with private registries where /v1/users is
|
// This case would happen with private registries where /v1/users is
|
||||||
// protected, so people can use `docker login` as an auth check.
|
// protected, so people can use `docker login` as an auth check.
|
||||||
req, err := factory.NewRequest("GET", serverAddress+"users/", nil)
|
req, err := http.NewRequest("GET", serverAddress+"users/", nil)
|
||||||
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||||
resp, err := client.Do(req)
|
resp, err := registryEndpoint.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -214,12 +209,11 @@ func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, facto
|
||||||
// now, users should create their account through other means like directly from a web page
|
// now, users should create their account through other means like directly from a web page
|
||||||
// served by the v2 registry service provider. Whether this will be supported in the future
|
// served by the v2 registry service provider. Whether this will be supported in the future
|
||||||
// is to be determined.
|
// is to be determined.
|
||||||
func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) {
|
func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
|
||||||
logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
|
logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
allErrors []error
|
allErrors []error
|
||||||
client = registryEndpoint.HTTPClient()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, challenge := range registryEndpoint.AuthChallenges {
|
for _, challenge := range registryEndpoint.AuthChallenges {
|
||||||
|
@ -227,9 +221,9 @@ func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, facto
|
||||||
|
|
||||||
switch strings.ToLower(challenge.Scheme) {
|
switch strings.ToLower(challenge.Scheme) {
|
||||||
case "basic":
|
case "basic":
|
||||||
err = tryV2BasicAuthLogin(authConfig, challenge.Parameters, registryEndpoint, client, factory)
|
err = tryV2BasicAuthLogin(authConfig, challenge.Parameters, registryEndpoint)
|
||||||
case "bearer":
|
case "bearer":
|
||||||
err = tryV2TokenAuthLogin(authConfig, challenge.Parameters, registryEndpoint, client, factory)
|
err = tryV2TokenAuthLogin(authConfig, challenge.Parameters, registryEndpoint)
|
||||||
default:
|
default:
|
||||||
// Unsupported challenge types are explicitly skipped.
|
// Unsupported challenge types are explicitly skipped.
|
||||||
err = fmt.Errorf("unsupported auth scheme: %q", challenge.Scheme)
|
err = fmt.Errorf("unsupported auth scheme: %q", challenge.Scheme)
|
||||||
|
@ -247,15 +241,15 @@ func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, facto
|
||||||
return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors)
|
return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error {
|
func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
|
||||||
req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil)
|
req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := registryEndpoint.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -268,20 +262,20 @@ func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]str
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error {
|
func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
|
||||||
token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint, client, factory)
|
token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil)
|
req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := registryEndpoint.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -12,7 +11,7 @@ import (
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"github.com/docker/distribution/registry/api/v2"
|
||||||
"github.com/docker/docker/pkg/requestdecorator"
|
"github.com/docker/docker/pkg/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
// for mocking in unit tests
|
// for mocking in unit tests
|
||||||
|
@ -43,9 +42,9 @@ func scanForAPIVersion(address string) (string, APIVersion) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEndpoint parses the given address to return a registry endpoint.
|
// NewEndpoint parses the given address to return a registry endpoint.
|
||||||
func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
|
func NewEndpoint(index *IndexInfo, metaHeaders http.Header) (*Endpoint, error) {
|
||||||
// *TODO: Allow per-registry configuration of endpoints.
|
// *TODO: Allow per-registry configuration of endpoints.
|
||||||
endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure)
|
endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure, metaHeaders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -83,7 +82,7 @@ func validateEndpoint(endpoint *Endpoint) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEndpoint(address string, secure bool) (*Endpoint, error) {
|
func newEndpoint(address string, secure bool, metaHeaders http.Header) (*Endpoint, error) {
|
||||||
var (
|
var (
|
||||||
endpoint = new(Endpoint)
|
endpoint = new(Endpoint)
|
||||||
trimmedAddress string
|
trimmedAddress string
|
||||||
|
@ -100,15 +99,18 @@ func newEndpoint(address string, secure bool) (*Endpoint, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
endpoint.IsSecure = secure
|
endpoint.IsSecure = secure
|
||||||
|
tr := NewTransport(ConnectTimeout, endpoint.IsSecure)
|
||||||
|
endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(metaHeaders)...))
|
||||||
return endpoint, nil
|
return endpoint, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
|
func (repoInfo *RepositoryInfo) GetEndpoint(metaHeaders http.Header) (*Endpoint, error) {
|
||||||
return NewEndpoint(repoInfo.Index)
|
return NewEndpoint(repoInfo.Index, metaHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endpoint stores basic information about a registry endpoint.
|
// Endpoint stores basic information about a registry endpoint.
|
||||||
type Endpoint struct {
|
type Endpoint struct {
|
||||||
|
client *http.Client
|
||||||
URL *url.URL
|
URL *url.URL
|
||||||
Version APIVersion
|
Version APIVersion
|
||||||
IsSecure bool
|
IsSecure bool
|
||||||
|
@ -135,25 +137,24 @@ func (e *Endpoint) Path(path string) string {
|
||||||
|
|
||||||
func (e *Endpoint) Ping() (RegistryInfo, error) {
|
func (e *Endpoint) Ping() (RegistryInfo, error) {
|
||||||
// The ping logic to use is determined by the registry endpoint version.
|
// The ping logic to use is determined by the registry endpoint version.
|
||||||
factory := HTTPRequestFactory(nil)
|
|
||||||
switch e.Version {
|
switch e.Version {
|
||||||
case APIVersion1:
|
case APIVersion1:
|
||||||
return e.pingV1(factory)
|
return e.pingV1()
|
||||||
case APIVersion2:
|
case APIVersion2:
|
||||||
return e.pingV2(factory)
|
return e.pingV2()
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIVersionUnknown
|
// APIVersionUnknown
|
||||||
// We should try v2 first...
|
// We should try v2 first...
|
||||||
e.Version = APIVersion2
|
e.Version = APIVersion2
|
||||||
regInfo, errV2 := e.pingV2(factory)
|
regInfo, errV2 := e.pingV2()
|
||||||
if errV2 == nil {
|
if errV2 == nil {
|
||||||
return regInfo, nil
|
return regInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... then fallback to v1.
|
// ... then fallback to v1.
|
||||||
e.Version = APIVersion1
|
e.Version = APIVersion1
|
||||||
regInfo, errV1 := e.pingV1(factory)
|
regInfo, errV1 := e.pingV1()
|
||||||
if errV1 == nil {
|
if errV1 == nil {
|
||||||
return regInfo, nil
|
return regInfo, nil
|
||||||
}
|
}
|
||||||
|
@ -162,7 +163,7 @@ func (e *Endpoint) Ping() (RegistryInfo, error) {
|
||||||
return RegistryInfo{}, fmt.Errorf("unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s", e, errV2, errV1)
|
return RegistryInfo{}, fmt.Errorf("unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s", e, errV2, errV1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Endpoint) pingV1(factory *requestdecorator.RequestFactory) (RegistryInfo, error) {
|
func (e *Endpoint) pingV1() (RegistryInfo, error) {
|
||||||
logrus.Debugf("attempting v1 ping for registry endpoint %s", e)
|
logrus.Debugf("attempting v1 ping for registry endpoint %s", e)
|
||||||
|
|
||||||
if e.String() == IndexServerAddress() {
|
if e.String() == IndexServerAddress() {
|
||||||
|
@ -171,12 +172,12 @@ func (e *Endpoint) pingV1(factory *requestdecorator.RequestFactory) (RegistryInf
|
||||||
return RegistryInfo{Standalone: false}, nil
|
return RegistryInfo{Standalone: false}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := factory.NewRequest("GET", e.Path("_ping"), nil)
|
req, err := http.NewRequest("GET", e.Path("_ping"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return RegistryInfo{Standalone: false}, err
|
return RegistryInfo{Standalone: false}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _, err := doRequest(req, nil, ConnectTimeout, e.IsSecure)
|
resp, err := e.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return RegistryInfo{Standalone: false}, err
|
return RegistryInfo{Standalone: false}, err
|
||||||
}
|
}
|
||||||
|
@ -216,15 +217,15 @@ func (e *Endpoint) pingV1(factory *requestdecorator.RequestFactory) (RegistryInf
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Endpoint) pingV2(factory *requestdecorator.RequestFactory) (RegistryInfo, error) {
|
func (e *Endpoint) pingV2() (RegistryInfo, error) {
|
||||||
logrus.Debugf("attempting v2 ping for registry endpoint %s", e)
|
logrus.Debugf("attempting v2 ping for registry endpoint %s", e)
|
||||||
|
|
||||||
req, err := factory.NewRequest("GET", e.Path(""), nil)
|
req, err := http.NewRequest("GET", e.Path(""), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return RegistryInfo{}, err
|
return RegistryInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _, err := doRequest(req, nil, ConnectTimeout, e.IsSecure)
|
resp, err := e.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return RegistryInfo{}, err
|
return RegistryInfo{}, err
|
||||||
}
|
}
|
||||||
|
@ -263,20 +264,3 @@ HeaderLoop:
|
||||||
|
|
||||||
return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode))
|
return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Endpoint) HTTPClient() *http.Client {
|
|
||||||
tlsConfig := tls.Config{
|
|
||||||
MinVersion: tls.VersionTLS10,
|
|
||||||
}
|
|
||||||
if !e.IsSecure {
|
|
||||||
tlsConfig.InsecureSkipVerify = true
|
|
||||||
}
|
|
||||||
return &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
DisableKeepAlives: true,
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
TLSClientConfig: &tlsConfig,
|
|
||||||
},
|
|
||||||
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ func TestEndpointParse(t *testing.T) {
|
||||||
{"0.0.0.0:5000", "https://0.0.0.0:5000/v0/"},
|
{"0.0.0.0:5000", "https://0.0.0.0:5000/v0/"},
|
||||||
}
|
}
|
||||||
for _, td := range testData {
|
for _, td := range testData {
|
||||||
e, err := newEndpoint(td.str, false)
|
e, err := newEndpoint(td.str, false, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%q: %s", td.str, err)
|
t.Errorf("%q: %s", td.str, err)
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,7 @@ func TestValidateEndpointAmbiguousAPIVersion(t *testing.T) {
|
||||||
testEndpoint := Endpoint{
|
testEndpoint := Endpoint{
|
||||||
URL: testServerURL,
|
URL: testServerURL,
|
||||||
Version: APIVersionUnknown,
|
Version: APIVersionUnknown,
|
||||||
|
client: HTTPClient(NewTransport(ConnectTimeout, false)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateEndpoint(&testEndpoint); err != nil {
|
if err = validateEndpoint(&testEndpoint); err != nil {
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
package registry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
|
||||||
"github.com/docker/docker/pkg/parsers/kernel"
|
|
||||||
"github.com/docker/docker/pkg/requestdecorator"
|
|
||||||
)
|
|
||||||
|
|
||||||
func HTTPRequestFactory(metaHeaders map[string][]string) *requestdecorator.RequestFactory {
|
|
||||||
// FIXME: this replicates the 'info' job.
|
|
||||||
httpVersion := make([]requestdecorator.UAVersionInfo, 0, 4)
|
|
||||||
httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("docker", dockerversion.VERSION))
|
|
||||||
httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("go", runtime.Version()))
|
|
||||||
httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("git-commit", dockerversion.GITCOMMIT))
|
|
||||||
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
|
|
||||||
httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("kernel", kernelVersion.String()))
|
|
||||||
}
|
|
||||||
httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("os", runtime.GOOS))
|
|
||||||
httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("arch", runtime.GOARCH))
|
|
||||||
uad := &requestdecorator.UserAgentDecorator{
|
|
||||||
Versions: httpVersion,
|
|
||||||
}
|
|
||||||
mhd := &requestdecorator.MetaHeadersDecorator{
|
|
||||||
Headers: metaHeaders,
|
|
||||||
}
|
|
||||||
factory := requestdecorator.NewRequestFactory(uad, mhd)
|
|
||||||
return factory
|
|
||||||
}
|
|
|
@ -8,13 +8,19 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/autogen/dockerversion"
|
||||||
|
"github.com/docker/docker/pkg/parsers/kernel"
|
||||||
"github.com/docker/docker/pkg/timeoutconn"
|
"github.com/docker/docker/pkg/timeoutconn"
|
||||||
|
"github.com/docker/docker/pkg/transport"
|
||||||
|
"github.com/docker/docker/pkg/useragent"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -31,66 +37,38 @@ const (
|
||||||
ConnectTimeout
|
ConnectTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
func newClient(jar http.CookieJar, roots *x509.CertPool, certs []tls.Certificate, timeout TimeoutType, secure bool) *http.Client {
|
// dockerUserAgent is the User-Agent the Docker client uses to identify itself.
|
||||||
tlsConfig := tls.Config{
|
// It is populated on init(), comprising version information of different components.
|
||||||
RootCAs: roots,
|
var dockerUserAgent string
|
||||||
// Avoid fallback to SSL protocols < TLS1.0
|
|
||||||
MinVersion: tls.VersionTLS10,
|
func init() {
|
||||||
Certificates: certs,
|
httpVersion := make([]useragent.VersionInfo, 0, 6)
|
||||||
|
httpVersion = append(httpVersion, useragent.VersionInfo{"docker", dockerversion.VERSION})
|
||||||
|
httpVersion = append(httpVersion, useragent.VersionInfo{"go", runtime.Version()})
|
||||||
|
httpVersion = append(httpVersion, useragent.VersionInfo{"git-commit", dockerversion.GITCOMMIT})
|
||||||
|
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
|
||||||
|
httpVersion = append(httpVersion, useragent.VersionInfo{"kernel", kernelVersion.String()})
|
||||||
}
|
}
|
||||||
|
httpVersion = append(httpVersion, useragent.VersionInfo{"os", runtime.GOOS})
|
||||||
|
httpVersion = append(httpVersion, useragent.VersionInfo{"arch", runtime.GOARCH})
|
||||||
|
|
||||||
if !secure {
|
dockerUserAgent = useragent.AppendVersions("", httpVersion...)
|
||||||
tlsConfig.InsecureSkipVerify = true
|
|
||||||
}
|
|
||||||
|
|
||||||
httpTransport := &http.Transport{
|
|
||||||
DisableKeepAlives: true,
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
TLSClientConfig: &tlsConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch timeout {
|
|
||||||
case ConnectTimeout:
|
|
||||||
httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
|
|
||||||
// Set the connect timeout to 30 seconds to allow for slower connection
|
|
||||||
// times...
|
|
||||||
d := net.Dialer{Timeout: 30 * time.Second, DualStack: true}
|
|
||||||
|
|
||||||
conn, err := d.Dial(proto, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Set the recv timeout to 10 seconds
|
|
||||||
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
case ReceiveTimeout:
|
|
||||||
httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
|
|
||||||
d := net.Dialer{DualStack: true}
|
|
||||||
|
|
||||||
conn, err := d.Dial(proto, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn = timeoutconn.New(conn, 1*time.Minute)
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &http.Client{
|
|
||||||
Transport: httpTransport,
|
|
||||||
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
|
|
||||||
Jar: jar,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*http.Response, *http.Client, error) {
|
type httpsRequestModifier struct{ tlsConfig *tls.Config }
|
||||||
|
|
||||||
|
// DRAGONS(tiborvass): If someone wonders why do we set tlsconfig in a roundtrip,
|
||||||
|
// it's because it's so as to match the current behavior in master: we generate the
|
||||||
|
// certpool on every-goddam-request. It's not great, but it allows people to just put
|
||||||
|
// the certs in /etc/docker/certs.d/.../ and let docker "pick it up" immediately. Would
|
||||||
|
// prefer an fsnotify implementation, but that was out of scope of my refactoring.
|
||||||
|
func (m *httpsRequestModifier) ModifyRequest(req *http.Request) error {
|
||||||
var (
|
var (
|
||||||
pool *x509.CertPool
|
roots *x509.CertPool
|
||||||
certs []tls.Certificate
|
certs []tls.Certificate
|
||||||
)
|
)
|
||||||
|
|
||||||
if secure && req.URL.Scheme == "https" {
|
if req.URL.Scheme == "https" {
|
||||||
hasFile := func(files []os.FileInfo, name string) bool {
|
hasFile := func(files []os.FileInfo, name string) bool {
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
if f.Name() == name {
|
if f.Name() == name {
|
||||||
|
@ -104,31 +82,31 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur
|
||||||
logrus.Debugf("hostDir: %s", hostDir)
|
logrus.Debugf("hostDir: %s", hostDir)
|
||||||
fs, err := ioutil.ReadDir(hostDir)
|
fs, err := ioutil.ReadDir(hostDir)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return nil, nil, err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range fs {
|
for _, f := range fs {
|
||||||
if strings.HasSuffix(f.Name(), ".crt") {
|
if strings.HasSuffix(f.Name(), ".crt") {
|
||||||
if pool == nil {
|
if roots == nil {
|
||||||
pool = x509.NewCertPool()
|
roots = x509.NewCertPool()
|
||||||
}
|
}
|
||||||
logrus.Debugf("crt: %s", hostDir+"/"+f.Name())
|
logrus.Debugf("crt: %s", hostDir+"/"+f.Name())
|
||||||
data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
|
data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return err
|
||||||
}
|
}
|
||||||
pool.AppendCertsFromPEM(data)
|
roots.AppendCertsFromPEM(data)
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(f.Name(), ".cert") {
|
if strings.HasSuffix(f.Name(), ".cert") {
|
||||||
certName := f.Name()
|
certName := f.Name()
|
||||||
keyName := certName[:len(certName)-5] + ".key"
|
keyName := certName[:len(certName)-5] + ".key"
|
||||||
logrus.Debugf("cert: %s", hostDir+"/"+f.Name())
|
logrus.Debugf("cert: %s", hostDir+"/"+f.Name())
|
||||||
if !hasFile(fs, keyName) {
|
if !hasFile(fs, keyName) {
|
||||||
return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
|
return fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
|
||||||
}
|
}
|
||||||
cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
|
cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return err
|
||||||
}
|
}
|
||||||
certs = append(certs, cert)
|
certs = append(certs, cert)
|
||||||
}
|
}
|
||||||
|
@ -137,24 +115,108 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur
|
||||||
certName := keyName[:len(keyName)-4] + ".cert"
|
certName := keyName[:len(keyName)-4] + ".cert"
|
||||||
logrus.Debugf("key: %s", hostDir+"/"+f.Name())
|
logrus.Debugf("key: %s", hostDir+"/"+f.Name())
|
||||||
if !hasFile(fs, certName) {
|
if !hasFile(fs, certName) {
|
||||||
return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
|
return fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m.tlsConfig.RootCAs = roots
|
||||||
|
m.tlsConfig.Certificates = certs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransport(timeout TimeoutType, secure bool) http.RoundTripper {
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
// Avoid fallback to SSL protocols < TLS1.0
|
||||||
|
MinVersion: tls.VersionTLS10,
|
||||||
|
InsecureSkipVerify: !secure,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(certs) == 0 {
|
tr := &http.Transport{
|
||||||
client := newClient(jar, pool, nil, timeout, secure)
|
DisableKeepAlives: true,
|
||||||
res, err := client.Do(req)
|
Proxy: http.ProxyFromEnvironment,
|
||||||
if err != nil {
|
TLSClientConfig: tlsConfig,
|
||||||
return nil, nil, err
|
}
|
||||||
|
|
||||||
|
switch timeout {
|
||||||
|
case ConnectTimeout:
|
||||||
|
tr.Dial = func(proto string, addr string) (net.Conn, error) {
|
||||||
|
// Set the connect timeout to 30 seconds to allow for slower connection
|
||||||
|
// times...
|
||||||
|
d := net.Dialer{Timeout: 30 * time.Second, DualStack: true}
|
||||||
|
|
||||||
|
conn, err := d.Dial(proto, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Set the recv timeout to 10 seconds
|
||||||
|
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
case ReceiveTimeout:
|
||||||
|
tr.Dial = func(proto string, addr string) (net.Conn, error) {
|
||||||
|
d := net.Dialer{DualStack: true}
|
||||||
|
|
||||||
|
conn, err := d.Dial(proto, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn = timeoutconn.New(conn, 1*time.Minute)
|
||||||
|
return conn, nil
|
||||||
}
|
}
|
||||||
return res, client, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client := newClient(jar, pool, certs, timeout, secure)
|
if secure {
|
||||||
res, err := client.Do(req)
|
// note: httpsTransport also handles http transport
|
||||||
return res, client, err
|
// but for HTTPS, it sets up the certs
|
||||||
|
return transport.NewTransport(tr, &httpsRequestModifier{tlsConfig})
|
||||||
|
}
|
||||||
|
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
|
||||||
|
// DockerHeaders returns request modifiers that ensure requests have
|
||||||
|
// the User-Agent header set to dockerUserAgent and that metaHeaders
|
||||||
|
// are added.
|
||||||
|
func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier {
|
||||||
|
modifiers := []transport.RequestModifier{
|
||||||
|
transport.NewHeaderRequestModifier(http.Header{"User-Agent": []string{dockerUserAgent}}),
|
||||||
|
}
|
||||||
|
if metaHeaders != nil {
|
||||||
|
modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
|
||||||
|
}
|
||||||
|
return modifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
type debugTransport struct{ http.RoundTripper }
|
||||||
|
|
||||||
|
func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
dump, err := httputil.DumpRequestOut(req, false)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("could not dump request")
|
||||||
|
}
|
||||||
|
fmt.Println(string(dump))
|
||||||
|
resp, err := tr.RoundTripper.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dump, err = httputil.DumpResponse(resp, false)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("could not dump response")
|
||||||
|
}
|
||||||
|
fmt.Println(string(dump))
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func HTTPClient(transport http.RoundTripper) *http.Client {
|
||||||
|
if transport == nil {
|
||||||
|
transport = NewTransport(ConnectTimeout, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func trustedLocation(req *http.Request) bool {
|
func trustedLocation(req *http.Request) bool {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/cliconfig"
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/pkg/requestdecorator"
|
"github.com/docker/docker/pkg/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -22,45 +22,34 @@ const (
|
||||||
|
|
||||||
func spawnTestRegistrySession(t *testing.T) *Session {
|
func spawnTestRegistrySession(t *testing.T) *Session {
|
||||||
authConfig := &cliconfig.AuthConfig{}
|
authConfig := &cliconfig.AuthConfig{}
|
||||||
endpoint, err := NewEndpoint(makeIndex("/v1/"))
|
endpoint, err := NewEndpoint(makeIndex("/v1/"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
r, err := NewSession(authConfig, requestdecorator.NewRequestFactory(), endpoint, true)
|
var tr http.RoundTripper = debugTransport{NewTransport(ReceiveTimeout, endpoint.IsSecure)}
|
||||||
|
tr = transport.NewTransport(AuthTransport(tr, authConfig, false), DockerHeaders(nil)...)
|
||||||
|
client := HTTPClient(tr)
|
||||||
|
r, err := NewSession(client, authConfig, endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
// In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true`
|
||||||
|
// header while authenticating, in order to retrieve a token that can be later used to
|
||||||
|
// perform authenticated actions.
|
||||||
|
//
|
||||||
|
// The mock v1 registry does not support that, (TODO(tiborvass): support it), instead,
|
||||||
|
// it will consider authenticated any request with the header `X-Docker-Token: fake-token`.
|
||||||
|
//
|
||||||
|
// Because we know that the client's transport is an `*authTransport` we simply cast it,
|
||||||
|
// in order to set the internal cached token to the fake token, and thus send that fake token
|
||||||
|
// upon every subsequent requests.
|
||||||
|
r.client.Transport.(*authTransport).token = token
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPublicSession(t *testing.T) {
|
|
||||||
authConfig := &cliconfig.AuthConfig{}
|
|
||||||
|
|
||||||
getSessionDecorators := func(index *IndexInfo) int {
|
|
||||||
endpoint, err := NewEndpoint(index)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
r, err := NewSession(authConfig, requestdecorator.NewRequestFactory(), endpoint, true)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return len(r.reqFactory.GetDecorators())
|
|
||||||
}
|
|
||||||
|
|
||||||
decorators := getSessionDecorators(makeIndex("/v1/"))
|
|
||||||
assertEqual(t, decorators, 0, "Expected no decorator on http session")
|
|
||||||
|
|
||||||
decorators = getSessionDecorators(makeHttpsIndex("/v1/"))
|
|
||||||
assertNotEqual(t, decorators, 0, "Expected decorator on https session")
|
|
||||||
|
|
||||||
decorators = getSessionDecorators(makePublicIndex())
|
|
||||||
assertEqual(t, decorators, 0, "Expected no decorator on public session")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPingRegistryEndpoint(t *testing.T) {
|
func TestPingRegistryEndpoint(t *testing.T) {
|
||||||
testPing := func(index *IndexInfo, expectedStandalone bool, assertMessage string) {
|
testPing := func(index *IndexInfo, expectedStandalone bool, assertMessage string) {
|
||||||
ep, err := NewEndpoint(index)
|
ep, err := NewEndpoint(index, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -80,7 +69,7 @@ func TestPingRegistryEndpoint(t *testing.T) {
|
||||||
func TestEndpoint(t *testing.T) {
|
func TestEndpoint(t *testing.T) {
|
||||||
// Simple wrapper to fail test if err != nil
|
// Simple wrapper to fail test if err != nil
|
||||||
expandEndpoint := func(index *IndexInfo) *Endpoint {
|
expandEndpoint := func(index *IndexInfo) *Endpoint {
|
||||||
endpoint, err := NewEndpoint(index)
|
endpoint, err := NewEndpoint(index, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -89,7 +78,7 @@ func TestEndpoint(t *testing.T) {
|
||||||
|
|
||||||
assertInsecureIndex := func(index *IndexInfo) {
|
assertInsecureIndex := func(index *IndexInfo) {
|
||||||
index.Secure = true
|
index.Secure = true
|
||||||
_, err := NewEndpoint(index)
|
_, err := NewEndpoint(index, nil)
|
||||||
assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
|
assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
|
||||||
assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry error for insecure index")
|
assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry error for insecure index")
|
||||||
index.Secure = false
|
index.Secure = false
|
||||||
|
@ -97,7 +86,7 @@ func TestEndpoint(t *testing.T) {
|
||||||
|
|
||||||
assertSecureIndex := func(index *IndexInfo) {
|
assertSecureIndex := func(index *IndexInfo) {
|
||||||
index.Secure = true
|
index.Secure = true
|
||||||
_, err := NewEndpoint(index)
|
_, err := NewEndpoint(index, nil)
|
||||||
assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
|
assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
|
||||||
assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
|
assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
|
||||||
index.Secure = false
|
index.Secure = false
|
||||||
|
@ -163,14 +152,14 @@ func TestEndpoint(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, address := range badEndpoints {
|
for _, address := range badEndpoints {
|
||||||
index.Name = address
|
index.Name = address
|
||||||
_, err := NewEndpoint(index)
|
_, err := NewEndpoint(index, nil)
|
||||||
checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
|
checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetRemoteHistory(t *testing.T) {
|
func TestGetRemoteHistory(t *testing.T) {
|
||||||
r := spawnTestRegistrySession(t)
|
r := spawnTestRegistrySession(t)
|
||||||
hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/"), token)
|
hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -182,16 +171,16 @@ func TestGetRemoteHistory(t *testing.T) {
|
||||||
|
|
||||||
func TestLookupRemoteImage(t *testing.T) {
|
func TestLookupRemoteImage(t *testing.T) {
|
||||||
r := spawnTestRegistrySession(t)
|
r := spawnTestRegistrySession(t)
|
||||||
err := r.LookupRemoteImage(imageID, makeURL("/v1/"), token)
|
err := r.LookupRemoteImage(imageID, makeURL("/v1/"))
|
||||||
assertEqual(t, err, nil, "Expected error of remote lookup to nil")
|
assertEqual(t, err, nil, "Expected error of remote lookup to nil")
|
||||||
if err := r.LookupRemoteImage("abcdef", makeURL("/v1/"), token); err == nil {
|
if err := r.LookupRemoteImage("abcdef", makeURL("/v1/")); err == nil {
|
||||||
t.Fatal("Expected error of remote lookup to not nil")
|
t.Fatal("Expected error of remote lookup to not nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetRemoteImageJSON(t *testing.T) {
|
func TestGetRemoteImageJSON(t *testing.T) {
|
||||||
r := spawnTestRegistrySession(t)
|
r := spawnTestRegistrySession(t)
|
||||||
json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/"), token)
|
json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -200,7 +189,7 @@ func TestGetRemoteImageJSON(t *testing.T) {
|
||||||
t.Fatal("Expected non-empty json")
|
t.Fatal("Expected non-empty json")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/"), token)
|
_, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected image not found error")
|
t.Fatal("Expected image not found error")
|
||||||
}
|
}
|
||||||
|
@ -208,7 +197,7 @@ func TestGetRemoteImageJSON(t *testing.T) {
|
||||||
|
|
||||||
func TestGetRemoteImageLayer(t *testing.T) {
|
func TestGetRemoteImageLayer(t *testing.T) {
|
||||||
r := spawnTestRegistrySession(t)
|
r := spawnTestRegistrySession(t)
|
||||||
data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), token, 0)
|
data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -216,7 +205,7 @@ func TestGetRemoteImageLayer(t *testing.T) {
|
||||||
t.Fatal("Expected non-nil data result")
|
t.Fatal("Expected non-nil data result")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), token, 0)
|
_, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), 0)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected image not found error")
|
t.Fatal("Expected image not found error")
|
||||||
}
|
}
|
||||||
|
@ -224,14 +213,14 @@ func TestGetRemoteImageLayer(t *testing.T) {
|
||||||
|
|
||||||
func TestGetRemoteTags(t *testing.T) {
|
func TestGetRemoteTags(t *testing.T) {
|
||||||
r := spawnTestRegistrySession(t)
|
r := spawnTestRegistrySession(t)
|
||||||
tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO, token)
|
tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assertEqual(t, len(tags), 1, "Expected one tag")
|
assertEqual(t, len(tags), 1, "Expected one tag")
|
||||||
assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID)
|
assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID)
|
||||||
|
|
||||||
_, err = r.GetRemoteTags([]string{makeURL("/v1/")}, "foo42/baz", token)
|
_, err = r.GetRemoteTags([]string{makeURL("/v1/")}, "foo42/baz")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected error when fetching tags for bogus repo")
|
t.Fatal("Expected error when fetching tags for bogus repo")
|
||||||
}
|
}
|
||||||
|
@ -265,7 +254,7 @@ func TestPushImageJSONRegistry(t *testing.T) {
|
||||||
Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
|
Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
|
||||||
}
|
}
|
||||||
|
|
||||||
err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/"), token)
|
err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -274,7 +263,7 @@ func TestPushImageJSONRegistry(t *testing.T) {
|
||||||
func TestPushImageLayerRegistry(t *testing.T) {
|
func TestPushImageLayerRegistry(t *testing.T) {
|
||||||
r := spawnTestRegistrySession(t)
|
r := spawnTestRegistrySession(t)
|
||||||
layer := strings.NewReader("")
|
layer := strings.NewReader("")
|
||||||
_, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), token, []byte{})
|
_, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), []byte{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -694,7 +683,7 @@ func TestNewIndexInfo(t *testing.T) {
|
||||||
|
|
||||||
func TestPushRegistryTag(t *testing.T) {
|
func TestPushRegistryTag(t *testing.T) {
|
||||||
r := spawnTestRegistrySession(t)
|
r := spawnTestRegistrySession(t)
|
||||||
err := r.PushRegistryTag("foo42/bar", imageID, "stable", makeURL("/v1/"), token)
|
err := r.PushRegistryTag("foo42/bar", imageID, "stable", makeURL("/v1/"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package registry
|
package registry
|
||||||
|
|
||||||
import "github.com/docker/docker/cliconfig"
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
Config *ServiceConfig
|
Config *ServiceConfig
|
||||||
|
@ -27,12 +31,12 @@ func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
endpoint, err := NewEndpoint(index)
|
endpoint, err := NewEndpoint(index, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
authConfig.ServerAddress = endpoint.String()
|
authConfig.ServerAddress = endpoint.String()
|
||||||
return Login(authConfig, endpoint, HTTPRequestFactory(nil))
|
return Login(authConfig, endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search queries the public registry for images matching the specified
|
// Search queries the public registry for images matching the specified
|
||||||
|
@ -42,12 +46,13 @@ func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// *TODO: Search multiple indexes.
|
// *TODO: Search multiple indexes.
|
||||||
endpoint, err := repoInfo.GetEndpoint()
|
endpoint, err := repoInfo.GetEndpoint(http.Header(headers))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
r, err := NewSession(authConfig, HTTPRequestFactory(headers), endpoint, true)
|
r, err := NewSession(endpoint.client, authConfig, endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package registry
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
// this is required for some certificates
|
// this is required for some certificates
|
||||||
_ "crypto/sha512"
|
_ "crypto/sha512"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
@ -20,64 +22,143 @@ import (
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/cliconfig"
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/pkg/httputils"
|
"github.com/docker/docker/pkg/httputils"
|
||||||
"github.com/docker/docker/pkg/requestdecorator"
|
|
||||||
"github.com/docker/docker/pkg/tarsum"
|
"github.com/docker/docker/pkg/tarsum"
|
||||||
|
"github.com/docker/docker/pkg/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
authConfig *cliconfig.AuthConfig
|
|
||||||
reqFactory *requestdecorator.RequestFactory
|
|
||||||
indexEndpoint *Endpoint
|
indexEndpoint *Endpoint
|
||||||
jar *cookiejar.Jar
|
client *http.Client
|
||||||
timeout TimeoutType
|
// TODO(tiborvass): remove authConfig
|
||||||
|
authConfig *cliconfig.AuthConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSession(authConfig *cliconfig.AuthConfig, factory *requestdecorator.RequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) {
|
type authTransport struct {
|
||||||
|
http.RoundTripper
|
||||||
|
*cliconfig.AuthConfig
|
||||||
|
|
||||||
|
alwaysSetBasicAuth bool
|
||||||
|
token []string
|
||||||
|
|
||||||
|
mu sync.Mutex // guards modReq
|
||||||
|
modReq map[*http.Request]*http.Request // original -> modified
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthTransport handles the auth layer when communicating with a v1 registry (private or official)
|
||||||
|
//
|
||||||
|
// For private v1 registries, set alwaysSetBasicAuth to true.
|
||||||
|
//
|
||||||
|
// For the official v1 registry, if there isn't already an Authorization header in the request,
|
||||||
|
// but there is an X-Docker-Token header set to true, then Basic Auth will be used to set the Authorization header.
|
||||||
|
// After sending the request with the provided base http.RoundTripper, if an X-Docker-Token header, representing
|
||||||
|
// a token, is present in the response, then it gets cached and sent in the Authorization header of all subsequent
|
||||||
|
// requests.
|
||||||
|
//
|
||||||
|
// If the server sends a token without the client having requested it, it is ignored.
|
||||||
|
//
|
||||||
|
// This RoundTripper also has a CancelRequest method important for correct timeout handling.
|
||||||
|
func AuthTransport(base http.RoundTripper, authConfig *cliconfig.AuthConfig, alwaysSetBasicAuth bool) http.RoundTripper {
|
||||||
|
if base == nil {
|
||||||
|
base = http.DefaultTransport
|
||||||
|
}
|
||||||
|
return &authTransport{
|
||||||
|
RoundTripper: base,
|
||||||
|
AuthConfig: authConfig,
|
||||||
|
alwaysSetBasicAuth: alwaysSetBasicAuth,
|
||||||
|
modReq: make(map[*http.Request]*http.Request),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *authTransport) RoundTrip(orig *http.Request) (*http.Response, error) {
|
||||||
|
req := transport.CloneRequest(orig)
|
||||||
|
tr.mu.Lock()
|
||||||
|
tr.modReq[orig] = req
|
||||||
|
tr.mu.Unlock()
|
||||||
|
|
||||||
|
if tr.alwaysSetBasicAuth {
|
||||||
|
req.SetBasicAuth(tr.Username, tr.Password)
|
||||||
|
return tr.RoundTripper.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
var askedForToken bool
|
||||||
|
|
||||||
|
// Don't override
|
||||||
|
if req.Header.Get("Authorization") == "" {
|
||||||
|
if req.Header.Get("X-Docker-Token") == "true" {
|
||||||
|
req.SetBasicAuth(tr.Username, tr.Password)
|
||||||
|
askedForToken = true
|
||||||
|
} else if len(tr.token) > 0 {
|
||||||
|
req.Header.Set("Authorization", "Token "+strings.Join(tr.token, ","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp, err := tr.RoundTripper.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
delete(tr.modReq, orig)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if askedForToken && len(resp.Header["X-Docker-Token"]) > 0 {
|
||||||
|
tr.token = resp.Header["X-Docker-Token"]
|
||||||
|
}
|
||||||
|
resp.Body = &transport.OnEOFReader{
|
||||||
|
Rc: resp.Body,
|
||||||
|
Fn: func() { delete(tr.modReq, orig) },
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelRequest cancels an in-flight request by closing its connection.
|
||||||
|
func (tr *authTransport) CancelRequest(req *http.Request) {
|
||||||
|
type canceler interface {
|
||||||
|
CancelRequest(*http.Request)
|
||||||
|
}
|
||||||
|
if cr, ok := tr.RoundTripper.(canceler); ok {
|
||||||
|
tr.mu.Lock()
|
||||||
|
modReq := tr.modReq[req]
|
||||||
|
delete(tr.modReq, req)
|
||||||
|
tr.mu.Unlock()
|
||||||
|
cr.CancelRequest(modReq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(tiborvass): remove authConfig param once registry client v2 is vendored
|
||||||
|
func NewSession(client *http.Client, authConfig *cliconfig.AuthConfig, endpoint *Endpoint) (r *Session, err error) {
|
||||||
r = &Session{
|
r = &Session{
|
||||||
authConfig: authConfig,
|
authConfig: authConfig,
|
||||||
|
client: client,
|
||||||
indexEndpoint: endpoint,
|
indexEndpoint: endpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
if timeout {
|
var alwaysSetBasicAuth bool
|
||||||
r.timeout = ReceiveTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
r.jar, err = cookiejar.New(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
|
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
|
||||||
// alongside our requests.
|
// alongside all our requests.
|
||||||
if r.indexEndpoint.VersionString(1) != IndexServerAddress() && r.indexEndpoint.URL.Scheme == "https" {
|
if endpoint.VersionString(1) != IndexServerAddress() && endpoint.URL.Scheme == "https" {
|
||||||
info, err := r.indexEndpoint.Ping()
|
info, err := endpoint.Ping()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if info.Standalone && authConfig != nil && factory != nil {
|
|
||||||
logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", r.indexEndpoint.String())
|
if info.Standalone && authConfig != nil {
|
||||||
dec := requestdecorator.NewAuthDecorator(authConfig.Username, authConfig.Password)
|
logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", endpoint.String())
|
||||||
factory.AddDecorator(dec)
|
alwaysSetBasicAuth = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r.reqFactory = factory
|
client.Transport = AuthTransport(client.Transport, authConfig, alwaysSetBasicAuth)
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Session) doRequest(req *http.Request) (*http.Response, *http.Client, error) {
|
jar, err := cookiejar.New(nil)
|
||||||
return doRequest(req, r.jar, r.timeout, r.indexEndpoint.IsSecure)
|
if err != nil {
|
||||||
|
return nil, errors.New("cookiejar.New is not supposed to return an error")
|
||||||
|
}
|
||||||
|
client.Jar = jar
|
||||||
|
|
||||||
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the history of a given image from the Registry.
|
// Retrieve the history of a given image from the Registry.
|
||||||
// Return a list of the parent's json (requested image included)
|
// Return a list of the parent's json (requested image included)
|
||||||
func (r *Session) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) {
|
func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) {
|
||||||
req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/ancestry", nil)
|
res, err := r.client.Get(registry + "images/" + imgID + "/ancestry")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
setTokenAuth(req, token)
|
|
||||||
res, _, err := r.doRequest(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -89,27 +170,18 @@ func (r *Session) GetRemoteHistory(imgID, registry string, token []string) ([]st
|
||||||
return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res)
|
return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res)
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonString, err := ioutil.ReadAll(res.Body)
|
var history []string
|
||||||
if err != nil {
|
if err := json.NewDecoder(res.Body).Decode(&history); err != nil {
|
||||||
return nil, fmt.Errorf("Error while reading the http response: %s", err)
|
return nil, fmt.Errorf("Error while reading the http response: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("Ancestry: %s", jsonString)
|
logrus.Debugf("Ancestry: %v", history)
|
||||||
history := new([]string)
|
return history, nil
|
||||||
if err := json.Unmarshal(jsonString, history); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return *history, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if an image exists in the Registry
|
// Check if an image exists in the Registry
|
||||||
func (r *Session) LookupRemoteImage(imgID, registry string, token []string) error {
|
func (r *Session) LookupRemoteImage(imgID, registry string) error {
|
||||||
req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
|
res, err := r.client.Get(registry + "images/" + imgID + "/json")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setTokenAuth(req, token)
|
|
||||||
res, _, err := r.doRequest(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -121,14 +193,8 @@ func (r *Session) LookupRemoteImage(imgID, registry string, token []string) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve an image from the Registry.
|
// Retrieve an image from the Registry.
|
||||||
func (r *Session) GetRemoteImageJSON(imgID, registry string, token []string) ([]byte, int, error) {
|
func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int, error) {
|
||||||
// Get the JSON
|
res, err := r.client.Get(registry + "images/" + imgID + "/json")
|
||||||
req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, fmt.Errorf("Failed to download json: %s", err)
|
|
||||||
}
|
|
||||||
setTokenAuth(req, token)
|
|
||||||
res, _, err := r.doRequest(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, fmt.Errorf("Failed to download json: %s", err)
|
return nil, -1, fmt.Errorf("Failed to download json: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -147,44 +213,44 @@ func (r *Session) GetRemoteImageJSON(imgID, registry string, token []string) ([]
|
||||||
|
|
||||||
jsonString, err := ioutil.ReadAll(res.Body)
|
jsonString, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
|
return nil, -1, fmt.Errorf("Failed to parse downloaded json: %v (%s)", err, jsonString)
|
||||||
}
|
}
|
||||||
return jsonString, imageSize, nil
|
return jsonString, imageSize, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Session) GetRemoteImageLayer(imgID, registry string, token []string, imgSize int64) (io.ReadCloser, error) {
|
func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io.ReadCloser, error) {
|
||||||
var (
|
var (
|
||||||
retries = 5
|
retries = 5
|
||||||
statusCode = 0
|
statusCode = 0
|
||||||
client *http.Client
|
|
||||||
res *http.Response
|
res *http.Response
|
||||||
|
err error
|
||||||
imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID)
|
imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID)
|
||||||
)
|
)
|
||||||
|
|
||||||
req, err := r.reqFactory.NewRequest("GET", imageURL, nil)
|
req, err := http.NewRequest("GET", imageURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
|
return nil, fmt.Errorf("Error while getting from the server: %v", err)
|
||||||
}
|
}
|
||||||
setTokenAuth(req, token)
|
// TODO: why are we doing retries at this level?
|
||||||
|
// These retries should be generic to both v1 and v2
|
||||||
for i := 1; i <= retries; i++ {
|
for i := 1; i <= retries; i++ {
|
||||||
statusCode = 0
|
statusCode = 0
|
||||||
res, client, err = r.doRequest(req)
|
res, err = r.client.Do(req)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
logrus.Debugf("Error contacting registry: %s", err)
|
break
|
||||||
if res != nil {
|
|
||||||
if res.Body != nil {
|
|
||||||
res.Body.Close()
|
|
||||||
}
|
|
||||||
statusCode = res.StatusCode
|
|
||||||
}
|
|
||||||
if i == retries {
|
|
||||||
return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
|
|
||||||
statusCode, imgID)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Duration(i) * 5 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
break
|
logrus.Debugf("Error contacting registry %s: %v", registry, err)
|
||||||
|
if res != nil {
|
||||||
|
if res.Body != nil {
|
||||||
|
res.Body.Close()
|
||||||
|
}
|
||||||
|
statusCode = res.StatusCode
|
||||||
|
}
|
||||||
|
if i == retries {
|
||||||
|
return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
|
||||||
|
statusCode, imgID)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(i) * 5 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
|
@ -195,13 +261,13 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, token []string, im
|
||||||
|
|
||||||
if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 {
|
if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 {
|
||||||
logrus.Debugf("server supports resume")
|
logrus.Debugf("server supports resume")
|
||||||
return httputils.ResumableRequestReaderWithInitialResponse(client, req, 5, imgSize, res), nil
|
return httputils.ResumableRequestReaderWithInitialResponse(r.client, req, 5, imgSize, res), nil
|
||||||
}
|
}
|
||||||
logrus.Debugf("server doesn't support resume")
|
logrus.Debugf("server doesn't support resume")
|
||||||
return res.Body, nil
|
return res.Body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Session) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) {
|
func (r *Session) GetRemoteTags(registries []string, repository string) (map[string]string, error) {
|
||||||
if strings.Count(repository, "/") == 0 {
|
if strings.Count(repository, "/") == 0 {
|
||||||
// This will be removed once the Registry supports auto-resolution on
|
// This will be removed once the Registry supports auto-resolution on
|
||||||
// the "library" namespace
|
// the "library" namespace
|
||||||
|
@ -209,13 +275,7 @@ func (r *Session) GetRemoteTags(registries []string, repository string, token []
|
||||||
}
|
}
|
||||||
for _, host := range registries {
|
for _, host := range registries {
|
||||||
endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
|
endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
|
||||||
req, err := r.reqFactory.NewRequest("GET", endpoint, nil)
|
res, err := r.client.Get(endpoint)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
setTokenAuth(req, token)
|
|
||||||
res, _, err := r.doRequest(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -263,16 +323,13 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
|
||||||
|
|
||||||
logrus.Debugf("[registry] Calling GET %s", repositoryTarget)
|
logrus.Debugf("[registry] Calling GET %s", repositoryTarget)
|
||||||
|
|
||||||
req, err := r.reqFactory.NewRequest("GET", repositoryTarget, nil)
|
req, err := http.NewRequest("GET", repositoryTarget, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if r.authConfig != nil && len(r.authConfig.Username) > 0 {
|
// this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests
|
||||||
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
|
|
||||||
}
|
|
||||||
req.Header.Set("X-Docker-Token", "true")
|
req.Header.Set("X-Docker-Token", "true")
|
||||||
|
res, err := r.client.Do(req)
|
||||||
res, _, err := r.doRequest(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -292,11 +349,6 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
|
||||||
return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res)
|
return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokens []string
|
|
||||||
if res.Header.Get("X-Docker-Token") != "" {
|
|
||||||
tokens = res.Header["X-Docker-Token"]
|
|
||||||
}
|
|
||||||
|
|
||||||
var endpoints []string
|
var endpoints []string
|
||||||
if res.Header.Get("X-Docker-Endpoints") != "" {
|
if res.Header.Get("X-Docker-Endpoints") != "" {
|
||||||
endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.VersionString(1))
|
endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.VersionString(1))
|
||||||
|
@ -322,29 +374,29 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
|
||||||
return &RepositoryData{
|
return &RepositoryData{
|
||||||
ImgList: imgsData,
|
ImgList: imgsData,
|
||||||
Endpoints: endpoints,
|
Endpoints: endpoints,
|
||||||
Tokens: tokens,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string, token []string) error {
|
func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string) error {
|
||||||
|
|
||||||
logrus.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum")
|
u := registry + "images/" + imgData.ID + "/checksum"
|
||||||
|
|
||||||
req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil)
|
logrus.Debugf("[registry] Calling PUT %s", u)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", u, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
setTokenAuth(req, token)
|
|
||||||
req.Header.Set("X-Docker-Checksum", imgData.Checksum)
|
req.Header.Set("X-Docker-Checksum", imgData.Checksum)
|
||||||
req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload)
|
req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload)
|
||||||
|
|
||||||
res, _, err := r.doRequest(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to upload metadata: %s", err)
|
return fmt.Errorf("Failed to upload metadata: %v", err)
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if len(res.Cookies()) > 0 {
|
if len(res.Cookies()) > 0 {
|
||||||
r.jar.SetCookies(req.URL, res.Cookies())
|
r.client.Jar.SetCookies(req.URL, res.Cookies())
|
||||||
}
|
}
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
errBody, err := ioutil.ReadAll(res.Body)
|
errBody, err := ioutil.ReadAll(res.Body)
|
||||||
|
@ -363,18 +415,19 @@ func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string, t
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push a local image to the registry
|
// Push a local image to the registry
|
||||||
func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
|
func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string) error {
|
||||||
|
|
||||||
logrus.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json")
|
u := registry + "images/" + imgData.ID + "/json"
|
||||||
|
|
||||||
req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw))
|
logrus.Debugf("[registry] Calling PUT %s", u)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", u, bytes.NewReader(jsonRaw))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Add("Content-type", "application/json")
|
req.Header.Add("Content-type", "application/json")
|
||||||
setTokenAuth(req, token)
|
|
||||||
|
|
||||||
res, _, err := r.doRequest(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to upload metadata: %s", err)
|
return fmt.Errorf("Failed to upload metadata: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -398,9 +451,11 @@ func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regist
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
|
func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
|
||||||
|
|
||||||
logrus.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer")
|
u := registry + "images/" + imgID + "/layer"
|
||||||
|
|
||||||
|
logrus.Debugf("[registry] Calling PUT %s", u)
|
||||||
|
|
||||||
tarsumLayer, err := tarsum.NewTarSum(layer, false, tarsum.Version0)
|
tarsumLayer, err := tarsum.NewTarSum(layer, false, tarsum.Version0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -411,17 +466,16 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry
|
||||||
h.Write([]byte{'\n'})
|
h.Write([]byte{'\n'})
|
||||||
checksumLayer := io.TeeReader(tarsumLayer, h)
|
checksumLayer := io.TeeReader(tarsumLayer, h)
|
||||||
|
|
||||||
req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", checksumLayer)
|
req, err := http.NewRequest("PUT", u, checksumLayer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
req.Header.Add("Content-Type", "application/octet-stream")
|
req.Header.Add("Content-Type", "application/octet-stream")
|
||||||
req.ContentLength = -1
|
req.ContentLength = -1
|
||||||
req.TransferEncoding = []string{"chunked"}
|
req.TransferEncoding = []string{"chunked"}
|
||||||
setTokenAuth(req, token)
|
res, err := r.client.Do(req)
|
||||||
res, _, err := r.doRequest(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("Failed to upload layer: %s", err)
|
return "", "", fmt.Errorf("Failed to upload layer: %v", err)
|
||||||
}
|
}
|
||||||
if rc, ok := layer.(io.Closer); ok {
|
if rc, ok := layer.(io.Closer); ok {
|
||||||
if err := rc.Close(); err != nil {
|
if err := rc.Close(); err != nil {
|
||||||
|
@ -444,19 +498,18 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry
|
||||||
|
|
||||||
// push a tag on the registry.
|
// push a tag on the registry.
|
||||||
// Remote has the format '<user>/<repo>
|
// Remote has the format '<user>/<repo>
|
||||||
func (r *Session) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
|
func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error {
|
||||||
// "jsonify" the string
|
// "jsonify" the string
|
||||||
revision = "\"" + revision + "\""
|
revision = "\"" + revision + "\""
|
||||||
path := fmt.Sprintf("repositories/%s/tags/%s", remote, tag)
|
path := fmt.Sprintf("repositories/%s/tags/%s", remote, tag)
|
||||||
|
|
||||||
req, err := r.reqFactory.NewRequest("PUT", registry+path, strings.NewReader(revision))
|
req, err := http.NewRequest("PUT", registry+path, strings.NewReader(revision))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Add("Content-type", "application/json")
|
req.Header.Add("Content-type", "application/json")
|
||||||
setTokenAuth(req, token)
|
|
||||||
req.ContentLength = int64(len(revision))
|
req.ContentLength = int64(len(revision))
|
||||||
res, _, err := r.doRequest(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -491,7 +544,8 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
|
||||||
logrus.Debugf("[registry] PUT %s", u)
|
logrus.Debugf("[registry] PUT %s", u)
|
||||||
logrus.Debugf("Image list pushed to index:\n%s", imgListJSON)
|
logrus.Debugf("Image list pushed to index:\n%s", imgListJSON)
|
||||||
headers := map[string][]string{
|
headers := map[string][]string{
|
||||||
"Content-type": {"application/json"},
|
"Content-type": {"application/json"},
|
||||||
|
// this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests
|
||||||
"X-Docker-Token": {"true"},
|
"X-Docker-Token": {"true"},
|
||||||
}
|
}
|
||||||
if validate {
|
if validate {
|
||||||
|
@ -526,9 +580,6 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
|
||||||
}
|
}
|
||||||
return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res)
|
return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res)
|
||||||
}
|
}
|
||||||
if res.Header.Get("X-Docker-Token") == "" {
|
|
||||||
return nil, fmt.Errorf("Index response didn't contain an access token")
|
|
||||||
}
|
|
||||||
tokens = res.Header["X-Docker-Token"]
|
tokens = res.Header["X-Docker-Token"]
|
||||||
logrus.Debugf("Auth token: %v", tokens)
|
logrus.Debugf("Auth token: %v", tokens)
|
||||||
|
|
||||||
|
@ -539,8 +590,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if validate {
|
|
||||||
if res.StatusCode != 204 {
|
if res.StatusCode != 204 {
|
||||||
errBody, err := ioutil.ReadAll(res.Body)
|
errBody, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -551,22 +601,20 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
|
||||||
}
|
}
|
||||||
|
|
||||||
return &RepositoryData{
|
return &RepositoryData{
|
||||||
Tokens: tokens,
|
|
||||||
Endpoints: endpoints,
|
Endpoints: endpoints,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Session) putImageRequest(u string, headers map[string][]string, body []byte) (*http.Response, error) {
|
func (r *Session) putImageRequest(u string, headers map[string][]string, body []byte) (*http.Response, error) {
|
||||||
req, err := r.reqFactory.NewRequest("PUT", u, bytes.NewReader(body))
|
req, err := http.NewRequest("PUT", u, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
|
|
||||||
req.ContentLength = int64(len(body))
|
req.ContentLength = int64(len(body))
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
req.Header[k] = v
|
req.Header[k] = v
|
||||||
}
|
}
|
||||||
response, _, err := r.doRequest(req)
|
response, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -580,15 +628,7 @@ func shouldRedirect(response *http.Response) bool {
|
||||||
func (r *Session) SearchRepositories(term string) (*SearchResults, error) {
|
func (r *Session) SearchRepositories(term string) (*SearchResults, error) {
|
||||||
logrus.Debugf("Index server: %s", r.indexEndpoint)
|
logrus.Debugf("Index server: %s", r.indexEndpoint)
|
||||||
u := r.indexEndpoint.VersionString(1) + "search?q=" + url.QueryEscape(term)
|
u := r.indexEndpoint.VersionString(1) + "search?q=" + url.QueryEscape(term)
|
||||||
req, err := r.reqFactory.NewRequest("GET", u, nil)
|
res, err := r.client.Get(u)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if r.authConfig != nil && len(r.authConfig.Username) > 0 {
|
|
||||||
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
|
|
||||||
}
|
|
||||||
req.Header.Set("X-Docker-Token", "true")
|
|
||||||
res, _, err := r.doRequest(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -600,6 +640,7 @@ func (r *Session) SearchRepositories(term string) (*SearchResults, error) {
|
||||||
return result, json.NewDecoder(res.Body).Decode(result)
|
return result, json.NewDecoder(res.Body).Decode(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(tiborvass): remove this once registry client v2 is vendored
|
||||||
func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig {
|
func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig {
|
||||||
password := ""
|
password := ""
|
||||||
if withPasswd {
|
if withPasswd {
|
||||||
|
@ -611,9 +652,3 @@ func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig {
|
||||||
Email: r.authConfig.Email,
|
Email: r.authConfig.Email,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setTokenAuth(req *http.Request, token []string) {
|
|
||||||
if req.Header.Get("Authorization") == "" { // Don't override
|
|
||||||
req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ func getV2Builder(e *Endpoint) *v2.URLBuilder {
|
||||||
func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error) {
|
func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error) {
|
||||||
// TODO check if should use Mirror
|
// TODO check if should use Mirror
|
||||||
if index.Official {
|
if index.Official {
|
||||||
ep, err = newEndpoint(REGISTRYSERVER, true)
|
ep, err = newEndpoint(REGISTRYSERVER, true, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error)
|
||||||
} else if r.indexEndpoint.String() == index.GetAuthConfigKey() {
|
} else if r.indexEndpoint.String() == index.GetAuthConfigKey() {
|
||||||
ep = r.indexEndpoint
|
ep = r.indexEndpoint
|
||||||
} else {
|
} else {
|
||||||
ep, err = NewEndpoint(index)
|
ep, err = NewEndpoint(index, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -77,14 +77,14 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au
|
||||||
method := "GET"
|
method := "GET"
|
||||||
logrus.Debugf("[registry] Calling %q %s", method, routeURL)
|
logrus.Debugf("[registry] Calling %q %s", method, routeURL)
|
||||||
|
|
||||||
req, err := r.reqFactory.NewRequest(method, routeURL, nil)
|
req, err := http.NewRequest(method, routeURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
if err := auth.Authorize(req); err != nil {
|
if err := auth.Authorize(req); err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
res, _, err := r.doRequest(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
@ -118,14 +118,14 @@ func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Di
|
||||||
method := "HEAD"
|
method := "HEAD"
|
||||||
logrus.Debugf("[registry] Calling %q %s", method, routeURL)
|
logrus.Debugf("[registry] Calling %q %s", method, routeURL)
|
||||||
|
|
||||||
req, err := r.reqFactory.NewRequest(method, routeURL, nil)
|
req, err := http.NewRequest(method, routeURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if err := auth.Authorize(req); err != nil {
|
if err := auth.Authorize(req); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
res, _, err := r.doRequest(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -152,14 +152,14 @@ func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Dig
|
||||||
|
|
||||||
method := "GET"
|
method := "GET"
|
||||||
logrus.Debugf("[registry] Calling %q %s", method, routeURL)
|
logrus.Debugf("[registry] Calling %q %s", method, routeURL)
|
||||||
req, err := r.reqFactory.NewRequest(method, routeURL, nil)
|
req, err := http.NewRequest(method, routeURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := auth.Authorize(req); err != nil {
|
if err := auth.Authorize(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
res, _, err := r.doRequest(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -183,14 +183,14 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName string, dgst dige
|
||||||
|
|
||||||
method := "GET"
|
method := "GET"
|
||||||
logrus.Debugf("[registry] Calling %q %s", method, routeURL)
|
logrus.Debugf("[registry] Calling %q %s", method, routeURL)
|
||||||
req, err := r.reqFactory.NewRequest(method, routeURL, nil)
|
req, err := http.NewRequest(method, routeURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
if err := auth.Authorize(req); err != nil {
|
if err := auth.Authorize(req); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
res, _, err := r.doRequest(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Dig
|
||||||
|
|
||||||
method := "PUT"
|
method := "PUT"
|
||||||
logrus.Debugf("[registry] Calling %q %s", method, location)
|
logrus.Debugf("[registry] Calling %q %s", method, location)
|
||||||
req, err := r.reqFactory.NewRequest(method, location, ioutil.NopCloser(blobRdr))
|
req, err := http.NewRequest(method, location, ioutil.NopCloser(blobRdr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -230,7 +230,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Dig
|
||||||
if err := auth.Authorize(req); err != nil {
|
if err := auth.Authorize(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
res, _, err := r.doRequest(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -259,7 +259,7 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("[registry] Calling %q %s", "POST", routeURL)
|
logrus.Debugf("[registry] Calling %q %s", "POST", routeURL)
|
||||||
req, err := r.reqFactory.NewRequest("POST", routeURL, nil)
|
req, err := http.NewRequest("POST", routeURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -267,7 +267,7 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque
|
||||||
if err := auth.Authorize(req); err != nil {
|
if err := auth.Authorize(req); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
res, _, err := r.doRequest(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -305,14 +305,14 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, si
|
||||||
|
|
||||||
method := "PUT"
|
method := "PUT"
|
||||||
logrus.Debugf("[registry] Calling %q %s", method, routeURL)
|
logrus.Debugf("[registry] Calling %q %s", method, routeURL)
|
||||||
req, err := r.reqFactory.NewRequest(method, routeURL, bytes.NewReader(signedManifest))
|
req, err := http.NewRequest(method, routeURL, bytes.NewReader(signedManifest))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := auth.Authorize(req); err != nil {
|
if err := auth.Authorize(req); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
res, _, err := r.doRequest(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -366,14 +366,14 @@ func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestA
|
||||||
method := "GET"
|
method := "GET"
|
||||||
logrus.Debugf("[registry] Calling %q %s", method, routeURL)
|
logrus.Debugf("[registry] Calling %q %s", method, routeURL)
|
||||||
|
|
||||||
req, err := r.reqFactory.NewRequest(method, routeURL, nil)
|
req, err := http.NewRequest(method, routeURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := auth.Authorize(req); err != nil {
|
if err := auth.Authorize(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res, _, err := r.doRequest(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,15 +7,13 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/requestdecorator"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type tokenResponse struct {
|
type tokenResponse struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getToken(username, password string, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) (token string, err error) {
|
func getToken(username, password string, params map[string]string, registryEndpoint *Endpoint) (token string, err error) {
|
||||||
realm, ok := params["realm"]
|
realm, ok := params["realm"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", errors.New("no realm specified for token auth challenge")
|
return "", errors.New("no realm specified for token auth challenge")
|
||||||
|
@ -34,7 +32,7 @@ func getToken(username, password string, params map[string]string, registryEndpo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := factory.NewRequest("GET", realmURL.String(), nil)
|
req, err := http.NewRequest("GET", realmURL.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -58,7 +56,7 @@ func getToken(username, password string, params map[string]string, registryEndpo
|
||||||
|
|
||||||
req.URL.RawQuery = reqParams.Encode()
|
req.URL.RawQuery = reqParams.Encode()
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := registryEndpoint.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче