github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/container/create_test.go (about)

     1  package container
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"runtime"
    10  	"sort"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/docker/cli/cli"
    15  	"github.com/docker/cli/cli/config/configfile"
    16  	"github.com/docker/cli/internal/test"
    17  	"github.com/docker/cli/internal/test/notary"
    18  	"github.com/docker/docker/api/types/container"
    19  	"github.com/docker/docker/api/types/image"
    20  	"github.com/docker/docker/api/types/network"
    21  	"github.com/docker/docker/api/types/system"
    22  	"github.com/google/go-cmp/cmp"
    23  	specs "github.com/opencontainers/image-spec/specs-go/v1"
    24  	"github.com/spf13/pflag"
    25  	"gotest.tools/v3/assert"
    26  	is "gotest.tools/v3/assert/cmp"
    27  	"gotest.tools/v3/fs"
    28  	"gotest.tools/v3/golden"
    29  )
    30  
    31  func TestCIDFileNoOPWithNoFilename(t *testing.T) {
    32  	file, err := newCIDFile("")
    33  	assert.NilError(t, err)
    34  	assert.DeepEqual(t, &cidFile{}, file, cmp.AllowUnexported(cidFile{}))
    35  
    36  	assert.NilError(t, file.Write("id"))
    37  	assert.NilError(t, file.Close())
    38  }
    39  
    40  func TestNewCIDFileWhenFileAlreadyExists(t *testing.T) {
    41  	tempfile := fs.NewFile(t, "test-cid-file")
    42  	defer tempfile.Remove()
    43  
    44  	_, err := newCIDFile(tempfile.Path())
    45  	assert.ErrorContains(t, err, "container ID file found")
    46  }
    47  
    48  func TestCIDFileCloseWithNoWrite(t *testing.T) {
    49  	tempdir := fs.NewDir(t, "test-cid-file")
    50  	defer tempdir.Remove()
    51  
    52  	path := tempdir.Join("cidfile")
    53  	file, err := newCIDFile(path)
    54  	assert.NilError(t, err)
    55  	assert.Check(t, is.Equal(file.path, path))
    56  
    57  	assert.NilError(t, file.Close())
    58  	_, err = os.Stat(path)
    59  	assert.Check(t, os.IsNotExist(err))
    60  }
    61  
    62  func TestCIDFileCloseWithWrite(t *testing.T) {
    63  	tempdir := fs.NewDir(t, "test-cid-file")
    64  	defer tempdir.Remove()
    65  
    66  	path := tempdir.Join("cidfile")
    67  	file, err := newCIDFile(path)
    68  	assert.NilError(t, err)
    69  
    70  	content := "id"
    71  	assert.NilError(t, file.Write(content))
    72  
    73  	actual, err := os.ReadFile(path)
    74  	assert.NilError(t, err)
    75  	assert.Check(t, is.Equal(content, string(actual)))
    76  
    77  	assert.NilError(t, file.Close())
    78  	_, err = os.Stat(path)
    79  	assert.NilError(t, err)
    80  }
    81  
    82  func TestCreateContainerImagePullPolicy(t *testing.T) {
    83  	const (
    84  		imageName   = "does-not-exist-locally"
    85  		containerID = "abcdef"
    86  	)
    87  	config := &containerConfig{
    88  		Config: &container.Config{
    89  			Image: imageName,
    90  		},
    91  		HostConfig: &container.HostConfig{},
    92  	}
    93  
    94  	cases := []struct {
    95  		PullPolicy      string
    96  		ExpectedPulls   int
    97  		ExpectedID      string
    98  		ExpectedErrMsg  string
    99  		ResponseCounter int
   100  	}{
   101  		{
   102  			PullPolicy:    PullImageMissing,
   103  			ExpectedPulls: 1,
   104  			ExpectedID:    containerID,
   105  		}, {
   106  			PullPolicy:      PullImageAlways,
   107  			ExpectedPulls:   1,
   108  			ExpectedID:      containerID,
   109  			ResponseCounter: 1, // This lets us return a container on the first pull
   110  		}, {
   111  			PullPolicy:     PullImageNever,
   112  			ExpectedPulls:  0,
   113  			ExpectedErrMsg: "error fake not found",
   114  		},
   115  	}
   116  	for _, tc := range cases {
   117  		tc := tc
   118  		t.Run(tc.PullPolicy, func(t *testing.T) {
   119  			pullCounter := 0
   120  
   121  			client := &fakeClient{
   122  				createContainerFunc: func(
   123  					config *container.Config,
   124  					hostConfig *container.HostConfig,
   125  					networkingConfig *network.NetworkingConfig,
   126  					platform *specs.Platform,
   127  					containerName string,
   128  				) (container.CreateResponse, error) {
   129  					defer func() { tc.ResponseCounter++ }()
   130  					switch tc.ResponseCounter {
   131  					case 0:
   132  						return container.CreateResponse{}, fakeNotFound{}
   133  					default:
   134  						return container.CreateResponse{ID: containerID}, nil
   135  					}
   136  				},
   137  				imageCreateFunc: func(parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
   138  					defer func() { pullCounter++ }()
   139  					return io.NopCloser(strings.NewReader("")), nil
   140  				},
   141  				infoFunc: func() (system.Info, error) {
   142  					return system.Info{IndexServerAddress: "https://indexserver.example.com"}, nil
   143  				},
   144  			}
   145  			fakeCLI := test.NewFakeCli(client)
   146  			id, err := createContainer(context.Background(), fakeCLI, config, &createOptions{
   147  				name:      "name",
   148  				platform:  runtime.GOOS,
   149  				untrusted: true,
   150  				pull:      tc.PullPolicy,
   151  			})
   152  
   153  			if tc.ExpectedErrMsg != "" {
   154  				assert.Check(t, is.ErrorContains(err, tc.ExpectedErrMsg))
   155  			} else {
   156  				assert.Check(t, err)
   157  				assert.Check(t, is.Equal(tc.ExpectedID, id))
   158  			}
   159  
   160  			assert.Check(t, is.Equal(tc.ExpectedPulls, pullCounter))
   161  		})
   162  	}
   163  }
   164  
   165  func TestCreateContainerImagePullPolicyInvalid(t *testing.T) {
   166  	cases := []struct {
   167  		PullPolicy     string
   168  		ExpectedErrMsg string
   169  	}{
   170  		{
   171  			PullPolicy:     "busybox:latest",
   172  			ExpectedErrMsg: `invalid pull option: 'busybox:latest': must be one of "always", "missing" or "never"`,
   173  		},
   174  		{
   175  			PullPolicy:     "--network=foo",
   176  			ExpectedErrMsg: `invalid pull option: '--network=foo': must be one of "always", "missing" or "never"`,
   177  		},
   178  	}
   179  	for _, tc := range cases {
   180  		tc := tc
   181  		t.Run(tc.PullPolicy, func(t *testing.T) {
   182  			dockerCli := test.NewFakeCli(&fakeClient{})
   183  			err := runCreate(
   184  				context.TODO(),
   185  				dockerCli,
   186  				&pflag.FlagSet{},
   187  				&createOptions{pull: tc.PullPolicy},
   188  				&containerOptions{},
   189  			)
   190  
   191  			statusErr := cli.StatusError{}
   192  			assert.Check(t, errors.As(err, &statusErr))
   193  			assert.Equal(t, statusErr.StatusCode, 125)
   194  			assert.Check(t, is.Contains(dockerCli.ErrBuffer().String(), tc.ExpectedErrMsg))
   195  		})
   196  	}
   197  }
   198  
   199  func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
   200  	testCases := []struct {
   201  		name          string
   202  		args          []string
   203  		expectedError string
   204  		notaryFunc    test.NotaryClientFuncType
   205  	}{
   206  		{
   207  			name:          "offline-notary-server",
   208  			notaryFunc:    notary.GetOfflineNotaryRepository,
   209  			expectedError: "client is offline",
   210  			args:          []string{"image:tag"},
   211  		},
   212  		{
   213  			name:          "uninitialized-notary-server",
   214  			notaryFunc:    notary.GetUninitializedNotaryRepository,
   215  			expectedError: "remote trust data does not exist",
   216  			args:          []string{"image:tag"},
   217  		},
   218  		{
   219  			name:          "empty-notary-server",
   220  			notaryFunc:    notary.GetEmptyTargetsNotaryRepository,
   221  			expectedError: "No valid trust data for tag",
   222  			args:          []string{"image:tag"},
   223  		},
   224  	}
   225  	for _, tc := range testCases {
   226  		tc := tc
   227  		fakeCLI := test.NewFakeCli(&fakeClient{
   228  			createContainerFunc: func(config *container.Config,
   229  				hostConfig *container.HostConfig,
   230  				networkingConfig *network.NetworkingConfig,
   231  				platform *specs.Platform,
   232  				containerName string,
   233  			) (container.CreateResponse, error) {
   234  				return container.CreateResponse{}, fmt.Errorf("shouldn't try to pull image")
   235  			},
   236  		}, test.EnableContentTrust)
   237  		fakeCLI.SetNotaryClient(tc.notaryFunc)
   238  		cmd := NewCreateCommand(fakeCLI)
   239  		cmd.SetOut(io.Discard)
   240  		cmd.SetArgs(tc.args)
   241  		err := cmd.Execute()
   242  		assert.ErrorContains(t, err, tc.expectedError)
   243  	}
   244  }
   245  
   246  func TestNewCreateCommandWithWarnings(t *testing.T) {
   247  	testCases := []struct {
   248  		name    string
   249  		args    []string
   250  		warning bool
   251  	}{
   252  		{
   253  			name: "container-create-without-oom-kill-disable",
   254  			args: []string{"image:tag"},
   255  		},
   256  		{
   257  			name: "container-create-oom-kill-disable-false",
   258  			args: []string{"--oom-kill-disable=false", "image:tag"},
   259  		},
   260  		{
   261  			name:    "container-create-oom-kill-without-memory-limit",
   262  			args:    []string{"--oom-kill-disable", "image:tag"},
   263  			warning: true,
   264  		},
   265  		{
   266  			name:    "container-create-oom-kill-true-without-memory-limit",
   267  			args:    []string{"--oom-kill-disable=true", "image:tag"},
   268  			warning: true,
   269  		},
   270  		{
   271  			name: "container-create-oom-kill-true-with-memory-limit",
   272  			args: []string{"--oom-kill-disable=true", "--memory=100M", "image:tag"},
   273  		},
   274  		{
   275  			name:    "container-create-localhost-dns",
   276  			args:    []string{"--dns=127.0.0.11", "image:tag"},
   277  			warning: true,
   278  		},
   279  		{
   280  			name:    "container-create-localhost-dns-ipv6",
   281  			args:    []string{"--dns=::1", "image:tag"},
   282  			warning: true,
   283  		},
   284  	}
   285  	for _, tc := range testCases {
   286  		tc := tc
   287  		t.Run(tc.name, func(t *testing.T) {
   288  			cli := test.NewFakeCli(&fakeClient{
   289  				createContainerFunc: func(config *container.Config,
   290  					hostConfig *container.HostConfig,
   291  					networkingConfig *network.NetworkingConfig,
   292  					platform *specs.Platform,
   293  					containerName string,
   294  				) (container.CreateResponse, error) {
   295  					return container.CreateResponse{}, nil
   296  				},
   297  			})
   298  			cmd := NewCreateCommand(cli)
   299  			cmd.SetOut(io.Discard)
   300  			cmd.SetArgs(tc.args)
   301  			err := cmd.Execute()
   302  			assert.NilError(t, err)
   303  			if tc.warning {
   304  				golden.Assert(t, cli.ErrBuffer().String(), tc.name+".golden")
   305  			} else {
   306  				assert.Equal(t, cli.ErrBuffer().String(), "")
   307  			}
   308  		})
   309  	}
   310  }
   311  
   312  func TestCreateContainerWithProxyConfig(t *testing.T) {
   313  	expected := []string{
   314  		"HTTP_PROXY=httpProxy",
   315  		"http_proxy=httpProxy",
   316  		"HTTPS_PROXY=httpsProxy",
   317  		"https_proxy=httpsProxy",
   318  		"NO_PROXY=noProxy",
   319  		"no_proxy=noProxy",
   320  		"FTP_PROXY=ftpProxy",
   321  		"ftp_proxy=ftpProxy",
   322  		"ALL_PROXY=allProxy",
   323  		"all_proxy=allProxy",
   324  	}
   325  	sort.Strings(expected)
   326  
   327  	fakeCLI := test.NewFakeCli(&fakeClient{
   328  		createContainerFunc: func(config *container.Config,
   329  			hostConfig *container.HostConfig,
   330  			networkingConfig *network.NetworkingConfig,
   331  			platform *specs.Platform,
   332  			containerName string,
   333  		) (container.CreateResponse, error) {
   334  			sort.Strings(config.Env)
   335  			assert.DeepEqual(t, config.Env, expected)
   336  			return container.CreateResponse{}, nil
   337  		},
   338  	})
   339  	fakeCLI.SetConfigFile(&configfile.ConfigFile{
   340  		Proxies: map[string]configfile.ProxyConfig{
   341  			"default": {
   342  				HTTPProxy:  "httpProxy",
   343  				HTTPSProxy: "httpsProxy",
   344  				NoProxy:    "noProxy",
   345  				FTPProxy:   "ftpProxy",
   346  				AllProxy:   "allProxy",
   347  			},
   348  		},
   349  	})
   350  	cmd := NewCreateCommand(fakeCLI)
   351  	cmd.SetOut(io.Discard)
   352  	cmd.SetArgs([]string{"image:tag"})
   353  	err := cmd.Execute()
   354  	assert.NilError(t, err)
   355  }
   356  
   357  type fakeNotFound struct{}
   358  
   359  func (f fakeNotFound) NotFound()     {}
   360  func (f fakeNotFound) Error() string { return "error fake not found" }