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