github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/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/Prakhar-Agarwal-byte/moby/api/types"
    15  	"github.com/Prakhar-Agarwal-byte/moby/testutil"
    16  	"github.com/Prakhar-Agarwal-byte/moby/testutil/fakecontext"
    17  	"github.com/Prakhar-Agarwal-byte/moby/testutil/fakegit"
    18  	"github.com/Prakhar-Agarwal-byte/moby/testutil/fakestorage"
    19  	"github.com/Prakhar-Agarwal-byte/moby/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, types.ImagePushOptions{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  	// new feature added in 1.31 - https://github.com/moby/moby/pull/34263
   417  	testRequires(c, DaemonIsLinux, MinimumAPIVersion("1.31"))
   418  	dockerfile := `FROM busybox
   419  		RUN echo 'test1:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   420  		RUN echo 'test1:x:1001:' >> /etc/group
   421  		RUN echo 'test2:x:1002:' >> /etc/group
   422  		COPY --chown=test1:1002 . /new_dir
   423  		RUN ls -l /
   424  		RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'test1:test2' ]
   425  		RUN [ $(ls -nl / | grep new_dir | awk '{print $3":"$4}') = '1001:1002' ]
   426  	`
   427  	bCtx := fakecontext.New(c, "",
   428  		fakecontext.WithDockerfile(dockerfile),
   429  		fakecontext.WithFile("test_file1", "some test content"),
   430  	)
   431  	defer bCtx.Close()
   432  
   433  	ctx := testutil.GetContext(c)
   434  	res, body, err := request.Post(
   435  		ctx,
   436  		"/build",
   437  		request.RawContent(bCtx.AsTarReader(c)),
   438  		request.ContentType("application/x-tar"))
   439  	assert.NilError(c, err)
   440  	assert.Equal(c, res.StatusCode, http.StatusOK)
   441  
   442  	out, err := request.ReadBody(body)
   443  	assert.NilError(c, err)
   444  	assert.Check(c, is.Contains(string(out), "Successfully built"))
   445  }
   446  
   447  func (s *DockerAPISuite) TestBuildCopyCacheOnFileChange(c *testing.T) {
   448  	dockerfile := `FROM busybox
   449  COPY file /file`
   450  
   451  	ctx1 := fakecontext.New(c, "",
   452  		fakecontext.WithDockerfile(dockerfile),
   453  		fakecontext.WithFile("file", "foo"))
   454  	ctx2 := fakecontext.New(c, "",
   455  		fakecontext.WithDockerfile(dockerfile),
   456  		fakecontext.WithFile("file", "bar"))
   457  
   458  	ctx := testutil.GetContext(c)
   459  	build := func(bCtx *fakecontext.Fake) string {
   460  		res, body, err := request.Post(ctx, "/build",
   461  			request.RawContent(bCtx.AsTarReader(c)),
   462  			request.ContentType("application/x-tar"))
   463  
   464  		assert.NilError(c, err)
   465  		assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
   466  
   467  		out, err := request.ReadBody(body)
   468  		assert.NilError(c, err)
   469  		assert.Assert(c, is.Contains(string(out), "Successfully built"))
   470  
   471  		ids := getImageIDsFromBuild(c, out)
   472  		assert.Assert(c, is.Len(ids, 1))
   473  		return ids[len(ids)-1]
   474  	}
   475  
   476  	id1 := build(ctx1)
   477  	id2 := build(ctx1)
   478  	id3 := build(ctx2)
   479  
   480  	if id1 != id2 {
   481  		c.Fatal("didn't use the cache")
   482  	}
   483  	if id1 == id3 {
   484  		c.Fatal("COPY With different source file should not share same cache")
   485  	}
   486  }
   487  
   488  func (s *DockerAPISuite) TestBuildAddCacheOnFileChange(c *testing.T) {
   489  	dockerfile := `FROM busybox
   490  ADD file /file`
   491  
   492  	ctx1 := fakecontext.New(c, "",
   493  		fakecontext.WithDockerfile(dockerfile),
   494  		fakecontext.WithFile("file", "foo"))
   495  	ctx2 := fakecontext.New(c, "",
   496  		fakecontext.WithDockerfile(dockerfile),
   497  		fakecontext.WithFile("file", "bar"))
   498  
   499  	ctx := testutil.GetContext(c)
   500  	build := func(bCtx *fakecontext.Fake) string {
   501  		res, body, err := request.Post(ctx, "/build",
   502  			request.RawContent(bCtx.AsTarReader(c)),
   503  			request.ContentType("application/x-tar"))
   504  
   505  		assert.NilError(c, err)
   506  		assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
   507  
   508  		out, err := request.ReadBody(body)
   509  		assert.NilError(c, err)
   510  		assert.Assert(c, is.Contains(string(out), "Successfully built"))
   511  
   512  		ids := getImageIDsFromBuild(c, out)
   513  		assert.Assert(c, is.Len(ids, 1))
   514  		return ids[len(ids)-1]
   515  	}
   516  
   517  	id1 := build(ctx1)
   518  	id2 := build(ctx1)
   519  	id3 := build(ctx2)
   520  
   521  	if id1 != id2 {
   522  		c.Fatal("didn't use the cache")
   523  	}
   524  	if id1 == id3 {
   525  		c.Fatal("COPY With different source file should not share same cache")
   526  	}
   527  }
   528  
   529  func (s *DockerAPISuite) TestBuildScratchCopy(c *testing.T) {
   530  	testRequires(c, DaemonIsLinux)
   531  	dockerfile := `FROM scratch
   532  ADD Dockerfile /
   533  ENV foo bar`
   534  	bCtx := fakecontext.New(c, "",
   535  		fakecontext.WithDockerfile(dockerfile),
   536  	)
   537  	defer bCtx.Close()
   538  
   539  	ctx := testutil.GetContext(c)
   540  	res, body, err := request.Post(
   541  		ctx,
   542  		"/build",
   543  		request.RawContent(bCtx.AsTarReader(c)),
   544  		request.ContentType("application/x-tar"))
   545  	assert.NilError(c, err)
   546  	assert.Equal(c, res.StatusCode, http.StatusOK)
   547  
   548  	out, err := request.ReadBody(body)
   549  	assert.NilError(c, err)
   550  	assert.Check(c, is.Contains(string(out), "Successfully built"))
   551  }
   552  
   553  type buildLine struct {
   554  	Stream string
   555  	Aux    struct {
   556  		ID string
   557  	}
   558  }
   559  
   560  func getImageIDsFromBuild(c *testing.T, output []byte) []string {
   561  	var ids []string
   562  	for _, line := range bytes.Split(output, []byte("\n")) {
   563  		if len(line) == 0 {
   564  			continue
   565  		}
   566  		entry := buildLine{}
   567  		assert.NilError(c, json.Unmarshal(line, &entry))
   568  		if entry.Aux.ID != "" {
   569  			ids = append(ids, entry.Aux.ID)
   570  		}
   571  	}
   572  	return ids
   573  }