package main import ( "fmt" "os" "os/exec" "path/filepath" "strings" "testing" "time" "github.com/dotcloud/docker/archive" ) func TestBuildCacheADD(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildCacheADD", "1") buildCmd := exec.Command(dockerBinary, "build", "-t", "testcacheadd1", ".") buildCmd.Dir = buildDirectory exitCode, err := runCommand(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v", err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } buildDirectory = filepath.Join(workingDirectory, "build_tests", "TestBuildCacheADD", "2") buildCmd = exec.Command(dockerBinary, "build", "-t", "testcacheadd2", ".") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } if strings.Contains(out, "Using cache") { t.Fatal("2nd build used cache on ADD, it shouldn't") } deleteImages("testcacheadd1") deleteImages("testcacheadd2") logDone("build - build two images with ADD") } func TestBuildSixtySteps(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildSixtySteps") buildCmd := exec.Command(dockerBinary, "build", "-t", "foobuildsixtysteps", ".") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("foobuildsixtysteps") logDone("build - build an image with sixty build steps") } func TestAddSingleFileToRoot(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd", "SingleFileToRoot") f, err := os.OpenFile(filepath.Join(buildDirectory, "test_file"), os.O_CREATE, 0644) if err != nil { t.Fatal(err) } f.Close() buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", ".") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testaddimg") logDone("build - add single file to root") } // Issue #3960: "ADD src ." hangs func TestAddSingleFileToWorkdir(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd", "SingleFileToWorkdir") f, err := os.OpenFile(filepath.Join(buildDirectory, "test_file"), os.O_CREATE, 0644) if err != nil { t.Fatal(err) } f.Close() buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", ".") buildCmd.Dir = buildDirectory done := make(chan error) go func() { out, exitCode, err := runCommandWithOutput(buildCmd) if err != nil || exitCode != 0 { done <- fmt.Errorf("build failed to complete: %s %v", out, err) return } done <- nil }() select { case <-time.After(5 * time.Second): if err := buildCmd.Process.Kill(); err != nil { fmt.Printf("could not kill build (pid=%d): %v\n", buildCmd.Process.Pid, err) } t.Fatal("build timed out") case err := <-done: if err != nil { t.Fatal(err) } } deleteImages("testaddimg") logDone("build - add single file to workdir") } func TestAddSingleFileToExistDir(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "SingleFileToExistDir") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testaddimg") logDone("build - add single file to existing dir") } func TestAddSingleFileToNonExistDir(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "SingleFileToNonExistDir") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testaddimg") logDone("build - add single file to non-existing dir") } func TestAddDirContentToRoot(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "DirContentToRoot") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testaddimg") logDone("build - add directory contents to root") } func TestAddDirContentToExistDir(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "DirContentToExistDir") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testaddimg") logDone("build - add directory contents to existing dir") } func TestAddWholeDirToRoot(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd", "WholeDirToRoot") test_dir := filepath.Join(buildDirectory, "test_dir") if err := os.MkdirAll(test_dir, 0755); err != nil { t.Fatal(err) } f, err := os.OpenFile(filepath.Join(test_dir, "test_file"), os.O_CREATE, 0644) if err != nil { t.Fatal(err) } f.Close() buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", ".") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testaddimg") logDone("build - add whole directory to root") } func TestAddEtcToRoot(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "EtcToRoot") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testaddimg") logDone("build - add etc directory to root") } func TestCopySingleFileToRoot(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy", "SingleFileToRoot") f, err := os.OpenFile(filepath.Join(buildDirectory, "test_file"), os.O_CREATE, 0644) if err != nil { t.Fatal(err) } f.Close() buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", ".") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testcopyimg") logDone("build - copy single file to root") } // Issue #3960: "ADD src ." hangs - adapted for COPY func TestCopySingleFileToWorkdir(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy", "SingleFileToWorkdir") f, err := os.OpenFile(filepath.Join(buildDirectory, "test_file"), os.O_CREATE, 0644) if err != nil { t.Fatal(err) } f.Close() buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", ".") buildCmd.Dir = buildDirectory done := make(chan error) go func() { out, exitCode, err := runCommandWithOutput(buildCmd) if err != nil || exitCode != 0 { done <- fmt.Errorf("build failed to complete: %s %v", out, err) return } done <- nil }() select { case <-time.After(5 * time.Second): if err := buildCmd.Process.Kill(); err != nil { fmt.Printf("could not kill build (pid=%d): %v\n", buildCmd.Process.Pid, err) } t.Fatal("build timed out") case err := <-done: if err != nil { t.Fatal(err) } } deleteImages("testcopyimg") logDone("build - copy single file to workdir") } func TestCopySingleFileToExistDir(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy") buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", "SingleFileToExistDir") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testcopyimg") logDone("build - add single file to existing dir") } func TestCopySingleFileToNonExistDir(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy") buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", "SingleFileToNonExistDir") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testcopyimg") logDone("build - copy single file to non-existing dir") } func TestCopyDirContentToRoot(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy") buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", "DirContentToRoot") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testcopyimg") logDone("build - copy directory contents to root") } func TestCopyDirContentToExistDir(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy") buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", "DirContentToExistDir") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testcopyimg") logDone("build - copy directory contents to existing dir") } func TestCopyWholeDirToRoot(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy", "WholeDirToRoot") test_dir := filepath.Join(buildDirectory, "test_dir") if err := os.MkdirAll(test_dir, 0755); err != nil { t.Fatal(err) } f, err := os.OpenFile(filepath.Join(test_dir, "test_file"), os.O_CREATE, 0644) if err != nil { t.Fatal(err) } f.Close() buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", ".") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testcopyimg") logDone("build - copy whole directory to root") } func TestCopyEtcToRoot(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy") buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", "EtcToRoot") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } deleteImages("testcopyimg") logDone("build - copy etc directory to root") } func TestCopyDisallowRemote(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy") buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", "DisallowRemote") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) if err == nil || exitCode == 0 { t.Fatalf("building the image should've failed; output: %s", out) } deleteImages("testcopyimg") logDone("build - copy - disallow copy from remote") } // Issue #5270 - ensure we throw a better error than "unexpected EOF" // when we can't access files in the context. func TestBuildWithInaccessibleFilesInContext(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildWithInaccessibleFilesInContext") { // This is used to ensure we detect inaccessible files early during build in the cli client pathToInaccessibleFileBuildDirectory := filepath.Join(buildDirectory, "inaccessiblefile") pathToFileWithoutReadAccess := filepath.Join(pathToInaccessibleFileBuildDirectory, "fileWithoutReadAccess") err := os.Chown(pathToFileWithoutReadAccess, 0, 0) errorOut(err, t, fmt.Sprintf("failed to chown file to root: %s", err)) err = os.Chmod(pathToFileWithoutReadAccess, 0700) errorOut(err, t, fmt.Sprintf("failed to chmod file to 700: %s", err)) buildCommandStatement := fmt.Sprintf("%s build -t inaccessiblefiles .", dockerBinary) buildCmd := exec.Command("su", "unprivilegeduser", "-c", buildCommandStatement) buildCmd.Dir = pathToInaccessibleFileBuildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) if err == nil || exitCode == 0 { t.Fatalf("build should have failed: %s %s", err, out) } // check if we've detected the failure before we started building if !strings.Contains(out, "no permission to read from ") { t.Fatalf("output should've contained the string: no permission to read from but contained: %s", out) } if !strings.Contains(out, "Error checking context is accessible") { t.Fatalf("output should've contained the string: Error checking context is accessible") } } { // This is used to ensure we detect inaccessible directories early during build in the cli client pathToInaccessibleDirectoryBuildDirectory := filepath.Join(buildDirectory, "inaccessibledirectory") pathToDirectoryWithoutReadAccess := filepath.Join(pathToInaccessibleDirectoryBuildDirectory, "directoryWeCantStat") pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar") err := os.Chown(pathToDirectoryWithoutReadAccess, 0, 0) errorOut(err, t, fmt.Sprintf("failed to chown directory to root: %s", err)) err = os.Chmod(pathToDirectoryWithoutReadAccess, 0444) errorOut(err, t, fmt.Sprintf("failed to chmod directory to 755: %s", err)) err = os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0700) errorOut(err, t, fmt.Sprintf("failed to chmod file to 444: %s", err)) buildCommandStatement := fmt.Sprintf("%s build -t inaccessiblefiles .", dockerBinary) buildCmd := exec.Command("su", "unprivilegeduser", "-c", buildCommandStatement) buildCmd.Dir = pathToInaccessibleDirectoryBuildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) if err == nil || exitCode == 0 { t.Fatalf("build should have failed: %s %s", err, out) } // check if we've detected the failure before we started building if !strings.Contains(out, "can't stat") { t.Fatalf("output should've contained the string: can't access %s", out) } if !strings.Contains(out, "Error checking context is accessible") { t.Fatalf("output should've contained the string: Error checking context is accessible") } } { // This is used to ensure we don't follow links when checking if everything in the context is accessible // This test doesn't require that we run commands as an unprivileged user pathToDirectoryWhichContainsLinks := filepath.Join(buildDirectory, "linksdirectory") buildCmd := exec.Command(dockerBinary, "build", "-t", "testlinksok", ".") buildCmd.Dir = pathToDirectoryWhichContainsLinks out, exitCode, err := runCommandWithOutput(buildCmd) if err != nil || exitCode != 0 { t.Fatalf("build should have worked: %s %s", err, out) } deleteImages("testlinksok") } deleteImages("inaccessiblefiles") logDone("build - ADD from context with inaccessible files must fail") logDone("build - ADD from context with accessible links must work") } func TestBuildForceRm(t *testing.T) { containerCountBefore, err := getContainerCount() if err != nil { t.Fatalf("failed to get the container count: %s", err) } buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildForceRm") buildCmd := exec.Command(dockerBinary, "build", "--force-rm", ".") buildCmd.Dir = buildDirectory _, exitCode, err := runCommandWithOutput(buildCmd) if err == nil || exitCode == 0 { t.Fatal("failed to build the image") } containerCountAfter, err := getContainerCount() if err != nil { t.Fatalf("failed to get the container count: %s", err) } if containerCountBefore != containerCountAfter { t.Fatalf("--force-rm shouldn't have left containers behind") } logDone("build - ensure --force-rm doesn't leave containers behind") } func TestBuildRm(t *testing.T) { { containerCountBefore, err := getContainerCount() if err != nil { t.Fatalf("failed to get the container count: %s", err) } buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildRm") buildCmd := exec.Command(dockerBinary, "build", "--rm", "-t", "testbuildrm", ".") buildCmd.Dir = buildDirectory _, exitCode, err := runCommandWithOutput(buildCmd) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } containerCountAfter, err := getContainerCount() if err != nil { t.Fatalf("failed to get the container count: %s", err) } if containerCountBefore != containerCountAfter { t.Fatalf("-rm shouldn't have left containers behind") } deleteImages("testbuildrm") } { containerCountBefore, err := getContainerCount() if err != nil { t.Fatalf("failed to get the container count: %s", err) } buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildRm") buildCmd := exec.Command(dockerBinary, "build", "-t", "testbuildrm", ".") buildCmd.Dir = buildDirectory _, exitCode, err := runCommandWithOutput(buildCmd) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } containerCountAfter, err := getContainerCount() if err != nil { t.Fatalf("failed to get the container count: %s", err) } if containerCountBefore != containerCountAfter { t.Fatalf("--rm shouldn't have left containers behind") } deleteImages("testbuildrm") } { containerCountBefore, err := getContainerCount() if err != nil { t.Fatalf("failed to get the container count: %s", err) } buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildRm") buildCmd := exec.Command(dockerBinary, "build", "--rm=false", "-t", "testbuildrm", ".") buildCmd.Dir = buildDirectory _, exitCode, err := runCommandWithOutput(buildCmd) if err != nil || exitCode != 0 { t.Fatal("failed to build the image") } containerCountAfter, err := getContainerCount() if err != nil { t.Fatalf("failed to get the container count: %s", err) } if containerCountBefore == containerCountAfter { t.Fatalf("--rm=false should have left containers behind") } deleteAllContainers() deleteImages("testbuildrm") } logDone("build - ensure --rm doesn't leave containers behind and that --rm=true is the default") logDone("build - ensure --rm=false overrides the default") } func TestBuildWithVolumes(t *testing.T) { name := "testbuildvolumes" expected := "map[/test1:map[] /test2:map[]]" defer deleteImages(name) _, err := buildImage(name, `FROM scratch VOLUME /test1 VOLUME /test2`, true) if err != nil { t.Fatal(err) } res, err := inspectField(name, "Config.Volumes") if err != nil { t.Fatal(err) } if res != expected { t.Fatalf("Volumes %s, expected %s", res, expected) } logDone("build - with volumes") } func TestBuildMaintainer(t *testing.T) { name := "testbuildmaintainer" expected := "dockerio" defer deleteImages(name) _, err := buildImage(name, `FROM scratch MAINTAINER dockerio`, true) if err != nil { t.Fatal(err) } res, err := inspectField(name, "Author") if err != nil { t.Fatal(err) } if res != expected { t.Fatalf("Maintainer %s, expected %s", res, expected) } logDone("build - maintainer") } func TestBuildUser(t *testing.T) { name := "testbuilduser" expected := "dockerio" defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd USER dockerio RUN [ $(whoami) = 'dockerio' ]`, true) if err != nil { t.Fatal(err) } res, err := inspectField(name, "Config.User") if err != nil { t.Fatal(err) } if res != expected { t.Fatalf("User %s, expected %s", res, expected) } logDone("build - user") } func TestBuildRelativeWorkdir(t *testing.T) { name := "testbuildrelativeworkdir" expected := "/test2/test3" defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN [ "$PWD" = '/' ] WORKDIR test1 RUN [ "$PWD" = '/test1' ] WORKDIR /test2 RUN [ "$PWD" = '/test2' ] WORKDIR test3 RUN [ "$PWD" = '/test2/test3' ]`, true) if err != nil { t.Fatal(err) } res, err := inspectField(name, "Config.WorkingDir") if err != nil { t.Fatal(err) } if res != expected { t.Fatalf("Workdir %s, expected %s", res, expected) } logDone("build - relative workdir") } func TestBuildEnv(t *testing.T) { name := "testbuildenv" expected := "[HOME=/ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=2375]" defer deleteImages(name) _, err := buildImage(name, `FROM busybox ENV PORT 2375 RUN [ $(env | grep PORT) = 'PORT=2375' ]`, true) if err != nil { t.Fatal(err) } res, err := inspectField(name, "Config.Env") if err != nil { t.Fatal(err) } if res != expected { t.Fatalf("Env %s, expected %s", res, expected) } logDone("build - env") } func TestBuildCmd(t *testing.T) { name := "testbuildcmd" expected := "[/bin/echo Hello World]" defer deleteImages(name) _, err := buildImage(name, `FROM scratch CMD ["/bin/echo", "Hello World"]`, true) if err != nil { t.Fatal(err) } res, err := inspectField(name, "Config.Cmd") if err != nil { t.Fatal(err) } if res != expected { t.Fatalf("Cmd %s, expected %s", res, expected) } logDone("build - cmd") } func TestBuildExpose(t *testing.T) { name := "testbuildexpose" expected := "map[2375/tcp:map[]]" defer deleteImages(name) _, err := buildImage(name, `FROM scratch EXPOSE 2375`, true) if err != nil { t.Fatal(err) } res, err := inspectField(name, "Config.ExposedPorts") if err != nil { t.Fatal(err) } if res != expected { t.Fatalf("Exposed ports %s, expected %s", res, expected) } logDone("build - expose") } func TestBuildEntrypoint(t *testing.T) { name := "testbuildentrypoint" expected := "[/bin/echo]" defer deleteImages(name) _, err := buildImage(name, `FROM scratch ENTRYPOINT ["/bin/echo"]`, true) if err != nil { t.Fatal(err) } res, err := inspectField(name, "Config.Entrypoint") if err != nil { t.Fatal(err) } if res != expected { t.Fatalf("Entrypoint %s, expected %s", res, expected) } logDone("build - entrypoint") } // #6445 ensure ONBUILD triggers aren't committed to grandchildren func TestBuildOnBuildLimitedInheritence(t *testing.T) { name1 := "testonbuildtrigger1" dockerfile1 := ` FROM busybox RUN echo "GRANDPARENT" ONBUILD RUN echo "ONBUILD PARENT" ` ctx1, err := fakeContext(dockerfile1, nil) if err != nil { t.Fatal(err) } buildCmd := exec.Command(dockerBinary, "build", "-t", name1, ".") buildCmd.Dir = ctx1.Dir out1, _, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out1, err)) defer deleteImages(name1) name2 := "testonbuildtrigger2" dockerfile2 := ` FROM testonbuildtrigger1 ` ctx2, err := fakeContext(dockerfile2, nil) if err != nil { t.Fatal(err) } buildCmd = exec.Command(dockerBinary, "build", "-t", name2, ".") buildCmd.Dir = ctx2.Dir out2, _, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out2, err)) defer deleteImages(name2) name3 := "testonbuildtrigger3" dockerfile3 := ` FROM testonbuildtrigger2 ` ctx3, err := fakeContext(dockerfile3, nil) if err != nil { t.Fatal(err) } buildCmd = exec.Command(dockerBinary, "build", "-t", name3, ".") buildCmd.Dir = ctx3.Dir out3, _, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out3, err)) defer deleteImages(name3) // ONBUILD should be run in second build. if !strings.Contains(out2, "ONBUILD PARENT") { t.Fatalf("ONBUILD instruction did not run in child of ONBUILD parent") } // ONBUILD should *not* be run in third build. if strings.Contains(out3, "ONBUILD PARENT") { t.Fatalf("ONBUILD instruction ran in grandchild of ONBUILD parent") } logDone("build - onbuild") } func TestBuildWithCache(t *testing.T) { name := "testbuildwithcache" defer deleteImages(name) id1, err := buildImage(name, `FROM scratch MAINTAINER dockerio EXPOSE 5432 ENTRYPOINT ["/bin/echo"]`, true) if err != nil { t.Fatal(err) } id2, err := buildImage(name, `FROM scratch MAINTAINER dockerio EXPOSE 5432 ENTRYPOINT ["/bin/echo"]`, true) if err != nil { t.Fatal(err) } if id1 != id2 { t.Fatal("The cache should have been used but hasn't.") } logDone("build - with cache") } func TestBuildWithoutCache(t *testing.T) { name := "testbuildwithoutcache" defer deleteImages(name) id1, err := buildImage(name, `FROM scratch MAINTAINER dockerio EXPOSE 5432 ENTRYPOINT ["/bin/echo"]`, true) if err != nil { t.Fatal(err) } id2, err := buildImage(name, `FROM scratch MAINTAINER dockerio EXPOSE 5432 ENTRYPOINT ["/bin/echo"]`, false) if err != nil { t.Fatal(err) } if id1 == id2 { t.Fatal("The cache should have been invalided but hasn't.") } logDone("build - without cache") } func TestBuildADDLocalFileWithCache(t *testing.T) { name := "testbuildaddlocalfilewithcache" defer deleteImages(name) dockerfile := ` FROM busybox MAINTAINER dockerio ADD foo /usr/lib/bla/bar RUN [ "$(cat /usr/lib/bla/bar)" = "hello" ]` ctx, err := fakeContext(dockerfile, map[string]string{ "foo": "hello", }) defer ctx.Close() if err != nil { t.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { t.Fatal(err) } id2, err := buildImageFromContext(name, ctx, true) if err != nil { t.Fatal(err) } if id1 != id2 { t.Fatal("The cache should have been used but hasn't.") } logDone("build - add local file with cache") } func TestBuildADDLocalFileWithoutCache(t *testing.T) { name := "testbuildaddlocalfilewithoutcache" defer deleteImages(name) dockerfile := ` FROM busybox MAINTAINER dockerio ADD foo /usr/lib/bla/bar RUN [ "$(cat /usr/lib/bla/bar)" = "hello" ]` ctx, err := fakeContext(dockerfile, map[string]string{ "foo": "hello", }) defer ctx.Close() if err != nil { t.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { t.Fatal(err) } id2, err := buildImageFromContext(name, ctx, false) if err != nil { t.Fatal(err) } if id1 == id2 { t.Fatal("The cache should have been invalided but hasn't.") } logDone("build - add local file without cache") } func TestBuildADDCurrentDirWithCache(t *testing.T) { name := "testbuildaddcurrentdirwithcache" defer deleteImages(name) dockerfile := ` FROM scratch MAINTAINER dockerio ADD . /usr/lib/bla` ctx, err := fakeContext(dockerfile, map[string]string{ "foo": "hello", }) defer ctx.Close() if err != nil { t.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { t.Fatal(err) } // Check that adding file invalidate cache of "ADD ." if err := ctx.Add("bar", "hello2"); err != nil { t.Fatal(err) } id2, err := buildImageFromContext(name, ctx, true) if err != nil { t.Fatal(err) } if id1 == id2 { t.Fatal("The cache should have been invalided but hasn't.") } // Check that changing file invalidate cache of "ADD ." if err := ctx.Add("foo", "hello1"); err != nil { t.Fatal(err) } id3, err := buildImageFromContext(name, ctx, true) if err != nil { t.Fatal(err) } if id2 == id3 { t.Fatal("The cache should have been invalided but hasn't.") } // Check that changing file to same content invalidate cache of "ADD ." time.Sleep(1 * time.Second) // wait second because of mtime precision if err := ctx.Add("foo", "hello1"); err != nil { t.Fatal(err) } id4, err := buildImageFromContext(name, ctx, true) if err != nil { t.Fatal(err) } if id3 == id4 { t.Fatal("The cache should have been invalided but hasn't.") } id5, err := buildImageFromContext(name, ctx, true) if err != nil { t.Fatal(err) } if id4 != id5 { t.Fatal("The cache should have been used but hasn't.") } logDone("build - add current directory with cache") } func TestBuildADDCurrentDirWithoutCache(t *testing.T) { name := "testbuildaddcurrentdirwithoutcache" defer deleteImages(name) dockerfile := ` FROM scratch MAINTAINER dockerio ADD . /usr/lib/bla` ctx, err := fakeContext(dockerfile, map[string]string{ "foo": "hello", }) defer ctx.Close() if err != nil { t.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { t.Fatal(err) } id2, err := buildImageFromContext(name, ctx, false) if err != nil { t.Fatal(err) } if id1 == id2 { t.Fatal("The cache should have been invalided but hasn't.") } logDone("build - add current directory without cache") } func TestBuildADDRemoteFileWithCache(t *testing.T) { name := "testbuildaddremotefilewithcache" defer deleteImages(name) server, err := fakeStorage(map[string]string{ "baz": "hello", }) if err != nil { t.Fatal(err) } defer server.Close() id1, err := buildImage(name, fmt.Sprintf(`FROM scratch MAINTAINER dockerio ADD %s/baz /usr/lib/baz/quux`, server.URL), true) if err != nil { t.Fatal(err) } id2, err := buildImage(name, fmt.Sprintf(`FROM scratch MAINTAINER dockerio ADD %s/baz /usr/lib/baz/quux`, server.URL), true) if err != nil { t.Fatal(err) } if id1 != id2 { t.Fatal("The cache should have been used but hasn't.") } logDone("build - add remote file with cache") } func TestBuildADDRemoteFileWithoutCache(t *testing.T) { name := "testbuildaddremotefilewithoutcache" defer deleteImages(name) server, err := fakeStorage(map[string]string{ "baz": "hello", }) if err != nil { t.Fatal(err) } defer server.Close() id1, err := buildImage(name, fmt.Sprintf(`FROM scratch MAINTAINER dockerio ADD %s/baz /usr/lib/baz/quux`, server.URL), true) if err != nil { t.Fatal(err) } id2, err := buildImage(name, fmt.Sprintf(`FROM scratch MAINTAINER dockerio ADD %s/baz /usr/lib/baz/quux`, server.URL), false) if err != nil { t.Fatal(err) } if id1 == id2 { t.Fatal("The cache should have been invalided but hasn't.") } logDone("build - add remote file without cache") } func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) { name := "testbuildaddlocalandremotefilewithcache" defer deleteImages(name) server, err := fakeStorage(map[string]string{ "baz": "hello", }) if err != nil { t.Fatal(err) } defer server.Close() ctx, err := fakeContext(fmt.Sprintf(`FROM scratch MAINTAINER dockerio ADD foo /usr/lib/bla/bar ADD %s/baz /usr/lib/baz/quux`, server.URL), map[string]string{ "foo": "hello world", }) if err != nil { t.Fatal(err) } defer ctx.Close() id1, err := buildImageFromContext(name, ctx, true) if err != nil { t.Fatal(err) } id2, err := buildImageFromContext(name, ctx, true) if err != nil { t.Fatal(err) } if id1 != id2 { t.Fatal("The cache should have been used but hasn't.") } logDone("build - add local and remote file with cache") } func testContextTar(t *testing.T, compression archive.Compression) { contextDirectory := filepath.Join(workingDirectory, "build_tests", "TestContextTar") context, err := archive.Tar(contextDirectory, compression) if err != nil { t.Fatalf("failed to build context tar: %v", err) } buildCmd := exec.Command(dockerBinary, "build", "-t", "contexttar", "-") buildCmd.Stdin = context out, exitCode, err := runCommandWithOutput(buildCmd) if err != nil || exitCode != 0 { t.Fatalf("build failed to complete: %v %v", out, err) } deleteImages("contexttar") logDone(fmt.Sprintf("build - build an image with a context tar, compression: %v", compression)) } func TestContextTarGzip(t *testing.T) { testContextTar(t, archive.Gzip) } func TestContextTarNoCompression(t *testing.T) { testContextTar(t, archive.Uncompressed) } func TestNoContext(t *testing.T) { buildCmd := exec.Command(dockerBinary, "build", "-t", "nocontext", "-") buildCmd.Stdin = strings.NewReader("FROM busybox\nCMD echo ok\n") out, exitCode, err := runCommandWithOutput(buildCmd) if err != nil || exitCode != 0 { t.Fatalf("build failed to complete: %v %v", out, err) } out, exitCode, err = cmd(t, "run", "nocontext") if out != "ok\n" { t.Fatalf("run produced invalid output: %q, expected %q", out, "ok") } deleteImages("nocontext") logDone("build - build an image with no context") } // TODO: TestCaching func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) { name := "testbuildaddlocalandremotefilewithoutcache" defer deleteImages(name) server, err := fakeStorage(map[string]string{ "baz": "hello", }) if err != nil { t.Fatal(err) } defer server.Close() ctx, err := fakeContext(fmt.Sprintf(`FROM scratch MAINTAINER dockerio ADD foo /usr/lib/bla/bar ADD %s/baz /usr/lib/baz/quux`, server.URL), map[string]string{ "foo": "hello world", }) if err != nil { t.Fatal(err) } defer ctx.Close() id1, err := buildImageFromContext(name, ctx, true) if err != nil { t.Fatal(err) } id2, err := buildImageFromContext(name, ctx, false) if err != nil { t.Fatal(err) } if id1 == id2 { t.Fatal("The cache should have been invalided but hasn't.") } logDone("build - add local and remote file without cache") } func TestBuildWithVolumeOwnership(t *testing.T) { name := "testbuildimg" defer deleteImages(name) _, err := buildImage(name, `FROM busybox:latest RUN mkdir /test && chown daemon:daemon /test && chmod 0600 /test VOLUME /test`, true) if err != nil { t.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "--rm", "testbuildimg", "ls", "-la", "/test") out, _, err := runCommandWithOutput(cmd) if err != nil { t.Fatal(err) } if expected := "drw-------"; !strings.Contains(out, expected) { t.Fatalf("expected %s received %s", expected, out) } if expected := "daemon daemon"; !strings.Contains(out, expected) { t.Fatalf("expected %s received %s", expected, out) } logDone("build - volume ownership") } // testing #1405 - config.Cmd does not get cleaned up if // utilizing cache func TestBuildEntrypointRunCleanup(t *testing.T) { name := "testbuildcmdcleanup" defer deleteImages(name) if _, err := buildImage(name, `FROM busybox RUN echo "hello"`, true); err != nil { t.Fatal(err) } ctx, err := fakeContext(`FROM busybox RUN echo "hello" ADD foo /foo ENTRYPOINT ["/bin/echo"]`, map[string]string{ "foo": "hello", }) defer ctx.Close() if err != nil { t.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { t.Fatal(err) } res, err := inspectField(name, "Config.Cmd") if err != nil { t.Fatal(err) } // Cmd inherited from busybox, maybe will be fixed in #5147 if expected := "[/bin/sh]"; res != expected { t.Fatalf("Cmd %s, expected %s", res, expected) } logDone("build - cleanup cmd after RUN") } func TestBuldForbiddenContextPath(t *testing.T) { name := "testbuildforbidpath" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD ../../ test/ `, map[string]string{ "test.txt": "test1", "other.txt": "other", }) defer ctx.Close() if err != nil { t.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { if !strings.Contains(err.Error(), "Forbidden path outside the build context: ../../ (/)") { t.Fatal("Wrong error, must be about forbidden ../../ path") } } else { t.Fatal("Error must not be nil") } logDone("build - forbidden context path") } func TestBuildADDFileNotFound(t *testing.T) { name := "testbuildaddnotfound" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD foo /usr/local/bar`, map[string]string{"bar": "hello"}) defer ctx.Close() if err != nil { t.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { if !strings.Contains(err.Error(), "foo: no such file or directory") { t.Fatalf("Wrong error %v, must be about missing foo file or directory", err) } } else { t.Fatal("Error must not be nil") } logDone("build - add file not found") } func TestBuildInheritance(t *testing.T) { name := "testbuildinheritance" defer deleteImages(name) _, err := buildImage(name, `FROM scratch EXPOSE 2375`, true) if err != nil { t.Fatal(err) } ports1, err := inspectField(name, "Config.ExposedPorts") if err != nil { t.Fatal(err) } _, err = buildImage(name, fmt.Sprintf(`FROM %s ENTRYPOINT ["/bin/echo"]`, name), true) if err != nil { t.Fatal(err) } res, err := inspectField(name, "Config.Entrypoint") if err != nil { t.Fatal(err) } if expected := "[/bin/echo]"; res != expected { t.Fatalf("Entrypoint %s, expected %s", res, expected) } ports2, err := inspectField(name, "Config.ExposedPorts") if err != nil { t.Fatal(err) } if ports1 != ports2 { t.Fatalf("Ports must be same: %s != %s", ports1, ports2) } logDone("build - inheritance") } func TestBuildFails(t *testing.T) { name := "testbuildfails" defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN sh -c "exit 23"`, true) if err != nil { if !strings.Contains(err.Error(), "returned a non-zero code: 23") { t.Fatalf("Wrong error %v, must be about non-zero code 23", err) } } else { t.Fatal("Error must not be nil") } logDone("build - fails") } func TestBuildFailsDockerfileEmpty(t *testing.T) { name := "testbuildfails" defer deleteImages(name) _, err := buildImage(name, ``, true) if err != nil { if !strings.Contains(err.Error(), "Dockerfile cannot be empty") { t.Fatalf("Wrong error %v, must be about empty Dockerfile", err) } } else { t.Fatal("Error must not be nil") } logDone("build - fails with empty dockerfile") } func TestBuildOnBuild(t *testing.T) { name := "testbuildonbuild" defer deleteImages(name) _, err := buildImage(name, `FROM busybox ONBUILD RUN touch foobar`, true) if err != nil { t.Fatal(err) } _, err = buildImage(name, fmt.Sprintf(`FROM %s RUN [ -f foobar ]`, name), true) if err != nil { t.Fatal(err) } logDone("build - onbuild") } func TestBuildOnBuildForbiddenChained(t *testing.T) { name := "testbuildonbuildforbiddenchained" defer deleteImages(name) _, err := buildImage(name, `FROM busybox ONBUILD ONBUILD RUN touch foobar`, true) if err != nil { if !strings.Contains(err.Error(), "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") { t.Fatalf("Wrong error %v, must be about chaining ONBUILD", err) } } else { t.Fatal("Error must not be nil") } logDone("build - onbuild forbidden chained") } func TestBuildOnBuildForbiddenFrom(t *testing.T) { name := "testbuildonbuildforbiddenfrom" defer deleteImages(name) _, err := buildImage(name, `FROM busybox ONBUILD FROM scratch`, true) if err != nil { if !strings.Contains(err.Error(), "FROM isn't allowed as an ONBUILD trigger") { t.Fatalf("Wrong error %v, must be about FROM forbidden", err) } } else { t.Fatal("Error must not be nil") } logDone("build - onbuild forbidden from") } func TestBuildOnBuildForbiddenMaintainer(t *testing.T) { name := "testbuildonbuildforbiddenmaintainer" defer deleteImages(name) _, err := buildImage(name, `FROM busybox ONBUILD MAINTAINER docker.io`, true) if err != nil { if !strings.Contains(err.Error(), "MAINTAINER isn't allowed as an ONBUILD trigger") { t.Fatalf("Wrong error %v, must be about MAINTAINER forbidden", err) } } else { t.Fatal("Error must not be nil") } logDone("build - onbuild forbidden maintainer") } // gh #2446 func TestBuildAddToSymlinkDest(t *testing.T) { name := "testbuildaddtosymlinkdest" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN mkdir /foo RUN ln -s /foo /bar ADD foo /bar/ RUN [ -f /bar/foo ] RUN [ -f /foo/foo ]`, map[string]string{ "foo": "hello", }) if err != nil { t.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { t.Fatal(err) } logDone("build - add to symlink destination") } func TestBuildEscapeWhitespace(t *testing.T) { name := "testbuildescaping" defer deleteImages(name) _, err := buildImage(name, ` FROM busybox MAINTAINER "Docker \ IO " `, true) res, err := inspectField(name, "Author") if err != nil { t.Fatal(err) } if res != "Docker IO " { t.Fatal("Parsed string did not match the escaped string") } logDone("build - validate escaping whitespace") } func TestDockerignore(t *testing.T) { name := "testbuilddockerignore" defer deleteImages(name) dockerfile := ` FROM busybox ADD . /bla RUN [[ -f /bla/src/x.go ]] RUN [[ -f /bla/Makefile ]] RUN [[ ! -e /bla/src/_vendor ]] RUN [[ ! -e /bla/.gitignore ]] RUN [[ ! -e /bla/README.md ]] RUN [[ ! -e /bla/.git ]]` ctx, err := fakeContext(dockerfile, map[string]string{ "Makefile": "all:", ".git/HEAD": "ref: foo", "src/x.go": "package main", "src/_vendor/v.go": "package main", ".gitignore": "", "README.md": "readme", ".dockerignore": ".git\npkg\n.gitignore\nsrc/_vendor\n*.md", }) defer ctx.Close() if err != nil { t.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { t.Fatal(err) } logDone("build - test .dockerignore") } func TestDockerignoringDockerfile(t *testing.T) { name := "testbuilddockerignoredockerfile" defer deleteImages(name) dockerfile := ` FROM scratch` ctx, err := fakeContext(dockerfile, map[string]string{ "Dockerfile": "FROM scratch", ".dockerignore": "Dockerfile\n", }) defer ctx.Close() if err != nil { t.Fatal(err) } if _, err = buildImageFromContext(name, ctx, true); err == nil { t.Fatalf("Didn't get expected error from ignoring Dockerfile") } logDone("build - test .dockerignore of Dockerfile") }