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