Skip to content

Commit

Permalink
Support dockerfile and Dockerfile
Browse files Browse the repository at this point in the history
Closes moby#10807

Adds support for `dockerfile` ONLY when `Dockerfile` can't be found.
If we're building from a Dockerfile via stdin/URL then always download
it a `Dockerfile` and ignore the -f flag.

Signed-off-by: Doug Davis <[email protected]>
  • Loading branch information
Doug Davis committed Mar 4, 2015
1 parent a61716e commit 15924f2
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 27 deletions.
17 changes: 14 additions & 3 deletions api/client/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
if err != nil {
return fmt.Errorf("failed to read Dockerfile from STDIN: %v", err)
}
if *dockerfileName == "" {
*dockerfileName = api.DefaultDockerfileName
}

// -f option has no meaning when we're reading it from stdin,
// so just use our default Dockerfile name
*dockerfileName = api.DefaultDockerfileName
context, err = archive.Generate(*dockerfileName, string(dockerfile))
} else {
context = ioutil.NopCloser(buf)
Expand Down Expand Up @@ -156,6 +157,16 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
// No -f/--file was specified so use the default
*dockerfileName = api.DefaultDockerfileName
filename = filepath.Join(absRoot, *dockerfileName)

// Just to be nice ;-) look for 'dockerfile' too but only
// use it if we found it, otherwise ignore this check
if _, err = os.Lstat(filename); os.IsNotExist(err) {
tmpFN := path.Join(absRoot, strings.ToLower(*dockerfileName))
if _, err = os.Lstat(tmpFN); err == nil {
*dockerfileName = strings.ToLower(*dockerfileName)
filename = tmpFN
}
}
}

origDockerfile := *dockerfileName // used for error msg
Expand Down
21 changes: 19 additions & 2 deletions builder/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"strings"

log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/builder/command"
"github.com/docker/docker/builder/parser"
"github.com/docker/docker/daemon"
Expand Down Expand Up @@ -146,7 +147,7 @@ func (b *Builder) Run(context io.Reader) (string, error) {
}
}()

if err := b.readDockerfile(b.dockerfileName); err != nil {
if err := b.readDockerfile(); err != nil {
return "", err
}

Expand Down Expand Up @@ -177,7 +178,23 @@ func (b *Builder) Run(context io.Reader) (string, error) {

// Reads a Dockerfile from the current context. It assumes that the
// 'filename' is a relative path from the root of the context
func (b *Builder) readDockerfile(origFile string) error {
func (b *Builder) readDockerfile() error {
// If no -f was specified then look for 'Dockerfile'. If we can't find
// that then look for 'dockerfile'. If neither are found then default
// back to 'Dockerfile' and use that in the error message.
if b.dockerfileName == "" {
b.dockerfileName = api.DefaultDockerfileName
tmpFN := filepath.Join(b.contextPath, api.DefaultDockerfileName)
if _, err := os.Lstat(tmpFN); err != nil {
tmpFN = filepath.Join(b.contextPath, strings.ToLower(api.DefaultDockerfileName))
if _, err := os.Lstat(tmpFN); err == nil {
b.dockerfileName = strings.ToLower(api.DefaultDockerfileName)
}
}
}

origFile := b.dockerfileName

filename, err := symlink.FollowSymlinkInScope(filepath.Join(b.contextPath, origFile), b.contextPath)
if err != nil {
return fmt.Errorf("The Dockerfile (%s) must be within the build context", origFile)
Expand Down
9 changes: 5 additions & 4 deletions builder/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,6 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
}
}

if dockerfileName == "" {
dockerfileName = api.DefaultDockerfileName
}

if remoteURL == "" {
context = ioutil.NopCloser(job.Stdin)
} else if urlutil.IsGitURL(remoteURL) {
Expand Down Expand Up @@ -113,6 +109,11 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
if err != nil {
return job.Error(err)
}

// When we're downloading just a Dockerfile put it in
// the default name - don't allow the client to move/specify it
dockerfileName = api.DefaultDockerfileName

c, err := archive.Generate(dockerfileName, string(dockerFile))
if err != nil {
return job.Error(err)
Expand Down
7 changes: 5 additions & 2 deletions docs/sources/reference/api/docker_remote_api_v1.18.md
Original file line number Diff line number Diff line change
Expand Up @@ -1073,10 +1073,13 @@ command*](/reference/builder/#dockerbuilder)).

Query Parameters:

- **dockerfile** - path within the build context to the Dockerfile
- **dockerfile** - path within the build context to the Dockerfile. This is
ignored if `remote` is specified and points to an individual filename.
- **t** – repository name (and optionally a tag) to be applied to
the resulting image in case of success
- **remote** – git or HTTP/HTTPS URI build source
- **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the
URI specifies a filename, the file's contents are placed into a file
called `Dockerfile`.
- **q** – suppress verbose build output
- **nocache** – do not use the cache when building the image
- **pull** - attempt to pull the image even if an older image exists locally
Expand Down
38 changes: 22 additions & 16 deletions docs/sources/reference/commandline/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,21 +506,27 @@ is returned by the `docker attach` command to its caller too:
--rm=true Remove intermediate containers after a successful build
-t, --tag="" Repository name (and optionally a tag) for the image

Use this command to build Docker images from a Dockerfile and a
"context".

The files at `PATH` or `URL` are called the "context" of the build. The
build process may refer to any of the files in the context, for example
when using an [*ADD*](/reference/builder/#add) instruction.
When a single Dockerfile is given as `URL` or is piped through `STDIN`
(`docker build - < Dockerfile`), then no context is set.

When a Git repository is set as `URL`, then the repository is used as
the context. The Git repository is cloned with its submodules
(`git clone -recursive`). A fresh `git clone` occurs in a temporary directory
on your local host, and then this is sent to the Docker daemon as the
context. This way, your local user credentials and VPN's etc can be
used to access private repositories.
Builds Docker images from a Dockerfile and a "context". A build's context is
the files located in the specified `PATH` or `URL`. The build process can
refer to any of the files in the context. For example, your build can use
an [*ADD*](/reference/builder/#add) instruction to reference a file in the
context.

The `URL` parameter can specify the location of a Git repository; in this
case, the repository is the context. The Git repository is recursively
cloned with its submodules. The system does a fresh `git clone -recursive`
in a temporary directory on your local host. Then, this clone is sent to
the Docker daemon as the context. Local clones give you the ability to
access private repositories using local user credentials, VPN's, and so forth.

Instead of specifying a context, you can pass a single Dockerfile in the
`URL` or pipe the file in via `STDIN`. To pipe a Dockerfile from `STDIN`:

docker build - < Dockerfile

If you use STDIN or specify a `URL`, the system places the contents into a
file called `Dockerfile`, and any `-f`, `--file` option is ignored. In this
scenario, there is no context.

If a file named `.dockerignore` exists in the root of `PATH` then it
is interpreted as a newline-separated list of exclusion patterns.
Expand All @@ -529,7 +535,7 @@ will be excluded from the context. Globbing is done using Go's
[filepath.Match](http://golang.org/pkg/path/filepath#Match) rules.

Please note that `.dockerignore` files in other subdirectories are
considered as normal files. Filepaths in .dockerignore are absolute with
considered as normal files. Filepaths in `.dockerignore` are absolute with
the current directory as the root. Wildcards are allowed but the search
is not recursive.

Expand Down
100 changes: 100 additions & 0 deletions integration-cli/docker_api_containers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,106 @@ func TestBuildApiDockerfilePath(t *testing.T) {
logDone("container REST API - check build w/bad Dockerfile path")
}

func TestBuildApiDockerFileRemote(t *testing.T) {
server, err := fakeStorage(map[string]string{
"testD": `FROM busybox
COPY * /tmp/
RUN find /tmp/`,
})
if err != nil {
t.Fatal(err)
}
defer server.Close()

buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL+"/testD", nil, "application/json")
if err != nil {
t.Fatalf("Build failed: %s", err)
}

out := string(buf)
if !strings.Contains(out, "/tmp/Dockerfile") ||
strings.Contains(out, "/tmp/baz") {
t.Fatalf("Incorrect output: %s", out)
}

logDone("container REST API - check build with -f from remote")
}

func TestBuildApiLowerDockerfile(t *testing.T) {
git, err := fakeGIT("repo", map[string]string{
"dockerfile": `FROM busybox
RUN echo from dockerfile`,
})
if err != nil {
t.Fatal(err)
}
defer git.Close()

buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
if err != nil {
t.Fatalf("Build failed: %s\n%q", err, buf)
}

out := string(buf)
if !strings.Contains(out, "from dockerfile") {
t.Fatalf("Incorrect output: %s", out)
}

logDone("container REST API - check build with lower dockerfile")
}

func TestBuildApiBuildGitWithF(t *testing.T) {
git, err := fakeGIT("repo", map[string]string{
"baz": `FROM busybox
RUN echo from baz`,
"Dockerfile": `FROM busybox
RUN echo from Dockerfile`,
})
if err != nil {
t.Fatal(err)
}
defer git.Close()

// Make sure it tries to 'dockerfile' query param value
buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json")
if err != nil {
t.Fatalf("Build failed: %s\n%q", err, buf)
}

out := string(buf)
if !strings.Contains(out, "from baz") {
t.Fatalf("Incorrect output: %s", out)
}

logDone("container REST API - check build from git w/F")
}

func TestBuildApiDoubleDockerfile(t *testing.T) {
git, err := fakeGIT("repo", map[string]string{
"Dockerfile": `FROM busybox
RUN echo from Dockerfile`,
"dockerfile": `FROM busybox
RUN echo from dockerfile`,
})
if err != nil {
t.Fatal(err)
}
defer git.Close()

// Make sure it tries to 'dockerfile' query param value
buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
if err != nil {
t.Fatalf("Build failed: %s", err)
}

out := string(buf)
if !strings.Contains(out, "from Dockerfile") {
t.Fatalf("Incorrect output: %s", out)
}

logDone("container REST API - check build with two dockerfiles")
}

func TestBuildApiDockerfileSymlink(t *testing.T) {
// Test to make sure we stop people from trying to leave the
// build context when specifying a symlink as the path to the dockerfile
Expand Down
119 changes: 119 additions & 0 deletions integration-cli/docker_cli_build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4699,6 +4699,125 @@ func TestBuildRenamedDockerfile(t *testing.T) {
logDone("build - rename dockerfile")
}

func TestBuildFromMixedcaseDockerfile(t *testing.T) {
defer deleteImages("test1")

ctx, err := fakeContext(`FROM busybox
RUN echo from dockerfile`,
map[string]string{
"dockerfile": "FROM busybox\nRUN echo from dockerfile",
})
defer ctx.Close()
if err != nil {
t.Fatal(err)
}

out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".")
if err != nil {
t.Fatalf("Failed to build: %s\n%s", out, err)
}

if !strings.Contains(out, "from dockerfile") {
t.Fatalf("Missing proper output: %s", out)
}

logDone("build - mixedcase Dockerfile")
}

func TestBuildWithTwoDockerfiles(t *testing.T) {
defer deleteImages("test1")

ctx, err := fakeContext(`FROM busybox
RUN echo from Dockerfile`,
map[string]string{
"dockerfile": "FROM busybox\nRUN echo from dockerfile",
})
defer ctx.Close()
if err != nil {
t.Fatal(err)
}

out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".")
if err != nil {
t.Fatalf("Failed to build: %s\n%s", out, err)
}

if !strings.Contains(out, "from Dockerfile") {
t.Fatalf("Missing proper output: %s", out)
}

logDone("build - two Dockerfiles")
}

func TestBuildFromURLWithF(t *testing.T) {
defer deleteImages("test1")

server, err := fakeStorage(map[string]string{"baz": `FROM busybox
RUN echo from baz
COPY * /tmp/
RUN find /tmp/`})
if err != nil {
t.Fatal(err)
}
defer server.Close()

ctx, err := fakeContext(`FROM busybox
RUN echo from Dockerfile`,
map[string]string{})
defer ctx.Close()
if err != nil {
t.Fatal(err)
}

// Make sure that -f is ignored and that we don't use the Dockerfile
// that's in the current dir
out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-f", "baz", "-t", "test1", server.URL+"/baz")
if err != nil {
t.Fatalf("Failed to build: %s\n%s", out, err)
}

if !strings.Contains(out, "from baz") ||
strings.Contains(out, "/tmp/baz") ||
!strings.Contains(out, "/tmp/Dockerfile") {
t.Fatalf("Missing proper output: %s", out)
}

logDone("build - from URL with -f")
}

func TestBuildFromStdinWithF(t *testing.T) {
defer deleteImages("test1")

ctx, err := fakeContext(`FROM busybox
RUN echo from Dockerfile`,
map[string]string{})
defer ctx.Close()
if err != nil {
t.Fatal(err)
}

// Make sure that -f is ignored and that we don't use the Dockerfile
// that's in the current dir
dockerCommand := exec.Command(dockerBinary, "build", "-f", "baz", "-t", "test1", "-")
dockerCommand.Dir = ctx.Dir
dockerCommand.Stdin = strings.NewReader(`FROM busybox
RUN echo from baz
COPY * /tmp/
RUN find /tmp/`)
out, status, err := runCommandWithOutput(dockerCommand)
if err != nil || status != 0 {
t.Fatalf("Error building: %s", err)
}

if !strings.Contains(out, "from baz") ||
strings.Contains(out, "/tmp/baz") ||
!strings.Contains(out, "/tmp/Dockerfile") {
t.Fatalf("Missing proper output: %s", out)
}

logDone("build - from stdin with -f")
}

func TestBuildFromOfficialNames(t *testing.T) {
name := "testbuildfromofficial"
fromNames := []string{
Expand Down

0 comments on commit 15924f2

Please sign in to comment.