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