Allow for Dockerfile to be named something else.

Add a check to make sure Dockerfile is in the build context
Add docs and a testcase
Make -f relative to current dir, not build context

Signed-off-by: Doug Davis <dug@us.ibm.com>
This commit is contained in:
Doug Davis 2014-09-11 07:42:17 -07:00
parent 6d780139c4
commit eb3ea3b43c
9 changed files with 253 additions and 32 deletions

View File

@ -14,6 +14,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strconv"
"strings"
@ -84,6 +85,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers, even after unsuccessful builds")
pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile(Default is 'Dockerfile' at context root)")
cmd.Require(flag.Exact, 1)
utils.ParseFlags(cmd, args, true)
@ -109,7 +112,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
if err != nil {
return fmt.Errorf("failed to read Dockerfile from STDIN: %v", err)
}
context, err = archive.Generate("Dockerfile", string(dockerfile))
if *dockerfileName == "" {
*dockerfileName = api.DefaultDockerfileName
}
context, err = archive.Generate(*dockerfileName, string(dockerfile))
} else {
context = ioutil.NopCloser(buf)
}
@ -136,9 +142,40 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
if _, err := os.Stat(root); err != nil {
return err
}
filename := path.Join(root, "Dockerfile")
absRoot, err := filepath.Abs(root)
if err != nil {
return err
}
var filename string // path to Dockerfile
var origDockerfile string // used for error msg
if *dockerfileName == "" {
// No -f/--file was specified so use the default
origDockerfile = api.DefaultDockerfileName
*dockerfileName = origDockerfile
filename = path.Join(absRoot, *dockerfileName)
} else {
origDockerfile = *dockerfileName
if filename, err = filepath.Abs(*dockerfileName); err != nil {
return err
}
// Verify that 'filename' is within the build context
if !strings.HasSuffix(absRoot, string(os.PathSeparator)) {
absRoot += string(os.PathSeparator)
}
if !strings.HasPrefix(filename, absRoot) {
return fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", *dockerfileName, root)
}
// Now reset the dockerfileName to be relative to the build context
*dockerfileName = filename[len(absRoot):]
}
if _, err = os.Stat(filename); os.IsNotExist(err) {
return fmt.Errorf("no Dockerfile found in %s", cmd.Arg(0))
return fmt.Errorf("Can not locate Dockerfile: %s", origDockerfile)
}
var includes []string = []string{"."}
@ -147,16 +184,16 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
return err
}
// If .dockerignore mentions .dockerignore or Dockerfile
// If .dockerignore mentions .dockerignore or the Dockerfile
// then make sure we send both files over to the daemon
// because Dockerfile is, obviously, needed no matter what, and
// .dockerignore is needed to know if either one needs to be
// removed. The deamon will remove them for us, if needed, after it
// parses the Dockerfile.
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
keepThem2, _ := fileutils.Matches("Dockerfile", excludes)
keepThem2, _ := fileutils.Matches(*dockerfileName, excludes)
if keepThem1 || keepThem2 {
includes = append(includes, ".dockerignore", "Dockerfile")
includes = append(includes, ".dockerignore", *dockerfileName)
}
if err = utils.ValidateContextDirectory(root, excludes); err != nil {
@ -219,6 +256,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
if *pull {
v.Set("pull", "1")
}
v.Set("dockerfile", *dockerfileName)
cli.LoadConfigFile()
headers := http.Header(make(map[string][]string))

View File

@ -15,9 +15,10 @@ import (
)
const (
APIVERSION version.Version = "1.16"
DEFAULTHTTPHOST = "127.0.0.1"
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
APIVERSION version.Version = "1.16"
DEFAULTHTTPHOST = "127.0.0.1"
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
DefaultDockerfileName string = "Dockerfile"
)
func ValidateHost(val string) (string, error) {

View File

@ -1035,6 +1035,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
}
job.Stdin.Add(r.Body)
job.Setenv("remote", r.FormValue("remote"))
job.Setenv("dockerfile", r.FormValue("dockerfile"))
job.Setenv("t", r.FormValue("t"))
job.Setenv("q", r.FormValue("q"))
job.Setenv("nocache", r.FormValue("nocache"))

View File

@ -105,13 +105,14 @@ type Builder struct {
// both of these are controlled by the Remove and ForceRemove options in BuildOpts
TmpContainers map[string]struct{} // a map of containers used for removes
dockerfile *parser.Node // the syntax tree of the dockerfile
image string // image name for commit processing
maintainer string // maintainer name. could probably be removed.
cmdSet bool // indicates is CMD was set in current Dockerfile
context tarsum.TarSum // the context is a tarball that is uploaded by the client
contextPath string // the path of the temporary directory the local context is unpacked to (server side)
noBaseImage bool // indicates that this build does not start from any base image, but is being built from an empty file system.
dockerfileName string // name of Dockerfile
dockerfile *parser.Node // the syntax tree of the dockerfile
image string // image name for commit processing
maintainer string // maintainer name. could probably be removed.
cmdSet bool // indicates is CMD was set in current Dockerfile
context tarsum.TarSum // the context is a tarball that is uploaded by the client
contextPath string // the path of the temporary directory the local context is unpacked to (server side)
noBaseImage bool // indicates that this build does not start from any base image, but is being built from an empty file system.
}
// Run the builder with the context. This is the lynchpin of this package. This
@ -137,7 +138,7 @@ func (b *Builder) Run(context io.Reader) (string, error) {
}
}()
if err := b.readDockerfile("Dockerfile"); err != nil {
if err := b.readDockerfile(b.dockerfileName); err != nil {
return "", err
}
@ -205,9 +206,9 @@ func (b *Builder) readDockerfile(filename string) error {
os.Remove(path.Join(b.contextPath, ".dockerignore"))
b.context.(tarsum.BuilderContext).Remove(".dockerignore")
}
if rm, _ := fileutils.Matches("Dockerfile", excludes); rm == true {
os.Remove(path.Join(b.contextPath, "Dockerfile"))
b.context.(tarsum.BuilderContext).Remove("Dockerfile")
if rm, _ := fileutils.Matches(b.dockerfileName, excludes); rm == true {
os.Remove(path.Join(b.contextPath, b.dockerfileName))
b.context.(tarsum.BuilderContext).Remove(b.dockerfileName)
}
return nil

View File

@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"github.com/docker/docker/api"
"github.com/docker/docker/daemon"
"github.com/docker/docker/engine"
"github.com/docker/docker/graph"
@ -30,6 +31,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
return job.Errorf("Usage: %s\n", job.Name)
}
var (
dockerfileName = job.Getenv("dockerfile")
remoteURL = job.Getenv("remote")
repoName = job.Getenv("t")
suppressOutput = job.GetenvBool("q")
@ -42,6 +44,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
tag string
context io.ReadCloser
)
job.GetenvJson("authConfig", authConfig)
job.GetenvJson("configFile", configFile)
@ -57,6 +60,10 @@ 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) {
@ -88,7 +95,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
if err != nil {
return job.Error(err)
}
c, err := archive.Generate("Dockerfile", string(dockerFile))
c, err := archive.Generate(dockerfileName, string(dockerFile))
if err != nil {
return job.Error(err)
}
@ -118,6 +125,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
StreamFormatter: sf,
AuthConfig: authConfig,
AuthConfigFile: configFile,
dockerfileName: dockerfileName,
}
id, err := builder.Run(context)

View File

@ -7,6 +7,7 @@ docker-build - Build a new image from the source code at PATH
# SYNOPSIS
**docker build**
[**--help**]
[**-f**|**--file**[=*Dockerfile*]]
[**--force-rm**[=*false*]]
[**--no-cache**[=*false*]]
[**-q**|**--quiet**[=*false*]]
@ -31,6 +32,9 @@ When a Git repository is set as the **URL**, the repository is used
as context.
# OPTIONS
**-f**, **--file**=*Dockerfile*
Path to the Dockerfile to use. If the path is a relative path then it must be relative to the current directory. The file must be within the build context. The default is *Dockerfile*.
**--force-rm**=*true*|*false*
Always remove intermediate containers, even after unsuccessful builds. The default is *false*.

View File

@ -1157,16 +1157,21 @@ Build an image from Dockerfile via stdin
{"stream": "..."}
{"error": "Error...", "errorDetail": {"code": 123, "message": "Error..."}}
The stream must be a tar archive compressed with one of the
following algorithms: identity (no compression), gzip, bzip2, xz.
The input stream must be a tar archive compressed with one of the
following algorithms: identity (no compression), gzip, bzip2, xz.
The archive must include a file called `Dockerfile`
at its root. It may include any number of other files,
which will be accessible in the build context (See the [*ADD build
command*](/reference/builder/#dockerbuilder)).
The archive must include a build instructions file, typically called
`Dockerfile` at the root of the archive. The `f` parameter may be used
to specify a different build instructions file by having its value be
the path to the alternate build instructions file to use.
The archive may include any number of other files,
which will be accessible in the build context (See the [*ADD build
command*](/reference/builder/#dockerbuilder)).
Query Parameters:
- **dockerfile** - path within the build context to the Dockerfile
- **t** repository name (and optionally a tag) to be applied to
the resulting image in case of success
- **q** suppress verbose build output

View File

@ -461,11 +461,12 @@ To kill the container, use `docker kill`.
Build a new image from the source code at PATH
--force-rm=false Always remove intermediate containers, even after unsuccessful builds
--no-cache=false Do not use cache when building the image
-q, --quiet=false Suppress the verbose output generated by the containers
--rm=true Remove intermediate containers after a successful build
-t, --tag="" Repository name (and optionally a tag) to be applied to the resulting image in case of success
-f, --file="" Location of the Dockerfile to use. Default is 'Dockerfile' at the root of the build context
--force-rm=false Always remove intermediate containers, even after unsuccessful builds
--no-cache=false Do not use cache when building the image
-q, --quiet=false Suppress the verbose output generated by the containers
--rm=true Remove intermediate containers after a successful build
-t, --tag="" Repository name (and optionally a tag) to be applied to the resulting image in case of success
Use this command to build Docker images from a Dockerfile and a
"context".
@ -510,6 +511,13 @@ For example, the files `tempa`, `tempb` are ignored from the root directory.
Currently there is no support for regular expressions. Formats
like `[^temp*]` are ignored.
By default the `docker build` command will look for a `Dockerfile` at the
root of the build context. The `-f`, `--file`, option lets you specify
the path to an alternative file to use instead. This is useful
in cases where the same set of files are used for multiple builds. The path
must be to a file within the build context. If a relative path is specified
then it must to be relative to the current directory.
See also:
@ -612,6 +620,28 @@ repository is used as Dockerfile. Note that you
can specify an arbitrary Git repository by using the `git://` or `git@`
schema.
$ sudo docker build -f Dockerfile.debug .
This will use a file called `Dockerfile.debug` for the build
instructions instead of `Dockerfile`.
$ sudo docker build -f dockerfiles/Dockerfile.debug -t myapp_debug .
$ sudo docker build -f dockerfiles/Dockerfile.prod -t myapp_prod .
The above commands will build the current build context (as specified by
the `.`) twice, once using a debug version of a `Dockerfile` and once using
a production version.
$ cd /home/me/myapp/some/dir/really/deep
$ sudo docker build -f /home/me/myapp/dockerfiles/debug /home/me/myapp
$ sudo docker build -f ../../../../dockerfiles/debug /home/me/myapp
These two `docker build` commands do the exact same thing. They both
use the contents of the `debug` file instead of looking for a `Dockerfile`
and will use `/home/me/myapp` as the root of the build context. Note that
`debug` is in the directory structure of the build context, regardless of how
you refer to it on the command line.
> **Note:** `docker build` will return a `no such file or directory` error
> if the file or directory does not exist in the uploaded context. This may
> happen if there is no context, or if you specify a file that is elsewhere

View File

@ -3155,6 +3155,36 @@ func TestBuildDockerignoringDockerfile(t *testing.T) {
logDone("build - test .dockerignore of Dockerfile")
}
func TestBuildDockerignoringRenamedDockerfile(t *testing.T) {
name := "testbuilddockerignoredockerfile"
defer deleteImages(name)
dockerfile := `
FROM busybox
ADD . /tmp/
RUN ls /tmp/Dockerfile
RUN ! 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 {
t.Fatal(err)
}
if _, err = buildImageFromContext(name, ctx, true); err != nil {
t.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 {
t.Fatalf("Didn't ignore ./MyDockerfile correctly:%s", err)
}
logDone("build - test .dockerignore of renamed Dockerfile")
}
func TestBuildDockerignoringDockerignore(t *testing.T) {
name := "testbuilddockerignoredockerignore"
defer deleteImages(name)
@ -4170,3 +4200,104 @@ CMD cat /foo/file`,
logDone("build - volumes retain contents in build")
}
func TestBuildRenamedDockerfile(t *testing.T) {
defer deleteAllContainers()
ctx, err := fakeContext(`FROM busybox
RUN echo from Dockerfile`,
map[string]string{
"Dockerfile": "FROM busybox\nRUN echo from Dockerfile",
"files/Dockerfile": "FROM busybox\nRUN echo from files/Dockerfile",
"files/dFile": "FROM busybox\nRUN echo from files/dFile",
"dFile": "FROM busybox\nRUN echo from dFile",
})
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("Should have used Dockerfile, output:%s", out)
}
out, _, err = dockerCmdInDir(t, ctx.Dir, "build", "-f", "files/Dockerfile", "-t", "test2", ".")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out, "from files/Dockerfile") {
t.Fatalf("Should have used files/Dockerfile, output:%s", out)
}
out, _, err = dockerCmdInDir(t, ctx.Dir, "build", "--file=files/dFile", "-t", "test3", ".")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out, "from files/dFile") {
t.Fatalf("Should have used files/dFile, output:%s", out)
}
out, _, err = dockerCmdInDir(t, ctx.Dir, "build", "--file=dFile", "-t", "test4", ".")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out, "from dFile") {
t.Fatalf("Should have used dFile, output:%s", out)
}
out, _, err = dockerCmdInDir(t, ctx.Dir, "build", "--file=/etc/passwd", "-t", "test5", ".")
if err == nil {
t.Fatalf("Was supposed to fail to find passwd")
}
if !strings.Contains(out, "The Dockerfile (/etc/passwd) must be within the build context (.)") {
t.Fatalf("Wrong error message for passwd:%v", out)
}
out, _, err = dockerCmdInDir(t, ctx.Dir+"/files", "build", "-f", "../Dockerfile", "-t", "test5", "..")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out, "from Dockerfile") {
t.Fatalf("Should have used root Dockerfile, output:%s", out)
}
out, _, err = dockerCmdInDir(t, ctx.Dir+"/files", "build", "-f", ctx.Dir+"/files/Dockerfile", "-t", "test6", "..")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out, "from files/Dockerfile") {
t.Fatalf("Should have used files Dockerfile - 2, output:%s", out)
}
out, _, err = dockerCmdInDir(t, ctx.Dir+"/files", "build", "-f", "../Dockerfile", "-t", "test7", ".")
if err == nil || !strings.Contains(out, "must be within the build context") {
t.Fatalf("Should have failed with Dockerfile out of context")
}
out, _, err = dockerCmdInDir(t, "/tmp", "build", "-t", "test6", ctx.Dir)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out, "from Dockerfile") {
t.Fatalf("Should have used root Dockerfile, output:%s", out)
}
logDone("build - rename dockerfile")
}