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