gopkg.in/docker/docker.v20@v20.10.27/integration-cli/docker_api_build_test.go (about)

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