Check bad syntax on dockerfile before building.
| ... | ... |
@@ -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 |
+} |