github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/builder/dockerfile/dispatchers_test.go (about)

     1  package dockerfile
     2  
     3  import (
     4  	"fmt"
     5  	"runtime"
     6  	"testing"
     7  
     8  	"bytes"
     9  	"context"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/api/types/backend"
    13  	"github.com/docker/docker/api/types/container"
    14  	"github.com/docker/docker/api/types/strslice"
    15  	"github.com/docker/docker/builder"
    16  	"github.com/docker/docker/builder/dockerfile/parser"
    17  	"github.com/docker/docker/pkg/system"
    18  	"github.com/docker/docker/pkg/testutil"
    19  	"github.com/docker/go-connections/nat"
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  type commandWithFunction struct {
    25  	name     string
    26  	function func(args []string) error
    27  }
    28  
    29  func withArgs(f dispatcher) func([]string) error {
    30  	return func(args []string) error {
    31  		return f(dispatchRequest{args: args})
    32  	}
    33  }
    34  
    35  func withBuilderAndArgs(builder *Builder, f dispatcher) func([]string) error {
    36  	return func(args []string) error {
    37  		return f(defaultDispatchReq(builder, args...))
    38  	}
    39  }
    40  
    41  func defaultDispatchReq(builder *Builder, args ...string) dispatchRequest {
    42  	return dispatchRequest{
    43  		builder: builder,
    44  		args:    args,
    45  		flags:   NewBFlags(),
    46  		shlex:   NewShellLex(parser.DefaultEscapeToken),
    47  		state:   &dispatchState{runConfig: &container.Config{}},
    48  	}
    49  }
    50  
    51  func newBuilderWithMockBackend() *Builder {
    52  	mockBackend := &MockBackend{}
    53  	ctx := context.Background()
    54  	b := &Builder{
    55  		options:       &types.ImageBuildOptions{},
    56  		docker:        mockBackend,
    57  		buildArgs:     newBuildArgs(make(map[string]*string)),
    58  		Stdout:        new(bytes.Buffer),
    59  		clientCtx:     ctx,
    60  		disableCommit: true,
    61  		imageSources: newImageSources(ctx, builderOptions{
    62  			Options: &types.ImageBuildOptions{},
    63  			Backend: mockBackend,
    64  		}),
    65  		buildStages:      newBuildStages(),
    66  		imageProber:      newImageProber(mockBackend, nil, false),
    67  		containerManager: newContainerManager(mockBackend),
    68  	}
    69  	return b
    70  }
    71  
    72  func TestCommandsExactlyOneArgument(t *testing.T) {
    73  	commands := []commandWithFunction{
    74  		{"MAINTAINER", withArgs(maintainer)},
    75  		{"WORKDIR", withArgs(workdir)},
    76  		{"USER", withArgs(user)},
    77  		{"STOPSIGNAL", withArgs(stopSignal)},
    78  	}
    79  
    80  	for _, command := range commands {
    81  		err := command.function([]string{})
    82  		assert.EqualError(t, err, errExactlyOneArgument(command.name).Error())
    83  	}
    84  }
    85  
    86  func TestCommandsAtLeastOneArgument(t *testing.T) {
    87  	commands := []commandWithFunction{
    88  		{"ENV", withArgs(env)},
    89  		{"LABEL", withArgs(label)},
    90  		{"ONBUILD", withArgs(onbuild)},
    91  		{"HEALTHCHECK", withArgs(healthcheck)},
    92  		{"EXPOSE", withArgs(expose)},
    93  		{"VOLUME", withArgs(volume)},
    94  	}
    95  
    96  	for _, command := range commands {
    97  		err := command.function([]string{})
    98  		assert.EqualError(t, err, errAtLeastOneArgument(command.name).Error())
    99  	}
   100  }
   101  
   102  func TestCommandsAtLeastTwoArguments(t *testing.T) {
   103  	commands := []commandWithFunction{
   104  		{"ADD", withArgs(add)},
   105  		{"COPY", withArgs(dispatchCopy)}}
   106  
   107  	for _, command := range commands {
   108  		err := command.function([]string{"arg1"})
   109  		assert.EqualError(t, err, errAtLeastTwoArguments(command.name).Error())
   110  	}
   111  }
   112  
   113  func TestCommandsTooManyArguments(t *testing.T) {
   114  	commands := []commandWithFunction{
   115  		{"ENV", withArgs(env)},
   116  		{"LABEL", withArgs(label)}}
   117  
   118  	for _, command := range commands {
   119  		err := command.function([]string{"arg1", "arg2", "arg3"})
   120  		assert.EqualError(t, err, errTooManyArguments(command.name).Error())
   121  	}
   122  }
   123  
   124  func TestCommandsBlankNames(t *testing.T) {
   125  	builder := newBuilderWithMockBackend()
   126  	commands := []commandWithFunction{
   127  		{"ENV", withBuilderAndArgs(builder, env)},
   128  		{"LABEL", withBuilderAndArgs(builder, label)},
   129  	}
   130  
   131  	for _, command := range commands {
   132  		err := command.function([]string{"", ""})
   133  		assert.EqualError(t, err, errBlankCommandNames(command.name).Error())
   134  	}
   135  }
   136  
   137  func TestEnv2Variables(t *testing.T) {
   138  	b := newBuilderWithMockBackend()
   139  
   140  	args := []string{"var1", "val1", "var2", "val2"}
   141  	req := defaultDispatchReq(b, args...)
   142  	err := env(req)
   143  	require.NoError(t, err)
   144  
   145  	expected := []string{
   146  		fmt.Sprintf("%s=%s", args[0], args[1]),
   147  		fmt.Sprintf("%s=%s", args[2], args[3]),
   148  	}
   149  	assert.Equal(t, expected, req.state.runConfig.Env)
   150  }
   151  
   152  func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
   153  	b := newBuilderWithMockBackend()
   154  
   155  	args := []string{"var1", "val1"}
   156  	req := defaultDispatchReq(b, args...)
   157  	req.state.runConfig.Env = []string{"var1=old", "var2=fromenv"}
   158  	err := env(req)
   159  	require.NoError(t, err)
   160  
   161  	expected := []string{
   162  		fmt.Sprintf("%s=%s", args[0], args[1]),
   163  		"var2=fromenv",
   164  	}
   165  	assert.Equal(t, expected, req.state.runConfig.Env)
   166  }
   167  
   168  func TestMaintainer(t *testing.T) {
   169  	maintainerEntry := "Some Maintainer <maintainer@example.com>"
   170  
   171  	b := newBuilderWithMockBackend()
   172  	req := defaultDispatchReq(b, maintainerEntry)
   173  	err := maintainer(req)
   174  	require.NoError(t, err)
   175  	assert.Equal(t, maintainerEntry, req.state.maintainer)
   176  }
   177  
   178  func TestLabel(t *testing.T) {
   179  	labelName := "label"
   180  	labelValue := "value"
   181  
   182  	labelEntry := []string{labelName, labelValue}
   183  	b := newBuilderWithMockBackend()
   184  	req := defaultDispatchReq(b, labelEntry...)
   185  	err := label(req)
   186  	require.NoError(t, err)
   187  
   188  	require.Contains(t, req.state.runConfig.Labels, labelName)
   189  	assert.Equal(t, req.state.runConfig.Labels[labelName], labelValue)
   190  }
   191  
   192  func TestFromScratch(t *testing.T) {
   193  	b := newBuilderWithMockBackend()
   194  	req := defaultDispatchReq(b, "scratch")
   195  	err := from(req)
   196  
   197  	if runtime.GOOS == "windows" {
   198  		assert.EqualError(t, err, "Windows does not support FROM scratch")
   199  		return
   200  	}
   201  
   202  	require.NoError(t, err)
   203  	assert.True(t, req.state.hasFromImage())
   204  	assert.Equal(t, "", req.state.imageID)
   205  	assert.Equal(t, []string{"PATH=" + system.DefaultPathEnv}, req.state.runConfig.Env)
   206  }
   207  
   208  func TestFromWithArg(t *testing.T) {
   209  	tag, expected := ":sometag", "expectedthisid"
   210  
   211  	getImage := func(name string) (builder.Image, builder.ReleaseableLayer, error) {
   212  		assert.Equal(t, "alpine"+tag, name)
   213  		return &mockImage{id: "expectedthisid"}, nil, nil
   214  	}
   215  	b := newBuilderWithMockBackend()
   216  	b.docker.(*MockBackend).getImageFunc = getImage
   217  
   218  	require.NoError(t, arg(defaultDispatchReq(b, "THETAG="+tag)))
   219  	req := defaultDispatchReq(b, "alpine${THETAG}")
   220  	err := from(req)
   221  
   222  	require.NoError(t, err)
   223  	assert.Equal(t, expected, req.state.imageID)
   224  	assert.Equal(t, expected, req.state.baseImage.ImageID())
   225  	assert.Len(t, b.buildArgs.GetAllAllowed(), 0)
   226  	assert.Len(t, b.buildArgs.GetAllMeta(), 1)
   227  }
   228  
   229  func TestFromWithUndefinedArg(t *testing.T) {
   230  	tag, expected := "sometag", "expectedthisid"
   231  
   232  	getImage := func(name string) (builder.Image, builder.ReleaseableLayer, error) {
   233  		assert.Equal(t, "alpine", name)
   234  		return &mockImage{id: "expectedthisid"}, nil, nil
   235  	}
   236  	b := newBuilderWithMockBackend()
   237  	b.docker.(*MockBackend).getImageFunc = getImage
   238  	b.options.BuildArgs = map[string]*string{"THETAG": &tag}
   239  
   240  	req := defaultDispatchReq(b, "alpine${THETAG}")
   241  	err := from(req)
   242  	require.NoError(t, err)
   243  	assert.Equal(t, expected, req.state.imageID)
   244  }
   245  
   246  func TestFromMultiStageWithScratchNamedStage(t *testing.T) {
   247  	if runtime.GOOS == "windows" {
   248  		t.Skip("Windows does not support scratch")
   249  	}
   250  	b := newBuilderWithMockBackend()
   251  	req := defaultDispatchReq(b, "scratch", "AS", "base")
   252  
   253  	require.NoError(t, from(req))
   254  	assert.True(t, req.state.hasFromImage())
   255  
   256  	req.args = []string{"base"}
   257  	require.NoError(t, from(req))
   258  	assert.True(t, req.state.hasFromImage())
   259  }
   260  
   261  func TestOnbuildIllegalTriggers(t *testing.T) {
   262  	triggers := []struct{ command, expectedError string }{
   263  		{"ONBUILD", "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed"},
   264  		{"MAINTAINER", "MAINTAINER isn't allowed as an ONBUILD trigger"},
   265  		{"FROM", "FROM isn't allowed as an ONBUILD trigger"}}
   266  
   267  	for _, trigger := range triggers {
   268  		b := newBuilderWithMockBackend()
   269  
   270  		err := onbuild(defaultDispatchReq(b, trigger.command))
   271  		testutil.ErrorContains(t, err, trigger.expectedError)
   272  	}
   273  }
   274  
   275  func TestOnbuild(t *testing.T) {
   276  	b := newBuilderWithMockBackend()
   277  
   278  	req := defaultDispatchReq(b, "ADD", ".", "/app/src")
   279  	req.original = "ONBUILD ADD . /app/src"
   280  	req.state.runConfig = &container.Config{}
   281  
   282  	err := onbuild(req)
   283  	require.NoError(t, err)
   284  	assert.Equal(t, "ADD . /app/src", req.state.runConfig.OnBuild[0])
   285  }
   286  
   287  func TestWorkdir(t *testing.T) {
   288  	b := newBuilderWithMockBackend()
   289  	workingDir := "/app"
   290  	if runtime.GOOS == "windows" {
   291  		workingDir = "C:\app"
   292  	}
   293  
   294  	req := defaultDispatchReq(b, workingDir)
   295  	err := workdir(req)
   296  	require.NoError(t, err)
   297  	assert.Equal(t, workingDir, req.state.runConfig.WorkingDir)
   298  }
   299  
   300  func TestCmd(t *testing.T) {
   301  	b := newBuilderWithMockBackend()
   302  	command := "./executable"
   303  
   304  	req := defaultDispatchReq(b, command)
   305  	err := cmd(req)
   306  	require.NoError(t, err)
   307  
   308  	var expectedCommand strslice.StrSlice
   309  	if runtime.GOOS == "windows" {
   310  		expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command))
   311  	} else {
   312  		expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
   313  	}
   314  
   315  	assert.Equal(t, expectedCommand, req.state.runConfig.Cmd)
   316  	assert.True(t, req.state.cmdSet)
   317  }
   318  
   319  func TestHealthcheckNone(t *testing.T) {
   320  	b := newBuilderWithMockBackend()
   321  
   322  	req := defaultDispatchReq(b, "NONE")
   323  	err := healthcheck(req)
   324  	require.NoError(t, err)
   325  
   326  	require.NotNil(t, req.state.runConfig.Healthcheck)
   327  	assert.Equal(t, []string{"NONE"}, req.state.runConfig.Healthcheck.Test)
   328  }
   329  
   330  func TestHealthcheckCmd(t *testing.T) {
   331  	b := newBuilderWithMockBackend()
   332  
   333  	args := []string{"CMD", "curl", "-f", "http://localhost/", "||", "exit", "1"}
   334  	req := defaultDispatchReq(b, args...)
   335  	err := healthcheck(req)
   336  	require.NoError(t, err)
   337  
   338  	require.NotNil(t, req.state.runConfig.Healthcheck)
   339  	expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
   340  	assert.Equal(t, expectedTest, req.state.runConfig.Healthcheck.Test)
   341  }
   342  
   343  func TestEntrypoint(t *testing.T) {
   344  	b := newBuilderWithMockBackend()
   345  	entrypointCmd := "/usr/sbin/nginx"
   346  
   347  	req := defaultDispatchReq(b, entrypointCmd)
   348  	err := entrypoint(req)
   349  	require.NoError(t, err)
   350  	require.NotNil(t, req.state.runConfig.Entrypoint)
   351  
   352  	var expectedEntrypoint strslice.StrSlice
   353  	if runtime.GOOS == "windows" {
   354  		expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd))
   355  	} else {
   356  		expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
   357  	}
   358  	assert.Equal(t, expectedEntrypoint, req.state.runConfig.Entrypoint)
   359  }
   360  
   361  func TestExpose(t *testing.T) {
   362  	b := newBuilderWithMockBackend()
   363  
   364  	exposedPort := "80"
   365  	req := defaultDispatchReq(b, exposedPort)
   366  	err := expose(req)
   367  	require.NoError(t, err)
   368  
   369  	require.NotNil(t, req.state.runConfig.ExposedPorts)
   370  	require.Len(t, req.state.runConfig.ExposedPorts, 1)
   371  
   372  	portsMapping, err := nat.ParsePortSpec(exposedPort)
   373  	require.NoError(t, err)
   374  	assert.Contains(t, req.state.runConfig.ExposedPorts, portsMapping[0].Port)
   375  }
   376  
   377  func TestUser(t *testing.T) {
   378  	b := newBuilderWithMockBackend()
   379  	userCommand := "foo"
   380  
   381  	req := defaultDispatchReq(b, userCommand)
   382  	err := user(req)
   383  	require.NoError(t, err)
   384  	assert.Equal(t, userCommand, req.state.runConfig.User)
   385  }
   386  
   387  func TestVolume(t *testing.T) {
   388  	b := newBuilderWithMockBackend()
   389  
   390  	exposedVolume := "/foo"
   391  
   392  	req := defaultDispatchReq(b, exposedVolume)
   393  	err := volume(req)
   394  	require.NoError(t, err)
   395  
   396  	require.NotNil(t, req.state.runConfig.Volumes)
   397  	assert.Len(t, req.state.runConfig.Volumes, 1)
   398  	assert.Contains(t, req.state.runConfig.Volumes, exposedVolume)
   399  }
   400  
   401  func TestStopSignal(t *testing.T) {
   402  	b := newBuilderWithMockBackend()
   403  	signal := "SIGKILL"
   404  
   405  	req := defaultDispatchReq(b, signal)
   406  	err := stopSignal(req)
   407  	require.NoError(t, err)
   408  	assert.Equal(t, signal, req.state.runConfig.StopSignal)
   409  }
   410  
   411  func TestArg(t *testing.T) {
   412  	b := newBuilderWithMockBackend()
   413  
   414  	argName := "foo"
   415  	argVal := "bar"
   416  	argDef := fmt.Sprintf("%s=%s", argName, argVal)
   417  
   418  	err := arg(defaultDispatchReq(b, argDef))
   419  	require.NoError(t, err)
   420  
   421  	expected := map[string]string{argName: argVal}
   422  	assert.Equal(t, expected, b.buildArgs.GetAllAllowed())
   423  }
   424  
   425  func TestShell(t *testing.T) {
   426  	b := newBuilderWithMockBackend()
   427  
   428  	shellCmd := "powershell"
   429  	req := defaultDispatchReq(b, shellCmd)
   430  	req.attributes = map[string]bool{"json": true}
   431  
   432  	err := shell(req)
   433  	require.NoError(t, err)
   434  
   435  	expectedShell := strslice.StrSlice([]string{shellCmd})
   436  	assert.Equal(t, expectedShell, req.state.runConfig.Shell)
   437  }
   438  
   439  func TestParseOptInterval(t *testing.T) {
   440  	flInterval := &Flag{
   441  		name:     "interval",
   442  		flagType: stringType,
   443  		Value:    "50ns",
   444  	}
   445  	_, err := parseOptInterval(flInterval)
   446  	testutil.ErrorContains(t, err, "cannot be less than 1ms")
   447  
   448  	flInterval.Value = "1ms"
   449  	_, err = parseOptInterval(flInterval)
   450  	require.NoError(t, err)
   451  }
   452  
   453  func TestPrependEnvOnCmd(t *testing.T) {
   454  	buildArgs := newBuildArgs(nil)
   455  	buildArgs.AddArg("NO_PROXY", nil)
   456  
   457  	args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"}
   458  	cmd := []string{"foo", "bar"}
   459  	cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd)
   460  	expected := strslice.StrSlice([]string{
   461  		"|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"})
   462  	assert.Equal(t, expected, cmdWithEnv)
   463  }
   464  
   465  func TestRunWithBuildArgs(t *testing.T) {
   466  	b := newBuilderWithMockBackend()
   467  	b.buildArgs.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
   468  	b.disableCommit = false
   469  
   470  	runConfig := &container.Config{}
   471  	origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
   472  	cmdWithShell := strslice.StrSlice(append(getShell(runConfig), "echo foo"))
   473  	envVars := []string{"|1", "one=two"}
   474  	cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
   475  
   476  	imageCache := &mockImageCache{
   477  		getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
   478  			// Check the runConfig.Cmd sent to probeCache()
   479  			assert.Equal(t, cachedCmd, cfg.Cmd)
   480  			assert.Equal(t, strslice.StrSlice(nil), cfg.Entrypoint)
   481  			return "", nil
   482  		},
   483  	}
   484  
   485  	mockBackend := b.docker.(*MockBackend)
   486  	mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
   487  		return imageCache
   488  	}
   489  	b.imageProber = newImageProber(mockBackend, nil, false)
   490  	mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ReleaseableLayer, error) {
   491  		return &mockImage{
   492  			id:     "abcdef",
   493  			config: &container.Config{Cmd: origCmd},
   494  		}, nil, nil
   495  	}
   496  	mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
   497  		// Check the runConfig.Cmd sent to create()
   498  		assert.Equal(t, cmdWithShell, config.Config.Cmd)
   499  		assert.Contains(t, config.Config.Env, "one=two")
   500  		assert.Equal(t, strslice.StrSlice{""}, config.Config.Entrypoint)
   501  		return container.ContainerCreateCreatedBody{ID: "12345"}, nil
   502  	}
   503  	mockBackend.commitFunc = func(cID string, cfg *backend.ContainerCommitConfig) (string, error) {
   504  		// Check the runConfig.Cmd sent to commit()
   505  		assert.Equal(t, origCmd, cfg.Config.Cmd)
   506  		assert.Equal(t, cachedCmd, cfg.ContainerConfig.Cmd)
   507  		assert.Equal(t, strslice.StrSlice(nil), cfg.Config.Entrypoint)
   508  		return "", nil
   509  	}
   510  
   511  	req := defaultDispatchReq(b, "abcdef")
   512  	require.NoError(t, from(req))
   513  	b.buildArgs.AddArg("one", strPtr("two"))
   514  
   515  	req.args = []string{"echo foo"}
   516  	require.NoError(t, run(req))
   517  
   518  	// Check that runConfig.Cmd has not been modified by run
   519  	assert.Equal(t, origCmd, req.state.runConfig.Cmd)
   520  }