github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/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  
    15  	"github.com/docker/docker/api/types"
    16  	"github.com/docker/docker/internal/test/fakecontext"
    17  	"github.com/docker/docker/internal/test/fakegit"
    18  	"github.com/docker/docker/internal/test/fakestorage"
    19  	"github.com/docker/docker/internal/test/request"
    20  	"github.com/go-check/check"
    21  	"gotest.tools/assert"
    22  	is "gotest.tools/assert/cmp"
    23  )
    24  
    25  func (s *DockerSuite) TestBuildAPIDockerFileRemote(c *check.C) {
    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 *check.C) {
    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 *check.C) {
    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 *check.C) {
   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 *check.C) {
   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 *check.C) {
   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 *check.C) {
   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  		//failed to write tar file header
   214  		assert.NilError(c, err)
   215  
   216  		_, err = tw.Write(dockerfile)
   217  		// failed to write Dockerfile in tar file content
   218  		assert.NilError(c, err)
   219  
   220  		err = tw.WriteHeader(&tar.Header{
   221  			Name: "dir/./file",
   222  			Size: int64(len(fileContents)),
   223  		})
   224  		//failed to write tar file header
   225  		assert.NilError(c, err)
   226  
   227  		_, err = tw.Write(fileContents)
   228  		// failed to write file contents in tar file content
   229  		assert.NilError(c, err)
   230  
   231  		// failed to close tar archive
   232  		assert.NilError(c, tw.Close())
   233  
   234  		res, body, err := request.Post("/build", request.RawContent(ioutil.NopCloser(buffer)), request.ContentType("application/x-tar"))
   235  		assert.NilError(c, err)
   236  		assert.Equal(c, res.StatusCode, http.StatusOK)
   237  
   238  		out, err := request.ReadBody(body)
   239  		assert.NilError(c, err)
   240  		lines := strings.Split(string(out), "\n")
   241  		assert.Assert(c, len(lines) > 1)
   242  		matched, err := regexp.MatchString(".*Successfully built [0-9a-f]{12}.*", lines[len(lines)-2])
   243  		assert.NilError(c, err)
   244  		assert.Assert(c, matched)
   245  
   246  		re := regexp.MustCompile("Successfully built ([0-9a-f]{12})")
   247  		matches := re.FindStringSubmatch(lines[len(lines)-2])
   248  		return matches[1]
   249  	}
   250  
   251  	imageA := buildFromTarContext([]byte("abc"))
   252  	imageB := buildFromTarContext([]byte("def"))
   253  
   254  	assert.Assert(c, imageA != imageB)
   255  }
   256  
   257  func (s *DockerSuite) TestBuildOnBuildWithCopy(c *check.C) {
   258  	dockerfile := `
   259  		FROM ` + minimalBaseImage() + ` as onbuildbase
   260  		ONBUILD COPY file /file
   261  
   262  		FROM onbuildbase
   263  	`
   264  	ctx := fakecontext.New(c, "",
   265  		fakecontext.WithDockerfile(dockerfile),
   266  		fakecontext.WithFile("file", "some content"),
   267  	)
   268  	defer ctx.Close()
   269  
   270  	res, body, err := request.Post(
   271  		"/build",
   272  		request.RawContent(ctx.AsTarReader(c)),
   273  		request.ContentType("application/x-tar"))
   274  	assert.NilError(c, err)
   275  	assert.Equal(c, res.StatusCode, http.StatusOK)
   276  
   277  	out, err := request.ReadBody(body)
   278  	assert.NilError(c, err)
   279  	assert.Assert(c, is.Contains(string(out), "Successfully built"))
   280  }
   281  
   282  func (s *DockerSuite) TestBuildOnBuildCache(c *check.C) {
   283  	build := func(dockerfile string) []byte {
   284  		ctx := fakecontext.New(c, "",
   285  			fakecontext.WithDockerfile(dockerfile),
   286  		)
   287  		defer ctx.Close()
   288  
   289  		res, body, err := request.Post(
   290  			"/build",
   291  			request.RawContent(ctx.AsTarReader(c)),
   292  			request.ContentType("application/x-tar"))
   293  		assert.NilError(c, err)
   294  		assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
   295  
   296  		out, err := request.ReadBody(body)
   297  		assert.NilError(c, err)
   298  		assert.Check(c, is.Contains(string(out), "Successfully built"))
   299  		return out
   300  	}
   301  
   302  	dockerfile := `
   303  		FROM ` + minimalBaseImage() + ` as onbuildbase
   304  		ENV something=bar
   305  		ONBUILD ENV foo=bar
   306  	`
   307  	build(dockerfile)
   308  
   309  	dockerfile += "FROM onbuildbase"
   310  	out := build(dockerfile)
   311  
   312  	imageIDs := getImageIDsFromBuild(c, out)
   313  	assert.Check(c, is.Len(imageIDs, 2))
   314  	parentID, childID := imageIDs[0], imageIDs[1]
   315  
   316  	client := testEnv.APIClient()
   317  
   318  	// check parentID is correct
   319  	image, _, err := client.ImageInspectWithRaw(context.Background(), childID)
   320  	assert.NilError(c, err)
   321  	assert.Check(c, is.Equal(parentID, image.Parent))
   322  }
   323  
   324  func (s *DockerRegistrySuite) TestBuildCopyFromForcePull(c *check.C) {
   325  	client := testEnv.APIClient()
   326  
   327  	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
   328  	// tag the image to upload it to the private registry
   329  	err := client.ImageTag(context.TODO(), "busybox", repoName)
   330  	assert.Check(c, err)
   331  	// push the image to the registry
   332  	rc, err := client.ImagePush(context.TODO(), repoName, types.ImagePushOptions{RegistryAuth: "{}"})
   333  	assert.Check(c, err)
   334  	_, err = io.Copy(ioutil.Discard, rc)
   335  	assert.Check(c, err)
   336  
   337  	dockerfile := fmt.Sprintf(`
   338  		FROM %s AS foo
   339  		RUN touch abc
   340  		FROM %s
   341  		COPY --from=foo /abc /
   342  		`, repoName, repoName)
   343  
   344  	ctx := fakecontext.New(c, "",
   345  		fakecontext.WithDockerfile(dockerfile),
   346  	)
   347  	defer ctx.Close()
   348  
   349  	res, body, err := request.Post(
   350  		"/build?pull=1",
   351  		request.RawContent(ctx.AsTarReader(c)),
   352  		request.ContentType("application/x-tar"))
   353  	assert.NilError(c, err)
   354  	assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
   355  
   356  	out, err := request.ReadBody(body)
   357  	assert.NilError(c, err)
   358  	assert.Check(c, is.Contains(string(out), "Successfully built"))
   359  }
   360  
   361  func (s *DockerSuite) TestBuildAddRemoteNoDecompress(c *check.C) {
   362  	buffer := new(bytes.Buffer)
   363  	tw := tar.NewWriter(buffer)
   364  	dt := []byte("contents")
   365  	err := tw.WriteHeader(&tar.Header{
   366  		Name:     "foo",
   367  		Size:     int64(len(dt)),
   368  		Mode:     0600,
   369  		Typeflag: tar.TypeReg,
   370  	})
   371  	assert.NilError(c, err)
   372  	_, err = tw.Write(dt)
   373  	assert.NilError(c, err)
   374  	err = tw.Close()
   375  	assert.NilError(c, err)
   376  
   377  	server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{
   378  		"test.tar": buffer,
   379  	}))
   380  	defer server.Close()
   381  
   382  	dockerfile := fmt.Sprintf(`
   383  		FROM busybox
   384  		ADD %s/test.tar /
   385  		RUN [ -f test.tar ]
   386  		`, server.URL())
   387  
   388  	ctx := fakecontext.New(c, "",
   389  		fakecontext.WithDockerfile(dockerfile),
   390  	)
   391  	defer ctx.Close()
   392  
   393  	res, body, err := request.Post(
   394  		"/build",
   395  		request.RawContent(ctx.AsTarReader(c)),
   396  		request.ContentType("application/x-tar"))
   397  	assert.NilError(c, err)
   398  	assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
   399  
   400  	out, err := request.ReadBody(body)
   401  	assert.NilError(c, err)
   402  	assert.Check(c, is.Contains(string(out), "Successfully built"))
   403  }
   404  
   405  func (s *DockerSuite) TestBuildChownOnCopy(c *check.C) {
   406  	// new feature added in 1.31 - https://github.com/moby/moby/pull/34263
   407  	testRequires(c, DaemonIsLinux, MinimumAPIVersion("1.31"))
   408  	dockerfile := `FROM busybox
   409  		RUN echo 'test1:x:1001:1001::/bin:/bin/false' >> /etc/passwd
   410  		RUN echo 'test1:x:1001:' >> /etc/group
   411  		RUN echo 'test2:x:1002:' >> /etc/group
   412  		COPY --chown=test1:1002 . /new_dir
   413  		RUN ls -l /
   414  		RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'test1:test2' ]
   415  		RUN [ $(ls -nl / | grep new_dir | awk '{print $3":"$4}') = '1001:1002' ]
   416  	`
   417  	ctx := fakecontext.New(c, "",
   418  		fakecontext.WithDockerfile(dockerfile),
   419  		fakecontext.WithFile("test_file1", "some test content"),
   420  	)
   421  	defer ctx.Close()
   422  
   423  	res, body, err := request.Post(
   424  		"/build",
   425  		request.RawContent(ctx.AsTarReader(c)),
   426  		request.ContentType("application/x-tar"))
   427  	assert.NilError(c, err)
   428  	assert.Equal(c, res.StatusCode, http.StatusOK)
   429  
   430  	out, err := request.ReadBody(body)
   431  	assert.NilError(c, err)
   432  	assert.Check(c, is.Contains(string(out), "Successfully built"))
   433  }
   434  
   435  func (s *DockerSuite) TestBuildCopyCacheOnFileChange(c *check.C) {
   436  
   437  	dockerfile := `FROM busybox
   438  COPY file /file`
   439  
   440  	ctx1 := fakecontext.New(c, "",
   441  		fakecontext.WithDockerfile(dockerfile),
   442  		fakecontext.WithFile("file", "foo"))
   443  	ctx2 := fakecontext.New(c, "",
   444  		fakecontext.WithDockerfile(dockerfile),
   445  		fakecontext.WithFile("file", "bar"))
   446  
   447  	var build = func(ctx *fakecontext.Fake) string {
   448  		res, body, err := request.Post("/build",
   449  			request.RawContent(ctx.AsTarReader(c)),
   450  			request.ContentType("application/x-tar"))
   451  
   452  		assert.NilError(c, err)
   453  		assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
   454  
   455  		out, err := request.ReadBody(body)
   456  		assert.NilError(c, err)
   457  
   458  		ids := getImageIDsFromBuild(c, out)
   459  		return ids[len(ids)-1]
   460  	}
   461  
   462  	id1 := build(ctx1)
   463  	id2 := build(ctx1)
   464  	id3 := build(ctx2)
   465  
   466  	if id1 != id2 {
   467  		c.Fatal("didn't use the cache")
   468  	}
   469  	if id1 == id3 {
   470  		c.Fatal("COPY With different source file should not share same cache")
   471  	}
   472  }
   473  
   474  func (s *DockerSuite) TestBuildAddCacheOnFileChange(c *check.C) {
   475  
   476  	dockerfile := `FROM busybox
   477  ADD file /file`
   478  
   479  	ctx1 := fakecontext.New(c, "",
   480  		fakecontext.WithDockerfile(dockerfile),
   481  		fakecontext.WithFile("file", "foo"))
   482  	ctx2 := fakecontext.New(c, "",
   483  		fakecontext.WithDockerfile(dockerfile),
   484  		fakecontext.WithFile("file", "bar"))
   485  
   486  	var build = func(ctx *fakecontext.Fake) string {
   487  		res, body, err := request.Post("/build",
   488  			request.RawContent(ctx.AsTarReader(c)),
   489  			request.ContentType("application/x-tar"))
   490  
   491  		assert.NilError(c, err)
   492  		assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
   493  
   494  		out, err := request.ReadBody(body)
   495  		assert.NilError(c, err)
   496  
   497  		ids := getImageIDsFromBuild(c, out)
   498  		return ids[len(ids)-1]
   499  	}
   500  
   501  	id1 := build(ctx1)
   502  	id2 := build(ctx1)
   503  	id3 := build(ctx2)
   504  
   505  	if id1 != id2 {
   506  		c.Fatal("didn't use the cache")
   507  	}
   508  	if id1 == id3 {
   509  		c.Fatal("COPY With different source file should not share same cache")
   510  	}
   511  }
   512  
   513  func (s *DockerSuite) TestBuildScratchCopy(c *check.C) {
   514  	testRequires(c, DaemonIsLinux)
   515  	dockerfile := `FROM scratch
   516  ADD Dockerfile /
   517  ENV foo bar`
   518  	ctx := fakecontext.New(c, "",
   519  		fakecontext.WithDockerfile(dockerfile),
   520  	)
   521  	defer ctx.Close()
   522  
   523  	res, body, err := request.Post(
   524  		"/build",
   525  		request.RawContent(ctx.AsTarReader(c)),
   526  		request.ContentType("application/x-tar"))
   527  	assert.NilError(c, err)
   528  	assert.Equal(c, res.StatusCode, http.StatusOK)
   529  
   530  	out, err := request.ReadBody(body)
   531  	assert.NilError(c, err)
   532  	assert.Check(c, is.Contains(string(out), "Successfully built"))
   533  }
   534  
   535  type buildLine struct {
   536  	Stream string
   537  	Aux    struct {
   538  		ID string
   539  	}
   540  }
   541  
   542  func getImageIDsFromBuild(c *check.C, output []byte) []string {
   543  	var ids []string
   544  	for _, line := range bytes.Split(output, []byte("\n")) {
   545  		if len(line) == 0 {
   546  			continue
   547  		}
   548  		entry := buildLine{}
   549  		assert.NilError(c, json.Unmarshal(line, &entry))
   550  		if entry.Aux.ID != "" {
   551  			ids = append(ids, entry.Aux.ID)
   552  		}
   553  	}
   554  	return ids
   555  }