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