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