From 1c101d006bceabd606de6938219a4399a6195d71 Mon Sep 17 00:00:00 2001 From: Fabio Falci Date: Sun, 9 Mar 2014 01:49:36 +0000 Subject: [PATCH 1/9] Remove manual http cookie management Since docker uses cookiejar it doesn't need to manage cookies manually anymore. Managing cookie was duplicating it. Docker-DCO-1.1-Signed-off-by: Fabio Falci (github: fabiofalci) --- docs/registry.go | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/docs/registry.go b/docs/registry.go index 543dcea3..cc2e985c 100644 --- a/docs/registry.go +++ b/docs/registry.go @@ -149,20 +149,6 @@ func ExpandAndVerifyRegistryUrl(hostname string) (string, error) { return endpoint, nil } -func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { - for _, cookie := range c.Jar.Cookies(req.URL) { - req.AddCookie(cookie) - } - res, err := c.Do(req) - if err != nil { - return nil, err - } - if len(res.Cookies()) > 0 { - c.Jar.SetCookies(req.URL, res.Cookies()) - } - return res, err -} - func setTokenAuth(req *http.Request, token []string) { if req.Header.Get("Authorization") == "" { // Don't override req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) @@ -177,7 +163,7 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s return nil, err } setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return nil, err } @@ -212,7 +198,7 @@ func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) boo return false } setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { utils.Errorf("Error in LookupRemoteImage %s", err) return false @@ -229,7 +215,7 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ return nil, -1, fmt.Errorf("Failed to download json: %s", err) } setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) } @@ -256,7 +242,7 @@ func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) ( return nil, fmt.Errorf("Error while getting from the server: %s\n", err) } setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return nil, err } @@ -282,7 +268,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ return nil, err } setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return nil, err } @@ -388,7 +374,7 @@ func (r *Registry) PushImageChecksumRegistry(imgData *ImgData, registry string, req.Header.Set("X-Docker-Checksum", imgData.Checksum) req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return fmt.Errorf("Failed to upload metadata: %s", err) } @@ -424,7 +410,7 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis req.Header.Add("Content-type", "application/json") setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return fmt.Errorf("Failed to upload metadata: %s", err) } @@ -460,7 +446,7 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr req.ContentLength = -1 req.TransferEncoding = []string{"chunked"} setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return "", "", fmt.Errorf("Failed to upload layer: %s", err) } @@ -497,7 +483,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token req.Header.Add("Content-type", "application/json") setTokenAuth(req, token) req.ContentLength = int64(len(revision)) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return err } From f6fefb0bc1c18bc4889718dccf275c4ea3a41309 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 10 Mar 2014 17:16:58 -0700 Subject: [PATCH 2/9] Merge auth package within registry Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- docs/auth.go | 290 ++++++++++++++++++++++++++++++++++++++++++ docs/auth_test.go | 149 ++++++++++++++++++++++ docs/registry.go | 19 ++- docs/registry_test.go | 5 +- 4 files changed, 450 insertions(+), 13 deletions(-) create mode 100644 docs/auth.go create mode 100644 docs/auth_test.go diff --git a/docs/auth.go b/docs/auth.go new file mode 100644 index 00000000..4fdd51fd --- /dev/null +++ b/docs/auth.go @@ -0,0 +1,290 @@ +package registry + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "github.com/dotcloud/docker/utils" + "io/ioutil" + "net/http" + "os" + "path" + "strings" +) + +// Where we store the config file +const CONFIGFILE = ".dockercfg" + +// Only used for user auth + account creation +const INDEXSERVER = "https://index.docker.io/v1/" + +//const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/" + +var ( + ErrConfigFileMissing = errors.New("The Auth config file is missing") +) + +type AuthConfig struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Auth string `json:"auth"` + Email string `json:"email"` + ServerAddress string `json:"serveraddress,omitempty"` +} + +type ConfigFile struct { + Configs map[string]AuthConfig `json:"configs,omitempty"` + rootPath string +} + +func IndexServerAddress() string { + return INDEXSERVER +} + +// create a base64 encoded auth string to store in config +func encodeAuth(authConfig *AuthConfig) string { + authStr := authConfig.Username + ":" + authConfig.Password + msg := []byte(authStr) + encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) + base64.StdEncoding.Encode(encoded, msg) + return string(encoded) +} + +// decode the auth string +func decodeAuth(authStr string) (string, string, error) { + decLen := base64.StdEncoding.DecodedLen(len(authStr)) + decoded := make([]byte, decLen) + authByte := []byte(authStr) + n, err := base64.StdEncoding.Decode(decoded, authByte) + if err != nil { + return "", "", err + } + if n > decLen { + return "", "", fmt.Errorf("Something went wrong decoding auth config") + } + arr := strings.SplitN(string(decoded), ":", 2) + if len(arr) != 2 { + return "", "", fmt.Errorf("Invalid auth configuration file") + } + password := strings.Trim(arr[1], "\x00") + return arr[0], password, nil +} + +// load up the auth config information and return values +// FIXME: use the internal golang config parser +func LoadConfig(rootPath string) (*ConfigFile, error) { + configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath} + confFile := path.Join(rootPath, CONFIGFILE) + if _, err := os.Stat(confFile); err != nil { + return &configFile, nil //missing file is not an error + } + b, err := ioutil.ReadFile(confFile) + if err != nil { + return &configFile, err + } + + if err := json.Unmarshal(b, &configFile.Configs); err != nil { + arr := strings.Split(string(b), "\n") + if len(arr) < 2 { + return &configFile, fmt.Errorf("The Auth config file is empty") + } + authConfig := AuthConfig{} + origAuth := strings.Split(arr[0], " = ") + if len(origAuth) != 2 { + return &configFile, fmt.Errorf("Invalid Auth config file") + } + authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) + if err != nil { + return &configFile, err + } + origEmail := strings.Split(arr[1], " = ") + if len(origEmail) != 2 { + return &configFile, fmt.Errorf("Invalid Auth config file") + } + authConfig.Email = origEmail[1] + authConfig.ServerAddress = IndexServerAddress() + configFile.Configs[IndexServerAddress()] = authConfig + } else { + for k, authConfig := range configFile.Configs { + authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) + if err != nil { + return &configFile, err + } + authConfig.Auth = "" + configFile.Configs[k] = authConfig + authConfig.ServerAddress = k + } + } + return &configFile, nil +} + +// save the auth config +func SaveConfig(configFile *ConfigFile) error { + confFile := path.Join(configFile.rootPath, CONFIGFILE) + if len(configFile.Configs) == 0 { + os.Remove(confFile) + return nil + } + + configs := make(map[string]AuthConfig, len(configFile.Configs)) + for k, authConfig := range configFile.Configs { + authCopy := authConfig + + authCopy.Auth = encodeAuth(&authCopy) + authCopy.Username = "" + authCopy.Password = "" + authCopy.ServerAddress = "" + configs[k] = authCopy + } + + b, err := json.Marshal(configs) + if err != nil { + return err + } + err = ioutil.WriteFile(confFile, b, 0600) + if err != nil { + return err + } + return nil +} + +// try to register/login to the registry server +func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) { + var ( + status string + reqBody []byte + err error + client = &http.Client{} + reqStatusCode = 0 + serverAddress = authConfig.ServerAddress + ) + + if serverAddress == "" { + serverAddress = IndexServerAddress() + } + + loginAgainstOfficialIndex := serverAddress == IndexServerAddress() + + // to avoid sending the server address to the server it should be removed before being marshalled + authCopy := *authConfig + authCopy.ServerAddress = "" + + jsonBody, err := json.Marshal(authCopy) + if err != nil { + return "", fmt.Errorf("Config Error: %s", err) + } + + // using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status. + b := strings.NewReader(string(jsonBody)) + req1, err := http.Post(serverAddress+"users/", "application/json; charset=utf-8", b) + if err != nil { + return "", fmt.Errorf("Server Error: %s", err) + } + reqStatusCode = req1.StatusCode + defer req1.Body.Close() + reqBody, err = ioutil.ReadAll(req1.Body) + if err != nil { + return "", fmt.Errorf("Server Error: [%#v] %s", reqStatusCode, err) + } + + if reqStatusCode == 201 { + if loginAgainstOfficialIndex { + status = "Account created. Please use the confirmation link we sent" + + " to your e-mail to activate it." + } else { + status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it." + } + } else if reqStatusCode == 400 { + if string(reqBody) == "\"Username or email already exists\"" { + req, err := factory.NewRequest("GET", serverAddress+"users/", nil) + req.SetBasicAuth(authConfig.Username, authConfig.Password) + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + if resp.StatusCode == 200 { + status = "Login Succeeded" + } else if resp.StatusCode == 401 { + return "", fmt.Errorf("Wrong login/password, please try again") + } else if resp.StatusCode == 403 { + if loginAgainstOfficialIndex { + return "", fmt.Errorf("Login: Account is not Active. Please check your e-mail for a confirmation link.") + } + return "", fmt.Errorf("Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress) + } else { + return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, resp.StatusCode, resp.Header) + } + } else { + return "", fmt.Errorf("Registration: %s", reqBody) + } + } else if reqStatusCode == 401 { + // This case would happen with private registries where /v1/users is + // protected, so people can use `docker login` as an auth check. + req, err := factory.NewRequest("GET", serverAddress+"users/", nil) + req.SetBasicAuth(authConfig.Username, authConfig.Password) + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + if resp.StatusCode == 200 { + status = "Login Succeeded" + } else if resp.StatusCode == 401 { + return "", fmt.Errorf("Wrong login/password, please try again") + } else { + return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, + resp.StatusCode, resp.Header) + } + } else { + return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody) + } + return status, nil +} + +// this method matches a auth configuration to a server address or a url +func (config *ConfigFile) ResolveAuthConfig(hostname string) AuthConfig { + if hostname == IndexServerAddress() || len(hostname) == 0 { + // default to the index server + return config.Configs[IndexServerAddress()] + } + + // First try the happy case + if c, found := config.Configs[hostname]; found { + return c + } + + convertToHostname := func(url string) string { + stripped := url + if strings.HasPrefix(url, "http://") { + stripped = strings.Replace(url, "http://", "", 1) + } else if strings.HasPrefix(url, "https://") { + stripped = strings.Replace(url, "https://", "", 1) + } + + nameParts := strings.SplitN(stripped, "/", 2) + + return nameParts[0] + } + + // Maybe they have a legacy config file, we will iterate the keys converting + // them to the new format and testing + normalizedHostename := convertToHostname(hostname) + for registry, config := range config.Configs { + if registryHostname := convertToHostname(registry); registryHostname == normalizedHostename { + return config + } + } + + // When all else fails, return an empty auth config + return AuthConfig{} +} diff --git a/docs/auth_test.go b/docs/auth_test.go new file mode 100644 index 00000000..3cb1a9ac --- /dev/null +++ b/docs/auth_test.go @@ -0,0 +1,149 @@ +package registry + +import ( + "io/ioutil" + "os" + "testing" +) + +func TestEncodeAuth(t *testing.T) { + newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} + authStr := encodeAuth(newAuthConfig) + decAuthConfig := &AuthConfig{} + var err error + decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr) + if err != nil { + t.Fatal(err) + } + if newAuthConfig.Username != decAuthConfig.Username { + t.Fatal("Encode Username doesn't match decoded Username") + } + if newAuthConfig.Password != decAuthConfig.Password { + t.Fatal("Encode Password doesn't match decoded Password") + } + if authStr != "a2VuOnRlc3Q=" { + t.Fatal("AuthString encoding isn't correct.") + } +} + +func setupTempConfigFile() (*ConfigFile, error) { + root, err := ioutil.TempDir("", "docker-test-auth") + if err != nil { + return nil, err + } + configFile := &ConfigFile{ + rootPath: root, + Configs: make(map[string]AuthConfig), + } + + for _, registry := range []string{"testIndex", IndexServerAddress()} { + configFile.Configs[registry] = AuthConfig{ + Username: "docker-user", + Password: "docker-pass", + Email: "docker@docker.io", + } + } + + return configFile, nil +} + +func TestSameAuthDataPostSave(t *testing.T) { + configFile, err := setupTempConfigFile() + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(configFile.rootPath) + + err = SaveConfig(configFile) + if err != nil { + t.Fatal(err) + } + + authConfig := configFile.Configs["testIndex"] + if authConfig.Username != "docker-user" { + t.Fail() + } + if authConfig.Password != "docker-pass" { + t.Fail() + } + if authConfig.Email != "docker@docker.io" { + t.Fail() + } + if authConfig.Auth != "" { + t.Fail() + } +} + +func TestResolveAuthConfigIndexServer(t *testing.T) { + configFile, err := setupTempConfigFile() + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(configFile.rootPath) + + for _, registry := range []string{"", IndexServerAddress()} { + resolved := configFile.ResolveAuthConfig(registry) + if resolved != configFile.Configs[IndexServerAddress()] { + t.Fail() + } + } +} + +func TestResolveAuthConfigFullURL(t *testing.T) { + configFile, err := setupTempConfigFile() + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(configFile.rootPath) + + registryAuth := AuthConfig{ + Username: "foo-user", + Password: "foo-pass", + Email: "foo@example.com", + } + localAuth := AuthConfig{ + Username: "bar-user", + Password: "bar-pass", + Email: "bar@example.com", + } + configFile.Configs["https://registry.example.com/v1/"] = registryAuth + configFile.Configs["http://localhost:8000/v1/"] = localAuth + configFile.Configs["registry.com"] = registryAuth + + validRegistries := map[string][]string{ + "https://registry.example.com/v1/": { + "https://registry.example.com/v1/", + "http://registry.example.com/v1/", + "registry.example.com", + "registry.example.com/v1/", + }, + "http://localhost:8000/v1/": { + "https://localhost:8000/v1/", + "http://localhost:8000/v1/", + "localhost:8000", + "localhost:8000/v1/", + }, + "registry.com": { + "https://registry.com/v1/", + "http://registry.com/v1/", + "registry.com", + "registry.com/v1/", + }, + } + + for configKey, registries := range validRegistries { + for _, registry := range registries { + var ( + configured AuthConfig + ok bool + ) + resolved := configFile.ResolveAuthConfig(registry) + if configured, ok = configFile.Configs[configKey]; !ok { + t.Fail() + } + if resolved.Email != configured.Email { + t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email) + } + } + } +} diff --git a/docs/registry.go b/docs/registry.go index cc2e985c..dbf5d539 100644 --- a/docs/registry.go +++ b/docs/registry.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -27,7 +26,7 @@ var ( ) func pingRegistryEndpoint(endpoint string) (bool, error) { - if endpoint == auth.IndexServerAddress() { + 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 @@ -103,7 +102,7 @@ func ResolveRepositoryName(reposName string) (string, string, error) { nameParts[0] != "localhost" { // This is a Docker Index repos (ex: samalba/hipache or ubuntu) err := validateRepositoryName(reposName) - return auth.IndexServerAddress(), reposName, err + return IndexServerAddress(), reposName, err } if len(nameParts) < 2 { // There is a dot in repos name (and no registry address) @@ -601,7 +600,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { utils.Debugf("Index server: %s", r.indexEndpoint) - u := auth.IndexServerAddress() + "search?q=" + url.QueryEscape(term) + u := IndexServerAddress() + "search?q=" + url.QueryEscape(term) req, err := r.reqFactory.NewRequest("GET", u, nil) if err != nil { return nil, err @@ -627,12 +626,12 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { return result, err } -func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig { +func (r *Registry) GetAuthConfig(withPasswd bool) *AuthConfig { password := "" if withPasswd { password = r.authConfig.Password } - return &auth.AuthConfig{ + return &AuthConfig{ Username: r.authConfig.Username, Password: password, Email: r.authConfig.Email, @@ -668,12 +667,12 @@ type ImgData struct { type Registry struct { client *http.Client - authConfig *auth.AuthConfig + authConfig *AuthConfig reqFactory *utils.HTTPRequestFactory indexEndpoint string } -func NewRegistry(authConfig *auth.AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string) (r *Registry, err error) { +func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string) (r *Registry, err error) { httpTransport := &http.Transport{ DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment, @@ -693,13 +692,13 @@ func NewRegistry(authConfig *auth.AuthConfig, factory *utils.HTTPRequestFactory, // If we're working with a standalone private registry over HTTPS, send Basic Auth headers // alongside our requests. - if indexEndpoint != auth.IndexServerAddress() && strings.HasPrefix(indexEndpoint, "https://") { + if indexEndpoint != IndexServerAddress() && strings.HasPrefix(indexEndpoint, "https://") { standalone, err := pingRegistryEndpoint(indexEndpoint) if err != nil { return nil, err } if standalone { - utils.Debugf("Endpoint %s is eligible for private registry auth. Enabling decorator.", indexEndpoint) + utils.Debugf("Endpoint %s is eligible for private registry registry. Enabling decorator.", indexEndpoint) dec := utils.NewHTTPAuthDecorator(authConfig.Username, authConfig.Password) factory.AddDecorator(dec) } diff --git a/docs/registry_test.go b/docs/registry_test.go index 82a27a16..f21814c7 100644 --- a/docs/registry_test.go +++ b/docs/registry_test.go @@ -1,7 +1,6 @@ package registry import ( - "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/utils" "strings" "testing" @@ -14,7 +13,7 @@ var ( ) func spawnTestRegistry(t *testing.T) *Registry { - authConfig := &auth.AuthConfig{} + authConfig := &AuthConfig{} r, err := NewRegistry(authConfig, utils.NewHTTPRequestFactory(), makeURL("/v1/")) if err != nil { t.Fatal(err) @@ -137,7 +136,7 @@ func TestResolveRepositoryName(t *testing.T) { if err != nil { t.Fatal(err) } - assertEqual(t, ep, auth.IndexServerAddress(), "Expected endpoint to be index server address") + assertEqual(t, ep, IndexServerAddress(), "Expected endpoint to be index server address") assertEqual(t, repo, "fooo/bar", "Expected resolved repo to be foo/bar") u := makeURL("")[7:] From 47c4e542ba329f6e1324fb5f3f468b6a8d434f5b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 13 Mar 2014 17:40:34 +0000 Subject: [PATCH 3/9] use mock for search Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- docs/registry.go | 2 +- docs/registry_mock_test.go | 7 ++++++- docs/registry_test.go | 6 ++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/registry.go b/docs/registry.go index dbf5d539..346132bc 100644 --- a/docs/registry.go +++ b/docs/registry.go @@ -600,7 +600,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { utils.Debugf("Index server: %s", r.indexEndpoint) - u := IndexServerAddress() + "search?q=" + url.QueryEscape(term) + u := r.indexEndpoint + "search?q=" + url.QueryEscape(term) req, err := r.reqFactory.NewRequest("GET", u, nil) if err != nil { return nil, err diff --git a/docs/registry_mock_test.go b/docs/registry_mock_test.go index 6eb94b63..dd5da6bd 100644 --- a/docs/registry_mock_test.go +++ b/docs/registry_mock_test.go @@ -321,7 +321,12 @@ func handlerAuth(w http.ResponseWriter, r *http.Request) { } func handlerSearch(w http.ResponseWriter, r *http.Request) { - writeResponse(w, "{}", 200) + result := &SearchResults{ + Query: "fakequery", + NumResults: 1, + Results: []SearchResult{{Name: "fakeimage", StarCount: 42}}, + } + writeResponse(w, result, 200) } func TestPing(t *testing.T) { diff --git a/docs/registry_test.go b/docs/registry_test.go index f21814c7..ebfb99b4 100644 --- a/docs/registry_test.go +++ b/docs/registry_test.go @@ -186,14 +186,16 @@ func TestPushImageJSONIndex(t *testing.T) { func TestSearchRepositories(t *testing.T) { r := spawnTestRegistry(t) - results, err := r.SearchRepositories("supercalifragilisticepsialidocious") + results, err := r.SearchRepositories("fakequery") if err != nil { t.Fatal(err) } if results == nil { t.Fatal("Expected non-nil SearchResults object") } - assertEqual(t, results.NumResults, 0, "Expected 0 search results") + assertEqual(t, results.NumResults, 1, "Expected 1 search results") + assertEqual(t, results.Query, "fakequery", "Expected 'fakequery' as query") + assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' a ot hae 42 stars") } func TestValidRepositoryName(t *testing.T) { From 9bad706a1ffd5e5b21088e1dc8b1e29fe140f030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djibril=20Kon=C3=A9?= Date: Fri, 21 Mar 2014 00:40:58 +0100 Subject: [PATCH 4/9] Harmonize / across all name-related commands/Validate images names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Djibril Koné (github: enokd) --- docs/registry_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/registry_test.go b/docs/registry_test.go index ebfb99b4..c072da41 100644 --- a/docs/registry_test.go +++ b/docs/registry_test.go @@ -206,4 +206,8 @@ func TestValidRepositoryName(t *testing.T) { t.Log("Repository name should be invalid") t.Fail() } + if err := validateRepositoryName("docker///docker"); err == nil { + t.Log("Repository name should be invalid") + t.Fail() + } } From fffa920a895aa81b9d56b36474d98af1d3fbf39a Mon Sep 17 00:00:00 2001 From: Ryan Thomas Date: Tue, 25 Mar 2014 14:45:11 +1100 Subject: [PATCH 5/9] Docker-DCO-1.1-Signed-off-by: Ryan Thomas (github: rthomas) --- docs/registry.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/registry.go b/docs/registry.go index 346132bc..01583f97 100644 --- a/docs/registry.go +++ b/docs/registry.go @@ -41,7 +41,10 @@ func pingRegistryEndpoint(endpoint string) (bool, error) { conn.SetDeadline(time.Now().Add(time.Duration(10) * time.Second)) return conn, nil } - httpTransport := &http.Transport{Dial: httpDial} + httpTransport := &http.Transport{ + Dial: httpDial, + Proxy: http.ProxyFromEnvironment, + } client := &http.Client{Transport: httpTransport} resp, err := client.Get(endpoint + "_ping") if err != nil { From 50ec0bbd4e5973cd42f8a61b0d5e8ca5e3a1fc71 Mon Sep 17 00:00:00 2001 From: Ryan Thomas Date: Fri, 28 Mar 2014 06:31:04 +1100 Subject: [PATCH 6/9] Docker-DCO-1.1-Signed-off-by: Ryan Thomas (github: rthomas) --- docs/registry.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/registry.go b/docs/registry.go index 01583f97..182ec78a 100644 --- a/docs/registry.go +++ b/docs/registry.go @@ -42,9 +42,9 @@ func pingRegistryEndpoint(endpoint string) (bool, error) { return conn, nil } httpTransport := &http.Transport{ - Dial: httpDial, - Proxy: http.ProxyFromEnvironment, - } + Dial: httpDial, + Proxy: http.ProxyFromEnvironment, + } client := &http.Client{Transport: httpTransport} resp, err := client.Get(endpoint + "_ping") if err != nil { From d2b2bf039386b25ed82dcb08649fb9532a44a02f Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Mon, 31 Mar 2014 17:56:25 -0700 Subject: [PATCH 7/9] Inverted layer checksum and tarsum. The checksum of the payload has to be computed on the Gzip'ed content. Docker-DCO-1.1-Signed-off-by: Sam Alba (github: samalba) --- docs/registry.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/registry.go b/docs/registry.go index 182ec78a..414283b8 100644 --- a/docs/registry.go +++ b/docs/registry.go @@ -438,10 +438,10 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer") h := sha256.New() - checksumLayer := &utils.CheckSum{Reader: layer, Hash: h} - tarsumLayer := &utils.TarSum{Reader: checksumLayer} + tarsumLayer := &utils.TarSum{Reader: layer} + checksumLayer := &utils.CheckSum{Reader: tarsumLayer, Hash: h} - req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", tarsumLayer) + req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", checksumLayer) if err != nil { return "", "", err } From 4f29181d9b516e006896bc0df8bc92a0e99b701a Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Mon, 31 Mar 2014 18:31:15 -0700 Subject: [PATCH 8/9] Payload checksum now match the checksum simple Backported for backward compatibility. Docker-DCO-1.1-Signed-off-by: Sam Alba (github: samalba) --- docs/registry.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/registry.go b/docs/registry.go index 414283b8..5ac04f9e 100644 --- a/docs/registry.go +++ b/docs/registry.go @@ -437,8 +437,10 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer") - h := sha256.New() tarsumLayer := &utils.TarSum{Reader: layer} + h := sha256.New() + h.Write(jsonRaw) + h.Write([]byte{'\n'}) checksumLayer := &utils.CheckSum{Reader: tarsumLayer, Hash: h} req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", checksumLayer) From dbb929653108f390a3023bea6e2028a20478ba1c Mon Sep 17 00:00:00 2001 From: shin- Date: Tue, 8 Apr 2014 16:53:16 +0200 Subject: [PATCH 9/9] Added specific error message when hitting 401 over HTTP on push Docker-DCO-1.1-Signed-off-by: Joffrey F (github: shin-) --- docs/registry.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/registry.go b/docs/registry.go index 5ac04f9e..817c08af 100644 --- a/docs/registry.go +++ b/docs/registry.go @@ -417,6 +417,9 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis return fmt.Errorf("Failed to upload metadata: %s", err) } defer res.Body.Close() + if res.StatusCode == 401 && strings.HasPrefix(registry, "http://") { + return utils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res) + } if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil {