github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/builder/dockerfile/dispatchers_test.go (about)

     1  package dockerfile
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"runtime"
     7  	"testing"
     8  
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/api/types/backend"
    11  	"github.com/docker/docker/api/types/container"
    12  	"github.com/docker/docker/api/types/strslice"
    13  	"github.com/docker/docker/builder"
    14  	"github.com/docker/docker/builder/dockerfile/instructions"
    15  	"github.com/docker/docker/pkg/system"
    16  	"github.com/docker/go-connections/nat"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  func newBuilderWithMockBackend() *Builder {
    22  	mockBackend := &MockBackend{}
    23  	ctx := context.Background()
    24  	b := &Builder{
    25  		options:       &types.ImageBuildOptions{Platform: runtime.GOOS},
    26  		docker:        mockBackend,
    27  		Stdout:        new(bytes.Buffer),
    28  		clientCtx:     ctx,
    29  		disableCommit: true,
    30  		imageSources: newImageSources(ctx, builderOptions{
    31  			Options: &types.ImageBuildOptions{Platform: runtime.GOOS},
    32  			Backend: mockBackend,
    33  		}),
    34  		imageProber:      newImageProber(mockBackend, nil, runtime.GOOS, false),
    35  		containerManager: newContainerManager(mockBackend),
    36  	}
    37  	return b
    38  }
    39  
    40  func TestEnv2Variables(t *testing.T) {
    41  	b := newBuilderWithMockBackend()
    42  	sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
    43  	envCommand := &instructions.EnvCommand{
    44  		Env: instructions.KeyValuePairs{
    45  			instructions.KeyValuePair{Key: "var1", Value: "val1"},
    46  			instructions.KeyValuePair{Key: "var2", Value: "val2"},
    47  		},
    48  	}
    49  	err := dispatch(sb, envCommand)
    50  	require.NoError(t, err)
    51  
    52  	expected := []string{
    53  		"var1=val1",
    54  		"var2=val2",
    55  	}
    56  	assert.Equal(t, expected, sb.state.runConfig.Env)
    57  }
    58  
    59  func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
    60  	b := newBuilderWithMockBackend()
    61  	sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
    62  	sb.state.runConfig.Env = []string{"var1=old", "var2=fromenv"}
    63  	envCommand := &instructions.EnvCommand{
    64  		Env: instructions.KeyValuePairs{
    65  			instructions.KeyValuePair{Key: "var1", Value: "val1"},
    66  		},
    67  	}
    68  	err := dispatch(sb, envCommand)
    69  	require.NoError(t, err)
    70  	expected := []string{
    71  		"var1=val1",
    72  		"var2=fromenv",
    73  	}
    74  	assert.Equal(t, expected, sb.state.runConfig.Env)
    75  }
    76  
    77  func TestMaintainer(t *testing.T) {
    78  	maintainerEntry := "Some Maintainer <maintainer@example.com>"
    79  	b := newBuilderWithMockBackend()
    80  	sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
    81  	cmd := &instructions.MaintainerCommand{Maintainer: maintainerEntry}
    82  	err := dispatch(sb, cmd)
    83  	require.NoError(t, err)
    84  	assert.Equal(t, maintainerEntry, sb.state.maintainer)
    85  }
    86  
    87  func TestLabel(t *testing.T) {
    88  	labelName := "label"
    89  	labelValue := "value"
    90  
    91  	b := newBuilderWithMockBackend()
    92  	sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
    93  	cmd := &instructions.LabelCommand{
    94  		Labels: instructions.KeyValuePairs{
    95  			instructions.KeyValuePair{Key: labelName, Value: labelValue},
    96  		},
    97  	}
    98  	err := dispatch(sb, cmd)
    99  	require.NoError(t, err)
   100  
   101  	require.Contains(t, sb.state.runConfig.Labels, labelName)
   102  	assert.Equal(t, sb.state.runConfig.Labels[labelName], labelValue)
   103  }
   104  
   105  func TestFromScratch(t *testing.T) {
   106  	b := newBuilderWithMockBackend()
   107  	sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   108  	cmd := &instructions.Stage{
   109  		BaseName: "scratch",
   110  	}
   111  	err := initializeStage(sb, cmd)
   112  
   113  	if runtime.GOOS == "windows" && !system.LCOWSupported() {
   114  		assert.EqualError(t, err, "Windows does not support FROM scratch")
   115  		return
   116  	}
   117  
   118  	require.NoError(t, err)
   119  	assert.True(t, sb.state.hasFromImage())
   120  	assert.Equal(t, "", sb.state.imageID)
   121  	expected := "PATH=" + system.DefaultPathEnv(runtime.GOOS)
   122  	assert.Equal(t, []string{expected}, sb.state.runConfig.Env)
   123  }
   124  
   125  func TestFromWithArg(t *testing.T) {
   126  	tag, expected := ":sometag", "expectedthisid"
   127  
   128  	getImage := func(name string) (builder.Image, builder.ReleaseableLayer, error) {
   129  		assert.Equal(t, "alpine"+tag, name)
   130  		return &mockImage{id: "expectedthisid"}, nil, nil
   131  	}
   132  	b := newBuilderWithMockBackend()
   133  	b.docker.(*MockBackend).getImageFunc = getImage
   134  	args := newBuildArgs(make(map[string]*string))
   135  
   136  	val := "sometag"
   137  	metaArg := instructions.ArgCommand{
   138  		Key:   "THETAG",
   139  		Value: &val,
   140  	}
   141  	cmd := &instructions.Stage{
   142  		BaseName: "alpine:${THETAG}",
   143  	}
   144  	err := processMetaArg(metaArg, NewShellLex('\\'), args)
   145  
   146  	sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults())
   147  	require.NoError(t, err)
   148  	err = initializeStage(sb, cmd)
   149  	require.NoError(t, err)
   150  
   151  	assert.Equal(t, expected, sb.state.imageID)
   152  	assert.Equal(t, expected, sb.state.baseImage.ImageID())
   153  	assert.Len(t, sb.state.buildArgs.GetAllAllowed(), 0)
   154  	assert.Len(t, sb.state.buildArgs.GetAllMeta(), 1)
   155  }
   156  
   157  func TestFromWithUndefinedArg(t *testing.T) {
   158  	tag, expected := "sometag", "expectedthisid"
   159  
   160  	getImage := func(name string) (builder.Image, builder.ReleaseableLayer, error) {
   161  		assert.Equal(t, "alpine", name)
   162  		return &mockImage{id: "expectedthisid"}, nil, nil
   163  	}
   164  	b := newBuilderWithMockBackend()
   165  	b.docker.(*MockBackend).getImageFunc = getImage
   166  	sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   167  
   168  	b.options.BuildArgs = map[string]*string{"THETAG": &tag}
   169  
   170  	cmd := &instructions.Stage{
   171  		BaseName: "alpine${THETAG}",
   172  	}
   173  	err := initializeStage(sb, cmd)
   174  	require.NoError(t, err)
   175  	assert.Equal(t, expected, sb.state.imageID)
   176  }
   177  
   178  func TestFromMultiStageWithNamedStage(t *testing.T) {
   179  	b := newBuilderWithMockBackend()
   180  	firstFrom := &instructions.Stage{BaseName: "someimg", Name: "base"}
   181  	secondFrom := &instructions.Stage{BaseName: "base"}
   182  	previousResults := newStagesBuildResults()
   183  	firstSB := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), previousResults)
   184  	secondSB := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), previousResults)
   185  	err := initializeStage(firstSB, firstFrom)
   186  	require.NoError(t, err)
   187  	assert.True(t, firstSB.state.hasFromImage())
   188  	previousResults.indexed["base"] = firstSB.state.runConfig
   189  	previousResults.flat = append(previousResults.flat, firstSB.state.runConfig)
   190  	err = initializeStage(secondSB, secondFrom)
   191  	require.NoError(t, err)
   192  	assert.True(t, secondSB.state.hasFromImage())
   193  }
   194  
   195  func TestOnbuild(t *testing.T) {
   196  	b := newBuilderWithMockBackend()
   197  	sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   198  	cmd := &instructions.OnbuildCommand{
   199  		Expression: "ADD . /app/src",
   200  	}
   201  	err := dispatch(sb, cmd)
   202  	require.NoError(t, err)
   203  	assert.Equal(t, "ADD . /app/src", sb.state.runConfig.OnBuild[0])
   204  }
   205  
   206  func TestWorkdir(t *testing.T) {
   207  	b := newBuilderWithMockBackend()
   208  	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   209  	workingDir := "/app"
   210  	if runtime.GOOS == "windows" {
   211  		workingDir = "C:\\app"
   212  	}
   213  	cmd := &instructions.WorkdirCommand{
   214  		Path: workingDir,
   215  	}
   216  
   217  	err := dispatch(sb, cmd)
   218  	require.NoError(t, err)
   219  	assert.Equal(t, workingDir, sb.state.runConfig.WorkingDir)
   220  }
   221  
   222  func TestCmd(t *testing.T) {
   223  	b := newBuilderWithMockBackend()
   224  	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   225  	command := "./executable"
   226  
   227  	cmd := &instructions.CmdCommand{
   228  		ShellDependantCmdLine: instructions.ShellDependantCmdLine{
   229  			CmdLine:      strslice.StrSlice{command},
   230  			PrependShell: true,
   231  		},
   232  	}
   233  	err := dispatch(sb, cmd)
   234  	require.NoError(t, err)
   235  
   236  	var expectedCommand strslice.StrSlice
   237  	if runtime.GOOS == "windows" {
   238  		expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command))
   239  	} else {
   240  		expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
   241  	}
   242  
   243  	assert.Equal(t, expectedCommand, sb.state.runConfig.Cmd)
   244  	assert.True(t, sb.state.cmdSet)
   245  }
   246  
   247  func TestHealthcheckNone(t *testing.T) {
   248  	b := newBuilderWithMockBackend()
   249  	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   250  	cmd := &instructions.HealthCheckCommand{
   251  		Health: &container.HealthConfig{
   252  			Test: []string{"NONE"},
   253  		},
   254  	}
   255  	err := dispatch(sb, cmd)
   256  	require.NoError(t, err)
   257  
   258  	require.NotNil(t, sb.state.runConfig.Healthcheck)
   259  	assert.Equal(t, []string{"NONE"}, sb.state.runConfig.Healthcheck.Test)
   260  }
   261  
   262  func TestHealthcheckCmd(t *testing.T) {
   263  
   264  	b := newBuilderWithMockBackend()
   265  	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   266  	expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
   267  	cmd := &instructions.HealthCheckCommand{
   268  		Health: &container.HealthConfig{
   269  			Test: expectedTest,
   270  		},
   271  	}
   272  	err := dispatch(sb, cmd)
   273  	require.NoError(t, err)
   274  
   275  	require.NotNil(t, sb.state.runConfig.Healthcheck)
   276  	assert.Equal(t, expectedTest, sb.state.runConfig.Healthcheck.Test)
   277  }
   278  
   279  func TestEntrypoint(t *testing.T) {
   280  	b := newBuilderWithMockBackend()
   281  	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   282  	entrypointCmd := "/usr/sbin/nginx"
   283  
   284  	cmd := &instructions.EntrypointCommand{
   285  		ShellDependantCmdLine: instructions.ShellDependantCmdLine{
   286  			CmdLine:      strslice.StrSlice{entrypointCmd},
   287  			PrependShell: true,
   288  		},
   289  	}
   290  	err := dispatch(sb, cmd)
   291  	require.NoError(t, err)
   292  	require.NotNil(t, sb.state.runConfig.Entrypoint)
   293  
   294  	var expectedEntrypoint strslice.StrSlice
   295  	if runtime.GOOS == "windows" {
   296  		expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd))
   297  	} else {
   298  		expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
   299  	}
   300  	assert.Equal(t, expectedEntrypoint, sb.state.runConfig.Entrypoint)
   301  }
   302  
   303  func TestExpose(t *testing.T) {
   304  	b := newBuilderWithMockBackend()
   305  	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   306  
   307  	exposedPort := "80"
   308  	cmd := &instructions.ExposeCommand{
   309  		Ports: []string{exposedPort},
   310  	}
   311  	err := dispatch(sb, cmd)
   312  	require.NoError(t, err)
   313  
   314  	require.NotNil(t, sb.state.runConfig.ExposedPorts)
   315  	require.Len(t, sb.state.runConfig.ExposedPorts, 1)
   316  
   317  	portsMapping, err := nat.ParsePortSpec(exposedPort)
   318  	require.NoError(t, err)
   319  	assert.Contains(t, sb.state.runConfig.ExposedPorts, portsMapping[0].Port)
   320  }
   321  
   322  func TestUser(t *testing.T) {
   323  	b := newBuilderWithMockBackend()
   324  	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   325  
   326  	cmd := &instructions.UserCommand{
   327  		User: "test",
   328  	}
   329  	err := dispatch(sb, cmd)
   330  	require.NoError(t, err)
   331  	assert.Equal(t, "test", sb.state.runConfig.User)
   332  }
   333  
   334  func TestVolume(t *testing.T) {
   335  	b := newBuilderWithMockBackend()
   336  	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   337  
   338  	exposedVolume := "/foo"
   339  
   340  	cmd := &instructions.VolumeCommand{
   341  		Volumes: []string{exposedVolume},
   342  	}
   343  	err := dispatch(sb, cmd)
   344  	require.NoError(t, err)
   345  	require.NotNil(t, sb.state.runConfig.Volumes)
   346  	assert.Len(t, sb.state.runConfig.Volumes, 1)
   347  	assert.Contains(t, sb.state.runConfig.Volumes, exposedVolume)
   348  }
   349  
   350  func TestStopSignal(t *testing.T) {
   351  	if runtime.GOOS == "windows" {
   352  		t.Skip("Windows does not support stopsignal")
   353  		return
   354  	}
   355  	b := newBuilderWithMockBackend()
   356  	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   357  	signal := "SIGKILL"
   358  
   359  	cmd := &instructions.StopSignalCommand{
   360  		Signal: signal,
   361  	}
   362  	err := dispatch(sb, cmd)
   363  	require.NoError(t, err)
   364  	assert.Equal(t, signal, sb.state.runConfig.StopSignal)
   365  }
   366  
   367  func TestArg(t *testing.T) {
   368  	b := newBuilderWithMockBackend()
   369  	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   370  
   371  	argName := "foo"
   372  	argVal := "bar"
   373  	cmd := &instructions.ArgCommand{Key: argName, Value: &argVal}
   374  	err := dispatch(sb, cmd)
   375  	require.NoError(t, err)
   376  
   377  	expected := map[string]string{argName: argVal}
   378  	assert.Equal(t, expected, sb.state.buildArgs.GetAllAllowed())
   379  }
   380  
   381  func TestShell(t *testing.T) {
   382  	b := newBuilderWithMockBackend()
   383  	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
   384  
   385  	shellCmd := "powershell"
   386  	cmd := &instructions.ShellCommand{Shell: strslice.StrSlice{shellCmd}}
   387  
   388  	err := dispatch(sb, cmd)
   389  	require.NoError(t, err)
   390  
   391  	expectedShell := strslice.StrSlice([]string{shellCmd})
   392  	assert.Equal(t, expectedShell, sb.state.runConfig.Shell)
   393  }
   394  
   395  func TestPrependEnvOnCmd(t *testing.T) {
   396  	buildArgs := newBuildArgs(nil)
   397  	buildArgs.AddArg("NO_PROXY", nil)
   398  
   399  	args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"}
   400  	cmd := []string{"foo", "bar"}
   401  	cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd)
   402  	expected := strslice.StrSlice([]string{
   403  		"|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"})
   404  	assert.Equal(t, expected, cmdWithEnv)
   405  }
   406  
   407  func TestRunWithBuildArgs(t *testing.T) {
   408  	b := newBuilderWithMockBackend()
   409  	args := newBuildArgs(make(map[string]*string))
   410  	args.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
   411  	b.disableCommit = false
   412  	sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults())
   413  
   414  	runConfig := &container.Config{}
   415  	origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
   416  	cmdWithShell := strslice.StrSlice(append(getShell(runConfig, runtime.GOOS), "echo foo"))
   417  	envVars := []string{"|1", "one=two"}
   418  	cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
   419  
   420  	imageCache := &mockImageCache{
   421  		getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
   422  			// Check the runConfig.Cmd sent to probeCache()
   423  			assert.Equal(t, cachedCmd, cfg.Cmd)
   424  			assert.Equal(t, strslice.StrSlice(nil), cfg.Entrypoint)
   425  			return "", nil
   426  		},
   427  	}
   428  
   429  	mockBackend := b.docker.(*MockBackend)
   430  	mockBackend.makeImageCacheFunc = func(_ []string, _ string) builder.ImageCache {
   431  		return imageCache
   432  	}
   433  	b.imageProber = newImageProber(mockBackend, nil, runtime.GOOS, false)
   434  	mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ReleaseableLayer, error) {
   435  		return &mockImage{
   436  			id:     "abcdef",
   437  			config: &container.Config{Cmd: origCmd},
   438  		}, nil, nil
   439  	}
   440  	mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
   441  		// Check the runConfig.Cmd sent to create()
   442  		assert.Equal(t, cmdWithShell, config.Config.Cmd)
   443  		assert.Contains(t, config.Config.Env, "one=two")
   444  		assert.Equal(t, strslice.StrSlice{""}, config.Config.Entrypoint)
   445  		return container.ContainerCreateCreatedBody{ID: "12345"}, nil
   446  	}
   447  	mockBackend.commitFunc = func(cID string, cfg *backend.ContainerCommitConfig) (string, error) {
   448  		// Check the runConfig.Cmd sent to commit()
   449  		assert.Equal(t, origCmd, cfg.Config.Cmd)
   450  		assert.Equal(t, cachedCmd, cfg.ContainerConfig.Cmd)
   451  		assert.Equal(t, strslice.StrSlice(nil), cfg.Config.Entrypoint)
   452  		return "", nil
   453  	}
   454  	from := &instructions.Stage{BaseName: "abcdef"}
   455  	err := initializeStage(sb, from)
   456  	require.NoError(t, err)
   457  	sb.state.buildArgs.AddArg("one", strPtr("two"))
   458  	run := &instructions.RunCommand{
   459  		ShellDependantCmdLine: instructions.ShellDependantCmdLine{
   460  			CmdLine:      strslice.StrSlice{"echo foo"},
   461  			PrependShell: true,
   462  		},
   463  	}
   464  	require.NoError(t, dispatch(sb, run))
   465  
   466  	// Check that runConfig.Cmd has not been modified by run
   467  	assert.Equal(t, origCmd, sb.state.runConfig.Cmd)
   468  }