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