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>
... | ... |
@@ -114,9 +114,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error { |
114 | 114 |
if err != nil { |
115 | 115 |
return fmt.Errorf("failed to read Dockerfile from STDIN: %v", err) |
116 | 116 |
} |
117 |
- if *dockerfileName == "" { |
|
118 |
- *dockerfileName = api.DefaultDockerfileName |
|
119 |
- } |
|
117 |
+ |
|
118 |
+ // -f option has no meaning when we're reading it from stdin, |
|
119 |
+ // so just use our default Dockerfile name |
|
120 |
+ *dockerfileName = api.DefaultDockerfileName |
|
120 | 121 |
context, err = archive.Generate(*dockerfileName, string(dockerfile)) |
121 | 122 |
} else { |
122 | 123 |
context = ioutil.NopCloser(buf) |
... | ... |
@@ -156,6 +157,16 @@ func (cli *DockerCli) CmdBuild(args ...string) error { |
156 | 156 |
// No -f/--file was specified so use the default |
157 | 157 |
*dockerfileName = api.DefaultDockerfileName |
158 | 158 |
filename = filepath.Join(absRoot, *dockerfileName) |
159 |
+ |
|
160 |
+ // Just to be nice ;-) look for 'dockerfile' too but only |
|
161 |
+ // use it if we found it, otherwise ignore this check |
|
162 |
+ if _, err = os.Lstat(filename); os.IsNotExist(err) { |
|
163 |
+ tmpFN := path.Join(absRoot, strings.ToLower(*dockerfileName)) |
|
164 |
+ if _, err = os.Lstat(tmpFN); err == nil { |
|
165 |
+ *dockerfileName = strings.ToLower(*dockerfileName) |
|
166 |
+ filename = tmpFN |
|
167 |
+ } |
|
168 |
+ } |
|
159 | 169 |
} |
160 | 170 |
|
161 | 171 |
origDockerfile := *dockerfileName // used for error msg |
... | ... |
@@ -28,6 +28,7 @@ import ( |
28 | 28 |
"strings" |
29 | 29 |
|
30 | 30 |
log "github.com/Sirupsen/logrus" |
31 |
+ "github.com/docker/docker/api" |
|
31 | 32 |
"github.com/docker/docker/builder/command" |
32 | 33 |
"github.com/docker/docker/builder/parser" |
33 | 34 |
"github.com/docker/docker/daemon" |
... | ... |
@@ -146,7 +147,7 @@ func (b *Builder) Run(context io.Reader) (string, error) { |
146 | 146 |
} |
147 | 147 |
}() |
148 | 148 |
|
149 |
- if err := b.readDockerfile(b.dockerfileName); err != nil { |
|
149 |
+ if err := b.readDockerfile(); err != nil { |
|
150 | 150 |
return "", err |
151 | 151 |
} |
152 | 152 |
|
... | ... |
@@ -177,7 +178,23 @@ func (b *Builder) Run(context io.Reader) (string, error) { |
177 | 177 |
|
178 | 178 |
// Reads a Dockerfile from the current context. It assumes that the |
179 | 179 |
// 'filename' is a relative path from the root of the context |
180 |
-func (b *Builder) readDockerfile(origFile string) error { |
|
180 |
+func (b *Builder) readDockerfile() error { |
|
181 |
+ // If no -f was specified then look for 'Dockerfile'. If we can't find |
|
182 |
+ // that then look for 'dockerfile'. If neither are found then default |
|
183 |
+ // back to 'Dockerfile' and use that in the error message. |
|
184 |
+ if b.dockerfileName == "" { |
|
185 |
+ b.dockerfileName = api.DefaultDockerfileName |
|
186 |
+ tmpFN := filepath.Join(b.contextPath, api.DefaultDockerfileName) |
|
187 |
+ if _, err := os.Lstat(tmpFN); err != nil { |
|
188 |
+ tmpFN = filepath.Join(b.contextPath, strings.ToLower(api.DefaultDockerfileName)) |
|
189 |
+ if _, err := os.Lstat(tmpFN); err == nil { |
|
190 |
+ b.dockerfileName = strings.ToLower(api.DefaultDockerfileName) |
|
191 |
+ } |
|
192 |
+ } |
|
193 |
+ } |
|
194 |
+ |
|
195 |
+ origFile := b.dockerfileName |
|
196 |
+ |
|
181 | 197 |
filename, err := symlink.FollowSymlinkInScope(filepath.Join(b.contextPath, origFile), b.contextPath) |
182 | 198 |
if err != nil { |
183 | 199 |
return fmt.Errorf("The Dockerfile (%s) must be within the build context", origFile) |
... | ... |
@@ -78,10 +78,6 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status { |
78 | 78 |
} |
79 | 79 |
} |
80 | 80 |
|
81 |
- if dockerfileName == "" { |
|
82 |
- dockerfileName = api.DefaultDockerfileName |
|
83 |
- } |
|
84 |
- |
|
85 | 81 |
if remoteURL == "" { |
86 | 82 |
context = ioutil.NopCloser(job.Stdin) |
87 | 83 |
} else if urlutil.IsGitURL(remoteURL) { |
... | ... |
@@ -113,6 +109,11 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status { |
113 | 113 |
if err != nil { |
114 | 114 |
return job.Error(err) |
115 | 115 |
} |
116 |
+ |
|
117 |
+ // When we're downloading just a Dockerfile put it in |
|
118 |
+ // the default name - don't allow the client to move/specify it |
|
119 |
+ dockerfileName = api.DefaultDockerfileName |
|
120 |
+ |
|
116 | 121 |
c, err := archive.Generate(dockerfileName, string(dockerFile)) |
117 | 122 |
if err != nil { |
118 | 123 |
return job.Error(err) |
... | ... |
@@ -1073,10 +1073,13 @@ command*](/reference/builder/#dockerbuilder)). |
1073 | 1073 |
|
1074 | 1074 |
Query Parameters: |
1075 | 1075 |
|
1076 |
-- **dockerfile** - path within the build context to the Dockerfile |
|
1076 |
+- **dockerfile** - path within the build context to the Dockerfile. This is |
|
1077 |
+ ignored if `remote` is specified and points to an individual filename. |
|
1077 | 1078 |
- **t** – repository name (and optionally a tag) to be applied to |
1078 | 1079 |
the resulting image in case of success |
1079 |
-- **remote** – git or HTTP/HTTPS URI build source |
|
1080 |
+- **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the |
|
1081 |
+ URI specifies a filename, the file's contents are placed into a file |
|
1082 |
+ called `Dockerfile`. |
|
1080 | 1083 |
- **q** – suppress verbose build output |
1081 | 1084 |
- **nocache** – do not use the cache when building the image |
1082 | 1085 |
- **pull** - attempt to pull the image even if an older image exists locally |
... | ... |
@@ -506,21 +506,27 @@ is returned by the `docker attach` command to its caller too: |
506 | 506 |
--rm=true Remove intermediate containers after a successful build |
507 | 507 |
-t, --tag="" Repository name (and optionally a tag) for the image |
508 | 508 |
|
509 |
-Use this command to build Docker images from a Dockerfile and a |
|
510 |
-"context". |
|
511 |
- |
|
512 |
-The files at `PATH` or `URL` are called the "context" of the build. The |
|
513 |
-build process may refer to any of the files in the context, for example |
|
514 |
-when using an [*ADD*](/reference/builder/#add) instruction. |
|
515 |
-When a single Dockerfile is given as `URL` or is piped through `STDIN` |
|
516 |
-(`docker build - < Dockerfile`), then no context is set. |
|
517 |
- |
|
518 |
-When a Git repository is set as `URL`, then the repository is used as |
|
519 |
-the context. The Git repository is cloned with its submodules |
|
520 |
-(`git clone -recursive`). A fresh `git clone` occurs in a temporary directory |
|
521 |
-on your local host, and then this is sent to the Docker daemon as the |
|
522 |
-context. This way, your local user credentials and VPN's etc can be |
|
523 |
-used to access private repositories. |
|
509 |
+Builds Docker images from a Dockerfile and a "context". A build's context is |
|
510 |
+the files located in the specified `PATH` or `URL`. The build process can |
|
511 |
+refer to any of the files in the context. For example, your build can use |
|
512 |
+an [*ADD*](/reference/builder/#add) instruction to reference a file in the |
|
513 |
+context. |
|
514 |
+ |
|
515 |
+The `URL` parameter can specify the location of a Git repository; in this |
|
516 |
+case, the repository is the context. The Git repository is recursively |
|
517 |
+cloned with its submodules. The system does a fresh `git clone -recursive` |
|
518 |
+in a temporary directory on your local host. Then, this clone is sent to |
|
519 |
+the Docker daemon as the context. Local clones give you the ability to |
|
520 |
+access private repositories using local user credentials, VPN's, and so forth. |
|
521 |
+ |
|
522 |
+Instead of specifying a context, you can pass a single Dockerfile in the |
|
523 |
+`URL` or pipe the file in via `STDIN`. To pipe a Dockerfile from `STDIN`: |
|
524 |
+ |
|
525 |
+ docker build - < Dockerfile |
|
526 |
+ |
|
527 |
+If you use STDIN or specify a `URL`, the system places the contents into a |
|
528 |
+file called `Dockerfile`, and any `-f`, `--file` option is ignored. In this |
|
529 |
+scenario, there is no context. |
|
524 | 530 |
|
525 | 531 |
If a file named `.dockerignore` exists in the root of `PATH` then it |
526 | 532 |
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 |
529 | 529 |
[filepath.Match](http://golang.org/pkg/path/filepath#Match) rules. |
530 | 530 |
|
531 | 531 |
Please note that `.dockerignore` files in other subdirectories are |
532 |
-considered as normal files. Filepaths in .dockerignore are absolute with |
|
532 |
+considered as normal files. Filepaths in `.dockerignore` are absolute with |
|
533 | 533 |
the current directory as the root. Wildcards are allowed but the search |
534 | 534 |
is not recursive. |
535 | 535 |
|
... | ... |
@@ -353,6 +353,106 @@ func TestBuildApiDockerfilePath(t *testing.T) { |
353 | 353 |
logDone("container REST API - check build w/bad Dockerfile path") |
354 | 354 |
} |
355 | 355 |
|
356 |
+func TestBuildApiDockerFileRemote(t *testing.T) { |
|
357 |
+ server, err := fakeStorage(map[string]string{ |
|
358 |
+ "testD": `FROM busybox |
|
359 |
+COPY * /tmp/ |
|
360 |
+RUN find /tmp/`, |
|
361 |
+ }) |
|
362 |
+ if err != nil { |
|
363 |
+ t.Fatal(err) |
|
364 |
+ } |
|
365 |
+ defer server.Close() |
|
366 |
+ |
|
367 |
+ buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL+"/testD", nil, "application/json") |
|
368 |
+ if err != nil { |
|
369 |
+ t.Fatalf("Build failed: %s", err) |
|
370 |
+ } |
|
371 |
+ |
|
372 |
+ out := string(buf) |
|
373 |
+ if !strings.Contains(out, "/tmp/Dockerfile") || |
|
374 |
+ strings.Contains(out, "/tmp/baz") { |
|
375 |
+ t.Fatalf("Incorrect output: %s", out) |
|
376 |
+ } |
|
377 |
+ |
|
378 |
+ logDone("container REST API - check build with -f from remote") |
|
379 |
+} |
|
380 |
+ |
|
381 |
+func TestBuildApiLowerDockerfile(t *testing.T) { |
|
382 |
+ git, err := fakeGIT("repo", map[string]string{ |
|
383 |
+ "dockerfile": `FROM busybox |
|
384 |
+RUN echo from dockerfile`, |
|
385 |
+ }) |
|
386 |
+ if err != nil { |
|
387 |
+ t.Fatal(err) |
|
388 |
+ } |
|
389 |
+ defer git.Close() |
|
390 |
+ |
|
391 |
+ buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") |
|
392 |
+ if err != nil { |
|
393 |
+ t.Fatalf("Build failed: %s\n%q", err, buf) |
|
394 |
+ } |
|
395 |
+ |
|
396 |
+ out := string(buf) |
|
397 |
+ if !strings.Contains(out, "from dockerfile") { |
|
398 |
+ t.Fatalf("Incorrect output: %s", out) |
|
399 |
+ } |
|
400 |
+ |
|
401 |
+ logDone("container REST API - check build with lower dockerfile") |
|
402 |
+} |
|
403 |
+ |
|
404 |
+func TestBuildApiBuildGitWithF(t *testing.T) { |
|
405 |
+ git, err := fakeGIT("repo", map[string]string{ |
|
406 |
+ "baz": `FROM busybox |
|
407 |
+RUN echo from baz`, |
|
408 |
+ "Dockerfile": `FROM busybox |
|
409 |
+RUN echo from Dockerfile`, |
|
410 |
+ }) |
|
411 |
+ if err != nil { |
|
412 |
+ t.Fatal(err) |
|
413 |
+ } |
|
414 |
+ defer git.Close() |
|
415 |
+ |
|
416 |
+ // Make sure it tries to 'dockerfile' query param value |
|
417 |
+ buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") |
|
418 |
+ if err != nil { |
|
419 |
+ t.Fatalf("Build failed: %s\n%q", err, buf) |
|
420 |
+ } |
|
421 |
+ |
|
422 |
+ out := string(buf) |
|
423 |
+ if !strings.Contains(out, "from baz") { |
|
424 |
+ t.Fatalf("Incorrect output: %s", out) |
|
425 |
+ } |
|
426 |
+ |
|
427 |
+ logDone("container REST API - check build from git w/F") |
|
428 |
+} |
|
429 |
+ |
|
430 |
+func TestBuildApiDoubleDockerfile(t *testing.T) { |
|
431 |
+ git, err := fakeGIT("repo", map[string]string{ |
|
432 |
+ "Dockerfile": `FROM busybox |
|
433 |
+RUN echo from Dockerfile`, |
|
434 |
+ "dockerfile": `FROM busybox |
|
435 |
+RUN echo from dockerfile`, |
|
436 |
+ }) |
|
437 |
+ if err != nil { |
|
438 |
+ t.Fatal(err) |
|
439 |
+ } |
|
440 |
+ defer git.Close() |
|
441 |
+ |
|
442 |
+ // Make sure it tries to 'dockerfile' query param value |
|
443 |
+ buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") |
|
444 |
+ if err != nil { |
|
445 |
+ t.Fatalf("Build failed: %s", err) |
|
446 |
+ } |
|
447 |
+ |
|
448 |
+ out := string(buf) |
|
449 |
+ if !strings.Contains(out, "from Dockerfile") { |
|
450 |
+ t.Fatalf("Incorrect output: %s", out) |
|
451 |
+ } |
|
452 |
+ |
|
453 |
+ logDone("container REST API - check build with two dockerfiles") |
|
454 |
+} |
|
455 |
+ |
|
356 | 456 |
func TestBuildApiDockerfileSymlink(t *testing.T) { |
357 | 457 |
// Test to make sure we stop people from trying to leave the |
358 | 458 |
// build context when specifying a symlink as the path to the dockerfile |
... | ... |
@@ -4699,6 +4699,125 @@ func TestBuildRenamedDockerfile(t *testing.T) { |
4699 | 4699 |
logDone("build - rename dockerfile") |
4700 | 4700 |
} |
4701 | 4701 |
|
4702 |
+func TestBuildFromMixedcaseDockerfile(t *testing.T) { |
|
4703 |
+ defer deleteImages("test1") |
|
4704 |
+ |
|
4705 |
+ ctx, err := fakeContext(`FROM busybox |
|
4706 |
+ RUN echo from dockerfile`, |
|
4707 |
+ map[string]string{ |
|
4708 |
+ "dockerfile": "FROM busybox\nRUN echo from dockerfile", |
|
4709 |
+ }) |
|
4710 |
+ defer ctx.Close() |
|
4711 |
+ if err != nil { |
|
4712 |
+ t.Fatal(err) |
|
4713 |
+ } |
|
4714 |
+ |
|
4715 |
+ out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".") |
|
4716 |
+ if err != nil { |
|
4717 |
+ t.Fatalf("Failed to build: %s\n%s", out, err) |
|
4718 |
+ } |
|
4719 |
+ |
|
4720 |
+ if !strings.Contains(out, "from dockerfile") { |
|
4721 |
+ t.Fatalf("Missing proper output: %s", out) |
|
4722 |
+ } |
|
4723 |
+ |
|
4724 |
+ logDone("build - mixedcase Dockerfile") |
|
4725 |
+} |
|
4726 |
+ |
|
4727 |
+func TestBuildWithTwoDockerfiles(t *testing.T) { |
|
4728 |
+ defer deleteImages("test1") |
|
4729 |
+ |
|
4730 |
+ ctx, err := fakeContext(`FROM busybox |
|
4731 |
+RUN echo from Dockerfile`, |
|
4732 |
+ map[string]string{ |
|
4733 |
+ "dockerfile": "FROM busybox\nRUN echo from dockerfile", |
|
4734 |
+ }) |
|
4735 |
+ defer ctx.Close() |
|
4736 |
+ if err != nil { |
|
4737 |
+ t.Fatal(err) |
|
4738 |
+ } |
|
4739 |
+ |
|
4740 |
+ out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".") |
|
4741 |
+ if err != nil { |
|
4742 |
+ t.Fatalf("Failed to build: %s\n%s", out, err) |
|
4743 |
+ } |
|
4744 |
+ |
|
4745 |
+ if !strings.Contains(out, "from Dockerfile") { |
|
4746 |
+ t.Fatalf("Missing proper output: %s", out) |
|
4747 |
+ } |
|
4748 |
+ |
|
4749 |
+ logDone("build - two Dockerfiles") |
|
4750 |
+} |
|
4751 |
+ |
|
4752 |
+func TestBuildFromURLWithF(t *testing.T) { |
|
4753 |
+ defer deleteImages("test1") |
|
4754 |
+ |
|
4755 |
+ server, err := fakeStorage(map[string]string{"baz": `FROM busybox |
|
4756 |
+RUN echo from baz |
|
4757 |
+COPY * /tmp/ |
|
4758 |
+RUN find /tmp/`}) |
|
4759 |
+ if err != nil { |
|
4760 |
+ t.Fatal(err) |
|
4761 |
+ } |
|
4762 |
+ defer server.Close() |
|
4763 |
+ |
|
4764 |
+ ctx, err := fakeContext(`FROM busybox |
|
4765 |
+RUN echo from Dockerfile`, |
|
4766 |
+ map[string]string{}) |
|
4767 |
+ defer ctx.Close() |
|
4768 |
+ if err != nil { |
|
4769 |
+ t.Fatal(err) |
|
4770 |
+ } |
|
4771 |
+ |
|
4772 |
+ // Make sure that -f is ignored and that we don't use the Dockerfile |
|
4773 |
+ // that's in the current dir |
|
4774 |
+ out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-f", "baz", "-t", "test1", server.URL+"/baz") |
|
4775 |
+ if err != nil { |
|
4776 |
+ t.Fatalf("Failed to build: %s\n%s", out, err) |
|
4777 |
+ } |
|
4778 |
+ |
|
4779 |
+ if !strings.Contains(out, "from baz") || |
|
4780 |
+ strings.Contains(out, "/tmp/baz") || |
|
4781 |
+ !strings.Contains(out, "/tmp/Dockerfile") { |
|
4782 |
+ t.Fatalf("Missing proper output: %s", out) |
|
4783 |
+ } |
|
4784 |
+ |
|
4785 |
+ logDone("build - from URL with -f") |
|
4786 |
+} |
|
4787 |
+ |
|
4788 |
+func TestBuildFromStdinWithF(t *testing.T) { |
|
4789 |
+ defer deleteImages("test1") |
|
4790 |
+ |
|
4791 |
+ ctx, err := fakeContext(`FROM busybox |
|
4792 |
+RUN echo from Dockerfile`, |
|
4793 |
+ map[string]string{}) |
|
4794 |
+ defer ctx.Close() |
|
4795 |
+ if err != nil { |
|
4796 |
+ t.Fatal(err) |
|
4797 |
+ } |
|
4798 |
+ |
|
4799 |
+ // Make sure that -f is ignored and that we don't use the Dockerfile |
|
4800 |
+ // that's in the current dir |
|
4801 |
+ dockerCommand := exec.Command(dockerBinary, "build", "-f", "baz", "-t", "test1", "-") |
|
4802 |
+ dockerCommand.Dir = ctx.Dir |
|
4803 |
+ dockerCommand.Stdin = strings.NewReader(`FROM busybox |
|
4804 |
+RUN echo from baz |
|
4805 |
+COPY * /tmp/ |
|
4806 |
+RUN find /tmp/`) |
|
4807 |
+ out, status, err := runCommandWithOutput(dockerCommand) |
|
4808 |
+ if err != nil || status != 0 { |
|
4809 |
+ t.Fatalf("Error building: %s", err) |
|
4810 |
+ } |
|
4811 |
+ |
|
4812 |
+ if !strings.Contains(out, "from baz") || |
|
4813 |
+ strings.Contains(out, "/tmp/baz") || |
|
4814 |
+ !strings.Contains(out, "/tmp/Dockerfile") { |
|
4815 |
+ t.Fatalf("Missing proper output: %s", out) |
|
4816 |
+ } |
|
4817 |
+ |
|
4818 |
+ logDone("build - from stdin with -f") |
|
4819 |
+} |
|
4820 |
+ |
|
4702 | 4821 |
func TestBuildFromOfficialNames(t *testing.T) { |
4703 | 4822 |
name := "testbuildfromofficial" |
4704 | 4823 |
fromNames := []string{ |