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