Browse code

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>

Doug Davis authored on 2015/02/18 03:25:36
Showing 7 changed files
... ...
@@ -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{