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