github.com/adityamillind98/moby@v23.0.0-rc.4+incompatible/client/client_test.go (about) 1 package client // import "github.com/docker/docker/client" 2 3 import ( 4 "bytes" 5 "context" 6 "io" 7 "net/http" 8 "net/url" 9 "runtime" 10 "strings" 11 "testing" 12 13 "github.com/docker/docker/api" 14 "github.com/docker/docker/api/types" 15 "gotest.tools/v3/assert" 16 is "gotest.tools/v3/assert/cmp" 17 "gotest.tools/v3/env" 18 "gotest.tools/v3/skip" 19 ) 20 21 func TestNewClientWithOpsFromEnv(t *testing.T) { 22 skip.If(t, runtime.GOOS == "windows") 23 24 testcases := []struct { 25 doc string 26 envs map[string]string 27 expectedError string 28 expectedVersion string 29 }{ 30 { 31 doc: "default api version", 32 envs: map[string]string{}, 33 expectedVersion: api.DefaultVersion, 34 }, 35 { 36 doc: "invalid cert path", 37 envs: map[string]string{ 38 "DOCKER_CERT_PATH": "invalid/path", 39 }, 40 expectedError: "Could not load X509 key pair: open invalid/path/cert.pem: no such file or directory", 41 }, 42 { 43 doc: "default api version with cert path", 44 envs: map[string]string{ 45 "DOCKER_CERT_PATH": "testdata/", 46 }, 47 expectedVersion: api.DefaultVersion, 48 }, 49 { 50 doc: "default api version with cert path and tls verify", 51 envs: map[string]string{ 52 "DOCKER_CERT_PATH": "testdata/", 53 "DOCKER_TLS_VERIFY": "1", 54 }, 55 expectedVersion: api.DefaultVersion, 56 }, 57 { 58 doc: "default api version with cert path and host", 59 envs: map[string]string{ 60 "DOCKER_CERT_PATH": "testdata/", 61 "DOCKER_HOST": "https://notaunixsocket", 62 }, 63 expectedVersion: api.DefaultVersion, 64 }, 65 { 66 doc: "invalid docker host", 67 envs: map[string]string{ 68 "DOCKER_HOST": "host", 69 }, 70 expectedError: "unable to parse docker host `host`", 71 }, 72 { 73 doc: "invalid docker host, with good format", 74 envs: map[string]string{ 75 "DOCKER_HOST": "invalid://url", 76 }, 77 expectedVersion: api.DefaultVersion, 78 }, 79 { 80 doc: "override api version", 81 envs: map[string]string{ 82 "DOCKER_API_VERSION": "1.22", 83 }, 84 expectedVersion: "1.22", 85 }, 86 } 87 88 defer env.PatchAll(t, nil)() 89 for _, tc := range testcases { 90 env.PatchAll(t, tc.envs) 91 client, err := NewClientWithOpts(FromEnv) 92 if tc.expectedError != "" { 93 assert.Check(t, is.Error(err, tc.expectedError), tc.doc) 94 } else { 95 assert.Check(t, err, tc.doc) 96 assert.Check(t, is.Equal(client.ClientVersion(), tc.expectedVersion), tc.doc) 97 } 98 99 if tc.envs["DOCKER_TLS_VERIFY"] != "" { 100 // pedantic checking that this is handled correctly 101 tr := client.client.Transport.(*http.Transport) 102 assert.Assert(t, tr.TLSClientConfig != nil, tc.doc) 103 assert.Check(t, is.Equal(tr.TLSClientConfig.InsecureSkipVerify, false), tc.doc) 104 } 105 } 106 } 107 108 func TestGetAPIPath(t *testing.T) { 109 testcases := []struct { 110 version string 111 path string 112 query url.Values 113 expected string 114 }{ 115 {"", "/containers/json", nil, "/v" + api.DefaultVersion + "/containers/json"}, 116 {"", "/containers/json", url.Values{}, "/v" + api.DefaultVersion + "/containers/json"}, 117 {"", "/containers/json", url.Values{"s": []string{"c"}}, "/v" + api.DefaultVersion + "/containers/json?s=c"}, 118 {"1.22", "/containers/json", nil, "/v1.22/containers/json"}, 119 {"1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"}, 120 {"1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"}, 121 {"v1.22", "/containers/json", nil, "/v1.22/containers/json"}, 122 {"v1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"}, 123 {"v1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"}, 124 {"v1.22", "/networks/kiwl$%^", nil, "/v1.22/networks/kiwl$%25%5E"}, 125 } 126 127 ctx := context.TODO() 128 for _, tc := range testcases { 129 client, err := NewClientWithOpts( 130 WithVersion(tc.version), 131 WithHost("tcp://localhost:2375"), 132 ) 133 assert.NilError(t, err) 134 actual := client.getAPIPath(ctx, tc.path, tc.query) 135 assert.Check(t, is.Equal(actual, tc.expected)) 136 } 137 } 138 139 func TestParseHostURL(t *testing.T) { 140 testcases := []struct { 141 host string 142 expected *url.URL 143 expectedErr string 144 }{ 145 { 146 host: "", 147 expectedErr: "unable to parse docker host", 148 }, 149 { 150 host: "foobar", 151 expectedErr: "unable to parse docker host", 152 }, 153 { 154 host: "foo://bar", 155 expected: &url.URL{Scheme: "foo", Host: "bar"}, 156 }, 157 { 158 host: "tcp://localhost:2476", 159 expected: &url.URL{Scheme: "tcp", Host: "localhost:2476"}, 160 }, 161 { 162 host: "tcp://localhost:2476/path", 163 expected: &url.URL{Scheme: "tcp", Host: "localhost:2476", Path: "/path"}, 164 }, 165 } 166 167 for _, testcase := range testcases { 168 actual, err := ParseHostURL(testcase.host) 169 if testcase.expectedErr != "" { 170 assert.Check(t, is.ErrorContains(err, testcase.expectedErr)) 171 } 172 assert.Check(t, is.DeepEqual(actual, testcase.expected)) 173 } 174 } 175 176 func TestNewClientWithOpsFromEnvSetsDefaultVersion(t *testing.T) { 177 env.PatchAll(t, map[string]string{ 178 "DOCKER_HOST": "", 179 "DOCKER_API_VERSION": "", 180 "DOCKER_TLS_VERIFY": "", 181 "DOCKER_CERT_PATH": "", 182 }) 183 184 client, err := NewClientWithOpts(FromEnv) 185 if err != nil { 186 t.Fatal(err) 187 } 188 assert.Check(t, is.Equal(client.ClientVersion(), api.DefaultVersion)) 189 190 const expected = "1.22" 191 t.Setenv("DOCKER_API_VERSION", expected) 192 client, err = NewClientWithOpts(FromEnv) 193 if err != nil { 194 t.Fatal(err) 195 } 196 assert.Check(t, is.Equal(client.ClientVersion(), expected)) 197 } 198 199 // TestNegotiateAPIVersionEmpty asserts that client.Client version negotiation 200 // downgrades to the correct API version if the API's ping response does not 201 // return an API version. 202 func TestNegotiateAPIVersionEmpty(t *testing.T) { 203 t.Setenv("DOCKER_API_VERSION", "") 204 205 client, err := NewClientWithOpts(FromEnv) 206 assert.NilError(t, err) 207 208 // set our version to something new 209 client.version = "1.25" 210 211 // if no version from server, expect the earliest 212 // version before APIVersion was implemented 213 const expected = "1.24" 214 215 // test downgrade 216 client.NegotiateAPIVersionPing(types.Ping{}) 217 assert.Equal(t, client.ClientVersion(), expected) 218 } 219 220 // TestNegotiateAPIVersion asserts that client.Client can 221 // negotiate a compatible APIVersion with the server 222 func TestNegotiateAPIVersion(t *testing.T) { 223 tests := []struct { 224 doc string 225 clientVersion string 226 pingVersion string 227 expectedVersion string 228 }{ 229 { 230 // client should downgrade to the version reported by the daemon. 231 doc: "downgrade from default", 232 pingVersion: "1.21", 233 expectedVersion: "1.21", 234 }, 235 { 236 // client should not downgrade to the version reported by the 237 // daemon if a custom version was set. 238 doc: "no downgrade from custom version", 239 clientVersion: "1.25", 240 pingVersion: "1.21", 241 expectedVersion: "1.25", 242 }, 243 { 244 // client should downgrade to the last version before version 245 // negotiation was added (1.24) if the daemon does not report 246 // a version. 247 doc: "downgrade legacy", 248 pingVersion: "", 249 expectedVersion: "1.24", 250 }, 251 { 252 // client should downgrade to the version reported by the daemon. 253 // version negotiation was added in API 1.25, so this is theoretical, 254 // but it should negotiate to versions before that if the daemon 255 // gives that as a response. 256 doc: "downgrade old", 257 pingVersion: "1.19", 258 expectedVersion: "1.19", 259 }, 260 { 261 // client should not upgrade to a newer version if a version was set, 262 // even if both the daemon and the client support it. 263 doc: "no upgrade", 264 clientVersion: "1.20", 265 pingVersion: "1.21", 266 expectedVersion: "1.20", 267 }, 268 } 269 270 for _, tc := range tests { 271 tc := tc 272 t.Run(tc.doc, func(t *testing.T) { 273 opts := make([]Opt, 0) 274 if tc.clientVersion != "" { 275 // Note that this check is redundant, as WithVersion() considers 276 // an empty version equivalent to "not setting a version", but 277 // doing this just to be explicit we are using the default. 278 opts = append(opts, WithVersion(tc.clientVersion)) 279 } 280 client, err := NewClientWithOpts(opts...) 281 assert.NilError(t, err) 282 client.NegotiateAPIVersionPing(types.Ping{APIVersion: tc.pingVersion}) 283 assert.Equal(t, tc.expectedVersion, client.ClientVersion()) 284 }) 285 } 286 } 287 288 // TestNegotiateAPIVersionOverride asserts that we honor the DOCKER_API_VERSION 289 // environment variable when negotiating versions. 290 func TestNegotiateAPVersionOverride(t *testing.T) { 291 const expected = "9.99" 292 t.Setenv("DOCKER_API_VERSION", expected) 293 294 client, err := NewClientWithOpts(FromEnv) 295 assert.NilError(t, err) 296 297 // test that we honored the env var 298 client.NegotiateAPIVersionPing(types.Ping{APIVersion: "1.24"}) 299 assert.Equal(t, client.ClientVersion(), expected) 300 } 301 302 func TestNegotiateAPIVersionAutomatic(t *testing.T) { 303 var pingVersion string 304 httpClient := newMockClient(func(req *http.Request) (*http.Response, error) { 305 resp := &http.Response{StatusCode: http.StatusOK, Header: http.Header{}} 306 resp.Header.Set("API-Version", pingVersion) 307 resp.Body = io.NopCloser(strings.NewReader("OK")) 308 return resp, nil 309 }) 310 311 ctx := context.Background() 312 client, err := NewClientWithOpts( 313 WithHTTPClient(httpClient), 314 WithAPIVersionNegotiation(), 315 ) 316 assert.NilError(t, err) 317 318 // Client defaults to use api.DefaultVersion before version-negotiation. 319 expected := api.DefaultVersion 320 assert.Equal(t, client.ClientVersion(), expected) 321 322 // First request should trigger negotiation 323 pingVersion = "1.35" 324 expected = "1.35" 325 _, _ = client.Info(ctx) 326 assert.Equal(t, client.ClientVersion(), expected) 327 328 // Once successfully negotiated, subsequent requests should not re-negotiate 329 pingVersion = "1.25" 330 expected = "1.35" 331 _, _ = client.Info(ctx) 332 assert.Equal(t, client.ClientVersion(), expected) 333 } 334 335 // TestNegotiateAPIVersionWithEmptyVersion asserts that initializing a client 336 // with an empty version string does still allow API-version negotiation 337 func TestNegotiateAPIVersionWithEmptyVersion(t *testing.T) { 338 client, err := NewClientWithOpts(WithVersion("")) 339 assert.NilError(t, err) 340 341 const expected = "1.35" 342 client.NegotiateAPIVersionPing(types.Ping{APIVersion: expected}) 343 assert.Equal(t, client.ClientVersion(), expected) 344 } 345 346 // TestNegotiateAPIVersionWithFixedVersion asserts that initializing a client 347 // with a fixed version disables API-version negotiation 348 func TestNegotiateAPIVersionWithFixedVersion(t *testing.T) { 349 const customVersion = "1.35" 350 client, err := NewClientWithOpts(WithVersion(customVersion)) 351 assert.NilError(t, err) 352 353 client.NegotiateAPIVersionPing(types.Ping{APIVersion: "1.31"}) 354 assert.Equal(t, client.ClientVersion(), customVersion) 355 } 356 357 type roundTripFunc func(*http.Request) (*http.Response, error) 358 359 func (rtf roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { 360 return rtf(req) 361 } 362 363 type bytesBufferClose struct { 364 *bytes.Buffer 365 } 366 367 func (bbc bytesBufferClose) Close() error { 368 return nil 369 } 370 371 func TestClientRedirect(t *testing.T) { 372 client := &http.Client{ 373 CheckRedirect: CheckRedirect, 374 Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) { 375 if req.URL.String() == "/bla" { 376 return &http.Response{StatusCode: 404}, nil 377 } 378 return &http.Response{ 379 StatusCode: 301, 380 Header: map[string][]string{"Location": {"/bla"}}, 381 Body: bytesBufferClose{bytes.NewBuffer(nil)}, 382 }, nil 383 }), 384 } 385 386 cases := []struct { 387 httpMethod string 388 expectedErr *url.Error 389 statusCode int 390 }{ 391 {http.MethodGet, nil, 301}, 392 {http.MethodPost, &url.Error{Op: "Post", URL: "/bla", Err: ErrRedirect}, 301}, 393 {http.MethodPut, &url.Error{Op: "Put", URL: "/bla", Err: ErrRedirect}, 301}, 394 {http.MethodDelete, &url.Error{Op: "Delete", URL: "/bla", Err: ErrRedirect}, 301}, 395 } 396 397 for _, tc := range cases { 398 tc := tc 399 t.Run(tc.httpMethod, func(t *testing.T) { 400 req, err := http.NewRequest(tc.httpMethod, "/redirectme", nil) 401 assert.Check(t, err) 402 resp, err := client.Do(req) 403 assert.Check(t, is.Equal(resp.StatusCode, tc.statusCode)) 404 if tc.expectedErr == nil { 405 assert.NilError(t, err) 406 } else { 407 urlError, ok := err.(*url.Error) 408 assert.Assert(t, ok, "%T is not *url.Error", err) 409 assert.Check(t, is.Equal(*urlError, *tc.expectedErr)) 410 } 411 }) 412 } 413 }