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" }