github.com/moby/docker@v26.1.3+incompatible/integration-cli/docker_cli_build_test.go (about)

     1  package main
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"context"
     7  	"encoding/json"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"reflect"
    12  	"regexp"
    13  	"runtime"
    14  	"strconv"
    15  	"strings"
    16  	"testing"
    17  	"text/template"
    18  	"time"
    19  
    20  	"github.com/docker/docker/api/types/versions"
    21  	"github.com/docker/docker/integration-cli/cli"
    22  	"github.com/docker/docker/integration-cli/cli/build"
    23  	"github.com/docker/docker/pkg/archive"
    24  	"github.com/docker/docker/testutil"
    25  	"github.com/docker/docker/testutil/fakecontext"
    26  	"github.com/docker/docker/testutil/fakegit"
    27  	"github.com/docker/docker/testutil/fakestorage"
    28  	"github.com/moby/buildkit/frontend/dockerfile/command"
    29  	"github.com/opencontainers/go-digest"
    30  	"gotest.tools/v3/assert"
    31  	is "gotest.tools/v3/assert/cmp"
    32  	"gotest.tools/v3/icmd"
    33  	"gotest.tools/v3/skip"
    34  )
    35  
    36  type DockerCLIBuildSuite struct {
    37  	ds *DockerSuite
    38  }
    39  
    40  func (s *DockerCLIBuildSuite) TearDownTest(ctx context.Context, c *testing.T) {
    41  	s.ds.TearDownTest(ctx, c)
    42  }
    43  
    44  func (s *DockerCLIBuildSuite) OnTimeout(c *testing.T) {
    45  	s.ds.OnTimeout(c)
    46  }
    47  
    48  func (s *DockerCLIBuildSuite) TestBuildJSONEmptyRun(c *testing.T) {
    49  	cli.BuildCmd(c, "testbuildjsonemptyrun", build.WithDockerfile(`
    50      FROM busybox
    51      RUN []
    52      `))
    53  }
    54  
    55  func (s *DockerCLIBuildSuite) TestBuildShCmdJSONEntrypoint(c *testing.T) {
    56  	const name = "testbuildshcmdjsonentrypoint"
    57  	expected := "/bin/sh -c echo test"
    58  	if testEnv.DaemonInfo.OSType == "windows" {
    59  		expected = "cmd /S /C echo test"
    60  	}
    61  
    62  	buildImageSuccessfully(c, name, build.WithDockerfile(`
    63      FROM busybox
    64      ENTRYPOINT ["echo"]
    65      CMD echo test
    66      `))
    67  	out := cli.DockerCmd(c, "run", "--rm", name).Combined()
    68  
    69  	if strings.TrimSpace(out) != expected {
    70  		c.Fatalf("CMD did not contain %q : %q", expected, out)
    71  	}
    72  }
    73  
    74  func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementUser(c *testing.T) {
    75  	// Windows does not support FROM scratch or the USER command
    76  	testRequires(c, DaemonIsLinux)
    77  	const name = "testbuildenvironmentreplacement"
    78  
    79  	buildImageSuccessfully(c, name, build.WithDockerfile(`
    80    FROM scratch
    81    ENV user foo
    82    USER ${user}
    83    `))
    84  	res := inspectFieldJSON(c, name, "Config.User")
    85  
    86  	if res != `"foo"` {
    87  		c.Fatal("User foo from environment not in Config.User on image")
    88  	}
    89  }
    90  
    91  func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementVolume(c *testing.T) {
    92  	const name = "testbuildenvironmentreplacement"
    93  
    94  	var volumePath string
    95  
    96  	if testEnv.DaemonInfo.OSType == "windows" {
    97  		volumePath = "c:/quux"
    98  	} else {
    99  		volumePath = "/quux"
   100  	}
   101  
   102  	buildImageSuccessfully(c, name, build.WithDockerfile(`
   103    FROM `+minimalBaseImage()+`
   104    ENV volume `+volumePath+`
   105    VOLUME ${volume}
   106    `))
   107  
   108  	var volumes map[string]interface{}
   109  	inspectFieldAndUnmarshall(c, name, "Config.Volumes", &volumes)
   110  	if _, ok := volumes[volumePath]; !ok {
   111  		c.Fatal("Volume " + volumePath + " from environment not in Config.Volumes on image")
   112  	}
   113  }
   114  
   115  func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementExpose(c *testing.T) {
   116  	// Windows does not support FROM scratch or the EXPOSE command
   117  	testRequires(c, DaemonIsLinux)
   118  	const name = "testbuildenvironmentreplacement"
   119  
   120  	buildImageSuccessfully(c, name, build.WithDockerfile(`
   121    FROM scratch
   122    ENV port 80
   123    EXPOSE ${port}
   124    ENV ports "  99   100 "
   125    EXPOSE ${ports}
   126    `))
   127  
   128  	var exposedPorts map[string]interface{}
   129  	inspectFieldAndUnmarshall(c, name, "Config.ExposedPorts", &exposedPorts)
   130  	exp := []int{80, 99, 100}
   131  	for _, p := range exp {
   132  		tmp := fmt.Sprintf("%d/tcp", p)
   133  		if _, ok := exposedPorts[tmp]; !ok {
   134  			c.Fatalf("Exposed port %d from environment not in Config.ExposedPorts on image", p)
   135  		}
   136  	}
   137  }
   138  
   139  func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementWorkdir(c *testing.T) {
   140  	const name = "testbuildenvironmentreplacement"
   141  
   142  	buildImageSuccessfully(c, name, build.WithDockerfile(`
   143    FROM busybox
   144    ENV MYWORKDIR /work
   145    RUN mkdir ${MYWORKDIR}
   146    WORKDIR ${MYWORKDIR}
   147    `))
   148  	res := inspectFieldJSON(c, name, "Config.WorkingDir")
   149  
   150  	expected := `"/work"`
   151  	if testEnv.DaemonInfo.OSType == "windows" {
   152  		expected = `"C:\\work"`
   153  	}
   154  	if res != expected {
   155  		c.Fatalf("Workdir /workdir from environment not in Config.WorkingDir on image: %s", res)
   156  	}
   157  }
   158  
   159  func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementAddCopy(c *testing.T) {
   160  	const name = "testbuildenvironmentreplacement"
   161  
   162  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
   163  		build.WithFile("Dockerfile", `
   164    FROM `+minimalBaseImage()+`
   165    ENV baz foo
   166    ENV quux bar
   167    ENV dot .
   168    ENV fee fff
   169    ENV gee ggg
   170  
   171    ADD ${baz} ${dot}
   172    COPY ${quux} ${dot}
   173    ADD ${zzz:-${fee}} ${dot}
   174    COPY ${zzz:-${gee}} ${dot}
   175    `),
   176  		build.WithFile("foo", "test1"),
   177  		build.WithFile("bar", "test2"),
   178  		build.WithFile("fff", "test3"),
   179  		build.WithFile("ggg", "test4"),
   180  	))
   181  }
   182  
   183  func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementEnv(c *testing.T) {
   184  	// ENV expansions work differently in Windows
   185  	testRequires(c, DaemonIsLinux)
   186  	const name = "testbuildenvironmentreplacement"
   187  
   188  	buildImageSuccessfully(c, name, build.WithDockerfile(`
   189    FROM busybox
   190    ENV foo zzz
   191    ENV bar ${foo}
   192    ENV abc1='$foo'
   193    ENV env1=$foo env2=${foo} env3="$foo" env4="${foo}"
   194    RUN [ "$abc1" = '$foo' ] && (echo "$abc1" | grep -q foo)
   195    ENV abc2="\$foo"
   196    RUN [ "$abc2" = '$foo' ] && (echo "$abc2" | grep -q foo)
   197    ENV abc3 '$foo'
   198    RUN [ "$abc3" = '$foo' ] && (echo "$abc3" | grep -q foo)
   199    ENV abc4 "\$foo"
   200    RUN [ "$abc4" = '$foo' ] && (echo "$abc4" | grep -q foo)
   201    ENV foo2="abc\def"
   202    RUN [ "$foo2" = 'abc\def' ]
   203    ENV foo3="abc\\def"
   204    RUN [ "$foo3" = 'abc\def' ]
   205    ENV foo4='abc\\def'
   206    RUN [ "$foo4" = 'abc\\def' ]
   207    ENV foo5='abc\def'
   208    RUN [ "$foo5" = 'abc\def' ]
   209    `))
   210  
   211  	var envResult []string
   212  	inspectFieldAndUnmarshall(c, name, "Config.Env", &envResult)
   213  	found := false
   214  	envCount := 0
   215  
   216  	for _, env := range envResult {
   217  		k, v, _ := strings.Cut(env, "=")
   218  		if k == "bar" {
   219  			found = true
   220  			if v != "zzz" {
   221  				c.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", v)
   222  			}
   223  		} else if strings.HasPrefix(k, "env") {
   224  			envCount++
   225  			if v != "zzz" {
   226  				c.Fatalf("%s should be 'zzz' but instead its %q", k, v)
   227  			}
   228  		} else if strings.HasPrefix(k, "env") {
   229  			envCount++
   230  			if v != "foo" {
   231  				c.Fatalf("%s should be 'foo' but instead its %q", k, v)
   232  			}
   233  		}
   234  	}
   235  
   236  	if !found {
   237  		c.Fatal("Never found the `bar` env variable")
   238  	}
   239  
   240  	if envCount != 4 {
   241  		c.Fatalf("Didn't find all env vars - only saw %d\n%s", envCount, envResult)
   242  	}
   243  }
   244  
   245  func (s *DockerCLIBuildSuite) TestBuildHandleEscapesInVolume(c *testing.T) {
   246  	// The volume paths used in this test are invalid on Windows
   247  	testRequires(c, DaemonIsLinux)
   248  	const name = "testbuildhandleescapes"
   249  
   250  	testCases := []struct {
   251  		volumeValue string
   252  		expected    string
   253  	}{
   254  		{
   255  			volumeValue: "${FOO}",
   256  			expected:    "bar",
   257  		},
   258  		{
   259  			volumeValue: `\${FOO}`,
   260  			expected:    "${FOO}",
   261  		},
   262  		// this test in particular provides *7* backslashes and expects 6 to come back.
   263  		// Like above, the first escape is swallowed and the rest are treated as
   264  		// literals, this one is just less obvious because of all the character noise.
   265  		{
   266  			volumeValue: `\\\\\\\${FOO}`,
   267  			expected:    `\\\${FOO}`,
   268  		},
   269  	}
   270  
   271  	for _, tc := range testCases {
   272  		buildImageSuccessfully(c, name, build.WithDockerfile(fmt.Sprintf(`
   273    FROM scratch
   274    ENV FOO bar
   275    VOLUME %s
   276    `, tc.volumeValue)))
   277  
   278  		var result map[string]map[string]struct{}
   279  		inspectFieldAndUnmarshall(c, name, "Config.Volumes", &result)
   280  		if _, ok := result[tc.expected]; !ok {
   281  			c.Fatalf("Could not find volume %s set from env foo in volumes table, got %q", tc.expected, result)
   282  		}
   283  
   284  		// Remove the image for the next iteration
   285  		cli.DockerCmd(c, "rmi", name)
   286  	}
   287  }
   288  
   289  func (s *DockerCLIBuildSuite) TestBuildOnBuildLowercase(c *testing.T) {
   290  	const name = "testbuildonbuildlowercase"
   291  	const name2 = "testbuildonbuildlowercase2"
   292  
   293  	buildImageSuccessfully(c, name, build.WithDockerfile(`
   294    FROM busybox
   295    onbuild run echo quux
   296    `))
   297  
   298  	result := buildImage(name2, build.WithDockerfile(fmt.Sprintf(`
   299    FROM %s
   300    `, name)))
   301  	result.Assert(c, icmd.Success)
   302  
   303  	if !strings.Contains(result.Combined(), "quux") {
   304  		c.Fatalf("Did not receive the expected echo text, got %s", result.Combined())
   305  	}
   306  
   307  	//nolint:dupword
   308  	if strings.Contains(result.Combined(), "ONBUILD ONBUILD") {
   309  		c.Fatalf("Got an ONBUILD ONBUILD error with no error: got %s", result.Combined())
   310  	}
   311  }
   312  
   313  func (s *DockerCLIBuildSuite) TestBuildEnvEscapes(c *testing.T) {
   314  	// ENV expansions work differently in Windows
   315  	testRequires(c, DaemonIsLinux)
   316  	const name = "testbuildenvescapes"
   317  	buildImageSuccessfully(c, name, build.WithDockerfile(`
   318      FROM busybox
   319      ENV TEST foo
   320      CMD echo \$
   321      `))
   322  
   323  	out := cli.DockerCmd(c, "run", "-t", name).Combined()
   324  	if strings.TrimSpace(out) != "$" {
   325  		c.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out))
   326  	}
   327  }
   328  
   329  func (s *DockerCLIBuildSuite) TestBuildEnvOverwrite(c *testing.T) {
   330  	// ENV expansions work differently in Windows
   331  	testRequires(c, DaemonIsLinux)
   332  	const name = "testbuildenvoverwrite"
   333  	buildImageSuccessfully(c, name, build.WithDockerfile(`
   334      FROM busybox
   335      ENV TEST foo
   336      CMD echo ${TEST}
   337      `))
   338  
   339  	out := cli.DockerCmd(c, "run", "-e", "TEST=bar", "-t", name).Combined()
   340  	if strings.TrimSpace(out) != "bar" {
   341  		c.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out))
   342  	}
   343  }
   344  
   345  // FIXME(vdemeester) why we disabled cache here ?
   346  func (s *DockerCLIBuildSuite) TestBuildOnBuildCmdEntrypointJSON(c *testing.T) {
   347  	const name1 = "onbuildcmd"
   348  	const name2 = "onbuildgenerated"
   349  
   350  	cli.BuildCmd(c, name1, build.WithDockerfile(`
   351  FROM busybox
   352  ONBUILD CMD ["hello world"]
   353  ONBUILD ENTRYPOINT ["echo"]
   354  ONBUILD RUN ["true"]`))
   355  
   356  	cli.BuildCmd(c, name2, build.WithDockerfile(fmt.Sprintf(`FROM %s`, name1)))
   357  
   358  	result := cli.DockerCmd(c, "run", name2)
   359  	result.Assert(c, icmd.Expected{Out: "hello world"})
   360  }
   361  
   362  // FIXME(vdemeester) why we disabled cache here ?
   363  func (s *DockerCLIBuildSuite) TestBuildOnBuildEntrypointJSON(c *testing.T) {
   364  	const name1 = "onbuildcmd"
   365  	const name2 = "onbuildgenerated"
   366  
   367  	buildImageSuccessfully(c, name1, build.WithDockerfile(`
   368  FROM busybox
   369  ONBUILD ENTRYPOINT ["echo"]`))
   370  
   371  	buildImageSuccessfully(c, name2, build.WithDockerfile(fmt.Sprintf("FROM %s\nCMD [\"hello world\"]\n", name1)))
   372  
   373  	out := cli.DockerCmd(c, "run", name2).Combined()
   374  	if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) {
   375  		c.Fatal("got malformed output from onbuild", out)
   376  	}
   377  }
   378  
   379  func (s *DockerCLIBuildSuite) TestBuildCacheAdd(c *testing.T) {
   380  	testRequires(c, DaemonIsLinux) // Windows doesn't have httpserver image yet
   381  	const name = "testbuildtwoimageswithadd"
   382  	server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{
   383  		"robots.txt": "hello",
   384  		"index.html": "world",
   385  	}))
   386  	defer server.Close()
   387  
   388  	cli.BuildCmd(c, name, build.WithDockerfile(fmt.Sprintf(`FROM scratch
   389  		ADD %s/robots.txt /`, server.URL())))
   390  
   391  	result := cli.Docker(cli.Args("build", "-t", name), build.WithDockerfile(fmt.Sprintf(`FROM scratch
   392  		ADD %s/index.html /`, server.URL())))
   393  	result.Assert(c, icmd.Success)
   394  	if strings.Contains(result.Combined(), "Using cache") {
   395  		c.Fatal("2nd build used cache on ADD, it shouldn't")
   396  	}
   397  }
   398  
   399  func (s *DockerCLIBuildSuite) TestBuildLastModified(c *testing.T) {
   400  	// Temporary fix for #30890. TODO: figure out what
   401  	// has changed in the master busybox image.
   402  	testRequires(c, DaemonIsLinux)
   403  
   404  	const name = "testbuildlastmodified"
   405  
   406  	server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{
   407  		"file": "hello",
   408  	}))
   409  	defer server.Close()
   410  
   411  	var out, out2 string
   412  	args := []string{"run", name, "ls", "-l", "--full-time", "/file"}
   413  
   414  	dFmt := `FROM busybox
   415  ADD %s/file /`
   416  	dockerfile := fmt.Sprintf(dFmt, server.URL())
   417  
   418  	cli.BuildCmd(c, name, build.WithoutCache, build.WithDockerfile(dockerfile))
   419  	out = cli.DockerCmd(c, args...).Combined()
   420  
   421  	// Build it again and make sure the mtime of the file didn't change.
   422  	// Wait a few seconds to make sure the time changed enough to notice
   423  	time.Sleep(2 * time.Second)
   424  
   425  	cli.BuildCmd(c, name, build.WithoutCache, build.WithDockerfile(dockerfile))
   426  	out2 = cli.DockerCmd(c, args...).Combined()
   427  
   428  	if out != out2 {
   429  		c.Fatalf("MTime changed:\nOrigin:%s\nNew:%s", out, out2)
   430  	}
   431  
   432  	// Now 'touch' the file and make sure the timestamp DID change this time
   433  	// Create a new fakeStorage instead of just using Add() to help windows
   434  	server = fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{
   435  		"file": "hello",
   436  	}))
   437  	defer server.Close()
   438  
   439  	dockerfile = fmt.Sprintf(dFmt, server.URL())
   440  	cli.BuildCmd(c, name, build.WithoutCache, build.WithDockerfile(dockerfile))
   441  	out2 = cli.DockerCmd(c, args...).Combined()
   442  
   443  	if out == out2 {
   444  		c.Fatalf("MTime didn't change:\nOrigin:%s\nNew:%s", out, out2)
   445  	}
   446  }
   447  
   448  // Regression for https://github.com/docker/docker/pull/27805
   449  // Makes sure that we don't use the cache if the contents of
   450  // a file in a subfolder of the context is modified and we re-build.
   451  func (s *DockerCLIBuildSuite) TestBuildModifyFileInFolder(c *testing.T) {
   452  	const name = "testbuildmodifyfileinfolder"
   453  
   454  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`FROM busybox
   455  RUN ["mkdir", "/test"]
   456  ADD folder/file /test/changetarget`))
   457  	defer ctx.Close()
   458  	if err := ctx.Add("folder/file", "first"); err != nil {
   459  		c.Fatal(err)
   460  	}
   461  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
   462  	id1 := getIDByName(c, name)
   463  	if err := ctx.Add("folder/file", "second"); err != nil {
   464  		c.Fatal(err)
   465  	}
   466  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
   467  	id2 := getIDByName(c, name)
   468  	if id1 == id2 {
   469  		c.Fatal("cache was used even though file contents in folder was changed")
   470  	}
   471  }
   472  
   473  func (s *DockerCLIBuildSuite) TestBuildAddSingleFileToRoot(c *testing.T) {
   474  	testRequires(c, DaemonIsLinux) // Linux specific test
   475  	buildImageSuccessfully(c, "testaddimg", build.WithBuildContext(c,
   476  		build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox
   477  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   478  RUN echo 'dockerio:x:1001:' >> /etc/group
   479  RUN touch /exists
   480  RUN chown dockerio.dockerio /exists
   481  ADD test_file /
   482  RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ]
   483  RUN [ $(ls -l /test_file | awk '{print $1}') = '%s' ]
   484  RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod)),
   485  		build.WithFile("test_file", "test1")))
   486  }
   487  
   488  // Issue #3960: "ADD src ." hangs
   489  func (s *DockerCLIBuildSuite) TestBuildAddSingleFileToWorkdir(c *testing.T) {
   490  	const name = "testaddsinglefiletoworkdir"
   491  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(
   492  		`FROM busybox
   493  	       ADD test_file .`),
   494  		fakecontext.WithFiles(map[string]string{
   495  			"test_file": "test1",
   496  		}))
   497  	defer ctx.Close()
   498  
   499  	errChan := make(chan error, 1)
   500  	go func() {
   501  		errChan <- buildImage(name, build.WithExternalBuildContext(ctx)).Error
   502  		close(errChan)
   503  	}()
   504  	select {
   505  	case <-time.After(15 * time.Second):
   506  		c.Fatal("Build with adding to workdir timed out")
   507  	case err := <-errChan:
   508  		assert.NilError(c, err)
   509  	}
   510  }
   511  
   512  func (s *DockerCLIBuildSuite) TestBuildAddSingleFileToExistDir(c *testing.T) {
   513  	testRequires(c, DaemonIsLinux) // Linux specific test
   514  	cli.BuildCmd(c, "testaddsinglefiletoexistdir", build.WithBuildContext(c,
   515  		build.WithFile("Dockerfile", `FROM busybox
   516  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   517  RUN echo 'dockerio:x:1001:' >> /etc/group
   518  RUN mkdir /exists
   519  RUN touch /exists/exists_file
   520  RUN chown -R dockerio.dockerio /exists
   521  ADD test_file /exists/
   522  RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]
   523  RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]
   524  RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`),
   525  		build.WithFile("test_file", "test1")))
   526  }
   527  
   528  func (s *DockerCLIBuildSuite) TestBuildCopyAddMultipleFiles(c *testing.T) {
   529  	testRequires(c, DaemonIsLinux) // Linux specific test
   530  	server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{
   531  		"robots.txt": "hello",
   532  	}))
   533  	defer server.Close()
   534  
   535  	cli.BuildCmd(c, "testcopymultiplefilestofile", build.WithBuildContext(c,
   536  		build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox
   537  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   538  RUN echo 'dockerio:x:1001:' >> /etc/group
   539  RUN mkdir /exists
   540  RUN touch /exists/exists_file
   541  RUN chown -R dockerio.dockerio /exists
   542  COPY test_file1 test_file2 /exists/
   543  ADD test_file3 test_file4 %s/robots.txt /exists/
   544  RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]
   545  RUN [ $(ls -l /exists/test_file1 | awk '{print $3":"$4}') = 'root:root' ]
   546  RUN [ $(ls -l /exists/test_file2 | awk '{print $3":"$4}') = 'root:root' ]
   547  RUN [ $(ls -l /exists/test_file3 | awk '{print $3":"$4}') = 'root:root' ]
   548  RUN [ $(ls -l /exists/test_file4 | awk '{print $3":"$4}') = 'root:root' ]
   549  RUN [ $(ls -l /exists/robots.txt | awk '{print $3":"$4}') = 'root:root' ]
   550  RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ]
   551  `, server.URL())),
   552  		build.WithFile("test_file1", "test1"),
   553  		build.WithFile("test_file2", "test2"),
   554  		build.WithFile("test_file3", "test3"),
   555  		build.WithFile("test_file3", "test3"),
   556  		build.WithFile("test_file4", "test4")))
   557  }
   558  
   559  // These tests are mainly for user namespaces to verify that new directories
   560  // are created as the remapped root uid/gid pair
   561  func (s *DockerCLIBuildSuite) TestBuildUsernamespaceValidateRemappedRoot(c *testing.T) {
   562  	testRequires(c, DaemonIsLinux)
   563  	testCases := []string{
   564  		"ADD . /new_dir",
   565  		"COPY test_dir /new_dir",
   566  		"WORKDIR /new_dir",
   567  	}
   568  	const name = "testbuildusernamespacevalidateremappedroot"
   569  	for _, tc := range testCases {
   570  		cli.BuildCmd(c, name, build.WithBuildContext(c,
   571  			build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox
   572  %s
   573  RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'root:root' ]`, tc)),
   574  			build.WithFile("test_dir/test_file", "test file")))
   575  
   576  		cli.DockerCmd(c, "rmi", name)
   577  	}
   578  }
   579  
   580  func (s *DockerCLIBuildSuite) TestBuildAddAndCopyFileWithWhitespace(c *testing.T) {
   581  	testRequires(c, DaemonIsLinux) // Not currently passing on Windows
   582  	const name = "testaddfilewithwhitespace"
   583  
   584  	for _, command := range []string{"ADD", "COPY"} {
   585  		cli.BuildCmd(c, name, build.WithBuildContext(c,
   586  			build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox
   587  RUN mkdir "/test dir"
   588  RUN mkdir "/test_dir"
   589  %s [ "test file1", "/test_file1" ]
   590  %s [ "test_file2", "/test file2" ]
   591  %s [ "test file3", "/test file3" ]
   592  %s [ "test dir/test_file4", "/test_dir/test_file4" ]
   593  %s [ "test_dir/test_file5", "/test dir/test_file5" ]
   594  %s [ "test dir/test_file6", "/test dir/test_file6" ]
   595  RUN [ $(cat "/test_file1") = 'test1' ]
   596  RUN [ $(cat "/test file2") = 'test2' ]
   597  RUN [ $(cat "/test file3") = 'test3' ]
   598  RUN [ $(cat "/test_dir/test_file4") = 'test4' ]
   599  RUN [ $(cat "/test dir/test_file5") = 'test5' ]
   600  RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, command, command, command, command, command, command)),
   601  			build.WithFile("test file1", "test1"),
   602  			build.WithFile("test_file2", "test2"),
   603  			build.WithFile("test file3", "test3"),
   604  			build.WithFile("test dir/test_file4", "test4"),
   605  			build.WithFile("test_dir/test_file5", "test5"),
   606  			build.WithFile("test dir/test_file6", "test6"),
   607  		))
   608  
   609  		cli.DockerCmd(c, "rmi", name)
   610  	}
   611  }
   612  
   613  func (s *DockerCLIBuildSuite) TestBuildCopyFileWithWhitespaceOnWindows(c *testing.T) {
   614  	testRequires(c, DaemonIsWindows)
   615  	dockerfile := `FROM ` + testEnv.PlatformDefaults.BaseImage + `
   616  RUN mkdir "C:/test dir"
   617  RUN mkdir "C:/test_dir"
   618  COPY [ "test file1", "/test_file1" ]
   619  COPY [ "test_file2", "/test file2" ]
   620  COPY [ "test file3", "/test file3" ]
   621  COPY [ "test dir/test_file4", "/test_dir/test_file4" ]
   622  COPY [ "test_dir/test_file5", "/test dir/test_file5" ]
   623  COPY [ "test dir/test_file6", "/test dir/test_file6" ]
   624  RUN find "test1" "C:/test_file1"
   625  RUN find "test2" "C:/test file2"
   626  RUN find "test3" "C:/test file3"
   627  RUN find "test4" "C:/test_dir/test_file4"
   628  RUN find "test5" "C:/test dir/test_file5"
   629  RUN find "test6" "C:/test dir/test_file6"`
   630  
   631  	const name = "testcopyfilewithwhitespace"
   632  	cli.BuildCmd(c, name, build.WithBuildContext(c,
   633  		build.WithFile("Dockerfile", dockerfile),
   634  		build.WithFile("test file1", "test1"),
   635  		build.WithFile("test_file2", "test2"),
   636  		build.WithFile("test file3", "test3"),
   637  		build.WithFile("test dir/test_file4", "test4"),
   638  		build.WithFile("test_dir/test_file5", "test5"),
   639  		build.WithFile("test dir/test_file6", "test6"),
   640  	))
   641  }
   642  
   643  func (s *DockerCLIBuildSuite) TestBuildCopyWildcard(c *testing.T) {
   644  	const name = "testcopywildcard"
   645  	server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{
   646  		"robots.txt": "hello",
   647  		"index.html": "world",
   648  	}))
   649  	defer server.Close()
   650  
   651  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(fmt.Sprintf(`FROM busybox
   652  	COPY file*.txt /tmp/
   653  	RUN ls /tmp/file1.txt /tmp/file2.txt
   654  	RUN [ "mkdir",  "/tmp1" ]
   655  	COPY dir* /tmp1/
   656  	RUN ls /tmp1/dirt /tmp1/nested_file /tmp1/nested_dir/nest_nest_file
   657  	RUN [ "mkdir",  "/tmp2" ]
   658          ADD dir/*dir %s/robots.txt /tmp2/
   659  	RUN ls /tmp2/nest_nest_file /tmp2/robots.txt
   660  	`, server.URL())),
   661  		fakecontext.WithFiles(map[string]string{
   662  			"file1.txt":                     "test1",
   663  			"file2.txt":                     "test2",
   664  			"dir/nested_file":               "nested file",
   665  			"dir/nested_dir/nest_nest_file": "2 times nested",
   666  			"dirt":                          "dirty",
   667  		}))
   668  	defer ctx.Close()
   669  
   670  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
   671  	id1 := getIDByName(c, name)
   672  
   673  	// Now make sure we use a cache the 2nd time
   674  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
   675  	id2 := getIDByName(c, name)
   676  
   677  	if id1 != id2 {
   678  		c.Fatal("didn't use the cache")
   679  	}
   680  }
   681  
   682  func (s *DockerCLIBuildSuite) TestBuildCopyWildcardInName(c *testing.T) {
   683  	// Run this only on Linux
   684  	// Below is the original comment (that I don't agree with — vdemeester)
   685  	// Normally we would do c.Fatal(err) here but given that
   686  	// the odds of this failing are so rare, it must be because
   687  	// the OS we're running the client on doesn't support * in
   688  	// filenames (like windows).  So, instead of failing the test
   689  	// just let it pass. Then we don't need to explicitly
   690  	// say which OSs this works on or not.
   691  	testRequires(c, DaemonIsLinux, UnixCli)
   692  
   693  	buildImageSuccessfully(c, "testcopywildcardinname", build.WithBuildContext(c,
   694  		build.WithFile("Dockerfile", `FROM busybox
   695  	COPY *.txt /tmp/
   696  	RUN [ "$(cat /tmp/\*.txt)" = 'hi there' ]
   697  	`),
   698  		build.WithFile("*.txt", "hi there"),
   699  	))
   700  }
   701  
   702  func (s *DockerCLIBuildSuite) TestBuildCopyWildcardCache(c *testing.T) {
   703  	const name = "testcopywildcardcache"
   704  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`FROM busybox
   705  	COPY file1.txt /tmp/`),
   706  		fakecontext.WithFiles(map[string]string{
   707  			"file1.txt": "test1",
   708  		}))
   709  	defer ctx.Close()
   710  
   711  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
   712  	id1 := getIDByName(c, name)
   713  
   714  	// Now make sure we use a cache the 2nd time even with wild cards.
   715  	// Use the same context so the file is the same and the checksum will match
   716  	ctx.Add("Dockerfile", `FROM busybox
   717  	COPY file*.txt /tmp/`)
   718  
   719  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
   720  	id2 := getIDByName(c, name)
   721  
   722  	if id1 != id2 {
   723  		c.Fatal("didn't use the cache")
   724  	}
   725  }
   726  
   727  func (s *DockerCLIBuildSuite) TestBuildAddSingleFileToNonExistingDir(c *testing.T) {
   728  	testRequires(c, DaemonIsLinux) // Linux specific test
   729  	buildImageSuccessfully(c, "testaddsinglefiletononexistingdir", build.WithBuildContext(c,
   730  		build.WithFile("Dockerfile", `FROM busybox
   731  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   732  RUN echo 'dockerio:x:1001:' >> /etc/group
   733  RUN touch /exists
   734  RUN chown dockerio.dockerio /exists
   735  ADD test_file /test_dir/
   736  RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ]
   737  RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ]
   738  RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`),
   739  		build.WithFile("test_file", "test1")))
   740  }
   741  
   742  func (s *DockerCLIBuildSuite) TestBuildAddDirContentToRoot(c *testing.T) {
   743  	testRequires(c, DaemonIsLinux) // Linux specific test
   744  	buildImageSuccessfully(c, "testadddircontenttoroot", build.WithBuildContext(c,
   745  		build.WithFile("Dockerfile", `FROM busybox
   746  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   747  RUN echo 'dockerio:x:1001:' >> /etc/group
   748  RUN touch /exists
   749  RUN chown dockerio.dockerio exists
   750  ADD test_dir /
   751  RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ]
   752  RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`),
   753  		build.WithFile("test_dir/test_file", "test1")))
   754  }
   755  
   756  func (s *DockerCLIBuildSuite) TestBuildAddDirContentToExistingDir(c *testing.T) {
   757  	testRequires(c, DaemonIsLinux) // Linux specific test
   758  	buildImageSuccessfully(c, "testadddircontenttoexistingdir", build.WithBuildContext(c,
   759  		build.WithFile("Dockerfile", `FROM busybox
   760  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   761  RUN echo 'dockerio:x:1001:' >> /etc/group
   762  RUN mkdir /exists
   763  RUN touch /exists/exists_file
   764  RUN chown -R dockerio.dockerio /exists
   765  ADD test_dir/ /exists/
   766  RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]
   767  RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ]
   768  RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`),
   769  		build.WithFile("test_dir/test_file", "test1")))
   770  }
   771  
   772  func (s *DockerCLIBuildSuite) TestBuildAddWholeDirToRoot(c *testing.T) {
   773  	testRequires(c, DaemonIsLinux) // Linux specific test
   774  	buildImageSuccessfully(c, "testaddwholedirtoroot", build.WithBuildContext(c,
   775  		build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox
   776  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   777  RUN echo 'dockerio:x:1001:' >> /etc/group
   778  RUN touch /exists
   779  RUN chown dockerio.dockerio exists
   780  ADD test_dir /test_dir
   781  RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ]
   782  RUN [ $(ls -l / | grep test_dir | awk '{print $1}') = 'drwxr-xr-x' ]
   783  RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ]
   784  RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '%s' ]
   785  RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod)),
   786  		build.WithFile("test_dir/test_file", "test1")))
   787  }
   788  
   789  // Testing #5941 : Having an etc directory in context conflicts with the /etc/mtab
   790  func (s *DockerCLIBuildSuite) TestBuildAddOrCopyEtcToRootShouldNotConflict(c *testing.T) {
   791  	buildImageSuccessfully(c, "testaddetctoroot", build.WithBuildContext(c,
   792  		build.WithFile("Dockerfile", `FROM `+minimalBaseImage()+`
   793  ADD . /`),
   794  		build.WithFile("etc/test_file", "test1")))
   795  	buildImageSuccessfully(c, "testcopyetctoroot", build.WithBuildContext(c,
   796  		build.WithFile("Dockerfile", `FROM `+minimalBaseImage()+`
   797  COPY . /`),
   798  		build.WithFile("etc/test_file", "test1")))
   799  }
   800  
   801  // Testing #9401 : Losing setuid flag after a ADD
   802  func (s *DockerCLIBuildSuite) TestBuildAddPreservesFilesSpecialBits(c *testing.T) {
   803  	testRequires(c, DaemonIsLinux) // Linux specific test
   804  	buildImageSuccessfully(c, "testaddetctoroot", build.WithBuildContext(c,
   805  		build.WithFile("Dockerfile", `FROM busybox
   806  ADD suidbin /usr/bin/suidbin
   807  RUN chmod 4755 /usr/bin/suidbin
   808  RUN [ $(ls -l /usr/bin/suidbin | awk '{print $1}') = '-rwsr-xr-x' ]
   809  ADD ./data/ /
   810  RUN [ $(ls -l /usr/bin/suidbin | awk '{print $1}') = '-rwsr-xr-x' ]`),
   811  		build.WithFile("suidbin", "suidbin"),
   812  		build.WithFile("/data/usr/test_file", "test1")))
   813  }
   814  
   815  func (s *DockerCLIBuildSuite) TestBuildCopySingleFileToRoot(c *testing.T) {
   816  	testRequires(c, DaemonIsLinux) // Linux specific test
   817  	buildImageSuccessfully(c, "testcopysinglefiletoroot", build.WithBuildContext(c,
   818  		build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox
   819  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   820  RUN echo 'dockerio:x:1001:' >> /etc/group
   821  RUN touch /exists
   822  RUN chown dockerio.dockerio /exists
   823  COPY test_file /
   824  RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ]
   825  RUN [ $(ls -l /test_file | awk '{print $1}') = '%s' ]
   826  RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod)),
   827  		build.WithFile("test_file", "test1")))
   828  }
   829  
   830  // Issue #3960: "ADD src ." hangs - adapted for COPY
   831  func (s *DockerCLIBuildSuite) TestBuildCopySingleFileToWorkdir(c *testing.T) {
   832  	const name = "testcopysinglefiletoworkdir"
   833  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`FROM busybox
   834  COPY test_file .`),
   835  		fakecontext.WithFiles(map[string]string{
   836  			"test_file": "test1",
   837  		}))
   838  	defer ctx.Close()
   839  
   840  	errChan := make(chan error, 1)
   841  	go func() {
   842  		errChan <- buildImage(name, build.WithExternalBuildContext(ctx)).Error
   843  		close(errChan)
   844  	}()
   845  	select {
   846  	case <-time.After(15 * time.Second):
   847  		c.Fatal("Build with adding to workdir timed out")
   848  	case err := <-errChan:
   849  		assert.NilError(c, err)
   850  	}
   851  }
   852  
   853  func (s *DockerCLIBuildSuite) TestBuildCopySingleFileToExistDir(c *testing.T) {
   854  	testRequires(c, DaemonIsLinux) // Linux specific test
   855  	buildImageSuccessfully(c, "testcopysinglefiletoexistdir", build.WithBuildContext(c,
   856  		build.WithFile("Dockerfile", `FROM busybox
   857  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   858  RUN echo 'dockerio:x:1001:' >> /etc/group
   859  RUN mkdir /exists
   860  RUN touch /exists/exists_file
   861  RUN chown -R dockerio.dockerio /exists
   862  COPY test_file /exists/
   863  RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]
   864  RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]
   865  RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`),
   866  		build.WithFile("test_file", "test1")))
   867  }
   868  
   869  func (s *DockerCLIBuildSuite) TestBuildCopySingleFileToNonExistDir(c *testing.T) {
   870  	testRequires(c, DaemonIsLinux) // Linux specific
   871  	buildImageSuccessfully(c, "testcopysinglefiletononexistdir", build.WithBuildContext(c,
   872  		build.WithFile("Dockerfile", `FROM busybox
   873  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   874  RUN echo 'dockerio:x:1001:' >> /etc/group
   875  RUN touch /exists
   876  RUN chown dockerio.dockerio /exists
   877  COPY test_file /test_dir/
   878  RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ]
   879  RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ]
   880  RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`),
   881  		build.WithFile("test_file", "test1")))
   882  }
   883  
   884  func (s *DockerCLIBuildSuite) TestBuildCopyDirContentToRoot(c *testing.T) {
   885  	testRequires(c, DaemonIsLinux) // Linux specific test
   886  	buildImageSuccessfully(c, "testcopydircontenttoroot", build.WithBuildContext(c,
   887  		build.WithFile("Dockerfile", `FROM busybox
   888  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   889  RUN echo 'dockerio:x:1001:' >> /etc/group
   890  RUN touch /exists
   891  RUN chown dockerio.dockerio exists
   892  COPY test_dir /
   893  RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ]
   894  RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`),
   895  		build.WithFile("test_dir/test_file", "test1")))
   896  }
   897  
   898  func (s *DockerCLIBuildSuite) TestBuildCopyDirContentToExistDir(c *testing.T) {
   899  	testRequires(c, DaemonIsLinux) // Linux specific test
   900  	buildImageSuccessfully(c, "testcopydircontenttoexistdir", build.WithBuildContext(c,
   901  		build.WithFile("Dockerfile", `FROM busybox
   902  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   903  RUN echo 'dockerio:x:1001:' >> /etc/group
   904  RUN mkdir /exists
   905  RUN touch /exists/exists_file
   906  RUN chown -R dockerio.dockerio /exists
   907  COPY test_dir/ /exists/
   908  RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]
   909  RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ]
   910  RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`),
   911  		build.WithFile("test_dir/test_file", "test1")))
   912  }
   913  
   914  func (s *DockerCLIBuildSuite) TestBuildCopyWholeDirToRoot(c *testing.T) {
   915  	testRequires(c, DaemonIsLinux) // Linux specific test
   916  	buildImageSuccessfully(c, "testcopywholedirtoroot", build.WithBuildContext(c,
   917  		build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox
   918  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   919  RUN echo 'dockerio:x:1001:' >> /etc/group
   920  RUN touch /exists
   921  RUN chown dockerio.dockerio exists
   922  COPY test_dir /test_dir
   923  RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ]
   924  RUN [ $(ls -l / | grep test_dir | awk '{print $1}') = 'drwxr-xr-x' ]
   925  RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ]
   926  RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '%s' ]
   927  RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod)),
   928  		build.WithFile("test_dir/test_file", "test1")))
   929  }
   930  
   931  func (s *DockerCLIBuildSuite) TestBuildAddBadLinks(c *testing.T) {
   932  	testRequires(c, DaemonIsLinux) // Not currently working on Windows
   933  
   934  	dockerfile := `
   935  		FROM scratch
   936  		ADD links.tar /
   937  		ADD foo.txt /symlink/
   938  		`
   939  	targetFile := "foo.txt"
   940  	const name = "test-link-absolute"
   941  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile))
   942  	defer ctx.Close()
   943  
   944  	tempDir, err := os.MkdirTemp("", "test-link-absolute-temp-")
   945  	if err != nil {
   946  		c.Fatalf("failed to create temporary directory: %s", tempDir)
   947  	}
   948  	defer os.RemoveAll(tempDir)
   949  
   950  	var symlinkTarget string
   951  	if runtime.GOOS == "windows" {
   952  		var driveLetter string
   953  		if abs, err := filepath.Abs(tempDir); err != nil {
   954  			c.Fatal(err)
   955  		} else {
   956  			driveLetter = abs[:1]
   957  		}
   958  		tempDirWithoutDrive := tempDir[2:]
   959  		symlinkTarget = fmt.Sprintf(`%s:\..\..\..\..\..\..\..\..\..\..\..\..%s`, driveLetter, tempDirWithoutDrive)
   960  	} else {
   961  		symlinkTarget = fmt.Sprintf("/../../../../../../../../../../../..%s", tempDir)
   962  	}
   963  
   964  	tarPath := filepath.Join(ctx.Dir, "links.tar")
   965  	nonExistingFile := filepath.Join(tempDir, targetFile)
   966  	fooPath := filepath.Join(ctx.Dir, targetFile)
   967  
   968  	tarOut, err := os.Create(tarPath)
   969  	if err != nil {
   970  		c.Fatal(err)
   971  	}
   972  	defer tarOut.Close()
   973  
   974  	tarWriter := tar.NewWriter(tarOut)
   975  	defer tarWriter.Close()
   976  
   977  	header := &tar.Header{
   978  		Name:     "symlink",
   979  		Typeflag: tar.TypeSymlink,
   980  		Linkname: symlinkTarget,
   981  		Mode:     0o755,
   982  		Uid:      0,
   983  		Gid:      0,
   984  	}
   985  
   986  	err = tarWriter.WriteHeader(header)
   987  	if err != nil {
   988  		c.Fatal(err)
   989  	}
   990  
   991  	err = os.WriteFile(fooPath, []byte("test"), 0666)
   992  	if err != nil {
   993  		c.Fatal(err)
   994  	}
   995  
   996  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
   997  	if _, err := os.Stat(nonExistingFile); err == nil || !os.IsNotExist(err) {
   998  		c.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile)
   999  	}
  1000  }
  1001  
  1002  func (s *DockerCLIBuildSuite) TestBuildAddBadLinksVolume(c *testing.T) {
  1003  	testRequires(c, DaemonIsLinux) // ln not implemented on Windows busybox
  1004  	const (
  1005  		dockerfileTemplate = `
  1006  		FROM busybox
  1007  		RUN ln -s /../../../../../../../../%s /x
  1008  		VOLUME /x
  1009  		ADD foo.txt /x/`
  1010  		targetFile = "foo.txt"
  1011  	)
  1012  
  1013  	tempDir, err := os.MkdirTemp("", "test-link-absolute-volume-temp-")
  1014  	if err != nil {
  1015  		c.Fatalf("failed to create temporary directory: %s", tempDir)
  1016  	}
  1017  	defer os.RemoveAll(tempDir)
  1018  
  1019  	dockerfile := fmt.Sprintf(dockerfileTemplate, tempDir)
  1020  	nonExistingFile := filepath.Join(tempDir, targetFile)
  1021  
  1022  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile))
  1023  	defer ctx.Close()
  1024  	fooPath := filepath.Join(ctx.Dir, targetFile)
  1025  
  1026  	err = os.WriteFile(fooPath, []byte("test"), 0666)
  1027  	if err != nil {
  1028  		c.Fatal(err)
  1029  	}
  1030  
  1031  	buildImageSuccessfully(c, "test-link-absolute-volume", build.WithExternalBuildContext(ctx))
  1032  	if _, err := os.Stat(nonExistingFile); err == nil || !os.IsNotExist(err) {
  1033  		c.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile)
  1034  	}
  1035  }
  1036  
  1037  // Issue #5270 - ensure we throw a better error than "unexpected EOF"
  1038  // when we can't access files in the context.
  1039  func (s *DockerCLIBuildSuite) TestBuildWithInaccessibleFilesInContext(c *testing.T) {
  1040  	testRequires(c, DaemonIsLinux, UnixCli, testEnv.IsLocalDaemon) // test uses chown/chmod: not available on windows
  1041  
  1042  	{
  1043  		const name = "testbuildinaccessiblefiles"
  1044  		ctx := fakecontext.New(c, "",
  1045  			fakecontext.WithDockerfile("FROM scratch\nADD . /foo/"),
  1046  			fakecontext.WithFiles(map[string]string{"fileWithoutReadAccess": "foo"}),
  1047  		)
  1048  		defer ctx.Close()
  1049  		// This is used to ensure we detect inaccessible files early during build in the cli client
  1050  		pathToFileWithoutReadAccess := filepath.Join(ctx.Dir, "fileWithoutReadAccess")
  1051  
  1052  		if err := os.Chown(pathToFileWithoutReadAccess, 0, 0); err != nil {
  1053  			c.Fatalf("failed to chown file to root: %s", err)
  1054  		}
  1055  		if err := os.Chmod(pathToFileWithoutReadAccess, 0o700); err != nil {
  1056  			c.Fatalf("failed to chmod file to 700: %s", err)
  1057  		}
  1058  		result := icmd.RunCmd(icmd.Cmd{
  1059  			Command: []string{"su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)},
  1060  			Dir:     ctx.Dir,
  1061  		})
  1062  		if result.Error == nil {
  1063  			c.Fatalf("build should have failed: %s %s", result.Error, result.Combined())
  1064  		}
  1065  
  1066  		// check if we've detected the failure before we started building
  1067  		if !strings.Contains(result.Combined(), "no permission to read from ") {
  1068  			c.Fatalf("output should've contained the string: no permission to read from but contained: %s", result.Combined())
  1069  		}
  1070  
  1071  		if !strings.Contains(result.Combined(), "error checking context") {
  1072  			c.Fatalf("output should've contained the string: error checking context")
  1073  		}
  1074  	}
  1075  	{
  1076  		const name = "testbuildinaccessibledirectory"
  1077  		ctx := fakecontext.New(c, "",
  1078  			fakecontext.WithDockerfile("FROM scratch\nADD . /foo/"),
  1079  			fakecontext.WithFiles(map[string]string{"directoryWeCantStat/bar": "foo"}),
  1080  		)
  1081  		defer ctx.Close()
  1082  		// This is used to ensure we detect inaccessible directories early during build in the cli client
  1083  		pathToDirectoryWithoutReadAccess := filepath.Join(ctx.Dir, "directoryWeCantStat")
  1084  		pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar")
  1085  
  1086  		if err := os.Chown(pathToDirectoryWithoutReadAccess, 0, 0); err != nil {
  1087  			c.Fatalf("failed to chown directory to root: %s", err)
  1088  		}
  1089  		if err := os.Chmod(pathToDirectoryWithoutReadAccess, 0o444); err != nil {
  1090  			c.Fatalf("failed to chmod directory to 444: %s", err)
  1091  		}
  1092  		if err := os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0o700); err != nil {
  1093  			c.Fatalf("failed to chmod file to 700: %s", err)
  1094  		}
  1095  
  1096  		result := icmd.RunCmd(icmd.Cmd{
  1097  			Command: []string{"su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)},
  1098  			Dir:     ctx.Dir,
  1099  		})
  1100  		if result.Error == nil {
  1101  			c.Fatalf("build should have failed: %s %s", result.Error, result.Combined())
  1102  		}
  1103  
  1104  		// check if we've detected the failure before we started building
  1105  		if !strings.Contains(result.Combined(), "can't stat") {
  1106  			c.Fatalf("output should've contained the string: can't access %s", result.Combined())
  1107  		}
  1108  
  1109  		if !strings.Contains(result.Combined(), "error checking context") {
  1110  			c.Fatalf("output should've contained the string: error checking context\ngot:%s", result.Combined())
  1111  		}
  1112  	}
  1113  	{
  1114  		const name = "testlinksok"
  1115  		ctx := fakecontext.New(c, "", fakecontext.WithDockerfile("FROM scratch\nADD . /foo/"))
  1116  		defer ctx.Close()
  1117  
  1118  		target := "../../../../../../../../../../../../../../../../../../../azA"
  1119  		if err := os.Symlink(filepath.Join(ctx.Dir, "g"), target); err != nil {
  1120  			c.Fatal(err)
  1121  		}
  1122  		defer os.Remove(target)
  1123  		// This is used to ensure we don't follow links when checking if everything in the context is accessible
  1124  		// This test doesn't require that we run commands as an unprivileged user
  1125  		buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
  1126  	}
  1127  	{
  1128  		const name = "testbuildignoredinaccessible"
  1129  		ctx := fakecontext.New(c, "",
  1130  			fakecontext.WithDockerfile("FROM scratch\nADD . /foo/"),
  1131  			fakecontext.WithFiles(map[string]string{
  1132  				"directoryWeCantStat/bar": "foo",
  1133  				".dockerignore":           "directoryWeCantStat",
  1134  			}),
  1135  		)
  1136  		defer ctx.Close()
  1137  		// This is used to ensure we don't try to add inaccessible files when they are ignored by a .dockerignore pattern
  1138  		pathToDirectoryWithoutReadAccess := filepath.Join(ctx.Dir, "directoryWeCantStat")
  1139  		pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar")
  1140  		if err := os.Chown(pathToDirectoryWithoutReadAccess, 0, 0); err != nil {
  1141  			c.Fatalf("failed to chown directory to root: %s", err)
  1142  		}
  1143  		if err := os.Chmod(pathToDirectoryWithoutReadAccess, 0o444); err != nil {
  1144  			c.Fatalf("failed to chmod directory to 444: %s", err)
  1145  		}
  1146  		if err := os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0o700); err != nil {
  1147  			c.Fatalf("failed to chmod file to 700: %s", err)
  1148  		}
  1149  
  1150  		result := icmd.RunCmd(icmd.Cmd{
  1151  			Dir: ctx.Dir,
  1152  			Command: []string{
  1153  				"su", "unprivilegeduser", "-c",
  1154  				fmt.Sprintf("%s build -t %s .", dockerBinary, name),
  1155  			},
  1156  		})
  1157  		result.Assert(c, icmd.Expected{})
  1158  	}
  1159  }
  1160  
  1161  func (s *DockerCLIBuildSuite) TestBuildForceRm(c *testing.T) {
  1162  	containerCountBefore := getContainerCount(c)
  1163  	const name = "testbuildforcerm"
  1164  
  1165  	r := buildImage(name, cli.WithFlags("--force-rm"), build.WithBuildContext(c,
  1166  		build.WithFile("Dockerfile", `FROM busybox
  1167  	RUN true
  1168  	RUN thiswillfail`)))
  1169  	if r.ExitCode != 1 && r.ExitCode != 127 { // different on Linux / Windows
  1170  		c.Fatalf("Wrong exit code")
  1171  	}
  1172  
  1173  	containerCountAfter := getContainerCount(c)
  1174  	if containerCountBefore != containerCountAfter {
  1175  		c.Fatalf("--force-rm shouldn't have left containers behind")
  1176  	}
  1177  }
  1178  
  1179  func (s *DockerCLIBuildSuite) TestBuildRm(c *testing.T) {
  1180  	const name = "testbuildrm"
  1181  
  1182  	testCases := []struct {
  1183  		buildflags                []string
  1184  		shouldLeftContainerBehind bool
  1185  	}{
  1186  		// Default case (i.e. --rm=true)
  1187  		{
  1188  			buildflags:                []string{},
  1189  			shouldLeftContainerBehind: false,
  1190  		},
  1191  		{
  1192  			buildflags:                []string{"--rm"},
  1193  			shouldLeftContainerBehind: false,
  1194  		},
  1195  		{
  1196  			buildflags:                []string{"--rm=false"},
  1197  			shouldLeftContainerBehind: true,
  1198  		},
  1199  	}
  1200  
  1201  	for _, tc := range testCases {
  1202  		containerCountBefore := getContainerCount(c)
  1203  
  1204  		buildImageSuccessfully(c, name, cli.WithFlags(tc.buildflags...), build.WithDockerfile(`FROM busybox
  1205  	RUN echo hello world`))
  1206  
  1207  		containerCountAfter := getContainerCount(c)
  1208  		if tc.shouldLeftContainerBehind {
  1209  			if containerCountBefore == containerCountAfter {
  1210  				c.Fatalf("flags %v should have left containers behind", tc.buildflags)
  1211  			}
  1212  		} else {
  1213  			if containerCountBefore != containerCountAfter {
  1214  				c.Fatalf("flags %v shouldn't have left containers behind", tc.buildflags)
  1215  			}
  1216  		}
  1217  
  1218  		cli.DockerCmd(c, "rmi", name)
  1219  	}
  1220  }
  1221  
  1222  func (s *DockerCLIBuildSuite) TestBuildWithVolumes(c *testing.T) {
  1223  	testRequires(c, DaemonIsLinux) // Invalid volume paths on Windows
  1224  	var (
  1225  		result   map[string]map[string]struct{}
  1226  		name     = "testbuildvolumes"
  1227  		emptyMap = make(map[string]struct{})
  1228  		expected = map[string]map[string]struct{}{
  1229  			"/test1":  emptyMap,
  1230  			"/test2":  emptyMap,
  1231  			"/test3":  emptyMap,
  1232  			"/test4":  emptyMap,
  1233  			"/test5":  emptyMap,
  1234  			"/test6":  emptyMap,
  1235  			"[/test7": emptyMap,
  1236  			"/test8]": emptyMap,
  1237  		}
  1238  	)
  1239  
  1240  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM scratch
  1241  		VOLUME /test1
  1242  		VOLUME /test2
  1243      VOLUME /test3 /test4
  1244      VOLUME ["/test5", "/test6"]
  1245      VOLUME [/test7 /test8]
  1246      `))
  1247  
  1248  	inspectFieldAndUnmarshall(c, name, "Config.Volumes", &result)
  1249  
  1250  	equal := reflect.DeepEqual(&result, &expected)
  1251  	if !equal {
  1252  		c.Fatalf("Volumes %s, expected %s", result, expected)
  1253  	}
  1254  }
  1255  
  1256  func (s *DockerCLIBuildSuite) TestBuildMaintainer(c *testing.T) {
  1257  	const name = "testbuildmaintainer"
  1258  
  1259  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+`
  1260          MAINTAINER dockerio`))
  1261  
  1262  	expected := "dockerio"
  1263  	res := inspectField(c, name, "Author")
  1264  	if res != expected {
  1265  		c.Fatalf("Maintainer %s, expected %s", res, expected)
  1266  	}
  1267  }
  1268  
  1269  func (s *DockerCLIBuildSuite) TestBuildUser(c *testing.T) {
  1270  	testRequires(c, DaemonIsLinux)
  1271  	const name = "testbuilduser"
  1272  	expected := "dockerio"
  1273  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  1274  		RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
  1275  		USER dockerio
  1276  		RUN [ $(whoami) = 'dockerio' ]`))
  1277  	res := inspectField(c, name, "Config.User")
  1278  	if res != expected {
  1279  		c.Fatalf("User %s, expected %s", res, expected)
  1280  	}
  1281  }
  1282  
  1283  func (s *DockerCLIBuildSuite) TestBuildRelativeWorkdir(c *testing.T) {
  1284  	const name = "testbuildrelativeworkdir"
  1285  
  1286  	var (
  1287  		expected1     string
  1288  		expected2     string
  1289  		expected3     string
  1290  		expected4     string
  1291  		expectedFinal string
  1292  	)
  1293  
  1294  	if testEnv.DaemonInfo.OSType == "windows" {
  1295  		expected1 = `C:/`
  1296  		expected2 = `C:/test1`
  1297  		expected3 = `C:/test2`
  1298  		expected4 = `C:/test2/test3`
  1299  		expectedFinal = `C:\test2\test3` // Note inspect is going to return Windows paths, as it's not in busybox
  1300  	} else {
  1301  		expected1 = `/`
  1302  		expected2 = `/test1`
  1303  		expected3 = `/test2`
  1304  		expected4 = `/test2/test3`
  1305  		expectedFinal = `/test2/test3`
  1306  	}
  1307  
  1308  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  1309  		RUN sh -c "[ "$PWD" = "`+expected1+`" ]"
  1310  		WORKDIR test1
  1311  		RUN sh -c "[ "$PWD" = "`+expected2+`" ]"
  1312  		WORKDIR /test2
  1313  		RUN sh -c "[ "$PWD" = "`+expected3+`" ]"
  1314  		WORKDIR test3
  1315  		RUN sh -c "[ "$PWD" = "`+expected4+`" ]"`))
  1316  
  1317  	res := inspectField(c, name, "Config.WorkingDir")
  1318  	if res != expectedFinal {
  1319  		c.Fatalf("Workdir %s, expected %s", res, expectedFinal)
  1320  	}
  1321  }
  1322  
  1323  // #22181 Regression test. Single end-to-end test of using
  1324  // Windows semantics. Most path handling verifications are in unit tests
  1325  func (s *DockerCLIBuildSuite) TestBuildWindowsWorkdirProcessing(c *testing.T) {
  1326  	testRequires(c, DaemonIsWindows)
  1327  	buildImageSuccessfully(c, "testbuildwindowsworkdirprocessing", build.WithDockerfile(`FROM busybox
  1328  		WORKDIR C:\\foo
  1329  		WORKDIR bar
  1330  		RUN sh -c "[ "$PWD" = "C:/foo/bar" ]"
  1331  		`))
  1332  }
  1333  
  1334  // #22181 Regression test. Most paths handling verifications are in unit test.
  1335  // One functional test for end-to-end
  1336  func (s *DockerCLIBuildSuite) TestBuildWindowsAddCopyPathProcessing(c *testing.T) {
  1337  	testRequires(c, DaemonIsWindows)
  1338  	// TODO Windows. Needs a follow-up PR to 22181 to
  1339  	// support backslash such as .\\ being equivalent to ./ and c:\\ being
  1340  	// equivalent to c:/. This is not currently (nor ever has been) supported
  1341  	// by docker on the Windows platform.
  1342  	buildImageSuccessfully(c, "testbuildwindowsaddcopypathprocessing", build.WithBuildContext(c,
  1343  		build.WithFile("Dockerfile", `FROM busybox
  1344  			# No trailing slash on COPY/ADD
  1345  			# Results in dir being changed to a file
  1346  			WORKDIR /wc1
  1347  			COPY wc1 c:/wc1
  1348  			WORKDIR /wc2
  1349  			ADD wc2 c:/wc2
  1350  			WORKDIR c:/
  1351  			RUN sh -c "[ $(cat c:/wc1/wc1) = 'hellowc1' ]"
  1352  			RUN sh -c "[ $(cat c:/wc2/wc2) = 'worldwc2' ]"
  1353  
  1354  			# Trailing slash on COPY/ADD, Windows-style path.
  1355  			WORKDIR /wd1
  1356  			COPY wd1 c:/wd1/
  1357  			WORKDIR /wd2
  1358  			ADD wd2 c:/wd2/
  1359  			RUN sh -c "[ $(cat c:/wd1/wd1) = 'hellowd1' ]"
  1360  			RUN sh -c "[ $(cat c:/wd2/wd2) = 'worldwd2' ]"
  1361  			`),
  1362  		build.WithFile("wc1", "hellowc1"),
  1363  		build.WithFile("wc2", "worldwc2"),
  1364  		build.WithFile("wd1", "hellowd1"),
  1365  		build.WithFile("wd2", "worldwd2"),
  1366  	))
  1367  }
  1368  
  1369  func (s *DockerCLIBuildSuite) TestBuildWorkdirWithEnvVariables(c *testing.T) {
  1370  	const name = "testbuildworkdirwithenvvariables"
  1371  
  1372  	var expected string
  1373  	if testEnv.DaemonInfo.OSType == "windows" {
  1374  		expected = `C:\test1\test2`
  1375  	} else {
  1376  		expected = `/test1/test2`
  1377  	}
  1378  
  1379  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  1380  		ENV DIRPATH /test1
  1381  		ENV SUBDIRNAME test2
  1382  		WORKDIR $DIRPATH
  1383  		WORKDIR $SUBDIRNAME/$MISSING_VAR`))
  1384  	res := inspectField(c, name, "Config.WorkingDir")
  1385  	if res != expected {
  1386  		c.Fatalf("Workdir %s, expected %s", res, expected)
  1387  	}
  1388  }
  1389  
  1390  func (s *DockerCLIBuildSuite) TestBuildRelativeCopy(c *testing.T) {
  1391  	// cat /test1/test2/foo gets permission denied for the user
  1392  	testRequires(c, NotUserNamespace)
  1393  
  1394  	var expected string
  1395  	if testEnv.DaemonInfo.OSType == "windows" {
  1396  		expected = `C:/test1/test2`
  1397  	} else {
  1398  		expected = `/test1/test2`
  1399  	}
  1400  
  1401  	buildImageSuccessfully(c, "testbuildrelativecopy", build.WithBuildContext(c,
  1402  		build.WithFile("Dockerfile", `FROM busybox
  1403  			WORKDIR /test1
  1404  			WORKDIR test2
  1405  			RUN sh -c "[ "$PWD" = '`+expected+`' ]"
  1406  			COPY foo ./
  1407  			RUN sh -c "[ $(cat /test1/test2/foo) = 'hello' ]"
  1408  			ADD foo ./bar/baz
  1409  			RUN sh -c "[ $(cat /test1/test2/bar/baz) = 'hello' ]"
  1410  			COPY foo ./bar/baz2
  1411  			RUN sh -c "[ $(cat /test1/test2/bar/baz2) = 'hello' ]"
  1412  			WORKDIR ..
  1413  			COPY foo ./
  1414  			RUN sh -c "[ $(cat /test1/foo) = 'hello' ]"
  1415  			COPY foo /test3/
  1416  			RUN sh -c "[ $(cat /test3/foo) = 'hello' ]"
  1417  			WORKDIR /test4
  1418  			COPY . .
  1419  			RUN sh -c "[ $(cat /test4/foo) = 'hello' ]"
  1420  			WORKDIR /test5/test6
  1421  			COPY foo ../
  1422  			RUN sh -c "[ $(cat /test5/foo) = 'hello' ]"
  1423  			`),
  1424  		build.WithFile("foo", "hello"),
  1425  	))
  1426  }
  1427  
  1428  // FIXME(vdemeester) should be unit test
  1429  func (s *DockerCLIBuildSuite) TestBuildBlankName(c *testing.T) {
  1430  	const name = "testbuildblankname"
  1431  	testCases := []struct {
  1432  		expression     string
  1433  		expectedStderr string
  1434  	}{
  1435  		{
  1436  			expression:     "ENV =",
  1437  			expectedStderr: "ENV names can not be blank",
  1438  		},
  1439  		{
  1440  			expression:     "LABEL =",
  1441  			expectedStderr: "LABEL names can not be blank",
  1442  		},
  1443  		{
  1444  			expression:     "ARG =foo",
  1445  			expectedStderr: "ARG names can not be blank",
  1446  		},
  1447  	}
  1448  
  1449  	for _, tc := range testCases {
  1450  		buildImage(name, build.WithDockerfile(fmt.Sprintf(`FROM busybox
  1451  		%s`, tc.expression))).Assert(c, icmd.Expected{
  1452  			ExitCode: 1,
  1453  			Err:      tc.expectedStderr,
  1454  		})
  1455  	}
  1456  }
  1457  
  1458  func (s *DockerCLIBuildSuite) TestBuildEnv(c *testing.T) {
  1459  	testRequires(c, DaemonIsLinux) // ENV expansion is different in Windows
  1460  	const name = "testbuildenv"
  1461  	expected := "[PATH=/test:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=2375]"
  1462  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  1463  		ENV PATH /test:$PATH
  1464  		ENV PORT 2375
  1465  		RUN [ $(env | grep PORT) = 'PORT=2375' ]`))
  1466  	res := inspectField(c, name, "Config.Env")
  1467  	if res != expected {
  1468  		c.Fatalf("Env %s, expected %s", res, expected)
  1469  	}
  1470  }
  1471  
  1472  func (s *DockerCLIBuildSuite) TestBuildPATH(c *testing.T) {
  1473  	testRequires(c, DaemonIsLinux) // ENV expansion is different in Windows
  1474  
  1475  	defPath := "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  1476  
  1477  	fn := func(dockerfile string, expected string) {
  1478  		buildImageSuccessfully(c, "testbldpath", build.WithDockerfile(dockerfile))
  1479  		res := inspectField(c, "testbldpath", "Config.Env")
  1480  		if res != expected {
  1481  			c.Fatalf("Env %q, expected %q for dockerfile:%q", res, expected, dockerfile)
  1482  		}
  1483  	}
  1484  
  1485  	tests := []struct{ dockerfile, exp string }{
  1486  		{"FROM scratch\nMAINTAINER me", "[PATH=" + defPath + "]"},
  1487  		{"FROM busybox\nMAINTAINER me", "[PATH=" + defPath + "]"},
  1488  		{"FROM scratch\nENV FOO=bar", "[PATH=" + defPath + " FOO=bar]"},
  1489  		{"FROM busybox\nENV FOO=bar", "[PATH=" + defPath + " FOO=bar]"},
  1490  		{"FROM scratch\nENV PATH=/test", "[PATH=/test]"},
  1491  		{"FROM busybox\nENV PATH=/test", "[PATH=/test]"},
  1492  		{"FROM scratch\nENV PATH=''", "[PATH=]"},
  1493  		{"FROM busybox\nENV PATH=''", "[PATH=]"},
  1494  	}
  1495  
  1496  	for _, test := range tests {
  1497  		fn(test.dockerfile, test.exp)
  1498  	}
  1499  }
  1500  
  1501  func (s *DockerCLIBuildSuite) TestBuildContextCleanup(c *testing.T) {
  1502  	testRequires(c, testEnv.IsLocalDaemon)
  1503  
  1504  	const name = "testbuildcontextcleanup"
  1505  	entries, err := os.ReadDir(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "tmp"))
  1506  	if err != nil {
  1507  		c.Fatalf("failed to list contents of tmp dir: %s", err)
  1508  	}
  1509  
  1510  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+`
  1511          ENTRYPOINT ["/bin/echo"]`))
  1512  
  1513  	entriesFinal, err := os.ReadDir(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "tmp"))
  1514  	if err != nil {
  1515  		c.Fatalf("failed to list contents of tmp dir: %s", err)
  1516  	}
  1517  	if err = compareDirectoryEntries(entries, entriesFinal); err != nil {
  1518  		c.Fatalf("context should have been deleted, but wasn't")
  1519  	}
  1520  }
  1521  
  1522  func (s *DockerCLIBuildSuite) TestBuildContextCleanupFailedBuild(c *testing.T) {
  1523  	testRequires(c, testEnv.IsLocalDaemon)
  1524  
  1525  	const name = "testbuildcontextcleanup"
  1526  	entries, err := os.ReadDir(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "tmp"))
  1527  	if err != nil {
  1528  		c.Fatalf("failed to list contents of tmp dir: %s", err)
  1529  	}
  1530  
  1531  	buildImage(name, build.WithDockerfile(`FROM `+minimalBaseImage()+`
  1532  	RUN /non/existing/command`)).Assert(c, icmd.Expected{
  1533  		ExitCode: 1,
  1534  	})
  1535  
  1536  	entriesFinal, err := os.ReadDir(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "tmp"))
  1537  	if err != nil {
  1538  		c.Fatalf("failed to list contents of tmp dir: %s", err)
  1539  	}
  1540  	if err = compareDirectoryEntries(entries, entriesFinal); err != nil {
  1541  		c.Fatalf("context should have been deleted, but wasn't")
  1542  	}
  1543  }
  1544  
  1545  // compareDirectoryEntries compares two sets of DirEntry (usually taken from a directory)
  1546  // and returns an error if different.
  1547  func compareDirectoryEntries(e1 []os.DirEntry, e2 []os.DirEntry) error {
  1548  	var (
  1549  		e1Entries = make(map[string]struct{})
  1550  		e2Entries = make(map[string]struct{})
  1551  	)
  1552  	for _, e := range e1 {
  1553  		e1Entries[e.Name()] = struct{}{}
  1554  	}
  1555  	for _, e := range e2 {
  1556  		e2Entries[e.Name()] = struct{}{}
  1557  	}
  1558  	if !reflect.DeepEqual(e1Entries, e2Entries) {
  1559  		return fmt.Errorf("entries differ")
  1560  	}
  1561  	return nil
  1562  }
  1563  
  1564  func (s *DockerCLIBuildSuite) TestBuildCmd(c *testing.T) {
  1565  	const name = "testbuildcmd"
  1566  	expected := "[/bin/echo Hello World]"
  1567  
  1568  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+`
  1569          CMD ["/bin/echo", "Hello World"]`))
  1570  
  1571  	res := inspectField(c, name, "Config.Cmd")
  1572  	if res != expected {
  1573  		c.Fatalf("Cmd %s, expected %s", res, expected)
  1574  	}
  1575  }
  1576  
  1577  func (s *DockerCLIBuildSuite) TestBuildExpose(c *testing.T) {
  1578  	testRequires(c, DaemonIsLinux) // Expose not implemented on Windows
  1579  	const name = "testbuildexpose"
  1580  	expected := "map[2375/tcp:{}]"
  1581  
  1582  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM scratch
  1583          EXPOSE 2375`))
  1584  
  1585  	res := inspectField(c, name, "Config.ExposedPorts")
  1586  	if res != expected {
  1587  		c.Fatalf("Exposed ports %s, expected %s", res, expected)
  1588  	}
  1589  }
  1590  
  1591  func (s *DockerCLIBuildSuite) TestBuildExposeMorePorts(c *testing.T) {
  1592  	testRequires(c, DaemonIsLinux) // Expose not implemented on Windows
  1593  	// start building docker file with a large number of ports
  1594  	portList := make([]string, 50)
  1595  	line := make([]string, 100)
  1596  	expectedPorts := make([]int, len(portList)*len(line))
  1597  	for i := 0; i < len(portList); i++ {
  1598  		for j := 0; j < len(line); j++ {
  1599  			p := i*len(line) + j + 1
  1600  			line[j] = strconv.Itoa(p)
  1601  			expectedPorts[p-1] = p
  1602  		}
  1603  		if i == len(portList)-1 {
  1604  			portList[i] = strings.Join(line, " ")
  1605  		} else {
  1606  			portList[i] = strings.Join(line, " ") + ` \`
  1607  		}
  1608  	}
  1609  
  1610  	dockerfile := `FROM scratch
  1611  	EXPOSE {{range .}} {{.}}
  1612  	{{end}}`
  1613  	tmpl := template.Must(template.New("dockerfile").Parse(dockerfile))
  1614  	buf := bytes.NewBuffer(nil)
  1615  	tmpl.Execute(buf, portList)
  1616  
  1617  	const name = "testbuildexpose"
  1618  	buildImageSuccessfully(c, name, build.WithDockerfile(buf.String()))
  1619  
  1620  	// check if all the ports are saved inside Config.ExposedPorts
  1621  	res := inspectFieldJSON(c, name, "Config.ExposedPorts")
  1622  	var exposedPorts map[string]interface{}
  1623  	if err := json.Unmarshal([]byte(res), &exposedPorts); err != nil {
  1624  		c.Fatal(err)
  1625  	}
  1626  
  1627  	for _, p := range expectedPorts {
  1628  		ep := fmt.Sprintf("%d/tcp", p)
  1629  		if _, ok := exposedPorts[ep]; !ok {
  1630  			c.Errorf("Port(%s) is not exposed", ep)
  1631  		} else {
  1632  			delete(exposedPorts, ep)
  1633  		}
  1634  	}
  1635  	if len(exposedPorts) != 0 {
  1636  		c.Errorf("Unexpected extra exposed ports %v", exposedPorts)
  1637  	}
  1638  }
  1639  
  1640  func (s *DockerCLIBuildSuite) TestBuildExposeOrder(c *testing.T) {
  1641  	testRequires(c, DaemonIsLinux) // Expose not implemented on Windows
  1642  	buildID := func(name, exposed string) string {
  1643  		buildImageSuccessfully(c, name, build.WithDockerfile(fmt.Sprintf(`FROM scratch
  1644  		EXPOSE %s`, exposed)))
  1645  		id := inspectField(c, name, "Id")
  1646  		return id
  1647  	}
  1648  
  1649  	id1 := buildID("testbuildexpose1", "80 2375")
  1650  	id2 := buildID("testbuildexpose2", "2375 80")
  1651  	if id1 != id2 {
  1652  		c.Errorf("EXPOSE should invalidate the cache only when ports actually changed")
  1653  	}
  1654  }
  1655  
  1656  func (s *DockerCLIBuildSuite) TestBuildExposeUpperCaseProto(c *testing.T) {
  1657  	testRequires(c, DaemonIsLinux) // Expose not implemented on Windows
  1658  	const name = "testbuildexposeuppercaseproto"
  1659  	expected := "map[5678/udp:{}]"
  1660  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM scratch
  1661          EXPOSE 5678/UDP`))
  1662  	res := inspectField(c, name, "Config.ExposedPorts")
  1663  	if res != expected {
  1664  		c.Fatalf("Exposed ports %s, expected %s", res, expected)
  1665  	}
  1666  }
  1667  
  1668  func (s *DockerCLIBuildSuite) TestBuildEmptyEntrypointInheritance(c *testing.T) {
  1669  	const name = "testbuildentrypointinheritance"
  1670  	const name2 = "testbuildentrypointinheritance2"
  1671  
  1672  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  1673          ENTRYPOINT ["/bin/echo"]`))
  1674  	res := inspectField(c, name, "Config.Entrypoint")
  1675  
  1676  	expected := "[/bin/echo]"
  1677  	if res != expected {
  1678  		c.Fatalf("Entrypoint %s, expected %s", res, expected)
  1679  	}
  1680  
  1681  	buildImageSuccessfully(c, name2, build.WithDockerfile(fmt.Sprintf(`FROM %s
  1682          ENTRYPOINT []`, name)))
  1683  	res = inspectField(c, name2, "Config.Entrypoint")
  1684  
  1685  	expected = "[]"
  1686  	if res != expected {
  1687  		c.Fatalf("Entrypoint %s, expected %s", res, expected)
  1688  	}
  1689  }
  1690  
  1691  func (s *DockerCLIBuildSuite) TestBuildEmptyEntrypoint(c *testing.T) {
  1692  	const name = "testbuildentrypoint"
  1693  	expected := "[]"
  1694  
  1695  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  1696          ENTRYPOINT []`))
  1697  
  1698  	res := inspectField(c, name, "Config.Entrypoint")
  1699  	if res != expected {
  1700  		c.Fatalf("Entrypoint %s, expected %s", res, expected)
  1701  	}
  1702  }
  1703  
  1704  func (s *DockerCLIBuildSuite) TestBuildEntrypoint(c *testing.T) {
  1705  	const name = "testbuildentrypoint"
  1706  
  1707  	expected := "[/bin/echo]"
  1708  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+`
  1709          ENTRYPOINT ["/bin/echo"]`))
  1710  
  1711  	res := inspectField(c, name, "Config.Entrypoint")
  1712  	if res != expected {
  1713  		c.Fatalf("Entrypoint %s, expected %s", res, expected)
  1714  	}
  1715  }
  1716  
  1717  // #6445 ensure ONBUILD triggers aren't committed to grandchildren
  1718  func (s *DockerCLIBuildSuite) TestBuildOnBuildLimitedInheritance(c *testing.T) {
  1719  	buildImageSuccessfully(c, "testonbuildtrigger1", build.WithDockerfile(`
  1720  		FROM busybox
  1721  		RUN echo "GRANDPARENT"
  1722  		ONBUILD RUN echo "ONBUILD PARENT"
  1723  		`))
  1724  	// ONBUILD should be run in second build.
  1725  	buildImage("testonbuildtrigger2", build.WithDockerfile("FROM testonbuildtrigger1")).Assert(c, icmd.Expected{
  1726  		Out: "ONBUILD PARENT",
  1727  	})
  1728  	// ONBUILD should *not* be run in third build.
  1729  	result := buildImage("testonbuildtrigger3", build.WithDockerfile("FROM testonbuildtrigger2"))
  1730  	result.Assert(c, icmd.Success)
  1731  	if strings.Contains(result.Combined(), "ONBUILD PARENT") {
  1732  		c.Fatalf("ONBUILD instruction ran in grandchild of ONBUILD parent")
  1733  	}
  1734  }
  1735  
  1736  func (s *DockerCLIBuildSuite) TestBuildSameDockerfileWithAndWithoutCache(c *testing.T) {
  1737  	testRequires(c, DaemonIsLinux) // Expose not implemented on Windows
  1738  	const name = "testbuildwithcache"
  1739  	dockerfile := `FROM scratch
  1740  		MAINTAINER dockerio
  1741  		EXPOSE 5432
  1742          ENTRYPOINT ["/bin/echo"]`
  1743  	buildImageSuccessfully(c, name, build.WithDockerfile(dockerfile))
  1744  	id1 := getIDByName(c, name)
  1745  	buildImageSuccessfully(c, name, build.WithDockerfile(dockerfile))
  1746  	id2 := getIDByName(c, name)
  1747  	buildImageSuccessfully(c, name, build.WithoutCache, build.WithDockerfile(dockerfile))
  1748  	id3 := getIDByName(c, name)
  1749  	if id1 != id2 {
  1750  		c.Fatal("The cache should have been used but hasn't.")
  1751  	}
  1752  	if id1 == id3 {
  1753  		c.Fatal("The cache should have been invalided but hasn't.")
  1754  	}
  1755  }
  1756  
  1757  // Make sure that ADD/COPY still populate the cache even if they don't use it
  1758  func (s *DockerCLIBuildSuite) TestBuildConditionalCache(c *testing.T) {
  1759  	const name = "testbuildconditionalcache"
  1760  
  1761  	dockerfile := `
  1762  		FROM busybox
  1763          ADD foo /tmp/`
  1764  	ctx := fakecontext.New(c, "",
  1765  		fakecontext.WithDockerfile(dockerfile),
  1766  		fakecontext.WithFiles(map[string]string{
  1767  			"foo": "hello",
  1768  		}))
  1769  	defer ctx.Close()
  1770  
  1771  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  1772  	id1 := getIDByName(c, name)
  1773  
  1774  	if err := ctx.Add("foo", "bye"); err != nil {
  1775  		c.Fatalf("Error modifying foo: %s", err)
  1776  	}
  1777  
  1778  	// Updating a file should invalidate the cache
  1779  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  1780  	id2 := getIDByName(c, name)
  1781  	if id2 == id1 {
  1782  		c.Fatal("Should not have used the cache")
  1783  	}
  1784  
  1785  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  1786  	id3 := getIDByName(c, name)
  1787  	if id3 != id2 {
  1788  		c.Fatal("Should have used the cache")
  1789  	}
  1790  }
  1791  
  1792  func (s *DockerCLIBuildSuite) TestBuildAddMultipleLocalFileWithAndWithoutCache(c *testing.T) {
  1793  	const name = "testbuildaddmultiplelocalfilewithcache"
  1794  	baseName := name + "-base"
  1795  
  1796  	cli.BuildCmd(c, baseName, build.WithDockerfile(`
  1797  		FROM busybox
  1798  		ENTRYPOINT ["/bin/sh"]
  1799  	`))
  1800  
  1801  	dockerfile := `
  1802  		FROM testbuildaddmultiplelocalfilewithcache-base
  1803          MAINTAINER dockerio
  1804          ADD foo Dockerfile /usr/lib/bla/
  1805  		RUN sh -c "[ $(cat /usr/lib/bla/foo) = "hello" ]"`
  1806  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile), fakecontext.WithFiles(map[string]string{
  1807  		"foo": "hello",
  1808  	}))
  1809  	defer ctx.Close()
  1810  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  1811  	id1 := getIDByName(c, name)
  1812  	result2 := cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  1813  	id2 := getIDByName(c, name)
  1814  	result3 := cli.BuildCmd(c, name, build.WithoutCache, build.WithExternalBuildContext(ctx))
  1815  	id3 := getIDByName(c, name)
  1816  	if id1 != id2 {
  1817  		c.Fatalf("The cache should have been used but hasn't: %s", result2.Stdout())
  1818  	}
  1819  	if id1 == id3 {
  1820  		c.Fatalf("The cache should have been invalided but hasn't: %s", result3.Stdout())
  1821  	}
  1822  }
  1823  
  1824  func (s *DockerCLIBuildSuite) TestBuildCopyDirButNotFile(c *testing.T) {
  1825  	const name = "testbuildcopydirbutnotfile"
  1826  	const name2 = "testbuildcopydirbutnotfile2"
  1827  
  1828  	dockerfile := `
  1829          FROM ` + minimalBaseImage() + `
  1830          COPY dir /tmp/`
  1831  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile), fakecontext.WithFiles(map[string]string{
  1832  		"dir/foo": "hello",
  1833  	}))
  1834  	defer ctx.Close()
  1835  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  1836  	id1 := getIDByName(c, name)
  1837  	// Check that adding file with similar name doesn't mess with cache
  1838  	if err := ctx.Add("dir_file", "hello2"); err != nil {
  1839  		c.Fatal(err)
  1840  	}
  1841  	cli.BuildCmd(c, name2, build.WithExternalBuildContext(ctx))
  1842  	id2 := getIDByName(c, name2)
  1843  	if id1 != id2 {
  1844  		c.Fatal("The cache should have been used but wasn't")
  1845  	}
  1846  }
  1847  
  1848  func (s *DockerCLIBuildSuite) TestBuildAddCurrentDirWithCache(c *testing.T) {
  1849  	const name = "testbuildaddcurrentdirwithcache"
  1850  	const name2 = name + "2"
  1851  	const name3 = name + "3"
  1852  	const name4 = name + "4"
  1853  	dockerfile := `
  1854          FROM ` + minimalBaseImage() + `
  1855          MAINTAINER dockerio
  1856          ADD . /usr/lib/bla`
  1857  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile), fakecontext.WithFiles(map[string]string{
  1858  		"foo": "hello",
  1859  	}))
  1860  	defer ctx.Close()
  1861  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
  1862  	id1 := getIDByName(c, name)
  1863  	// Check that adding file invalidate cache of "ADD ."
  1864  	if err := ctx.Add("bar", "hello2"); err != nil {
  1865  		c.Fatal(err)
  1866  	}
  1867  	buildImageSuccessfully(c, name2, build.WithExternalBuildContext(ctx))
  1868  	id2 := getIDByName(c, name2)
  1869  	if id1 == id2 {
  1870  		c.Fatal("The cache should have been invalided but hasn't.")
  1871  	}
  1872  	// Check that changing file invalidate cache of "ADD ."
  1873  	if err := ctx.Add("foo", "hello1"); err != nil {
  1874  		c.Fatal(err)
  1875  	}
  1876  	buildImageSuccessfully(c, name3, build.WithExternalBuildContext(ctx))
  1877  	id3 := getIDByName(c, name3)
  1878  	if id2 == id3 {
  1879  		c.Fatal("The cache should have been invalided but hasn't.")
  1880  	}
  1881  	// Check that changing file to same content with different mtime does not
  1882  	// invalidate cache of "ADD ."
  1883  	time.Sleep(1 * time.Second) // wait second because of mtime precision
  1884  	if err := ctx.Add("foo", "hello1"); err != nil {
  1885  		c.Fatal(err)
  1886  	}
  1887  	buildImageSuccessfully(c, name4, build.WithExternalBuildContext(ctx))
  1888  	id4 := getIDByName(c, name4)
  1889  	if id3 != id4 {
  1890  		c.Fatal("The cache should have been used but hasn't.")
  1891  	}
  1892  }
  1893  
  1894  // FIXME(vdemeester) this really seems to test the same thing as before (TestBuildAddMultipleLocalFileWithAndWithoutCache)
  1895  func (s *DockerCLIBuildSuite) TestBuildAddCurrentDirWithoutCache(c *testing.T) {
  1896  	const name = "testbuildaddcurrentdirwithoutcache"
  1897  	dockerfile := `
  1898          FROM ` + minimalBaseImage() + `
  1899          MAINTAINER dockerio
  1900          ADD . /usr/lib/bla`
  1901  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile), fakecontext.WithFiles(map[string]string{
  1902  		"foo": "hello",
  1903  	}))
  1904  	defer ctx.Close()
  1905  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
  1906  	id1 := getIDByName(c, name)
  1907  	buildImageSuccessfully(c, name, build.WithoutCache, build.WithExternalBuildContext(ctx))
  1908  	id2 := getIDByName(c, name)
  1909  	if id1 == id2 {
  1910  		c.Fatal("The cache should have been invalided but hasn't.")
  1911  	}
  1912  }
  1913  
  1914  func (s *DockerCLIBuildSuite) TestBuildAddRemoteFileWithAndWithoutCache(c *testing.T) {
  1915  	const name = "testbuildaddremotefilewithcache"
  1916  	server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{
  1917  		"baz": "hello",
  1918  	}))
  1919  	defer server.Close()
  1920  
  1921  	dockerfile := fmt.Sprintf(`FROM `+minimalBaseImage()+`
  1922          MAINTAINER dockerio
  1923          ADD %s/baz /usr/lib/baz/quux`, server.URL())
  1924  	cli.BuildCmd(c, name, build.WithDockerfile(dockerfile))
  1925  	id1 := getIDByName(c, name)
  1926  	cli.BuildCmd(c, name, build.WithDockerfile(dockerfile))
  1927  	id2 := getIDByName(c, name)
  1928  	cli.BuildCmd(c, name, build.WithoutCache, build.WithDockerfile(dockerfile))
  1929  	id3 := getIDByName(c, name)
  1930  
  1931  	if id1 != id2 {
  1932  		c.Fatal("The cache should have been used but hasn't.")
  1933  	}
  1934  	if id1 == id3 {
  1935  		c.Fatal("The cache should have been invalided but hasn't.")
  1936  	}
  1937  }
  1938  
  1939  func (s *DockerCLIBuildSuite) TestBuildAddRemoteFileMTime(c *testing.T) {
  1940  	const name = "testbuildaddremotefilemtime"
  1941  	const name2 = name + "2"
  1942  	const name3 = name + "3"
  1943  
  1944  	files := map[string]string{"baz": "hello"}
  1945  	server := fakestorage.New(c, "", fakecontext.WithFiles(files))
  1946  	defer server.Close()
  1947  
  1948  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(fmt.Sprintf(`FROM `+minimalBaseImage()+`
  1949          MAINTAINER dockerio
  1950          ADD %s/baz /usr/lib/baz/quux`, server.URL())))
  1951  	defer ctx.Close()
  1952  
  1953  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  1954  	id1 := getIDByName(c, name)
  1955  	cli.BuildCmd(c, name2, build.WithExternalBuildContext(ctx))
  1956  	id2 := getIDByName(c, name2)
  1957  	if id1 != id2 {
  1958  		c.Fatal("The cache should have been used but wasn't - #1")
  1959  	}
  1960  
  1961  	// Now create a different server with same contents (causes different mtime)
  1962  	// The cache should still be used
  1963  
  1964  	// allow some time for clock to pass as mtime precision is only 1s
  1965  	time.Sleep(2 * time.Second)
  1966  
  1967  	server2 := fakestorage.New(c, "", fakecontext.WithFiles(files))
  1968  	defer server2.Close()
  1969  
  1970  	ctx2 := fakecontext.New(c, "", fakecontext.WithDockerfile(fmt.Sprintf(`FROM `+minimalBaseImage()+`
  1971          MAINTAINER dockerio
  1972          ADD %s/baz /usr/lib/baz/quux`, server2.URL())))
  1973  	defer ctx2.Close()
  1974  	cli.BuildCmd(c, name3, build.WithExternalBuildContext(ctx2))
  1975  	id3 := getIDByName(c, name3)
  1976  	if id1 != id3 {
  1977  		c.Fatal("The cache should have been used but wasn't")
  1978  	}
  1979  }
  1980  
  1981  // FIXME(vdemeester) this really seems to test the same thing as before (combined)
  1982  func (s *DockerCLIBuildSuite) TestBuildAddLocalAndRemoteFilesWithAndWithoutCache(c *testing.T) {
  1983  	const name = "testbuildaddlocalandremotefilewithcache"
  1984  	server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{
  1985  		"baz": "hello",
  1986  	}))
  1987  	defer server.Close()
  1988  
  1989  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(fmt.Sprintf(`FROM `+minimalBaseImage()+`
  1990          MAINTAINER dockerio
  1991          ADD foo /usr/lib/bla/bar
  1992          ADD %s/baz /usr/lib/baz/quux`, server.URL())),
  1993  		fakecontext.WithFiles(map[string]string{
  1994  			"foo": "hello world",
  1995  		}))
  1996  	defer ctx.Close()
  1997  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
  1998  	id1 := getIDByName(c, name)
  1999  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
  2000  	id2 := getIDByName(c, name)
  2001  	buildImageSuccessfully(c, name, build.WithoutCache, build.WithExternalBuildContext(ctx))
  2002  	id3 := getIDByName(c, name)
  2003  	if id1 != id2 {
  2004  		c.Fatal("The cache should have been used but hasn't.")
  2005  	}
  2006  	if id1 == id3 {
  2007  		c.Fatal("The cache should have been invalidated but hasn't.")
  2008  	}
  2009  }
  2010  
  2011  func testContextTar(c *testing.T, compression archive.Compression) {
  2012  	ctx := fakecontext.New(c, "",
  2013  		fakecontext.WithDockerfile(`FROM busybox
  2014  ADD foo /foo
  2015  CMD ["cat", "/foo"]`),
  2016  		fakecontext.WithFiles(map[string]string{
  2017  			"foo": "bar",
  2018  		}),
  2019  	)
  2020  	defer ctx.Close()
  2021  	context, err := archive.Tar(ctx.Dir, compression)
  2022  	if err != nil {
  2023  		c.Fatalf("failed to build context tar: %v", err)
  2024  	}
  2025  	const name = "contexttar"
  2026  
  2027  	cli.BuildCmd(c, name, build.WithStdinContext(context))
  2028  }
  2029  
  2030  func (s *DockerCLIBuildSuite) TestBuildContextTarGzip(c *testing.T) {
  2031  	testContextTar(c, archive.Gzip)
  2032  }
  2033  
  2034  func (s *DockerCLIBuildSuite) TestBuildContextTarNoCompression(c *testing.T) {
  2035  	testContextTar(c, archive.Uncompressed)
  2036  }
  2037  
  2038  func (s *DockerCLIBuildSuite) TestBuildNoContext(c *testing.T) {
  2039  	const name = "nocontext"
  2040  	icmd.RunCmd(icmd.Cmd{
  2041  		Command: []string{dockerBinary, "build", "-t", name, "-"},
  2042  		Stdin: strings.NewReader(
  2043  			`FROM busybox
  2044  			CMD ["echo", "ok"]`),
  2045  	}).Assert(c, icmd.Success)
  2046  
  2047  	if out := cli.DockerCmd(c, "run", "--rm", "nocontext").Combined(); out != "ok\n" {
  2048  		c.Fatalf("run produced invalid output: %q, expected %q", out, "ok")
  2049  	}
  2050  }
  2051  
  2052  // FIXME(vdemeester) migrate to docker/cli e2e
  2053  func (s *DockerCLIBuildSuite) TestBuildDockerfileStdin(c *testing.T) {
  2054  	const name = "stdindockerfile"
  2055  	tmpDir, err := os.MkdirTemp("", "fake-context")
  2056  	assert.NilError(c, err)
  2057  	err = os.WriteFile(filepath.Join(tmpDir, "foo"), []byte("bar"), 0o600)
  2058  	assert.NilError(c, err)
  2059  
  2060  	icmd.RunCmd(icmd.Cmd{
  2061  		Command: []string{dockerBinary, "build", "-t", name, "-f", "-", tmpDir},
  2062  		Stdin: strings.NewReader(
  2063  			`FROM busybox
  2064  ADD foo /foo
  2065  CMD ["cat", "/foo"]`),
  2066  	}).Assert(c, icmd.Success)
  2067  
  2068  	res := inspectField(c, name, "Config.Cmd")
  2069  	assert.Equal(c, strings.TrimSpace(res), `[cat /foo]`)
  2070  }
  2071  
  2072  // FIXME(vdemeester) migrate to docker/cli tests (unit or e2e)
  2073  func (s *DockerCLIBuildSuite) TestBuildDockerfileStdinConflict(c *testing.T) {
  2074  	const name = "stdindockerfiletarcontext"
  2075  	icmd.RunCmd(icmd.Cmd{
  2076  		Command: []string{dockerBinary, "build", "-t", name, "-f", "-", "-"},
  2077  	}).Assert(c, icmd.Expected{
  2078  		ExitCode: 1,
  2079  		Err:      "use stdin for both build context and dockerfile",
  2080  	})
  2081  }
  2082  
  2083  func (s *DockerCLIBuildSuite) TestBuildDockerfileStdinNoExtraFiles(c *testing.T) {
  2084  	s.testBuildDockerfileStdinNoExtraFiles(c, false, false)
  2085  }
  2086  
  2087  func (s *DockerCLIBuildSuite) TestBuildDockerfileStdinDockerignore(c *testing.T) {
  2088  	s.testBuildDockerfileStdinNoExtraFiles(c, true, false)
  2089  }
  2090  
  2091  func (s *DockerCLIBuildSuite) TestBuildDockerfileStdinDockerignoreIgnored(c *testing.T) {
  2092  	s.testBuildDockerfileStdinNoExtraFiles(c, true, true)
  2093  }
  2094  
  2095  func (s *DockerCLIBuildSuite) testBuildDockerfileStdinNoExtraFiles(c *testing.T, hasDockerignore, ignoreDockerignore bool) {
  2096  	const name = "stdindockerfilenoextra"
  2097  	tmpDir, err := os.MkdirTemp("", "fake-context")
  2098  	assert.NilError(c, err)
  2099  	defer os.RemoveAll(tmpDir)
  2100  
  2101  	writeFile := func(filename, content string) {
  2102  		err = os.WriteFile(filepath.Join(tmpDir, filename), []byte(content), 0o600)
  2103  		assert.NilError(c, err)
  2104  	}
  2105  
  2106  	writeFile("foo", "bar")
  2107  
  2108  	if hasDockerignore {
  2109  		// Add an empty Dockerfile to verify that it is not added to the image
  2110  		writeFile("Dockerfile", "")
  2111  
  2112  		ignores := "Dockerfile\n"
  2113  		if ignoreDockerignore {
  2114  			ignores += ".dockerignore\n"
  2115  		}
  2116  		writeFile(".dockerignore", ignores)
  2117  	}
  2118  
  2119  	result := icmd.RunCmd(icmd.Cmd{
  2120  		Command: []string{dockerBinary, "build", "-t", name, "-f", "-", tmpDir},
  2121  		Stdin: strings.NewReader(
  2122  			`FROM busybox
  2123  COPY . /baz`),
  2124  	})
  2125  	result.Assert(c, icmd.Success)
  2126  
  2127  	result = cli.DockerCmd(c, "run", "--rm", name, "ls", "-A", "/baz")
  2128  	if hasDockerignore && !ignoreDockerignore {
  2129  		assert.Equal(c, result.Stdout(), ".dockerignore\nfoo\n")
  2130  	} else {
  2131  		assert.Equal(c, result.Stdout(), "foo\n")
  2132  	}
  2133  }
  2134  
  2135  func (s *DockerCLIBuildSuite) TestBuildWithVolumeOwnership(c *testing.T) {
  2136  	testRequires(c, DaemonIsLinux)
  2137  	const name = "testbuildimg"
  2138  
  2139  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox:latest
  2140          RUN mkdir /test && chown daemon:wheel /test && chmod 0600 /test
  2141          VOLUME /test`))
  2142  
  2143  	out := cli.DockerCmd(c, "run", "--rm", "testbuildimg", "ls", "-la", "/test").Combined()
  2144  	if expected := "drw-------"; !strings.Contains(out, expected) {
  2145  		c.Fatalf("expected %s received %s", expected, out)
  2146  	}
  2147  	if expected := "daemon   wheel"; !strings.Contains(out, expected) {
  2148  		c.Fatalf("expected %s received %s", expected, out)
  2149  	}
  2150  }
  2151  
  2152  // testing #1405 - config.Cmd does not get cleaned up if
  2153  // utilizing cache
  2154  func (s *DockerCLIBuildSuite) TestBuildEntrypointRunCleanup(c *testing.T) {
  2155  	const name = "testbuildcmdcleanup"
  2156  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  2157          RUN echo "hello"`))
  2158  
  2159  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2160  		build.WithFile("Dockerfile", `FROM busybox
  2161          RUN echo "hello"
  2162          ADD foo /foo
  2163          ENTRYPOINT ["/bin/echo"]`),
  2164  		build.WithFile("foo", "hello")))
  2165  
  2166  	res := inspectField(c, name, "Config.Cmd")
  2167  	// Cmd must be cleaned up
  2168  	if res != "[]" {
  2169  		c.Fatalf("Cmd %s, expected nil", res)
  2170  	}
  2171  }
  2172  
  2173  func (s *DockerCLIBuildSuite) TestBuildAddFileNotFound(c *testing.T) {
  2174  	const name = "testbuildaddnotfound"
  2175  
  2176  	buildImage(name, build.WithBuildContext(c,
  2177  		build.WithFile("Dockerfile", `FROM `+minimalBaseImage()+`
  2178          ADD foo /usr/local/bar`),
  2179  		build.WithFile("bar", "hello"))).Assert(c, icmd.Expected{
  2180  		ExitCode: 1,
  2181  		Err:      "stat foo: file does not exist",
  2182  	})
  2183  }
  2184  
  2185  func (s *DockerCLIBuildSuite) TestBuildInheritance(c *testing.T) {
  2186  	testRequires(c, DaemonIsLinux)
  2187  	const name = "testbuildinheritance"
  2188  
  2189  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM scratch
  2190  		EXPOSE 2375`))
  2191  	ports1 := inspectField(c, name, "Config.ExposedPorts")
  2192  
  2193  	buildImageSuccessfully(c, name, build.WithDockerfile(fmt.Sprintf(`FROM %s
  2194  		ENTRYPOINT ["/bin/echo"]`, name)))
  2195  
  2196  	res := inspectField(c, name, "Config.Entrypoint")
  2197  	if expected := "[/bin/echo]"; res != expected {
  2198  		c.Fatalf("Entrypoint %s, expected %s", res, expected)
  2199  	}
  2200  	ports2 := inspectField(c, name, "Config.ExposedPorts")
  2201  	if ports1 != ports2 {
  2202  		c.Fatalf("Ports must be same: %s != %s", ports1, ports2)
  2203  	}
  2204  }
  2205  
  2206  func (s *DockerCLIBuildSuite) TestBuildFails(c *testing.T) {
  2207  	const name = "testbuildfails"
  2208  	buildImage(name, build.WithDockerfile(`FROM busybox
  2209  		RUN sh -c "exit 23"`)).Assert(c, icmd.Expected{
  2210  		ExitCode: 23,
  2211  		Err:      "returned a non-zero code: 23",
  2212  	})
  2213  }
  2214  
  2215  func (s *DockerCLIBuildSuite) TestBuildOnBuild(c *testing.T) {
  2216  	const name = "testbuildonbuild"
  2217  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  2218  		ONBUILD RUN touch foobar`))
  2219  	buildImageSuccessfully(c, name, build.WithDockerfile(fmt.Sprintf(`FROM %s
  2220  		RUN [ -f foobar ]`, name)))
  2221  }
  2222  
  2223  // gh #2446
  2224  func (s *DockerCLIBuildSuite) TestBuildAddToSymlinkDest(c *testing.T) {
  2225  	makeLink := `ln -s /foo /bar`
  2226  	if testEnv.DaemonInfo.OSType == "windows" {
  2227  		makeLink = `mklink /D C:\bar C:\foo`
  2228  	}
  2229  	const name = "testbuildaddtosymlinkdest"
  2230  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2231  		build.WithFile("Dockerfile", `
  2232  		FROM busybox
  2233  		RUN sh -c "mkdir /foo"
  2234  		RUN `+makeLink+`
  2235  		ADD foo /bar/
  2236  		RUN sh -c "[ -f /bar/foo ]"
  2237  		RUN sh -c "[ -f /foo/foo ]"`),
  2238  		build.WithFile("foo", "hello"),
  2239  	))
  2240  }
  2241  
  2242  func (s *DockerCLIBuildSuite) TestBuildEscapeWhitespace(c *testing.T) {
  2243  	const name = "testbuildescapewhitespace"
  2244  
  2245  	buildImageSuccessfully(c, name, build.WithDockerfile(`
  2246    # ESCAPE=\
  2247    FROM busybox
  2248    MAINTAINER "Docker \
  2249  IO <io@\
  2250  docker.com>"
  2251    `))
  2252  
  2253  	res := inspectField(c, name, "Author")
  2254  	if res != `"Docker IO <io@docker.com>"` {
  2255  		c.Fatalf("Parsed string did not match the escaped string. Got: %q", res)
  2256  	}
  2257  }
  2258  
  2259  func (s *DockerCLIBuildSuite) TestBuildVerifyIntString(c *testing.T) {
  2260  	// Verify that strings that look like ints are still passed as strings
  2261  	const name = "testbuildstringing"
  2262  
  2263  	buildImageSuccessfully(c, name, build.WithDockerfile(`
  2264  	FROM busybox
  2265  	MAINTAINER 123`))
  2266  
  2267  	out := cli.DockerCmd(c, "inspect", name).Stdout()
  2268  	if !strings.Contains(out, `"123"`) {
  2269  		c.Fatalf("Output does not contain the int as a string:\n%s", out)
  2270  	}
  2271  }
  2272  
  2273  func (s *DockerCLIBuildSuite) TestBuildDockerignore(c *testing.T) {
  2274  	const name = "testbuilddockerignore"
  2275  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2276  		build.WithFile("Dockerfile", `
  2277  		FROM busybox
  2278  		 ADD . /bla
  2279  		RUN sh -c "[[ -f /bla/src/x.go ]]"
  2280  		RUN sh -c "[[ -f /bla/Makefile ]]"
  2281  		RUN sh -c "[[ ! -e /bla/src/_vendor ]]"
  2282  		RUN sh -c "[[ ! -e /bla/.gitignore ]]"
  2283  		RUN sh -c "[[ ! -e /bla/README.md ]]"
  2284  		RUN sh -c "[[ ! -e /bla/dir/foo ]]"
  2285  		RUN sh -c "[[ ! -e /bla/foo ]]"
  2286  		RUN sh -c "[[ ! -e /bla/.git ]]"
  2287  		RUN sh -c "[[ ! -e v.cc ]]"
  2288  		RUN sh -c "[[ ! -e src/v.cc ]]"
  2289  		RUN sh -c "[[ ! -e src/_vendor/v.cc ]]"`),
  2290  		build.WithFile("Makefile", "all:"),
  2291  		build.WithFile(".git/HEAD", "ref: foo"),
  2292  		build.WithFile("src/x.go", "package main"),
  2293  		build.WithFile("src/_vendor/v.go", "package main"),
  2294  		build.WithFile("src/_vendor/v.cc", "package main"),
  2295  		build.WithFile("src/v.cc", "package main"),
  2296  		build.WithFile("v.cc", "package main"),
  2297  		build.WithFile("dir/foo", ""),
  2298  		build.WithFile(".gitignore", ""),
  2299  		build.WithFile("README.md", "readme"),
  2300  		build.WithFile(".dockerignore", `
  2301  .git
  2302  pkg
  2303  .gitignore
  2304  src/_vendor
  2305  *.md
  2306  **/*.cc
  2307  dir`),
  2308  	))
  2309  }
  2310  
  2311  func (s *DockerCLIBuildSuite) TestBuildDockerignoreCleanPaths(c *testing.T) {
  2312  	const name = "testbuilddockerignorecleanpaths"
  2313  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2314  		build.WithFile("Dockerfile", `
  2315          FROM busybox
  2316          ADD . /tmp/
  2317          RUN sh -c "(! ls /tmp/foo) && (! ls /tmp/foo2) && (! ls /tmp/dir1/foo)"`),
  2318  		build.WithFile("foo", "foo"),
  2319  		build.WithFile("foo2", "foo2"),
  2320  		build.WithFile("dir1/foo", "foo in dir1"),
  2321  		build.WithFile(".dockerignore", "./foo\ndir1//foo\n./dir1/../foo2"),
  2322  	))
  2323  }
  2324  
  2325  func (s *DockerCLIBuildSuite) TestBuildDockerignoreExceptions(c *testing.T) {
  2326  	const name = "testbuilddockerignoreexceptions"
  2327  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2328  		build.WithFile("Dockerfile", `
  2329  		FROM busybox
  2330  		ADD . /bla
  2331  		RUN sh -c "[[ -f /bla/src/x.go ]]"
  2332  		RUN sh -c "[[ -f /bla/Makefile ]]"
  2333  		RUN sh -c "[[ ! -e /bla/src/_vendor ]]"
  2334  		RUN sh -c "[[ ! -e /bla/.gitignore ]]"
  2335  		RUN sh -c "[[ ! -e /bla/README.md ]]"
  2336  		RUN sh -c "[[  -e /bla/dir/dir/foo ]]"
  2337  		RUN sh -c "[[ ! -e /bla/dir/foo1 ]]"
  2338  		RUN sh -c "[[ -f /bla/dir/e ]]"
  2339  		RUN sh -c "[[ -f /bla/dir/e-dir/foo ]]"
  2340  		RUN sh -c "[[ ! -e /bla/foo ]]"
  2341  		RUN sh -c "[[ ! -e /bla/.git ]]"
  2342  		RUN sh -c "[[ -e /bla/dir/a.cc ]]"`),
  2343  		build.WithFile("Makefile", "all:"),
  2344  		build.WithFile(".git/HEAD", "ref: foo"),
  2345  		build.WithFile("src/x.go", "package main"),
  2346  		build.WithFile("src/_vendor/v.go", "package main"),
  2347  		build.WithFile("dir/foo", ""),
  2348  		build.WithFile("dir/foo1", ""),
  2349  		build.WithFile("dir/dir/f1", ""),
  2350  		build.WithFile("dir/dir/foo", ""),
  2351  		build.WithFile("dir/e", ""),
  2352  		build.WithFile("dir/e-dir/foo", ""),
  2353  		build.WithFile(".gitignore", ""),
  2354  		build.WithFile("README.md", "readme"),
  2355  		build.WithFile("dir/a.cc", "hello"),
  2356  		build.WithFile(".dockerignore", `
  2357  .git
  2358  pkg
  2359  .gitignore
  2360  src/_vendor
  2361  *.md
  2362  dir
  2363  !dir/e*
  2364  !dir/dir/foo
  2365  **/*.cc
  2366  !**/*.cc`),
  2367  	))
  2368  }
  2369  
  2370  func (s *DockerCLIBuildSuite) TestBuildDockerignoringDockerfile(c *testing.T) {
  2371  	const name = "testbuilddockerignoredockerfile"
  2372  	dockerfile := `
  2373  		FROM busybox
  2374  		ADD . /tmp/
  2375  		RUN sh -c "! ls /tmp/Dockerfile"
  2376  		RUN ls /tmp/.dockerignore`
  2377  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2378  		build.WithFile("Dockerfile", dockerfile),
  2379  		build.WithFile(".dockerignore", "Dockerfile\n"),
  2380  	))
  2381  	// FIXME(vdemeester) why twice ?
  2382  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2383  		build.WithFile("Dockerfile", dockerfile),
  2384  		build.WithFile(".dockerignore", "./Dockerfile\n"),
  2385  	))
  2386  }
  2387  
  2388  func (s *DockerCLIBuildSuite) TestBuildDockerignoringRenamedDockerfile(c *testing.T) {
  2389  	const name = "testbuilddockerignoredockerfile"
  2390  	dockerfile := `
  2391  		FROM busybox
  2392  		ADD . /tmp/
  2393  		RUN ls /tmp/Dockerfile
  2394  		RUN sh -c "! ls /tmp/MyDockerfile"
  2395  		RUN ls /tmp/.dockerignore`
  2396  	buildImageSuccessfully(c, name, cli.WithFlags("-f", "MyDockerfile"), build.WithBuildContext(c,
  2397  		build.WithFile("Dockerfile", "Should not use me"),
  2398  		build.WithFile("MyDockerfile", dockerfile),
  2399  		build.WithFile(".dockerignore", "MyDockerfile\n"),
  2400  	))
  2401  	// FIXME(vdemeester) why twice ?
  2402  	buildImageSuccessfully(c, name, cli.WithFlags("-f", "MyDockerfile"), build.WithBuildContext(c,
  2403  		build.WithFile("Dockerfile", "Should not use me"),
  2404  		build.WithFile("MyDockerfile", dockerfile),
  2405  		build.WithFile(".dockerignore", "./MyDockerfile\n"),
  2406  	))
  2407  }
  2408  
  2409  func (s *DockerCLIBuildSuite) TestBuildDockerignoringDockerignore(c *testing.T) {
  2410  	const name = "testbuilddockerignoredockerignore"
  2411  	dockerfile := `
  2412  		FROM busybox
  2413  		ADD . /tmp/
  2414  		RUN sh -c "! ls /tmp/.dockerignore"
  2415  		RUN ls /tmp/Dockerfile`
  2416  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2417  		build.WithFile("Dockerfile", dockerfile),
  2418  		build.WithFile(".dockerignore", ".dockerignore\n"),
  2419  	))
  2420  }
  2421  
  2422  func (s *DockerCLIBuildSuite) TestBuildDockerignoreTouchDockerfile(c *testing.T) {
  2423  	const name = "testbuilddockerignoretouchdockerfile"
  2424  	dockerfile := `
  2425          FROM busybox
  2426  		ADD . /tmp/`
  2427  	ctx := fakecontext.New(c, "",
  2428  		fakecontext.WithDockerfile(dockerfile),
  2429  		fakecontext.WithFiles(map[string]string{
  2430  			".dockerignore": "Dockerfile\n",
  2431  		}))
  2432  	defer ctx.Close()
  2433  
  2434  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  2435  	id1 := getIDByName(c, name)
  2436  
  2437  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  2438  	id2 := getIDByName(c, name)
  2439  	if id1 != id2 {
  2440  		c.Fatalf("Didn't use the cache - 1")
  2441  	}
  2442  
  2443  	// Now make sure touching Dockerfile doesn't invalidate the cache
  2444  	if err := ctx.Add("Dockerfile", dockerfile+"\n# hi"); err != nil {
  2445  		c.Fatalf("Didn't add Dockerfile: %s", err)
  2446  	}
  2447  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  2448  	id2 = getIDByName(c, name)
  2449  	if id1 != id2 {
  2450  		c.Fatalf("Didn't use the cache - 2")
  2451  	}
  2452  
  2453  	// One more time but just 'touch' it instead of changing the content
  2454  	if err := ctx.Add("Dockerfile", dockerfile+"\n# hi"); err != nil {
  2455  		c.Fatalf("Didn't add Dockerfile: %s", err)
  2456  	}
  2457  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  2458  	id2 = getIDByName(c, name)
  2459  	if id1 != id2 {
  2460  		c.Fatalf("Didn't use the cache - 3")
  2461  	}
  2462  }
  2463  
  2464  func (s *DockerCLIBuildSuite) TestBuildDockerignoringWholeDir(c *testing.T) {
  2465  	const name = "testbuilddockerignorewholedir"
  2466  
  2467  	dockerfile := `
  2468  		FROM busybox
  2469  		COPY . /
  2470  		RUN sh -c "[[ ! -e /.gitignore ]]"
  2471  		RUN sh -c "[[ ! -e /Makefile ]]"`
  2472  
  2473  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2474  		build.WithFile("Dockerfile", dockerfile),
  2475  		build.WithFile(".dockerignore", "*\n"),
  2476  		build.WithFile("Makefile", "all:"),
  2477  		build.WithFile(".gitignore", ""),
  2478  	))
  2479  }
  2480  
  2481  func (s *DockerCLIBuildSuite) TestBuildDockerignoringOnlyDotfiles(c *testing.T) {
  2482  	const name = "testbuilddockerignorewholedir"
  2483  
  2484  	dockerfile := `
  2485  		FROM busybox
  2486  		COPY . /
  2487  		RUN sh -c "[[ ! -e /.gitignore ]]"
  2488  		RUN sh -c "[[ -f /Makefile ]]"`
  2489  
  2490  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2491  		build.WithFile("Dockerfile", dockerfile),
  2492  		build.WithFile(".dockerignore", ".*"),
  2493  		build.WithFile("Makefile", "all:"),
  2494  		build.WithFile(".gitignore", ""),
  2495  	))
  2496  }
  2497  
  2498  func (s *DockerCLIBuildSuite) TestBuildDockerignoringBadExclusion(c *testing.T) {
  2499  	const name = "testbuilddockerignorebadexclusion"
  2500  	buildImage(name, build.WithBuildContext(c,
  2501  		build.WithFile("Dockerfile", `
  2502  		FROM busybox
  2503  		COPY . /
  2504  		RUN sh -c "[[ ! -e /.gitignore ]]"
  2505  		RUN sh -c "[[ -f /Makefile ]]"`),
  2506  		build.WithFile("Makefile", "all:"),
  2507  		build.WithFile(".gitignore", ""),
  2508  		build.WithFile(".dockerignore", "!\n"),
  2509  	)).Assert(c, icmd.Expected{
  2510  		ExitCode: 1,
  2511  		Err:      `illegal exclusion pattern: "!"`,
  2512  	})
  2513  }
  2514  
  2515  func (s *DockerCLIBuildSuite) TestBuildDockerignoringWildTopDir(c *testing.T) {
  2516  	dockerfile := `
  2517  		FROM busybox
  2518  		COPY . /
  2519  		RUN sh -c "[[ ! -e /.dockerignore ]]"
  2520  		RUN sh -c "[[ ! -e /Dockerfile ]]"
  2521  		RUN sh -c "[[ ! -e /file1 ]]"
  2522  		RUN sh -c "[[ ! -e /dir ]]"`
  2523  
  2524  	// All of these should result in ignoring all files
  2525  	for _, variant := range []string{"**", "**/", "**/**", "*"} {
  2526  		buildImageSuccessfully(c, "noname", build.WithBuildContext(c,
  2527  			build.WithFile("Dockerfile", dockerfile),
  2528  			build.WithFile("file1", ""),
  2529  			build.WithFile("dir/file1", ""),
  2530  			build.WithFile(".dockerignore", variant),
  2531  		))
  2532  
  2533  		cli.DockerCmd(c, "rmi", "noname")
  2534  	}
  2535  }
  2536  
  2537  func (s *DockerCLIBuildSuite) TestBuildDockerignoringWildDirs(c *testing.T) {
  2538  	dockerfile := `
  2539          FROM busybox
  2540  		COPY . /
  2541  		#RUN sh -c "[[ -e /.dockerignore ]]"
  2542  		RUN sh -c "[[ -e /Dockerfile ]]           && \
  2543  		           [[ ! -e /file0 ]]              && \
  2544  		           [[ ! -e /dir1/file0 ]]         && \
  2545  		           [[ ! -e /dir2/file0 ]]         && \
  2546  		           [[ ! -e /file1 ]]              && \
  2547  		           [[ ! -e /dir1/file1 ]]         && \
  2548  		           [[ ! -e /dir1/dir2/file1 ]]    && \
  2549  		           [[ ! -e /dir1/file2 ]]         && \
  2550  		           [[   -e /dir1/dir2/file2 ]]    && \
  2551  		           [[ ! -e /dir1/dir2/file4 ]]    && \
  2552  		           [[ ! -e /dir1/dir2/file5 ]]    && \
  2553  		           [[ ! -e /dir1/dir2/file6 ]]    && \
  2554  		           [[ ! -e /dir1/dir3/file7 ]]    && \
  2555  		           [[ ! -e /dir1/dir3/file8 ]]    && \
  2556  		           [[   -e /dir1/dir3 ]]          && \
  2557  		           [[   -e /dir1/dir4 ]]          && \
  2558  		           [[ ! -e 'dir1/dir5/fileAA' ]]  && \
  2559  		           [[   -e 'dir1/dir5/fileAB' ]]  && \
  2560  		           [[   -e 'dir1/dir5/fileB' ]]"   # "." in pattern means nothing
  2561  
  2562  		RUN echo all done!`
  2563  
  2564  	dockerignore := `
  2565  **/file0
  2566  **/*file1
  2567  **/dir1/file2
  2568  dir1/**/file4
  2569  **/dir2/file5
  2570  **/dir1/dir2/file6
  2571  dir1/dir3/**
  2572  **/dir4/**
  2573  **/file?A
  2574  **/file\?B
  2575  **/dir5/file.
  2576  `
  2577  
  2578  	buildImageSuccessfully(c, "noname", build.WithBuildContext(c,
  2579  		build.WithFile("Dockerfile", dockerfile),
  2580  		build.WithFile(".dockerignore", dockerignore),
  2581  		build.WithFile("dir1/file0", ""),
  2582  		build.WithFile("dir1/dir2/file0", ""),
  2583  		build.WithFile("file1", ""),
  2584  		build.WithFile("dir1/file1", ""),
  2585  		build.WithFile("dir1/dir2/file1", ""),
  2586  		build.WithFile("dir1/file2", ""),
  2587  		build.WithFile("dir1/dir2/file2", ""), // remains
  2588  		build.WithFile("dir1/dir2/file4", ""),
  2589  		build.WithFile("dir1/dir2/file5", ""),
  2590  		build.WithFile("dir1/dir2/file6", ""),
  2591  		build.WithFile("dir1/dir3/file7", ""),
  2592  		build.WithFile("dir1/dir3/file8", ""),
  2593  		build.WithFile("dir1/dir4/file9", ""),
  2594  		build.WithFile("dir1/dir5/fileAA", ""),
  2595  		build.WithFile("dir1/dir5/fileAB", ""),
  2596  		build.WithFile("dir1/dir5/fileB", ""),
  2597  	))
  2598  }
  2599  
  2600  func (s *DockerCLIBuildSuite) TestBuildLineBreak(c *testing.T) {
  2601  	testRequires(c, DaemonIsLinux)
  2602  	const name = "testbuildlinebreak"
  2603  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM  busybox
  2604  RUN    sh -c 'echo root:testpass \
  2605  	> /tmp/passwd'
  2606  RUN    mkdir -p /var/run/sshd
  2607  RUN    sh -c "[ "$(cat /tmp/passwd)" = "root:testpass" ]"
  2608  RUN    sh -c "[ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]"`))
  2609  }
  2610  
  2611  func (s *DockerCLIBuildSuite) TestBuildEOLInLine(c *testing.T) {
  2612  	testRequires(c, DaemonIsLinux)
  2613  	const name = "testbuildeolinline"
  2614  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM   busybox
  2615  RUN    sh -c 'echo root:testpass > /tmp/passwd'
  2616  RUN    echo "foo \n bar"; echo "baz"
  2617  RUN    mkdir -p /var/run/sshd
  2618  RUN    sh -c "[ "$(cat /tmp/passwd)" = "root:testpass" ]"
  2619  RUN    sh -c "[ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]"`))
  2620  }
  2621  
  2622  func (s *DockerCLIBuildSuite) TestBuildCommentsShebangs(c *testing.T) {
  2623  	testRequires(c, DaemonIsLinux)
  2624  	const name = "testbuildcomments"
  2625  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  2626  # This is an ordinary comment.
  2627  RUN { echo '#!/bin/sh'; echo 'echo hello world'; } > /hello.sh
  2628  RUN [ ! -x /hello.sh ]
  2629  # comment with line break \
  2630  RUN chmod +x /hello.sh
  2631  RUN [ -x /hello.sh ]
  2632  RUN [ "$(cat /hello.sh)" = $'#!/bin/sh\necho hello world' ]
  2633  RUN [ "$(/hello.sh)" = "hello world" ]`))
  2634  }
  2635  
  2636  func (s *DockerCLIBuildSuite) TestBuildUsersAndGroups(c *testing.T) {
  2637  	testRequires(c, DaemonIsLinux)
  2638  	const name = "testbuildusers"
  2639  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  2640  
  2641  # Make sure our defaults work
  2642  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)" = '0:0/root:root' ]
  2643  
  2644  # TODO decide if "args.user = strconv.Itoa(syscall.Getuid())" is acceptable behavior for changeUser in sysvinit instead of "return nil" when "USER" isn't specified (so that we get the proper group list even if that is the empty list, even in the default case of not supplying an explicit USER to run as, which implies USER 0)
  2645  USER root
  2646  RUN [ "$(id -G):$(id -Gn)" = '0 10:root wheel' ]
  2647  
  2648  # Setup dockerio user and group
  2649  RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd && \
  2650  	echo 'dockerio:x:1001:' >> /etc/group
  2651  
  2652  # Make sure we can switch to our user and all the information is exactly as we expect it to be
  2653  USER dockerio
  2654  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ]
  2655  
  2656  # Switch back to root and double check that worked exactly as we might expect it to
  2657  USER root
  2658  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '0:0/root:root/0 10:root wheel' ] && \
  2659          # Add a "supplementary" group for our dockerio user
  2660  	echo 'supplementary:x:1002:dockerio' >> /etc/group
  2661  
  2662  # ... and then go verify that we get it like we expect
  2663  USER dockerio
  2664  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001 1002:dockerio supplementary' ]
  2665  USER 1001
  2666  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001 1002:dockerio supplementary' ]
  2667  
  2668  # super test the new "user:group" syntax
  2669  USER dockerio:dockerio
  2670  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ]
  2671  USER 1001:dockerio
  2672  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ]
  2673  USER dockerio:1001
  2674  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ]
  2675  USER 1001:1001
  2676  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ]
  2677  USER dockerio:supplementary
  2678  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ]
  2679  USER dockerio:1002
  2680  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ]
  2681  USER 1001:supplementary
  2682  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ]
  2683  USER 1001:1002
  2684  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ]
  2685  
  2686  # make sure unknown uid/gid still works properly
  2687  USER 1042:1043
  2688  RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1042:1043/1042:1043/1043:1043' ]`))
  2689  }
  2690  
  2691  // FIXME(vdemeester) rename this test (and probably "merge" it with the one below TestBuildEnvUsage2)
  2692  func (s *DockerCLIBuildSuite) TestBuildEnvUsage(c *testing.T) {
  2693  	// /docker/world/hello is not owned by the correct user
  2694  	testRequires(c, NotUserNamespace)
  2695  	testRequires(c, DaemonIsLinux)
  2696  	const name = "testbuildenvusage"
  2697  	dockerfile := `FROM busybox
  2698  ENV    HOME /root
  2699  ENV    PATH $HOME/bin:$PATH
  2700  ENV    PATH /tmp:$PATH
  2701  RUN    [ "$PATH" = "/tmp:$HOME/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ]
  2702  ENV    FOO /foo/baz
  2703  ENV    BAR /bar
  2704  ENV    BAZ $BAR
  2705  ENV    FOOPATH $PATH:$FOO
  2706  RUN    [ "$BAR" = "$BAZ" ]
  2707  RUN    [ "$FOOPATH" = "$PATH:/foo/baz" ]
  2708  ENV    FROM hello/docker/world
  2709  ENV    TO /docker/world/hello
  2710  ADD    $FROM $TO
  2711  RUN    [ "$(cat $TO)" = "hello" ]
  2712  ENV    abc=def
  2713  ENV    ghi=$abc
  2714  RUN    [ "$ghi" = "def" ]
  2715  `
  2716  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2717  		build.WithFile("Dockerfile", dockerfile),
  2718  		build.WithFile("hello/docker/world", "hello"),
  2719  	))
  2720  }
  2721  
  2722  // FIXME(vdemeester) rename this test (and probably "merge" it with the one above TestBuildEnvUsage)
  2723  func (s *DockerCLIBuildSuite) TestBuildEnvUsage2(c *testing.T) {
  2724  	// /docker/world/hello is not owned by the correct user
  2725  	testRequires(c, NotUserNamespace)
  2726  	testRequires(c, DaemonIsLinux)
  2727  	const name = "testbuildenvusage2"
  2728  	dockerfile := `FROM busybox
  2729  ENV    abc=def def="hello world"
  2730  RUN    [ "$abc,$def" = "def,hello world" ]
  2731  ENV    def=hello\ world v1=abc v2="hi there" v3='boogie nights' v4="with'quotes too"
  2732  RUN    [ "$def,$v1,$v2,$v3,$v4" = "hello world,abc,hi there,boogie nights,with'quotes too" ]
  2733  ENV    abc=zzz FROM=hello/docker/world
  2734  ENV    abc=zzz TO=/docker/world/hello
  2735  ADD    $FROM $TO
  2736  RUN    [ "$abc,$(cat $TO)" = "zzz,hello" ]
  2737  ENV    abc 'yyy'
  2738  RUN    [ $abc = 'yyy' ]
  2739  ENV    abc=
  2740  RUN    [ "$abc" = "" ]
  2741  
  2742  # use grep to make sure if the builder substitutes \$foo by mistake
  2743  # we don't get a false positive
  2744  ENV    abc=\$foo
  2745  RUN    [ "$abc" = "\$foo" ] && (echo "$abc" | grep foo)
  2746  ENV    abc \$foo
  2747  RUN    [ "$abc" = "\$foo" ] && (echo "$abc" | grep foo)
  2748  
  2749  ENV    abc=\'foo\' abc2=\"foo\"
  2750  RUN    [ "$abc,$abc2" = "'foo',\"foo\"" ]
  2751  ENV    abc "foo"
  2752  RUN    [ "$abc" = "foo" ]
  2753  ENV    abc 'foo'
  2754  RUN    [ "$abc" = 'foo' ]
  2755  ENV    abc \'foo\'
  2756  RUN    [ "$abc" = "'foo'" ]
  2757  ENV    abc \"foo\"
  2758  RUN    [ "$abc" = '"foo"' ]
  2759  
  2760  ENV    abc=ABC
  2761  RUN    [ "$abc" = "ABC" ]
  2762  ENV    def1=${abc:-DEF} def2=${ccc:-DEF}
  2763  ENV    def3=${ccc:-${def2}xx} def4=${abc:+ALT} def5=${def2:+${abc}:} def6=${ccc:-\$abc:} def7=${ccc:-\${abc}:}
  2764  RUN    [ "$def1,$def2,$def3,$def4,$def5,$def6,$def7" = 'ABC,DEF,DEFxx,ALT,ABC:,$abc:,${abc:}' ]
  2765  ENV    mypath=${mypath:+$mypath:}/home
  2766  ENV    mypath=${mypath:+$mypath:}/away
  2767  RUN    [ "$mypath" = '/home:/away' ]
  2768  
  2769  ENV    e1=bar
  2770  ENV    e2=$e1 e3=$e11 e4=\$e1 e5=\$e11
  2771  RUN    [ "$e0,$e1,$e2,$e3,$e4,$e5" = ',bar,bar,,$e1,$e11' ]
  2772  
  2773  ENV    ee1 bar
  2774  ENV    ee2 $ee1
  2775  ENV    ee3 $ee11
  2776  ENV    ee4 \$ee1
  2777  ENV    ee5 \$ee11
  2778  RUN    [ "$ee1,$ee2,$ee3,$ee4,$ee5" = 'bar,bar,,$ee1,$ee11' ]
  2779  
  2780  ENV    eee1="foo" eee2='foo'
  2781  ENV    eee3 "foo"
  2782  ENV    eee4 'foo'
  2783  RUN    [ "$eee1,$eee2,$eee3,$eee4" = 'foo,foo,foo,foo' ]
  2784  
  2785  `
  2786  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2787  		build.WithFile("Dockerfile", dockerfile),
  2788  		build.WithFile("hello/docker/world", "hello"),
  2789  	))
  2790  }
  2791  
  2792  func (s *DockerCLIBuildSuite) TestBuildAddScript(c *testing.T) {
  2793  	testRequires(c, DaemonIsLinux)
  2794  	const name = "testbuildaddscript"
  2795  	dockerfile := `
  2796  FROM busybox
  2797  ADD test /test
  2798  RUN ["chmod","+x","/test"]
  2799  RUN ["/test"]
  2800  RUN [ "$(cat /testfile)" = 'test!' ]`
  2801  
  2802  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2803  		build.WithFile("Dockerfile", dockerfile),
  2804  		build.WithFile("test", "#!/bin/sh\necho 'test!' > /testfile"),
  2805  	))
  2806  }
  2807  
  2808  func (s *DockerCLIBuildSuite) TestBuildAddTar(c *testing.T) {
  2809  	// /test/foo is not owned by the correct user
  2810  	testRequires(c, NotUserNamespace)
  2811  	const name = "testbuildaddtar"
  2812  
  2813  	ctx := func() *fakecontext.Fake {
  2814  		dockerfile := `
  2815  FROM busybox
  2816  ADD test.tar /
  2817  RUN cat /test/foo | grep Hi
  2818  ADD test.tar /test.tar
  2819  RUN cat /test.tar/test/foo | grep Hi
  2820  ADD test.tar /unlikely-to-exist
  2821  RUN cat /unlikely-to-exist/test/foo | grep Hi
  2822  ADD test.tar /unlikely-to-exist-trailing-slash/
  2823  RUN cat /unlikely-to-exist-trailing-slash/test/foo | grep Hi
  2824  RUN sh -c "mkdir /existing-directory" #sh -c is needed on Windows to use the correct mkdir
  2825  ADD test.tar /existing-directory
  2826  RUN cat /existing-directory/test/foo | grep Hi
  2827  ADD test.tar /existing-directory-trailing-slash/
  2828  RUN cat /existing-directory-trailing-slash/test/foo | grep Hi`
  2829  		tmpDir, err := os.MkdirTemp("", "fake-context")
  2830  		assert.NilError(c, err)
  2831  		testTar, err := os.Create(filepath.Join(tmpDir, "test.tar"))
  2832  		if err != nil {
  2833  			c.Fatalf("failed to create test.tar archive: %v", err)
  2834  		}
  2835  		defer testTar.Close()
  2836  
  2837  		tw := tar.NewWriter(testTar)
  2838  
  2839  		if err := tw.WriteHeader(&tar.Header{
  2840  			Name: "test/foo",
  2841  			Size: 2,
  2842  		}); err != nil {
  2843  			c.Fatalf("failed to write tar file header: %v", err)
  2844  		}
  2845  		if _, err := tw.Write([]byte("Hi")); err != nil {
  2846  			c.Fatalf("failed to write tar file content: %v", err)
  2847  		}
  2848  		if err := tw.Close(); err != nil {
  2849  			c.Fatalf("failed to close tar archive: %v", err)
  2850  		}
  2851  
  2852  		if err := os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0o644); err != nil {
  2853  			c.Fatalf("failed to open destination dockerfile: %v", err)
  2854  		}
  2855  		return fakecontext.New(c, tmpDir)
  2856  	}()
  2857  	defer ctx.Close()
  2858  
  2859  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
  2860  }
  2861  
  2862  func (s *DockerCLIBuildSuite) TestBuildAddBrokenTar(c *testing.T) {
  2863  	const name = "testbuildaddbrokentar"
  2864  
  2865  	ctx := func() *fakecontext.Fake {
  2866  		dockerfile := `
  2867  FROM busybox
  2868  ADD test.tar /`
  2869  		tmpDir, err := os.MkdirTemp("", "fake-context")
  2870  		assert.NilError(c, err)
  2871  		testTar, err := os.Create(filepath.Join(tmpDir, "test.tar"))
  2872  		if err != nil {
  2873  			c.Fatalf("failed to create test.tar archive: %v", err)
  2874  		}
  2875  		defer testTar.Close()
  2876  
  2877  		tw := tar.NewWriter(testTar)
  2878  
  2879  		if err := tw.WriteHeader(&tar.Header{
  2880  			Name: "test/foo",
  2881  			Size: 2,
  2882  		}); err != nil {
  2883  			c.Fatalf("failed to write tar file header: %v", err)
  2884  		}
  2885  		if _, err := tw.Write([]byte("Hi")); err != nil {
  2886  			c.Fatalf("failed to write tar file content: %v", err)
  2887  		}
  2888  		if err := tw.Close(); err != nil {
  2889  			c.Fatalf("failed to close tar archive: %v", err)
  2890  		}
  2891  
  2892  		// Corrupt the tar by removing one byte off the end
  2893  		stat, err := testTar.Stat()
  2894  		if err != nil {
  2895  			c.Fatalf("failed to stat tar archive: %v", err)
  2896  		}
  2897  		if err := testTar.Truncate(stat.Size() - 1); err != nil {
  2898  			c.Fatalf("failed to truncate tar archive: %v", err)
  2899  		}
  2900  
  2901  		if err := os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0o644); err != nil {
  2902  			c.Fatalf("failed to open destination dockerfile: %v", err)
  2903  		}
  2904  		return fakecontext.New(c, tmpDir)
  2905  	}()
  2906  	defer ctx.Close()
  2907  
  2908  	buildImage(name, build.WithExternalBuildContext(ctx)).Assert(c, icmd.Expected{
  2909  		ExitCode: 1,
  2910  	})
  2911  }
  2912  
  2913  func (s *DockerCLIBuildSuite) TestBuildAddNonTar(c *testing.T) {
  2914  	const name = "testbuildaddnontar"
  2915  
  2916  	// Should not try to extract test.tar
  2917  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  2918  		build.WithFile("Dockerfile", `
  2919  		FROM busybox
  2920  		ADD test.tar /
  2921  		RUN test -f /test.tar`),
  2922  		build.WithFile("test.tar", "not_a_tar_file"),
  2923  	))
  2924  }
  2925  
  2926  func (s *DockerCLIBuildSuite) TestBuildAddTarXz(c *testing.T) {
  2927  	// /test/foo is not owned by the correct user
  2928  	testRequires(c, NotUserNamespace)
  2929  	testRequires(c, DaemonIsLinux)
  2930  	const name = "testbuildaddtarxz"
  2931  
  2932  	ctx := func() *fakecontext.Fake {
  2933  		dockerfile := `
  2934  			FROM busybox
  2935  			ADD test.tar.xz /
  2936  			RUN cat /test/foo | grep Hi`
  2937  		tmpDir, err := os.MkdirTemp("", "fake-context")
  2938  		assert.NilError(c, err)
  2939  		testTar, err := os.Create(filepath.Join(tmpDir, "test.tar"))
  2940  		if err != nil {
  2941  			c.Fatalf("failed to create test.tar archive: %v", err)
  2942  		}
  2943  		defer testTar.Close()
  2944  
  2945  		tw := tar.NewWriter(testTar)
  2946  
  2947  		if err := tw.WriteHeader(&tar.Header{
  2948  			Name: "test/foo",
  2949  			Size: 2,
  2950  		}); err != nil {
  2951  			c.Fatalf("failed to write tar file header: %v", err)
  2952  		}
  2953  		if _, err := tw.Write([]byte("Hi")); err != nil {
  2954  			c.Fatalf("failed to write tar file content: %v", err)
  2955  		}
  2956  		if err := tw.Close(); err != nil {
  2957  			c.Fatalf("failed to close tar archive: %v", err)
  2958  		}
  2959  
  2960  		icmd.RunCmd(icmd.Cmd{
  2961  			Command: []string{"xz", "-k", "test.tar"},
  2962  			Dir:     tmpDir,
  2963  		}).Assert(c, icmd.Success)
  2964  		if err := os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0o644); err != nil {
  2965  			c.Fatalf("failed to open destination dockerfile: %v", err)
  2966  		}
  2967  		return fakecontext.New(c, tmpDir)
  2968  	}()
  2969  
  2970  	defer ctx.Close()
  2971  
  2972  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
  2973  }
  2974  
  2975  func (s *DockerCLIBuildSuite) TestBuildAddTarXzGz(c *testing.T) {
  2976  	testRequires(c, DaemonIsLinux)
  2977  	const name = "testbuildaddtarxzgz"
  2978  
  2979  	ctx := func() *fakecontext.Fake {
  2980  		dockerfile := `
  2981  			FROM busybox
  2982  			ADD test.tar.xz.gz /
  2983  			RUN ls /test.tar.xz.gz`
  2984  		tmpDir, err := os.MkdirTemp("", "fake-context")
  2985  		assert.NilError(c, err)
  2986  		testTar, err := os.Create(filepath.Join(tmpDir, "test.tar"))
  2987  		if err != nil {
  2988  			c.Fatalf("failed to create test.tar archive: %v", err)
  2989  		}
  2990  		defer testTar.Close()
  2991  
  2992  		tw := tar.NewWriter(testTar)
  2993  
  2994  		if err := tw.WriteHeader(&tar.Header{
  2995  			Name: "test/foo",
  2996  			Size: 2,
  2997  		}); err != nil {
  2998  			c.Fatalf("failed to write tar file header: %v", err)
  2999  		}
  3000  		if _, err := tw.Write([]byte("Hi")); err != nil {
  3001  			c.Fatalf("failed to write tar file content: %v", err)
  3002  		}
  3003  		if err := tw.Close(); err != nil {
  3004  			c.Fatalf("failed to close tar archive: %v", err)
  3005  		}
  3006  
  3007  		icmd.RunCmd(icmd.Cmd{
  3008  			Command: []string{"xz", "-k", "test.tar"},
  3009  			Dir:     tmpDir,
  3010  		}).Assert(c, icmd.Success)
  3011  
  3012  		icmd.RunCmd(icmd.Cmd{
  3013  			Command: []string{"gzip", "test.tar.xz"},
  3014  			Dir:     tmpDir,
  3015  		})
  3016  		if err := os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0o644); err != nil {
  3017  			c.Fatalf("failed to open destination dockerfile: %v", err)
  3018  		}
  3019  		return fakecontext.New(c, tmpDir)
  3020  	}()
  3021  
  3022  	defer ctx.Close()
  3023  
  3024  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
  3025  }
  3026  
  3027  // FIXME(vdemeester) most of the from git tests could be moved to `docker/cli` e2e tests
  3028  func (s *DockerCLIBuildSuite) TestBuildFromGit(c *testing.T) {
  3029  	const name = "testbuildfromgit"
  3030  	git := fakegit.New(c, "repo", map[string]string{
  3031  		"Dockerfile": `FROM busybox
  3032  		ADD first /first
  3033  		RUN [ -f /first ]
  3034  		MAINTAINER docker`,
  3035  		"first": "test git data",
  3036  	}, true)
  3037  	defer git.Close()
  3038  
  3039  	buildImageSuccessfully(c, name, build.WithContextPath(git.RepoURL))
  3040  
  3041  	res := inspectField(c, name, "Author")
  3042  	if res != "docker" {
  3043  		c.Fatalf("Maintainer should be docker, got %s", res)
  3044  	}
  3045  }
  3046  
  3047  func (s *DockerCLIBuildSuite) TestBuildFromGitWithContext(c *testing.T) {
  3048  	const name = "testbuildfromgit"
  3049  	git := fakegit.New(c, "repo", map[string]string{
  3050  		"docker/Dockerfile": `FROM busybox
  3051  					ADD first /first
  3052  					RUN [ -f /first ]
  3053  					MAINTAINER docker`,
  3054  		"docker/first": "test git data",
  3055  	}, true)
  3056  	defer git.Close()
  3057  
  3058  	buildImageSuccessfully(c, name, build.WithContextPath(fmt.Sprintf("%s#master:docker", git.RepoURL)))
  3059  
  3060  	res := inspectField(c, name, "Author")
  3061  	if res != "docker" {
  3062  		c.Fatalf("Maintainer should be docker, got %s", res)
  3063  	}
  3064  }
  3065  
  3066  func (s *DockerCLIBuildSuite) TestBuildFromGitWithF(c *testing.T) {
  3067  	const name = "testbuildfromgitwithf"
  3068  	git := fakegit.New(c, "repo", map[string]string{
  3069  		"myApp/myDockerfile": `FROM busybox
  3070  					RUN echo hi from Dockerfile`,
  3071  	}, true)
  3072  	defer git.Close()
  3073  
  3074  	buildImage(name, cli.WithFlags("-f", "myApp/myDockerfile"), build.WithContextPath(git.RepoURL)).Assert(c, icmd.Expected{
  3075  		Out: "hi from Dockerfile",
  3076  	})
  3077  }
  3078  
  3079  func (s *DockerCLIBuildSuite) TestBuildFromRemoteTarball(c *testing.T) {
  3080  	const name = "testbuildfromremotetarball"
  3081  
  3082  	buffer := new(bytes.Buffer)
  3083  	tw := tar.NewWriter(buffer)
  3084  	defer tw.Close()
  3085  
  3086  	dockerfile := []byte(`FROM busybox
  3087  					MAINTAINER docker`)
  3088  	if err := tw.WriteHeader(&tar.Header{
  3089  		Name: "Dockerfile",
  3090  		Size: int64(len(dockerfile)),
  3091  	}); err != nil {
  3092  		c.Fatalf("failed to write tar file header: %v", err)
  3093  	}
  3094  	if _, err := tw.Write(dockerfile); err != nil {
  3095  		c.Fatalf("failed to write tar file content: %v", err)
  3096  	}
  3097  	if err := tw.Close(); err != nil {
  3098  		c.Fatalf("failed to close tar archive: %v", err)
  3099  	}
  3100  
  3101  	server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{
  3102  		"testT.tar": buffer,
  3103  	}))
  3104  	defer server.Close()
  3105  
  3106  	cli.BuildCmd(c, name, build.WithContextPath(server.URL()+"/testT.tar"))
  3107  
  3108  	res := inspectField(c, name, "Author")
  3109  	if res != "docker" {
  3110  		c.Fatalf("Maintainer should be docker, got %s", res)
  3111  	}
  3112  }
  3113  
  3114  func (s *DockerCLIBuildSuite) TestBuildCleanupCmdOnEntrypoint(c *testing.T) {
  3115  	const name = "testbuildcmdcleanuponentrypoint"
  3116  
  3117  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+`
  3118  		CMD ["test"]
  3119  		ENTRYPOINT ["echo"]`))
  3120  	buildImageSuccessfully(c, name, build.WithDockerfile(fmt.Sprintf(`FROM %s
  3121  		ENTRYPOINT ["cat"]`, name)))
  3122  
  3123  	res := inspectField(c, name, "Config.Cmd")
  3124  	if res != "[]" {
  3125  		c.Fatalf("Cmd %s, expected nil", res)
  3126  	}
  3127  	res = inspectField(c, name, "Config.Entrypoint")
  3128  	if expected := "[cat]"; res != expected {
  3129  		c.Fatalf("Entrypoint %s, expected %s", res, expected)
  3130  	}
  3131  }
  3132  
  3133  func (s *DockerCLIBuildSuite) TestBuildClearCmd(c *testing.T) {
  3134  	const name = "testbuildclearcmd"
  3135  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+`
  3136     ENTRYPOINT ["/bin/bash"]
  3137     CMD []`))
  3138  
  3139  	cmd := inspectFieldJSON(c, name, "Config.Cmd")
  3140  	// OCI types specify `omitempty` JSON annotation which doesn't serialize
  3141  	// empty arrays and the Cmd will not be present at all.
  3142  	if testEnv.UsingSnapshotter() {
  3143  		assert.Check(c, is.Equal(cmd, "null"))
  3144  	} else {
  3145  		assert.Check(c, is.Equal(cmd, "[]"))
  3146  	}
  3147  }
  3148  
  3149  func (s *DockerCLIBuildSuite) TestBuildEmptyCmd(c *testing.T) {
  3150  	// Skip on Windows. Base image on Windows has a CMD set in the image.
  3151  	testRequires(c, DaemonIsLinux)
  3152  
  3153  	const name = "testbuildemptycmd"
  3154  	buildImageSuccessfully(c, name, build.WithDockerfile("FROM "+minimalBaseImage()+"\nMAINTAINER quux\n"))
  3155  
  3156  	res := inspectFieldJSON(c, name, "Config.Cmd")
  3157  	if res != "null" {
  3158  		c.Fatalf("Cmd %s, expected %s", res, "null")
  3159  	}
  3160  }
  3161  
  3162  func (s *DockerCLIBuildSuite) TestBuildOnBuildOutput(c *testing.T) {
  3163  	const name = "testbuildonbuildparent"
  3164  	buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nONBUILD RUN echo foo\n"))
  3165  
  3166  	buildImage(name, build.WithDockerfile("FROM "+name+"\nMAINTAINER quux\n")).Assert(c, icmd.Expected{
  3167  		Out: "# Executing 1 build trigger",
  3168  	})
  3169  }
  3170  
  3171  // FIXME(vdemeester) should be a unit test
  3172  func (s *DockerCLIBuildSuite) TestBuildInvalidTag(c *testing.T) {
  3173  	name := "abcd:" + testutil.GenerateRandomAlphaOnlyString(200)
  3174  	buildImage(name, build.WithDockerfile("FROM "+minimalBaseImage()+"\nMAINTAINER quux\n")).Assert(c, icmd.Expected{
  3175  		ExitCode: 125,
  3176  		Err:      "invalid reference format",
  3177  	})
  3178  }
  3179  
  3180  func (s *DockerCLIBuildSuite) TestBuildCmdShDashC(c *testing.T) {
  3181  	const name = "testbuildcmdshc"
  3182  	buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nCMD echo cmd\n"))
  3183  
  3184  	res := inspectFieldJSON(c, name, "Config.Cmd")
  3185  	expected := `["/bin/sh","-c","echo cmd"]`
  3186  	if testEnv.DaemonInfo.OSType == "windows" {
  3187  		expected = `["cmd /S /C echo cmd"]`
  3188  	}
  3189  	if res != expected {
  3190  		c.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res)
  3191  	}
  3192  }
  3193  
  3194  func (s *DockerCLIBuildSuite) TestBuildCmdSpaces(c *testing.T) {
  3195  	// Test to make sure that when we strcat arrays we take into account
  3196  	// the arg separator to make sure ["echo","hi"] and ["echo hi"] don't
  3197  	// look the same
  3198  	const name = "testbuildcmdspaces"
  3199  
  3200  	buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nCMD [\"echo hi\"]\n"))
  3201  	id1 := getIDByName(c, name)
  3202  	buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nCMD [\"echo\", \"hi\"]\n"))
  3203  	id2 := getIDByName(c, name)
  3204  
  3205  	if id1 == id2 {
  3206  		c.Fatal("Should not have resulted in the same CMD")
  3207  	}
  3208  
  3209  	// Now do the same with ENTRYPOINT
  3210  	buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nENTRYPOINT [\"echo hi\"]\n"))
  3211  	id1 = getIDByName(c, name)
  3212  	buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nENTRYPOINT [\"echo\", \"hi\"]\n"))
  3213  	id2 = getIDByName(c, name)
  3214  
  3215  	if id1 == id2 {
  3216  		c.Fatal("Should not have resulted in the same ENTRYPOINT")
  3217  	}
  3218  }
  3219  
  3220  func (s *DockerCLIBuildSuite) TestBuildCmdJSONNoShDashC(c *testing.T) {
  3221  	const name = "testbuildcmdjson"
  3222  	buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nCMD [\"echo\", \"cmd\"]"))
  3223  
  3224  	res := inspectFieldJSON(c, name, "Config.Cmd")
  3225  	expected := `["echo","cmd"]`
  3226  	if res != expected {
  3227  		c.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res)
  3228  	}
  3229  }
  3230  
  3231  func (s *DockerCLIBuildSuite) TestBuildEntrypointCanBeOverriddenByChild(c *testing.T) {
  3232  	buildImageSuccessfully(c, "parent", build.WithDockerfile(`
  3233      FROM busybox
  3234      ENTRYPOINT exit 130
  3235      `))
  3236  
  3237  	icmd.RunCommand(dockerBinary, "run", "parent").Assert(c, icmd.Expected{
  3238  		ExitCode: 130,
  3239  	})
  3240  
  3241  	buildImageSuccessfully(c, "child", build.WithDockerfile(`
  3242      FROM parent
  3243      ENTRYPOINT exit 5
  3244      `))
  3245  
  3246  	icmd.RunCommand(dockerBinary, "run", "child").Assert(c, icmd.Expected{
  3247  		ExitCode: 5,
  3248  	})
  3249  }
  3250  
  3251  func (s *DockerCLIBuildSuite) TestBuildEntrypointCanBeOverriddenByChildInspect(c *testing.T) {
  3252  	var (
  3253  		name     = "testbuildepinherit"
  3254  		name2    = "testbuildepinherit2"
  3255  		expected = `["/bin/sh","-c","echo quux"]`
  3256  	)
  3257  
  3258  	if testEnv.DaemonInfo.OSType == "windows" {
  3259  		expected = `["cmd /S /C echo quux"]`
  3260  	}
  3261  
  3262  	buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nENTRYPOINT /foo/bar"))
  3263  	buildImageSuccessfully(c, name2, build.WithDockerfile(fmt.Sprintf("FROM %s\nENTRYPOINT echo quux", name)))
  3264  
  3265  	res := inspectFieldJSON(c, name2, "Config.Entrypoint")
  3266  	if res != expected {
  3267  		c.Fatalf("Expected value %s not in Config.Entrypoint: %s", expected, res)
  3268  	}
  3269  
  3270  	icmd.RunCommand(dockerBinary, "run", name2).Assert(c, icmd.Expected{
  3271  		Out: "quux",
  3272  	})
  3273  }
  3274  
  3275  func (s *DockerCLIBuildSuite) TestBuildRunShEntrypoint(c *testing.T) {
  3276  	const name = "testbuildentrypoint"
  3277  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  3278                                  ENTRYPOINT echo`))
  3279  	cli.DockerCmd(c, "run", "--rm", name)
  3280  }
  3281  
  3282  func (s *DockerCLIBuildSuite) TestBuildExoticShellInterpolation(c *testing.T) {
  3283  	testRequires(c, DaemonIsLinux)
  3284  	const name = "testbuildexoticshellinterpolation"
  3285  
  3286  	buildImageSuccessfully(c, name, build.WithDockerfile(`
  3287  		FROM busybox
  3288  
  3289  		ENV SOME_VAR a.b.c
  3290  
  3291  		RUN [ "$SOME_VAR"       = 'a.b.c' ]
  3292  		RUN [ "${SOME_VAR}"     = 'a.b.c' ]
  3293  		RUN [ "${SOME_VAR%.*}"  = 'a.b'   ]
  3294  		RUN [ "${SOME_VAR%%.*}" = 'a'     ]
  3295  		RUN [ "${SOME_VAR#*.}"  = 'b.c'   ]
  3296  		RUN [ "${SOME_VAR##*.}" = 'c'     ]
  3297  		RUN [ "${SOME_VAR/c/d}" = 'a.b.d' ]
  3298  		RUN [ "${#SOME_VAR}"    = '5'     ]
  3299  
  3300  		RUN [ "${SOME_UNSET_VAR:-$SOME_VAR}" = 'a.b.c' ]
  3301  		RUN [ "${SOME_VAR:+Version: ${SOME_VAR}}" = 'Version: a.b.c' ]
  3302  		RUN [ "${SOME_UNSET_VAR:+${SOME_VAR}}" = '' ]
  3303  		RUN [ "${SOME_UNSET_VAR:-${SOME_VAR:-d.e.f}}" = 'a.b.c' ]
  3304  	`))
  3305  }
  3306  
  3307  func (s *DockerCLIBuildSuite) TestBuildVerifySingleQuoteFails(c *testing.T) {
  3308  	// This testcase is supposed to generate an error because the
  3309  	// JSON array we're passing in on the CMD uses single quotes instead
  3310  	// of double quotes (per the JSON spec). This means we interpret it
  3311  	// as a "string" instead of "JSON array" and pass it on to "sh -c" and
  3312  	// it should barf on it.
  3313  	const name = "testbuildsinglequotefails"
  3314  	expectedExitCode := 2
  3315  
  3316  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  3317  		CMD [ '/bin/sh', '-c', 'echo hi' ]`))
  3318  
  3319  	icmd.RunCommand(dockerBinary, "run", "--rm", name).Assert(c, icmd.Expected{
  3320  		ExitCode: expectedExitCode,
  3321  	})
  3322  }
  3323  
  3324  func (s *DockerCLIBuildSuite) TestBuildVerboseOut(c *testing.T) {
  3325  	const name = "testbuildverboseout"
  3326  	expected := "\n123\n"
  3327  
  3328  	if testEnv.DaemonInfo.OSType == "windows" {
  3329  		expected = "\n123\r\n"
  3330  	}
  3331  
  3332  	buildImage(name, build.WithDockerfile(`FROM busybox
  3333  RUN echo 123`)).Assert(c, icmd.Expected{
  3334  		Out: expected,
  3335  	})
  3336  }
  3337  
  3338  func (s *DockerCLIBuildSuite) TestBuildWithTabs(c *testing.T) {
  3339  	skip.If(c, versions.GreaterThan(testEnv.DaemonAPIVersion(), "1.44"), "ContainerConfig is deprecated")
  3340  	skip.If(c, testEnv.UsingSnapshotter, "ContainerConfig is not filled in c8d")
  3341  
  3342  	const name = "testbuildwithtabs"
  3343  	buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nRUN echo\tone\t\ttwo"))
  3344  	res := inspectFieldJSON(c, name, "ContainerConfig.Cmd")
  3345  	expected1 := `["/bin/sh","-c","echo\tone\t\ttwo"]`
  3346  	expected2 := `["/bin/sh","-c","echo\u0009one\u0009\u0009two"]` // syntactically equivalent, and what Go 1.3 generates
  3347  	if testEnv.DaemonInfo.OSType == "windows" {
  3348  		expected1 = `["cmd /S /C echo\tone\t\ttwo"]`
  3349  		expected2 = `["cmd /S /C echo\u0009one\u0009\u0009two"]` // syntactically equivalent, and what Go 1.3 generates
  3350  	}
  3351  	if res != expected1 && res != expected2 {
  3352  		c.Fatalf("Missing tabs.\nGot: %s\nExp: %s or %s", res, expected1, expected2)
  3353  	}
  3354  }
  3355  
  3356  func (s *DockerCLIBuildSuite) TestBuildLabels(c *testing.T) {
  3357  	const name = "testbuildlabel"
  3358  	expected := `{"License":"GPL","Vendor":"Acme"}`
  3359  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  3360  		LABEL Vendor=Acme
  3361                  LABEL License GPL`))
  3362  	res := inspectFieldJSON(c, name, "Config.Labels")
  3363  	if res != expected {
  3364  		c.Fatalf("Labels %s, expected %s", res, expected)
  3365  	}
  3366  }
  3367  
  3368  func (s *DockerCLIBuildSuite) TestBuildLabelsCache(c *testing.T) {
  3369  	const name = "testbuildlabelcache"
  3370  
  3371  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  3372  		LABEL Vendor=Acme`))
  3373  	id1 := getIDByName(c, name)
  3374  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  3375  		LABEL Vendor=Acme`))
  3376  	id2 := getIDByName(c, name)
  3377  	if id1 != id2 {
  3378  		c.Fatalf("Build 2 should have worked & used cache(%s,%s)", id1, id2)
  3379  	}
  3380  
  3381  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  3382  		LABEL Vendor=Acme1`))
  3383  	id2 = getIDByName(c, name)
  3384  	if id1 == id2 {
  3385  		c.Fatalf("Build 3 should have worked & NOT used cache(%s,%s)", id1, id2)
  3386  	}
  3387  
  3388  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  3389  		LABEL Vendor Acme`))
  3390  	id2 = getIDByName(c, name)
  3391  	if id1 != id2 {
  3392  		c.Fatalf("Build 4 should have worked & used cache(%s,%s)", id1, id2)
  3393  	}
  3394  
  3395  	// Now make sure the cache isn't used by mistake
  3396  	buildImageSuccessfully(c, name, build.WithoutCache, build.WithDockerfile(`FROM busybox
  3397         LABEL f1=b1 f2=b2`))
  3398  
  3399  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  3400         LABEL f1=b1 f2=b2`))
  3401  	id2 = getIDByName(c, name)
  3402  	if id1 == id2 {
  3403  		c.Fatalf("Build 6 should have worked & NOT used the cache(%s,%s)", id1, id2)
  3404  	}
  3405  }
  3406  
  3407  // FIXME(vdemeester) port to docker/cli e2e tests (api tests should test suppressOutput option though)
  3408  func (s *DockerCLIBuildSuite) TestBuildNotVerboseSuccess(c *testing.T) {
  3409  	// This test makes sure that -q works correctly when build is successful:
  3410  	// stdout has only the image ID (long image ID) and stderr is empty.
  3411  	outRegexp := regexp.MustCompile(`^(sha256:|)[a-z0-9]{64}\n$`)
  3412  	buildFlags := cli.WithFlags("-q")
  3413  
  3414  	tt := []struct {
  3415  		Name      string
  3416  		BuildFunc func(string) *icmd.Result
  3417  	}{
  3418  		{
  3419  			Name: "quiet_build_stdin_success",
  3420  			BuildFunc: func(name string) *icmd.Result {
  3421  				return buildImage(name, buildFlags, build.WithDockerfile("FROM busybox"))
  3422  			},
  3423  		},
  3424  		{
  3425  			Name: "quiet_build_ctx_success",
  3426  			BuildFunc: func(name string) *icmd.Result {
  3427  				return buildImage(name, buildFlags, build.WithBuildContext(c,
  3428  					build.WithFile("Dockerfile", "FROM busybox"),
  3429  					build.WithFile("quiet_build_success_fctx", "test"),
  3430  				))
  3431  			},
  3432  		},
  3433  		{
  3434  			Name: "quiet_build_git_success",
  3435  			BuildFunc: func(name string) *icmd.Result {
  3436  				git := fakegit.New(c, "repo", map[string]string{
  3437  					"Dockerfile": "FROM busybox",
  3438  				}, true)
  3439  				return buildImage(name, buildFlags, build.WithContextPath(git.RepoURL))
  3440  			},
  3441  		},
  3442  	}
  3443  
  3444  	for _, te := range tt {
  3445  		result := te.BuildFunc(te.Name)
  3446  		result.Assert(c, icmd.Success)
  3447  		if outRegexp.Find([]byte(result.Stdout())) == nil {
  3448  			c.Fatalf("Test %s expected stdout to match the [%v] regexp, but it is [%v]", te.Name, outRegexp, result.Stdout())
  3449  		}
  3450  
  3451  		if result.Stderr() != "" {
  3452  			c.Fatalf("Test %s expected stderr to be empty, but it is [%#v]", te.Name, result.Stderr())
  3453  		}
  3454  	}
  3455  }
  3456  
  3457  // FIXME(vdemeester) migrate to docker/cli tests
  3458  func (s *DockerCLIBuildSuite) TestBuildNotVerboseFailureWithNonExistImage(c *testing.T) {
  3459  	// This test makes sure that -q works correctly when build fails by
  3460  	// comparing between the stderr output in quiet mode and in stdout
  3461  	// and stderr output in verbose mode
  3462  	testRequires(c, Network)
  3463  	testName := "quiet_build_not_exists_image"
  3464  	dockerfile := "FROM busybox11"
  3465  	quietResult := buildImage(testName, cli.WithFlags("-q"), build.WithDockerfile(dockerfile))
  3466  	quietResult.Assert(c, icmd.Expected{
  3467  		ExitCode: 1,
  3468  	})
  3469  	result := buildImage(testName, build.WithDockerfile(dockerfile))
  3470  	result.Assert(c, icmd.Expected{
  3471  		ExitCode: 1,
  3472  	})
  3473  	if quietResult.Stderr() != result.Combined() {
  3474  		c.Fatal(fmt.Errorf("Test[%s] expected that quiet stderr and verbose stdout are equal; quiet [%v], verbose [%v]", testName, quietResult.Stderr(), result.Combined()))
  3475  	}
  3476  }
  3477  
  3478  // FIXME(vdemeester) migrate to docker/cli tests
  3479  func (s *DockerCLIBuildSuite) TestBuildNotVerboseFailure(c *testing.T) {
  3480  	// This test makes sure that -q works correctly when build fails by
  3481  	// comparing between the stderr output in quiet mode and in stdout
  3482  	// and stderr output in verbose mode
  3483  	testCases := []struct {
  3484  		testName   string
  3485  		dockerfile string
  3486  	}{
  3487  		{"quiet_build_no_from_at_the_beginning", "RUN whoami"},
  3488  		{"quiet_build_unknown_instr", "FROMD busybox"},
  3489  	}
  3490  
  3491  	for _, tc := range testCases {
  3492  		quietResult := buildImage(tc.testName, cli.WithFlags("-q"), build.WithDockerfile(tc.dockerfile))
  3493  		quietResult.Assert(c, icmd.Expected{
  3494  			ExitCode: 1,
  3495  		})
  3496  		result := buildImage(tc.testName, build.WithDockerfile(tc.dockerfile))
  3497  		result.Assert(c, icmd.Expected{
  3498  			ExitCode: 1,
  3499  		})
  3500  		if quietResult.Stderr() != result.Combined() {
  3501  			c.Fatal(fmt.Errorf("Test[%s] expected that quiet stderr and verbose stdout are equal; quiet [%v], verbose [%v]", tc.testName, quietResult.Stderr(), result.Combined()))
  3502  		}
  3503  	}
  3504  }
  3505  
  3506  // FIXME(vdemeester) migrate to docker/cli tests
  3507  func (s *DockerCLIBuildSuite) TestBuildNotVerboseFailureRemote(c *testing.T) {
  3508  	// This test ensures that when given a wrong URL, stderr in quiet mode and
  3509  	// stderr in verbose mode are identical.
  3510  	// TODO(vdemeester) with cobra, stdout has a carriage return too much so this test should not check stdout
  3511  	URL := "http://something.invalid"
  3512  	const name = "quiet_build_wrong_remote"
  3513  	quietResult := buildImage(name, cli.WithFlags("-q"), build.WithContextPath(URL))
  3514  	quietResult.Assert(c, icmd.Expected{
  3515  		ExitCode: 1,
  3516  	})
  3517  	result := buildImage(name, build.WithContextPath(URL))
  3518  	result.Assert(c, icmd.Expected{
  3519  		ExitCode: 1,
  3520  	})
  3521  
  3522  	// An error message should contain name server IP and port, like this:
  3523  	//  "dial tcp: lookup something.invalid on 172.29.128.11:53: no such host"
  3524  	// The IP:port need to be removed in order to not trigger a test failur
  3525  	// when more than one nameserver is configured.
  3526  	// While at it, also strip excessive newlines.
  3527  	normalize := func(msg string) string {
  3528  		return strings.TrimSpace(regexp.MustCompile("[1-9][0-9.]+:[0-9]+").ReplaceAllLiteralString(msg, "<ip:port>"))
  3529  	}
  3530  
  3531  	if normalize(quietResult.Stderr()) != normalize(result.Combined()) {
  3532  		c.Fatal(fmt.Errorf("Test[%s] expected that quiet stderr and verbose stdout are equal; quiet [%v], verbose [%v]", name, quietResult.Stderr(), result.Combined()))
  3533  	}
  3534  }
  3535  
  3536  // FIXME(vdemeester) migrate to docker/cli tests
  3537  func (s *DockerCLIBuildSuite) TestBuildStderr(c *testing.T) {
  3538  	// This test just makes sure that no non-error output goes
  3539  	// to stderr
  3540  	const name = "testbuildstderr"
  3541  	result := buildImage(name, build.WithDockerfile("FROM busybox\nRUN echo one"))
  3542  	result.Assert(c, icmd.Success)
  3543  
  3544  	// Windows to non-Windows should have a security warning
  3545  	if runtime.GOOS == "windows" && testEnv.DaemonInfo.OSType != "windows" && !strings.Contains(result.Stdout(), "SECURITY WARNING:") {
  3546  		c.Fatalf("Stdout contains unexpected output: %q", result.Stdout())
  3547  	}
  3548  
  3549  	// Stderr should always be empty
  3550  	if result.Stderr() != "" {
  3551  		c.Fatalf("Stderr should have been empty, instead it's: %q", result.Stderr())
  3552  	}
  3553  }
  3554  
  3555  func (s *DockerCLIBuildSuite) TestBuildChownSingleFile(c *testing.T) {
  3556  	testRequires(c, UnixCli, DaemonIsLinux) // test uses chown: not available on windows
  3557  
  3558  	const name = "testbuildchownsinglefile"
  3559  
  3560  	ctx := fakecontext.New(c, "",
  3561  		fakecontext.WithDockerfile(`
  3562  FROM busybox
  3563  COPY test /
  3564  RUN ls -l /test
  3565  RUN [ $(ls -l /test | awk '{print $3":"$4}') = 'root:root' ]
  3566  `),
  3567  		fakecontext.WithFiles(map[string]string{
  3568  			"test": "test",
  3569  		}))
  3570  	defer ctx.Close()
  3571  
  3572  	if err := os.Chown(filepath.Join(ctx.Dir, "test"), 4242, 4242); err != nil {
  3573  		c.Fatal(err)
  3574  	}
  3575  
  3576  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  3577  }
  3578  
  3579  func (s *DockerCLIBuildSuite) TestBuildSymlinkBreakout(c *testing.T) {
  3580  	skip.If(c, testEnv.UsingSnapshotter(), "FIXME: https://github.com/moby/moby/issues/47107")
  3581  	const name = "testbuildsymlinkbreakout"
  3582  	tmpdir, err := os.MkdirTemp("", name)
  3583  	assert.NilError(c, err)
  3584  
  3585  	// See https://github.com/moby/moby/pull/37770 for reason for next line.
  3586  	tmpdir, err = getLongPathName(tmpdir)
  3587  	assert.NilError(c, err)
  3588  
  3589  	defer os.RemoveAll(tmpdir)
  3590  	ctx := filepath.Join(tmpdir, "context")
  3591  	if err := os.MkdirAll(ctx, 0o755); err != nil {
  3592  		c.Fatal(err)
  3593  	}
  3594  	if err := os.WriteFile(filepath.Join(ctx, "Dockerfile"), []byte(`
  3595  	from busybox
  3596  	add symlink.tar /
  3597  	add inject /symlink/
  3598  	`), 0o644); err != nil {
  3599  		c.Fatal(err)
  3600  	}
  3601  	inject := filepath.Join(ctx, "inject")
  3602  	if err := os.WriteFile(inject, nil, 0o644); err != nil {
  3603  		c.Fatal(err)
  3604  	}
  3605  	f, err := os.Create(filepath.Join(ctx, "symlink.tar"))
  3606  	if err != nil {
  3607  		c.Fatal(err)
  3608  	}
  3609  	w := tar.NewWriter(f)
  3610  	w.WriteHeader(&tar.Header{
  3611  		Name:     "symlink2",
  3612  		Typeflag: tar.TypeSymlink,
  3613  		Linkname: "/../../../../../../../../../../../../../../",
  3614  		Uid:      os.Getuid(),
  3615  		Gid:      os.Getgid(),
  3616  	})
  3617  	w.WriteHeader(&tar.Header{
  3618  		Name:     "symlink",
  3619  		Typeflag: tar.TypeSymlink,
  3620  		Linkname: filepath.Join("symlink2", tmpdir),
  3621  		Uid:      os.Getuid(),
  3622  		Gid:      os.Getgid(),
  3623  	})
  3624  	w.Close()
  3625  	f.Close()
  3626  
  3627  	buildImageSuccessfully(c, name, build.WithoutCache, build.WithExternalBuildContext(fakecontext.New(c, ctx)))
  3628  	if _, err := os.Lstat(filepath.Join(tmpdir, "inject")); err == nil {
  3629  		c.Fatal("symlink breakout - inject")
  3630  	} else if !os.IsNotExist(err) {
  3631  		c.Fatalf("unexpected error: %v", err)
  3632  	}
  3633  }
  3634  
  3635  func (s *DockerCLIBuildSuite) TestBuildXZHost(c *testing.T) {
  3636  	// /usr/local/sbin/xz gets permission denied for the user
  3637  	testRequires(c, NotUserNamespace)
  3638  	testRequires(c, DaemonIsLinux)
  3639  	const name = "testbuildxzhost"
  3640  
  3641  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  3642  		build.WithFile("Dockerfile", `
  3643  FROM busybox
  3644  ADD xz /usr/local/sbin/
  3645  RUN chmod 755 /usr/local/sbin/xz
  3646  ADD test.xz /
  3647  RUN [ ! -e /injected ]`),
  3648  		build.WithFile("test.xz", "\xfd\x37\x7a\x58\x5a\x00\x00\x04\xe6\xd6\xb4\x46\x02\x00"+"\x21\x01\x16\x00\x00\x00\x74\x2f\xe5\xa3\x01\x00\x3f\xfd"+"\x37\x7a\x58\x5a\x00\x00\x04\xe6\xd6\xb4\x46\x02\x00\x21"),
  3649  		build.WithFile("xz", "#!/bin/sh\ntouch /injected"),
  3650  	))
  3651  }
  3652  
  3653  func (s *DockerCLIBuildSuite) TestBuildVolumesRetainContents(c *testing.T) {
  3654  	// /foo/file gets permission denied for the user
  3655  	testRequires(c, NotUserNamespace)
  3656  	testRequires(c, DaemonIsLinux) // TODO Windows: Issue #20127
  3657  	var (
  3658  		name     = "testbuildvolumescontent"
  3659  		expected = "some text"
  3660  		volName  = "/foo"
  3661  	)
  3662  
  3663  	if testEnv.DaemonInfo.OSType == "windows" {
  3664  		volName = "C:/foo"
  3665  	}
  3666  
  3667  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  3668  		build.WithFile("Dockerfile", `
  3669  FROM busybox
  3670  COPY content /foo/file
  3671  VOLUME `+volName+`
  3672  CMD cat /foo/file`),
  3673  		build.WithFile("content", expected),
  3674  	))
  3675  
  3676  	out := cli.DockerCmd(c, "run", "--rm", name).Combined()
  3677  	if out != expected {
  3678  		c.Fatalf("expected file contents for /foo/file to be %q but received %q", expected, out)
  3679  	}
  3680  }
  3681  
  3682  func (s *DockerCLIBuildSuite) TestBuildFromMixedcaseDockerfile(c *testing.T) {
  3683  	testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows
  3684  	testRequires(c, DaemonIsLinux)
  3685  
  3686  	// If Dockerfile is not present, use dockerfile
  3687  	buildImage("test1", build.WithBuildContext(c,
  3688  		build.WithFile("dockerfile", `FROM busybox
  3689  	RUN echo from dockerfile`),
  3690  	)).Assert(c, icmd.Expected{
  3691  		Out: "from dockerfile",
  3692  	})
  3693  
  3694  	// Prefer Dockerfile in place of dockerfile
  3695  	buildImage("test1", build.WithBuildContext(c,
  3696  		build.WithFile("dockerfile", `FROM busybox
  3697  	RUN echo from dockerfile`),
  3698  		build.WithFile("Dockerfile", `FROM busybox
  3699  	RUN echo from Dockerfile`),
  3700  	)).Assert(c, icmd.Expected{
  3701  		Out: "from Dockerfile",
  3702  	})
  3703  }
  3704  
  3705  // FIXME(vdemeester) should migrate to docker/cli tests
  3706  func (s *DockerCLIBuildSuite) TestBuildFromURLWithF(c *testing.T) {
  3707  	server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{"baz": `FROM busybox
  3708  RUN echo from baz
  3709  COPY * /tmp/
  3710  RUN find /tmp/`}))
  3711  	defer server.Close()
  3712  
  3713  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`FROM busybox
  3714  	RUN echo from Dockerfile`))
  3715  	defer ctx.Close()
  3716  
  3717  	// Make sure that -f is ignored and that we don't use the Dockerfile
  3718  	// that's in the current dir
  3719  	result := cli.BuildCmd(c, "test1", cli.WithFlags("-f", "baz", server.URL()+"/baz"), func(cmd *icmd.Cmd) func() {
  3720  		cmd.Dir = ctx.Dir
  3721  		return nil
  3722  	})
  3723  
  3724  	if !strings.Contains(result.Combined(), "from baz") ||
  3725  		strings.Contains(result.Combined(), "/tmp/baz") ||
  3726  		!strings.Contains(result.Combined(), "/tmp/Dockerfile") {
  3727  		c.Fatalf("Missing proper output: %s", result.Combined())
  3728  	}
  3729  }
  3730  
  3731  // FIXME(vdemeester) should migrate to docker/cli tests
  3732  func (s *DockerCLIBuildSuite) TestBuildFromStdinWithF(c *testing.T) {
  3733  	testRequires(c, DaemonIsLinux) // TODO Windows: This test is flaky; no idea why
  3734  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`FROM busybox
  3735  RUN echo "from Dockerfile"`))
  3736  	defer ctx.Close()
  3737  
  3738  	// Make sure that -f is ignored and that we don't use the Dockerfile
  3739  	// that's in the current dir
  3740  	result := cli.BuildCmd(c, "test1", cli.WithFlags("-f", "baz", "-"), func(cmd *icmd.Cmd) func() {
  3741  		cmd.Dir = ctx.Dir
  3742  		cmd.Stdin = strings.NewReader(`FROM busybox
  3743  RUN echo "from baz"
  3744  COPY * /tmp/
  3745  RUN sh -c "find /tmp/" # sh -c is needed on Windows to use the correct find`)
  3746  		return nil
  3747  	})
  3748  
  3749  	if !strings.Contains(result.Combined(), "from baz") ||
  3750  		strings.Contains(result.Combined(), "/tmp/baz") ||
  3751  		!strings.Contains(result.Combined(), "/tmp/Dockerfile") {
  3752  		c.Fatalf("Missing proper output: %s", result.Combined())
  3753  	}
  3754  }
  3755  
  3756  func (s *DockerCLIBuildSuite) TestBuildFromOfficialNames(c *testing.T) {
  3757  	const name = "testbuildfromofficial"
  3758  	fromNames := []string{
  3759  		"busybox",
  3760  		"docker.io/busybox",
  3761  		"index.docker.io/busybox",
  3762  		"library/busybox",
  3763  		"docker.io/library/busybox",
  3764  		"index.docker.io/library/busybox",
  3765  	}
  3766  	for idx, fromName := range fromNames {
  3767  		imgName := fmt.Sprintf("%s%d", name, idx)
  3768  		buildImageSuccessfully(c, imgName, build.WithDockerfile("FROM "+fromName))
  3769  		cli.DockerCmd(c, "rmi", imgName)
  3770  	}
  3771  }
  3772  
  3773  // FIXME(vdemeester) should be a unit test
  3774  func (s *DockerCLIBuildSuite) TestBuildSpaces(c *testing.T) {
  3775  	// Test to make sure that leading/trailing spaces on a command
  3776  	// doesn't change the error msg we get
  3777  	const name = "testspaces"
  3778  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile("FROM busybox\nCOPY\n"))
  3779  	defer ctx.Close()
  3780  
  3781  	result1 := cli.Docker(cli.Args("build", "-t", name), build.WithExternalBuildContext(ctx))
  3782  	result1.Assert(c, icmd.Expected{
  3783  		ExitCode: 1,
  3784  	})
  3785  
  3786  	ctx.Add("Dockerfile", "FROM busybox\nCOPY    ")
  3787  	result2 := cli.Docker(cli.Args("build", "-t", name), build.WithExternalBuildContext(ctx))
  3788  	result2.Assert(c, icmd.Expected{
  3789  		ExitCode: 1,
  3790  	})
  3791  
  3792  	removeLogTimestamps := func(s string) string {
  3793  		return regexp.MustCompile(`time="(.*?)"`).ReplaceAllString(s, `time=[TIMESTAMP]`)
  3794  	}
  3795  
  3796  	// Skip over the times
  3797  	e1 := removeLogTimestamps(result1.Error.Error())
  3798  	e2 := removeLogTimestamps(result2.Error.Error())
  3799  
  3800  	// Ignore whitespace since that's what were verifying doesn't change stuff
  3801  	if strings.ReplaceAll(e1, " ", "") != strings.ReplaceAll(e2, " ", "") {
  3802  		c.Fatalf("Build 2's error wasn't the same as build 1's\n1:%s\n2:%s", result1.Error, result2.Error)
  3803  	}
  3804  
  3805  	ctx.Add("Dockerfile", "FROM busybox\n   COPY")
  3806  	result2 = cli.Docker(cli.Args("build", "-t", name), build.WithoutCache, build.WithExternalBuildContext(ctx))
  3807  	result2.Assert(c, icmd.Expected{
  3808  		ExitCode: 1,
  3809  	})
  3810  
  3811  	// Skip over the times
  3812  	e1 = removeLogTimestamps(result1.Error.Error())
  3813  	e2 = removeLogTimestamps(result2.Error.Error())
  3814  
  3815  	// Ignore whitespace since that's what were verifying doesn't change stuff
  3816  	if strings.ReplaceAll(e1, " ", "") != strings.ReplaceAll(e2, " ", "") {
  3817  		c.Fatalf("Build 3's error wasn't the same as build 1's\n1:%s\n3:%s", result1.Error, result2.Error)
  3818  	}
  3819  
  3820  	ctx.Add("Dockerfile", "FROM busybox\n   COPY    ")
  3821  	result2 = cli.Docker(cli.Args("build", "-t", name), build.WithoutCache, build.WithExternalBuildContext(ctx))
  3822  	result2.Assert(c, icmd.Expected{
  3823  		ExitCode: 1,
  3824  	})
  3825  
  3826  	// Skip over the times
  3827  	e1 = removeLogTimestamps(result1.Error.Error())
  3828  	e2 = removeLogTimestamps(result2.Error.Error())
  3829  
  3830  	// Ignore whitespace since that's what were verifying doesn't change stuff
  3831  	if strings.ReplaceAll(e1, " ", "") != strings.ReplaceAll(e2, " ", "") {
  3832  		c.Fatalf("Build 4's error wasn't the same as build 1's\n1:%s\n4:%s", result1.Error, result2.Error)
  3833  	}
  3834  }
  3835  
  3836  func (s *DockerCLIBuildSuite) TestBuildSpacesWithQuotes(c *testing.T) {
  3837  	// Test to make sure that spaces in quotes aren't lost
  3838  	const name = "testspacesquotes"
  3839  
  3840  	dockerfile := `FROM busybox
  3841  RUN echo "  \
  3842    foo  "`
  3843  
  3844  	expected := "\n    foo  \n"
  3845  	// Windows uses the builtin echo, which preserves quotes
  3846  	if testEnv.DaemonInfo.OSType == "windows" {
  3847  		expected = "\"    foo  \""
  3848  	}
  3849  
  3850  	buildImage(name, build.WithDockerfile(dockerfile)).Assert(c, icmd.Expected{
  3851  		Out: expected,
  3852  	})
  3853  }
  3854  
  3855  // #4393
  3856  func (s *DockerCLIBuildSuite) TestBuildVolumeFileExistsinContainer(c *testing.T) {
  3857  	testRequires(c, DaemonIsLinux) // TODO Windows: This should error out
  3858  	buildImage("docker-test-errcreatevolumewithfile", build.WithDockerfile(`
  3859  	FROM busybox
  3860  	RUN touch /foo
  3861  	VOLUME /foo
  3862  	`)).Assert(c, icmd.Expected{
  3863  		ExitCode: 1,
  3864  		Err:      "file exists",
  3865  	})
  3866  }
  3867  
  3868  // FIXME(vdemeester) should be a unit test
  3869  func (s *DockerCLIBuildSuite) TestBuildMissingArgs(c *testing.T) {
  3870  	// Test to make sure that all Dockerfile commands (except the ones listed
  3871  	// in skipCmds) will generate an error if no args are provided.
  3872  	// Note: INSERT is deprecated so we exclude it because of that.
  3873  	skipCmds := map[string]struct{}{
  3874  		"CMD":        {},
  3875  		"RUN":        {},
  3876  		"ENTRYPOINT": {},
  3877  		"INSERT":     {},
  3878  	}
  3879  
  3880  	if testEnv.DaemonInfo.OSType == "windows" {
  3881  		skipCmds = map[string]struct{}{
  3882  			"CMD":        {},
  3883  			"RUN":        {},
  3884  			"ENTRYPOINT": {},
  3885  			"INSERT":     {},
  3886  			"STOPSIGNAL": {},
  3887  			"ARG":        {},
  3888  			"USER":       {},
  3889  			"EXPOSE":     {},
  3890  		}
  3891  	}
  3892  
  3893  	for cmd := range command.Commands {
  3894  		cmd = strings.ToUpper(cmd)
  3895  		if _, ok := skipCmds[cmd]; ok {
  3896  			continue
  3897  		}
  3898  		var dockerfile string
  3899  		if cmd == "FROM" {
  3900  			dockerfile = cmd
  3901  		} else {
  3902  			// Add FROM to make sure we don't complain about it missing
  3903  			dockerfile = "FROM busybox\n" + cmd
  3904  		}
  3905  
  3906  		buildImage("args", build.WithDockerfile(dockerfile)).Assert(c, icmd.Expected{
  3907  			ExitCode: 1,
  3908  			Err:      cmd + " requires",
  3909  		})
  3910  	}
  3911  }
  3912  
  3913  func (s *DockerCLIBuildSuite) TestBuildEmptyScratch(c *testing.T) {
  3914  	testRequires(c, DaemonIsLinux)
  3915  	buildImage("sc", build.WithDockerfile("FROM scratch")).Assert(c, icmd.Expected{
  3916  		ExitCode: 1,
  3917  		Err:      "No image was generated",
  3918  	})
  3919  }
  3920  
  3921  func (s *DockerCLIBuildSuite) TestBuildDotDotFile(c *testing.T) {
  3922  	buildImageSuccessfully(c, "sc", build.WithBuildContext(c,
  3923  		build.WithFile("Dockerfile", "FROM busybox\n"),
  3924  		build.WithFile("..gitme", ""),
  3925  	))
  3926  }
  3927  
  3928  func (s *DockerCLIBuildSuite) TestBuildRUNoneJSON(c *testing.T) {
  3929  	testRequires(c, DaemonIsLinux) // No hello-world Windows image
  3930  	const name = "testbuildrunonejson"
  3931  
  3932  	buildImage(name, build.WithDockerfile(`FROM hello-world:frozen
  3933  RUN [ "/hello" ]`)).Assert(c, icmd.Expected{
  3934  		Out: "Hello from Docker",
  3935  	})
  3936  }
  3937  
  3938  func (s *DockerCLIBuildSuite) TestBuildEmptyStringVolume(c *testing.T) {
  3939  	const name = "testbuildemptystringvolume"
  3940  
  3941  	buildImage(name, build.WithDockerfile(`
  3942    FROM busybox
  3943    ENV foo=""
  3944    VOLUME $foo
  3945    `)).Assert(c, icmd.Expected{
  3946  		ExitCode: 1,
  3947  	})
  3948  }
  3949  
  3950  func (s *DockerCLIBuildSuite) TestBuildContainerWithCgroupParent(c *testing.T) {
  3951  	testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux)
  3952  
  3953  	cgroupParent := "test"
  3954  	data, err := os.ReadFile("/proc/self/cgroup")
  3955  	if err != nil {
  3956  		c.Fatalf("failed to read '/proc/self/cgroup - %v", err)
  3957  	}
  3958  	selfCgroupPaths := ParseCgroupPaths(string(data))
  3959  	_, found := selfCgroupPaths["memory"]
  3960  	if !found {
  3961  		c.Fatalf("unable to find self memory cgroup path. CgroupsPath: %v", selfCgroupPaths)
  3962  	}
  3963  	result := buildImage("buildcgroupparent",
  3964  		cli.WithFlags("--cgroup-parent", cgroupParent),
  3965  		build.WithDockerfile(`
  3966  FROM busybox
  3967  RUN cat /proc/self/cgroup
  3968  `))
  3969  	result.Assert(c, icmd.Success)
  3970  	m, err := regexp.MatchString(fmt.Sprintf("memory:.*/%s/.*", cgroupParent), result.Combined())
  3971  	assert.NilError(c, err)
  3972  	if !m {
  3973  		c.Fatalf("There is no expected memory cgroup with parent /%s/: %s", cgroupParent, result.Combined())
  3974  	}
  3975  }
  3976  
  3977  // FIXME(vdemeester) could be a unit test
  3978  func (s *DockerCLIBuildSuite) TestBuildNoDupOutput(c *testing.T) {
  3979  	// Check to make sure our build output prints the Dockerfile cmd
  3980  	// property - there was a bug that caused it to be duplicated on the
  3981  	// Step X  line
  3982  	const name = "testbuildnodupoutput"
  3983  	result := buildImage(name, build.WithDockerfile(`
  3984    FROM busybox
  3985    RUN env`))
  3986  	result.Assert(c, icmd.Success)
  3987  	exp := "\nStep 2/2 : RUN env\n"
  3988  	if !strings.Contains(result.Combined(), exp) {
  3989  		c.Fatalf("Bad output\nGot:%s\n\nExpected to contain:%s\n", result.Combined(), exp)
  3990  	}
  3991  }
  3992  
  3993  // GH15826
  3994  // FIXME(vdemeester) could be a unit test
  3995  func (s *DockerCLIBuildSuite) TestBuildStartsFromOne(c *testing.T) {
  3996  	// Explicit check to ensure that build starts from step 1 rather than 0
  3997  	const name = "testbuildstartsfromone"
  3998  	result := buildImage(name, build.WithDockerfile(`FROM busybox`))
  3999  	result.Assert(c, icmd.Success)
  4000  	exp := "\nStep 1/1 : FROM busybox\n"
  4001  	if !strings.Contains(result.Combined(), exp) {
  4002  		c.Fatalf("Bad output\nGot:%s\n\nExpected to contain:%s\n", result.Combined(), exp)
  4003  	}
  4004  }
  4005  
  4006  func (s *DockerCLIBuildSuite) TestBuildRUNErrMsg(c *testing.T) {
  4007  	// Test to make sure the bad command is quoted with just "s and
  4008  	// not as a Go []string
  4009  	const name = "testbuildbadrunerrmsg"
  4010  	shell := "/bin/sh -c"
  4011  	exitCode := 127
  4012  	if testEnv.DaemonInfo.OSType == "windows" {
  4013  		shell = "cmd /S /C"
  4014  		// architectural - Windows has to start the container to determine the exe is bad, Linux does not
  4015  		exitCode = 1
  4016  	}
  4017  	exp := fmt.Sprintf(`The command '%s badEXE a1 \& a2	a3' returned a non-zero code: %d`, shell, exitCode)
  4018  
  4019  	buildImage(name, build.WithDockerfile(`
  4020    FROM busybox
  4021    RUN badEXE a1 \& a2	a3`)).Assert(c, icmd.Expected{
  4022  		ExitCode: exitCode,
  4023  		Err:      exp,
  4024  	})
  4025  }
  4026  
  4027  // Issue #15634: COPY fails when path starts with "null"
  4028  func (s *DockerCLIBuildSuite) TestBuildNullStringInAddCopyVolume(c *testing.T) {
  4029  	const name = "testbuildnullstringinaddcopyvolume"
  4030  	volName := "nullvolume"
  4031  	if testEnv.DaemonInfo.OSType == "windows" {
  4032  		volName = `C:\\nullvolume`
  4033  	}
  4034  
  4035  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  4036  		build.WithFile("Dockerfile", `
  4037  		FROM busybox
  4038  
  4039  		ADD null /
  4040  		COPY nullfile /
  4041  		VOLUME `+volName+`
  4042  		`),
  4043  		build.WithFile("null", "test1"),
  4044  		build.WithFile("nullfile", "test2"),
  4045  	))
  4046  }
  4047  
  4048  func (s *DockerCLIBuildSuite) TestBuildStopSignal(c *testing.T) {
  4049  	testRequires(c, DaemonIsLinux) // Windows does not support STOPSIGNAL yet
  4050  	imgName := "test_build_stop_signal"
  4051  	buildImageSuccessfully(c, imgName, build.WithDockerfile(`FROM busybox
  4052  		 STOPSIGNAL SIGKILL`))
  4053  	res := inspectFieldJSON(c, imgName, "Config.StopSignal")
  4054  	if res != `"SIGKILL"` {
  4055  		c.Fatalf("Signal %s, expected SIGKILL", res)
  4056  	}
  4057  
  4058  	containerName := "test-container-stop-signal"
  4059  	cli.DockerCmd(c, "run", "-d", "--name", containerName, imgName, "top")
  4060  	res = inspectFieldJSON(c, containerName, "Config.StopSignal")
  4061  	if res != `"SIGKILL"` {
  4062  		c.Fatalf("Signal %s, expected SIGKILL", res)
  4063  	}
  4064  }
  4065  
  4066  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArg(c *testing.T) {
  4067  	imgName := "bldargtest"
  4068  	envKey := "foo"
  4069  	envVal := "bar"
  4070  	var dockerfile string
  4071  	if testEnv.DaemonInfo.OSType == "windows" {
  4072  		// Bugs in Windows busybox port - use the default base image and native cmd stuff
  4073  		dockerfile = fmt.Sprintf(`FROM `+minimalBaseImage()+`
  4074  			ARG %s
  4075  			RUN echo %%%s%%
  4076  			CMD setlocal enableextensions && if defined %s (echo %%%s%%)`, envKey, envKey, envKey, envKey)
  4077  	} else {
  4078  		dockerfile = fmt.Sprintf(`FROM busybox
  4079  			ARG %s
  4080  			RUN echo $%s
  4081  			CMD echo $%s`, envKey, envKey, envKey)
  4082  	}
  4083  	buildImage(imgName,
  4084  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)),
  4085  		build.WithDockerfile(dockerfile),
  4086  	).Assert(c, icmd.Expected{
  4087  		Out: envVal,
  4088  	})
  4089  
  4090  	containerName := "bldargCont"
  4091  	out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined()
  4092  	out = strings.Trim(out, " \r\n'")
  4093  	if out != "" {
  4094  		c.Fatalf("run produced invalid output: %q, expected empty string", out)
  4095  	}
  4096  }
  4097  
  4098  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgHistory(c *testing.T) {
  4099  	imgName := "bldargtest"
  4100  	envKey := "foo"
  4101  	envVal := "bar"
  4102  	envDef := "bar1"
  4103  	dockerfile := fmt.Sprintf(`FROM busybox
  4104  		ARG %s=%s`, envKey, envDef)
  4105  	buildImage(imgName,
  4106  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)),
  4107  		build.WithDockerfile(dockerfile),
  4108  	).Assert(c, icmd.Expected{
  4109  		Out: envVal,
  4110  	})
  4111  
  4112  	out := cli.DockerCmd(c, "history", "--no-trunc", imgName).Combined()
  4113  	outputTabs := strings.Split(out, "\n")[1]
  4114  	if !strings.Contains(outputTabs, envDef) {
  4115  		c.Fatalf("failed to find arg default in image history output: %q expected: %q", outputTabs, envDef)
  4116  	}
  4117  }
  4118  
  4119  func (s *DockerCLIBuildSuite) TestBuildTimeArgHistoryExclusions(c *testing.T) {
  4120  	imgName := "bldargtest"
  4121  	envKey := "foo"
  4122  	envVal := "bar"
  4123  	proxy := "HTTP_PROXY=http://user:password@proxy.example.com"
  4124  	explicitProxyKey := "http_proxy"
  4125  	explicitProxyVal := "http://user:password@someproxy.example.com"
  4126  	dockerfile := fmt.Sprintf(`FROM busybox
  4127  		ARG %s
  4128  		ARG %s
  4129  		RUN echo "Testing Build Args!"`, envKey, explicitProxyKey)
  4130  
  4131  	buildImage := func(imgName string) string {
  4132  		cli.BuildCmd(c, imgName,
  4133  			cli.WithFlags("--build-arg", "https_proxy=https://proxy.example.com",
  4134  				"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
  4135  				"--build-arg", fmt.Sprintf("%s=%s", explicitProxyKey, explicitProxyVal),
  4136  				"--build-arg", proxy),
  4137  			build.WithDockerfile(dockerfile),
  4138  		)
  4139  		return getIDByName(c, imgName)
  4140  	}
  4141  
  4142  	origID := buildImage(imgName)
  4143  	result := cli.DockerCmd(c, "history", "--no-trunc", imgName)
  4144  	out := result.Stdout()
  4145  
  4146  	if strings.Contains(out, proxy) {
  4147  		c.Fatalf("failed to exclude proxy settings from history!")
  4148  	}
  4149  	if strings.Contains(out, "https_proxy") {
  4150  		c.Fatalf("failed to exclude proxy settings from history!")
  4151  	}
  4152  	result.Assert(c, icmd.Expected{Out: fmt.Sprintf("%s=%s", envKey, envVal)})
  4153  	result.Assert(c, icmd.Expected{Out: fmt.Sprintf("%s=%s", explicitProxyKey, explicitProxyVal)})
  4154  
  4155  	cacheID := buildImage(imgName + "-two")
  4156  	assert.Equal(c, origID, cacheID)
  4157  }
  4158  
  4159  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgCacheHit(c *testing.T) {
  4160  	imgName := "bldargtest"
  4161  	envKey := "foo"
  4162  	envVal := "bar"
  4163  	dockerfile := fmt.Sprintf(`FROM busybox
  4164  		ARG %s
  4165  		RUN echo $%s`, envKey, envKey)
  4166  	buildImageSuccessfully(c, imgName,
  4167  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)),
  4168  		build.WithDockerfile(dockerfile),
  4169  	)
  4170  	origImgID := getIDByName(c, imgName)
  4171  
  4172  	imgNameCache := "bldargtestcachehit"
  4173  	buildImageSuccessfully(c, imgNameCache,
  4174  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)),
  4175  		build.WithDockerfile(dockerfile),
  4176  	)
  4177  	newImgID := getIDByName(c, imgName)
  4178  	if newImgID != origImgID {
  4179  		c.Fatalf("build didn't use cache! expected image id: %q built image id: %q", origImgID, newImgID)
  4180  	}
  4181  }
  4182  
  4183  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgCacheMissExtraArg(c *testing.T) {
  4184  	imgName := "bldargtest"
  4185  	envKey := "foo"
  4186  	envVal := "bar"
  4187  	extraEnvKey := "foo1"
  4188  	extraEnvVal := "bar1"
  4189  	dockerfile := fmt.Sprintf(`FROM busybox
  4190  		ARG %s
  4191  		ARG %s
  4192  		RUN echo $%s`, envKey, extraEnvKey, envKey)
  4193  	buildImageSuccessfully(c, imgName,
  4194  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)),
  4195  		build.WithDockerfile(dockerfile),
  4196  	)
  4197  	origImgID := getIDByName(c, imgName)
  4198  
  4199  	imgNameCache := "bldargtestcachemiss"
  4200  	buildImageSuccessfully(c, imgNameCache,
  4201  		cli.WithFlags(
  4202  			"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
  4203  			"--build-arg", fmt.Sprintf("%s=%s", extraEnvKey, extraEnvVal),
  4204  		),
  4205  		build.WithDockerfile(dockerfile),
  4206  	)
  4207  	newImgID := getIDByName(c, imgNameCache)
  4208  
  4209  	if newImgID == origImgID {
  4210  		c.Fatalf("build used cache, expected a miss!")
  4211  	}
  4212  }
  4213  
  4214  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgCacheMissSameArgDiffVal(c *testing.T) {
  4215  	imgName := "bldargtest"
  4216  	envKey := "foo"
  4217  	envVal := "bar"
  4218  	newEnvVal := "bar1"
  4219  	dockerfile := fmt.Sprintf(`FROM busybox
  4220  		ARG %s
  4221  		RUN echo $%s`, envKey, envKey)
  4222  	buildImageSuccessfully(c, imgName,
  4223  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)),
  4224  		build.WithDockerfile(dockerfile),
  4225  	)
  4226  	origImgID := getIDByName(c, imgName)
  4227  
  4228  	imgNameCache := "bldargtestcachemiss"
  4229  	buildImageSuccessfully(c, imgNameCache,
  4230  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, newEnvVal)),
  4231  		build.WithDockerfile(dockerfile),
  4232  	)
  4233  	newImgID := getIDByName(c, imgNameCache)
  4234  	if newImgID == origImgID {
  4235  		c.Fatalf("build used cache, expected a miss!")
  4236  	}
  4237  }
  4238  
  4239  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgOverrideArgDefinedBeforeEnv(c *testing.T) {
  4240  	testRequires(c, DaemonIsLinux) // Windows does not support ARG
  4241  	imgName := "bldargtest"
  4242  	envKey := "foo"
  4243  	envVal := "bar"
  4244  	envValOverride := "barOverride"
  4245  	dockerfile := fmt.Sprintf(`FROM busybox
  4246  		ARG %s
  4247  		ENV %s %s
  4248  		RUN echo $%s
  4249  		CMD echo $%s
  4250          `, envKey, envKey, envValOverride, envKey, envKey)
  4251  
  4252  	result := buildImage(imgName,
  4253  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)),
  4254  		build.WithDockerfile(dockerfile),
  4255  	)
  4256  	result.Assert(c, icmd.Success)
  4257  	if strings.Count(result.Combined(), envValOverride) != 2 {
  4258  		c.Fatalf("failed to access environment variable in output: %q expected: %q", result.Combined(), envValOverride)
  4259  	}
  4260  
  4261  	containerName := "bldargCont"
  4262  	if out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined(); !strings.Contains(out, envValOverride) {
  4263  		c.Fatalf("run produced invalid output: %q, expected %q", out, envValOverride)
  4264  	}
  4265  }
  4266  
  4267  // FIXME(vdemeester) might be useful to merge with the one above ?
  4268  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgOverrideEnvDefinedBeforeArg(c *testing.T) {
  4269  	testRequires(c, DaemonIsLinux) // Windows does not support ARG
  4270  	imgName := "bldargtest"
  4271  	envKey := "foo"
  4272  	envVal := "bar"
  4273  	envValOverride := "barOverride"
  4274  	dockerfile := fmt.Sprintf(`FROM busybox
  4275  		ENV %s %s
  4276  		ARG %s
  4277  		RUN echo $%s
  4278  		CMD echo $%s
  4279          `, envKey, envValOverride, envKey, envKey, envKey)
  4280  	result := buildImage(imgName,
  4281  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)),
  4282  		build.WithDockerfile(dockerfile),
  4283  	)
  4284  	result.Assert(c, icmd.Success)
  4285  	if strings.Count(result.Combined(), envValOverride) != 2 {
  4286  		c.Fatalf("failed to access environment variable in output: %q expected: %q", result.Combined(), envValOverride)
  4287  	}
  4288  
  4289  	containerName := "bldargCont"
  4290  	if out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined(); !strings.Contains(out, envValOverride) {
  4291  		c.Fatalf("run produced invalid output: %q, expected %q", out, envValOverride)
  4292  	}
  4293  }
  4294  
  4295  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgExpansion(c *testing.T) {
  4296  	imgName := "bldvarstest"
  4297  
  4298  	wdVar := "WDIR"
  4299  	wdVal := "/tmp"
  4300  	addVar := "AFILE"
  4301  	addVal := "addFile"
  4302  	copyVar := "CFILE"
  4303  	copyVal := "copyFile"
  4304  	envVar := "foo"
  4305  	envVal := "bar"
  4306  	exposeVar := "EPORT"
  4307  	exposeVal := "9999"
  4308  	userVar := "USER"
  4309  	userVal := "testUser"
  4310  	volVar := "VOL"
  4311  	volVal := "/testVol/"
  4312  	if DaemonIsWindows() {
  4313  		volVal = "C:\\testVol"
  4314  		wdVal = "C:\\tmp"
  4315  	}
  4316  
  4317  	buildImageSuccessfully(c, imgName,
  4318  		cli.WithFlags(
  4319  			"--build-arg", fmt.Sprintf("%s=%s", wdVar, wdVal),
  4320  			"--build-arg", fmt.Sprintf("%s=%s", addVar, addVal),
  4321  			"--build-arg", fmt.Sprintf("%s=%s", copyVar, copyVal),
  4322  			"--build-arg", fmt.Sprintf("%s=%s", envVar, envVal),
  4323  			"--build-arg", fmt.Sprintf("%s=%s", exposeVar, exposeVal),
  4324  			"--build-arg", fmt.Sprintf("%s=%s", userVar, userVal),
  4325  			"--build-arg", fmt.Sprintf("%s=%s", volVar, volVal),
  4326  		),
  4327  		build.WithBuildContext(c,
  4328  			build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox
  4329  		ARG %s
  4330  		WORKDIR ${%s}
  4331  		ARG %s
  4332  		ADD ${%s} testDir/
  4333  		ARG %s
  4334  		COPY $%s testDir/
  4335  		ARG %s
  4336  		ENV %s=${%s}
  4337  		ARG %s
  4338  		EXPOSE $%s
  4339  		ARG %s
  4340  		USER $%s
  4341  		ARG %s
  4342  		VOLUME ${%s}`,
  4343  				wdVar, wdVar, addVar, addVar, copyVar, copyVar, envVar, envVar,
  4344  				envVar, exposeVar, exposeVar, userVar, userVar, volVar, volVar)),
  4345  			build.WithFile(addVal, "some stuff"),
  4346  			build.WithFile(copyVal, "some stuff"),
  4347  		),
  4348  	)
  4349  
  4350  	res := inspectField(c, imgName, "Config.WorkingDir")
  4351  	assert.Equal(c, filepath.ToSlash(res), filepath.ToSlash(wdVal))
  4352  
  4353  	var resArr []string
  4354  	inspectFieldAndUnmarshall(c, imgName, "Config.Env", &resArr)
  4355  
  4356  	found := false
  4357  	for _, v := range resArr {
  4358  		if fmt.Sprintf("%s=%s", envVar, envVal) == v {
  4359  			found = true
  4360  			break
  4361  		}
  4362  	}
  4363  	if !found {
  4364  		c.Fatalf("Config.Env value mismatch. Expected <key=value> to exist: %s=%s, got: %v",
  4365  			envVar, envVal, resArr)
  4366  	}
  4367  
  4368  	var resMap map[string]interface{}
  4369  	inspectFieldAndUnmarshall(c, imgName, "Config.ExposedPorts", &resMap)
  4370  	if _, ok := resMap[fmt.Sprintf("%s/tcp", exposeVal)]; !ok {
  4371  		c.Fatalf("Config.ExposedPorts value mismatch. Expected exposed port: %s/tcp, got: %v", exposeVal, resMap)
  4372  	}
  4373  
  4374  	res = inspectField(c, imgName, "Config.User")
  4375  	if res != userVal {
  4376  		c.Fatalf("Config.User value mismatch. Expected: %s, got: %s", userVal, res)
  4377  	}
  4378  
  4379  	inspectFieldAndUnmarshall(c, imgName, "Config.Volumes", &resMap)
  4380  	if _, ok := resMap[volVal]; !ok {
  4381  		c.Fatalf("Config.Volumes value mismatch. Expected volume: %s, got: %v", volVal, resMap)
  4382  	}
  4383  }
  4384  
  4385  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgExpansionOverride(c *testing.T) {
  4386  	testRequires(c, DaemonIsLinux) // Windows does not support ARG
  4387  	imgName := "bldvarstest"
  4388  	envKey := "foo"
  4389  	envVal := "bar"
  4390  	envKey1 := "foo1"
  4391  	envValOverride := "barOverride"
  4392  	dockerfile := fmt.Sprintf(`FROM busybox
  4393  		ARG %s
  4394  		ENV %s %s
  4395  		ENV %s ${%s}
  4396  		RUN echo $%s
  4397  		CMD echo $%s`, envKey, envKey, envValOverride, envKey1, envKey, envKey1, envKey1)
  4398  	result := buildImage(imgName,
  4399  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)),
  4400  		build.WithDockerfile(dockerfile),
  4401  	)
  4402  	result.Assert(c, icmd.Success)
  4403  	if strings.Count(result.Combined(), envValOverride) != 2 {
  4404  		c.Fatalf("failed to access environment variable in output: %q expected: %q", result.Combined(), envValOverride)
  4405  	}
  4406  
  4407  	containerName := "bldargCont"
  4408  	if out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined(); !strings.Contains(out, envValOverride) {
  4409  		c.Fatalf("run produced invalid output: %q, expected %q", out, envValOverride)
  4410  	}
  4411  }
  4412  
  4413  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgUntrustedDefinedAfterUse(c *testing.T) {
  4414  	testRequires(c, DaemonIsLinux) // Windows does not support ARG
  4415  	imgName := "bldargtest"
  4416  	envKey := "foo"
  4417  	envVal := "bar"
  4418  	dockerfile := fmt.Sprintf(`FROM busybox
  4419  		RUN echo $%s
  4420  		ARG %s
  4421  		CMD echo $%s`, envKey, envKey, envKey)
  4422  	result := buildImage(imgName,
  4423  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)),
  4424  		build.WithDockerfile(dockerfile),
  4425  	)
  4426  	result.Assert(c, icmd.Success)
  4427  	if strings.Contains(result.Combined(), envVal) {
  4428  		c.Fatalf("able to access environment variable in output: %q expected to be missing", result.Combined())
  4429  	}
  4430  
  4431  	containerName := "bldargCont"
  4432  	if out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined(); out != "\n" {
  4433  		c.Fatalf("run produced invalid output: %q, expected empty string", out)
  4434  	}
  4435  }
  4436  
  4437  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgBuiltinArg(c *testing.T) {
  4438  	testRequires(c, DaemonIsLinux) // Windows does not support --build-arg
  4439  	imgName := "bldargtest"
  4440  	envKey := "HTTP_PROXY"
  4441  	envVal := "bar"
  4442  	dockerfile := fmt.Sprintf(`FROM busybox
  4443  		RUN echo $%s
  4444  		CMD echo $%s`, envKey, envKey)
  4445  
  4446  	result := buildImage(imgName,
  4447  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)),
  4448  		build.WithDockerfile(dockerfile),
  4449  	)
  4450  	result.Assert(c, icmd.Success)
  4451  	if !strings.Contains(result.Combined(), envVal) {
  4452  		c.Fatalf("failed to access environment variable in output: %q expected: %q", result.Combined(), envVal)
  4453  	}
  4454  	containerName := "bldargCont"
  4455  	if out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined(); out != "\n" {
  4456  		c.Fatalf("run produced invalid output: %q, expected empty string", out)
  4457  	}
  4458  }
  4459  
  4460  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgDefaultOverride(c *testing.T) {
  4461  	testRequires(c, DaemonIsLinux) // Windows does not support ARG
  4462  	imgName := "bldargtest"
  4463  	envKey := "foo"
  4464  	envVal := "bar"
  4465  	envValOverride := "barOverride"
  4466  	dockerfile := fmt.Sprintf(`FROM busybox
  4467  		ARG %s=%s
  4468  		ENV %s $%s
  4469  		RUN echo $%s
  4470  		CMD echo $%s`, envKey, envVal, envKey, envKey, envKey, envKey)
  4471  	result := buildImage(imgName,
  4472  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envValOverride)),
  4473  		build.WithDockerfile(dockerfile),
  4474  	)
  4475  	result.Assert(c, icmd.Success)
  4476  	if strings.Count(result.Combined(), envValOverride) != 1 {
  4477  		c.Fatalf("failed to access environment variable in output: %q expected: %q", result.Combined(), envValOverride)
  4478  	}
  4479  
  4480  	containerName := "bldargCont"
  4481  	if out := cli.DockerCmd(c, "run", "--name", containerName, imgName).Combined(); !strings.Contains(out, envValOverride) {
  4482  		c.Fatalf("run produced invalid output: %q, expected %q", out, envValOverride)
  4483  	}
  4484  }
  4485  
  4486  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgUnconsumedArg(c *testing.T) {
  4487  	imgName := "bldargtest"
  4488  	envKey := "foo"
  4489  	envVal := "bar"
  4490  	dockerfile := fmt.Sprintf(`FROM busybox
  4491  		RUN echo $%s
  4492  		CMD echo $%s`, envKey, envKey)
  4493  	warnStr := "[Warning] One or more build-args"
  4494  	buildImage(imgName,
  4495  		cli.WithFlags("--build-arg", fmt.Sprintf("%s=%s", envKey, envVal)),
  4496  		build.WithDockerfile(dockerfile),
  4497  	).Assert(c, icmd.Expected{
  4498  		Out: warnStr,
  4499  	})
  4500  }
  4501  
  4502  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgEnv(c *testing.T) {
  4503  	testRequires(c, DaemonIsLinux) // Windows does not support ARG
  4504  	dockerfile := `FROM busybox
  4505  		ARG FOO1=fromfile
  4506  		ARG FOO2=fromfile
  4507  		ARG FOO3=fromfile
  4508  		ARG FOO4=fromfile
  4509  		ARG FOO5
  4510  		ARG FOO6
  4511  		ARG FO10
  4512  		RUN env
  4513  		RUN [ "$FOO1" = "fromcmd" ]
  4514  		RUN [ "$FOO2" = "" ]
  4515  		RUN [ "$FOO3" = "fromenv" ]
  4516  		RUN [ "$FOO4" = "fromfile" ]
  4517  		RUN [ "$FOO5" = "fromcmd" ]
  4518  		# The following should not exist at all in the env
  4519  		RUN [ "$(env | grep FOO6)" = "" ]
  4520  		RUN [ "$(env | grep FOO7)" = "" ]
  4521  		RUN [ "$(env | grep FOO8)" = "" ]
  4522  		RUN [ "$(env | grep FOO9)" = "" ]
  4523  		RUN [ "$FO10" = "" ]
  4524  	    `
  4525  	result := buildImage("testbuildtimeargenv",
  4526  		cli.WithFlags(
  4527  			"--build-arg", "FOO1=fromcmd",
  4528  			"--build-arg", "FOO2=",
  4529  			"--build-arg", "FOO3", // set in env
  4530  			"--build-arg", "FOO4", // not set in env
  4531  			"--build-arg", "FOO5=fromcmd",
  4532  			// FOO6 is not set at all
  4533  			"--build-arg", "FOO7=fromcmd", // should produce a warning
  4534  			"--build-arg", "FOO8=", // should produce a warning
  4535  			"--build-arg", "FOO9", // should produce a warning
  4536  			"--build-arg", "FO10", // not set in env, empty value
  4537  		),
  4538  		cli.WithEnvironmentVariables(append(os.Environ(),
  4539  			"FOO1=fromenv",
  4540  			"FOO2=fromenv",
  4541  			"FOO3=fromenv")...),
  4542  		build.WithBuildContext(c,
  4543  			build.WithFile("Dockerfile", dockerfile),
  4544  		),
  4545  	)
  4546  	result.Assert(c, icmd.Success)
  4547  
  4548  	// Now check to make sure we got a warning msg about unused build-args
  4549  	i := strings.Index(result.Combined(), "[Warning]")
  4550  	if i < 0 {
  4551  		c.Fatalf("Missing the build-arg warning in %q", result.Combined())
  4552  	}
  4553  
  4554  	out := result.Combined()[i:] // "out" should contain just the warning message now
  4555  
  4556  	// These were specified on a --build-arg but no ARG was in the Dockerfile
  4557  	assert.Assert(c, strings.Contains(out, "FOO7"))
  4558  	assert.Assert(c, strings.Contains(out, "FOO8"))
  4559  	assert.Assert(c, strings.Contains(out, "FOO9"))
  4560  }
  4561  
  4562  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgQuotedValVariants(c *testing.T) {
  4563  	imgName := "bldargtest"
  4564  	envKey := "foo"
  4565  	envKey1 := "foo1"
  4566  	envKey2 := "foo2"
  4567  	envKey3 := "foo3"
  4568  	dockerfile := fmt.Sprintf(`FROM busybox
  4569  		ARG %s=""
  4570  		ARG %s=''
  4571  		ARG %s="''"
  4572  		ARG %s='""'
  4573  		RUN [ "$%s" != "$%s" ]
  4574  		RUN [ "$%s" != "$%s" ]
  4575  		RUN [ "$%s" != "$%s" ]
  4576  		RUN [ "$%s" != "$%s" ]
  4577  		RUN [ "$%s" != "$%s" ]`, envKey, envKey1, envKey2, envKey3,
  4578  		envKey, envKey2, envKey, envKey3, envKey1, envKey2, envKey1, envKey3,
  4579  		envKey2, envKey3)
  4580  	buildImageSuccessfully(c, imgName, build.WithDockerfile(dockerfile))
  4581  }
  4582  
  4583  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgEmptyValVariants(c *testing.T) {
  4584  	testRequires(c, DaemonIsLinux) // Windows does not support ARG
  4585  	imgName := "bldargtest"
  4586  	envKey := "foo"
  4587  	envKey1 := "foo1"
  4588  	envKey2 := "foo2"
  4589  	dockerfile := fmt.Sprintf(`FROM busybox
  4590  		ARG %s=
  4591  		ARG %s=""
  4592  		ARG %s=''
  4593  		RUN [ "$%s" = "$%s" ]
  4594  		RUN [ "$%s" = "$%s" ]
  4595  		RUN [ "$%s" = "$%s" ]`, envKey, envKey1, envKey2, envKey, envKey1, envKey1, envKey2, envKey, envKey2)
  4596  	buildImageSuccessfully(c, imgName, build.WithDockerfile(dockerfile))
  4597  }
  4598  
  4599  func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgDefinitionWithNoEnvInjection(c *testing.T) {
  4600  	imgName := "bldargtest"
  4601  	envKey := "foo"
  4602  	dockerfile := fmt.Sprintf(`FROM busybox
  4603  		ARG %s
  4604  		RUN env`, envKey)
  4605  
  4606  	result := cli.BuildCmd(c, imgName, build.WithDockerfile(dockerfile))
  4607  	result.Assert(c, icmd.Success)
  4608  	if strings.Count(result.Combined(), envKey) != 1 {
  4609  		c.Fatalf("unexpected number of occurrences of the arg in output: %q expected: 1", result.Combined())
  4610  	}
  4611  }
  4612  
  4613  func (s *DockerCLIBuildSuite) TestBuildMultiStageArg(c *testing.T) {
  4614  	imgName := "multifrombldargtest"
  4615  	dockerfile := `FROM busybox
  4616      ARG foo=abc
  4617      LABEL multifromtest=1
  4618      RUN env > /out
  4619      FROM busybox
  4620      ARG bar=def
  4621      RUN env > /out`
  4622  
  4623  	result := cli.BuildCmd(c, imgName, build.WithDockerfile(dockerfile))
  4624  	result.Assert(c, icmd.Success)
  4625  
  4626  	result = cli.DockerCmd(c, "images", "-q", "-f", "label=multifromtest=1")
  4627  	result.Assert(c, icmd.Success)
  4628  
  4629  	imgs := strings.Split(strings.TrimSpace(result.Stdout()), "\n")
  4630  	assert.Assert(c, is.Len(imgs, 1), `only one image with "multifromtest" label is expected`)
  4631  
  4632  	parentID := imgs[0]
  4633  
  4634  	result = cli.DockerCmd(c, "run", "--rm", parentID, "cat", "/out")
  4635  	assert.Assert(c, strings.Contains(result.Stdout(), "foo=abc"))
  4636  	result = cli.DockerCmd(c, "run", "--rm", imgName, "cat", "/out")
  4637  	assert.Assert(c, !strings.Contains(result.Stdout(), "foo"))
  4638  	assert.Assert(c, strings.Contains(result.Stdout(), "bar=def"))
  4639  }
  4640  
  4641  func (s *DockerCLIBuildSuite) TestBuildMultiStageGlobalArg(c *testing.T) {
  4642  	imgName := "multifrombldargtest"
  4643  	dockerfile := `ARG tag=nosuchtag
  4644       FROM busybox:${tag}
  4645       LABEL multifromtest2=1
  4646       RUN env > /out
  4647       FROM busybox:${tag}
  4648       ARG tag
  4649       RUN env > /out`
  4650  
  4651  	result := cli.BuildCmd(c, imgName,
  4652  		build.WithDockerfile(dockerfile),
  4653  		cli.WithFlags("--build-arg", "tag=latest"))
  4654  	result.Assert(c, icmd.Success)
  4655  
  4656  	result = cli.DockerCmd(c, "images", "-q", "-f", "label=multifromtest2=1")
  4657  	result.Assert(c, icmd.Success)
  4658  
  4659  	imgs := strings.Split(strings.TrimSpace(result.Stdout()), "\n")
  4660  	assert.Assert(c, is.Len(imgs, 1), `only one image with "multifromtest" label is expected`)
  4661  
  4662  	parentID := imgs[0]
  4663  
  4664  	result = cli.DockerCmd(c, "run", "--rm", parentID, "cat", "/out")
  4665  	assert.Assert(c, !strings.Contains(result.Stdout(), "tag"))
  4666  	result = cli.DockerCmd(c, "run", "--rm", imgName, "cat", "/out")
  4667  	assert.Assert(c, strings.Contains(result.Stdout(), "tag=latest"))
  4668  }
  4669  
  4670  func (s *DockerCLIBuildSuite) TestBuildMultiStageUnusedArg(c *testing.T) {
  4671  	imgName := "multifromunusedarg"
  4672  	dockerfile := `FROM busybox
  4673      ARG foo
  4674      FROM busybox
  4675      ARG bar
  4676      RUN env > /out`
  4677  
  4678  	result := cli.BuildCmd(c, imgName,
  4679  		build.WithDockerfile(dockerfile),
  4680  		cli.WithFlags("--build-arg", "baz=abc"))
  4681  	result.Assert(c, icmd.Success)
  4682  	assert.Assert(c, strings.Contains(result.Combined(), "[Warning]"))
  4683  	assert.Assert(c, strings.Contains(result.Combined(), "[baz] were not consumed"))
  4684  	result = cli.DockerCmd(c, "run", "--rm", imgName, "cat", "/out")
  4685  	assert.Assert(c, !strings.Contains(result.Stdout(), "bar"))
  4686  	assert.Assert(c, !strings.Contains(result.Stdout(), "baz"))
  4687  }
  4688  
  4689  func (s *DockerCLIBuildSuite) TestBuildNoNamedVolume(c *testing.T) {
  4690  	volName := "testname:/foo"
  4691  
  4692  	if testEnv.DaemonInfo.OSType == "windows" {
  4693  		volName = "testname:C:\\foo"
  4694  	}
  4695  	cli.DockerCmd(c, "run", "-v", volName, "busybox", "sh", "-c", "touch /foo/oops")
  4696  
  4697  	dockerFile := `FROM busybox
  4698  	VOLUME ` + volName + `
  4699  	RUN ls /foo/oops
  4700  	`
  4701  	buildImage("test", build.WithDockerfile(dockerFile)).Assert(c, icmd.Expected{
  4702  		ExitCode: 1,
  4703  	})
  4704  }
  4705  
  4706  func (s *DockerCLIBuildSuite) TestBuildTagEvent(c *testing.T) {
  4707  	since := daemonUnixTime(c)
  4708  
  4709  	dockerFile := `FROM busybox
  4710  	RUN echo events
  4711  	`
  4712  	buildImageSuccessfully(c, "test", build.WithDockerfile(dockerFile))
  4713  
  4714  	until := daemonUnixTime(c)
  4715  	out := cli.DockerCmd(c, "events", "--since", since, "--until", until, "--filter", "type=image").Stdout()
  4716  	events := strings.Split(strings.TrimSpace(out), "\n")
  4717  	actions := eventActionsByIDAndType(c, events, "test:latest", "image")
  4718  	var foundTag bool
  4719  	for _, a := range actions {
  4720  		if a == "tag" {
  4721  			foundTag = true
  4722  			break
  4723  		}
  4724  	}
  4725  
  4726  	assert.Assert(c, foundTag, "No tag event found:\n%s", out)
  4727  }
  4728  
  4729  // #15780
  4730  func (s *DockerCLIBuildSuite) TestBuildMultipleTags(c *testing.T) {
  4731  	dockerfile := `
  4732  	FROM busybox
  4733  	MAINTAINER test-15780
  4734  	`
  4735  	buildImageSuccessfully(c, "tag1", cli.WithFlags("-t", "tag2:v2", "-t", "tag1:latest", "-t", "tag1"), build.WithDockerfile(dockerfile))
  4736  
  4737  	id1 := getIDByName(c, "tag1")
  4738  	id2 := getIDByName(c, "tag2:v2")
  4739  	assert.Equal(c, id1, id2)
  4740  }
  4741  
  4742  // #17290
  4743  func (s *DockerCLIBuildSuite) TestBuildCacheBrokenSymlink(c *testing.T) {
  4744  	const name = "testbuildbrokensymlink"
  4745  	ctx := fakecontext.New(c, "",
  4746  		fakecontext.WithDockerfile(`
  4747  	FROM busybox
  4748  	COPY . ./`),
  4749  		fakecontext.WithFiles(map[string]string{
  4750  			"foo": "bar",
  4751  		}))
  4752  	defer ctx.Close()
  4753  
  4754  	err := os.Symlink(filepath.Join(ctx.Dir, "nosuchfile"), filepath.Join(ctx.Dir, "asymlink"))
  4755  	assert.NilError(c, err)
  4756  
  4757  	// warm up cache
  4758  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  4759  
  4760  	// add new file to context, should invalidate cache
  4761  	err = os.WriteFile(filepath.Join(ctx.Dir, "newfile"), []byte("foo"), 0o644)
  4762  	assert.NilError(c, err)
  4763  
  4764  	result := cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  4765  	if strings.Contains(result.Combined(), "Using cache") {
  4766  		c.Fatal("2nd build used cache on ADD, it shouldn't")
  4767  	}
  4768  }
  4769  
  4770  func (s *DockerCLIBuildSuite) TestBuildFollowSymlinkToFile(c *testing.T) {
  4771  	const name = "testbuildbrokensymlink"
  4772  	ctx := fakecontext.New(c, "",
  4773  		fakecontext.WithDockerfile(`
  4774  	FROM busybox
  4775  	COPY asymlink target`),
  4776  		fakecontext.WithFiles(map[string]string{
  4777  			"foo": "bar",
  4778  		}))
  4779  	defer ctx.Close()
  4780  
  4781  	err := os.Symlink("foo", filepath.Join(ctx.Dir, "asymlink"))
  4782  	assert.NilError(c, err)
  4783  
  4784  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  4785  
  4786  	out := cli.DockerCmd(c, "run", "--rm", name, "cat", "target").Combined()
  4787  	assert.Assert(c, is.Regexp("^bar$", out))
  4788  
  4789  	// change target file should invalidate cache
  4790  	err = os.WriteFile(filepath.Join(ctx.Dir, "foo"), []byte("baz"), 0o644)
  4791  	assert.NilError(c, err)
  4792  
  4793  	result := cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  4794  	assert.Assert(c, !strings.Contains(result.Combined(), "Using cache"))
  4795  	out = cli.DockerCmd(c, "run", "--rm", name, "cat", "target").Combined()
  4796  	assert.Assert(c, is.Regexp("^baz$", out))
  4797  }
  4798  
  4799  func (s *DockerCLIBuildSuite) TestBuildFollowSymlinkToDir(c *testing.T) {
  4800  	const name = "testbuildbrokensymlink"
  4801  	ctx := fakecontext.New(c, "",
  4802  		fakecontext.WithDockerfile(`
  4803  	FROM busybox
  4804  	COPY asymlink /`),
  4805  		fakecontext.WithFiles(map[string]string{
  4806  			"foo/abc": "bar",
  4807  			"foo/def": "baz",
  4808  		}))
  4809  	defer ctx.Close()
  4810  
  4811  	err := os.Symlink("foo", filepath.Join(ctx.Dir, "asymlink"))
  4812  	assert.NilError(c, err)
  4813  
  4814  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  4815  
  4816  	out := cli.DockerCmd(c, "run", "--rm", name, "cat", "abc", "def").Combined()
  4817  	assert.Assert(c, is.Regexp("^barbaz$", out))
  4818  
  4819  	// change target file should invalidate cache
  4820  	err = os.WriteFile(filepath.Join(ctx.Dir, "foo/def"), []byte("bax"), 0o644)
  4821  	assert.NilError(c, err)
  4822  
  4823  	result := cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  4824  	assert.Assert(c, !strings.Contains(result.Combined(), "Using cache"))
  4825  	out = cli.DockerCmd(c, "run", "--rm", name, "cat", "abc", "def").Combined()
  4826  	assert.Assert(c, is.Regexp("^barbax$", out))
  4827  }
  4828  
  4829  // TestBuildSymlinkBasename tests that target file gets basename from symlink,
  4830  // not from the target file.
  4831  func (s *DockerCLIBuildSuite) TestBuildSymlinkBasename(c *testing.T) {
  4832  	const name = "testbuildbrokensymlink"
  4833  	ctx := fakecontext.New(c, "",
  4834  		fakecontext.WithDockerfile(`
  4835  	FROM busybox
  4836  	COPY asymlink /`),
  4837  		fakecontext.WithFiles(map[string]string{
  4838  			"foo": "bar",
  4839  		}))
  4840  	defer ctx.Close()
  4841  
  4842  	err := os.Symlink("foo", filepath.Join(ctx.Dir, "asymlink"))
  4843  	assert.NilError(c, err)
  4844  
  4845  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  4846  
  4847  	out := cli.DockerCmd(c, "run", "--rm", name, "cat", "asymlink").Combined()
  4848  	assert.Assert(c, is.Regexp("^bar$", out))
  4849  }
  4850  
  4851  // #17827
  4852  func (s *DockerCLIBuildSuite) TestBuildCacheRootSource(c *testing.T) {
  4853  	const name = "testbuildrootsource"
  4854  	ctx := fakecontext.New(c, "",
  4855  		fakecontext.WithDockerfile(`
  4856  	FROM busybox
  4857  	COPY / /data`),
  4858  		fakecontext.WithFiles(map[string]string{
  4859  			"foo": "bar",
  4860  		}))
  4861  	defer ctx.Close()
  4862  
  4863  	// warm up cache
  4864  	cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  4865  
  4866  	// change file, should invalidate cache
  4867  	err := os.WriteFile(filepath.Join(ctx.Dir, "foo"), []byte("baz"), 0o644)
  4868  	assert.NilError(c, err)
  4869  
  4870  	result := cli.BuildCmd(c, name, build.WithExternalBuildContext(ctx))
  4871  
  4872  	assert.Assert(c, !strings.Contains(result.Combined(), "Using cache"))
  4873  }
  4874  
  4875  // #19375
  4876  // FIXME(vdemeester) should migrate to docker/cli tests
  4877  func (s *DockerCLIBuildSuite) TestBuildFailsGitNotCallable(c *testing.T) {
  4878  	buildImage("gitnotcallable", cli.WithEnvironmentVariables("PATH="),
  4879  		build.WithContextPath("github.com/docker/v1.10-migrator.git")).Assert(c, icmd.Expected{
  4880  		ExitCode: 1,
  4881  		Err:      "unable to prepare context: unable to find 'git': ",
  4882  	})
  4883  
  4884  	buildImage("gitnotcallable", cli.WithEnvironmentVariables("PATH="),
  4885  		build.WithContextPath("https://github.com/docker/v1.10-migrator.git")).Assert(c, icmd.Expected{
  4886  		ExitCode: 1,
  4887  		Err:      "unable to prepare context: unable to find 'git': ",
  4888  	})
  4889  }
  4890  
  4891  // TestBuildWorkdirWindowsPath tests that a Windows style path works as a workdir
  4892  func (s *DockerCLIBuildSuite) TestBuildWorkdirWindowsPath(c *testing.T) {
  4893  	testRequires(c, DaemonIsWindows)
  4894  	const name = "testbuildworkdirwindowspath"
  4895  	buildImageSuccessfully(c, name, build.WithDockerfile(`
  4896  	FROM `+testEnv.PlatformDefaults.BaseImage+`
  4897  	RUN mkdir C:\\work
  4898  	WORKDIR C:\\work
  4899  	RUN if "%CD%" NEQ "C:\work" exit -1
  4900  	`))
  4901  }
  4902  
  4903  func (s *DockerCLIBuildSuite) TestBuildLabel(c *testing.T) {
  4904  	const name = "testbuildlabel"
  4905  	testLabel := "foo"
  4906  
  4907  	buildImageSuccessfully(c, name, cli.WithFlags("--label", testLabel),
  4908  		build.WithDockerfile(`
  4909    FROM `+minimalBaseImage()+`
  4910    LABEL default foo
  4911  `))
  4912  
  4913  	var labels map[string]string
  4914  	inspectFieldAndUnmarshall(c, name, "Config.Labels", &labels)
  4915  	if _, ok := labels[testLabel]; !ok {
  4916  		c.Fatal("label not found in image")
  4917  	}
  4918  }
  4919  
  4920  func (s *DockerCLIBuildSuite) TestBuildLabelOneNode(c *testing.T) {
  4921  	const name = "testbuildlabel"
  4922  	buildImageSuccessfully(c, name, cli.WithFlags("--label", "foo=bar"),
  4923  		build.WithDockerfile("FROM busybox"))
  4924  
  4925  	var labels map[string]string
  4926  	inspectFieldAndUnmarshall(c, name, "Config.Labels", &labels)
  4927  	v, ok := labels["foo"]
  4928  	if !ok {
  4929  		c.Fatal("label `foo` not found in image")
  4930  	}
  4931  	assert.Equal(c, v, "bar")
  4932  }
  4933  
  4934  func (s *DockerCLIBuildSuite) TestBuildLabelCacheCommit(c *testing.T) {
  4935  	const name = "testbuildlabelcachecommit"
  4936  	testLabel := "foo"
  4937  
  4938  	buildImageSuccessfully(c, name, build.WithDockerfile(`
  4939    FROM `+minimalBaseImage()+`
  4940    LABEL default foo
  4941    `))
  4942  	buildImageSuccessfully(c, name, cli.WithFlags("--label", testLabel),
  4943  		build.WithDockerfile(`
  4944    FROM `+minimalBaseImage()+`
  4945    LABEL default foo
  4946    `))
  4947  
  4948  	var labels map[string]string
  4949  	inspectFieldAndUnmarshall(c, name, "Config.Labels", &labels)
  4950  	if _, ok := labels[testLabel]; !ok {
  4951  		c.Fatal("label not found in image")
  4952  	}
  4953  }
  4954  
  4955  func (s *DockerCLIBuildSuite) TestBuildLabelMultiple(c *testing.T) {
  4956  	const name = "testbuildlabelmultiple"
  4957  	testLabels := map[string]string{
  4958  		"foo": "bar",
  4959  		"123": "456",
  4960  	}
  4961  	var labelArgs []string
  4962  	for k, v := range testLabels {
  4963  		labelArgs = append(labelArgs, "--label", k+"="+v)
  4964  	}
  4965  
  4966  	buildImageSuccessfully(c, name, cli.WithFlags(labelArgs...),
  4967  		build.WithDockerfile(`
  4968    FROM `+minimalBaseImage()+`
  4969    LABEL default foo
  4970  `))
  4971  
  4972  	var labels map[string]string
  4973  	inspectFieldAndUnmarshall(c, name, "Config.Labels", &labels)
  4974  	for k, v := range testLabels {
  4975  		if x, ok := labels[k]; !ok || x != v {
  4976  			c.Fatalf("label %s=%s not found in image", k, v)
  4977  		}
  4978  	}
  4979  }
  4980  
  4981  func (s *DockerRegistryAuthHtpasswdSuite) TestBuildFromAuthenticatedRegistry(c *testing.T) {
  4982  	cli.DockerCmd(c, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), privateRegistryURL)
  4983  	baseImage := privateRegistryURL + "/baseimage"
  4984  
  4985  	buildImageSuccessfully(c, baseImage, build.WithDockerfile(`
  4986  	FROM busybox
  4987  	ENV env1 val1
  4988  	`))
  4989  
  4990  	cli.DockerCmd(c, "push", baseImage)
  4991  	cli.DockerCmd(c, "rmi", baseImage)
  4992  
  4993  	buildImageSuccessfully(c, baseImage, build.WithDockerfile(fmt.Sprintf(`
  4994  	FROM %s
  4995  	ENV env2 val2
  4996  	`, baseImage)))
  4997  }
  4998  
  4999  func (s *DockerRegistryAuthHtpasswdSuite) TestBuildWithExternalAuth(c *testing.T) {
  5000  	workingDir, err := os.Getwd()
  5001  	assert.NilError(c, err)
  5002  	absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth"))
  5003  	assert.NilError(c, err)
  5004  
  5005  	osPath := os.Getenv("PATH")
  5006  	testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute)
  5007  	c.Setenv("PATH", testPath)
  5008  
  5009  	repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL)
  5010  
  5011  	tmp, err := os.MkdirTemp("", "integration-cli-")
  5012  	assert.NilError(c, err)
  5013  
  5014  	externalAuthConfig := `{ "credsStore": "shell-test" }`
  5015  
  5016  	configPath := filepath.Join(tmp, "config.json")
  5017  	err = os.WriteFile(configPath, []byte(externalAuthConfig), 0o644)
  5018  	assert.NilError(c, err)
  5019  
  5020  	cli.DockerCmd(c, "--config", tmp, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), privateRegistryURL)
  5021  
  5022  	b, err := os.ReadFile(configPath)
  5023  	assert.NilError(c, err)
  5024  	assert.Assert(c, !strings.Contains(string(b), "\"auth\":"))
  5025  	cli.DockerCmd(c, "--config", tmp, "tag", "busybox", repoName)
  5026  	cli.DockerCmd(c, "--config", tmp, "push", repoName)
  5027  
  5028  	// make sure the image is pulled when building
  5029  	cli.DockerCmd(c, "rmi", repoName)
  5030  
  5031  	icmd.RunCmd(icmd.Cmd{
  5032  		Command: []string{dockerBinary, "--config", tmp, "build", "-"},
  5033  		Stdin:   strings.NewReader(fmt.Sprintf("FROM %s", repoName)),
  5034  	}).Assert(c, icmd.Success)
  5035  }
  5036  
  5037  // Test cases in #22036
  5038  func (s *DockerCLIBuildSuite) TestBuildLabelsOverride(c *testing.T) {
  5039  	// Command line option labels will always override
  5040  	name := "scratchy"
  5041  	expected := `{"bar":"from-flag","foo":"from-flag"}`
  5042  	buildImageSuccessfully(c, name, cli.WithFlags("--label", "foo=from-flag", "--label", "bar=from-flag"),
  5043  		build.WithDockerfile(`FROM `+minimalBaseImage()+`
  5044                  LABEL foo=from-dockerfile`))
  5045  	res := inspectFieldJSON(c, name, "Config.Labels")
  5046  	if res != expected {
  5047  		c.Fatalf("Labels %s, expected %s", res, expected)
  5048  	}
  5049  
  5050  	name = "from"
  5051  	expected = `{"foo":"from-dockerfile"}`
  5052  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+`
  5053                  LABEL foo from-dockerfile`))
  5054  	res = inspectFieldJSON(c, name, "Config.Labels")
  5055  	if res != expected {
  5056  		c.Fatalf("Labels %s, expected %s", res, expected)
  5057  	}
  5058  
  5059  	// Command line option label will override even via `FROM`
  5060  	name = "new"
  5061  	expected = `{"bar":"from-dockerfile2","foo":"new"}`
  5062  	buildImageSuccessfully(c, name, cli.WithFlags("--label", "foo=new"),
  5063  		build.WithDockerfile(`FROM from
  5064                  LABEL bar from-dockerfile2`))
  5065  	res = inspectFieldJSON(c, name, "Config.Labels")
  5066  	if res != expected {
  5067  		c.Fatalf("Labels %s, expected %s", res, expected)
  5068  	}
  5069  
  5070  	// Command line option without a value set (--label foo, --label bar=)
  5071  	// will be treated as --label foo="", --label bar=""
  5072  	name = "scratchy2"
  5073  	expected = `{"bar":"","foo":""}`
  5074  	buildImageSuccessfully(c, name, cli.WithFlags("--label", "foo", "--label", "bar="),
  5075  		build.WithDockerfile(`FROM `+minimalBaseImage()+`
  5076                  LABEL foo=from-dockerfile`))
  5077  	res = inspectFieldJSON(c, name, "Config.Labels")
  5078  	if res != expected {
  5079  		c.Fatalf("Labels %s, expected %s", res, expected)
  5080  	}
  5081  
  5082  	// Command line option without a value set (--label foo, --label bar=)
  5083  	// will be treated as --label foo="", --label bar=""
  5084  	// This time is for inherited images
  5085  	name = "new2"
  5086  	expected = `{"bar":"","foo":""}`
  5087  	buildImageSuccessfully(c, name, cli.WithFlags("--label", "foo=", "--label", "bar"),
  5088  		build.WithDockerfile(`FROM from
  5089                  LABEL bar from-dockerfile2`))
  5090  	res = inspectFieldJSON(c, name, "Config.Labels")
  5091  	if res != expected {
  5092  		c.Fatalf("Labels %s, expected %s", res, expected)
  5093  	}
  5094  
  5095  	// Command line option labels with only `FROM`
  5096  	name = "scratchy"
  5097  	expected = `{"bar":"from-flag","foo":"from-flag"}`
  5098  	buildImageSuccessfully(c, name, cli.WithFlags("--label", "foo=from-flag", "--label", "bar=from-flag"),
  5099  		build.WithDockerfile(`FROM `+minimalBaseImage()))
  5100  	res = inspectFieldJSON(c, name, "Config.Labels")
  5101  	if res != expected {
  5102  		c.Fatalf("Labels %s, expected %s", res, expected)
  5103  	}
  5104  
  5105  	// Command line option labels with env var
  5106  	name = "scratchz"
  5107  	expected = `{"bar":"$PATH"}`
  5108  	buildImageSuccessfully(c, name, cli.WithFlags("--label", "bar=$PATH"),
  5109  		build.WithDockerfile(`FROM `+minimalBaseImage()))
  5110  	res = inspectFieldJSON(c, name, "Config.Labels")
  5111  	if res != expected {
  5112  		c.Fatalf("Labels %s, expected %s", res, expected)
  5113  	}
  5114  }
  5115  
  5116  // Test case for #22855
  5117  func (s *DockerCLIBuildSuite) TestBuildDeleteCommittedFile(c *testing.T) {
  5118  	const name = "test-delete-committed-file"
  5119  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  5120  		RUN echo test > file
  5121  		RUN test -e file
  5122  		RUN rm file
  5123  		RUN sh -c "! test -e file"`))
  5124  }
  5125  
  5126  // #20083
  5127  func (s *DockerCLIBuildSuite) TestBuildDockerignoreComment(c *testing.T) {
  5128  	// TODO Windows: Figure out why this test is flakey on TP5. If you add
  5129  	// something like RUN sleep 5, or even RUN ls /tmp after the ADD line,
  5130  	// it is more reliable, but that's not a good fix.
  5131  	testRequires(c, DaemonIsLinux)
  5132  
  5133  	const name = "testbuilddockerignorecleanpaths"
  5134  	dockerfile := `
  5135          FROM busybox
  5136          ADD . /tmp/
  5137          RUN sh -c "(ls -la /tmp/#1)"
  5138          RUN sh -c "(! ls -la /tmp/#2)"
  5139          RUN sh -c "(! ls /tmp/foo) && (! ls /tmp/foo2) && (ls /tmp/dir1/foo)"`
  5140  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  5141  		build.WithFile("Dockerfile", dockerfile),
  5142  		build.WithFile("foo", "foo"),
  5143  		build.WithFile("foo2", "foo2"),
  5144  		build.WithFile("dir1/foo", "foo in dir1"),
  5145  		build.WithFile("#1", "# file 1"),
  5146  		build.WithFile("#2", "# file 2"),
  5147  		build.WithFile(".dockerignore", `# Visual C++ cache files
  5148  # because we have git ;-)
  5149  # The above comment is from #20083
  5150  foo
  5151  #dir1/foo
  5152  foo2
  5153  # The following is considered as comment as # is at the beginning
  5154  #1
  5155  # The following is not considered as comment as # is not at the beginning
  5156    #2
  5157  `)))
  5158  }
  5159  
  5160  // Test case for #23221
  5161  func (s *DockerCLIBuildSuite) TestBuildWithUTF8BOM(c *testing.T) {
  5162  	const name = "test-with-utf8-bom"
  5163  	dockerfile := []byte(`FROM busybox`)
  5164  	bomDockerfile := append([]byte{0xEF, 0xBB, 0xBF}, dockerfile...)
  5165  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  5166  		build.WithFile("Dockerfile", string(bomDockerfile)),
  5167  	))
  5168  }
  5169  
  5170  // Test case for UTF-8 BOM in .dockerignore, related to #23221
  5171  func (s *DockerCLIBuildSuite) TestBuildWithUTF8BOMDockerignore(c *testing.T) {
  5172  	const name = "test-with-utf8-bom-dockerignore"
  5173  	dockerfile := `
  5174          FROM busybox
  5175  		ADD . /tmp/
  5176  		RUN ls -la /tmp
  5177  		RUN sh -c "! ls /tmp/Dockerfile"
  5178  		RUN ls /tmp/.dockerignore`
  5179  	dockerignore := []byte("./Dockerfile\n")
  5180  	bomDockerignore := append([]byte{0xEF, 0xBB, 0xBF}, dockerignore...)
  5181  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  5182  		build.WithFile("Dockerfile", dockerfile),
  5183  		build.WithFile(".dockerignore", string(bomDockerignore)),
  5184  	))
  5185  }
  5186  
  5187  // #22489 Shell test to confirm config gets updated correctly
  5188  func (s *DockerCLIBuildSuite) TestBuildShellUpdatesConfig(c *testing.T) {
  5189  	skip.If(c, versions.GreaterThan(testEnv.DaemonAPIVersion(), "1.44"), "ContainerConfig is deprecated")
  5190  	skip.If(c, testEnv.UsingSnapshotter, "ContainerConfig is not filled in c8d")
  5191  
  5192  	const name = "testbuildshellupdatesconfig"
  5193  
  5194  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM `+minimalBaseImage()+`
  5195          SHELL ["foo", "-bar"]`))
  5196  	expected := `["foo","-bar","#(nop) ","SHELL [foo -bar]"]`
  5197  	res := inspectFieldJSON(c, name, "ContainerConfig.Cmd")
  5198  	if res != expected {
  5199  		c.Fatalf("%s, expected %s", res, expected)
  5200  	}
  5201  	res = inspectFieldJSON(c, name, "ContainerConfig.Shell")
  5202  	if res != `["foo","-bar"]` {
  5203  		c.Fatalf(`%s, expected ["foo","-bar"]`, res)
  5204  	}
  5205  }
  5206  
  5207  // #22489 Changing the shell multiple times and CMD after.
  5208  func (s *DockerCLIBuildSuite) TestBuildShellMultiple(c *testing.T) {
  5209  	const name = "testbuildshellmultiple"
  5210  
  5211  	result := buildImage(name, build.WithDockerfile(`FROM busybox
  5212  		RUN echo defaultshell
  5213  		SHELL ["echo"]
  5214  		RUN echoshell
  5215  		SHELL ["ls"]
  5216  		RUN -l
  5217  		CMD -l`))
  5218  	result.Assert(c, icmd.Success)
  5219  
  5220  	// Must contain 'defaultshell' twice
  5221  	if len(strings.Split(result.Combined(), "defaultshell")) != 3 {
  5222  		c.Fatalf("defaultshell should have appeared twice in %s", result.Combined())
  5223  	}
  5224  
  5225  	// Must contain 'echoshell' twice
  5226  	if len(strings.Split(result.Combined(), "echoshell")) != 3 {
  5227  		c.Fatalf("echoshell should have appeared twice in %s", result.Combined())
  5228  	}
  5229  
  5230  	// Must contain "total " (part of ls -l)
  5231  	if !strings.Contains(result.Combined(), "total ") {
  5232  		c.Fatalf("%s should have contained 'total '", result.Combined())
  5233  	}
  5234  
  5235  	// A container started from the image uses the shell-form CMD.
  5236  	// Last shell is ls. CMD is -l. So should contain 'total '.
  5237  	outrun := cli.DockerCmd(c, "run", "--rm", name).Combined()
  5238  	if !strings.Contains(outrun, "total ") {
  5239  		c.Fatalf("Expected started container to run ls -l. %s", outrun)
  5240  	}
  5241  }
  5242  
  5243  // #22489. Changed SHELL with ENTRYPOINT
  5244  func (s *DockerCLIBuildSuite) TestBuildShellEntrypoint(c *testing.T) {
  5245  	const name = "testbuildshellentrypoint"
  5246  
  5247  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
  5248  		SHELL ["ls"]
  5249  		ENTRYPOINT -l`))
  5250  	// A container started from the image uses the shell-form ENTRYPOINT.
  5251  	// Shell is ls. ENTRYPOINT is -l. So should contain 'total '.
  5252  	outrun := cli.DockerCmd(c, "run", "--rm", name).Combined()
  5253  	if !strings.Contains(outrun, "total ") {
  5254  		c.Fatalf("Expected started container to run ls -l. %s", outrun)
  5255  	}
  5256  }
  5257  
  5258  // #22489 Shell test to confirm shell is inherited in a subsequent build
  5259  func (s *DockerCLIBuildSuite) TestBuildShellInherited(c *testing.T) {
  5260  	const name1 = "testbuildshellinherited1"
  5261  	buildImageSuccessfully(c, name1, build.WithDockerfile(`FROM busybox
  5262          SHELL ["ls"]`))
  5263  	const name2 = "testbuildshellinherited2"
  5264  	buildImage(name2, build.WithDockerfile(`FROM `+name1+`
  5265          RUN -l`)).Assert(c, icmd.Expected{
  5266  		// ls -l has "total " followed by some number in it, ls without -l does not.
  5267  		Out: "total ",
  5268  	})
  5269  }
  5270  
  5271  // #22489 Shell test to confirm non-JSON doesn't work
  5272  func (s *DockerCLIBuildSuite) TestBuildShellNotJSON(c *testing.T) {
  5273  	const name = "testbuildshellnotjson"
  5274  
  5275  	buildImage(name, build.WithDockerfile(`FROM `+minimalBaseImage()+`
  5276          sHeLl exec -form`, // Casing explicit to ensure error is upper-cased.
  5277  	)).Assert(c, icmd.Expected{
  5278  		ExitCode: 1,
  5279  		Err:      "SHELL requires the arguments to be in JSON form",
  5280  	})
  5281  }
  5282  
  5283  // #22489 Windows shell test to confirm native is powershell if executing a PS command
  5284  // This would error if the default shell were still cmd.
  5285  func (s *DockerCLIBuildSuite) TestBuildShellWindowsPowershell(c *testing.T) {
  5286  	testRequires(c, DaemonIsWindows)
  5287  	const name = "testbuildshellpowershell"
  5288  	buildImage(name, build.WithDockerfile(`FROM `+minimalBaseImage()+`
  5289          SHELL ["powershell", "-command"]
  5290  		RUN Write-Host John`)).Assert(c, icmd.Expected{
  5291  		Out: "\nJohn\n",
  5292  	})
  5293  }
  5294  
  5295  // Verify that escape is being correctly applied to words when escape directive is not \.
  5296  // Tests WORKDIR, ADD
  5297  func (s *DockerCLIBuildSuite) TestBuildEscapeNotBackslashWordTest(c *testing.T) {
  5298  	testRequires(c, DaemonIsWindows)
  5299  	const name1 = "testbuildescapenotbackslashwordtesta"
  5300  	buildImage(name1, build.WithDockerfile(`# escape= `+"`"+`
  5301  		FROM `+minimalBaseImage()+`
  5302          WORKDIR c:\windows
  5303  		RUN dir /w`)).Assert(c, icmd.Expected{
  5304  		Out: "[System32]",
  5305  	})
  5306  
  5307  	const name2 = "testbuildescapenotbackslashwordtestb"
  5308  	buildImage(name2, build.WithDockerfile(`# escape= `+"`"+`
  5309  		FROM `+minimalBaseImage()+`
  5310  		SHELL ["powershell.exe"]
  5311          WORKDIR c:\foo
  5312  		ADD Dockerfile c:\foo\
  5313  		RUN dir Dockerfile`)).Assert(c, icmd.Expected{
  5314  		Out: "-a----",
  5315  	})
  5316  }
  5317  
  5318  // #22868. Make sure shell-form CMD is not marked as escaped in the config of the image,
  5319  // but an exec-form CMD is marked.
  5320  func (s *DockerCLIBuildSuite) TestBuildCmdShellArgsEscaped(c *testing.T) {
  5321  	testRequires(c, DaemonIsWindows)
  5322  	const name1 = "testbuildcmdshellescapedshellform"
  5323  	buildImageSuccessfully(c, name1, build.WithDockerfile(`
  5324    FROM `+minimalBaseImage()+`
  5325    CMD "ipconfig"
  5326    `))
  5327  	res := inspectFieldJSON(c, name1, "Config.ArgsEscaped")
  5328  	if res != "true" {
  5329  		c.Fatalf("CMD did not update Config.ArgsEscaped on image: %v", res)
  5330  	}
  5331  	cli.DockerCmd(c, "run", "--name", "inspectme1", name1)
  5332  	cli.DockerCmd(c, "wait", "inspectme1")
  5333  	res = inspectFieldJSON(c, name1, "Config.Cmd")
  5334  
  5335  	if res != `["cmd /S /C \"ipconfig\""]` {
  5336  		c.Fatalf("CMD incorrect in Config.Cmd: got %v", res)
  5337  	}
  5338  
  5339  	// Now in JSON/exec-form
  5340  	const name2 = "testbuildcmdshellescapedexecform"
  5341  	buildImageSuccessfully(c, name2, build.WithDockerfile(`
  5342    FROM `+minimalBaseImage()+`
  5343    CMD ["ipconfig"]
  5344    `))
  5345  	res = inspectFieldJSON(c, name2, "Config.ArgsEscaped")
  5346  	if res != "false" {
  5347  		c.Fatalf("CMD set Config.ArgsEscaped on image: %v", res)
  5348  	}
  5349  	cli.DockerCmd(c, "run", "--name", "inspectme2", name2)
  5350  	cli.DockerCmd(c, "wait", "inspectme2")
  5351  	res = inspectFieldJSON(c, name2, "Config.Cmd")
  5352  
  5353  	if res != `["ipconfig"]` {
  5354  		c.Fatalf("CMD incorrect in Config.Cmd: got %v", res)
  5355  	}
  5356  }
  5357  
  5358  // Test case for #24912.
  5359  func (s *DockerCLIBuildSuite) TestBuildStepsWithProgress(c *testing.T) {
  5360  	const name = "testbuildstepswithprogress"
  5361  	totalRun := 5
  5362  	result := buildImage(name, build.WithDockerfile("FROM busybox\n"+strings.Repeat("RUN echo foo\n", totalRun)))
  5363  	result.Assert(c, icmd.Success)
  5364  	assert.Assert(c, strings.Contains(result.Combined(), fmt.Sprintf("Step 1/%d : FROM busybox", 1+totalRun)))
  5365  	for i := 2; i <= 1+totalRun; i++ {
  5366  		assert.Assert(c, strings.Contains(result.Combined(), fmt.Sprintf("Step %d/%d : RUN echo foo", i, 1+totalRun)))
  5367  	}
  5368  }
  5369  
  5370  func (s *DockerCLIBuildSuite) TestBuildWithFailure(c *testing.T) {
  5371  	const name = "testbuildwithfailure"
  5372  
  5373  	// First test case can only detect `nobody` in runtime so all steps will show up
  5374  	dockerfile := "FROM busybox\nRUN nobody"
  5375  	result := buildImage(name, build.WithDockerfile(dockerfile))
  5376  	assert.Assert(c, result.Error != nil)
  5377  	assert.Assert(c, strings.Contains(result.Stdout(), "Step 1/2 : FROM busybox"))
  5378  	assert.Assert(c, strings.Contains(result.Stdout(), "Step 2/2 : RUN nobody"))
  5379  	// Second test case `FFOM` should have been detected before build runs so no steps
  5380  	dockerfile = "FFOM nobody\nRUN nobody"
  5381  	result = buildImage(name, build.WithDockerfile(dockerfile))
  5382  	assert.Assert(c, result.Error != nil)
  5383  	assert.Assert(c, !strings.Contains(result.Stdout(), "Step 1/2 : FROM busybox"))
  5384  	assert.Assert(c, !strings.Contains(result.Stdout(), "Step 2/2 : RUN nobody"))
  5385  }
  5386  
  5387  func (s *DockerCLIBuildSuite) TestBuildCacheFromEqualDiffIDsLength(c *testing.T) {
  5388  	dockerfile := `
  5389  		FROM busybox
  5390  		RUN echo "test"
  5391  		ENTRYPOINT ["sh"]`
  5392  	ctx := fakecontext.New(c, "",
  5393  		fakecontext.WithDockerfile(dockerfile),
  5394  		fakecontext.WithFiles(map[string]string{
  5395  			"Dockerfile": dockerfile,
  5396  		}))
  5397  	defer ctx.Close()
  5398  
  5399  	cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx))
  5400  	id1 := getIDByName(c, "build1")
  5401  
  5402  	// rebuild with cache-from
  5403  	result := cli.BuildCmd(c, "build2", cli.WithFlags("--cache-from=build1"), build.WithExternalBuildContext(ctx))
  5404  	id2 := getIDByName(c, "build2")
  5405  	assert.Equal(c, id1, id2)
  5406  	assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 2)
  5407  }
  5408  
  5409  func (s *DockerCLIBuildSuite) TestBuildCacheFrom(c *testing.T) {
  5410  	testRequires(c, DaemonIsLinux) // All tests that do save are skipped in windows
  5411  	dockerfile := `
  5412  		FROM busybox
  5413  		ENV FOO=bar
  5414  		ADD baz /
  5415  		RUN touch bax`
  5416  	ctx := fakecontext.New(c, "",
  5417  		fakecontext.WithDockerfile(dockerfile),
  5418  		fakecontext.WithFiles(map[string]string{
  5419  			"Dockerfile": dockerfile,
  5420  			"baz":        "baz",
  5421  		}))
  5422  	defer ctx.Close()
  5423  
  5424  	cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx))
  5425  	id1 := getIDByName(c, "build1")
  5426  
  5427  	// rebuild with cache-from
  5428  	result := cli.BuildCmd(c, "build2", cli.WithFlags("--cache-from=build1"), build.WithExternalBuildContext(ctx))
  5429  	id2 := getIDByName(c, "build2")
  5430  	assert.Equal(c, id1, id2)
  5431  	assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 3)
  5432  	cli.DockerCmd(c, "rmi", "build2")
  5433  
  5434  	// no cache match with unknown source
  5435  	result = cli.BuildCmd(c, "build2", cli.WithFlags("--cache-from=nosuchtag"), build.WithExternalBuildContext(ctx))
  5436  	id2 = getIDByName(c, "build2")
  5437  	assert.Assert(c, id1 != id2)
  5438  	assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 0)
  5439  	cli.DockerCmd(c, "rmi", "build2")
  5440  
  5441  	// Modify file, everything up to last command and layers are reused
  5442  	dockerfile = `
  5443  		FROM busybox
  5444  		ENV FOO=bar
  5445  		ADD baz /
  5446  		RUN touch newfile`
  5447  	err := os.WriteFile(filepath.Join(ctx.Dir, "Dockerfile"), []byte(dockerfile), 0o644)
  5448  	assert.NilError(c, err)
  5449  
  5450  	result = cli.BuildCmd(c, "build2", cli.WithFlags("--cache-from=build1"), build.WithExternalBuildContext(ctx))
  5451  	id2 = getIDByName(c, "build2")
  5452  	assert.Assert(c, id1 != id2)
  5453  	assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 2)
  5454  
  5455  	layers1Str := cli.DockerCmd(c, "inspect", "-f", "{{json .RootFS.Layers}}", "build1").Combined()
  5456  	layers2Str := cli.DockerCmd(c, "inspect", "-f", "{{json .RootFS.Layers}}", "build2").Combined()
  5457  
  5458  	var layers1 []string
  5459  	var layers2 []string
  5460  	assert.Assert(c, json.Unmarshal([]byte(layers1Str), &layers1) == nil)
  5461  	assert.Assert(c, json.Unmarshal([]byte(layers2Str), &layers2) == nil)
  5462  
  5463  	assert.Equal(c, len(layers1), len(layers2))
  5464  	for i := 0; i < len(layers1)-1; i++ {
  5465  		assert.Equal(c, layers1[i], layers2[i])
  5466  	}
  5467  	assert.Assert(c, layers1[len(layers1)-1] != layers2[len(layers1)-1])
  5468  }
  5469  
  5470  func (s *DockerCLIBuildSuite) TestBuildCacheFromLoad(c *testing.T) {
  5471  	skip.If(c, testEnv.UsingSnapshotter, "Parent-child relations are lost when save/load-ing with the containerd image store")
  5472  	testRequires(c, DaemonIsLinux) // All tests that do save are skipped in windows
  5473  	dockerfile := `
  5474  		FROM busybox
  5475  		ENV FOO=bar
  5476  		ADD baz /
  5477  		RUN touch bax`
  5478  	ctx := fakecontext.New(c, "",
  5479  		fakecontext.WithDockerfile(dockerfile),
  5480  		fakecontext.WithFiles(map[string]string{
  5481  			"Dockerfile": dockerfile,
  5482  			"baz":        "baz",
  5483  		}))
  5484  	defer ctx.Close()
  5485  
  5486  	cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx))
  5487  	id1 := getIDByName(c, "build1")
  5488  
  5489  	// clear parent images
  5490  	tempDir, err := os.MkdirTemp("", "test-build-cache-from-")
  5491  	if err != nil {
  5492  		c.Fatalf("failed to create temporary directory: %s", tempDir)
  5493  	}
  5494  	defer os.RemoveAll(tempDir)
  5495  	tempFile := filepath.Join(tempDir, "img.tar")
  5496  	cli.DockerCmd(c, "save", "-o", tempFile, "build1")
  5497  	cli.DockerCmd(c, "rmi", "build1")
  5498  	cli.DockerCmd(c, "load", "-i", tempFile)
  5499  	parentID := cli.DockerCmd(c, "inspect", "-f", "{{.Parent}}", "build1").Combined()
  5500  	assert.Equal(c, strings.TrimSpace(parentID), "")
  5501  
  5502  	// cache still applies without parents
  5503  	result := cli.BuildCmd(c, "build2", cli.WithFlags("--cache-from=build1"), build.WithExternalBuildContext(ctx))
  5504  	id2 := getIDByName(c, "build2")
  5505  	assert.Equal(c, id1, id2)
  5506  	assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 3)
  5507  	history1 := cli.DockerCmd(c, "history", "-q", "build2").Combined()
  5508  	// Retry, no new intermediate images
  5509  	result = cli.BuildCmd(c, "build3", cli.WithFlags("--cache-from=build1"), build.WithExternalBuildContext(ctx))
  5510  	id3 := getIDByName(c, "build3")
  5511  	assert.Equal(c, id1, id3)
  5512  	assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 3)
  5513  	history2 := cli.DockerCmd(c, "history", "-q", "build3").Combined()
  5514  
  5515  	assert.Equal(c, history1, history2)
  5516  	cli.DockerCmd(c, "rmi", "build2")
  5517  	cli.DockerCmd(c, "rmi", "build3")
  5518  	cli.DockerCmd(c, "rmi", "build1")
  5519  	cli.DockerCmd(c, "load", "-i", tempFile)
  5520  }
  5521  
  5522  func (s *DockerCLIBuildSuite) TestBuildMultiStageCache(c *testing.T) {
  5523  	testRequires(c, DaemonIsLinux) // All tests that do save are skipped in windows
  5524  	dockerfile := `
  5525  		FROM busybox
  5526  		ADD baz /
  5527  		FROM busybox
  5528      ADD baz /`
  5529  	ctx := fakecontext.New(c, "",
  5530  		fakecontext.WithDockerfile(dockerfile),
  5531  		fakecontext.WithFiles(map[string]string{
  5532  			"Dockerfile": dockerfile,
  5533  			"baz":        "baz",
  5534  		}))
  5535  	defer ctx.Close()
  5536  
  5537  	result := cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx))
  5538  	// second part of dockerfile was a repeat of first so should be cached
  5539  	assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 1)
  5540  
  5541  	result = cli.BuildCmd(c, "build2", cli.WithFlags("--cache-from=build1"), build.WithExternalBuildContext(ctx))
  5542  	// now both parts of dockerfile should be cached
  5543  	assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 2)
  5544  }
  5545  
  5546  func (s *DockerCLIBuildSuite) TestBuildNetNone(c *testing.T) {
  5547  	testRequires(c, DaemonIsLinux)
  5548  	const name = "testbuildnetnone"
  5549  	buildImage(name, cli.WithFlags("--network=none"), build.WithDockerfile(`
  5550    FROM busybox
  5551    RUN ping -c 1 8.8.8.8
  5552    `)).Assert(c, icmd.Expected{
  5553  		ExitCode: 1,
  5554  		Out:      "unreachable",
  5555  	})
  5556  }
  5557  
  5558  func (s *DockerCLIBuildSuite) TestBuildNetContainer(c *testing.T) {
  5559  	testRequires(c, DaemonIsLinux)
  5560  
  5561  	id := cli.DockerCmd(c, "run", "--hostname", "foobar", "-d", "busybox", "nc", "-ll", "-p", "1234", "-e", "hostname").Stdout()
  5562  
  5563  	const name = "testbuildnetcontainer"
  5564  	buildImageSuccessfully(c, name, cli.WithFlags("--network=container:"+strings.TrimSpace(id)),
  5565  		build.WithDockerfile(`
  5566    FROM busybox
  5567    RUN nc localhost 1234 > /otherhost
  5568    `))
  5569  
  5570  	host := cli.DockerCmd(c, "run", "testbuildnetcontainer", "cat", "/otherhost").Combined()
  5571  	assert.Equal(c, strings.TrimSpace(host), "foobar")
  5572  }
  5573  
  5574  func (s *DockerCLIBuildSuite) TestBuildWithExtraHost(c *testing.T) {
  5575  	testRequires(c, DaemonIsLinux)
  5576  
  5577  	const name = "testbuildwithextrahost"
  5578  	buildImageSuccessfully(c, name,
  5579  		cli.WithFlags(
  5580  			"--add-host", "foo:127.0.0.1",
  5581  			"--add-host", "bar:127.0.0.1",
  5582  		),
  5583  		build.WithDockerfile(`
  5584    FROM busybox
  5585    RUN ping -c 1 foo
  5586    RUN ping -c 1 bar
  5587    `))
  5588  }
  5589  
  5590  func (s *DockerCLIBuildSuite) TestBuildWithExtraHostInvalidFormat(c *testing.T) {
  5591  	testRequires(c, DaemonIsLinux)
  5592  	dockerfile := `
  5593  		FROM busybox
  5594  		RUN ping -c 1 foo`
  5595  
  5596  	testCases := []struct {
  5597  		testName   string
  5598  		dockerfile string
  5599  		buildFlag  string
  5600  	}{
  5601  		{"extra_host_missing_ip", dockerfile, "--add-host=foo"},
  5602  		{"extra_host_missing_ip_with_delimiter", dockerfile, "--add-host=foo:"},
  5603  		{"extra_host_missing_hostname", dockerfile, "--add-host=:127.0.0.1"},
  5604  		{"extra_host_invalid_ipv4", dockerfile, "--add-host=foo:101.10.2"},
  5605  		{"extra_host_invalid_ipv6", dockerfile, "--add-host=foo:2001::1::3F"},
  5606  	}
  5607  
  5608  	for _, tc := range testCases {
  5609  		result := buildImage(tc.testName, cli.WithFlags(tc.buildFlag), build.WithDockerfile(tc.dockerfile))
  5610  		result.Assert(c, icmd.Expected{
  5611  			ExitCode: 125,
  5612  		})
  5613  	}
  5614  }
  5615  
  5616  func (s *DockerCLIBuildSuite) TestBuildMultiStageCopyFromSyntax(c *testing.T) {
  5617  	//nolint:dupword
  5618  	const dockerfile = `
  5619  		FROM busybox AS first
  5620  		COPY foo bar
  5621  
  5622  		FROM busybox
  5623  		%s
  5624  		COPY baz baz
  5625  		RUN echo mno > baz/cc
  5626  
  5627  		FROM busybox
  5628  		COPY bar /
  5629  		COPY --from=1 baz sub/
  5630  		COPY --from=0 bar baz
  5631  		COPY --from=first bar bay`
  5632  
  5633  	ctx := fakecontext.New(c, "",
  5634  		fakecontext.WithDockerfile(fmt.Sprintf(dockerfile, "")),
  5635  		fakecontext.WithFiles(map[string]string{
  5636  			"foo":    "abc",
  5637  			"bar":    "def",
  5638  			"baz/aa": "ghi",
  5639  			"baz/bb": "jkl",
  5640  		}))
  5641  	defer ctx.Close()
  5642  
  5643  	cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx))
  5644  
  5645  	cli.DockerCmd(c, "run", "build1", "cat", "bar").Assert(c, icmd.Expected{Out: "def"})
  5646  	cli.DockerCmd(c, "run", "build1", "cat", "sub/aa").Assert(c, icmd.Expected{Out: "ghi"})
  5647  	cli.DockerCmd(c, "run", "build1", "cat", "sub/cc").Assert(c, icmd.Expected{Out: "mno"})
  5648  	cli.DockerCmd(c, "run", "build1", "cat", "baz").Assert(c, icmd.Expected{Out: "abc"})
  5649  	cli.DockerCmd(c, "run", "build1", "cat", "bay").Assert(c, icmd.Expected{Out: "abc"})
  5650  
  5651  	result := cli.BuildCmd(c, "build2", build.WithExternalBuildContext(ctx))
  5652  
  5653  	// all commands should be cached
  5654  	assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 7)
  5655  	assert.Equal(c, getIDByName(c, "build1"), getIDByName(c, "build2"))
  5656  
  5657  	err := os.WriteFile(filepath.Join(ctx.Dir, "Dockerfile"), []byte(fmt.Sprintf(dockerfile, "COPY baz/aa foo")), 0o644)
  5658  	assert.NilError(c, err)
  5659  
  5660  	// changing file in parent block should not affect last block
  5661  	result = cli.BuildCmd(c, "build3", build.WithExternalBuildContext(ctx))
  5662  	assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 5)
  5663  
  5664  	err = os.WriteFile(filepath.Join(ctx.Dir, "foo"), []byte("pqr"), 0o644)
  5665  	assert.NilError(c, err)
  5666  
  5667  	// changing file in parent block should affect both first and last block
  5668  	result = cli.BuildCmd(c, "build4", build.WithExternalBuildContext(ctx))
  5669  	assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 5)
  5670  
  5671  	cli.DockerCmd(c, "run", "build4", "cat", "bay").Assert(c, icmd.Expected{Out: "pqr"})
  5672  	cli.DockerCmd(c, "run", "build4", "cat", "baz").Assert(c, icmd.Expected{Out: "pqr"})
  5673  }
  5674  
  5675  func (s *DockerCLIBuildSuite) TestBuildMultiStageCopyFromErrors(c *testing.T) {
  5676  	testCases := []struct {
  5677  		dockerfile    string
  5678  		expectedError string
  5679  	}{
  5680  		{
  5681  			dockerfile: `
  5682  		FROM busybox
  5683  		COPY --from=foo foo bar`,
  5684  			expectedError: "invalid from flag value foo",
  5685  		},
  5686  		{
  5687  			dockerfile: `
  5688  		FROM busybox
  5689  		COPY --from=0 foo bar`,
  5690  			expectedError: "invalid from flag value 0: refers to current build stage",
  5691  		},
  5692  		{
  5693  			dockerfile: `
  5694  		FROM busybox AS foo
  5695  		COPY --from=bar foo bar`,
  5696  			expectedError: "invalid from flag value bar",
  5697  		},
  5698  		{
  5699  			dockerfile: `
  5700  		FROM busybox AS 1
  5701  		COPY --from=1 foo bar`,
  5702  			expectedError: "invalid name for build stage",
  5703  		},
  5704  	}
  5705  
  5706  	for _, tc := range testCases {
  5707  		ctx := fakecontext.New(c, "",
  5708  			fakecontext.WithDockerfile(tc.dockerfile),
  5709  			fakecontext.WithFiles(map[string]string{
  5710  				"foo": "abc",
  5711  			}))
  5712  
  5713  		cli.Docker(cli.Args("build", "-t", "build1"), build.WithExternalBuildContext(ctx)).Assert(c, icmd.Expected{
  5714  			ExitCode: 1,
  5715  			Err:      tc.expectedError,
  5716  		})
  5717  
  5718  		ctx.Close()
  5719  	}
  5720  }
  5721  
  5722  func (s *DockerCLIBuildSuite) TestBuildMultiStageMultipleBuilds(c *testing.T) {
  5723  	dockerfile := `
  5724  		FROM busybox
  5725  		COPY foo bar`
  5726  	ctx := fakecontext.New(c, "",
  5727  		fakecontext.WithDockerfile(dockerfile),
  5728  		fakecontext.WithFiles(map[string]string{
  5729  			"foo": "abc",
  5730  		}))
  5731  	defer ctx.Close()
  5732  
  5733  	cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx))
  5734  
  5735  	dockerfile = `
  5736  		FROM build1:latest AS foo
  5737      FROM busybox
  5738  		COPY --from=foo bar /
  5739  		COPY foo /`
  5740  	ctx = fakecontext.New(c, "",
  5741  		fakecontext.WithDockerfile(dockerfile),
  5742  		fakecontext.WithFiles(map[string]string{
  5743  			"foo": "def",
  5744  		}))
  5745  	defer ctx.Close()
  5746  
  5747  	cli.BuildCmd(c, "build2", build.WithExternalBuildContext(ctx))
  5748  
  5749  	out := cli.DockerCmd(c, "run", "build2", "cat", "bar").Combined()
  5750  	assert.Equal(c, strings.TrimSpace(out), "abc")
  5751  	out = cli.DockerCmd(c, "run", "build2", "cat", "foo").Combined()
  5752  	assert.Equal(c, strings.TrimSpace(out), "def")
  5753  }
  5754  
  5755  func (s *DockerCLIBuildSuite) TestBuildMultiStageImplicitFrom(c *testing.T) {
  5756  	dockerfile := `
  5757  		FROM busybox
  5758  		COPY --from=busybox /etc/passwd /mypasswd
  5759  		RUN cmp /etc/passwd /mypasswd`
  5760  
  5761  	if DaemonIsWindows() {
  5762  		dockerfile = `
  5763  			FROM busybox
  5764  			COPY --from=busybox License.txt foo`
  5765  	}
  5766  
  5767  	ctx := fakecontext.New(c, "",
  5768  		fakecontext.WithDockerfile(dockerfile),
  5769  	)
  5770  	defer ctx.Close()
  5771  
  5772  	cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx))
  5773  
  5774  	if DaemonIsWindows() {
  5775  		out := cli.DockerCmd(c, "run", "build1", "cat", "License.txt").Combined()
  5776  		assert.Assert(c, len(out) > 10)
  5777  		out2 := cli.DockerCmd(c, "run", "build1", "cat", "foo").Combined()
  5778  		assert.Equal(c, out, out2)
  5779  	}
  5780  }
  5781  
  5782  func (s *DockerRegistrySuite) TestBuildMultiStageImplicitPull(c *testing.T) {
  5783  	repoName := fmt.Sprintf("%v/dockercli/testf", privateRegistryURL)
  5784  
  5785  	dockerfile := `
  5786  		FROM busybox
  5787  		COPY foo bar`
  5788  	ctx := fakecontext.New(c, "",
  5789  		fakecontext.WithDockerfile(dockerfile),
  5790  		fakecontext.WithFiles(map[string]string{
  5791  			"foo": "abc",
  5792  		}))
  5793  	defer ctx.Close()
  5794  
  5795  	cli.BuildCmd(c, repoName, build.WithExternalBuildContext(ctx))
  5796  
  5797  	cli.DockerCmd(c, "push", repoName)
  5798  	cli.DockerCmd(c, "rmi", repoName)
  5799  
  5800  	dockerfile = `
  5801  		FROM busybox
  5802  		COPY --from=%s bar baz`
  5803  
  5804  	ctx = fakecontext.New(c, "", fakecontext.WithDockerfile(fmt.Sprintf(dockerfile, repoName)))
  5805  	defer ctx.Close()
  5806  
  5807  	cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx))
  5808  
  5809  	cli.Docker(cli.Args("run", "build1", "cat", "baz")).Assert(c, icmd.Expected{Out: "abc"})
  5810  }
  5811  
  5812  func (s *DockerCLIBuildSuite) TestBuildMultiStageNameVariants(c *testing.T) {
  5813  	dockerfile := `
  5814  		FROM busybox as foo
  5815  		COPY foo /
  5816  		FROM foo as foo1
  5817  		RUN echo 1 >> foo
  5818  		FROM foo as foO2
  5819  		RUN echo 2 >> foo
  5820  		FROM foo
  5821  		COPY --from=foo1 foo f1
  5822  		COPY --from=FOo2 foo f2
  5823  		` // foo2 case also tests that names are case insensitive
  5824  	ctx := fakecontext.New(c, "",
  5825  		fakecontext.WithDockerfile(dockerfile),
  5826  		fakecontext.WithFiles(map[string]string{
  5827  			"foo": "bar",
  5828  		}))
  5829  	defer ctx.Close()
  5830  
  5831  	cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx))
  5832  	cli.Docker(cli.Args("run", "build1", "cat", "foo")).Assert(c, icmd.Expected{Out: "bar"})
  5833  	cli.Docker(cli.Args("run", "build1", "cat", "f1")).Assert(c, icmd.Expected{Out: "bar1"})
  5834  	cli.Docker(cli.Args("run", "build1", "cat", "f2")).Assert(c, icmd.Expected{Out: "bar2"})
  5835  }
  5836  
  5837  func (s *DockerCLIBuildSuite) TestBuildMultiStageMultipleBuildsWindows(c *testing.T) {
  5838  	testRequires(c, DaemonIsWindows)
  5839  	dockerfile := `
  5840  		FROM ` + testEnv.PlatformDefaults.BaseImage + `
  5841  		COPY foo c:\\bar`
  5842  	ctx := fakecontext.New(c, "",
  5843  		fakecontext.WithDockerfile(dockerfile),
  5844  		fakecontext.WithFiles(map[string]string{
  5845  			"foo": "abc",
  5846  		}))
  5847  	defer ctx.Close()
  5848  
  5849  	cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx))
  5850  
  5851  	dockerfile = `
  5852  		FROM build1:latest
  5853      	FROM ` + testEnv.PlatformDefaults.BaseImage + `
  5854  		COPY --from=0 c:\\bar /
  5855  		COPY foo /`
  5856  	ctx = fakecontext.New(c, "",
  5857  		fakecontext.WithDockerfile(dockerfile),
  5858  		fakecontext.WithFiles(map[string]string{
  5859  			"foo": "def",
  5860  		}))
  5861  	defer ctx.Close()
  5862  
  5863  	cli.BuildCmd(c, "build2", build.WithExternalBuildContext(ctx))
  5864  
  5865  	out := cli.DockerCmd(c, "run", "build2", "cmd.exe", "/s", "/c", "type", "c:\\bar").Combined()
  5866  	assert.Equal(c, strings.TrimSpace(out), "abc")
  5867  	out = cli.DockerCmd(c, "run", "build2", "cmd.exe", "/s", "/c", "type", "c:\\foo").Combined()
  5868  	assert.Equal(c, strings.TrimSpace(out), "def")
  5869  }
  5870  
  5871  func (s *DockerCLIBuildSuite) TestBuildCopyFromForbidWindowsSystemPaths(c *testing.T) {
  5872  	testRequires(c, DaemonIsWindows)
  5873  	dockerfile := `
  5874  		FROM ` + testEnv.PlatformDefaults.BaseImage + `
  5875  		FROM ` + testEnv.PlatformDefaults.BaseImage + `
  5876  		COPY --from=0 %s c:\\oscopy
  5877  		`
  5878  	exp := icmd.Expected{
  5879  		ExitCode: 1,
  5880  		Err:      "copy from c:\\ or c:\\windows is not allowed on windows",
  5881  	}
  5882  	buildImage("testforbidsystempaths1", build.WithDockerfile(fmt.Sprintf(dockerfile, "c:\\\\"))).Assert(c, exp)
  5883  	buildImage("testforbidsystempaths2", build.WithDockerfile(fmt.Sprintf(dockerfile, "C:\\\\"))).Assert(c, exp)
  5884  	buildImage("testforbidsystempaths3", build.WithDockerfile(fmt.Sprintf(dockerfile, "c:\\\\windows"))).Assert(c, exp)
  5885  	buildImage("testforbidsystempaths4", build.WithDockerfile(fmt.Sprintf(dockerfile, "c:\\\\wInDows"))).Assert(c, exp)
  5886  }
  5887  
  5888  func (s *DockerCLIBuildSuite) TestBuildCopyFromForbidWindowsRelativePaths(c *testing.T) {
  5889  	testRequires(c, DaemonIsWindows)
  5890  	dockerfile := `
  5891  		FROM ` + testEnv.PlatformDefaults.BaseImage + `
  5892  		FROM ` + testEnv.PlatformDefaults.BaseImage + `
  5893  		COPY --from=0 %s c:\\oscopy
  5894  		`
  5895  	exp := icmd.Expected{
  5896  		ExitCode: 1,
  5897  		Err:      "copy from c:\\ or c:\\windows is not allowed on windows",
  5898  	}
  5899  	buildImage("testforbidsystempaths1", build.WithDockerfile(fmt.Sprintf(dockerfile, "c:"))).Assert(c, exp)
  5900  	buildImage("testforbidsystempaths2", build.WithDockerfile(fmt.Sprintf(dockerfile, "."))).Assert(c, exp)
  5901  	buildImage("testforbidsystempaths3", build.WithDockerfile(fmt.Sprintf(dockerfile, "..\\\\"))).Assert(c, exp)
  5902  	buildImage("testforbidsystempaths4", build.WithDockerfile(fmt.Sprintf(dockerfile, ".\\\\windows"))).Assert(c, exp)
  5903  	buildImage("testforbidsystempaths5", build.WithDockerfile(fmt.Sprintf(dockerfile, "\\\\windows"))).Assert(c, exp)
  5904  }
  5905  
  5906  func (s *DockerCLIBuildSuite) TestBuildCopyFromWindowsIsCaseInsensitive(c *testing.T) {
  5907  	testRequires(c, DaemonIsWindows)
  5908  	dockerfile := `
  5909  		FROM ` + testEnv.PlatformDefaults.BaseImage + `
  5910  		COPY foo /
  5911  		FROM ` + testEnv.PlatformDefaults.BaseImage + `
  5912  		COPY --from=0 c:\\fOo c:\\copied
  5913  		RUN type c:\\copied
  5914  		`
  5915  	cli.Docker(cli.Args("build", "-t", "copyfrom-windows-insensitive"), build.WithBuildContext(c,
  5916  		build.WithFile("Dockerfile", dockerfile),
  5917  		build.WithFile("foo", "hello world"),
  5918  	)).Assert(c, icmd.Expected{
  5919  		ExitCode: 0,
  5920  		Out:      "hello world",
  5921  	})
  5922  }
  5923  
  5924  // #33176
  5925  func (s *DockerCLIBuildSuite) TestBuildMultiStageResetScratch(c *testing.T) {
  5926  	testRequires(c, DaemonIsLinux)
  5927  
  5928  	dockerfile := `
  5929  		FROM busybox
  5930  		WORKDIR /foo/bar
  5931  		FROM scratch
  5932  		ENV FOO=bar
  5933  		`
  5934  	ctx := fakecontext.New(c, "",
  5935  		fakecontext.WithDockerfile(dockerfile),
  5936  	)
  5937  	defer ctx.Close()
  5938  
  5939  	cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx))
  5940  
  5941  	res := cli.InspectCmd(c, "build1", cli.Format(".Config.WorkingDir")).Combined()
  5942  	assert.Equal(c, strings.TrimSpace(res), "")
  5943  }
  5944  
  5945  func (s *DockerCLIBuildSuite) TestBuildIntermediateTarget(c *testing.T) {
  5946  	dockerfile := `
  5947  		FROM busybox AS build-env
  5948  		CMD ["/dev"]
  5949  		FROM busybox
  5950  		CMD ["/dist"]
  5951  		`
  5952  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(dockerfile))
  5953  	defer ctx.Close()
  5954  
  5955  	cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx),
  5956  		cli.WithFlags("--target", "build-env"))
  5957  
  5958  	res := cli.InspectCmd(c, "build1", cli.Format("json .Config.Cmd")).Combined()
  5959  	assert.Equal(c, strings.TrimSpace(res), `["/dev"]`)
  5960  
  5961  	// Stage name is case-insensitive by design
  5962  	cli.BuildCmd(c, "build1", build.WithExternalBuildContext(ctx),
  5963  		cli.WithFlags("--target", "BUIld-EnV"))
  5964  
  5965  	res = cli.InspectCmd(c, "build1", cli.Format("json .Config.Cmd")).Combined()
  5966  	assert.Equal(c, strings.TrimSpace(res), `["/dev"]`)
  5967  
  5968  	result := cli.Docker(cli.Args("build", "-t", "build1"), build.WithExternalBuildContext(ctx),
  5969  		cli.WithFlags("--target", "nosuchtarget"))
  5970  	result.Assert(c, icmd.Expected{
  5971  		ExitCode: 1,
  5972  		Err:      "target stage \"nosuchtarget\" could not be found",
  5973  	})
  5974  }
  5975  
  5976  // TestBuildOpaqueDirectory tests that a build succeeds which
  5977  // creates opaque directories.
  5978  // See https://github.com/docker/docker/issues/25244
  5979  func (s *DockerCLIBuildSuite) TestBuildOpaqueDirectory(c *testing.T) {
  5980  	testRequires(c, DaemonIsLinux)
  5981  	dockerFile := `
  5982  		FROM busybox
  5983  		RUN mkdir /dir1 && touch /dir1/f1
  5984  		RUN rm -rf /dir1 && mkdir /dir1 && touch /dir1/f2
  5985  		RUN touch /dir1/f3
  5986  		RUN [ -f /dir1/f2 ]
  5987  		`
  5988  	// Test that build succeeds, last command fails if opaque directory
  5989  	// was not handled correctly
  5990  	buildImageSuccessfully(c, "testopaquedirectory", build.WithDockerfile(dockerFile))
  5991  }
  5992  
  5993  // Windows test for USER in dockerfile
  5994  func (s *DockerCLIBuildSuite) TestBuildWindowsUser(c *testing.T) {
  5995  	testRequires(c, DaemonIsWindows)
  5996  	const name = "testbuildwindowsuser"
  5997  	//nolint:dupword
  5998  	buildImage(name, build.WithDockerfile(`FROM `+testEnv.PlatformDefaults.BaseImage+`
  5999  		RUN net user user /add
  6000  		USER user
  6001  		RUN set username
  6002  		`)).Assert(c, icmd.Expected{
  6003  		Out: "USERNAME=user",
  6004  	})
  6005  }
  6006  
  6007  // Verifies if COPY file . when WORKDIR is set to a non-existing directory,
  6008  // the directory is created and the file is copied into the directory,
  6009  // as opposed to the file being copied as a file with the name of the
  6010  // directory. Fix for 27545 (found on Windows, but regression good for Linux too).
  6011  // Note 27545 was reverted in 28505, but a new fix was added subsequently in 28514.
  6012  func (s *DockerCLIBuildSuite) TestBuildCopyFileDotWithWorkdir(c *testing.T) {
  6013  	const name = "testbuildcopyfiledotwithworkdir"
  6014  	buildImageSuccessfully(c, name, build.WithBuildContext(c,
  6015  		build.WithFile("Dockerfile", `FROM busybox
  6016  WORKDIR /foo
  6017  COPY file .
  6018  RUN ["cat", "/foo/file"]
  6019  `),
  6020  		build.WithFile("file", "content"),
  6021  	))
  6022  }
  6023  
  6024  // Case-insensitive environment variables on Windows
  6025  func (s *DockerCLIBuildSuite) TestBuildWindowsEnvCaseInsensitive(c *testing.T) {
  6026  	testRequires(c, DaemonIsWindows)
  6027  	const name = "testbuildwindowsenvcaseinsensitive"
  6028  	buildImageSuccessfully(c, name, build.WithDockerfile(`
  6029  		FROM `+testEnv.PlatformDefaults.BaseImage+`
  6030  		ENV FOO=bar foo=baz
  6031    `))
  6032  	res := inspectFieldJSON(c, name, "Config.Env")
  6033  	if res != `["foo=baz"]` { // Should not have FOO=bar in it - takes the last one processed. And only one entry as deduped.
  6034  		c.Fatalf("Case insensitive environment variables on Windows failed. Got %s", res)
  6035  	}
  6036  }
  6037  
  6038  // Test case for 29667
  6039  func (s *DockerCLIBuildSuite) TestBuildWorkdirImageCmd(c *testing.T) {
  6040  	imgName := "testworkdirimagecmd"
  6041  	buildImageSuccessfully(c, imgName, build.WithDockerfile(`
  6042  FROM busybox
  6043  WORKDIR /foo/bar
  6044  `))
  6045  	out := cli.DockerCmd(c, "inspect", "--format", "{{ json .Config.Cmd }}", imgName).Stdout()
  6046  	assert.Equal(c, strings.TrimSpace(out), `["sh"]`)
  6047  
  6048  	imgName = "testworkdirlabelimagecmd"
  6049  	buildImageSuccessfully(c, imgName, build.WithDockerfile(`
  6050  FROM busybox
  6051  WORKDIR /foo/bar
  6052  LABEL a=b
  6053  `))
  6054  
  6055  	out = cli.DockerCmd(c, "inspect", "--format", "{{ json .Config.Cmd }}", imgName).Stdout()
  6056  	assert.Equal(c, strings.TrimSpace(out), `["sh"]`)
  6057  }
  6058  
  6059  // Test case for 28902/28909
  6060  func (s *DockerCLIBuildSuite) TestBuildWorkdirCmd(c *testing.T) {
  6061  	testRequires(c, DaemonIsLinux)
  6062  	const name = "testbuildworkdircmd"
  6063  	dockerFile := `
  6064                  FROM busybox
  6065                  WORKDIR /
  6066                  `
  6067  	buildImageSuccessfully(c, name, build.WithDockerfile(dockerFile))
  6068  	result := buildImage(name, build.WithDockerfile(dockerFile))
  6069  	result.Assert(c, icmd.Success)
  6070  	assert.Equal(c, strings.Count(result.Combined(), "Using cache"), 1)
  6071  }
  6072  
  6073  // FIXME(vdemeester) should be a unit test
  6074  func (s *DockerCLIBuildSuite) TestBuildLineErrorOnBuild(c *testing.T) {
  6075  	const name = "test_build_line_error_onbuild"
  6076  	buildImage(name, build.WithDockerfile(`FROM busybox
  6077    ONBUILD
  6078    `)).Assert(c, icmd.Expected{
  6079  		ExitCode: 1,
  6080  		Err:      "parse error on line 2: ONBUILD requires at least one argument",
  6081  	})
  6082  }
  6083  
  6084  // FIXME(vdemeester) should be a unit test
  6085  func (s *DockerCLIBuildSuite) TestBuildLineErrorUnknownInstruction(c *testing.T) {
  6086  	const name = "test_build_line_error_unknown_instruction"
  6087  	cli.Docker(cli.Args("build", "-t", name), build.WithDockerfile(`FROM busybox
  6088    RUN echo hello world
  6089    NOINSTRUCTION echo ba
  6090    RUN echo hello
  6091    ERROR
  6092    `)).Assert(c, icmd.Expected{
  6093  		ExitCode: 1,
  6094  		Err:      "parse error on line 3: unknown instruction: NOINSTRUCTION",
  6095  	})
  6096  }
  6097  
  6098  // FIXME(vdemeester) should be a unit test
  6099  func (s *DockerCLIBuildSuite) TestBuildLineErrorWithEmptyLines(c *testing.T) {
  6100  	const name = "test_build_line_error_with_empty_lines"
  6101  	cli.Docker(cli.Args("build", "-t", name), build.WithDockerfile(`
  6102    FROM busybox
  6103  
  6104    RUN echo hello world
  6105  
  6106    NOINSTRUCTION echo ba
  6107  
  6108    CMD ["/bin/init"]
  6109    `)).Assert(c, icmd.Expected{
  6110  		ExitCode: 1,
  6111  		Err:      "parse error on line 6: unknown instruction: NOINSTRUCTION",
  6112  	})
  6113  }
  6114  
  6115  // FIXME(vdemeester) should be a unit test
  6116  func (s *DockerCLIBuildSuite) TestBuildLineErrorWithComments(c *testing.T) {
  6117  	const name = "test_build_line_error_with_comments"
  6118  	cli.Docker(cli.Args("build", "-t", name), build.WithDockerfile(`FROM busybox
  6119    # This will print hello world
  6120    # and then ba
  6121    RUN echo hello world
  6122    NOINSTRUCTION echo ba
  6123    `)).Assert(c, icmd.Expected{
  6124  		ExitCode: 1,
  6125  		Err:      "parse error on line 5: unknown instruction: NOINSTRUCTION",
  6126  	})
  6127  }
  6128  
  6129  // #31957
  6130  func (s *DockerCLIBuildSuite) TestBuildSetCommandWithDefinedShell(c *testing.T) {
  6131  	buildImageSuccessfully(c, "build1", build.WithDockerfile(`
  6132  FROM busybox
  6133  SHELL ["/bin/sh", "-c"]
  6134  `))
  6135  	buildImageSuccessfully(c, "build2", build.WithDockerfile(`
  6136  FROM build1
  6137  CMD echo foo
  6138  `))
  6139  
  6140  	out := cli.DockerCmd(c, "inspect", "--format", "{{ json .Config.Cmd }}", "build2").Stdout()
  6141  	expected := `["/bin/sh","-c","echo foo"]`
  6142  	if testEnv.DaemonInfo.OSType == "windows" {
  6143  		expected = `["/bin/sh -c echo foo"]`
  6144  	}
  6145  	assert.Equal(c, strings.TrimSpace(out), expected)
  6146  }
  6147  
  6148  // FIXME(vdemeester) should migrate to docker/cli tests
  6149  func (s *DockerCLIBuildSuite) TestBuildIidFile(c *testing.T) {
  6150  	tmpDir, err := os.MkdirTemp("", "TestBuildIidFile")
  6151  	if err != nil {
  6152  		c.Fatal(err)
  6153  	}
  6154  	defer os.RemoveAll(tmpDir)
  6155  	tmpIidFile := filepath.Join(tmpDir, "iid")
  6156  
  6157  	const name = "testbuildiidfile"
  6158  	// Use a Dockerfile with multiple stages to ensure we get the last one
  6159  	cli.BuildCmd(c, name,
  6160  		build.WithDockerfile(`FROM `+minimalBaseImage()+` AS stage1
  6161  ENV FOO FOO1
  6162  FROM `+minimalBaseImage()+`
  6163  ENV BAR BAZ`),
  6164  		cli.WithFlags("--iidfile", tmpIidFile))
  6165  
  6166  	id, err := os.ReadFile(tmpIidFile)
  6167  	assert.NilError(c, err)
  6168  	d, err := digest.Parse(string(id))
  6169  	assert.NilError(c, err)
  6170  	assert.Equal(c, d.String(), getIDByName(c, name))
  6171  }
  6172  
  6173  // FIXME(vdemeester) should migrate to docker/cli tests
  6174  func (s *DockerCLIBuildSuite) TestBuildIidFileCleanupOnFail(c *testing.T) {
  6175  	tmpDir, err := os.MkdirTemp("", "TestBuildIidFileCleanupOnFail")
  6176  	if err != nil {
  6177  		c.Fatal(err)
  6178  	}
  6179  	defer os.RemoveAll(tmpDir)
  6180  	tmpIidFile := filepath.Join(tmpDir, "iid")
  6181  
  6182  	err = os.WriteFile(tmpIidFile, []byte("Dummy"), 0o666)
  6183  	assert.NilError(c, err)
  6184  
  6185  	cli.Docker(cli.Args("build", "-t", "testbuildiidfilecleanuponfail"),
  6186  		build.WithDockerfile(`FROM `+minimalBaseImage()+`
  6187  	RUN /non/existing/command`),
  6188  		cli.WithFlags("--iidfile", tmpIidFile)).Assert(c, icmd.Expected{
  6189  		ExitCode: 1,
  6190  	})
  6191  	_, err = os.Stat(tmpIidFile)
  6192  	assert.ErrorContains(c, err, "")
  6193  	assert.Equal(c, os.IsNotExist(err), true)
  6194  }