github.com/moby/docker@v26.1.3+incompatible/integration-cli/docker_cli_build_test.go (about) 1 package main 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "context" 7 "encoding/json" 8 "fmt" 9 "os" 10 "path/filepath" 11 "reflect" 12 "regexp" 13 "runtime" 14 "strconv" 15 "strings" 16 "testing" 17 "text/template" 18 "time" 19 20 "github.com/docker/docker/api/types/versions" 21 "github.com/docker/docker/integration-cli/cli" 22 "github.com/docker/docker/integration-cli/cli/build" 23 "github.com/docker/docker/pkg/archive" 24 "github.com/docker/docker/testutil" 25 "github.com/docker/docker/testutil/fakecontext" 26 "github.com/docker/docker/testutil/fakegit" 27 "github.com/docker/docker/testutil/fakestorage" 28 "github.com/moby/buildkit/frontend/dockerfile/command" 29 "github.com/opencontainers/go-digest" 30 "gotest.tools/v3/assert" 31 is "gotest.tools/v3/assert/cmp" 32 "gotest.tools/v3/icmd" 33 "gotest.tools/v3/skip" 34 ) 35 36 type DockerCLIBuildSuite struct { 37 ds *DockerSuite 38 } 39 40 func (s *DockerCLIBuildSuite) TearDownTest(ctx context.Context, c *testing.T) { 41 s.ds.TearDownTest(ctx, c) 42 } 43 44 func (s *DockerCLIBuildSuite) OnTimeout(c *testing.T) { 45 s.ds.OnTimeout(c) 46 } 47 48 func (s *DockerCLIBuildSuite) TestBuildJSONEmptyRun(c *testing.T) { 49 cli.BuildCmd(c, "testbuildjsonemptyrun", build.WithDockerfile(` 50 FROM busybox 51 RUN [] 52 `)) 53 } 54 55 func (s *DockerCLIBuildSuite) TestBuildShCmdJSONEntrypoint(c *testing.T) { 56 const name = "testbuildshcmdjsonentrypoint" 57 expected := "/bin/sh -c echo test" 58 if testEnv.DaemonInfo.OSType == "windows" { 59 expected = "cmd /S /C echo test" 60 } 61 62 buildImageSuccessfully(c, name, build.WithDockerfile(` 63 FROM busybox 64 ENTRYPOINT ["echo"] 65 CMD echo test 66 `)) 67 out := cli.DockerCmd(c, "run", "--rm", name).Combined() 68 69 if strings.TrimSpace(out) != expected { 70 c.Fatalf("CMD did not contain %q : %q", expected, out) 71 } 72 } 73 74 func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementUser(c *testing.T) { 75 // Windows does not support FROM scratch or the USER command 76 testRequires(c, DaemonIsLinux) 77 const name = "testbuildenvironmentreplacement" 78 79 buildImageSuccessfully(c, name, build.WithDockerfile(` 80 FROM scratch 81 ENV user foo 82 USER ${user} 83 `)) 84 res := inspectFieldJSON(c, name, "Config.User") 85 86 if res != `"foo"` { 87 c.Fatal("User foo from environment not in Config.User on image") 88 } 89 } 90 91 func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementVolume(c *testing.T) { 92 const name = "testbuildenvironmentreplacement" 93 94 var volumePath string 95 96 if testEnv.DaemonInfo.OSType == "windows" { 97 volumePath = "c:/quux" 98 } else { 99 volumePath = "/quux" 100 } 101 102 buildImageSuccessfully(c, name, build.WithDockerfile(` 103 FROM `+minimalBaseImage()+` 104 ENV volume `+volumePath+` 105 VOLUME ${volume} 106 `)) 107 108 var volumes map[string]interface{} 109 inspectFieldAndUnmarshall(c, name, "Config.Volumes", &volumes) 110 if _, ok := volumes[volumePath]; !ok { 111 c.Fatal("Volume " + volumePath + " from environment not in Config.Volumes on image") 112 } 113 } 114 115 func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementExpose(c *testing.T) { 116 // Windows does not support FROM scratch or the EXPOSE command 117 testRequires(c, DaemonIsLinux) 118 const name = "testbuildenvironmentreplacement" 119 120 buildImageSuccessfully(c, name, build.WithDockerfile(` 121 FROM scratch 122 ENV port 80 123 EXPOSE ${port} 124 ENV ports " 99 100 " 125 EXPOSE ${ports} 126 `)) 127 128 var exposedPorts map[string]interface{} 129 inspectFieldAndUnmarshall(c, name, "Config.ExposedPorts", &exposedPorts) 130 exp := []int{80, 99, 100} 131 for _, p := range exp { 132 tmp := fmt.Sprintf("%d/tcp", p) 133 if _, ok := exposedPorts[tmp]; !ok { 134 c.Fatalf("Exposed port %d from environment not in Config.ExposedPorts on image", p) 135 } 136 } 137 } 138 139 func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementWorkdir(c *testing.T) { 140 const name = "testbuildenvironmentreplacement" 141 142 buildImageSuccessfully(c, name, build.WithDockerfile(` 143 FROM busybox 144 ENV MYWORKDIR /work 145 RUN mkdir ${MYWORKDIR} 146 WORKDIR ${MYWORKDIR} 147 `)) 148 res := inspectFieldJSON(c, name, "Config.WorkingDir") 149 150 expected := `"/work"` 151 if testEnv.DaemonInfo.OSType == "windows" { 152 expected = `"C:\\work"` 153 } 154 if res != expected { 155 c.Fatalf("Workdir /workdir from environment not in Config.WorkingDir on image: %s", res) 156 } 157 } 158 159 func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementAddCopy(c *testing.T) { 160 const name = "testbuildenvironmentreplacement" 161 162 buildImageSuccessfully(c, name, build.WithBuildContext(c, 163 build.WithFile("Dockerfile", ` 164 FROM `+minimalBaseImage()+` 165 ENV baz foo 166 ENV quux bar 167 ENV dot . 168 ENV fee fff 169 ENV gee ggg 170 171 ADD ${baz} ${dot} 172 COPY ${quux} ${dot} 173 ADD ${zzz:-${fee}} ${dot} 174 COPY ${zzz:-${gee}} ${dot} 175 `), 176 build.WithFile("foo", "test1"), 177 build.WithFile("bar", "test2"), 178 build.WithFile("fff", "test3"), 179 build.WithFile("ggg", "test4"), 180 )) 181 } 182 183 func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementEnv(c *testing.T) { 184 // ENV expansions work differently in Windows 185 testRequires(c, DaemonIsLinux) 186 const name = "testbuildenvironmentreplacement" 187 188 buildImageSuccessfully(c, name, build.WithDockerfile(` 189 FROM busybox 190 ENV foo zzz 191 ENV bar ${foo} 192 ENV abc1='$foo' 193 ENV env1=$foo env2=${foo} env3="$foo" env4="${foo}" 194 RUN [ "$abc1" = '$foo' ] && (echo "$abc1" | grep -q foo) 195 ENV abc2="\$foo" 196 RUN [ "$abc2" = '$foo' ] && (echo "$abc2" | grep -q foo) 197 ENV abc3 '$foo' 198 RUN [ "$abc3" = '$foo' ] && (echo "$abc3" | grep -q foo) 199 ENV abc4 "\$foo" 200 RUN [ "$abc4" = '$foo' ] && (echo "$abc4" | grep -q foo) 201 ENV foo2="abc\def" 202 RUN [ "$foo2" = 'abc\def' ] 203 ENV foo3="abc\\def" 204 RUN [ "$foo3" = 'abc\def' ] 205 ENV foo4='abc\\def' 206 RUN [ "$foo4" = 'abc\\def' ] 207 ENV foo5='abc\def' 208 RUN [ "$foo5" = 'abc\def' ] 209 `)) 210 211 var envResult []string 212 inspectFieldAndUnmarshall(c, name, "Config.Env", &envResult) 213 found := false 214 envCount := 0 215 216 for _, env := range envResult { 217 k, v, _ := strings.Cut(env, "=") 218 if k == "bar" { 219 found = true 220 if v != "zzz" { 221 c.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", v) 222 } 223 } else if strings.HasPrefix(k, "env") { 224 envCount++ 225 if v != "zzz" { 226 c.Fatalf("%s should be 'zzz' but instead its %q", k, v) 227 } 228 } else if strings.HasPrefix(k, "env") { 229 envCount++ 230 if v != "foo" { 231 c.Fatalf("%s should be 'foo' but instead its %q", k, v) 232 } 233 } 234 } 235 236 if !found { 237 c.Fatal("Never found the `bar` env variable") 238 } 239 240 if envCount != 4 { 241 c.Fatalf("Didn't find all env vars - only saw %d\n%s", envCount, envResult) 242 } 243 } 244 245 func (s *DockerCLIBuildSuite) TestBuildHandleEscapesInVolume(c *testing.T) { 246 // The volume paths used in this test are invalid on Windows 247 testRequires(c, DaemonIsLinux) 248 const name = "testbuildhandleescapes" 249 250 testCases := []struct { 251 volumeValue string 252 expected string 253 }{ 254 { 255 volumeValue: "${FOO}", 256 expected: "bar", 257 }, 258 { 259 volumeValue: `\${FOO}`, 260 expected: "${FOO}", 261 }, 262 // this test in particular provides *7* backslashes and expects 6 to come back. 263 // Like above, the first escape is swallowed and the rest are treated as 264 // literals, this one is just less obvious because of all the character noise. 265 { 266 volumeValue: `\\\\\\\${FOO}`, 267 expected: `\\\${FOO}`, 268 }, 269 } 270 271 for _, tc := range testCases { 272 buildImageSuccessfully(c, name, build.WithDockerfile(fmt.Sprintf(` 273 FROM scratch 274 ENV FOO bar 275 VOLUME %s 276 `, tc.volumeValue))) 277 278 var result map[string]map[string]struct{} 279 inspectFieldAndUnmarshall(c, name, "Config.Volumes", &result) 280 if _, ok := result[tc.expected]; !ok { 281 c.Fatalf("Could not find volume %s set from env foo in volumes table, got %q", tc.expected, result) 282 } 283 284 // Remove the image for the next iteration 285 cli.DockerCmd(c, "rmi", name) 286 } 287 } 288 289 func (s *DockerCLIBuildSuite) TestBuildOnBuildLowercase(c *testing.T) { 290 const name = "testbuildonbuildlowercase" 291 const name2 = "testbuildonbuildlowercase2" 292 293 buildImageSuccessfully(c, name, build.WithDockerfile(` 294 FROM busybox 295 onbuild run echo quux 296 `)) 297 298 result := buildImage(name2, build.WithDockerfile(fmt.Sprintf(` 299 FROM %s 300 `, name))) 301 result.Assert(c, icmd.Success) 302 303 if !strings.Contains(result.Combined(), "quux") { 304 c.Fatalf("Did not receive the expected echo text, got %s", result.Combined()) 305 } 306 307 //nolint:dupword 308 if strings.Contains(result.Combined(), "ONBUILD ONBUILD") { 309 c.Fatalf("Got an ONBUILD ONBUILD error with no error: got %s", result.Combined()) 310 } 311 } 312 313 func (s *DockerCLIBuildSuite) TestBuildEnvEscapes(c *testing.T) { 314 // ENV expansions work differently in Windows 315 testRequires(c, DaemonIsLinux) 316 const name = "testbuildenvescapes" 317 buildImageSuccessfully(c, name, build.WithDockerfile(` 318 FROM busybox 319 ENV TEST foo 320 CMD echo \$ 321 `)) 322 323 out := cli.DockerCmd(c, "run", "-t", name).Combined() 324 if strings.TrimSpace(out) != "$" { 325 c.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out)) 326 } 327 } 328 329 func (s *DockerCLIBuildSuite) TestBuildEnvOverwrite(c *testing.T) { 330 // ENV expansions work differently in Windows 331 testRequires(c, DaemonIsLinux) 332 const name = "testbuildenvoverwrite" 333 buildImageSuccessfully(c, name, build.WithDockerfile(` 334 FROM busybox 335 ENV TEST foo 336 CMD echo ${TEST} 337 `)) 338 339 out := cli.DockerCmd(c, "run", "-e", "TEST=bar", "-t", name).Combined() 340 if strings.TrimSpace(out) != "bar" { 341 c.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out)) 342 } 343 } 344 345 // FIXME(vdemeester) why we disabled cache here ? 346 func (s *DockerCLIBuildSuite) TestBuildOnBuildCmdEntrypointJSON(c *testing.T) { 347 const name1 = "onbuildcmd" 348 const name2 = "onbuildgenerated" 349 350 cli.BuildCmd(c, name1, build.WithDockerfile(` 351 FROM busybox 352 ONBUILD CMD ["hello world"] 353 ONBUILD ENTRYPOINT ["echo"] 354 ONBUILD RUN ["true"]`)) 355 356 cli.BuildCmd(c, name2, build.WithDockerfile(fmt.Sprintf(`FROM %s`, name1))) 357 358 result := cli.DockerCmd(c, "run", name2) 359 result.Assert(c, icmd.Expected{Out: "hello world"}) 360 } 361 362 // FIXME(vdemeester) why we disabled cache here ? 363 func (s *DockerCLIBuildSuite) TestBuildOnBuildEntrypointJSON(c *testing.T) { 364 const name1 = "onbuildcmd" 365 const name2 = "onbuildgenerated" 366 367 buildImageSuccessfully(c, name1, build.WithDockerfile(` 368 FROM busybox 369 ONBUILD ENTRYPOINT ["echo"]`)) 370 371 buildImageSuccessfully(c, name2, build.WithDockerfile(fmt.Sprintf("FROM %s\nCMD [\"hello world\"]\n", name1))) 372 373 out := cli.DockerCmd(c, "run", name2).Combined() 374 if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) { 375 c.Fatal("got malformed output from onbuild", out) 376 } 377 } 378 379 func (s *DockerCLIBuildSuite) TestBuildCacheAdd(c *testing.T) { 380 testRequires(c, DaemonIsLinux) // Windows doesn't have httpserver image yet 381 const name = "testbuildtwoimageswithadd" 382 server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{ 383 "robots.txt": "hello", 384 "index.html": "world", 385 })) 386 defer server.Close() 387 388 cli.BuildCmd(c, name, build.WithDockerfile(fmt.Sprintf(`FROM scratch 389 ADD %s/robots.txt /`, server.URL()))) 390 391 result := cli.Docker(cli.Args("build", "-t", name), build.WithDockerfile(fmt.Sprintf(`FROM scratch 392 ADD %s/index.html /`, server.URL()))) 393 result.Assert(c, icmd.Success) 394 if strings.Contains(result.Combined(), "Using cache") { 395 c.Fatal("2nd build used cache on ADD, it shouldn't") 396 } 397 } 398 399 func (s *DockerCLIBuildSuite) TestBuildLastModified(c *testing.T) { 400 // Temporary fix for #30890. TODO: figure out what 401 // has changed in the master busybox image. 402 testRequires(c, DaemonIsLinux) 403 404 const name = "testbuildlastmodified" 405 406 server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{ 407 "file": "hello", 408 })) 409 defer server.Close() 410 411 var out, out2 string 412 args := []string{"run", name, "ls", "-l", "--full-time", "/file"} 413 414 dFmt := `FROM busybox 415 ADD %s/file /` 416 dockerfile := fmt.Sprintf(dFmt, server.URL()) 417 418 cli.BuildCmd(c, name, build.WithoutCache, build.WithDockerfile(dockerfile)) 419 out = cli.DockerCmd(c, args...).Combined() 420 421 // Build it again and make sure the mtime of the file didn't change. 422 // Wait a few seconds to make sure the time changed enough to notice 423 time.Sleep(2 * time.Second) 424 425 cli.BuildCmd(c, name, build.WithoutCache, build.WithDockerfile(dockerfile)) 426 out2 = cli.DockerCmd(c, args...).Combined() 427 428 if out != out2 { 429 c.Fatalf("MTime changed:\nOrigin:%s\nNew:%s", out, out2) 430 } 431 432 // Now 'touch' the file and make sure the timestamp DID change this time 433 // Create a new fakeStorage instead of just using Add() to help windows 434 server = fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{ 435 "file": "hello", 436 })) 437 defer server.Close() 438 439 dockerfile = fmt.Sprintf(dFmt, server.URL()) 440 cli.BuildCmd(c, name, build.WithoutCache, build.WithDockerfile(dockerfile)) 441 out2 = cli.DockerCmd(c, args...).Combined() 442 443 if out == out2 { 444 c.Fatalf("MTime didn't change:\nOrigin:%s\nNew:%s", out, out2) 445 } 446 } 447 448 // Regression for https://github.com/docker/docker/pull/27805 449 // Makes sure that we don't use the cache if the contents of 450 // a file in a subfolder of the context is modified and we re-build. 451 func (s *DockerCLIBuildSuite) TestBuildModifyFileInFolder(c *testing.T) { 452 const name = "testbuildmodifyfileinfolder" 453 454 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`FROM busybox 455 RUN ["mkdir", "/test"] 456 ADD folder/file /test/changetarget`)) 457 defer ctx.Close() 458 if err := ctx.Add("folder/file", "first"); err != nil { 459 c.Fatal(err) 460 } 461 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 462 id1 := getIDByName(c, name) 463 if err := ctx.Add("folder/file", "second"); err != nil { 464 c.Fatal(err) 465 } 466 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 467 id2 := getIDByName(c, name) 468 if id1 == id2 { 469 c.Fatal("cache was used even though file contents in folder was changed") 470 } 471 } 472 473 func (s *DockerCLIBuildSuite) TestBuildAddSingleFileToRoot(c *testing.T) { 474 testRequires(c, DaemonIsLinux) // Linux specific test 475 buildImageSuccessfully(c, "testaddimg", build.WithBuildContext(c, 476 build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox 477 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 478 RUN echo 'dockerio:x:1001:' >> /etc/group 479 RUN touch /exists 480 RUN chown dockerio.dockerio /exists 481 ADD test_file / 482 RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] 483 RUN [ $(ls -l /test_file | awk '{print $1}') = '%s' ] 484 RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod)), 485 build.WithFile("test_file", "test1"))) 486 } 487 488 // Issue #3960: "ADD src ." hangs 489 func (s *DockerCLIBuildSuite) TestBuildAddSingleFileToWorkdir(c *testing.T) { 490 const name = "testaddsinglefiletoworkdir" 491 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile( 492 `FROM busybox 493 ADD test_file .`), 494 fakecontext.WithFiles(map[string]string{ 495 "test_file": "test1", 496 })) 497 defer ctx.Close() 498 499 errChan := make(chan error, 1) 500 go func() { 501 errChan <- buildImage(name, build.WithExternalBuildContext(ctx)).Error 502 close(errChan) 503 }() 504 select { 505 case <-time.After(15 * time.Second): 506 c.Fatal("Build with adding to workdir timed out") 507 case err := <-errChan: 508 assert.NilError(c, err) 509 } 510 } 511 512 func (s *DockerCLIBuildSuite) TestBuildAddSingleFileToExistDir(c *testing.T) { 513 testRequires(c, DaemonIsLinux) // Linux specific test 514 cli.BuildCmd(c, "testaddsinglefiletoexistdir", build.WithBuildContext(c, 515 build.WithFile("Dockerfile", `FROM busybox 516 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 517 RUN echo 'dockerio:x:1001:' >> /etc/group 518 RUN mkdir /exists 519 RUN touch /exists/exists_file 520 RUN chown -R dockerio.dockerio /exists 521 ADD test_file /exists/ 522 RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] 523 RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ] 524 RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`), 525 build.WithFile("test_file", "test1"))) 526 } 527 528 func (s *DockerCLIBuildSuite) TestBuildCopyAddMultipleFiles(c *testing.T) { 529 testRequires(c, DaemonIsLinux) // Linux specific test 530 server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{ 531 "robots.txt": "hello", 532 })) 533 defer server.Close() 534 535 cli.BuildCmd(c, "testcopymultiplefilestofile", build.WithBuildContext(c, 536 build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox 537 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 538 RUN echo 'dockerio:x:1001:' >> /etc/group 539 RUN mkdir /exists 540 RUN touch /exists/exists_file 541 RUN chown -R dockerio.dockerio /exists 542 COPY test_file1 test_file2 /exists/ 543 ADD test_file3 test_file4 %s/robots.txt /exists/ 544 RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] 545 RUN [ $(ls -l /exists/test_file1 | awk '{print $3":"$4}') = 'root:root' ] 546 RUN [ $(ls -l /exists/test_file2 | awk '{print $3":"$4}') = 'root:root' ] 547 RUN [ $(ls -l /exists/test_file3 | awk '{print $3":"$4}') = 'root:root' ] 548 RUN [ $(ls -l /exists/test_file4 | awk '{print $3":"$4}') = 'root:root' ] 549 RUN [ $(ls -l /exists/robots.txt | awk '{print $3":"$4}') = 'root:root' ] 550 RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ] 551 `, server.URL())), 552 build.WithFile("test_file1", "test1"), 553 build.WithFile("test_file2", "test2"), 554 build.WithFile("test_file3", "test3"), 555 build.WithFile("test_file3", "test3"), 556 build.WithFile("test_file4", "test4"))) 557 } 558 559 // These tests are mainly for user namespaces to verify that new directories 560 // are created as the remapped root uid/gid pair 561 func (s *DockerCLIBuildSuite) TestBuildUsernamespaceValidateRemappedRoot(c *testing.T) { 562 testRequires(c, DaemonIsLinux) 563 testCases := []string{ 564 "ADD . /new_dir", 565 "COPY test_dir /new_dir", 566 "WORKDIR /new_dir", 567 } 568 const name = "testbuildusernamespacevalidateremappedroot" 569 for _, tc := range testCases { 570 cli.BuildCmd(c, name, build.WithBuildContext(c, 571 build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox 572 %s 573 RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'root:root' ]`, tc)), 574 build.WithFile("test_dir/test_file", "test file"))) 575 576 cli.DockerCmd(c, "rmi", name) 577 } 578 } 579 580 func (s *DockerCLIBuildSuite) TestBuildAddAndCopyFileWithWhitespace(c *testing.T) { 581 testRequires(c, DaemonIsLinux) // Not currently passing on Windows 582 const name = "testaddfilewithwhitespace" 583 584 for _, command := range []string{"ADD", "COPY"} { 585 cli.BuildCmd(c, name, build.WithBuildContext(c, 586 build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox 587 RUN mkdir "/test dir" 588 RUN mkdir "/test_dir" 589 %s [ "test file1", "/test_file1" ] 590 %s [ "test_file2", "/test file2" ] 591 %s [ "test file3", "/test file3" ] 592 %s [ "test dir/test_file4", "/test_dir/test_file4" ] 593 %s [ "test_dir/test_file5", "/test dir/test_file5" ] 594 %s [ "test dir/test_file6", "/test dir/test_file6" ] 595 RUN [ $(cat "/test_file1") = 'test1' ] 596 RUN [ $(cat "/test file2") = 'test2' ] 597 RUN [ $(cat "/test file3") = 'test3' ] 598 RUN [ $(cat "/test_dir/test_file4") = 'test4' ] 599 RUN [ $(cat "/test dir/test_file5") = 'test5' ] 600 RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, command, command, command, command, command, command)), 601 build.WithFile("test file1", "test1"), 602 build.WithFile("test_file2", "test2"), 603 build.WithFile("test file3", "test3"), 604 build.WithFile("test dir/test_file4", "test4"), 605 build.WithFile("test_dir/test_file5", "test5"), 606 build.WithFile("test dir/test_file6", "test6"), 607 )) 608 609 cli.DockerCmd(c, "rmi", name) 610 } 611 } 612 613 func (s *DockerCLIBuildSuite) TestBuildCopyFileWithWhitespaceOnWindows(c *testing.T) { 614 testRequires(c, DaemonIsWindows) 615 dockerfile := `FROM ` + testEnv.PlatformDefaults.BaseImage + ` 616 RUN mkdir "C:/test dir" 617 RUN mkdir "C:/test_dir" 618 COPY [ "test file1", "/test_file1" ] 619 COPY [ "test_file2", "/test file2" ] 620 COPY [ "test file3", "/test file3" ] 621 COPY [ "test dir/test_file4", "/test_dir/test_file4" ] 622 COPY [ "test_dir/test_file5", "/test dir/test_file5" ] 623 COPY [ "test dir/test_file6", "/test dir/test_file6" ] 624 RUN find "test1" "C:/test_file1" 625 RUN find "test2" "C:/test file2" 626 RUN find "test3" "C:/test file3" 627 RUN find "test4" "C:/test_dir/test_file4" 628 RUN find "test5" "C:/test dir/test_file5" 629 RUN find "test6" "C:/test dir/test_file6"` 630 631 const name = "testcopyfilewithwhitespace" 632 cli.BuildCmd(c, name, build.WithBuildContext(c, 633 build.WithFile("Dockerfile", dockerfile), 634 build.WithFile("test file1", "test1"), 635 build.WithFile("test_file2", "test2"), 636 build.WithFile("test file3", "test3"), 637 build.WithFile("test dir/test_file4", "test4"), 638 build.WithFile("test_dir/test_file5", "test5"), 639 build.WithFile("test dir/test_file6", "test6"), 640 )) 641 } 642 643 func (s *DockerCLIBuildSuite) TestBuildCopyWildcard(c *testing.T) { 644 const name = "testcopywildcard" 645 server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{ 646 "robots.txt": "hello", 647 "index.html": "world", 648 })) 649 defer server.Close() 650 651 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(fmt.Sprintf(`FROM busybox 652 COPY file*.txt /tmp/ 653 RUN ls /tmp/file1.txt /tmp/file2.txt 654 RUN [ "mkdir", "/tmp1" ] 655 COPY dir* /tmp1/ 656 RUN ls /tmp1/dirt /tmp1/nested_file /tmp1/nested_dir/nest_nest_file 657 RUN [ "mkdir", "/tmp2" ] 658 ADD dir/*dir %s/robots.txt /tmp2/ 659 RUN ls /tmp2/nest_nest_file /tmp2/robots.txt 660 `, server.URL())), 661 fakecontext.WithFiles(map[string]string{ 662 "file1.txt": "test1", 663 "file2.txt": "test2", 664 "dir/nested_file": "nested file", 665 "dir/nested_dir/nest_nest_file": "2 times nested", 666 "dirt": "dirty", 667 })) 668 defer ctx.Close() 669 670 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 671 id1 := getIDByName(c, name) 672 673 // Now make sure we use a cache the 2nd time 674 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 675 id2 := getIDByName(c, name) 676 677 if id1 != id2 { 678 c.Fatal("didn't use the cache") 679 } 680 } 681 682 func (s *DockerCLIBuildSuite) TestBuildCopyWildcardInName(c *testing.T) { 683 // Run this only on Linux 684 // Below is the original comment (that I don't agree with — vdemeester) 685 // Normally we would do c.Fatal(err) here but given that 686 // the odds of this failing are so rare, it must be because 687 // the OS we're running the client on doesn't support * in 688 // filenames (like windows). So, instead of failing the test 689 // just let it pass. Then we don't need to explicitly 690 // say which OSs this works on or not. 691 testRequires(c, DaemonIsLinux, UnixCli) 692 693 buildImageSuccessfully(c, "testcopywildcardinname", build.WithBuildContext(c, 694 build.WithFile("Dockerfile", `FROM busybox 695 COPY *.txt /tmp/ 696 RUN [ "$(cat /tmp/\*.txt)" = 'hi there' ] 697 `), 698 build.WithFile("*.txt", "hi there"), 699 )) 700 } 701 702 func (s *DockerCLIBuildSuite) TestBuildCopyWildcardCache(c *testing.T) { 703 const name = "testcopywildcardcache" 704 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`FROM busybox 705 COPY file1.txt /tmp/`), 706 fakecontext.WithFiles(map[string]string{ 707 "file1.txt": "test1", 708 })) 709 defer ctx.Close() 710 711 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 712 id1 := getIDByName(c, name) 713 714 // Now make sure we use a cache the 2nd time even with wild cards. 715 // Use the same context so the file is the same and the checksum will match 716 ctx.Add("Dockerfile", `FROM busybox 717 COPY file*.txt /tmp/`) 718 719 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 720 id2 := getIDByName(c, name) 721 722 if id1 != id2 { 723 c.Fatal("didn't use the cache") 724 } 725 } 726 727 func (s *DockerCLIBuildSuite) TestBuildAddSingleFileToNonExistingDir(c *testing.T) { 728 testRequires(c, DaemonIsLinux) // Linux specific test 729 buildImageSuccessfully(c, "testaddsinglefiletononexistingdir", build.WithBuildContext(c, 730 build.WithFile("Dockerfile", `FROM busybox 731 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 732 RUN echo 'dockerio:x:1001:' >> /etc/group 733 RUN touch /exists 734 RUN chown dockerio.dockerio /exists 735 ADD test_file /test_dir/ 736 RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] 737 RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] 738 RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`), 739 build.WithFile("test_file", "test1"))) 740 } 741 742 func (s *DockerCLIBuildSuite) TestBuildAddDirContentToRoot(c *testing.T) { 743 testRequires(c, DaemonIsLinux) // Linux specific test 744 buildImageSuccessfully(c, "testadddircontenttoroot", build.WithBuildContext(c, 745 build.WithFile("Dockerfile", `FROM busybox 746 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 747 RUN echo 'dockerio:x:1001:' >> /etc/group 748 RUN touch /exists 749 RUN chown dockerio.dockerio exists 750 ADD test_dir / 751 RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] 752 RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`), 753 build.WithFile("test_dir/test_file", "test1"))) 754 } 755 756 func (s *DockerCLIBuildSuite) TestBuildAddDirContentToExistingDir(c *testing.T) { 757 testRequires(c, DaemonIsLinux) // Linux specific test 758 buildImageSuccessfully(c, "testadddircontenttoexistingdir", build.WithBuildContext(c, 759 build.WithFile("Dockerfile", `FROM busybox 760 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 761 RUN echo 'dockerio:x:1001:' >> /etc/group 762 RUN mkdir /exists 763 RUN touch /exists/exists_file 764 RUN chown -R dockerio.dockerio /exists 765 ADD test_dir/ /exists/ 766 RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] 767 RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ] 768 RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`), 769 build.WithFile("test_dir/test_file", "test1"))) 770 } 771 772 func (s *DockerCLIBuildSuite) TestBuildAddWholeDirToRoot(c *testing.T) { 773 testRequires(c, DaemonIsLinux) // Linux specific test 774 buildImageSuccessfully(c, "testaddwholedirtoroot", build.WithBuildContext(c, 775 build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox 776 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 777 RUN echo 'dockerio:x:1001:' >> /etc/group 778 RUN touch /exists 779 RUN chown dockerio.dockerio exists 780 ADD test_dir /test_dir 781 RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] 782 RUN [ $(ls -l / | grep test_dir | awk '{print $1}') = 'drwxr-xr-x' ] 783 RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] 784 RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '%s' ] 785 RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod)), 786 build.WithFile("test_dir/test_file", "test1"))) 787 } 788 789 // Testing #5941 : Having an etc directory in context conflicts with the /etc/mtab 790 func (s *DockerCLIBuildSuite) TestBuildAddOrCopyEtcToRootShouldNotConflict(c *testing.T) { 791 buildImageSuccessfully(c, "testaddetctoroot", build.WithBuildContext(c, 792 build.WithFile("Dockerfile", `FROM `+minimalBaseImage()+` 793 ADD . /`), 794 build.WithFile("etc/test_file", "test1"))) 795 buildImageSuccessfully(c, "testcopyetctoroot", build.WithBuildContext(c, 796 build.WithFile("Dockerfile", `FROM `+minimalBaseImage()+` 797 COPY . /`), 798 build.WithFile("etc/test_file", "test1"))) 799 } 800 801 // Testing #9401 : Losing setuid flag after a ADD 802 func (s *DockerCLIBuildSuite) TestBuildAddPreservesFilesSpecialBits(c *testing.T) { 803 testRequires(c, DaemonIsLinux) // Linux specific test 804 buildImageSuccessfully(c, "testaddetctoroot", build.WithBuildContext(c, 805 build.WithFile("Dockerfile", `FROM busybox 806 ADD suidbin /usr/bin/suidbin 807 RUN chmod 4755 /usr/bin/suidbin 808 RUN [ $(ls -l /usr/bin/suidbin | awk '{print $1}') = '-rwsr-xr-x' ] 809 ADD ./data/ / 810 RUN [ $(ls -l /usr/bin/suidbin | awk '{print $1}') = '-rwsr-xr-x' ]`), 811 build.WithFile("suidbin", "suidbin"), 812 build.WithFile("/data/usr/test_file", "test1"))) 813 } 814 815 func (s *DockerCLIBuildSuite) TestBuildCopySingleFileToRoot(c *testing.T) { 816 testRequires(c, DaemonIsLinux) // Linux specific test 817 buildImageSuccessfully(c, "testcopysinglefiletoroot", build.WithBuildContext(c, 818 build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox 819 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 820 RUN echo 'dockerio:x:1001:' >> /etc/group 821 RUN touch /exists 822 RUN chown dockerio.dockerio /exists 823 COPY test_file / 824 RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] 825 RUN [ $(ls -l /test_file | awk '{print $1}') = '%s' ] 826 RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod)), 827 build.WithFile("test_file", "test1"))) 828 } 829 830 // Issue #3960: "ADD src ." hangs - adapted for COPY 831 func (s *DockerCLIBuildSuite) TestBuildCopySingleFileToWorkdir(c *testing.T) { 832 const name = "testcopysinglefiletoworkdir" 833 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`FROM busybox 834 COPY test_file .`), 835 fakecontext.WithFiles(map[string]string{ 836 "test_file": "test1", 837 })) 838 defer ctx.Close() 839 840 errChan := make(chan error, 1) 841 go func() { 842 errChan <- buildImage(name, build.WithExternalBuildContext(ctx)).Error 843 close(errChan) 844 }() 845 select { 846 case <-time.After(15 * time.Second): 847 c.Fatal("Build with adding to workdir timed out") 848 case err := <-errChan: 849 assert.NilError(c, err) 850 } 851 } 852 853 func (s *DockerCLIBuildSuite) TestBuildCopySingleFileToExistDir(c *testing.T) { 854 testRequires(c, DaemonIsLinux) // Linux specific test 855 buildImageSuccessfully(c, "testcopysinglefiletoexistdir", build.WithBuildContext(c, 856 build.WithFile("Dockerfile", `FROM busybox 857 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 858 RUN echo 'dockerio:x:1001:' >> /etc/group 859 RUN mkdir /exists 860 RUN touch /exists/exists_file 861 RUN chown -R dockerio.dockerio /exists 862 COPY test_file /exists/ 863 RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] 864 RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ] 865 RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`), 866 build.WithFile("test_file", "test1"))) 867 } 868 869 func (s *DockerCLIBuildSuite) TestBuildCopySingleFileToNonExistDir(c *testing.T) { 870 testRequires(c, DaemonIsLinux) // Linux specific 871 buildImageSuccessfully(c, "testcopysinglefiletononexistdir", build.WithBuildContext(c, 872 build.WithFile("Dockerfile", `FROM busybox 873 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 874 RUN echo 'dockerio:x:1001:' >> /etc/group 875 RUN touch /exists 876 RUN chown dockerio.dockerio /exists 877 COPY test_file /test_dir/ 878 RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] 879 RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] 880 RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`), 881 build.WithFile("test_file", "test1"))) 882 } 883 884 func (s *DockerCLIBuildSuite) TestBuildCopyDirContentToRoot(c *testing.T) { 885 testRequires(c, DaemonIsLinux) // Linux specific test 886 buildImageSuccessfully(c, "testcopydircontenttoroot", build.WithBuildContext(c, 887 build.WithFile("Dockerfile", `FROM busybox 888 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 889 RUN echo 'dockerio:x:1001:' >> /etc/group 890 RUN touch /exists 891 RUN chown dockerio.dockerio exists 892 COPY test_dir / 893 RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] 894 RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`), 895 build.WithFile("test_dir/test_file", "test1"))) 896 } 897 898 func (s *DockerCLIBuildSuite) TestBuildCopyDirContentToExistDir(c *testing.T) { 899 testRequires(c, DaemonIsLinux) // Linux specific test 900 buildImageSuccessfully(c, "testcopydircontenttoexistdir", build.WithBuildContext(c, 901 build.WithFile("Dockerfile", `FROM busybox 902 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 903 RUN echo 'dockerio:x:1001:' >> /etc/group 904 RUN mkdir /exists 905 RUN touch /exists/exists_file 906 RUN chown -R dockerio.dockerio /exists 907 COPY test_dir/ /exists/ 908 RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] 909 RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ] 910 RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`), 911 build.WithFile("test_dir/test_file", "test1"))) 912 } 913 914 func (s *DockerCLIBuildSuite) TestBuildCopyWholeDirToRoot(c *testing.T) { 915 testRequires(c, DaemonIsLinux) // Linux specific test 916 buildImageSuccessfully(c, "testcopywholedirtoroot", build.WithBuildContext(c, 917 build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox 918 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 919 RUN echo 'dockerio:x:1001:' >> /etc/group 920 RUN touch /exists 921 RUN chown dockerio.dockerio exists 922 COPY test_dir /test_dir 923 RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] 924 RUN [ $(ls -l / | grep test_dir | awk '{print $1}') = 'drwxr-xr-x' ] 925 RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] 926 RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '%s' ] 927 RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod)), 928 build.WithFile("test_dir/test_file", "test1"))) 929 } 930 931 func (s *DockerCLIBuildSuite) TestBuildAddBadLinks(c *testing.T) { 932 testRequires(c, DaemonIsLinux) // Not currently working on Windows 933 934 dockerfile := ` 935 FROM scratch 936 ADD links.tar / 937 ADD foo.txt /symlink/ 938 ` 939 targetFile := "foo.txt" 940 const name = "test-link-absolute" 941 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile)) 942 defer ctx.Close() 943 944 tempDir, err := os.MkdirTemp("", "test-link-absolute-temp-") 945 if err != nil { 946 c.Fatalf("failed to create temporary directory: %s", tempDir) 947 } 948 defer os.RemoveAll(tempDir) 949 950 var symlinkTarget string 951 if runtime.GOOS == "windows" { 952 var driveLetter string 953 if abs, err := filepath.Abs(tempDir); err != nil { 954 c.Fatal(err) 955 } else { 956 driveLetter = abs[:1] 957 } 958 tempDirWithoutDrive := tempDir[2:] 959 symlinkTarget = fmt.Sprintf(`%s:\..\..\..\..\..\..\..\..\..\..\..\..%s`, driveLetter, tempDirWithoutDrive) 960 } else { 961 symlinkTarget = fmt.Sprintf("/../../../../../../../../../../../..%s", tempDir) 962 } 963 964 tarPath := filepath.Join(ctx.Dir, "links.tar") 965 nonExistingFile := filepath.Join(tempDir, targetFile) 966 fooPath := filepath.Join(ctx.Dir, targetFile) 967 968 tarOut, err := os.Create(tarPath) 969 if err != nil { 970 c.Fatal(err) 971 } 972 defer tarOut.Close() 973 974 tarWriter := tar.NewWriter(tarOut) 975 defer tarWriter.Close() 976 977 header := &tar.Header{ 978 Name: "symlink", 979 Typeflag: tar.TypeSymlink, 980 Linkname: symlinkTarget, 981 Mode: 0o755, 982 Uid: 0, 983 Gid: 0, 984 } 985 986 err = tarWriter.WriteHeader(header) 987 if err != nil { 988 c.Fatal(err) 989 } 990 991 err = os.WriteFile(fooPath, []byte("test"), 0666) 992 if err != nil { 993 c.Fatal(err) 994 } 995 996 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 997 if _, err := os.Stat(nonExistingFile); err == nil || !os.IsNotExist(err) { 998 c.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile) 999 } 1000 } 1001 1002 func (s *DockerCLIBuildSuite) TestBuildAddBadLinksVolume(c *testing.T) { 1003 testRequires(c, DaemonIsLinux) // ln not implemented on Windows busybox 1004 const ( 1005 dockerfileTemplate = ` 1006 FROM busybox 1007 RUN ln -s /../../../../../../../../%s /x 1008 VOLUME /x 1009 ADD foo.txt /x/` 1010 targetFile = "foo.txt" 1011 ) 1012 1013 tempDir, err := os.MkdirTemp("", "test-link-absolute-volume-temp-") 1014 if err != nil { 1015 c.Fatalf("failed to create temporary directory: %s", tempDir) 1016 } 1017 defer os.RemoveAll(tempDir) 1018 1019 dockerfile := fmt.Sprintf(dockerfileTemplate, tempDir) 1020 nonExistingFile := filepath.Join(tempDir, targetFile) 1021 1022 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile)) 1023 defer ctx.Close() 1024 fooPath := filepath.Join(ctx.Dir, targetFile) 1025 1026 err = os.WriteFile(fooPath, []byte("test"), 0666) 1027 if err != nil { 1028 c.Fatal(err) 1029 } 1030 1031 buildImageSuccessfully(c, "test-link-absolute-volume", build.WithExternalBuildContext(ctx)) 1032 if _, err := os.Stat(nonExistingFile); err == nil || !os.IsNotExist(err) { 1033 c.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile) 1034 } 1035 } 1036 1037 // Issue #5270 - ensure we throw a better error than "unexpected EOF" 1038 // when we can't access files in the context. 1039 func (s *DockerCLIBuildSuite) TestBuildWithInaccessibleFilesInContext(c *testing.T) { 1040 testRequires(c, DaemonIsLinux, UnixCli, testEnv.IsLocalDaemon) // test uses chown/chmod: not available on windows 1041 1042 { 1043 const name = "testbuildinaccessiblefiles" 1044 ctx := fakecontext.New(c, "", 1045 fakecontext.WithDockerfile("FROM scratch\nADD . /foo/"), 1046 fakecontext.WithFiles(map[string]string{"fileWithoutReadAccess": "foo"}), 1047 ) 1048 defer ctx.Close() 1049 // This is used to ensure we detect inaccessible files early during build in the cli client 1050 pathToFileWithoutReadAccess := filepath.Join(ctx.Dir, "fileWithoutReadAccess") 1051 1052 if err := os.Chown(pathToFileWithoutReadAccess, 0, 0); err != nil { 1053 c.Fatalf("failed to chown file to root: %s", err) 1054 } 1055 if err := os.Chmod(pathToFileWithoutReadAccess, 0o700); err != nil { 1056 c.Fatalf("failed to chmod file to 700: %s", err) 1057 } 1058 result := icmd.RunCmd(icmd.Cmd{ 1059 Command: []string{"su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)}, 1060 Dir: ctx.Dir, 1061 }) 1062 if result.Error == nil { 1063 c.Fatalf("build should have failed: %s %s", result.Error, result.Combined()) 1064 } 1065 1066 // check if we've detected the failure before we started building 1067 if !strings.Contains(result.Combined(), "no permission to read from ") { 1068 c.Fatalf("output should've contained the string: no permission to read from but contained: %s", result.Combined()) 1069 } 1070 1071 if !strings.Contains(result.Combined(), "error checking context") { 1072 c.Fatalf("output should've contained the string: error checking context") 1073 } 1074 } 1075 { 1076 const name = "testbuildinaccessibledirectory" 1077 ctx := fakecontext.New(c, "", 1078 fakecontext.WithDockerfile("FROM scratch\nADD . /foo/"), 1079 fakecontext.WithFiles(map[string]string{"directoryWeCantStat/bar": "foo"}), 1080 ) 1081 defer ctx.Close() 1082 // This is used to ensure we detect inaccessible directories early during build in the cli client 1083 pathToDirectoryWithoutReadAccess := filepath.Join(ctx.Dir, "directoryWeCantStat") 1084 pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar") 1085 1086 if err := os.Chown(pathToDirectoryWithoutReadAccess, 0, 0); err != nil { 1087 c.Fatalf("failed to chown directory to root: %s", err) 1088 } 1089 if err := os.Chmod(pathToDirectoryWithoutReadAccess, 0o444); err != nil { 1090 c.Fatalf("failed to chmod directory to 444: %s", err) 1091 } 1092 if err := os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0o700); err != nil { 1093 c.Fatalf("failed to chmod file to 700: %s", err) 1094 } 1095 1096 result := icmd.RunCmd(icmd.Cmd{ 1097 Command: []string{"su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)}, 1098 Dir: ctx.Dir, 1099 }) 1100 if result.Error == nil { 1101 c.Fatalf("build should have failed: %s %s", result.Error, result.Combined()) 1102 } 1103 1104 // check if we've detected the failure before we started building 1105 if !strings.Contains(result.Combined(), "can't stat") { 1106 c.Fatalf("output should've contained the string: can't access %s", result.Combined()) 1107 } 1108 1109 if !strings.Contains(result.Combined(), "error checking context") { 1110 c.Fatalf("output should've contained the string: error checking context\ngot:%s", result.Combined()) 1111 } 1112 } 1113 { 1114 const name = "testlinksok" 1115 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile("FROM scratch\nADD . /foo/")) 1116 defer ctx.Close() 1117 1118 target := "../../../../../../../../../../../../../../../../../../../azA" 1119 if err := os.Symlink(filepath.Join(ctx.Dir, "g"), target); err != nil { 1120 c.Fatal(err) 1121 } 1122 defer os.Remove(target) 1123 // This is used to ensure we don't follow links when checking if everything in the context is accessible 1124 // This test doesn't require that we run commands as an unprivileged user 1125 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 1126 } 1127 { 1128 const name = "testbuildignoredinaccessible" 1129 ctx := fakecontext.New(c, "", 1130 fakecontext.WithDockerfile("FROM scratch\nADD . /foo/"), 1131 fakecontext.WithFiles(map[string]string{ 1132 "directoryWeCantStat/bar": "foo", 1133 ".dockerignore": "directoryWeCantStat", 1134 }), 1135 ) 1136 defer ctx.Close() 1137 // This is used to ensure we don't try to add inaccessible files when they are ignored by a .dockerignore pattern 1138 pathToDirectoryWithoutReadAccess := filepath.Join(ctx.Dir, "directoryWeCantStat") 1139 pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar") 1140 if err := os.Chown(pathToDirectoryWithoutReadAccess, 0, 0); err != nil { 1141 c.Fatalf("failed to chown directory to root: %s", err) 1142 } 1143 if err := os.Chmod(pathToDirectoryWithoutReadAccess, 0o444); err != nil { 1144 c.Fatalf("failed to chmod directory to 444: %s", err) 1145 } 1146 if err := os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0o700); err != nil { 1147 c.Fatalf("failed to chmod file to 700: %s", err) 1148 } 1149 1150 result := icmd.RunCmd(icmd.Cmd{ 1151 Dir: ctx.Dir, 1152 Command: []string{ 1153 "su", "unprivilegeduser", "-c", 1154 fmt.Sprintf("%s build -t %s .", dockerBinary, name), 1155 }, 1156 }) 1157 result.Assert(c, icmd.Expected{}) 1158 } 1159 } 1160 1161 func (s *DockerCLIBuildSuite) TestBuildForceRm(c *testing.T) { 1162 containerCountBefore := getContainerCount(c) 1163 const name = "testbuildforcerm" 1164 1165 r := buildImage(name, cli.WithFlags("--force-rm"), build.WithBuildContext(c, 1166 build.WithFile("Dockerfile", `FROM busybox 1167 RUN true 1168 RUN thiswillfail`))) 1169 if r.ExitCode != 1 && r.ExitCode != 127 { // different on Linux / Windows 1170 c.Fatalf("Wrong exit code") 1171 } 1172 1173 containerCountAfter := getContainerCount(c) 1174 if containerCountBefore != containerCountAfter { 1175 c.Fatalf("--force-rm shouldn't have left containers behind") 1176 } 1177 } 1178 1179 func (s *DockerCLIBuildSuite) TestBuildRm(c *testing.T) { 1180 const name = "testbuildrm" 1181 1182 testCases := []struct { 1183 buildflags []string 1184 shouldLeftContainerBehind bool 1185 }{ 1186 // Default case (i.e. --rm=true) 1187 { 1188 buildflags: []string{}, 1189 shouldLeftContainerBehind: false, 1190 }, 1191 { 1192 buildflags: []string{"--rm"}, 1193 shouldLeftContainerBehind: false, 1194 }, 1195 { 1196 buildflags: []string{"--rm=false"}, 1197 shouldLeftContainerBehind: true, 1198 }, 1199 } 1200 1201 for _, tc := range testCases { 1202 containerCountBefore := getContainerCount(c) 1203 1204 buildImageSuccessfully(c, name, cli.WithFlags(tc.buildflags...), build.WithDockerfile(`FROM busybox 1205 RUN echo hello world`)) 1206 1207 containerCountAfter := getContainerCount(c) 1208 if tc.shouldLeftContainerBehind { 1209 if containerCountBefore == containerCountAfter { 1210 c.Fatalf("flags %v should have left containers behind", tc.buildflags) 1211 } 1212 } else { 1213 if containerCountBefore != containerCountAfter { 1214 c.Fatalf("flags %v shouldn't have left containers behind", tc.buildflags) 1215 } 1216 } 1217 1218 cli.DockerCmd(c, "rmi", name) 1219 } 1220 } 1221 1222 func (s *DockerCLIBuildSuite) TestBuildWithVolumes(c *testing.T) { 1223 testRequires(c, DaemonIsLinux) // Invalid volume paths on Windows 1224 var ( 1225 result map[string]map[string]struct{} 1226 name = "testbuildvolumes" 1227 emptyMap = make(map[string]struct{}) 1228 expected = map[string]map[string]struct{}{ 1229 "/test1": emptyMap, 1230 "/test2": emptyMap, 1231 "/test3": emptyMap, 1232 "/test4": emptyMap, 1233 "/test5": emptyMap, 1234 "/test6": emptyMap, 1235 "[/test7": emptyMap, 1236 "/test8]": emptyMap, 1237 } 1238 ) 1239 1240 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM scratch 1241 VOLUME /test1 1242 VOLUME /test2 1243 VOLUME /test3 /test4 1244 VOLUME ["/test5", "/test6"] 1245 VOLUME [/test7 /test8] 1246 `)) 1247 1248 inspectFieldAndUnmarshall(c, name, "Config.Volumes", &result) 1249 1250 equal := reflect.DeepEqual(&result, &expected) 1251 if !equal { 1252 c.Fatalf("Volumes %s, expected %s", result, expected) 1253 } 1254 } 1255 1256 func (s *DockerCLIBuildSuite) TestBuildMaintainer(c *testing.T) { 1257 const name = "testbuildmaintainer" 1258 1259 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+` 1260 MAINTAINER dockerio`)) 1261 1262 expected := "dockerio" 1263 res := inspectField(c, name, "Author") 1264 if res != expected { 1265 c.Fatalf("Maintainer %s, expected %s", res, expected) 1266 } 1267 } 1268 1269 func (s *DockerCLIBuildSuite) TestBuildUser(c *testing.T) { 1270 testRequires(c, DaemonIsLinux) 1271 const name = "testbuilduser" 1272 expected := "dockerio" 1273 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 1274 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 1275 USER dockerio 1276 RUN [ $(whoami) = 'dockerio' ]`)) 1277 res := inspectField(c, name, "Config.User") 1278 if res != expected { 1279 c.Fatalf("User %s, expected %s", res, expected) 1280 } 1281 } 1282 1283 func (s *DockerCLIBuildSuite) TestBuildRelativeWorkdir(c *testing.T) { 1284 const name = "testbuildrelativeworkdir" 1285 1286 var ( 1287 expected1 string 1288 expected2 string 1289 expected3 string 1290 expected4 string 1291 expectedFinal string 1292 ) 1293 1294 if testEnv.DaemonInfo.OSType == "windows" { 1295 expected1 = `C:/` 1296 expected2 = `C:/test1` 1297 expected3 = `C:/test2` 1298 expected4 = `C:/test2/test3` 1299 expectedFinal = `C:\test2\test3` // Note inspect is going to return Windows paths, as it's not in busybox 1300 } else { 1301 expected1 = `/` 1302 expected2 = `/test1` 1303 expected3 = `/test2` 1304 expected4 = `/test2/test3` 1305 expectedFinal = `/test2/test3` 1306 } 1307 1308 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 1309 RUN sh -c "[ "$PWD" = "`+expected1+`" ]" 1310 WORKDIR test1 1311 RUN sh -c "[ "$PWD" = "`+expected2+`" ]" 1312 WORKDIR /test2 1313 RUN sh -c "[ "$PWD" = "`+expected3+`" ]" 1314 WORKDIR test3 1315 RUN sh -c "[ "$PWD" = "`+expected4+`" ]"`)) 1316 1317 res := inspectField(c, name, "Config.WorkingDir") 1318 if res != expectedFinal { 1319 c.Fatalf("Workdir %s, expected %s", res, expectedFinal) 1320 } 1321 } 1322 1323 // #22181 Regression test. Single end-to-end test of using 1324 // Windows semantics. Most path handling verifications are in unit tests 1325 func (s *DockerCLIBuildSuite) TestBuildWindowsWorkdirProcessing(c *testing.T) { 1326 testRequires(c, DaemonIsWindows) 1327 buildImageSuccessfully(c, "testbuildwindowsworkdirprocessing", build.WithDockerfile(`FROM busybox 1328 WORKDIR C:\\foo 1329 WORKDIR bar 1330 RUN sh -c "[ "$PWD" = "C:/foo/bar" ]" 1331 `)) 1332 } 1333 1334 // #22181 Regression test. Most paths handling verifications are in unit test. 1335 // One functional test for end-to-end 1336 func (s *DockerCLIBuildSuite) TestBuildWindowsAddCopyPathProcessing(c *testing.T) { 1337 testRequires(c, DaemonIsWindows) 1338 // TODO Windows. Needs a follow-up PR to 22181 to 1339 // support backslash such as .\\ being equivalent to ./ and c:\\ being 1340 // equivalent to c:/. This is not currently (nor ever has been) supported 1341 // by docker on the Windows platform. 1342 buildImageSuccessfully(c, "testbuildwindowsaddcopypathprocessing", build.WithBuildContext(c, 1343 build.WithFile("Dockerfile", `FROM busybox 1344 # No trailing slash on COPY/ADD 1345 # Results in dir being changed to a file 1346 WORKDIR /wc1 1347 COPY wc1 c:/wc1 1348 WORKDIR /wc2 1349 ADD wc2 c:/wc2 1350 WORKDIR c:/ 1351 RUN sh -c "[ $(cat c:/wc1/wc1) = 'hellowc1' ]" 1352 RUN sh -c "[ $(cat c:/wc2/wc2) = 'worldwc2' ]" 1353 1354 # Trailing slash on COPY/ADD, Windows-style path. 1355 WORKDIR /wd1 1356 COPY wd1 c:/wd1/ 1357 WORKDIR /wd2 1358 ADD wd2 c:/wd2/ 1359 RUN sh -c "[ $(cat c:/wd1/wd1) = 'hellowd1' ]" 1360 RUN sh -c "[ $(cat c:/wd2/wd2) = 'worldwd2' ]" 1361 `), 1362 build.WithFile("wc1", "hellowc1"), 1363 build.WithFile("wc2", "worldwc2"), 1364 build.WithFile("wd1", "hellowd1"), 1365 build.WithFile("wd2", "worldwd2"), 1366 )) 1367 } 1368 1369 func (s *DockerCLIBuildSuite) TestBuildWorkdirWithEnvVariables(c *testing.T) { 1370 const name = "testbuildworkdirwithenvvariables" 1371 1372 var expected string 1373 if testEnv.DaemonInfo.OSType == "windows" { 1374 expected = `C:\test1\test2` 1375 } else { 1376 expected = `/test1/test2` 1377 } 1378 1379 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 1380 ENV DIRPATH /test1 1381 ENV SUBDIRNAME test2 1382 WORKDIR $DIRPATH 1383 WORKDIR $SUBDIRNAME/$MISSING_VAR`)) 1384 res := inspectField(c, name, "Config.WorkingDir") 1385 if res != expected { 1386 c.Fatalf("Workdir %s, expected %s", res, expected) 1387 } 1388 } 1389 1390 func (s *DockerCLIBuildSuite) TestBuildRelativeCopy(c *testing.T) { 1391 // cat /test1/test2/foo gets permission denied for the user 1392 testRequires(c, NotUserNamespace) 1393 1394 var expected string 1395 if testEnv.DaemonInfo.OSType == "windows" { 1396 expected = `C:/test1/test2` 1397 } else { 1398 expected = `/test1/test2` 1399 } 1400 1401 buildImageSuccessfully(c, "testbuildrelativecopy", build.WithBuildContext(c, 1402 build.WithFile("Dockerfile", `FROM busybox 1403 WORKDIR /test1 1404 WORKDIR test2 1405 RUN sh -c "[ "$PWD" = '`+expected+`' ]" 1406 COPY foo ./ 1407 RUN sh -c "[ $(cat /test1/test2/foo) = 'hello' ]" 1408 ADD foo ./bar/baz 1409 RUN sh -c "[ $(cat /test1/test2/bar/baz) = 'hello' ]" 1410 COPY foo ./bar/baz2 1411 RUN sh -c "[ $(cat /test1/test2/bar/baz2) = 'hello' ]" 1412 WORKDIR .. 1413 COPY foo ./ 1414 RUN sh -c "[ $(cat /test1/foo) = 'hello' ]" 1415 COPY foo /test3/ 1416 RUN sh -c "[ $(cat /test3/foo) = 'hello' ]" 1417 WORKDIR /test4 1418 COPY . . 1419 RUN sh -c "[ $(cat /test4/foo) = 'hello' ]" 1420 WORKDIR /test5/test6 1421 COPY foo ../ 1422 RUN sh -c "[ $(cat /test5/foo) = 'hello' ]" 1423 `), 1424 build.WithFile("foo", "hello"), 1425 )) 1426 } 1427 1428 // FIXME(vdemeester) should be unit test 1429 func (s *DockerCLIBuildSuite) TestBuildBlankName(c *testing.T) { 1430 const name = "testbuildblankname" 1431 testCases := []struct { 1432 expression string 1433 expectedStderr string 1434 }{ 1435 { 1436 expression: "ENV =", 1437 expectedStderr: "ENV names can not be blank", 1438 }, 1439 { 1440 expression: "LABEL =", 1441 expectedStderr: "LABEL names can not be blank", 1442 }, 1443 { 1444 expression: "ARG =foo", 1445 expectedStderr: "ARG names can not be blank", 1446 }, 1447 } 1448 1449 for _, tc := range testCases { 1450 buildImage(name, build.WithDockerfile(fmt.Sprintf(`FROM busybox 1451 %s`, tc.expression))).Assert(c, icmd.Expected{ 1452 ExitCode: 1, 1453 Err: tc.expectedStderr, 1454 }) 1455 } 1456 } 1457 1458 func (s *DockerCLIBuildSuite) TestBuildEnv(c *testing.T) { 1459 testRequires(c, DaemonIsLinux) // ENV expansion is different in Windows 1460 const name = "testbuildenv" 1461 expected := "[PATH=/test:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=2375]" 1462 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 1463 ENV PATH /test:$PATH 1464 ENV PORT 2375 1465 RUN [ $(env | grep PORT) = 'PORT=2375' ]`)) 1466 res := inspectField(c, name, "Config.Env") 1467 if res != expected { 1468 c.Fatalf("Env %s, expected %s", res, expected) 1469 } 1470 } 1471 1472 func (s *DockerCLIBuildSuite) TestBuildPATH(c *testing.T) { 1473 testRequires(c, DaemonIsLinux) // ENV expansion is different in Windows 1474 1475 defPath := "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 1476 1477 fn := func(dockerfile string, expected string) { 1478 buildImageSuccessfully(c, "testbldpath", build.WithDockerfile(dockerfile)) 1479 res := inspectField(c, "testbldpath", "Config.Env") 1480 if res != expected { 1481 c.Fatalf("Env %q, expected %q for dockerfile:%q", res, expected, dockerfile) 1482 } 1483 } 1484 1485 tests := []struct{ dockerfile, exp string }{ 1486 {"FROM scratch\nMAINTAINER me", "[PATH=" + defPath + "]"}, 1487 {"FROM busybox\nMAINTAINER me", "[PATH=" + defPath + "]"}, 1488 {"FROM scratch\nENV FOO=bar", "[PATH=" + defPath + " FOO=bar]"}, 1489 {"FROM busybox\nENV FOO=bar", "[PATH=" + defPath + " FOO=bar]"}, 1490 {"FROM scratch\nENV PATH=/test", "[PATH=/test]"}, 1491 {"FROM busybox\nENV PATH=/test", "[PATH=/test]"}, 1492 {"FROM scratch\nENV PATH=''", "[PATH=]"}, 1493 {"FROM busybox\nENV PATH=''", "[PATH=]"}, 1494 } 1495 1496 for _, test := range tests { 1497 fn(test.dockerfile, test.exp) 1498 } 1499 } 1500 1501 func (s *DockerCLIBuildSuite) TestBuildContextCleanup(c *testing.T) { 1502 testRequires(c, testEnv.IsLocalDaemon) 1503 1504 const name = "testbuildcontextcleanup" 1505 entries, err := os.ReadDir(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "tmp")) 1506 if err != nil { 1507 c.Fatalf("failed to list contents of tmp dir: %s", err) 1508 } 1509 1510 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+` 1511 ENTRYPOINT ["/bin/echo"]`)) 1512 1513 entriesFinal, err := os.ReadDir(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "tmp")) 1514 if err != nil { 1515 c.Fatalf("failed to list contents of tmp dir: %s", err) 1516 } 1517 if err = compareDirectoryEntries(entries, entriesFinal); err != nil { 1518 c.Fatalf("context should have been deleted, but wasn't") 1519 } 1520 } 1521 1522 func (s *DockerCLIBuildSuite) TestBuildContextCleanupFailedBuild(c *testing.T) { 1523 testRequires(c, testEnv.IsLocalDaemon) 1524 1525 const name = "testbuildcontextcleanup" 1526 entries, err := os.ReadDir(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "tmp")) 1527 if err != nil { 1528 c.Fatalf("failed to list contents of tmp dir: %s", err) 1529 } 1530 1531 buildImage(name, build.WithDockerfile(`FROM `+minimalBaseImage()+` 1532 RUN /non/existing/command`)).Assert(c, icmd.Expected{ 1533 ExitCode: 1, 1534 }) 1535 1536 entriesFinal, err := os.ReadDir(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "tmp")) 1537 if err != nil { 1538 c.Fatalf("failed to list contents of tmp dir: %s", err) 1539 } 1540 if err = compareDirectoryEntries(entries, entriesFinal); err != nil { 1541 c.Fatalf("context should have been deleted, but wasn't") 1542 } 1543 } 1544 1545 // compareDirectoryEntries compares two sets of DirEntry (usually taken from a directory) 1546 // and returns an error if different. 1547 func compareDirectoryEntries(e1 []os.DirEntry, e2 []os.DirEntry) error { 1548 var ( 1549 e1Entries = make(map[string]struct{}) 1550 e2Entries = make(map[string]struct{}) 1551 ) 1552 for _, e := range e1 { 1553 e1Entries[e.Name()] = struct{}{} 1554 } 1555 for _, e := range e2 { 1556 e2Entries[e.Name()] = struct{}{} 1557 } 1558 if !reflect.DeepEqual(e1Entries, e2Entries) { 1559 return fmt.Errorf("entries differ") 1560 } 1561 return nil 1562 } 1563 1564 func (s *DockerCLIBuildSuite) TestBuildCmd(c *testing.T) { 1565 const name = "testbuildcmd" 1566 expected := "[/bin/echo Hello World]" 1567 1568 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+` 1569 CMD ["/bin/echo", "Hello World"]`)) 1570 1571 res := inspectField(c, name, "Config.Cmd") 1572 if res != expected { 1573 c.Fatalf("Cmd %s, expected %s", res, expected) 1574 } 1575 } 1576 1577 func (s *DockerCLIBuildSuite) TestBuildExpose(c *testing.T) { 1578 testRequires(c, DaemonIsLinux) // Expose not implemented on Windows 1579 const name = "testbuildexpose" 1580 expected := "map[2375/tcp:{}]" 1581 1582 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM scratch 1583 EXPOSE 2375`)) 1584 1585 res := inspectField(c, name, "Config.ExposedPorts") 1586 if res != expected { 1587 c.Fatalf("Exposed ports %s, expected %s", res, expected) 1588 } 1589 } 1590 1591 func (s *DockerCLIBuildSuite) TestBuildExposeMorePorts(c *testing.T) { 1592 testRequires(c, DaemonIsLinux) // Expose not implemented on Windows 1593 // start building docker file with a large number of ports 1594 portList := make([]string, 50) 1595 line := make([]string, 100) 1596 expectedPorts := make([]int, len(portList)*len(line)) 1597 for i := 0; i < len(portList); i++ { 1598 for j := 0; j < len(line); j++ { 1599 p := i*len(line) + j + 1 1600 line[j] = strconv.Itoa(p) 1601 expectedPorts[p-1] = p 1602 } 1603 if i == len(portList)-1 { 1604 portList[i] = strings.Join(line, " ") 1605 } else { 1606 portList[i] = strings.Join(line, " ") + ` \` 1607 } 1608 } 1609 1610 dockerfile := `FROM scratch 1611 EXPOSE {{range .}} {{.}} 1612 {{end}}` 1613 tmpl := template.Must(template.New("dockerfile").Parse(dockerfile)) 1614 buf := bytes.NewBuffer(nil) 1615 tmpl.Execute(buf, portList) 1616 1617 const name = "testbuildexpose" 1618 buildImageSuccessfully(c, name, build.WithDockerfile(buf.String())) 1619 1620 // check if all the ports are saved inside Config.ExposedPorts 1621 res := inspectFieldJSON(c, name, "Config.ExposedPorts") 1622 var exposedPorts map[string]interface{} 1623 if err := json.Unmarshal([]byte(res), &exposedPorts); err != nil { 1624 c.Fatal(err) 1625 } 1626 1627 for _, p := range expectedPorts { 1628 ep := fmt.Sprintf("%d/tcp", p) 1629 if _, ok := exposedPorts[ep]; !ok { 1630 c.Errorf("Port(%s) is not exposed", ep) 1631 } else { 1632 delete(exposedPorts, ep) 1633 } 1634 } 1635 if len(exposedPorts) != 0 { 1636 c.Errorf("Unexpected extra exposed ports %v", exposedPorts) 1637 } 1638 } 1639 1640 func (s *DockerCLIBuildSuite) TestBuildExposeOrder(c *testing.T) { 1641 testRequires(c, DaemonIsLinux) // Expose not implemented on Windows 1642 buildID := func(name, exposed string) string { 1643 buildImageSuccessfully(c, name, build.WithDockerfile(fmt.Sprintf(`FROM scratch 1644 EXPOSE %s`, exposed))) 1645 id := inspectField(c, name, "Id") 1646 return id 1647 } 1648 1649 id1 := buildID("testbuildexpose1", "80 2375") 1650 id2 := buildID("testbuildexpose2", "2375 80") 1651 if id1 != id2 { 1652 c.Errorf("EXPOSE should invalidate the cache only when ports actually changed") 1653 } 1654 } 1655 1656 func (s *DockerCLIBuildSuite) TestBuildExposeUpperCaseProto(c *testing.T) { 1657 testRequires(c, DaemonIsLinux) // Expose not implemented on Windows 1658 const name = "testbuildexposeuppercaseproto" 1659 expected := "map[5678/udp:{}]" 1660 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM scratch 1661 EXPOSE 5678/UDP`)) 1662 res := inspectField(c, name, "Config.ExposedPorts") 1663 if res != expected { 1664 c.Fatalf("Exposed ports %s, expected %s", res, expected) 1665 } 1666 } 1667 1668 func (s *DockerCLIBuildSuite) TestBuildEmptyEntrypointInheritance(c *testing.T) { 1669 const name = "testbuildentrypointinheritance" 1670 const name2 = "testbuildentrypointinheritance2" 1671 1672 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 1673 ENTRYPOINT ["/bin/echo"]`)) 1674 res := inspectField(c, name, "Config.Entrypoint") 1675 1676 expected := "[/bin/echo]" 1677 if res != expected { 1678 c.Fatalf("Entrypoint %s, expected %s", res, expected) 1679 } 1680 1681 buildImageSuccessfully(c, name2, build.WithDockerfile(fmt.Sprintf(`FROM %s 1682 ENTRYPOINT []`, name))) 1683 res = inspectField(c, name2, "Config.Entrypoint") 1684 1685 expected = "[]" 1686 if res != expected { 1687 c.Fatalf("Entrypoint %s, expected %s", res, expected) 1688 } 1689 } 1690 1691 func (s *DockerCLIBuildSuite) TestBuildEmptyEntrypoint(c *testing.T) { 1692 const name = "testbuildentrypoint" 1693 expected := "[]" 1694 1695 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 1696 ENTRYPOINT []`)) 1697 1698 res := inspectField(c, name, "Config.Entrypoint") 1699 if res != expected { 1700 c.Fatalf("Entrypoint %s, expected %s", res, expected) 1701 } 1702 } 1703 1704 func (s *DockerCLIBuildSuite) TestBuildEntrypoint(c *testing.T) { 1705 const name = "testbuildentrypoint" 1706 1707 expected := "[/bin/echo]" 1708 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+` 1709 ENTRYPOINT ["/bin/echo"]`)) 1710 1711 res := inspectField(c, name, "Config.Entrypoint") 1712 if res != expected { 1713 c.Fatalf("Entrypoint %s, expected %s", res, expected) 1714 } 1715 } 1716 1717 // #6445 ensure ONBUILD triggers aren't committed to grandchildren 1718 func (s *DockerCLIBuildSuite) TestBuildOnBuildLimitedInheritance(c *testing.T) { 1719 buildImageSuccessfully(c, "testonbuildtrigger1", build.WithDockerfile(` 1720 FROM busybox 1721 RUN echo "GRANDPARENT" 1722 ONBUILD RUN echo "ONBUILD PARENT" 1723 `)) 1724 // ONBUILD should be run in second build. 1725 buildImage("testonbuildtrigger2", build.WithDockerfile("FROM testonbuildtrigger1")).Assert(c, icmd.Expected{ 1726 Out: "ONBUILD PARENT", 1727 }) 1728 // ONBUILD should *not* be run in third build. 1729 result := buildImage("testonbuildtrigger3", build.WithDockerfile("FROM testonbuildtrigger2")) 1730 result.Assert(c, icmd.Success) 1731 if strings.Contains(result.Combined(), "ONBUILD PARENT") { 1732 c.Fatalf("ONBUILD instruction ran in grandchild of ONBUILD parent") 1733 } 1734 } 1735 1736 func (s *DockerCLIBuildSuite) TestBuildSameDockerfileWithAndWithoutCache(c *testing.T) { 1737 testRequires(c, DaemonIsLinux) // Expose not implemented on Windows 1738 const name = "testbuildwithcache" 1739 dockerfile := `FROM scratch 1740 MAINTAINER dockerio 1741 EXPOSE 5432 1742 ENTRYPOINT ["/bin/echo"]` 1743 buildImageSuccessfully(c, name, build.WithDockerfile(dockerfile)) 1744 id1 := getIDByName(c, name) 1745 buildImageSuccessfully(c, name, build.WithDockerfile(dockerfile)) 1746 id2 := getIDByName(c, name) 1747 buildImageSuccessfully(c, name, build.WithoutCache, build.WithDockerfile(dockerfile)) 1748 id3 := getIDByName(c, name) 1749 if id1 != id2 { 1750 c.Fatal("The cache should have been used but hasn't.") 1751 } 1752 if id1 == id3 { 1753 c.Fatal("The cache should have been invalided but hasn't.") 1754 } 1755 } 1756 1757 // Make sure that ADD/COPY still populate the cache even if they don't use it 1758 func (s *DockerCLIBuildSuite) TestBuildConditionalCache(c *testing.T) { 1759 const name = "testbuildconditionalcache" 1760 1761 dockerfile := ` 1762 FROM busybox 1763 ADD foo /tmp/` 1764 ctx := fakecontext.New(c, "", 1765 fakecontext.WithDockerfile(dockerfile), 1766 fakecontext.WithFiles(map[string]string{ 1767 "foo": "hello", 1768 })) 1769 defer ctx.Close() 1770 1771 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 1772 id1 := getIDByName(c, name) 1773 1774 if err := ctx.Add("foo", "bye"); err != nil { 1775 c.Fatalf("Error modifying foo: %s", err) 1776 } 1777 1778 // Updating a file should invalidate the cache 1779 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 1780 id2 := getIDByName(c, name) 1781 if id2 == id1 { 1782 c.Fatal("Should not have used the cache") 1783 } 1784 1785 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 1786 id3 := getIDByName(c, name) 1787 if id3 != id2 { 1788 c.Fatal("Should have used the cache") 1789 } 1790 } 1791 1792 func (s *DockerCLIBuildSuite) TestBuildAddMultipleLocalFileWithAndWithoutCache(c *testing.T) { 1793 const name = "testbuildaddmultiplelocalfilewithcache" 1794 baseName := name + "-base" 1795 1796 cli.BuildCmd(c, baseName, build.WithDockerfile(` 1797 FROM busybox 1798 ENTRYPOINT ["/bin/sh"] 1799 `)) 1800 1801 dockerfile := ` 1802 FROM testbuildaddmultiplelocalfilewithcache-base 1803 MAINTAINER dockerio 1804 ADD foo Dockerfile /usr/lib/bla/ 1805 RUN sh -c "[ $(cat /usr/lib/bla/foo) = "hello" ]"` 1806 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile), fakecontext.WithFiles(map[string]string{ 1807 "foo": "hello", 1808 })) 1809 defer ctx.Close() 1810 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 1811 id1 := getIDByName(c, name) 1812 result2 := cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 1813 id2 := getIDByName(c, name) 1814 result3 := cli.BuildCmd(c, name, build.WithoutCache, build.WithExternalBuildContext(ctx)) 1815 id3 := getIDByName(c, name) 1816 if id1 != id2 { 1817 c.Fatalf("The cache should have been used but hasn't: %s", result2.Stdout()) 1818 } 1819 if id1 == id3 { 1820 c.Fatalf("The cache should have been invalided but hasn't: %s", result3.Stdout()) 1821 } 1822 } 1823 1824 func (s *DockerCLIBuildSuite) TestBuildCopyDirButNotFile(c *testing.T) { 1825 const name = "testbuildcopydirbutnotfile" 1826 const name2 = "testbuildcopydirbutnotfile2" 1827 1828 dockerfile := ` 1829 FROM ` + minimalBaseImage() + ` 1830 COPY dir /tmp/` 1831 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile), fakecontext.WithFiles(map[string]string{ 1832 "dir/foo": "hello", 1833 })) 1834 defer ctx.Close() 1835 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 1836 id1 := getIDByName(c, name) 1837 // Check that adding file with similar name doesn't mess with cache 1838 if err := ctx.Add("dir_file", "hello2"); err != nil { 1839 c.Fatal(err) 1840 } 1841 cli.BuildCmd(c, name2, build.WithExternalBuildContext(ctx)) 1842 id2 := getIDByName(c, name2) 1843 if id1 != id2 { 1844 c.Fatal("The cache should have been used but wasn't") 1845 } 1846 } 1847 1848 func (s *DockerCLIBuildSuite) TestBuildAddCurrentDirWithCache(c *testing.T) { 1849 const name = "testbuildaddcurrentdirwithcache" 1850 const name2 = name + "2" 1851 const name3 = name + "3" 1852 const name4 = name + "4" 1853 dockerfile := ` 1854 FROM ` + minimalBaseImage() + ` 1855 MAINTAINER dockerio 1856 ADD . /usr/lib/bla` 1857 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile), fakecontext.WithFiles(map[string]string{ 1858 "foo": "hello", 1859 })) 1860 defer ctx.Close() 1861 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 1862 id1 := getIDByName(c, name) 1863 // Check that adding file invalidate cache of "ADD ." 1864 if err := ctx.Add("bar", "hello2"); err != nil { 1865 c.Fatal(err) 1866 } 1867 buildImageSuccessfully(c, name2, build.WithExternalBuildContext(ctx)) 1868 id2 := getIDByName(c, name2) 1869 if id1 == id2 { 1870 c.Fatal("The cache should have been invalided but hasn't.") 1871 } 1872 // Check that changing file invalidate cache of "ADD ." 1873 if err := ctx.Add("foo", "hello1"); err != nil { 1874 c.Fatal(err) 1875 } 1876 buildImageSuccessfully(c, name3, build.WithExternalBuildContext(ctx)) 1877 id3 := getIDByName(c, name3) 1878 if id2 == id3 { 1879 c.Fatal("The cache should have been invalided but hasn't.") 1880 } 1881 // Check that changing file to same content with different mtime does not 1882 // invalidate cache of "ADD ." 1883 time.Sleep(1 * time.Second) // wait second because of mtime precision 1884 if err := ctx.Add("foo", "hello1"); err != nil { 1885 c.Fatal(err) 1886 } 1887 buildImageSuccessfully(c, name4, build.WithExternalBuildContext(ctx)) 1888 id4 := getIDByName(c, name4) 1889 if id3 != id4 { 1890 c.Fatal("The cache should have been used but hasn't.") 1891 } 1892 } 1893 1894 // FIXME(vdemeester) this really seems to test the same thing as before (TestBuildAddMultipleLocalFileWithAndWithoutCache) 1895 func (s *DockerCLIBuildSuite) TestBuildAddCurrentDirWithoutCache(c *testing.T) { 1896 const name = "testbuildaddcurrentdirwithoutcache" 1897 dockerfile := ` 1898 FROM ` + minimalBaseImage() + ` 1899 MAINTAINER dockerio 1900 ADD . /usr/lib/bla` 1901 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile), fakecontext.WithFiles(map[string]string{ 1902 "foo": "hello", 1903 })) 1904 defer ctx.Close() 1905 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 1906 id1 := getIDByName(c, name) 1907 buildImageSuccessfully(c, name, build.WithoutCache, build.WithExternalBuildContext(ctx)) 1908 id2 := getIDByName(c, name) 1909 if id1 == id2 { 1910 c.Fatal("The cache should have been invalided but hasn't.") 1911 } 1912 } 1913 1914 func (s *DockerCLIBuildSuite) TestBuildAddRemoteFileWithAndWithoutCache(c *testing.T) { 1915 const name = "testbuildaddremotefilewithcache" 1916 server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{ 1917 "baz": "hello", 1918 })) 1919 defer server.Close() 1920 1921 dockerfile := fmt.Sprintf(`FROM `+minimalBaseImage()+` 1922 MAINTAINER dockerio 1923 ADD %s/baz /usr/lib/baz/quux`, server.URL()) 1924 cli.BuildCmd(c, name, build.WithDockerfile(dockerfile)) 1925 id1 := getIDByName(c, name) 1926 cli.BuildCmd(c, name, build.WithDockerfile(dockerfile)) 1927 id2 := getIDByName(c, name) 1928 cli.BuildCmd(c, name, build.WithoutCache, build.WithDockerfile(dockerfile)) 1929 id3 := getIDByName(c, name) 1930 1931 if id1 != id2 { 1932 c.Fatal("The cache should have been used but hasn't.") 1933 } 1934 if id1 == id3 { 1935 c.Fatal("The cache should have been invalided but hasn't.") 1936 } 1937 } 1938 1939 func (s *DockerCLIBuildSuite) TestBuildAddRemoteFileMTime(c *testing.T) { 1940 const name = "testbuildaddremotefilemtime" 1941 const name2 = name + "2" 1942 const name3 = name + "3" 1943 1944 files := map[string]string{"baz": "hello"} 1945 server := fakestorage.New(c, "", fakecontext.WithFiles(files)) 1946 defer server.Close() 1947 1948 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(fmt.Sprintf(`FROM `+minimalBaseImage()+` 1949 MAINTAINER dockerio 1950 ADD %s/baz /usr/lib/baz/quux`, server.URL()))) 1951 defer ctx.Close() 1952 1953 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 1954 id1 := getIDByName(c, name) 1955 cli.BuildCmd(c, name2, build.WithExternalBuildContext(ctx)) 1956 id2 := getIDByName(c, name2) 1957 if id1 != id2 { 1958 c.Fatal("The cache should have been used but wasn't - #1") 1959 } 1960 1961 // Now create a different server with same contents (causes different mtime) 1962 // The cache should still be used 1963 1964 // allow some time for clock to pass as mtime precision is only 1s 1965 time.Sleep(2 * time.Second) 1966 1967 server2 := fakestorage.New(c, "", fakecontext.WithFiles(files)) 1968 defer server2.Close() 1969 1970 ctx2 := fakecontext.New(c, "", fakecontext.WithDockerfile(fmt.Sprintf(`FROM `+minimalBaseImage()+` 1971 MAINTAINER dockerio 1972 ADD %s/baz /usr/lib/baz/quux`, server2.URL()))) 1973 defer ctx2.Close() 1974 cli.BuildCmd(c, name3, build.WithExternalBuildContext(ctx2)) 1975 id3 := getIDByName(c, name3) 1976 if id1 != id3 { 1977 c.Fatal("The cache should have been used but wasn't") 1978 } 1979 } 1980 1981 // FIXME(vdemeester) this really seems to test the same thing as before (combined) 1982 func (s *DockerCLIBuildSuite) TestBuildAddLocalAndRemoteFilesWithAndWithoutCache(c *testing.T) { 1983 const name = "testbuildaddlocalandremotefilewithcache" 1984 server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{ 1985 "baz": "hello", 1986 })) 1987 defer server.Close() 1988 1989 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(fmt.Sprintf(`FROM `+minimalBaseImage()+` 1990 MAINTAINER dockerio 1991 ADD foo /usr/lib/bla/bar 1992 ADD %s/baz /usr/lib/baz/quux`, server.URL())), 1993 fakecontext.WithFiles(map[string]string{ 1994 "foo": "hello world", 1995 })) 1996 defer ctx.Close() 1997 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 1998 id1 := getIDByName(c, name) 1999 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 2000 id2 := getIDByName(c, name) 2001 buildImageSuccessfully(c, name, build.WithoutCache, build.WithExternalBuildContext(ctx)) 2002 id3 := getIDByName(c, name) 2003 if id1 != id2 { 2004 c.Fatal("The cache should have been used but hasn't.") 2005 } 2006 if id1 == id3 { 2007 c.Fatal("The cache should have been invalidated but hasn't.") 2008 } 2009 } 2010 2011 func testContextTar(c *testing.T, compression archive.Compression) { 2012 ctx := fakecontext.New(c, "", 2013 fakecontext.WithDockerfile(`FROM busybox 2014 ADD foo /foo 2015 CMD ["cat", "/foo"]`), 2016 fakecontext.WithFiles(map[string]string{ 2017 "foo": "bar", 2018 }), 2019 ) 2020 defer ctx.Close() 2021 context, err := archive.Tar(ctx.Dir, compression) 2022 if err != nil { 2023 c.Fatalf("failed to build context tar: %v", err) 2024 } 2025 const name = "contexttar" 2026 2027 cli.BuildCmd(c, name, build.WithStdinContext(context)) 2028 } 2029 2030 func (s *DockerCLIBuildSuite) TestBuildContextTarGzip(c *testing.T) { 2031 testContextTar(c, archive.Gzip) 2032 } 2033 2034 func (s *DockerCLIBuildSuite) TestBuildContextTarNoCompression(c *testing.T) { 2035 testContextTar(c, archive.Uncompressed) 2036 } 2037 2038 func (s *DockerCLIBuildSuite) TestBuildNoContext(c *testing.T) { 2039 const name = "nocontext" 2040 icmd.RunCmd(icmd.Cmd{ 2041 Command: []string{dockerBinary, "build", "-t", name, "-"}, 2042 Stdin: strings.NewReader( 2043 `FROM busybox 2044 CMD ["echo", "ok"]`), 2045 }).Assert(c, icmd.Success) 2046 2047 if out := cli.DockerCmd(c, "run", "--rm", "nocontext").Combined(); out != "ok\n" { 2048 c.Fatalf("run produced invalid output: %q, expected %q", out, "ok") 2049 } 2050 } 2051 2052 // FIXME(vdemeester) migrate to docker/cli e2e 2053 func (s *DockerCLIBuildSuite) TestBuildDockerfileStdin(c *testing.T) { 2054 const name = "stdindockerfile" 2055 tmpDir, err := os.MkdirTemp("", "fake-context") 2056 assert.NilError(c, err) 2057 err = os.WriteFile(filepath.Join(tmpDir, "foo"), []byte("bar"), 0o600) 2058 assert.NilError(c, err) 2059 2060 icmd.RunCmd(icmd.Cmd{ 2061 Command: []string{dockerBinary, "build", "-t", name, "-f", "-", tmpDir}, 2062 Stdin: strings.NewReader( 2063 `FROM busybox 2064 ADD foo /foo 2065 CMD ["cat", "/foo"]`), 2066 }).Assert(c, icmd.Success) 2067 2068 res := inspectField(c, name, "Config.Cmd") 2069 assert.Equal(c, strings.TrimSpace(res), `[cat /foo]`) 2070 } 2071 2072 // FIXME(vdemeester) migrate to docker/cli tests (unit or e2e) 2073 func (s *DockerCLIBuildSuite) TestBuildDockerfileStdinConflict(c *testing.T) { 2074 const name = "stdindockerfiletarcontext" 2075 icmd.RunCmd(icmd.Cmd{ 2076 Command: []string{dockerBinary, "build", "-t", name, "-f", "-", "-"}, 2077 }).Assert(c, icmd.Expected{ 2078 ExitCode: 1, 2079 Err: "use stdin for both build context and dockerfile", 2080 }) 2081 } 2082 2083 func (s *DockerCLIBuildSuite) TestBuildDockerfileStdinNoExtraFiles(c *testing.T) { 2084 s.testBuildDockerfileStdinNoExtraFiles(c, false, false) 2085 } 2086 2087 func (s *DockerCLIBuildSuite) TestBuildDockerfileStdinDockerignore(c *testing.T) { 2088 s.testBuildDockerfileStdinNoExtraFiles(c, true, false) 2089 } 2090 2091 func (s *DockerCLIBuildSuite) TestBuildDockerfileStdinDockerignoreIgnored(c *testing.T) { 2092 s.testBuildDockerfileStdinNoExtraFiles(c, true, true) 2093 } 2094 2095 func (s *DockerCLIBuildSuite) testBuildDockerfileStdinNoExtraFiles(c *testing.T, hasDockerignore, ignoreDockerignore bool) { 2096 const name = "stdindockerfilenoextra" 2097 tmpDir, err := os.MkdirTemp("", "fake-context") 2098 assert.NilError(c, err) 2099 defer os.RemoveAll(tmpDir) 2100 2101 writeFile := func(filename, content string) { 2102 err = os.WriteFile(filepath.Join(tmpDir, filename), []byte(content), 0o600) 2103 assert.NilError(c, err) 2104 } 2105 2106 writeFile("foo", "bar") 2107 2108 if hasDockerignore { 2109 // Add an empty Dockerfile to verify that it is not added to the image 2110 writeFile("Dockerfile", "") 2111 2112 ignores := "Dockerfile\n" 2113 if ignoreDockerignore { 2114 ignores += ".dockerignore\n" 2115 } 2116 writeFile(".dockerignore", ignores) 2117 } 2118 2119 result := icmd.RunCmd(icmd.Cmd{ 2120 Command: []string{dockerBinary, "build", "-t", name, "-f", "-", tmpDir}, 2121 Stdin: strings.NewReader( 2122 `FROM busybox 2123 COPY . /baz`), 2124 }) 2125 result.Assert(c, icmd.Success) 2126 2127 result = cli.DockerCmd(c, "run", "--rm", name, "ls", "-A", "/baz") 2128 if hasDockerignore && !ignoreDockerignore { 2129 assert.Equal(c, result.Stdout(), ".dockerignore\nfoo\n") 2130 } else { 2131 assert.Equal(c, result.Stdout(), "foo\n") 2132 } 2133 } 2134 2135 func (s *DockerCLIBuildSuite) TestBuildWithVolumeOwnership(c *testing.T) { 2136 testRequires(c, DaemonIsLinux) 2137 const name = "testbuildimg" 2138 2139 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox:latest 2140 RUN mkdir /test && chown daemon:wheel /test && chmod 0600 /test 2141 VOLUME /test`)) 2142 2143 out := cli.DockerCmd(c, "run", "--rm", "testbuildimg", "ls", "-la", "/test").Combined() 2144 if expected := "drw-------"; !strings.Contains(out, expected) { 2145 c.Fatalf("expected %s received %s", expected, out) 2146 } 2147 if expected := "daemon wheel"; !strings.Contains(out, expected) { 2148 c.Fatalf("expected %s received %s", expected, out) 2149 } 2150 } 2151 2152 // testing #1405 - config.Cmd does not get cleaned up if 2153 // utilizing cache 2154 func (s *DockerCLIBuildSuite) TestBuildEntrypointRunCleanup(c *testing.T) { 2155 const name = "testbuildcmdcleanup" 2156 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 2157 RUN echo "hello"`)) 2158 2159 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2160 build.WithFile("Dockerfile", `FROM busybox 2161 RUN echo "hello" 2162 ADD foo /foo 2163 ENTRYPOINT ["/bin/echo"]`), 2164 build.WithFile("foo", "hello"))) 2165 2166 res := inspectField(c, name, "Config.Cmd") 2167 // Cmd must be cleaned up 2168 if res != "[]" { 2169 c.Fatalf("Cmd %s, expected nil", res) 2170 } 2171 } 2172 2173 func (s *DockerCLIBuildSuite) TestBuildAddFileNotFound(c *testing.T) { 2174 const name = "testbuildaddnotfound" 2175 2176 buildImage(name, build.WithBuildContext(c, 2177 build.WithFile("Dockerfile", `FROM `+minimalBaseImage()+` 2178 ADD foo /usr/local/bar`), 2179 build.WithFile("bar", "hello"))).Assert(c, icmd.Expected{ 2180 ExitCode: 1, 2181 Err: "stat foo: file does not exist", 2182 }) 2183 } 2184 2185 func (s *DockerCLIBuildSuite) TestBuildInheritance(c *testing.T) { 2186 testRequires(c, DaemonIsLinux) 2187 const name = "testbuildinheritance" 2188 2189 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM scratch 2190 EXPOSE 2375`)) 2191 ports1 := inspectField(c, name, "Config.ExposedPorts") 2192 2193 buildImageSuccessfully(c, name, build.WithDockerfile(fmt.Sprintf(`FROM %s 2194 ENTRYPOINT ["/bin/echo"]`, name))) 2195 2196 res := inspectField(c, name, "Config.Entrypoint") 2197 if expected := "[/bin/echo]"; res != expected { 2198 c.Fatalf("Entrypoint %s, expected %s", res, expected) 2199 } 2200 ports2 := inspectField(c, name, "Config.ExposedPorts") 2201 if ports1 != ports2 { 2202 c.Fatalf("Ports must be same: %s != %s", ports1, ports2) 2203 } 2204 } 2205 2206 func (s *DockerCLIBuildSuite) TestBuildFails(c *testing.T) { 2207 const name = "testbuildfails" 2208 buildImage(name, build.WithDockerfile(`FROM busybox 2209 RUN sh -c "exit 23"`)).Assert(c, icmd.Expected{ 2210 ExitCode: 23, 2211 Err: "returned a non-zero code: 23", 2212 }) 2213 } 2214 2215 func (s *DockerCLIBuildSuite) TestBuildOnBuild(c *testing.T) { 2216 const name = "testbuildonbuild" 2217 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 2218 ONBUILD RUN touch foobar`)) 2219 buildImageSuccessfully(c, name, build.WithDockerfile(fmt.Sprintf(`FROM %s 2220 RUN [ -f foobar ]`, name))) 2221 } 2222 2223 // gh #2446 2224 func (s *DockerCLIBuildSuite) TestBuildAddToSymlinkDest(c *testing.T) { 2225 makeLink := `ln -s /foo /bar` 2226 if testEnv.DaemonInfo.OSType == "windows" { 2227 makeLink = `mklink /D C:\bar C:\foo` 2228 } 2229 const name = "testbuildaddtosymlinkdest" 2230 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2231 build.WithFile("Dockerfile", ` 2232 FROM busybox 2233 RUN sh -c "mkdir /foo" 2234 RUN `+makeLink+` 2235 ADD foo /bar/ 2236 RUN sh -c "[ -f /bar/foo ]" 2237 RUN sh -c "[ -f /foo/foo ]"`), 2238 build.WithFile("foo", "hello"), 2239 )) 2240 } 2241 2242 func (s *DockerCLIBuildSuite) TestBuildEscapeWhitespace(c *testing.T) { 2243 const name = "testbuildescapewhitespace" 2244 2245 buildImageSuccessfully(c, name, build.WithDockerfile(` 2246 # ESCAPE=\ 2247 FROM busybox 2248 MAINTAINER "Docker \ 2249 IO <io@\ 2250 docker.com>" 2251 `)) 2252 2253 res := inspectField(c, name, "Author") 2254 if res != `"Docker IO <io@docker.com>"` { 2255 c.Fatalf("Parsed string did not match the escaped string. Got: %q", res) 2256 } 2257 } 2258 2259 func (s *DockerCLIBuildSuite) TestBuildVerifyIntString(c *testing.T) { 2260 // Verify that strings that look like ints are still passed as strings 2261 const name = "testbuildstringing" 2262 2263 buildImageSuccessfully(c, name, build.WithDockerfile(` 2264 FROM busybox 2265 MAINTAINER 123`)) 2266 2267 out := cli.DockerCmd(c, "inspect", name).Stdout() 2268 if !strings.Contains(out, `"123"`) { 2269 c.Fatalf("Output does not contain the int as a string:\n%s", out) 2270 } 2271 } 2272 2273 func (s *DockerCLIBuildSuite) TestBuildDockerignore(c *testing.T) { 2274 const name = "testbuilddockerignore" 2275 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2276 build.WithFile("Dockerfile", ` 2277 FROM busybox 2278 ADD . /bla 2279 RUN sh -c "[[ -f /bla/src/x.go ]]" 2280 RUN sh -c "[[ -f /bla/Makefile ]]" 2281 RUN sh -c "[[ ! -e /bla/src/_vendor ]]" 2282 RUN sh -c "[[ ! -e /bla/.gitignore ]]" 2283 RUN sh -c "[[ ! -e /bla/README.md ]]" 2284 RUN sh -c "[[ ! -e /bla/dir/foo ]]" 2285 RUN sh -c "[[ ! -e /bla/foo ]]" 2286 RUN sh -c "[[ ! -e /bla/.git ]]" 2287 RUN sh -c "[[ ! -e v.cc ]]" 2288 RUN sh -c "[[ ! -e src/v.cc ]]" 2289 RUN sh -c "[[ ! -e src/_vendor/v.cc ]]"`), 2290 build.WithFile("Makefile", "all:"), 2291 build.WithFile(".git/HEAD", "ref: foo"), 2292 build.WithFile("src/x.go", "package main"), 2293 build.WithFile("src/_vendor/v.go", "package main"), 2294 build.WithFile("src/_vendor/v.cc", "package main"), 2295 build.WithFile("src/v.cc", "package main"), 2296 build.WithFile("v.cc", "package main"), 2297 build.WithFile("dir/foo", ""), 2298 build.WithFile(".gitignore", ""), 2299 build.WithFile("README.md", "readme"), 2300 build.WithFile(".dockerignore", ` 2301 .git 2302 pkg 2303 .gitignore 2304 src/_vendor 2305 *.md 2306 **/*.cc 2307 dir`), 2308 )) 2309 } 2310 2311 func (s *DockerCLIBuildSuite) TestBuildDockerignoreCleanPaths(c *testing.T) { 2312 const name = "testbuilddockerignorecleanpaths" 2313 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2314 build.WithFile("Dockerfile", ` 2315 FROM busybox 2316 ADD . /tmp/ 2317 RUN sh -c "(! ls /tmp/foo) && (! ls /tmp/foo2) && (! ls /tmp/dir1/foo)"`), 2318 build.WithFile("foo", "foo"), 2319 build.WithFile("foo2", "foo2"), 2320 build.WithFile("dir1/foo", "foo in dir1"), 2321 build.WithFile(".dockerignore", "./foo\ndir1//foo\n./dir1/../foo2"), 2322 )) 2323 } 2324 2325 func (s *DockerCLIBuildSuite) TestBuildDockerignoreExceptions(c *testing.T) { 2326 const name = "testbuilddockerignoreexceptions" 2327 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2328 build.WithFile("Dockerfile", ` 2329 FROM busybox 2330 ADD . /bla 2331 RUN sh -c "[[ -f /bla/src/x.go ]]" 2332 RUN sh -c "[[ -f /bla/Makefile ]]" 2333 RUN sh -c "[[ ! -e /bla/src/_vendor ]]" 2334 RUN sh -c "[[ ! -e /bla/.gitignore ]]" 2335 RUN sh -c "[[ ! -e /bla/README.md ]]" 2336 RUN sh -c "[[ -e /bla/dir/dir/foo ]]" 2337 RUN sh -c "[[ ! -e /bla/dir/foo1 ]]" 2338 RUN sh -c "[[ -f /bla/dir/e ]]" 2339 RUN sh -c "[[ -f /bla/dir/e-dir/foo ]]" 2340 RUN sh -c "[[ ! -e /bla/foo ]]" 2341 RUN sh -c "[[ ! -e /bla/.git ]]" 2342 RUN sh -c "[[ -e /bla/dir/a.cc ]]"`), 2343 build.WithFile("Makefile", "all:"), 2344 build.WithFile(".git/HEAD", "ref: foo"), 2345 build.WithFile("src/x.go", "package main"), 2346 build.WithFile("src/_vendor/v.go", "package main"), 2347 build.WithFile("dir/foo", ""), 2348 build.WithFile("dir/foo1", ""), 2349 build.WithFile("dir/dir/f1", ""), 2350 build.WithFile("dir/dir/foo", ""), 2351 build.WithFile("dir/e", ""), 2352 build.WithFile("dir/e-dir/foo", ""), 2353 build.WithFile(".gitignore", ""), 2354 build.WithFile("README.md", "readme"), 2355 build.WithFile("dir/a.cc", "hello"), 2356 build.WithFile(".dockerignore", ` 2357 .git 2358 pkg 2359 .gitignore 2360 src/_vendor 2361 *.md 2362 dir 2363 !dir/e* 2364 !dir/dir/foo 2365 **/*.cc 2366 !**/*.cc`), 2367 )) 2368 } 2369 2370 func (s *DockerCLIBuildSuite) TestBuildDockerignoringDockerfile(c *testing.T) { 2371 const name = "testbuilddockerignoredockerfile" 2372 dockerfile := ` 2373 FROM busybox 2374 ADD . /tmp/ 2375 RUN sh -c "! ls /tmp/Dockerfile" 2376 RUN ls /tmp/.dockerignore` 2377 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2378 build.WithFile("Dockerfile", dockerfile), 2379 build.WithFile(".dockerignore", "Dockerfile\n"), 2380 )) 2381 // FIXME(vdemeester) why twice ? 2382 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2383 build.WithFile("Dockerfile", dockerfile), 2384 build.WithFile(".dockerignore", "./Dockerfile\n"), 2385 )) 2386 } 2387 2388 func (s *DockerCLIBuildSuite) TestBuildDockerignoringRenamedDockerfile(c *testing.T) { 2389 const name = "testbuilddockerignoredockerfile" 2390 dockerfile := ` 2391 FROM busybox 2392 ADD . /tmp/ 2393 RUN ls /tmp/Dockerfile 2394 RUN sh -c "! ls /tmp/MyDockerfile" 2395 RUN ls /tmp/.dockerignore` 2396 buildImageSuccessfully(c, name, cli.WithFlags("-f", "MyDockerfile"), build.WithBuildContext(c, 2397 build.WithFile("Dockerfile", "Should not use me"), 2398 build.WithFile("MyDockerfile", dockerfile), 2399 build.WithFile(".dockerignore", "MyDockerfile\n"), 2400 )) 2401 // FIXME(vdemeester) why twice ? 2402 buildImageSuccessfully(c, name, cli.WithFlags("-f", "MyDockerfile"), build.WithBuildContext(c, 2403 build.WithFile("Dockerfile", "Should not use me"), 2404 build.WithFile("MyDockerfile", dockerfile), 2405 build.WithFile(".dockerignore", "./MyDockerfile\n"), 2406 )) 2407 } 2408 2409 func (s *DockerCLIBuildSuite) TestBuildDockerignoringDockerignore(c *testing.T) { 2410 const name = "testbuilddockerignoredockerignore" 2411 dockerfile := ` 2412 FROM busybox 2413 ADD . /tmp/ 2414 RUN sh -c "! ls /tmp/.dockerignore" 2415 RUN ls /tmp/Dockerfile` 2416 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2417 build.WithFile("Dockerfile", dockerfile), 2418 build.WithFile(".dockerignore", ".dockerignore\n"), 2419 )) 2420 } 2421 2422 func (s *DockerCLIBuildSuite) TestBuildDockerignoreTouchDockerfile(c *testing.T) { 2423 const name = "testbuilddockerignoretouchdockerfile" 2424 dockerfile := ` 2425 FROM busybox 2426 ADD . /tmp/` 2427 ctx := fakecontext.New(c, "", 2428 fakecontext.WithDockerfile(dockerfile), 2429 fakecontext.WithFiles(map[string]string{ 2430 ".dockerignore": "Dockerfile\n", 2431 })) 2432 defer ctx.Close() 2433 2434 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 2435 id1 := getIDByName(c, name) 2436 2437 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 2438 id2 := getIDByName(c, name) 2439 if id1 != id2 { 2440 c.Fatalf("Didn't use the cache - 1") 2441 } 2442 2443 // Now make sure touching Dockerfile doesn't invalidate the cache 2444 if err := ctx.Add("Dockerfile", dockerfile+"\n# hi"); err != nil { 2445 c.Fatalf("Didn't add Dockerfile: %s", err) 2446 } 2447 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 2448 id2 = getIDByName(c, name) 2449 if id1 != id2 { 2450 c.Fatalf("Didn't use the cache - 2") 2451 } 2452 2453 // One more time but just 'touch' it instead of changing the content 2454 if err := ctx.Add("Dockerfile", dockerfile+"\n# hi"); err != nil { 2455 c.Fatalf("Didn't add Dockerfile: %s", err) 2456 } 2457 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 2458 id2 = getIDByName(c, name) 2459 if id1 != id2 { 2460 c.Fatalf("Didn't use the cache - 3") 2461 } 2462 } 2463 2464 func (s *DockerCLIBuildSuite) TestBuildDockerignoringWholeDir(c *testing.T) { 2465 const name = "testbuilddockerignorewholedir" 2466 2467 dockerfile := ` 2468 FROM busybox 2469 COPY . / 2470 RUN sh -c "[[ ! -e /.gitignore ]]" 2471 RUN sh -c "[[ ! -e /Makefile ]]"` 2472 2473 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2474 build.WithFile("Dockerfile", dockerfile), 2475 build.WithFile(".dockerignore", "*\n"), 2476 build.WithFile("Makefile", "all:"), 2477 build.WithFile(".gitignore", ""), 2478 )) 2479 } 2480 2481 func (s *DockerCLIBuildSuite) TestBuildDockerignoringOnlyDotfiles(c *testing.T) { 2482 const name = "testbuilddockerignorewholedir" 2483 2484 dockerfile := ` 2485 FROM busybox 2486 COPY . / 2487 RUN sh -c "[[ ! -e /.gitignore ]]" 2488 RUN sh -c "[[ -f /Makefile ]]"` 2489 2490 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2491 build.WithFile("Dockerfile", dockerfile), 2492 build.WithFile(".dockerignore", ".*"), 2493 build.WithFile("Makefile", "all:"), 2494 build.WithFile(".gitignore", ""), 2495 )) 2496 } 2497 2498 func (s *DockerCLIBuildSuite) TestBuildDockerignoringBadExclusion(c *testing.T) { 2499 const name = "testbuilddockerignorebadexclusion" 2500 buildImage(name, build.WithBuildContext(c, 2501 build.WithFile("Dockerfile", ` 2502 FROM busybox 2503 COPY . / 2504 RUN sh -c "[[ ! -e /.gitignore ]]" 2505 RUN sh -c "[[ -f /Makefile ]]"`), 2506 build.WithFile("Makefile", "all:"), 2507 build.WithFile(".gitignore", ""), 2508 build.WithFile(".dockerignore", "!\n"), 2509 )).Assert(c, icmd.Expected{ 2510 ExitCode: 1, 2511 Err: `illegal exclusion pattern: "!"`, 2512 }) 2513 } 2514 2515 func (s *DockerCLIBuildSuite) TestBuildDockerignoringWildTopDir(c *testing.T) { 2516 dockerfile := ` 2517 FROM busybox 2518 COPY . / 2519 RUN sh -c "[[ ! -e /.dockerignore ]]" 2520 RUN sh -c "[[ ! -e /Dockerfile ]]" 2521 RUN sh -c "[[ ! -e /file1 ]]" 2522 RUN sh -c "[[ ! -e /dir ]]"` 2523 2524 // All of these should result in ignoring all files 2525 for _, variant := range []string{"**", "**/", "**/**", "*"} { 2526 buildImageSuccessfully(c, "noname", build.WithBuildContext(c, 2527 build.WithFile("Dockerfile", dockerfile), 2528 build.WithFile("file1", ""), 2529 build.WithFile("dir/file1", ""), 2530 build.WithFile(".dockerignore", variant), 2531 )) 2532 2533 cli.DockerCmd(c, "rmi", "noname") 2534 } 2535 } 2536 2537 func (s *DockerCLIBuildSuite) TestBuildDockerignoringWildDirs(c *testing.T) { 2538 dockerfile := ` 2539 FROM busybox 2540 COPY . / 2541 #RUN sh -c "[[ -e /.dockerignore ]]" 2542 RUN sh -c "[[ -e /Dockerfile ]] && \ 2543 [[ ! -e /file0 ]] && \ 2544 [[ ! -e /dir1/file0 ]] && \ 2545 [[ ! -e /dir2/file0 ]] && \ 2546 [[ ! -e /file1 ]] && \ 2547 [[ ! -e /dir1/file1 ]] && \ 2548 [[ ! -e /dir1/dir2/file1 ]] && \ 2549 [[ ! -e /dir1/file2 ]] && \ 2550 [[ -e /dir1/dir2/file2 ]] && \ 2551 [[ ! -e /dir1/dir2/file4 ]] && \ 2552 [[ ! -e /dir1/dir2/file5 ]] && \ 2553 [[ ! -e /dir1/dir2/file6 ]] && \ 2554 [[ ! -e /dir1/dir3/file7 ]] && \ 2555 [[ ! -e /dir1/dir3/file8 ]] && \ 2556 [[ -e /dir1/dir3 ]] && \ 2557 [[ -e /dir1/dir4 ]] && \ 2558 [[ ! -e 'dir1/dir5/fileAA' ]] && \ 2559 [[ -e 'dir1/dir5/fileAB' ]] && \ 2560 [[ -e 'dir1/dir5/fileB' ]]" # "." in pattern means nothing 2561 2562 RUN echo all done!` 2563 2564 dockerignore := ` 2565 **/file0 2566 **/*file1 2567 **/dir1/file2 2568 dir1/**/file4 2569 **/dir2/file5 2570 **/dir1/dir2/file6 2571 dir1/dir3/** 2572 **/dir4/** 2573 **/file?A 2574 **/file\?B 2575 **/dir5/file. 2576 ` 2577 2578 buildImageSuccessfully(c, "noname", build.WithBuildContext(c, 2579 build.WithFile("Dockerfile", dockerfile), 2580 build.WithFile(".dockerignore", dockerignore), 2581 build.WithFile("dir1/file0", ""), 2582 build.WithFile("dir1/dir2/file0", ""), 2583 build.WithFile("file1", ""), 2584 build.WithFile("dir1/file1", ""), 2585 build.WithFile("dir1/dir2/file1", ""), 2586 build.WithFile("dir1/file2", ""), 2587 build.WithFile("dir1/dir2/file2", ""), // remains 2588 build.WithFile("dir1/dir2/file4", ""), 2589 build.WithFile("dir1/dir2/file5", ""), 2590 build.WithFile("dir1/dir2/file6", ""), 2591 build.WithFile("dir1/dir3/file7", ""), 2592 build.WithFile("dir1/dir3/file8", ""), 2593 build.WithFile("dir1/dir4/file9", ""), 2594 build.WithFile("dir1/dir5/fileAA", ""), 2595 build.WithFile("dir1/dir5/fileAB", ""), 2596 build.WithFile("dir1/dir5/fileB", ""), 2597 )) 2598 } 2599 2600 func (s *DockerCLIBuildSuite) TestBuildLineBreak(c *testing.T) { 2601 testRequires(c, DaemonIsLinux) 2602 const name = "testbuildlinebreak" 2603 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 2604 RUN sh -c 'echo root:testpass \ 2605 > /tmp/passwd' 2606 RUN mkdir -p /var/run/sshd 2607 RUN sh -c "[ "$(cat /tmp/passwd)" = "root:testpass" ]" 2608 RUN sh -c "[ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]"`)) 2609 } 2610 2611 func (s *DockerCLIBuildSuite) TestBuildEOLInLine(c *testing.T) { 2612 testRequires(c, DaemonIsLinux) 2613 const name = "testbuildeolinline" 2614 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 2615 RUN sh -c 'echo root:testpass > /tmp/passwd' 2616 RUN echo "foo \n bar"; echo "baz" 2617 RUN mkdir -p /var/run/sshd 2618 RUN sh -c "[ "$(cat /tmp/passwd)" = "root:testpass" ]" 2619 RUN sh -c "[ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]"`)) 2620 } 2621 2622 func (s *DockerCLIBuildSuite) TestBuildCommentsShebangs(c *testing.T) { 2623 testRequires(c, DaemonIsLinux) 2624 const name = "testbuildcomments" 2625 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 2626 # This is an ordinary comment. 2627 RUN { echo '#!/bin/sh'; echo 'echo hello world'; } > /hello.sh 2628 RUN [ ! -x /hello.sh ] 2629 # comment with line break \ 2630 RUN chmod +x /hello.sh 2631 RUN [ -x /hello.sh ] 2632 RUN [ "$(cat /hello.sh)" = $'#!/bin/sh\necho hello world' ] 2633 RUN [ "$(/hello.sh)" = "hello world" ]`)) 2634 } 2635 2636 func (s *DockerCLIBuildSuite) TestBuildUsersAndGroups(c *testing.T) { 2637 testRequires(c, DaemonIsLinux) 2638 const name = "testbuildusers" 2639 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 2640 2641 # Make sure our defaults work 2642 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)" = '0:0/root:root' ] 2643 2644 # TODO decide if "args.user = strconv.Itoa(syscall.Getuid())" is acceptable behavior for changeUser in sysvinit instead of "return nil" when "USER" isn't specified (so that we get the proper group list even if that is the empty list, even in the default case of not supplying an explicit USER to run as, which implies USER 0) 2645 USER root 2646 RUN [ "$(id -G):$(id -Gn)" = '0 10:root wheel' ] 2647 2648 # Setup dockerio user and group 2649 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd && \ 2650 echo 'dockerio:x:1001:' >> /etc/group 2651 2652 # Make sure we can switch to our user and all the information is exactly as we expect it to be 2653 USER dockerio 2654 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] 2655 2656 # Switch back to root and double check that worked exactly as we might expect it to 2657 USER root 2658 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '0:0/root:root/0 10:root wheel' ] && \ 2659 # Add a "supplementary" group for our dockerio user 2660 echo 'supplementary:x:1002:dockerio' >> /etc/group 2661 2662 # ... and then go verify that we get it like we expect 2663 USER dockerio 2664 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001 1002:dockerio supplementary' ] 2665 USER 1001 2666 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001 1002:dockerio supplementary' ] 2667 2668 # super test the new "user:group" syntax 2669 USER dockerio:dockerio 2670 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] 2671 USER 1001:dockerio 2672 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] 2673 USER dockerio:1001 2674 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] 2675 USER 1001:1001 2676 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] 2677 USER dockerio:supplementary 2678 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ] 2679 USER dockerio:1002 2680 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ] 2681 USER 1001:supplementary 2682 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ] 2683 USER 1001:1002 2684 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ] 2685 2686 # make sure unknown uid/gid still works properly 2687 USER 1042:1043 2688 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1042:1043/1042:1043/1043:1043' ]`)) 2689 } 2690 2691 // FIXME(vdemeester) rename this test (and probably "merge" it with the one below TestBuildEnvUsage2) 2692 func (s *DockerCLIBuildSuite) TestBuildEnvUsage(c *testing.T) { 2693 // /docker/world/hello is not owned by the correct user 2694 testRequires(c, NotUserNamespace) 2695 testRequires(c, DaemonIsLinux) 2696 const name = "testbuildenvusage" 2697 dockerfile := `FROM busybox 2698 ENV HOME /root 2699 ENV PATH $HOME/bin:$PATH 2700 ENV PATH /tmp:$PATH 2701 RUN [ "$PATH" = "/tmp:$HOME/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ] 2702 ENV FOO /foo/baz 2703 ENV BAR /bar 2704 ENV BAZ $BAR 2705 ENV FOOPATH $PATH:$FOO 2706 RUN [ "$BAR" = "$BAZ" ] 2707 RUN [ "$FOOPATH" = "$PATH:/foo/baz" ] 2708 ENV FROM hello/docker/world 2709 ENV TO /docker/world/hello 2710 ADD $FROM $TO 2711 RUN [ "$(cat $TO)" = "hello" ] 2712 ENV abc=def 2713 ENV ghi=$abc 2714 RUN [ "$ghi" = "def" ] 2715 ` 2716 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2717 build.WithFile("Dockerfile", dockerfile), 2718 build.WithFile("hello/docker/world", "hello"), 2719 )) 2720 } 2721 2722 // FIXME(vdemeester) rename this test (and probably "merge" it with the one above TestBuildEnvUsage) 2723 func (s *DockerCLIBuildSuite) TestBuildEnvUsage2(c *testing.T) { 2724 // /docker/world/hello is not owned by the correct user 2725 testRequires(c, NotUserNamespace) 2726 testRequires(c, DaemonIsLinux) 2727 const name = "testbuildenvusage2" 2728 dockerfile := `FROM busybox 2729 ENV abc=def def="hello world" 2730 RUN [ "$abc,$def" = "def,hello world" ] 2731 ENV def=hello\ world v1=abc v2="hi there" v3='boogie nights' v4="with'quotes too" 2732 RUN [ "$def,$v1,$v2,$v3,$v4" = "hello world,abc,hi there,boogie nights,with'quotes too" ] 2733 ENV abc=zzz FROM=hello/docker/world 2734 ENV abc=zzz TO=/docker/world/hello 2735 ADD $FROM $TO 2736 RUN [ "$abc,$(cat $TO)" = "zzz,hello" ] 2737 ENV abc 'yyy' 2738 RUN [ $abc = 'yyy' ] 2739 ENV abc= 2740 RUN [ "$abc" = "" ] 2741 2742 # use grep to make sure if the builder substitutes \$foo by mistake 2743 # we don't get a false positive 2744 ENV abc=\$foo 2745 RUN [ "$abc" = "\$foo" ] && (echo "$abc" | grep foo) 2746 ENV abc \$foo 2747 RUN [ "$abc" = "\$foo" ] && (echo "$abc" | grep foo) 2748 2749 ENV abc=\'foo\' abc2=\"foo\" 2750 RUN [ "$abc,$abc2" = "'foo',\"foo\"" ] 2751 ENV abc "foo" 2752 RUN [ "$abc" = "foo" ] 2753 ENV abc 'foo' 2754 RUN [ "$abc" = 'foo' ] 2755 ENV abc \'foo\' 2756 RUN [ "$abc" = "'foo'" ] 2757 ENV abc \"foo\" 2758 RUN [ "$abc" = '"foo"' ] 2759 2760 ENV abc=ABC 2761 RUN [ "$abc" = "ABC" ] 2762 ENV def1=${abc:-DEF} def2=${ccc:-DEF} 2763 ENV def3=${ccc:-${def2}xx} def4=${abc:+ALT} def5=${def2:+${abc}:} def6=${ccc:-\$abc:} def7=${ccc:-\${abc}:} 2764 RUN [ "$def1,$def2,$def3,$def4,$def5,$def6,$def7" = 'ABC,DEF,DEFxx,ALT,ABC:,$abc:,${abc:}' ] 2765 ENV mypath=${mypath:+$mypath:}/home 2766 ENV mypath=${mypath:+$mypath:}/away 2767 RUN [ "$mypath" = '/home:/away' ] 2768 2769 ENV e1=bar 2770 ENV e2=$e1 e3=$e11 e4=\$e1 e5=\$e11 2771 RUN [ "$e0,$e1,$e2,$e3,$e4,$e5" = ',bar,bar,,$e1,$e11' ] 2772 2773 ENV ee1 bar 2774 ENV ee2 $ee1 2775 ENV ee3 $ee11 2776 ENV ee4 \$ee1 2777 ENV ee5 \$ee11 2778 RUN [ "$ee1,$ee2,$ee3,$ee4,$ee5" = 'bar,bar,,$ee1,$ee11' ] 2779 2780 ENV eee1="foo" eee2='foo' 2781 ENV eee3 "foo" 2782 ENV eee4 'foo' 2783 RUN [ "$eee1,$eee2,$eee3,$eee4" = 'foo,foo,foo,foo' ] 2784 2785 ` 2786 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2787 build.WithFile("Dockerfile", dockerfile), 2788 build.WithFile("hello/docker/world", "hello"), 2789 )) 2790 } 2791 2792 func (s *DockerCLIBuildSuite) TestBuildAddScript(c *testing.T) { 2793 testRequires(c, DaemonIsLinux) 2794 const name = "testbuildaddscript" 2795 dockerfile := ` 2796 FROM busybox 2797 ADD test /test 2798 RUN ["chmod","+x","/test"] 2799 RUN ["/test"] 2800 RUN [ "$(cat /testfile)" = 'test!' ]` 2801 2802 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2803 build.WithFile("Dockerfile", dockerfile), 2804 build.WithFile("test", "#!/bin/sh\necho 'test!' > /testfile"), 2805 )) 2806 } 2807 2808 func (s *DockerCLIBuildSuite) TestBuildAddTar(c *testing.T) { 2809 // /test/foo is not owned by the correct user 2810 testRequires(c, NotUserNamespace) 2811 const name = "testbuildaddtar" 2812 2813 ctx := func() *fakecontext.Fake { 2814 dockerfile := ` 2815 FROM busybox 2816 ADD test.tar / 2817 RUN cat /test/foo | grep Hi 2818 ADD test.tar /test.tar 2819 RUN cat /test.tar/test/foo | grep Hi 2820 ADD test.tar /unlikely-to-exist 2821 RUN cat /unlikely-to-exist/test/foo | grep Hi 2822 ADD test.tar /unlikely-to-exist-trailing-slash/ 2823 RUN cat /unlikely-to-exist-trailing-slash/test/foo | grep Hi 2824 RUN sh -c "mkdir /existing-directory" #sh -c is needed on Windows to use the correct mkdir 2825 ADD test.tar /existing-directory 2826 RUN cat /existing-directory/test/foo | grep Hi 2827 ADD test.tar /existing-directory-trailing-slash/ 2828 RUN cat /existing-directory-trailing-slash/test/foo | grep Hi` 2829 tmpDir, err := os.MkdirTemp("", "fake-context") 2830 assert.NilError(c, err) 2831 testTar, err := os.Create(filepath.Join(tmpDir, "test.tar")) 2832 if err != nil { 2833 c.Fatalf("failed to create test.tar archive: %v", err) 2834 } 2835 defer testTar.Close() 2836 2837 tw := tar.NewWriter(testTar) 2838 2839 if err := tw.WriteHeader(&tar.Header{ 2840 Name: "test/foo", 2841 Size: 2, 2842 }); err != nil { 2843 c.Fatalf("failed to write tar file header: %v", err) 2844 } 2845 if _, err := tw.Write([]byte("Hi")); err != nil { 2846 c.Fatalf("failed to write tar file content: %v", err) 2847 } 2848 if err := tw.Close(); err != nil { 2849 c.Fatalf("failed to close tar archive: %v", err) 2850 } 2851 2852 if err := os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0o644); err != nil { 2853 c.Fatalf("failed to open destination dockerfile: %v", err) 2854 } 2855 return fakecontext.New(c, tmpDir) 2856 }() 2857 defer ctx.Close() 2858 2859 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 2860 } 2861 2862 func (s *DockerCLIBuildSuite) TestBuildAddBrokenTar(c *testing.T) { 2863 const name = "testbuildaddbrokentar" 2864 2865 ctx := func() *fakecontext.Fake { 2866 dockerfile := ` 2867 FROM busybox 2868 ADD test.tar /` 2869 tmpDir, err := os.MkdirTemp("", "fake-context") 2870 assert.NilError(c, err) 2871 testTar, err := os.Create(filepath.Join(tmpDir, "test.tar")) 2872 if err != nil { 2873 c.Fatalf("failed to create test.tar archive: %v", err) 2874 } 2875 defer testTar.Close() 2876 2877 tw := tar.NewWriter(testTar) 2878 2879 if err := tw.WriteHeader(&tar.Header{ 2880 Name: "test/foo", 2881 Size: 2, 2882 }); err != nil { 2883 c.Fatalf("failed to write tar file header: %v", err) 2884 } 2885 if _, err := tw.Write([]byte("Hi")); err != nil { 2886 c.Fatalf("failed to write tar file content: %v", err) 2887 } 2888 if err := tw.Close(); err != nil { 2889 c.Fatalf("failed to close tar archive: %v", err) 2890 } 2891 2892 // Corrupt the tar by removing one byte off the end 2893 stat, err := testTar.Stat() 2894 if err != nil { 2895 c.Fatalf("failed to stat tar archive: %v", err) 2896 } 2897 if err := testTar.Truncate(stat.Size() - 1); err != nil { 2898 c.Fatalf("failed to truncate tar archive: %v", err) 2899 } 2900 2901 if err := os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0o644); err != nil { 2902 c.Fatalf("failed to open destination dockerfile: %v", err) 2903 } 2904 return fakecontext.New(c, tmpDir) 2905 }() 2906 defer ctx.Close() 2907 2908 buildImage(name, build.WithExternalBuildContext(ctx)).Assert(c, icmd.Expected{ 2909 ExitCode: 1, 2910 }) 2911 } 2912 2913 func (s *DockerCLIBuildSuite) TestBuildAddNonTar(c *testing.T) { 2914 const name = "testbuildaddnontar" 2915 2916 // Should not try to extract test.tar 2917 buildImageSuccessfully(c, name, build.WithBuildContext(c, 2918 build.WithFile("Dockerfile", ` 2919 FROM busybox 2920 ADD test.tar / 2921 RUN test -f /test.tar`), 2922 build.WithFile("test.tar", "not_a_tar_file"), 2923 )) 2924 } 2925 2926 func (s *DockerCLIBuildSuite) TestBuildAddTarXz(c *testing.T) { 2927 // /test/foo is not owned by the correct user 2928 testRequires(c, NotUserNamespace) 2929 testRequires(c, DaemonIsLinux) 2930 const name = "testbuildaddtarxz" 2931 2932 ctx := func() *fakecontext.Fake { 2933 dockerfile := ` 2934 FROM busybox 2935 ADD test.tar.xz / 2936 RUN cat /test/foo | grep Hi` 2937 tmpDir, err := os.MkdirTemp("", "fake-context") 2938 assert.NilError(c, err) 2939 testTar, err := os.Create(filepath.Join(tmpDir, "test.tar")) 2940 if err != nil { 2941 c.Fatalf("failed to create test.tar archive: %v", err) 2942 } 2943 defer testTar.Close() 2944 2945 tw := tar.NewWriter(testTar) 2946 2947 if err := tw.WriteHeader(&tar.Header{ 2948 Name: "test/foo", 2949 Size: 2, 2950 }); err != nil { 2951 c.Fatalf("failed to write tar file header: %v", err) 2952 } 2953 if _, err := tw.Write([]byte("Hi")); err != nil { 2954 c.Fatalf("failed to write tar file content: %v", err) 2955 } 2956 if err := tw.Close(); err != nil { 2957 c.Fatalf("failed to close tar archive: %v", err) 2958 } 2959 2960 icmd.RunCmd(icmd.Cmd{ 2961 Command: []string{"xz", "-k", "test.tar"}, 2962 Dir: tmpDir, 2963 }).Assert(c, icmd.Success) 2964 if err := os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0o644); err != nil { 2965 c.Fatalf("failed to open destination dockerfile: %v", err) 2966 } 2967 return fakecontext.New(c, tmpDir) 2968 }() 2969 2970 defer ctx.Close() 2971 2972 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 2973 } 2974 2975 func (s *DockerCLIBuildSuite) TestBuildAddTarXzGz(c *testing.T) { 2976 testRequires(c, DaemonIsLinux) 2977 const name = "testbuildaddtarxzgz" 2978 2979 ctx := func() *fakecontext.Fake { 2980 dockerfile := ` 2981 FROM busybox 2982 ADD test.tar.xz.gz / 2983 RUN ls /test.tar.xz.gz` 2984 tmpDir, err := os.MkdirTemp("", "fake-context") 2985 assert.NilError(c, err) 2986 testTar, err := os.Create(filepath.Join(tmpDir, "test.tar")) 2987 if err != nil { 2988 c.Fatalf("failed to create test.tar archive: %v", err) 2989 } 2990 defer testTar.Close() 2991 2992 tw := tar.NewWriter(testTar) 2993 2994 if err := tw.WriteHeader(&tar.Header{ 2995 Name: "test/foo", 2996 Size: 2, 2997 }); err != nil { 2998 c.Fatalf("failed to write tar file header: %v", err) 2999 } 3000 if _, err := tw.Write([]byte("Hi")); err != nil { 3001 c.Fatalf("failed to write tar file content: %v", err) 3002 } 3003 if err := tw.Close(); err != nil { 3004 c.Fatalf("failed to close tar archive: %v", err) 3005 } 3006 3007 icmd.RunCmd(icmd.Cmd{ 3008 Command: []string{"xz", "-k", "test.tar"}, 3009 Dir: tmpDir, 3010 }).Assert(c, icmd.Success) 3011 3012 icmd.RunCmd(icmd.Cmd{ 3013 Command: []string{"gzip", "test.tar.xz"}, 3014 Dir: tmpDir, 3015 }) 3016 if err := os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0o644); err != nil { 3017 c.Fatalf("failed to open destination dockerfile: %v", err) 3018 } 3019 return fakecontext.New(c, tmpDir) 3020 }() 3021 3022 defer ctx.Close() 3023 3024 buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx)) 3025 } 3026 3027 // FIXME(vdemeester) most of the from git tests could be moved to `docker/cli` e2e tests 3028 func (s *DockerCLIBuildSuite) TestBuildFromGit(c *testing.T) { 3029 const name = "testbuildfromgit" 3030 git := fakegit.New(c, "repo", map[string]string{ 3031 "Dockerfile": `FROM busybox 3032 ADD first /first 3033 RUN [ -f /first ] 3034 MAINTAINER docker`, 3035 "first": "test git data", 3036 }, true) 3037 defer git.Close() 3038 3039 buildImageSuccessfully(c, name, build.WithContextPath(git.RepoURL)) 3040 3041 res := inspectField(c, name, "Author") 3042 if res != "docker" { 3043 c.Fatalf("Maintainer should be docker, got %s", res) 3044 } 3045 } 3046 3047 func (s *DockerCLIBuildSuite) TestBuildFromGitWithContext(c *testing.T) { 3048 const name = "testbuildfromgit" 3049 git := fakegit.New(c, "repo", map[string]string{ 3050 "docker/Dockerfile": `FROM busybox 3051 ADD first /first 3052 RUN [ -f /first ] 3053 MAINTAINER docker`, 3054 "docker/first": "test git data", 3055 }, true) 3056 defer git.Close() 3057 3058 buildImageSuccessfully(c, name, build.WithContextPath(fmt.Sprintf("%s#master:docker", git.RepoURL))) 3059 3060 res := inspectField(c, name, "Author") 3061 if res != "docker" { 3062 c.Fatalf("Maintainer should be docker, got %s", res) 3063 } 3064 } 3065 3066 func (s *DockerCLIBuildSuite) TestBuildFromGitWithF(c *testing.T) { 3067 const name = "testbuildfromgitwithf" 3068 git := fakegit.New(c, "repo", map[string]string{ 3069 "myApp/myDockerfile": `FROM busybox 3070 RUN echo hi from Dockerfile`, 3071 }, true) 3072 defer git.Close() 3073 3074 buildImage(name, cli.WithFlags("-f", "myApp/myDockerfile"), build.WithContextPath(git.RepoURL)).Assert(c, icmd.Expected{ 3075 Out: "hi from Dockerfile", 3076 }) 3077 } 3078 3079 func (s *DockerCLIBuildSuite) TestBuildFromRemoteTarball(c *testing.T) { 3080 const name = "testbuildfromremotetarball" 3081 3082 buffer := new(bytes.Buffer) 3083 tw := tar.NewWriter(buffer) 3084 defer tw.Close() 3085 3086 dockerfile := []byte(`FROM busybox 3087 MAINTAINER docker`) 3088 if err := tw.WriteHeader(&tar.Header{ 3089 Name: "Dockerfile", 3090 Size: int64(len(dockerfile)), 3091 }); err != nil { 3092 c.Fatalf("failed to write tar file header: %v", err) 3093 } 3094 if _, err := tw.Write(dockerfile); err != nil { 3095 c.Fatalf("failed to write tar file content: %v", err) 3096 } 3097 if err := tw.Close(); err != nil { 3098 c.Fatalf("failed to close tar archive: %v", err) 3099 } 3100 3101 server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{ 3102 "testT.tar": buffer, 3103 })) 3104 defer server.Close() 3105 3106 cli.BuildCmd(c, name, build.WithContextPath(server.URL()+"/testT.tar")) 3107 3108 res := inspectField(c, name, "Author") 3109 if res != "docker" { 3110 c.Fatalf("Maintainer should be docker, got %s", res) 3111 } 3112 } 3113 3114 func (s *DockerCLIBuildSuite) TestBuildCleanupCmdOnEntrypoint(c *testing.T) { 3115 const name = "testbuildcmdcleanuponentrypoint" 3116 3117 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+` 3118 CMD ["test"] 3119 ENTRYPOINT ["echo"]`)) 3120 buildImageSuccessfully(c, name, build.WithDockerfile(fmt.Sprintf(`FROM %s 3121 ENTRYPOINT ["cat"]`, name))) 3122 3123 res := inspectField(c, name, "Config.Cmd") 3124 if res != "[]" { 3125 c.Fatalf("Cmd %s, expected nil", res) 3126 } 3127 res = inspectField(c, name, "Config.Entrypoint") 3128 if expected := "[cat]"; res != expected { 3129 c.Fatalf("Entrypoint %s, expected %s", res, expected) 3130 } 3131 } 3132 3133 func (s *DockerCLIBuildSuite) TestBuildClearCmd(c *testing.T) { 3134 const name = "testbuildclearcmd" 3135 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+` 3136 ENTRYPOINT ["/bin/bash"] 3137 CMD []`)) 3138 3139 cmd := inspectFieldJSON(c, name, "Config.Cmd") 3140 // OCI types specify `omitempty` JSON annotation which doesn't serialize 3141 // empty arrays and the Cmd will not be present at all. 3142 if testEnv.UsingSnapshotter() { 3143 assert.Check(c, is.Equal(cmd, "null")) 3144 } else { 3145 assert.Check(c, is.Equal(cmd, "[]")) 3146 } 3147 } 3148 3149 func (s *DockerCLIBuildSuite) TestBuildEmptyCmd(c *testing.T) { 3150 // Skip on Windows. Base image on Windows has a CMD set in the image. 3151 testRequires(c, DaemonIsLinux) 3152 3153 const name = "testbuildemptycmd" 3154 buildImageSuccessfully(c, name, build.WithDockerfile("FROM "+minimalBaseImage()+"\nMAINTAINER quux\n")) 3155 3156 res := inspectFieldJSON(c, name, "Config.Cmd") 3157 if res != "null" { 3158 c.Fatalf("Cmd %s, expected %s", res, "null") 3159 } 3160 } 3161 3162 func (s *DockerCLIBuildSuite) TestBuildOnBuildOutput(c *testing.T) { 3163 const name = "testbuildonbuildparent" 3164 buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nONBUILD RUN echo foo\n")) 3165 3166 buildImage(name, build.WithDockerfile("FROM "+name+"\nMAINTAINER quux\n")).Assert(c, icmd.Expected{ 3167 Out: "# Executing 1 build trigger", 3168 }) 3169 } 3170 3171 // FIXME(vdemeester) should be a unit test 3172 func (s *DockerCLIBuildSuite) TestBuildInvalidTag(c *testing.T) { 3173 name := "abcd:" + testutil.GenerateRandomAlphaOnlyString(200) 3174 buildImage(name, build.WithDockerfile("FROM "+minimalBaseImage()+"\nMAINTAINER quux\n")).Assert(c, icmd.Expected{ 3175 ExitCode: 125, 3176 Err: "invalid reference format", 3177 }) 3178 } 3179 3180 func (s *DockerCLIBuildSuite) TestBuildCmdShDashC(c *testing.T) { 3181 const name = "testbuildcmdshc" 3182 buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nCMD echo cmd\n")) 3183 3184 res := inspectFieldJSON(c, name, "Config.Cmd") 3185 expected := `["/bin/sh","-c","echo cmd"]` 3186 if testEnv.DaemonInfo.OSType == "windows" { 3187 expected = `["cmd /S /C echo cmd"]` 3188 } 3189 if res != expected { 3190 c.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res) 3191 } 3192 } 3193 3194 func (s *DockerCLIBuildSuite) TestBuildCmdSpaces(c *testing.T) { 3195 // Test to make sure that when we strcat arrays we take into account 3196 // the arg separator to make sure ["echo","hi"] and ["echo hi"] don't 3197 // look the same 3198 const name = "testbuildcmdspaces" 3199 3200 buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nCMD [\"echo hi\"]\n")) 3201 id1 := getIDByName(c, name) 3202 buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nCMD [\"echo\", \"hi\"]\n")) 3203 id2 := getIDByName(c, name) 3204 3205 if id1 == id2 { 3206 c.Fatal("Should not have resulted in the same CMD") 3207 } 3208 3209 // Now do the same with ENTRYPOINT 3210 buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nENTRYPOINT [\"echo hi\"]\n")) 3211 id1 = getIDByName(c, name) 3212 buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nENTRYPOINT [\"echo\", \"hi\"]\n")) 3213 id2 = getIDByName(c, name) 3214 3215 if id1 == id2 { 3216 c.Fatal("Should not have resulted in the same ENTRYPOINT") 3217 } 3218 } 3219 3220 func (s *DockerCLIBuildSuite) TestBuildCmdJSONNoShDashC(c *testing.T) { 3221 const name = "testbuildcmdjson" 3222 buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nCMD [\"echo\", \"cmd\"]")) 3223 3224 res := inspectFieldJSON(c, name, "Config.Cmd") 3225 expected := `["echo","cmd"]` 3226 if res != expected { 3227 c.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res) 3228 } 3229 } 3230 3231 func (s *DockerCLIBuildSuite) TestBuildEntrypointCanBeOverriddenByChild(c *testing.T) { 3232 buildImageSuccessfully(c, "parent", build.WithDockerfile(` 3233 FROM busybox 3234 ENTRYPOINT exit 130 3235 `)) 3236 3237 icmd.RunCommand(dockerBinary, "run", "parent").Assert(c, icmd.Expected{ 3238 ExitCode: 130, 3239 }) 3240 3241 buildImageSuccessfully(c, "child", build.WithDockerfile(` 3242 FROM parent 3243 ENTRYPOINT exit 5 3244 `)) 3245 3246 icmd.RunCommand(dockerBinary, "run", "child").Assert(c, icmd.Expected{ 3247 ExitCode: 5, 3248 }) 3249 } 3250 3251 func (s *DockerCLIBuildSuite) TestBuildEntrypointCanBeOverriddenByChildInspect(c *testing.T) { 3252 var ( 3253 name = "testbuildepinherit" 3254 name2 = "testbuildepinherit2" 3255 expected = `["/bin/sh","-c","echo quux"]` 3256 ) 3257 3258 if testEnv.DaemonInfo.OSType == "windows" { 3259 expected = `["cmd /S /C echo quux"]` 3260 } 3261 3262 buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nENTRYPOINT /foo/bar")) 3263 buildImageSuccessfully(c, name2, build.WithDockerfile(fmt.Sprintf("FROM %s\nENTRYPOINT echo quux", name))) 3264 3265 res := inspectFieldJSON(c, name2, "Config.Entrypoint") 3266 if res != expected { 3267 c.Fatalf("Expected value %s not in Config.Entrypoint: %s", expected, res) 3268 } 3269 3270 icmd.RunCommand(dockerBinary, "run", name2).Assert(c, icmd.Expected{ 3271 Out: "quux", 3272 }) 3273 } 3274 3275 func (s *DockerCLIBuildSuite) TestBuildRunShEntrypoint(c *testing.T) { 3276 const name = "testbuildentrypoint" 3277 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 3278 ENTRYPOINT echo`)) 3279 cli.DockerCmd(c, "run", "--rm", name) 3280 } 3281 3282 func (s *DockerCLIBuildSuite) TestBuildExoticShellInterpolation(c *testing.T) { 3283 testRequires(c, DaemonIsLinux) 3284 const name = "testbuildexoticshellinterpolation" 3285 3286 buildImageSuccessfully(c, name, build.WithDockerfile(` 3287 FROM busybox 3288 3289 ENV SOME_VAR a.b.c 3290 3291 RUN [ "$SOME_VAR" = 'a.b.c' ] 3292 RUN [ "${SOME_VAR}" = 'a.b.c' ] 3293 RUN [ "${SOME_VAR%.*}" = 'a.b' ] 3294 RUN [ "${SOME_VAR%%.*}" = 'a' ] 3295 RUN [ "${SOME_VAR#*.}" = 'b.c' ] 3296 RUN [ "${SOME_VAR##*.}" = 'c' ] 3297 RUN [ "${SOME_VAR/c/d}" = 'a.b.d' ] 3298 RUN [ "${#SOME_VAR}" = '5' ] 3299 3300 RUN [ "${SOME_UNSET_VAR:-$SOME_VAR}" = 'a.b.c' ] 3301 RUN [ "${SOME_VAR:+Version: ${SOME_VAR}}" = 'Version: a.b.c' ] 3302 RUN [ "${SOME_UNSET_VAR:+${SOME_VAR}}" = '' ] 3303 RUN [ "${SOME_UNSET_VAR:-${SOME_VAR:-d.e.f}}" = 'a.b.c' ] 3304 `)) 3305 } 3306 3307 func (s *DockerCLIBuildSuite) TestBuildVerifySingleQuoteFails(c *testing.T) { 3308 // This testcase is supposed to generate an error because the 3309 // JSON array we're passing in on the CMD uses single quotes instead 3310 // of double quotes (per the JSON spec). This means we interpret it 3311 // as a "string" instead of "JSON array" and pass it on to "sh -c" and 3312 // it should barf on it. 3313 const name = "testbuildsinglequotefails" 3314 expectedExitCode := 2 3315 3316 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 3317 CMD [ '/bin/sh', '-c', 'echo hi' ]`)) 3318 3319 icmd.RunCommand(dockerBinary, "run", "--rm", name).Assert(c, icmd.Expected{ 3320 ExitCode: expectedExitCode, 3321 }) 3322 } 3323 3324 func (s *DockerCLIBuildSuite) TestBuildVerboseOut(c *testing.T) { 3325 const name = "testbuildverboseout" 3326 expected := "\n123\n" 3327 3328 if testEnv.DaemonInfo.OSType == "windows" { 3329 expected = "\n123\r\n" 3330 } 3331 3332 buildImage(name, build.WithDockerfile(`FROM busybox 3333 RUN echo 123`)).Assert(c, icmd.Expected{ 3334 Out: expected, 3335 }) 3336 } 3337 3338 func (s *DockerCLIBuildSuite) TestBuildWithTabs(c *testing.T) { 3339 skip.If(c, versions.GreaterThan(testEnv.DaemonAPIVersion(), "1.44"), "ContainerConfig is deprecated") 3340 skip.If(c, testEnv.UsingSnapshotter, "ContainerConfig is not filled in c8d") 3341 3342 const name = "testbuildwithtabs" 3343 buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nRUN echo\tone\t\ttwo")) 3344 res := inspectFieldJSON(c, name, "ContainerConfig.Cmd") 3345 expected1 := `["/bin/sh","-c","echo\tone\t\ttwo"]` 3346 expected2 := `["/bin/sh","-c","echo\u0009one\u0009\u0009two"]` // syntactically equivalent, and what Go 1.3 generates 3347 if testEnv.DaemonInfo.OSType == "windows" { 3348 expected1 = `["cmd /S /C echo\tone\t\ttwo"]` 3349 expected2 = `["cmd /S /C echo\u0009one\u0009\u0009two"]` // syntactically equivalent, and what Go 1.3 generates 3350 } 3351 if res != expected1 && res != expected2 { 3352 c.Fatalf("Missing tabs.\nGot: %s\nExp: %s or %s", res, expected1, expected2) 3353 } 3354 } 3355 3356 func (s *DockerCLIBuildSuite) TestBuildLabels(c *testing.T) { 3357 const name = "testbuildlabel" 3358 expected := `{"License":"GPL","Vendor":"Acme"}` 3359 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 3360 LABEL Vendor=Acme 3361 LABEL License GPL`)) 3362 res := inspectFieldJSON(c, name, "Config.Labels") 3363 if res != expected { 3364 c.Fatalf("Labels %s, expected %s", res, expected) 3365 } 3366 } 3367 3368 func (s *DockerCLIBuildSuite) TestBuildLabelsCache(c *testing.T) { 3369 const name = "testbuildlabelcache" 3370 3371 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 3372 LABEL Vendor=Acme`)) 3373 id1 := getIDByName(c, name) 3374 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 3375 LABEL Vendor=Acme`)) 3376 id2 := getIDByName(c, name) 3377 if id1 != id2 { 3378 c.Fatalf("Build 2 should have worked & used cache(%s,%s)", id1, id2) 3379 } 3380 3381 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 3382 LABEL Vendor=Acme1`)) 3383 id2 = getIDByName(c, name) 3384 if id1 == id2 { 3385 c.Fatalf("Build 3 should have worked & NOT used cache(%s,%s)", id1, id2) 3386 } 3387 3388 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 3389 LABEL Vendor Acme`)) 3390 id2 = getIDByName(c, name) 3391 if id1 != id2 { 3392 c.Fatalf("Build 4 should have worked & used cache(%s,%s)", id1, id2) 3393 } 3394 3395 // Now make sure the cache isn't used by mistake 3396 buildImageSuccessfully(c, name, build.WithoutCache, build.WithDockerfile(`FROM busybox 3397 LABEL f1=b1 f2=b2`)) 3398 3399 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 3400 LABEL f1=b1 f2=b2`)) 3401 id2 = getIDByName(c, name) 3402 if id1 == id2 { 3403 c.Fatalf("Build 6 should have worked & NOT used the cache(%s,%s)", id1, id2) 3404 } 3405 } 3406 3407 // FIXME(vdemeester) port to docker/cli e2e tests (api tests should test suppressOutput option though) 3408 func (s *DockerCLIBuildSuite) TestBuildNotVerboseSuccess(c *testing.T) { 3409 // This test makes sure that -q works correctly when build is successful: 3410 // stdout has only the image ID (long image ID) and stderr is empty. 3411 outRegexp := regexp.MustCompile(`^(sha256:|)[a-z0-9]{64}\n$`) 3412 buildFlags := cli.WithFlags("-q") 3413 3414 tt := []struct { 3415 Name string 3416 BuildFunc func(string) *icmd.Result 3417 }{ 3418 { 3419 Name: "quiet_build_stdin_success", 3420 BuildFunc: func(name string) *icmd.Result { 3421 return buildImage(name, buildFlags, build.WithDockerfile("FROM busybox")) 3422 }, 3423 }, 3424 { 3425 Name: "quiet_build_ctx_success", 3426 BuildFunc: func(name string) *icmd.Result { 3427 return buildImage(name, buildFlags, build.WithBuildContext(c, 3428 build.WithFile("Dockerfile", "FROM busybox"), 3429 build.WithFile("quiet_build_success_fctx", "test"), 3430 )) 3431 }, 3432 }, 3433 { 3434 Name: "quiet_build_git_success", 3435 BuildFunc: func(name string) *icmd.Result { 3436 git := fakegit.New(c, "repo", map[string]string{ 3437 "Dockerfile": "FROM busybox", 3438 }, true) 3439 return buildImage(name, buildFlags, build.WithContextPath(git.RepoURL)) 3440 }, 3441 }, 3442 } 3443 3444 for _, te := range tt { 3445 result := te.BuildFunc(te.Name) 3446 result.Assert(c, icmd.Success) 3447 if outRegexp.Find([]byte(result.Stdout())) == nil { 3448 c.Fatalf("Test %s expected stdout to match the [%v] regexp, but it is [%v]", te.Name, outRegexp, result.Stdout()) 3449 } 3450 3451 if result.Stderr() != "" { 3452 c.Fatalf("Test %s expected stderr to be empty, but it is [%#v]", te.Name, result.Stderr()) 3453 } 3454 } 3455 } 3456 3457 // FIXME(vdemeester) migrate to docker/cli tests 3458 func (s *DockerCLIBuildSuite) TestBuildNotVerboseFailureWithNonExistImage(c *testing.T) { 3459 // This test makes sure that -q works correctly when build fails by 3460 // comparing between the stderr output in quiet mode and in stdout 3461 // and stderr output in verbose mode 3462 testRequires(c, Network) 3463 testName := "quiet_build_not_exists_image" 3464 dockerfile := "FROM busybox11" 3465 quietResult := buildImage(testName, cli.WithFlags("-q"), build.WithDockerfile(dockerfile)) 3466 quietResult.Assert(c, icmd.Expected{ 3467 ExitCode: 1, 3468 }) 3469 result := buildImage(testName, build.WithDockerfile(dockerfile)) 3470 result.Assert(c, icmd.Expected{ 3471 ExitCode: 1, 3472 }) 3473 if quietResult.Stderr() != result.Combined() { 3474 c.Fatal(fmt.Errorf("Test[%s] expected that quiet stderr and verbose stdout are equal; quiet [%v], verbose [%v]", testName, quietResult.Stderr(), result.Combined())) 3475 } 3476 } 3477 3478 // FIXME(vdemeester) migrate to docker/cli tests 3479 func (s *DockerCLIBuildSuite) TestBuildNotVerboseFailure(c *testing.T) { 3480 // This test makes sure that -q works correctly when build fails by 3481 // comparing between the stderr output in quiet mode and in stdout 3482 // and stderr output in verbose mode 3483 testCases := []struct { 3484 testName string 3485 dockerfile string 3486 }{ 3487 {"quiet_build_no_from_at_the_beginning", "RUN whoami"}, 3488 {"quiet_build_unknown_instr", "FROMD busybox"}, 3489 } 3490 3491 for _, tc := range testCases { 3492 quietResult := buildImage(tc.testName, cli.WithFlags("-q"), build.WithDockerfile(tc.dockerfile)) 3493 quietResult.Assert(c, icmd.Expected{ 3494 ExitCode: 1, 3495 }) 3496 result := buildImage(tc.testName, build.WithDockerfile(tc.dockerfile)) 3497 result.Assert(c, icmd.Expected{ 3498 ExitCode: 1, 3499 }) 3500 if quietResult.Stderr() != result.Combined() { 3501 c.Fatal(fmt.Errorf("Test[%s] expected that quiet stderr and verbose stdout are equal; quiet [%v], verbose [%v]", tc.testName, quietResult.Stderr(), result.Combined())) 3502 } 3503 } 3504 } 3505 3506 // FIXME(vdemeester) migrate to docker/cli tests 3507 func (s *DockerCLIBuildSuite) TestBuildNotVerboseFailureRemote(c *testing.T) { 3508 // This test ensures that when given a wrong URL, stderr in quiet mode and 3509 // stderr in verbose mode are identical. 3510 // TODO(vdemeester) with cobra, stdout has a carriage return too much so this test should not check stdout 3511 URL := "http://something.invalid" 3512 const name = "quiet_build_wrong_remote" 3513 quietResult := buildImage(name, cli.WithFlags("-q"), build.WithContextPath(URL)) 3514 quietResult.Assert(c, icmd.Expected{ 3515 ExitCode: 1, 3516 }) 3517 result := buildImage(name, build.WithContextPath(URL)) 3518 result.Assert(c, icmd.Expected{ 3519 ExitCode: 1, 3520 }) 3521 3522 // An error message should contain name server IP and port, like this: 3523 // "dial tcp: lookup something.invalid on 172.29.128.11:53: no such host" 3524 // The IP:port need to be removed in order to not trigger a test failur 3525 // when more than one nameserver is configured. 3526 // While at it, also strip excessive newlines. 3527 normalize := func(msg string) string { 3528 return strings.TrimSpace(regexp.MustCompile("[1-9][0-9.]+:[0-9]+").ReplaceAllLiteralString(msg, "<ip:port>")) 3529 } 3530 3531 if normalize(quietResult.Stderr()) != normalize(result.Combined()) { 3532 c.Fatal(fmt.Errorf("Test[%s] expected that quiet stderr and verbose stdout are equal; quiet [%v], verbose [%v]", name, quietResult.Stderr(), result.Combined())) 3533 } 3534 } 3535 3536 // FIXME(vdemeester) migrate to docker/cli tests 3537 func (s *DockerCLIBuildSuite) TestBuildStderr(c *testing.T) { 3538 // This test just makes sure that no non-error output goes 3539 // to stderr 3540 const name = "testbuildstderr" 3541 result := buildImage(name, build.WithDockerfile("FROM busybox\nRUN echo one")) 3542 result.Assert(c, icmd.Success) 3543 3544 // Windows to non-Windows should have a security warning 3545 if runtime.GOOS == "windows" && testEnv.DaemonInfo.OSType != "windows" && !strings.Contains(result.Stdout(), "SECURITY WARNING:") { 3546 c.Fatalf("Stdout contains unexpected output: %q", result.Stdout()) 3547 } 3548 3549 // Stderr should always be empty 3550 if result.Stderr() != "" { 3551 c.Fatalf("Stderr should have been empty, instead it's: %q", result.Stderr()) 3552 } 3553 } 3554 3555 func (s *DockerCLIBuildSuite) TestBuildChownSingleFile(c *testing.T) { 3556 testRequires(c, UnixCli, DaemonIsLinux) // test uses chown: not available on windows 3557 3558 const name = "testbuildchownsinglefile" 3559 3560 ctx := fakecontext.New(c, "", 3561 fakecontext.WithDockerfile(` 3562 FROM busybox 3563 COPY test / 3564 RUN ls -l /test 3565 RUN [ $(ls -l /test | awk '{print $3":"$4}') = 'root:root' ] 3566 `), 3567 fakecontext.WithFiles(map[string]string{ 3568 "test": "test", 3569 })) 3570 defer ctx.Close() 3571 3572 if err := os.Chown(filepath.Join(ctx.Dir, "test"), 4242, 4242); err != nil { 3573 c.Fatal(err) 3574 } 3575 3576 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 3577 } 3578 3579 func (s *DockerCLIBuildSuite) TestBuildSymlinkBreakout(c *testing.T) { 3580 skip.If(c, testEnv.UsingSnapshotter(), "FIXME: https://github.com/moby/moby/issues/47107") 3581 const name = "testbuildsymlinkbreakout" 3582 tmpdir, err := os.MkdirTemp("", name) 3583 assert.NilError(c, err) 3584 3585 // See https://github.com/moby/moby/pull/37770 for reason for next line. 3586 tmpdir, err = getLongPathName(tmpdir) 3587 assert.NilError(c, err) 3588 3589 defer os.RemoveAll(tmpdir) 3590 ctx := filepath.Join(tmpdir, "context") 3591 if err := os.MkdirAll(ctx, 0o755); err != nil { 3592 c.Fatal(err) 3593 } 3594 if err := os.WriteFile(filepath.Join(ctx, "Dockerfile"), []byte(` 3595 from busybox 3596 add symlink.tar / 3597 add inject /symlink/ 3598 `), 0o644); err != nil { 3599 c.Fatal(err) 3600 } 3601 inject := filepath.Join(ctx, "inject") 3602 if err := os.WriteFile(inject, nil, 0o644); err != nil { 3603 c.Fatal(err) 3604 } 3605 f, err := os.Create(filepath.Join(ctx, "symlink.tar")) 3606 if err != nil { 3607 c.Fatal(err) 3608 } 3609 w := tar.NewWriter(f) 3610 w.WriteHeader(&tar.Header{ 3611 Name: "symlink2", 3612 Typeflag: tar.TypeSymlink, 3613 Linkname: "/../../../../../../../../../../../../../../", 3614 Uid: os.Getuid(), 3615 Gid: os.Getgid(), 3616 }) 3617 w.WriteHeader(&tar.Header{ 3618 Name: "symlink", 3619 Typeflag: tar.TypeSymlink, 3620 Linkname: filepath.Join("symlink2", tmpdir), 3621 Uid: os.Getuid(), 3622 Gid: os.Getgid(), 3623 }) 3624 w.Close() 3625 f.Close() 3626 3627 buildImageSuccessfully(c, name, build.WithoutCache, build.WithExternalBuildContext(fakecontext.New(c, ctx))) 3628 if _, err := os.Lstat(filepath.Join(tmpdir, "inject")); err == nil { 3629 c.Fatal("symlink breakout - inject") 3630 } else if !os.IsNotExist(err) { 3631 c.Fatalf("unexpected error: %v", err) 3632 } 3633 } 3634 3635 func (s *DockerCLIBuildSuite) TestBuildXZHost(c *testing.T) { 3636 // /usr/local/sbin/xz gets permission denied for the user 3637 testRequires(c, NotUserNamespace) 3638 testRequires(c, DaemonIsLinux) 3639 const name = "testbuildxzhost" 3640 3641 buildImageSuccessfully(c, name, build.WithBuildContext(c, 3642 build.WithFile("Dockerfile", ` 3643 FROM busybox 3644 ADD xz /usr/local/sbin/ 3645 RUN chmod 755 /usr/local/sbin/xz 3646 ADD test.xz / 3647 RUN [ ! -e /injected ]`), 3648 build.WithFile("test.xz", "\xfd\x37\x7a\x58\x5a\x00\x00\x04\xe6\xd6\xb4\x46\x02\x00"+"\x21\x01\x16\x00\x00\x00\x74\x2f\xe5\xa3\x01\x00\x3f\xfd"+"\x37\x7a\x58\x5a\x00\x00\x04\xe6\xd6\xb4\x46\x02\x00\x21"), 3649 build.WithFile("xz", "#!/bin/sh\ntouch /injected"), 3650 )) 3651 } 3652 3653 func (s *DockerCLIBuildSuite) TestBuildVolumesRetainContents(c *testing.T) { 3654 // /foo/file gets permission denied for the user 3655 testRequires(c, NotUserNamespace) 3656 testRequires(c, DaemonIsLinux) // TODO Windows: Issue #20127 3657 var ( 3658 name = "testbuildvolumescontent" 3659 expected = "some text" 3660 volName = "/foo" 3661 ) 3662 3663 if testEnv.DaemonInfo.OSType == "windows" { 3664 volName = "C:/foo" 3665 } 3666 3667 buildImageSuccessfully(c, name, build.WithBuildContext(c, 3668 build.WithFile("Dockerfile", ` 3669 FROM busybox 3670 COPY content /foo/file 3671 VOLUME `+volName+` 3672 CMD cat /foo/file`), 3673 build.WithFile("content", expected), 3674 )) 3675 3676 out := cli.DockerCmd(c, "run", "--rm", name).Combined() 3677 if out != expected { 3678 c.Fatalf("expected file contents for /foo/file to be %q but received %q", expected, out) 3679 } 3680 } 3681 3682 func (s *DockerCLIBuildSuite) TestBuildFromMixedcaseDockerfile(c *testing.T) { 3683 testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows 3684 testRequires(c, DaemonIsLinux) 3685 3686 // If Dockerfile is not present, use dockerfile 3687 buildImage("test1", build.WithBuildContext(c, 3688 build.WithFile("dockerfile", `FROM busybox 3689 RUN echo from dockerfile`), 3690 )).Assert(c, icmd.Expected{ 3691 Out: "from dockerfile", 3692 }) 3693 3694 // Prefer Dockerfile in place of dockerfile 3695 buildImage("test1", build.WithBuildContext(c, 3696 build.WithFile("dockerfile", `FROM busybox 3697 RUN echo from dockerfile`), 3698 build.WithFile("Dockerfile", `FROM busybox 3699 RUN echo from Dockerfile`), 3700 )).Assert(c, icmd.Expected{ 3701 Out: "from Dockerfile", 3702 }) 3703 } 3704 3705 // FIXME(vdemeester) should migrate to docker/cli tests 3706 func (s *DockerCLIBuildSuite) TestBuildFromURLWithF(c *testing.T) { 3707 server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{"baz": `FROM busybox 3708 RUN echo from baz 3709 COPY * /tmp/ 3710 RUN find /tmp/`})) 3711 defer server.Close() 3712 3713 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`FROM busybox 3714 RUN echo from Dockerfile`)) 3715 defer ctx.Close() 3716 3717 // Make sure that -f is ignored and that we don't use the Dockerfile 3718 // that's in the current dir 3719 result := cli.BuildCmd(c, "test1", cli.WithFlags("-f", "baz", server.URL()+"/baz"), func(cmd *icmd.Cmd) func() { 3720 cmd.Dir = ctx.Dir 3721 return nil 3722 }) 3723 3724 if !strings.Contains(result.Combined(), "from baz") || 3725 strings.Contains(result.Combined(), "/tmp/baz") || 3726 !strings.Contains(result.Combined(), "/tmp/Dockerfile") { 3727 c.Fatalf("Missing proper output: %s", result.Combined()) 3728 } 3729 } 3730 3731 // FIXME(vdemeester) should migrate to docker/cli tests 3732 func (s *DockerCLIBuildSuite) TestBuildFromStdinWithF(c *testing.T) { 3733 testRequires(c, DaemonIsLinux) // TODO Windows: This test is flaky; no idea why 3734 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`FROM busybox 3735 RUN echo "from Dockerfile"`)) 3736 defer ctx.Close() 3737 3738 // Make sure that -f is ignored and that we don't use the Dockerfile 3739 // that's in the current dir 3740 result := cli.BuildCmd(c, "test1", cli.WithFlags("-f", "baz", "-"), func(cmd *icmd.Cmd) func() { 3741 cmd.Dir = ctx.Dir 3742 cmd.Stdin = strings.NewReader(`FROM busybox 3743 RUN echo "from baz" 3744 COPY * /tmp/ 3745 RUN sh -c "find /tmp/" # sh -c is needed on Windows to use the correct find`) 3746 return nil 3747 }) 3748 3749 if !strings.Contains(result.Combined(), "from baz") || 3750 strings.Contains(result.Combined(), "/tmp/baz") || 3751 !strings.Contains(result.Combined(), "/tmp/Dockerfile") { 3752 c.Fatalf("Missing proper output: %s", result.Combined()) 3753 } 3754 } 3755 3756 func (s *DockerCLIBuildSuite) TestBuildFromOfficialNames(c *testing.T) { 3757 const name = "testbuildfromofficial" 3758 fromNames := []string{ 3759 "busybox", 3760 "docker.io/busybox", 3761 "index.docker.io/busybox", 3762 "library/busybox", 3763 "docker.io/library/busybox", 3764 "index.docker.io/library/busybox", 3765 } 3766 for idx, fromName := range fromNames { 3767 imgName := fmt.Sprintf("%s%d", name, idx) 3768 buildImageSuccessfully(c, imgName, build.WithDockerfile("FROM "+fromName)) 3769 cli.DockerCmd(c, "rmi", imgName) 3770 } 3771 } 3772 3773 // FIXME(vdemeester) should be a unit test 3774 func (s *DockerCLIBuildSuite) TestBuildSpaces(c *testing.T) { 3775 // Test to make sure that leading/trailing spaces on a command 3776 // doesn't change the error msg we get 3777 const name = "testspaces" 3778 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile("FROM busybox\nCOPY\n")) 3779 defer ctx.Close() 3780 3781 result1 := cli.Docker(cli.Args("build", "-t", name), build.WithExternalBuildContext(ctx)) 3782 result1.Assert(c, icmd.Expected{ 3783 ExitCode: 1, 3784 }) 3785 3786 ctx.Add("Dockerfile", "FROM busybox\nCOPY ") 3787 result2 := cli.Docker(cli.Args("build", "-t", name), build.WithExternalBuildContext(ctx)) 3788 result2.Assert(c, icmd.Expected{ 3789 ExitCode: 1, 3790 }) 3791 3792 removeLogTimestamps := func(s string) string { 3793 return regexp.MustCompile(`time="(.*?)"`).ReplaceAllString(s, `time=[TIMESTAMP]`) 3794 } 3795 3796 // Skip over the times 3797 e1 := removeLogTimestamps(result1.Error.Error()) 3798 e2 := removeLogTimestamps(result2.Error.Error()) 3799 3800 // Ignore whitespace since that's what were verifying doesn't change stuff 3801 if strings.ReplaceAll(e1, " ", "") != strings.ReplaceAll(e2, " ", "") { 3802 c.Fatalf("Build 2's error wasn't the same as build 1's\n1:%s\n2:%s", result1.Error, result2.Error) 3803 } 3804 3805 ctx.Add("Dockerfile", "FROM busybox\n COPY") 3806 result2 = cli.Docker(cli.Args("build", "-t", name), build.WithoutCache, build.WithExternalBuildContext(ctx)) 3807 result2.Assert(c, icmd.Expected{ 3808 ExitCode: 1, 3809 }) 3810 3811 // Skip over the times 3812 e1 = removeLogTimestamps(result1.Error.Error()) 3813 e2 = removeLogTimestamps(result2.Error.Error()) 3814 3815 // Ignore whitespace since that's what were verifying doesn't change stuff 3816 if strings.ReplaceAll(e1, " ", "") != strings.ReplaceAll(e2, " ", "") { 3817 c.Fatalf("Build 3's error wasn't the same as build 1's\n1:%s\n3:%s", result1.Error, result2.Error) 3818 } 3819 3820 ctx.Add("Dockerfile", "FROM busybox\n COPY ") 3821 result2 = cli.Docker(cli.Args("build", "-t", name), build.WithoutCache, build.WithExternalBuildContext(ctx)) 3822 result2.Assert(c, icmd.Expected{ 3823 ExitCode: 1, 3824 }) 3825 3826 // Skip over the times 3827 e1 = removeLogTimestamps(result1.Error.Error()) 3828 e2 = removeLogTimestamps(result2.Error.Error()) 3829 3830 // Ignore whitespace since that's what were verifying doesn't change stuff 3831 if strings.ReplaceAll(e1, " ", "") != strings.ReplaceAll(e2, " ", "") { 3832 c.Fatalf("Build 4's error wasn't the same as build 1's\n1:%s\n4:%s", result1.Error, result2.Error) 3833 } 3834 } 3835 3836 func (s *DockerCLIBuildSuite) TestBuildSpacesWithQuotes(c *testing.T) { 3837 // Test to make sure that spaces in quotes aren't lost 3838 const name = "testspacesquotes" 3839 3840 dockerfile := `FROM busybox 3841 RUN echo " \ 3842 foo "` 3843 3844 expected := "\n foo \n" 3845 // Windows uses the builtin echo, which preserves quotes 3846 if testEnv.DaemonInfo.OSType == "windows" { 3847 expected = "\" foo \"" 3848 } 3849 3850 buildImage(name, build.WithDockerfile(dockerfile)).Assert(c, icmd.Expected{ 3851 Out: expected, 3852 }) 3853 } 3854 3855 // #4393 3856 func (s *DockerCLIBuildSuite) TestBuildVolumeFileExistsinContainer(c *testing.T) { 3857 testRequires(c, DaemonIsLinux) // TODO Windows: This should error out 3858 buildImage("docker-test-errcreatevolumewithfile", build.WithDockerfile(` 3859 FROM busybox 3860 RUN touch /foo 3861 VOLUME /foo 3862 `)).Assert(c, icmd.Expected{ 3863 ExitCode: 1, 3864 Err: "file exists", 3865 }) 3866 } 3867 3868 // FIXME(vdemeester) should be a unit test 3869 func (s *DockerCLIBuildSuite) TestBuildMissingArgs(c *testing.T) { 3870 // Test to make sure that all Dockerfile commands (except the ones listed 3871 // in skipCmds) will generate an error if no args are provided. 3872 // Note: INSERT is deprecated so we exclude it because of that. 3873 skipCmds := map[string]struct{}{ 3874 "CMD": {}, 3875 "RUN": {}, 3876 "ENTRYPOINT": {}, 3877 "INSERT": {}, 3878 } 3879 3880 if testEnv.DaemonInfo.OSType == "windows" { 3881 skipCmds = map[string]struct{}{ 3882 "CMD": {}, 3883 "RUN": {}, 3884 "ENTRYPOINT": {}, 3885 "INSERT": {}, 3886 "STOPSIGNAL": {}, 3887 "ARG": {}, 3888 "USER": {}, 3889 "EXPOSE": {}, 3890 } 3891 } 3892 3893 for cmd := range command.Commands { 3894 cmd = strings.ToUpper(cmd) 3895 if _, ok := skipCmds[cmd]; ok { 3896 continue 3897 } 3898 var dockerfile string 3899 if cmd == "FROM" { 3900 dockerfile = cmd 3901 } else { 3902 // Add FROM to make sure we don't complain about it missing 3903 dockerfile = "FROM busybox\n" + cmd 3904 } 3905 3906 buildImage("args", build.WithDockerfile(dockerfile)).Assert(c, icmd.Expected{ 3907 ExitCode: 1, 3908 Err: cmd + " requires", 3909 }) 3910 } 3911 } 3912 3913 func (s *DockerCLIBuildSuite) TestBuildEmptyScratch(c *testing.T) { 3914 testRequires(c, DaemonIsLinux) 3915 buildImage("sc", build.WithDockerfile("FROM scratch")).Assert(c, icmd.Expected{ 3916 ExitCode: 1, 3917 Err: "No image was generated", 3918 }) 3919 } 3920 3921 func (s *DockerCLIBuildSuite) TestBuildDotDotFile(c *testing.T) { 3922 buildImageSuccessfully(c, "sc", build.WithBuildContext(c, 3923 build.WithFile("Dockerfile", "FROM busybox\n"), 3924 build.WithFile("..gitme", ""), 3925 )) 3926 } 3927 3928 func (s *DockerCLIBuildSuite) TestBuildRUNoneJSON(c *testing.T) { 3929 testRequires(c, DaemonIsLinux) // No hello-world Windows image 3930 const name = "testbuildrunonejson" 3931 3932 buildImage(name, build.WithDockerfile(`FROM hello-world:frozen 3933 RUN [ "/hello" ]`)).Assert(c, icmd.Expected{ 3934 Out: "Hello from Docker", 3935 }) 3936 } 3937 3938 func (s *DockerCLIBuildSuite) TestBuildEmptyStringVolume(c *testing.T) { 3939 const name = "testbuildemptystringvolume" 3940 3941 buildImage(name, build.WithDockerfile(` 3942 FROM busybox 3943 ENV foo="" 3944 VOLUME $foo 3945 `)).Assert(c, icmd.Expected{ 3946 ExitCode: 1, 3947 }) 3948 } 3949 3950 func (s *DockerCLIBuildSuite) TestBuildContainerWithCgroupParent(c *testing.T) { 3951 testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux) 3952 3953 cgroupParent := "test" 3954 data, err := os.ReadFile("/proc/self/cgroup") 3955 if err != nil { 3956 c.Fatalf("failed to read '/proc/self/cgroup - %v", err) 3957 } 3958 selfCgroupPaths := ParseCgroupPaths(string(data)) 3959 _, found := selfCgroupPaths["memory"] 3960 if !found { 3961 c.Fatalf("unable to find self memory cgroup path. CgroupsPath: %v", selfCgroupPaths) 3962 } 3963 result := buildImage("buildcgroupparent", 3964 cli.WithFlags("--cgroup-parent", cgroupParent), 3965 build.WithDockerfile(` 3966 FROM busybox 3967 RUN cat /proc/self/cgroup 3968 `)) 3969 result.Assert(c, icmd.Success) 3970 m, err := regexp.MatchString(fmt.Sprintf("memory:.*/%s/.*", cgroupParent), result.Combined()) 3971 assert.NilError(c, err) 3972 if !m { 3973 c.Fatalf("There is no expected memory cgroup with parent /%s/: %s", cgroupParent, result.Combined()) 3974 } 3975 } 3976 3977 // FIXME(vdemeester) could be a unit test 3978 func (s *DockerCLIBuildSuite) TestBuildNoDupOutput(c *testing.T) { 3979 // Check to make sure our build output prints the Dockerfile cmd 3980 // property - there was a bug that caused it to be duplicated on the 3981 // Step X line 3982 const name = "testbuildnodupoutput" 3983 result := buildImage(name, build.WithDockerfile(` 3984 FROM busybox 3985 RUN env`)) 3986 result.Assert(c, icmd.Success) 3987 exp := "\nStep 2/2 : RUN env\n" 3988 if !strings.Contains(result.Combined(), exp) { 3989 c.Fatalf("Bad output\nGot:%s\n\nExpected to contain:%s\n", result.Combined(), exp) 3990 } 3991 } 3992 3993 // GH15826 3994 // FIXME(vdemeester) could be a unit test 3995 func (s *DockerCLIBuildSuite) TestBuildStartsFromOne(c *testing.T) { 3996 // Explicit check to ensure that build starts from step 1 rather than 0 3997 const name = "testbuildstartsfromone" 3998 result := buildImage(name, build.WithDockerfile(`FROM busybox`)) 3999 result.Assert(c, icmd.Success) 4000 exp := "\nStep 1/1 : FROM busybox\n" 4001 if !strings.Contains(result.Combined(), exp) { 4002 c.Fatalf("Bad output\nGot:%s\n\nExpected to contain:%s\n", result.Combined(), exp) 4003 } 4004 } 4005 4006 func (s *DockerCLIBuildSuite) TestBuildRUNErrMsg(c *testing.T) { 4007 // Test to make sure the bad command is quoted with just "s and 4008 // not as a Go []string 4009 const name = "testbuildbadrunerrmsg" 4010 shell := "/bin/sh -c" 4011 exitCode := 127 4012 if testEnv.DaemonInfo.OSType == "windows" { 4013 shell = "cmd /S /C" 4014 // architectural - Windows has to start the container to determine the exe is bad, Linux does not 4015 exitCode = 1 4016 } 4017 exp := fmt.Sprintf(`The command '%s badEXE a1 \& a2 a3' returned a non-zero code: %d`, shell, exitCode) 4018 4019 buildImage(name, build.WithDockerfile(` 4020 FROM busybox 4021 RUN badEXE a1 \& a2 a3`)).Assert(c, icmd.Expected{ 4022 ExitCode: exitCode, 4023 Err: exp, 4024 }) 4025 } 4026 4027 // Issue #15634: COPY fails when path starts with "null" 4028 func (s *DockerCLIBuildSuite) TestBuildNullStringInAddCopyVolume(c *testing.T) { 4029 const name = "testbuildnullstringinaddcopyvolume" 4030 volName := "nullvolume" 4031 if testEnv.DaemonInfo.OSType == "windows" { 4032 volName = `C:\\nullvolume` 4033 } 4034 4035 buildImageSuccessfully(c, name, build.WithBuildContext(c, 4036 build.WithFile("Dockerfile", ` 4037 FROM busybox 4038 4039 ADD null / 4040 COPY nullfile / 4041 VOLUME `+volName+` 4042 `), 4043 build.WithFile("null", "test1"), 4044 build.WithFile("nullfile", "test2"), 4045 )) 4046 } 4047 4048 func (s *DockerCLIBuildSuite) TestBuildStopSignal(c *testing.T) { 4049 testRequires(c, DaemonIsLinux) // Windows does not support STOPSIGNAL yet 4050 imgName := "test_build_stop_signal" 4051 buildImageSuccessfully(c, imgName, build.WithDockerfile(`FROM busybox 4052 STOPSIGNAL SIGKILL`)) 4053 res := inspectFieldJSON(c, imgName, "Config.StopSignal") 4054 if res != `"SIGKILL"` { 4055 c.Fatalf("Signal %s, expected SIGKILL", res) 4056 } 4057 4058 containerName := "test-container-stop-signal" 4059 cli.DockerCmd(c, "run", "-d", "--name", containerName, imgName, "top") 4060 res = inspectFieldJSON(c, containerName, "Config.StopSignal") 4061 if res != `"SIGKILL"` { 4062 c.Fatalf("Signal %s, expected SIGKILL", res) 4063 } 4064 } 4065 4066 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArg(c *testing.T) { 4067 imgName := "bldargtest" 4068 envKey := "foo" 4069 envVal := "bar" 4070 var dockerfile string 4071 if testEnv.DaemonInfo.OSType == "windows" { 4072 // Bugs in Windows busybox port - use the default base image and native cmd stuff 4073 dockerfile = fmt.Sprintf(`FROM `+minimalBaseImage()+` 4074 ARG %s 4075 RUN echo %%%s%% 4076 CMD setlocal enableextensions && if defined %s (echo %%%s%%)`, envKey, envKey, envKey, envKey) 4077 } else { 4078 dockerfile = fmt.Sprintf(`FROM busybox 4079 ARG %s 4080 RUN echo $%s 4081 CMD echo $%s`, envKey, envKey, envKey) 4082 } 4083 buildImage(imgName, 4084 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)), 4085 build.WithDockerfile(dockerfile), 4086 ).Assert(c, icmd.Expected{ 4087 Out: envVal, 4088 }) 4089 4090 containerName := "bldargCont" 4091 out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined() 4092 out = strings.Trim(out, " \r\n'") 4093 if out != "" { 4094 c.Fatalf("run produced invalid output: %q, expected empty string", out) 4095 } 4096 } 4097 4098 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgHistory(c *testing.T) { 4099 imgName := "bldargtest" 4100 envKey := "foo" 4101 envVal := "bar" 4102 envDef := "bar1" 4103 dockerfile := fmt.Sprintf(`FROM busybox 4104 ARG %s=%s`, envKey, envDef) 4105 buildImage(imgName, 4106 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)), 4107 build.WithDockerfile(dockerfile), 4108 ).Assert(c, icmd.Expected{ 4109 Out: envVal, 4110 }) 4111 4112 out := cli.DockerCmd(c, "history", "--no-trunc", imgName).Combined() 4113 outputTabs := strings.Split(out, "\n")[1] 4114 if !strings.Contains(outputTabs, envDef) { 4115 c.Fatalf("failed to find arg default in image history output: %q expected: %q", outputTabs, envDef) 4116 } 4117 } 4118 4119 func (s *DockerCLIBuildSuite) TestBuildTimeArgHistoryExclusions(c *testing.T) { 4120 imgName := "bldargtest" 4121 envKey := "foo" 4122 envVal := "bar" 4123 proxy := "HTTP_PROXY=http://user:password@proxy.example.com" 4124 explicitProxyKey := "http_proxy" 4125 explicitProxyVal := "http://user:password@someproxy.example.com" 4126 dockerfile := fmt.Sprintf(`FROM busybox 4127 ARG %s 4128 ARG %s 4129 RUN echo "Testing Build Args!"`, envKey, explicitProxyKey) 4130 4131 buildImage := func(imgName string) string { 4132 cli.BuildCmd(c, imgName, 4133 cli.WithFlags("--build-arg", "https_proxy=https://proxy.example.com", 4134 "--build-arg", fmt.Sprintf("%s=%s", envKey, envVal), 4135 "--build-arg", fmt.Sprintf("%s=%s", explicitProxyKey, explicitProxyVal), 4136 "--build-arg", proxy), 4137 build.WithDockerfile(dockerfile), 4138 ) 4139 return getIDByName(c, imgName) 4140 } 4141 4142 origID := buildImage(imgName) 4143 result := cli.DockerCmd(c, "history", "--no-trunc", imgName) 4144 out := result.Stdout() 4145 4146 if strings.Contains(out, proxy) { 4147 c.Fatalf("failed to exclude proxy settings from history!") 4148 } 4149 if strings.Contains(out, "https_proxy") { 4150 c.Fatalf("failed to exclude proxy settings from history!") 4151 } 4152 result.Assert(c, icmd.Expected{Out: fmt.Sprintf("%s=%s", envKey, envVal)}) 4153 result.Assert(c, icmd.Expected{Out: fmt.Sprintf("%s=%s", explicitProxyKey, explicitProxyVal)}) 4154 4155 cacheID := buildImage(imgName + "-two") 4156 assert.Equal(c, origID, cacheID) 4157 } 4158 4159 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgCacheHit(c *testing.T) { 4160 imgName := "bldargtest" 4161 envKey := "foo" 4162 envVal := "bar" 4163 dockerfile := fmt.Sprintf(`FROM busybox 4164 ARG %s 4165 RUN echo $%s`, envKey, envKey) 4166 buildImageSuccessfully(c, imgName, 4167 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)), 4168 build.WithDockerfile(dockerfile), 4169 ) 4170 origImgID := getIDByName(c, imgName) 4171 4172 imgNameCache := "bldargtestcachehit" 4173 buildImageSuccessfully(c, imgNameCache, 4174 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)), 4175 build.WithDockerfile(dockerfile), 4176 ) 4177 newImgID := getIDByName(c, imgName) 4178 if newImgID != origImgID { 4179 c.Fatalf("build didn't use cache! expected image id: %q built image id: %q", origImgID, newImgID) 4180 } 4181 } 4182 4183 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgCacheMissExtraArg(c *testing.T) { 4184 imgName := "bldargtest" 4185 envKey := "foo" 4186 envVal := "bar" 4187 extraEnvKey := "foo1" 4188 extraEnvVal := "bar1" 4189 dockerfile := fmt.Sprintf(`FROM busybox 4190 ARG %s 4191 ARG %s 4192 RUN echo $%s`, envKey, extraEnvKey, envKey) 4193 buildImageSuccessfully(c, imgName, 4194 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)), 4195 build.WithDockerfile(dockerfile), 4196 ) 4197 origImgID := getIDByName(c, imgName) 4198 4199 imgNameCache := "bldargtestcachemiss" 4200 buildImageSuccessfully(c, imgNameCache, 4201 cli.WithFlags( 4202 "--build-arg", fmt.Sprintf("%s=%s", envKey, envVal), 4203 "--build-arg", fmt.Sprintf("%s=%s", extraEnvKey, extraEnvVal), 4204 ), 4205 build.WithDockerfile(dockerfile), 4206 ) 4207 newImgID := getIDByName(c, imgNameCache) 4208 4209 if newImgID == origImgID { 4210 c.Fatalf("build used cache, expected a miss!") 4211 } 4212 } 4213 4214 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgCacheMissSameArgDiffVal(c *testing.T) { 4215 imgName := "bldargtest" 4216 envKey := "foo" 4217 envVal := "bar" 4218 newEnvVal := "bar1" 4219 dockerfile := fmt.Sprintf(`FROM busybox 4220 ARG %s 4221 RUN echo $%s`, envKey, envKey) 4222 buildImageSuccessfully(c, imgName, 4223 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)), 4224 build.WithDockerfile(dockerfile), 4225 ) 4226 origImgID := getIDByName(c, imgName) 4227 4228 imgNameCache := "bldargtestcachemiss" 4229 buildImageSuccessfully(c, imgNameCache, 4230 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, newEnvVal)), 4231 build.WithDockerfile(dockerfile), 4232 ) 4233 newImgID := getIDByName(c, imgNameCache) 4234 if newImgID == origImgID { 4235 c.Fatalf("build used cache, expected a miss!") 4236 } 4237 } 4238 4239 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgOverrideArgDefinedBeforeEnv(c *testing.T) { 4240 testRequires(c, DaemonIsLinux) // Windows does not support ARG 4241 imgName := "bldargtest" 4242 envKey := "foo" 4243 envVal := "bar" 4244 envValOverride := "barOverride" 4245 dockerfile := fmt.Sprintf(`FROM busybox 4246 ARG %s 4247 ENV %s %s 4248 RUN echo $%s 4249 CMD echo $%s 4250 `, envKey, envKey, envValOverride, envKey, envKey) 4251 4252 result := buildImage(imgName, 4253 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)), 4254 build.WithDockerfile(dockerfile), 4255 ) 4256 result.Assert(c, icmd.Success) 4257 if strings.Count(result.Combined(), envValOverride) != 2 { 4258 c.Fatalf("failed to access environment variable in output: %q expected: %q", result.Combined(), envValOverride) 4259 } 4260 4261 containerName := "bldargCont" 4262 if out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined(); !strings.Contains(out, envValOverride) { 4263 c.Fatalf("run produced invalid output: %q, expected %q", out, envValOverride) 4264 } 4265 } 4266 4267 // FIXME(vdemeester) might be useful to merge with the one above ? 4268 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgOverrideEnvDefinedBeforeArg(c *testing.T) { 4269 testRequires(c, DaemonIsLinux) // Windows does not support ARG 4270 imgName := "bldargtest" 4271 envKey := "foo" 4272 envVal := "bar" 4273 envValOverride := "barOverride" 4274 dockerfile := fmt.Sprintf(`FROM busybox 4275 ENV %s %s 4276 ARG %s 4277 RUN echo $%s 4278 CMD echo $%s 4279 `, envKey, envValOverride, envKey, envKey, envKey) 4280 result := buildImage(imgName, 4281 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)), 4282 build.WithDockerfile(dockerfile), 4283 ) 4284 result.Assert(c, icmd.Success) 4285 if strings.Count(result.Combined(), envValOverride) != 2 { 4286 c.Fatalf("failed to access environment variable in output: %q expected: %q", result.Combined(), envValOverride) 4287 } 4288 4289 containerName := "bldargCont" 4290 if out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined(); !strings.Contains(out, envValOverride) { 4291 c.Fatalf("run produced invalid output: %q, expected %q", out, envValOverride) 4292 } 4293 } 4294 4295 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgExpansion(c *testing.T) { 4296 imgName := "bldvarstest" 4297 4298 wdVar := "WDIR" 4299 wdVal := "/tmp" 4300 addVar := "AFILE" 4301 addVal := "addFile" 4302 copyVar := "CFILE" 4303 copyVal := "copyFile" 4304 envVar := "foo" 4305 envVal := "bar" 4306 exposeVar := "EPORT" 4307 exposeVal := "9999" 4308 userVar := "USER" 4309 userVal := "testUser" 4310 volVar := "VOL" 4311 volVal := "/testVol/" 4312 if DaemonIsWindows() { 4313 volVal = "C:\\testVol" 4314 wdVal = "C:\\tmp" 4315 } 4316 4317 buildImageSuccessfully(c, imgName, 4318 cli.WithFlags( 4319 "--build-arg", fmt.Sprintf("%s=%s", wdVar, wdVal), 4320 "--build-arg", fmt.Sprintf("%s=%s", addVar, addVal), 4321 "--build-arg", fmt.Sprintf("%s=%s", copyVar, copyVal), 4322 "--build-arg", fmt.Sprintf("%s=%s", envVar, envVal), 4323 "--build-arg", fmt.Sprintf("%s=%s", exposeVar, exposeVal), 4324 "--build-arg", fmt.Sprintf("%s=%s", userVar, userVal), 4325 "--build-arg", fmt.Sprintf("%s=%s", volVar, volVal), 4326 ), 4327 build.WithBuildContext(c, 4328 build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox 4329 ARG %s 4330 WORKDIR ${%s} 4331 ARG %s 4332 ADD ${%s} testDir/ 4333 ARG %s 4334 COPY $%s testDir/ 4335 ARG %s 4336 ENV %s=${%s} 4337 ARG %s 4338 EXPOSE $%s 4339 ARG %s 4340 USER $%s 4341 ARG %s 4342 VOLUME ${%s}`, 4343 wdVar, wdVar, addVar, addVar, copyVar, copyVar, envVar, envVar, 4344 envVar, exposeVar, exposeVar, userVar, userVar, volVar, volVar)), 4345 build.WithFile(addVal, "some stuff"), 4346 build.WithFile(copyVal, "some stuff"), 4347 ), 4348 ) 4349 4350 res := inspectField(c, imgName, "Config.WorkingDir") 4351 assert.Equal(c, filepath.ToSlash(res), filepath.ToSlash(wdVal)) 4352 4353 var resArr []string 4354 inspectFieldAndUnmarshall(c, imgName, "Config.Env", &resArr) 4355 4356 found := false 4357 for _, v := range resArr { 4358 if fmt.Sprintf("%s=%s", envVar, envVal) == v { 4359 found = true 4360 break 4361 } 4362 } 4363 if !found { 4364 c.Fatalf("Config.Env value mismatch. Expected <key=value> to exist: %s=%s, got: %v", 4365 envVar, envVal, resArr) 4366 } 4367 4368 var resMap map[string]interface{} 4369 inspectFieldAndUnmarshall(c, imgName, "Config.ExposedPorts", &resMap) 4370 if _, ok := resMap[fmt.Sprintf("%s/tcp", exposeVal)]; !ok { 4371 c.Fatalf("Config.ExposedPorts value mismatch. Expected exposed port: %s/tcp, got: %v", exposeVal, resMap) 4372 } 4373 4374 res = inspectField(c, imgName, "Config.User") 4375 if res != userVal { 4376 c.Fatalf("Config.User value mismatch. Expected: %s, got: %s", userVal, res) 4377 } 4378 4379 inspectFieldAndUnmarshall(c, imgName, "Config.Volumes", &resMap) 4380 if _, ok := resMap[volVal]; !ok { 4381 c.Fatalf("Config.Volumes value mismatch. Expected volume: %s, got: %v", volVal, resMap) 4382 } 4383 } 4384 4385 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgExpansionOverride(c *testing.T) { 4386 testRequires(c, DaemonIsLinux) // Windows does not support ARG 4387 imgName := "bldvarstest" 4388 envKey := "foo" 4389 envVal := "bar" 4390 envKey1 := "foo1" 4391 envValOverride := "barOverride" 4392 dockerfile := fmt.Sprintf(`FROM busybox 4393 ARG %s 4394 ENV %s %s 4395 ENV %s ${%s} 4396 RUN echo $%s 4397 CMD echo $%s`, envKey, envKey, envValOverride, envKey1, envKey, envKey1, envKey1) 4398 result := buildImage(imgName, 4399 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)), 4400 build.WithDockerfile(dockerfile), 4401 ) 4402 result.Assert(c, icmd.Success) 4403 if strings.Count(result.Combined(), envValOverride) != 2 { 4404 c.Fatalf("failed to access environment variable in output: %q expected: %q", result.Combined(), envValOverride) 4405 } 4406 4407 containerName := "bldargCont" 4408 if out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined(); !strings.Contains(out, envValOverride) { 4409 c.Fatalf("run produced invalid output: %q, expected %q", out, envValOverride) 4410 } 4411 } 4412 4413 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgUntrustedDefinedAfterUse(c *testing.T) { 4414 testRequires(c, DaemonIsLinux) // Windows does not support ARG 4415 imgName := "bldargtest" 4416 envKey := "foo" 4417 envVal := "bar" 4418 dockerfile := fmt.Sprintf(`FROM busybox 4419 RUN echo $%s 4420 ARG %s 4421 CMD echo $%s`, envKey, envKey, envKey) 4422 result := buildImage(imgName, 4423 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)), 4424 build.WithDockerfile(dockerfile), 4425 ) 4426 result.Assert(c, icmd.Success) 4427 if strings.Contains(result.Combined(), envVal) { 4428 c.Fatalf("able to access environment variable in output: %q expected to be missing", result.Combined()) 4429 } 4430 4431 containerName := "bldargCont" 4432 if out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined(); out != "\n" { 4433 c.Fatalf("run produced invalid output: %q, expected empty string", out) 4434 } 4435 } 4436 4437 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgBuiltinArg(c *testing.T) { 4438 testRequires(c, DaemonIsLinux) // Windows does not support --build-arg 4439 imgName := "bldargtest" 4440 envKey := "HTTP_PROXY" 4441 envVal := "bar" 4442 dockerfile := fmt.Sprintf(`FROM busybox 4443 RUN echo $%s 4444 CMD echo $%s`, envKey, envKey) 4445 4446 result := buildImage(imgName, 4447 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)), 4448 build.WithDockerfile(dockerfile), 4449 ) 4450 result.Assert(c, icmd.Success) 4451 if !strings.Contains(result.Combined(), envVal) { 4452 c.Fatalf("failed to access environment variable in output: %q expected: %q", result.Combined(), envVal) 4453 } 4454 containerName := "bldargCont" 4455 if out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined(); out != "\n" { 4456 c.Fatalf("run produced invalid output: %q, expected empty string", out) 4457 } 4458 } 4459 4460 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgDefaultOverride(c *testing.T) { 4461 testRequires(c, DaemonIsLinux) // Windows does not support ARG 4462 imgName := "bldargtest" 4463 envKey := "foo" 4464 envVal := "bar" 4465 envValOverride := "barOverride" 4466 dockerfile := fmt.Sprintf(`FROM busybox 4467 ARG %s=%s 4468 ENV %s $%s 4469 RUN echo $%s 4470 CMD echo $%s`, envKey, envVal, envKey, envKey, envKey, envKey) 4471 result := buildImage(imgName, 4472 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envValOverride)), 4473 build.WithDockerfile(dockerfile), 4474 ) 4475 result.Assert(c, icmd.Success) 4476 if strings.Count(result.Combined(), envValOverride) != 1 { 4477 c.Fatalf("failed to access environment variable in output: %q expected: %q", result.Combined(), envValOverride) 4478 } 4479 4480 containerName := "bldargCont" 4481 if out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined(); !strings.Contains(out, envValOverride) { 4482 c.Fatalf("run produced invalid output: %q, expected %q", out, envValOverride) 4483 } 4484 } 4485 4486 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgUnconsumedArg(c *testing.T) { 4487 imgName := "bldargtest" 4488 envKey := "foo" 4489 envVal := "bar" 4490 dockerfile := fmt.Sprintf(`FROM busybox 4491 RUN echo $%s 4492 CMD echo $%s`, envKey, envKey) 4493 warnStr := "[Warning] One or more build-args" 4494 buildImage(imgName, 4495 cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)), 4496 build.WithDockerfile(dockerfile), 4497 ).Assert(c, icmd.Expected{ 4498 Out: warnStr, 4499 }) 4500 } 4501 4502 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgEnv(c *testing.T) { 4503 testRequires(c, DaemonIsLinux) // Windows does not support ARG 4504 dockerfile := `FROM busybox 4505 ARG FOO1=fromfile 4506 ARG FOO2=fromfile 4507 ARG FOO3=fromfile 4508 ARG FOO4=fromfile 4509 ARG FOO5 4510 ARG FOO6 4511 ARG FO10 4512 RUN env 4513 RUN [ "$FOO1" = "fromcmd" ] 4514 RUN [ "$FOO2" = "" ] 4515 RUN [ "$FOO3" = "fromenv" ] 4516 RUN [ "$FOO4" = "fromfile" ] 4517 RUN [ "$FOO5" = "fromcmd" ] 4518 # The following should not exist at all in the env 4519 RUN [ "$(env | grep FOO6)" = "" ] 4520 RUN [ "$(env | grep FOO7)" = "" ] 4521 RUN [ "$(env | grep FOO8)" = "" ] 4522 RUN [ "$(env | grep FOO9)" = "" ] 4523 RUN [ "$FO10" = "" ] 4524 ` 4525 result := buildImage("testbuildtimeargenv", 4526 cli.WithFlags( 4527 "--build-arg", "FOO1=fromcmd", 4528 "--build-arg", "FOO2=", 4529 "--build-arg", "FOO3", // set in env 4530 "--build-arg", "FOO4", // not set in env 4531 "--build-arg", "FOO5=fromcmd", 4532 // FOO6 is not set at all 4533 "--build-arg", "FOO7=fromcmd", // should produce a warning 4534 "--build-arg", "FOO8=", // should produce a warning 4535 "--build-arg", "FOO9", // should produce a warning 4536 "--build-arg", "FO10", // not set in env, empty value 4537 ), 4538 cli.WithEnvironmentVariables(append(os.Environ(), 4539 "FOO1=fromenv", 4540 "FOO2=fromenv", 4541 "FOO3=fromenv")...), 4542 build.WithBuildContext(c, 4543 build.WithFile("Dockerfile", dockerfile), 4544 ), 4545 ) 4546 result.Assert(c, icmd.Success) 4547 4548 // Now check to make sure we got a warning msg about unused build-args 4549 i := strings.Index(result.Combined(), "[Warning]") 4550 if i < 0 { 4551 c.Fatalf("Missing the build-arg warning in %q", result.Combined()) 4552 } 4553 4554 out := result.Combined()[i:] // "out" should contain just the warning message now 4555 4556 // These were specified on a --build-arg but no ARG was in the Dockerfile 4557 assert.Assert(c, strings.Contains(out, "FOO7")) 4558 assert.Assert(c, strings.Contains(out, "FOO8")) 4559 assert.Assert(c, strings.Contains(out, "FOO9")) 4560 } 4561 4562 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgQuotedValVariants(c *testing.T) { 4563 imgName := "bldargtest" 4564 envKey := "foo" 4565 envKey1 := "foo1" 4566 envKey2 := "foo2" 4567 envKey3 := "foo3" 4568 dockerfile := fmt.Sprintf(`FROM busybox 4569 ARG %s="" 4570 ARG %s='' 4571 ARG %s="''" 4572 ARG %s='""' 4573 RUN [ "$%s" != "$%s" ] 4574 RUN [ "$%s" != "$%s" ] 4575 RUN [ "$%s" != "$%s" ] 4576 RUN [ "$%s" != "$%s" ] 4577 RUN [ "$%s" != "$%s" ]`, envKey, envKey1, envKey2, envKey3, 4578 envKey, envKey2, envKey, envKey3, envKey1, envKey2, envKey1, envKey3, 4579 envKey2, envKey3) 4580 buildImageSuccessfully(c, imgName, build.WithDockerfile(dockerfile)) 4581 } 4582 4583 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgEmptyValVariants(c *testing.T) { 4584 testRequires(c, DaemonIsLinux) // Windows does not support ARG 4585 imgName := "bldargtest" 4586 envKey := "foo" 4587 envKey1 := "foo1" 4588 envKey2 := "foo2" 4589 dockerfile := fmt.Sprintf(`FROM busybox 4590 ARG %s= 4591 ARG %s="" 4592 ARG %s='' 4593 RUN [ "$%s" = "$%s" ] 4594 RUN [ "$%s" = "$%s" ] 4595 RUN [ "$%s" = "$%s" ]`, envKey, envKey1, envKey2, envKey, envKey1, envKey1, envKey2, envKey, envKey2) 4596 buildImageSuccessfully(c, imgName, build.WithDockerfile(dockerfile)) 4597 } 4598 4599 func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgDefinitionWithNoEnvInjection(c *testing.T) { 4600 imgName := "bldargtest" 4601 envKey := "foo" 4602 dockerfile := fmt.Sprintf(`FROM busybox 4603 ARG %s 4604 RUN env`, envKey) 4605 4606 result := cli.BuildCmd(c, imgName, build.WithDockerfile(dockerfile)) 4607 result.Assert(c, icmd.Success) 4608 if strings.Count(result.Combined(), envKey) != 1 { 4609 c.Fatalf("unexpected number of occurrences of the arg in output: %q expected: 1", result.Combined()) 4610 } 4611 } 4612 4613 func (s *DockerCLIBuildSuite) TestBuildMultiStageArg(c *testing.T) { 4614 imgName := "multifrombldargtest" 4615 dockerfile := `FROM busybox 4616 ARG foo=abc 4617 LABEL multifromtest=1 4618 RUN env > /out 4619 FROM busybox 4620 ARG bar=def 4621 RUN env > /out` 4622 4623 result := cli.BuildCmd(c, imgName, build.WithDockerfile(dockerfile)) 4624 result.Assert(c, icmd.Success) 4625 4626 result = cli.DockerCmd(c, "images", "-q", "-f", "label=multifromtest=1") 4627 result.Assert(c, icmd.Success) 4628 4629 imgs := strings.Split(strings.TrimSpace(result.Stdout()), "\n") 4630 assert.Assert(c, is.Len(imgs, 1), `only one image with "multifromtest" label is expected`) 4631 4632 parentID := imgs[0] 4633 4634 result = cli.DockerCmd(c, "run", "--rm", parentID, "cat", "/out") 4635 assert.Assert(c, strings.Contains(result.Stdout(), "foo=abc")) 4636 result = cli.DockerCmd(c, "run", "--rm", imgName, "cat", "/out") 4637 assert.Assert(c, !strings.Contains(result.Stdout(), "foo")) 4638 assert.Assert(c, strings.Contains(result.Stdout(), "bar=def")) 4639 } 4640 4641 func (s *DockerCLIBuildSuite) TestBuildMultiStageGlobalArg(c *testing.T) { 4642 imgName := "multifrombldargtest" 4643 dockerfile := `ARG tag=nosuchtag 4644 FROM busybox:${tag} 4645 LABEL multifromtest2=1 4646 RUN env > /out 4647 FROM busybox:${tag} 4648 ARG tag 4649 RUN env > /out` 4650 4651 result := cli.BuildCmd(c, imgName, 4652 build.WithDockerfile(dockerfile), 4653 cli.WithFlags("--build-arg", "tag=latest")) 4654 result.Assert(c, icmd.Success) 4655 4656 result = cli.DockerCmd(c, "images", "-q", "-f", "label=multifromtest2=1") 4657 result.Assert(c, icmd.Success) 4658 4659 imgs := strings.Split(strings.TrimSpace(result.Stdout()), "\n") 4660 assert.Assert(c, is.Len(imgs, 1), `only one image with "multifromtest" label is expected`) 4661 4662 parentID := imgs[0] 4663 4664 result = cli.DockerCmd(c, "run", "--rm", parentID, "cat", "/out") 4665 assert.Assert(c, !strings.Contains(result.Stdout(), "tag")) 4666 result = cli.DockerCmd(c, "run", "--rm", imgName, "cat", "/out") 4667 assert.Assert(c, strings.Contains(result.Stdout(), "tag=latest")) 4668 } 4669 4670 func (s *DockerCLIBuildSuite) TestBuildMultiStageUnusedArg(c *testing.T) { 4671 imgName := "multifromunusedarg" 4672 dockerfile := `FROM busybox 4673 ARG foo 4674 FROM busybox 4675 ARG bar 4676 RUN env > /out` 4677 4678 result := cli.BuildCmd(c, imgName, 4679 build.WithDockerfile(dockerfile), 4680 cli.WithFlags("--build-arg", "baz=abc")) 4681 result.Assert(c, icmd.Success) 4682 assert.Assert(c, strings.Contains(result.Combined(), "[Warning]")) 4683 assert.Assert(c, strings.Contains(result.Combined(), "[baz] were not consumed")) 4684 result = cli.DockerCmd(c, "run", "--rm", imgName, "cat", "/out") 4685 assert.Assert(c, !strings.Contains(result.Stdout(), "bar")) 4686 assert.Assert(c, !strings.Contains(result.Stdout(), "baz")) 4687 } 4688 4689 func (s *DockerCLIBuildSuite) TestBuildNoNamedVolume(c *testing.T) { 4690 volName := "testname:/foo" 4691 4692 if testEnv.DaemonInfo.OSType == "windows" { 4693 volName = "testname:C:\\foo" 4694 } 4695 cli.DockerCmd(c, "run", "-v", volName, "busybox", "sh", "-c", "touch /foo/oops") 4696 4697 dockerFile := `FROM busybox 4698 VOLUME ` + volName + ` 4699 RUN ls /foo/oops 4700 ` 4701 buildImage("test", build.WithDockerfile(dockerFile)).Assert(c, icmd.Expected{ 4702 ExitCode: 1, 4703 }) 4704 } 4705 4706 func (s *DockerCLIBuildSuite) TestBuildTagEvent(c *testing.T) { 4707 since := daemonUnixTime(c) 4708 4709 dockerFile := `FROM busybox 4710 RUN echo events 4711 ` 4712 buildImageSuccessfully(c, "test", build.WithDockerfile(dockerFile)) 4713 4714 until := daemonUnixTime(c) 4715 out := cli.DockerCmd(c, "events", "--since", since, "--until", until, "--filter", "type=image").Stdout() 4716 events := strings.Split(strings.TrimSpace(out), "\n") 4717 actions := eventActionsByIDAndType(c, events, "test:latest", "image") 4718 var foundTag bool 4719 for _, a := range actions { 4720 if a == "tag" { 4721 foundTag = true 4722 break 4723 } 4724 } 4725 4726 assert.Assert(c, foundTag, "No tag event found:\n%s", out) 4727 } 4728 4729 // #15780 4730 func (s *DockerCLIBuildSuite) TestBuildMultipleTags(c *testing.T) { 4731 dockerfile := ` 4732 FROM busybox 4733 MAINTAINER test-15780 4734 ` 4735 buildImageSuccessfully(c, "tag1", cli.WithFlags("-t", "tag2:v2", "-t", "tag1:latest", "-t", "tag1"), build.WithDockerfile(dockerfile)) 4736 4737 id1 := getIDByName(c, "tag1") 4738 id2 := getIDByName(c, "tag2:v2") 4739 assert.Equal(c, id1, id2) 4740 } 4741 4742 // #17290 4743 func (s *DockerCLIBuildSuite) TestBuildCacheBrokenSymlink(c *testing.T) { 4744 const name = "testbuildbrokensymlink" 4745 ctx := fakecontext.New(c, "", 4746 fakecontext.WithDockerfile(` 4747 FROM busybox 4748 COPY . ./`), 4749 fakecontext.WithFiles(map[string]string{ 4750 "foo": "bar", 4751 })) 4752 defer ctx.Close() 4753 4754 err := os.Symlink(filepath.Join(ctx.Dir, "nosuchfile"), filepath.Join(ctx.Dir, "asymlink")) 4755 assert.NilError(c, err) 4756 4757 // warm up cache 4758 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 4759 4760 // add new file to context, should invalidate cache 4761 err = os.WriteFile(filepath.Join(ctx.Dir, "newfile"), []byte("foo"), 0o644) 4762 assert.NilError(c, err) 4763 4764 result := cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 4765 if strings.Contains(result.Combined(), "Using cache") { 4766 c.Fatal("2nd build used cache on ADD, it shouldn't") 4767 } 4768 } 4769 4770 func (s *DockerCLIBuildSuite) TestBuildFollowSymlinkToFile(c *testing.T) { 4771 const name = "testbuildbrokensymlink" 4772 ctx := fakecontext.New(c, "", 4773 fakecontext.WithDockerfile(` 4774 FROM busybox 4775 COPY asymlink target`), 4776 fakecontext.WithFiles(map[string]string{ 4777 "foo": "bar", 4778 })) 4779 defer ctx.Close() 4780 4781 err := os.Symlink("foo", filepath.Join(ctx.Dir, "asymlink")) 4782 assert.NilError(c, err) 4783 4784 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 4785 4786 out := cli.DockerCmd(c, "run", "--rm", name, "cat", "target").Combined() 4787 assert.Assert(c, is.Regexp("^bar$", out)) 4788 4789 // change target file should invalidate cache 4790 err = os.WriteFile(filepath.Join(ctx.Dir, "foo"), []byte("baz"), 0o644) 4791 assert.NilError(c, err) 4792 4793 result := cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 4794 assert.Assert(c, !strings.Contains(result.Combined(), "Using cache")) 4795 out = cli.DockerCmd(c, "run", "--rm", name, "cat", "target").Combined() 4796 assert.Assert(c, is.Regexp("^baz$", out)) 4797 } 4798 4799 func (s *DockerCLIBuildSuite) TestBuildFollowSymlinkToDir(c *testing.T) { 4800 const name = "testbuildbrokensymlink" 4801 ctx := fakecontext.New(c, "", 4802 fakecontext.WithDockerfile(` 4803 FROM busybox 4804 COPY asymlink /`), 4805 fakecontext.WithFiles(map[string]string{ 4806 "foo/abc": "bar", 4807 "foo/def": "baz", 4808 })) 4809 defer ctx.Close() 4810 4811 err := os.Symlink("foo", filepath.Join(ctx.Dir, "asymlink")) 4812 assert.NilError(c, err) 4813 4814 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 4815 4816 out := cli.DockerCmd(c, "run", "--rm", name, "cat", "abc", "def").Combined() 4817 assert.Assert(c, is.Regexp("^barbaz$", out)) 4818 4819 // change target file should invalidate cache 4820 err = os.WriteFile(filepath.Join(ctx.Dir, "foo/def"), []byte("bax"), 0o644) 4821 assert.NilError(c, err) 4822 4823 result := cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 4824 assert.Assert(c, !strings.Contains(result.Combined(), "Using cache")) 4825 out = cli.DockerCmd(c, "run", "--rm", name, "cat", "abc", "def").Combined() 4826 assert.Assert(c, is.Regexp("^barbax$", out)) 4827 } 4828 4829 // TestBuildSymlinkBasename tests that target file gets basename from symlink, 4830 // not from the target file. 4831 func (s *DockerCLIBuildSuite) TestBuildSymlinkBasename(c *testing.T) { 4832 const name = "testbuildbrokensymlink" 4833 ctx := fakecontext.New(c, "", 4834 fakecontext.WithDockerfile(` 4835 FROM busybox 4836 COPY asymlink /`), 4837 fakecontext.WithFiles(map[string]string{ 4838 "foo": "bar", 4839 })) 4840 defer ctx.Close() 4841 4842 err := os.Symlink("foo", filepath.Join(ctx.Dir, "asymlink")) 4843 assert.NilError(c, err) 4844 4845 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 4846 4847 out := cli.DockerCmd(c, "run", "--rm", name, "cat", "asymlink").Combined() 4848 assert.Assert(c, is.Regexp("^bar$", out)) 4849 } 4850 4851 // #17827 4852 func (s *DockerCLIBuildSuite) TestBuildCacheRootSource(c *testing.T) { 4853 const name = "testbuildrootsource" 4854 ctx := fakecontext.New(c, "", 4855 fakecontext.WithDockerfile(` 4856 FROM busybox 4857 COPY / /data`), 4858 fakecontext.WithFiles(map[string]string{ 4859 "foo": "bar", 4860 })) 4861 defer ctx.Close() 4862 4863 // warm up cache 4864 cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 4865 4866 // change file, should invalidate cache 4867 err := os.WriteFile(filepath.Join(ctx.Dir, "foo"), []byte("baz"), 0o644) 4868 assert.NilError(c, err) 4869 4870 result := cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx)) 4871 4872 assert.Assert(c, !strings.Contains(result.Combined(), "Using cache")) 4873 } 4874 4875 // #19375 4876 // FIXME(vdemeester) should migrate to docker/cli tests 4877 func (s *DockerCLIBuildSuite) TestBuildFailsGitNotCallable(c *testing.T) { 4878 buildImage("gitnotcallable", cli.WithEnvironmentVariables("PATH="), 4879 build.WithContextPath("github.com/docker/v1.10-migrator.git")).Assert(c, icmd.Expected{ 4880 ExitCode: 1, 4881 Err: "unable to prepare context: unable to find 'git': ", 4882 }) 4883 4884 buildImage("gitnotcallable", cli.WithEnvironmentVariables("PATH="), 4885 build.WithContextPath("https://github.com/docker/v1.10-migrator.git")).Assert(c, icmd.Expected{ 4886 ExitCode: 1, 4887 Err: "unable to prepare context: unable to find 'git': ", 4888 }) 4889 } 4890 4891 // TestBuildWorkdirWindowsPath tests that a Windows style path works as a workdir 4892 func (s *DockerCLIBuildSuite) TestBuildWorkdirWindowsPath(c *testing.T) { 4893 testRequires(c, DaemonIsWindows) 4894 const name = "testbuildworkdirwindowspath" 4895 buildImageSuccessfully(c, name, build.WithDockerfile(` 4896 FROM `+testEnv.PlatformDefaults.BaseImage+` 4897 RUN mkdir C:\\work 4898 WORKDIR C:\\work 4899 RUN if "%CD%" NEQ "C:\work" exit -1 4900 `)) 4901 } 4902 4903 func (s *DockerCLIBuildSuite) TestBuildLabel(c *testing.T) { 4904 const name = "testbuildlabel" 4905 testLabel := "foo" 4906 4907 buildImageSuccessfully(c, name, cli.WithFlags("--label", testLabel), 4908 build.WithDockerfile(` 4909 FROM `+minimalBaseImage()+` 4910 LABEL default foo 4911 `)) 4912 4913 var labels map[string]string 4914 inspectFieldAndUnmarshall(c, name, "Config.Labels", &labels) 4915 if _, ok := labels[testLabel]; !ok { 4916 c.Fatal("label not found in image") 4917 } 4918 } 4919 4920 func (s *DockerCLIBuildSuite) TestBuildLabelOneNode(c *testing.T) { 4921 const name = "testbuildlabel" 4922 buildImageSuccessfully(c, name, cli.WithFlags("--label", "foo=bar"), 4923 build.WithDockerfile("FROM busybox")) 4924 4925 var labels map[string]string 4926 inspectFieldAndUnmarshall(c, name, "Config.Labels", &labels) 4927 v, ok := labels["foo"] 4928 if !ok { 4929 c.Fatal("label `foo` not found in image") 4930 } 4931 assert.Equal(c, v, "bar") 4932 } 4933 4934 func (s *DockerCLIBuildSuite) TestBuildLabelCacheCommit(c *testing.T) { 4935 const name = "testbuildlabelcachecommit" 4936 testLabel := "foo" 4937 4938 buildImageSuccessfully(c, name, build.WithDockerfile(` 4939 FROM `+minimalBaseImage()+` 4940 LABEL default foo 4941 `)) 4942 buildImageSuccessfully(c, name, cli.WithFlags("--label", testLabel), 4943 build.WithDockerfile(` 4944 FROM `+minimalBaseImage()+` 4945 LABEL default foo 4946 `)) 4947 4948 var labels map[string]string 4949 inspectFieldAndUnmarshall(c, name, "Config.Labels", &labels) 4950 if _, ok := labels[testLabel]; !ok { 4951 c.Fatal("label not found in image") 4952 } 4953 } 4954 4955 func (s *DockerCLIBuildSuite) TestBuildLabelMultiple(c *testing.T) { 4956 const name = "testbuildlabelmultiple" 4957 testLabels := map[string]string{ 4958 "foo": "bar", 4959 "123": "456", 4960 } 4961 var labelArgs []string 4962 for k, v := range testLabels { 4963 labelArgs = append(labelArgs, "--label", k+"="+v) 4964 } 4965 4966 buildImageSuccessfully(c, name, cli.WithFlags(labelArgs...), 4967 build.WithDockerfile(` 4968 FROM `+minimalBaseImage()+` 4969 LABEL default foo 4970 `)) 4971 4972 var labels map[string]string 4973 inspectFieldAndUnmarshall(c, name, "Config.Labels", &labels) 4974 for k, v := range testLabels { 4975 if x, ok := labels[k]; !ok || x != v { 4976 c.Fatalf("label %s=%s not found in image", k, v) 4977 } 4978 } 4979 } 4980 4981 func (s *DockerRegistryAuthHtpasswdSuite) TestBuildFromAuthenticatedRegistry(c *testing.T) { 4982 cli.DockerCmd(c, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), privateRegistryURL) 4983 baseImage := privateRegistryURL + "/baseimage" 4984 4985 buildImageSuccessfully(c, baseImage, build.WithDockerfile(` 4986 FROM busybox 4987 ENV env1 val1 4988 `)) 4989 4990 cli.DockerCmd(c, "push", baseImage) 4991 cli.DockerCmd(c, "rmi", baseImage) 4992 4993 buildImageSuccessfully(c, baseImage, build.WithDockerfile(fmt.Sprintf(` 4994 FROM %s 4995 ENV env2 val2 4996 `, baseImage))) 4997 } 4998 4999 func (s *DockerRegistryAuthHtpasswdSuite) TestBuildWithExternalAuth(c *testing.T) { 5000 workingDir, err := os.Getwd() 5001 assert.NilError(c, err) 5002 absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth")) 5003 assert.NilError(c, err) 5004 5005 osPath := os.Getenv("PATH") 5006 testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute) 5007 c.Setenv("PATH", testPath) 5008 5009 repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL) 5010 5011 tmp, err := os.MkdirTemp("", "integration-cli-") 5012 assert.NilError(c, err) 5013 5014 externalAuthConfig := `{ "credsStore": "shell-test" }` 5015 5016 configPath := filepath.Join(tmp, "config.json") 5017 err = os.WriteFile(configPath, []byte(externalAuthConfig), 0o644) 5018 assert.NilError(c, err) 5019 5020 cli.DockerCmd(c, "--config", tmp, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), privateRegistryURL) 5021 5022 b, err := os.ReadFile(configPath) 5023 assert.NilError(c, err) 5024 assert.Assert(c, !strings.Contains(string(b), "\"auth\":")) 5025 cli.DockerCmd(c, "--config", tmp, "tag", "busybox", repoName) 5026 cli.DockerCmd(c, "--config", tmp, "push", repoName) 5027 5028 // make sure the image is pulled when building 5029 cli.DockerCmd(c, "rmi", repoName) 5030 5031 icmd.RunCmd(icmd.Cmd{ 5032 Command: []string{dockerBinary, "--config", tmp, "build", "-"}, 5033 Stdin: strings.NewReader(fmt.Sprintf("FROM %s", repoName)), 5034 }).Assert(c, icmd.Success) 5035 } 5036 5037 // Test cases in #22036 5038 func (s *DockerCLIBuildSuite) TestBuildLabelsOverride(c *testing.T) { 5039 // Command line option labels will always override 5040 name := "scratchy" 5041 expected := `{"bar":"from-flag","foo":"from-flag"}` 5042 buildImageSuccessfully(c, name, cli.WithFlags("--label", "foo=from-flag", "--label", "bar=from-flag"), 5043 build.WithDockerfile(`FROM `+minimalBaseImage()+` 5044 LABEL foo=from-dockerfile`)) 5045 res := inspectFieldJSON(c, name, "Config.Labels") 5046 if res != expected { 5047 c.Fatalf("Labels %s, expected %s", res, expected) 5048 } 5049 5050 name = "from" 5051 expected = `{"foo":"from-dockerfile"}` 5052 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+` 5053 LABEL foo from-dockerfile`)) 5054 res = inspectFieldJSON(c, name, "Config.Labels") 5055 if res != expected { 5056 c.Fatalf("Labels %s, expected %s", res, expected) 5057 } 5058 5059 // Command line option label will override even via `FROM` 5060 name = "new" 5061 expected = `{"bar":"from-dockerfile2","foo":"new"}` 5062 buildImageSuccessfully(c, name, cli.WithFlags("--label", "foo=new"), 5063 build.WithDockerfile(`FROM from 5064 LABEL bar from-dockerfile2`)) 5065 res = inspectFieldJSON(c, name, "Config.Labels") 5066 if res != expected { 5067 c.Fatalf("Labels %s, expected %s", res, expected) 5068 } 5069 5070 // Command line option without a value set (--label foo, --label bar=) 5071 // will be treated as --label foo="", --label bar="" 5072 name = "scratchy2" 5073 expected = `{"bar":"","foo":""}` 5074 buildImageSuccessfully(c, name, cli.WithFlags("--label", "foo", "--label", "bar="), 5075 build.WithDockerfile(`FROM `+minimalBaseImage()+` 5076 LABEL foo=from-dockerfile`)) 5077 res = inspectFieldJSON(c, name, "Config.Labels") 5078 if res != expected { 5079 c.Fatalf("Labels %s, expected %s", res, expected) 5080 } 5081 5082 // Command line option without a value set (--label foo, --label bar=) 5083 // will be treated as --label foo="", --label bar="" 5084 // This time is for inherited images 5085 name = "new2" 5086 expected = `{"bar":"","foo":""}` 5087 buildImageSuccessfully(c, name, cli.WithFlags("--label", "foo=", "--label", "bar"), 5088 build.WithDockerfile(`FROM from 5089 LABEL bar from-dockerfile2`)) 5090 res = inspectFieldJSON(c, name, "Config.Labels") 5091 if res != expected { 5092 c.Fatalf("Labels %s, expected %s", res, expected) 5093 } 5094 5095 // Command line option labels with only `FROM` 5096 name = "scratchy" 5097 expected = `{"bar":"from-flag","foo":"from-flag"}` 5098 buildImageSuccessfully(c, name, cli.WithFlags("--label", "foo=from-flag", "--label", "bar=from-flag"), 5099 build.WithDockerfile(`FROM `+minimalBaseImage())) 5100 res = inspectFieldJSON(c, name, "Config.Labels") 5101 if res != expected { 5102 c.Fatalf("Labels %s, expected %s", res, expected) 5103 } 5104 5105 // Command line option labels with env var 5106 name = "scratchz" 5107 expected = `{"bar":"$PATH"}` 5108 buildImageSuccessfully(c, name, cli.WithFlags("--label", "bar=$PATH"), 5109 build.WithDockerfile(`FROM `+minimalBaseImage())) 5110 res = inspectFieldJSON(c, name, "Config.Labels") 5111 if res != expected { 5112 c.Fatalf("Labels %s, expected %s", res, expected) 5113 } 5114 } 5115 5116 // Test case for #22855 5117 func (s *DockerCLIBuildSuite) TestBuildDeleteCommittedFile(c *testing.T) { 5118 const name = "test-delete-committed-file" 5119 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 5120 RUN echo test > file 5121 RUN test -e file 5122 RUN rm file 5123 RUN sh -c "! test -e file"`)) 5124 } 5125 5126 // #20083 5127 func (s *DockerCLIBuildSuite) TestBuildDockerignoreComment(c *testing.T) { 5128 // TODO Windows: Figure out why this test is flakey on TP5. If you add 5129 // something like RUN sleep 5, or even RUN ls /tmp after the ADD line, 5130 // it is more reliable, but that's not a good fix. 5131 testRequires(c, DaemonIsLinux) 5132 5133 const name = "testbuilddockerignorecleanpaths" 5134 dockerfile := ` 5135 FROM busybox 5136 ADD . /tmp/ 5137 RUN sh -c "(ls -la /tmp/#1)" 5138 RUN sh -c "(! ls -la /tmp/#2)" 5139 RUN sh -c "(! ls /tmp/foo) && (! ls /tmp/foo2) && (ls /tmp/dir1/foo)"` 5140 buildImageSuccessfully(c, name, build.WithBuildContext(c, 5141 build.WithFile("Dockerfile", dockerfile), 5142 build.WithFile("foo", "foo"), 5143 build.WithFile("foo2", "foo2"), 5144 build.WithFile("dir1/foo", "foo in dir1"), 5145 build.WithFile("#1", "# file 1"), 5146 build.WithFile("#2", "# file 2"), 5147 build.WithFile(".dockerignore", `# Visual C++ cache files 5148 # because we have git ;-) 5149 # The above comment is from #20083 5150 foo 5151 #dir1/foo 5152 foo2 5153 # The following is considered as comment as # is at the beginning 5154 #1 5155 # The following is not considered as comment as # is not at the beginning 5156 #2 5157 `))) 5158 } 5159 5160 // Test case for #23221 5161 func (s *DockerCLIBuildSuite) TestBuildWithUTF8BOM(c *testing.T) { 5162 const name = "test-with-utf8-bom" 5163 dockerfile := []byte(`FROM busybox`) 5164 bomDockerfile := append([]byte{0xEF, 0xBB, 0xBF}, dockerfile...) 5165 buildImageSuccessfully(c, name, build.WithBuildContext(c, 5166 build.WithFile("Dockerfile", string(bomDockerfile)), 5167 )) 5168 } 5169 5170 // Test case for UTF-8 BOM in .dockerignore, related to #23221 5171 func (s *DockerCLIBuildSuite) TestBuildWithUTF8BOMDockerignore(c *testing.T) { 5172 const name = "test-with-utf8-bom-dockerignore" 5173 dockerfile := ` 5174 FROM busybox 5175 ADD . /tmp/ 5176 RUN ls -la /tmp 5177 RUN sh -c "! ls /tmp/Dockerfile" 5178 RUN ls /tmp/.dockerignore` 5179 dockerignore := []byte("./Dockerfile\n") 5180 bomDockerignore := append([]byte{0xEF, 0xBB, 0xBF}, dockerignore...) 5181 buildImageSuccessfully(c, name, build.WithBuildContext(c, 5182 build.WithFile("Dockerfile", dockerfile), 5183 build.WithFile(".dockerignore", string(bomDockerignore)), 5184 )) 5185 } 5186 5187 // #22489 Shell test to confirm config gets updated correctly 5188 func (s *DockerCLIBuildSuite) TestBuildShellUpdatesConfig(c *testing.T) { 5189 skip.If(c, versions.GreaterThan(testEnv.DaemonAPIVersion(), "1.44"), "ContainerConfig is deprecated") 5190 skip.If(c, testEnv.UsingSnapshotter, "ContainerConfig is not filled in c8d") 5191 5192 const name = "testbuildshellupdatesconfig" 5193 5194 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+` 5195 SHELL ["foo", "-bar"]`)) 5196 expected := `["foo","-bar","#(nop) ","SHELL [foo -bar]"]` 5197 res := inspectFieldJSON(c, name, "ContainerConfig.Cmd") 5198 if res != expected { 5199 c.Fatalf("%s, expected %s", res, expected) 5200 } 5201 res = inspectFieldJSON(c, name, "ContainerConfig.Shell") 5202 if res != `["foo","-bar"]` { 5203 c.Fatalf(`%s, expected ["foo","-bar"]`, res) 5204 } 5205 } 5206 5207 // #22489 Changing the shell multiple times and CMD after. 5208 func (s *DockerCLIBuildSuite) TestBuildShellMultiple(c *testing.T) { 5209 const name = "testbuildshellmultiple" 5210 5211 result := buildImage(name, build.WithDockerfile(`FROM busybox 5212 RUN echo defaultshell 5213 SHELL ["echo"] 5214 RUN echoshell 5215 SHELL ["ls"] 5216 RUN -l 5217 CMD -l`)) 5218 result.Assert(c, icmd.Success) 5219 5220 // Must contain 'defaultshell' twice 5221 if len(strings.Split(result.Combined(), "defaultshell")) != 3 { 5222 c.Fatalf("defaultshell should have appeared twice in %s", result.Combined()) 5223 } 5224 5225 // Must contain 'echoshell' twice 5226 if len(strings.Split(result.Combined(), "echoshell")) != 3 { 5227 c.Fatalf("echoshell should have appeared twice in %s", result.Combined()) 5228 } 5229 5230 // Must contain "total " (part of ls -l) 5231 if !strings.Contains(result.Combined(), "total ") { 5232 c.Fatalf("%s should have contained 'total '", result.Combined()) 5233 } 5234 5235 // A container started from the image uses the shell-form CMD. 5236 // Last shell is ls. CMD is -l. So should contain 'total '. 5237 outrun := cli.DockerCmd(c, "run", "--rm", name).Combined() 5238 if !strings.Contains(outrun, "total ") { 5239 c.Fatalf("Expected started container to run ls -l. %s", outrun) 5240 } 5241 } 5242 5243 // #22489. Changed SHELL with ENTRYPOINT 5244 func (s *DockerCLIBuildSuite) TestBuildShellEntrypoint(c *testing.T) { 5245 const name = "testbuildshellentrypoint" 5246 5247 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 5248 SHELL ["ls"] 5249 ENTRYPOINT -l`)) 5250 // A container started from the image uses the shell-form ENTRYPOINT. 5251 // Shell is ls. ENTRYPOINT is -l. So should contain 'total '. 5252 outrun := cli.DockerCmd(c, "run", "--rm", name).Combined() 5253 if !strings.Contains(outrun, "total ") { 5254 c.Fatalf("Expected started container to run ls -l. %s", outrun) 5255 } 5256 } 5257 5258 // #22489 Shell test to confirm shell is inherited in a subsequent build 5259 func (s *DockerCLIBuildSuite) TestBuildShellInherited(c *testing.T) { 5260 const name1 = "testbuildshellinherited1" 5261 buildImageSuccessfully(c, name1, build.WithDockerfile(`FROM busybox 5262 SHELL ["ls"]`)) 5263 const name2 = "testbuildshellinherited2" 5264 buildImage(name2, build.WithDockerfile(`FROM `+name1+` 5265 RUN -l`)).Assert(c, icmd.Expected{ 5266 // ls -l has "total " followed by some number in it, ls without -l does not. 5267 Out: "total ", 5268 }) 5269 } 5270 5271 // #22489 Shell test to confirm non-JSON doesn't work 5272 func (s *DockerCLIBuildSuite) TestBuildShellNotJSON(c *testing.T) { 5273 const name = "testbuildshellnotjson" 5274 5275 buildImage(name, build.WithDockerfile(`FROM `+minimalBaseImage()+` 5276 sHeLl exec -form`, // Casing explicit to ensure error is upper-cased. 5277 )).Assert(c, icmd.Expected{ 5278 ExitCode: 1, 5279 Err: "SHELL requires the arguments to be in JSON form", 5280 }) 5281 } 5282 5283 // #22489 Windows shell test to confirm native is powershell if executing a PS command 5284 // This would error if the default shell were still cmd. 5285 func (s *DockerCLIBuildSuite) TestBuildShellWindowsPowershell(c *testing.T) { 5286 testRequires(c, DaemonIsWindows) 5287 const name = "testbuildshellpowershell" 5288 buildImage(name, build.WithDockerfile(`FROM `+minimalBaseImage()+` 5289 SHELL ["powershell", "-command"] 5290 RUN Write-Host John`)).Assert(c, icmd.Expected{ 5291 Out: "\nJohn\n", 5292 }) 5293 } 5294 5295 // Verify that escape is being correctly applied to words when escape directive is not \. 5296 // Tests WORKDIR, ADD 5297 func (s *DockerCLIBuildSuite) TestBuildEscapeNotBackslashWordTest(c *testing.T) { 5298 testRequires(c, DaemonIsWindows) 5299 const name1 = "testbuildescapenotbackslashwordtesta" 5300 buildImage(name1, build.WithDockerfile(`# escape= `+"`"+` 5301 FROM `+minimalBaseImage()+` 5302 WORKDIR c:\windows 5303 RUN dir /w`)).Assert(c, icmd.Expected{ 5304 Out: "[System32]", 5305 }) 5306 5307 const name2 = "testbuildescapenotbackslashwordtestb" 5308 buildImage(name2, build.WithDockerfile(`# escape= `+"`"+` 5309 FROM `+minimalBaseImage()+` 5310 SHELL ["powershell.exe"] 5311 WORKDIR c:\foo 5312 ADD Dockerfile c:\foo\ 5313 RUN dir Dockerfile`)).Assert(c, icmd.Expected{ 5314 Out: "-a----", 5315 }) 5316 } 5317 5318 // #22868. Make sure shell-form CMD is not marked as escaped in the config of the image, 5319 // but an exec-form CMD is marked. 5320 func (s *DockerCLIBuildSuite) TestBuildCmdShellArgsEscaped(c *testing.T) { 5321 testRequires(c, DaemonIsWindows) 5322 const name1 = "testbuildcmdshellescapedshellform" 5323 buildImageSuccessfully(c, name1, build.WithDockerfile(` 5324 FROM `+minimalBaseImage()+` 5325 CMD "ipconfig" 5326 `)) 5327 res := inspectFieldJSON(c, name1, "Config.ArgsEscaped") 5328 if res != "true" { 5329 c.Fatalf("CMD did not update Config.ArgsEscaped on image: %v", res) 5330 } 5331 cli.DockerCmd(c, "run", "--name", "inspectme1", name1) 5332 cli.DockerCmd(c, "wait", "inspectme1") 5333 res = inspectFieldJSON(c, name1, "Config.Cmd") 5334 5335 if res != `["cmd /S /C \"ipconfig\""]` { 5336 c.Fatalf("CMD incorrect in Config.Cmd: got %v", res) 5337 } 5338 5339 // Now in JSON/exec-form 5340 const name2 = "testbuildcmdshellescapedexecform" 5341 buildImageSuccessfully(c, name2, build.WithDockerfile(` 5342 FROM `+minimalBaseImage()+` 5343 CMD ["ipconfig"] 5344 `)) 5345 res = inspectFieldJSON(c, name2, "Config.ArgsEscaped") 5346 if res != "false" { 5347 c.Fatalf("CMD set Config.ArgsEscaped on image: %v", res) 5348 } 5349 cli.DockerCmd(c, "run", "--name", "inspectme2", name2) 5350 cli.DockerCmd(c, "wait", "inspectme2") 5351 res = inspectFieldJSON(c, name2, "Config.Cmd") 5352 5353 if res != `["ipconfig"]` { 5354 c.Fatalf("CMD incorrect in Config.Cmd: got %v", res) 5355 } 5356 } 5357 5358 // Test case for #24912. 5359 func (s *DockerCLIBuildSuite) TestBuildStepsWithProgress(c *testing.T) { 5360 const name = "testbuildstepswithprogress" 5361 totalRun := 5 5362 result := buildImage(name, build.WithDockerfile("FROM busybox\n"+strings.Repeat("RUN echo foo\n", totalRun))) 5363 result.Assert(c, icmd.Success) 5364 assert.Assert(c, strings.Contains(result.Combined(), fmt.Sprintf("Step 1/%d : FROM busybox", 1+totalRun))) 5365 for i := 2; i <= 1+totalRun; i++ { 5366 assert.Assert(c, strings.Contains(result.Combined(), fmt.Sprintf("Step %d/%d : RUN echo foo", i, 1+totalRun))) 5367 } 5368 } 5369 5370 func (s *DockerCLIBuildSuite) TestBuildWithFailure(c *testing.T) { 5371 const name = "testbuildwithfailure" 5372 5373 // First test case can only detect `nobody` in runtime so all steps will show up 5374 dockerfile := "FROM busybox\nRUN nobody" 5375 result := buildImage(name, build.WithDockerfile(dockerfile)) 5376 assert.Assert(c, result.Error != nil) 5377 assert.Assert(c, strings.Contains(result.Stdout(), "Step 1/2 : FROM busybox")) 5378 assert.Assert(c, strings.Contains(result.Stdout(), "Step 2/2 : RUN nobody")) 5379 // Second test case `FFOM` should have been detected before build runs so no steps 5380 dockerfile = "FFOM nobody\nRUN nobody" 5381 result = buildImage(name, build.WithDockerfile(dockerfile)) 5382 assert.Assert(c, result.Error != nil) 5383 assert.Assert(c, !strings.Contains(result.Stdout(), "Step 1/2 : FROM busybox")) 5384 assert.Assert(c, !strings.Contains(result.Stdout(), "Step 2/2 : RUN nobody")) 5385 } 5386 5387 func (s *DockerCLIBuildSuite) TestBuildCacheFromEqualDiffIDsLength(c *testing.T) { 5388 dockerfile := ` 5389 FROM busybox 5390 RUN echo "test" 5391 ENTRYPOINT ["sh"]` 5392 ctx := fakecontext.New(c, "", 5393 fakecontext.WithDockerfile(dockerfile), 5394 fakecontext.WithFiles(map[string]string{ 5395 "Dockerfile": dockerfile, 5396 })) 5397 defer ctx.Close() 5398 5399 cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx)) 5400 id1 := getIDByName(c, "build1") 5401 5402 // rebuild with cache-from 5403 result := cli.BuildCmd(c, "build2", cli.WithFlags("--cache-from=build1"), build.WithExternalBuildContext(ctx)) 5404 id2 := getIDByName(c, "build2") 5405 assert.Equal(c, id1, id2) 5406 assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 2) 5407 } 5408 5409 func (s *DockerCLIBuildSuite) TestBuildCacheFrom(c *testing.T) { 5410 testRequires(c, DaemonIsLinux) // All tests that do save are skipped in windows 5411 dockerfile := ` 5412 FROM busybox 5413 ENV FOO=bar 5414 ADD baz / 5415 RUN touch bax` 5416 ctx := fakecontext.New(c, "", 5417 fakecontext.WithDockerfile(dockerfile), 5418 fakecontext.WithFiles(map[string]string{ 5419 "Dockerfile": dockerfile, 5420 "baz": "baz", 5421 })) 5422 defer ctx.Close() 5423 5424 cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx)) 5425 id1 := getIDByName(c, "build1") 5426 5427 // rebuild with cache-from 5428 result := cli.BuildCmd(c, "build2", cli.WithFlags("--cache-from=build1"), build.WithExternalBuildContext(ctx)) 5429 id2 := getIDByName(c, "build2") 5430 assert.Equal(c, id1, id2) 5431 assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 3) 5432 cli.DockerCmd(c, "rmi", "build2") 5433 5434 // no cache match with unknown source 5435 result = cli.BuildCmd(c, "build2", cli.WithFlags("--cache-from=nosuchtag"), build.WithExternalBuildContext(ctx)) 5436 id2 = getIDByName(c, "build2") 5437 assert.Assert(c, id1 != id2) 5438 assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 0) 5439 cli.DockerCmd(c, "rmi", "build2") 5440 5441 // Modify file, everything up to last command and layers are reused 5442 dockerfile = ` 5443 FROM busybox 5444 ENV FOO=bar 5445 ADD baz / 5446 RUN touch newfile` 5447 err := os.WriteFile(filepath.Join(ctx.Dir, "Dockerfile"), []byte(dockerfile), 0o644) 5448 assert.NilError(c, err) 5449 5450 result = cli.BuildCmd(c, "build2", cli.WithFlags("--cache-from=build1"), build.WithExternalBuildContext(ctx)) 5451 id2 = getIDByName(c, "build2") 5452 assert.Assert(c, id1 != id2) 5453 assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 2) 5454 5455 layers1Str := cli.DockerCmd(c, "inspect", "-f", "{{json .RootFS.Layers}}", "build1").Combined() 5456 layers2Str := cli.DockerCmd(c, "inspect", "-f", "{{json .RootFS.Layers}}", "build2").Combined() 5457 5458 var layers1 []string 5459 var layers2 []string 5460 assert.Assert(c, json.Unmarshal([]byte(layers1Str), &layers1) == nil) 5461 assert.Assert(c, json.Unmarshal([]byte(layers2Str), &layers2) == nil) 5462 5463 assert.Equal(c, len(layers1), len(layers2)) 5464 for i := 0; i < len(layers1)-1; i++ { 5465 assert.Equal(c, layers1[i], layers2[i]) 5466 } 5467 assert.Assert(c, layers1[len(layers1)-1] != layers2[len(layers1)-1]) 5468 } 5469 5470 func (s *DockerCLIBuildSuite) TestBuildCacheFromLoad(c *testing.T) { 5471 skip.If(c, testEnv.UsingSnapshotter, "Parent-child relations are lost when save/load-ing with the containerd image store") 5472 testRequires(c, DaemonIsLinux) // All tests that do save are skipped in windows 5473 dockerfile := ` 5474 FROM busybox 5475 ENV FOO=bar 5476 ADD baz / 5477 RUN touch bax` 5478 ctx := fakecontext.New(c, "", 5479 fakecontext.WithDockerfile(dockerfile), 5480 fakecontext.WithFiles(map[string]string{ 5481 "Dockerfile": dockerfile, 5482 "baz": "baz", 5483 })) 5484 defer ctx.Close() 5485 5486 cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx)) 5487 id1 := getIDByName(c, "build1") 5488 5489 // clear parent images 5490 tempDir, err := os.MkdirTemp("", "test-build-cache-from-") 5491 if err != nil { 5492 c.Fatalf("failed to create temporary directory: %s", tempDir) 5493 } 5494 defer os.RemoveAll(tempDir) 5495 tempFile := filepath.Join(tempDir, "img.tar") 5496 cli.DockerCmd(c, "save", "-o", tempFile, "build1") 5497 cli.DockerCmd(c, "rmi", "build1") 5498 cli.DockerCmd(c, "load", "-i", tempFile) 5499 parentID := cli.DockerCmd(c, "inspect", "-f", "{{.Parent}}", "build1").Combined() 5500 assert.Equal(c, strings.TrimSpace(parentID), "") 5501 5502 // cache still applies without parents 5503 result := cli.BuildCmd(c, "build2", cli.WithFlags("--cache-from=build1"), build.WithExternalBuildContext(ctx)) 5504 id2 := getIDByName(c, "build2") 5505 assert.Equal(c, id1, id2) 5506 assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 3) 5507 history1 := cli.DockerCmd(c, "history", "-q", "build2").Combined() 5508 // Retry, no new intermediate images 5509 result = cli.BuildCmd(c, "build3", cli.WithFlags("--cache-from=build1"), build.WithExternalBuildContext(ctx)) 5510 id3 := getIDByName(c, "build3") 5511 assert.Equal(c, id1, id3) 5512 assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 3) 5513 history2 := cli.DockerCmd(c, "history", "-q", "build3").Combined() 5514 5515 assert.Equal(c, history1, history2) 5516 cli.DockerCmd(c, "rmi", "build2") 5517 cli.DockerCmd(c, "rmi", "build3") 5518 cli.DockerCmd(c, "rmi", "build1") 5519 cli.DockerCmd(c, "load", "-i", tempFile) 5520 } 5521 5522 func (s *DockerCLIBuildSuite) TestBuildMultiStageCache(c *testing.T) { 5523 testRequires(c, DaemonIsLinux) // All tests that do save are skipped in windows 5524 dockerfile := ` 5525 FROM busybox 5526 ADD baz / 5527 FROM busybox 5528 ADD baz /` 5529 ctx := fakecontext.New(c, "", 5530 fakecontext.WithDockerfile(dockerfile), 5531 fakecontext.WithFiles(map[string]string{ 5532 "Dockerfile": dockerfile, 5533 "baz": "baz", 5534 })) 5535 defer ctx.Close() 5536 5537 result := cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx)) 5538 // second part of dockerfile was a repeat of first so should be cached 5539 assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 1) 5540 5541 result = cli.BuildCmd(c, "build2", cli.WithFlags("--cache-from=build1"), build.WithExternalBuildContext(ctx)) 5542 // now both parts of dockerfile should be cached 5543 assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 2) 5544 } 5545 5546 func (s *DockerCLIBuildSuite) TestBuildNetNone(c *testing.T) { 5547 testRequires(c, DaemonIsLinux) 5548 const name = "testbuildnetnone" 5549 buildImage(name, cli.WithFlags("--network=none"), build.WithDockerfile(` 5550 FROM busybox 5551 RUN ping -c 1 8.8.8.8 5552 `)).Assert(c, icmd.Expected{ 5553 ExitCode: 1, 5554 Out: "unreachable", 5555 }) 5556 } 5557 5558 func (s *DockerCLIBuildSuite) TestBuildNetContainer(c *testing.T) { 5559 testRequires(c, DaemonIsLinux) 5560 5561 id := cli.DockerCmd(c, "run", "--hostname", "foobar", "-d", "busybox", "nc", "-ll", "-p", "1234", "-e", "hostname").Stdout() 5562 5563 const name = "testbuildnetcontainer" 5564 buildImageSuccessfully(c, name, cli.WithFlags("--network=container:"+strings.TrimSpace(id)), 5565 build.WithDockerfile(` 5566 FROM busybox 5567 RUN nc localhost 1234 > /otherhost 5568 `)) 5569 5570 host := cli.DockerCmd(c, "run", "testbuildnetcontainer", "cat", "/otherhost").Combined() 5571 assert.Equal(c, strings.TrimSpace(host), "foobar") 5572 } 5573 5574 func (s *DockerCLIBuildSuite) TestBuildWithExtraHost(c *testing.T) { 5575 testRequires(c, DaemonIsLinux) 5576 5577 const name = "testbuildwithextrahost" 5578 buildImageSuccessfully(c, name, 5579 cli.WithFlags( 5580 "--add-host", "foo:127.0.0.1", 5581 "--add-host", "bar:127.0.0.1", 5582 ), 5583 build.WithDockerfile(` 5584 FROM busybox 5585 RUN ping -c 1 foo 5586 RUN ping -c 1 bar 5587 `)) 5588 } 5589 5590 func (s *DockerCLIBuildSuite) TestBuildWithExtraHostInvalidFormat(c *testing.T) { 5591 testRequires(c, DaemonIsLinux) 5592 dockerfile := ` 5593 FROM busybox 5594 RUN ping -c 1 foo` 5595 5596 testCases := []struct { 5597 testName string 5598 dockerfile string 5599 buildFlag string 5600 }{ 5601 {"extra_host_missing_ip", dockerfile, "--add-host=foo"}, 5602 {"extra_host_missing_ip_with_delimiter", dockerfile, "--add-host=foo:"}, 5603 {"extra_host_missing_hostname", dockerfile, "--add-host=:127.0.0.1"}, 5604 {"extra_host_invalid_ipv4", dockerfile, "--add-host=foo:101.10.2"}, 5605 {"extra_host_invalid_ipv6", dockerfile, "--add-host=foo:2001::1::3F"}, 5606 } 5607 5608 for _, tc := range testCases { 5609 result := buildImage(tc.testName, cli.WithFlags(tc.buildFlag), build.WithDockerfile(tc.dockerfile)) 5610 result.Assert(c, icmd.Expected{ 5611 ExitCode: 125, 5612 }) 5613 } 5614 } 5615 5616 func (s *DockerCLIBuildSuite) TestBuildMultiStageCopyFromSyntax(c *testing.T) { 5617 //nolint:dupword 5618 const dockerfile = ` 5619 FROM busybox AS first 5620 COPY foo bar 5621 5622 FROM busybox 5623 %s 5624 COPY baz baz 5625 RUN echo mno > baz/cc 5626 5627 FROM busybox 5628 COPY bar / 5629 COPY --from=1 baz sub/ 5630 COPY --from=0 bar baz 5631 COPY --from=first bar bay` 5632 5633 ctx := fakecontext.New(c, "", 5634 fakecontext.WithDockerfile(fmt.Sprintf(dockerfile, "")), 5635 fakecontext.WithFiles(map[string]string{ 5636 "foo": "abc", 5637 "bar": "def", 5638 "baz/aa": "ghi", 5639 "baz/bb": "jkl", 5640 })) 5641 defer ctx.Close() 5642 5643 cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx)) 5644 5645 cli.DockerCmd(c, "run", "build1", "cat", "bar").Assert(c, icmd.Expected{Out: "def"}) 5646 cli.DockerCmd(c, "run", "build1", "cat", "sub/aa").Assert(c, icmd.Expected{Out: "ghi"}) 5647 cli.DockerCmd(c, "run", "build1", "cat", "sub/cc").Assert(c, icmd.Expected{Out: "mno"}) 5648 cli.DockerCmd(c, "run", "build1", "cat", "baz").Assert(c, icmd.Expected{Out: "abc"}) 5649 cli.DockerCmd(c, "run", "build1", "cat", "bay").Assert(c, icmd.Expected{Out: "abc"}) 5650 5651 result := cli.BuildCmd(c, "build2", build.WithExternalBuildContext(ctx)) 5652 5653 // all commands should be cached 5654 assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 7) 5655 assert.Equal(c, getIDByName(c, "build1"), getIDByName(c, "build2")) 5656 5657 err := os.WriteFile(filepath.Join(ctx.Dir, "Dockerfile"), []byte(fmt.Sprintf(dockerfile, "COPY baz/aa foo")), 0o644) 5658 assert.NilError(c, err) 5659 5660 // changing file in parent block should not affect last block 5661 result = cli.BuildCmd(c, "build3", build.WithExternalBuildContext(ctx)) 5662 assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 5) 5663 5664 err = os.WriteFile(filepath.Join(ctx.Dir, "foo"), []byte("pqr"), 0o644) 5665 assert.NilError(c, err) 5666 5667 // changing file in parent block should affect both first and last block 5668 result = cli.BuildCmd(c, "build4", build.WithExternalBuildContext(ctx)) 5669 assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 5) 5670 5671 cli.DockerCmd(c, "run", "build4", "cat", "bay").Assert(c, icmd.Expected{Out: "pqr"}) 5672 cli.DockerCmd(c, "run", "build4", "cat", "baz").Assert(c, icmd.Expected{Out: "pqr"}) 5673 } 5674 5675 func (s *DockerCLIBuildSuite) TestBuildMultiStageCopyFromErrors(c *testing.T) { 5676 testCases := []struct { 5677 dockerfile string 5678 expectedError string 5679 }{ 5680 { 5681 dockerfile: ` 5682 FROM busybox 5683 COPY --from=foo foo bar`, 5684 expectedError: "invalid from flag value foo", 5685 }, 5686 { 5687 dockerfile: ` 5688 FROM busybox 5689 COPY --from=0 foo bar`, 5690 expectedError: "invalid from flag value 0: refers to current build stage", 5691 }, 5692 { 5693 dockerfile: ` 5694 FROM busybox AS foo 5695 COPY --from=bar foo bar`, 5696 expectedError: "invalid from flag value bar", 5697 }, 5698 { 5699 dockerfile: ` 5700 FROM busybox AS 1 5701 COPY --from=1 foo bar`, 5702 expectedError: "invalid name for build stage", 5703 }, 5704 } 5705 5706 for _, tc := range testCases { 5707 ctx := fakecontext.New(c, "", 5708 fakecontext.WithDockerfile(tc.dockerfile), 5709 fakecontext.WithFiles(map[string]string{ 5710 "foo": "abc", 5711 })) 5712 5713 cli.Docker(cli.Args("build", "-t", "build1"), build.WithExternalBuildContext(ctx)).Assert(c, icmd.Expected{ 5714 ExitCode: 1, 5715 Err: tc.expectedError, 5716 }) 5717 5718 ctx.Close() 5719 } 5720 } 5721 5722 func (s *DockerCLIBuildSuite) TestBuildMultiStageMultipleBuilds(c *testing.T) { 5723 dockerfile := ` 5724 FROM busybox 5725 COPY foo bar` 5726 ctx := fakecontext.New(c, "", 5727 fakecontext.WithDockerfile(dockerfile), 5728 fakecontext.WithFiles(map[string]string{ 5729 "foo": "abc", 5730 })) 5731 defer ctx.Close() 5732 5733 cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx)) 5734 5735 dockerfile = ` 5736 FROM build1:latest AS foo 5737 FROM busybox 5738 COPY --from=foo bar / 5739 COPY foo /` 5740 ctx = fakecontext.New(c, "", 5741 fakecontext.WithDockerfile(dockerfile), 5742 fakecontext.WithFiles(map[string]string{ 5743 "foo": "def", 5744 })) 5745 defer ctx.Close() 5746 5747 cli.BuildCmd(c, "build2", build.WithExternalBuildContext(ctx)) 5748 5749 out := cli.DockerCmd(c, "run", "build2", "cat", "bar").Combined() 5750 assert.Equal(c, strings.TrimSpace(out), "abc") 5751 out = cli.DockerCmd(c, "run", "build2", "cat", "foo").Combined() 5752 assert.Equal(c, strings.TrimSpace(out), "def") 5753 } 5754 5755 func (s *DockerCLIBuildSuite) TestBuildMultiStageImplicitFrom(c *testing.T) { 5756 dockerfile := ` 5757 FROM busybox 5758 COPY --from=busybox /etc/passwd /mypasswd 5759 RUN cmp /etc/passwd /mypasswd` 5760 5761 if DaemonIsWindows() { 5762 dockerfile = ` 5763 FROM busybox 5764 COPY --from=busybox License.txt foo` 5765 } 5766 5767 ctx := fakecontext.New(c, "", 5768 fakecontext.WithDockerfile(dockerfile), 5769 ) 5770 defer ctx.Close() 5771 5772 cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx)) 5773 5774 if DaemonIsWindows() { 5775 out := cli.DockerCmd(c, "run", "build1", "cat", "License.txt").Combined() 5776 assert.Assert(c, len(out) > 10) 5777 out2 := cli.DockerCmd(c, "run", "build1", "cat", "foo").Combined() 5778 assert.Equal(c, out, out2) 5779 } 5780 } 5781 5782 func (s *DockerRegistrySuite) TestBuildMultiStageImplicitPull(c *testing.T) { 5783 repoName := fmt.Sprintf("%v/dockercli/testf", privateRegistryURL) 5784 5785 dockerfile := ` 5786 FROM busybox 5787 COPY foo bar` 5788 ctx := fakecontext.New(c, "", 5789 fakecontext.WithDockerfile(dockerfile), 5790 fakecontext.WithFiles(map[string]string{ 5791 "foo": "abc", 5792 })) 5793 defer ctx.Close() 5794 5795 cli.BuildCmd(c, repoName, build.WithExternalBuildContext(ctx)) 5796 5797 cli.DockerCmd(c, "push", repoName) 5798 cli.DockerCmd(c, "rmi", repoName) 5799 5800 dockerfile = ` 5801 FROM busybox 5802 COPY --from=%s bar baz` 5803 5804 ctx = fakecontext.New(c, "", fakecontext.WithDockerfile(fmt.Sprintf(dockerfile, repoName))) 5805 defer ctx.Close() 5806 5807 cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx)) 5808 5809 cli.Docker(cli.Args("run", "build1", "cat", "baz")).Assert(c, icmd.Expected{Out: "abc"}) 5810 } 5811 5812 func (s *DockerCLIBuildSuite) TestBuildMultiStageNameVariants(c *testing.T) { 5813 dockerfile := ` 5814 FROM busybox as foo 5815 COPY foo / 5816 FROM foo as foo1 5817 RUN echo 1 >> foo 5818 FROM foo as foO2 5819 RUN echo 2 >> foo 5820 FROM foo 5821 COPY --from=foo1 foo f1 5822 COPY --from=FOo2 foo f2 5823 ` // foo2 case also tests that names are case insensitive 5824 ctx := fakecontext.New(c, "", 5825 fakecontext.WithDockerfile(dockerfile), 5826 fakecontext.WithFiles(map[string]string{ 5827 "foo": "bar", 5828 })) 5829 defer ctx.Close() 5830 5831 cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx)) 5832 cli.Docker(cli.Args("run", "build1", "cat", "foo")).Assert(c, icmd.Expected{Out: "bar"}) 5833 cli.Docker(cli.Args("run", "build1", "cat", "f1")).Assert(c, icmd.Expected{Out: "bar1"}) 5834 cli.Docker(cli.Args("run", "build1", "cat", "f2")).Assert(c, icmd.Expected{Out: "bar2"}) 5835 } 5836 5837 func (s *DockerCLIBuildSuite) TestBuildMultiStageMultipleBuildsWindows(c *testing.T) { 5838 testRequires(c, DaemonIsWindows) 5839 dockerfile := ` 5840 FROM ` + testEnv.PlatformDefaults.BaseImage + ` 5841 COPY foo c:\\bar` 5842 ctx := fakecontext.New(c, "", 5843 fakecontext.WithDockerfile(dockerfile), 5844 fakecontext.WithFiles(map[string]string{ 5845 "foo": "abc", 5846 })) 5847 defer ctx.Close() 5848 5849 cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx)) 5850 5851 dockerfile = ` 5852 FROM build1:latest 5853 FROM ` + testEnv.PlatformDefaults.BaseImage + ` 5854 COPY --from=0 c:\\bar / 5855 COPY foo /` 5856 ctx = fakecontext.New(c, "", 5857 fakecontext.WithDockerfile(dockerfile), 5858 fakecontext.WithFiles(map[string]string{ 5859 "foo": "def", 5860 })) 5861 defer ctx.Close() 5862 5863 cli.BuildCmd(c, "build2", build.WithExternalBuildContext(ctx)) 5864 5865 out := cli.DockerCmd(c, "run", "build2", "cmd.exe", "/s", "/c", "type", "c:\\bar").Combined() 5866 assert.Equal(c, strings.TrimSpace(out), "abc") 5867 out = cli.DockerCmd(c, "run", "build2", "cmd.exe", "/s", "/c", "type", "c:\\foo").Combined() 5868 assert.Equal(c, strings.TrimSpace(out), "def") 5869 } 5870 5871 func (s *DockerCLIBuildSuite) TestBuildCopyFromForbidWindowsSystemPaths(c *testing.T) { 5872 testRequires(c, DaemonIsWindows) 5873 dockerfile := ` 5874 FROM ` + testEnv.PlatformDefaults.BaseImage + ` 5875 FROM ` + testEnv.PlatformDefaults.BaseImage + ` 5876 COPY --from=0 %s c:\\oscopy 5877 ` 5878 exp := icmd.Expected{ 5879 ExitCode: 1, 5880 Err: "copy from c:\\ or c:\\windows is not allowed on windows", 5881 } 5882 buildImage("testforbidsystempaths1", build.WithDockerfile(fmt.Sprintf(dockerfile, "c:\\\\"))).Assert(c, exp) 5883 buildImage("testforbidsystempaths2", build.WithDockerfile(fmt.Sprintf(dockerfile, "C:\\\\"))).Assert(c, exp) 5884 buildImage("testforbidsystempaths3", build.WithDockerfile(fmt.Sprintf(dockerfile, "c:\\\\windows"))).Assert(c, exp) 5885 buildImage("testforbidsystempaths4", build.WithDockerfile(fmt.Sprintf(dockerfile, "c:\\\\wInDows"))).Assert(c, exp) 5886 } 5887 5888 func (s *DockerCLIBuildSuite) TestBuildCopyFromForbidWindowsRelativePaths(c *testing.T) { 5889 testRequires(c, DaemonIsWindows) 5890 dockerfile := ` 5891 FROM ` + testEnv.PlatformDefaults.BaseImage + ` 5892 FROM ` + testEnv.PlatformDefaults.BaseImage + ` 5893 COPY --from=0 %s c:\\oscopy 5894 ` 5895 exp := icmd.Expected{ 5896 ExitCode: 1, 5897 Err: "copy from c:\\ or c:\\windows is not allowed on windows", 5898 } 5899 buildImage("testforbidsystempaths1", build.WithDockerfile(fmt.Sprintf(dockerfile, "c:"))).Assert(c, exp) 5900 buildImage("testforbidsystempaths2", build.WithDockerfile(fmt.Sprintf(dockerfile, "."))).Assert(c, exp) 5901 buildImage("testforbidsystempaths3", build.WithDockerfile(fmt.Sprintf(dockerfile, "..\\\\"))).Assert(c, exp) 5902 buildImage("testforbidsystempaths4", build.WithDockerfile(fmt.Sprintf(dockerfile, ".\\\\windows"))).Assert(c, exp) 5903 buildImage("testforbidsystempaths5", build.WithDockerfile(fmt.Sprintf(dockerfile, "\\\\windows"))).Assert(c, exp) 5904 } 5905 5906 func (s *DockerCLIBuildSuite) TestBuildCopyFromWindowsIsCaseInsensitive(c *testing.T) { 5907 testRequires(c, DaemonIsWindows) 5908 dockerfile := ` 5909 FROM ` + testEnv.PlatformDefaults.BaseImage + ` 5910 COPY foo / 5911 FROM ` + testEnv.PlatformDefaults.BaseImage + ` 5912 COPY --from=0 c:\\fOo c:\\copied 5913 RUN type c:\\copied 5914 ` 5915 cli.Docker(cli.Args("build", "-t", "copyfrom-windows-insensitive"), build.WithBuildContext(c, 5916 build.WithFile("Dockerfile", dockerfile), 5917 build.WithFile("foo", "hello world"), 5918 )).Assert(c, icmd.Expected{ 5919 ExitCode: 0, 5920 Out: "hello world", 5921 }) 5922 } 5923 5924 // #33176 5925 func (s *DockerCLIBuildSuite) TestBuildMultiStageResetScratch(c *testing.T) { 5926 testRequires(c, DaemonIsLinux) 5927 5928 dockerfile := ` 5929 FROM busybox 5930 WORKDIR /foo/bar 5931 FROM scratch 5932 ENV FOO=bar 5933 ` 5934 ctx := fakecontext.New(c, "", 5935 fakecontext.WithDockerfile(dockerfile), 5936 ) 5937 defer ctx.Close() 5938 5939 cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx)) 5940 5941 res := cli.InspectCmd(c, "build1", cli.Format(".Config.WorkingDir")).Combined() 5942 assert.Equal(c, strings.TrimSpace(res), "") 5943 } 5944 5945 func (s *DockerCLIBuildSuite) TestBuildIntermediateTarget(c *testing.T) { 5946 dockerfile := ` 5947 FROM busybox AS build-env 5948 CMD ["/dev"] 5949 FROM busybox 5950 CMD ["/dist"] 5951 ` 5952 ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile)) 5953 defer ctx.Close() 5954 5955 cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx), 5956 cli.WithFlags("--target", "build-env")) 5957 5958 res := cli.InspectCmd(c, "build1", cli.Format("json .Config.Cmd")).Combined() 5959 assert.Equal(c, strings.TrimSpace(res), `["/dev"]`) 5960 5961 // Stage name is case-insensitive by design 5962 cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx), 5963 cli.WithFlags("--target", "BUIld-EnV")) 5964 5965 res = cli.InspectCmd(c, "build1", cli.Format("json .Config.Cmd")).Combined() 5966 assert.Equal(c, strings.TrimSpace(res), `["/dev"]`) 5967 5968 result := cli.Docker(cli.Args("build", "-t", "build1"), build.WithExternalBuildContext(ctx), 5969 cli.WithFlags("--target", "nosuchtarget")) 5970 result.Assert(c, icmd.Expected{ 5971 ExitCode: 1, 5972 Err: "target stage \"nosuchtarget\" could not be found", 5973 }) 5974 } 5975 5976 // TestBuildOpaqueDirectory tests that a build succeeds which 5977 // creates opaque directories. 5978 // See https://github.com/docker/docker/issues/25244 5979 func (s *DockerCLIBuildSuite) TestBuildOpaqueDirectory(c *testing.T) { 5980 testRequires(c, DaemonIsLinux) 5981 dockerFile := ` 5982 FROM busybox 5983 RUN mkdir /dir1 && touch /dir1/f1 5984 RUN rm -rf /dir1 && mkdir /dir1 && touch /dir1/f2 5985 RUN touch /dir1/f3 5986 RUN [ -f /dir1/f2 ] 5987 ` 5988 // Test that build succeeds, last command fails if opaque directory 5989 // was not handled correctly 5990 buildImageSuccessfully(c, "testopaquedirectory", build.WithDockerfile(dockerFile)) 5991 } 5992 5993 // Windows test for USER in dockerfile 5994 func (s *DockerCLIBuildSuite) TestBuildWindowsUser(c *testing.T) { 5995 testRequires(c, DaemonIsWindows) 5996 const name = "testbuildwindowsuser" 5997 //nolint:dupword 5998 buildImage(name, build.WithDockerfile(`FROM `+testEnv.PlatformDefaults.BaseImage+` 5999 RUN net user user /add 6000 USER user 6001 RUN set username 6002 `)).Assert(c, icmd.Expected{ 6003 Out: "USERNAME=user", 6004 }) 6005 } 6006 6007 // Verifies if COPY file . when WORKDIR is set to a non-existing directory, 6008 // the directory is created and the file is copied into the directory, 6009 // as opposed to the file being copied as a file with the name of the 6010 // directory. Fix for 27545 (found on Windows, but regression good for Linux too). 6011 // Note 27545 was reverted in 28505, but a new fix was added subsequently in 28514. 6012 func (s *DockerCLIBuildSuite) TestBuildCopyFileDotWithWorkdir(c *testing.T) { 6013 const name = "testbuildcopyfiledotwithworkdir" 6014 buildImageSuccessfully(c, name, build.WithBuildContext(c, 6015 build.WithFile("Dockerfile", `FROM busybox 6016 WORKDIR /foo 6017 COPY file . 6018 RUN ["cat", "/foo/file"] 6019 `), 6020 build.WithFile("file", "content"), 6021 )) 6022 } 6023 6024 // Case-insensitive environment variables on Windows 6025 func (s *DockerCLIBuildSuite) TestBuildWindowsEnvCaseInsensitive(c *testing.T) { 6026 testRequires(c, DaemonIsWindows) 6027 const name = "testbuildwindowsenvcaseinsensitive" 6028 buildImageSuccessfully(c, name, build.WithDockerfile(` 6029 FROM `+testEnv.PlatformDefaults.BaseImage+` 6030 ENV FOO=bar foo=baz 6031 `)) 6032 res := inspectFieldJSON(c, name, "Config.Env") 6033 if res != `["foo=baz"]` { // Should not have FOO=bar in it - takes the last one processed. And only one entry as deduped. 6034 c.Fatalf("Case insensitive environment variables on Windows failed. Got %s", res) 6035 } 6036 } 6037 6038 // Test case for 29667 6039 func (s *DockerCLIBuildSuite) TestBuildWorkdirImageCmd(c *testing.T) { 6040 imgName := "testworkdirimagecmd" 6041 buildImageSuccessfully(c, imgName, build.WithDockerfile(` 6042 FROM busybox 6043 WORKDIR /foo/bar 6044 `)) 6045 out := cli.DockerCmd(c, "inspect", "--format", "{{ json .Config.Cmd }}", imgName).Stdout() 6046 assert.Equal(c, strings.TrimSpace(out), `["sh"]`) 6047 6048 imgName = "testworkdirlabelimagecmd" 6049 buildImageSuccessfully(c, imgName, build.WithDockerfile(` 6050 FROM busybox 6051 WORKDIR /foo/bar 6052 LABEL a=b 6053 `)) 6054 6055 out = cli.DockerCmd(c, "inspect", "--format", "{{ json .Config.Cmd }}", imgName).Stdout() 6056 assert.Equal(c, strings.TrimSpace(out), `["sh"]`) 6057 } 6058 6059 // Test case for 28902/28909 6060 func (s *DockerCLIBuildSuite) TestBuildWorkdirCmd(c *testing.T) { 6061 testRequires(c, DaemonIsLinux) 6062 const name = "testbuildworkdircmd" 6063 dockerFile := ` 6064 FROM busybox 6065 WORKDIR / 6066 ` 6067 buildImageSuccessfully(c, name, build.WithDockerfile(dockerFile)) 6068 result := buildImage(name, build.WithDockerfile(dockerFile)) 6069 result.Assert(c, icmd.Success) 6070 assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 1) 6071 } 6072 6073 // FIXME(vdemeester) should be a unit test 6074 func (s *DockerCLIBuildSuite) TestBuildLineErrorOnBuild(c *testing.T) { 6075 const name = "test_build_line_error_onbuild" 6076 buildImage(name, build.WithDockerfile(`FROM busybox 6077 ONBUILD 6078 `)).Assert(c, icmd.Expected{ 6079 ExitCode: 1, 6080 Err: "parse error on line 2: ONBUILD requires at least one argument", 6081 }) 6082 } 6083 6084 // FIXME(vdemeester) should be a unit test 6085 func (s *DockerCLIBuildSuite) TestBuildLineErrorUnknownInstruction(c *testing.T) { 6086 const name = "test_build_line_error_unknown_instruction" 6087 cli.Docker(cli.Args("build", "-t", name), build.WithDockerfile(`FROM busybox 6088 RUN echo hello world 6089 NOINSTRUCTION echo ba 6090 RUN echo hello 6091 ERROR 6092 `)).Assert(c, icmd.Expected{ 6093 ExitCode: 1, 6094 Err: "parse error on line 3: unknown instruction: NOINSTRUCTION", 6095 }) 6096 } 6097 6098 // FIXME(vdemeester) should be a unit test 6099 func (s *DockerCLIBuildSuite) TestBuildLineErrorWithEmptyLines(c *testing.T) { 6100 const name = "test_build_line_error_with_empty_lines" 6101 cli.Docker(cli.Args("build", "-t", name), build.WithDockerfile(` 6102 FROM busybox 6103 6104 RUN echo hello world 6105 6106 NOINSTRUCTION echo ba 6107 6108 CMD ["/bin/init"] 6109 `)).Assert(c, icmd.Expected{ 6110 ExitCode: 1, 6111 Err: "parse error on line 6: unknown instruction: NOINSTRUCTION", 6112 }) 6113 } 6114 6115 // FIXME(vdemeester) should be a unit test 6116 func (s *DockerCLIBuildSuite) TestBuildLineErrorWithComments(c *testing.T) { 6117 const name = "test_build_line_error_with_comments" 6118 cli.Docker(cli.Args("build", "-t", name), build.WithDockerfile(`FROM busybox 6119 # This will print hello world 6120 # and then ba 6121 RUN echo hello world 6122 NOINSTRUCTION echo ba 6123 `)).Assert(c, icmd.Expected{ 6124 ExitCode: 1, 6125 Err: "parse error on line 5: unknown instruction: NOINSTRUCTION", 6126 }) 6127 } 6128 6129 // #31957 6130 func (s *DockerCLIBuildSuite) TestBuildSetCommandWithDefinedShell(c *testing.T) { 6131 buildImageSuccessfully(c, "build1", build.WithDockerfile(` 6132 FROM busybox 6133 SHELL ["/bin/sh", "-c"] 6134 `)) 6135 buildImageSuccessfully(c, "build2", build.WithDockerfile(` 6136 FROM build1 6137 CMD echo foo 6138 `)) 6139 6140 out := cli.DockerCmd(c, "inspect", "--format", "{{ json .Config.Cmd }}", "build2").Stdout() 6141 expected := `["/bin/sh","-c","echo foo"]` 6142 if testEnv.DaemonInfo.OSType == "windows" { 6143 expected = `["/bin/sh -c echo foo"]` 6144 } 6145 assert.Equal(c, strings.TrimSpace(out), expected) 6146 } 6147 6148 // FIXME(vdemeester) should migrate to docker/cli tests 6149 func (s *DockerCLIBuildSuite) TestBuildIidFile(c *testing.T) { 6150 tmpDir, err := os.MkdirTemp("", "TestBuildIidFile") 6151 if err != nil { 6152 c.Fatal(err) 6153 } 6154 defer os.RemoveAll(tmpDir) 6155 tmpIidFile := filepath.Join(tmpDir, "iid") 6156 6157 const name = "testbuildiidfile" 6158 // Use a Dockerfile with multiple stages to ensure we get the last one 6159 cli.BuildCmd(c, name, 6160 build.WithDockerfile(`FROM `+minimalBaseImage()+` AS stage1 6161 ENV FOO FOO1 6162 FROM `+minimalBaseImage()+` 6163 ENV BAR BAZ`), 6164 cli.WithFlags("--iidfile", tmpIidFile)) 6165 6166 id, err := os.ReadFile(tmpIidFile) 6167 assert.NilError(c, err) 6168 d, err := digest.Parse(string(id)) 6169 assert.NilError(c, err) 6170 assert.Equal(c, d.String(), getIDByName(c, name)) 6171 } 6172 6173 // FIXME(vdemeester) should migrate to docker/cli tests 6174 func (s *DockerCLIBuildSuite) TestBuildIidFileCleanupOnFail(c *testing.T) { 6175 tmpDir, err := os.MkdirTemp("", "TestBuildIidFileCleanupOnFail") 6176 if err != nil { 6177 c.Fatal(err) 6178 } 6179 defer os.RemoveAll(tmpDir) 6180 tmpIidFile := filepath.Join(tmpDir, "iid") 6181 6182 err = os.WriteFile(tmpIidFile, []byte("Dummy"), 0o666) 6183 assert.NilError(c, err) 6184 6185 cli.Docker(cli.Args("build", "-t", "testbuildiidfilecleanuponfail"), 6186 build.WithDockerfile(`FROM `+minimalBaseImage()+` 6187 RUN /non/existing/command`), 6188 cli.WithFlags("--iidfile", tmpIidFile)).Assert(c, icmd.Expected{ 6189 ExitCode: 1, 6190 }) 6191 _, err = os.Stat(tmpIidFile) 6192 assert.ErrorContains(c, err, "") 6193 assert.Equal(c, os.IsNotExist(err), true) 6194 }