1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/builder/dockerfile/dispatchers_test.go
Daniel Nephin 2f0ebba0e7 Some refactoring of dispatch()
Remove runConfig from Builder and dispatchRequest. It is not only on
dispatchState.

Move dispatch state fields from Builder to dispatchState

Move stageName tracking to dispatchRequest.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
2017-05-04 17:11:08 -04:00

490 lines
14 KiB
Go

package dockerfile
import (
"fmt"
"runtime"
"testing"
"bytes"
"context"
"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/builder/dockerfile/parser"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type commandWithFunction struct {
name string
function func(args []string) error
}
func withArgs(f dispatcher) func([]string) error {
return func(args []string) error {
return f(dispatchRequest{args: args})
}
}
func withBuilderAndArgs(builder *Builder, f dispatcher) func([]string) error {
return func(args []string) error {
return f(defaultDispatchReq(builder, args...))
}
}
func defaultDispatchReq(builder *Builder, args ...string) dispatchRequest {
return dispatchRequest{
builder: builder,
args: args,
flags: NewBFlags(),
shlex: NewShellLex(parser.DefaultEscapeToken),
state: &dispatchState{runConfig: &container.Config{}},
}
}
func newBuilderWithMockBackend() *Builder {
b := &Builder{
options: &types.ImageBuildOptions{},
docker: &MockBackend{},
buildArgs: newBuildArgs(make(map[string]*string)),
tmpContainers: make(map[string]struct{}),
Stdout: new(bytes.Buffer),
clientCtx: context.Background(),
disableCommit: true,
}
b.imageContexts = &imageContexts{b: b}
return b
}
func TestCommandsExactlyOneArgument(t *testing.T) {
commands := []commandWithFunction{
{"MAINTAINER", withArgs(maintainer)},
{"WORKDIR", withArgs(workdir)},
{"USER", withArgs(user)},
{"STOPSIGNAL", withArgs(stopSignal)},
}
for _, command := range commands {
err := command.function([]string{})
assert.EqualError(t, err, errExactlyOneArgument(command.name).Error())
}
}
func TestCommandsAtLeastOneArgument(t *testing.T) {
commands := []commandWithFunction{
{"ENV", withArgs(env)},
{"LABEL", withArgs(label)},
{"ONBUILD", withArgs(onbuild)},
{"HEALTHCHECK", withArgs(healthcheck)},
{"EXPOSE", withArgs(expose)},
{"VOLUME", withArgs(volume)},
}
for _, command := range commands {
err := command.function([]string{})
assert.EqualError(t, err, errAtLeastOneArgument(command.name).Error())
}
}
func TestCommandsAtLeastTwoArguments(t *testing.T) {
commands := []commandWithFunction{
{"ADD", withArgs(add)},
{"COPY", withArgs(dispatchCopy)}}
for _, command := range commands {
err := command.function([]string{"arg1"})
assert.EqualError(t, err, errAtLeastTwoArguments(command.name).Error())
}
}
func TestCommandsTooManyArguments(t *testing.T) {
commands := []commandWithFunction{
{"ENV", withArgs(env)},
{"LABEL", withArgs(label)}}
for _, command := range commands {
err := command.function([]string{"arg1", "arg2", "arg3"})
assert.EqualError(t, err, errTooManyArguments(command.name).Error())
}
}
func TestCommandsBlankNames(t *testing.T) {
builder := newBuilderWithMockBackend()
commands := []commandWithFunction{
{"ENV", withBuilderAndArgs(builder, env)},
{"LABEL", withBuilderAndArgs(builder, label)},
}
for _, command := range commands {
err := command.function([]string{"", ""})
assert.EqualError(t, err, errBlankCommandNames(command.name).Error())
}
}
func TestEnv2Variables(t *testing.T) {
b := newBuilderWithMockBackend()
args := []string{"var1", "val1", "var2", "val2"}
req := defaultDispatchReq(b, args...)
err := env(req)
require.NoError(t, err)
expected := []string{
fmt.Sprintf("%s=%s", args[0], args[1]),
fmt.Sprintf("%s=%s", args[2], args[3]),
}
assert.Equal(t, expected, req.state.runConfig.Env)
}
func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
b := newBuilderWithMockBackend()
args := []string{"var1", "val1"}
req := defaultDispatchReq(b, args...)
req.state.runConfig.Env = []string{"var1=old", "var2=fromenv"}
err := env(req)
require.NoError(t, err)
expected := []string{
fmt.Sprintf("%s=%s", args[0], args[1]),
"var2=fromenv",
}
assert.Equal(t, expected, req.state.runConfig.Env)
}
func TestMaintainer(t *testing.T) {
maintainerEntry := "Some Maintainer <maintainer@example.com>"
b := newBuilderWithMockBackend()
req := defaultDispatchReq(b, maintainerEntry)
err := maintainer(req)
require.NoError(t, err)
assert.Equal(t, maintainerEntry, req.state.maintainer)
}
func TestLabel(t *testing.T) {
labelName := "label"
labelValue := "value"
labelEntry := []string{labelName, labelValue}
b := newBuilderWithMockBackend()
req := defaultDispatchReq(b, labelEntry...)
err := label(req)
require.NoError(t, err)
require.Contains(t, req.state.runConfig.Labels, labelName)
assert.Equal(t, req.state.runConfig.Labels[labelName], labelValue)
}
func TestFromScratch(t *testing.T) {
b := newBuilderWithMockBackend()
req := defaultDispatchReq(b, "scratch")
err := from(req)
if runtime.GOOS == "windows" {
assert.EqualError(t, err, "Windows does not support FROM scratch")
return
}
require.NoError(t, err)
assert.Equal(t, "", req.state.imageID)
assert.Equal(t, true, req.state.noBaseImage)
}
func TestFromWithArg(t *testing.T) {
tag, expected := ":sometag", "expectedthisid"
getImage := func(name string) (builder.Image, error) {
assert.Equal(t, "alpine"+tag, name)
return &mockImage{id: "expectedthisid"}, nil
}
b := newBuilderWithMockBackend()
b.docker.(*MockBackend).getImageOnBuildFunc = getImage
require.NoError(t, arg(defaultDispatchReq(b, "THETAG="+tag)))
req := defaultDispatchReq(b, "alpine${THETAG}")
err := from(req)
require.NoError(t, err)
assert.Equal(t, expected, req.state.imageID)
assert.Equal(t, expected, req.state.baseImage.ImageID())
assert.Len(t, b.buildArgs.GetAllAllowed(), 0)
assert.Len(t, b.buildArgs.GetAllMeta(), 1)
}
func TestFromWithUndefinedArg(t *testing.T) {
tag, expected := "sometag", "expectedthisid"
getImage := func(name string) (builder.Image, error) {
assert.Equal(t, "alpine", name)
return &mockImage{id: "expectedthisid"}, nil
}
b := newBuilderWithMockBackend()
b.docker.(*MockBackend).getImageOnBuildFunc = getImage
b.options.BuildArgs = map[string]*string{"THETAG": &tag}
req := defaultDispatchReq(b, "alpine${THETAG}")
err := from(req)
require.NoError(t, err)
assert.Equal(t, expected, req.state.imageID)
}
func TestOnbuildIllegalTriggers(t *testing.T) {
triggers := []struct{ command, expectedError string }{
{"ONBUILD", "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed"},
{"MAINTAINER", "MAINTAINER isn't allowed as an ONBUILD trigger"},
{"FROM", "FROM isn't allowed as an ONBUILD trigger"}}
for _, trigger := range triggers {
b := newBuilderWithMockBackend()
err := onbuild(defaultDispatchReq(b, trigger.command))
testutil.ErrorContains(t, err, trigger.expectedError)
}
}
func TestOnbuild(t *testing.T) {
b := newBuilderWithMockBackend()
req := defaultDispatchReq(b, "ADD", ".", "/app/src")
req.original = "ONBUILD ADD . /app/src"
req.state.runConfig = &container.Config{}
err := onbuild(req)
require.NoError(t, err)
assert.Equal(t, "ADD . /app/src", req.state.runConfig.OnBuild[0])
}
func TestWorkdir(t *testing.T) {
b := newBuilderWithMockBackend()
workingDir := "/app"
if runtime.GOOS == "windows" {
workingDir = "C:\app"
}
req := defaultDispatchReq(b, workingDir)
err := workdir(req)
require.NoError(t, err)
assert.Equal(t, workingDir, req.state.runConfig.WorkingDir)
}
func TestCmd(t *testing.T) {
b := newBuilderWithMockBackend()
command := "./executable"
req := defaultDispatchReq(b, command)
err := cmd(req)
require.NoError(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.Equal(t, expectedCommand, req.state.runConfig.Cmd)
assert.True(t, req.state.cmdSet)
}
func TestHealthcheckNone(t *testing.T) {
b := newBuilderWithMockBackend()
req := defaultDispatchReq(b, "NONE")
err := healthcheck(req)
require.NoError(t, err)
require.NotNil(t, req.state.runConfig.Healthcheck)
assert.Equal(t, []string{"NONE"}, req.state.runConfig.Healthcheck.Test)
}
func TestHealthcheckCmd(t *testing.T) {
b := newBuilderWithMockBackend()
args := []string{"CMD", "curl", "-f", "http://localhost/", "||", "exit", "1"}
req := defaultDispatchReq(b, args...)
err := healthcheck(req)
require.NoError(t, err)
require.NotNil(t, req.state.runConfig.Healthcheck)
expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
assert.Equal(t, expectedTest, req.state.runConfig.Healthcheck.Test)
}
func TestEntrypoint(t *testing.T) {
b := newBuilderWithMockBackend()
entrypointCmd := "/usr/sbin/nginx"
req := defaultDispatchReq(b, entrypointCmd)
err := entrypoint(req)
require.NoError(t, err)
require.NotNil(t, req.state.runConfig.Entrypoint)
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.Equal(t, expectedEntrypoint, req.state.runConfig.Entrypoint)
}
func TestExpose(t *testing.T) {
b := newBuilderWithMockBackend()
exposedPort := "80"
req := defaultDispatchReq(b, exposedPort)
err := expose(req)
require.NoError(t, err)
require.NotNil(t, req.state.runConfig.ExposedPorts)
require.Len(t, req.state.runConfig.ExposedPorts, 1)
portsMapping, err := nat.ParsePortSpec(exposedPort)
require.NoError(t, err)
assert.Contains(t, req.state.runConfig.ExposedPorts, portsMapping[0].Port)
}
func TestUser(t *testing.T) {
b := newBuilderWithMockBackend()
userCommand := "foo"
req := defaultDispatchReq(b, userCommand)
err := user(req)
require.NoError(t, err)
assert.Equal(t, userCommand, req.state.runConfig.User)
}
func TestVolume(t *testing.T) {
b := newBuilderWithMockBackend()
exposedVolume := "/foo"
req := defaultDispatchReq(b, exposedVolume)
err := volume(req)
require.NoError(t, err)
require.NotNil(t, req.state.runConfig.Volumes)
assert.Len(t, req.state.runConfig.Volumes, 1)
assert.Contains(t, req.state.runConfig.Volumes, exposedVolume)
}
func TestStopSignal(t *testing.T) {
b := newBuilderWithMockBackend()
signal := "SIGKILL"
req := defaultDispatchReq(b, signal)
err := stopSignal(req)
require.NoError(t, err)
assert.Equal(t, signal, req.state.runConfig.StopSignal)
}
func TestArg(t *testing.T) {
b := newBuilderWithMockBackend()
argName := "foo"
argVal := "bar"
argDef := fmt.Sprintf("%s=%s", argName, argVal)
err := arg(defaultDispatchReq(b, argDef))
require.NoError(t, err)
expected := map[string]string{argName: argVal}
assert.Equal(t, expected, b.buildArgs.GetAllAllowed())
}
func TestShell(t *testing.T) {
b := newBuilderWithMockBackend()
shellCmd := "powershell"
req := defaultDispatchReq(b, shellCmd)
req.attributes = map[string]bool{"json": true}
err := shell(req)
require.NoError(t, err)
expectedShell := strslice.StrSlice([]string{shellCmd})
assert.Equal(t, expectedShell, req.state.runConfig.Shell)
}
func TestParseOptInterval(t *testing.T) {
flInterval := &Flag{
name: "interval",
flagType: stringType,
Value: "50ns",
}
_, err := parseOptInterval(flInterval)
testutil.ErrorContains(t, err, "cannot be less than 1ms")
flInterval.Value = "1ms"
_, err = parseOptInterval(flInterval)
require.NoError(t, err)
}
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.Equal(t, expected, cmdWithEnv)
}
func TestRunWithBuildArgs(t *testing.T) {
b := newBuilderWithMockBackend()
b.buildArgs.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
b.disableCommit = false
runConfig := &container.Config{}
origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
cmdWithShell := strslice.StrSlice(append(getShell(runConfig), "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.Equal(t, cachedCmd, cfg.Cmd)
assert.Equal(t, strslice.StrSlice(nil), cfg.Entrypoint)
return "", nil
},
}
b.imageCache = imageCache
mockBackend := b.docker.(*MockBackend)
mockBackend.getImageOnBuildImage = &mockImage{
id: "abcdef",
config: &container.Config{Cmd: origCmd},
}
mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
// Check the runConfig.Cmd sent to create()
assert.Equal(t, cmdWithShell, config.Config.Cmd)
assert.Contains(t, config.Config.Env, "one=two")
assert.Equal(t, strslice.StrSlice{""}, config.Config.Entrypoint)
return container.ContainerCreateCreatedBody{ID: "12345"}, nil
}
mockBackend.commitFunc = func(cID string, cfg *backend.ContainerCommitConfig) (string, error) {
// Check the runConfig.Cmd sent to commit()
assert.Equal(t, origCmd, cfg.Config.Cmd)
assert.Equal(t, cachedCmd, cfg.ContainerConfig.Cmd)
assert.Equal(t, strslice.StrSlice(nil), cfg.Config.Entrypoint)
return "", nil
}
req := defaultDispatchReq(b, "abcdef")
require.NoError(t, from(req))
b.buildArgs.AddArg("one", strPtr("two"))
req.args = []string{"echo foo"}
require.NoError(t, run(req))
// Check that runConfig.Cmd has not been modified by run
assert.Equal(t, origCmd, req.state.runConfig.Cmd)
}