github.com/yogeshlonkar/moby@v1.13.2-0.20201203103638-c0b64beaea94/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  	if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() {
   625  		return nil, nil, nil, fmt.Errorf("Conflicting options: --restart and --rm")
   626  	}
   627  
   628  	// only set this value if the user provided the flag, else it should default to nil
   629  	if flags.Changed("init") {
   630  		hostConfig.Init = &copts.init
   631  	}
   632  
   633  	// When allocating stdin in attached mode, close stdin at client disconnect
   634  	if config.OpenStdin && config.AttachStdin {
   635  		config.StdinOnce = true
   636  	}
   637  
   638  	networkingConfig := &networktypes.NetworkingConfig{
   639  		EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
   640  	}
   641  
   642  	if copts.ipv4Address != "" || copts.ipv6Address != "" || copts.linkLocalIPs.Len() > 0 {
   643  		epConfig := &networktypes.EndpointSettings{}
   644  		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
   645  
   646  		epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{
   647  			IPv4Address: copts.ipv4Address,
   648  			IPv6Address: copts.ipv6Address,
   649  		}
   650  
   651  		if copts.linkLocalIPs.Len() > 0 {
   652  			epConfig.IPAMConfig.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len())
   653  			copy(epConfig.IPAMConfig.LinkLocalIPs, copts.linkLocalIPs.GetAll())
   654  		}
   655  	}
   656  
   657  	if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 {
   658  		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
   659  		if epConfig == nil {
   660  			epConfig = &networktypes.EndpointSettings{}
   661  		}
   662  		epConfig.Links = make([]string, len(hostConfig.Links))
   663  		copy(epConfig.Links, hostConfig.Links)
   664  		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
   665  	}
   666  
   667  	if copts.aliases.Len() > 0 {
   668  		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
   669  		if epConfig == nil {
   670  			epConfig = &networktypes.EndpointSettings{}
   671  		}
   672  		epConfig.Aliases = make([]string, copts.aliases.Len())
   673  		copy(epConfig.Aliases, copts.aliases.GetAll())
   674  		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
   675  	}
   676  
   677  	return config, hostConfig, networkingConfig, nil
   678  }
   679  
   680  // ReadKVStrings reads a file of line terminated key=value pairs, and overrides any keys
   681  // present in the file with additional pairs specified in the override parameter
   682  func ReadKVStrings(files []string, override []string) ([]string, error) {
   683  	envVariables := []string{}
   684  	for _, ef := range files {
   685  		parsedVars, err := ParseEnvFile(ef)
   686  		if err != nil {
   687  			return nil, err
   688  		}
   689  		envVariables = append(envVariables, parsedVars...)
   690  	}
   691  	// parse the '-e' and '--env' after, to allow override
   692  	envVariables = append(envVariables, override...)
   693  
   694  	return envVariables, nil
   695  }
   696  
   697  // ConvertKVStringsToMap converts ["key=value"] to {"key":"value"}
   698  func ConvertKVStringsToMap(values []string) map[string]string {
   699  	result := make(map[string]string, len(values))
   700  	for _, value := range values {
   701  		kv := strings.SplitN(value, "=", 2)
   702  		if len(kv) == 1 {
   703  			result[kv[0]] = ""
   704  		} else {
   705  			result[kv[0]] = kv[1]
   706  		}
   707  	}
   708  
   709  	return result
   710  }
   711  
   712  // ConvertKVStringsToMapWithNil converts ["key=value"] to {"key":"value"}
   713  // but set unset keys to nil - meaning the ones with no "=" in them.
   714  // We use this in cases where we need to distinguish between
   715  //   FOO=  and FOO
   716  // where the latter case just means FOO was mentioned but not given a value
   717  func ConvertKVStringsToMapWithNil(values []string) map[string]*string {
   718  	result := make(map[string]*string, len(values))
   719  	for _, value := range values {
   720  		kv := strings.SplitN(value, "=", 2)
   721  		if len(kv) == 1 {
   722  			result[kv[0]] = nil
   723  		} else {
   724  			result[kv[0]] = &kv[1]
   725  		}
   726  	}
   727  
   728  	return result
   729  }
   730  
   731  func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
   732  	loggingOptsMap := ConvertKVStringsToMap(loggingOpts)
   733  	if loggingDriver == "none" && len(loggingOpts) > 0 {
   734  		return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver)
   735  	}
   736  	return loggingOptsMap, nil
   737  }
   738  
   739  // takes a local seccomp daemon, reads the file contents for sending to the daemon
   740  func parseSecurityOpts(securityOpts []string) ([]string, error) {
   741  	for key, opt := range securityOpts {
   742  		con := strings.SplitN(opt, "=", 2)
   743  		if len(con) == 1 && con[0] != "no-new-privileges" {
   744  			if strings.Contains(opt, ":") {
   745  				con = strings.SplitN(opt, ":", 2)
   746  			} else {
   747  				return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt)
   748  			}
   749  		}
   750  		if con[0] == "seccomp" && con[1] != "unconfined" {
   751  			f, err := ioutil.ReadFile(con[1])
   752  			if err != nil {
   753  				return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
   754  			}
   755  			b := bytes.NewBuffer(nil)
   756  			if err := json.Compact(b, f); err != nil {
   757  				return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err)
   758  			}
   759  			securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
   760  		}
   761  	}
   762  
   763  	return securityOpts, nil
   764  }
   765  
   766  // parses storage options per container into a map
   767  func parseStorageOpts(storageOpts []string) (map[string]string, error) {
   768  	m := make(map[string]string)
   769  	for _, option := range storageOpts {
   770  		if strings.Contains(option, "=") {
   771  			opt := strings.SplitN(option, "=", 2)
   772  			m[opt[0]] = opt[1]
   773  		} else {
   774  			return nil, fmt.Errorf("invalid storage option")
   775  		}
   776  	}
   777  	return m, nil
   778  }
   779  
   780  // ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect
   781  func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
   782  	p := container.RestartPolicy{}
   783  
   784  	if policy == "" {
   785  		return p, nil
   786  	}
   787  
   788  	parts := strings.Split(policy, ":")
   789  
   790  	if len(parts) > 2 {
   791  		return p, fmt.Errorf("invalid restart policy format")
   792  	}
   793  	if len(parts) == 2 {
   794  		count, err := strconv.Atoi(parts[1])
   795  		if err != nil {
   796  			return p, fmt.Errorf("maximum retry count must be an integer")
   797  		}
   798  
   799  		p.MaximumRetryCount = count
   800  	}
   801  
   802  	p.Name = parts[0]
   803  
   804  	return p, nil
   805  }
   806  
   807  // ParseDevice parses a device mapping string to a container.DeviceMapping struct
   808  func ParseDevice(device string) (container.DeviceMapping, error) {
   809  	src := ""
   810  	dst := ""
   811  	permissions := "rwm"
   812  	arr := strings.Split(device, ":")
   813  	switch len(arr) {
   814  	case 3:
   815  		permissions = arr[2]
   816  		fallthrough
   817  	case 2:
   818  		if ValidDeviceMode(arr[1]) {
   819  			permissions = arr[1]
   820  		} else {
   821  			dst = arr[1]
   822  		}
   823  		fallthrough
   824  	case 1:
   825  		src = arr[0]
   826  	default:
   827  		return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device)
   828  	}
   829  
   830  	if dst == "" {
   831  		dst = src
   832  	}
   833  
   834  	deviceMapping := container.DeviceMapping{
   835  		PathOnHost:        src,
   836  		PathInContainer:   dst,
   837  		CgroupPermissions: permissions,
   838  	}
   839  	return deviceMapping, nil
   840  }
   841  
   842  // ParseLink parses and validates the specified string as a link format (name:alias)
   843  func ParseLink(val string) (string, string, error) {
   844  	if val == "" {
   845  		return "", "", fmt.Errorf("empty string specified for links")
   846  	}
   847  	arr := strings.Split(val, ":")
   848  	if len(arr) > 2 {
   849  		return "", "", fmt.Errorf("bad format for links: %s", val)
   850  	}
   851  	if len(arr) == 1 {
   852  		return val, val, nil
   853  	}
   854  	// This is kept because we can actually get a HostConfig with links
   855  	// from an already created container and the format is not `foo:bar`
   856  	// but `/foo:/c1/bar`
   857  	if strings.HasPrefix(arr[0], "/") {
   858  		_, alias := path.Split(arr[1])
   859  		return arr[0][1:], alias, nil
   860  	}
   861  	return arr[0], arr[1], nil
   862  }
   863  
   864  // ValidateLink validates that the specified string has a valid link format (containerName:alias).
   865  func ValidateLink(val string) (string, error) {
   866  	if _, _, err := ParseLink(val); err != nil {
   867  		return val, err
   868  	}
   869  	return val, nil
   870  }
   871  
   872  // ValidDeviceMode checks if the mode for device is valid or not.
   873  // Valid mode is a composition of r (read), w (write), and m (mknod).
   874  func ValidDeviceMode(mode string) bool {
   875  	var legalDeviceMode = map[rune]bool{
   876  		'r': true,
   877  		'w': true,
   878  		'm': true,
   879  	}
   880  	if mode == "" {
   881  		return false
   882  	}
   883  	for _, c := range mode {
   884  		if !legalDeviceMode[c] {
   885  			return false
   886  		}
   887  		legalDeviceMode[c] = false
   888  	}
   889  	return true
   890  }
   891  
   892  // ValidateDevice validates a path for devices
   893  // It will make sure 'val' is in the form:
   894  //    [host-dir:]container-path[:mode]
   895  // It also validates the device mode.
   896  func ValidateDevice(val string) (string, error) {
   897  	return validatePath(val, ValidDeviceMode)
   898  }
   899  
   900  func validatePath(val string, validator func(string) bool) (string, error) {
   901  	var containerPath string
   902  	var mode string
   903  
   904  	if strings.Count(val, ":") > 2 {
   905  		return val, fmt.Errorf("bad format for path: %s", val)
   906  	}
   907  
   908  	split := strings.SplitN(val, ":", 3)
   909  	if split[0] == "" {
   910  		return val, fmt.Errorf("bad format for path: %s", val)
   911  	}
   912  	switch len(split) {
   913  	case 1:
   914  		containerPath = split[0]
   915  		val = path.Clean(containerPath)
   916  	case 2:
   917  		if isValid := validator(split[1]); isValid {
   918  			containerPath = split[0]
   919  			mode = split[1]
   920  			val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
   921  		} else {
   922  			containerPath = split[1]
   923  			val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
   924  		}
   925  	case 3:
   926  		containerPath = split[1]
   927  		mode = split[2]
   928  		if isValid := validator(split[2]); !isValid {
   929  			return val, fmt.Errorf("bad mode specified: %s", mode)
   930  		}
   931  		val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
   932  	}
   933  
   934  	if !path.IsAbs(containerPath) {
   935  		return val, fmt.Errorf("%s is not an absolute path", containerPath)
   936  	}
   937  	return val, nil
   938  }
   939  
   940  // volumeSplitN splits raw into a maximum of n parts, separated by a separator colon.
   941  // A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
   942  // In Windows driver letter appears in two situations:
   943  // a. `^[a-zA-Z]:` (A colon followed  by `^[a-zA-Z]:` is OK as colon is the separator in volume option)
   944  // b. A string in the format like `\\?\C:\Windows\...` (UNC).
   945  // Therefore, a driver letter can only follow either a `:` or `\\`
   946  // This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`.
   947  func volumeSplitN(raw string, n int) []string {
   948  	var array []string
   949  	if len(raw) == 0 || raw[0] == ':' {
   950  		// invalid
   951  		return nil
   952  	}
   953  	// numberOfParts counts the number of parts separated by a separator colon
   954  	numberOfParts := 0
   955  	// left represents the left-most cursor in raw, updated at every `:` character considered as a separator.
   956  	left := 0
   957  	// right represents the right-most cursor in raw incremented with the loop. Note this
   958  	// starts at index 1 as index 0 is already handle above as a special case.
   959  	for right := 1; right < len(raw); right++ {
   960  		// stop parsing if reached maximum number of parts
   961  		if n >= 0 && numberOfParts >= n {
   962  			break
   963  		}
   964  		if raw[right] != ':' {
   965  			continue
   966  		}
   967  		potentialDriveLetter := raw[right-1]
   968  		if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
   969  			if right > 1 {
   970  				beforePotentialDriveLetter := raw[right-2]
   971  				// Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`)
   972  				if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' {
   973  					// e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
   974  					array = append(array, raw[left:right])
   975  					left = right + 1
   976  					numberOfParts++
   977  				}
   978  				// else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing.
   979  			}
   980  			// if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing.
   981  		} else {
   982  			// if `:` is not preceded by a potential drive letter, then consider it as a delimiter.
   983  			array = append(array, raw[left:right])
   984  			left = right + 1
   985  			numberOfParts++
   986  		}
   987  	}
   988  	// need to take care of the last part
   989  	if left < len(raw) {
   990  		if n >= 0 && numberOfParts >= n {
   991  			// if the maximum number of parts is reached, just append the rest to the last part
   992  			// left-1 is at the last `:` that needs to be included since not considered a separator.
   993  			array[n-1] += raw[left-1:]
   994  		} else {
   995  			array = append(array, raw[left:])
   996  		}
   997  	}
   998  	return array
   999  }