github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/cli/command/container/opts.go (about)

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