github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/builder/dockerfile/dispatchers_test.go (about)

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