484e6b784c
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>
638 lines
22 KiB
Go
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))
|
|
}
|
|
})
|
|
}
|