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