builder/dockerfile/containerbackend.go
19f3b071
 package dockerfile
 
 import (
 	"fmt"
 	"io"
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/builder"
 	containerpkg "github.com/docker/docker/container"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/pkg/errors"
1009e6a4
 	"github.com/sirupsen/logrus"
19f3b071
 	"golang.org/x/net/context"
 )
 
 type containerManager struct {
 	tmpContainers map[string]struct{}
 	backend       builder.ExecBackend
 }
 
 // newContainerManager creates a new container backend
 func newContainerManager(docker builder.ExecBackend) *containerManager {
 	return &containerManager{
 		backend:       docker,
 		tmpContainers: make(map[string]struct{}),
 	}
 }
 
 // Create a container
08252bc9
 func (c *containerManager) Create(runConfig *container.Config, hostConfig *container.HostConfig, platform string) (container.ContainerCreateCreatedBody, error) {
19f3b071
 	container, err := c.backend.ContainerCreate(types.ContainerCreateConfig{
 		Config:     runConfig,
 		HostConfig: hostConfig,
 	})
 	if err != nil {
 		return container, err
 	}
 	c.tmpContainers[container.ID] = struct{}{}
 	return container, nil
 }
 
 var errCancelled = errors.New("build cancelled")
 
 // Run a container by ID
 func (c *containerManager) Run(ctx context.Context, cID string, stdout, stderr io.Writer) (err error) {
 	attached := make(chan struct{})
 	errCh := make(chan error)
 	go func() {
 		errCh <- c.backend.ContainerAttachRaw(cID, nil, stdout, stderr, true, attached)
 	}()
 	select {
 	case err := <-errCh:
 		return err
 	case <-attached:
 	}
 
 	finished := make(chan struct{})
 	cancelErrCh := make(chan error, 1)
 	go func() {
 		select {
 		case <-ctx.Done():
 			logrus.Debugln("Build cancelled, killing and removing container:", cID)
 			c.backend.ContainerKill(cID, 0)
 			c.removeContainer(cID, stdout)
 			cancelErrCh <- errCancelled
 		case <-finished:
 			cancelErrCh <- nil
 		}
 	}()
 
 	if err := c.backend.ContainerStart(cID, nil, "", ""); err != nil {
 		close(finished)
 		logCancellationError(cancelErrCh, "error from ContainerStart: "+err.Error())
 		return err
 	}
 
 	// Block on reading output from container, stop on err or chan closed
 	if err := <-errCh; err != nil {
 		close(finished)
 		logCancellationError(cancelErrCh, "error from errCh: "+err.Error())
 		return err
 	}
 
 	waitC, err := c.backend.ContainerWait(ctx, cID, containerpkg.WaitConditionNotRunning)
 	if err != nil {
 		close(finished)
 		logCancellationError(cancelErrCh, fmt.Sprintf("unable to begin ContainerWait: %s", err))
 		return err
 	}
 
 	if status := <-waitC; status.ExitCode() != 0 {
 		close(finished)
 		logCancellationError(cancelErrCh,
 			fmt.Sprintf("a non-zero code from ContainerWait: %d", status.ExitCode()))
 		return &statusCodeError{code: status.ExitCode(), err: err}
 	}
 
 	close(finished)
 	return <-cancelErrCh
 }
 
 func logCancellationError(cancelErrCh chan error, msg string) {
 	if cancelErr := <-cancelErrCh; cancelErr != nil {
 		logrus.Debugf("Build cancelled (%v): ", cancelErr, msg)
 	}
 }
 
 type statusCodeError struct {
 	code int
 	err  error
 }
 
 func (e *statusCodeError) Error() string {
 	return e.err.Error()
 }
 
 func (e *statusCodeError) StatusCode() int {
 	return e.code
 }
 
 func (c *containerManager) removeContainer(containerID string, stdout io.Writer) error {
 	rmConfig := &types.ContainerRmConfig{
 		ForceRemove:  true,
 		RemoveVolume: true,
 	}
 	if err := c.backend.ContainerRm(containerID, rmConfig); err != nil {
 		fmt.Fprintf(stdout, "Error removing intermediate container %s: %v\n", stringid.TruncateID(containerID), err)
 		return err
 	}
 	return nil
 }
 
 // RemoveAll containers managed by this container manager
 func (c *containerManager) RemoveAll(stdout io.Writer) {
 	for containerID := range c.tmpContainers {
 		if err := c.removeContainer(containerID, stdout); err != nil {
 			return
 		}
 		delete(c.tmpContainers, containerID)
 		fmt.Fprintf(stdout, "Removing intermediate container %s\n", stringid.TruncateID(containerID))
 	}
 }