github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/client/client_test.go (about)

     1  package client // import "github.com/docker/docker/client"
     2  
     3  import (
     4  	"bytes"
     5  	"net/http"
     6  	"net/url"
     7  	"os"
     8  	"runtime"
     9  	"testing"
    10  
    11  	"github.com/docker/docker/api"
    12  	"github.com/docker/docker/api/types"
    13  	"gotest.tools/assert"
    14  	is "gotest.tools/assert/cmp"
    15  	"gotest.tools/env"
    16  	"gotest.tools/skip"
    17  )
    18  
    19  func TestNewClientWithOpsFromEnv(t *testing.T) {
    20  	skip.If(t, runtime.GOOS == "windows")
    21  
    22  	testcases := []struct {
    23  		doc             string
    24  		envs            map[string]string
    25  		expectedError   string
    26  		expectedVersion string
    27  	}{
    28  		{
    29  			doc:             "default api version",
    30  			envs:            map[string]string{},
    31  			expectedVersion: api.DefaultVersion,
    32  		},
    33  		{
    34  			doc: "invalid cert path",
    35  			envs: map[string]string{
    36  				"DOCKER_CERT_PATH": "invalid/path",
    37  			},
    38  			expectedError: "Could not load X509 key pair: open invalid/path/cert.pem: no such file or directory",
    39  		},
    40  		{
    41  			doc: "default api version with cert path",
    42  			envs: map[string]string{
    43  				"DOCKER_CERT_PATH": "testdata/",
    44  			},
    45  			expectedVersion: api.DefaultVersion,
    46  		},
    47  		{
    48  			doc: "default api version with cert path and tls verify",
    49  			envs: map[string]string{
    50  				"DOCKER_CERT_PATH":  "testdata/",
    51  				"DOCKER_TLS_VERIFY": "1",
    52  			},
    53  			expectedVersion: api.DefaultVersion,
    54  		},
    55  		{
    56  			doc: "default api version with cert path and host",
    57  			envs: map[string]string{
    58  				"DOCKER_CERT_PATH": "testdata/",
    59  				"DOCKER_HOST":      "https://notaunixsocket",
    60  			},
    61  			expectedVersion: api.DefaultVersion,
    62  		},
    63  		{
    64  			doc: "invalid docker host",
    65  			envs: map[string]string{
    66  				"DOCKER_HOST": "host",
    67  			},
    68  			expectedError: "unable to parse docker host `host`",
    69  		},
    70  		{
    71  			doc: "invalid docker host, with good format",
    72  			envs: map[string]string{
    73  				"DOCKER_HOST": "invalid://url",
    74  			},
    75  			expectedVersion: api.DefaultVersion,
    76  		},
    77  		{
    78  			doc: "override api version",
    79  			envs: map[string]string{
    80  				"DOCKER_API_VERSION": "1.22",
    81  			},
    82  			expectedVersion: "1.22",
    83  		},
    84  	}
    85  
    86  	defer env.PatchAll(t, nil)()
    87  	for _, c := range testcases {
    88  		env.PatchAll(t, c.envs)
    89  		apiclient, err := NewClientWithOpts(FromEnv)
    90  		if c.expectedError != "" {
    91  			assert.Check(t, is.Error(err, c.expectedError), c.doc)
    92  		} else {
    93  			assert.Check(t, err, c.doc)
    94  			version := apiclient.ClientVersion()
    95  			assert.Check(t, is.Equal(c.expectedVersion, version), c.doc)
    96  		}
    97  
    98  		if c.envs["DOCKER_TLS_VERIFY"] != "" {
    99  			// pedantic checking that this is handled correctly
   100  			tr := apiclient.client.Transport.(*http.Transport)
   101  			assert.Assert(t, tr.TLSClientConfig != nil, c.doc)
   102  			assert.Check(t, is.Equal(tr.TLSClientConfig.InsecureSkipVerify, false), c.doc)
   103  		}
   104  	}
   105  }
   106  
   107  func TestGetAPIPath(t *testing.T) {
   108  	testcases := []struct {
   109  		version  string
   110  		path     string
   111  		query    url.Values
   112  		expected string
   113  	}{
   114  		{"", "/containers/json", nil, "/containers/json"},
   115  		{"", "/containers/json", url.Values{}, "/containers/json"},
   116  		{"", "/containers/json", url.Values{"s": []string{"c"}}, "/containers/json?s=c"},
   117  		{"1.22", "/containers/json", nil, "/v1.22/containers/json"},
   118  		{"1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"},
   119  		{"1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"},
   120  		{"v1.22", "/containers/json", nil, "/v1.22/containers/json"},
   121  		{"v1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"},
   122  		{"v1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"},
   123  		{"v1.22", "/networks/kiwl$%^", nil, "/v1.22/networks/kiwl$%25%5E"},
   124  	}
   125  
   126  	for _, testcase := range testcases {
   127  		c := Client{version: testcase.version, basePath: "/"}
   128  		actual := c.getAPIPath(testcase.path, testcase.query)
   129  		assert.Check(t, is.Equal(actual, testcase.expected))
   130  	}
   131  }
   132  
   133  func TestParseHostURL(t *testing.T) {
   134  	testcases := []struct {
   135  		host        string
   136  		expected    *url.URL
   137  		expectedErr string
   138  	}{
   139  		{
   140  			host:        "",
   141  			expectedErr: "unable to parse docker host",
   142  		},
   143  		{
   144  			host:        "foobar",
   145  			expectedErr: "unable to parse docker host",
   146  		},
   147  		{
   148  			host:     "foo://bar",
   149  			expected: &url.URL{Scheme: "foo", Host: "bar"},
   150  		},
   151  		{
   152  			host:     "tcp://localhost:2476",
   153  			expected: &url.URL{Scheme: "tcp", Host: "localhost:2476"},
   154  		},
   155  		{
   156  			host:     "tcp://localhost:2476/path",
   157  			expected: &url.URL{Scheme: "tcp", Host: "localhost:2476", Path: "/path"},
   158  		},
   159  	}
   160  
   161  	for _, testcase := range testcases {
   162  		actual, err := ParseHostURL(testcase.host)
   163  		if testcase.expectedErr != "" {
   164  			assert.Check(t, is.ErrorContains(err, testcase.expectedErr))
   165  		}
   166  		assert.Check(t, is.DeepEqual(testcase.expected, actual))
   167  	}
   168  }
   169  
   170  func TestNewClientWithOpsFromEnvSetsDefaultVersion(t *testing.T) {
   171  	defer env.PatchAll(t, map[string]string{
   172  		"DOCKER_HOST":        "",
   173  		"DOCKER_API_VERSION": "",
   174  		"DOCKER_TLS_VERIFY":  "",
   175  		"DOCKER_CERT_PATH":   "",
   176  	})()
   177  
   178  	client, err := NewClientWithOpts(FromEnv)
   179  	if err != nil {
   180  		t.Fatal(err)
   181  	}
   182  	assert.Check(t, is.Equal(client.version, api.DefaultVersion))
   183  
   184  	expected := "1.22"
   185  	os.Setenv("DOCKER_API_VERSION", expected)
   186  	client, err = NewClientWithOpts(FromEnv)
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  	assert.Check(t, is.Equal(expected, client.version))
   191  }
   192  
   193  // TestNegotiateAPIVersionEmpty asserts that client.Client can
   194  // negotiate a compatible APIVersion when omitted
   195  func TestNegotiateAPIVersionEmpty(t *testing.T) {
   196  	defer env.PatchAll(t, map[string]string{"DOCKER_API_VERSION": ""})()
   197  
   198  	client, err := NewClientWithOpts(FromEnv)
   199  	assert.NilError(t, err)
   200  
   201  	ping := types.Ping{
   202  		APIVersion:   "",
   203  		OSType:       "linux",
   204  		Experimental: false,
   205  	}
   206  
   207  	// set our version to something new
   208  	client.version = "1.25"
   209  
   210  	// if no version from server, expect the earliest
   211  	// version before APIVersion was implemented
   212  	expected := "1.24"
   213  
   214  	// test downgrade
   215  	client.NegotiateAPIVersionPing(ping)
   216  	assert.Check(t, is.Equal(expected, client.version))
   217  }
   218  
   219  // TestNegotiateAPIVersion asserts that client.Client can
   220  // negotiate a compatible APIVersion with the server
   221  func TestNegotiateAPIVersion(t *testing.T) {
   222  	client, err := NewClientWithOpts(FromEnv)
   223  	assert.NilError(t, err)
   224  
   225  	expected := "1.21"
   226  	ping := types.Ping{
   227  		APIVersion:   expected,
   228  		OSType:       "linux",
   229  		Experimental: false,
   230  	}
   231  
   232  	// set our version to something new
   233  	client.version = "1.22"
   234  
   235  	// test downgrade
   236  	client.NegotiateAPIVersionPing(ping)
   237  	assert.Check(t, is.Equal(expected, client.version))
   238  
   239  	// set the client version to something older, and verify that we keep the
   240  	// original setting.
   241  	expected = "1.20"
   242  	client.version = expected
   243  	client.NegotiateAPIVersionPing(ping)
   244  	assert.Check(t, is.Equal(expected, client.version))
   245  
   246  }
   247  
   248  // TestNegotiateAPIVersionOverride asserts that we honor
   249  // the environment variable DOCKER_API_VERSION when negotiating versions
   250  func TestNegotiateAPVersionOverride(t *testing.T) {
   251  	expected := "9.99"
   252  	defer env.PatchAll(t, map[string]string{"DOCKER_API_VERSION": expected})()
   253  
   254  	client, err := NewClientWithOpts(FromEnv)
   255  	assert.NilError(t, err)
   256  
   257  	ping := types.Ping{
   258  		APIVersion:   "1.24",
   259  		OSType:       "linux",
   260  		Experimental: false,
   261  	}
   262  
   263  	// test that we honored the env var
   264  	client.NegotiateAPIVersionPing(ping)
   265  	assert.Check(t, is.Equal(expected, client.version))
   266  }
   267  
   268  type roundTripFunc func(*http.Request) (*http.Response, error)
   269  
   270  func (rtf roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
   271  	return rtf(req)
   272  }
   273  
   274  type bytesBufferClose struct {
   275  	*bytes.Buffer
   276  }
   277  
   278  func (bbc bytesBufferClose) Close() error {
   279  	return nil
   280  }
   281  
   282  func TestClientRedirect(t *testing.T) {
   283  	client := &http.Client{
   284  		CheckRedirect: CheckRedirect,
   285  		Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
   286  			if req.URL.String() == "/bla" {
   287  				return &http.Response{StatusCode: 404}, nil
   288  			}
   289  			return &http.Response{
   290  				StatusCode: 301,
   291  				Header:     map[string][]string{"Location": {"/bla"}},
   292  				Body:       bytesBufferClose{bytes.NewBuffer(nil)},
   293  			}, nil
   294  		}),
   295  	}
   296  
   297  	cases := []struct {
   298  		httpMethod  string
   299  		expectedErr *url.Error
   300  		statusCode  int
   301  	}{
   302  		{http.MethodGet, nil, 301},
   303  		{http.MethodPost, &url.Error{Op: "Post", URL: "/bla", Err: ErrRedirect}, 301},
   304  		{http.MethodPut, &url.Error{Op: "Put", URL: "/bla", Err: ErrRedirect}, 301},
   305  		{http.MethodDelete, &url.Error{Op: "Delete", URL: "/bla", Err: ErrRedirect}, 301},
   306  	}
   307  
   308  	for _, tc := range cases {
   309  		req, err := http.NewRequest(tc.httpMethod, "/redirectme", nil)
   310  		assert.Check(t, err)
   311  		resp, err := client.Do(req)
   312  		assert.Check(t, is.Equal(tc.statusCode, resp.StatusCode))
   313  		if tc.expectedErr == nil {
   314  			assert.Check(t, is.Nil(err))
   315  		} else {
   316  			urlError, ok := err.(*url.Error)
   317  			assert.Assert(t, ok, "%T is not *url.Error", err)
   318  			assert.Check(t, is.Equal(*tc.expectedErr, *urlError))
   319  		}
   320  	}
   321  }