Browse code

Merge pull request #26516 from yongtang/26453-build-bad-syntax

Check bad syntax on dockerfile before building.

Vincent Demeester authored on 2016/09/23 19:24:20
Showing 15 changed files
... ...
@@ -234,6 +234,12 @@ func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (stri
234 234
 
235 235
 	var shortImgID string
236 236
 	total := len(b.dockerfile.Children)
237
+	for _, n := range b.dockerfile.Children {
238
+		if err := b.checkDispatch(n, false); err != nil {
239
+			return "", err
240
+		}
241
+	}
242
+
237 243
 	for i, n := range b.dockerfile.Children {
238 244
 		select {
239 245
 		case <-b.clientCtx.Done():
... ...
@@ -243,6 +249,7 @@ func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (stri
243 243
 		default:
244 244
 			// Not cancelled yet, keep going...
245 245
 		}
246
+
246 247
 		if err := b.dispatch(i, total, n); err != nil {
247 248
 			if b.options.ForceRemove {
248 249
 				b.clearTmp()
... ...
@@ -322,6 +329,12 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
322 322
 	b.disableCommit = true
323 323
 
324 324
 	total := len(ast.Children)
325
+	for _, n := range ast.Children {
326
+		if err := b.checkDispatch(n, false); err != nil {
327
+			return nil, err
328
+		}
329
+	}
330
+
325 331
 	for i, n := range ast.Children {
326 332
 		if err := b.dispatch(i, total, n); err != nil {
327 333
 			return nil, err
... ...
@@ -201,3 +201,44 @@ func (b *Builder) dispatch(stepN int, stepTotal int, ast *parser.Node) error {
201 201
 
202 202
 	return fmt.Errorf("Unknown instruction: %s", upperCasedCmd)
203 203
 }
204
+
205
+// checkDispatch does a simple check for syntax errors of the Dockerfile.
206
+// Because some of the instructions can only be validated through runtime,
207
+// arg, env, etc., this syntax check will not be complete and could not replace
208
+// the runtime check. Instead, this function is only a helper that allows
209
+// user to find out the obvious error in Dockerfile earlier on.
210
+// onbuild bool: indicate if instruction XXX is part of `ONBUILD XXX` trigger
211
+func (b *Builder) checkDispatch(ast *parser.Node, onbuild bool) error {
212
+	cmd := ast.Value
213
+	upperCasedCmd := strings.ToUpper(cmd)
214
+
215
+	// To ensure the user is given a decent error message if the platform
216
+	// on which the daemon is running does not support a builder command.
217
+	if err := platformSupports(strings.ToLower(cmd)); err != nil {
218
+		return err
219
+	}
220
+
221
+	// The instruction itself is ONBUILD, we will make sure it follows with at
222
+	// least one argument
223
+	if upperCasedCmd == "ONBUILD" {
224
+		if ast.Next == nil {
225
+			return fmt.Errorf("ONBUILD requires at least one argument")
226
+		}
227
+	}
228
+
229
+	// The instruction is part of ONBUILD trigger (not the instruction itself)
230
+	if onbuild {
231
+		switch upperCasedCmd {
232
+		case "ONBUILD":
233
+			return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
234
+		case "MAINTAINER", "FROM":
235
+			return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", upperCasedCmd)
236
+		}
237
+	}
238
+
239
+	if _, ok := evaluateTable[cmd]; ok {
240
+		return nil
241
+	}
242
+
243
+	return fmt.Errorf("Unknown instruction: %s", upperCasedCmd)
244
+}
... ...
@@ -423,14 +423,12 @@ func (b *Builder) processImageFrom(img builder.Image) error {
423 423
 		}
424 424
 
425 425
 		total := len(ast.Children)
426
-		for i, n := range ast.Children {
427
-			switch strings.ToUpper(n.Value) {
428
-			case "ONBUILD":
429
-				return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
430
-			case "MAINTAINER", "FROM":
431
-				return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", n.Value)
426
+		for _, n := range ast.Children {
427
+			if err := b.checkDispatch(n, true); err != nil {
428
+				return err
432 429
 			}
433
-
430
+		}
431
+		for i, n := range ast.Children {
434 432
 			if err := b.dispatch(i, total, n); err != nil {
435 433
 				return err
436 434
 			}
... ...
@@ -293,6 +293,9 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
293 293
 
294 294
 	response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
295 295
 	if err != nil {
296
+		if options.quiet {
297
+			fmt.Fprintf(dockerCli.Err(), "%s", progBuff)
298
+		}
296 299
 		return err
297 300
 	}
298 301
 	defer response.Body.Close()
... ...
@@ -123,6 +123,7 @@ This section lists each version from latest to oldest.  Each listing includes a
123 123
 * `POST /containers/create/` and `POST /containers/(name)/update` now validates restart policies.
124 124
 * `POST /containers/create` now validates IPAMConfig in NetworkingConfig, and returns error for invalid IPv4 and IPv6 addresses (`--ip` and `--ip6` in `docker create/run`).
125 125
 * `POST /containers/create` now takes a `Mounts` field in `HostConfig` which replaces `Binds` and `Volumes`. *note*: `Binds` and `Volumes` are still available but are exclusive with `Mounts`
126
+* `POST /build` now performs a preliminary validation of the `Dockerfile` before starting the build, and returns an error if the syntax is incorrect. Note that this change is _unversioned_ and applied to all API versions.
126 127
 
127 128
 ### v1.24 API changes
128 129
 
... ...
@@ -1204,6 +1204,10 @@ The archive may include any number of other files,
1204 1204
 which are accessible in the build context (See the [*ADD build
1205 1205
 command*](../../reference/builder.md#add)).
1206 1206
 
1207
+The Docker daemon performs a preliminary validation of the `Dockerfile` before
1208
+starting the build, and returns an error if the syntax is incorrect. After that,
1209
+each instruction is run one-by-one until the ID of the new image is output.
1210
+
1207 1211
 The build is canceled if the client drops the connection by quitting
1208 1212
 or being killed.
1209 1213
 
... ...
@@ -1246,6 +1246,10 @@ The archive may include any number of other files,
1246 1246
 which are accessible in the build context (See the [*ADD build
1247 1247
 command*](../../reference/builder.md#add)).
1248 1248
 
1249
+The Docker daemon performs a preliminary validation of the `Dockerfile` before
1250
+starting the build, and returns an error if the syntax is incorrect. After that,
1251
+each instruction is run one-by-one until the ID of the new image is output.
1252
+
1249 1253
 The build is canceled if the client drops the connection by quitting
1250 1254
 or being killed.
1251 1255
 
... ...
@@ -1373,6 +1373,10 @@ The archive may include any number of other files,
1373 1373
 which are accessible in the build context (See the [*ADD build
1374 1374
 command*](../../reference/builder.md#add)).
1375 1375
 
1376
+The Docker daemon performs a preliminary validation of the `Dockerfile` before
1377
+starting the build, and returns an error if the syntax is incorrect. After that,
1378
+each instruction is run one-by-one until the ID of the new image is output.
1379
+
1376 1380
 The build is canceled if the client drops the connection by quitting
1377 1381
 or being killed.
1378 1382
 
... ...
@@ -1454,6 +1454,10 @@ The archive may include any number of other files,
1454 1454
 which are accessible in the build context (See the [*ADD build
1455 1455
 command*](../../reference/builder.md#add)).
1456 1456
 
1457
+The Docker daemon performs a preliminary validation of the `Dockerfile` before
1458
+starting the build, and returns an error if the syntax is incorrect. After that,
1459
+each instruction is run one-by-one until the ID of the new image is output.
1460
+
1457 1461
 The build is canceled if the client drops the connection by quitting
1458 1462
 or being killed.
1459 1463
 
... ...
@@ -1632,6 +1632,10 @@ The archive may include any number of other files,
1632 1632
 which are accessible in the build context (See the [*ADD build
1633 1633
 command*](../../reference/builder.md#add)).
1634 1634
 
1635
+The Docker daemon performs a preliminary validation of the `Dockerfile` before
1636
+starting the build, and returns an error if the syntax is incorrect. After that,
1637
+each instruction is run one-by-one until the ID of the new image is output.
1638
+
1635 1639
 The build is canceled if the client drops the connection by quitting
1636 1640
 or being killed.
1637 1641
 
... ...
@@ -1665,6 +1665,10 @@ The archive may include any number of other files,
1665 1665
 which are accessible in the build context (See the [*ADD build
1666 1666
 command*](../../reference/builder.md#add)).
1667 1667
 
1668
+The Docker daemon performs a preliminary validation of the `Dockerfile` before
1669
+starting the build, and returns an error if the syntax is incorrect. After that,
1670
+each instruction is run one-by-one until the ID of the new image is output.
1671
+
1668 1672
 The build is canceled if the client drops the connection by quitting
1669 1673
 or being killed.
1670 1674
 
... ...
@@ -1666,6 +1666,10 @@ The archive may include any number of other files,
1666 1666
 which are accessible in the build context (See the [*ADD build
1667 1667
 command*](../../reference/builder.md#add)).
1668 1668
 
1669
+The Docker daemon performs a preliminary validation of the `Dockerfile` before
1670
+starting the build, and returns an error if the syntax is incorrect. After that,
1671
+each instruction is run one-by-one until the ID of the new image is output.
1672
+
1669 1673
 The build is canceled if the client drops the connection by quitting
1670 1674
 or being killed.
1671 1675
 
... ...
@@ -1692,6 +1692,10 @@ The archive may include any number of other files,
1692 1692
 which are accessible in the build context (See the [*ADD build
1693 1693
 command*](../../reference/builder.md#add)).
1694 1694
 
1695
+The Docker daemon performs a preliminary validation of the `Dockerfile` before
1696
+starting the build, and returns an error if the syntax is incorrect. After that,
1697
+each instruction is run one-by-one until the ID of the new image is output.
1698
+
1695 1699
 The build is canceled if the client drops the connection by quitting
1696 1700
 or being killed.
1697 1701
 
... ...
@@ -68,6 +68,13 @@ add multiple `-t` parameters when you run the `build` command:
68 68
 
69 69
     $ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
70 70
 
71
+Before the Docker daemon runs the instructions in the `Dockerfile`, it performs
72
+a preliminary validation of the `Dockerfile` and returns an error if the syntax is incorrect:
73
+
74
+    $ docker build -t test/myapp .
75
+    Sending build context to Docker daemon 2.048 kB
76
+    Error response from daemon: Unknown instruction: RUNCMD
77
+
71 78
 The Docker daemon runs the instructions in the `Dockerfile` one-by-one,
72 79
 committing the result of each instruction
73 80
 to a new image if necessary, before finally outputting the ID of your
... ...
@@ -6913,3 +6913,21 @@ func (s *DockerSuite) TestBuildStepsWithProgress(c *check.C) {
6913 6913
 		c.Assert(out, checker.Contains, fmt.Sprintf("Step %d/%d : RUN echo foo", i, 1+totalRun))
6914 6914
 	}
6915 6915
 }
6916
+
6917
+func (s *DockerSuite) TestBuildWithFailure(c *check.C) {
6918
+	name := "testbuildwithfailure"
6919
+
6920
+	// First test case can only detect `nobody` in runtime so all steps will show up
6921
+	buildCmd := "FROM busybox\nRUN nobody"
6922
+	_, stdout, _, err := buildImageWithStdoutStderr(name, buildCmd, false, "--force-rm", "--rm")
6923
+	c.Assert(err, checker.NotNil)
6924
+	c.Assert(stdout, checker.Contains, "Step 1/2 : FROM busybox")
6925
+	c.Assert(stdout, checker.Contains, "Step 2/2 : RUN nobody")
6926
+
6927
+	// Second test case `FFOM` should have been detected before build runs so no steps
6928
+	buildCmd = "FFOM nobody\nRUN nobody"
6929
+	_, stdout, _, err = buildImageWithStdoutStderr(name, buildCmd, false, "--force-rm", "--rm")
6930
+	c.Assert(err, checker.NotNil)
6931
+	c.Assert(stdout, checker.Not(checker.Contains), "Step 1/2 : FROM busybox")
6932
+	c.Assert(stdout, checker.Not(checker.Contains), "Step 2/2 : RUN nobody")
6933
+}