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