github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/client/client_test.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "net/http" 6 "net/url" 7 "os" 8 "runtime" 9 "strings" 10 "testing" 11 12 "github.com/docker/docker/api" 13 "github.com/docker/docker/api/types" 14 "github.com/docker/docker/internal/testutil" 15 "github.com/stretchr/testify/assert" 16 ) 17 18 func TestNewEnvClient(t *testing.T) { 19 if runtime.GOOS == "windows" { 20 t.Skip("skipping unix only test for windows") 21 } 22 cases := []struct { 23 envs map[string]string 24 expectedError string 25 expectedVersion string 26 }{ 27 { 28 envs: map[string]string{}, 29 expectedVersion: api.DefaultVersion, 30 }, 31 { 32 envs: map[string]string{ 33 "DOCKER_CERT_PATH": "invalid/path", 34 }, 35 expectedError: "Could not load X509 key pair: open invalid/path/cert.pem: no such file or directory", 36 }, 37 { 38 envs: map[string]string{ 39 "DOCKER_CERT_PATH": "testdata/", 40 }, 41 expectedVersion: api.DefaultVersion, 42 }, 43 { 44 envs: map[string]string{ 45 "DOCKER_CERT_PATH": "testdata/", 46 "DOCKER_TLS_VERIFY": "1", 47 }, 48 expectedVersion: api.DefaultVersion, 49 }, 50 { 51 envs: map[string]string{ 52 "DOCKER_CERT_PATH": "testdata/", 53 "DOCKER_HOST": "https://notaunixsocket", 54 }, 55 expectedVersion: api.DefaultVersion, 56 }, 57 { 58 envs: map[string]string{ 59 "DOCKER_HOST": "host", 60 }, 61 expectedError: "unable to parse docker host `host`", 62 }, 63 { 64 envs: map[string]string{ 65 "DOCKER_HOST": "invalid://url", 66 }, 67 expectedVersion: api.DefaultVersion, 68 }, 69 { 70 envs: map[string]string{ 71 "DOCKER_API_VERSION": "anything", 72 }, 73 expectedVersion: "anything", 74 }, 75 { 76 envs: map[string]string{ 77 "DOCKER_API_VERSION": "1.22", 78 }, 79 expectedVersion: "1.22", 80 }, 81 } 82 83 env := envToMap() 84 defer mapToEnv(env) 85 for _, c := range cases { 86 mapToEnv(env) 87 mapToEnv(c.envs) 88 apiclient, err := NewEnvClient() 89 if c.expectedError != "" { 90 assert.Error(t, err) 91 assert.Equal(t, c.expectedError, err.Error()) 92 } else { 93 assert.NoError(t, err) 94 version := apiclient.ClientVersion() 95 assert.Equal(t, c.expectedVersion, version) 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.NotNil(t, tr.TLSClientConfig) 102 assert.Equal(t, tr.TLSClientConfig.InsecureSkipVerify, false) 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.Equal(t, actual, testcase.expected) 130 } 131 } 132 133 func TestParseHost(t *testing.T) { 134 cases := []struct { 135 host string 136 proto string 137 addr string 138 base string 139 err bool 140 }{ 141 {"", "", "", "", true}, 142 {"foobar", "", "", "", true}, 143 {"foo://bar", "foo", "bar", "", false}, 144 {"tcp://localhost:2476", "tcp", "localhost:2476", "", false}, 145 {"tcp://localhost:2476/path", "tcp", "localhost:2476", "/path", false}, 146 } 147 148 for _, cs := range cases { 149 p, a, b, e := ParseHost(cs.host) 150 if cs.err { 151 assert.Error(t, e) 152 } 153 assert.Equal(t, cs.proto, p) 154 assert.Equal(t, cs.addr, a) 155 assert.Equal(t, cs.base, b) 156 } 157 } 158 159 func TestParseHostURL(t *testing.T) { 160 testcases := []struct { 161 host string 162 expected *url.URL 163 expectedErr string 164 }{ 165 { 166 host: "", 167 expectedErr: "unable to parse docker host", 168 }, 169 { 170 host: "foobar", 171 expectedErr: "unable to parse docker host", 172 }, 173 { 174 host: "foo://bar", 175 expected: &url.URL{Scheme: "foo", Host: "bar"}, 176 }, 177 { 178 host: "tcp://localhost:2476", 179 expected: &url.URL{Scheme: "tcp", Host: "localhost:2476"}, 180 }, 181 { 182 host: "tcp://localhost:2476/path", 183 expected: &url.URL{Scheme: "tcp", Host: "localhost:2476", Path: "/path"}, 184 }, 185 } 186 187 for _, testcase := range testcases { 188 actual, err := ParseHostURL(testcase.host) 189 if testcase.expectedErr != "" { 190 testutil.ErrorContains(t, err, testcase.expectedErr) 191 } 192 assert.Equal(t, testcase.expected, actual) 193 } 194 } 195 196 func TestNewEnvClientSetsDefaultVersion(t *testing.T) { 197 env := envToMap() 198 defer mapToEnv(env) 199 200 envMap := map[string]string{ 201 "DOCKER_HOST": "", 202 "DOCKER_API_VERSION": "", 203 "DOCKER_TLS_VERIFY": "", 204 "DOCKER_CERT_PATH": "", 205 } 206 mapToEnv(envMap) 207 208 client, err := NewEnvClient() 209 if err != nil { 210 t.Fatal(err) 211 } 212 assert.Equal(t, client.version, api.DefaultVersion) 213 214 expected := "1.22" 215 os.Setenv("DOCKER_API_VERSION", expected) 216 client, err = NewEnvClient() 217 if err != nil { 218 t.Fatal(err) 219 } 220 assert.Equal(t, expected, client.version) 221 } 222 223 // TestNegotiateAPIVersionEmpty asserts that client.Client can 224 // negotiate a compatible APIVersion when omitted 225 func TestNegotiateAPIVersionEmpty(t *testing.T) { 226 env := envToMap() 227 defer mapToEnv(env) 228 229 envMap := map[string]string{ 230 "DOCKER_API_VERSION": "", 231 } 232 mapToEnv(envMap) 233 234 client, err := NewEnvClient() 235 if err != nil { 236 t.Fatal(err) 237 } 238 239 ping := types.Ping{ 240 APIVersion: "", 241 OSType: "linux", 242 Experimental: false, 243 } 244 245 // set our version to something new 246 client.version = "1.25" 247 248 // if no version from server, expect the earliest 249 // version before APIVersion was implemented 250 expected := "1.24" 251 252 // test downgrade 253 client.NegotiateAPIVersionPing(ping) 254 assert.Equal(t, expected, client.version) 255 } 256 257 // TestNegotiateAPIVersion asserts that client.Client can 258 // negotiate a compatible APIVersion with the server 259 func TestNegotiateAPIVersion(t *testing.T) { 260 client, err := NewEnvClient() 261 if err != nil { 262 t.Fatal(err) 263 } 264 265 expected := "1.21" 266 267 ping := types.Ping{ 268 APIVersion: expected, 269 OSType: "linux", 270 Experimental: false, 271 } 272 273 // set our version to something new 274 client.version = "1.22" 275 276 // test downgrade 277 client.NegotiateAPIVersionPing(ping) 278 assert.Equal(t, expected, client.version) 279 280 // set the client version to something older, and verify that we keep the 281 // original setting. 282 expected = "1.20" 283 client.version = expected 284 client.NegotiateAPIVersionPing(ping) 285 assert.Equal(t, expected, client.version) 286 287 } 288 289 // TestNegotiateAPIVersionOverride asserts that we honor 290 // the environment variable DOCKER_API_VERSION when negotianing versions 291 func TestNegotiateAPVersionOverride(t *testing.T) { 292 env := envToMap() 293 defer mapToEnv(env) 294 295 envMap := map[string]string{ 296 "DOCKER_API_VERSION": "9.99", 297 } 298 mapToEnv(envMap) 299 300 client, err := NewEnvClient() 301 if err != nil { 302 t.Fatal(err) 303 } 304 305 ping := types.Ping{ 306 APIVersion: "1.24", 307 OSType: "linux", 308 Experimental: false, 309 } 310 311 expected := envMap["DOCKER_API_VERSION"] 312 313 // test that we honored the env var 314 client.NegotiateAPIVersionPing(ping) 315 assert.Equal(t, expected, client.version) 316 } 317 318 // mapToEnv takes a map of environment variables and sets them 319 func mapToEnv(env map[string]string) { 320 for k, v := range env { 321 os.Setenv(k, v) 322 } 323 } 324 325 // envToMap returns a map of environment variables 326 func envToMap() map[string]string { 327 env := make(map[string]string) 328 for _, e := range os.Environ() { 329 kv := strings.SplitAfterN(e, "=", 2) 330 env[kv[0]] = kv[1] 331 } 332 333 return env 334 } 335 336 type roundTripFunc func(*http.Request) (*http.Response, error) 337 338 func (rtf roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { 339 return rtf(req) 340 } 341 342 type bytesBufferClose struct { 343 *bytes.Buffer 344 } 345 346 func (bbc bytesBufferClose) Close() error { 347 return nil 348 } 349 350 func TestClientRedirect(t *testing.T) { 351 client := &http.Client{ 352 CheckRedirect: CheckRedirect, 353 Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) { 354 if req.URL.String() == "/bla" { 355 return &http.Response{StatusCode: 404}, nil 356 } 357 return &http.Response{ 358 StatusCode: 301, 359 Header: map[string][]string{"Location": {"/bla"}}, 360 Body: bytesBufferClose{bytes.NewBuffer(nil)}, 361 }, nil 362 }), 363 } 364 365 cases := []struct { 366 httpMethod string 367 expectedErr error 368 statusCode int 369 }{ 370 {http.MethodGet, nil, 301}, 371 {http.MethodPost, &url.Error{Op: "Post", URL: "/bla", Err: ErrRedirect}, 301}, 372 {http.MethodPut, &url.Error{Op: "Put", URL: "/bla", Err: ErrRedirect}, 301}, 373 {http.MethodDelete, &url.Error{Op: "Delete", URL: "/bla", Err: ErrRedirect}, 301}, 374 } 375 376 for _, tc := range cases { 377 req, err := http.NewRequest(tc.httpMethod, "/redirectme", nil) 378 assert.NoError(t, err) 379 resp, err := client.Do(req) 380 assert.Equal(t, tc.expectedErr, err) 381 assert.Equal(t, tc.statusCode, resp.StatusCode) 382 } 383 }