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