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 }