integration-cli/docker_api_build_test.go
96e37f67
 package main
 
 import (
 	"archive/tar"
 	"bytes"
7d62e40f
 	"context"
f1ade82d
 	"encoding/json"
c268d9da
 	"fmt"
 	"io"
f85ee178
 	"io/ioutil"
96e37f67
 	"net/http"
8691a77e
 	"regexp"
 	"strings"
e25352a4
 	"testing"
96e37f67
 
c268d9da
 	"github.com/docker/docker/api/types"
06256408
 	"github.com/docker/docker/internal/test/fakecontext"
 	"github.com/docker/docker/internal/test/fakegit"
 	"github.com/docker/docker/internal/test/fakestorage"
42f6fdf0
 	"github.com/docker/docker/internal/test/request"
38457285
 	"gotest.tools/assert"
 	is "gotest.tools/assert/cmp"
96e37f67
 )
 
64a928a3
 func (s *DockerSuite) TestBuildAPIDockerFileRemote(c *testing.T) {
86f9eb4a
 	testRequires(c, NotUserNamespace)
f089a1df
 
f453261b
 	var testD string
18a771a7
 	if testEnv.OSType == "windows" {
f453261b
 		testD = `FROM busybox
96e37f67
 RUN find / -name ba*
f453261b
 RUN find /tmp/`
 	} else {
 		// -xdev is required because sysfs can cause EPERM
 		testD = `FROM busybox
 RUN find / -xdev -name ba*
 RUN find /tmp/`
 	}
56fb4653
 	server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{"testD": testD}))
96e37f67
 	defer server.Close()
 
b11ba123
 	res, body, err := request.Post("/build?dockerfile=baz&remote="+server.URL()+"/testD", request.JSON)
6345208b
 	assert.NilError(c, err)
 	assert.Equal(c, res.StatusCode, http.StatusOK)
96e37f67
 
4f304e72
 	buf, err := request.ReadBody(body)
6345208b
 	assert.NilError(c, err)
96e37f67
 
 	// Make sure Dockerfile exists.
 	// Make sure 'baz' doesn't exist ANYWHERE despite being mentioned in the URL
 	out := string(buf)
6345208b
 	assert.Assert(c, is.Contains(out, "RUN find /tmp"))
 	assert.Assert(c, !strings.Contains(out, "baz"))
96e37f67
 }
 
64a928a3
 func (s *DockerSuite) TestBuildAPIRemoteTarballContext(c *testing.T) {
96e37f67
 	buffer := new(bytes.Buffer)
 	tw := tar.NewWriter(buffer)
 	defer tw.Close()
 
 	dockerfile := []byte("FROM busybox")
253f975f
 	err := tw.WriteHeader(&tar.Header{
96e37f67
 		Name: "Dockerfile",
 		Size: int64(len(dockerfile)),
253f975f
 	})
6345208b
 	assert.NilError(c, err, "failed to write tar file header")
253f975f
 
 	_, err = tw.Write(dockerfile)
6345208b
 	assert.NilError(c, err, "failed to write tar file content")
 	assert.NilError(c, tw.Close(), "failed to close tar archive")
96e37f67
 
56fb4653
 	server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{
96e37f67
 		"testT.tar": buffer,
56fb4653
 	}))
96e37f67
 	defer server.Close()
 
b11ba123
 	res, b, err := request.Post("/build?remote="+server.URL()+"/testT.tar", request.ContentType("application/tar"))
6345208b
 	assert.NilError(c, err)
 	assert.Equal(c, res.StatusCode, http.StatusOK)
96e37f67
 	b.Close()
 }
 
64a928a3
 func (s *DockerSuite) TestBuildAPIRemoteTarballContextWithCustomDockerfile(c *testing.T) {
96e37f67
 	buffer := new(bytes.Buffer)
 	tw := tar.NewWriter(buffer)
 	defer tw.Close()
 
 	dockerfile := []byte(`FROM busybox
 RUN echo 'wrong'`)
253f975f
 	err := tw.WriteHeader(&tar.Header{
96e37f67
 		Name: "Dockerfile",
 		Size: int64(len(dockerfile)),
253f975f
 	})
 	// failed to write tar file header
6345208b
 	assert.NilError(c, err)
253f975f
 
 	_, err = tw.Write(dockerfile)
 	// failed to write tar file content
6345208b
 	assert.NilError(c, err)
96e37f67
 
 	custom := []byte(`FROM busybox
 RUN echo 'right'
 `)
253f975f
 	err = tw.WriteHeader(&tar.Header{
96e37f67
 		Name: "custom",
 		Size: int64(len(custom)),
253f975f
 	})
 
 	// failed to write tar file header
6345208b
 	assert.NilError(c, err)
96e37f67
 
253f975f
 	_, err = tw.Write(custom)
 	// failed to write tar file content
6345208b
 	assert.NilError(c, err)
253f975f
 
 	// failed to close tar archive
6345208b
 	assert.NilError(c, tw.Close())
96e37f67
 
56fb4653
 	server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{
96e37f67
 		"testT.tar": buffer,
56fb4653
 	}))
96e37f67
 	defer server.Close()
c10f6ef4
 
96e37f67
 	url := "/build?dockerfile=custom&remote=" + server.URL() + "/testT.tar"
b11ba123
 	res, body, err := request.Post(url, request.ContentType("application/tar"))
6345208b
 	assert.NilError(c, err)
 	assert.Equal(c, res.StatusCode, http.StatusOK)
96e37f67
 
 	defer body.Close()
4f304e72
 	content, err := request.ReadBody(body)
6345208b
 	assert.NilError(c, err)
96e37f67
 
253f975f
 	// Build used the wrong dockerfile.
6345208b
 	assert.Assert(c, !strings.Contains(string(content), "wrong"))
96e37f67
 }
 
64a928a3
 func (s *DockerSuite) TestBuildAPILowerDockerfile(c *testing.T) {
a582d9dc
 	git := fakegit.New(c, "repo", map[string]string{
96e37f67
 		"dockerfile": `FROM busybox
 RUN echo from dockerfile`,
 	}, false)
 	defer git.Close()
 
b11ba123
 	res, body, err := request.Post("/build?remote="+git.RepoURL, request.JSON)
6345208b
 	assert.NilError(c, err)
 	assert.Equal(c, res.StatusCode, http.StatusOK)
96e37f67
 
4f304e72
 	buf, err := request.ReadBody(body)
6345208b
 	assert.NilError(c, err)
96e37f67
 
 	out := string(buf)
6345208b
 	assert.Assert(c, is.Contains(out, "from dockerfile"))
96e37f67
 }
 
64a928a3
 func (s *DockerSuite) TestBuildAPIBuildGitWithF(c *testing.T) {
a582d9dc
 	git := fakegit.New(c, "repo", map[string]string{
96e37f67
 		"baz": `FROM busybox
 RUN echo from baz`,
 		"Dockerfile": `FROM busybox
 RUN echo from Dockerfile`,
 	}, false)
 	defer git.Close()
 
 	// Make sure it tries to 'dockerfile' query param value
b11ba123
 	res, body, err := request.Post("/build?dockerfile=baz&remote="+git.RepoURL, request.JSON)
6345208b
 	assert.NilError(c, err)
 	assert.Equal(c, res.StatusCode, http.StatusOK)
96e37f67
 
4f304e72
 	buf, err := request.ReadBody(body)
6345208b
 	assert.NilError(c, err)
96e37f67
 
 	out := string(buf)
6345208b
 	assert.Assert(c, is.Contains(out, "from baz"))
96e37f67
 }
 
64a928a3
 func (s *DockerSuite) TestBuildAPIDoubleDockerfile(c *testing.T) {
86f9eb4a
 	testRequires(c, UnixCli) // dockerfile overwrites Dockerfile on Windows
a582d9dc
 	git := fakegit.New(c, "repo", map[string]string{
96e37f67
 		"Dockerfile": `FROM busybox
 RUN echo from Dockerfile`,
 		"dockerfile": `FROM busybox
 RUN echo from dockerfile`,
 	}, false)
 	defer git.Close()
 
 	// Make sure it tries to 'dockerfile' query param value
b11ba123
 	res, body, err := request.Post("/build?remote="+git.RepoURL, request.JSON)
6345208b
 	assert.NilError(c, err)
 	assert.Equal(c, res.StatusCode, http.StatusOK)
96e37f67
 
4f304e72
 	buf, err := request.ReadBody(body)
6345208b
 	assert.NilError(c, err)
96e37f67
 
 	out := string(buf)
6345208b
 	assert.Assert(c, is.Contains(out, "from Dockerfile"))
96e37f67
 }
 
64a928a3
 func (s *DockerSuite) TestBuildAPIUnnormalizedTarPaths(c *testing.T) {
8691a77e
 	// Make sure that build context tars with entries of the form
 	// x/./y don't cause caching false positives.
 
 	buildFromTarContext := func(fileContents []byte) string {
 		buffer := new(bytes.Buffer)
 		tw := tar.NewWriter(buffer)
 		defer tw.Close()
 
 		dockerfile := []byte(`FROM busybox
 	COPY dir /dir/`)
 		err := tw.WriteHeader(&tar.Header{
 			Name: "Dockerfile",
 			Size: int64(len(dockerfile)),
 		})
 		//failed to write tar file header
6345208b
 		assert.NilError(c, err)
8691a77e
 
 		_, err = tw.Write(dockerfile)
 		// failed to write Dockerfile in tar file content
6345208b
 		assert.NilError(c, err)
8691a77e
 
 		err = tw.WriteHeader(&tar.Header{
 			Name: "dir/./file",
 			Size: int64(len(fileContents)),
 		})
 		//failed to write tar file header
6345208b
 		assert.NilError(c, err)
8691a77e
 
 		_, err = tw.Write(fileContents)
 		// failed to write file contents in tar file content
6345208b
 		assert.NilError(c, err)
8691a77e
 
 		// failed to close tar archive
6345208b
 		assert.NilError(c, tw.Close())
8691a77e
 
b11ba123
 		res, body, err := request.Post("/build", request.RawContent(ioutil.NopCloser(buffer)), request.ContentType("application/x-tar"))
6345208b
 		assert.NilError(c, err)
 		assert.Equal(c, res.StatusCode, http.StatusOK)
8691a77e
 
4f304e72
 		out, err := request.ReadBody(body)
6345208b
 		assert.NilError(c, err)
8691a77e
 		lines := strings.Split(string(out), "\n")
6345208b
 		assert.Assert(c, len(lines) > 1)
 		matched, err := regexp.MatchString(".*Successfully built [0-9a-f]{12}.*", lines[len(lines)-2])
 		assert.NilError(c, err)
 		assert.Assert(c, matched)
8691a77e
 
 		re := regexp.MustCompile("Successfully built ([0-9a-f]{12})")
 		matches := re.FindStringSubmatch(lines[len(lines)-2])
 		return matches[1]
 	}
 
 	imageA := buildFromTarContext([]byte("abc"))
 	imageB := buildFromTarContext([]byte("def"))
 
6345208b
 	assert.Assert(c, imageA != imageB)
8691a77e
 }
3f260415
 
64a928a3
 func (s *DockerSuite) TestBuildOnBuildWithCopy(c *testing.T) {
3f260415
 	dockerfile := `
 		FROM ` + minimalBaseImage() + ` as onbuildbase
 		ONBUILD COPY file /file
 
 		FROM onbuildbase
 	`
 	ctx := fakecontext.New(c, "",
 		fakecontext.WithDockerfile(dockerfile),
 		fakecontext.WithFile("file", "some content"),
 	)
 	defer ctx.Close()
 
 	res, body, err := request.Post(
 		"/build",
 		request.RawContent(ctx.AsTarReader(c)),
 		request.ContentType("application/x-tar"))
6345208b
 	assert.NilError(c, err)
 	assert.Equal(c, res.StatusCode, http.StatusOK)
3f260415
 
4f304e72
 	out, err := request.ReadBody(body)
6345208b
 	assert.NilError(c, err)
 	assert.Assert(c, is.Contains(string(out), "Successfully built"))
3f260415
 }
f1ade82d
 
64a928a3
 func (s *DockerSuite) TestBuildOnBuildCache(c *testing.T) {
f1ade82d
 	build := func(dockerfile string) []byte {
 		ctx := fakecontext.New(c, "",
 			fakecontext.WithDockerfile(dockerfile),
 		)
 		defer ctx.Close()
 
 		res, body, err := request.Post(
 			"/build",
 			request.RawContent(ctx.AsTarReader(c)),
 			request.ContentType("application/x-tar"))
6be0f709
 		assert.NilError(c, err)
 		assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
f1ade82d
 
4f304e72
 		out, err := request.ReadBody(body)
6be0f709
 		assert.NilError(c, err)
7e7ff2a0
 		assert.Assert(c, is.Contains(string(out), "Successfully built"))
f1ade82d
 		return out
 	}
 
 	dockerfile := `
 		FROM ` + minimalBaseImage() + ` as onbuildbase
 		ENV something=bar
 		ONBUILD ENV foo=bar
 	`
 	build(dockerfile)
 
 	dockerfile += "FROM onbuildbase"
 	out := build(dockerfile)
 
 	imageIDs := getImageIDsFromBuild(c, out)
7e7ff2a0
 	assert.Assert(c, is.Len(imageIDs, 2))
f1ade82d
 	parentID, childID := imageIDs[0], imageIDs[1]
 
0a91ba2d
 	client := testEnv.APIClient()
f1ade82d
 
 	// check parentID is correct
 	image, _, err := client.ImageInspectWithRaw(context.Background(), childID)
6be0f709
 	assert.NilError(c, err)
 	assert.Check(c, is.Equal(parentID, image.Parent))
f1ade82d
 }
 
64a928a3
 func (s *DockerRegistrySuite) TestBuildCopyFromForcePull(c *testing.T) {
0a91ba2d
 	client := testEnv.APIClient()
c268d9da
 
 	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
 	// tag the image to upload it to the private registry
0a91ba2d
 	err := client.ImageTag(context.TODO(), "busybox", repoName)
6be0f709
 	assert.Check(c, err)
c268d9da
 	// push the image to the registry
 	rc, err := client.ImagePush(context.TODO(), repoName, types.ImagePushOptions{RegistryAuth: "{}"})
6be0f709
 	assert.Check(c, err)
c268d9da
 	_, err = io.Copy(ioutil.Discard, rc)
6be0f709
 	assert.Check(c, err)
c268d9da
 
 	dockerfile := fmt.Sprintf(`
 		FROM %s AS foo
 		RUN touch abc
 		FROM %s
 		COPY --from=foo /abc /
 		`, repoName, repoName)
 
 	ctx := fakecontext.New(c, "",
 		fakecontext.WithDockerfile(dockerfile),
 	)
 	defer ctx.Close()
 
 	res, body, err := request.Post(
 		"/build?pull=1",
 		request.RawContent(ctx.AsTarReader(c)),
 		request.ContentType("application/x-tar"))
6be0f709
 	assert.NilError(c, err)
 	assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
c268d9da
 
4f304e72
 	out, err := request.ReadBody(body)
6be0f709
 	assert.NilError(c, err)
 	assert.Check(c, is.Contains(string(out), "Successfully built"))
2981667e
 }
 
64a928a3
 func (s *DockerSuite) TestBuildAddRemoteNoDecompress(c *testing.T) {
2981667e
 	buffer := new(bytes.Buffer)
 	tw := tar.NewWriter(buffer)
 	dt := []byte("contents")
 	err := tw.WriteHeader(&tar.Header{
 		Name:     "foo",
 		Size:     int64(len(dt)),
 		Mode:     0600,
 		Typeflag: tar.TypeReg,
 	})
6be0f709
 	assert.NilError(c, err)
2981667e
 	_, err = tw.Write(dt)
6be0f709
 	assert.NilError(c, err)
2981667e
 	err = tw.Close()
6be0f709
 	assert.NilError(c, err)
2981667e
 
 	server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{
 		"test.tar": buffer,
 	}))
 	defer server.Close()
 
 	dockerfile := fmt.Sprintf(`
 		FROM busybox
 		ADD %s/test.tar /
 		RUN [ -f test.tar ]
 		`, server.URL())
 
 	ctx := fakecontext.New(c, "",
 		fakecontext.WithDockerfile(dockerfile),
 	)
 	defer ctx.Close()
 
 	res, body, err := request.Post(
 		"/build",
 		request.RawContent(ctx.AsTarReader(c)),
 		request.ContentType("application/x-tar"))
6be0f709
 	assert.NilError(c, err)
 	assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
2981667e
 
4f304e72
 	out, err := request.ReadBody(body)
6be0f709
 	assert.NilError(c, err)
 	assert.Check(c, is.Contains(string(out), "Successfully built"))
19a29f6f
 }
 
64a928a3
 func (s *DockerSuite) TestBuildChownOnCopy(c *testing.T) {
e4408318
 	// new feature added in 1.31 - https://github.com/moby/moby/pull/34263
 	testRequires(c, DaemonIsLinux, MinimumAPIVersion("1.31"))
19a29f6f
 	dockerfile := `FROM busybox
 		RUN echo 'test1:x:1001:1001::/bin:/bin/false' >> /etc/passwd
 		RUN echo 'test1:x:1001:' >> /etc/group
 		RUN echo 'test2:x:1002:' >> /etc/group
 		COPY --chown=test1:1002 . /new_dir
 		RUN ls -l /
 		RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'test1:test2' ]
 		RUN [ $(ls -nl / | grep new_dir | awk '{print $3":"$4}') = '1001:1002' ]
 	`
 	ctx := fakecontext.New(c, "",
 		fakecontext.WithDockerfile(dockerfile),
 		fakecontext.WithFile("test_file1", "some test content"),
 	)
 	defer ctx.Close()
 
 	res, body, err := request.Post(
 		"/build",
 		request.RawContent(ctx.AsTarReader(c)),
 		request.ContentType("application/x-tar"))
6345208b
 	assert.NilError(c, err)
 	assert.Equal(c, res.StatusCode, http.StatusOK)
19a29f6f
 
ba6f9e4c
 	out, err := request.ReadBody(body)
6be0f709
 	assert.NilError(c, err)
 	assert.Check(c, is.Contains(string(out), "Successfully built"))
c268d9da
 }
 
64a928a3
 func (s *DockerSuite) TestBuildCopyCacheOnFileChange(c *testing.T) {
669c0677
 
 	dockerfile := `FROM busybox
 COPY file /file`
 
 	ctx1 := fakecontext.New(c, "",
 		fakecontext.WithDockerfile(dockerfile),
 		fakecontext.WithFile("file", "foo"))
 	ctx2 := fakecontext.New(c, "",
 		fakecontext.WithDockerfile(dockerfile),
 		fakecontext.WithFile("file", "bar"))
 
 	var build = func(ctx *fakecontext.Fake) string {
 		res, body, err := request.Post("/build",
 			request.RawContent(ctx.AsTarReader(c)),
 			request.ContentType("application/x-tar"))
 
6be0f709
 		assert.NilError(c, err)
 		assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
669c0677
 
 		out, err := request.ReadBody(body)
6be0f709
 		assert.NilError(c, err)
7e7ff2a0
 		assert.Assert(c, is.Contains(string(out), "Successfully built"))
669c0677
 
 		ids := getImageIDsFromBuild(c, out)
7e7ff2a0
 		assert.Assert(c, is.Len(ids, 1))
669c0677
 		return ids[len(ids)-1]
 	}
 
 	id1 := build(ctx1)
 	id2 := build(ctx1)
 	id3 := build(ctx2)
 
 	if id1 != id2 {
 		c.Fatal("didn't use the cache")
 	}
 	if id1 == id3 {
 		c.Fatal("COPY With different source file should not share same cache")
 	}
 }
 
64a928a3
 func (s *DockerSuite) TestBuildAddCacheOnFileChange(c *testing.T) {
669c0677
 
 	dockerfile := `FROM busybox
 ADD file /file`
 
 	ctx1 := fakecontext.New(c, "",
 		fakecontext.WithDockerfile(dockerfile),
 		fakecontext.WithFile("file", "foo"))
 	ctx2 := fakecontext.New(c, "",
 		fakecontext.WithDockerfile(dockerfile),
 		fakecontext.WithFile("file", "bar"))
 
 	var build = func(ctx *fakecontext.Fake) string {
 		res, body, err := request.Post("/build",
 			request.RawContent(ctx.AsTarReader(c)),
 			request.ContentType("application/x-tar"))
 
6be0f709
 		assert.NilError(c, err)
 		assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode))
669c0677
 
 		out, err := request.ReadBody(body)
6be0f709
 		assert.NilError(c, err)
7e7ff2a0
 		assert.Assert(c, is.Contains(string(out), "Successfully built"))
669c0677
 
 		ids := getImageIDsFromBuild(c, out)
7e7ff2a0
 		assert.Assert(c, is.Len(ids, 1))
669c0677
 		return ids[len(ids)-1]
 	}
 
 	id1 := build(ctx1)
 	id2 := build(ctx1)
 	id3 := build(ctx2)
 
 	if id1 != id2 {
 		c.Fatal("didn't use the cache")
 	}
 	if id1 == id3 {
 		c.Fatal("COPY With different source file should not share same cache")
 	}
 }
 
64a928a3
 func (s *DockerSuite) TestBuildScratchCopy(c *testing.T) {
a97817b6
 	testRequires(c, DaemonIsLinux)
 	dockerfile := `FROM scratch
 ADD Dockerfile /
 ENV foo bar`
 	ctx := fakecontext.New(c, "",
 		fakecontext.WithDockerfile(dockerfile),
 	)
 	defer ctx.Close()
 
 	res, body, err := request.Post(
 		"/build",
 		request.RawContent(ctx.AsTarReader(c)),
 		request.ContentType("application/x-tar"))
6345208b
 	assert.NilError(c, err)
 	assert.Equal(c, res.StatusCode, http.StatusOK)
a97817b6
 
 	out, err := request.ReadBody(body)
6be0f709
 	assert.NilError(c, err)
 	assert.Check(c, is.Contains(string(out), "Successfully built"))
a97817b6
 }
 
f1ade82d
 type buildLine struct {
 	Stream string
 	Aux    struct {
 		ID string
 	}
 }
 
64a928a3
 func getImageIDsFromBuild(c *testing.T, output []byte) []string {
f23c00d8
 	var ids []string
f1ade82d
 	for _, line := range bytes.Split(output, []byte("\n")) {
 		if len(line) == 0 {
 			continue
 		}
 		entry := buildLine{}
6be0f709
 		assert.NilError(c, json.Unmarshal(line, &entry))
f1ade82d
 		if entry.Aux.ID != "" {
 			ids = append(ids, entry.Aux.ID)
 		}
 	}
 	return ids
 }