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