From 2a2c694758d6a48125cc9adf446f2054b52db201 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 10 Mar 2014 16:11:03 -0400 Subject: [PATCH 1/3] registry: make certain headers optional For a pull-only, static registry, there only a couple of headers that need to be optional (that are presently required. * X-Docker-Registry-Version * X-Docker-Size * X-Docker-Endpoints Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- registry/registry.go | 53 +++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index dbf5d539ff..30079e9aa9 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -25,12 +25,8 @@ var ( errLoginRequired = errors.New("Authentication is required.") ) -func pingRegistryEndpoint(endpoint string) (bool, error) { - if endpoint == IndexServerAddress() { - // Skip the check, we now this one is valid - // (and we never want to fallback to http in case of error) - return false, nil - } +// reuse this chunk of code +func newClient() *http.Client { httpDial := func(proto string, addr string) (net.Conn, error) { // Set the connect timeout to 5 seconds conn, err := net.DialTimeout(proto, addr, time.Duration(5)*time.Second) @@ -42,17 +38,39 @@ func pingRegistryEndpoint(endpoint string) (bool, error) { return conn, nil } httpTransport := &http.Transport{Dial: httpDial} - client := &http.Client{Transport: httpTransport} + return &http.Client{Transport: httpTransport} +} + +// Have an API to access the version of the registry +func getRegistryVersion(endpoint string) (string, error) { + + client := newClient() + resp, err := client.Get(endpoint + "_version") + if err != nil { + return "", err + } + defer resp.Body.Close() + + if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" { + return hdr, nil + } + versionBody, err := ioutil.ReadAll(resp.Body) + return string(versionBody), err +} + +func pingRegistryEndpoint(endpoint string) (bool, error) { + if endpoint == IndexServerAddress() { + // Skip the check, we now this one is valid + // (and we never want to fallback to http in case of error) + return false, nil + } + client := newClient() resp, err := client.Get(endpoint + "_ping") if err != nil { return false, err } defer resp.Body.Close() - if resp.Header.Get("X-Docker-Registry-Version") == "" { - return false, errors.New("This does not look like a Registry server (\"X-Docker-Registry-Version\" header not found in the response)") - } - standalone := resp.Header.Get("X-Docker-Registry-Standalone") utils.Debugf("Registry standalone header: '%s'", standalone) // If the header is absent, we assume true for compatibility with earlier @@ -223,9 +241,13 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ return nil, -1, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) } - imageSize, err := strconv.Atoi(res.Header.Get("X-Docker-Size")) - if err != nil { - return nil, -1, err + // if the size header is not present, then set it to '-1' + imageSize := -1 + if hdr := res.Header.Get("X-Docker-Size"); hdr != "" { + imageSize, err = strconv.Atoi(hdr) + if err != nil { + return nil, -1, err + } } jsonString, err := ioutil.ReadAll(res.Body) @@ -336,7 +358,8 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep)) } } else { - return nil, fmt.Errorf("Index response didn't contain any endpoints") + // Assume the endpoint is on the same host + endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, req.URL.Host)) } checksumsJSON, err := ioutil.ReadAll(res.Body) From 2b855afaeedcab3117876815ec2f8d4450a742b5 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 11 Mar 2014 23:36:51 -0400 Subject: [PATCH 2/3] registry: Info collection roll version and standalone information into the _ping. And to support Headers they are checked after the JSON is loaded (if there is anything to load). To stay backwards compatible, if the _ping contents are not able to unmarshal to RegistryInfo, do not stop, but continue with the same behavior. Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- registry/registry.go | 84 +++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 30079e9aa9..6040d75003 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -25,8 +25,12 @@ var ( errLoginRequired = errors.New("Authentication is required.") ) -// reuse this chunk of code -func newClient() *http.Client { +func pingRegistryEndpoint(endpoint string) (RegistryInfo, error) { + if endpoint == IndexServerAddress() { + // Skip the check, we now this one is valid + // (and we never want to fallback to http in case of error) + return RegistryInfo{Standalone: false}, nil + } httpDial := func(proto string, addr string) (net.Conn, error) { // Set the connect timeout to 5 seconds conn, err := net.DialTimeout(proto, addr, time.Duration(5)*time.Second) @@ -38,51 +42,44 @@ func newClient() *http.Client { return conn, nil } httpTransport := &http.Transport{Dial: httpDial} - return &http.Client{Transport: httpTransport} -} - -// Have an API to access the version of the registry -func getRegistryVersion(endpoint string) (string, error) { - - client := newClient() - resp, err := client.Get(endpoint + "_version") - if err != nil { - return "", err - } - defer resp.Body.Close() - - if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" { - return hdr, nil - } - versionBody, err := ioutil.ReadAll(resp.Body) - return string(versionBody), err -} - -func pingRegistryEndpoint(endpoint string) (bool, error) { - if endpoint == IndexServerAddress() { - // Skip the check, we now this one is valid - // (and we never want to fallback to http in case of error) - return false, nil - } - client := newClient() + client := &http.Client{Transport: httpTransport} resp, err := client.Get(endpoint + "_ping") if err != nil { - return false, err + return RegistryInfo{Standalone: false}, err } defer resp.Body.Close() + jsonString, err := ioutil.ReadAll(resp.Body) + if err != nil { + return RegistryInfo{Standalone: false}, fmt.Errorf("Error while reading the http response: %s", err) + } + + // If the header is absent, we assume true for compatibility with earlier + // versions of the registry. default to true + info := RegistryInfo{ + Standalone: true, + } + if err := json.Unmarshal(jsonString, &info); err != nil { + utils.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err) + // don't stop here. Just assume sane defaults + } + if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" { + utils.Debugf("Registry version header: '%s'", hdr) + info.Version = hdr + } + utils.Debugf("RegistryInfo.Version: %q", info.Version) + standalone := resp.Header.Get("X-Docker-Registry-Standalone") utils.Debugf("Registry standalone header: '%s'", standalone) - // If the header is absent, we assume true for compatibility with earlier - // versions of the registry - if standalone == "" { - return true, nil - // Accepted values are "true" (case-insensitive) and "1". - } else if strings.EqualFold(standalone, "true") || standalone == "1" { - return true, nil + // Accepted values are "true" (case-insensitive) and "1". + if strings.EqualFold(standalone, "true") || standalone == "1" { + info.Standalone = true + } else if len(standalone) > 0 { + // there is a header set, and it is not "true" or "1", so assume fails + info.Standalone = false } - // Otherwise, not standalone - return false, nil + utils.Debugf("RegistryInfo.Standalone: %q", info.Standalone) + return info, nil } func validateRepositoryName(repositoryName string) error { @@ -688,6 +685,11 @@ type ImgData struct { Tag string `json:",omitempty"` } +type RegistryInfo struct { + Version string `json:"version"` + Standalone bool `json:"standalone"` +} + type Registry struct { client *http.Client authConfig *AuthConfig @@ -716,11 +718,11 @@ func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, inde // If we're working with a standalone private registry over HTTPS, send Basic Auth headers // alongside our requests. if indexEndpoint != IndexServerAddress() && strings.HasPrefix(indexEndpoint, "https://") { - standalone, err := pingRegistryEndpoint(indexEndpoint) + info, err := pingRegistryEndpoint(indexEndpoint) if err != nil { return nil, err } - if standalone { + if info.Standalone { utils.Debugf("Endpoint %s is eligible for private registry registry. Enabling decorator.", indexEndpoint) dec := utils.NewHTTPAuthDecorator(authConfig.Username, authConfig.Password) factory.AddDecorator(dec) From 7790a77b6afeda6f223202288a89f4b431f033a0 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Fri, 25 Apr 2014 20:01:25 -0400 Subject: [PATCH 3/3] static_registry: update the test for the new struct Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- registry/registry_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/registry_test.go b/registry/registry_test.go index f21814c791..f53345c1fd 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -22,11 +22,11 @@ func spawnTestRegistry(t *testing.T) *Registry { } func TestPingRegistryEndpoint(t *testing.T) { - standalone, err := pingRegistryEndpoint(makeURL("/v1/")) + regInfo, err := pingRegistryEndpoint(makeURL("/v1/")) if err != nil { t.Fatal(err) } - assertEqual(t, standalone, true, "Expected standalone to be true (default)") + assertEqual(t, regInfo.Standalone, true, "Expected standalone to be true (default)") } func TestGetRemoteHistory(t *testing.T) {