github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/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  	"fmt"
     7  	"runtime"
     8  	"strings"
     9  	"testing"
    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/image"
    17  	"github.com/docker/docker/pkg/system"
    18  	"github.com/docker/go-connections/nat"
    19  	"github.com/moby/buildkit/frontend/dockerfile/instructions"
    20  	"github.com/moby/buildkit/frontend/dockerfile/parser"
    21  	"github.com/moby/buildkit/frontend/dockerfile/shell"
    22  	"gotest.tools/v3/assert"
    23  	is "gotest.tools/v3/assert/cmp"
    24  )
    25  
    26  func newBuilderWithMockBackend() *Builder {
    27  	mockBackend := &MockBackend{}
    28  	opts := &types.ImageBuildOptions{}
    29  	ctx := context.Background()
    30  	b := &Builder{
    31  		options:       opts,
    32  		docker:        mockBackend,
    33  		Stdout:        new(bytes.Buffer),
    34  		clientCtx:     ctx,
    35  		disableCommit: true,
    36  		imageSources: newImageSources(ctx, builderOptions{
    37  			Options: opts,
    38  			Backend: mockBackend,
    39  		}),
    40  		imageProber:      newImageProber(mockBackend, nil, false),
    41  		containerManager: newContainerManager(mockBackend),
    42  	}
    43  	return b
    44  }
    45  
    46  func TestEnv2Variables(t *testing.T) {
    47  	b := newBuilderWithMockBackend()
    48  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
    49  	envCommand := &instructions.EnvCommand{
    50  		Env: instructions.KeyValuePairs{
    51  			instructions.KeyValuePair{Key: "var1", Value: "val1"},
    52  			instructions.KeyValuePair{Key: "var2", Value: "val2"},
    53  		},
    54  	}
    55  	err := dispatch(sb, envCommand)
    56  	assert.NilError(t, err)
    57  
    58  	expected := []string{
    59  		"var1=val1",
    60  		"var2=val2",
    61  	}
    62  	assert.Check(t, is.DeepEqual(expected, sb.state.runConfig.Env))
    63  }
    64  
    65  func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
    66  	b := newBuilderWithMockBackend()
    67  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
    68  	sb.state.runConfig.Env = []string{"var1=old", "var2=fromenv"}
    69  	envCommand := &instructions.EnvCommand{
    70  		Env: instructions.KeyValuePairs{
    71  			instructions.KeyValuePair{Key: "var1", Value: "val1"},
    72  		},
    73  	}
    74  	err := dispatch(sb, envCommand)
    75  	assert.NilError(t, err)
    76  	expected := []string{
    77  		"var1=val1",
    78  		"var2=fromenv",
    79  	}
    80  	assert.Check(t, is.DeepEqual(expected, sb.state.runConfig.Env))
    81  }
    82  
    83  func TestMaintainer(t *testing.T) {
    84  	maintainerEntry := "Some Maintainer <maintainer@example.com>"
    85  	b := newBuilderWithMockBackend()
    86  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
    87  	cmd := &instructions.MaintainerCommand{Maintainer: maintainerEntry}
    88  	err := dispatch(sb, cmd)
    89  	assert.NilError(t, err)
    90  	assert.Check(t, is.Equal(maintainerEntry, sb.state.maintainer))
    91  }
    92  
    93  func TestLabel(t *testing.T) {
    94  	labelName := "label"
    95  	labelValue := "value"
    96  
    97  	b := newBuilderWithMockBackend()
    98  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
    99  	cmd := &instructions.LabelCommand{
   100  		Labels: instructions.KeyValuePairs{
   101  			instructions.KeyValuePair{Key: labelName, Value: labelValue},
   102  		},
   103  	}
   104  	err := dispatch(sb, cmd)
   105  	assert.NilError(t, err)
   106  
   107  	assert.Assert(t, is.Contains(sb.state.runConfig.Labels, labelName))
   108  	assert.Check(t, is.Equal(sb.state.runConfig.Labels[labelName], labelValue))
   109  }
   110  
   111  func TestFromScratch(t *testing.T) {
   112  	b := newBuilderWithMockBackend()
   113  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   114  	cmd := &instructions.Stage{
   115  		BaseName: "scratch",
   116  	}
   117  	err := initializeStage(sb, cmd)
   118  
   119  	if runtime.GOOS == "windows" && !system.LCOWSupported() {
   120  		assert.Check(t, is.Error(err, "Linux containers are not supported on this system"))
   121  		return
   122  	}
   123  
   124  	assert.NilError(t, err)
   125  	assert.Check(t, sb.state.hasFromImage())
   126  	assert.Check(t, is.Equal("", sb.state.imageID))
   127  	expected := "PATH=" + system.DefaultPathEnv(runtime.GOOS)
   128  	assert.Check(t, is.DeepEqual([]string{expected}, sb.state.runConfig.Env))
   129  }
   130  
   131  func TestFromWithArg(t *testing.T) {
   132  	tag, expected := ":sometag", "expectedthisid"
   133  
   134  	getImage := func(name string) (builder.Image, builder.ROLayer, error) {
   135  		assert.Check(t, is.Equal("alpine"+tag, name))
   136  		return &mockImage{id: "expectedthisid"}, nil, nil
   137  	}
   138  	b := newBuilderWithMockBackend()
   139  	b.docker.(*MockBackend).getImageFunc = getImage
   140  	args := NewBuildArgs(make(map[string]*string))
   141  
   142  	val := "sometag"
   143  	metaArg := instructions.ArgCommand{Args: []instructions.KeyValuePairOptional{{
   144  		Key:   "THETAG",
   145  		Value: &val,
   146  	}}}
   147  	cmd := &instructions.Stage{
   148  		BaseName: "alpine:${THETAG}",
   149  	}
   150  	err := processMetaArg(metaArg, shell.NewLex('\\'), args)
   151  
   152  	sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults())
   153  	assert.NilError(t, err)
   154  	err = initializeStage(sb, cmd)
   155  	assert.NilError(t, err)
   156  
   157  	assert.Check(t, is.Equal(expected, sb.state.imageID))
   158  	assert.Check(t, is.Equal(expected, sb.state.baseImage.ImageID()))
   159  	assert.Check(t, is.Len(sb.state.buildArgs.GetAllAllowed(), 0))
   160  	assert.Check(t, is.Len(sb.state.buildArgs.GetAllMeta(), 1))
   161  }
   162  
   163  func TestFromWithArgButBuildArgsNotGiven(t *testing.T) {
   164  	b := newBuilderWithMockBackend()
   165  	args := NewBuildArgs(make(map[string]*string))
   166  
   167  	metaArg := instructions.ArgCommand{}
   168  	cmd := &instructions.Stage{
   169  		BaseName: "${THETAG}",
   170  	}
   171  	err := processMetaArg(metaArg, shell.NewLex('\\'), args)
   172  
   173  	sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults())
   174  	assert.NilError(t, err)
   175  	err = initializeStage(sb, cmd)
   176  	assert.Error(t, err, "base name (${THETAG}) should not be blank")
   177  }
   178  
   179  func TestFromWithUndefinedArg(t *testing.T) {
   180  	tag, expected := "sometag", "expectedthisid"
   181  
   182  	getImage := func(name string) (builder.Image, builder.ROLayer, error) {
   183  		assert.Check(t, is.Equal("alpine", name))
   184  		return &mockImage{id: "expectedthisid"}, nil, nil
   185  	}
   186  	b := newBuilderWithMockBackend()
   187  	b.docker.(*MockBackend).getImageFunc = getImage
   188  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   189  
   190  	b.options.BuildArgs = map[string]*string{"THETAG": &tag}
   191  
   192  	cmd := &instructions.Stage{
   193  		BaseName: "alpine${THETAG}",
   194  	}
   195  	err := initializeStage(sb, cmd)
   196  	assert.NilError(t, err)
   197  	assert.Check(t, is.Equal(expected, sb.state.imageID))
   198  }
   199  
   200  func TestFromMultiStageWithNamedStage(t *testing.T) {
   201  	b := newBuilderWithMockBackend()
   202  	firstFrom := &instructions.Stage{BaseName: "someimg", Name: "base"}
   203  	secondFrom := &instructions.Stage{BaseName: "base"}
   204  	previousResults := newStagesBuildResults()
   205  	firstSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults)
   206  	secondSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults)
   207  	err := initializeStage(firstSB, firstFrom)
   208  	assert.NilError(t, err)
   209  	assert.Check(t, firstSB.state.hasFromImage())
   210  	previousResults.indexed["base"] = firstSB.state.runConfig
   211  	previousResults.flat = append(previousResults.flat, firstSB.state.runConfig)
   212  	err = initializeStage(secondSB, secondFrom)
   213  	assert.NilError(t, err)
   214  	assert.Check(t, secondSB.state.hasFromImage())
   215  }
   216  
   217  func TestOnbuild(t *testing.T) {
   218  	b := newBuilderWithMockBackend()
   219  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   220  	cmd := &instructions.OnbuildCommand{
   221  		Expression: "ADD . /app/src",
   222  	}
   223  	err := dispatch(sb, cmd)
   224  	assert.NilError(t, err)
   225  	assert.Check(t, is.Equal("ADD . /app/src", sb.state.runConfig.OnBuild[0]))
   226  }
   227  
   228  func TestWorkdir(t *testing.T) {
   229  	b := newBuilderWithMockBackend()
   230  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   231  	sb.state.baseImage = &mockImage{}
   232  	workingDir := "/app"
   233  	if runtime.GOOS == "windows" {
   234  		workingDir = "C:\\app"
   235  	}
   236  	cmd := &instructions.WorkdirCommand{
   237  		Path: workingDir,
   238  	}
   239  
   240  	err := dispatch(sb, cmd)
   241  	assert.NilError(t, err)
   242  	assert.Check(t, is.Equal(workingDir, sb.state.runConfig.WorkingDir))
   243  }
   244  
   245  func TestCmd(t *testing.T) {
   246  	b := newBuilderWithMockBackend()
   247  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   248  	sb.state.baseImage = &mockImage{}
   249  	command := "./executable"
   250  
   251  	cmd := &instructions.CmdCommand{
   252  		ShellDependantCmdLine: instructions.ShellDependantCmdLine{
   253  			CmdLine:      strslice.StrSlice{command},
   254  			PrependShell: true,
   255  		},
   256  	}
   257  	err := dispatch(sb, cmd)
   258  	assert.NilError(t, err)
   259  
   260  	var expectedCommand strslice.StrSlice
   261  	if runtime.GOOS == "windows" {
   262  		expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command))
   263  	} else {
   264  		expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
   265  	}
   266  
   267  	assert.Check(t, is.DeepEqual(expectedCommand, sb.state.runConfig.Cmd))
   268  	assert.Check(t, sb.state.cmdSet)
   269  }
   270  
   271  func TestHealthcheckNone(t *testing.T) {
   272  	b := newBuilderWithMockBackend()
   273  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   274  	cmd := &instructions.HealthCheckCommand{
   275  		Health: &container.HealthConfig{
   276  			Test: []string{"NONE"},
   277  		},
   278  	}
   279  	err := dispatch(sb, cmd)
   280  	assert.NilError(t, err)
   281  
   282  	assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
   283  	assert.Check(t, is.DeepEqual([]string{"NONE"}, sb.state.runConfig.Healthcheck.Test))
   284  }
   285  
   286  func TestHealthcheckCmd(t *testing.T) {
   287  
   288  	b := newBuilderWithMockBackend()
   289  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   290  	expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
   291  	cmd := &instructions.HealthCheckCommand{
   292  		Health: &container.HealthConfig{
   293  			Test: expectedTest,
   294  		},
   295  	}
   296  	err := dispatch(sb, cmd)
   297  	assert.NilError(t, err)
   298  
   299  	assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
   300  	assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test))
   301  }
   302  
   303  func TestEntrypoint(t *testing.T) {
   304  	b := newBuilderWithMockBackend()
   305  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   306  	sb.state.baseImage = &mockImage{}
   307  	entrypointCmd := "/usr/sbin/nginx"
   308  
   309  	cmd := &instructions.EntrypointCommand{
   310  		ShellDependantCmdLine: instructions.ShellDependantCmdLine{
   311  			CmdLine:      strslice.StrSlice{entrypointCmd},
   312  			PrependShell: true,
   313  		},
   314  	}
   315  	err := dispatch(sb, cmd)
   316  	assert.NilError(t, err)
   317  	assert.Assert(t, sb.state.runConfig.Entrypoint != nil)
   318  
   319  	var expectedEntrypoint strslice.StrSlice
   320  	if runtime.GOOS == "windows" {
   321  		expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd))
   322  	} else {
   323  		expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
   324  	}
   325  	assert.Check(t, is.DeepEqual(expectedEntrypoint, sb.state.runConfig.Entrypoint))
   326  }
   327  
   328  func TestExpose(t *testing.T) {
   329  	b := newBuilderWithMockBackend()
   330  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   331  
   332  	exposedPort := "80"
   333  	cmd := &instructions.ExposeCommand{
   334  		Ports: []string{exposedPort},
   335  	}
   336  	err := dispatch(sb, cmd)
   337  	assert.NilError(t, err)
   338  
   339  	assert.Assert(t, sb.state.runConfig.ExposedPorts != nil)
   340  	assert.Assert(t, is.Len(sb.state.runConfig.ExposedPorts, 1))
   341  
   342  	portsMapping, err := nat.ParsePortSpec(exposedPort)
   343  	assert.NilError(t, err)
   344  	assert.Check(t, is.Contains(sb.state.runConfig.ExposedPorts, portsMapping[0].Port))
   345  }
   346  
   347  func TestUser(t *testing.T) {
   348  	b := newBuilderWithMockBackend()
   349  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   350  
   351  	cmd := &instructions.UserCommand{
   352  		User: "test",
   353  	}
   354  	err := dispatch(sb, cmd)
   355  	assert.NilError(t, err)
   356  	assert.Check(t, is.Equal("test", sb.state.runConfig.User))
   357  }
   358  
   359  func TestVolume(t *testing.T) {
   360  	b := newBuilderWithMockBackend()
   361  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   362  
   363  	exposedVolume := "/foo"
   364  
   365  	cmd := &instructions.VolumeCommand{
   366  		Volumes: []string{exposedVolume},
   367  	}
   368  	err := dispatch(sb, cmd)
   369  	assert.NilError(t, err)
   370  	assert.Assert(t, sb.state.runConfig.Volumes != nil)
   371  	assert.Check(t, is.Len(sb.state.runConfig.Volumes, 1))
   372  	assert.Check(t, is.Contains(sb.state.runConfig.Volumes, exposedVolume))
   373  }
   374  
   375  func TestStopSignal(t *testing.T) {
   376  	if runtime.GOOS == "windows" {
   377  		t.Skip("Windows does not support stopsignal")
   378  		return
   379  	}
   380  	b := newBuilderWithMockBackend()
   381  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   382  	sb.state.baseImage = &mockImage{}
   383  	signal := "SIGKILL"
   384  
   385  	cmd := &instructions.StopSignalCommand{
   386  		Signal: signal,
   387  	}
   388  	err := dispatch(sb, cmd)
   389  	assert.NilError(t, err)
   390  	assert.Check(t, is.Equal(signal, sb.state.runConfig.StopSignal))
   391  }
   392  
   393  func TestArg(t *testing.T) {
   394  	b := newBuilderWithMockBackend()
   395  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   396  
   397  	argName := "foo"
   398  	argVal := "bar"
   399  	cmd := &instructions.ArgCommand{Args: []instructions.KeyValuePairOptional{{Key: argName, Value: &argVal}}}
   400  	err := dispatch(sb, cmd)
   401  	assert.NilError(t, err)
   402  
   403  	expected := map[string]string{argName: argVal}
   404  	assert.Check(t, is.DeepEqual(expected, sb.state.buildArgs.GetAllAllowed()))
   405  }
   406  
   407  func TestShell(t *testing.T) {
   408  	b := newBuilderWithMockBackend()
   409  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   410  
   411  	shellCmd := "powershell"
   412  	cmd := &instructions.ShellCommand{Shell: strslice.StrSlice{shellCmd}}
   413  
   414  	err := dispatch(sb, cmd)
   415  	assert.NilError(t, err)
   416  
   417  	expectedShell := strslice.StrSlice([]string{shellCmd})
   418  	assert.Check(t, is.DeepEqual(expectedShell, sb.state.runConfig.Shell))
   419  }
   420  
   421  func TestPrependEnvOnCmd(t *testing.T) {
   422  	buildArgs := NewBuildArgs(nil)
   423  	buildArgs.AddArg("NO_PROXY", nil)
   424  
   425  	args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"}
   426  	cmd := []string{"foo", "bar"}
   427  	cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd)
   428  	expected := strslice.StrSlice([]string{
   429  		"|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"})
   430  	assert.Check(t, is.DeepEqual(expected, cmdWithEnv))
   431  }
   432  
   433  func TestRunWithBuildArgs(t *testing.T) {
   434  	b := newBuilderWithMockBackend()
   435  	args := NewBuildArgs(make(map[string]*string))
   436  	args.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
   437  	b.disableCommit = false
   438  	sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults())
   439  
   440  	runConfig := &container.Config{}
   441  	origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
   442  
   443  	var cmdWithShell strslice.StrSlice
   444  	if runtime.GOOS == "windows" {
   445  		cmdWithShell = strslice.StrSlice([]string{strings.Join(append(getShell(runConfig, runtime.GOOS), []string{"echo foo"}...), " ")})
   446  	} else {
   447  		cmdWithShell = strslice.StrSlice(append(getShell(runConfig, runtime.GOOS), "echo foo"))
   448  	}
   449  
   450  	envVars := []string{"|1", "one=two"}
   451  	cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
   452  
   453  	imageCache := &mockImageCache{
   454  		getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
   455  			// Check the runConfig.Cmd sent to probeCache()
   456  			assert.Check(t, is.DeepEqual(cachedCmd, cfg.Cmd))
   457  			assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Entrypoint))
   458  			return "", nil
   459  		},
   460  	}
   461  
   462  	mockBackend := b.docker.(*MockBackend)
   463  	mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
   464  		return imageCache
   465  	}
   466  	b.imageProber = newImageProber(mockBackend, nil, false)
   467  	mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) {
   468  		return &mockImage{
   469  			id:     "abcdef",
   470  			config: &container.Config{Cmd: origCmd},
   471  		}, nil, nil
   472  	}
   473  	mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
   474  		// Check the runConfig.Cmd sent to create()
   475  		assert.Check(t, is.DeepEqual(cmdWithShell, config.Config.Cmd))
   476  		assert.Check(t, is.Contains(config.Config.Env, "one=two"))
   477  		assert.Check(t, is.DeepEqual(strslice.StrSlice{""}, config.Config.Entrypoint))
   478  		return container.ContainerCreateCreatedBody{ID: "12345"}, nil
   479  	}
   480  	mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) {
   481  		// Check the runConfig.Cmd sent to commit()
   482  		assert.Check(t, is.DeepEqual(origCmd, cfg.Config.Cmd))
   483  		assert.Check(t, is.DeepEqual(cachedCmd, cfg.ContainerConfig.Cmd))
   484  		assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Config.Entrypoint))
   485  		return "", nil
   486  	}
   487  	from := &instructions.Stage{BaseName: "abcdef"}
   488  	err := initializeStage(sb, from)
   489  	assert.NilError(t, err)
   490  	sb.state.buildArgs.AddArg("one", strPtr("two"))
   491  
   492  	// This is hugely annoying. On the Windows side, it relies on the
   493  	// RunCommand being able to emit String() and Name() (as implemented by
   494  	// withNameAndCode). Unfortunately, that is internal, and no way to directly
   495  	// set. However, we can fortunately use ParseInstruction in the instructions
   496  	// package to parse a fake node which can be used as our instructions.RunCommand
   497  	// instead.
   498  	node := &parser.Node{
   499  		Original: `RUN echo foo`,
   500  		Value:    "run",
   501  	}
   502  	runint, err := instructions.ParseInstruction(node)
   503  	assert.NilError(t, err)
   504  	runinst := runint.(*instructions.RunCommand)
   505  	runinst.CmdLine = strslice.StrSlice{"echo foo"}
   506  	runinst.PrependShell = true
   507  
   508  	assert.NilError(t, dispatch(sb, runinst))
   509  
   510  	// Check that runConfig.Cmd has not been modified by run
   511  	assert.Check(t, is.DeepEqual(origCmd, sb.state.runConfig.Cmd))
   512  }
   513  
   514  func TestRunIgnoresHealthcheck(t *testing.T) {
   515  	b := newBuilderWithMockBackend()
   516  	args := NewBuildArgs(make(map[string]*string))
   517  	sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults())
   518  	b.disableCommit = false
   519  
   520  	origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
   521  
   522  	imageCache := &mockImageCache{
   523  		getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
   524  			return "", nil
   525  		},
   526  	}
   527  
   528  	mockBackend := b.docker.(*MockBackend)
   529  	mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
   530  		return imageCache
   531  	}
   532  	b.imageProber = newImageProber(mockBackend, nil, false)
   533  	mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) {
   534  		return &mockImage{
   535  			id:     "abcdef",
   536  			config: &container.Config{Cmd: origCmd},
   537  		}, nil, nil
   538  	}
   539  	mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
   540  		return container.ContainerCreateCreatedBody{ID: "12345"}, nil
   541  	}
   542  	mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) {
   543  		return "", nil
   544  	}
   545  	from := &instructions.Stage{BaseName: "abcdef"}
   546  	err := initializeStage(sb, from)
   547  	assert.NilError(t, err)
   548  
   549  	expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
   550  	cmd := &instructions.HealthCheckCommand{
   551  		Health: &container.HealthConfig{
   552  			Test: expectedTest,
   553  		},
   554  	}
   555  	assert.NilError(t, dispatch(sb, cmd))
   556  	assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
   557  
   558  	mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
   559  		// Check the Healthcheck is disabled.
   560  		assert.Check(t, is.DeepEqual([]string{"NONE"}, config.Config.Healthcheck.Test))
   561  		return container.ContainerCreateCreatedBody{ID: "123456"}, nil
   562  	}
   563  
   564  	sb.state.buildArgs.AddArg("one", strPtr("two"))
   565  	run := &instructions.RunCommand{
   566  		ShellDependantCmdLine: instructions.ShellDependantCmdLine{
   567  			CmdLine:      strslice.StrSlice{"echo foo"},
   568  			PrependShell: true,
   569  		},
   570  	}
   571  	assert.NilError(t, dispatch(sb, run))
   572  	assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test))
   573  }
   574  
   575  func TestDispatchUnsupportedOptions(t *testing.T) {
   576  	b := newBuilderWithMockBackend()
   577  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   578  	sb.state.baseImage = &mockImage{}
   579  	sb.state.operatingSystem = runtime.GOOS
   580  
   581  	t.Run("ADD with chmod", func(t *testing.T) {
   582  		cmd := &instructions.AddCommand{SourcesAndDest: []string{".", "."}, Chmod: "0655"}
   583  		err := dispatch(sb, cmd)
   584  		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")
   585  	})
   586  
   587  	t.Run("COPY with chmod", func(t *testing.T) {
   588  		cmd := &instructions.CopyCommand{SourcesAndDest: []string{".", "."}, Chmod: "0655"}
   589  		err := dispatch(sb, cmd)
   590  		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")
   591  	})
   592  
   593  	t.Run("RUN with unsupported options", func(t *testing.T) {
   594  		cmd := &instructions.RunCommand{
   595  			ShellDependantCmdLine: instructions.ShellDependantCmdLine{
   596  				CmdLine:      strslice.StrSlice{"echo foo"},
   597  				PrependShell: true,
   598  			},
   599  		}
   600  
   601  		// classic builder "RUN" currently doesn't support any flags, but testing
   602  		// both "known" flags and "bogus" flags for completeness, and in case
   603  		// one or more of these flags will be supported in future
   604  		for _, f := range []string{"mount", "network", "security", "any-flag"} {
   605  			cmd.FlagsUsed = []string{f}
   606  			err := dispatch(sb, cmd)
   607  			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))
   608  		}
   609  	})
   610  }