Support dockerfile and Dockerfile
| ... | ... |
@@ -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{
|