Browse Source

Merge pull request #10858 from duglin/10807-MixedcaseDockerfile

Support dockerfile and Dockerfile
Jessie Frazelle 10 years ago
parent
commit
beea697be3

+ 14 - 3
api/client/commands.go

@@ -114,9 +114,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 			if err != nil {
 			if err != nil {
 				return fmt.Errorf("failed to read Dockerfile from STDIN: %v", err)
 				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))
 			context, err = archive.Generate(*dockerfileName, string(dockerfile))
 		} else {
 		} else {
 			context = ioutil.NopCloser(buf)
 			context = ioutil.NopCloser(buf)
@@ -156,6 +157,16 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 			// No -f/--file was specified so use the default
 			// No -f/--file was specified so use the default
 			*dockerfileName = api.DefaultDockerfileName
 			*dockerfileName = api.DefaultDockerfileName
 			filename = filepath.Join(absRoot, *dockerfileName)
 			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
 		origDockerfile := *dockerfileName // used for error msg

+ 19 - 2
builder/evaluator.go

@@ -28,6 +28,7 @@ import (
 	"strings"
 	"strings"
 
 
 	log "github.com/Sirupsen/logrus"
 	log "github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api"
 	"github.com/docker/docker/builder/command"
 	"github.com/docker/docker/builder/command"
 	"github.com/docker/docker/builder/parser"
 	"github.com/docker/docker/builder/parser"
 	"github.com/docker/docker/daemon"
 	"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
 		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
 // Reads a Dockerfile from the current context. It assumes that the
 // 'filename' is a relative path from the root of the context
 // '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)
 	filename, err := symlink.FollowSymlinkInScope(filepath.Join(b.contextPath, origFile), b.contextPath)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("The Dockerfile (%s) must be within the build context", origFile)
 		return fmt.Errorf("The Dockerfile (%s) must be within the build context", origFile)

+ 5 - 4
builder/job.go

@@ -78,10 +78,6 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
 		}
 		}
 	}
 	}
 
 
-	if dockerfileName == "" {
-		dockerfileName = api.DefaultDockerfileName
-	}
-
 	if remoteURL == "" {
 	if remoteURL == "" {
 		context = ioutil.NopCloser(job.Stdin)
 		context = ioutil.NopCloser(job.Stdin)
 	} else if urlutil.IsGitURL(remoteURL) {
 	} else if urlutil.IsGitURL(remoteURL) {
@@ -113,6 +109,11 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
 		if err != nil {
 		if err != nil {
 			return job.Error(err)
 			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))
 		c, err := archive.Generate(dockerfileName, string(dockerFile))
 		if err != nil {
 		if err != nil {
 			return job.Error(err)
 			return job.Error(err)

+ 5 - 2
docs/sources/reference/api/docker_remote_api_v1.18.md

@@ -1073,10 +1073,13 @@ command*](/reference/builder/#dockerbuilder)).
 
 
 Query Parameters:
 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
 -   **t** – repository name (and optionally a tag) to be applied to
         the resulting image in case of success
         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
 -   **q** – suppress verbose build output
 -   **nocache** – do not use the cache when building the image
 -   **nocache** – do not use the cache when building the image
 -   **pull** - attempt to pull the image even if an older image exists locally
 -   **pull** - attempt to pull the image even if an older image exists locally

+ 22 - 16
docs/sources/reference/commandline/cli.md

@@ -506,21 +506,27 @@ is returned by the `docker attach` command to its caller too:
       --rm=true                Remove intermediate containers after a successful build
       --rm=true                Remove intermediate containers after a successful build
       -t, --tag=""             Repository name (and optionally a tag) for the image
       -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
 If a file named `.dockerignore` exists in the root of `PATH` then it
 is interpreted as a newline-separated list of exclusion patterns.
 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.
 [filepath.Match](http://golang.org/pkg/path/filepath#Match) rules.
 
 
 Please note that `.dockerignore` files in other subdirectories are
 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
 the current directory as the root. Wildcards are allowed but the search
 is not recursive.
 is not recursive.
 
 

+ 100 - 0
integration-cli/docker_api_containers_test.go

@@ -353,6 +353,106 @@ func TestBuildApiDockerfilePath(t *testing.T) {
 	logDone("container REST API - check build w/bad Dockerfile path")
 	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) {
 func TestBuildApiDockerfileSymlink(t *testing.T) {
 	// Test to make sure we stop people from trying to leave the
 	// Test to make sure we stop people from trying to leave the
 	// build context when specifying a symlink as the path to the dockerfile
 	// build context when specifying a symlink as the path to the dockerfile

+ 119 - 0
integration-cli/docker_cli_build_test.go

@@ -4699,6 +4699,125 @@ func TestBuildRenamedDockerfile(t *testing.T) {
 	logDone("build - rename dockerfile")
 	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) {
 func TestBuildFromOfficialNames(t *testing.T) {
 	name := "testbuildfromofficial"
 	name := "testbuildfromofficial"
 	fromNames := []string{
 	fromNames := []string{