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