github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+incompatible/integration-cli/docker_api_build_test.go (about)

     1  package main
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"regexp"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/docker/docker/api/types/image"
    15  	"github.com/docker/docker/testutil"
    16  	"github.com/docker/docker/testutil/fakecontext"
    17  	"github.com/docker/docker/testutil/fakegit"
    18  	"github.com/docker/docker/testutil/fakestorage"
    19  	"github.com/docker/docker/testutil/request"
    20  	"gotest.tools/v3/assert"
    21  	is "gotest.tools/v3/assert/cmp"
    22  )
    23  
    24  func (s *DockerAPISuite) TestBuildAPIDockerFileRemote(c *testing.T) {
    25  	testRequires(c, NotUserNamespace)
    26  	ctx := testutil.GetContext(c)
    27  
    28  	// -xdev is required because sysfs can cause EPERM
    29  	testD := `FROM busybox
    30  RUN find / -xdev -name ba*
    31  RUN find /tmp/`
    32  	server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{"testD": testD}))
    33  	defer server.Close()
    34  
    35  	res, body, err := request.Post(ctx, "/build?dockerfile=baz&remote="+server.URL()+"/testD", request.JSON)
    36  	assert.NilError(c, err)
    37  	assert.Equal(c, res.StatusCode, http.StatusOK)
    38  
    39  	buf, err := request.ReadBody(body)
    40  	assert.NilError(c, err)
    41  
    42  	// Make sure Dockerfile exists.
    43  	// Make sure 'baz' doesn't exist ANYWHERE despite being mentioned in the URL
    44  	out := string(buf)
    45  	assert.Assert(c, is.Contains(out, "RUN find /tmp"))
    46  	assert.Assert(c, !strings.Contains(out, "baz"))
    47  }
    48  
    49  func (s *DockerAPISuite) TestBuildAPIRemoteTarballContext(c *testing.T) {
    50  	ctx := testutil.GetContext(c)
    51  
    52  	buffer := new(bytes.Buffer)
    53  	tw := tar.NewWriter(buffer)
    54  	defer tw.Close()
    55  
    56  	dockerfile := []byte("FROM busybox")
    57  	err := tw.WriteHeader(&tar.Header{
    58  		Name: "Dockerfile",
    59  		Size: int64(len(dockerfile)),
    60  	})
    61  	assert.NilError(c, err, "failed to write tar file header")
    62  
    63  	_, err = tw.Write(dockerfile)
    64  	assert.NilError(c, err, "failed to write tar file content")
    65  	assert.NilError(c, tw.Close(), "failed to close tar archive")
    66  
    67  	server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{
    68  		"testT.tar": buffer,
    69  	}))
    70  	defer server.Close()
    71  
    72  	res, b, err := request.Post(ctx, "/build?remote="+server.URL()+"/testT.tar", request.ContentType("application/tar"))
    73  	assert.NilError(c, err)
    74  	assert.Equal(c, res.StatusCode, http.StatusOK)
    75  	b.Close()
    76  }
    77  
    78  func (s *DockerAPISuite) TestBuildAPIRemoteTarballContextWithCustomDockerfile(c *testing.T) {
    79  	buffer := new(bytes.Buffer)
    80  	tw := tar.NewWriter(buffer)
    81  	defer tw.Close()
    82  
    83  	dockerfile := []byte(`FROM busybox
    84  RUN echo 'wrong'`)
    85  	err := tw.WriteHeader(&tar.Header{
    86  		Name: "Dockerfile",
    87  		Size: int64(len(dockerfile)),
    88  	})
    89  	// failed to write tar file header
    90  	assert.NilError(c, err)
    91  
    92  	_, err = tw.Write(dockerfile)
    93  	// failed to write tar file content
    94  	assert.NilError(c, err)
    95  
    96  	custom := []byte(`FROM busybox
    97  RUN echo 'right'
    98  `)
    99  	err = tw.WriteHeader(&tar.Header{
   100  		Name: "custom",
   101  		Size: int64(len(custom)),
   102  	})
   103  
   104  	// failed to write tar file header
   105  	assert.NilError(c, err)
   106  
   107  	_, err = tw.Write(custom)
   108  	// failed to write tar file content
   109  	assert.NilError(c, err)
   110  
   111  	// failed to close tar archive
   112  	assert.NilError(c, tw.Close())
   113  
   114  	server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{
   115  		"testT.tar": buffer,
   116  	}))
   117  	defer server.Close()
   118  
   119  	ctx := testutil.GetContext(c)
   120  	url := "/build?dockerfile=custom&remote=" + server.URL() + "/testT.tar"
   121  	res, body, err := request.Post(ctx, url, request.ContentType("application/tar"))
   122  	assert.NilError(c, err)
   123  	assert.Equal(c, res.StatusCode, http.StatusOK)
   124  
   125  	defer body.Close()
   126  	content, err := request.ReadBody(body)
   127  	assert.NilError(c, err)
   128  
   129  	// Build used the wrong dockerfile.
   130  	assert.Assert(c, !strings.Contains(string(content), "wrong"))
   131  }
   132  
   133  func (s *DockerAPISuite) TestBuildAPILowerDockerfile(c *testing.T) {
   134  	git := fakegit.New(c, "repo", map[string]string{
   135  		"dockerfile": `FROM busybox
   136  RUN echo from dockerfile`,
   137  	}, false)
   138  	defer git.Close()
   139  
   140  	ctx := testutil.GetContext(c)
   141  	res, body, err := request.Post(ctx, "/build?remote="+git.RepoURL, request.JSON)
   142  	assert.NilError(c, err)
   143  	assert.Equal(c, res.StatusCode, http.StatusOK)
   144  
   145  	buf, err := request.ReadBody(body)
   146  	assert.NilError(c, err)
   147  
   148  	out := string(buf)
   149  	assert.Assert(c, is.Contains(out, "from dockerfile"))
   150  }
   151  
   152  func (s *DockerAPISuite) TestBuildAPIBuildGitWithF(c *testing.T) {
   153  	git := fakegit.New(c, "repo", map[string]string{
   154  		"baz": `FROM busybox
   155  RUN echo from baz`,
   156  		"Dockerfile": `FROM busybox
   157  RUN echo from Dockerfile`,
   158  	}, false)
   159  	defer git.Close()
   160  
   161  	ctx := testutil.GetContext(c)
   162  	// Make sure it tries to 'dockerfile' query param value
   163  	res, body, err := request.Post(ctx, "/build?dockerfile=baz&remote="+git.RepoURL, request.JSON)
   164  	assert.NilError(c, err)
   165  	assert.Equal(c, res.StatusCode, http.StatusOK)
   166  
   167  	buf, err := request.ReadBody(body)
   168  	assert.NilError(c, err)
   169  
   170  	out := string(buf)
   171  	assert.Assert(c, is.Contains(out, "from baz"))
   172  }
   173  
   174  func (s *DockerAPISuite) TestBuildAPIDoubleDockerfile(c *testing.T) {
   175  	testRequires(c, UnixCli) // dockerfile overwrites Dockerfile on Windows
   176  	git := fakegit.New(c, "repo", map[string]string{
   177  		"Dockerfile": `FROM busybox
   178  RUN echo from Dockerfile`,
   179  		"dockerfile": `FROM busybox
   180  RUN echo from dockerfile`,
   181  	}, false)
   182  	defer git.Close()
   183  
   184  	ctx := testutil.GetContext(c)
   185  
   186  	// Make sure it tries to 'dockerfile' query param value
   187  	res, body, err := request.Post(ctx, "/build?remote="+git.RepoURL, request.JSON)
   188  	assert.NilError(c, err)
   189  	assert.Equal(c, res.StatusCode, http.StatusOK)
   190  
   191  	buf, err := request.ReadBody(body)
   192  	assert.NilError(c, err)
   193  
   194  	out := string(buf)
   195  	assert.Assert(c, is.Contains(out, "from Dockerfile"))
   196  }
   197  
   198  func (s *DockerAPISuite) TestBuildAPIUnnormalizedTarPaths(c *testing.T) {
   199  	// Make sure that build context tars with entries of the form
   200  	// x/./y don't cause caching false positives.
   201  
   202  	buildFromTarContext := func(fileContents []byte) string {
   203  		buffer := new(bytes.Buffer)
   204  		tw := tar.NewWriter(buffer)
   205  		defer tw.Close()
   206  
   207  		dockerfile := []byte(`FROM busybox
   208  	COPY dir /dir/`)
   209  		err := tw.WriteHeader(&tar.Header{
   210  			Name: "Dockerfile",
   211  			Size: int64(len(dockerfile)),
   212  		})
   213  		assert.NilError(c, err, "failed to write tar file header")
   214  
   215  		_, err = tw.Write(dockerfile)
   216  		assert.NilError(c, err, "failed to write Dockerfile in tar file content")
   217  
   218  		err = tw.WriteHeader(&tar.Header{
   219  			Name: "dir/./file",
   220  			Size: int64(len(fileContents)),
   221  		})
   222  		assert.NilError(c, err, "failed to write tar file header")
   223  
   224  		_, err = tw.Write(fileContents)
   225  		assert.NilError(c, err, "failed to write file contents in tar file content")
   226  
   227  		assert.NilError(c, tw.Close(), "failed to close tar archive")
   228  
   229  		ctx := testutil.GetContext(c)
   230  
   231  		res, body, err := request.Post(ctx, "/build", request.RawContent(io.NopCloser(buffer)), request.ContentType("application/x-tar"))
   232  		assert.NilError(c, err)
   233  		assert.Equal(c, res.StatusCode, http.StatusOK)
   234  
   235  		out, err := request.ReadBody(body)
   236  		assert.NilError(c, err)
   237  		lines := strings.Split(string(out), "\n")
   238  		assert.Assert(c, len(lines) > 1)
   239  		matched, err := regexp.MatchString(".*Successfully built [0-9a-f]{12}.*", lines[len(lines)-2])
   240  		assert.NilError(c, err)
   241  		assert.Assert(c, matched)
   242  
   243  		re := regexp.MustCompile("Successfully built ([0-9a-f]{12})")
   244  		matches := re.FindStringSubmatch(lines[len(lines)-2])
   245  		return matches[1]
   246  	}
   247  
   248  	imageA := buildFromTarContext([]byte("abc"))
   249  	imageB := buildFromTarContext([]byte("def"))
   250  
   251  	assert.Assert(c, imageA != imageB)
   252  }
   253  
   254  func (s *DockerAPISuite) TestBuildOnBuildWithCopy(c *testing.T) {
   255  	dockerfile := `
   256  		FROM ` + minimalBaseImage() + ` as onbuildbase
   257  		ONBUILD COPY file /file
   258  
   259  		FROM onbuildbase
   260  	`
   261  	bCtx := fakecontext.New(c, "",
   262  		fakecontext.WithDockerfile(dockerfile),
   263  		fakecontext.WithFile("file", "some content"),
   264  	)
   265  	defer bCtx.Close()
   266  
   267  	ctx := testutil.GetContext(c)
   268  	res, body, err := request.Post(
   269  		ctx,
   270  		"/build",
   271  		request.RawContent(bCtx.AsTarReader(c)),
   272  		request.ContentType("application/x-tar"))
   273  	assert.NilError(c, err)
   274  	assert.Equal(c, res.StatusCode, http.StatusOK)
   275  
   276  	out, err := request.ReadBody(body)
   277  	assert.NilError(c, err)
   278  	assert.Assert(c, is.Contains(string(out), "Successfully built"))
   279  }
   280  
   281  func (s *DockerAPISuite) TestBuildOnBuildCache(c *testing.T) {
   282  	build := func(dockerfile string) []byte {
   283  		bCtx := fakecontext.New(c, "",
   284  			fakecontext.WithDockerfile(dockerfile),
   285  		)
   286  		defer bCtx.Close()
   287  
   288  		ctx := testutil.GetContext(c)
   289  		res, body, err := request.Post(
   290  			ctx,
   291  			"/build",
   292  			request.RawContent(bCtx.AsTarReader(c)),
   293  			request.ContentType("application/x-tar"))
   294  		assert.NilError(c, err)
   295  		assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
   296  
   297  		out, err := request.ReadBody(body)
   298  		assert.NilError(c, err)
   299  		assert.Assert(c, is.Contains(string(out), "Successfully built"))
   300  		return out
   301  	}
   302  
   303  	dockerfile := `
   304  		FROM ` + minimalBaseImage() + ` as onbuildbase
   305  		ENV something=bar
   306  		ONBUILD ENV foo=bar
   307  	`
   308  	build(dockerfile)
   309  
   310  	dockerfile += "FROM onbuildbase"
   311  	out := build(dockerfile)
   312  
   313  	imageIDs := getImageIDsFromBuild(c, out)
   314  	assert.Assert(c, is.Len(imageIDs, 2))
   315  	parentID, childID := imageIDs[0], imageIDs[1]
   316  
   317  	client := testEnv.APIClient()
   318  	ctx := testutil.GetContext(c)
   319  
   320  	// check parentID is correct
   321  	// Parent is graphdriver-only
   322  	if !testEnv.UsingSnapshotter() {
   323  		image, _, err := client.ImageInspectWithRaw(ctx, childID)
   324  		assert.NilError(c, err)
   325  
   326  		assert.Check(c, is.Equal(parentID, image.Parent))
   327  	}
   328  }
   329  
   330  func (s *DockerRegistrySuite) TestBuildCopyFromForcePull(c *testing.T) {
   331  	client := testEnv.APIClient()
   332  
   333  	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
   334  	// tag the image to upload it to the private registry
   335  	ctx := testutil.GetContext(c)
   336  	err := client.ImageTag(ctx, "busybox", repoName)
   337  	assert.Check(c, err)
   338  	// push the image to the registry
   339  	rc, err := client.ImagePush(ctx, repoName, image.PushOptions{RegistryAuth: "{}"})
   340  	assert.Check(c, err)
   341  	_, err = io.Copy(io.Discard, rc)
   342  	assert.Check(c, err)
   343  
   344  	dockerfile := fmt.Sprintf(`
   345  		FROM %s AS foo
   346  		RUN touch abc
   347  		FROM %s
   348  		COPY --from=foo /abc /
   349  		`, repoName, repoName)
   350  
   351  	bCtx := fakecontext.New(c, "",
   352  		fakecontext.WithDockerfile(dockerfile),
   353  	)
   354  	defer bCtx.Close()
   355  
   356  	res, body, err := request.Post(
   357  		ctx,
   358  		"/build?pull=1",
   359  		request.RawContent(bCtx.AsTarReader(c)),
   360  		request.ContentType("application/x-tar"))
   361  	assert.NilError(c, err)
   362  	assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
   363  
   364  	out, err := request.ReadBody(body)
   365  	assert.NilError(c, err)
   366  	assert.Check(c, is.Contains(string(out), "Successfully built"))
   367  }
   368  
   369  func (s *DockerAPISuite) TestBuildAddRemoteNoDecompress(c *testing.T) {
   370  	buffer := new(bytes.Buffer)
   371  	tw := tar.NewWriter(buffer)
   372  	dt := []byte("contents")
   373  	err := tw.WriteHeader(&tar.Header{
   374  		Name:     "foo",
   375  		Size:     int64(len(dt)),
   376  		Mode:     0o600,
   377  		Typeflag: tar.TypeReg,
   378  	})
   379  	assert.NilError(c, err)
   380  	_, err = tw.Write(dt)
   381  	assert.NilError(c, err)
   382  	err = tw.Close()
   383  	assert.NilError(c, err)
   384  
   385  	server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{
   386  		"test.tar": buffer,
   387  	}))
   388  	defer server.Close()
   389  
   390  	dockerfile := fmt.Sprintf(`
   391  		FROM busybox
   392  		ADD %s/test.tar /
   393  		RUN [ -f test.tar ]
   394  		`, server.URL())
   395  
   396  	bCtx := fakecontext.New(c, "",
   397  		fakecontext.WithDockerfile(dockerfile),
   398  	)
   399  	defer bCtx.Close()
   400  
   401  	ctx := testutil.GetContext(c)
   402  	res, body, err := request.Post(
   403  		ctx,
   404  		"/build",
   405  		request.RawContent(bCtx.AsTarReader(c)),
   406  		request.ContentType("application/x-tar"))
   407  	assert.NilError(c, err)
   408  	assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
   409  
   410  	out, err := request.ReadBody(body)
   411  	assert.NilError(c, err)
   412  	assert.Check(c, is.Contains(string(out), "Successfully built"))
   413  }
   414  
   415  func (s *DockerAPISuite) TestBuildChownOnCopy(c *testing.T) {
   416  	testRequires(c, DaemonIsLinux)
   417  	dockerfile := `FROM busybox
   418  		RUN echo 'test1:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   419  		RUN echo 'test1:x:1001:' >> /etc/group
   420  		RUN echo 'test2:x:1002:' >> /etc/group
   421  		COPY --chown=test1:1002 . /new_dir
   422  		RUN ls -l /
   423  		RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'test1:test2' ]
   424  		RUN [ $(ls -nl / | grep new_dir | awk '{print $3":"$4}') = '1001:1002' ]
   425  	`
   426  	bCtx := fakecontext.New(c, "",
   427  		fakecontext.WithDockerfile(dockerfile),
   428  		fakecontext.WithFile("test_file1", "some test content"),
   429  	)
   430  	defer bCtx.Close()
   431  
   432  	ctx := testutil.GetContext(c)
   433  	res, body, err := request.Post(
   434  		ctx,
   435  		"/build",
   436  		request.RawContent(bCtx.AsTarReader(c)),
   437  		request.ContentType("application/x-tar"))
   438  	assert.NilError(c, err)
   439  	assert.Equal(c, res.StatusCode, http.StatusOK)
   440  
   441  	out, err := request.ReadBody(body)
   442  	assert.NilError(c, err)
   443  	assert.Check(c, is.Contains(string(out), "Successfully built"))
   444  }
   445  
   446  func (s *DockerAPISuite) TestBuildCopyCacheOnFileChange(c *testing.T) {
   447  	dockerfile := `FROM busybox
   448  COPY file /file`
   449  
   450  	ctx1 := fakecontext.New(c, "",
   451  		fakecontext.WithDockerfile(dockerfile),
   452  		fakecontext.WithFile("file", "foo"))
   453  	ctx2 := fakecontext.New(c, "",
   454  		fakecontext.WithDockerfile(dockerfile),
   455  		fakecontext.WithFile("file", "bar"))
   456  
   457  	ctx := testutil.GetContext(c)
   458  	build := func(bCtx *fakecontext.Fake) string {
   459  		res, body, err := request.Post(ctx, "/build",
   460  			request.RawContent(bCtx.AsTarReader(c)),
   461  			request.ContentType("application/x-tar"))
   462  
   463  		assert.NilError(c, err)
   464  		assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
   465  
   466  		out, err := request.ReadBody(body)
   467  		assert.NilError(c, err)
   468  		assert.Assert(c, is.Contains(string(out), "Successfully built"))
   469  
   470  		ids := getImageIDsFromBuild(c, out)
   471  		assert.Assert(c, is.Len(ids, 1))
   472  		return ids[len(ids)-1]
   473  	}
   474  
   475  	id1 := build(ctx1)
   476  	id2 := build(ctx1)
   477  	id3 := build(ctx2)
   478  
   479  	if id1 != id2 {
   480  		c.Fatal("didn't use the cache")
   481  	}
   482  	if id1 == id3 {
   483  		c.Fatal("COPY With different source file should not share same cache")
   484  	}
   485  }
   486  
   487  func (s *DockerAPISuite) TestBuildAddCacheOnFileChange(c *testing.T) {
   488  	dockerfile := `FROM busybox
   489  ADD file /file`
   490  
   491  	ctx1 := fakecontext.New(c, "",
   492  		fakecontext.WithDockerfile(dockerfile),
   493  		fakecontext.WithFile("file", "foo"))
   494  	ctx2 := fakecontext.New(c, "",
   495  		fakecontext.WithDockerfile(dockerfile),
   496  		fakecontext.WithFile("file", "bar"))
   497  
   498  	ctx := testutil.GetContext(c)
   499  	build := func(bCtx *fakecontext.Fake) string {
   500  		res, body, err := request.Post(ctx, "/build",
   501  			request.RawContent(bCtx.AsTarReader(c)),
   502  			request.ContentType("application/x-tar"))
   503  
   504  		assert.NilError(c, err)
   505  		assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
   506  
   507  		out, err := request.ReadBody(body)
   508  		assert.NilError(c, err)
   509  		assert.Assert(c, is.Contains(string(out), "Successfully built"))
   510  
   511  		ids := getImageIDsFromBuild(c, out)
   512  		assert.Assert(c, is.Len(ids, 1))
   513  		return ids[len(ids)-1]
   514  	}
   515  
   516  	id1 := build(ctx1)
   517  	id2 := build(ctx1)
   518  	id3 := build(ctx2)
   519  
   520  	if id1 != id2 {
   521  		c.Fatal("didn't use the cache")
   522  	}
   523  	if id1 == id3 {
   524  		c.Fatal("COPY With different source file should not share same cache")
   525  	}
   526  }
   527  
   528  func (s *DockerAPISuite) TestBuildScratchCopy(c *testing.T) {
   529  	testRequires(c, DaemonIsLinux)
   530  	dockerfile := `FROM scratch
   531  ADD Dockerfile /
   532  ENV foo bar`
   533  	bCtx := fakecontext.New(c, "",
   534  		fakecontext.WithDockerfile(dockerfile),
   535  	)
   536  	defer bCtx.Close()
   537  
   538  	ctx := testutil.GetContext(c)
   539  	res, body, err := request.Post(
   540  		ctx,
   541  		"/build",
   542  		request.RawContent(bCtx.AsTarReader(c)),
   543  		request.ContentType("application/x-tar"))
   544  	assert.NilError(c, err)
   545  	assert.Equal(c, res.StatusCode, http.StatusOK)
   546  
   547  	out, err := request.ReadBody(body)
   548  	assert.NilError(c, err)
   549  	assert.Check(c, is.Contains(string(out), "Successfully built"))
   550  }
   551  
   552  type buildLine struct {
   553  	Stream string
   554  	Aux    struct {
   555  		ID string
   556  	}
   557  }
   558  
   559  func getImageIDsFromBuild(c *testing.T, output []byte) []string {
   560  	var ids []string
   561  	for _, line := range bytes.Split(output, []byte("\n")) {
   562  		if len(line) == 0 {
   563  			continue
   564  		}
   565  		entry := buildLine{}
   566  		assert.NilError(c, json.Unmarshal(line, &entry))
   567  		if entry.Aux.ID != "" {
   568  			ids = append(ids, entry.Aux.ID)
   569  		}
   570  	}
   571  	return ids
   572  }