eb36d60216
From Go 1.8 HTTP client redirect behaviour is changed: When status code is 301, 307 or 308, the client automatically converts it to a new HTTP request. This behaviour change manifests in the client in that before the 301 was not followed and the client did not generate an error, but now results in an error message: "Error response from daemon: page not found." To fix that a new redirect policy is forced by setting HTTP Client's CheckRedirect. That policy is to return an error for any 301, 307 or 308 in the response's status code to a non-GET request. The error message specifies that the daemon could not process the request and it is probably due to bad arguments that were provided by the user. Signed-off-by: Boaz Shuster <ripcurld.github@gmail.com>
334 lines
7.8 KiB
Go
334 lines
7.8 KiB
Go
package client
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/docker/docker/api"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/stretchr/testify/assert"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
func TestNewEnvClient(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("skipping unix only test for windows")
|
|
}
|
|
cases := []struct {
|
|
envs map[string]string
|
|
expectedError string
|
|
expectedVersion string
|
|
}{
|
|
{
|
|
envs: map[string]string{},
|
|
expectedVersion: api.DefaultVersion,
|
|
},
|
|
{
|
|
envs: map[string]string{
|
|
"DOCKER_CERT_PATH": "invalid/path",
|
|
},
|
|
expectedError: "Could not load X509 key pair: open invalid/path/cert.pem: no such file or directory",
|
|
},
|
|
{
|
|
envs: map[string]string{
|
|
"DOCKER_CERT_PATH": "testdata/",
|
|
},
|
|
expectedVersion: api.DefaultVersion,
|
|
},
|
|
{
|
|
envs: map[string]string{
|
|
"DOCKER_CERT_PATH": "testdata/",
|
|
"DOCKER_TLS_VERIFY": "1",
|
|
},
|
|
expectedVersion: api.DefaultVersion,
|
|
},
|
|
{
|
|
envs: map[string]string{
|
|
"DOCKER_CERT_PATH": "testdata/",
|
|
"DOCKER_HOST": "https://notaunixsocket",
|
|
},
|
|
expectedVersion: api.DefaultVersion,
|
|
},
|
|
{
|
|
envs: map[string]string{
|
|
"DOCKER_HOST": "host",
|
|
},
|
|
expectedError: "unable to parse docker host `host`",
|
|
},
|
|
{
|
|
envs: map[string]string{
|
|
"DOCKER_HOST": "invalid://url",
|
|
},
|
|
expectedVersion: api.DefaultVersion,
|
|
},
|
|
{
|
|
envs: map[string]string{
|
|
"DOCKER_API_VERSION": "anything",
|
|
},
|
|
expectedVersion: "anything",
|
|
},
|
|
{
|
|
envs: map[string]string{
|
|
"DOCKER_API_VERSION": "1.22",
|
|
},
|
|
expectedVersion: "1.22",
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
recoverEnvs := setupEnvs(t, c.envs)
|
|
apiclient, err := NewEnvClient()
|
|
if c.expectedError != "" {
|
|
if err == nil {
|
|
t.Errorf("expected an error for %v", c)
|
|
} else if err.Error() != c.expectedError {
|
|
t.Errorf("expected an error %s, got %s, for %v", c.expectedError, err.Error(), c)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
version := apiclient.ClientVersion()
|
|
if version != c.expectedVersion {
|
|
t.Errorf("expected %s, got %s, for %v", c.expectedVersion, version, c)
|
|
}
|
|
}
|
|
|
|
if c.envs["DOCKER_TLS_VERIFY"] != "" {
|
|
// pedantic checking that this is handled correctly
|
|
tr := apiclient.client.Transport.(*http.Transport)
|
|
if tr.TLSClientConfig == nil {
|
|
t.Error("no TLS config found when DOCKER_TLS_VERIFY enabled")
|
|
}
|
|
|
|
if tr.TLSClientConfig.InsecureSkipVerify {
|
|
t.Error("TLS verification should be enabled")
|
|
}
|
|
}
|
|
|
|
recoverEnvs(t)
|
|
}
|
|
}
|
|
|
|
func setupEnvs(t *testing.T, envs map[string]string) func(*testing.T) {
|
|
oldEnvs := map[string]string{}
|
|
for key, value := range envs {
|
|
oldEnv := os.Getenv(key)
|
|
oldEnvs[key] = oldEnv
|
|
err := os.Setenv(key, value)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
return func(t *testing.T) {
|
|
for key, value := range oldEnvs {
|
|
err := os.Setenv(key, value)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetAPIPath(t *testing.T) {
|
|
cases := []struct {
|
|
v string
|
|
p string
|
|
q url.Values
|
|
e string
|
|
}{
|
|
{"", "/containers/json", nil, "/containers/json"},
|
|
{"", "/containers/json", url.Values{}, "/containers/json"},
|
|
{"", "/containers/json", url.Values{"s": []string{"c"}}, "/containers/json?s=c"},
|
|
{"1.22", "/containers/json", nil, "/v1.22/containers/json"},
|
|
{"1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"},
|
|
{"1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"},
|
|
{"v1.22", "/containers/json", nil, "/v1.22/containers/json"},
|
|
{"v1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"},
|
|
{"v1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"},
|
|
{"v1.22", "/networks/kiwl$%^", nil, "/v1.22/networks/kiwl$%25%5E"},
|
|
}
|
|
|
|
for _, cs := range cases {
|
|
c, err := NewClient("unix:///var/run/docker.sock", cs.v, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
g := c.getAPIPath(cs.p, cs.q)
|
|
if g != cs.e {
|
|
t.Fatalf("Expected %s, got %s", cs.e, g)
|
|
}
|
|
|
|
err = c.Close()
|
|
if nil != err {
|
|
t.Fatalf("close client failed, error message: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseHost(t *testing.T) {
|
|
cases := []struct {
|
|
host string
|
|
proto string
|
|
addr string
|
|
base string
|
|
err bool
|
|
}{
|
|
{"", "", "", "", true},
|
|
{"foobar", "", "", "", true},
|
|
{"foo://bar", "foo", "bar", "", false},
|
|
{"tcp://localhost:2476", "tcp", "localhost:2476", "", false},
|
|
{"tcp://localhost:2476/path", "tcp", "localhost:2476", "/path", false},
|
|
}
|
|
|
|
for _, cs := range cases {
|
|
p, a, b, e := ParseHost(cs.host)
|
|
if cs.err && e == nil {
|
|
t.Fatalf("expected error, got nil")
|
|
}
|
|
if !cs.err && e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
if cs.proto != p {
|
|
t.Fatalf("expected proto %s, got %s", cs.proto, p)
|
|
}
|
|
if cs.addr != a {
|
|
t.Fatalf("expected addr %s, got %s", cs.addr, a)
|
|
}
|
|
if cs.base != b {
|
|
t.Fatalf("expected base %s, got %s", cs.base, b)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpdateClientVersion(t *testing.T) {
|
|
client := &Client{
|
|
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
|
splitQuery := strings.Split(req.URL.Path, "/")
|
|
queryVersion := splitQuery[1]
|
|
b, err := json.Marshal(types.Version{
|
|
APIVersion: queryVersion,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Body: ioutil.NopCloser(bytes.NewReader(b)),
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
cases := []struct {
|
|
v string
|
|
}{
|
|
{"1.20"},
|
|
{"v1.21"},
|
|
{"1.22"},
|
|
{"v1.22"},
|
|
}
|
|
|
|
for _, cs := range cases {
|
|
client.UpdateClientVersion(cs.v)
|
|
r, err := client.ServerVersion(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.TrimPrefix(r.APIVersion, "v") != strings.TrimPrefix(cs.v, "v") {
|
|
t.Fatalf("Expected %s, got %s", cs.v, r.APIVersion)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNewEnvClientSetsDefaultVersion(t *testing.T) {
|
|
// Unset environment variables
|
|
envVarKeys := []string{
|
|
"DOCKER_HOST",
|
|
"DOCKER_API_VERSION",
|
|
"DOCKER_TLS_VERIFY",
|
|
"DOCKER_CERT_PATH",
|
|
}
|
|
envVarValues := make(map[string]string)
|
|
for _, key := range envVarKeys {
|
|
envVarValues[key] = os.Getenv(key)
|
|
os.Setenv(key, "")
|
|
}
|
|
|
|
client, err := NewEnvClient()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if client.version != api.DefaultVersion {
|
|
t.Fatalf("Expected %s, got %s", api.DefaultVersion, client.version)
|
|
}
|
|
|
|
expected := "1.22"
|
|
os.Setenv("DOCKER_API_VERSION", expected)
|
|
client, err = NewEnvClient()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if client.version != expected {
|
|
t.Fatalf("Expected %s, got %s", expected, client.version)
|
|
}
|
|
|
|
// Restore environment variables
|
|
for _, key := range envVarKeys {
|
|
os.Setenv(key, envVarValues[key])
|
|
}
|
|
}
|
|
|
|
type roundTripFunc func(*http.Request) (*http.Response, error)
|
|
|
|
func (rtf roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
return rtf(req)
|
|
}
|
|
|
|
type bytesBufferClose struct {
|
|
*bytes.Buffer
|
|
}
|
|
|
|
func (bbc bytesBufferClose) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func TestClientRedirect(t *testing.T) {
|
|
client := &http.Client{
|
|
CheckRedirect: CheckRedirect,
|
|
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
if req.URL.String() == "/bla" {
|
|
return &http.Response{StatusCode: 404}, nil
|
|
}
|
|
return &http.Response{
|
|
StatusCode: 301,
|
|
Header: map[string][]string{"Location": {"/bla"}},
|
|
Body: bytesBufferClose{bytes.NewBuffer(nil)},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
cases := []struct {
|
|
httpMethod string
|
|
expectedErr error
|
|
statusCode int
|
|
}{
|
|
{http.MethodGet, nil, 301},
|
|
{http.MethodPost, &url.Error{Op: "Post", URL: "/bla", Err: ErrRedirect}, 301},
|
|
{http.MethodPut, &url.Error{Op: "Put", URL: "/bla", Err: ErrRedirect}, 301},
|
|
{http.MethodDelete, &url.Error{Op: "Delete", URL: "/bla", Err: ErrRedirect}, 301},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
req, err := http.NewRequest(tc.httpMethod, "/redirectme", nil)
|
|
assert.NoError(t, err)
|
|
resp, err := client.Do(req)
|
|
assert.Equal(t, tc.expectedErr, err)
|
|
assert.Equal(t, tc.statusCode, resp.StatusCode)
|
|
}
|
|
}
|