moby/builder/dockerfile/dispatchers_test.go
Sebastiaan van Stijn 484e6b784c
api/types: move ContainerCreateConfig, ContainerRmConfig to api/types/backend
The `ContainerCreateConfig` and `ContainerRmConfig` structs are used for
options to be passed to the backend, and are not used in client code.

Thess struct currently is intended for internal use only (for example, the
`AdjustCPUShares` is an internal implementation details to adjust the container's
config when older API versions are used).

Somewhat ironically, the signature of the Backend has a nicer UX than that
of the client's `ContainerCreate` signature (which expects all options to
be passed as separate arguments), so we may want to update that signature
to be closer to what the backend is using, but that can be left as a future
exercise.

This patch moves the `ContainerCreateConfig` and `ContainerRmConfig` structs
to the backend package to prevent it being imported in the client, and to make
it more clear that this is part of internal APIs, and not public-facing.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-12-05 16:41:36 +01:00

638 lines
22 KiB
Go

package dockerfile // import "github.com/docker/docker/builder/dockerfile"
import (
"bytes"
"context"
"fmt"
"runtime"
"strings"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/builder"
"github.com/docker/docker/image"
"github.com/docker/docker/oci"
"github.com/docker/go-connections/nat"
"github.com/moby/buildkit/frontend/dockerfile/instructions"
"github.com/moby/buildkit/frontend/dockerfile/parser"
"github.com/moby/buildkit/frontend/dockerfile/shell"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func newBuilderWithMockBackend(t *testing.T) *Builder {
t.Helper()
mockBackend := &MockBackend{}
opts := &types.ImageBuildOptions{}
ctx := context.Background()
imageProber, err := newImageProber(ctx, mockBackend, nil, false)
assert.NilError(t, err, "Could not create image prober")
b := &Builder{
options: opts,
docker: mockBackend,
Stdout: new(bytes.Buffer),
disableCommit: true,
imageSources: newImageSources(builderOptions{
Options: opts,
Backend: mockBackend,
}),
imageProber: imageProber,
containerManager: newContainerManager(mockBackend),
}
return b
}
func TestEnv2Variables(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
envCommand := &instructions.EnvCommand{
Env: instructions.KeyValuePairs{
instructions.KeyValuePair{Key: "var1", Value: "val1"},
instructions.KeyValuePair{Key: "var2", Value: "val2"},
},
}
err := dispatch(context.TODO(), sb, envCommand)
assert.NilError(t, err)
expected := []string{
"var1=val1",
"var2=val2",
}
assert.Check(t, is.DeepEqual(expected, sb.state.runConfig.Env))
}
func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
sb.state.runConfig.Env = []string{"var1=old", "var2=fromenv"}
envCommand := &instructions.EnvCommand{
Env: instructions.KeyValuePairs{
instructions.KeyValuePair{Key: "var1", Value: "val1"},
},
}
err := dispatch(context.TODO(), sb, envCommand)
assert.NilError(t, err)
expected := []string{
"var1=val1",
"var2=fromenv",
}
assert.Check(t, is.DeepEqual(expected, sb.state.runConfig.Env))
}
func TestMaintainer(t *testing.T) {
maintainerEntry := "Some Maintainer <maintainer@example.com>"
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
cmd := &instructions.MaintainerCommand{Maintainer: maintainerEntry}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
assert.Check(t, is.Equal(maintainerEntry, sb.state.maintainer))
}
func TestLabel(t *testing.T) {
labelName := "label"
labelValue := "value"
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
cmd := &instructions.LabelCommand{
Labels: instructions.KeyValuePairs{
instructions.KeyValuePair{Key: labelName, Value: labelValue},
},
}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
assert.Assert(t, is.Contains(sb.state.runConfig.Labels, labelName))
assert.Check(t, is.Equal(sb.state.runConfig.Labels[labelName], labelValue))
}
func TestFromScratch(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
cmd := &instructions.Stage{
BaseName: "scratch",
}
err := initializeStage(context.TODO(), sb, cmd)
if runtime.GOOS == "windows" {
assert.Check(t, is.Error(err, "Windows does not support FROM scratch"))
return
}
assert.NilError(t, err)
assert.Check(t, sb.state.hasFromImage())
assert.Check(t, is.Equal("", sb.state.imageID))
// TODO(thaJeztah): use github.com/moby/buildkit/util/system.DefaultPathEnv() once https://github.com/moby/buildkit/pull/3158 is resolved.
expected := "PATH=" + oci.DefaultPathEnv(runtime.GOOS)
assert.Check(t, is.DeepEqual([]string{expected}, sb.state.runConfig.Env))
}
func TestFromWithArg(t *testing.T) {
tag, expected := ":sometag", "expectedthisid"
getImage := func(name string) (builder.Image, builder.ROLayer, error) {
assert.Check(t, is.Equal("alpine"+tag, name))
return &mockImage{id: "expectedthisid"}, nil, nil
}
b := newBuilderWithMockBackend(t)
b.docker.(*MockBackend).getImageFunc = getImage
args := NewBuildArgs(make(map[string]*string))
val := "sometag"
metaArg := instructions.ArgCommand{Args: []instructions.KeyValuePairOptional{{
Key: "THETAG",
Value: &val,
}}}
cmd := &instructions.Stage{
BaseName: "alpine:${THETAG}",
}
err := processMetaArg(metaArg, shell.NewLex('\\'), args)
sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults())
assert.NilError(t, err)
err = initializeStage(context.TODO(), sb, cmd)
assert.NilError(t, err)
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))
}
func TestFromWithArgButBuildArgsNotGiven(t *testing.T) {
b := newBuilderWithMockBackend(t)
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(context.TODO(), sb, cmd)
assert.Error(t, err, "base name (${THETAG}) should not be blank")
}
func TestFromWithUndefinedArg(t *testing.T) {
tag, expected := "sometag", "expectedthisid"
getImage := func(name string) (builder.Image, builder.ROLayer, error) {
assert.Check(t, is.Equal("alpine", name))
return &mockImage{id: "expectedthisid"}, nil, nil
}
b := newBuilderWithMockBackend(t)
b.docker.(*MockBackend).getImageFunc = getImage
sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
b.options.BuildArgs = map[string]*string{"THETAG": &tag}
cmd := &instructions.Stage{
BaseName: "alpine${THETAG}",
}
err := initializeStage(context.TODO(), sb, cmd)
assert.NilError(t, err)
assert.Check(t, is.Equal(expected, sb.state.imageID))
}
func TestFromMultiStageWithNamedStage(t *testing.T) {
b := newBuilderWithMockBackend(t)
firstFrom := &instructions.Stage{BaseName: "someimg", Name: "base"}
secondFrom := &instructions.Stage{BaseName: "base"}
previousResults := newStagesBuildResults()
firstSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults)
secondSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults)
err := initializeStage(context.TODO(), firstSB, firstFrom)
assert.NilError(t, err)
assert.Check(t, firstSB.state.hasFromImage())
previousResults.indexed["base"] = firstSB.state.runConfig
previousResults.flat = append(previousResults.flat, firstSB.state.runConfig)
err = initializeStage(context.TODO(), secondSB, secondFrom)
assert.NilError(t, err)
assert.Check(t, secondSB.state.hasFromImage())
}
func TestOnbuild(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
cmd := &instructions.OnbuildCommand{
Expression: "ADD . /app/src",
}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
assert.Check(t, is.Equal("ADD . /app/src", sb.state.runConfig.OnBuild[0]))
}
func TestWorkdir(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
sb.state.baseImage = &mockImage{}
workingDir := "/app"
if runtime.GOOS == "windows" {
workingDir = "C:\\app"
}
cmd := &instructions.WorkdirCommand{
Path: workingDir,
}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
assert.Check(t, is.Equal(workingDir, sb.state.runConfig.WorkingDir))
}
func TestCmd(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
sb.state.baseImage = &mockImage{}
command := "./executable"
cmd := &instructions.CmdCommand{
ShellDependantCmdLine: instructions.ShellDependantCmdLine{
CmdLine: strslice.StrSlice{command},
PrependShell: true,
},
}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
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))
}
assert.Check(t, is.DeepEqual(expectedCommand, sb.state.runConfig.Cmd))
assert.Check(t, sb.state.cmdSet)
}
func TestHealthcheckNone(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
cmd := &instructions.HealthCheckCommand{
Health: &container.HealthConfig{
Test: []string{"NONE"},
},
}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
assert.Check(t, is.DeepEqual([]string{"NONE"}, sb.state.runConfig.Healthcheck.Test))
}
func TestHealthcheckCmd(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
cmd := &instructions.HealthCheckCommand{
Health: &container.HealthConfig{
Test: expectedTest,
},
}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test))
}
func TestEntrypoint(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
sb.state.baseImage = &mockImage{}
entrypointCmd := "/usr/sbin/nginx"
cmd := &instructions.EntrypointCommand{
ShellDependantCmdLine: instructions.ShellDependantCmdLine{
CmdLine: strslice.StrSlice{entrypointCmd},
PrependShell: true,
},
}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
assert.Assert(t, sb.state.runConfig.Entrypoint != nil)
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))
}
assert.Check(t, is.DeepEqual(expectedEntrypoint, sb.state.runConfig.Entrypoint))
}
func TestExpose(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
exposedPort := "80"
cmd := &instructions.ExposeCommand{
Ports: []string{exposedPort},
}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
assert.Assert(t, sb.state.runConfig.ExposedPorts != nil)
assert.Assert(t, is.Len(sb.state.runConfig.ExposedPorts, 1))
portsMapping, err := nat.ParsePortSpec(exposedPort)
assert.NilError(t, err)
assert.Check(t, is.Contains(sb.state.runConfig.ExposedPorts, portsMapping[0].Port))
}
func TestUser(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
cmd := &instructions.UserCommand{
User: "test",
}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
assert.Check(t, is.Equal("test", sb.state.runConfig.User))
}
func TestVolume(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
exposedVolume := "/foo"
cmd := &instructions.VolumeCommand{
Volumes: []string{exposedVolume},
}
err := dispatch(context.TODO(), sb, cmd)
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))
}
func TestStopSignal(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Windows does not support stopsignal")
return
}
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
sb.state.baseImage = &mockImage{}
signal := "SIGKILL"
cmd := &instructions.StopSignalCommand{
Signal: signal,
}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
assert.Check(t, is.Equal(signal, sb.state.runConfig.StopSignal))
}
func TestArg(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
argName := "foo"
argVal := "bar"
cmd := &instructions.ArgCommand{Args: []instructions.KeyValuePairOptional{{Key: argName, Value: &argVal}}}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
expected := map[string]string{argName: argVal}
assert.Check(t, is.DeepEqual(expected, sb.state.buildArgs.GetAllAllowed()))
}
func TestShell(t *testing.T) {
b := newBuilderWithMockBackend(t)
sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
shellCmd := "powershell"
cmd := &instructions.ShellCommand{Shell: strslice.StrSlice{shellCmd}}
err := dispatch(context.TODO(), sb, cmd)
assert.NilError(t, err)
expectedShell := strslice.StrSlice([]string{shellCmd})
assert.Check(t, is.DeepEqual(expectedShell, sb.state.runConfig.Shell))
}
func TestPrependEnvOnCmd(t *testing.T) {
buildArgs := NewBuildArgs(nil)
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",
})
assert.Check(t, is.DeepEqual(expected, cmdWithEnv))
}
func TestRunWithBuildArgs(t *testing.T) {
b := newBuilderWithMockBackend(t)
args := NewBuildArgs(make(map[string]*string))
args.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
b.disableCommit = false
sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults())
runConfig := &container.Config{}
origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
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"))
}
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()
assert.Check(t, is.DeepEqual(cachedCmd, cfg.Cmd))
assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Entrypoint))
return "", nil
},
}
mockBackend := b.docker.(*MockBackend)
mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
return imageCache
}
imageProber, err := newImageProber(context.TODO(), mockBackend, nil, false)
assert.NilError(t, err, "Could not create image prober")
b.imageProber = imageProber
mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) {
return &mockImage{
id: "abcdef",
config: &container.Config{Cmd: origCmd},
}, nil, nil
}
mockBackend.containerCreateFunc = func(config backend.ContainerCreateConfig) (container.CreateResponse, error) {
// Check the runConfig.Cmd sent to create()
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))
return container.CreateResponse{ID: "12345"}, nil
}
mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) {
// Check the runConfig.Cmd sent to commit()
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))
return "", nil
}
from := &instructions.Stage{BaseName: "abcdef"}
err = initializeStage(context.TODO(), sb, from)
assert.NilError(t, err)
sb.state.buildArgs.AddArg("one", strPtr("two"))
// 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(context.TODO(), sb, runinst))
// Check that runConfig.Cmd has not been modified by run
assert.Check(t, is.DeepEqual(origCmd, sb.state.runConfig.Cmd))
}
func TestRunIgnoresHealthcheck(t *testing.T) {
b := newBuilderWithMockBackend(t)
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
}
imageProber, err := newImageProber(context.TODO(), mockBackend, nil, false)
assert.NilError(t, err, "Could not create image prober")
b.imageProber = imageProber
mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) {
return &mockImage{
id: "abcdef",
config: &container.Config{Cmd: origCmd},
}, nil, nil
}
mockBackend.containerCreateFunc = func(config backend.ContainerCreateConfig) (container.CreateResponse, error) {
return container.CreateResponse{ID: "12345"}, nil
}
mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) {
return "", nil
}
from := &instructions.Stage{BaseName: "abcdef"}
err = initializeStage(context.TODO(), sb, from)
assert.NilError(t, err)
expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
healthint, err := instructions.ParseInstruction(&parser.Node{
Original: `HEALTHCHECK CMD curl -f http://localhost/ || exit 1`,
Value: "healthcheck",
Next: &parser.Node{
Value: "cmd",
Next: &parser.Node{
Value: `curl -f http://localhost/ || exit 1`,
},
},
})
assert.NilError(t, err)
cmd := healthint.(*instructions.HealthCheckCommand)
assert.NilError(t, dispatch(context.TODO(), sb, cmd))
assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
mockBackend.containerCreateFunc = func(config backend.ContainerCreateConfig) (container.CreateResponse, error) {
// Check the Healthcheck is disabled.
assert.Check(t, is.DeepEqual([]string{"NONE"}, config.Config.Healthcheck.Test))
return container.CreateResponse{ID: "123456"}, nil
}
sb.state.buildArgs.AddArg("one", strPtr("two"))
runint, err := instructions.ParseInstruction(&parser.Node{Original: `RUN echo foo`, Value: "run"})
assert.NilError(t, err)
run := runint.(*instructions.RunCommand)
run.PrependShell = true
assert.NilError(t, dispatch(context.TODO(), sb, run))
assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test))
}
func TestDispatchUnsupportedOptions(t *testing.T) {
b := newBuilderWithMockBackend(t)
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: instructions.SourcesAndDest{
SourcePaths: []string{"."},
DestPath: ".",
},
Chmod: "0655",
}
err := dispatch(context.TODO(), 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: instructions.SourcesAndDest{
SourcePaths: []string{"."},
DestPath: ".",
},
Chmod: "0655",
}
err := dispatch(context.TODO(), 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) {
runint, err := instructions.ParseInstruction(&parser.Node{Original: `RUN echo foo`, Value: "run"})
assert.NilError(t, err)
cmd := runint.(*instructions.RunCommand)
// 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(context.TODO(), 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))
}
})
}