2018-02-05 16:05:59 -05:00
package dockerfile // import "github.com/docker/docker/builder/dockerfile"
2016-06-18 06:49:17 -04:00
import (
2017-04-21 15:08:11 -04:00
"bytes"
"context"
builder: produce error when using unsupported Dockerfile option
With the promotion of the experimental Dockerfile syntax to "stable", the Dockerfile
syntax now includes some options that are supported by BuildKit, but not (yet)
supported in the classic builder.
As a result, parsing a Dockerfile may succeed, but any flag that's known to BuildKit,
but not supported by the classic builder is silently ignored;
$ mkdir buildkit_flags && cd buildkit_flags
$ touch foo.txt
For example, `RUN --mount`:
DOCKER_BUILDKIT=0 docker build --no-cache -f- . <<EOF
FROM busybox
RUN --mount=type=cache,target=/foo echo hello
EOF
Sending build context to Docker daemon 2.095kB
Step 1/2 : FROM busybox
---> 219ee5171f80
Step 2/2 : RUN --mount=type=cache,target=/foo echo hello
---> Running in 022fdb856bc8
hello
Removing intermediate container 022fdb856bc8
---> e9f0988844d1
Successfully built e9f0988844d1
Or `COPY --chmod` (same for `ADD --chmod`):
DOCKER_BUILDKIT=0 docker build --no-cache -f- . <<EOF
FROM busybox
COPY --chmod=0777 /foo.txt /foo.txt
EOF
Sending build context to Docker daemon 2.095kB
Step 1/2 : FROM busybox
---> 219ee5171f80
Step 2/2 : COPY --chmod=0777 /foo.txt /foo.txt
---> 8b7117932a2a
Successfully built 8b7117932a2a
Note that unknown flags still produce and error, for example, the below fails because `--hello` is an unknown flag;
DOCKER_BUILDKIT=0 docker build -<<EOF
FROM busybox
RUN --hello echo hello
EOF
Sending build context to Docker daemon 2.048kB
Error response from daemon: dockerfile parse error line 2: Unknown flag: hello
With this patch applied
----------------------------
With this patch applied, flags that are known in the Dockerfile spec, but are not
supported by the classic builder, produce an error, which includes a link to the
documentation how to enable BuildKit:
DOCKER_BUILDKIT=0 docker build --no-cache -f- . <<EOF
FROM busybox
RUN --mount=type=cache,target=/foo echo hello
EOF
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM busybox
---> b97242f89c8a
Step 2/2 : RUN --mount=type=cache,target=/foo echo hello
the --mount option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled
DOCKER_BUILDKIT=0 docker build --no-cache -f- . <<EOF
FROM busybox
COPY --chmod=0777 /foo.txt /foo.txt
EOF
Sending build context to Docker daemon 2.095kB
Step 1/2 : FROM busybox
---> b97242f89c8a
Step 2/2 : COPY --chmod=0777 /foo.txt /foo.txt
the --chmod option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2021-01-21 07:25:18 -05:00
"fmt"
2017-05-22 11:21:17 -04:00
"runtime"
Windows: (WCOW) Generate OCI spec that remote runtime can escape
Signed-off-by: John Howard <jhoward@microsoft.com>
Also fixes https://github.com/moby/moby/issues/22874
This commit is a pre-requisite to moving moby/moby on Windows to using
Containerd for its runtime.
The reason for this is that the interface between moby and containerd
for the runtime is an OCI spec which must be unambigious.
It is the responsibility of the runtime (runhcs in the case of
containerd on Windows) to ensure that arguments are escaped prior
to calling into HCS and onwards to the Win32 CreateProcess call.
Previously, the builder was always escaping arguments which has
led to several bugs in moby. Because the local runtime in
libcontainerd had context of whether or not arguments were escaped,
it was possible to hack around in daemon/oci_windows.go with
knowledge of the context of the call (from builder or not).
With a remote runtime, this is not possible as there's rightly
no context of the caller passed across in the OCI spec. Put another
way, as I put above, the OCI spec must be unambigious.
The other previous limitation (which leads to various subtle bugs)
is that moby is coded entirely from a Linux-centric point of view.
Unfortunately, Windows != Linux. Windows CreateProcess uses a
command line, not an array of arguments. And it has very specific
rules about how to escape a command line. Some interesting reading
links about this are:
https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
https://stackoverflow.com/questions/31838469/how-do-i-convert-argv-to-lpcommandline-parameter-of-createprocess
https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments?view=vs-2017
For this reason, the OCI spec has recently been updated to cater
for more natural syntax by including a CommandLine option in
Process.
What does this commit do?
Primary objective is to ensure that the built OCI spec is unambigious.
It changes the builder so that `ArgsEscaped` as commited in a
layer is only controlled by the use of CMD or ENTRYPOINT.
Subsequently, when calling in to create a container from the builder,
if follows a different path to both `docker run` and `docker create`
using the added `ContainerCreateIgnoreImagesArgsEscaped`. This allows
a RUN from the builder to control how to escape in the OCI spec.
It changes the builder so that when shell form is used for RUN,
CMD or ENTRYPOINT, it builds (for WCOW) a more natural command line
using the original as put by the user in the dockerfile, not
the parsed version as a set of args which loses fidelity.
This command line is put into args[0] and `ArgsEscaped` is set
to true for CMD or ENTRYPOINT. A RUN statement does not commit
`ArgsEscaped` to the commited layer regardless or whether shell
or exec form were used.
2019-01-17 19:03:29 -05:00
"strings"
2017-05-22 11:21:17 -04:00
"testing"
2017-04-13 18:44:36 -04:00
2016-09-06 14:18:12 -04:00
"github.com/docker/docker/api/types"
2017-04-21 15:08:11 -04:00
"github.com/docker/docker/api/types/backend"
2016-09-06 14:18:12 -04:00
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/strslice"
2017-04-04 13:40:37 -04:00
"github.com/docker/docker/builder"
2018-02-06 13:27:55 -05:00
"github.com/docker/docker/image"
2017-05-05 13:05:25 -04:00
"github.com/docker/docker/pkg/system"
2016-06-26 16:01:28 -04:00
"github.com/docker/go-connections/nat"
2018-06-02 12:46:53 -04:00
"github.com/moby/buildkit/frontend/dockerfile/instructions"
Windows: (WCOW) Generate OCI spec that remote runtime can escape
Signed-off-by: John Howard <jhoward@microsoft.com>
Also fixes https://github.com/moby/moby/issues/22874
This commit is a pre-requisite to moving moby/moby on Windows to using
Containerd for its runtime.
The reason for this is that the interface between moby and containerd
for the runtime is an OCI spec which must be unambigious.
It is the responsibility of the runtime (runhcs in the case of
containerd on Windows) to ensure that arguments are escaped prior
to calling into HCS and onwards to the Win32 CreateProcess call.
Previously, the builder was always escaping arguments which has
led to several bugs in moby. Because the local runtime in
libcontainerd had context of whether or not arguments were escaped,
it was possible to hack around in daemon/oci_windows.go with
knowledge of the context of the call (from builder or not).
With a remote runtime, this is not possible as there's rightly
no context of the caller passed across in the OCI spec. Put another
way, as I put above, the OCI spec must be unambigious.
The other previous limitation (which leads to various subtle bugs)
is that moby is coded entirely from a Linux-centric point of view.
Unfortunately, Windows != Linux. Windows CreateProcess uses a
command line, not an array of arguments. And it has very specific
rules about how to escape a command line. Some interesting reading
links about this are:
https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
https://stackoverflow.com/questions/31838469/how-do-i-convert-argv-to-lpcommandline-parameter-of-createprocess
https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments?view=vs-2017
For this reason, the OCI spec has recently been updated to cater
for more natural syntax by including a CommandLine option in
Process.
What does this commit do?
Primary objective is to ensure that the built OCI spec is unambigious.
It changes the builder so that `ArgsEscaped` as commited in a
layer is only controlled by the use of CMD or ENTRYPOINT.
Subsequently, when calling in to create a container from the builder,
if follows a different path to both `docker run` and `docker create`
using the added `ContainerCreateIgnoreImagesArgsEscaped`. This allows
a RUN from the builder to control how to escape in the OCI spec.
It changes the builder so that when shell form is used for RUN,
CMD or ENTRYPOINT, it builds (for WCOW) a more natural command line
using the original as put by the user in the dockerfile, not
the parsed version as a set of args which loses fidelity.
This command line is put into args[0] and `ArgsEscaped` is set
to true for CMD or ENTRYPOINT. A RUN statement does not commit
`ArgsEscaped` to the commited layer regardless or whether shell
or exec form were used.
2019-01-17 19:03:29 -05:00
"github.com/moby/buildkit/frontend/dockerfile/parser"
2018-06-02 12:46:53 -04:00
"github.com/moby/buildkit/frontend/dockerfile/shell"
2020-02-07 08:39:24 -05:00
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
2016-06-18 06:49:17 -04:00
)
2017-04-11 14:34:05 -04:00
func newBuilderWithMockBackend ( ) * Builder {
2017-03-27 21:36:28 -04:00
mockBackend := & MockBackend { }
2018-07-02 22:31:05 -04:00
opts := & types . ImageBuildOptions { }
2017-05-05 18:52:11 -04:00
ctx := context . Background ( )
2017-04-11 14:34:05 -04:00
b := & Builder {
2018-06-26 17:49:33 -04:00
options : opts ,
2017-03-27 21:36:28 -04:00
docker : mockBackend ,
2017-04-21 15:08:11 -04:00
Stdout : new ( bytes . Buffer ) ,
2017-05-05 18:52:11 -04:00
clientCtx : ctx ,
2017-04-11 14:34:05 -04:00
disableCommit : true ,
2017-05-05 18:52:11 -04:00
imageSources : newImageSources ( ctx , builderOptions {
2018-06-26 17:49:33 -04:00
Options : opts ,
2017-05-05 18:52:11 -04:00
Backend : mockBackend ,
} ) ,
2017-08-24 14:48:16 -04:00
imageProber : newImageProber ( mockBackend , nil , false ) ,
2017-04-13 18:44:36 -04:00
containerManager : newContainerManager ( mockBackend ) ,
2017-04-11 14:34:05 -04:00
}
return b
}
2016-06-18 06:49:17 -04:00
func TestEnv2Variables ( t * testing . T ) {
2017-04-04 12:28:59 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '\\' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-05-22 11:21:17 -04:00
envCommand := & instructions . EnvCommand {
Env : instructions . KeyValuePairs {
instructions . KeyValuePair { Key : "var1" , Value : "val1" } ,
instructions . KeyValuePair { Key : "var2" , Value : "val2" } ,
} ,
}
err := dispatch ( sb , envCommand )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
2016-06-18 06:49:17 -04:00
2017-04-04 12:28:59 -04:00
expected := [ ] string {
2017-05-22 11:21:17 -04:00
"var1=val1" ,
"var2=val2" ,
2016-06-18 06:49:17 -04:00
}
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . DeepEqual ( expected , sb . state . runConfig . Env ) )
2017-04-04 12:28:59 -04:00
}
2016-06-18 06:49:17 -04:00
2017-04-04 12:28:59 -04:00
func TestEnvValueWithExistingRunConfigEnv ( t * testing . T ) {
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '\\' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-05-22 11:21:17 -04:00
sb . state . runConfig . Env = [ ] string { "var1=old" , "var2=fromenv" }
envCommand := & instructions . EnvCommand {
Env : instructions . KeyValuePairs {
instructions . KeyValuePair { Key : "var1" , Value : "val1" } ,
} ,
}
err := dispatch ( sb , envCommand )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
2017-04-04 12:28:59 -04:00
expected := [ ] string {
2017-05-22 11:21:17 -04:00
"var1=val1" ,
2017-04-04 12:28:59 -04:00
"var2=fromenv" ,
2016-06-18 06:49:17 -04:00
}
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . DeepEqual ( expected , sb . state . runConfig . Env ) )
2016-06-18 06:49:17 -04:00
}
func TestMaintainer ( t * testing . T ) {
maintainerEntry := "Some Maintainer <maintainer@example.com>"
2017-04-11 14:34:05 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '\\' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-05-22 11:21:17 -04:00
cmd := & instructions . MaintainerCommand { Maintainer : maintainerEntry }
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
assert . Check ( t , is . Equal ( maintainerEntry , sb . state . maintainer ) )
2016-06-18 06:49:17 -04:00
}
func TestLabel ( t * testing . T ) {
labelName := "label"
labelValue := "value"
2017-04-11 14:34:05 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '\\' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-05-22 11:21:17 -04:00
cmd := & instructions . LabelCommand {
Labels : instructions . KeyValuePairs {
instructions . KeyValuePair { Key : labelName , Value : labelValue } ,
} ,
}
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
2016-06-18 06:49:17 -04:00
2018-03-13 15:28:34 -04:00
assert . Assert ( t , is . Contains ( sb . state . runConfig . Labels , labelName ) )
assert . Check ( t , is . Equal ( sb . state . runConfig . Labels [ labelName ] , labelValue ) )
2017-04-04 13:40:37 -04:00
}
func TestFromScratch ( t * testing . T ) {
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '\\' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-05-22 11:21:17 -04:00
cmd := & instructions . Stage {
BaseName : "scratch" ,
}
err := initializeStage ( sb , cmd )
2016-06-18 06:49:17 -04:00
2017-05-17 20:08:01 -04:00
if runtime . GOOS == "windows" && ! system . LCOWSupported ( ) {
2018-07-02 22:31:05 -04:00
assert . Check ( t , is . Error ( err , "Linux containers are not supported on this system" ) )
2017-04-04 13:40:37 -04:00
return
}
2016-06-18 06:49:17 -04:00
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
assert . Check ( t , sb . state . hasFromImage ( ) )
assert . Check ( t , is . Equal ( "" , sb . state . imageID ) )
2017-05-26 19:14:18 -04:00
expected := "PATH=" + system . DefaultPathEnv ( runtime . GOOS )
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . DeepEqual ( [ ] string { expected } , sb . state . runConfig . Env ) )
2017-04-04 13:40:37 -04:00
}
2016-06-18 06:49:17 -04:00
2017-04-04 13:40:37 -04:00
func TestFromWithArg ( t * testing . T ) {
tag , expected := ":sometag" , "expectedthisid"
2016-06-18 06:49:17 -04:00
2018-02-16 16:50:57 -05:00
getImage := func ( name string ) ( builder . Image , builder . ROLayer , error ) {
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . Equal ( "alpine" + tag , name ) )
2017-03-27 21:36:28 -04:00
return & mockImage { id : "expectedthisid" } , nil , nil
2017-04-04 13:40:37 -04:00
}
b := newBuilderWithMockBackend ( )
2017-03-27 21:36:28 -04:00
b . docker . ( * MockBackend ) . getImageFunc = getImage
2018-05-07 13:49:13 -04:00
args := NewBuildArgs ( make ( map [ string ] * string ) )
2016-06-18 06:49:17 -04:00
2017-05-22 11:21:17 -04:00
val := "sometag"
2020-11-12 21:14:57 -05:00
metaArg := instructions . ArgCommand { Args : [ ] instructions . KeyValuePairOptional { {
2017-05-22 11:21:17 -04:00
Key : "THETAG" ,
Value : & val ,
2020-11-12 21:14:57 -05:00
} } }
2017-05-22 11:21:17 -04:00
cmd := & instructions . Stage {
BaseName : "alpine:${THETAG}" ,
}
2018-01-30 18:58:21 -05:00
err := processMetaArg ( metaArg , shell . NewLex ( '\\' ) , args )
2017-04-04 13:40:37 -04:00
2017-05-22 11:21:17 -04:00
sb := newDispatchRequest ( b , '\\' , nil , args , newStagesBuildResults ( ) )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
2017-05-22 11:21:17 -04:00
err = initializeStage ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
2017-05-22 11:21:17 -04:00
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . Equal ( expected , sb . state . imageID ) )
assert . Check ( t , is . Equal ( expected , sb . state . baseImage . ImageID ( ) ) )
assert . Check ( t , is . Len ( sb . state . buildArgs . GetAllAllowed ( ) , 0 ) )
assert . Check ( t , is . Len ( sb . state . buildArgs . GetAllMeta ( ) , 1 ) )
2017-04-04 13:40:37 -04:00
}
2018-07-04 20:23:15 -04:00
func TestFromWithArgButBuildArgsNotGiven ( t * testing . T ) {
b := newBuilderWithMockBackend ( )
args := NewBuildArgs ( make ( map [ string ] * string ) )
metaArg := instructions . ArgCommand { }
cmd := & instructions . Stage {
BaseName : "${THETAG}" ,
}
err := processMetaArg ( metaArg , shell . NewLex ( '\\' ) , args )
sb := newDispatchRequest ( b , '\\' , nil , args , newStagesBuildResults ( ) )
assert . NilError ( t , err )
err = initializeStage ( sb , cmd )
assert . Error ( t , err , "base name (${THETAG}) should not be blank" )
}
2017-04-04 13:40:37 -04:00
func TestFromWithUndefinedArg ( t * testing . T ) {
tag , expected := "sometag" , "expectedthisid"
2018-02-16 16:50:57 -05:00
getImage := func ( name string ) ( builder . Image , builder . ROLayer , error ) {
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . Equal ( "alpine" , name ) )
2017-03-27 21:36:28 -04:00
return & mockImage { id : "expectedthisid" } , nil , nil
2016-06-18 06:49:17 -04:00
}
2017-04-04 13:40:37 -04:00
b := newBuilderWithMockBackend ( )
2017-03-27 21:36:28 -04:00
b . docker . ( * MockBackend ) . getImageFunc = getImage
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '\\' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-05-22 11:21:17 -04:00
2017-04-04 13:40:37 -04:00
b . options . BuildArgs = map [ string ] * string { "THETAG" : & tag }
2017-05-22 11:21:17 -04:00
cmd := & instructions . Stage {
BaseName : "alpine${THETAG}" ,
}
err := initializeStage ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
assert . Check ( t , is . Equal ( expected , sb . state . imageID ) )
2016-06-18 06:49:17 -04:00
}
2017-05-22 11:21:17 -04:00
func TestFromMultiStageWithNamedStage ( t * testing . T ) {
2017-05-03 14:02:46 -04:00
b := newBuilderWithMockBackend ( )
2017-05-22 11:21:17 -04:00
firstFrom := & instructions . Stage { BaseName : "someimg" , Name : "base" }
secondFrom := & instructions . Stage { BaseName : "base" }
previousResults := newStagesBuildResults ( )
2018-05-07 13:49:13 -04:00
firstSB := newDispatchRequest ( b , '\\' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , previousResults )
secondSB := newDispatchRequest ( b , '\\' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , previousResults )
2017-05-22 11:21:17 -04:00
err := initializeStage ( firstSB , firstFrom )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
assert . Check ( t , firstSB . state . hasFromImage ( ) )
2017-05-22 11:21:17 -04:00
previousResults . indexed [ "base" ] = firstSB . state . runConfig
previousResults . flat = append ( previousResults . flat , firstSB . state . runConfig )
err = initializeStage ( secondSB , secondFrom )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
assert . Check ( t , secondSB . state . hasFromImage ( ) )
2016-06-18 06:49:17 -04:00
}
func TestOnbuild ( t * testing . T ) {
2017-04-11 14:34:05 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '\\' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-05-22 11:21:17 -04:00
cmd := & instructions . OnbuildCommand {
Expression : "ADD . /app/src" ,
}
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
assert . Check ( t , is . Equal ( "ADD . /app/src" , sb . state . runConfig . OnBuild [ 0 ] ) )
2016-06-18 06:49:17 -04:00
}
2016-06-26 16:01:28 -04:00
func TestWorkdir ( t * testing . T ) {
2017-04-11 14:34:05 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '`' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-10-03 14:32:54 -04:00
sb . state . baseImage = & mockImage { }
2016-06-26 16:01:28 -04:00
workingDir := "/app"
if runtime . GOOS == "windows" {
2017-05-22 11:21:17 -04:00
workingDir = "C:\\app"
}
cmd := & instructions . WorkdirCommand {
Path : workingDir ,
2016-06-26 16:01:28 -04:00
}
2017-05-22 11:21:17 -04:00
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
assert . Check ( t , is . Equal ( workingDir , sb . state . runConfig . WorkingDir ) )
2016-06-26 16:01:28 -04:00
}
func TestCmd ( t * testing . T ) {
2017-04-11 14:34:05 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '`' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-10-04 17:26:56 -04:00
sb . state . baseImage = & mockImage { }
2016-06-26 16:01:28 -04:00
command := "./executable"
2017-05-22 11:21:17 -04:00
cmd := & instructions . CmdCommand {
ShellDependantCmdLine : instructions . ShellDependantCmdLine {
CmdLine : strslice . StrSlice { command } ,
PrependShell : true ,
} ,
}
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
2016-06-26 16:01:28 -04:00
var expectedCommand strslice . StrSlice
if runtime . GOOS == "windows" {
expectedCommand = strslice . StrSlice ( append ( [ ] string { "cmd" } , "/S" , "/C" , command ) )
} else {
expectedCommand = strslice . StrSlice ( append ( [ ] string { "/bin/sh" } , "-c" , command ) )
}
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . DeepEqual ( expectedCommand , sb . state . runConfig . Cmd ) )
assert . Check ( t , sb . state . cmdSet )
2016-06-26 16:01:28 -04:00
}
func TestHealthcheckNone ( t * testing . T ) {
2017-04-11 14:34:05 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '`' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-05-22 11:21:17 -04:00
cmd := & instructions . HealthCheckCommand {
Health : & container . HealthConfig {
Test : [ ] string { "NONE" } ,
} ,
}
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
2016-06-26 16:01:28 -04:00
2018-03-13 15:28:34 -04:00
assert . Assert ( t , sb . state . runConfig . Healthcheck != nil )
assert . Check ( t , is . DeepEqual ( [ ] string { "NONE" } , sb . state . runConfig . Healthcheck . Test ) )
2016-06-26 16:01:28 -04:00
}
func TestHealthcheckCmd ( t * testing . T ) {
2017-05-22 11:21:17 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '`' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-05-22 11:21:17 -04:00
expectedTest := [ ] string { "CMD-SHELL" , "curl -f http://localhost/ || exit 1" }
cmd := & instructions . HealthCheckCommand {
Health : & container . HealthConfig {
Test : expectedTest ,
} ,
}
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
2016-06-26 16:01:28 -04:00
2018-03-13 15:28:34 -04:00
assert . Assert ( t , sb . state . runConfig . Healthcheck != nil )
assert . Check ( t , is . DeepEqual ( expectedTest , sb . state . runConfig . Healthcheck . Test ) )
2016-06-26 16:01:28 -04:00
}
func TestEntrypoint ( t * testing . T ) {
2017-04-11 14:34:05 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '`' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-10-04 17:26:56 -04:00
sb . state . baseImage = & mockImage { }
2016-06-26 16:01:28 -04:00
entrypointCmd := "/usr/sbin/nginx"
2017-05-22 11:21:17 -04:00
cmd := & instructions . EntrypointCommand {
ShellDependantCmdLine : instructions . ShellDependantCmdLine {
CmdLine : strslice . StrSlice { entrypointCmd } ,
PrependShell : true ,
} ,
}
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
assert . Assert ( t , sb . state . runConfig . Entrypoint != nil )
2016-06-26 16:01:28 -04:00
var expectedEntrypoint strslice . StrSlice
if runtime . GOOS == "windows" {
expectedEntrypoint = strslice . StrSlice ( append ( [ ] string { "cmd" } , "/S" , "/C" , entrypointCmd ) )
} else {
expectedEntrypoint = strslice . StrSlice ( append ( [ ] string { "/bin/sh" } , "-c" , entrypointCmd ) )
}
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . DeepEqual ( expectedEntrypoint , sb . state . runConfig . Entrypoint ) )
2016-06-26 16:01:28 -04:00
}
func TestExpose ( t * testing . T ) {
2017-04-11 14:34:05 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '`' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2016-06-26 16:01:28 -04:00
exposedPort := "80"
2017-05-22 11:21:17 -04:00
cmd := & instructions . ExposeCommand {
Ports : [ ] string { exposedPort } ,
}
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
2016-06-26 16:01:28 -04:00
2018-03-13 15:28:34 -04:00
assert . Assert ( t , sb . state . runConfig . ExposedPorts != nil )
assert . Assert ( t , is . Len ( sb . state . runConfig . ExposedPorts , 1 ) )
2016-06-26 16:01:28 -04:00
portsMapping , err := nat . ParsePortSpec ( exposedPort )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
assert . Check ( t , is . Contains ( sb . state . runConfig . ExposedPorts , portsMapping [ 0 ] . Port ) )
2016-06-26 16:01:28 -04:00
}
func TestUser ( t * testing . T ) {
2017-04-11 14:34:05 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '`' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2016-06-26 16:01:28 -04:00
2017-05-22 11:21:17 -04:00
cmd := & instructions . UserCommand {
User : "test" ,
}
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
assert . Check ( t , is . Equal ( "test" , sb . state . runConfig . User ) )
2016-06-26 16:01:28 -04:00
}
func TestVolume ( t * testing . T ) {
2017-04-11 14:34:05 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '`' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2016-06-26 16:01:28 -04:00
exposedVolume := "/foo"
2017-05-22 11:21:17 -04:00
cmd := & instructions . VolumeCommand {
Volumes : [ ] string { exposedVolume } ,
}
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
assert . Assert ( t , sb . state . runConfig . Volumes != nil )
assert . Check ( t , is . Len ( sb . state . runConfig . Volumes , 1 ) )
assert . Check ( t , is . Contains ( sb . state . runConfig . Volumes , exposedVolume ) )
2016-06-26 16:01:28 -04:00
}
func TestStopSignal ( t * testing . T ) {
2017-05-22 11:21:17 -04:00
if runtime . GOOS == "windows" {
t . Skip ( "Windows does not support stopsignal" )
return
}
2017-04-11 14:34:05 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '`' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2017-10-04 17:26:56 -04:00
sb . state . baseImage = & mockImage { }
2016-06-26 16:01:28 -04:00
signal := "SIGKILL"
2017-05-22 11:21:17 -04:00
cmd := & instructions . StopSignalCommand {
Signal : signal ,
}
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
assert . Check ( t , is . Equal ( signal , sb . state . runConfig . StopSignal ) )
2016-06-26 16:01:28 -04:00
}
func TestArg ( t * testing . T ) {
2017-04-06 17:38:02 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '`' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2016-06-26 16:01:28 -04:00
argName := "foo"
argVal := "bar"
2020-11-12 21:14:57 -05:00
cmd := & instructions . ArgCommand { Args : [ ] instructions . KeyValuePairOptional { { Key : argName , Value : & argVal } } }
2017-05-22 11:21:17 -04:00
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
2016-06-26 16:01:28 -04:00
2017-04-06 17:38:02 -04:00
expected := map [ string ] string { argName : argVal }
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . DeepEqual ( expected , sb . state . buildArgs . GetAllAllowed ( ) ) )
2016-06-26 16:01:28 -04:00
}
func TestShell ( t * testing . T ) {
2017-04-11 14:34:05 -04:00
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
sb := newDispatchRequest ( b , '`' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
2016-06-26 16:01:28 -04:00
shellCmd := "powershell"
2017-05-22 11:21:17 -04:00
cmd := & instructions . ShellCommand { Shell : strslice . StrSlice { shellCmd } }
2016-06-26 16:01:28 -04:00
2017-05-22 11:21:17 -04:00
err := dispatch ( sb , cmd )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
2016-06-26 16:01:28 -04:00
expectedShell := strslice . StrSlice ( [ ] string { shellCmd } )
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . DeepEqual ( expectedShell , sb . state . runConfig . Shell ) )
2017-04-21 15:08:11 -04:00
}
func TestPrependEnvOnCmd ( t * testing . T ) {
2018-05-07 13:49:13 -04:00
buildArgs := NewBuildArgs ( nil )
2017-04-21 15:08:11 -04:00
buildArgs . AddArg ( "NO_PROXY" , nil )
args := [ ] string { "sorted=nope" , "args=not" , "http_proxy=foo" , "NO_PROXY=YA" }
cmd := [ ] string { "foo" , "bar" }
cmdWithEnv := prependEnvOnCmd ( buildArgs , args , cmd )
expected := strslice . StrSlice ( [ ] string {
"|3" , "NO_PROXY=YA" , "args=not" , "sorted=nope" , "foo" , "bar" } )
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . DeepEqual ( expected , cmdWithEnv ) )
2017-04-21 15:08:11 -04:00
}
func TestRunWithBuildArgs ( t * testing . T ) {
b := newBuilderWithMockBackend ( )
2018-05-07 13:49:13 -04:00
args := NewBuildArgs ( make ( map [ string ] * string ) )
2017-05-22 11:21:17 -04:00
args . argsFromOptions [ "HTTP_PROXY" ] = strPtr ( "FOO" )
2017-04-21 15:08:11 -04:00
b . disableCommit = false
2017-05-22 11:21:17 -04:00
sb := newDispatchRequest ( b , '`' , nil , args , newStagesBuildResults ( ) )
2017-04-21 15:08:11 -04:00
2017-04-26 17:45:16 -04:00
runConfig := & container . Config { }
2017-04-21 15:08:11 -04:00
origCmd := strslice . StrSlice ( [ ] string { "cmd" , "in" , "from" , "image" } )
Windows: (WCOW) Generate OCI spec that remote runtime can escape
Signed-off-by: John Howard <jhoward@microsoft.com>
Also fixes https://github.com/moby/moby/issues/22874
This commit is a pre-requisite to moving moby/moby on Windows to using
Containerd for its runtime.
The reason for this is that the interface between moby and containerd
for the runtime is an OCI spec which must be unambigious.
It is the responsibility of the runtime (runhcs in the case of
containerd on Windows) to ensure that arguments are escaped prior
to calling into HCS and onwards to the Win32 CreateProcess call.
Previously, the builder was always escaping arguments which has
led to several bugs in moby. Because the local runtime in
libcontainerd had context of whether or not arguments were escaped,
it was possible to hack around in daemon/oci_windows.go with
knowledge of the context of the call (from builder or not).
With a remote runtime, this is not possible as there's rightly
no context of the caller passed across in the OCI spec. Put another
way, as I put above, the OCI spec must be unambigious.
The other previous limitation (which leads to various subtle bugs)
is that moby is coded entirely from a Linux-centric point of view.
Unfortunately, Windows != Linux. Windows CreateProcess uses a
command line, not an array of arguments. And it has very specific
rules about how to escape a command line. Some interesting reading
links about this are:
https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
https://stackoverflow.com/questions/31838469/how-do-i-convert-argv-to-lpcommandline-parameter-of-createprocess
https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments?view=vs-2017
For this reason, the OCI spec has recently been updated to cater
for more natural syntax by including a CommandLine option in
Process.
What does this commit do?
Primary objective is to ensure that the built OCI spec is unambigious.
It changes the builder so that `ArgsEscaped` as commited in a
layer is only controlled by the use of CMD or ENTRYPOINT.
Subsequently, when calling in to create a container from the builder,
if follows a different path to both `docker run` and `docker create`
using the added `ContainerCreateIgnoreImagesArgsEscaped`. This allows
a RUN from the builder to control how to escape in the OCI spec.
It changes the builder so that when shell form is used for RUN,
CMD or ENTRYPOINT, it builds (for WCOW) a more natural command line
using the original as put by the user in the dockerfile, not
the parsed version as a set of args which loses fidelity.
This command line is put into args[0] and `ArgsEscaped` is set
to true for CMD or ENTRYPOINT. A RUN statement does not commit
`ArgsEscaped` to the commited layer regardless or whether shell
or exec form were used.
2019-01-17 19:03:29 -05:00
var cmdWithShell strslice . StrSlice
if runtime . GOOS == "windows" {
cmdWithShell = strslice . StrSlice ( [ ] string { strings . Join ( append ( getShell ( runConfig , runtime . GOOS ) , [ ] string { "echo foo" } ... ) , " " ) } )
} else {
cmdWithShell = strslice . StrSlice ( append ( getShell ( runConfig , runtime . GOOS ) , "echo foo" ) )
}
2017-04-21 15:08:11 -04:00
envVars := [ ] string { "|1" , "one=two" }
cachedCmd := strslice . StrSlice ( append ( envVars , cmdWithShell ... ) )
imageCache := & mockImageCache {
getCacheFunc : func ( parentID string , cfg * container . Config ) ( string , error ) {
// Check the runConfig.Cmd sent to probeCache()
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . DeepEqual ( cachedCmd , cfg . Cmd ) )
assert . Check ( t , is . DeepEqual ( strslice . StrSlice ( nil ) , cfg . Entrypoint ) )
2017-04-21 15:08:11 -04:00
return "" , nil
} ,
2017-04-10 21:58:31 -04:00
}
2017-04-21 15:08:11 -04:00
mockBackend := b . docker . ( * MockBackend )
2017-08-24 14:48:16 -04:00
mockBackend . makeImageCacheFunc = func ( _ [ ] string ) builder . ImageCache {
2017-04-13 18:44:36 -04:00
return imageCache
}
2017-08-24 14:48:16 -04:00
b . imageProber = newImageProber ( mockBackend , nil , false )
2018-02-16 16:50:57 -05:00
mockBackend . getImageFunc = func ( _ string ) ( builder . Image , builder . ROLayer , error ) {
2017-03-27 21:36:28 -04:00
return & mockImage {
id : "abcdef" ,
config : & container . Config { Cmd : origCmd } ,
} , nil , nil
2017-04-21 15:08:11 -04:00
}
mockBackend . containerCreateFunc = func ( config types . ContainerCreateConfig ) ( container . ContainerCreateCreatedBody , error ) {
// Check the runConfig.Cmd sent to create()
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . DeepEqual ( cmdWithShell , config . Config . Cmd ) )
assert . Check ( t , is . Contains ( config . Config . Env , "one=two" ) )
assert . Check ( t , is . DeepEqual ( strslice . StrSlice { "" } , config . Config . Entrypoint ) )
2017-04-21 15:08:11 -04:00
return container . ContainerCreateCreatedBody { ID : "12345" } , nil
}
2018-02-06 13:27:55 -05:00
mockBackend . commitFunc = func ( cfg backend . CommitConfig ) ( image . ID , error ) {
2017-04-21 15:08:11 -04:00
// Check the runConfig.Cmd sent to commit()
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . DeepEqual ( origCmd , cfg . Config . Cmd ) )
assert . Check ( t , is . DeepEqual ( cachedCmd , cfg . ContainerConfig . Cmd ) )
assert . Check ( t , is . DeepEqual ( strslice . StrSlice ( nil ) , cfg . Config . Entrypoint ) )
2017-04-21 15:08:11 -04:00
return "" , nil
}
2017-05-22 11:21:17 -04:00
from := & instructions . Stage { BaseName : "abcdef" }
err := initializeStage ( sb , from )
2018-03-13 15:28:34 -04:00
assert . NilError ( t , err )
2017-05-22 11:21:17 -04:00
sb . state . buildArgs . AddArg ( "one" , strPtr ( "two" ) )
Windows: (WCOW) Generate OCI spec that remote runtime can escape
Signed-off-by: John Howard <jhoward@microsoft.com>
Also fixes https://github.com/moby/moby/issues/22874
This commit is a pre-requisite to moving moby/moby on Windows to using
Containerd for its runtime.
The reason for this is that the interface between moby and containerd
for the runtime is an OCI spec which must be unambigious.
It is the responsibility of the runtime (runhcs in the case of
containerd on Windows) to ensure that arguments are escaped prior
to calling into HCS and onwards to the Win32 CreateProcess call.
Previously, the builder was always escaping arguments which has
led to several bugs in moby. Because the local runtime in
libcontainerd had context of whether or not arguments were escaped,
it was possible to hack around in daemon/oci_windows.go with
knowledge of the context of the call (from builder or not).
With a remote runtime, this is not possible as there's rightly
no context of the caller passed across in the OCI spec. Put another
way, as I put above, the OCI spec must be unambigious.
The other previous limitation (which leads to various subtle bugs)
is that moby is coded entirely from a Linux-centric point of view.
Unfortunately, Windows != Linux. Windows CreateProcess uses a
command line, not an array of arguments. And it has very specific
rules about how to escape a command line. Some interesting reading
links about this are:
https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
https://stackoverflow.com/questions/31838469/how-do-i-convert-argv-to-lpcommandline-parameter-of-createprocess
https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments?view=vs-2017
For this reason, the OCI spec has recently been updated to cater
for more natural syntax by including a CommandLine option in
Process.
What does this commit do?
Primary objective is to ensure that the built OCI spec is unambigious.
It changes the builder so that `ArgsEscaped` as commited in a
layer is only controlled by the use of CMD or ENTRYPOINT.
Subsequently, when calling in to create a container from the builder,
if follows a different path to both `docker run` and `docker create`
using the added `ContainerCreateIgnoreImagesArgsEscaped`. This allows
a RUN from the builder to control how to escape in the OCI spec.
It changes the builder so that when shell form is used for RUN,
CMD or ENTRYPOINT, it builds (for WCOW) a more natural command line
using the original as put by the user in the dockerfile, not
the parsed version as a set of args which loses fidelity.
This command line is put into args[0] and `ArgsEscaped` is set
to true for CMD or ENTRYPOINT. A RUN statement does not commit
`ArgsEscaped` to the commited layer regardless or whether shell
or exec form were used.
2019-01-17 19:03:29 -05:00
// This is hugely annoying. On the Windows side, it relies on the
// RunCommand being able to emit String() and Name() (as implemented by
// withNameAndCode). Unfortunately, that is internal, and no way to directly
// set. However, we can fortunately use ParseInstruction in the instructions
// package to parse a fake node which can be used as our instructions.RunCommand
// instead.
node := & parser . Node {
Original : ` RUN echo foo ` ,
Value : "run" ,
}
runint , err := instructions . ParseInstruction ( node )
assert . NilError ( t , err )
runinst := runint . ( * instructions . RunCommand )
runinst . CmdLine = strslice . StrSlice { "echo foo" }
runinst . PrependShell = true
assert . NilError ( t , dispatch ( sb , runinst ) )
2017-04-21 15:08:11 -04:00
// Check that runConfig.Cmd has not been modified by run
2018-03-13 15:28:34 -04:00
assert . Check ( t , is . DeepEqual ( origCmd , sb . state . runConfig . Cmd ) )
2017-04-10 21:58:31 -04:00
}
2018-07-07 20:36:50 -04:00
func TestRunIgnoresHealthcheck ( t * testing . T ) {
b := newBuilderWithMockBackend ( )
args := NewBuildArgs ( make ( map [ string ] * string ) )
sb := newDispatchRequest ( b , '`' , nil , args , newStagesBuildResults ( ) )
b . disableCommit = false
origCmd := strslice . StrSlice ( [ ] string { "cmd" , "in" , "from" , "image" } )
imageCache := & mockImageCache {
getCacheFunc : func ( parentID string , cfg * container . Config ) ( string , error ) {
return "" , nil
} ,
}
mockBackend := b . docker . ( * MockBackend )
mockBackend . makeImageCacheFunc = func ( _ [ ] string ) builder . ImageCache {
return imageCache
}
b . imageProber = newImageProber ( mockBackend , nil , false )
mockBackend . getImageFunc = func ( _ string ) ( builder . Image , builder . ROLayer , error ) {
return & mockImage {
id : "abcdef" ,
config : & container . Config { Cmd : origCmd } ,
} , nil , nil
}
mockBackend . containerCreateFunc = func ( config types . ContainerCreateConfig ) ( container . ContainerCreateCreatedBody , error ) {
return container . ContainerCreateCreatedBody { ID : "12345" } , nil
}
mockBackend . commitFunc = func ( cfg backend . CommitConfig ) ( image . ID , error ) {
return "" , nil
}
from := & instructions . Stage { BaseName : "abcdef" }
err := initializeStage ( sb , from )
assert . NilError ( t , err )
expectedTest := [ ] string { "CMD-SHELL" , "curl -f http://localhost/ || exit 1" }
cmd := & instructions . HealthCheckCommand {
Health : & container . HealthConfig {
Test : expectedTest ,
} ,
}
assert . NilError ( t , dispatch ( sb , cmd ) )
assert . Assert ( t , sb . state . runConfig . Healthcheck != nil )
mockBackend . containerCreateFunc = func ( config types . ContainerCreateConfig ) ( container . ContainerCreateCreatedBody , error ) {
// Check the Healthcheck is disabled.
assert . Check ( t , is . DeepEqual ( [ ] string { "NONE" } , config . Config . Healthcheck . Test ) )
return container . ContainerCreateCreatedBody { ID : "123456" } , nil
}
sb . state . buildArgs . AddArg ( "one" , strPtr ( "two" ) )
run := & instructions . RunCommand {
ShellDependantCmdLine : instructions . ShellDependantCmdLine {
CmdLine : strslice . StrSlice { "echo foo" } ,
PrependShell : true ,
} ,
}
assert . NilError ( t , dispatch ( sb , run ) )
assert . Check ( t , is . DeepEqual ( expectedTest , sb . state . runConfig . Healthcheck . Test ) )
}
builder: produce error when using unsupported Dockerfile option
With the promotion of the experimental Dockerfile syntax to "stable", the Dockerfile
syntax now includes some options that are supported by BuildKit, but not (yet)
supported in the classic builder.
As a result, parsing a Dockerfile may succeed, but any flag that's known to BuildKit,
but not supported by the classic builder is silently ignored;
$ mkdir buildkit_flags && cd buildkit_flags
$ touch foo.txt
For example, `RUN --mount`:
DOCKER_BUILDKIT=0 docker build --no-cache -f- . <<EOF
FROM busybox
RUN --mount=type=cache,target=/foo echo hello
EOF
Sending build context to Docker daemon 2.095kB
Step 1/2 : FROM busybox
---> 219ee5171f80
Step 2/2 : RUN --mount=type=cache,target=/foo echo hello
---> Running in 022fdb856bc8
hello
Removing intermediate container 022fdb856bc8
---> e9f0988844d1
Successfully built e9f0988844d1
Or `COPY --chmod` (same for `ADD --chmod`):
DOCKER_BUILDKIT=0 docker build --no-cache -f- . <<EOF
FROM busybox
COPY --chmod=0777 /foo.txt /foo.txt
EOF
Sending build context to Docker daemon 2.095kB
Step 1/2 : FROM busybox
---> 219ee5171f80
Step 2/2 : COPY --chmod=0777 /foo.txt /foo.txt
---> 8b7117932a2a
Successfully built 8b7117932a2a
Note that unknown flags still produce and error, for example, the below fails because `--hello` is an unknown flag;
DOCKER_BUILDKIT=0 docker build -<<EOF
FROM busybox
RUN --hello echo hello
EOF
Sending build context to Docker daemon 2.048kB
Error response from daemon: dockerfile parse error line 2: Unknown flag: hello
With this patch applied
----------------------------
With this patch applied, flags that are known in the Dockerfile spec, but are not
supported by the classic builder, produce an error, which includes a link to the
documentation how to enable BuildKit:
DOCKER_BUILDKIT=0 docker build --no-cache -f- . <<EOF
FROM busybox
RUN --mount=type=cache,target=/foo echo hello
EOF
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM busybox
---> b97242f89c8a
Step 2/2 : RUN --mount=type=cache,target=/foo echo hello
the --mount option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled
DOCKER_BUILDKIT=0 docker build --no-cache -f- . <<EOF
FROM busybox
COPY --chmod=0777 /foo.txt /foo.txt
EOF
Sending build context to Docker daemon 2.095kB
Step 1/2 : FROM busybox
---> b97242f89c8a
Step 2/2 : COPY --chmod=0777 /foo.txt /foo.txt
the --chmod option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2021-01-21 07:25:18 -05:00
func TestDispatchUnsupportedOptions ( t * testing . T ) {
b := newBuilderWithMockBackend ( )
sb := newDispatchRequest ( b , '`' , nil , NewBuildArgs ( make ( map [ string ] * string ) ) , newStagesBuildResults ( ) )
sb . state . baseImage = & mockImage { }
sb . state . operatingSystem = runtime . GOOS
t . Run ( "ADD with chmod" , func ( t * testing . T ) {
cmd := & instructions . AddCommand { SourcesAndDest : [ ] string { "." , "." } , Chmod : "0655" }
err := dispatch ( sb , cmd )
assert . Error ( t , err , "the --chmod option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled" )
} )
t . Run ( "COPY with chmod" , func ( t * testing . T ) {
cmd := & instructions . CopyCommand { SourcesAndDest : [ ] string { "." , "." } , Chmod : "0655" }
err := dispatch ( sb , cmd )
assert . Error ( t , err , "the --chmod option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled" )
} )
t . Run ( "RUN with unsupported options" , func ( t * testing . T ) {
cmd := & instructions . RunCommand {
ShellDependantCmdLine : instructions . ShellDependantCmdLine {
CmdLine : strslice . StrSlice { "echo foo" } ,
PrependShell : true ,
} ,
}
// classic builder "RUN" currently doesn't support any flags, but testing
// both "known" flags and "bogus" flags for completeness, and in case
// one or more of these flags will be supported in future
for _ , f := range [ ] string { "mount" , "network" , "security" , "any-flag" } {
cmd . FlagsUsed = [ ] string { f }
err := dispatch ( sb , cmd )
assert . Error ( t , err , fmt . Sprintf ( "the --%s option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled" , f ) )
}
} )
}