| package main |
| |
| import ( |
| "archive/tar" |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "reflect" |
| "regexp" |
| "runtime" |
| "strconv" |
| "strings" |
| "text/template" |
| "time" |
| |
| "github.com/docker/docker/builder/dockerfile/command" |
| "github.com/docker/docker/pkg/archive" |
| "github.com/docker/docker/pkg/integration/checker" |
| "github.com/docker/docker/pkg/stringutils" |
| "github.com/go-check/check" |
| ) |
| |
| func (s *DockerSuite) TestBuildJSONEmptyRun(c *check.C) { |
| name := "testbuildjsonemptyrun" |
| |
| _, err := buildImage( |
| name, |
| ` |
| FROM busybox |
| RUN [] |
| `, |
| true) |
| |
| if err != nil { |
| c.Fatal("error when dealing with a RUN statement with empty JSON array") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildShCmdJSONEntrypoint(c *check.C) { |
| name := "testbuildshcmdjsonentrypoint" |
| |
| _, err := buildImage( |
| name, |
| ` |
| FROM busybox |
| ENTRYPOINT ["echo"] |
| CMD echo test |
| `, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| out, _ := dockerCmd(c, "run", "--rm", name) |
| |
| if daemonPlatform == "windows" { |
| if !strings.Contains(out, "cmd /S /C echo test") { |
| c.Fatalf("CMD did not contain cmd /S /C echo test : %q", out) |
| } |
| } else { |
| if strings.TrimSpace(out) != "/bin/sh -c echo test" { |
| c.Fatalf("CMD did not contain /bin/sh -c : %q", out) |
| } |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildEnvironmentReplacementUser(c *check.C) { |
| // Windows does not support FROM scratch or the USER command |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildenvironmentreplacement" |
| |
| _, err := buildImage(name, ` |
| FROM scratch |
| ENV user foo |
| USER ${user} |
| `, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| res := inspectFieldJSON(c, name, "Config.User") |
| |
| if res != `"foo"` { |
| c.Fatal("User foo from environment not in Config.User on image") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildEnvironmentReplacementVolume(c *check.C) { |
| name := "testbuildenvironmentreplacement" |
| |
| var volumePath string |
| |
| if daemonPlatform == "windows" { |
| volumePath = "c:/quux" |
| } else { |
| volumePath = "/quux" |
| } |
| |
| _, err := buildImage(name, ` |
| FROM `+minimalBaseImage()+` |
| ENV volume `+volumePath+` |
| VOLUME ${volume} |
| `, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| res := inspectFieldJSON(c, name, "Config.Volumes") |
| |
| var volumes map[string]interface{} |
| |
| if err := json.Unmarshal([]byte(res), &volumes); err != nil { |
| c.Fatal(err) |
| } |
| |
| if _, ok := volumes[volumePath]; !ok { |
| c.Fatal("Volume " + volumePath + " from environment not in Config.Volumes on image") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildEnvironmentReplacementExpose(c *check.C) { |
| // Windows does not support FROM scratch or the EXPOSE command |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildenvironmentreplacement" |
| |
| _, err := buildImage(name, ` |
| FROM scratch |
| ENV port 80 |
| EXPOSE ${port} |
| ENV ports " 99 100 " |
| EXPOSE ${ports} |
| `, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| res := inspectFieldJSON(c, name, "Config.ExposedPorts") |
| |
| var exposedPorts map[string]interface{} |
| |
| if err := json.Unmarshal([]byte(res), &exposedPorts); err != nil { |
| c.Fatal(err) |
| } |
| |
| exp := []int{80, 99, 100} |
| |
| for _, p := range exp { |
| tmp := fmt.Sprintf("%d/tcp", p) |
| if _, ok := exposedPorts[tmp]; !ok { |
| c.Fatalf("Exposed port %d from environment not in Config.ExposedPorts on image", p) |
| } |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildEnvironmentReplacementWorkdir(c *check.C) { |
| name := "testbuildenvironmentreplacement" |
| |
| _, err := buildImage(name, ` |
| FROM busybox |
| ENV MYWORKDIR /work |
| RUN mkdir ${MYWORKDIR} |
| WORKDIR ${MYWORKDIR} |
| `, true) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildEnvironmentReplacementAddCopy(c *check.C) { |
| name := "testbuildenvironmentreplacement" |
| |
| ctx, err := fakeContext(` |
| FROM `+minimalBaseImage()+` |
| ENV baz foo |
| ENV quux bar |
| ENV dot . |
| ENV fee fff |
| ENV gee ggg |
| |
| ADD ${baz} ${dot} |
| COPY ${quux} ${dot} |
| ADD ${zzz:-${fee}} ${dot} |
| COPY ${zzz:-${gee}} ${dot} |
| `, |
| map[string]string{ |
| "foo": "test1", |
| "bar": "test2", |
| "fff": "test3", |
| "ggg": "test4", |
| }) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildEnvironmentReplacementEnv(c *check.C) { |
| // ENV expansions work differently in Windows |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildenvironmentreplacement" |
| |
| _, err := buildImage(name, |
| ` |
| FROM busybox |
| ENV foo zzz |
| ENV bar ${foo} |
| ENV abc1='$foo' |
| ENV env1=$foo env2=${foo} env3="$foo" env4="${foo}" |
| RUN [ "$abc1" = '$foo' ] && (echo "$abc1" | grep -q foo) |
| ENV abc2="\$foo" |
| RUN [ "$abc2" = '$foo' ] && (echo "$abc2" | grep -q foo) |
| ENV abc3 '$foo' |
| RUN [ "$abc3" = '$foo' ] && (echo "$abc3" | grep -q foo) |
| ENV abc4 "\$foo" |
| RUN [ "$abc4" = '$foo' ] && (echo "$abc4" | grep -q foo) |
| `, true) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| res := inspectFieldJSON(c, name, "Config.Env") |
| |
| envResult := []string{} |
| |
| if err = unmarshalJSON([]byte(res), &envResult); err != nil { |
| c.Fatal(err) |
| } |
| |
| found := false |
| envCount := 0 |
| |
| for _, env := range envResult { |
| parts := strings.SplitN(env, "=", 2) |
| if parts[0] == "bar" { |
| found = true |
| if parts[1] != "zzz" { |
| c.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", parts[1]) |
| } |
| } else if strings.HasPrefix(parts[0], "env") { |
| envCount++ |
| if parts[1] != "zzz" { |
| c.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) |
| } |
| } else if strings.HasPrefix(parts[0], "env") { |
| envCount++ |
| if parts[1] != "foo" { |
| c.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) |
| } |
| } |
| } |
| |
| if !found { |
| c.Fatal("Never found the `bar` env variable") |
| } |
| |
| if envCount != 4 { |
| c.Fatalf("Didn't find all env vars - only saw %d\n%s", envCount, envResult) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildHandleEscapes(c *check.C) { |
| // The volume paths used in this test are invalid on Windows |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildhandleescapes" |
| |
| _, err := buildImage(name, |
| ` |
| FROM scratch |
| ENV FOO bar |
| VOLUME ${FOO} |
| `, true) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| var result map[string]map[string]struct{} |
| |
| res := inspectFieldJSON(c, name, "Config.Volumes") |
| |
| if err = unmarshalJSON([]byte(res), &result); err != nil { |
| c.Fatal(err) |
| } |
| |
| if _, ok := result["bar"]; !ok { |
| c.Fatal("Could not find volume bar set from env foo in volumes table") |
| } |
| |
| deleteImages(name) |
| |
| _, err = buildImage(name, |
| ` |
| FROM scratch |
| ENV FOO bar |
| VOLUME \${FOO} |
| `, true) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| res = inspectFieldJSON(c, name, "Config.Volumes") |
| |
| if err = unmarshalJSON([]byte(res), &result); err != nil { |
| c.Fatal(err) |
| } |
| |
| if _, ok := result["${FOO}"]; !ok { |
| c.Fatal("Could not find volume ${FOO} set from env foo in volumes table") |
| } |
| |
| deleteImages(name) |
| |
| // this test in particular provides *7* backslashes and expects 6 to come back. |
| // Like above, the first escape is swallowed and the rest are treated as |
| // literals, this one is just less obvious because of all the character noise. |
| |
| _, err = buildImage(name, |
| ` |
| FROM scratch |
| ENV FOO bar |
| VOLUME \\\\\\\${FOO} |
| `, true) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| res = inspectFieldJSON(c, name, "Config.Volumes") |
| |
| if err = unmarshalJSON([]byte(res), &result); err != nil { |
| c.Fatal(err) |
| } |
| |
| if _, ok := result[`\\\${FOO}`]; !ok { |
| c.Fatal(`Could not find volume \\\${FOO} set from env foo in volumes table`, result) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildOnBuildLowercase(c *check.C) { |
| name := "testbuildonbuildlowercase" |
| name2 := "testbuildonbuildlowercase2" |
| |
| _, err := buildImage(name, |
| ` |
| FROM busybox |
| onbuild run echo quux |
| `, true) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| _, out, err := buildImageWithOut(name2, fmt.Sprintf(` |
| FROM %s |
| `, name), true) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| if !strings.Contains(out, "quux") { |
| c.Fatalf("Did not receive the expected echo text, got %s", out) |
| } |
| |
| if strings.Contains(out, "ONBUILD ONBUILD") { |
| c.Fatalf("Got an ONBUILD ONBUILD error with no error: got %s", out) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildEnvEscapes(c *check.C) { |
| // ENV expansions work differently in Windows |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildenvescapes" |
| _, err := buildImage(name, |
| ` |
| FROM busybox |
| ENV TEST foo |
| CMD echo \$ |
| `, |
| true) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| out, _ := dockerCmd(c, "run", "-t", name) |
| |
| if strings.TrimSpace(out) != "$" { |
| c.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out)) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildEnvOverwrite(c *check.C) { |
| // ENV expansions work differently in Windows |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildenvoverwrite" |
| |
| _, err := buildImage(name, |
| ` |
| FROM busybox |
| ENV TEST foo |
| CMD echo ${TEST} |
| `, |
| true) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| out, _ := dockerCmd(c, "run", "-e", "TEST=bar", "-t", name) |
| |
| if strings.TrimSpace(out) != "bar" { |
| c.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out)) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainerInSourceImage(c *check.C) { |
| name := "testbuildonbuildforbiddenmaintainerinsourceimage" |
| |
| out, _ := dockerCmd(c, "create", "busybox", "true") |
| |
| cleanedContainerID := strings.TrimSpace(out) |
| |
| dockerCmd(c, "commit", "--run", "{\"OnBuild\":[\"MAINTAINER docker.io\"]}", cleanedContainerID, "onbuild") |
| |
| _, err := buildImage(name, |
| `FROM onbuild`, |
| true) |
| if err != nil { |
| if !strings.Contains(err.Error(), "maintainer isn't allowed as an ONBUILD trigger") { |
| c.Fatalf("Wrong error %v, must be about MAINTAINER and ONBUILD in source image", err) |
| } |
| } else { |
| c.Fatal("Error must not be nil") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildOnBuildForbiddenFromInSourceImage(c *check.C) { |
| name := "testbuildonbuildforbiddenfrominsourceimage" |
| |
| out, _ := dockerCmd(c, "create", "busybox", "true") |
| |
| cleanedContainerID := strings.TrimSpace(out) |
| |
| dockerCmd(c, "commit", "--run", "{\"OnBuild\":[\"FROM busybox\"]}", cleanedContainerID, "onbuild") |
| |
| _, err := buildImage(name, |
| `FROM onbuild`, |
| true) |
| if err != nil { |
| if !strings.Contains(err.Error(), "from isn't allowed as an ONBUILD trigger") { |
| c.Fatalf("Wrong error %v, must be about FROM and ONBUILD in source image", err) |
| } |
| } else { |
| c.Fatal("Error must not be nil") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildOnBuildForbiddenChainedInSourceImage(c *check.C) { |
| name := "testbuildonbuildforbiddenchainedinsourceimage" |
| |
| out, _ := dockerCmd(c, "create", "busybox", "true") |
| |
| cleanedContainerID := strings.TrimSpace(out) |
| |
| dockerCmd(c, "commit", "--run", "{\"OnBuild\":[\"ONBUILD RUN ls\"]}", cleanedContainerID, "onbuild") |
| |
| _, err := buildImage(name, |
| `FROM onbuild`, |
| true) |
| if err != nil { |
| if !strings.Contains(err.Error(), "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") { |
| c.Fatalf("Wrong error %v, must be about chaining ONBUILD in source image", err) |
| } |
| } else { |
| c.Fatal("Error must not be nil") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildOnBuildCmdEntrypointJSON(c *check.C) { |
| name1 := "onbuildcmd" |
| name2 := "onbuildgenerated" |
| |
| _, err := buildImage(name1, ` |
| FROM busybox |
| ONBUILD CMD ["hello world"] |
| ONBUILD ENTRYPOINT ["echo"] |
| ONBUILD RUN ["true"]`, |
| false) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| _, err = buildImage(name2, fmt.Sprintf(`FROM %s`, name1), false) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| out, _ := dockerCmd(c, "run", name2) |
| |
| if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) { |
| c.Fatalf("did not get echo output from onbuild. Got: %q", out) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildOnBuildEntrypointJSON(c *check.C) { |
| name1 := "onbuildcmd" |
| name2 := "onbuildgenerated" |
| |
| _, err := buildImage(name1, ` |
| FROM busybox |
| ONBUILD ENTRYPOINT ["echo"]`, |
| false) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| _, err = buildImage(name2, fmt.Sprintf("FROM %s\nCMD [\"hello world\"]\n", name1), false) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| out, _ := dockerCmd(c, "run", name2) |
| |
| if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) { |
| c.Fatal("got malformed output from onbuild", out) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildCacheAdd(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Windows doesn't have httpserver image yet |
| name := "testbuildtwoimageswithadd" |
| server, err := fakeStorage(map[string]string{ |
| "robots.txt": "hello", |
| "index.html": "world", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer server.Close() |
| |
| if _, err := buildImage(name, |
| fmt.Sprintf(`FROM scratch |
| ADD %s/robots.txt /`, server.URL()), |
| true); err != nil { |
| c.Fatal(err) |
| } |
| if err != nil { |
| c.Fatal(err) |
| } |
| deleteImages(name) |
| _, out, err := buildImageWithOut(name, |
| fmt.Sprintf(`FROM scratch |
| ADD %s/index.html /`, server.URL()), |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if strings.Contains(out, "Using cache") { |
| c.Fatal("2nd build used cache on ADD, it shouldn't") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildLastModified(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Windows doesn't have httpserver image yet |
| name := "testbuildlastmodified" |
| |
| server, err := fakeStorage(map[string]string{ |
| "file": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer server.Close() |
| |
| var out, out2 string |
| |
| dFmt := `FROM busybox |
| ADD %s/file / |
| RUN ls -le /file` |
| |
| dockerfile := fmt.Sprintf(dFmt, server.URL()) |
| |
| if _, out, err = buildImageWithOut(name, dockerfile, false); err != nil { |
| c.Fatal(err) |
| } |
| |
| originMTime := regexp.MustCompile(`root.*/file.*\n`).FindString(out) |
| // Make sure our regexp is correct |
| if strings.Index(originMTime, "/file") < 0 { |
| c.Fatalf("Missing ls info on 'file':\n%s", out) |
| } |
| |
| // Build it again and make sure the mtime of the file didn't change. |
| // Wait a few seconds to make sure the time changed enough to notice |
| time.Sleep(2 * time.Second) |
| |
| if _, out2, err = buildImageWithOut(name, dockerfile, false); err != nil { |
| c.Fatal(err) |
| } |
| |
| newMTime := regexp.MustCompile(`root.*/file.*\n`).FindString(out2) |
| if newMTime != originMTime { |
| c.Fatalf("MTime changed:\nOrigin:%s\nNew:%s", originMTime, newMTime) |
| } |
| |
| // Now 'touch' the file and make sure the timestamp DID change this time |
| // Create a new fakeStorage instead of just using Add() to help windows |
| server, err = fakeStorage(map[string]string{ |
| "file": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer server.Close() |
| |
| dockerfile = fmt.Sprintf(dFmt, server.URL()) |
| |
| if _, out2, err = buildImageWithOut(name, dockerfile, false); err != nil { |
| c.Fatal(err) |
| } |
| |
| newMTime = regexp.MustCompile(`root.*/file.*\n`).FindString(out2) |
| if newMTime == originMTime { |
| c.Fatalf("MTime didn't change:\nOrigin:%s\nNew:%s", originMTime, newMTime) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildSixtySteps(c *check.C) { |
| testRequires(c, DaemonIsLinux) // TODO Windows: This test passes on Windows, |
| // but currently adds a disproportionate amount of time for the value it has. |
| // Removing it from Windows CI for now, but this will be revisited in the |
| // TP5 timeframe when perf is better. |
| name := "foobuildsixtysteps" |
| |
| ctx, err := fakeContext("FROM "+minimalBaseImage()+"\n"+strings.Repeat("ADD foo /\n", 60), |
| map[string]string{ |
| "foo": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddSingleFileToRoot(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testaddimg" |
| ctx, err := fakeContext(fmt.Sprintf(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN touch /exists |
| RUN chown dockerio.dockerio /exists |
| ADD test_file / |
| RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /test_file | awk '{print $1}') = '%s' ] |
| RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod), |
| map[string]string{ |
| "test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| // Issue #3960: "ADD src ." hangs |
| func (s *DockerSuite) TestBuildAddSingleFileToWorkdir(c *check.C) { |
| name := "testaddsinglefiletoworkdir" |
| ctx, err := fakeContext(`FROM busybox |
| ADD test_file .`, |
| map[string]string{ |
| "test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| errChan := make(chan error) |
| go func() { |
| _, err := buildImageFromContext(name, ctx, true) |
| errChan <- err |
| close(errChan) |
| }() |
| select { |
| case <-time.After(15 * time.Second): |
| c.Fatal("Build with adding to workdir timed out") |
| case err := <-errChan: |
| c.Assert(err, check.IsNil) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddSingleFileToExistDir(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testaddsinglefiletoexistdir" |
| ctx, err := fakeContext(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN mkdir /exists |
| RUN touch /exists/exists_file |
| RUN chown -R dockerio.dockerio /exists |
| ADD test_file /exists/ |
| RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] |
| RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, |
| map[string]string{ |
| "test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCopyAddMultipleFiles(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| server, err := fakeStorage(map[string]string{ |
| "robots.txt": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer server.Close() |
| |
| name := "testcopymultiplefilestofile" |
| ctx, err := fakeContext(fmt.Sprintf(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN mkdir /exists |
| RUN touch /exists/exists_file |
| RUN chown -R dockerio.dockerio /exists |
| COPY test_file1 test_file2 /exists/ |
| ADD test_file3 test_file4 %s/robots.txt /exists/ |
| RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] |
| RUN [ $(ls -l /exists/test_file1 | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /exists/test_file2 | awk '{print $3":"$4}') = 'root:root' ] |
| |
| RUN [ $(ls -l /exists/test_file3 | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /exists/test_file4 | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /exists/robots.txt | awk '{print $3":"$4}') = 'root:root' ] |
| |
| RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ] |
| `, server.URL()), |
| map[string]string{ |
| "test_file1": "test1", |
| "test_file2": "test2", |
| "test_file3": "test3", |
| "test_file4": "test4", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| // This test is mainly for user namespaces to verify that new directories |
| // are created as the remapped root uid/gid pair |
| func (s *DockerSuite) TestBuildAddToNewDestination(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testaddtonewdest" |
| ctx, err := fakeContext(`FROM busybox |
| ADD . /new_dir |
| RUN ls -l / |
| RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'root:root' ]`, |
| map[string]string{ |
| "test_dir/test_file": "test file", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| // This test is mainly for user namespaces to verify that new directories |
| // are created as the remapped root uid/gid pair |
| func (s *DockerSuite) TestBuildCopyToNewParentDirectory(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testcopytonewdir" |
| ctx, err := fakeContext(`FROM busybox |
| COPY test_dir /new_dir |
| RUN ls -l /new_dir |
| RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'root:root' ]`, |
| map[string]string{ |
| "test_dir/test_file": "test file", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| // This test is mainly for user namespaces to verify that new directories |
| // are created as the remapped root uid/gid pair |
| func (s *DockerSuite) TestBuildWorkdirIsContainerRoot(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testworkdirownership" |
| if _, err := buildImage(name, `FROM busybox |
| WORKDIR /new_dir |
| RUN ls -l / |
| RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'root:root' ]`, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddFileWithWhitespace(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Not currently passing on Windows |
| name := "testaddfilewithwhitespace" |
| ctx, err := fakeContext(`FROM busybox |
| RUN mkdir "/test dir" |
| RUN mkdir "/test_dir" |
| ADD [ "test file1", "/test_file1" ] |
| ADD [ "test_file2", "/test file2" ] |
| ADD [ "test file3", "/test file3" ] |
| ADD [ "test dir/test_file4", "/test_dir/test_file4" ] |
| ADD [ "test_dir/test_file5", "/test dir/test_file5" ] |
| ADD [ "test dir/test_file6", "/test dir/test_file6" ] |
| RUN [ $(cat "/test_file1") = 'test1' ] |
| RUN [ $(cat "/test file2") = 'test2' ] |
| RUN [ $(cat "/test file3") = 'test3' ] |
| RUN [ $(cat "/test_dir/test_file4") = 'test4' ] |
| RUN [ $(cat "/test dir/test_file5") = 'test5' ] |
| RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, |
| map[string]string{ |
| "test file1": "test1", |
| "test_file2": "test2", |
| "test file3": "test3", |
| "test dir/test_file4": "test4", |
| "test_dir/test_file5": "test5", |
| "test dir/test_file6": "test6", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCopyFileWithWhitespace(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Not currently passing on Windows |
| name := "testcopyfilewithwhitespace" |
| ctx, err := fakeContext(`FROM busybox |
| RUN mkdir "/test dir" |
| RUN mkdir "/test_dir" |
| COPY [ "test file1", "/test_file1" ] |
| COPY [ "test_file2", "/test file2" ] |
| COPY [ "test file3", "/test file3" ] |
| COPY [ "test dir/test_file4", "/test_dir/test_file4" ] |
| COPY [ "test_dir/test_file5", "/test dir/test_file5" ] |
| COPY [ "test dir/test_file6", "/test dir/test_file6" ] |
| RUN [ $(cat "/test_file1") = 'test1' ] |
| RUN [ $(cat "/test file2") = 'test2' ] |
| RUN [ $(cat "/test file3") = 'test3' ] |
| RUN [ $(cat "/test_dir/test_file4") = 'test4' ] |
| RUN [ $(cat "/test dir/test_file5") = 'test5' ] |
| RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, |
| map[string]string{ |
| "test file1": "test1", |
| "test_file2": "test2", |
| "test file3": "test3", |
| "test dir/test_file4": "test4", |
| "test_dir/test_file5": "test5", |
| "test dir/test_file6": "test6", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCopyWildcard(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Windows doesn't have httpserver image yet |
| name := "testcopywildcard" |
| server, err := fakeStorage(map[string]string{ |
| "robots.txt": "hello", |
| "index.html": "world", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer server.Close() |
| |
| ctx, err := fakeContext(fmt.Sprintf(`FROM busybox |
| COPY file*.txt /tmp/ |
| RUN ls /tmp/file1.txt /tmp/file2.txt |
| RUN mkdir /tmp1 |
| COPY dir* /tmp1/ |
| RUN ls /tmp1/dirt /tmp1/nested_file /tmp1/nested_dir/nest_nest_file |
| RUN mkdir /tmp2 |
| ADD dir/*dir %s/robots.txt /tmp2/ |
| RUN ls /tmp2/nest_nest_file /tmp2/robots.txt |
| `, server.URL()), |
| map[string]string{ |
| "file1.txt": "test1", |
| "file2.txt": "test2", |
| "dir/nested_file": "nested file", |
| "dir/nested_dir/nest_nest_file": "2 times nested", |
| "dirt": "dirty", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| id1, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| // Now make sure we use a cache the 2nd time |
| id2, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| if id1 != id2 { |
| c.Fatal("didn't use the cache") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildCopyWildcardInName(c *check.C) { |
| name := "testcopywildcardinname" |
| ctx, err := fakeContext(`FROM busybox |
| COPY *.txt /tmp/ |
| RUN [ "$(cat /tmp/\*.txt)" = 'hi there' ] |
| `, map[string]string{"*.txt": "hi there"}) |
| |
| if err != nil { |
| // Normally we would do c.Fatal(err) here but given that |
| // the odds of this failing are so rare, it must be because |
| // the OS we're running the client on doesn't support * in |
| // filenames (like windows). So, instead of failing the test |
| // just let it pass. Then we don't need to explicitly |
| // say which OSs this works on or not. |
| return |
| } |
| defer ctx.Close() |
| |
| _, err = buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatalf("should have built: %q", err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCopyWildcardCache(c *check.C) { |
| name := "testcopywildcardcache" |
| ctx, err := fakeContext(`FROM busybox |
| COPY file1.txt /tmp/`, |
| map[string]string{ |
| "file1.txt": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| id1, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| // Now make sure we use a cache the 2nd time even with wild cards. |
| // Use the same context so the file is the same and the checksum will match |
| ctx.Add("Dockerfile", `FROM busybox |
| COPY file*.txt /tmp/`) |
| |
| id2, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| if id1 != id2 { |
| c.Fatal("didn't use the cache") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildAddSingleFileToNonExistingDir(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testaddsinglefiletononexistingdir" |
| ctx, err := fakeContext(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN touch /exists |
| RUN chown dockerio.dockerio /exists |
| ADD test_file /test_dir/ |
| RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, |
| map[string]string{ |
| "test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildAddDirContentToRoot(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testadddircontenttoroot" |
| ctx, err := fakeContext(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN touch /exists |
| RUN chown dockerio.dockerio exists |
| ADD test_dir / |
| RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, |
| map[string]string{ |
| "test_dir/test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddDirContentToExistingDir(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testadddircontenttoexistingdir" |
| ctx, err := fakeContext(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN mkdir /exists |
| RUN touch /exists/exists_file |
| RUN chown -R dockerio.dockerio /exists |
| ADD test_dir/ /exists/ |
| RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] |
| RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ] |
| RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`, |
| map[string]string{ |
| "test_dir/test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddWholeDirToRoot(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testaddwholedirtoroot" |
| ctx, err := fakeContext(fmt.Sprintf(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN touch /exists |
| RUN chown dockerio.dockerio exists |
| ADD test_dir /test_dir |
| RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l / | grep test_dir | awk '{print $1}') = 'drwxr-xr-x' ] |
| RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '%s' ] |
| RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod), |
| map[string]string{ |
| "test_dir/test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| // Testing #5941 |
| func (s *DockerSuite) TestBuildAddEtcToRoot(c *check.C) { |
| name := "testaddetctoroot" |
| |
| ctx, err := fakeContext(`FROM `+minimalBaseImage()+` |
| ADD . /`, |
| map[string]string{ |
| "etc/test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| // Testing #9401 |
| func (s *DockerSuite) TestBuildAddPreservesFilesSpecialBits(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testaddpreservesfilesspecialbits" |
| ctx, err := fakeContext(`FROM busybox |
| ADD suidbin /usr/bin/suidbin |
| RUN chmod 4755 /usr/bin/suidbin |
| RUN [ $(ls -l /usr/bin/suidbin | awk '{print $1}') = '-rwsr-xr-x' ] |
| ADD ./data/ / |
| RUN [ $(ls -l /usr/bin/suidbin | awk '{print $1}') = '-rwsr-xr-x' ]`, |
| map[string]string{ |
| "suidbin": "suidbin", |
| "/data/usr/test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCopySingleFileToRoot(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testcopysinglefiletoroot" |
| ctx, err := fakeContext(fmt.Sprintf(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN touch /exists |
| RUN chown dockerio.dockerio /exists |
| COPY test_file / |
| RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /test_file | awk '{print $1}') = '%s' ] |
| RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod), |
| map[string]string{ |
| "test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| // Issue #3960: "ADD src ." hangs - adapted for COPY |
| func (s *DockerSuite) TestBuildCopySingleFileToWorkdir(c *check.C) { |
| name := "testcopysinglefiletoworkdir" |
| ctx, err := fakeContext(`FROM busybox |
| COPY test_file .`, |
| map[string]string{ |
| "test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| errChan := make(chan error) |
| go func() { |
| _, err := buildImageFromContext(name, ctx, true) |
| errChan <- err |
| close(errChan) |
| }() |
| select { |
| case <-time.After(15 * time.Second): |
| c.Fatal("Build with adding to workdir timed out") |
| case err := <-errChan: |
| c.Assert(err, check.IsNil) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCopySingleFileToExistDir(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testcopysinglefiletoexistdir" |
| ctx, err := fakeContext(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN mkdir /exists |
| RUN touch /exists/exists_file |
| RUN chown -R dockerio.dockerio /exists |
| COPY test_file /exists/ |
| RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] |
| RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, |
| map[string]string{ |
| "test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCopySingleFileToNonExistDir(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testcopysinglefiletononexistdir" |
| ctx, err := fakeContext(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN touch /exists |
| RUN chown dockerio.dockerio /exists |
| COPY test_file /test_dir/ |
| RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, |
| map[string]string{ |
| "test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCopyDirContentToRoot(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testcopydircontenttoroot" |
| ctx, err := fakeContext(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN touch /exists |
| RUN chown dockerio.dockerio exists |
| COPY test_dir / |
| RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, |
| map[string]string{ |
| "test_dir/test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCopyDirContentToExistDir(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testcopydircontenttoexistdir" |
| ctx, err := fakeContext(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN mkdir /exists |
| RUN touch /exists/exists_file |
| RUN chown -R dockerio.dockerio /exists |
| COPY test_dir/ /exists/ |
| RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] |
| RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ] |
| RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`, |
| map[string]string{ |
| "test_dir/test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCopyWholeDirToRoot(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Linux specific test |
| name := "testcopywholedirtoroot" |
| ctx, err := fakeContext(fmt.Sprintf(`FROM busybox |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
| RUN echo 'dockerio:x:1001:' >> /etc/group |
| RUN touch /exists |
| RUN chown dockerio.dockerio exists |
| COPY test_dir /test_dir |
| RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l / | grep test_dir | awk '{print $1}') = 'drwxr-xr-x' ] |
| RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] |
| RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '%s' ] |
| RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod), |
| map[string]string{ |
| "test_dir/test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCopyEtcToRoot(c *check.C) { |
| name := "testcopyetctoroot" |
| |
| ctx, err := fakeContext(`FROM `+minimalBaseImage()+` |
| COPY . /`, |
| map[string]string{ |
| "etc/test_file": "test1", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddBadLinks(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Not currently working on Windows |
| |
| dockerfile := ` |
| FROM scratch |
| ADD links.tar / |
| ADD foo.txt /symlink/ |
| ` |
| targetFile := "foo.txt" |
| var ( |
| name = "test-link-absolute" |
| ) |
| ctx, err := fakeContext(dockerfile, nil) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| tempDir, err := ioutil.TempDir("", "test-link-absolute-temp-") |
| if err != nil { |
| c.Fatalf("failed to create temporary directory: %s", tempDir) |
| } |
| defer os.RemoveAll(tempDir) |
| |
| var symlinkTarget string |
| if runtime.GOOS == "windows" { |
| var driveLetter string |
| if abs, err := filepath.Abs(tempDir); err != nil { |
| c.Fatal(err) |
| } else { |
| driveLetter = abs[:1] |
| } |
| tempDirWithoutDrive := tempDir[2:] |
| symlinkTarget = fmt.Sprintf(`%s:\..\..\..\..\..\..\..\..\..\..\..\..%s`, driveLetter, tempDirWithoutDrive) |
| } else { |
| symlinkTarget = fmt.Sprintf("/../../../../../../../../../../../..%s", tempDir) |
| } |
| |
| tarPath := filepath.Join(ctx.Dir, "links.tar") |
| nonExistingFile := filepath.Join(tempDir, targetFile) |
| fooPath := filepath.Join(ctx.Dir, targetFile) |
| |
| tarOut, err := os.Create(tarPath) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| tarWriter := tar.NewWriter(tarOut) |
| |
| header := &tar.Header{ |
| Name: "symlink", |
| Typeflag: tar.TypeSymlink, |
| Linkname: symlinkTarget, |
| Mode: 0755, |
| Uid: 0, |
| Gid: 0, |
| } |
| |
| err = tarWriter.WriteHeader(header) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| tarWriter.Close() |
| tarOut.Close() |
| |
| foo, err := os.Create(fooPath) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer foo.Close() |
| |
| if _, err := foo.WriteString("test"); err != nil { |
| c.Fatal(err) |
| } |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| |
| if _, err := os.Stat(nonExistingFile); err == nil || err != nil && !os.IsNotExist(err) { |
| c.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildAddBadLinksVolume(c *check.C) { |
| testRequires(c, DaemonIsLinux) // ln not implemented on Windows busybox |
| const ( |
| dockerfileTemplate = ` |
| FROM busybox |
| RUN ln -s /../../../../../../../../%s /x |
| VOLUME /x |
| ADD foo.txt /x/` |
| targetFile = "foo.txt" |
| ) |
| var ( |
| name = "test-link-absolute-volume" |
| dockerfile = "" |
| ) |
| |
| tempDir, err := ioutil.TempDir("", "test-link-absolute-volume-temp-") |
| if err != nil { |
| c.Fatalf("failed to create temporary directory: %s", tempDir) |
| } |
| defer os.RemoveAll(tempDir) |
| |
| dockerfile = fmt.Sprintf(dockerfileTemplate, tempDir) |
| nonExistingFile := filepath.Join(tempDir, targetFile) |
| |
| ctx, err := fakeContext(dockerfile, nil) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| fooPath := filepath.Join(ctx.Dir, targetFile) |
| |
| foo, err := os.Create(fooPath) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer foo.Close() |
| |
| if _, err := foo.WriteString("test"); err != nil { |
| c.Fatal(err) |
| } |
| |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| |
| if _, err := os.Stat(nonExistingFile); err == nil || err != nil && !os.IsNotExist(err) { |
| c.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile) |
| } |
| |
| } |
| |
| // Issue #5270 - ensure we throw a better error than "unexpected EOF" |
| // when we can't access files in the context. |
| func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) { |
| testRequires(c, DaemonIsLinux, UnixCli) // test uses chown/chmod: not available on windows |
| |
| { |
| name := "testbuildinaccessiblefiles" |
| ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{"fileWithoutReadAccess": "foo"}) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| // This is used to ensure we detect inaccessible files early during build in the cli client |
| pathToFileWithoutReadAccess := filepath.Join(ctx.Dir, "fileWithoutReadAccess") |
| |
| if err = os.Chown(pathToFileWithoutReadAccess, 0, 0); err != nil { |
| c.Fatalf("failed to chown file to root: %s", err) |
| } |
| if err = os.Chmod(pathToFileWithoutReadAccess, 0700); err != nil { |
| c.Fatalf("failed to chmod file to 700: %s", err) |
| } |
| buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)) |
| buildCmd.Dir = ctx.Dir |
| out, _, err := runCommandWithOutput(buildCmd) |
| if err == nil { |
| c.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 ") { |
| c.Fatalf("output should've contained the string: no permission to read from but contained: %s", out) |
| } |
| |
| if !strings.Contains(out, "Error checking context") { |
| c.Fatalf("output should've contained the string: Error checking context") |
| } |
| } |
| { |
| name := "testbuildinaccessibledirectory" |
| ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{"directoryWeCantStat/bar": "foo"}) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| // This is used to ensure we detect inaccessible directories early during build in the cli client |
| pathToDirectoryWithoutReadAccess := filepath.Join(ctx.Dir, "directoryWeCantStat") |
| pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar") |
| |
| if err = os.Chown(pathToDirectoryWithoutReadAccess, 0, 0); err != nil { |
| c.Fatalf("failed to chown directory to root: %s", err) |
| } |
| if err = os.Chmod(pathToDirectoryWithoutReadAccess, 0444); err != nil { |
| c.Fatalf("failed to chmod directory to 444: %s", err) |
| } |
| if err = os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0700); err != nil { |
| c.Fatalf("failed to chmod file to 700: %s", err) |
| } |
| |
| buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)) |
| buildCmd.Dir = ctx.Dir |
| out, _, err := runCommandWithOutput(buildCmd) |
| if err == nil { |
| c.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") { |
| c.Fatalf("output should've contained the string: can't access %s", out) |
| } |
| |
| if !strings.Contains(out, "Error checking context") { |
| c.Fatalf("output should've contained the string: Error checking context\ngot:%s", out) |
| } |
| |
| } |
| { |
| name := "testlinksok" |
| ctx, err := fakeContext("FROM scratch\nADD . /foo/", nil) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| target := "../../../../../../../../../../../../../../../../../../../azA" |
| if err := os.Symlink(filepath.Join(ctx.Dir, "g"), target); err != nil { |
| c.Fatal(err) |
| } |
| defer os.Remove(target) |
| // 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 |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| { |
| name := "testbuildignoredinaccessible" |
| ctx, err := fakeContext("FROM scratch\nADD . /foo/", |
| map[string]string{ |
| "directoryWeCantStat/bar": "foo", |
| ".dockerignore": "directoryWeCantStat", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| // This is used to ensure we don't try to add inaccessible files when they are ignored by a .dockerignore pattern |
| pathToDirectoryWithoutReadAccess := filepath.Join(ctx.Dir, "directoryWeCantStat") |
| pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar") |
| if err = os.Chown(pathToDirectoryWithoutReadAccess, 0, 0); err != nil { |
| c.Fatalf("failed to chown directory to root: %s", err) |
| } |
| if err = os.Chmod(pathToDirectoryWithoutReadAccess, 0444); err != nil { |
| c.Fatalf("failed to chmod directory to 755: %s", err) |
| } |
| if err = os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0700); err != nil { |
| c.Fatalf("failed to chmod file to 444: %s", err) |
| } |
| |
| buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)) |
| buildCmd.Dir = ctx.Dir |
| if out, _, err := runCommandWithOutput(buildCmd); err != nil { |
| c.Fatalf("build should have worked: %s %s", err, out) |
| } |
| |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildForceRm(c *check.C) { |
| containerCountBefore, err := getContainerCount() |
| if err != nil { |
| c.Fatalf("failed to get the container count: %s", err) |
| } |
| name := "testbuildforcerm" |
| |
| ctx, err := fakeContext(`FROM `+minimalBaseImage()+` |
| RUN true |
| RUN thiswillfail`, nil) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| dockerCmdInDir(c, ctx.Dir, "build", "-t", name, "--force-rm", ".") |
| |
| containerCountAfter, err := getContainerCount() |
| if err != nil { |
| c.Fatalf("failed to get the container count: %s", err) |
| } |
| |
| if containerCountBefore != containerCountAfter { |
| c.Fatalf("--force-rm shouldn't have left containers behind") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildRm(c *check.C) { |
| name := "testbuildrm" |
| |
| ctx, err := fakeContext(`FROM `+minimalBaseImage()+` |
| ADD foo / |
| ADD foo /`, map[string]string{"foo": "bar"}) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| { |
| containerCountBefore, err := getContainerCount() |
| if err != nil { |
| c.Fatalf("failed to get the container count: %s", err) |
| } |
| |
| out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "--rm", "-t", name, ".") |
| |
| if err != nil { |
| c.Fatal("failed to build the image", out) |
| } |
| |
| containerCountAfter, err := getContainerCount() |
| if err != nil { |
| c.Fatalf("failed to get the container count: %s", err) |
| } |
| |
| if containerCountBefore != containerCountAfter { |
| c.Fatalf("-rm shouldn't have left containers behind") |
| } |
| deleteImages(name) |
| } |
| |
| { |
| containerCountBefore, err := getContainerCount() |
| if err != nil { |
| c.Fatalf("failed to get the container count: %s", err) |
| } |
| |
| out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", name, ".") |
| |
| if err != nil { |
| c.Fatal("failed to build the image", out) |
| } |
| |
| containerCountAfter, err := getContainerCount() |
| if err != nil { |
| c.Fatalf("failed to get the container count: %s", err) |
| } |
| |
| if containerCountBefore != containerCountAfter { |
| c.Fatalf("--rm shouldn't have left containers behind") |
| } |
| deleteImages(name) |
| } |
| |
| { |
| containerCountBefore, err := getContainerCount() |
| if err != nil { |
| c.Fatalf("failed to get the container count: %s", err) |
| } |
| |
| out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "--rm=false", "-t", name, ".") |
| |
| if err != nil { |
| c.Fatal("failed to build the image", out) |
| } |
| |
| containerCountAfter, err := getContainerCount() |
| if err != nil { |
| c.Fatalf("failed to get the container count: %s", err) |
| } |
| |
| if containerCountBefore == containerCountAfter { |
| c.Fatalf("--rm=false should have left containers behind") |
| } |
| deleteImages(name) |
| |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildWithVolumes(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Invalid volume paths on Windows |
| var ( |
| result map[string]map[string]struct{} |
| name = "testbuildvolumes" |
| emptyMap = make(map[string]struct{}) |
| expected = map[string]map[string]struct{}{ |
| "/test1": emptyMap, |
| "/test2": emptyMap, |
| "/test3": emptyMap, |
| "/test4": emptyMap, |
| "/test5": emptyMap, |
| "/test6": emptyMap, |
| "[/test7": emptyMap, |
| "/test8]": emptyMap, |
| } |
| ) |
| _, err := buildImage(name, |
| `FROM scratch |
| VOLUME /test1 |
| VOLUME /test2 |
| VOLUME /test3 /test4 |
| VOLUME ["/test5", "/test6"] |
| VOLUME [/test7 /test8] |
| `, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| res := inspectFieldJSON(c, name, "Config.Volumes") |
| |
| err = unmarshalJSON([]byte(res), &result) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| equal := reflect.DeepEqual(&result, &expected) |
| |
| if !equal { |
| c.Fatalf("Volumes %s, expected %s", result, expected) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildMaintainer(c *check.C) { |
| name := "testbuildmaintainer" |
| |
| expected := "dockerio" |
| _, err := buildImage(name, |
| `FROM `+minimalBaseImage()+` |
| MAINTAINER dockerio`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| res := inspectField(c, name, "Author") |
| if res != expected { |
| c.Fatalf("Maintainer %s, expected %s", res, expected) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildUser(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| name := "testbuilduser" |
| expected := "dockerio" |
| _, 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 { |
| c.Fatal(err) |
| } |
| res := inspectField(c, name, "Config.User") |
| if res != expected { |
| c.Fatalf("User %s, expected %s", res, expected) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildRelativeWorkdir(c *check.C) { |
| name := "testbuildrelativeworkdir" |
| |
| var ( |
| expected1 string |
| expected2 string |
| expected3 string |
| expected4 string |
| expectedFinal string |
| ) |
| |
| if daemonPlatform == "windows" { |
| expected1 = `C:/` |
| expected2 = `C:/test1` |
| expected3 = `C:/test2` |
| expected4 = `C:/test2/test3` |
| expectedFinal = `C:\test2\test3` // Note inspect is going to return Windows paths, as it's not in busybox |
| } else { |
| expected1 = `/` |
| expected2 = `/test1` |
| expected3 = `/test2` |
| expected4 = `/test2/test3` |
| expectedFinal = `/test2/test3` |
| } |
| |
| _, err := buildImage(name, |
| `FROM busybox |
| RUN sh -c "[ "$PWD" = "`+expected1+`" ]" |
| WORKDIR test1 |
| RUN sh -c "[ "$PWD" = "`+expected2+`" ]" |
| WORKDIR /test2 |
| RUN sh -c "[ "$PWD" = "`+expected3+`" ]" |
| WORKDIR test3 |
| RUN sh -c "[ "$PWD" = "`+expected4+`" ]"`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| res := inspectField(c, name, "Config.WorkingDir") |
| if res != expectedFinal { |
| c.Fatalf("Workdir %s, expected %s", res, expectedFinal) |
| } |
| } |
| |
| // #22181 Regression test. Single end-to-end test of using |
| // Windows semantics. Most path handling verifications are in unit tests |
| func (s *DockerSuite) TestBuildWindowsWorkdirProcessing(c *check.C) { |
| testRequires(c, DaemonIsWindows) |
| name := "testbuildwindowsworkdirprocessing" |
| _, err := buildImage(name, |
| `FROM busybox |
| WORKDIR C:\\foo |
| WORKDIR bar |
| RUN sh -c "[ "$PWD" = "C:/foo/bar" ]" |
| `, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| // #22181 Regression test. Most paths handling verifications are in unit test. |
| // One functional test for end-to-end |
| func (s *DockerSuite) TestBuildWindowsAddCopyPathProcessing(c *check.C) { |
| testRequires(c, DaemonIsWindows) |
| name := "testbuildwindowsaddcopypathprocessing" |
| // TODO Windows (@jhowardmsft). Needs a follow-up PR to 22181 to |
| // support backslash such as .\\ being equivalent to ./ and c:\\ being |
| // equivalent to c:/. This is not currently (nor ever has been) supported |
| // by docker on the Windows platform. |
| dockerfile := ` |
| FROM busybox |
| # No trailing slash on COPY/ADD |
| # Results in dir being changed to a file |
| WORKDIR /wc1 |
| COPY wc1 c:/wc1 |
| WORKDIR /wc2 |
| ADD wc2 c:/wc2 |
| WORKDIR c:/ |
| RUN sh -c "[ $(cat c:/wc1) = 'hellowc1' ]" |
| RUN sh -c "[ $(cat c:/wc2) = 'worldwc2' ]" |
| |
| # Trailing slash on COPY/ADD, Windows-style path. |
| WORKDIR /wd1 |
| COPY wd1 c:/wd1/ |
| WORKDIR /wd2 |
| ADD wd2 c:/wd2/ |
| RUN sh -c "[ $(cat c:/wd1/wd1) = 'hellowd1' ]" |
| RUN sh -c "[ $(cat c:/wd2/wd2) = 'worldwd2' ]" |
| ` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "wc1": "hellowc1", |
| "wc2": "worldwc2", |
| "wd1": "hellowd1", |
| "wd2": "worldwd2", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| _, err = buildImageFromContext(name, ctx, false) |
| if err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildWorkdirWithEnvVariables(c *check.C) { |
| name := "testbuildworkdirwithenvvariables" |
| |
| var expected string |
| if daemonPlatform == "windows" { |
| expected = `C:\test1\test2` |
| } else { |
| expected = `/test1/test2` |
| } |
| |
| _, err := buildImage(name, |
| `FROM busybox |
| ENV DIRPATH /test1 |
| ENV SUBDIRNAME test2 |
| WORKDIR $DIRPATH |
| WORKDIR $SUBDIRNAME/$MISSING_VAR`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| res := inspectField(c, name, "Config.WorkingDir") |
| if res != expected { |
| c.Fatalf("Workdir %s, expected %s", res, expected) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildRelativeCopy(c *check.C) { |
| // cat /test1/test2/foo gets permission denied for the user |
| testRequires(c, NotUserNamespace) |
| |
| var expected string |
| if daemonPlatform == "windows" { |
| expected = `C:/test1/test2` |
| } else { |
| expected = `/test1/test2` |
| } |
| |
| name := "testbuildrelativecopy" |
| dockerfile := ` |
| FROM busybox |
| WORKDIR /test1 |
| WORKDIR test2 |
| RUN sh -c "[ "$PWD" = '` + expected + `' ]" |
| COPY foo ./ |
| RUN sh -c "[ $(cat /test1/test2/foo) = 'hello' ]" |
| ADD foo ./bar/baz |
| RUN sh -c "[ $(cat /test1/test2/bar/baz) = 'hello' ]" |
| COPY foo ./bar/baz2 |
| RUN sh -c "[ $(cat /test1/test2/bar/baz2) = 'hello' ]" |
| WORKDIR .. |
| COPY foo ./ |
| RUN sh -c "[ $(cat /test1/foo) = 'hello' ]" |
| COPY foo /test3/ |
| RUN sh -c "[ $(cat /test3/foo) = 'hello' ]" |
| WORKDIR /test4 |
| COPY . . |
| RUN sh -c "[ $(cat /test4/foo) = 'hello' ]" |
| WORKDIR /test5/test6 |
| COPY foo ../ |
| RUN sh -c "[ $(cat /test5/foo) = 'hello' ]" |
| ` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "foo": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| _, err = buildImageFromContext(name, ctx, false) |
| if err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildEnv(c *check.C) { |
| testRequires(c, DaemonIsLinux) // ENV expansion is different in Windows |
| name := "testbuildenv" |
| expected := "[PATH=/test:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=2375]" |
| _, err := buildImage(name, |
| `FROM busybox |
| ENV PATH /test:$PATH |
| ENV PORT 2375 |
| RUN [ $(env | grep PORT) = 'PORT=2375' ]`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| res := inspectField(c, name, "Config.Env") |
| if res != expected { |
| c.Fatalf("Env %s, expected %s", res, expected) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildPATH(c *check.C) { |
| testRequires(c, DaemonIsLinux) // ENV expansion is different in Windows |
| |
| defPath := "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" |
| |
| fn := func(dockerfile string, exp string) { |
| _, err := buildImage("testbldpath", dockerfile, true) |
| c.Assert(err, check.IsNil) |
| |
| res := inspectField(c, "testbldpath", "Config.Env") |
| |
| if res != exp { |
| c.Fatalf("Env %q, expected %q for dockerfile:%q", res, exp, dockerfile) |
| } |
| } |
| |
| tests := []struct{ dockerfile, exp string }{ |
| {"FROM scratch\nMAINTAINER me", "[PATH=" + defPath + "]"}, |
| {"FROM busybox\nMAINTAINER me", "[PATH=" + defPath + "]"}, |
| {"FROM scratch\nENV FOO=bar", "[PATH=" + defPath + " FOO=bar]"}, |
| {"FROM busybox\nENV FOO=bar", "[PATH=" + defPath + " FOO=bar]"}, |
| {"FROM scratch\nENV PATH=/test", "[PATH=/test]"}, |
| {"FROM busybox\nENV PATH=/test", "[PATH=/test]"}, |
| {"FROM scratch\nENV PATH=''", "[PATH=]"}, |
| {"FROM busybox\nENV PATH=''", "[PATH=]"}, |
| } |
| |
| for _, test := range tests { |
| fn(test.dockerfile, test.exp) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildContextCleanup(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| testRequires(c, SameHostDaemon) |
| |
| name := "testbuildcontextcleanup" |
| entries, err := ioutil.ReadDir(filepath.Join(dockerBasePath, "tmp")) |
| if err != nil { |
| c.Fatalf("failed to list contents of tmp dir: %s", err) |
| } |
| _, err = buildImage(name, |
| `FROM scratch |
| ENTRYPOINT ["/bin/echo"]`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| entriesFinal, err := ioutil.ReadDir(filepath.Join(dockerBasePath, "tmp")) |
| if err != nil { |
| c.Fatalf("failed to list contents of tmp dir: %s", err) |
| } |
| if err = compareDirectoryEntries(entries, entriesFinal); err != nil { |
| c.Fatalf("context should have been deleted, but wasn't") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildContextCleanupFailedBuild(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| testRequires(c, SameHostDaemon) |
| |
| name := "testbuildcontextcleanup" |
| entries, err := ioutil.ReadDir(filepath.Join(dockerBasePath, "tmp")) |
| if err != nil { |
| c.Fatalf("failed to list contents of tmp dir: %s", err) |
| } |
| _, err = buildImage(name, |
| `FROM scratch |
| RUN /non/existing/command`, |
| true) |
| if err == nil { |
| c.Fatalf("expected build to fail, but it didn't") |
| } |
| entriesFinal, err := ioutil.ReadDir(filepath.Join(dockerBasePath, "tmp")) |
| if err != nil { |
| c.Fatalf("failed to list contents of tmp dir: %s", err) |
| } |
| if err = compareDirectoryEntries(entries, entriesFinal); err != nil { |
| c.Fatalf("context should have been deleted, but wasn't") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildCmd(c *check.C) { |
| name := "testbuildcmd" |
| |
| expected := "[/bin/echo Hello World]" |
| _, err := buildImage(name, |
| `FROM `+minimalBaseImage()+` |
| CMD ["/bin/echo", "Hello World"]`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| res := inspectField(c, name, "Config.Cmd") |
| if res != expected { |
| c.Fatalf("Cmd %s, expected %s", res, expected) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildExpose(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Expose not implemented on Windows |
| name := "testbuildexpose" |
| expected := "map[2375/tcp:{}]" |
| _, err := buildImage(name, |
| `FROM scratch |
| EXPOSE 2375`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| res := inspectField(c, name, "Config.ExposedPorts") |
| if res != expected { |
| c.Fatalf("Exposed ports %s, expected %s", res, expected) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildExposeMorePorts(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Expose not implemented on Windows |
| // start building docker file with a large number of ports |
| portList := make([]string, 50) |
| line := make([]string, 100) |
| expectedPorts := make([]int, len(portList)*len(line)) |
| for i := 0; i < len(portList); i++ { |
| for j := 0; j < len(line); j++ { |
| p := i*len(line) + j + 1 |
| line[j] = strconv.Itoa(p) |
| expectedPorts[p-1] = p |
| } |
| if i == len(portList)-1 { |
| portList[i] = strings.Join(line, " ") |
| } else { |
| portList[i] = strings.Join(line, " ") + ` \` |
| } |
| } |
| |
| dockerfile := `FROM scratch |
| EXPOSE {{range .}} {{.}} |
| {{end}}` |
| tmpl := template.Must(template.New("dockerfile").Parse(dockerfile)) |
| buf := bytes.NewBuffer(nil) |
| tmpl.Execute(buf, portList) |
| |
| name := "testbuildexpose" |
| _, err := buildImage(name, buf.String(), true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| // check if all the ports are saved inside Config.ExposedPorts |
| res := inspectFieldJSON(c, name, "Config.ExposedPorts") |
| var exposedPorts map[string]interface{} |
| if err := json.Unmarshal([]byte(res), &exposedPorts); err != nil { |
| c.Fatal(err) |
| } |
| |
| for _, p := range expectedPorts { |
| ep := fmt.Sprintf("%d/tcp", p) |
| if _, ok := exposedPorts[ep]; !ok { |
| c.Errorf("Port(%s) is not exposed", ep) |
| } else { |
| delete(exposedPorts, ep) |
| } |
| } |
| if len(exposedPorts) != 0 { |
| c.Errorf("Unexpected extra exposed ports %v", exposedPorts) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildExposeOrder(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Expose not implemented on Windows |
| buildID := func(name, exposed string) string { |
| _, err := buildImage(name, fmt.Sprintf(`FROM scratch |
| EXPOSE %s`, exposed), true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| id := inspectField(c, name, "Id") |
| return id |
| } |
| |
| id1 := buildID("testbuildexpose1", "80 2375") |
| id2 := buildID("testbuildexpose2", "2375 80") |
| if id1 != id2 { |
| c.Errorf("EXPOSE should invalidate the cache only when ports actually changed") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildExposeUpperCaseProto(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Expose not implemented on Windows |
| name := "testbuildexposeuppercaseproto" |
| expected := "map[5678/udp:{}]" |
| _, err := buildImage(name, |
| `FROM scratch |
| EXPOSE 5678/UDP`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| res := inspectField(c, name, "Config.ExposedPorts") |
| if res != expected { |
| c.Fatalf("Exposed ports %s, expected %s", res, expected) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { |
| name := "testbuildentrypointinheritance" |
| name2 := "testbuildentrypointinheritance2" |
| |
| _, err := buildImage(name, |
| `FROM busybox |
| ENTRYPOINT ["/bin/echo"]`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| res := inspectField(c, name, "Config.Entrypoint") |
| |
| expected := "[/bin/echo]" |
| if res != expected { |
| c.Fatalf("Entrypoint %s, expected %s", res, expected) |
| } |
| |
| _, err = buildImage(name2, |
| fmt.Sprintf(`FROM %s |
| ENTRYPOINT []`, name), |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| res = inspectField(c, name2, "Config.Entrypoint") |
| |
| expected = "[]" |
| |
| if res != expected { |
| c.Fatalf("Entrypoint %s, expected %s", res, expected) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildEmptyEntrypoint(c *check.C) { |
| name := "testbuildentrypoint" |
| expected := "[]" |
| |
| _, err := buildImage(name, |
| `FROM busybox |
| ENTRYPOINT []`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| res := inspectField(c, name, "Config.Entrypoint") |
| if res != expected { |
| c.Fatalf("Entrypoint %s, expected %s", res, expected) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildEntrypoint(c *check.C) { |
| name := "testbuildentrypoint" |
| |
| expected := "[/bin/echo]" |
| _, err := buildImage(name, |
| `FROM `+minimalBaseImage()+` |
| ENTRYPOINT ["/bin/echo"]`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| res := inspectField(c, name, "Config.Entrypoint") |
| if res != expected { |
| c.Fatalf("Entrypoint %s, expected %s", res, expected) |
| } |
| |
| } |
| |
| // #6445 ensure ONBUILD triggers aren't committed to grandchildren |
| func (s *DockerSuite) TestBuildOnBuildLimitedInheritence(c *check.C) { |
| var ( |
| out2, out3 string |
| ) |
| { |
| name1 := "testonbuildtrigger1" |
| dockerfile1 := ` |
| FROM busybox |
| RUN echo "GRANDPARENT" |
| ONBUILD RUN echo "ONBUILD PARENT" |
| ` |
| ctx, err := fakeContext(dockerfile1, nil) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| out1, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", name1, ".") |
| if err != nil { |
| c.Fatalf("build failed to complete: %s, %v", out1, err) |
| } |
| } |
| { |
| name2 := "testonbuildtrigger2" |
| dockerfile2 := ` |
| FROM testonbuildtrigger1 |
| ` |
| ctx, err := fakeContext(dockerfile2, nil) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| out2, _, err = dockerCmdInDir(c, ctx.Dir, "build", "-t", name2, ".") |
| if err != nil { |
| c.Fatalf("build failed to complete: %s, %v", out2, err) |
| } |
| } |
| { |
| name3 := "testonbuildtrigger3" |
| dockerfile3 := ` |
| FROM testonbuildtrigger2 |
| ` |
| ctx, err := fakeContext(dockerfile3, nil) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| out3, _, err = dockerCmdInDir(c, ctx.Dir, "build", "-t", name3, ".") |
| if err != nil { |
| c.Fatalf("build failed to complete: %s, %v", out3, err) |
| } |
| |
| } |
| |
| // ONBUILD should be run in second build. |
| if !strings.Contains(out2, "ONBUILD PARENT") { |
| c.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") { |
| c.Fatalf("ONBUILD instruction ran in grandchild of ONBUILD parent") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildWithCache(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Expose not implemented on Windows |
| name := "testbuildwithcache" |
| id1, err := buildImage(name, |
| `FROM scratch |
| MAINTAINER dockerio |
| EXPOSE 5432 |
| ENTRYPOINT ["/bin/echo"]`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| id2, err := buildImage(name, |
| `FROM scratch |
| MAINTAINER dockerio |
| EXPOSE 5432 |
| ENTRYPOINT ["/bin/echo"]`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 != id2 { |
| c.Fatal("The cache should have been used but hasn't.") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildWithoutCache(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Expose not implemented on Windows |
| name := "testbuildwithoutcache" |
| name2 := "testbuildwithoutcache2" |
| id1, err := buildImage(name, |
| `FROM scratch |
| MAINTAINER dockerio |
| EXPOSE 5432 |
| ENTRYPOINT ["/bin/echo"]`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| id2, err := buildImage(name2, |
| `FROM scratch |
| MAINTAINER dockerio |
| EXPOSE 5432 |
| ENTRYPOINT ["/bin/echo"]`, |
| false) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 == id2 { |
| c.Fatal("The cache should have been invalided but hasn't.") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildConditionalCache(c *check.C) { |
| name := "testbuildconditionalcache" |
| |
| dockerfile := ` |
| FROM busybox |
| ADD foo /tmp/` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "foo": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| id1, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatalf("Error building #1: %s", err) |
| } |
| |
| if err := ctx.Add("foo", "bye"); err != nil { |
| c.Fatalf("Error modifying foo: %s", err) |
| } |
| |
| id2, err := buildImageFromContext(name, ctx, false) |
| if err != nil { |
| c.Fatalf("Error building #2: %s", err) |
| } |
| if id2 == id1 { |
| c.Fatal("Should not have used the cache") |
| } |
| |
| id3, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatalf("Error building #3: %s", err) |
| } |
| if id3 != id2 { |
| c.Fatal("Should have used the cache") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddLocalFileWithCache(c *check.C) { |
| // local files are not owned by the correct user |
| testRequires(c, NotUserNamespace) |
| name := "testbuildaddlocalfilewithcache" |
| name2 := "testbuildaddlocalfilewithcache2" |
| dockerfile := ` |
| FROM busybox |
| MAINTAINER dockerio |
| ADD foo /usr/lib/bla/bar |
| RUN sh -c "[ $(cat /usr/lib/bla/bar) = "hello" ]"` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "foo": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| id1, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| id2, err := buildImageFromContext(name2, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 != id2 { |
| c.Fatal("The cache should have been used but hasn't.") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddMultipleLocalFileWithCache(c *check.C) { |
| name := "testbuildaddmultiplelocalfilewithcache" |
| name2 := "testbuildaddmultiplelocalfilewithcache2" |
| dockerfile := ` |
| FROM busybox |
| MAINTAINER dockerio |
| ADD foo Dockerfile /usr/lib/bla/ |
| RUN sh -c "[ $(cat /usr/lib/bla/foo) = "hello" ]"` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "foo": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| id1, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| id2, err := buildImageFromContext(name2, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 != id2 { |
| c.Fatal("The cache should have been used but hasn't.") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddLocalFileWithoutCache(c *check.C) { |
| // local files are not owned by the correct user |
| testRequires(c, NotUserNamespace) |
| name := "testbuildaddlocalfilewithoutcache" |
| name2 := "testbuildaddlocalfilewithoutcache2" |
| dockerfile := ` |
| FROM busybox |
| MAINTAINER dockerio |
| ADD foo /usr/lib/bla/bar |
| RUN sh -c "[ $(cat /usr/lib/bla/bar) = "hello" ]"` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "foo": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| id1, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| id2, err := buildImageFromContext(name2, ctx, false) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 == id2 { |
| c.Fatal("The cache should have been invalided but hasn't.") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCopyDirButNotFile(c *check.C) { |
| name := "testbuildcopydirbutnotfile" |
| name2 := "testbuildcopydirbutnotfile2" |
| |
| dockerfile := ` |
| FROM ` + minimalBaseImage() + ` |
| COPY dir /tmp/` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "dir/foo": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| id1, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| // Check that adding file with similar name doesn't mess with cache |
| if err := ctx.Add("dir_file", "hello2"); err != nil { |
| c.Fatal(err) |
| } |
| id2, err := buildImageFromContext(name2, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 != id2 { |
| c.Fatal("The cache should have been used but wasn't") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddCurrentDirWithCache(c *check.C) { |
| name := "testbuildaddcurrentdirwithcache" |
| name2 := name + "2" |
| name3 := name + "3" |
| name4 := name + "4" |
| dockerfile := ` |
| FROM ` + minimalBaseImage() + ` |
| MAINTAINER dockerio |
| ADD . /usr/lib/bla` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "foo": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| id1, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| // Check that adding file invalidate cache of "ADD ." |
| if err := ctx.Add("bar", "hello2"); err != nil { |
| c.Fatal(err) |
| } |
| id2, err := buildImageFromContext(name2, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 == id2 { |
| c.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 { |
| c.Fatal(err) |
| } |
| id3, err := buildImageFromContext(name3, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id2 == id3 { |
| c.Fatal("The cache should have been invalided but hasn't.") |
| } |
| // Check that changing file to same content with different mtime does not |
| // invalidate cache of "ADD ." |
| time.Sleep(1 * time.Second) // wait second because of mtime precision |
| if err := ctx.Add("foo", "hello1"); err != nil { |
| c.Fatal(err) |
| } |
| id4, err := buildImageFromContext(name4, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id3 != id4 { |
| c.Fatal("The cache should have been used but hasn't.") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddCurrentDirWithoutCache(c *check.C) { |
| name := "testbuildaddcurrentdirwithoutcache" |
| name2 := "testbuildaddcurrentdirwithoutcache2" |
| dockerfile := ` |
| FROM ` + minimalBaseImage() + ` |
| MAINTAINER dockerio |
| ADD . /usr/lib/bla` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "foo": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| id1, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| id2, err := buildImageFromContext(name2, ctx, false) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 == id2 { |
| c.Fatal("The cache should have been invalided but hasn't.") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddRemoteFileWithCache(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Windows doesn't have httpserver image yet |
| name := "testbuildaddremotefilewithcache" |
| server, err := fakeStorage(map[string]string{ |
| "baz": "hello", |
| }) |
| if err != nil { |
| c.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 { |
| c.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 { |
| c.Fatal(err) |
| } |
| if id1 != id2 { |
| c.Fatal("The cache should have been used but hasn't.") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddRemoteFileWithoutCache(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Windows doesn't have httpserver image yet |
| name := "testbuildaddremotefilewithoutcache" |
| name2 := "testbuildaddremotefilewithoutcache2" |
| server, err := fakeStorage(map[string]string{ |
| "baz": "hello", |
| }) |
| if err != nil { |
| c.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 { |
| c.Fatal(err) |
| } |
| id2, err := buildImage(name2, |
| fmt.Sprintf(`FROM scratch |
| MAINTAINER dockerio |
| ADD %s/baz /usr/lib/baz/quux`, server.URL()), |
| false) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 == id2 { |
| c.Fatal("The cache should have been invalided but hasn't.") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddRemoteFileMTime(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Windows doesn't have httpserver image yet |
| name := "testbuildaddremotefilemtime" |
| name2 := name + "2" |
| name3 := name + "3" |
| |
| files := map[string]string{"baz": "hello"} |
| server, err := fakeStorage(files) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer server.Close() |
| |
| ctx, err := fakeContext(fmt.Sprintf(`FROM scratch |
| MAINTAINER dockerio |
| ADD %s/baz /usr/lib/baz/quux`, server.URL()), nil) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| id1, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| id2, err := buildImageFromContext(name2, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 != id2 { |
| c.Fatal("The cache should have been used but wasn't - #1") |
| } |
| |
| // Now create a different server with same contents (causes different mtime) |
| // The cache should still be used |
| |
| // allow some time for clock to pass as mtime precision is only 1s |
| time.Sleep(2 * time.Second) |
| |
| server2, err := fakeStorage(files) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer server2.Close() |
| |
| ctx2, err := fakeContext(fmt.Sprintf(`FROM scratch |
| MAINTAINER dockerio |
| ADD %s/baz /usr/lib/baz/quux`, server2.URL()), nil) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx2.Close() |
| id3, err := buildImageFromContext(name3, ctx2, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 != id3 { |
| c.Fatal("The cache should have been used but wasn't") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddLocalAndRemoteFilesWithCache(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Windows doesn't have httpserver image yet |
| name := "testbuildaddlocalandremotefilewithcache" |
| server, err := fakeStorage(map[string]string{ |
| "baz": "hello", |
| }) |
| if err != nil { |
| c.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 { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| id1, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| id2, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 != id2 { |
| c.Fatal("The cache should have been used but hasn't.") |
| } |
| } |
| |
| func testContextTar(c *check.C, compression archive.Compression) { |
| ctx, err := fakeContext( |
| `FROM busybox |
| ADD foo /foo |
| CMD ["cat", "/foo"]`, |
| map[string]string{ |
| "foo": "bar", |
| }, |
| ) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| context, err := archive.Tar(ctx.Dir, compression) |
| if err != nil { |
| c.Fatalf("failed to build context tar: %v", err) |
| } |
| name := "contexttar" |
| buildCmd := exec.Command(dockerBinary, "build", "-t", name, "-") |
| buildCmd.Stdin = context |
| |
| if out, _, err := runCommandWithOutput(buildCmd); err != nil { |
| c.Fatalf("build failed to complete: %v %v", out, err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildContextTarGzip(c *check.C) { |
| testContextTar(c, archive.Gzip) |
| } |
| |
| func (s *DockerSuite) TestBuildContextTarNoCompression(c *check.C) { |
| testContextTar(c, archive.Uncompressed) |
| } |
| |
| func (s *DockerSuite) TestBuildNoContext(c *check.C) { |
| buildCmd := exec.Command(dockerBinary, "build", "-t", "nocontext", "-") |
| buildCmd.Stdin = strings.NewReader( |
| `FROM busybox |
| CMD ["echo", "ok"]`) |
| |
| if out, _, err := runCommandWithOutput(buildCmd); err != nil { |
| c.Fatalf("build failed to complete: %v %v", out, err) |
| } |
| |
| if out, _ := dockerCmd(c, "run", "--rm", "nocontext"); out != "ok\n" { |
| c.Fatalf("run produced invalid output: %q, expected %q", out, "ok") |
| } |
| } |
| |
| // TODO: TestCaching |
| func (s *DockerSuite) TestBuildAddLocalAndRemoteFilesWithoutCache(c *check.C) { |
| testRequires(c, DaemonIsLinux) // Windows doesn't have httpserver image yet |
| name := "testbuildaddlocalandremotefilewithoutcache" |
| name2 := "testbuildaddlocalandremotefilewithoutcache2" |
| server, err := fakeStorage(map[string]string{ |
| "baz": "hello", |
| }) |
| if err != nil { |
| c.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 { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| id1, err := buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| id2, err := buildImageFromContext(name2, ctx, false) |
| if err != nil { |
| c.Fatal(err) |
| } |
| if id1 == id2 { |
| c.Fatal("The cache should have been invalided but hasn't.") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildWithVolumeOwnership(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildimg" |
| |
| _, err := buildImage(name, |
| `FROM busybox:latest |
| RUN mkdir /test && chown daemon:daemon /test && chmod 0600 /test |
| VOLUME /test`, |
| true) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| out, _ := dockerCmd(c, "run", "--rm", "testbuildimg", "ls", "-la", "/test") |
| |
| if expected := "drw-------"; !strings.Contains(out, expected) { |
| c.Fatalf("expected %s received %s", expected, out) |
| } |
| |
| if expected := "daemon daemon"; !strings.Contains(out, expected) { |
| c.Fatalf("expected %s received %s", expected, out) |
| } |
| |
| } |
| |
| // testing #1405 - config.Cmd does not get cleaned up if |
| // utilizing cache |
| func (s *DockerSuite) TestBuildEntrypointRunCleanup(c *check.C) { |
| name := "testbuildcmdcleanup" |
| if _, err := buildImage(name, |
| `FROM busybox |
| RUN echo "hello"`, |
| true); err != nil { |
| c.Fatal(err) |
| } |
| |
| ctx, err := fakeContext(`FROM busybox |
| RUN echo "hello" |
| ADD foo /foo |
| ENTRYPOINT ["/bin/echo"]`, |
| map[string]string{ |
| "foo": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| res := inspectField(c, name, "Config.Cmd") |
| // Cmd must be cleaned up |
| if res != "[]" { |
| c.Fatalf("Cmd %s, expected nil", res) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildAddFileNotFound(c *check.C) { |
| name := "testbuildaddnotfound" |
| expected := "foo: no such file or directory" |
| |
| if daemonPlatform == "windows" { |
| expected = "foo: The system cannot find the file specified" |
| } |
| |
| ctx, err := fakeContext(`FROM `+minimalBaseImage()+` |
| ADD foo /usr/local/bar`, |
| map[string]string{"bar": "hello"}) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| if !strings.Contains(err.Error(), expected) { |
| c.Fatalf("Wrong error %v, must be about missing foo file or directory", err) |
| } |
| } else { |
| c.Fatal("Error must not be nil") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildInheritance(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildinheritance" |
| |
| _, err := buildImage(name, |
| `FROM scratch |
| EXPOSE 2375`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| ports1 := inspectField(c, name, "Config.ExposedPorts") |
| |
| _, err = buildImage(name, |
| fmt.Sprintf(`FROM %s |
| ENTRYPOINT ["/bin/echo"]`, name), |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| res := inspectField(c, name, "Config.Entrypoint") |
| if expected := "[/bin/echo]"; res != expected { |
| c.Fatalf("Entrypoint %s, expected %s", res, expected) |
| } |
| ports2 := inspectField(c, name, "Config.ExposedPorts") |
| if ports1 != ports2 { |
| c.Fatalf("Ports must be same: %s != %s", ports1, ports2) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildFails(c *check.C) { |
| name := "testbuildfails" |
| _, 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") { |
| c.Fatalf("Wrong error %v, must be about non-zero code 23", err) |
| } |
| } else { |
| c.Fatal("Error must not be nil") |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildOnBuild(c *check.C) { |
| name := "testbuildonbuild" |
| _, err := buildImage(name, |
| `FROM busybox |
| ONBUILD RUN touch foobar`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| _, err = buildImage(name, |
| fmt.Sprintf(`FROM %s |
| RUN [ -f foobar ]`, name), |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| // gh #2446 |
| func (s *DockerSuite) TestBuildAddToSymlinkDest(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildaddtosymlinkdest" |
| 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 { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildEscapeWhitespace(c *check.C) { |
| name := "testbuildescapewhitespace" |
| |
| _, err := buildImage(name, ` |
| # ESCAPE=\ |
| FROM busybox |
| MAINTAINER "Docker \ |
| IO <io@\ |
| docker.com>" |
| `, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| res := inspectField(c, name, "Author") |
| |
| if res != "\"Docker IO <io@docker.com>\"" { |
| c.Fatalf("Parsed string did not match the escaped string. Got: %q", res) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildVerifyIntString(c *check.C) { |
| // Verify that strings that look like ints are still passed as strings |
| name := "testbuildstringing" |
| |
| _, err := buildImage(name, ` |
| FROM busybox |
| MAINTAINER 123 |
| `, true) |
| |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| out, _ := dockerCmd(c, "inspect", name) |
| |
| if !strings.Contains(out, "\"123\"") { |
| c.Fatalf("Output does not contain the int as a string:\n%s", out) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildDockerignore(c *check.C) { |
| testRequires(c, DaemonIsLinux) // TODO Windows: This test passes on Windows, |
| // but currently adds a disproportionate amount of time for the value it has. |
| // Removing it from Windows CI for now, but this will be revisited in the |
| // TP5 timeframe when perf is better. |
| name := "testbuilddockerignore" |
| dockerfile := ` |
| FROM busybox |
| ADD . /bla |
| RUN sh -c "[[ -f /bla/src/x.go ]]" |
| RUN sh -c "[[ -f /bla/Makefile ]]" |
| RUN sh -c "[[ ! -e /bla/src/_vendor ]]" |
| RUN sh -c "[[ ! -e /bla/.gitignore ]]" |
| RUN sh -c "[[ ! -e /bla/README.md ]]" |
| RUN sh -c "[[ ! -e /bla/dir/foo ]]" |
| RUN sh -c "[[ ! -e /bla/foo ]]" |
| RUN sh -c "[[ ! -e /bla/.git ]]" |
| RUN sh -c "[[ ! -e v.cc ]]" |
| RUN sh -c "[[ ! -e src/v.cc ]]" |
| RUN sh -c "[[ ! -e src/_vendor/v.cc ]]"` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "Makefile": "all:", |
| ".git/HEAD": "ref: foo", |
| "src/x.go": "package main", |
| "src/_vendor/v.go": "package main", |
| "src/_vendor/v.cc": "package main", |
| "src/v.cc": "package main", |
| "v.cc": "package main", |
| "dir/foo": "", |
| ".gitignore": "", |
| "README.md": "readme", |
| ".dockerignore": ` |
| .git |
| pkg |
| .gitignore |
| src/_vendor |
| *.md |
| **/*.cc |
| dir`, |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildDockerignoreCleanPaths(c *check.C) { |
| name := "testbuilddockerignorecleanpaths" |
| dockerfile := ` |
| FROM busybox |
| ADD . /tmp/ |
| RUN sh -c "(! ls /tmp/foo) && (! ls /tmp/foo2) && (! ls /tmp/dir1/foo)"` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "foo": "foo", |
| "foo2": "foo2", |
| "dir1/foo": "foo in dir1", |
| ".dockerignore": "./foo\ndir1//foo\n./dir1/../foo2", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildDockerignoreExceptions(c *check.C) { |
| testRequires(c, DaemonIsLinux) // TODO Windows: This test passes on Windows, |
| // but currently adds a disproportionate amount of time for the value it has. |
| // Removing it from Windows CI for now, but this will be revisited in the |
| // TP5 timeframe when perf is better. |
| name := "testbuilddockerignoreexceptions" |
| dockerfile := ` |
| FROM busybox |
| ADD . /bla |
| RUN sh -c "[[ -f /bla/src/x.go ]]" |
| RUN sh -c "[[ -f /bla/Makefile ]]" |
| RUN sh -c "[[ ! -e /bla/src/_vendor ]]" |
| RUN sh -c "[[ ! -e /bla/.gitignore ]]" |
| RUN sh -c "[[ ! -e /bla/README.md ]]" |
| RUN sh -c "[[ -e /bla/dir/dir/foo ]]" |
| RUN sh -c "[[ ! -e /bla/dir/foo1 ]]" |
| RUN sh -c "[[ -f /bla/dir/e ]]" |
| RUN sh -c "[[ -f /bla/dir/e-dir/foo ]]" |
| RUN sh -c "[[ ! -e /bla/foo ]]" |
| RUN sh -c "[[ ! -e /bla/.git ]]" |
| RUN sh -c "[[ -e /bla/dir/a.cc ]]"` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "Makefile": "all:", |
| ".git/HEAD": "ref: foo", |
| "src/x.go": "package main", |
| "src/_vendor/v.go": "package main", |
| "dir/foo": "", |
| "dir/foo1": "", |
| "dir/dir/f1": "", |
| "dir/dir/foo": "", |
| "dir/e": "", |
| "dir/e-dir/foo": "", |
| ".gitignore": "", |
| "README.md": "readme", |
| "dir/a.cc": "hello", |
| ".dockerignore": ` |
| .git |
| pkg |
| .gitignore |
| src/_vendor |
| *.md |
| dir |
| !dir/e* |
| !dir/dir/foo |
| **/*.cc |
| !**/*.cc`, |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| if _, err := buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildDockerignoringDockerfile(c *check.C) { |
| name := "testbuilddockerignoredockerfile" |
| dockerfile := ` |
| FROM busybox |
| ADD . /tmp/ |
| RUN sh -c "! ls /tmp/Dockerfile" |
| RUN ls /tmp/.dockerignore` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "Dockerfile": dockerfile, |
| ".dockerignore": "Dockerfile\n", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatalf("Didn't ignore Dockerfile correctly:%s", err) |
| } |
| |
| // now try it with ./Dockerfile |
| ctx.Add(".dockerignore", "./Dockerfile\n") |
| if _, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatalf("Didn't ignore ./Dockerfile correctly:%s", err) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildDockerignoringRenamedDockerfile(c *check.C) { |
| name := "testbuilddockerignoredockerfile" |
| dockerfile := ` |
| FROM busybox |
| ADD . /tmp/ |
| RUN ls /tmp/Dockerfile |
| RUN sh -c "! ls /tmp/MyDockerfile" |
| RUN ls /tmp/.dockerignore` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "Dockerfile": "Should not use me", |
| "MyDockerfile": dockerfile, |
| ".dockerignore": "MyDockerfile\n", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if _, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatalf("Didn't ignore MyDockerfile correctly:%s", err) |
| } |
| |
| // now try it with ./MyDockerfile |
| ctx.Add(".dockerignore", "./MyDockerfile\n") |
| if _, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatalf("Didn't ignore ./MyDockerfile correctly:%s", err) |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildDockerignoringDockerignore(c *check.C) { |
| name := "testbuilddockerignoredockerignore" |
| dockerfile := ` |
| FROM busybox |
| ADD . /tmp/ |
| RUN sh -c "! ls /tmp/.dockerignore" |
| RUN ls /tmp/Dockerfile` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "Dockerfile": dockerfile, |
| ".dockerignore": ".dockerignore\n", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| if _, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatalf("Didn't ignore .dockerignore correctly:%s", err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildDockerignoreTouchDockerfile(c *check.C) { |
| var id1 string |
| var id2 string |
| |
| name := "testbuilddockerignoretouchdockerfile" |
| dockerfile := ` |
| FROM busybox |
| ADD . /tmp/` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "Dockerfile": dockerfile, |
| ".dockerignore": "Dockerfile\n", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| if id1, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatalf("Didn't build it correctly:%s", err) |
| } |
| |
| if id2, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatalf("Didn't build it correctly:%s", err) |
| } |
| if id1 != id2 { |
| c.Fatalf("Didn't use the cache - 1") |
| } |
| |
| // Now make sure touching Dockerfile doesn't invalidate the cache |
| if err = ctx.Add("Dockerfile", dockerfile+"\n# hi"); err != nil { |
| c.Fatalf("Didn't add Dockerfile: %s", err) |
| } |
| if id2, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatalf("Didn't build it correctly:%s", err) |
| } |
| if id1 != id2 { |
| c.Fatalf("Didn't use the cache - 2") |
| } |
| |
| // One more time but just 'touch' it instead of changing the content |
| if err = ctx.Add("Dockerfile", dockerfile+"\n# hi"); err != nil { |
| c.Fatalf("Didn't add Dockerfile: %s", err) |
| } |
| if id2, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatalf("Didn't build it correctly:%s", err) |
| } |
| if id1 != id2 { |
| c.Fatalf("Didn't use the cache - 3") |
| } |
| |
| } |
| |
| func (s *DockerSuite) TestBuildDockerignoringWholeDir(c *check.C) { |
| name := "testbuilddockerignorewholedir" |
| dockerfile := ` |
| FROM busybox |
| COPY . / |
| RUN sh -c "[[ ! -e /.gitignore ]]" |
| RUN sh -c "[[ -f /Makefile ]]"` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "Dockerfile": "FROM scratch", |
| "Makefile": "all:", |
| ".gitignore": "", |
| ".dockerignore": ".*\n", |
| }) |
| c.Assert(err, check.IsNil) |
| defer ctx.Close() |
| if _, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| |
| c.Assert(ctx.Add(".dockerfile", "*"), check.IsNil) |
| if _, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| |
| c.Assert(ctx.Add(".dockerfile", "."), check.IsNil) |
| if _, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| |
| c.Assert(ctx.Add(".dockerfile", "?"), check.IsNil) |
| if _, err = buildImageFromContext(name, ctx, true); err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildDockerignoringBadExclusion(c *check.C) { |
| name := "testbuilddockerignorebadexclusion" |
| dockerfile := ` |
| FROM busybox |
| COPY . / |
| RUN sh -c "[[ ! -e /.gitignore ]]" |
| RUN sh -c "[[ -f /Makefile ]]"` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "Dockerfile": "FROM scratch", |
| "Makefile": "all:", |
| ".gitignore": "", |
| ".dockerignore": "!\n", |
| }) |
| c.Assert(err, check.IsNil) |
| defer ctx.Close() |
| if _, err = buildImageFromContext(name, ctx, true); err == nil { |
| c.Fatalf("Build was supposed to fail but didn't") |
| } |
| |
| if err.Error() != "failed to build the image: Error checking context: 'Illegal exclusion pattern: !'.\n" { |
| c.Fatalf("Incorrect output, got:%q", err.Error()) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildDockerignoringWildTopDir(c *check.C) { |
| dockerfile := ` |
| FROM busybox |
| COPY . / |
| RUN sh -c "[[ ! -e /.dockerignore ]]" |
| RUN sh -c "[[ ! -e /Dockerfile ]]" |
| RUN sh -c "[[ ! -e /file1 ]]" |
| RUN sh -c "[[ ! -e /dir ]]"` |
| |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "Dockerfile": "FROM scratch", |
| "file1": "", |
| "dir/dfile1": "", |
| }) |
| c.Assert(err, check.IsNil) |
| defer ctx.Close() |
| |
| // All of these should result in ignoring all files |
| for _, variant := range []string{"**", "**/", "**/**", "*"} { |
| ctx.Add(".dockerignore", variant) |
| _, err = buildImageFromContext("noname", ctx, true) |
| c.Assert(err, check.IsNil, check.Commentf("variant: %s", variant)) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildDockerignoringWildDirs(c *check.C) { |
| testRequires(c, DaemonIsLinux) // TODO Windows: Fix this test; also perf |
| |
| dockerfile := ` |
| FROM busybox |
| COPY . / |
| #RUN sh -c "[[ -e /.dockerignore ]]" |
| RUN sh -c "[[ -e /Dockerfile ]] && \ |
| [[ ! -e /file0 ]] && \ |
| [[ ! -e /dir1/file0 ]] && \ |
| [[ ! -e /dir2/file0 ]] && \ |
| [[ ! -e /file1 ]] && \ |
| [[ ! -e /dir1/file1 ]] && \ |
| [[ ! -e /dir1/dir2/file1 ]] && \ |
| [[ ! -e /dir1/file2 ]] && \ |
| [[ -e /dir1/dir2/file2 ]] && \ |
| [[ ! -e /dir1/dir2/file4 ]] && \ |
| [[ ! -e /dir1/dir2/file5 ]] && \ |
| [[ ! -e /dir1/dir2/file6 ]] && \ |
| [[ ! -e /dir1/dir3/file7 ]] && \ |
| [[ ! -e /dir1/dir3/file8 ]] && \ |
| [[ -e /dir1/dir3 ]] && \ |
| [[ -e /dir1/dir4 ]] && \ |
| [[ ! -e 'dir1/dir5/fileAA' ]] && \ |
| [[ -e 'dir1/dir5/fileAB' ]] && \ |
| [[ -e 'dir1/dir5/fileB' ]]" # "." in pattern means nothing |
| |
| RUN echo all done!` |
| |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "Dockerfile": "FROM scratch", |
| "file0": "", |
| "dir1/file0": "", |
| "dir1/dir2/file0": "", |
| |
| "file1": "", |
| "dir1/file1": "", |
| "dir1/dir2/file1": "", |
| |
| "dir1/file2": "", |
| "dir1/dir2/file2": "", // remains |
| |
| "dir1/dir2/file4": "", |
| "dir1/dir2/file5": "", |
| "dir1/dir2/file6": "", |
| "dir1/dir3/file7": "", |
| "dir1/dir3/file8": "", |
| "dir1/dir4/file9": "", |
| |
| "dir1/dir5/fileAA": "", |
| "dir1/dir5/fileAB": "", |
| "dir1/dir5/fileB": "", |
| |
| ".dockerignore": ` |
| **/file0 |
| **/*file1 |
| **/dir1/file2 |
| dir1/**/file4 |
| **/dir2/file5 |
| **/dir1/dir2/file6 |
| dir1/dir3/** |
| **/dir4/** |
| **/file?A |
| **/file\?B |
| **/dir5/file. |
| `, |
| }) |
| c.Assert(err, check.IsNil) |
| defer ctx.Close() |
| |
| _, err = buildImageFromContext("noname", ctx, true) |
| c.Assert(err, check.IsNil) |
| } |
| |
| func (s *DockerSuite) TestBuildLineBreak(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildlinebreak" |
| _, err := buildImage(name, |
| `FROM busybox |
| RUN sh -c 'echo root:testpass \ |
| > /tmp/passwd' |
| RUN mkdir -p /var/run/sshd |
| RUN sh -c "[ "$(cat /tmp/passwd)" = "root:testpass" ]" |
| RUN sh -c "[ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]"`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildEOLInLine(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildeolinline" |
| _, err := buildImage(name, |
| `FROM busybox |
| RUN sh -c 'echo root:testpass > /tmp/passwd' |
| RUN echo "foo \n bar"; echo "baz" |
| RUN mkdir -p /var/run/sshd |
| RUN sh -c "[ "$(cat /tmp/passwd)" = "root:testpass" ]" |
| RUN sh -c "[ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]"`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildCommentsShebangs(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildcomments" |
| _, err := buildImage(name, |
| `FROM busybox |
| # This is an ordinary comment. |
| RUN { echo '#!/bin/sh'; echo 'echo hello world'; } > /hello.sh |
| RUN [ ! -x /hello.sh ] |
| # comment with line break \ |
| RUN chmod +x /hello.sh |
| RUN [ -x /hello.sh ] |
| RUN [ "$(cat /hello.sh)" = $'#!/bin/sh\necho hello world' ] |
| RUN [ "$(/hello.sh)" = "hello world" ]`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildUsersAndGroups(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildusers" |
| _, err := buildImage(name, |
| `FROM busybox |
| |
| # Make sure our defaults work |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)" = '0:0/root:root' ] |
| |
| # 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) |
| USER root |
| RUN [ "$(id -G):$(id -Gn)" = '0 10:root wheel' ] |
| |
| # Setup dockerio user and group |
| RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd && \ |
| echo 'dockerio:x:1001:' >> /etc/group |
| |
| # Make sure we can switch to our user and all the information is exactly as we expect it to be |
| USER dockerio |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] |
| |
| # Switch back to root and double check that worked exactly as we might expect it to |
| USER root |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '0:0/root:root/0 10:root wheel' ] && \ |
| # Add a "supplementary" group for our dockerio user \ |
| echo 'supplementary:x:1002:dockerio' >> /etc/group |
| |
| # ... and then go verify that we get it like we expect |
| USER dockerio |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001 1002:dockerio supplementary' ] |
| USER 1001 |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001 1002:dockerio supplementary' ] |
| |
| # super test the new "user:group" syntax |
| USER dockerio:dockerio |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] |
| USER 1001:dockerio |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] |
| USER dockerio:1001 |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] |
| USER 1001:1001 |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] |
| USER dockerio:supplementary |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ] |
| USER dockerio:1002 |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ] |
| USER 1001:supplementary |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ] |
| USER 1001:1002 |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ] |
| |
| # make sure unknown uid/gid still works properly |
| USER 1042:1043 |
| RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1042:1043/1042:1043/1043:1043' ]`, |
| true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildEnvUsage(c *check.C) { |
| // /docker/world/hello is not owned by the correct user |
| testRequires(c, NotUserNamespace) |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildenvusage" |
| dockerfile := `FROM busybox |
| ENV HOME /root |
| ENV PATH $HOME/bin:$PATH |
| ENV PATH /tmp:$PATH |
| RUN [ "$PATH" = "/tmp:$HOME/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ] |
| ENV FOO /foo/baz |
| ENV BAR /bar |
| ENV BAZ $BAR |
| ENV FOOPATH $PATH:$FOO |
| RUN [ "$BAR" = "$BAZ" ] |
| RUN [ "$FOOPATH" = "$PATH:/foo/baz" ] |
| ENV FROM hello/docker/world |
| ENV TO /docker/world/hello |
| ADD $FROM $TO |
| RUN [ "$(cat $TO)" = "hello" ] |
| ENV abc=def |
| ENV ghi=$abc |
| RUN [ "$ghi" = "def" ] |
| ` |
| ctx, err := fakeContext(dockerfile, map[string]string{ |
| "hello/docker/world": "hello", |
| }) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ctx.Close() |
| |
| _, err = buildImageFromContext(name, ctx, true) |
| if err != nil { |
| c.Fatal(err) |
| } |
| } |
| |
| func (s *DockerSuite) TestBuildEnvUsage2(c *check.C) { |
| // /docker/world/hello is not owned by the correct user |
| testRequires(c, NotUserNamespace) |
| testRequires(c, DaemonIsLinux) |
| name := "testbuildenvusage2" |
| dockerfile := `FROM busybox |
| ENV abc=def def="hello world" |
| RUN [ "$abc,$def" = "def,hello world" ] |
| ENV def=hello\ world v1=abc v2="hi there" v3='boogie nights' v4="with'quotes too" |
| RUN [ "$def,$v1,$v2,$v3,$v4" = "hello world,abc,hi there,boogie nights,with'quotes too" ] |
| ENV abc=zzz FROM=hello/docker/world |
| ENV abc=zzz TO=/docker/world/hello |
| ADD $FROM $TO |
| RUN [ "$abc,$(cat $TO)" = "zzz,hello" ] |
| ENV abc 'yyy' |
| RUN [ $abc = 'yyy' ] |
| ENV abc= |
| RUN [ "$abc" = "" ] |
| |
| # use grep to make sure if the builder substitutes \$foo by mistake |
| # we don't get a false positive |
| ENV abc=\$foo |
| RUN [ "$abc" = "\$foo" ] && (echo "$abc" | grep foo) |
| ENV abc \$foo |
| RUN [ "$abc" = "\$foo" ] && (echo "$abc" | grep foo) |
| |
| ENV abc=\'foo\' abc2=\"foo\" |
| RUN [ "$abc,$abc2" = "'foo',\"foo\"" ] |
| ENV abc "foo" |
| RUN [ "$abc" = "foo" ] |
| ENV abc 'foo' |
| RUN [ "$abc" = 'foo' ] |
| ENV abc \'foo\' |
| RUN [ "$abc" = "'foo'" ] |
| ENV abc \"foo\" |
| RUN [ "$abc" = '"foo"' ] |
| |
| ENV abc=ABC |
| RUN [ "$abc" = "ABC" ] |
| ENV def1=${abc:-DEF} def2=${ccc:-DEF} |
| ENV def3=${ccc:-${def2}xx} def4=${abc:+ALT} def5=${def2:+${abc}:} def6=${ccc:-\$abc:} def7=${ccc:-\${abc}:} |
| RUN [ "$def1,$def2,$def3,$def4,$def5,$def6,$def7" = 'ABC,DEF,DEFxx,ALT,ABC:,$abc:,${abc:}' ] |
| ENV mypath=${mypath:+$mypath:}/home |
| ENV mypath=${mypath:+$mypath:}/away |
|