Support dockerfile and Dockerfile

Closes #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 <dug@us.ibm.com>
This commit is contained in:
Doug Davis 2015-02-17 10:25:36 -08:00
parent a61716e5d9
commit 15924f2385
7 changed files with 282 additions and 25 deletions

View file

@ -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)
@ -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

View file

@ -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"
@ -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
}
@ -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)

View file

@ -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) {
@ -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)

View file

@ -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

View file

@ -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".
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 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.
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.
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.
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.
@ -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.

View file

@ -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

View file

@ -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{