integration-cli/docker_cli_build_unix_test.go
a9141012
 // +build !windows
 
 package main
 
 import (
851fe00c
 	"bufio"
 	"bytes"
a9141012
 	"encoding/json"
9aad7d20
 	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
851fe00c
 	"regexp"
a9141012
 	"strings"
6151d55e
 	"syscall"
e25352a4
 	"testing"
4f304e72
 	"time"
6151d55e
 
eeaa6c96
 	"github.com/docker/docker/integration-cli/cli"
56fb4653
 	"github.com/docker/docker/integration-cli/cli/build"
06256408
 	"github.com/docker/docker/internal/test/fakecontext"
f23c00d8
 	"github.com/docker/go-units"
6345208b
 	"gotest.tools/assert"
38457285
 	"gotest.tools/icmd"
a9141012
 )
 
64a928a3
 func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *testing.T) {
7dabed01
 	testRequires(c, cpuCfsQuota)
a9141012
 	name := "testbuildresourceconstraints"
7dabed01
 	buildLabel := "DockerSuite.TestBuildResourceConstraintsAreUsed"
a9141012
 
56fb4653
 	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`
a9141012
 	FROM hello-world:frozen
 	RUN ["/hello"]
56fb4653
 	`))
eeaa6c96
 	cli.Docker(
7dabed01
 		cli.Args("build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpuset-mems=0", "--cpu-shares=100", "--cpu-quota=8000", "--ulimit", "nofile=42", "--label="+buildLabel, "-t", name, "."),
eeaa6c96
 		cli.InDir(ctx.Dir),
 	).Assert(c, icmd.Success)
a9141012
 
7dabed01
 	out := cli.DockerCmd(c, "ps", "-lq", "--filter", "label="+buildLabel).Combined()
a9141012
 	cID := strings.TrimSpace(out)
 
 	type hostConfig struct {
 		Memory     int64
 		MemorySwap int64
 		CpusetCpus string
 		CpusetMems string
6b3c9281
 		CPUShares  int64
 		CPUQuota   int64
83237aab
 		Ulimits    []*units.Ulimit
a9141012
 	}
 
62a856e9
 	cfg := inspectFieldJSON(c, cID, "HostConfig")
a9141012
 
 	var c1 hostConfig
eeaa6c96
 	err := json.Unmarshal([]byte(cfg), &c1)
be28c059
 	assert.Assert(c, err == nil, cfg)
f410d7e7
 
2f069fa3
 	assert.Equal(c, c1.Memory, int64(64*1024*1024), "resource constraints not set properly for Memory")
 	assert.Equal(c, c1.MemorySwap, int64(-1), "resource constraints not set properly for MemorySwap")
 	assert.Equal(c, c1.CpusetCpus, "0", "resource constraints not set properly for CpusetCpus")
 	assert.Equal(c, c1.CpusetMems, "0", "resource constraints not set properly for CpusetMems")
 	assert.Equal(c, c1.CPUShares, int64(100), "resource constraints not set properly for CPUShares")
 	assert.Equal(c, c1.CPUQuota, int64(8000), "resource constraints not set properly for CPUQuota")
 	assert.Equal(c, c1.Ulimits[0].Name, "nofile", "resource constraints not set properly for Ulimits")
 	assert.Equal(c, c1.Ulimits[0].Hard, int64(42), "resource constraints not set properly for Ulimits")
a9141012
 
 	// Make sure constraints aren't saved to image
eeaa6c96
 	cli.DockerCmd(c, "run", "--name=test", name)
a9141012
 
62a856e9
 	cfg = inspectFieldJSON(c, "test", "HostConfig")
877dbbbd
 
f410d7e7
 	var c2 hostConfig
 	err = json.Unmarshal([]byte(cfg), &c2)
be28c059
 	assert.Assert(c, err == nil, cfg)
a11079a4
 
2f069fa3
 	assert.Assert(c, c2.Memory != int64(64*1024*1024), "resource leaked from build for Memory")
 	assert.Assert(c, c2.MemorySwap != int64(-1), "resource leaked from build for MemorySwap")
 	assert.Assert(c, c2.CpusetCpus != "0", "resource leaked from build for CpusetCpus")
 	assert.Assert(c, c2.CpusetMems != "0", "resource leaked from build for CpusetMems")
 	assert.Assert(c, c2.CPUShares != int64(100), "resource leaked from build for CPUShares")
 	assert.Assert(c, c2.CPUQuota != int64(8000), "resource leaked from build for CPUQuota")
 	assert.Assert(c, c2.Ulimits == nil, "resource leaked from build for Ulimits")
a9141012
 }
9aad7d20
 
64a928a3
 func (s *DockerSuite) TestBuildAddChangeOwnership(c *testing.T) {
9aad7d20
 	testRequires(c, DaemonIsLinux)
 	name := "testbuildaddown"
 
56fb4653
 	ctx := func() *fakecontext.Fake {
9aad7d20
 		dockerfile := `
 			FROM busybox
 			ADD foo /bar/
 			RUN [ $(stat -c %U:%G "/bar") = 'root:root' ]
 			RUN [ $(stat -c %U:%G "/bar/foo") = 'root:root' ]
 			`
 		tmpDir, err := ioutil.TempDir("", "fake-context")
6345208b
 		assert.NilError(c, err)
9aad7d20
 		testFile, err := os.Create(filepath.Join(tmpDir, "foo"))
 		if err != nil {
 			c.Fatalf("failed to create foo file: %v", err)
 		}
 		defer testFile.Close()
 
87e3fcfe
 		icmd.RunCmd(icmd.Cmd{
 			Command: []string{"chown", "daemon:daemon", "foo"},
303b1d20
 			Dir:     tmpDir,
87e3fcfe
 		}).Assert(c, icmd.Success)
9aad7d20
 
 		if err := ioutil.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644); err != nil {
 			c.Fatalf("failed to open destination dockerfile: %v", err)
 		}
56fb4653
 		return fakecontext.New(c, tmpDir)
9aad7d20
 	}()
 
 	defer ctx.Close()
 
56fb4653
 	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
851fe00c
 }
 
 // Test that an infinite sleep during a build is killed if the client disconnects.
 // This test is fairly hairy because there are lots of ways to race.
 // Strategy:
 // * Monitor the output of docker events starting from before
 // * Run a 1-year-long sleep from a docker build.
 // * When docker events sees container start, close the "docker build" command
 // * Wait for docker events to emit a dying event.
60a911df
 //
 // TODO(buildkit): this test needs to be rewritten for buildkit.
 // It has been manually tested positive. Confirmed issue: docker build output parsing.
 // Potential issue: newEventObserver uses docker events, which is not hooked up to buildkit.
64a928a3
 func (s *DockerSuite) TestBuildCancellationKillsSleep(c *testing.T) {
60a911df
 	testRequires(c, DaemonIsLinux, TODOBuildkit)
851fe00c
 	name := "testbuildcancellation"
 
 	observer, err := newEventObserver(c)
6345208b
 	assert.NilError(c, err)
851fe00c
 	err = observer.Start()
6345208b
 	assert.NilError(c, err)
851fe00c
 	defer observer.Stop()
9aad7d20
 
851fe00c
 	// (Note: one year, will never finish)
56fb4653
 	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile("FROM busybox\nRUN sleep 31536000"))
851fe00c
 	defer ctx.Close()
 
 	buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".")
 	buildCmd.Dir = ctx.Dir
 
 	stdoutBuild, err := buildCmd.StdoutPipe()
6345208b
 	assert.NilError(c, err)
ba0afd70
 
851fe00c
 	if err := buildCmd.Start(); err != nil {
 		c.Fatalf("failed to run build: %s", err)
 	}
ddae20c0
 	// always clean up
 	defer func() {
 		buildCmd.Process.Kill()
 		buildCmd.Wait()
 	}()
851fe00c
 
 	matchCID := regexp.MustCompile("Running in (.+)")
 	scanner := bufio.NewScanner(stdoutBuild)
 
 	outputBuffer := new(bytes.Buffer)
 	var buildID string
 	for scanner.Scan() {
 		line := scanner.Text()
 		outputBuffer.WriteString(line)
 		outputBuffer.WriteString("\n")
 		if matches := matchCID.FindStringSubmatch(line); len(matches) > 0 {
 			buildID = matches[1]
 			break
 		}
 	}
 
 	if buildID == "" {
 		c.Fatalf("Unable to find build container id in build output:\n%s", outputBuffer.String())
 	}
 
 	testActions := map[string]chan bool{
abbf2aa6
 		"start": make(chan bool, 1),
 		"die":   make(chan bool, 1),
851fe00c
 	}
 
 	matcher := matchEventLine(buildID, "container", testActions)
27b06049
 	processor := processEventMatch(testActions)
 	go observer.Match(matcher, processor)
851fe00c
 
 	select {
 	case <-time.After(10 * time.Second):
 		observer.CheckEventError(c, buildID, "start", matcher)
 	case <-testActions["start"]:
 		// ignore, done
 	}
 
 	// Send a kill to the `docker build` command.
 	// Causes the underlying build to be cancelled due to socket close.
 	if err := buildCmd.Process.Kill(); err != nil {
 		c.Fatalf("error killing build command: %s", err)
 	}
 
 	// Get the exit status of `docker build`, check it exited because killed.
6151d55e
 	if err := buildCmd.Wait(); err != nil && !isKilled(err) {
851fe00c
 		c.Fatalf("wait failed during build run: %T %s", err, err)
 	}
 
 	select {
 	case <-time.After(10 * time.Second):
 		observer.CheckEventError(c, buildID, "die", matcher)
 	case <-testActions["die"]:
 		// ignore, done
 	}
9aad7d20
 }
6151d55e
 
 func isKilled(err error) bool {
 	if exitErr, ok := err.(*exec.ExitError); ok {
 		status, ok := exitErr.Sys().(syscall.WaitStatus)
 		if !ok {
 			return false
 		}
 		// status.ExitStatus() is required on Windows because it does not
 		// implement Signal() nor Signaled(). Just check it had a bad exit
 		// status could mean it was killed (and in tests we do kill)
 		return (status.Signaled() && status.Signal() == os.Kill) || status.ExitStatus() != 0
 	}
 	return false
 }