github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/integration-cli/docker_cli_build_unix_test.go (about)

     1  //go:build !windows
     2  
     3  package main
     4  
     5  import (
     6  	"bufio"
     7  	"bytes"
     8  	"encoding/json"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"regexp"
    13  	"strings"
    14  	"syscall"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/docker/docker/integration-cli/cli"
    19  	"github.com/docker/docker/integration-cli/cli/build"
    20  	"github.com/docker/docker/testutil/fakecontext"
    21  	units "github.com/docker/go-units"
    22  	"gotest.tools/v3/assert"
    23  	"gotest.tools/v3/icmd"
    24  )
    25  
    26  func (s *DockerCLIBuildSuite) TestBuildResourceConstraintsAreUsed(c *testing.T) {
    27  	testRequires(c, cpuCfsQuota)
    28  	const name = "testbuildresourceconstraints"
    29  	const buildLabel = "DockerCLIBuildSuite.TestBuildResourceConstraintsAreUsed"
    30  
    31  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`
    32  	FROM hello-world:frozen
    33  	RUN ["/hello"]
    34  	`))
    35  	cli.Docker(
    36  		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, "."),
    37  		cli.InDir(ctx.Dir),
    38  	).Assert(c, icmd.Success)
    39  
    40  	out := cli.DockerCmd(c, "ps", "-lq", "--filter", "label="+buildLabel).Combined()
    41  	cID := strings.TrimSpace(out)
    42  
    43  	type hostConfig struct {
    44  		Memory     int64
    45  		MemorySwap int64
    46  		CpusetCpus string
    47  		CpusetMems string
    48  		CPUShares  int64
    49  		CPUQuota   int64
    50  		Ulimits    []*units.Ulimit
    51  	}
    52  
    53  	cfg := inspectFieldJSON(c, cID, "HostConfig")
    54  
    55  	var c1 hostConfig
    56  	err := json.Unmarshal([]byte(cfg), &c1)
    57  	assert.Assert(c, err == nil, cfg)
    58  
    59  	assert.Equal(c, c1.Memory, int64(64*1024*1024), "resource constraints not set properly for Memory")
    60  	assert.Equal(c, c1.MemorySwap, int64(-1), "resource constraints not set properly for MemorySwap")
    61  	assert.Equal(c, c1.CpusetCpus, "0", "resource constraints not set properly for CpusetCpus")
    62  	assert.Equal(c, c1.CpusetMems, "0", "resource constraints not set properly for CpusetMems")
    63  	assert.Equal(c, c1.CPUShares, int64(100), "resource constraints not set properly for CPUShares")
    64  	assert.Equal(c, c1.CPUQuota, int64(8000), "resource constraints not set properly for CPUQuota")
    65  	assert.Equal(c, c1.Ulimits[0].Name, "nofile", "resource constraints not set properly for Ulimits")
    66  	assert.Equal(c, c1.Ulimits[0].Hard, int64(42), "resource constraints not set properly for Ulimits")
    67  
    68  	// Make sure constraints aren't saved to image
    69  	cli.DockerCmd(c, "run", "--name=test", name)
    70  
    71  	cfg = inspectFieldJSON(c, "test", "HostConfig")
    72  
    73  	var c2 hostConfig
    74  	err = json.Unmarshal([]byte(cfg), &c2)
    75  	assert.Assert(c, err == nil, cfg)
    76  
    77  	assert.Assert(c, c2.Memory != int64(64*1024*1024), "resource leaked from build for Memory")
    78  	assert.Assert(c, c2.MemorySwap != int64(-1), "resource leaked from build for MemorySwap")
    79  	assert.Assert(c, c2.CpusetCpus != "0", "resource leaked from build for CpusetCpus")
    80  	assert.Assert(c, c2.CpusetMems != "0", "resource leaked from build for CpusetMems")
    81  	assert.Assert(c, c2.CPUShares != int64(100), "resource leaked from build for CPUShares")
    82  	assert.Assert(c, c2.CPUQuota != int64(8000), "resource leaked from build for CPUQuota")
    83  	assert.Assert(c, c2.Ulimits == nil, "resource leaked from build for Ulimits")
    84  }
    85  
    86  func (s *DockerCLIBuildSuite) TestBuildAddChangeOwnership(c *testing.T) {
    87  	testRequires(c, DaemonIsLinux)
    88  	const name = "testbuildaddown"
    89  
    90  	ctx := func() *fakecontext.Fake {
    91  		dockerfile := `
    92  			FROM busybox
    93  			ADD foo /bar/
    94  			RUN [ $(stat -c %U:%G "/bar") = 'root:root' ]
    95  			RUN [ $(stat -c %U:%G "/bar/foo") = 'root:root' ]
    96  			`
    97  		tmpDir, err := os.MkdirTemp("", "fake-context")
    98  		assert.NilError(c, err)
    99  		testFile, err := os.Create(filepath.Join(tmpDir, "foo"))
   100  		if err != nil {
   101  			c.Fatalf("failed to create foo file: %v", err)
   102  		}
   103  		defer testFile.Close()
   104  
   105  		icmd.RunCmd(icmd.Cmd{
   106  			Command: []string{"chown", "daemon:daemon", "foo"},
   107  			Dir:     tmpDir,
   108  		}).Assert(c, icmd.Success)
   109  
   110  		if err := os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0o644); err != nil {
   111  			c.Fatalf("failed to open destination dockerfile: %v", err)
   112  		}
   113  		return fakecontext.New(c, tmpDir)
   114  	}()
   115  
   116  	defer ctx.Close()
   117  
   118  	buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
   119  }
   120  
   121  // Test that an infinite sleep during a build is killed if the client disconnects.
   122  // This test is fairly hairy because there are lots of ways to race.
   123  // Strategy:
   124  // * Monitor the output of docker events starting from before
   125  // * Run a 1-year-long sleep from a docker build.
   126  // * When docker events sees container start, close the "docker build" command
   127  // * Wait for docker events to emit a dying event.
   128  //
   129  // TODO(buildkit): this test needs to be rewritten for buildkit.
   130  // It has been manually tested positive. Confirmed issue: docker build output parsing.
   131  // Potential issue: newEventObserver uses docker events, which is not hooked up to buildkit.
   132  func (s *DockerCLIBuildSuite) TestBuildCancellationKillsSleep(c *testing.T) {
   133  	testRequires(c, DaemonIsLinux, TODOBuildkit)
   134  	const name = "testbuildcancellation"
   135  
   136  	observer, err := newEventObserver(c)
   137  	assert.NilError(c, err)
   138  	err = observer.Start()
   139  	assert.NilError(c, err)
   140  	defer observer.Stop()
   141  
   142  	// (Note: one year, will never finish)
   143  	ctx := fakecontext.New(c, "", fakecontext.WithDockerfile("FROM busybox\nRUN sleep 31536000"))
   144  	defer ctx.Close()
   145  
   146  	buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".")
   147  	buildCmd.Dir = ctx.Dir
   148  
   149  	stdoutBuild, err := buildCmd.StdoutPipe()
   150  	assert.NilError(c, err)
   151  
   152  	if err := buildCmd.Start(); err != nil {
   153  		c.Fatalf("failed to run build: %s", err)
   154  	}
   155  	// always clean up
   156  	defer func() {
   157  		buildCmd.Process.Kill()
   158  		buildCmd.Wait()
   159  	}()
   160  
   161  	matchCID := regexp.MustCompile("Running in (.+)")
   162  	scanner := bufio.NewScanner(stdoutBuild)
   163  
   164  	outputBuffer := new(bytes.Buffer)
   165  	var buildID string
   166  	for scanner.Scan() {
   167  		line := scanner.Text()
   168  		outputBuffer.WriteString(line)
   169  		outputBuffer.WriteString("\n")
   170  		if matches := matchCID.FindStringSubmatch(line); len(matches) > 0 {
   171  			buildID = matches[1]
   172  			break
   173  		}
   174  	}
   175  
   176  	if buildID == "" {
   177  		c.Fatalf("Unable to find build container id in build output:\n%s", outputBuffer.String())
   178  	}
   179  
   180  	testActions := map[string]chan bool{
   181  		"start": make(chan bool, 1),
   182  		"die":   make(chan bool, 1),
   183  	}
   184  
   185  	matcher := matchEventLine(buildID, "container", testActions)
   186  	processor := processEventMatch(testActions)
   187  	go observer.Match(matcher, processor)
   188  
   189  	select {
   190  	case <-time.After(10 * time.Second):
   191  		observer.CheckEventError(c, buildID, "start", matcher)
   192  	case <-testActions["start"]:
   193  		// ignore, done
   194  	}
   195  
   196  	// Send a kill to the `docker build` command.
   197  	// Causes the underlying build to be cancelled due to socket close.
   198  	if err := buildCmd.Process.Kill(); err != nil {
   199  		c.Fatalf("error killing build command: %s", err)
   200  	}
   201  
   202  	// Get the exit status of `docker build`, check it exited because killed.
   203  	if err := buildCmd.Wait(); err != nil && !isKilled(err) {
   204  		c.Fatalf("wait failed during build run: %T %s", err, err)
   205  	}
   206  
   207  	select {
   208  	case <-time.After(10 * time.Second):
   209  		observer.CheckEventError(c, buildID, "die", matcher)
   210  	case <-testActions["die"]:
   211  		// ignore, done
   212  	}
   213  }
   214  
   215  func isKilled(err error) bool {
   216  	if exitErr, ok := err.(*exec.ExitError); ok {
   217  		status, ok := exitErr.Sys().(syscall.WaitStatus)
   218  		if !ok {
   219  			return false
   220  		}
   221  		// status.ExitStatus() is required on Windows because it does not
   222  		// implement Signal() nor Signaled(). Just check it had a bad exit
   223  		// status could mean it was killed (and in tests we do kill)
   224  		return (status.Signaled() && status.Signal() == os.Kill) || status.ExitStatus() != 0
   225  	}
   226  	return false
   227  }