diff --git a/graph/push.go b/graph/push.go index 46469daead4a4..94aa122e09fb5 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) @@ -378,6 +381,12 @@ 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"] = []string{"push"} + + repoInfo.Index.Headers = metaHeaders + if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil { return job.Error(err) } @@ -403,9 +412,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..54a906fbfee2d 100644 --- a/registry/endpoint.go +++ b/registry/endpoint.go @@ -2,6 +2,7 @@ package registry import ( "encoding/json" + "errors" "fmt" "io/ioutil" "net" @@ -11,11 +12,14 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/registry/v2" + "github.com/docker/docker/utils" ) // 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 ( @@ -43,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 } @@ -60,6 +64,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 +81,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) } @@ -81,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 @@ -98,6 +108,7 @@ func newEndpoint(address string, secure bool) (*Endpoint, error) { return nil, err } endpoint.IsSecure = secure + endpoint.ReqFactory = HTTPRequestFactory(headers) return endpoint, nil } @@ -112,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 @@ -168,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 } @@ -216,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 } @@ -240,5 +252,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)) } 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 {