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