github.com/rita33cool1/iot-system-gateway@v0.0.0-20200911033302-e65bde238cc5/docker-engine/builder/dockerfile/dispatchers_test.go (about)

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