github.com/emate/nomad@v0.8.2-wo-binpacking/client/driver/docker.go (about)

     1  package driver
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"log"
     8  	"net"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"syscall"
    17  	"time"
    18  
    19  	"github.com/armon/circbuf"
    20  	docker "github.com/fsouza/go-dockerclient"
    21  
    22  	"github.com/docker/docker/cli/config/configfile"
    23  	"github.com/docker/docker/reference"
    24  	"github.com/docker/docker/registry"
    25  
    26  	"github.com/hashicorp/go-multierror"
    27  	"github.com/hashicorp/go-plugin"
    28  	"github.com/hashicorp/nomad/client/allocdir"
    29  	"github.com/hashicorp/nomad/client/driver/env"
    30  	"github.com/hashicorp/nomad/client/driver/executor"
    31  	dstructs "github.com/hashicorp/nomad/client/driver/structs"
    32  	cstructs "github.com/hashicorp/nomad/client/structs"
    33  	"github.com/hashicorp/nomad/helper"
    34  	"github.com/hashicorp/nomad/helper/fields"
    35  	shelpers "github.com/hashicorp/nomad/helper/stats"
    36  	"github.com/hashicorp/nomad/nomad/structs"
    37  	"github.com/mitchellh/mapstructure"
    38  )
    39  
    40  var (
    41  	// createClientsLock is a lock that protects reading/writing global client
    42  	// variables
    43  	createClientsLock sync.Mutex
    44  
    45  	// client is a docker client with a timeout of 5 minutes. This is for doing
    46  	// all operations with the docker daemon besides which are not long running
    47  	// such as creating, killing containers, etc.
    48  	client *docker.Client
    49  
    50  	// waitClient is a docker client with no timeouts. This is used for long
    51  	// running operations such as waiting on containers and collect stats
    52  	waitClient *docker.Client
    53  
    54  	// healthCheckClient is a docker client with a timeout of 1 minute. This is
    55  	// necessary to have a shorter timeout than other API or fingerprint calls
    56  	healthCheckClient *docker.Client
    57  
    58  	// The statistics the Docker driver exposes
    59  	DockerMeasuredMemStats = []string{"RSS", "Cache", "Swap", "Max Usage"}
    60  	DockerMeasuredCpuStats = []string{"Throttled Periods", "Throttled Time", "Percent"}
    61  
    62  	// recoverableErrTimeouts returns a recoverable error if the error was due
    63  	// to timeouts
    64  	recoverableErrTimeouts = func(err error) error {
    65  		r := false
    66  		if strings.Contains(err.Error(), "Client.Timeout exceeded while awaiting headers") ||
    67  			strings.Contains(err.Error(), "EOF") {
    68  			r = true
    69  		}
    70  		return structs.NewRecoverableError(err, r)
    71  	}
    72  )
    73  
    74  const (
    75  	// NoSuchContainerError is returned by the docker daemon if the container
    76  	// does not exist.
    77  	NoSuchContainerError = "No such container"
    78  
    79  	// The key populated in Node Attributes to indicate presence of the Docker
    80  	// driver
    81  	dockerDriverAttr = "driver.docker"
    82  
    83  	// dockerSELinuxLabelConfigOption is the key for configuring the
    84  	// SELinux label for binds.
    85  	dockerSELinuxLabelConfigOption = "docker.volumes.selinuxlabel"
    86  
    87  	// dockerVolumesConfigOption is the key for enabling the use of custom
    88  	// bind volumes to arbitrary host paths.
    89  	dockerVolumesConfigOption  = "docker.volumes.enabled"
    90  	dockerVolumesConfigDefault = true
    91  
    92  	// dockerPrivilegedConfigOption is the key for running containers in
    93  	// Docker's privileged mode.
    94  	dockerPrivilegedConfigOption = "docker.privileged.enabled"
    95  
    96  	// dockerCleanupImageConfigOption is the key for whether or not to
    97  	// cleanup images after the task exits.
    98  	dockerCleanupImageConfigOption  = "docker.cleanup.image"
    99  	dockerCleanupImageConfigDefault = true
   100  
   101  	// dockerPullTimeoutConfigOption is the key for setting an images pull
   102  	// timeout
   103  	dockerImageRemoveDelayConfigOption  = "docker.cleanup.image.delay"
   104  	dockerImageRemoveDelayConfigDefault = 3 * time.Minute
   105  
   106  	// dockerCapsWhitelistConfigOption is the key for setting the list of
   107  	// allowed Linux capabilities
   108  	dockerCapsWhitelistConfigOption  = "docker.caps.whitelist"
   109  	dockerCapsWhitelistConfigDefault = dockerBasicCaps
   110  
   111  	// dockerTimeout is the length of time a request can be outstanding before
   112  	// it is timed out.
   113  	dockerTimeout = 5 * time.Minute
   114  
   115  	// dockerHealthCheckTimeout is the length of time a request for a health
   116  	// check client can be outstanding before it is timed out.
   117  	dockerHealthCheckTimeout = 1 * time.Minute
   118  
   119  	// dockerImageResKey is the CreatedResources key for docker images
   120  	dockerImageResKey = "image"
   121  
   122  	// dockerAuthHelperPrefix is the prefix to attach to the credential helper
   123  	// and should be found in the $PATH. Example: ${prefix-}${helper-name}
   124  	dockerAuthHelperPrefix = "docker-credential-"
   125  
   126  	// dockerBasicCaps is comma-separated list of Linux capabilities that are
   127  	// allowed by docker by default, as documented in
   128  	// https://docs.docker.com/engine/reference/run/#block-io-bandwidth-blkio-constraint
   129  	dockerBasicCaps = "CHOWN,DAC_OVERRIDE,FSETID,FOWNER,MKNOD,NET_RAW,SETGID," +
   130  		"SETUID,SETFCAP,SETPCAP,NET_BIND_SERVICE,SYS_CHROOT,KILL,AUDIT_WRITE"
   131  
   132  	// This is cpu.cfs_period_us: the length of a period.
   133  	// The default values is 100 milliseconds (ms) represented in microseconds (us).
   134  	// Below is the documentation:
   135  	// https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt
   136  	// https://docs.docker.com/engine/api/v1.35/#
   137  	defaultCFSPeriodUS = 100000
   138  )
   139  
   140  type DockerDriver struct {
   141  	DriverContext
   142  
   143  	driverConfig *DockerDriverConfig
   144  	imageID      string
   145  
   146  	// A tri-state boolean to know if the fingerprinting has happened and
   147  	// whether it has been successful
   148  	fingerprintSuccess *bool
   149  }
   150  
   151  type DockerDriverAuth struct {
   152  	Username      string `mapstructure:"username"`       // username for the registry
   153  	Password      string `mapstructure:"password"`       // password to access the registry
   154  	Email         string `mapstructure:"email"`          // email address of the user who is allowed to access the registry
   155  	ServerAddress string `mapstructure:"server_address"` // server address of the registry
   156  }
   157  
   158  type DockerLoggingOpts struct {
   159  	Type      string              `mapstructure:"type"`
   160  	ConfigRaw []map[string]string `mapstructure:"config"`
   161  	Config    map[string]string   `mapstructure:"-"`
   162  }
   163  
   164  type DockerMount struct {
   165  	Target        string                 `mapstructure:"target"`
   166  	Source        string                 `mapstructure:"source"`
   167  	ReadOnly      bool                   `mapstructure:"readonly"`
   168  	VolumeOptions []*DockerVolumeOptions `mapstructure:"volume_options"`
   169  }
   170  
   171  type DockerDevice struct {
   172  	HostPath          string `mapstructure:"host_path"`
   173  	ContainerPath     string `mapstructure:"container_path"`
   174  	CgroupPermissions string `mapstructure:"cgroup_permissions"`
   175  }
   176  
   177  type DockerVolumeOptions struct {
   178  	NoCopy       bool                       `mapstructure:"no_copy"`
   179  	Labels       []map[string]string        `mapstructure:"labels"`
   180  	DriverConfig []DockerVolumeDriverConfig `mapstructure:"driver_config"`
   181  }
   182  
   183  // VolumeDriverConfig holds a map of volume driver specific options
   184  type DockerVolumeDriverConfig struct {
   185  	Name    string              `mapstructure:"name"`
   186  	Options []map[string]string `mapstructure:"options"`
   187  }
   188  
   189  // DockerDriverConfig defines the user specified config block in a jobspec
   190  type DockerDriverConfig struct {
   191  	ImageName            string              `mapstructure:"image"`                  // Container's Image Name
   192  	LoadImage            string              `mapstructure:"load"`                   // LoadImage is a path to an image archive file
   193  	Command              string              `mapstructure:"command"`                // The Command to run when the container starts up
   194  	Args                 []string            `mapstructure:"args"`                   // The arguments to the Command
   195  	Entrypoint           []string            `mapstructure:"entrypoint"`             // Override the containers entrypoint
   196  	IpcMode              string              `mapstructure:"ipc_mode"`               // The IPC mode of the container - host and none
   197  	NetworkMode          string              `mapstructure:"network_mode"`           // The network mode of the container - host, nat and none
   198  	NetworkAliases       []string            `mapstructure:"network_aliases"`        // The network-scoped alias for the container
   199  	IPv4Address          string              `mapstructure:"ipv4_address"`           // The container ipv4 address
   200  	IPv6Address          string              `mapstructure:"ipv6_address"`           // the container ipv6 address
   201  	PidMode              string              `mapstructure:"pid_mode"`               // The PID mode of the container - host and none
   202  	UTSMode              string              `mapstructure:"uts_mode"`               // The UTS mode of the container - host and none
   203  	UsernsMode           string              `mapstructure:"userns_mode"`            // The User namespace mode of the container - host and none
   204  	PortMapRaw           []map[string]string `mapstructure:"port_map"`               //
   205  	PortMap              map[string]int      `mapstructure:"-"`                      // A map of host port labels and the ports exposed on the container
   206  	Privileged           bool                `mapstructure:"privileged"`             // Flag to run the container in privileged mode
   207  	SysctlRaw            []map[string]string `mapstructure:"sysctl"`                 //
   208  	Sysctl               map[string]string   `mapstructure:"-"`                      // The sysctl custom configurations
   209  	UlimitRaw            []map[string]string `mapstructure:"ulimit"`                 //
   210  	Ulimit               []docker.ULimit     `mapstructure:"-"`                      // The ulimit custom configurations
   211  	DNSServers           []string            `mapstructure:"dns_servers"`            // DNS Server for containers
   212  	DNSSearchDomains     []string            `mapstructure:"dns_search_domains"`     // DNS Search domains for containers
   213  	DNSOptions           []string            `mapstructure:"dns_options"`            // DNS Options
   214  	ExtraHosts           []string            `mapstructure:"extra_hosts"`            // Add host to /etc/hosts (host:IP)
   215  	Hostname             string              `mapstructure:"hostname"`               // Hostname for containers
   216  	LabelsRaw            []map[string]string `mapstructure:"labels"`                 //
   217  	Labels               map[string]string   `mapstructure:"-"`                      // Labels to set when the container starts up
   218  	Auth                 []DockerDriverAuth  `mapstructure:"auth"`                   // Authentication credentials for a private Docker registry
   219  	AuthSoftFail         bool                `mapstructure:"auth_soft_fail"`         // Soft-fail if auth creds are provided but fail
   220  	TTY                  bool                `mapstructure:"tty"`                    // Allocate a Pseudo-TTY
   221  	Interactive          bool                `mapstructure:"interactive"`            // Keep STDIN open even if not attached
   222  	ShmSize              int64               `mapstructure:"shm_size"`               // Size of /dev/shm of the container in bytes
   223  	WorkDir              string              `mapstructure:"work_dir"`               // Working directory inside the container
   224  	Logging              []DockerLoggingOpts `mapstructure:"logging"`                // Logging options for syslog server
   225  	Volumes              []string            `mapstructure:"volumes"`                // Host-Volumes to mount in, syntax: /path/to/host/directory:/destination/path/in/container
   226  	Mounts               []DockerMount       `mapstructure:"mounts"`                 // Docker volumes to mount
   227  	VolumeDriver         string              `mapstructure:"volume_driver"`          // Docker volume driver used for the container's volumes
   228  	ForcePull            bool                `mapstructure:"force_pull"`             // Always force pull before running image, useful if your tags are mutable
   229  	MacAddress           string              `mapstructure:"mac_address"`            // Pin mac address to container
   230  	SecurityOpt          []string            `mapstructure:"security_opt"`           // Flags to pass directly to security-opt
   231  	Devices              []DockerDevice      `mapstructure:"devices"`                // To allow mounting USB or other serial control devices
   232  	CapAdd               []string            `mapstructure:"cap_add"`                // Flags to pass directly to cap-add
   233  	CapDrop              []string            `mapstructure:"cap_drop"`               // Flags to pass directly to cap-drop
   234  	ReadonlyRootfs       bool                `mapstructure:"readonly_rootfs"`        // Mount the container’s root filesystem as read only
   235  	AdvertiseIPv6Address bool                `mapstructure:"advertise_ipv6_address"` // Flag to use the GlobalIPv6Address from the container as the detected IP
   236  	CPUHardLimit         bool                `mapstructure:"cpu_hard_limit"`         // Enforce CPU hard limit.
   237  }
   238  
   239  func sliceMergeUlimit(ulimitsRaw map[string]string) ([]docker.ULimit, error) {
   240  	var ulimits []docker.ULimit
   241  
   242  	for name, ulimitRaw := range ulimitsRaw {
   243  		if len(ulimitRaw) == 0 {
   244  			return []docker.ULimit{}, fmt.Errorf("Malformed ulimit specification %v: %q, cannot be empty", name, ulimitRaw)
   245  		}
   246  		// hard limit is optional
   247  		if strings.Contains(ulimitRaw, ":") == false {
   248  			ulimitRaw = ulimitRaw + ":" + ulimitRaw
   249  		}
   250  
   251  		splitted := strings.SplitN(ulimitRaw, ":", 2)
   252  		if len(splitted) < 2 {
   253  			return []docker.ULimit{}, fmt.Errorf("Malformed ulimit specification %v: %v", name, ulimitRaw)
   254  		}
   255  		soft, err := strconv.Atoi(splitted[0])
   256  		if err != nil {
   257  			return []docker.ULimit{}, fmt.Errorf("Malformed soft ulimit %v: %v", name, ulimitRaw)
   258  		}
   259  		hard, err := strconv.Atoi(splitted[1])
   260  		if err != nil {
   261  			return []docker.ULimit{}, fmt.Errorf("Malformed hard ulimit %v: %v", name, ulimitRaw)
   262  		}
   263  
   264  		ulimit := docker.ULimit{
   265  			Name: name,
   266  			Soft: int64(soft),
   267  			Hard: int64(hard),
   268  		}
   269  		ulimits = append(ulimits, ulimit)
   270  	}
   271  	return ulimits, nil
   272  }
   273  
   274  // Validate validates a docker driver config
   275  func (c *DockerDriverConfig) Validate() error {
   276  	if c.ImageName == "" {
   277  		return fmt.Errorf("Docker Driver needs an image name")
   278  	}
   279  	if len(c.Devices) > 0 {
   280  		for _, dev := range c.Devices {
   281  			if dev.HostPath == "" {
   282  				return fmt.Errorf("host path must be set in configuration for devices")
   283  			}
   284  			if dev.CgroupPermissions != "" {
   285  				for _, c := range dev.CgroupPermissions {
   286  					ch := string(c)
   287  					if ch != "r" && ch != "w" && ch != "m" {
   288  						return fmt.Errorf("invalid cgroup permission string: %q", dev.CgroupPermissions)
   289  					}
   290  				}
   291  			}
   292  		}
   293  	}
   294  	c.Sysctl = mapMergeStrStr(c.SysctlRaw...)
   295  	c.Labels = mapMergeStrStr(c.LabelsRaw...)
   296  	if len(c.Logging) > 0 {
   297  		c.Logging[0].Config = mapMergeStrStr(c.Logging[0].ConfigRaw...)
   298  	}
   299  
   300  	mergedUlimitsRaw := mapMergeStrStr(c.UlimitRaw...)
   301  	ulimit, err := sliceMergeUlimit(mergedUlimitsRaw)
   302  	if err != nil {
   303  		return err
   304  	}
   305  	c.Ulimit = ulimit
   306  	return nil
   307  }
   308  
   309  // NewDockerDriverConfig returns a docker driver config by parsing the HCL
   310  // config
   311  func NewDockerDriverConfig(task *structs.Task, env *env.TaskEnv) (*DockerDriverConfig, error) {
   312  	var dconf DockerDriverConfig
   313  
   314  	if err := mapstructure.WeakDecode(task.Config, &dconf); err != nil {
   315  		return nil, err
   316  	}
   317  
   318  	// Interpolate everything that is a string
   319  	dconf.ImageName = env.ReplaceEnv(dconf.ImageName)
   320  	dconf.Command = env.ReplaceEnv(dconf.Command)
   321  	dconf.Entrypoint = env.ParseAndReplace(dconf.Entrypoint)
   322  	dconf.IpcMode = env.ReplaceEnv(dconf.IpcMode)
   323  	dconf.NetworkMode = env.ReplaceEnv(dconf.NetworkMode)
   324  	dconf.NetworkAliases = env.ParseAndReplace(dconf.NetworkAliases)
   325  	dconf.IPv4Address = env.ReplaceEnv(dconf.IPv4Address)
   326  	dconf.IPv6Address = env.ReplaceEnv(dconf.IPv6Address)
   327  	dconf.PidMode = env.ReplaceEnv(dconf.PidMode)
   328  	dconf.UTSMode = env.ReplaceEnv(dconf.UTSMode)
   329  	dconf.Hostname = env.ReplaceEnv(dconf.Hostname)
   330  	dconf.WorkDir = env.ReplaceEnv(dconf.WorkDir)
   331  	dconf.LoadImage = env.ReplaceEnv(dconf.LoadImage)
   332  	dconf.Volumes = env.ParseAndReplace(dconf.Volumes)
   333  	dconf.VolumeDriver = env.ReplaceEnv(dconf.VolumeDriver)
   334  	dconf.DNSServers = env.ParseAndReplace(dconf.DNSServers)
   335  	dconf.DNSSearchDomains = env.ParseAndReplace(dconf.DNSSearchDomains)
   336  	dconf.DNSOptions = env.ParseAndReplace(dconf.DNSOptions)
   337  	dconf.ExtraHosts = env.ParseAndReplace(dconf.ExtraHosts)
   338  	dconf.MacAddress = env.ReplaceEnv(dconf.MacAddress)
   339  	dconf.SecurityOpt = env.ParseAndReplace(dconf.SecurityOpt)
   340  	dconf.CapAdd = env.ParseAndReplace(dconf.CapAdd)
   341  	dconf.CapDrop = env.ParseAndReplace(dconf.CapDrop)
   342  
   343  	for _, m := range dconf.SysctlRaw {
   344  		for k, v := range m {
   345  			delete(m, k)
   346  			m[env.ReplaceEnv(k)] = env.ReplaceEnv(v)
   347  		}
   348  	}
   349  
   350  	for _, m := range dconf.UlimitRaw {
   351  		for k, v := range m {
   352  			delete(m, k)
   353  			m[env.ReplaceEnv(k)] = env.ReplaceEnv(v)
   354  		}
   355  	}
   356  
   357  	for _, m := range dconf.LabelsRaw {
   358  		for k, v := range m {
   359  			delete(m, k)
   360  			m[env.ReplaceEnv(k)] = env.ReplaceEnv(v)
   361  		}
   362  	}
   363  	dconf.Labels = mapMergeStrStr(dconf.LabelsRaw...)
   364  
   365  	for i, a := range dconf.Auth {
   366  		dconf.Auth[i].Username = env.ReplaceEnv(a.Username)
   367  		dconf.Auth[i].Password = env.ReplaceEnv(a.Password)
   368  		dconf.Auth[i].Email = env.ReplaceEnv(a.Email)
   369  		dconf.Auth[i].ServerAddress = env.ReplaceEnv(a.ServerAddress)
   370  	}
   371  
   372  	for i, l := range dconf.Logging {
   373  		dconf.Logging[i].Type = env.ReplaceEnv(l.Type)
   374  		for _, c := range l.ConfigRaw {
   375  			for k, v := range c {
   376  				delete(c, k)
   377  				c[env.ReplaceEnv(k)] = env.ReplaceEnv(v)
   378  			}
   379  		}
   380  	}
   381  
   382  	for i, m := range dconf.Mounts {
   383  		dconf.Mounts[i].Target = env.ReplaceEnv(m.Target)
   384  		dconf.Mounts[i].Source = env.ReplaceEnv(m.Source)
   385  
   386  		if len(m.VolumeOptions) > 1 {
   387  			return nil, fmt.Errorf("Only one volume_options stanza allowed")
   388  		}
   389  
   390  		if len(m.VolumeOptions) == 1 {
   391  			vo := m.VolumeOptions[0]
   392  			if len(vo.Labels) > 1 {
   393  				return nil, fmt.Errorf("labels may only be specified once in volume_options stanza")
   394  			}
   395  
   396  			if len(vo.Labels) == 1 {
   397  				for k, v := range vo.Labels[0] {
   398  					if k != env.ReplaceEnv(k) {
   399  						delete(vo.Labels[0], k)
   400  					}
   401  					vo.Labels[0][env.ReplaceEnv(k)] = env.ReplaceEnv(v)
   402  				}
   403  			}
   404  
   405  			if len(vo.DriverConfig) > 1 {
   406  				return nil, fmt.Errorf("volume driver config may only be specified once")
   407  			}
   408  			if len(vo.DriverConfig) == 1 {
   409  				vo.DriverConfig[0].Name = env.ReplaceEnv(vo.DriverConfig[0].Name)
   410  				if len(vo.DriverConfig[0].Options) > 1 {
   411  					return nil, fmt.Errorf("volume driver options may only be specified once")
   412  				}
   413  
   414  				if len(vo.DriverConfig[0].Options) == 1 {
   415  					options := vo.DriverConfig[0].Options[0]
   416  					for k, v := range options {
   417  						if k != env.ReplaceEnv(k) {
   418  							delete(options, k)
   419  						}
   420  						options[env.ReplaceEnv(k)] = env.ReplaceEnv(v)
   421  					}
   422  				}
   423  			}
   424  		}
   425  	}
   426  
   427  	if len(dconf.Logging) > 0 {
   428  		dconf.Logging[0].Config = mapMergeStrStr(dconf.Logging[0].ConfigRaw...)
   429  	}
   430  
   431  	portMap := make(map[string]int)
   432  	for _, m := range dconf.PortMapRaw {
   433  		for k, v := range m {
   434  			ki, vi := env.ReplaceEnv(k), env.ReplaceEnv(v)
   435  			p, err := strconv.Atoi(vi)
   436  			if err != nil {
   437  				return nil, fmt.Errorf("failed to parse port map value %v to %v: %v", ki, vi, err)
   438  			}
   439  			portMap[ki] = p
   440  		}
   441  	}
   442  	dconf.PortMap = portMap
   443  
   444  	// Remove any http
   445  	if strings.Contains(dconf.ImageName, "https://") {
   446  		dconf.ImageName = strings.Replace(dconf.ImageName, "https://", "", 1)
   447  	}
   448  
   449  	// If devices are configured set default cgroup permissions
   450  	if len(dconf.Devices) > 0 {
   451  		for i, dev := range dconf.Devices {
   452  			if dev.CgroupPermissions == "" {
   453  				dev.CgroupPermissions = "rwm"
   454  			}
   455  			dconf.Devices[i] = dev
   456  		}
   457  	}
   458  
   459  	if err := dconf.Validate(); err != nil {
   460  		return nil, err
   461  	}
   462  	return &dconf, nil
   463  }
   464  
   465  type dockerPID struct {
   466  	Version        string
   467  	Image          string
   468  	ImageID        string
   469  	ContainerID    string
   470  	KillTimeout    time.Duration
   471  	MaxKillTimeout time.Duration
   472  	PluginConfig   *PluginReattachConfig
   473  }
   474  
   475  type DockerHandle struct {
   476  	pluginClient      *plugin.Client
   477  	executor          executor.Executor
   478  	client            *docker.Client
   479  	waitClient        *docker.Client
   480  	logger            *log.Logger
   481  	Image             string
   482  	ImageID           string
   483  	containerID       string
   484  	version           string
   485  	killTimeout       time.Duration
   486  	maxKillTimeout    time.Duration
   487  	resourceUsageLock sync.RWMutex
   488  	resourceUsage     *cstructs.TaskResourceUsage
   489  	waitCh            chan *dstructs.WaitResult
   490  	doneCh            chan bool
   491  }
   492  
   493  func NewDockerDriver(ctx *DriverContext) Driver {
   494  	return &DockerDriver{DriverContext: *ctx}
   495  }
   496  
   497  func (d *DockerDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error {
   498  	client, _, err := d.dockerClients()
   499  	if err != nil {
   500  		if d.fingerprintSuccess == nil || *d.fingerprintSuccess {
   501  			d.logger.Printf("[INFO] driver.docker: failed to initialize client: %s", err)
   502  		}
   503  		d.fingerprintSuccess = helper.BoolToPtr(false)
   504  		return nil
   505  	}
   506  
   507  	// This is the first operation taken on the client so we'll try to
   508  	// establish a connection to the Docker daemon. If this fails it means
   509  	// Docker isn't available so we'll simply disable the docker driver.
   510  	env, err := client.Version()
   511  	if err != nil {
   512  		if d.fingerprintSuccess == nil || *d.fingerprintSuccess {
   513  			d.logger.Printf("[DEBUG] driver.docker: could not connect to docker daemon at %s: %s", client.Endpoint(), err)
   514  		}
   515  		d.fingerprintSuccess = helper.BoolToPtr(false)
   516  		resp.RemoveAttribute(dockerDriverAttr)
   517  		return nil
   518  	}
   519  
   520  	resp.AddAttribute(dockerDriverAttr, "1")
   521  	resp.AddAttribute("driver.docker.version", env.Get("Version"))
   522  	resp.Detected = true
   523  
   524  	privileged := d.config.ReadBoolDefault(dockerPrivilegedConfigOption, false)
   525  	if privileged {
   526  		resp.AddAttribute(dockerPrivilegedConfigOption, "1")
   527  	}
   528  
   529  	// Advertise if this node supports Docker volumes
   530  	if d.config.ReadBoolDefault(dockerVolumesConfigOption, dockerVolumesConfigDefault) {
   531  		resp.AddAttribute("driver."+dockerVolumesConfigOption, "1")
   532  	}
   533  
   534  	// Detect bridge IP address - #2785
   535  	if nets, err := client.ListNetworks(); err != nil {
   536  		d.logger.Printf("[WARN] driver.docker: error discovering bridge IP: %v", err)
   537  	} else {
   538  		for _, n := range nets {
   539  			if n.Name != "bridge" {
   540  				continue
   541  			}
   542  
   543  			if len(n.IPAM.Config) == 0 {
   544  				d.logger.Printf("[WARN] driver.docker: no IPAM config for bridge network")
   545  				break
   546  			}
   547  
   548  			if n.IPAM.Config[0].Gateway != "" {
   549  				resp.AddAttribute("driver.docker.bridge_ip", n.IPAM.Config[0].Gateway)
   550  			} else if d.fingerprintSuccess == nil {
   551  				// Docker 17.09.0-ce dropped the Gateway IP from the bridge network
   552  				// See https://github.com/moby/moby/issues/32648
   553  				d.logger.Printf("[DEBUG] driver.docker: bridge_ip could not be discovered")
   554  			}
   555  			break
   556  		}
   557  	}
   558  
   559  	d.fingerprintSuccess = helper.BoolToPtr(true)
   560  	return nil
   561  }
   562  
   563  // HealthCheck implements the interface for the HealthCheck interface. This
   564  // performs a health check on the docker driver, asserting whether the docker
   565  // driver is responsive to a `docker ps` command.
   566  func (d *DockerDriver) HealthCheck(req *cstructs.HealthCheckRequest, resp *cstructs.HealthCheckResponse) error {
   567  	dinfo := &structs.DriverInfo{
   568  		UpdateTime: time.Now(),
   569  	}
   570  
   571  	healthCheckClient, err := d.dockerHealthCheckClient()
   572  	if err != nil {
   573  		d.logger.Printf("[WARN] driver.docker: failed to retrieve Docker client in the process of a docker health check: %v", err)
   574  		dinfo.HealthDescription = fmt.Sprintf("Failed retrieving Docker client: %v", err)
   575  		resp.AddDriverInfo("docker", dinfo)
   576  		return nil
   577  	}
   578  
   579  	_, err = healthCheckClient.ListContainers(docker.ListContainersOptions{All: false})
   580  	if err != nil {
   581  		d.logger.Printf("[WARN] driver.docker: failed to list Docker containers in the process of a Docker health check: %v", err)
   582  		dinfo.HealthDescription = fmt.Sprintf("Failed to list Docker containers: %v", err)
   583  		resp.AddDriverInfo("docker", dinfo)
   584  		return nil
   585  	}
   586  
   587  	d.logger.Printf("[TRACE] driver.docker: docker driver is available and is responsive to `docker ps`")
   588  	dinfo.Healthy = true
   589  	dinfo.HealthDescription = "Driver is available and responsive"
   590  	resp.AddDriverInfo("docker", dinfo)
   591  	return nil
   592  }
   593  
   594  // GetHealthChecks implements the interface for the HealthCheck interface. This
   595  // sets whether the driver is eligible for periodic health checks and the
   596  // interval at which to do them.
   597  func (d *DockerDriver) GetHealthCheckInterval(req *cstructs.HealthCheckIntervalRequest, resp *cstructs.HealthCheckIntervalResponse) error {
   598  	resp.Eligible = true
   599  	resp.Period = 1 * time.Minute
   600  	return nil
   601  }
   602  
   603  // Validate is used to validate the driver configuration
   604  func (d *DockerDriver) Validate(config map[string]interface{}) error {
   605  	fd := &fields.FieldData{
   606  		Raw: config,
   607  		Schema: map[string]*fields.FieldSchema{
   608  			"image": {
   609  				Type:     fields.TypeString,
   610  				Required: true,
   611  			},
   612  			"load": {
   613  				Type: fields.TypeString,
   614  			},
   615  			"command": {
   616  				Type: fields.TypeString,
   617  			},
   618  			"args": {
   619  				Type: fields.TypeArray,
   620  			},
   621  			"entrypoint": {
   622  				Type: fields.TypeArray,
   623  			},
   624  			"ipc_mode": {
   625  				Type: fields.TypeString,
   626  			},
   627  			"network_mode": {
   628  				Type: fields.TypeString,
   629  			},
   630  			"network_aliases": {
   631  				Type: fields.TypeArray,
   632  			},
   633  			"ipv4_address": {
   634  				Type: fields.TypeString,
   635  			},
   636  			"ipv6_address": {
   637  				Type: fields.TypeString,
   638  			},
   639  			"mac_address": {
   640  				Type: fields.TypeString,
   641  			},
   642  			"pid_mode": {
   643  				Type: fields.TypeString,
   644  			},
   645  			"uts_mode": {
   646  				Type: fields.TypeString,
   647  			},
   648  			"userns_mode": {
   649  				Type: fields.TypeString,
   650  			},
   651  			"sysctl": {
   652  				Type: fields.TypeArray,
   653  			},
   654  			"ulimit": {
   655  				Type: fields.TypeArray,
   656  			},
   657  			"port_map": {
   658  				Type: fields.TypeArray,
   659  			},
   660  			"privileged": {
   661  				Type: fields.TypeBool,
   662  			},
   663  			"dns_servers": {
   664  				Type: fields.TypeArray,
   665  			},
   666  			"dns_options": {
   667  				Type: fields.TypeArray,
   668  			},
   669  			"dns_search_domains": {
   670  				Type: fields.TypeArray,
   671  			},
   672  			"extra_hosts": {
   673  				Type: fields.TypeArray,
   674  			},
   675  			"hostname": {
   676  				Type: fields.TypeString,
   677  			},
   678  			"labels": {
   679  				Type: fields.TypeArray,
   680  			},
   681  			"auth": {
   682  				Type: fields.TypeArray,
   683  			},
   684  			"auth_soft_fail": {
   685  				Type: fields.TypeBool,
   686  			},
   687  			// COMPAT: Remove in 0.6.0. SSL is no longer needed
   688  			"ssl": {
   689  				Type: fields.TypeBool,
   690  			},
   691  			"tty": {
   692  				Type: fields.TypeBool,
   693  			},
   694  			"interactive": {
   695  				Type: fields.TypeBool,
   696  			},
   697  			"shm_size": {
   698  				Type: fields.TypeInt,
   699  			},
   700  			"work_dir": {
   701  				Type: fields.TypeString,
   702  			},
   703  			"logging": {
   704  				Type: fields.TypeArray,
   705  			},
   706  			"volumes": {
   707  				Type: fields.TypeArray,
   708  			},
   709  			"volume_driver": {
   710  				Type: fields.TypeString,
   711  			},
   712  			"mounts": {
   713  				Type: fields.TypeArray,
   714  			},
   715  			"force_pull": {
   716  				Type: fields.TypeBool,
   717  			},
   718  			"security_opt": {
   719  				Type: fields.TypeArray,
   720  			},
   721  			"devices": {
   722  				Type: fields.TypeArray,
   723  			},
   724  			"cap_add": {
   725  				Type: fields.TypeArray,
   726  			},
   727  			"cap_drop": {
   728  				Type: fields.TypeArray,
   729  			},
   730  			"readonly_rootfs": {
   731  				Type: fields.TypeBool,
   732  			},
   733  			"advertise_ipv6_address": {
   734  				Type: fields.TypeBool,
   735  			},
   736  			"cpu_hard_limit": {
   737  				Type: fields.TypeBool,
   738  			},
   739  		},
   740  	}
   741  
   742  	if err := fd.Validate(); err != nil {
   743  		return err
   744  	}
   745  
   746  	return nil
   747  }
   748  
   749  func (d *DockerDriver) Abilities() DriverAbilities {
   750  	return DriverAbilities{
   751  		SendSignals: true,
   752  		Exec:        true,
   753  	}
   754  }
   755  
   756  func (d *DockerDriver) FSIsolation() cstructs.FSIsolation {
   757  	return cstructs.FSIsolationImage
   758  }
   759  
   760  // getDockerCoordinator returns the docker coordinator and the caller ID to use when
   761  // interacting with the coordinator
   762  func (d *DockerDriver) getDockerCoordinator(client *docker.Client) (*dockerCoordinator, string) {
   763  	config := &dockerCoordinatorConfig{
   764  		client:      client,
   765  		cleanup:     d.config.ReadBoolDefault(dockerCleanupImageConfigOption, dockerCleanupImageConfigDefault),
   766  		logger:      d.logger,
   767  		removeDelay: d.config.ReadDurationDefault(dockerImageRemoveDelayConfigOption, dockerImageRemoveDelayConfigDefault),
   768  	}
   769  
   770  	return GetDockerCoordinator(config), fmt.Sprintf("%s-%s", d.DriverContext.allocID, d.DriverContext.taskName)
   771  }
   772  
   773  func (d *DockerDriver) Prestart(ctx *ExecContext, task *structs.Task) (*PrestartResponse, error) {
   774  	driverConfig, err := NewDockerDriverConfig(task, ctx.TaskEnv)
   775  	if err != nil {
   776  		return nil, err
   777  	}
   778  
   779  	// Set state needed by Start
   780  	d.driverConfig = driverConfig
   781  
   782  	// Initialize docker API clients
   783  	client, _, err := d.dockerClients()
   784  	if err != nil {
   785  		return nil, fmt.Errorf("Failed to connect to docker daemon: %s", err)
   786  	}
   787  
   788  	// Ensure the image is available
   789  	id, err := d.createImage(driverConfig, client, ctx.TaskDir)
   790  	if err != nil {
   791  		return nil, err
   792  	}
   793  	d.imageID = id
   794  
   795  	resp := NewPrestartResponse()
   796  	resp.CreatedResources.Add(dockerImageResKey, id)
   797  
   798  	// Return the PortMap if it's set
   799  	if len(driverConfig.PortMap) > 0 {
   800  		resp.Network = &cstructs.DriverNetwork{
   801  			PortMap: driverConfig.PortMap,
   802  		}
   803  	}
   804  	return resp, nil
   805  }
   806  
   807  func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) {
   808  	pluginLogFile := filepath.Join(ctx.TaskDir.Dir, "executor.out")
   809  	executorConfig := &dstructs.ExecutorConfig{
   810  		LogFile:  pluginLogFile,
   811  		LogLevel: d.config.LogLevel,
   812  	}
   813  
   814  	exec, pluginClient, err := createExecutor(d.config.LogOutput, d.config, executorConfig)
   815  	if err != nil {
   816  		return nil, err
   817  	}
   818  	executorCtx := &executor.ExecutorContext{
   819  		TaskEnv:        ctx.TaskEnv,
   820  		Task:           task,
   821  		Driver:         "docker",
   822  		LogDir:         ctx.TaskDir.LogDir,
   823  		TaskDir:        ctx.TaskDir.Dir,
   824  		PortLowerBound: d.config.ClientMinPort,
   825  		PortUpperBound: d.config.ClientMaxPort,
   826  	}
   827  	if err := exec.SetContext(executorCtx); err != nil {
   828  		pluginClient.Kill()
   829  		return nil, fmt.Errorf("failed to set executor context: %v", err)
   830  	}
   831  
   832  	// The user hasn't specified any logging options so launch our own syslog
   833  	// server if possible.
   834  	syslogAddr := ""
   835  	if len(d.driverConfig.Logging) == 0 {
   836  		if runtime.GOOS == "darwin" {
   837  			d.logger.Printf("[DEBUG] driver.docker: disabling syslog driver as Docker for Mac workaround")
   838  		} else {
   839  			ss, err := exec.LaunchSyslogServer()
   840  			if err != nil {
   841  				pluginClient.Kill()
   842  				return nil, fmt.Errorf("failed to start syslog collector: %v", err)
   843  			}
   844  			syslogAddr = ss.Addr
   845  		}
   846  	}
   847  
   848  	config, err := d.createContainerConfig(ctx, task, d.driverConfig, syslogAddr)
   849  	if err != nil {
   850  		d.logger.Printf("[ERR] driver.docker: failed to create container configuration for image %q (%q): %v", d.driverConfig.ImageName, d.imageID, err)
   851  		pluginClient.Kill()
   852  		return nil, fmt.Errorf("Failed to create container configuration for image %q (%q): %v", d.driverConfig.ImageName, d.imageID, err)
   853  	}
   854  
   855  	container, err := d.createContainer(client, config)
   856  	if err != nil {
   857  		wrapped := fmt.Sprintf("Failed to create container: %v", err)
   858  		d.logger.Printf("[ERR] driver.docker: %s", wrapped)
   859  		pluginClient.Kill()
   860  		return nil, structs.WrapRecoverable(wrapped, err)
   861  	}
   862  
   863  	d.logger.Printf("[INFO] driver.docker: created container %s", container.ID)
   864  
   865  	// We don't need to start the container if the container is already running
   866  	// since we don't create containers which are already present on the host
   867  	// and are running
   868  	if !container.State.Running {
   869  		// Start the container
   870  		if err := d.startContainer(container); err != nil {
   871  			d.logger.Printf("[ERR] driver.docker: failed to start container %s: %s", container.ID, err)
   872  			pluginClient.Kill()
   873  			return nil, structs.NewRecoverableError(fmt.Errorf("Failed to start container %s: %s", container.ID, err), structs.IsRecoverable(err))
   874  		}
   875  
   876  		// InspectContainer to get all of the container metadata as
   877  		// much of the metadata (eg networking) isn't populated until
   878  		// the container is started
   879  		runningContainer, err := client.InspectContainer(container.ID)
   880  		if err != nil {
   881  			err = fmt.Errorf("failed to inspect started container %s: %s", container.ID, err)
   882  			d.logger.Printf("[ERR] driver.docker: %v", err)
   883  			pluginClient.Kill()
   884  			return nil, structs.NewRecoverableError(err, true)
   885  		}
   886  		container = runningContainer
   887  		d.logger.Printf("[INFO] driver.docker: started container %s", container.ID)
   888  	} else {
   889  		d.logger.Printf("[DEBUG] driver.docker: re-attaching to container %s with status %q",
   890  			container.ID, container.State.String())
   891  	}
   892  
   893  	// Return a driver handle
   894  	maxKill := d.DriverContext.config.MaxKillTimeout
   895  	h := &DockerHandle{
   896  		client:         client,
   897  		waitClient:     waitClient,
   898  		executor:       exec,
   899  		pluginClient:   pluginClient,
   900  		logger:         d.logger,
   901  		Image:          d.driverConfig.ImageName,
   902  		ImageID:        d.imageID,
   903  		containerID:    container.ID,
   904  		version:        d.config.Version.VersionNumber(),
   905  		killTimeout:    GetKillTimeout(task.KillTimeout, maxKill),
   906  		maxKillTimeout: maxKill,
   907  		doneCh:         make(chan bool),
   908  		waitCh:         make(chan *dstructs.WaitResult, 1),
   909  	}
   910  	go h.collectStats()
   911  	go h.run()
   912  
   913  	// Detect container address
   914  	ip, autoUse := d.detectIP(container)
   915  
   916  	// Create a response with the driver handle and container network metadata
   917  	resp := &StartResponse{
   918  		Handle: h,
   919  		Network: &cstructs.DriverNetwork{
   920  			PortMap:       d.driverConfig.PortMap,
   921  			IP:            ip,
   922  			AutoAdvertise: autoUse,
   923  		},
   924  	}
   925  	return resp, nil
   926  }
   927  
   928  // detectIP of Docker container. Returns the first IP found as well as true if
   929  // the IP should be advertised (bridge network IPs return false). Returns an
   930  // empty string and false if no IP could be found.
   931  func (d *DockerDriver) detectIP(c *docker.Container) (string, bool) {
   932  	if c.NetworkSettings == nil {
   933  		// This should only happen if there's been a coding error (such
   934  		// as not calling InspectContainer after CreateContainer). Code
   935  		// defensively in case the Docker API changes subtly.
   936  		d.logger.Printf("[ERROR] driver.docker: no network settings for container %s", c.ID)
   937  		return "", false
   938  	}
   939  
   940  	ip, ipName := "", ""
   941  	auto := false
   942  	for name, net := range c.NetworkSettings.Networks {
   943  		if net.IPAddress == "" {
   944  			// Ignore networks without an IP address
   945  			continue
   946  		}
   947  
   948  		ip = net.IPAddress
   949  		if d.driverConfig.AdvertiseIPv6Address {
   950  			ip = net.GlobalIPv6Address
   951  			auto = true
   952  		}
   953  		ipName = name
   954  
   955  		// Don't auto-advertise IPs for default networks (bridge on
   956  		// Linux, nat on Windows)
   957  		if name != "bridge" && name != "nat" {
   958  			auto = true
   959  		}
   960  
   961  		break
   962  	}
   963  
   964  	if n := len(c.NetworkSettings.Networks); n > 1 {
   965  		d.logger.Printf("[WARN] driver.docker: task %s multiple (%d) Docker networks for container %q but Nomad only supports 1: choosing %q", d.taskName, n, c.ID, ipName)
   966  	}
   967  
   968  	return ip, auto
   969  }
   970  
   971  func (d *DockerDriver) Cleanup(_ *ExecContext, res *CreatedResources) error {
   972  	retry := false
   973  	var merr multierror.Error
   974  	for key, resources := range res.Resources {
   975  		switch key {
   976  		case dockerImageResKey:
   977  			for _, value := range resources {
   978  				err := d.cleanupImage(value)
   979  				if err != nil {
   980  					if structs.IsRecoverable(err) {
   981  						retry = true
   982  					}
   983  					merr.Errors = append(merr.Errors, err)
   984  					continue
   985  				}
   986  
   987  				// Remove cleaned image from resources
   988  				res.Remove(dockerImageResKey, value)
   989  			}
   990  		default:
   991  			d.logger.Printf("[ERR] driver.docker: unknown resource to cleanup: %q", key)
   992  		}
   993  	}
   994  	return structs.NewRecoverableError(merr.ErrorOrNil(), retry)
   995  }
   996  
   997  // cleanupImage removes a Docker image. No error is returned if the image
   998  // doesn't exist or is still in use. Requires the global client to already be
   999  // initialized.
  1000  func (d *DockerDriver) cleanupImage(imageID string) error {
  1001  	if !d.config.ReadBoolDefault(dockerCleanupImageConfigOption, dockerCleanupImageConfigDefault) {
  1002  		// Config says not to cleanup
  1003  		return nil
  1004  	}
  1005  
  1006  	coordinator, callerID := d.getDockerCoordinator(client)
  1007  	coordinator.RemoveImage(imageID, callerID)
  1008  
  1009  	return nil
  1010  }
  1011  
  1012  // dockerHealthCheckClient creates a single *docker.Client with a timeout of
  1013  // one minute, which will be used when performing Docker health checks.
  1014  func (d *DockerDriver) dockerHealthCheckClient() (*docker.Client, error) {
  1015  	createClientsLock.Lock()
  1016  	defer createClientsLock.Unlock()
  1017  
  1018  	if healthCheckClient != nil {
  1019  		return healthCheckClient, nil
  1020  	}
  1021  
  1022  	var err error
  1023  	healthCheckClient, err = d.newDockerClient(dockerHealthCheckTimeout)
  1024  	if err != nil {
  1025  		return nil, err
  1026  	}
  1027  
  1028  	return healthCheckClient, nil
  1029  }
  1030  
  1031  // dockerClients creates two *docker.Client, one for long running operations and
  1032  // the other for shorter operations. In test / dev mode we can use ENV vars to
  1033  // connect to the docker daemon. In production mode we will read docker.endpoint
  1034  // from the config file.
  1035  func (d *DockerDriver) dockerClients() (*docker.Client, *docker.Client, error) {
  1036  	createClientsLock.Lock()
  1037  	defer createClientsLock.Unlock()
  1038  
  1039  	if client != nil && waitClient != nil {
  1040  		return client, waitClient, nil
  1041  	}
  1042  
  1043  	var err error
  1044  
  1045  	// Onlt initialize the client if it hasn't yet been done
  1046  	if client == nil {
  1047  		client, err = d.newDockerClient(dockerTimeout)
  1048  		if err != nil {
  1049  			return nil, nil, err
  1050  		}
  1051  	}
  1052  
  1053  	// Only initialize the waitClient if it hasn't yet been done
  1054  	if waitClient == nil {
  1055  		waitClient, err = d.newDockerClient(0 * time.Minute)
  1056  		if err != nil {
  1057  			return nil, nil, err
  1058  		}
  1059  	}
  1060  
  1061  	return client, waitClient, nil
  1062  }
  1063  
  1064  // newDockerClient creates a new *docker.Client with a configurable timeout
  1065  func (d *DockerDriver) newDockerClient(timeout time.Duration) (*docker.Client, error) {
  1066  	var err error
  1067  	var merr multierror.Error
  1068  	var newClient *docker.Client
  1069  
  1070  	// Default to using whatever is configured in docker.endpoint. If this is
  1071  	// not specified we'll fall back on NewClientFromEnv which reads config from
  1072  	// the DOCKER_* environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and
  1073  	// DOCKER_CERT_PATH. This allows us to lock down the config in production
  1074  	// but also accept the standard ENV configs for dev and test.
  1075  	dockerEndpoint := d.config.Read("docker.endpoint")
  1076  	if dockerEndpoint != "" {
  1077  		cert := d.config.Read("docker.tls.cert")
  1078  		key := d.config.Read("docker.tls.key")
  1079  		ca := d.config.Read("docker.tls.ca")
  1080  
  1081  		if cert+key+ca != "" {
  1082  			d.logger.Printf("[DEBUG] driver.docker: using TLS client connection to %s", dockerEndpoint)
  1083  			newClient, err = docker.NewTLSClient(dockerEndpoint, cert, key, ca)
  1084  			if err != nil {
  1085  				merr.Errors = append(merr.Errors, err)
  1086  			}
  1087  		} else {
  1088  			d.logger.Printf("[DEBUG] driver.docker: using standard client connection to %s", dockerEndpoint)
  1089  			newClient, err = docker.NewClient(dockerEndpoint)
  1090  			if err != nil {
  1091  				merr.Errors = append(merr.Errors, err)
  1092  			}
  1093  		}
  1094  	} else {
  1095  		d.logger.Println("[DEBUG] driver.docker: using client connection initialized from environment")
  1096  		newClient, err = docker.NewClientFromEnv()
  1097  		if err != nil {
  1098  			merr.Errors = append(merr.Errors, err)
  1099  		}
  1100  	}
  1101  
  1102  	if timeout != 0 {
  1103  		newClient.SetTimeout(timeout)
  1104  	}
  1105  	return newClient, merr.ErrorOrNil()
  1106  }
  1107  
  1108  func (d *DockerDriver) containerBinds(driverConfig *DockerDriverConfig, ctx *ExecContext,
  1109  	task *structs.Task) ([]string, error) {
  1110  
  1111  	allocDirBind := fmt.Sprintf("%s:%s", ctx.TaskDir.SharedAllocDir, ctx.TaskEnv.EnvMap[env.AllocDir])
  1112  	taskLocalBind := fmt.Sprintf("%s:%s", ctx.TaskDir.LocalDir, ctx.TaskEnv.EnvMap[env.TaskLocalDir])
  1113  	secretDirBind := fmt.Sprintf("%s:%s", ctx.TaskDir.SecretsDir, ctx.TaskEnv.EnvMap[env.SecretsDir])
  1114  	binds := []string{allocDirBind, taskLocalBind, secretDirBind}
  1115  
  1116  	volumesEnabled := d.config.ReadBoolDefault(dockerVolumesConfigOption, dockerVolumesConfigDefault)
  1117  
  1118  	if !volumesEnabled && driverConfig.VolumeDriver != "" {
  1119  		return nil, fmt.Errorf("%s is false; cannot use volume driver %q", dockerVolumesConfigOption, driverConfig.VolumeDriver)
  1120  	}
  1121  
  1122  	for _, userbind := range driverConfig.Volumes {
  1123  		parts := strings.Split(userbind, ":")
  1124  		if len(parts) < 2 {
  1125  			return nil, fmt.Errorf("invalid docker volume: %q", userbind)
  1126  		}
  1127  
  1128  		// Resolve dotted path segments
  1129  		parts[0] = filepath.Clean(parts[0])
  1130  
  1131  		// Absolute paths aren't always supported
  1132  		if filepath.IsAbs(parts[0]) {
  1133  			if !volumesEnabled {
  1134  				// Disallow mounting arbitrary absolute paths
  1135  				return nil, fmt.Errorf("%s is false; cannot mount host paths: %+q", dockerVolumesConfigOption, userbind)
  1136  			}
  1137  			binds = append(binds, userbind)
  1138  			continue
  1139  		}
  1140  
  1141  		// Relative paths are always allowed as they mount within a container
  1142  		// When a VolumeDriver is set, we assume we receive a binding in the format volume-name:container-dest
  1143  		// Otherwise, we assume we receive a relative path binding in the format relative/to/task:/also/in/container
  1144  		if driverConfig.VolumeDriver == "" {
  1145  			// Expand path relative to alloc dir
  1146  			parts[0] = filepath.Join(ctx.TaskDir.Dir, parts[0])
  1147  		}
  1148  
  1149  		binds = append(binds, strings.Join(parts, ":"))
  1150  	}
  1151  
  1152  	if selinuxLabel := d.config.Read(dockerSELinuxLabelConfigOption); selinuxLabel != "" {
  1153  		// Apply SELinux Label to each volume
  1154  		for i := range binds {
  1155  			binds[i] = fmt.Sprintf("%s:%s", binds[i], selinuxLabel)
  1156  		}
  1157  	}
  1158  
  1159  	return binds, nil
  1160  }
  1161  
  1162  // createContainerConfig initializes a struct needed to call docker.client.CreateContainer()
  1163  func (d *DockerDriver) createContainerConfig(ctx *ExecContext, task *structs.Task,
  1164  	driverConfig *DockerDriverConfig, syslogAddr string) (docker.CreateContainerOptions, error) {
  1165  	var c docker.CreateContainerOptions
  1166  	if task.Resources == nil {
  1167  		// Guard against missing resources. We should never have been able to
  1168  		// schedule a job without specifying this.
  1169  		d.logger.Println("[ERR] driver.docker: task.Resources is empty")
  1170  		return c, fmt.Errorf("task.Resources is empty")
  1171  	}
  1172  
  1173  	binds, err := d.containerBinds(driverConfig, ctx, task)
  1174  	if err != nil {
  1175  		return c, err
  1176  	}
  1177  
  1178  	// create the config block that will later be consumed by go-dockerclient
  1179  	config := &docker.Config{
  1180  		Image:       d.imageID,
  1181  		Entrypoint:  driverConfig.Entrypoint,
  1182  		Hostname:    driverConfig.Hostname,
  1183  		User:        task.User,
  1184  		Tty:         driverConfig.TTY,
  1185  		OpenStdin:   driverConfig.Interactive,
  1186  		StopTimeout: int(task.KillTimeout.Seconds()),
  1187  		StopSignal:  task.KillSignal,
  1188  	}
  1189  
  1190  	if driverConfig.WorkDir != "" {
  1191  		config.WorkingDir = driverConfig.WorkDir
  1192  	}
  1193  
  1194  	memLimit := int64(task.Resources.MemoryMB) * 1024 * 1024
  1195  
  1196  	if len(driverConfig.Logging) == 0 {
  1197  		if runtime.GOOS == "darwin" {
  1198  			d.logger.Printf("[DEBUG] driver.docker: deferring logging to docker on Docker for Mac")
  1199  		} else {
  1200  			d.logger.Printf("[DEBUG] driver.docker: Setting default logging options to syslog and %s", syslogAddr)
  1201  			driverConfig.Logging = []DockerLoggingOpts{
  1202  				{Type: "syslog", Config: map[string]string{"syslog-address": syslogAddr}},
  1203  			}
  1204  		}
  1205  	}
  1206  
  1207  	hostConfig := &docker.HostConfig{
  1208  		// Convert MB to bytes. This is an absolute value.
  1209  		Memory: memLimit,
  1210  		// Convert Mhz to shares. This is a relative value.
  1211  		CPUShares: int64(task.Resources.CPU),
  1212  
  1213  		// Binds are used to mount a host volume into the container. We mount a
  1214  		// local directory for storage and a shared alloc directory that can be
  1215  		// used to share data between different tasks in the same task group.
  1216  		Binds: binds,
  1217  
  1218  		VolumeDriver: driverConfig.VolumeDriver,
  1219  	}
  1220  
  1221  	// Calculate CPU Quota
  1222  	// cfs_quota_us is the time per core, so we must
  1223  	// multiply the time by the number of cores available
  1224  	// See https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/resource_management_guide/sec-cpu
  1225  	if driverConfig.CPUHardLimit {
  1226  		numCores := runtime.NumCPU()
  1227  		percentTicks := float64(task.Resources.CPU) / float64(d.node.Resources.CPU)
  1228  		hostConfig.CPUQuota = int64(percentTicks*defaultCFSPeriodUS) * int64(numCores)
  1229  	}
  1230  
  1231  	// Windows does not support MemorySwap/MemorySwappiness #2193
  1232  	if runtime.GOOS == "windows" {
  1233  		hostConfig.MemorySwap = 0
  1234  		hostConfig.MemorySwappiness = -1
  1235  	} else {
  1236  		hostConfig.MemorySwap = memLimit // MemorySwap is memory + swap.
  1237  	}
  1238  
  1239  	if len(driverConfig.Logging) != 0 {
  1240  		d.logger.Printf("[DEBUG] driver.docker: Using config for logging: %+v", driverConfig.Logging[0])
  1241  		hostConfig.LogConfig = docker.LogConfig{
  1242  			Type:   driverConfig.Logging[0].Type,
  1243  			Config: driverConfig.Logging[0].Config,
  1244  		}
  1245  	}
  1246  
  1247  	d.logger.Printf("[DEBUG] driver.docker: using %d bytes memory for %s", hostConfig.Memory, task.Name)
  1248  	d.logger.Printf("[DEBUG] driver.docker: using %d cpu shares for %s", hostConfig.CPUShares, task.Name)
  1249  	if driverConfig.CPUHardLimit {
  1250  		d.logger.Printf("[DEBUG] driver.docker: using %dms cpu quota and %dms cpu period for %s", hostConfig.CPUQuota, defaultCFSPeriodUS, task.Name)
  1251  	}
  1252  	d.logger.Printf("[DEBUG] driver.docker: binding directories %#v for %s", hostConfig.Binds, task.Name)
  1253  
  1254  	//  set privileged mode
  1255  	hostPrivileged := d.config.ReadBoolDefault(dockerPrivilegedConfigOption, false)
  1256  	if driverConfig.Privileged && !hostPrivileged {
  1257  		return c, fmt.Errorf(`Docker privileged mode is disabled on this Nomad agent`)
  1258  	}
  1259  	hostConfig.Privileged = driverConfig.Privileged
  1260  
  1261  	// set capabilities
  1262  	hostCapsWhitelistConfig := d.config.ReadDefault(
  1263  		dockerCapsWhitelistConfigOption, dockerCapsWhitelistConfigDefault)
  1264  	hostCapsWhitelist := make(map[string]struct{})
  1265  	for _, cap := range strings.Split(hostCapsWhitelistConfig, ",") {
  1266  		cap = strings.ToLower(strings.TrimSpace(cap))
  1267  		hostCapsWhitelist[cap] = struct{}{}
  1268  	}
  1269  
  1270  	if _, ok := hostCapsWhitelist["all"]; !ok {
  1271  		effectiveCaps, err := tweakCapabilities(
  1272  			strings.Split(dockerBasicCaps, ","),
  1273  			driverConfig.CapAdd,
  1274  			driverConfig.CapDrop,
  1275  		)
  1276  		if err != nil {
  1277  			return c, err
  1278  		}
  1279  		var missingCaps []string
  1280  		for _, cap := range effectiveCaps {
  1281  			cap = strings.ToLower(cap)
  1282  			if _, ok := hostCapsWhitelist[cap]; !ok {
  1283  				missingCaps = append(missingCaps, cap)
  1284  			}
  1285  		}
  1286  		if len(missingCaps) > 0 {
  1287  			return c, fmt.Errorf("Docker driver doesn't have the following caps whitelisted on this Nomad agent: %s", missingCaps)
  1288  		}
  1289  	}
  1290  
  1291  	hostConfig.CapAdd = driverConfig.CapAdd
  1292  	hostConfig.CapDrop = driverConfig.CapDrop
  1293  
  1294  	// set SHM size
  1295  	if driverConfig.ShmSize != 0 {
  1296  		hostConfig.ShmSize = driverConfig.ShmSize
  1297  	}
  1298  
  1299  	// set DNS servers
  1300  	for _, ip := range driverConfig.DNSServers {
  1301  		if net.ParseIP(ip) != nil {
  1302  			hostConfig.DNS = append(hostConfig.DNS, ip)
  1303  		} else {
  1304  			d.logger.Printf("[ERR] driver.docker: invalid ip address for container dns server: %s", ip)
  1305  		}
  1306  	}
  1307  
  1308  	if len(driverConfig.Devices) > 0 {
  1309  		var devices []docker.Device
  1310  		for _, device := range driverConfig.Devices {
  1311  			dev := docker.Device{
  1312  				PathOnHost:        device.HostPath,
  1313  				PathInContainer:   device.ContainerPath,
  1314  				CgroupPermissions: device.CgroupPermissions}
  1315  			devices = append(devices, dev)
  1316  		}
  1317  		hostConfig.Devices = devices
  1318  	}
  1319  
  1320  	// Setup mounts
  1321  	for _, m := range driverConfig.Mounts {
  1322  		hm := docker.HostMount{
  1323  			Target:   m.Target,
  1324  			Source:   m.Source,
  1325  			Type:     "volume", // Only type supported
  1326  			ReadOnly: m.ReadOnly,
  1327  		}
  1328  		if len(m.VolumeOptions) == 1 {
  1329  			vo := m.VolumeOptions[0]
  1330  			hm.VolumeOptions = &docker.VolumeOptions{
  1331  				NoCopy: vo.NoCopy,
  1332  			}
  1333  
  1334  			if len(vo.DriverConfig) == 1 {
  1335  				dc := vo.DriverConfig[0]
  1336  				hm.VolumeOptions.DriverConfig = docker.VolumeDriverConfig{
  1337  					Name: dc.Name,
  1338  				}
  1339  				if len(dc.Options) == 1 {
  1340  					hm.VolumeOptions.DriverConfig.Options = dc.Options[0]
  1341  				}
  1342  			}
  1343  			if len(vo.Labels) == 1 {
  1344  				hm.VolumeOptions.Labels = vo.Labels[0]
  1345  			}
  1346  		}
  1347  		hostConfig.Mounts = append(hostConfig.Mounts, hm)
  1348  	}
  1349  
  1350  	// set DNS search domains and extra hosts
  1351  	hostConfig.DNSSearch = driverConfig.DNSSearchDomains
  1352  	hostConfig.DNSOptions = driverConfig.DNSOptions
  1353  	hostConfig.ExtraHosts = driverConfig.ExtraHosts
  1354  
  1355  	hostConfig.IpcMode = driverConfig.IpcMode
  1356  	hostConfig.PidMode = driverConfig.PidMode
  1357  	hostConfig.UTSMode = driverConfig.UTSMode
  1358  	hostConfig.UsernsMode = driverConfig.UsernsMode
  1359  	hostConfig.SecurityOpt = driverConfig.SecurityOpt
  1360  	hostConfig.Sysctls = driverConfig.Sysctl
  1361  	hostConfig.Ulimits = driverConfig.Ulimit
  1362  	hostConfig.ReadonlyRootfs = driverConfig.ReadonlyRootfs
  1363  
  1364  	hostConfig.NetworkMode = driverConfig.NetworkMode
  1365  	if hostConfig.NetworkMode == "" {
  1366  		// docker default
  1367  		d.logger.Printf("[DEBUG] driver.docker: networking mode not specified; defaulting to %s", defaultNetworkMode)
  1368  		hostConfig.NetworkMode = defaultNetworkMode
  1369  	}
  1370  
  1371  	// Setup port mapping and exposed ports
  1372  	if len(task.Resources.Networks) == 0 {
  1373  		d.logger.Println("[DEBUG] driver.docker: No network interfaces are available")
  1374  		if len(driverConfig.PortMap) > 0 {
  1375  			return c, fmt.Errorf("Trying to map ports but no network interface is available")
  1376  		}
  1377  	} else {
  1378  		// TODO add support for more than one network
  1379  		network := task.Resources.Networks[0]
  1380  		publishedPorts := map[docker.Port][]docker.PortBinding{}
  1381  		exposedPorts := map[docker.Port]struct{}{}
  1382  
  1383  		for _, port := range network.ReservedPorts {
  1384  			// By default we will map the allocated port 1:1 to the container
  1385  			containerPortInt := port.Value
  1386  
  1387  			// If the user has mapped a port using port_map we'll change it here
  1388  			if mapped, ok := driverConfig.PortMap[port.Label]; ok {
  1389  				containerPortInt = mapped
  1390  			}
  1391  
  1392  			hostPortStr := strconv.Itoa(port.Value)
  1393  			containerPort := docker.Port(strconv.Itoa(containerPortInt))
  1394  
  1395  			publishedPorts[containerPort+"/tcp"] = getPortBinding(network.IP, hostPortStr)
  1396  			publishedPorts[containerPort+"/udp"] = getPortBinding(network.IP, hostPortStr)
  1397  			d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %d (static)", network.IP, port.Value, port.Value)
  1398  
  1399  			exposedPorts[containerPort+"/tcp"] = struct{}{}
  1400  			exposedPorts[containerPort+"/udp"] = struct{}{}
  1401  			d.logger.Printf("[DEBUG] driver.docker: exposed port %d", port.Value)
  1402  		}
  1403  
  1404  		for _, port := range network.DynamicPorts {
  1405  			// By default we will map the allocated port 1:1 to the container
  1406  			containerPortInt := port.Value
  1407  
  1408  			// If the user has mapped a port using port_map we'll change it here
  1409  			if mapped, ok := driverConfig.PortMap[port.Label]; ok {
  1410  				containerPortInt = mapped
  1411  			}
  1412  
  1413  			hostPortStr := strconv.Itoa(port.Value)
  1414  			containerPort := docker.Port(strconv.Itoa(containerPortInt))
  1415  
  1416  			publishedPorts[containerPort+"/tcp"] = getPortBinding(network.IP, hostPortStr)
  1417  			publishedPorts[containerPort+"/udp"] = getPortBinding(network.IP, hostPortStr)
  1418  			d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %d (mapped)", network.IP, port.Value, containerPortInt)
  1419  
  1420  			exposedPorts[containerPort+"/tcp"] = struct{}{}
  1421  			exposedPorts[containerPort+"/udp"] = struct{}{}
  1422  			d.logger.Printf("[DEBUG] driver.docker: exposed port %s", containerPort)
  1423  		}
  1424  
  1425  		hostConfig.PortBindings = publishedPorts
  1426  		config.ExposedPorts = exposedPorts
  1427  	}
  1428  
  1429  	parsedArgs := ctx.TaskEnv.ParseAndReplace(driverConfig.Args)
  1430  
  1431  	// If the user specified a custom command to run, we'll inject it here.
  1432  	if driverConfig.Command != "" {
  1433  		// Validate command
  1434  		if err := validateCommand(driverConfig.Command, "args"); err != nil {
  1435  			return c, err
  1436  		}
  1437  
  1438  		cmd := []string{driverConfig.Command}
  1439  		if len(driverConfig.Args) != 0 {
  1440  			cmd = append(cmd, parsedArgs...)
  1441  		}
  1442  		d.logger.Printf("[DEBUG] driver.docker: setting container startup command to: %s", strings.Join(cmd, " "))
  1443  		config.Cmd = cmd
  1444  	} else if len(driverConfig.Args) != 0 {
  1445  		config.Cmd = parsedArgs
  1446  	}
  1447  
  1448  	if len(driverConfig.Labels) > 0 {
  1449  		config.Labels = driverConfig.Labels
  1450  		d.logger.Printf("[DEBUG] driver.docker: applied labels on the container: %+v", config.Labels)
  1451  	}
  1452  
  1453  	config.Env = ctx.TaskEnv.List()
  1454  
  1455  	containerName := fmt.Sprintf("%s-%s", task.Name, d.DriverContext.allocID)
  1456  	d.logger.Printf("[DEBUG] driver.docker: setting container name to: %s", containerName)
  1457  
  1458  	var networkingConfig *docker.NetworkingConfig
  1459  	if len(driverConfig.NetworkAliases) > 0 || driverConfig.IPv4Address != "" || driverConfig.IPv6Address != "" {
  1460  		networkingConfig = &docker.NetworkingConfig{
  1461  			EndpointsConfig: map[string]*docker.EndpointConfig{
  1462  				hostConfig.NetworkMode: {},
  1463  			},
  1464  		}
  1465  	}
  1466  
  1467  	if len(driverConfig.NetworkAliases) > 0 {
  1468  		networkingConfig.EndpointsConfig[hostConfig.NetworkMode].Aliases = driverConfig.NetworkAliases
  1469  		d.logger.Printf("[DEBUG] driver.docker: using network_mode %q with network aliases: %v",
  1470  			hostConfig.NetworkMode, strings.Join(driverConfig.NetworkAliases, ", "))
  1471  	}
  1472  
  1473  	if driverConfig.IPv4Address != "" || driverConfig.IPv6Address != "" {
  1474  		networkingConfig.EndpointsConfig[hostConfig.NetworkMode].IPAMConfig = &docker.EndpointIPAMConfig{
  1475  			IPv4Address: driverConfig.IPv4Address,
  1476  			IPv6Address: driverConfig.IPv6Address,
  1477  		}
  1478  		d.logger.Printf("[DEBUG] driver.docker: using network_mode %q with ipv4: %q and ipv6: %q",
  1479  			hostConfig.NetworkMode, driverConfig.IPv4Address, driverConfig.IPv6Address)
  1480  	}
  1481  
  1482  	if driverConfig.MacAddress != "" {
  1483  		config.MacAddress = driverConfig.MacAddress
  1484  		d.logger.Printf("[DEBUG] driver.docker: using pinned mac address: %q", config.MacAddress)
  1485  	}
  1486  
  1487  	return docker.CreateContainerOptions{
  1488  		Name:             containerName,
  1489  		Config:           config,
  1490  		HostConfig:       hostConfig,
  1491  		NetworkingConfig: networkingConfig,
  1492  	}, nil
  1493  }
  1494  
  1495  func (d *DockerDriver) Periodic() (bool, time.Duration) {
  1496  	return true, 15 * time.Second
  1497  }
  1498  
  1499  // createImage creates a docker image either by pulling it from a registry or by
  1500  // loading it from the file system
  1501  func (d *DockerDriver) createImage(driverConfig *DockerDriverConfig, client *docker.Client, taskDir *allocdir.TaskDir) (string, error) {
  1502  	image := driverConfig.ImageName
  1503  	repo, tag := docker.ParseRepositoryTag(image)
  1504  	if tag == "" {
  1505  		tag = "latest"
  1506  	}
  1507  
  1508  	coordinator, callerID := d.getDockerCoordinator(client)
  1509  
  1510  	// We're going to check whether the image is already downloaded. If the tag
  1511  	// is "latest", or ForcePull is set, we have to check for a new version every time so we don't
  1512  	// bother to check and cache the id here. We'll download first, then cache.
  1513  	if driverConfig.ForcePull {
  1514  		d.logger.Printf("[DEBUG] driver.docker: force pull image '%s:%s' instead of inspecting local", repo, tag)
  1515  	} else if tag != "latest" {
  1516  		if dockerImage, _ := client.InspectImage(image); dockerImage != nil {
  1517  			// Image exists so just increment its reference count
  1518  			coordinator.IncrementImageReference(dockerImage.ID, image, callerID)
  1519  			return dockerImage.ID, nil
  1520  		}
  1521  	}
  1522  
  1523  	// Load the image if specified
  1524  	if driverConfig.LoadImage != "" {
  1525  		return d.loadImage(driverConfig, client, taskDir)
  1526  	}
  1527  
  1528  	// Download the image
  1529  	return d.pullImage(driverConfig, client, repo, tag)
  1530  }
  1531  
  1532  // pullImage creates an image by pulling it from a docker registry
  1533  func (d *DockerDriver) pullImage(driverConfig *DockerDriverConfig, client *docker.Client, repo, tag string) (id string, err error) {
  1534  	authOptions, err := d.resolveRegistryAuthentication(driverConfig, repo)
  1535  	if err != nil {
  1536  		if d.driverConfig.AuthSoftFail {
  1537  			d.logger.Printf("[WARN] Failed to find docker auth for repo %q: %v", repo, err)
  1538  		} else {
  1539  			return "", fmt.Errorf("Failed to find docker auth for repo %q: %v", repo, err)
  1540  		}
  1541  	}
  1542  
  1543  	if authIsEmpty(authOptions) {
  1544  		d.logger.Printf("[DEBUG] driver.docker: did not find docker auth for repo %q", repo)
  1545  	}
  1546  
  1547  	d.emitEvent("Downloading image %s:%s", repo, tag)
  1548  	coordinator, callerID := d.getDockerCoordinator(client)
  1549  	return coordinator.PullImage(driverConfig.ImageName, authOptions, callerID)
  1550  }
  1551  
  1552  // authBackend encapsulates a function that resolves registry credentials.
  1553  type authBackend func(string) (*docker.AuthConfiguration, error)
  1554  
  1555  // resolveRegistryAuthentication attempts to retrieve auth credentials for the
  1556  // repo, trying all authentication-backends possible.
  1557  func (d *DockerDriver) resolveRegistryAuthentication(driverConfig *DockerDriverConfig, repo string) (*docker.AuthConfiguration, error) {
  1558  	return firstValidAuth(repo, []authBackend{
  1559  		authFromTaskConfig(driverConfig),
  1560  		authFromDockerConfig(d.config.Read("docker.auth.config")),
  1561  		authFromHelper(d.config.Read("docker.auth.helper")),
  1562  	})
  1563  }
  1564  
  1565  // loadImage creates an image by loading it from the file system
  1566  func (d *DockerDriver) loadImage(driverConfig *DockerDriverConfig, client *docker.Client,
  1567  	taskDir *allocdir.TaskDir) (id string, err error) {
  1568  
  1569  	archive := filepath.Join(taskDir.LocalDir, driverConfig.LoadImage)
  1570  	d.logger.Printf("[DEBUG] driver.docker: loading image from: %v", archive)
  1571  
  1572  	f, err := os.Open(archive)
  1573  	if err != nil {
  1574  		return "", fmt.Errorf("unable to open image archive: %v", err)
  1575  	}
  1576  
  1577  	if err := client.LoadImage(docker.LoadImageOptions{InputStream: f}); err != nil {
  1578  		return "", err
  1579  	}
  1580  	f.Close()
  1581  
  1582  	dockerImage, err := client.InspectImage(driverConfig.ImageName)
  1583  	if err != nil {
  1584  		return "", recoverableErrTimeouts(err)
  1585  	}
  1586  
  1587  	coordinator, callerID := d.getDockerCoordinator(client)
  1588  	coordinator.IncrementImageReference(dockerImage.ID, driverConfig.ImageName, callerID)
  1589  	return dockerImage.ID, nil
  1590  }
  1591  
  1592  // createContainer creates the container given the passed configuration. It
  1593  // attempts to handle any transient Docker errors.
  1594  func (d *DockerDriver) createContainer(client createContainerClient, config docker.CreateContainerOptions) (*docker.Container, error) {
  1595  	// Create a container
  1596  	attempted := 0
  1597  CREATE:
  1598  	container, createErr := client.CreateContainer(config)
  1599  	if createErr == nil {
  1600  		return container, nil
  1601  	}
  1602  
  1603  	d.logger.Printf("[DEBUG] driver.docker: failed to create container %q from image %q (ID: %q) (attempt %d): %v",
  1604  		config.Name, d.driverConfig.ImageName, d.imageID, attempted+1, createErr)
  1605  
  1606  	// Volume management tools like Portworx may not have detached a volume
  1607  	// from a previous node before Nomad started a task replacement task.
  1608  	// Treat these errors as recoverable so we retry.
  1609  	if strings.Contains(strings.ToLower(createErr.Error()), "volume is attached on another node") {
  1610  		return nil, structs.NewRecoverableError(createErr, true)
  1611  	}
  1612  
  1613  	// If the container already exists determine whether it's already
  1614  	// running or if it's dead and needs to be recreated.
  1615  	if strings.Contains(strings.ToLower(createErr.Error()), "container already exists") {
  1616  		containers, err := client.ListContainers(docker.ListContainersOptions{
  1617  			All: true,
  1618  		})
  1619  		if err != nil {
  1620  			d.logger.Printf("[ERR] driver.docker: failed to query list of containers matching name:%s", config.Name)
  1621  			return nil, recoverableErrTimeouts(fmt.Errorf("Failed to query list of containers: %s", err))
  1622  		}
  1623  
  1624  		// Delete matching containers
  1625  		// Adding a / infront of the container name since Docker returns the
  1626  		// container names with a / pre-pended to the Nomad generated container names
  1627  		containerName := "/" + config.Name
  1628  		d.logger.Printf("[DEBUG] driver.docker: searching for container name %q to purge", containerName)
  1629  		for _, shimContainer := range containers {
  1630  			d.logger.Printf("[DEBUG] driver.docker: listed container %+v", shimContainer.Names)
  1631  			found := false
  1632  			for _, name := range shimContainer.Names {
  1633  				if name == containerName {
  1634  					d.logger.Printf("[DEBUG] driver.docker: Found container %v: %v", containerName, shimContainer.ID)
  1635  					found = true
  1636  					break
  1637  				}
  1638  			}
  1639  
  1640  			if !found {
  1641  				continue
  1642  			}
  1643  
  1644  			// Inspect the container and if the container isn't dead then return
  1645  			// the container
  1646  			container, err := client.InspectContainer(shimContainer.ID)
  1647  			if err != nil {
  1648  				err = fmt.Errorf("Failed to inspect container %s: %s", shimContainer.ID, err)
  1649  
  1650  				// This error is always recoverable as it could
  1651  				// be caused by races between listing
  1652  				// containers and this container being removed.
  1653  				// See #2802
  1654  				return nil, structs.NewRecoverableError(err, true)
  1655  			}
  1656  			if container != nil && container.State.Running {
  1657  				return container, nil
  1658  			}
  1659  
  1660  			err = client.RemoveContainer(docker.RemoveContainerOptions{
  1661  				ID:    container.ID,
  1662  				Force: true,
  1663  			})
  1664  			if err != nil {
  1665  				d.logger.Printf("[ERR] driver.docker: failed to purge container %s", container.ID)
  1666  				return nil, recoverableErrTimeouts(fmt.Errorf("Failed to purge container %s: %s", container.ID, err))
  1667  			} else if err == nil {
  1668  				d.logger.Printf("[INFO] driver.docker: purged container %s", container.ID)
  1669  			}
  1670  		}
  1671  
  1672  		if attempted < 5 {
  1673  			attempted++
  1674  			time.Sleep(1 * time.Second)
  1675  			goto CREATE
  1676  		}
  1677  	} else if strings.Contains(strings.ToLower(createErr.Error()), "no such image") {
  1678  		// There is still a very small chance this is possible even with the
  1679  		// coordinator so retry.
  1680  		return nil, structs.NewRecoverableError(createErr, true)
  1681  	}
  1682  
  1683  	return nil, recoverableErrTimeouts(createErr)
  1684  }
  1685  
  1686  // startContainer starts the passed container. It attempts to handle any
  1687  // transient Docker errors.
  1688  func (d *DockerDriver) startContainer(c *docker.Container) error {
  1689  	// Start a container
  1690  	attempted := 0
  1691  START:
  1692  	startErr := client.StartContainer(c.ID, c.HostConfig)
  1693  	if startErr == nil {
  1694  		return nil
  1695  	}
  1696  
  1697  	d.logger.Printf("[DEBUG] driver.docker: failed to start container %q (attempt %d): %v", c.ID, attempted+1, startErr)
  1698  
  1699  	// If it is a 500 error it is likely we can retry and be successful
  1700  	if strings.Contains(startErr.Error(), "API error (500)") {
  1701  		if attempted < 5 {
  1702  			attempted++
  1703  			time.Sleep(1 * time.Second)
  1704  			goto START
  1705  		}
  1706  		return structs.NewRecoverableError(startErr, true)
  1707  	}
  1708  
  1709  	return recoverableErrTimeouts(startErr)
  1710  }
  1711  
  1712  func (d *DockerDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
  1713  	// Split the handle
  1714  	pidBytes := []byte(strings.TrimPrefix(handleID, "DOCKER:"))
  1715  	pid := &dockerPID{}
  1716  	if err := json.Unmarshal(pidBytes, pid); err != nil {
  1717  		return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err)
  1718  	}
  1719  	d.logger.Printf("[INFO] driver.docker: re-attaching to docker process: %s", pid.ContainerID)
  1720  	d.logger.Printf("[DEBUG] driver.docker: re-attached to handle: %s", handleID)
  1721  	pluginConfig := &plugin.ClientConfig{
  1722  		Reattach: pid.PluginConfig.PluginConfig(),
  1723  	}
  1724  
  1725  	client, waitClient, err := d.dockerClients()
  1726  	if err != nil {
  1727  		return nil, fmt.Errorf("Failed to connect to docker daemon: %s", err)
  1728  	}
  1729  
  1730  	// Look for a running container with this ID
  1731  	containers, err := client.ListContainers(docker.ListContainersOptions{
  1732  		Filters: map[string][]string{
  1733  			"id": {pid.ContainerID},
  1734  		},
  1735  	})
  1736  	if err != nil {
  1737  		return nil, fmt.Errorf("Failed to query for container %s: %v", pid.ContainerID, err)
  1738  	}
  1739  
  1740  	found := false
  1741  	for _, container := range containers {
  1742  		if container.ID == pid.ContainerID {
  1743  			found = true
  1744  		}
  1745  	}
  1746  	if !found {
  1747  		return nil, fmt.Errorf("Failed to find container %s", pid.ContainerID)
  1748  	}
  1749  	exec, pluginClient, err := createExecutorWithConfig(pluginConfig, d.config.LogOutput)
  1750  	if err != nil {
  1751  		d.logger.Printf("[INFO] driver.docker: couldn't re-attach to the plugin process: %v", err)
  1752  		d.logger.Printf("[DEBUG] driver.docker: stopping container %q", pid.ContainerID)
  1753  		if e := client.StopContainer(pid.ContainerID, uint(pid.KillTimeout.Seconds())); e != nil {
  1754  			d.logger.Printf("[DEBUG] driver.docker: couldn't stop container: %v", e)
  1755  		}
  1756  		return nil, err
  1757  	}
  1758  
  1759  	ver, _ := exec.Version()
  1760  	d.logger.Printf("[DEBUG] driver.docker: version of executor: %v", ver.Version)
  1761  
  1762  	// Increment the reference count since we successfully attached to this
  1763  	// container
  1764  	coordinator, callerID := d.getDockerCoordinator(client)
  1765  	coordinator.IncrementImageReference(pid.ImageID, pid.Image, callerID)
  1766  
  1767  	// Return a driver handle
  1768  	h := &DockerHandle{
  1769  		client:         client,
  1770  		waitClient:     waitClient,
  1771  		executor:       exec,
  1772  		pluginClient:   pluginClient,
  1773  		logger:         d.logger,
  1774  		Image:          pid.Image,
  1775  		ImageID:        pid.ImageID,
  1776  		containerID:    pid.ContainerID,
  1777  		version:        pid.Version,
  1778  		killTimeout:    pid.KillTimeout,
  1779  		maxKillTimeout: pid.MaxKillTimeout,
  1780  		doneCh:         make(chan bool),
  1781  		waitCh:         make(chan *dstructs.WaitResult, 1),
  1782  	}
  1783  	go h.collectStats()
  1784  	go h.run()
  1785  	return h, nil
  1786  }
  1787  
  1788  func (h *DockerHandle) ID() string {
  1789  	// Return a handle to the PID
  1790  	pid := dockerPID{
  1791  		Version:        h.version,
  1792  		ContainerID:    h.containerID,
  1793  		Image:          h.Image,
  1794  		ImageID:        h.ImageID,
  1795  		KillTimeout:    h.killTimeout,
  1796  		MaxKillTimeout: h.maxKillTimeout,
  1797  		PluginConfig:   NewPluginReattachConfig(h.pluginClient.ReattachConfig()),
  1798  	}
  1799  	data, err := json.Marshal(pid)
  1800  	if err != nil {
  1801  		h.logger.Printf("[ERR] driver.docker: failed to marshal docker PID to JSON: %s", err)
  1802  	}
  1803  	return fmt.Sprintf("DOCKER:%s", string(data))
  1804  }
  1805  
  1806  func (h *DockerHandle) ContainerID() string {
  1807  	return h.containerID
  1808  }
  1809  
  1810  func (h *DockerHandle) WaitCh() chan *dstructs.WaitResult {
  1811  	return h.waitCh
  1812  }
  1813  
  1814  func (h *DockerHandle) Update(task *structs.Task) error {
  1815  	// Store the updated kill timeout.
  1816  	h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout)
  1817  	if err := h.executor.UpdateTask(task); err != nil {
  1818  		h.logger.Printf("[DEBUG] driver.docker: failed to update log config: %v", err)
  1819  	}
  1820  
  1821  	// Update is not possible
  1822  	return nil
  1823  }
  1824  
  1825  func (h *DockerHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) {
  1826  	fullCmd := make([]string, len(args)+1)
  1827  	fullCmd[0] = cmd
  1828  	copy(fullCmd[1:], args)
  1829  	createExecOpts := docker.CreateExecOptions{
  1830  		AttachStdin:  false,
  1831  		AttachStdout: true,
  1832  		AttachStderr: true,
  1833  		Tty:          false,
  1834  		Cmd:          fullCmd,
  1835  		Container:    h.containerID,
  1836  		Context:      ctx,
  1837  	}
  1838  	exec, err := h.client.CreateExec(createExecOpts)
  1839  	if err != nil {
  1840  		return nil, 0, err
  1841  	}
  1842  
  1843  	output, _ := circbuf.NewBuffer(int64(dstructs.CheckBufSize))
  1844  	startOpts := docker.StartExecOptions{
  1845  		Detach:       false,
  1846  		Tty:          false,
  1847  		OutputStream: output,
  1848  		ErrorStream:  output,
  1849  		Context:      ctx,
  1850  	}
  1851  	if err := client.StartExec(exec.ID, startOpts); err != nil {
  1852  		return nil, 0, err
  1853  	}
  1854  	res, err := client.InspectExec(exec.ID)
  1855  	if err != nil {
  1856  		return output.Bytes(), 0, err
  1857  	}
  1858  	return output.Bytes(), res.ExitCode, nil
  1859  }
  1860  
  1861  func (h *DockerHandle) Signal(s os.Signal) error {
  1862  	// Convert types
  1863  	sysSig, ok := s.(syscall.Signal)
  1864  	if !ok {
  1865  		return fmt.Errorf("Failed to determine signal number")
  1866  	}
  1867  
  1868  	// TODO When we expose signals we will need a mapping layer that converts
  1869  	// MacOS signals to the correct signal number for docker. Or we change the
  1870  	// interface to take a signal string and leave it up to driver to map?
  1871  
  1872  	dockerSignal := docker.Signal(sysSig)
  1873  	opts := docker.KillContainerOptions{
  1874  		ID:     h.containerID,
  1875  		Signal: dockerSignal,
  1876  	}
  1877  	return h.client.KillContainer(opts)
  1878  
  1879  }
  1880  
  1881  // Kill is used to terminate the task. This uses `docker stop -t killTimeout`
  1882  func (h *DockerHandle) Kill() error {
  1883  	// Stop the container
  1884  	err := h.client.StopContainer(h.containerID, uint(h.killTimeout.Seconds()))
  1885  	if err != nil {
  1886  		h.executor.Exit()
  1887  		h.pluginClient.Kill()
  1888  
  1889  		// Container has already been removed.
  1890  		if strings.Contains(err.Error(), NoSuchContainerError) {
  1891  			h.logger.Printf("[DEBUG] driver.docker: attempted to stop nonexistent container %s", h.containerID)
  1892  			return nil
  1893  		}
  1894  		h.logger.Printf("[ERR] driver.docker: failed to stop container %s: %v", h.containerID, err)
  1895  		return fmt.Errorf("Failed to stop container %s: %s", h.containerID, err)
  1896  	}
  1897  	h.logger.Printf("[INFO] driver.docker: stopped container %s", h.containerID)
  1898  	return nil
  1899  }
  1900  
  1901  func (h *DockerHandle) Stats() (*cstructs.TaskResourceUsage, error) {
  1902  	h.resourceUsageLock.RLock()
  1903  	defer h.resourceUsageLock.RUnlock()
  1904  	var err error
  1905  	if h.resourceUsage == nil {
  1906  		err = fmt.Errorf("stats collection hasn't started yet")
  1907  	}
  1908  	return h.resourceUsage, err
  1909  }
  1910  
  1911  func (h *DockerHandle) run() {
  1912  	// Wait for it...
  1913  	exitCode, werr := h.waitClient.WaitContainer(h.containerID)
  1914  	if werr != nil {
  1915  		h.logger.Printf("[ERR] driver.docker: failed to wait for %s; container already terminated", h.containerID)
  1916  	}
  1917  
  1918  	if exitCode != 0 {
  1919  		werr = fmt.Errorf("Docker container exited with non-zero exit code: %d", exitCode)
  1920  	}
  1921  
  1922  	container, ierr := h.waitClient.InspectContainer(h.containerID)
  1923  	if ierr != nil {
  1924  		h.logger.Printf("[ERR] driver.docker: failed to inspect container %s: %v", h.containerID, ierr)
  1925  	} else if container.State.OOMKilled {
  1926  		werr = fmt.Errorf("OOM Killed")
  1927  	}
  1928  
  1929  	close(h.doneCh)
  1930  
  1931  	// Shutdown the syslog collector
  1932  	if err := h.executor.Exit(); err != nil {
  1933  		h.logger.Printf("[ERR] driver.docker: failed to kill the syslog collector: %v", err)
  1934  	}
  1935  	h.pluginClient.Kill()
  1936  
  1937  	// Stop the container just incase the docker daemon's wait returned
  1938  	// incorrectly
  1939  	if err := h.client.StopContainer(h.containerID, 0); err != nil {
  1940  		_, noSuchContainer := err.(*docker.NoSuchContainer)
  1941  		_, containerNotRunning := err.(*docker.ContainerNotRunning)
  1942  		if !containerNotRunning && !noSuchContainer {
  1943  			h.logger.Printf("[ERR] driver.docker: error stopping container: %v", err)
  1944  		}
  1945  	}
  1946  
  1947  	// Remove the container
  1948  	if err := h.client.RemoveContainer(docker.RemoveContainerOptions{ID: h.containerID, RemoveVolumes: true, Force: true}); err != nil {
  1949  		h.logger.Printf("[ERR] driver.docker: error removing container: %v", err)
  1950  	}
  1951  
  1952  	// Send the results
  1953  	h.waitCh <- dstructs.NewWaitResult(exitCode, 0, werr)
  1954  	close(h.waitCh)
  1955  }
  1956  
  1957  // collectStats starts collecting resource usage stats of a docker container
  1958  func (h *DockerHandle) collectStats() {
  1959  	statsCh := make(chan *docker.Stats)
  1960  	statsOpts := docker.StatsOptions{ID: h.containerID, Done: h.doneCh, Stats: statsCh, Stream: true}
  1961  	go func() {
  1962  		//TODO handle Stats error
  1963  		if err := h.waitClient.Stats(statsOpts); err != nil {
  1964  			h.logger.Printf("[DEBUG] driver.docker: error collecting stats from container %s: %v", h.containerID, err)
  1965  		}
  1966  	}()
  1967  	numCores := runtime.NumCPU()
  1968  	for {
  1969  		select {
  1970  		case s := <-statsCh:
  1971  			if s != nil {
  1972  				ms := &cstructs.MemoryStats{
  1973  					RSS:      s.MemoryStats.Stats.Rss,
  1974  					Cache:    s.MemoryStats.Stats.Cache,
  1975  					Swap:     s.MemoryStats.Stats.Swap,
  1976  					MaxUsage: s.MemoryStats.MaxUsage,
  1977  					Measured: DockerMeasuredMemStats,
  1978  				}
  1979  
  1980  				cs := &cstructs.CpuStats{
  1981  					ThrottledPeriods: s.CPUStats.ThrottlingData.ThrottledPeriods,
  1982  					ThrottledTime:    s.CPUStats.ThrottlingData.ThrottledTime,
  1983  					Measured:         DockerMeasuredCpuStats,
  1984  				}
  1985  
  1986  				// Calculate percentage
  1987  				cs.Percent = calculatePercent(
  1988  					s.CPUStats.CPUUsage.TotalUsage, s.PreCPUStats.CPUUsage.TotalUsage,
  1989  					s.CPUStats.SystemCPUUsage, s.PreCPUStats.SystemCPUUsage, numCores)
  1990  				cs.SystemMode = calculatePercent(
  1991  					s.CPUStats.CPUUsage.UsageInKernelmode, s.PreCPUStats.CPUUsage.UsageInKernelmode,
  1992  					s.CPUStats.CPUUsage.TotalUsage, s.PreCPUStats.CPUUsage.TotalUsage, numCores)
  1993  				cs.UserMode = calculatePercent(
  1994  					s.CPUStats.CPUUsage.UsageInUsermode, s.PreCPUStats.CPUUsage.UsageInUsermode,
  1995  					s.CPUStats.CPUUsage.TotalUsage, s.PreCPUStats.CPUUsage.TotalUsage, numCores)
  1996  				cs.TotalTicks = (cs.Percent / 100) * shelpers.TotalTicksAvailable() / float64(numCores)
  1997  
  1998  				h.resourceUsageLock.Lock()
  1999  				h.resourceUsage = &cstructs.TaskResourceUsage{
  2000  					ResourceUsage: &cstructs.ResourceUsage{
  2001  						MemoryStats: ms,
  2002  						CpuStats:    cs,
  2003  					},
  2004  					Timestamp: s.Read.UTC().UnixNano(),
  2005  				}
  2006  				h.resourceUsageLock.Unlock()
  2007  			}
  2008  		case <-h.doneCh:
  2009  			return
  2010  		}
  2011  	}
  2012  }
  2013  
  2014  func calculatePercent(newSample, oldSample, newTotal, oldTotal uint64, cores int) float64 {
  2015  	numerator := newSample - oldSample
  2016  	denom := newTotal - oldTotal
  2017  	if numerator <= 0 || denom <= 0 {
  2018  		return 0.0
  2019  	}
  2020  
  2021  	return (float64(numerator) / float64(denom)) * float64(cores) * 100.0
  2022  }
  2023  
  2024  // loadDockerConfig loads the docker config at the specified path, returning an
  2025  // error if it couldn't be read.
  2026  func loadDockerConfig(file string) (*configfile.ConfigFile, error) {
  2027  	f, err := os.Open(file)
  2028  	if err != nil {
  2029  		return nil, fmt.Errorf("Failed to open auth config file: %v, error: %v", file, err)
  2030  	}
  2031  	defer f.Close()
  2032  
  2033  	cfile := new(configfile.ConfigFile)
  2034  	if err = cfile.LoadFromReader(f); err != nil {
  2035  		return nil, fmt.Errorf("Failed to parse auth config file: %v", err)
  2036  	}
  2037  	return cfile, nil
  2038  }
  2039  
  2040  // parseRepositoryInfo takes a repo and returns the Docker RepositoryInfo. This
  2041  // is useful for interacting with a Docker config object.
  2042  func parseRepositoryInfo(repo string) (*registry.RepositoryInfo, error) {
  2043  	name, err := reference.ParseNamed(repo)
  2044  	if err != nil {
  2045  		return nil, fmt.Errorf("Failed to parse named repo %q: %v", repo, err)
  2046  	}
  2047  
  2048  	repoInfo, err := registry.ParseRepositoryInfo(name)
  2049  	if err != nil {
  2050  		return nil, fmt.Errorf("Failed to parse repository: %v", err)
  2051  	}
  2052  
  2053  	return repoInfo, nil
  2054  }
  2055  
  2056  // firstValidAuth tries a list of auth backends, returning first error or AuthConfiguration
  2057  func firstValidAuth(repo string, backends []authBackend) (*docker.AuthConfiguration, error) {
  2058  	for _, backend := range backends {
  2059  		auth, err := backend(repo)
  2060  		if auth != nil || err != nil {
  2061  			return auth, err
  2062  		}
  2063  	}
  2064  	return nil, nil
  2065  }
  2066  
  2067  // authFromTaskConfig generates an authBackend for any auth given in the task-configuration
  2068  func authFromTaskConfig(driverConfig *DockerDriverConfig) authBackend {
  2069  	return func(string) (*docker.AuthConfiguration, error) {
  2070  		if len(driverConfig.Auth) == 0 {
  2071  			return nil, nil
  2072  		}
  2073  		auth := driverConfig.Auth[0]
  2074  		return &docker.AuthConfiguration{
  2075  			Username:      auth.Username,
  2076  			Password:      auth.Password,
  2077  			Email:         auth.Email,
  2078  			ServerAddress: auth.ServerAddress,
  2079  		}, nil
  2080  	}
  2081  }
  2082  
  2083  // authFromDockerConfig generate an authBackend for a dockercfg-compatible file.
  2084  // The authBacken can either be from explicit auth definitions or via credential
  2085  // helpers
  2086  func authFromDockerConfig(file string) authBackend {
  2087  	return func(repo string) (*docker.AuthConfiguration, error) {
  2088  		if file == "" {
  2089  			return nil, nil
  2090  		}
  2091  		repoInfo, err := parseRepositoryInfo(repo)
  2092  		if err != nil {
  2093  			return nil, err
  2094  		}
  2095  
  2096  		cfile, err := loadDockerConfig(file)
  2097  		if err != nil {
  2098  			return nil, err
  2099  		}
  2100  
  2101  		return firstValidAuth(repo, []authBackend{
  2102  			func(string) (*docker.AuthConfiguration, error) {
  2103  				dockerAuthConfig := registry.ResolveAuthConfig(cfile.AuthConfigs, repoInfo.Index)
  2104  				auth := &docker.AuthConfiguration{
  2105  					Username:      dockerAuthConfig.Username,
  2106  					Password:      dockerAuthConfig.Password,
  2107  					Email:         dockerAuthConfig.Email,
  2108  					ServerAddress: dockerAuthConfig.ServerAddress,
  2109  				}
  2110  				if authIsEmpty(auth) {
  2111  					return nil, nil
  2112  				}
  2113  				return auth, nil
  2114  			},
  2115  			authFromHelper(cfile.CredentialHelpers[registry.GetAuthConfigKey(repoInfo.Index)]),
  2116  			authFromHelper(cfile.CredentialsStore),
  2117  		})
  2118  	}
  2119  }
  2120  
  2121  // authFromHelper generates an authBackend for a docker-credentials-helper;
  2122  // A script taking the requested domain on input, outputting JSON with
  2123  // "Username" and "Secret"
  2124  func authFromHelper(helperName string) authBackend {
  2125  	return func(repo string) (*docker.AuthConfiguration, error) {
  2126  		if helperName == "" {
  2127  			return nil, nil
  2128  		}
  2129  		helper := dockerAuthHelperPrefix + helperName
  2130  		cmd := exec.Command(helper, "get")
  2131  
  2132  		// Ensure that the HTTPs prefix exists
  2133  		if !strings.HasPrefix(repo, "https://") {
  2134  			repo = fmt.Sprintf("https://%s", repo)
  2135  		}
  2136  
  2137  		cmd.Stdin = strings.NewReader(repo)
  2138  
  2139  		output, err := cmd.Output()
  2140  		if err != nil {
  2141  			switch err.(type) {
  2142  			default:
  2143  				return nil, err
  2144  			case *exec.ExitError:
  2145  				return nil, fmt.Errorf("%s with input %q failed with stderr: %s", helper, repo, output)
  2146  			}
  2147  		}
  2148  
  2149  		var response map[string]string
  2150  		if err := json.Unmarshal(output, &response); err != nil {
  2151  			return nil, err
  2152  		}
  2153  
  2154  		auth := &docker.AuthConfiguration{
  2155  			Username: response["Username"],
  2156  			Password: response["Secret"],
  2157  		}
  2158  
  2159  		if authIsEmpty(auth) {
  2160  			return nil, nil
  2161  		}
  2162  		return auth, nil
  2163  	}
  2164  }
  2165  
  2166  // authIsEmpty returns if auth is nil or an empty structure
  2167  func authIsEmpty(auth *docker.AuthConfiguration) bool {
  2168  	if auth == nil {
  2169  		return false
  2170  	}
  2171  	return auth.Username == "" &&
  2172  		auth.Password == "" &&
  2173  		auth.Email == "" &&
  2174  		auth.ServerAddress == ""
  2175  }
  2176  
  2177  // createContainerClient is the subset of Docker Client methods used by the
  2178  // createContainer method to ease testing subtle error conditions.
  2179  type createContainerClient interface {
  2180  	CreateContainer(docker.CreateContainerOptions) (*docker.Container, error)
  2181  	InspectContainer(id string) (*docker.Container, error)
  2182  	ListContainers(docker.ListContainersOptions) ([]docker.APIContainers, error)
  2183  	RemoveContainer(opts docker.RemoveContainerOptions) error
  2184  }