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