gopkg.in/docker/docker.v20@v20.10.27/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  }