github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/runconfig/opts/parse.go (about)

     1  package opts
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"path"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/docker/docker/api/types/container"
    14  	networktypes "github.com/docker/docker/api/types/network"
    15  	"github.com/docker/docker/api/types/strslice"
    16  	"github.com/docker/docker/opts"
    17  	"github.com/docker/docker/pkg/mount"
    18  	"github.com/docker/docker/pkg/signal"
    19  	"github.com/docker/go-connections/nat"
    20  	units "github.com/docker/go-units"
    21  	"github.com/spf13/pflag"
    22  )
    23  
    24  // ContainerOptions is a data object with all the options for creating a container
    25  type ContainerOptions struct {
    26  	attach             opts.ListOpts
    27  	volumes            opts.ListOpts
    28  	tmpfs              opts.ListOpts
    29  	blkioWeightDevice  WeightdeviceOpt
    30  	deviceReadBps      ThrottledeviceOpt
    31  	deviceWriteBps     ThrottledeviceOpt
    32  	links              opts.ListOpts
    33  	aliases            opts.ListOpts
    34  	linkLocalIPs       opts.ListOpts
    35  	deviceReadIOps     ThrottledeviceOpt
    36  	deviceWriteIOps    ThrottledeviceOpt
    37  	env                opts.ListOpts
    38  	labels             opts.ListOpts
    39  	devices            opts.ListOpts
    40  	ulimits            *UlimitOpt
    41  	sysctls            *opts.MapOpts
    42  	publish            opts.ListOpts
    43  	expose             opts.ListOpts
    44  	dns                opts.ListOpts
    45  	dnsSearch          opts.ListOpts
    46  	dnsOptions         opts.ListOpts
    47  	extraHosts         opts.ListOpts
    48  	volumesFrom        opts.ListOpts
    49  	envFile            opts.ListOpts
    50  	capAdd             opts.ListOpts
    51  	capDrop            opts.ListOpts
    52  	groupAdd           opts.ListOpts
    53  	securityOpt        opts.ListOpts
    54  	storageOpt         opts.ListOpts
    55  	labelsFile         opts.ListOpts
    56  	loggingOpts        opts.ListOpts
    57  	privileged         bool
    58  	pidMode            string
    59  	utsMode            string
    60  	usernsMode         string
    61  	publishAll         bool
    62  	stdin              bool
    63  	tty                bool
    64  	oomKillDisable     bool
    65  	oomScoreAdj        int
    66  	containerIDFile    string
    67  	entrypoint         string
    68  	hostname           string
    69  	memoryString       string
    70  	memoryReservation  string
    71  	memorySwap         string
    72  	kernelMemory       string
    73  	user               string
    74  	workingDir         string
    75  	cpuShares          int64
    76  	cpuPercent         int64
    77  	cpuPeriod          int64
    78  	cpuRealtimePeriod  int64
    79  	cpuRealtimeRuntime int64
    80  	cpuQuota           int64
    81  	cpusetCpus         string
    82  	cpusetMems         string
    83  	blkioWeight        uint16
    84  	ioMaxBandwidth     string
    85  	ioMaxIOps          uint64
    86  	swappiness         int64
    87  	netMode            string
    88  	macAddress         string
    89  	ipv4Address        string
    90  	ipv6Address        string
    91  	ipcMode            string
    92  	pidsLimit          int64
    93  	restartPolicy      string
    94  	readonlyRootfs     bool
    95  	loggingDriver      string
    96  	cgroupParent       string
    97  	volumeDriver       string
    98  	stopSignal         string
    99  	stopTimeout        int
   100  	isolation          string
   101  	shmSize            string
   102  	noHealthcheck      bool
   103  	healthCmd          string
   104  	healthInterval     time.Duration
   105  	healthTimeout      time.Duration
   106  	healthRetries      int
   107  	runtime            string
   108  	autoRemove         bool
   109  	init               bool
   110  	initPath           string
   111  	credentialSpec     string
   112  
   113  	Image string
   114  	Args  []string
   115  }
   116  
   117  // AddFlags adds all command line flags that will be used by Parse to the FlagSet
   118  func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
   119  	copts := &ContainerOptions{
   120  		aliases:           opts.NewListOpts(nil),
   121  		attach:            opts.NewListOpts(ValidateAttach),
   122  		blkioWeightDevice: NewWeightdeviceOpt(ValidateWeightDevice),
   123  		capAdd:            opts.NewListOpts(nil),
   124  		capDrop:           opts.NewListOpts(nil),
   125  		dns:               opts.NewListOpts(opts.ValidateIPAddress),
   126  		dnsOptions:        opts.NewListOpts(nil),
   127  		dnsSearch:         opts.NewListOpts(opts.ValidateDNSSearch),
   128  		deviceReadBps:     NewThrottledeviceOpt(ValidateThrottleBpsDevice),
   129  		deviceReadIOps:    NewThrottledeviceOpt(ValidateThrottleIOpsDevice),
   130  		deviceWriteBps:    NewThrottledeviceOpt(ValidateThrottleBpsDevice),
   131  		deviceWriteIOps:   NewThrottledeviceOpt(ValidateThrottleIOpsDevice),
   132  		devices:           opts.NewListOpts(ValidateDevice),
   133  		env:               opts.NewListOpts(ValidateEnv),
   134  		envFile:           opts.NewListOpts(nil),
   135  		expose:            opts.NewListOpts(nil),
   136  		extraHosts:        opts.NewListOpts(ValidateExtraHost),
   137  		groupAdd:          opts.NewListOpts(nil),
   138  		labels:            opts.NewListOpts(ValidateEnv),
   139  		labelsFile:        opts.NewListOpts(nil),
   140  		linkLocalIPs:      opts.NewListOpts(nil),
   141  		links:             opts.NewListOpts(ValidateLink),
   142  		loggingOpts:       opts.NewListOpts(nil),
   143  		publish:           opts.NewListOpts(nil),
   144  		securityOpt:       opts.NewListOpts(nil),
   145  		storageOpt:        opts.NewListOpts(nil),
   146  		sysctls:           opts.NewMapOpts(nil, opts.ValidateSysctl),
   147  		tmpfs:             opts.NewListOpts(nil),
   148  		ulimits:           NewUlimitOpt(nil),
   149  		volumes:           opts.NewListOpts(nil),
   150  		volumesFrom:       opts.NewListOpts(nil),
   151  	}
   152  
   153  	// General purpose flags
   154  	flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR")
   155  	flags.Var(&copts.devices, "device", "Add a host device to the container")
   156  	flags.VarP(&copts.env, "env", "e", "Set environment variables")
   157  	flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables")
   158  	flags.StringVar(&copts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image")
   159  	flags.Var(&copts.groupAdd, "group-add", "Add additional groups to join")
   160  	flags.StringVarP(&copts.hostname, "hostname", "h", "", "Container host name")
   161  	flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached")
   162  	flags.VarP(&copts.labels, "label", "l", "Set meta data on a container")
   163  	flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels")
   164  	flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only")
   165  	flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits")
   166  	flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
   167  	flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container")
   168  	flags.Var(copts.sysctls, "sysctl", "Sysctl options")
   169  	flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
   170  	flags.Var(copts.ulimits, "ulimit", "Ulimit options")
   171  	flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
   172  	flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container")
   173  	flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits")
   174  
   175  	// Security
   176  	flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities")
   177  	flags.Var(&copts.capDrop, "cap-drop", "Drop Linux capabilities")
   178  	flags.BoolVar(&copts.privileged, "privileged", false, "Give extended privileges to this container")
   179  	flags.Var(&copts.securityOpt, "security-opt", "Security Options")
   180  	flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use")
   181  	flags.StringVar(&copts.credentialSpec, "credentialspec", "", "Credential spec for managed service account (Windows only)")
   182  
   183  	// Network and port publishing flag
   184  	flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
   185  	flags.Var(&copts.dns, "dns", "Set custom DNS servers")
   186  	flags.Var(&copts.dnsOptions, "dns-opt", "Set DNS options")
   187  	flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains")
   188  	flags.Var(&copts.expose, "expose", "Expose a port or a range of ports")
   189  	flags.StringVar(&copts.ipv4Address, "ip", "", "Container IPv4 address (e.g. 172.30.100.104)")
   190  	flags.StringVar(&copts.ipv6Address, "ip6", "", "Container IPv6 address (e.g. 2001:db8::33)")
   191  	flags.Var(&copts.links, "link", "Add link to another container")
   192  	flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses")
   193  	flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
   194  	flags.VarP(&copts.publish, "publish", "p", "Publish a container's port(s) to the host")
   195  	flags.BoolVarP(&copts.publishAll, "publish-all", "P", false, "Publish all exposed ports to random ports")
   196  	// We allow for both "--net" and "--network", although the latter is the recommended way.
   197  	flags.StringVar(&copts.netMode, "net", "default", "Connect a container to a network")
   198  	flags.StringVar(&copts.netMode, "network", "default", "Connect a container to a network")
   199  	flags.MarkHidden("net")
   200  	// We allow for both "--net-alias" and "--network-alias", although the latter is the recommended way.
   201  	flags.Var(&copts.aliases, "net-alias", "Add network-scoped alias for the container")
   202  	flags.Var(&copts.aliases, "network-alias", "Add network-scoped alias for the container")
   203  	flags.MarkHidden("net-alias")
   204  
   205  	// Logging and storage
   206  	flags.StringVar(&copts.loggingDriver, "log-driver", "", "Logging driver for the container")
   207  	flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container")
   208  	flags.Var(&copts.loggingOpts, "log-opt", "Log driver options")
   209  	flags.Var(&copts.storageOpt, "storage-opt", "Storage driver options for the container")
   210  	flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory")
   211  	flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
   212  	flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume")
   213  
   214  	// Health-checking
   215  	flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health")
   216  	flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check")
   217  	flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy")
   218  	flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run")
   219  	flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
   220  
   221  	// Resource management
   222  	flags.Uint16Var(&copts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000")
   223  	flags.Var(&copts.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)")
   224  	flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file")
   225  	flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
   226  	flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
   227  	flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)")
   228  	flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
   229  	flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
   230  	flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds")
   231  	flags.Int64Var(&copts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds")
   232  	flags.Int64VarP(&copts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
   233  	flags.Var(&copts.deviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device")
   234  	flags.Var(&copts.deviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device")
   235  	flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device")
   236  	flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device")
   237  	flags.StringVar(&copts.ioMaxBandwidth, "io-maxbandwidth", "", "Maximum IO bandwidth limit for the system drive (Windows only)")
   238  	flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)")
   239  	flags.StringVar(&copts.kernelMemory, "kernel-memory", "", "Kernel memory limit")
   240  	flags.StringVarP(&copts.memoryString, "memory", "m", "", "Memory limit")
   241  	flags.StringVar(&copts.memoryReservation, "memory-reservation", "", "Memory soft limit")
   242  	flags.StringVar(&copts.memorySwap, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
   243  	flags.Int64Var(&copts.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)")
   244  	flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer")
   245  	flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)")
   246  	flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)")
   247  
   248  	// Low-level execution (cgroups, namespaces, ...)
   249  	flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
   250  	flags.StringVar(&copts.ipcMode, "ipc", "", "IPC namespace to use")
   251  	flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology")
   252  	flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use")
   253  	flags.StringVar(&copts.shmSize, "shm-size", "", "Size of /dev/shm, default value is 64MB")
   254  	flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use")
   255  	flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container")
   256  
   257  	flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes")
   258  	flags.StringVar(&copts.initPath, "init-path", "", "Path to the docker-init binary")
   259  	return copts
   260  }
   261  
   262  // Parse parses the args for the specified command and generates a Config,
   263  // a HostConfig and returns them with the specified command.
   264  // If the specified args are not valid, it will return an error.
   265  func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
   266  	var (
   267  		attachStdin  = copts.attach.Get("stdin")
   268  		attachStdout = copts.attach.Get("stdout")
   269  		attachStderr = copts.attach.Get("stderr")
   270  	)
   271  
   272  	// Validate the input mac address
   273  	if copts.macAddress != "" {
   274  		if _, err := ValidateMACAddress(copts.macAddress); err != nil {
   275  			return nil, nil, nil, fmt.Errorf("%s is not a valid mac address", copts.macAddress)
   276  		}
   277  	}
   278  	if copts.stdin {
   279  		attachStdin = true
   280  	}
   281  	// If -a is not set, attach to stdout and stderr
   282  	if copts.attach.Len() == 0 {
   283  		attachStdout = true
   284  		attachStderr = true
   285  	}
   286  
   287  	var err error
   288  
   289  	var memory int64
   290  	if copts.memoryString != "" {
   291  		memory, err = units.RAMInBytes(copts.memoryString)
   292  		if err != nil {
   293  			return nil, nil, nil, err
   294  		}
   295  	}
   296  
   297  	var memoryReservation int64
   298  	if copts.memoryReservation != "" {
   299  		memoryReservation, err = units.RAMInBytes(copts.memoryReservation)
   300  		if err != nil {
   301  			return nil, nil, nil, err
   302  		}
   303  	}
   304  
   305  	var memorySwap int64
   306  	if copts.memorySwap != "" {
   307  		if copts.memorySwap == "-1" {
   308  			memorySwap = -1
   309  		} else {
   310  			memorySwap, err = units.RAMInBytes(copts.memorySwap)
   311  			if err != nil {
   312  				return nil, nil, nil, err
   313  			}
   314  		}
   315  	}
   316  
   317  	var kernelMemory int64
   318  	if copts.kernelMemory != "" {
   319  		kernelMemory, err = units.RAMInBytes(copts.kernelMemory)
   320  		if err != nil {
   321  			return nil, nil, nil, err
   322  		}
   323  	}
   324  
   325  	swappiness := copts.swappiness
   326  	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
   327  		return nil, nil, nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
   328  	}
   329  
   330  	var shmSize int64
   331  	if copts.shmSize != "" {
   332  		shmSize, err = units.RAMInBytes(copts.shmSize)
   333  		if err != nil {
   334  			return nil, nil, nil, err
   335  		}
   336  	}
   337  
   338  	// TODO FIXME units.RAMInBytes should have a uint64 version
   339  	var maxIOBandwidth int64
   340  	if copts.ioMaxBandwidth != "" {
   341  		maxIOBandwidth, err = units.RAMInBytes(copts.ioMaxBandwidth)
   342  		if err != nil {
   343  			return nil, nil, nil, err
   344  		}
   345  		if maxIOBandwidth < 0 {
   346  			return nil, nil, nil, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", copts.ioMaxBandwidth)
   347  		}
   348  	}
   349  
   350  	var binds []string
   351  	// add any bind targets to the list of container volumes
   352  	for bind := range copts.volumes.GetMap() {
   353  		if arr := volumeSplitN(bind, 2); len(arr) > 1 {
   354  			// after creating the bind mount we want to delete it from the copts.volumes values because
   355  			// we do not want bind mounts being committed to image configs
   356  			binds = append(binds, bind)
   357  			copts.volumes.Delete(bind)
   358  		}
   359  	}
   360  
   361  	// Can't evaluate options passed into --tmpfs until we actually mount
   362  	tmpfs := make(map[string]string)
   363  	for _, t := range copts.tmpfs.GetAll() {
   364  		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
   365  			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
   366  				return nil, nil, nil, err
   367  			}
   368  			tmpfs[arr[0]] = arr[1]
   369  		} else {
   370  			tmpfs[arr[0]] = ""
   371  		}
   372  	}
   373  
   374  	var (
   375  		runCmd     strslice.StrSlice
   376  		entrypoint strslice.StrSlice
   377  	)
   378  
   379  	if len(copts.Args) > 0 {
   380  		runCmd = strslice.StrSlice(copts.Args)
   381  	}
   382  
   383  	if copts.entrypoint != "" {
   384  		entrypoint = strslice.StrSlice{copts.entrypoint}
   385  	} else if flags.Changed("entrypoint") {
   386  		// if `--entrypoint=` is parsed then Entrypoint is reset
   387  		entrypoint = []string{""}
   388  	}
   389  
   390  	ports, portBindings, err := nat.ParsePortSpecs(copts.publish.GetAll())
   391  	if err != nil {
   392  		return nil, nil, nil, err
   393  	}
   394  
   395  	// Merge in exposed ports to the map of published ports
   396  	for _, e := range copts.expose.GetAll() {
   397  		if strings.Contains(e, ":") {
   398  			return nil, nil, nil, fmt.Errorf("invalid port format for --expose: %s", e)
   399  		}
   400  		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
   401  		proto, port := nat.SplitProtoPort(e)
   402  		//parse the start and end port and create a sequence of ports to expose
   403  		//if expose a port, the start and end port are the same
   404  		start, end, err := nat.ParsePortRange(port)
   405  		if err != nil {
   406  			return nil, nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err)
   407  		}
   408  		for i := start; i <= end; i++ {
   409  			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
   410  			if err != nil {
   411  				return nil, nil, nil, err
   412  			}
   413  			if _, exists := ports[p]; !exists {
   414  				ports[p] = struct{}{}
   415  			}
   416  		}
   417  	}
   418  
   419  	// parse device mappings
   420  	deviceMappings := []container.DeviceMapping{}
   421  	for _, device := range copts.devices.GetAll() {
   422  		deviceMapping, err := ParseDevice(device)
   423  		if err != nil {
   424  			return nil, nil, nil, err
   425  		}
   426  		deviceMappings = append(deviceMappings, deviceMapping)
   427  	}
   428  
   429  	// collect all the environment variables for the container
   430  	envVariables, err := readKVStrings(copts.envFile.GetAll(), copts.env.GetAll())
   431  	if err != nil {
   432  		return nil, nil, nil, err
   433  	}
   434  
   435  	// collect all the labels for the container
   436  	labels, err := readKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll())
   437  	if err != nil {
   438  		return nil, nil, nil, err
   439  	}
   440  
   441  	ipcMode := container.IpcMode(copts.ipcMode)
   442  	if !ipcMode.Valid() {
   443  		return nil, nil, nil, fmt.Errorf("--ipc: invalid IPC mode")
   444  	}
   445  
   446  	pidMode := container.PidMode(copts.pidMode)
   447  	if !pidMode.Valid() {
   448  		return nil, nil, nil, fmt.Errorf("--pid: invalid PID mode")
   449  	}
   450  
   451  	utsMode := container.UTSMode(copts.utsMode)
   452  	if !utsMode.Valid() {
   453  		return nil, nil, nil, fmt.Errorf("--uts: invalid UTS mode")
   454  	}
   455  
   456  	usernsMode := container.UsernsMode(copts.usernsMode)
   457  	if !usernsMode.Valid() {
   458  		return nil, nil, nil, fmt.Errorf("--userns: invalid USER mode")
   459  	}
   460  
   461  	restartPolicy, err := ParseRestartPolicy(copts.restartPolicy)
   462  	if err != nil {
   463  		return nil, nil, nil, err
   464  	}
   465  
   466  	loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll())
   467  	if err != nil {
   468  		return nil, nil, nil, err
   469  	}
   470  
   471  	securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll())
   472  	if err != nil {
   473  		return nil, nil, nil, err
   474  	}
   475  
   476  	storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll())
   477  	if err != nil {
   478  		return nil, nil, nil, err
   479  	}
   480  
   481  	// Healthcheck
   482  	var healthConfig *container.HealthConfig
   483  	haveHealthSettings := copts.healthCmd != "" ||
   484  		copts.healthInterval != 0 ||
   485  		copts.healthTimeout != 0 ||
   486  		copts.healthRetries != 0
   487  	if copts.noHealthcheck {
   488  		if haveHealthSettings {
   489  			return nil, nil, nil, fmt.Errorf("--no-healthcheck conflicts with --health-* options")
   490  		}
   491  		test := strslice.StrSlice{"NONE"}
   492  		healthConfig = &container.HealthConfig{Test: test}
   493  	} else if haveHealthSettings {
   494  		var probe strslice.StrSlice
   495  		if copts.healthCmd != "" {
   496  			args := []string{"CMD-SHELL", copts.healthCmd}
   497  			probe = strslice.StrSlice(args)
   498  		}
   499  		if copts.healthInterval < 0 {
   500  			return nil, nil, nil, fmt.Errorf("--health-interval cannot be negative")
   501  		}
   502  		if copts.healthTimeout < 0 {
   503  			return nil, nil, nil, fmt.Errorf("--health-timeout cannot be negative")
   504  		}
   505  
   506  		healthConfig = &container.HealthConfig{
   507  			Test:     probe,
   508  			Interval: copts.healthInterval,
   509  			Timeout:  copts.healthTimeout,
   510  			Retries:  copts.healthRetries,
   511  		}
   512  	}
   513  
   514  	resources := container.Resources{
   515  		CgroupParent:         copts.cgroupParent,
   516  		Memory:               memory,
   517  		MemoryReservation:    memoryReservation,
   518  		MemorySwap:           memorySwap,
   519  		MemorySwappiness:     &copts.swappiness,
   520  		KernelMemory:         kernelMemory,
   521  		OomKillDisable:       &copts.oomKillDisable,
   522  		CPUPercent:           copts.cpuPercent,
   523  		CPUShares:            copts.cpuShares,
   524  		CPUPeriod:            copts.cpuPeriod,
   525  		CpusetCpus:           copts.cpusetCpus,
   526  		CpusetMems:           copts.cpusetMems,
   527  		CPUQuota:             copts.cpuQuota,
   528  		CPURealtimePeriod:    copts.cpuRealtimePeriod,
   529  		CPURealtimeRuntime:   copts.cpuRealtimeRuntime,
   530  		PidsLimit:            copts.pidsLimit,
   531  		BlkioWeight:          copts.blkioWeight,
   532  		BlkioWeightDevice:    copts.blkioWeightDevice.GetList(),
   533  		BlkioDeviceReadBps:   copts.deviceReadBps.GetList(),
   534  		BlkioDeviceWriteBps:  copts.deviceWriteBps.GetList(),
   535  		BlkioDeviceReadIOps:  copts.deviceReadIOps.GetList(),
   536  		BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(),
   537  		IOMaximumIOps:        copts.ioMaxIOps,
   538  		IOMaximumBandwidth:   uint64(maxIOBandwidth),
   539  		Ulimits:              copts.ulimits.GetList(),
   540  		Devices:              deviceMappings,
   541  	}
   542  
   543  	config := &container.Config{
   544  		Hostname:     copts.hostname,
   545  		ExposedPorts: ports,
   546  		User:         copts.user,
   547  		Tty:          copts.tty,
   548  		// TODO: deprecated, it comes from -n, --networking
   549  		// it's still needed internally to set the network to disabled
   550  		// if e.g. bridge is none in daemon opts, and in inspect
   551  		NetworkDisabled: false,
   552  		OpenStdin:       copts.stdin,
   553  		AttachStdin:     attachStdin,
   554  		AttachStdout:    attachStdout,
   555  		AttachStderr:    attachStderr,
   556  		Env:             envVariables,
   557  		Cmd:             runCmd,
   558  		Image:           copts.Image,
   559  		Volumes:         copts.volumes.GetMap(),
   560  		MacAddress:      copts.macAddress,
   561  		Entrypoint:      entrypoint,
   562  		WorkingDir:      copts.workingDir,
   563  		Labels:          ConvertKVStringsToMap(labels),
   564  		Healthcheck:     healthConfig,
   565  	}
   566  	if flags.Changed("stop-signal") {
   567  		config.StopSignal = copts.stopSignal
   568  	}
   569  	if flags.Changed("stop-timeout") {
   570  		config.StopTimeout = &copts.stopTimeout
   571  	}
   572  
   573  	hostConfig := &container.HostConfig{
   574  		Binds:           binds,
   575  		ContainerIDFile: copts.containerIDFile,
   576  		OomScoreAdj:     copts.oomScoreAdj,
   577  		AutoRemove:      copts.autoRemove,
   578  		Privileged:      copts.privileged,
   579  		PortBindings:    portBindings,
   580  		Links:           copts.links.GetAll(),
   581  		PublishAllPorts: copts.publishAll,
   582  		// Make sure the dns fields are never nil.
   583  		// New containers don't ever have those fields nil,
   584  		// but pre created containers can still have those nil values.
   585  		// See https://github.com/docker/docker/pull/17779
   586  		// for a more detailed explanation on why we don't want that.
   587  		DNS:            copts.dns.GetAllOrEmpty(),
   588  		DNSSearch:      copts.dnsSearch.GetAllOrEmpty(),
   589  		DNSOptions:     copts.dnsOptions.GetAllOrEmpty(),
   590  		ExtraHosts:     copts.extraHosts.GetAll(),
   591  		VolumesFrom:    copts.volumesFrom.GetAll(),
   592  		NetworkMode:    container.NetworkMode(copts.netMode),
   593  		IpcMode:        ipcMode,
   594  		PidMode:        pidMode,
   595  		UTSMode:        utsMode,
   596  		UsernsMode:     usernsMode,
   597  		CapAdd:         strslice.StrSlice(copts.capAdd.GetAll()),
   598  		CapDrop:        strslice.StrSlice(copts.capDrop.GetAll()),
   599  		GroupAdd:       copts.groupAdd.GetAll(),
   600  		RestartPolicy:  restartPolicy,
   601  		SecurityOpt:    securityOpts,
   602  		StorageOpt:     storageOpts,
   603  		ReadonlyRootfs: copts.readonlyRootfs,
   604  		LogConfig:      container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts},
   605  		VolumeDriver:   copts.volumeDriver,
   606  		Isolation:      container.Isolation(copts.isolation),
   607  		ShmSize:        shmSize,
   608  		Resources:      resources,
   609  		Tmpfs:          tmpfs,
   610  		Sysctls:        copts.sysctls.GetAll(),
   611  		Runtime:        copts.runtime,
   612  	}
   613  
   614  	// only set this value if the user provided the flag, else it should default to nil
   615  	if flags.Changed("init") {
   616  		hostConfig.Init = &copts.init
   617  	}
   618  
   619  	// When allocating stdin in attached mode, close stdin at client disconnect
   620  	if config.OpenStdin && config.AttachStdin {
   621  		config.StdinOnce = true
   622  	}
   623  
   624  	networkingConfig := &networktypes.NetworkingConfig{
   625  		EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
   626  	}
   627  
   628  	if copts.ipv4Address != "" || copts.ipv6Address != "" || copts.linkLocalIPs.Len() > 0 {
   629  		epConfig := &networktypes.EndpointSettings{}
   630  		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
   631  
   632  		epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{
   633  			IPv4Address: copts.ipv4Address,
   634  			IPv6Address: copts.ipv6Address,
   635  		}
   636  
   637  		if copts.linkLocalIPs.Len() > 0 {
   638  			epConfig.IPAMConfig.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len())
   639  			copy(epConfig.IPAMConfig.LinkLocalIPs, copts.linkLocalIPs.GetAll())
   640  		}
   641  	}
   642  
   643  	if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 {
   644  		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
   645  		if epConfig == nil {
   646  			epConfig = &networktypes.EndpointSettings{}
   647  		}
   648  		epConfig.Links = make([]string, len(hostConfig.Links))
   649  		copy(epConfig.Links, hostConfig.Links)
   650  		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
   651  	}
   652  
   653  	if copts.aliases.Len() > 0 {
   654  		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
   655  		if epConfig == nil {
   656  			epConfig = &networktypes.EndpointSettings{}
   657  		}
   658  		epConfig.Aliases = make([]string, copts.aliases.Len())
   659  		copy(epConfig.Aliases, copts.aliases.GetAll())
   660  		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
   661  	}
   662  
   663  	return config, hostConfig, networkingConfig, nil
   664  }
   665  
   666  // reads a file of line terminated key=value pairs, and overrides any keys
   667  // present in the file with additional pairs specified in the override parameter
   668  func readKVStrings(files []string, override []string) ([]string, error) {
   669  	envVariables := []string{}
   670  	for _, ef := range files {
   671  		parsedVars, err := ParseEnvFile(ef)
   672  		if err != nil {
   673  			return nil, err
   674  		}
   675  		envVariables = append(envVariables, parsedVars...)
   676  	}
   677  	// parse the '-e' and '--env' after, to allow override
   678  	envVariables = append(envVariables, override...)
   679  
   680  	return envVariables, nil
   681  }
   682  
   683  // ConvertKVStringsToMap converts ["key=value"] to {"key":"value"}
   684  func ConvertKVStringsToMap(values []string) map[string]string {
   685  	result := make(map[string]string, len(values))
   686  	for _, value := range values {
   687  		kv := strings.SplitN(value, "=", 2)
   688  		if len(kv) == 1 {
   689  			result[kv[0]] = ""
   690  		} else {
   691  			result[kv[0]] = kv[1]
   692  		}
   693  	}
   694  
   695  	return result
   696  }
   697  
   698  func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
   699  	loggingOptsMap := ConvertKVStringsToMap(loggingOpts)
   700  	if loggingDriver == "none" && len(loggingOpts) > 0 {
   701  		return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver)
   702  	}
   703  	return loggingOptsMap, nil
   704  }
   705  
   706  // takes a local seccomp daemon, reads the file contents for sending to the daemon
   707  func parseSecurityOpts(securityOpts []string) ([]string, error) {
   708  	for key, opt := range securityOpts {
   709  		con := strings.SplitN(opt, "=", 2)
   710  		if len(con) == 1 && con[0] != "no-new-privileges" {
   711  			if strings.Contains(opt, ":") {
   712  				con = strings.SplitN(opt, ":", 2)
   713  			} else {
   714  				return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt)
   715  			}
   716  		}
   717  		if con[0] == "seccomp" && con[1] != "unconfined" {
   718  			f, err := ioutil.ReadFile(con[1])
   719  			if err != nil {
   720  				return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
   721  			}
   722  			b := bytes.NewBuffer(nil)
   723  			if err := json.Compact(b, f); err != nil {
   724  				return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err)
   725  			}
   726  			securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
   727  		}
   728  	}
   729  
   730  	return securityOpts, nil
   731  }
   732  
   733  // parses storage options per container into a map
   734  func parseStorageOpts(storageOpts []string) (map[string]string, error) {
   735  	m := make(map[string]string)
   736  	for _, option := range storageOpts {
   737  		if strings.Contains(option, "=") {
   738  			opt := strings.SplitN(option, "=", 2)
   739  			m[opt[0]] = opt[1]
   740  		} else {
   741  			return nil, fmt.Errorf("Invalid storage option.")
   742  		}
   743  	}
   744  	return m, nil
   745  }
   746  
   747  // ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect
   748  func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
   749  	p := container.RestartPolicy{}
   750  
   751  	if policy == "" {
   752  		return p, nil
   753  	}
   754  
   755  	parts := strings.Split(policy, ":")
   756  
   757  	if len(parts) > 2 {
   758  		return p, fmt.Errorf("invalid restart policy format")
   759  	}
   760  	if len(parts) == 2 {
   761  		count, err := strconv.Atoi(parts[1])
   762  		if err != nil {
   763  			return p, fmt.Errorf("maximum retry count must be an integer")
   764  		}
   765  
   766  		p.MaximumRetryCount = count
   767  	}
   768  
   769  	p.Name = parts[0]
   770  
   771  	return p, nil
   772  }
   773  
   774  // ParseDevice parses a device mapping string to a container.DeviceMapping struct
   775  func ParseDevice(device string) (container.DeviceMapping, error) {
   776  	src := ""
   777  	dst := ""
   778  	permissions := "rwm"
   779  	arr := strings.Split(device, ":")
   780  	switch len(arr) {
   781  	case 3:
   782  		permissions = arr[2]
   783  		fallthrough
   784  	case 2:
   785  		if ValidDeviceMode(arr[1]) {
   786  			permissions = arr[1]
   787  		} else {
   788  			dst = arr[1]
   789  		}
   790  		fallthrough
   791  	case 1:
   792  		src = arr[0]
   793  	default:
   794  		return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device)
   795  	}
   796  
   797  	if dst == "" {
   798  		dst = src
   799  	}
   800  
   801  	deviceMapping := container.DeviceMapping{
   802  		PathOnHost:        src,
   803  		PathInContainer:   dst,
   804  		CgroupPermissions: permissions,
   805  	}
   806  	return deviceMapping, nil
   807  }
   808  
   809  // ParseLink parses and validates the specified string as a link format (name:alias)
   810  func ParseLink(val string) (string, string, error) {
   811  	if val == "" {
   812  		return "", "", fmt.Errorf("empty string specified for links")
   813  	}
   814  	arr := strings.Split(val, ":")
   815  	if len(arr) > 2 {
   816  		return "", "", fmt.Errorf("bad format for links: %s", val)
   817  	}
   818  	if len(arr) == 1 {
   819  		return val, val, nil
   820  	}
   821  	// This is kept because we can actually get a HostConfig with links
   822  	// from an already created container and the format is not `foo:bar`
   823  	// but `/foo:/c1/bar`
   824  	if strings.HasPrefix(arr[0], "/") {
   825  		_, alias := path.Split(arr[1])
   826  		return arr[0][1:], alias, nil
   827  	}
   828  	return arr[0], arr[1], nil
   829  }
   830  
   831  // ValidateLink validates that the specified string has a valid link format (containerName:alias).
   832  func ValidateLink(val string) (string, error) {
   833  	if _, _, err := ParseLink(val); err != nil {
   834  		return val, err
   835  	}
   836  	return val, nil
   837  }
   838  
   839  // ValidDeviceMode checks if the mode for device is valid or not.
   840  // Valid mode is a composition of r (read), w (write), and m (mknod).
   841  func ValidDeviceMode(mode string) bool {
   842  	var legalDeviceMode = map[rune]bool{
   843  		'r': true,
   844  		'w': true,
   845  		'm': true,
   846  	}
   847  	if mode == "" {
   848  		return false
   849  	}
   850  	for _, c := range mode {
   851  		if !legalDeviceMode[c] {
   852  			return false
   853  		}
   854  		legalDeviceMode[c] = false
   855  	}
   856  	return true
   857  }
   858  
   859  // ValidateDevice validates a path for devices
   860  // It will make sure 'val' is in the form:
   861  //    [host-dir:]container-path[:mode]
   862  // It also validates the device mode.
   863  func ValidateDevice(val string) (string, error) {
   864  	return validatePath(val, ValidDeviceMode)
   865  }
   866  
   867  func validatePath(val string, validator func(string) bool) (string, error) {
   868  	var containerPath string
   869  	var mode string
   870  
   871  	if strings.Count(val, ":") > 2 {
   872  		return val, fmt.Errorf("bad format for path: %s", val)
   873  	}
   874  
   875  	split := strings.SplitN(val, ":", 3)
   876  	if split[0] == "" {
   877  		return val, fmt.Errorf("bad format for path: %s", val)
   878  	}
   879  	switch len(split) {
   880  	case 1:
   881  		containerPath = split[0]
   882  		val = path.Clean(containerPath)
   883  	case 2:
   884  		if isValid := validator(split[1]); isValid {
   885  			containerPath = split[0]
   886  			mode = split[1]
   887  			val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
   888  		} else {
   889  			containerPath = split[1]
   890  			val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
   891  		}
   892  	case 3:
   893  		containerPath = split[1]
   894  		mode = split[2]
   895  		if isValid := validator(split[2]); !isValid {
   896  			return val, fmt.Errorf("bad mode specified: %s", mode)
   897  		}
   898  		val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
   899  	}
   900  
   901  	if !path.IsAbs(containerPath) {
   902  		return val, fmt.Errorf("%s is not an absolute path", containerPath)
   903  	}
   904  	return val, nil
   905  }
   906  
   907  // volumeSplitN splits raw into a maximum of n parts, separated by a separator colon.
   908  // A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
   909  // In Windows driver letter appears in two situations:
   910  // a. `^[a-zA-Z]:` (A colon followed  by `^[a-zA-Z]:` is OK as colon is the separator in volume option)
   911  // b. A string in the format like `\\?\C:\Windows\...` (UNC).
   912  // Therefore, a driver letter can only follow either a `:` or `\\`
   913  // This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`.
   914  func volumeSplitN(raw string, n int) []string {
   915  	var array []string
   916  	if len(raw) == 0 || raw[0] == ':' {
   917  		// invalid
   918  		return nil
   919  	}
   920  	// numberOfParts counts the number of parts separated by a separator colon
   921  	numberOfParts := 0
   922  	// left represents the left-most cursor in raw, updated at every `:` character considered as a separator.
   923  	left := 0
   924  	// right represents the right-most cursor in raw incremented with the loop. Note this
   925  	// starts at index 1 as index 0 is already handle above as a special case.
   926  	for right := 1; right < len(raw); right++ {
   927  		// stop parsing if reached maximum number of parts
   928  		if n >= 0 && numberOfParts >= n {
   929  			break
   930  		}
   931  		if raw[right] != ':' {
   932  			continue
   933  		}
   934  		potentialDriveLetter := raw[right-1]
   935  		if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
   936  			if right > 1 {
   937  				beforePotentialDriveLetter := raw[right-2]
   938  				// Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`)
   939  				if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' {
   940  					// e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
   941  					array = append(array, raw[left:right])
   942  					left = right + 1
   943  					numberOfParts++
   944  				}
   945  				// else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing.
   946  			}
   947  			// if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing.
   948  		} else {
   949  			// if `:` is not preceded by a potential drive letter, then consider it as a delimiter.
   950  			array = append(array, raw[left:right])
   951  			left = right + 1
   952  			numberOfParts++
   953  		}
   954  	}
   955  	// need to take care of the last part
   956  	if left < len(raw) {
   957  		if n >= 0 && numberOfParts >= n {
   958  			// if the maximum number of parts is reached, just append the rest to the last part
   959  			// left-1 is at the last `:` that needs to be included since not considered a separator.
   960  			array[n-1] += raw[left-1:]
   961  		} else {
   962  			array = append(array, raw[left:])
   963  		}
   964  	}
   965  	return array
   966  }