github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/integration-cli/docker_utils_test.go (about) 1 package main 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "path" 12 "path/filepath" 13 "strconv" 14 "strings" 15 "testing" 16 "time" 17 18 "github.com/docker/docker/api/types" 19 "github.com/docker/docker/client" 20 "github.com/docker/docker/integration-cli/cli" 21 "github.com/docker/docker/integration-cli/daemon" 22 "gotest.tools/v3/assert" 23 "gotest.tools/v3/assert/cmp" 24 "gotest.tools/v3/icmd" 25 "gotest.tools/v3/poll" 26 ) 27 28 func deleteImages(images ...string) error { 29 args := []string{dockerBinary, "rmi", "-f"} 30 return icmd.RunCmd(icmd.Cmd{Command: append(args, images...)}).Error 31 } 32 33 // Deprecated: use cli.Docker or cli.DockerCmd 34 func dockerCmdWithError(args ...string) (string, int, error) { 35 result := cli.Docker(cli.Args(args...)) 36 if result.Error != nil { 37 return result.Combined(), result.ExitCode, result.Compare(icmd.Success) 38 } 39 return result.Combined(), result.ExitCode, result.Error 40 } 41 42 // Deprecated: use cli.Docker or cli.DockerCmd 43 func dockerCmd(c testing.TB, args ...string) (string, int) { 44 c.Helper() 45 result := cli.DockerCmd(c, args...) 46 return result.Combined(), result.ExitCode 47 } 48 49 // Deprecated: use cli.Docker or cli.DockerCmd 50 func dockerCmdWithResult(args ...string) *icmd.Result { 51 return cli.Docker(cli.Args(args...)) 52 } 53 54 func findContainerIP(c *testing.T, id string, network string) string { 55 c.Helper() 56 out, _ := dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.IPAddress }}'", network), id) 57 return strings.Trim(out, " \r\n'") 58 } 59 60 func getContainerCount(c *testing.T) int { 61 c.Helper() 62 const containers = "Containers:" 63 64 result := icmd.RunCommand(dockerBinary, "info") 65 result.Assert(c, icmd.Success) 66 67 lines := strings.Split(result.Combined(), "\n") 68 for _, line := range lines { 69 if strings.Contains(line, containers) { 70 output := strings.TrimSpace(line) 71 output = strings.TrimPrefix(output, containers) 72 output = strings.Trim(output, " ") 73 containerCount, err := strconv.Atoi(output) 74 assert.NilError(c, err) 75 return containerCount 76 } 77 } 78 return 0 79 } 80 81 func inspectFieldAndUnmarshall(c *testing.T, name, field string, output interface{}) { 82 c.Helper() 83 str := inspectFieldJSON(c, name, field) 84 err := json.Unmarshal([]byte(str), output) 85 if c != nil { 86 assert.Assert(c, err == nil, "failed to unmarshal: %v", err) 87 } 88 } 89 90 // Deprecated: use cli.Inspect 91 func inspectFilter(name, filter string) (string, error) { 92 format := fmt.Sprintf("{{%s}}", filter) 93 result := icmd.RunCommand(dockerBinary, "inspect", "-f", format, name) 94 if result.Error != nil || result.ExitCode != 0 { 95 return "", fmt.Errorf("failed to inspect %s: %s", name, result.Combined()) 96 } 97 return strings.TrimSpace(result.Combined()), nil 98 } 99 100 // Deprecated: use cli.Inspect 101 func inspectFieldWithError(name, field string) (string, error) { 102 return inspectFilter(name, fmt.Sprintf(".%s", field)) 103 } 104 105 // Deprecated: use cli.Inspect 106 func inspectField(c *testing.T, name, field string) string { 107 c.Helper() 108 out, err := inspectFilter(name, fmt.Sprintf(".%s", field)) 109 if c != nil { 110 assert.NilError(c, err) 111 } 112 return out 113 } 114 115 // Deprecated: use cli.Inspect 116 func inspectFieldJSON(c *testing.T, name, field string) string { 117 c.Helper() 118 out, err := inspectFilter(name, fmt.Sprintf("json .%s", field)) 119 if c != nil { 120 assert.NilError(c, err) 121 } 122 return out 123 } 124 125 // Deprecated: use cli.Inspect 126 func inspectFieldMap(c *testing.T, name, path, field string) string { 127 c.Helper() 128 out, err := inspectFilter(name, fmt.Sprintf("index .%s %q", path, field)) 129 if c != nil { 130 assert.NilError(c, err) 131 } 132 return out 133 } 134 135 // Deprecated: use cli.Inspect 136 func inspectMountSourceField(name, destination string) (string, error) { 137 m, err := inspectMountPoint(name, destination) 138 if err != nil { 139 return "", err 140 } 141 return m.Source, nil 142 } 143 144 // Deprecated: use cli.Inspect 145 func inspectMountPoint(name, destination string) (types.MountPoint, error) { 146 out, err := inspectFilter(name, "json .Mounts") 147 if err != nil { 148 return types.MountPoint{}, err 149 } 150 151 return inspectMountPointJSON(out, destination) 152 } 153 154 var errMountNotFound = errors.New("mount point not found") 155 156 // Deprecated: use cli.Inspect 157 func inspectMountPointJSON(j, destination string) (types.MountPoint, error) { 158 var mp []types.MountPoint 159 if err := json.Unmarshal([]byte(j), &mp); err != nil { 160 return types.MountPoint{}, err 161 } 162 163 var m *types.MountPoint 164 for _, c := range mp { 165 if c.Destination == destination { 166 m = &c 167 break 168 } 169 } 170 171 if m == nil { 172 return types.MountPoint{}, errMountNotFound 173 } 174 175 return *m, nil 176 } 177 178 func getIDByName(c *testing.T, name string) string { 179 c.Helper() 180 id, err := inspectFieldWithError(name, "Id") 181 assert.NilError(c, err) 182 return id 183 } 184 185 // Deprecated: use cli.Build 186 func buildImageSuccessfully(c *testing.T, name string, cmdOperators ...cli.CmdOperator) { 187 c.Helper() 188 buildImage(name, cmdOperators...).Assert(c, icmd.Success) 189 } 190 191 // Deprecated: use cli.Build 192 func buildImage(name string, cmdOperators ...cli.CmdOperator) *icmd.Result { 193 return cli.Docker(cli.Build(name), cmdOperators...) 194 } 195 196 // Write `content` to the file at path `dst`, creating it if necessary, 197 // as well as any missing directories. 198 // The file is truncated if it already exists. 199 // Fail the test when error occurs. 200 func writeFile(dst, content string, c *testing.T) { 201 c.Helper() 202 // Create subdirectories if necessary 203 assert.Assert(c, os.MkdirAll(path.Dir(dst), 0700) == nil) 204 f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) 205 assert.NilError(c, err) 206 defer f.Close() 207 // Write content (truncate if it exists) 208 _, err = io.Copy(f, strings.NewReader(content)) 209 assert.NilError(c, err) 210 } 211 212 // Return the contents of file at path `src`. 213 // Fail the test when error occurs. 214 func readFile(src string, c *testing.T) (content string) { 215 c.Helper() 216 data, err := ioutil.ReadFile(src) 217 assert.NilError(c, err) 218 219 return string(data) 220 } 221 222 func containerStorageFile(containerID, basename string) string { 223 return filepath.Join(testEnv.PlatformDefaults.ContainerStoragePath, containerID, basename) 224 } 225 226 // docker commands that use this function must be run with the '-d' switch. 227 func runCommandAndReadContainerFile(c *testing.T, filename string, command string, args ...string) []byte { 228 c.Helper() 229 result := icmd.RunCommand(command, args...) 230 result.Assert(c, icmd.Success) 231 contID := strings.TrimSpace(result.Combined()) 232 if err := waitRun(contID); err != nil { 233 c.Fatalf("%v: %q", contID, err) 234 } 235 return readContainerFile(c, contID, filename) 236 } 237 238 func readContainerFile(c *testing.T, containerID, filename string) []byte { 239 c.Helper() 240 f, err := os.Open(containerStorageFile(containerID, filename)) 241 assert.NilError(c, err) 242 defer f.Close() 243 244 content, err := ioutil.ReadAll(f) 245 assert.NilError(c, err) 246 return content 247 } 248 249 func readContainerFileWithExec(c *testing.T, containerID, filename string) []byte { 250 c.Helper() 251 result := icmd.RunCommand(dockerBinary, "exec", containerID, "cat", filename) 252 result.Assert(c, icmd.Success) 253 return []byte(result.Combined()) 254 } 255 256 // daemonTime provides the current time on the daemon host 257 func daemonTime(c *testing.T) time.Time { 258 c.Helper() 259 if testEnv.IsLocalDaemon() { 260 return time.Now() 261 } 262 cli, err := client.NewClientWithOpts(client.FromEnv) 263 assert.NilError(c, err) 264 defer cli.Close() 265 266 info, err := cli.Info(context.Background()) 267 assert.NilError(c, err) 268 269 dt, err := time.Parse(time.RFC3339Nano, info.SystemTime) 270 assert.Assert(c, err == nil, "invalid time format in GET /info response") 271 return dt 272 } 273 274 // daemonUnixTime returns the current time on the daemon host with nanoseconds precision. 275 // It return the time formatted how the client sends timestamps to the server. 276 func daemonUnixTime(c *testing.T) string { 277 c.Helper() 278 return parseEventTime(daemonTime(c)) 279 } 280 281 func parseEventTime(t time.Time) string { 282 return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())) 283 } 284 285 // appendBaseEnv appends the minimum set of environment variables to exec the 286 // docker cli binary for testing with correct configuration to the given env 287 // list. 288 func appendBaseEnv(isTLS bool, env ...string) []string { 289 preserveList := []string{ 290 // preserve remote test host 291 "DOCKER_HOST", 292 293 // windows: requires preserving SystemRoot, otherwise dial tcp fails 294 // with "GetAddrInfoW: A non-recoverable error occurred during a database lookup." 295 "SystemRoot", 296 297 // testing help text requires the $PATH to dockerd is set 298 "PATH", 299 } 300 if isTLS { 301 preserveList = append(preserveList, "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH") 302 } 303 304 for _, key := range preserveList { 305 if val := os.Getenv(key); val != "" { 306 env = append(env, fmt.Sprintf("%s=%s", key, val)) 307 } 308 } 309 return env 310 } 311 312 func createTmpFile(c *testing.T, content string) string { 313 c.Helper() 314 f, err := ioutil.TempFile("", "testfile") 315 assert.NilError(c, err) 316 317 filename := f.Name() 318 319 err = ioutil.WriteFile(filename, []byte(content), 0644) 320 assert.NilError(c, err) 321 322 return filename 323 } 324 325 // waitRun will wait for the specified container to be running, maximum 5 seconds. 326 // Deprecated: use cli.WaitFor 327 func waitRun(contID string) error { 328 return waitInspect(contID, "{{.State.Running}}", "true", 5*time.Second) 329 } 330 331 // waitInspect will wait for the specified container to have the specified string 332 // in the inspect output. It will wait until the specified timeout (in seconds) 333 // is reached. 334 // Deprecated: use cli.WaitFor 335 func waitInspect(name, expr, expected string, timeout time.Duration) error { 336 return waitInspectWithArgs(name, expr, expected, timeout) 337 } 338 339 // Deprecated: use cli.WaitFor 340 func waitInspectWithArgs(name, expr, expected string, timeout time.Duration, arg ...string) error { 341 return daemon.WaitInspectWithArgs(dockerBinary, name, expr, expected, timeout, arg...) 342 } 343 344 func getInspectBody(c *testing.T, version, id string) []byte { 345 c.Helper() 346 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion(version)) 347 assert.NilError(c, err) 348 defer cli.Close() 349 _, body, err := cli.ContainerInspectWithRaw(context.Background(), id, false) 350 assert.NilError(c, err) 351 return body 352 } 353 354 // Run a long running idle task in a background container using the 355 // system-specific default image and command. 356 func runSleepingContainer(c *testing.T, extraArgs ...string) string { 357 c.Helper() 358 return runSleepingContainerInImage(c, "busybox", extraArgs...) 359 } 360 361 // Run a long running idle task in a background container using the specified 362 // image and the system-specific command. 363 func runSleepingContainerInImage(c *testing.T, image string, extraArgs ...string) string { 364 c.Helper() 365 args := []string{"run", "-d"} 366 args = append(args, extraArgs...) 367 args = append(args, image) 368 args = append(args, sleepCommandForDaemonPlatform()...) 369 return strings.TrimSpace(cli.DockerCmd(c, args...).Combined()) 370 } 371 372 // minimalBaseImage returns the name of the minimal base image for the current 373 // daemon platform. 374 func minimalBaseImage() string { 375 return testEnv.PlatformDefaults.BaseImage 376 } 377 378 func getGoroutineNumber() (int, error) { 379 cli, err := client.NewClientWithOpts(client.FromEnv) 380 if err != nil { 381 return 0, err 382 } 383 defer cli.Close() 384 385 info, err := cli.Info(context.Background()) 386 if err != nil { 387 return 0, err 388 } 389 return info.NGoroutines, nil 390 } 391 392 func waitForGoroutines(expected int) error { 393 t := time.After(30 * time.Second) 394 for { 395 select { 396 case <-t: 397 n, err := getGoroutineNumber() 398 if err != nil { 399 return err 400 } 401 if n > expected { 402 return fmt.Errorf("leaked goroutines: expected less than or equal to %d, got: %d", expected, n) 403 } 404 default: 405 n, err := getGoroutineNumber() 406 if err != nil { 407 return err 408 } 409 if n <= expected { 410 return nil 411 } 412 time.Sleep(200 * time.Millisecond) 413 } 414 } 415 } 416 417 // getErrorMessage returns the error message from an error API response 418 func getErrorMessage(c *testing.T, body []byte) string { 419 c.Helper() 420 var resp types.ErrorResponse 421 assert.Assert(c, json.Unmarshal(body, &resp) == nil) 422 return strings.TrimSpace(resp.Message) 423 } 424 425 type checkF func(*testing.T) (interface{}, string) 426 type reducer func(...interface{}) interface{} 427 428 func pollCheck(t *testing.T, f checkF, compare func(x interface{}) assert.BoolOrComparison) poll.Check { 429 return func(poll.LogT) poll.Result { 430 t.Helper() 431 v, comment := f(t) 432 r := compare(v) 433 switch r := r.(type) { 434 case bool: 435 if r { 436 return poll.Success() 437 } 438 case cmp.Comparison: 439 if r().Success() { 440 return poll.Success() 441 } 442 default: 443 panic(fmt.Errorf("pollCheck: type %T not implemented", r)) 444 } 445 return poll.Continue(comment) 446 } 447 } 448 449 func reducedCheck(r reducer, funcs ...checkF) checkF { 450 return func(c *testing.T) (interface{}, string) { 451 c.Helper() 452 var values []interface{} 453 var comments []string 454 for _, f := range funcs { 455 v, comment := f(c) 456 values = append(values, v) 457 if len(comment) > 0 { 458 comments = append(comments, comment) 459 } 460 } 461 return r(values...), fmt.Sprintf("%v", strings.Join(comments, ", ")) 462 } 463 } 464 465 func sumAsIntegers(vals ...interface{}) interface{} { 466 var s int 467 for _, v := range vals { 468 s += v.(int) 469 } 470 return s 471 }