github.com/rish1988/moby@v25.0.2+incompatible/integration/build/build_test.go (about)

     1  package build // import "github.com/docker/docker/integration/build"
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"encoding/json"
     7  	"io"
     8  	"os"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/api/types/container"
    14  	"github.com/docker/docker/api/types/filters"
    15  	"github.com/docker/docker/errdefs"
    16  	"github.com/docker/docker/pkg/jsonmessage"
    17  	"github.com/docker/docker/testutil"
    18  	"github.com/docker/docker/testutil/fakecontext"
    19  	"gotest.tools/v3/assert"
    20  	is "gotest.tools/v3/assert/cmp"
    21  	"gotest.tools/v3/skip"
    22  )
    23  
    24  func TestBuildWithRemoveAndForceRemove(t *testing.T) {
    25  	ctx := setupTest(t)
    26  
    27  	cases := []struct {
    28  		name                           string
    29  		dockerfile                     string
    30  		numberOfIntermediateContainers int
    31  		rm                             bool
    32  		forceRm                        bool
    33  	}{
    34  		{
    35  			name: "successful build with no removal",
    36  			dockerfile: `FROM busybox
    37  			RUN exit 0
    38  			RUN exit 0`,
    39  			numberOfIntermediateContainers: 2,
    40  			rm:                             false,
    41  			forceRm:                        false,
    42  		},
    43  		{
    44  			name: "successful build with remove",
    45  			dockerfile: `FROM busybox
    46  			RUN exit 0
    47  			RUN exit 0`,
    48  			numberOfIntermediateContainers: 0,
    49  			rm:                             true,
    50  			forceRm:                        false,
    51  		},
    52  		{
    53  			name: "successful build with remove and force remove",
    54  			dockerfile: `FROM busybox
    55  			RUN exit 0
    56  			RUN exit 0`,
    57  			numberOfIntermediateContainers: 0,
    58  			rm:                             true,
    59  			forceRm:                        true,
    60  		},
    61  		{
    62  			name: "failed build with no removal",
    63  			dockerfile: `FROM busybox
    64  			RUN exit 0
    65  			RUN exit 1`,
    66  			numberOfIntermediateContainers: 2,
    67  			rm:                             false,
    68  			forceRm:                        false,
    69  		},
    70  		{
    71  			name: "failed build with remove",
    72  			dockerfile: `FROM busybox
    73  			RUN exit 0
    74  			RUN exit 1`,
    75  			numberOfIntermediateContainers: 1,
    76  			rm:                             true,
    77  			forceRm:                        false,
    78  		},
    79  		{
    80  			name: "failed build with remove and force remove",
    81  			dockerfile: `FROM busybox
    82  			RUN exit 0
    83  			RUN exit 1`,
    84  			numberOfIntermediateContainers: 0,
    85  			rm:                             true,
    86  			forceRm:                        true,
    87  		},
    88  	}
    89  
    90  	client := testEnv.APIClient()
    91  	for _, c := range cases {
    92  		c := c
    93  		t.Run(c.name, func(t *testing.T) {
    94  			t.Parallel()
    95  			ctx := testutil.StartSpan(ctx, t)
    96  			dockerfile := []byte(c.dockerfile)
    97  
    98  			buff := bytes.NewBuffer(nil)
    99  			tw := tar.NewWriter(buff)
   100  			assert.NilError(t, tw.WriteHeader(&tar.Header{
   101  				Name: "Dockerfile",
   102  				Size: int64(len(dockerfile)),
   103  			}))
   104  			_, err := tw.Write(dockerfile)
   105  			assert.NilError(t, err)
   106  			assert.NilError(t, tw.Close())
   107  			resp, err := client.ImageBuild(ctx, buff, types.ImageBuildOptions{Remove: c.rm, ForceRemove: c.forceRm, NoCache: true})
   108  			assert.NilError(t, err)
   109  			defer resp.Body.Close()
   110  			filter, err := buildContainerIdsFilter(resp.Body)
   111  			assert.NilError(t, err)
   112  			remainingContainers, err := client.ContainerList(ctx, container.ListOptions{Filters: filter, All: true})
   113  			assert.NilError(t, err)
   114  			assert.Equal(t, c.numberOfIntermediateContainers, len(remainingContainers), "Expected %v remaining intermediate containers, got %v", c.numberOfIntermediateContainers, len(remainingContainers))
   115  		})
   116  	}
   117  }
   118  
   119  func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) {
   120  	const intermediateContainerPrefix = " ---> Running in "
   121  	filter := filters.NewArgs()
   122  
   123  	dec := json.NewDecoder(buildOutput)
   124  	for {
   125  		m := jsonmessage.JSONMessage{}
   126  		err := dec.Decode(&m)
   127  		if err == io.EOF {
   128  			return filter, nil
   129  		}
   130  		if err != nil {
   131  			return filter, err
   132  		}
   133  		if ix := strings.Index(m.Stream, intermediateContainerPrefix); ix != -1 {
   134  			filter.Add("id", strings.TrimSpace(m.Stream[ix+len(intermediateContainerPrefix):]))
   135  		}
   136  	}
   137  }
   138  
   139  // TestBuildMultiStageCopy verifies that copying between stages works correctly.
   140  //
   141  // Regression test for docker/for-win#4349, ENGCORE-935, where creating the target
   142  // directory failed on Windows, because `os.MkdirAll()` was called with a volume
   143  // GUID path (\\?\Volume{dae8d3ac-b9a1-11e9-88eb-e8554b2ba1db}\newdir\hello}),
   144  // which currently isn't supported by Golang.
   145  func TestBuildMultiStageCopy(t *testing.T) {
   146  	ctx := setupTest(t)
   147  
   148  	dockerfile, err := os.ReadFile("testdata/Dockerfile." + t.Name())
   149  	assert.NilError(t, err)
   150  
   151  	source := fakecontext.New(t, "", fakecontext.WithDockerfile(string(dockerfile)))
   152  	defer source.Close()
   153  
   154  	apiclient := testEnv.APIClient()
   155  
   156  	for _, target := range []string{"copy_to_root", "copy_to_newdir", "copy_to_newdir_nested", "copy_to_existingdir", "copy_to_newsubdir"} {
   157  		t.Run(target, func(t *testing.T) {
   158  			imgName := strings.ToLower(t.Name())
   159  
   160  			resp, err := apiclient.ImageBuild(
   161  				ctx,
   162  				source.AsTarReader(t),
   163  				types.ImageBuildOptions{
   164  					Remove:      true,
   165  					ForceRemove: true,
   166  					Target:      target,
   167  					Tags:        []string{imgName},
   168  				},
   169  			)
   170  			assert.NilError(t, err)
   171  
   172  			out := bytes.NewBuffer(nil)
   173  			_, err = io.Copy(out, resp.Body)
   174  			_ = resp.Body.Close()
   175  			if err != nil {
   176  				t.Log(out)
   177  			}
   178  			assert.NilError(t, err)
   179  
   180  			// verify the image was successfully built
   181  			_, _, err = apiclient.ImageInspectWithRaw(ctx, imgName)
   182  			if err != nil {
   183  				t.Log(out)
   184  			}
   185  			assert.NilError(t, err)
   186  		})
   187  	}
   188  }
   189  
   190  func TestBuildMultiStageParentConfig(t *testing.T) {
   191  	dockerfile := `
   192  		FROM busybox AS stage0
   193  		ENV WHO=parent
   194  		WORKDIR /foo
   195  
   196  		FROM stage0
   197  		ENV WHO=sibling1
   198  		WORKDIR sub1
   199  
   200  		FROM stage0
   201  		WORKDIR sub2
   202  	`
   203  
   204  	ctx := setupTest(t)
   205  	source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
   206  	defer source.Close()
   207  
   208  	apiclient := testEnv.APIClient()
   209  	imgName := strings.ToLower(t.Name())
   210  	resp, err := apiclient.ImageBuild(ctx,
   211  		source.AsTarReader(t),
   212  		types.ImageBuildOptions{
   213  			Remove:      true,
   214  			ForceRemove: true,
   215  			Tags:        []string{imgName},
   216  		})
   217  	assert.NilError(t, err)
   218  	_, err = io.Copy(io.Discard, resp.Body)
   219  	assert.Check(t, resp.Body.Close())
   220  	assert.NilError(t, err)
   221  
   222  	img, _, err := apiclient.ImageInspectWithRaw(ctx, imgName)
   223  	assert.NilError(t, err)
   224  
   225  	expected := "/foo/sub2"
   226  	if testEnv.DaemonInfo.OSType == "windows" {
   227  		expected = `C:\foo\sub2`
   228  	}
   229  	assert.Check(t, is.Equal(expected, img.Config.WorkingDir))
   230  	assert.Check(t, is.Contains(img.Config.Env, "WHO=parent"))
   231  }
   232  
   233  // Test cases in #36996
   234  func TestBuildLabelWithTargets(t *testing.T) {
   235  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
   236  	imgName := strings.ToLower(t.Name() + "-a")
   237  	testLabels := map[string]string{
   238  		"foo":  "bar",
   239  		"dead": "beef",
   240  	}
   241  
   242  	dockerfile := `
   243  		FROM busybox AS target-a
   244  		CMD ["/dev"]
   245  		LABEL label-a=inline-a
   246  		FROM busybox AS target-b
   247  		CMD ["/dist"]
   248  		LABEL label-b=inline-b
   249  		`
   250  
   251  	ctx := setupTest(t)
   252  	source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
   253  	defer source.Close()
   254  
   255  	apiclient := testEnv.APIClient()
   256  	// For `target-a` build
   257  	resp, err := apiclient.ImageBuild(ctx,
   258  		source.AsTarReader(t),
   259  		types.ImageBuildOptions{
   260  			Remove:      true,
   261  			ForceRemove: true,
   262  			Tags:        []string{imgName},
   263  			Labels:      testLabels,
   264  			Target:      "target-a",
   265  		})
   266  	assert.NilError(t, err)
   267  	_, err = io.Copy(io.Discard, resp.Body)
   268  	assert.Check(t, resp.Body.Close())
   269  	assert.NilError(t, err)
   270  
   271  	img, _, err := apiclient.ImageInspectWithRaw(ctx, imgName)
   272  	assert.NilError(t, err)
   273  
   274  	testLabels["label-a"] = "inline-a"
   275  	for k, v := range testLabels {
   276  		x, ok := img.Config.Labels[k]
   277  		assert.Assert(t, ok)
   278  		assert.Assert(t, x == v)
   279  	}
   280  
   281  	// For `target-b` build
   282  	imgName = strings.ToLower(t.Name() + "-b")
   283  	delete(testLabels, "label-a")
   284  	resp, err = apiclient.ImageBuild(ctx,
   285  		source.AsTarReader(t),
   286  		types.ImageBuildOptions{
   287  			Remove:      true,
   288  			ForceRemove: true,
   289  			Tags:        []string{imgName},
   290  			Labels:      testLabels,
   291  			Target:      "target-b",
   292  		})
   293  	assert.NilError(t, err)
   294  	_, err = io.Copy(io.Discard, resp.Body)
   295  	assert.Check(t, resp.Body.Close())
   296  	assert.NilError(t, err)
   297  
   298  	img, _, err = apiclient.ImageInspectWithRaw(ctx, imgName)
   299  	assert.NilError(t, err)
   300  
   301  	testLabels["label-b"] = "inline-b"
   302  	for k, v := range testLabels {
   303  		x, ok := img.Config.Labels[k]
   304  		assert.Check(t, ok)
   305  		assert.Check(t, x == v)
   306  	}
   307  }
   308  
   309  func TestBuildWithEmptyLayers(t *testing.T) {
   310  	const dockerfile = `
   311  FROM    busybox
   312  COPY    1/ /target/
   313  COPY    2/ /target/
   314  COPY    3/ /target/
   315  `
   316  	ctx := setupTest(t)
   317  	source := fakecontext.New(t, "",
   318  		fakecontext.WithDockerfile(dockerfile),
   319  		fakecontext.WithFile("1/a", "asdf"),
   320  		fakecontext.WithFile("2/a", "asdf"),
   321  		fakecontext.WithFile("3/a", "asdf"))
   322  	defer source.Close()
   323  
   324  	apiclient := testEnv.APIClient()
   325  	resp, err := apiclient.ImageBuild(ctx,
   326  		source.AsTarReader(t),
   327  		types.ImageBuildOptions{
   328  			Remove:      true,
   329  			ForceRemove: true,
   330  		})
   331  	assert.NilError(t, err)
   332  	_, err = io.Copy(io.Discard, resp.Body)
   333  	assert.Check(t, resp.Body.Close())
   334  	assert.NilError(t, err)
   335  }
   336  
   337  // TestBuildMultiStageOnBuild checks that ONBUILD commands are applied to
   338  // multiple subsequent stages
   339  // #35652
   340  func TestBuildMultiStageOnBuild(t *testing.T) {
   341  	ctx := setupTest(t)
   342  
   343  	// test both metadata and layer based commands as they may be implemented differently
   344  	const dockerfile = `
   345  FROM busybox AS stage1
   346  ONBUILD RUN echo 'foo' >somefile
   347  ONBUILD ENV bar=baz
   348  
   349  FROM stage1
   350  # fails if ONBUILD RUN fails
   351  RUN cat somefile
   352  
   353  FROM stage1
   354  RUN cat somefile`
   355  
   356  	source := fakecontext.New(t, "",
   357  		fakecontext.WithDockerfile(dockerfile))
   358  	defer source.Close()
   359  
   360  	apiclient := testEnv.APIClient()
   361  	resp, err := apiclient.ImageBuild(ctx,
   362  		source.AsTarReader(t),
   363  		types.ImageBuildOptions{
   364  			Remove:      true,
   365  			ForceRemove: true,
   366  		})
   367  
   368  	out := bytes.NewBuffer(nil)
   369  	assert.NilError(t, err)
   370  	_, err = io.Copy(out, resp.Body)
   371  	assert.Check(t, resp.Body.Close())
   372  	assert.NilError(t, err)
   373  
   374  	assert.Check(t, is.Contains(out.String(), "Successfully built"))
   375  
   376  	imageIDs, err := getImageIDsFromBuild(out.Bytes())
   377  	assert.NilError(t, err)
   378  	assert.Assert(t, is.Equal(3, len(imageIDs)))
   379  
   380  	img, _, err := apiclient.ImageInspectWithRaw(ctx, imageIDs[2])
   381  	assert.NilError(t, err)
   382  	assert.Check(t, is.Contains(img.Config.Env, "bar=baz"))
   383  }
   384  
   385  // #35403 #36122
   386  func TestBuildUncleanTarFilenames(t *testing.T) {
   387  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
   388  
   389  	ctx := setupTest(t)
   390  
   391  	const dockerfile = `
   392  FROM scratch
   393  COPY foo /
   394  FROM scratch
   395  COPY bar /
   396  `
   397  
   398  	buf := bytes.NewBuffer(nil)
   399  	w := tar.NewWriter(buf)
   400  	writeTarRecord(t, w, "Dockerfile", dockerfile)
   401  	writeTarRecord(t, w, "../foo", "foocontents0")
   402  	writeTarRecord(t, w, "/bar", "barcontents0")
   403  	err := w.Close()
   404  	assert.NilError(t, err)
   405  
   406  	apiclient := testEnv.APIClient()
   407  	resp, err := apiclient.ImageBuild(ctx,
   408  		buf,
   409  		types.ImageBuildOptions{
   410  			Remove:      true,
   411  			ForceRemove: true,
   412  		})
   413  
   414  	out := bytes.NewBuffer(nil)
   415  	assert.NilError(t, err)
   416  	_, err = io.Copy(out, resp.Body)
   417  	assert.Check(t, resp.Body.Close())
   418  	assert.NilError(t, err)
   419  
   420  	// repeat with changed data should not cause cache hits
   421  
   422  	buf = bytes.NewBuffer(nil)
   423  	w = tar.NewWriter(buf)
   424  	writeTarRecord(t, w, "Dockerfile", dockerfile)
   425  	writeTarRecord(t, w, "../foo", "foocontents1")
   426  	writeTarRecord(t, w, "/bar", "barcontents1")
   427  	err = w.Close()
   428  	assert.NilError(t, err)
   429  
   430  	resp, err = apiclient.ImageBuild(ctx,
   431  		buf,
   432  		types.ImageBuildOptions{
   433  			Remove:      true,
   434  			ForceRemove: true,
   435  		})
   436  
   437  	out = bytes.NewBuffer(nil)
   438  	assert.NilError(t, err)
   439  	_, err = io.Copy(out, resp.Body)
   440  	assert.Check(t, resp.Body.Close())
   441  	assert.NilError(t, err)
   442  	assert.Assert(t, !strings.Contains(out.String(), "Using cache"))
   443  }
   444  
   445  // docker/for-linux#135
   446  // #35641
   447  func TestBuildMultiStageLayerLeak(t *testing.T) {
   448  	ctx := setupTest(t)
   449  
   450  	// all commands need to match until COPY
   451  	const dockerfile = `
   452  FROM busybox
   453  WORKDIR /foo
   454  COPY foo .
   455  FROM busybox
   456  WORKDIR /foo
   457  COPY bar .
   458  RUN [ -f bar ]
   459  RUN [ ! -f foo ]
   460  `
   461  
   462  	source := fakecontext.New(t, "",
   463  		fakecontext.WithFile("foo", "0"),
   464  		fakecontext.WithFile("bar", "1"),
   465  		fakecontext.WithDockerfile(dockerfile))
   466  	defer source.Close()
   467  
   468  	apiClient := testEnv.APIClient()
   469  	resp, err := apiClient.ImageBuild(ctx,
   470  		source.AsTarReader(t),
   471  		types.ImageBuildOptions{
   472  			Remove:      true,
   473  			ForceRemove: true,
   474  		})
   475  
   476  	out := bytes.NewBuffer(nil)
   477  	assert.NilError(t, err)
   478  	_, err = io.Copy(out, resp.Body)
   479  	assert.Check(t, resp.Body.Close())
   480  	assert.NilError(t, err)
   481  
   482  	assert.Check(t, is.Contains(out.String(), "Successfully built"))
   483  }
   484  
   485  // #37581
   486  // #40444 (Windows Containers only)
   487  func TestBuildWithHugeFile(t *testing.T) {
   488  	ctx := setupTest(t)
   489  
   490  	var dockerfile string
   491  	if testEnv.DaemonInfo.OSType == "windows" {
   492  		dockerfile = `
   493  FROM busybox
   494  
   495  # create a file with size of 8GB
   496  RUN powershell "fsutil.exe file createnew bigfile.txt 8589934592 ; dir bigfile.txt"
   497  `
   498  	} else {
   499  		dockerfile = `
   500  FROM busybox
   501  
   502  # create a sparse file with size over 8GB
   503  RUN for g in $(seq 0 8); do dd if=/dev/urandom of=rnd bs=1K count=1 seek=$((1024*1024*g)) status=none; done \
   504   && ls -la rnd && du -sk rnd
   505  `
   506  	}
   507  
   508  	buf := bytes.NewBuffer(nil)
   509  	w := tar.NewWriter(buf)
   510  	writeTarRecord(t, w, "Dockerfile", dockerfile)
   511  	err := w.Close()
   512  	assert.NilError(t, err)
   513  
   514  	apiClient := testEnv.APIClient()
   515  	resp, err := apiClient.ImageBuild(ctx,
   516  		buf,
   517  		types.ImageBuildOptions{
   518  			Remove:      true,
   519  			ForceRemove: true,
   520  		})
   521  
   522  	out := bytes.NewBuffer(nil)
   523  	assert.NilError(t, err)
   524  	_, err = io.Copy(out, resp.Body)
   525  	assert.Check(t, resp.Body.Close())
   526  	assert.NilError(t, err)
   527  	assert.Check(t, is.Contains(out.String(), "Successfully built"))
   528  }
   529  
   530  func TestBuildWCOWSandboxSize(t *testing.T) {
   531  	t.Skip("FLAKY_TEST that needs to be fixed; see https://github.com/moby/moby/issues/42743")
   532  	skip.If(t, testEnv.DaemonInfo.OSType != "windows", "only Windows has sandbox size control")
   533  	ctx := setupTest(t)
   534  
   535  	const dockerfile = `
   536  FROM busybox AS intermediate
   537  WORKDIR C:\\stuff
   538  # Create and delete a 21GB file
   539  RUN fsutil file createnew C:\\stuff\\bigfile_0.txt 22548578304 && del bigfile_0.txt
   540  # Create three 7GB files
   541  RUN fsutil file createnew C:\\stuff\\bigfile_1.txt 7516192768
   542  RUN fsutil file createnew C:\\stuff\\bigfile_2.txt 7516192768
   543  RUN fsutil file createnew C:\\stuff\\bigfile_3.txt 7516192768
   544  # Copy that 21GB of data out into a new target
   545  FROM busybox
   546  COPY --from=intermediate C:\\stuff C:\\stuff
   547  `
   548  
   549  	buf := bytes.NewBuffer(nil)
   550  	w := tar.NewWriter(buf)
   551  	writeTarRecord(t, w, "Dockerfile", dockerfile)
   552  	err := w.Close()
   553  	assert.NilError(t, err)
   554  
   555  	apiClient := testEnv.APIClient()
   556  	resp, err := apiClient.ImageBuild(ctx,
   557  		buf,
   558  		types.ImageBuildOptions{
   559  			Remove:      true,
   560  			ForceRemove: true,
   561  		})
   562  
   563  	out := bytes.NewBuffer(nil)
   564  	assert.NilError(t, err)
   565  	_, err = io.Copy(out, resp.Body)
   566  	assert.Check(t, resp.Body.Close())
   567  	assert.NilError(t, err)
   568  	// The test passes if either:
   569  	// - the image build succeeded; or
   570  	// - The "COPY --from=intermediate" step ran out of space during re-exec'd writing of the transport layer information to hcsshim's temp directory
   571  	// The latter case means we finished the COPY operation, so the sandbox must have been larger than 20GB, which was the test,
   572  	// and _then_ ran out of space on the host during `importLayer` in the WindowsFilter graph driver, while committing the layer.
   573  	// See https://github.com/moby/moby/pull/41636#issuecomment-723038517 for more details on the operations being done here.
   574  	// Specifically, this happens on the Docker Jenkins CI Windows-RS5 build nodes.
   575  	// The two parts of the acceptable-failure case are on different lines, so we need two regexp checks.
   576  	assert.Check(t, is.Regexp("Successfully built|COPY --from=intermediate", out.String()))
   577  	assert.Check(t, is.Regexp("Successfully built|re-exec error: exit status 1: output: write.*daemon\\\\\\\\tmp\\\\\\\\hcs.*bigfile_[1-3].txt: There is not enough space on the disk.", out.String()))
   578  }
   579  
   580  func TestBuildWithEmptyDockerfile(t *testing.T) {
   581  	ctx := setupTest(t)
   582  
   583  	tests := []struct {
   584  		name        string
   585  		dockerfile  string
   586  		expectedErr string
   587  	}{
   588  		{
   589  			name:        "empty-dockerfile",
   590  			dockerfile:  "",
   591  			expectedErr: "cannot be empty",
   592  		},
   593  		{
   594  			name: "empty-lines-dockerfile",
   595  			dockerfile: `
   596  
   597  
   598  
   599  			`,
   600  			expectedErr: "file with no instructions",
   601  		},
   602  		{
   603  			name:        "comment-only-dockerfile",
   604  			dockerfile:  `# this is a comment`,
   605  			expectedErr: "file with no instructions",
   606  		},
   607  	}
   608  
   609  	apiClient := testEnv.APIClient()
   610  
   611  	for _, tc := range tests {
   612  		tc := tc
   613  		t.Run(tc.name, func(t *testing.T) {
   614  			t.Parallel()
   615  
   616  			buf := bytes.NewBuffer(nil)
   617  			w := tar.NewWriter(buf)
   618  			writeTarRecord(t, w, "Dockerfile", tc.dockerfile)
   619  			err := w.Close()
   620  			assert.NilError(t, err)
   621  
   622  			_, err = apiClient.ImageBuild(ctx,
   623  				buf,
   624  				types.ImageBuildOptions{
   625  					Remove:      true,
   626  					ForceRemove: true,
   627  				})
   628  
   629  			assert.Check(t, is.Contains(err.Error(), tc.expectedErr))
   630  		})
   631  	}
   632  }
   633  
   634  func TestBuildPreserveOwnership(t *testing.T) {
   635  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
   636  
   637  	ctx := setupTest(t)
   638  
   639  	dockerfile, err := os.ReadFile("testdata/Dockerfile." + t.Name())
   640  	assert.NilError(t, err)
   641  
   642  	source := fakecontext.New(t, "", fakecontext.WithDockerfile(string(dockerfile)))
   643  	defer source.Close()
   644  
   645  	apiClient := testEnv.APIClient()
   646  
   647  	for _, target := range []string{"copy_from", "copy_from_chowned"} {
   648  		t.Run(target, func(t *testing.T) {
   649  			ctx := testutil.StartSpan(ctx, t)
   650  
   651  			resp, err := apiClient.ImageBuild(
   652  				ctx,
   653  				source.AsTarReader(t),
   654  				types.ImageBuildOptions{
   655  					Remove:      true,
   656  					ForceRemove: true,
   657  					Target:      target,
   658  				},
   659  			)
   660  			assert.NilError(t, err)
   661  
   662  			out := bytes.NewBuffer(nil)
   663  			_, err = io.Copy(out, resp.Body)
   664  			_ = resp.Body.Close()
   665  			if err != nil {
   666  				t.Log(out)
   667  			}
   668  			assert.NilError(t, err)
   669  		})
   670  	}
   671  }
   672  
   673  func TestBuildPlatformInvalid(t *testing.T) {
   674  	ctx := setupTest(t)
   675  
   676  	buf := bytes.NewBuffer(nil)
   677  	w := tar.NewWriter(buf)
   678  	writeTarRecord(t, w, "Dockerfile", `FROM busybox`)
   679  	err := w.Close()
   680  	assert.NilError(t, err)
   681  
   682  	_, err = testEnv.APIClient().ImageBuild(ctx, buf, types.ImageBuildOptions{
   683  		Remove:      true,
   684  		ForceRemove: true,
   685  		Platform:    "foobar",
   686  	})
   687  
   688  	assert.Check(t, is.ErrorContains(err, "unknown operating system or architecture"))
   689  	assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
   690  }
   691  
   692  func writeTarRecord(t *testing.T, w *tar.Writer, fn, contents string) {
   693  	err := w.WriteHeader(&tar.Header{
   694  		Name:     fn,
   695  		Mode:     0o600,
   696  		Size:     int64(len(contents)),
   697  		Typeflag: '0',
   698  	})
   699  	assert.NilError(t, err)
   700  	_, err = w.Write([]byte(contents))
   701  	assert.NilError(t, err)
   702  }
   703  
   704  type buildLine struct {
   705  	Stream string
   706  	Aux    struct {
   707  		ID string
   708  	}
   709  }
   710  
   711  func getImageIDsFromBuild(output []byte) ([]string, error) {
   712  	var ids []string
   713  	for _, line := range bytes.Split(output, []byte("\n")) {
   714  		if len(line) == 0 {
   715  			continue
   716  		}
   717  		entry := buildLine{}
   718  		if err := json.Unmarshal(line, &entry); err != nil {
   719  			return nil, err
   720  		}
   721  		if entry.Aux.ID != "" {
   722  			ids = append(ids, entry.Aux.ID)
   723  		}
   724  	}
   725  	return ids, nil
   726  }