From e3855d57856eb04faaa0383496212d4733feefbb Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Thu, 15 Jan 2015 17:02:56 -0800 Subject: [PATCH 1/4] Add fallback to v1 when v2 endpoint is not found Signed-off-by: Derek McGowan (github: dmcgowan) --- graph/push.go | 12 +++++++++--- registry/endpoint.go | 13 +++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/graph/push.go b/graph/push.go index 46469daead4a4..5bd68132dc741 100644 --- a/graph/push.go +++ b/graph/push.go @@ -262,6 +262,9 @@ func (s *TagStore) pushV2Repository(r *registry.Session, eng *engine.Engine, out endpoint, err := r.V2RegistryEndpoint(repoInfo.Index) if err != nil { + if err == registry.ErrEndpointNotFound { + return err + } return fmt.Errorf("error getting registry endpoint: %s", err) } auth, err := r.GetV2Authorization(endpoint, repoInfo.RemoteName, false) @@ -403,9 +406,12 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status { if err == nil { return engine.StatusOK } - - // error out, no fallback to V1 - return job.Error(err) + if err != registry.ErrEndpointNotFound { + // error out, no fallback to V1 + return job.Error(err) + } else { + log.Debugf("Skipping V2 registry: endpoint not available") + } } if err != nil { diff --git a/registry/endpoint.go b/registry/endpoint.go index 9ca9ed8b9a6e2..d9033458bc9ee 100644 --- a/registry/endpoint.go +++ b/registry/endpoint.go @@ -2,6 +2,7 @@ package registry import ( "encoding/json" + "errors" "fmt" "io/ioutil" "net" @@ -16,6 +17,8 @@ import ( // for mocking in unit tests var lookupIP = net.LookupIP +var ErrEndpointNotFound = errors.New("endpoint not found") + // scans string for api version in the URL path. returns the trimmed address, if version found, string and API version. func scanForAPIVersion(address string) (string, APIVersion) { var ( @@ -60,6 +63,9 @@ func validateEndpoint(endpoint *Endpoint) error { // Try HTTPS ping to registry endpoint.URL.Scheme = "https" if _, err := endpoint.Ping(); err != nil { + if err == ErrEndpointNotFound { + return err + } if endpoint.IsSecure { // If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry` // in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP. @@ -74,6 +80,9 @@ func validateEndpoint(endpoint *Endpoint) error { if _, err2 = endpoint.Ping(); err2 == nil { return nil } + if err2 == ErrEndpointNotFound { + return err2 + } return fmt.Errorf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2) } @@ -240,5 +249,9 @@ func (e *Endpoint) pingV2() (RegistryInfo, error) { return RegistryInfo{}, nil } + if resp.StatusCode == http.StatusNotFound { + return RegistryInfo{}, ErrEndpointNotFound + } + return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode)) } From 543d02143859cdef121ae95d9252126bca7a474c Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Thu, 15 Jan 2015 12:23:21 -0800 Subject: [PATCH 2/4] Identify push operation by command http header To allow remotes to understand the operation being carried out during an API request to the registry, we've added a header indicating the engine command. Mostly, this is advisory but a registry may take action based on the field. This changeset only adds this for the "push" command. Signed-off-by: Stephen J Day --- graph/push.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/graph/push.go b/graph/push.go index 5bd68132dc741..68c57affb9944 100644 --- a/graph/push.go +++ b/graph/push.go @@ -381,6 +381,10 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status { job.GetenvJson("authConfig", authConfig) job.GetenvJson("metaHeaders", &metaHeaders) + // Set a header so remotes can identify the command being carried out. If + // present, the remote may act on the field but this is mostly advisory. + metaHeaders["Docker-Command"] = "push" + if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil { return job.Error(err) } From 40fa5dd12b1badb1f4184098c3e5382dd994cbda Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Thu, 15 Jan 2015 19:17:54 -0800 Subject: [PATCH 3/4] Fix compilation of meta headers Signed-off-by: Derek McGowan (github: dmcgowan) --- graph/push.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/push.go b/graph/push.go index 68c57affb9944..e734c4fc0184d 100644 --- a/graph/push.go +++ b/graph/push.go @@ -383,7 +383,7 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status { // Set a header so remotes can identify the command being carried out. If // present, the remote may act on the field but this is mostly advisory. - metaHeaders["Docker-Command"] = "push" + metaHeaders["Docker-Command"] = []string{"push"} if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil { return job.Error(err) From c23b58c09020c544dd440f05435b42eb6ec07210 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Fri, 16 Jan 2015 15:16:33 -0800 Subject: [PATCH 4/4] Add meta headers to endpoint requests Signed-off-by: Derek McGowan (github: dmcgowan) --- graph/push.go | 2 ++ registry/endpoint.go | 11 +++++++---- registry/session_v2.go | 8 +++++++- registry/types.go | 1 + 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/graph/push.go b/graph/push.go index e734c4fc0184d..94aa122e09fb5 100644 --- a/graph/push.go +++ b/graph/push.go @@ -385,6 +385,8 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status { // present, the remote may act on the field but this is mostly advisory. metaHeaders["Docker-Command"] = []string{"push"} + repoInfo.Index.Headers = metaHeaders + if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil { return job.Error(err) } diff --git a/registry/endpoint.go b/registry/endpoint.go index d9033458bc9ee..54a906fbfee2d 100644 --- a/registry/endpoint.go +++ b/registry/endpoint.go @@ -12,6 +12,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/registry/v2" + "github.com/docker/docker/utils" ) // for mocking in unit tests @@ -46,7 +47,7 @@ func scanForAPIVersion(address string) (string, APIVersion) { // NewEndpoint parses the given address to return a registry endpoint. func NewEndpoint(index *IndexInfo) (*Endpoint, error) { // *TODO: Allow per-registry configuration of endpoints. - endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure) + endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure, index.Headers) if err != nil { return nil, err } @@ -90,7 +91,7 @@ func validateEndpoint(endpoint *Endpoint) error { return nil } -func newEndpoint(address string, secure bool) (*Endpoint, error) { +func newEndpoint(address string, secure bool, headers map[string][]string) (*Endpoint, error) { var ( endpoint = new(Endpoint) trimmedAddress string @@ -107,6 +108,7 @@ func newEndpoint(address string, secure bool) (*Endpoint, error) { return nil, err } endpoint.IsSecure = secure + endpoint.ReqFactory = HTTPRequestFactory(headers) return endpoint, nil } @@ -121,6 +123,7 @@ type Endpoint struct { IsSecure bool AuthChallenges []*AuthorizationChallenge URLBuilder *v2.URLBuilder + ReqFactory *utils.HTTPRequestFactory } // Get the formated URL for the root of this registry Endpoint @@ -177,7 +180,7 @@ func (e *Endpoint) pingV1() (RegistryInfo, error) { return RegistryInfo{Standalone: false}, nil } - req, err := http.NewRequest("GET", e.Path("_ping"), nil) + req, err := e.ReqFactory.NewRequest("GET", e.Path("_ping"), nil) if err != nil { return RegistryInfo{Standalone: false}, err } @@ -225,7 +228,7 @@ func (e *Endpoint) pingV1() (RegistryInfo, error) { func (e *Endpoint) pingV2() (RegistryInfo, error) { log.Debugf("attempting v2 ping for registry endpoint %s", e) - req, err := http.NewRequest("GET", e.Path(""), nil) + req, err := e.ReqFactory.NewRequest("GET", e.Path(""), nil) if err != nil { return RegistryInfo{}, err } diff --git a/registry/session_v2.go b/registry/session_v2.go index 11b96bd65a5f4..931516110fa42 100644 --- a/registry/session_v2.go +++ b/registry/session_v2.go @@ -22,7 +22,7 @@ func getV2Builder(e *Endpoint) *v2.URLBuilder { func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error) { // TODO check if should use Mirror if index.Official { - ep, err = newEndpoint(REGISTRYSERVER, true) + ep, err = newEndpoint(REGISTRYSERVER, index.Secure, index.Headers) if err != nil { return } @@ -222,6 +222,12 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string if err != nil { return err } + if res.StatusCode != 202 { + if res.StatusCode == 401 { + return errLoginRequired + } + return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob", res.StatusCode, imageName), res) + } location := res.Header.Get("Location") method := "PUT" diff --git a/registry/types.go b/registry/types.go index bd0bf8b75b0fd..8117a3ee7446f 100644 --- a/registry/types.go +++ b/registry/types.go @@ -98,6 +98,7 @@ type IndexInfo struct { Mirrors []string Secure bool Official bool + Headers map[string][]string } type RepositoryInfo struct {