Pārlūkot izejas kodu

Clean some stuff from runconfig that are cli only…

… or could be in `opts` package. Having `runconfig/opts` and `opts`
doesn't really make sense and make it difficult to know where to put
some code.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
Vincent Demeester 8 gadi atpakaļ
vecāks
revīzija
c424be21b7

+ 4 - 5
cli/command/container/create.go

@@ -18,7 +18,6 @@ import (
 	apiclient "github.com/docker/docker/client"
 	apiclient "github.com/docker/docker/client"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
-	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
 )
 )
@@ -30,7 +29,7 @@ type createOptions struct {
 // NewCreateCommand creates a new cobra.Command for `docker create`
 // NewCreateCommand creates a new cobra.Command for `docker create`
 func NewCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 func NewCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 	var opts createOptions
 	var opts createOptions
-	var copts *runconfigopts.ContainerOptions
+	var copts *containerOptions
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
 		Use:   "create [OPTIONS] IMAGE [COMMAND] [ARG...]",
 		Use:   "create [OPTIONS] IMAGE [COMMAND] [ARG...]",
@@ -55,12 +54,12 @@ func NewCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 	flags.Bool("help", false, "Print usage")
 	flags.Bool("help", false, "Print usage")
 
 
 	command.AddTrustedFlags(flags, true)
 	command.AddTrustedFlags(flags, true)
-	copts = runconfigopts.AddFlags(flags)
+	copts = addFlags(flags)
 	return cmd
 	return cmd
 }
 }
 
 
-func runCreate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *createOptions, copts *runconfigopts.ContainerOptions) error {
-	config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts)
+func runCreate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *createOptions, copts *containerOptions) error {
+	config, hostConfig, networkingConfig, err := parse(flags, copts)
 	if err != nil {
 	if err != nil {
 		reportError(dockerCli.Err(), "create", err.Error(), true)
 		reportError(dockerCli.Err(), "create", err.Error(), true)
 		return cli.StatusError{StatusCode: 125}
 		return cli.StatusError{StatusCode: 125}

+ 1 - 2
cli/command/container/exec.go

@@ -13,7 +13,6 @@ import (
 	apiclient "github.com/docker/docker/client"
 	apiclient "github.com/docker/docker/client"
 	options "github.com/docker/docker/opts"
 	options "github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/promise"
-	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
@@ -30,7 +29,7 @@ type execOptions struct {
 func newExecOptions() *execOptions {
 func newExecOptions() *execOptions {
 	var values []string
 	var values []string
 	return &execOptions{
 	return &execOptions{
-		env: options.NewListOptsRef(&values, runconfigopts.ValidateEnv),
+		env: options.NewListOptsRef(&values, options.ValidateEnv),
 	}
 	}
 }
 }
 
 

+ 899 - 0
cli/command/container/opts.go

@@ -0,0 +1,899 @@
+package container
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"path"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/docker/docker/api/types/container"
+	networktypes "github.com/docker/docker/api/types/network"
+	"github.com/docker/docker/api/types/strslice"
+	"github.com/docker/docker/opts"
+	"github.com/docker/docker/pkg/signal"
+	runconfigopts "github.com/docker/docker/runconfig/opts"
+	"github.com/docker/go-connections/nat"
+	units "github.com/docker/go-units"
+	"github.com/spf13/pflag"
+)
+
+// containerOptions is a data object with all the options for creating a container
+type containerOptions struct {
+	attach             opts.ListOpts
+	volumes            opts.ListOpts
+	tmpfs              opts.ListOpts
+	blkioWeightDevice  opts.WeightdeviceOpt
+	deviceReadBps      opts.ThrottledeviceOpt
+	deviceWriteBps     opts.ThrottledeviceOpt
+	links              opts.ListOpts
+	aliases            opts.ListOpts
+	linkLocalIPs       opts.ListOpts
+	deviceReadIOps     opts.ThrottledeviceOpt
+	deviceWriteIOps    opts.ThrottledeviceOpt
+	env                opts.ListOpts
+	labels             opts.ListOpts
+	devices            opts.ListOpts
+	ulimits            *opts.UlimitOpt
+	sysctls            *opts.MapOpts
+	publish            opts.ListOpts
+	expose             opts.ListOpts
+	dns                opts.ListOpts
+	dnsSearch          opts.ListOpts
+	dnsOptions         opts.ListOpts
+	extraHosts         opts.ListOpts
+	volumesFrom        opts.ListOpts
+	envFile            opts.ListOpts
+	capAdd             opts.ListOpts
+	capDrop            opts.ListOpts
+	groupAdd           opts.ListOpts
+	securityOpt        opts.ListOpts
+	storageOpt         opts.ListOpts
+	labelsFile         opts.ListOpts
+	loggingOpts        opts.ListOpts
+	privileged         bool
+	pidMode            string
+	utsMode            string
+	usernsMode         string
+	publishAll         bool
+	stdin              bool
+	tty                bool
+	oomKillDisable     bool
+	oomScoreAdj        int
+	containerIDFile    string
+	entrypoint         string
+	hostname           string
+	memoryString       string
+	memoryReservation  string
+	memorySwap         string
+	kernelMemory       string
+	user               string
+	workingDir         string
+	cpuCount           int64
+	cpuShares          int64
+	cpuPercent         int64
+	cpuPeriod          int64
+	cpuRealtimePeriod  int64
+	cpuRealtimeRuntime int64
+	cpuQuota           int64
+	cpus               opts.NanoCPUs
+	cpusetCpus         string
+	cpusetMems         string
+	blkioWeight        uint16
+	ioMaxBandwidth     string
+	ioMaxIOps          uint64
+	swappiness         int64
+	netMode            string
+	macAddress         string
+	ipv4Address        string
+	ipv6Address        string
+	ipcMode            string
+	pidsLimit          int64
+	restartPolicy      string
+	readonlyRootfs     bool
+	loggingDriver      string
+	cgroupParent       string
+	volumeDriver       string
+	stopSignal         string
+	stopTimeout        int
+	isolation          string
+	shmSize            string
+	noHealthcheck      bool
+	healthCmd          string
+	healthInterval     time.Duration
+	healthTimeout      time.Duration
+	healthRetries      int
+	runtime            string
+	autoRemove         bool
+	init               bool
+	initPath           string
+	credentialSpec     string
+
+	Image string
+	Args  []string
+}
+
+// addFlags adds all command line flags that will be used by parse to the FlagSet
+func addFlags(flags *pflag.FlagSet) *containerOptions {
+	copts := &containerOptions{
+		aliases:           opts.NewListOpts(nil),
+		attach:            opts.NewListOpts(validateAttach),
+		blkioWeightDevice: opts.NewWeightdeviceOpt(opts.ValidateWeightDevice),
+		capAdd:            opts.NewListOpts(nil),
+		capDrop:           opts.NewListOpts(nil),
+		dns:               opts.NewListOpts(opts.ValidateIPAddress),
+		dnsOptions:        opts.NewListOpts(nil),
+		dnsSearch:         opts.NewListOpts(opts.ValidateDNSSearch),
+		deviceReadBps:     opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice),
+		deviceReadIOps:    opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
+		deviceWriteBps:    opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice),
+		deviceWriteIOps:   opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
+		devices:           opts.NewListOpts(validateDevice),
+		env:               opts.NewListOpts(opts.ValidateEnv),
+		envFile:           opts.NewListOpts(nil),
+		expose:            opts.NewListOpts(nil),
+		extraHosts:        opts.NewListOpts(opts.ValidateExtraHost),
+		groupAdd:          opts.NewListOpts(nil),
+		labels:            opts.NewListOpts(opts.ValidateEnv),
+		labelsFile:        opts.NewListOpts(nil),
+		linkLocalIPs:      opts.NewListOpts(nil),
+		links:             opts.NewListOpts(opts.ValidateLink),
+		loggingOpts:       opts.NewListOpts(nil),
+		publish:           opts.NewListOpts(nil),
+		securityOpt:       opts.NewListOpts(nil),
+		storageOpt:        opts.NewListOpts(nil),
+		sysctls:           opts.NewMapOpts(nil, opts.ValidateSysctl),
+		tmpfs:             opts.NewListOpts(nil),
+		ulimits:           opts.NewUlimitOpt(nil),
+		volumes:           opts.NewListOpts(nil),
+		volumesFrom:       opts.NewListOpts(nil),
+	}
+
+	// General purpose flags
+	flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR")
+	flags.Var(&copts.devices, "device", "Add a host device to the container")
+	flags.VarP(&copts.env, "env", "e", "Set environment variables")
+	flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables")
+	flags.StringVar(&copts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image")
+	flags.Var(&copts.groupAdd, "group-add", "Add additional groups to join")
+	flags.StringVarP(&copts.hostname, "hostname", "h", "", "Container host name")
+	flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached")
+	flags.VarP(&copts.labels, "label", "l", "Set meta data on a container")
+	flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels")
+	flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only")
+	flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits")
+	flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
+	flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container")
+	flags.SetAnnotation("stop-timeout", "version", []string{"1.25"})
+	flags.Var(copts.sysctls, "sysctl", "Sysctl options")
+	flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
+	flags.Var(copts.ulimits, "ulimit", "Ulimit options")
+	flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
+	flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container")
+	flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits")
+
+	// Security
+	flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities")
+	flags.Var(&copts.capDrop, "cap-drop", "Drop Linux capabilities")
+	flags.BoolVar(&copts.privileged, "privileged", false, "Give extended privileges to this container")
+	flags.Var(&copts.securityOpt, "security-opt", "Security Options")
+	flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use")
+	flags.StringVar(&copts.credentialSpec, "credentialspec", "", "Credential spec for managed service account (Windows only)")
+
+	// Network and port publishing flag
+	flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
+	flags.Var(&copts.dns, "dns", "Set custom DNS servers")
+	// We allow for both "--dns-opt" and "--dns-option", although the latter is the recommended way.
+	// This is to be consistent with service create/update
+	flags.Var(&copts.dnsOptions, "dns-opt", "Set DNS options")
+	flags.Var(&copts.dnsOptions, "dns-option", "Set DNS options")
+	flags.MarkHidden("dns-opt")
+	flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains")
+	flags.Var(&copts.expose, "expose", "Expose a port or a range of ports")
+	flags.StringVar(&copts.ipv4Address, "ip", "", "Container IPv4 address (e.g. 172.30.100.104)")
+	flags.StringVar(&copts.ipv6Address, "ip6", "", "Container IPv6 address (e.g. 2001:db8::33)")
+	flags.Var(&copts.links, "link", "Add link to another container")
+	flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses")
+	flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
+	flags.VarP(&copts.publish, "publish", "p", "Publish a container's port(s) to the host")
+	flags.BoolVarP(&copts.publishAll, "publish-all", "P", false, "Publish all exposed ports to random ports")
+	// We allow for both "--net" and "--network", although the latter is the recommended way.
+	flags.StringVar(&copts.netMode, "net", "default", "Connect a container to a network")
+	flags.StringVar(&copts.netMode, "network", "default", "Connect a container to a network")
+	flags.MarkHidden("net")
+	// We allow for both "--net-alias" and "--network-alias", although the latter is the recommended way.
+	flags.Var(&copts.aliases, "net-alias", "Add network-scoped alias for the container")
+	flags.Var(&copts.aliases, "network-alias", "Add network-scoped alias for the container")
+	flags.MarkHidden("net-alias")
+
+	// Logging and storage
+	flags.StringVar(&copts.loggingDriver, "log-driver", "", "Logging driver for the container")
+	flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container")
+	flags.Var(&copts.loggingOpts, "log-opt", "Log driver options")
+	flags.Var(&copts.storageOpt, "storage-opt", "Storage driver options for the container")
+	flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory")
+	flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
+	flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume")
+
+	// Health-checking
+	flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health")
+	flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ns|us|ms|s|m|h) (default 0s)")
+	flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy")
+	flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ns|us|ms|s|m|h) (default 0s)")
+	flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
+
+	// Resource management
+	flags.Uint16Var(&copts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)")
+	flags.Var(&copts.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)")
+	flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file")
+	flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
+	flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
+	flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)")
+	flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)")
+	flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
+	flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
+	flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds")
+	flags.Int64Var(&copts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds")
+	flags.Int64VarP(&copts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
+	flags.Var(&copts.cpus, "cpus", "Number of CPUs")
+	flags.Var(&copts.deviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device")
+	flags.Var(&copts.deviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device")
+	flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device")
+	flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device")
+	flags.StringVar(&copts.ioMaxBandwidth, "io-maxbandwidth", "", "Maximum IO bandwidth limit for the system drive (Windows only)")
+	flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)")
+	flags.StringVar(&copts.kernelMemory, "kernel-memory", "", "Kernel memory limit")
+	flags.StringVarP(&copts.memoryString, "memory", "m", "", "Memory limit")
+	flags.StringVar(&copts.memoryReservation, "memory-reservation", "", "Memory soft limit")
+	flags.StringVar(&copts.memorySwap, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
+	flags.Int64Var(&copts.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)")
+	flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer")
+	flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)")
+	flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)")
+
+	// Low-level execution (cgroups, namespaces, ...)
+	flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
+	flags.StringVar(&copts.ipcMode, "ipc", "", "IPC namespace to use")
+	flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology")
+	flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use")
+	flags.StringVar(&copts.shmSize, "shm-size", "", "Size of /dev/shm, default value is 64MB")
+	flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use")
+	flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container")
+
+	flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes")
+	flags.StringVar(&copts.initPath, "init-path", "", "Path to the docker-init binary")
+	return copts
+}
+
+// parse parses the args for the specified command and generates a Config,
+// a HostConfig and returns them with the specified command.
+// If the specified args are not valid, it will return an error.
+func parse(flags *pflag.FlagSet, copts *containerOptions) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
+	var (
+		attachStdin  = copts.attach.Get("stdin")
+		attachStdout = copts.attach.Get("stdout")
+		attachStderr = copts.attach.Get("stderr")
+	)
+
+	// Validate the input mac address
+	if copts.macAddress != "" {
+		if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil {
+			return nil, nil, nil, fmt.Errorf("%s is not a valid mac address", copts.macAddress)
+		}
+	}
+	if copts.stdin {
+		attachStdin = true
+	}
+	// If -a is not set, attach to stdout and stderr
+	if copts.attach.Len() == 0 {
+		attachStdout = true
+		attachStderr = true
+	}
+
+	var err error
+
+	var memory int64
+	if copts.memoryString != "" {
+		memory, err = units.RAMInBytes(copts.memoryString)
+		if err != nil {
+			return nil, nil, nil, err
+		}
+	}
+
+	var memoryReservation int64
+	if copts.memoryReservation != "" {
+		memoryReservation, err = units.RAMInBytes(copts.memoryReservation)
+		if err != nil {
+			return nil, nil, nil, err
+		}
+	}
+
+	var memorySwap int64
+	if copts.memorySwap != "" {
+		if copts.memorySwap == "-1" {
+			memorySwap = -1
+		} else {
+			memorySwap, err = units.RAMInBytes(copts.memorySwap)
+			if err != nil {
+				return nil, nil, nil, err
+			}
+		}
+	}
+
+	var kernelMemory int64
+	if copts.kernelMemory != "" {
+		kernelMemory, err = units.RAMInBytes(copts.kernelMemory)
+		if err != nil {
+			return nil, nil, nil, err
+		}
+	}
+
+	swappiness := copts.swappiness
+	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
+		return nil, nil, nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
+	}
+
+	var shmSize int64
+	if copts.shmSize != "" {
+		shmSize, err = units.RAMInBytes(copts.shmSize)
+		if err != nil {
+			return nil, nil, nil, err
+		}
+	}
+
+	// TODO FIXME units.RAMInBytes should have a uint64 version
+	var maxIOBandwidth int64
+	if copts.ioMaxBandwidth != "" {
+		maxIOBandwidth, err = units.RAMInBytes(copts.ioMaxBandwidth)
+		if err != nil {
+			return nil, nil, nil, err
+		}
+		if maxIOBandwidth < 0 {
+			return nil, nil, nil, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", copts.ioMaxBandwidth)
+		}
+	}
+
+	var binds []string
+	volumes := copts.volumes.GetMap()
+	// add any bind targets to the list of container volumes
+	for bind := range copts.volumes.GetMap() {
+		if arr := volumeSplitN(bind, 2); len(arr) > 1 {
+			// after creating the bind mount we want to delete it from the copts.volumes values because
+			// we do not want bind mounts being committed to image configs
+			binds = append(binds, bind)
+			// We should delete from the map (`volumes`) here, as deleting from copts.volumes will not work if
+			// there are duplicates entries.
+			delete(volumes, bind)
+		}
+	}
+
+	// Can't evaluate options passed into --tmpfs until we actually mount
+	tmpfs := make(map[string]string)
+	for _, t := range copts.tmpfs.GetAll() {
+		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
+			tmpfs[arr[0]] = arr[1]
+		} else {
+			tmpfs[arr[0]] = ""
+		}
+	}
+
+	var (
+		runCmd     strslice.StrSlice
+		entrypoint strslice.StrSlice
+	)
+
+	if len(copts.Args) > 0 {
+		runCmd = strslice.StrSlice(copts.Args)
+	}
+
+	if copts.entrypoint != "" {
+		entrypoint = strslice.StrSlice{copts.entrypoint}
+	} else if flags.Changed("entrypoint") {
+		// if `--entrypoint=` is parsed then Entrypoint is reset
+		entrypoint = []string{""}
+	}
+
+	ports, portBindings, err := nat.ParsePortSpecs(copts.publish.GetAll())
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	// Merge in exposed ports to the map of published ports
+	for _, e := range copts.expose.GetAll() {
+		if strings.Contains(e, ":") {
+			return nil, nil, nil, fmt.Errorf("invalid port format for --expose: %s", e)
+		}
+		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
+		proto, port := nat.SplitProtoPort(e)
+		//parse the start and end port and create a sequence of ports to expose
+		//if expose a port, the start and end port are the same
+		start, end, err := nat.ParsePortRange(port)
+		if err != nil {
+			return nil, nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err)
+		}
+		for i := start; i <= end; i++ {
+			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
+			if err != nil {
+				return nil, nil, nil, err
+			}
+			if _, exists := ports[p]; !exists {
+				ports[p] = struct{}{}
+			}
+		}
+	}
+
+	// parse device mappings
+	deviceMappings := []container.DeviceMapping{}
+	for _, device := range copts.devices.GetAll() {
+		deviceMapping, err := parseDevice(device)
+		if err != nil {
+			return nil, nil, nil, err
+		}
+		deviceMappings = append(deviceMappings, deviceMapping)
+	}
+
+	// collect all the environment variables for the container
+	envVariables, err := runconfigopts.ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll())
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	// collect all the labels for the container
+	labels, err := runconfigopts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll())
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	ipcMode := container.IpcMode(copts.ipcMode)
+	if !ipcMode.Valid() {
+		return nil, nil, nil, fmt.Errorf("--ipc: invalid IPC mode")
+	}
+
+	pidMode := container.PidMode(copts.pidMode)
+	if !pidMode.Valid() {
+		return nil, nil, nil, fmt.Errorf("--pid: invalid PID mode")
+	}
+
+	utsMode := container.UTSMode(copts.utsMode)
+	if !utsMode.Valid() {
+		return nil, nil, nil, fmt.Errorf("--uts: invalid UTS mode")
+	}
+
+	usernsMode := container.UsernsMode(copts.usernsMode)
+	if !usernsMode.Valid() {
+		return nil, nil, nil, fmt.Errorf("--userns: invalid USER mode")
+	}
+
+	restartPolicy, err := runconfigopts.ParseRestartPolicy(copts.restartPolicy)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll())
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll())
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll())
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	// Healthcheck
+	var healthConfig *container.HealthConfig
+	haveHealthSettings := copts.healthCmd != "" ||
+		copts.healthInterval != 0 ||
+		copts.healthTimeout != 0 ||
+		copts.healthRetries != 0
+	if copts.noHealthcheck {
+		if haveHealthSettings {
+			return nil, nil, nil, fmt.Errorf("--no-healthcheck conflicts with --health-* options")
+		}
+		test := strslice.StrSlice{"NONE"}
+		healthConfig = &container.HealthConfig{Test: test}
+	} else if haveHealthSettings {
+		var probe strslice.StrSlice
+		if copts.healthCmd != "" {
+			args := []string{"CMD-SHELL", copts.healthCmd}
+			probe = strslice.StrSlice(args)
+		}
+		if copts.healthInterval < 0 {
+			return nil, nil, nil, fmt.Errorf("--health-interval cannot be negative")
+		}
+		if copts.healthTimeout < 0 {
+			return nil, nil, nil, fmt.Errorf("--health-timeout cannot be negative")
+		}
+
+		healthConfig = &container.HealthConfig{
+			Test:     probe,
+			Interval: copts.healthInterval,
+			Timeout:  copts.healthTimeout,
+			Retries:  copts.healthRetries,
+		}
+	}
+
+	resources := container.Resources{
+		CgroupParent:         copts.cgroupParent,
+		Memory:               memory,
+		MemoryReservation:    memoryReservation,
+		MemorySwap:           memorySwap,
+		MemorySwappiness:     &copts.swappiness,
+		KernelMemory:         kernelMemory,
+		OomKillDisable:       &copts.oomKillDisable,
+		NanoCPUs:             copts.cpus.Value(),
+		CPUCount:             copts.cpuCount,
+		CPUPercent:           copts.cpuPercent,
+		CPUShares:            copts.cpuShares,
+		CPUPeriod:            copts.cpuPeriod,
+		CpusetCpus:           copts.cpusetCpus,
+		CpusetMems:           copts.cpusetMems,
+		CPUQuota:             copts.cpuQuota,
+		CPURealtimePeriod:    copts.cpuRealtimePeriod,
+		CPURealtimeRuntime:   copts.cpuRealtimeRuntime,
+		PidsLimit:            copts.pidsLimit,
+		BlkioWeight:          copts.blkioWeight,
+		BlkioWeightDevice:    copts.blkioWeightDevice.GetList(),
+		BlkioDeviceReadBps:   copts.deviceReadBps.GetList(),
+		BlkioDeviceWriteBps:  copts.deviceWriteBps.GetList(),
+		BlkioDeviceReadIOps:  copts.deviceReadIOps.GetList(),
+		BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(),
+		IOMaximumIOps:        copts.ioMaxIOps,
+		IOMaximumBandwidth:   uint64(maxIOBandwidth),
+		Ulimits:              copts.ulimits.GetList(),
+		Devices:              deviceMappings,
+	}
+
+	config := &container.Config{
+		Hostname:     copts.hostname,
+		ExposedPorts: ports,
+		User:         copts.user,
+		Tty:          copts.tty,
+		// TODO: deprecated, it comes from -n, --networking
+		// it's still needed internally to set the network to disabled
+		// if e.g. bridge is none in daemon opts, and in inspect
+		NetworkDisabled: false,
+		OpenStdin:       copts.stdin,
+		AttachStdin:     attachStdin,
+		AttachStdout:    attachStdout,
+		AttachStderr:    attachStderr,
+		Env:             envVariables,
+		Cmd:             runCmd,
+		Image:           copts.Image,
+		Volumes:         volumes,
+		MacAddress:      copts.macAddress,
+		Entrypoint:      entrypoint,
+		WorkingDir:      copts.workingDir,
+		Labels:          runconfigopts.ConvertKVStringsToMap(labels),
+		Healthcheck:     healthConfig,
+	}
+	if flags.Changed("stop-signal") {
+		config.StopSignal = copts.stopSignal
+	}
+	if flags.Changed("stop-timeout") {
+		config.StopTimeout = &copts.stopTimeout
+	}
+
+	hostConfig := &container.HostConfig{
+		Binds:           binds,
+		ContainerIDFile: copts.containerIDFile,
+		OomScoreAdj:     copts.oomScoreAdj,
+		AutoRemove:      copts.autoRemove,
+		Privileged:      copts.privileged,
+		PortBindings:    portBindings,
+		Links:           copts.links.GetAll(),
+		PublishAllPorts: copts.publishAll,
+		// Make sure the dns fields are never nil.
+		// New containers don't ever have those fields nil,
+		// but pre created containers can still have those nil values.
+		// See https://github.com/docker/docker/pull/17779
+		// for a more detailed explanation on why we don't want that.
+		DNS:            copts.dns.GetAllOrEmpty(),
+		DNSSearch:      copts.dnsSearch.GetAllOrEmpty(),
+		DNSOptions:     copts.dnsOptions.GetAllOrEmpty(),
+		ExtraHosts:     copts.extraHosts.GetAll(),
+		VolumesFrom:    copts.volumesFrom.GetAll(),
+		NetworkMode:    container.NetworkMode(copts.netMode),
+		IpcMode:        ipcMode,
+		PidMode:        pidMode,
+		UTSMode:        utsMode,
+		UsernsMode:     usernsMode,
+		CapAdd:         strslice.StrSlice(copts.capAdd.GetAll()),
+		CapDrop:        strslice.StrSlice(copts.capDrop.GetAll()),
+		GroupAdd:       copts.groupAdd.GetAll(),
+		RestartPolicy:  restartPolicy,
+		SecurityOpt:    securityOpts,
+		StorageOpt:     storageOpts,
+		ReadonlyRootfs: copts.readonlyRootfs,
+		LogConfig:      container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts},
+		VolumeDriver:   copts.volumeDriver,
+		Isolation:      container.Isolation(copts.isolation),
+		ShmSize:        shmSize,
+		Resources:      resources,
+		Tmpfs:          tmpfs,
+		Sysctls:        copts.sysctls.GetAll(),
+		Runtime:        copts.runtime,
+	}
+
+	// only set this value if the user provided the flag, else it should default to nil
+	if flags.Changed("init") {
+		hostConfig.Init = &copts.init
+	}
+
+	// When allocating stdin in attached mode, close stdin at client disconnect
+	if config.OpenStdin && config.AttachStdin {
+		config.StdinOnce = true
+	}
+
+	networkingConfig := &networktypes.NetworkingConfig{
+		EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
+	}
+
+	if copts.ipv4Address != "" || copts.ipv6Address != "" || copts.linkLocalIPs.Len() > 0 {
+		epConfig := &networktypes.EndpointSettings{}
+		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
+
+		epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{
+			IPv4Address: copts.ipv4Address,
+			IPv6Address: copts.ipv6Address,
+		}
+
+		if copts.linkLocalIPs.Len() > 0 {
+			epConfig.IPAMConfig.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len())
+			copy(epConfig.IPAMConfig.LinkLocalIPs, copts.linkLocalIPs.GetAll())
+		}
+	}
+
+	if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 {
+		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
+		if epConfig == nil {
+			epConfig = &networktypes.EndpointSettings{}
+		}
+		epConfig.Links = make([]string, len(hostConfig.Links))
+		copy(epConfig.Links, hostConfig.Links)
+		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
+	}
+
+	if copts.aliases.Len() > 0 {
+		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
+		if epConfig == nil {
+			epConfig = &networktypes.EndpointSettings{}
+		}
+		epConfig.Aliases = make([]string, copts.aliases.Len())
+		copy(epConfig.Aliases, copts.aliases.GetAll())
+		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
+	}
+
+	return config, hostConfig, networkingConfig, nil
+}
+
+func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
+	loggingOptsMap := runconfigopts.ConvertKVStringsToMap(loggingOpts)
+	if loggingDriver == "none" && len(loggingOpts) > 0 {
+		return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver)
+	}
+	return loggingOptsMap, nil
+}
+
+// takes a local seccomp daemon, reads the file contents for sending to the daemon
+func parseSecurityOpts(securityOpts []string) ([]string, error) {
+	for key, opt := range securityOpts {
+		con := strings.SplitN(opt, "=", 2)
+		if len(con) == 1 && con[0] != "no-new-privileges" {
+			if strings.Contains(opt, ":") {
+				con = strings.SplitN(opt, ":", 2)
+			} else {
+				return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt)
+			}
+		}
+		if con[0] == "seccomp" && con[1] != "unconfined" {
+			f, err := ioutil.ReadFile(con[1])
+			if err != nil {
+				return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
+			}
+			b := bytes.NewBuffer(nil)
+			if err := json.Compact(b, f); err != nil {
+				return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err)
+			}
+			securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
+		}
+	}
+
+	return securityOpts, nil
+}
+
+// parses storage options per container into a map
+func parseStorageOpts(storageOpts []string) (map[string]string, error) {
+	m := make(map[string]string)
+	for _, option := range storageOpts {
+		if strings.Contains(option, "=") {
+			opt := strings.SplitN(option, "=", 2)
+			m[opt[0]] = opt[1]
+		} else {
+			return nil, fmt.Errorf("invalid storage option")
+		}
+	}
+	return m, nil
+}
+
+// parseDevice parses a device mapping string to a container.DeviceMapping struct
+func parseDevice(device string) (container.DeviceMapping, error) {
+	src := ""
+	dst := ""
+	permissions := "rwm"
+	arr := strings.Split(device, ":")
+	switch len(arr) {
+	case 3:
+		permissions = arr[2]
+		fallthrough
+	case 2:
+		if validDeviceMode(arr[1]) {
+			permissions = arr[1]
+		} else {
+			dst = arr[1]
+		}
+		fallthrough
+	case 1:
+		src = arr[0]
+	default:
+		return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device)
+	}
+
+	if dst == "" {
+		dst = src
+	}
+
+	deviceMapping := container.DeviceMapping{
+		PathOnHost:        src,
+		PathInContainer:   dst,
+		CgroupPermissions: permissions,
+	}
+	return deviceMapping, nil
+}
+
+// validDeviceMode checks if the mode for device is valid or not.
+// Valid mode is a composition of r (read), w (write), and m (mknod).
+func validDeviceMode(mode string) bool {
+	var legalDeviceMode = map[rune]bool{
+		'r': true,
+		'w': true,
+		'm': true,
+	}
+	if mode == "" {
+		return false
+	}
+	for _, c := range mode {
+		if !legalDeviceMode[c] {
+			return false
+		}
+		legalDeviceMode[c] = false
+	}
+	return true
+}
+
+// validateDevice validates a path for devices
+// It will make sure 'val' is in the form:
+//    [host-dir:]container-path[:mode]
+// It also validates the device mode.
+func validateDevice(val string) (string, error) {
+	return validatePath(val, validDeviceMode)
+}
+
+func validatePath(val string, validator func(string) bool) (string, error) {
+	var containerPath string
+	var mode string
+
+	if strings.Count(val, ":") > 2 {
+		return val, fmt.Errorf("bad format for path: %s", val)
+	}
+
+	split := strings.SplitN(val, ":", 3)
+	if split[0] == "" {
+		return val, fmt.Errorf("bad format for path: %s", val)
+	}
+	switch len(split) {
+	case 1:
+		containerPath = split[0]
+		val = path.Clean(containerPath)
+	case 2:
+		if isValid := validator(split[1]); isValid {
+			containerPath = split[0]
+			mode = split[1]
+			val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
+		} else {
+			containerPath = split[1]
+			val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
+		}
+	case 3:
+		containerPath = split[1]
+		mode = split[2]
+		if isValid := validator(split[2]); !isValid {
+			return val, fmt.Errorf("bad mode specified: %s", mode)
+		}
+		val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
+	}
+
+	if !path.IsAbs(containerPath) {
+		return val, fmt.Errorf("%s is not an absolute path", containerPath)
+	}
+	return val, nil
+}
+
+// volumeSplitN splits raw into a maximum of n parts, separated by a separator colon.
+// A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
+// In Windows driver letter appears in two situations:
+// a. `^[a-zA-Z]:` (A colon followed  by `^[a-zA-Z]:` is OK as colon is the separator in volume option)
+// b. A string in the format like `\\?\C:\Windows\...` (UNC).
+// Therefore, a driver letter can only follow either a `:` or `\\`
+// This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`.
+func volumeSplitN(raw string, n int) []string {
+	var array []string
+	if len(raw) == 0 || raw[0] == ':' {
+		// invalid
+		return nil
+	}
+	// numberOfParts counts the number of parts separated by a separator colon
+	numberOfParts := 0
+	// left represents the left-most cursor in raw, updated at every `:` character considered as a separator.
+	left := 0
+	// right represents the right-most cursor in raw incremented with the loop. Note this
+	// starts at index 1 as index 0 is already handle above as a special case.
+	for right := 1; right < len(raw); right++ {
+		// stop parsing if reached maximum number of parts
+		if n >= 0 && numberOfParts >= n {
+			break
+		}
+		if raw[right] != ':' {
+			continue
+		}
+		potentialDriveLetter := raw[right-1]
+		if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
+			if right > 1 {
+				beforePotentialDriveLetter := raw[right-2]
+				// Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`)
+				if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' {
+					// e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
+					array = append(array, raw[left:right])
+					left = right + 1
+					numberOfParts++
+				}
+				// else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing.
+			}
+			// if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing.
+		} else {
+			// if `:` is not preceded by a potential drive letter, then consider it as a delimiter.
+			array = append(array, raw[left:right])
+			left = right + 1
+			numberOfParts++
+		}
+	}
+	// need to take care of the last part
+	if left < len(raw) {
+		if n >= 0 && numberOfParts >= n {
+			// if the maximum number of parts is reached, just append the rest to the last part
+			// left-1 is at the last `:` that needs to be included since not considered a separator.
+			array[n-1] += raw[left-1:]
+		} else {
+			array = append(array, raw[left:])
+		}
+	}
+	return array
+}
+
+// validateAttach validates that the specified string is a valid attach option.
+func validateAttach(val string) (string, error) {
+	s := strings.ToLower(val)
+	for _, str := range []string{"stdin", "stdout", "stderr"} {
+		if s == str {
+			return s, nil
+		}
+	}
+	return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR")
+}

+ 238 - 275
runconfig/opts/parse_test.go → cli/command/container/opts_test.go

@@ -1,4 +1,4 @@
-package opts
+package container
 
 
 import (
 import (
 	"bytes"
 	"bytes"
@@ -18,24 +18,48 @@ import (
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
 )
 )
 
 
+func TestValidateAttach(t *testing.T) {
+	valid := []string{
+		"stdin",
+		"stdout",
+		"stderr",
+		"STDIN",
+		"STDOUT",
+		"STDERR",
+	}
+	if _, err := validateAttach("invalid"); err == nil {
+		t.Fatalf("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing")
+	}
+
+	for _, attach := range valid {
+		value, err := validateAttach(attach)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if value != strings.ToLower(attach) {
+			t.Fatalf("Expected [%v], got [%v]", attach, value)
+		}
+	}
+}
+
 func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
 func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
 	flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
 	flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
 	flags.SetOutput(ioutil.Discard)
 	flags.SetOutput(ioutil.Discard)
 	flags.Usage = nil
 	flags.Usage = nil
-	copts := AddFlags(flags)
+	copts := addFlags(flags)
 	if err := flags.Parse(args); err != nil {
 	if err := flags.Parse(args); err != nil {
 		return nil, nil, nil, err
 		return nil, nil, nil, err
 	}
 	}
-	return Parse(flags, copts)
+	return parse(flags, copts)
 }
 }
 
 
-func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
+func parsetest(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
 	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
 	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
 	return config, hostConfig, err
 	return config, hostConfig, err
 }
 }
 
 
 func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
 func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
-	config, hostConfig, err := parse(t, args)
+	config, hostConfig, err := parsetest(t, args)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -71,28 +95,28 @@ func TestParseRunAttach(t *testing.T) {
 		t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
 		t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
 	}
 	}
 
 
-	if _, _, err := parse(t, "-a"); err == nil {
+	if _, _, err := parsetest(t, "-a"); err == nil {
 		t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
 		t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
 	}
 	}
-	if _, _, err := parse(t, "-a invalid"); err == nil {
+	if _, _, err := parsetest(t, "-a invalid"); err == nil {
 		t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
 		t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
 	}
 	}
-	if _, _, err := parse(t, "-a invalid -a stdout"); err == nil {
+	if _, _, err := parsetest(t, "-a invalid -a stdout"); err == nil {
 		t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
 		t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
 	}
 	}
-	if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil {
+	if _, _, err := parsetest(t, "-a stdout -a stderr -d"); err == nil {
 		t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
 		t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
 	}
 	}
-	if _, _, err := parse(t, "-a stdin -d"); err == nil {
+	if _, _, err := parsetest(t, "-a stdin -d"); err == nil {
 		t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
 		t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
 	}
 	}
-	if _, _, err := parse(t, "-a stdout -d"); err == nil {
+	if _, _, err := parsetest(t, "-a stdout -d"); err == nil {
 		t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
 		t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
 	}
 	}
-	if _, _, err := parse(t, "-a stderr -d"); err == nil {
+	if _, _, err := parsetest(t, "-a stderr -d"); err == nil {
 		t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
 		t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
 	}
 	}
-	if _, _, err := parse(t, "-d --rm"); err == nil {
+	if _, _, err := parsetest(t, "-d --rm"); err == nil {
 		t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not")
 		t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not")
 	}
 	}
 }
 }
@@ -167,172 +191,6 @@ func TestParseRunVolumes(t *testing.T) {
 
 
 }
 }
 
 
-// This tests the cases for binds which are generated through
-// DecodeContainerConfig rather than Parse()
-func TestDecodeContainerConfigVolumes(t *testing.T) {
-
-	// Root to root
-	bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
-		t.Fatalf("binds %v should have failed", bindsOrVols)
-	}
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
-		t.Fatalf("volume %v should have failed", bindsOrVols)
-	}
-
-	// No destination path
-	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
-		t.Fatalf("binds %v should have failed", bindsOrVols)
-	}
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
-		t.Fatalf("volume %v should have failed", bindsOrVols)
-	}
-
-	//	// No destination path or mode
-	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
-		t.Fatalf("binds %v should have failed", bindsOrVols)
-	}
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
-		t.Fatalf("volume %v should have failed", bindsOrVols)
-	}
-
-	// A whole lot of nothing
-	bindsOrVols = []string{`:`}
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
-		t.Fatalf("binds %v should have failed", bindsOrVols)
-	}
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
-		t.Fatalf("volume %v should have failed", bindsOrVols)
-	}
-
-	// A whole lot of nothing with no mode
-	bindsOrVols = []string{`::`}
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
-		t.Fatalf("binds %v should have failed", bindsOrVols)
-	}
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
-		t.Fatalf("volume %v should have failed", bindsOrVols)
-	}
-
-	// Too much including an invalid mode
-	wTmp := os.Getenv("TEMP")
-	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
-		t.Fatalf("binds %v should have failed", bindsOrVols)
-	}
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
-		t.Fatalf("volume %v should have failed", bindsOrVols)
-	}
-
-	// Windows specific error tests
-	if runtime.GOOS == "windows" {
-		// Volume which does not include a drive letter
-		bindsOrVols = []string{`\tmp`}
-		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
-			t.Fatalf("binds %v should have failed", bindsOrVols)
-		}
-		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
-			t.Fatalf("volume %v should have failed", bindsOrVols)
-		}
-
-		// Root to C-Drive
-		bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
-		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
-			t.Fatalf("binds %v should have failed", bindsOrVols)
-		}
-		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
-			t.Fatalf("volume %v should have failed", bindsOrVols)
-		}
-
-		// Container path that does not include a drive letter
-		bindsOrVols = []string{`c:\windows:\somewhere`}
-		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
-			t.Fatalf("binds %v should have failed", bindsOrVols)
-		}
-		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
-			t.Fatalf("volume %v should have failed", bindsOrVols)
-		}
-	}
-
-	// Linux-specific error tests
-	if runtime.GOOS != "windows" {
-		// Just root
-		bindsOrVols = []string{`/`}
-		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
-			t.Fatalf("binds %v should have failed", bindsOrVols)
-		}
-		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
-			t.Fatalf("volume %v should have failed", bindsOrVols)
-		}
-
-		// A single volume that looks like a bind mount passed in Volumes.
-		// This should be handled as a bind mount, not a volume.
-		vols := []string{`/foo:/bar`}
-		if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
-			t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
-		} else if hostConfig.Binds != nil {
-			t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
-		} else if _, exists := config.Volumes[vols[0]]; !exists {
-			t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
-		}
-
-	}
-}
-
-// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes
-// to call DecodeContainerConfig. It effectively does what a client would
-// do when calling the daemon by constructing a JSON stream of a
-// ContainerConfigWrapper which is populated by the set of volume specs
-// passed into it. It returns a config and a hostconfig which can be
-// validated to ensure DecodeContainerConfig has manipulated the structures
-// correctly.
-func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) {
-	var (
-		b   []byte
-		err error
-		c   *container.Config
-		h   *container.HostConfig
-	)
-	w := runconfig.ContainerConfigWrapper{
-		Config: &container.Config{
-			Volumes: map[string]struct{}{},
-		},
-		HostConfig: &container.HostConfig{
-			NetworkMode: "none",
-			Binds:       binds,
-		},
-	}
-	for _, v := range volumes {
-		w.Config.Volumes[v] = struct{}{}
-	}
-	if b, err = json.Marshal(w); err != nil {
-		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
-	}
-	c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
-	if err != nil {
-		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
-	}
-	if c == nil || h == nil {
-		return nil, nil, fmt.Errorf("Empty config or hostconfig")
-	}
-
-	return c, h, err
-}
-
-// check if (a == c && b == d) || (a == d && b == c)
-// because maps are randomized
-func compareRandomizedStrings(a, b, c, d string) error {
-	if a == c && b == d {
-		return nil
-	}
-	if a == d && b == c {
-		return nil
-	}
-	return fmt.Errorf("strings don't match")
-}
-
 // setupPlatformVolume takes two arrays of volume specs - a Unix style
 // setupPlatformVolume takes two arrays of volume specs - a Unix style
 // spec and a Windows style spec. Depending on the platform being unit tested,
 // spec and a Windows style spec. Depending on the platform being unit tested,
 // it returns one of them, along with a volume string that would be passed
 // it returns one of them, along with a volume string that would be passed
@@ -351,6 +209,18 @@ func setupPlatformVolume(u []string, w []string) ([]string, string) {
 	return a, s
 	return a, s
 }
 }
 
 
+// check if (a == c && b == d) || (a == d && b == c)
+// because maps are randomized
+func compareRandomizedStrings(a, b, c, d string) error {
+	if a == c && b == d {
+		return nil
+	}
+	if a == d && b == c {
+		return nil
+	}
+	return fmt.Errorf("strings don't match")
+}
+
 // Simple parse with MacAddress validation
 // Simple parse with MacAddress validation
 func TestParseWithMacAddress(t *testing.T) {
 func TestParseWithMacAddress(t *testing.T) {
 	invalidMacAddress := "--mac-address=invalidMacAddress"
 	invalidMacAddress := "--mac-address=invalidMacAddress"
@@ -650,14 +520,14 @@ func TestParseEnvfileVariables(t *testing.T) {
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 	}
 	}
 	// env ok
 	// env ok
-	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
+	config, _, _, err := parseRun([]string{"--env-file=testdata/valid.env", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
 	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
 		t.Fatalf("Expected a config with [ENV1=value1], got %v", config.Env)
 		t.Fatalf("Expected a config with [ENV1=value1], got %v", config.Env)
 	}
 	}
-	config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
+	config, _, _, err = parseRun([]string{"--env-file=testdata/valid.env", "--env=ENV2=value2", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -668,7 +538,7 @@ func TestParseEnvfileVariables(t *testing.T) {
 
 
 func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) {
 func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) {
 	// UTF8 with BOM
 	// UTF8 with BOM
-	config, _, _, err := parseRun([]string{"--env-file=fixtures/utf8.env", "img", "cmd"})
+	config, _, _, err := parseRun([]string{"--env-file=testdata/utf8.env", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -684,11 +554,11 @@ func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) {
 
 
 	// UTF16 with BOM
 	// UTF16 with BOM
 	e := "contains invalid utf8 bytes at line"
 	e := "contains invalid utf8 bytes at line"
-	if _, _, _, err := parseRun([]string{"--env-file=fixtures/utf16.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
+	if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 	}
 	}
 	// UTF16BE with BOM
 	// UTF16BE with BOM
-	if _, _, _, err := parseRun([]string{"--env-file=fixtures/utf16be.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
+	if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16be.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 	}
 	}
 }
 }
@@ -703,14 +573,14 @@ func TestParseLabelfileVariables(t *testing.T) {
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 	}
 	}
 	// label ok
 	// label ok
-	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
+	config, _, _, err := parseRun([]string{"--label-file=testdata/valid.label", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
 	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
 		t.Fatalf("Expected a config with [LABEL1:value1], got %v", config.Labels)
 		t.Fatalf("Expected a config with [LABEL1:value1], got %v", config.Labels)
 	}
 	}
-	config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
+	config, _, _, err = parseRun([]string{"--label-file=testdata/valid.label", "--label=LABEL2=value2", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -729,118 +599,160 @@ func TestParseEntryPoint(t *testing.T) {
 	}
 	}
 }
 }
 
 
-func TestValidateLink(t *testing.T) {
-	valid := []string{
-		"name",
-		"dcdfbe62ecd0:alias",
-		"7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da",
-		"angry_torvalds:linus",
-	}
-	invalid := map[string]string{
-		"":               "empty string specified for links",
-		"too:much:of:it": "bad format for links: too:much:of:it",
-	}
+// This tests the cases for binds which are generated through
+// DecodeContainerConfig rather than Parse()
+func TestDecodeContainerConfigVolumes(t *testing.T) {
 
 
-	for _, link := range valid {
-		if _, err := ValidateLink(link); err != nil {
-			t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err)
-		}
+	// Root to root
+	bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
+		t.Fatalf("binds %v should have failed", bindsOrVols)
 	}
 	}
-
-	for link, expectedError := range invalid {
-		if _, err := ValidateLink(link); err == nil {
-			t.Fatalf("ValidateLink(`%q`) should have failed validation", link)
-		} else {
-			if !strings.Contains(err.Error(), expectedError) {
-				t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError)
-			}
-		}
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
+		t.Fatalf("volume %v should have failed", bindsOrVols)
 	}
 	}
-}
 
 
-func TestParseLink(t *testing.T) {
-	name, alias, err := ParseLink("name:alias")
-	if err != nil {
-		t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
+	// No destination path
+	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
+		t.Fatalf("binds %v should have failed", bindsOrVols)
 	}
 	}
-	if name != "name" {
-		t.Fatalf("Link name should have been name, got %s instead", name)
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
+		t.Fatalf("volume %v should have failed", bindsOrVols)
 	}
 	}
-	if alias != "alias" {
-		t.Fatalf("Link alias should have been alias, got %s instead", alias)
+
+	//	// No destination path or mode
+	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
+		t.Fatalf("binds %v should have failed", bindsOrVols)
 	}
 	}
-	// short format definition
-	name, alias, err = ParseLink("name")
-	if err != nil {
-		t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
+		t.Fatalf("volume %v should have failed", bindsOrVols)
 	}
 	}
-	if name != "name" {
-		t.Fatalf("Link name should have been name, got %s instead", name)
+
+	// A whole lot of nothing
+	bindsOrVols = []string{`:`}
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
+		t.Fatalf("binds %v should have failed", bindsOrVols)
 	}
 	}
-	if alias != "name" {
-		t.Fatalf("Link alias should have been name, got %s instead", alias)
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
+		t.Fatalf("volume %v should have failed", bindsOrVols)
 	}
 	}
-	// empty string link definition is not allowed
-	if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
-		t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
+
+	// A whole lot of nothing with no mode
+	bindsOrVols = []string{`::`}
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
+		t.Fatalf("binds %v should have failed", bindsOrVols)
 	}
 	}
-	// more than two colons are not allowed
-	if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
-		t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
+		t.Fatalf("volume %v should have failed", bindsOrVols)
 	}
 	}
-}
 
 
-func TestValidateDevice(t *testing.T) {
-	valid := []string{
-		"/home",
-		"/home:/home",
-		"/home:/something/else",
-		"/with space",
-		"/home:/with space",
-		"relative:/absolute-path",
-		"hostPath:/containerPath:r",
-		"/hostPath:/containerPath:rw",
-		"/hostPath:/containerPath:mrw",
+	// Too much including an invalid mode
+	wTmp := os.Getenv("TEMP")
+	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
+		t.Fatalf("binds %v should have failed", bindsOrVols)
 	}
 	}
-	invalid := map[string]string{
-		"":        "bad format for path: ",
-		"./":      "./ is not an absolute path",
-		"../":     "../ is not an absolute path",
-		"/:../":   "../ is not an absolute path",
-		"/:path":  "path is not an absolute path",
-		":":       "bad format for path: :",
-		"/tmp:":   " is not an absolute path",
-		":test":   "bad format for path: :test",
-		":/test":  "bad format for path: :/test",
-		"tmp:":    " is not an absolute path",
-		":test:":  "bad format for path: :test:",
-		"::":      "bad format for path: ::",
-		":::":     "bad format for path: :::",
-		"/tmp:::": "bad format for path: /tmp:::",
-		":/tmp::": "bad format for path: :/tmp::",
-		"path:ro": "ro is not an absolute path",
-		"path:rr": "rr is not an absolute path",
-		"a:/b:ro": "bad mode specified: ro",
-		"a:/b:rr": "bad mode specified: rr",
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
+		t.Fatalf("volume %v should have failed", bindsOrVols)
 	}
 	}
 
 
-	for _, path := range valid {
-		if _, err := ValidateDevice(path); err != nil {
-			t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
+	// Windows specific error tests
+	if runtime.GOOS == "windows" {
+		// Volume which does not include a drive letter
+		bindsOrVols = []string{`\tmp`}
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
+			t.Fatalf("binds %v should have failed", bindsOrVols)
+		}
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
+			t.Fatalf("volume %v should have failed", bindsOrVols)
+		}
+
+		// Root to C-Drive
+		bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
+			t.Fatalf("binds %v should have failed", bindsOrVols)
+		}
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
+			t.Fatalf("volume %v should have failed", bindsOrVols)
+		}
+
+		// Container path that does not include a drive letter
+		bindsOrVols = []string{`c:\windows:\somewhere`}
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
+			t.Fatalf("binds %v should have failed", bindsOrVols)
+		}
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
+			t.Fatalf("volume %v should have failed", bindsOrVols)
 		}
 		}
 	}
 	}
 
 
-	for path, expectedError := range invalid {
-		if _, err := ValidateDevice(path); err == nil {
-			t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
-		} else {
-			if err.Error() != expectedError {
-				t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
-			}
+	// Linux-specific error tests
+	if runtime.GOOS != "windows" {
+		// Just root
+		bindsOrVols = []string{`/`}
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
+			t.Fatalf("binds %v should have failed", bindsOrVols)
+		}
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
+			t.Fatalf("volume %v should have failed", bindsOrVols)
 		}
 		}
+
+		// A single volume that looks like a bind mount passed in Volumes.
+		// This should be handled as a bind mount, not a volume.
+		vols := []string{`/foo:/bar`}
+		if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
+			t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
+		} else if hostConfig.Binds != nil {
+			t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
+		} else if _, exists := config.Volumes[vols[0]]; !exists {
+			t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
+		}
+
 	}
 	}
 }
 }
 
 
+// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes
+// to call DecodeContainerConfig. It effectively does what a client would
+// do when calling the daemon by constructing a JSON stream of a
+// ContainerConfigWrapper which is populated by the set of volume specs
+// passed into it. It returns a config and a hostconfig which can be
+// validated to ensure DecodeContainerConfig has manipulated the structures
+// correctly.
+func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) {
+	var (
+		b   []byte
+		err error
+		c   *container.Config
+		h   *container.HostConfig
+	)
+	w := runconfig.ContainerConfigWrapper{
+		Config: &container.Config{
+			Volumes: map[string]struct{}{},
+		},
+		HostConfig: &container.HostConfig{
+			NetworkMode: "none",
+			Binds:       binds,
+		},
+	}
+	for _, v := range volumes {
+		w.Config.Volumes[v] = struct{}{}
+	}
+	if b, err = json.Marshal(w); err != nil {
+		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
+	}
+	c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
+	if err != nil {
+		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
+	}
+	if c == nil || h == nil {
+		return nil, nil, fmt.Errorf("Empty config or hostconfig")
+	}
+
+	return c, h, err
+}
+
 func TestVolumeSplitN(t *testing.T) {
 func TestVolumeSplitN(t *testing.T) {
 	for _, x := range []struct {
 	for _, x := range []struct {
 		input    string
 		input    string
@@ -892,3 +804,54 @@ func TestVolumeSplitN(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
+
+func TestValidateDevice(t *testing.T) {
+	valid := []string{
+		"/home",
+		"/home:/home",
+		"/home:/something/else",
+		"/with space",
+		"/home:/with space",
+		"relative:/absolute-path",
+		"hostPath:/containerPath:r",
+		"/hostPath:/containerPath:rw",
+		"/hostPath:/containerPath:mrw",
+	}
+	invalid := map[string]string{
+		"":        "bad format for path: ",
+		"./":      "./ is not an absolute path",
+		"../":     "../ is not an absolute path",
+		"/:../":   "../ is not an absolute path",
+		"/:path":  "path is not an absolute path",
+		":":       "bad format for path: :",
+		"/tmp:":   " is not an absolute path",
+		":test":   "bad format for path: :test",
+		":/test":  "bad format for path: :/test",
+		"tmp:":    " is not an absolute path",
+		":test:":  "bad format for path: :test:",
+		"::":      "bad format for path: ::",
+		":::":     "bad format for path: :::",
+		"/tmp:::": "bad format for path: /tmp:::",
+		":/tmp::": "bad format for path: :/tmp::",
+		"path:ro": "ro is not an absolute path",
+		"path:rr": "rr is not an absolute path",
+		"a:/b:ro": "bad mode specified: ro",
+		"a:/b:rr": "bad mode specified: rr",
+	}
+
+	for _, path := range valid {
+		if _, err := validateDevice(path); err != nil {
+			t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
+		}
+	}
+
+	for path, expectedError := range invalid {
+		if _, err := validateDevice(path); err == nil {
+			t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
+		} else {
+			if err.Error() != expectedError {
+				t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
+			}
+		}
+	}
+}

+ 5 - 6
cli/command/container/run.go

@@ -18,7 +18,6 @@ import (
 	opttypes "github.com/docker/docker/opts"
 	opttypes "github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
-	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/docker/libnetwork/resolvconf/dns"
 	"github.com/docker/libnetwork/resolvconf/dns"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
@@ -34,7 +33,7 @@ type runOptions struct {
 // NewRunCommand create a new `docker run` command
 // NewRunCommand create a new `docker run` command
 func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command {
 func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command {
 	var opts runOptions
 	var opts runOptions
-	var copts *runconfigopts.ContainerOptions
+	var copts *containerOptions
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
 		Use:   "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
 		Use:   "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
@@ -63,11 +62,11 @@ func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command {
 	flags.Bool("help", false, "Print usage")
 	flags.Bool("help", false, "Print usage")
 
 
 	command.AddTrustedFlags(flags, true)
 	command.AddTrustedFlags(flags, true)
-	copts = runconfigopts.AddFlags(flags)
+	copts = addFlags(flags)
 	return cmd
 	return cmd
 }
 }
 
 
-func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *runconfigopts.ContainerOptions) error {
+func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *containerOptions) error {
 	stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In()
 	stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In()
 	client := dockerCli.Client()
 	client := dockerCli.Client()
 	// TODO: pass this as an argument
 	// TODO: pass this as an argument
@@ -79,9 +78,9 @@ func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions
 		ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
 		ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
 	)
 	)
 
 
-	config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts)
+	config, hostConfig, networkingConfig, err := parse(flags, copts)
 
 
-	// just in case the Parse does not exit
+	// just in case the parse does not exit
 	if err != nil {
 	if err != nil {
 		reportError(stderr, cmdPath, err.Error(), true)
 		reportError(stderr, cmdPath, err.Error(), true)
 		return cli.StatusError{StatusCode: 125}
 		return cli.StatusError{StatusCode: 125}

+ 0 - 0
runconfig/opts/fixtures/utf16.env → cli/command/container/testdata/utf16.env


+ 0 - 0
runconfig/opts/fixtures/utf16be.env → cli/command/container/testdata/utf16be.env


+ 0 - 0
runconfig/opts/fixtures/utf8.env → cli/command/container/testdata/utf8.env


+ 0 - 0
runconfig/opts/fixtures/valid.env → cli/command/container/testdata/valid.env


+ 0 - 0
runconfig/opts/fixtures/valid.label → cli/command/container/testdata/valid.label


+ 5 - 6
cli/command/image/build.go

@@ -11,8 +11,6 @@ import (
 	"regexp"
 	"regexp"
 	"runtime"
 	"runtime"
 
 
-	"golang.org/x/net/context"
-
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
@@ -31,6 +29,7 @@ import (
 	runconfigopts "github.com/docker/docker/runconfig/opts"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/docker/go-units"
 	"github.com/docker/go-units"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
 )
 )
 
 
 type buildOptions struct {
 type buildOptions struct {
@@ -39,7 +38,7 @@ type buildOptions struct {
 	tags           opts.ListOpts
 	tags           opts.ListOpts
 	labels         opts.ListOpts
 	labels         opts.ListOpts
 	buildArgs      opts.ListOpts
 	buildArgs      opts.ListOpts
-	ulimits        *runconfigopts.UlimitOpt
+	ulimits        *opts.UlimitOpt
 	memory         string
 	memory         string
 	memorySwap     string
 	memorySwap     string
 	shmSize        string
 	shmSize        string
@@ -67,9 +66,9 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
 	ulimits := make(map[string]*units.Ulimit)
 	ulimits := make(map[string]*units.Ulimit)
 	options := buildOptions{
 	options := buildOptions{
 		tags:      opts.NewListOpts(validateTag),
 		tags:      opts.NewListOpts(validateTag),
-		buildArgs: opts.NewListOpts(runconfigopts.ValidateEnv),
-		ulimits:   runconfigopts.NewUlimitOpt(&ulimits),
-		labels:    opts.NewListOpts(runconfigopts.ValidateEnv),
+		buildArgs: opts.NewListOpts(opts.ValidateEnv),
+		ulimits:   opts.NewUlimitOpt(&ulimits),
+		labels:    opts.NewListOpts(opts.ValidateEnv),
 	}
 	}
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{

+ 1 - 2
cli/command/network/connect.go

@@ -7,7 +7,6 @@ import (
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
-	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
@@ -23,7 +22,7 @@ type connectOptions struct {
 
 
 func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
 func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
 	opts := connectOptions{
 	opts := connectOptions{
-		links: opts.NewListOpts(runconfigopts.ValidateLink),
+		links: opts.NewListOpts(opts.ValidateLink),
 	}
 	}
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{

+ 1 - 1
cli/command/network/create.go

@@ -36,7 +36,7 @@ type createOptions struct {
 func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 	opts := createOptions{
 	opts := createOptions{
 		driverOpts: *opts.NewMapOpts(nil, nil),
 		driverOpts: *opts.NewMapOpts(nil, nil),
-		labels:     opts.NewListOpts(runconfigopts.ValidateEnv),
+		labels:     opts.NewListOpts(opts.ValidateEnv),
 		ipamAux:    *opts.NewMapOpts(nil, nil),
 		ipamAux:    *opts.NewMapOpts(nil, nil),
 		ipamOpt:    *opts.NewMapOpts(nil, nil),
 		ipamOpt:    *opts.NewMapOpts(nil, nil),
 	}
 	}

+ 1 - 1
cli/command/secret/create.go

@@ -23,7 +23,7 @@ type createOptions struct {
 
 
 func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 	createOpts := createOptions{
 	createOpts := createOptions{
-		labels: opts.NewListOpts(runconfigopts.ValidateEnv),
+		labels: opts.NewListOpts(opts.ValidateEnv),
 	}
 	}
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{

+ 5 - 5
cli/command/service/opts.go

@@ -304,7 +304,7 @@ type logDriverOptions struct {
 }
 }
 
 
 func newLogDriverOptions() logDriverOptions {
 func newLogDriverOptions() logDriverOptions {
-	return logDriverOptions{opts: opts.NewListOpts(runconfigopts.ValidateEnv)}
+	return logDriverOptions{opts: opts.NewListOpts(opts.ValidateEnv)}
 }
 }
 
 
 func (ldo *logDriverOptions) toLogDriver() *swarm.Driver {
 func (ldo *logDriverOptions) toLogDriver() *swarm.Driver {
@@ -426,17 +426,17 @@ type serviceOptions struct {
 
 
 func newServiceOptions() *serviceOptions {
 func newServiceOptions() *serviceOptions {
 	return &serviceOptions{
 	return &serviceOptions{
-		labels:          opts.NewListOpts(runconfigopts.ValidateEnv),
+		labels:          opts.NewListOpts(opts.ValidateEnv),
 		constraints:     opts.NewListOpts(nil),
 		constraints:     opts.NewListOpts(nil),
-		containerLabels: opts.NewListOpts(runconfigopts.ValidateEnv),
-		env:             opts.NewListOpts(runconfigopts.ValidateEnv),
+		containerLabels: opts.NewListOpts(opts.ValidateEnv),
+		env:             opts.NewListOpts(opts.ValidateEnv),
 		envFile:         opts.NewListOpts(nil),
 		envFile:         opts.NewListOpts(nil),
 		groups:          opts.NewListOpts(nil),
 		groups:          opts.NewListOpts(nil),
 		logDriver:       newLogDriverOptions(),
 		logDriver:       newLogDriverOptions(),
 		dns:             opts.NewListOpts(opts.ValidateIPAddress),
 		dns:             opts.NewListOpts(opts.ValidateIPAddress),
 		dnsOption:       opts.NewListOpts(nil),
 		dnsOption:       opts.NewListOpts(nil),
 		dnsSearch:       opts.NewListOpts(opts.ValidateDNSSearch),
 		dnsSearch:       opts.NewListOpts(opts.ValidateDNSSearch),
-		hosts:           opts.NewListOpts(runconfigopts.ValidateExtraHost),
+		hosts:           opts.NewListOpts(opts.ValidateExtraHost),
 		networks:        opts.NewListOpts(nil),
 		networks:        opts.NewListOpts(nil),
 	}
 	}
 }
 }

+ 2 - 3
cli/command/volume/create.go

@@ -3,14 +3,13 @@ package volume
 import (
 import (
 	"fmt"
 	"fmt"
 
 
-	"golang.org/x/net/context"
-
 	volumetypes "github.com/docker/docker/api/types/volume"
 	volumetypes "github.com/docker/docker/api/types/volume"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
 )
 )
 
 
 type createOptions struct {
 type createOptions struct {
@@ -23,7 +22,7 @@ type createOptions struct {
 func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 	opts := createOptions{
 	opts := createOptions{
 		driverOpts: *opts.NewMapOpts(nil, nil),
 		driverOpts: *opts.NewMapOpts(nil, nil),
-		labels:     opts.NewListOpts(runconfigopts.ValidateEnv),
+		labels:     opts.NewListOpts(opts.ValidateEnv),
 	}
 	}
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{

+ 2 - 2
container/container.go

@@ -28,6 +28,7 @@ import (
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/libcontainerd"
 	"github.com/docker/docker/libcontainerd"
+	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/promise"
@@ -35,7 +36,6 @@ import (
 	"github.com/docker/docker/pkg/symlink"
 	"github.com/docker/docker/pkg/symlink"
 	"github.com/docker/docker/restartmanager"
 	"github.com/docker/docker/restartmanager"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
-	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork"
@@ -815,7 +815,7 @@ func (container *Container) BuildJoinOptions(n libnetwork.Network) ([]libnetwork
 	var joinOptions []libnetwork.EndpointOption
 	var joinOptions []libnetwork.EndpointOption
 	if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
 	if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
 		for _, str := range epConfig.Links {
 		for _, str := range epConfig.Links {
-			name, alias, err := runconfigopts.ParseLink(str)
+			name, alias, err := opts.ParseLink(str)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}

+ 1 - 2
daemon/config_common_unix.go

@@ -7,7 +7,6 @@ import (
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
-	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
 )
 )
 
 
@@ -41,7 +40,7 @@ func (config *Config) InstallCommonUnixFlags(flags *pflag.FlagSet) {
 	flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv6, ""), "default-gateway-v6", "Container default gateway IPv6 address")
 	flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv6, ""), "default-gateway-v6", "Container default gateway IPv6 address")
 	flags.BoolVar(&config.bridgeConfig.InterContainerCommunication, "icc", true, "Enable inter-container communication")
 	flags.BoolVar(&config.bridgeConfig.InterContainerCommunication, "icc", true, "Enable inter-container communication")
 	flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultIP, "0.0.0.0"), "ip", "Default IP when binding container ports")
 	flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultIP, "0.0.0.0"), "ip", "Default IP when binding container ports")
-	flags.Var(runconfigopts.NewNamedRuntimeOpt("runtimes", &config.Runtimes, stockRuntimeName), "add-runtime", "Register an additional OCI compatible runtime")
+	flags.Var(opts.NewNamedRuntimeOpt("runtimes", &config.Runtimes, stockRuntimeName), "add-runtime", "Register an additional OCI compatible runtime")
 	flags.StringVar(&config.DefaultRuntime, "default-runtime", stockRuntimeName, "Default OCI runtime for containers")
 	flags.StringVar(&config.DefaultRuntime, "default-runtime", stockRuntimeName, "Default OCI runtime for containers")
 
 
 }
 }

+ 2 - 2
daemon/config_unix.go

@@ -5,7 +5,7 @@ package daemon
 import (
 import (
 	"fmt"
 	"fmt"
 
 
-	runconfigopts "github.com/docker/docker/runconfig/opts"
+	"github.com/docker/docker/opts"
 	units "github.com/docker/go-units"
 	units "github.com/docker/go-units"
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
 )
 )
@@ -68,7 +68,7 @@ func (config *Config) InstallFlags(flags *pflag.FlagSet) {
 
 
 	// Then platform-specific install flags
 	// Then platform-specific install flags
 	flags.BoolVar(&config.EnableSelinuxSupport, "selinux-enabled", false, "Enable selinux support")
 	flags.BoolVar(&config.EnableSelinuxSupport, "selinux-enabled", false, "Enable selinux support")
-	flags.Var(runconfigopts.NewUlimitOpt(&config.Ulimits), "default-ulimit", "Default ulimits for containers")
+	flags.Var(opts.NewUlimitOpt(&config.Ulimits), "default-ulimit", "Default ulimits for containers")
 	flags.BoolVar(&config.bridgeConfig.EnableIPTables, "iptables", true, "Enable addition of iptables rules")
 	flags.BoolVar(&config.bridgeConfig.EnableIPTables, "iptables", true, "Enable addition of iptables rules")
 	flags.BoolVar(&config.bridgeConfig.EnableIPForward, "ip-forward", true, "Enable net.ipv4.ip_forward")
 	flags.BoolVar(&config.bridgeConfig.EnableIPForward, "ip-forward", true, "Enable net.ipv4.ip_forward")
 	flags.BoolVar(&config.bridgeConfig.EnableIPMasq, "ip-masq", true, "Enable IP masquerading")
 	flags.BoolVar(&config.bridgeConfig.EnableIPMasq, "ip-masq", true, "Enable IP masquerading")

+ 1 - 1
daemon/container.go

@@ -12,10 +12,10 @@ import (
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/daemon/network"
 	"github.com/docker/docker/daemon/network"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
+	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/pkg/truncindex"
 	"github.com/docker/docker/pkg/truncindex"
-	"github.com/docker/docker/runconfig/opts"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
 )
 )
 
 

+ 2 - 2
daemon/daemon_unix.go

@@ -23,12 +23,12 @@ import (
 	containertypes "github.com/docker/docker/api/types/container"
 	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
+	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/parsers"
 	"github.com/docker/docker/pkg/parsers"
 	"github.com/docker/docker/pkg/parsers/kernel"
 	"github.com/docker/docker/pkg/parsers/kernel"
 	"github.com/docker/docker/pkg/sysinfo"
 	"github.com/docker/docker/pkg/sysinfo"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
-	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork"
 	nwconfig "github.com/docker/libnetwork/config"
 	nwconfig "github.com/docker/libnetwork/config"
 	"github.com/docker/libnetwork/drivers/bridge"
 	"github.com/docker/libnetwork/drivers/bridge"
@@ -1098,7 +1098,7 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *
 	}
 	}
 
 
 	for _, l := range hostConfig.Links {
 	for _, l := range hostConfig.Links {
-		name, alias, err := runconfigopts.ParseLink(l)
+		name, alias, err := opts.ParseLink(l)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}

+ 46 - 0
opts/env.go

@@ -0,0 +1,46 @@
+package opts
+
+import (
+	"fmt"
+	"os"
+	"runtime"
+	"strings"
+)
+
+// ValidateEnv validates an environment variable and returns it.
+// If no value is specified, it returns the current value using os.Getenv.
+//
+// As on ParseEnvFile and related to #16585, environment variable names
+// are not validate what so ever, it's up to application inside docker
+// to validate them or not.
+//
+// The only validation here is to check if name is empty, per #25099
+func ValidateEnv(val string) (string, error) {
+	arr := strings.Split(val, "=")
+	if arr[0] == "" {
+		return "", fmt.Errorf("invalid environment variable: %s", val)
+	}
+	if len(arr) > 1 {
+		return val, nil
+	}
+	if !doesEnvExist(val) {
+		return val, nil
+	}
+	return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
+}
+
+func doesEnvExist(name string) bool {
+	for _, entry := range os.Environ() {
+		parts := strings.SplitN(entry, "=", 2)
+		if runtime.GOOS == "windows" {
+			// Environment variable are case-insensitive on Windows. PaTh, path and PATH are equivalent.
+			if strings.EqualFold(parts[0], name) {
+				return true
+			}
+		}
+		if parts[0] == name {
+			return true
+		}
+	}
+	return false
+}

+ 42 - 0
opts/env_test.go

@@ -0,0 +1,42 @@
+package opts
+
+import (
+	"fmt"
+	"os"
+	"runtime"
+	"testing"
+)
+
+func TestValidateEnv(t *testing.T) {
+	valids := map[string]string{
+		"a":                   "a",
+		"something":           "something",
+		"_=a":                 "_=a",
+		"env1=value1":         "env1=value1",
+		"_env1=value1":        "_env1=value1",
+		"env2=value2=value3":  "env2=value2=value3",
+		"env3=abc!qwe":        "env3=abc!qwe",
+		"env_4=value 4":       "env_4=value 4",
+		"PATH":                fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
+		"PATH=something":      "PATH=something",
+		"asd!qwe":             "asd!qwe",
+		"1asd":                "1asd",
+		"123":                 "123",
+		"some space":          "some space",
+		"  some space before": "  some space before",
+		"some space after  ":  "some space after  ",
+	}
+	// Environment variables are case in-sensitive on Windows
+	if runtime.GOOS == "windows" {
+		valids["PaTh"] = fmt.Sprintf("PaTh=%v", os.Getenv("PATH"))
+	}
+	for value, expected := range valids {
+		actual, err := ValidateEnv(value)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if actual != expected {
+			t.Fatalf("Expected [%v], got [%v]", expected, actual)
+		}
+	}
+}

+ 14 - 0
opts/hosts.go

@@ -149,3 +149,17 @@ func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
 
 
 	return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil
 	return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil
 }
 }
+
+// ValidateExtraHost validates that the specified string is a valid extrahost and returns it.
+// ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6).
+func ValidateExtraHost(val string) (string, error) {
+	// allow for IPv6 addresses in extra hosts by only splitting on first ":"
+	arr := strings.SplitN(val, ":", 2)
+	if len(arr) != 2 || len(arr[0]) == 0 {
+		return "", fmt.Errorf("bad format for add-host: %q", val)
+	}
+	if _, err := ValidateIPAddress(arr[1]); err != nil {
+		return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1])
+	}
+	return val, nil
+}

+ 33 - 0
opts/hosts_test.go

@@ -2,6 +2,7 @@ package opts
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"strings"
 	"testing"
 	"testing"
 )
 )
 
 
@@ -146,3 +147,35 @@ func TestParseInvalidUnixAddrInvalid(t *testing.T) {
 		t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock")
 		t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock")
 	}
 	}
 }
 }
+
+func TestValidateExtraHosts(t *testing.T) {
+	valid := []string{
+		`myhost:192.168.0.1`,
+		`thathost:10.0.2.1`,
+		`anipv6host:2003:ab34:e::1`,
+		`ipv6local:::1`,
+	}
+
+	invalid := map[string]string{
+		`myhost:192.notanipaddress.1`:  `invalid IP`,
+		`thathost-nosemicolon10.0.0.1`: `bad format`,
+		`anipv6host:::::1`:             `invalid IP`,
+		`ipv6local:::0::`:              `invalid IP`,
+	}
+
+	for _, extrahost := range valid {
+		if _, err := ValidateExtraHost(extrahost); err != nil {
+			t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err)
+		}
+	}
+
+	for extraHost, expectedError := range invalid {
+		if _, err := ValidateExtraHost(extraHost); err == nil {
+			t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost)
+		} else {
+			if !strings.Contains(err.Error(), expectedError) {
+				t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError)
+			}
+		}
+	}
+}

+ 38 - 0
opts/opts.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"fmt"
 	"math/big"
 	"math/big"
 	"net"
 	"net"
+	"path"
 	"regexp"
 	"regexp"
 	"strings"
 	"strings"
 
 
@@ -231,6 +232,15 @@ func ValidateIPAddress(val string) (string, error) {
 	return "", fmt.Errorf("%s is not an ip address", val)
 	return "", fmt.Errorf("%s is not an ip address", val)
 }
 }
 
 
+// ValidateMACAddress validates a MAC address.
+func ValidateMACAddress(val string) (string, error) {
+	_, err := net.ParseMAC(strings.TrimSpace(val))
+	if err != nil {
+		return "", err
+	}
+	return val, nil
+}
+
 // ValidateDNSSearch validates domain for resolvconf search configuration.
 // ValidateDNSSearch validates domain for resolvconf search configuration.
 // A zero length domain is represented by a dot (.).
 // A zero length domain is represented by a dot (.).
 func ValidateDNSSearch(val string) (string, error) {
 func ValidateDNSSearch(val string) (string, error) {
@@ -364,3 +374,31 @@ func ParseCPUs(value string) (int64, error) {
 	}
 	}
 	return nano.Num().Int64(), nil
 	return nano.Num().Int64(), nil
 }
 }
+
+// ParseLink parses and validates the specified string as a link format (name:alias)
+func ParseLink(val string) (string, string, error) {
+	if val == "" {
+		return "", "", fmt.Errorf("empty string specified for links")
+	}
+	arr := strings.Split(val, ":")
+	if len(arr) > 2 {
+		return "", "", fmt.Errorf("bad format for links: %s", val)
+	}
+	if len(arr) == 1 {
+		return val, val, nil
+	}
+	// This is kept because we can actually get a HostConfig with links
+	// from an already created container and the format is not `foo:bar`
+	// but `/foo:/c1/bar`
+	if strings.HasPrefix(arr[0], "/") {
+		_, alias := path.Split(arr[1])
+		return arr[0][1:], alias, nil
+	}
+	return arr[0], arr[1], nil
+}
+
+// ValidateLink validates that the specified string has a valid link format (containerName:alias).
+func ValidateLink(val string) (string, error) {
+	_, _, err := ParseLink(val)
+	return val, err
+}

+ 75 - 0
opts/opts_test.go

@@ -230,3 +230,78 @@ func TestNamedMapOpts(t *testing.T) {
 		t.Errorf("expected map-size to be in the values, got %v", tmpMap)
 		t.Errorf("expected map-size to be in the values, got %v", tmpMap)
 	}
 	}
 }
 }
+
+func TestValidateMACAddress(t *testing.T) {
+	if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil {
+		t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err)
+	}
+
+	if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil {
+		t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC")
+	}
+
+	if _, err := ValidateMACAddress(`random invalid string`); err == nil {
+		t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC")
+	}
+}
+
+func TestValidateLink(t *testing.T) {
+	valid := []string{
+		"name",
+		"dcdfbe62ecd0:alias",
+		"7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da",
+		"angry_torvalds:linus",
+	}
+	invalid := map[string]string{
+		"":               "empty string specified for links",
+		"too:much:of:it": "bad format for links: too:much:of:it",
+	}
+
+	for _, link := range valid {
+		if _, err := ValidateLink(link); err != nil {
+			t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err)
+		}
+	}
+
+	for link, expectedError := range invalid {
+		if _, err := ValidateLink(link); err == nil {
+			t.Fatalf("ValidateLink(`%q`) should have failed validation", link)
+		} else {
+			if !strings.Contains(err.Error(), expectedError) {
+				t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError)
+			}
+		}
+	}
+}
+
+func TestParseLink(t *testing.T) {
+	name, alias, err := ParseLink("name:alias")
+	if err != nil {
+		t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
+	}
+	if name != "name" {
+		t.Fatalf("Link name should have been name, got %s instead", name)
+	}
+	if alias != "alias" {
+		t.Fatalf("Link alias should have been alias, got %s instead", alias)
+	}
+	// short format definition
+	name, alias, err = ParseLink("name")
+	if err != nil {
+		t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
+	}
+	if name != "name" {
+		t.Fatalf("Link name should have been name, got %s instead", name)
+	}
+	if alias != "name" {
+		t.Fatalf("Link alias should have been name, got %s instead", alias)
+	}
+	// empty string link definition is not allowed
+	if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
+		t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
+	}
+	// more than two colons are not allowed
+	if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
+		t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
+	}
+}

+ 0 - 0
runconfig/opts/runtime.go → opts/runtime.go


+ 0 - 0
runconfig/opts/throttledevice.go → opts/throttledevice.go


+ 0 - 0
runconfig/opts/ulimit.go → opts/ulimit.go


+ 0 - 0
runconfig/opts/ulimit_test.go → opts/ulimit_test.go


+ 0 - 0
runconfig/opts/weightdevice.go → opts/weightdevice.go


+ 0 - 83
runconfig/opts/opts.go

@@ -1,83 +0,0 @@
-package opts
-
-import (
-	"fmt"
-	"net"
-	"os"
-	"runtime"
-	"strings"
-
-	fopts "github.com/docker/docker/opts"
-)
-
-// ValidateAttach validates that the specified string is a valid attach option.
-func ValidateAttach(val string) (string, error) {
-	s := strings.ToLower(val)
-	for _, str := range []string{"stdin", "stdout", "stderr"} {
-		if s == str {
-			return s, nil
-		}
-	}
-	return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR")
-}
-
-// ValidateEnv validates an environment variable and returns it.
-// If no value is specified, it returns the current value using os.Getenv.
-//
-// As on ParseEnvFile and related to #16585, environment variable names
-// are not validate what so ever, it's up to application inside docker
-// to validate them or not.
-//
-// The only validation here is to check if name is empty, per #25099
-func ValidateEnv(val string) (string, error) {
-	arr := strings.Split(val, "=")
-	if arr[0] == "" {
-		return "", fmt.Errorf("invalid environment variable: %s", val)
-	}
-	if len(arr) > 1 {
-		return val, nil
-	}
-	if !doesEnvExist(val) {
-		return val, nil
-	}
-	return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
-}
-
-func doesEnvExist(name string) bool {
-	for _, entry := range os.Environ() {
-		parts := strings.SplitN(entry, "=", 2)
-		if runtime.GOOS == "windows" {
-			// Environment variable are case-insensitive on Windows. PaTh, path and PATH are equivalent.
-			if strings.EqualFold(parts[0], name) {
-				return true
-			}
-		}
-		if parts[0] == name {
-			return true
-		}
-	}
-	return false
-}
-
-// ValidateExtraHost validates that the specified string is a valid extrahost and returns it.
-// ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6).
-func ValidateExtraHost(val string) (string, error) {
-	// allow for IPv6 addresses in extra hosts by only splitting on first ":"
-	arr := strings.SplitN(val, ":", 2)
-	if len(arr) != 2 || len(arr[0]) == 0 {
-		return "", fmt.Errorf("bad format for add-host: %q", val)
-	}
-	if _, err := fopts.ValidateIPAddress(arr[1]); err != nil {
-		return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1])
-	}
-	return val, nil
-}
-
-// ValidateMACAddress validates a MAC address.
-func ValidateMACAddress(val string) (string, error) {
-	_, err := net.ParseMAC(strings.TrimSpace(val))
-	if err != nil {
-		return "", err
-	}
-	return val, nil
-}

+ 0 - 113
runconfig/opts/opts_test.go

@@ -1,113 +0,0 @@
-package opts
-
-import (
-	"fmt"
-	"os"
-	"runtime"
-	"strings"
-	"testing"
-)
-
-func TestValidateAttach(t *testing.T) {
-	valid := []string{
-		"stdin",
-		"stdout",
-		"stderr",
-		"STDIN",
-		"STDOUT",
-		"STDERR",
-	}
-	if _, err := ValidateAttach("invalid"); err == nil {
-		t.Fatalf("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing")
-	}
-
-	for _, attach := range valid {
-		value, err := ValidateAttach(attach)
-		if err != nil {
-			t.Fatal(err)
-		}
-		if value != strings.ToLower(attach) {
-			t.Fatalf("Expected [%v], got [%v]", attach, value)
-		}
-	}
-}
-
-func TestValidateEnv(t *testing.T) {
-	valids := map[string]string{
-		"a":                   "a",
-		"something":           "something",
-		"_=a":                 "_=a",
-		"env1=value1":         "env1=value1",
-		"_env1=value1":        "_env1=value1",
-		"env2=value2=value3":  "env2=value2=value3",
-		"env3=abc!qwe":        "env3=abc!qwe",
-		"env_4=value 4":       "env_4=value 4",
-		"PATH":                fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
-		"PATH=something":      "PATH=something",
-		"asd!qwe":             "asd!qwe",
-		"1asd":                "1asd",
-		"123":                 "123",
-		"some space":          "some space",
-		"  some space before": "  some space before",
-		"some space after  ":  "some space after  ",
-	}
-	// Environment variables are case in-sensitive on Windows
-	if runtime.GOOS == "windows" {
-		valids["PaTh"] = fmt.Sprintf("PaTh=%v", os.Getenv("PATH"))
-	}
-	for value, expected := range valids {
-		actual, err := ValidateEnv(value)
-		if err != nil {
-			t.Fatal(err)
-		}
-		if actual != expected {
-			t.Fatalf("Expected [%v], got [%v]", expected, actual)
-		}
-	}
-}
-
-func TestValidateExtraHosts(t *testing.T) {
-	valid := []string{
-		`myhost:192.168.0.1`,
-		`thathost:10.0.2.1`,
-		`anipv6host:2003:ab34:e::1`,
-		`ipv6local:::1`,
-	}
-
-	invalid := map[string]string{
-		`myhost:192.notanipaddress.1`:  `invalid IP`,
-		`thathost-nosemicolon10.0.0.1`: `bad format`,
-		`anipv6host:::::1`:             `invalid IP`,
-		`ipv6local:::0::`:              `invalid IP`,
-	}
-
-	for _, extrahost := range valid {
-		if _, err := ValidateExtraHost(extrahost); err != nil {
-			t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err)
-		}
-	}
-
-	for extraHost, expectedError := range invalid {
-		if _, err := ValidateExtraHost(extraHost); err == nil {
-			t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost)
-		} else {
-			if !strings.Contains(err.Error(), expectedError) {
-				t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError)
-			}
-		}
-	}
-}
-
-func TestValidateMACAddress(t *testing.T) {
-	if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil {
-		t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err)
-	}
-
-	if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil {
-		t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC")
-	}
-
-	if _, err := ValidateMACAddress(`random invalid string`); err == nil {
-		t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC")
-	}
-}

+ 0 - 906
runconfig/opts/parse.go

@@ -1,678 +1,13 @@
 package opts
 package opts
 
 
 import (
 import (
-	"bytes"
-	"encoding/json"
 	"fmt"
 	"fmt"
-	"io/ioutil"
-	"path"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
-	"time"
 
 
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
-	networktypes "github.com/docker/docker/api/types/network"
-	"github.com/docker/docker/api/types/strslice"
-	"github.com/docker/docker/opts"
-	"github.com/docker/docker/pkg/signal"
-	"github.com/docker/go-connections/nat"
-	units "github.com/docker/go-units"
-	"github.com/spf13/pflag"
 )
 )
 
 
-// ContainerOptions is a data object with all the options for creating a container
-type ContainerOptions struct {
-	attach             opts.ListOpts
-	volumes            opts.ListOpts
-	tmpfs              opts.ListOpts
-	blkioWeightDevice  WeightdeviceOpt
-	deviceReadBps      ThrottledeviceOpt
-	deviceWriteBps     ThrottledeviceOpt
-	links              opts.ListOpts
-	aliases            opts.ListOpts
-	linkLocalIPs       opts.ListOpts
-	deviceReadIOps     ThrottledeviceOpt
-	deviceWriteIOps    ThrottledeviceOpt
-	env                opts.ListOpts
-	labels             opts.ListOpts
-	devices            opts.ListOpts
-	ulimits            *UlimitOpt
-	sysctls            *opts.MapOpts
-	publish            opts.ListOpts
-	expose             opts.ListOpts
-	dns                opts.ListOpts
-	dnsSearch          opts.ListOpts
-	dnsOptions         opts.ListOpts
-	extraHosts         opts.ListOpts
-	volumesFrom        opts.ListOpts
-	envFile            opts.ListOpts
-	capAdd             opts.ListOpts
-	capDrop            opts.ListOpts
-	groupAdd           opts.ListOpts
-	securityOpt        opts.ListOpts
-	storageOpt         opts.ListOpts
-	labelsFile         opts.ListOpts
-	loggingOpts        opts.ListOpts
-	privileged         bool
-	pidMode            string
-	utsMode            string
-	usernsMode         string
-	publishAll         bool
-	stdin              bool
-	tty                bool
-	oomKillDisable     bool
-	oomScoreAdj        int
-	containerIDFile    string
-	entrypoint         string
-	hostname           string
-	memoryString       string
-	memoryReservation  string
-	memorySwap         string
-	kernelMemory       string
-	user               string
-	workingDir         string
-	cpuCount           int64
-	cpuShares          int64
-	cpuPercent         int64
-	cpuPeriod          int64
-	cpuRealtimePeriod  int64
-	cpuRealtimeRuntime int64
-	cpuQuota           int64
-	cpus               opts.NanoCPUs
-	cpusetCpus         string
-	cpusetMems         string
-	blkioWeight        uint16
-	ioMaxBandwidth     string
-	ioMaxIOps          uint64
-	swappiness         int64
-	netMode            string
-	macAddress         string
-	ipv4Address        string
-	ipv6Address        string
-	ipcMode            string
-	pidsLimit          int64
-	restartPolicy      string
-	readonlyRootfs     bool
-	loggingDriver      string
-	cgroupParent       string
-	volumeDriver       string
-	stopSignal         string
-	stopTimeout        int
-	isolation          string
-	shmSize            string
-	noHealthcheck      bool
-	healthCmd          string
-	healthInterval     time.Duration
-	healthTimeout      time.Duration
-	healthRetries      int
-	runtime            string
-	autoRemove         bool
-	init               bool
-	initPath           string
-	credentialSpec     string
-
-	Image string
-	Args  []string
-}
-
-// AddFlags adds all command line flags that will be used by Parse to the FlagSet
-func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
-	copts := &ContainerOptions{
-		aliases:           opts.NewListOpts(nil),
-		attach:            opts.NewListOpts(ValidateAttach),
-		blkioWeightDevice: NewWeightdeviceOpt(ValidateWeightDevice),
-		capAdd:            opts.NewListOpts(nil),
-		capDrop:           opts.NewListOpts(nil),
-		dns:               opts.NewListOpts(opts.ValidateIPAddress),
-		dnsOptions:        opts.NewListOpts(nil),
-		dnsSearch:         opts.NewListOpts(opts.ValidateDNSSearch),
-		deviceReadBps:     NewThrottledeviceOpt(ValidateThrottleBpsDevice),
-		deviceReadIOps:    NewThrottledeviceOpt(ValidateThrottleIOpsDevice),
-		deviceWriteBps:    NewThrottledeviceOpt(ValidateThrottleBpsDevice),
-		deviceWriteIOps:   NewThrottledeviceOpt(ValidateThrottleIOpsDevice),
-		devices:           opts.NewListOpts(ValidateDevice),
-		env:               opts.NewListOpts(ValidateEnv),
-		envFile:           opts.NewListOpts(nil),
-		expose:            opts.NewListOpts(nil),
-		extraHosts:        opts.NewListOpts(ValidateExtraHost),
-		groupAdd:          opts.NewListOpts(nil),
-		labels:            opts.NewListOpts(ValidateEnv),
-		labelsFile:        opts.NewListOpts(nil),
-		linkLocalIPs:      opts.NewListOpts(nil),
-		links:             opts.NewListOpts(ValidateLink),
-		loggingOpts:       opts.NewListOpts(nil),
-		publish:           opts.NewListOpts(nil),
-		securityOpt:       opts.NewListOpts(nil),
-		storageOpt:        opts.NewListOpts(nil),
-		sysctls:           opts.NewMapOpts(nil, opts.ValidateSysctl),
-		tmpfs:             opts.NewListOpts(nil),
-		ulimits:           NewUlimitOpt(nil),
-		volumes:           opts.NewListOpts(nil),
-		volumesFrom:       opts.NewListOpts(nil),
-	}
-
-	// General purpose flags
-	flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR")
-	flags.Var(&copts.devices, "device", "Add a host device to the container")
-	flags.VarP(&copts.env, "env", "e", "Set environment variables")
-	flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables")
-	flags.StringVar(&copts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image")
-	flags.Var(&copts.groupAdd, "group-add", "Add additional groups to join")
-	flags.StringVarP(&copts.hostname, "hostname", "h", "", "Container host name")
-	flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached")
-	flags.VarP(&copts.labels, "label", "l", "Set meta data on a container")
-	flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels")
-	flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only")
-	flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits")
-	flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
-	flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container")
-	flags.SetAnnotation("stop-timeout", "version", []string{"1.25"})
-	flags.Var(copts.sysctls, "sysctl", "Sysctl options")
-	flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
-	flags.Var(copts.ulimits, "ulimit", "Ulimit options")
-	flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
-	flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container")
-	flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits")
-
-	// Security
-	flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities")
-	flags.Var(&copts.capDrop, "cap-drop", "Drop Linux capabilities")
-	flags.BoolVar(&copts.privileged, "privileged", false, "Give extended privileges to this container")
-	flags.Var(&copts.securityOpt, "security-opt", "Security Options")
-	flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use")
-	flags.StringVar(&copts.credentialSpec, "credentialspec", "", "Credential spec for managed service account (Windows only)")
-
-	// Network and port publishing flag
-	flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
-	flags.Var(&copts.dns, "dns", "Set custom DNS servers")
-	// We allow for both "--dns-opt" and "--dns-option", although the latter is the recommended way.
-	// This is to be consistent with service create/update
-	flags.Var(&copts.dnsOptions, "dns-opt", "Set DNS options")
-	flags.Var(&copts.dnsOptions, "dns-option", "Set DNS options")
-	flags.MarkHidden("dns-opt")
-	flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains")
-	flags.Var(&copts.expose, "expose", "Expose a port or a range of ports")
-	flags.StringVar(&copts.ipv4Address, "ip", "", "Container IPv4 address (e.g. 172.30.100.104)")
-	flags.StringVar(&copts.ipv6Address, "ip6", "", "Container IPv6 address (e.g. 2001:db8::33)")
-	flags.Var(&copts.links, "link", "Add link to another container")
-	flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses")
-	flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
-	flags.VarP(&copts.publish, "publish", "p", "Publish a container's port(s) to the host")
-	flags.BoolVarP(&copts.publishAll, "publish-all", "P", false, "Publish all exposed ports to random ports")
-	// We allow for both "--net" and "--network", although the latter is the recommended way.
-	flags.StringVar(&copts.netMode, "net", "default", "Connect a container to a network")
-	flags.StringVar(&copts.netMode, "network", "default", "Connect a container to a network")
-	flags.MarkHidden("net")
-	// We allow for both "--net-alias" and "--network-alias", although the latter is the recommended way.
-	flags.Var(&copts.aliases, "net-alias", "Add network-scoped alias for the container")
-	flags.Var(&copts.aliases, "network-alias", "Add network-scoped alias for the container")
-	flags.MarkHidden("net-alias")
-
-	// Logging and storage
-	flags.StringVar(&copts.loggingDriver, "log-driver", "", "Logging driver for the container")
-	flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container")
-	flags.Var(&copts.loggingOpts, "log-opt", "Log driver options")
-	flags.Var(&copts.storageOpt, "storage-opt", "Storage driver options for the container")
-	flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory")
-	flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
-	flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume")
-
-	// Health-checking
-	flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health")
-	flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ns|us|ms|s|m|h) (default 0s)")
-	flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy")
-	flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ns|us|ms|s|m|h) (default 0s)")
-	flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
-
-	// Resource management
-	flags.Uint16Var(&copts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)")
-	flags.Var(&copts.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)")
-	flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file")
-	flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
-	flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
-	flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)")
-	flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)")
-	flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
-	flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
-	flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds")
-	flags.Int64Var(&copts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds")
-	flags.Int64VarP(&copts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
-	flags.Var(&copts.cpus, "cpus", "Number of CPUs")
-	flags.Var(&copts.deviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device")
-	flags.Var(&copts.deviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device")
-	flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device")
-	flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device")
-	flags.StringVar(&copts.ioMaxBandwidth, "io-maxbandwidth", "", "Maximum IO bandwidth limit for the system drive (Windows only)")
-	flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)")
-	flags.StringVar(&copts.kernelMemory, "kernel-memory", "", "Kernel memory limit")
-	flags.StringVarP(&copts.memoryString, "memory", "m", "", "Memory limit")
-	flags.StringVar(&copts.memoryReservation, "memory-reservation", "", "Memory soft limit")
-	flags.StringVar(&copts.memorySwap, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
-	flags.Int64Var(&copts.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)")
-	flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer")
-	flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)")
-	flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)")
-
-	// Low-level execution (cgroups, namespaces, ...)
-	flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
-	flags.StringVar(&copts.ipcMode, "ipc", "", "IPC namespace to use")
-	flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology")
-	flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use")
-	flags.StringVar(&copts.shmSize, "shm-size", "", "Size of /dev/shm, default value is 64MB")
-	flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use")
-	flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container")
-
-	flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes")
-	flags.StringVar(&copts.initPath, "init-path", "", "Path to the docker-init binary")
-	return copts
-}
-
-// Parse parses the args for the specified command and generates a Config,
-// a HostConfig and returns them with the specified command.
-// If the specified args are not valid, it will return an error.
-func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
-	var (
-		attachStdin  = copts.attach.Get("stdin")
-		attachStdout = copts.attach.Get("stdout")
-		attachStderr = copts.attach.Get("stderr")
-	)
-
-	// Validate the input mac address
-	if copts.macAddress != "" {
-		if _, err := ValidateMACAddress(copts.macAddress); err != nil {
-			return nil, nil, nil, fmt.Errorf("%s is not a valid mac address", copts.macAddress)
-		}
-	}
-	if copts.stdin {
-		attachStdin = true
-	}
-	// If -a is not set, attach to stdout and stderr
-	if copts.attach.Len() == 0 {
-		attachStdout = true
-		attachStderr = true
-	}
-
-	var err error
-
-	var memory int64
-	if copts.memoryString != "" {
-		memory, err = units.RAMInBytes(copts.memoryString)
-		if err != nil {
-			return nil, nil, nil, err
-		}
-	}
-
-	var memoryReservation int64
-	if copts.memoryReservation != "" {
-		memoryReservation, err = units.RAMInBytes(copts.memoryReservation)
-		if err != nil {
-			return nil, nil, nil, err
-		}
-	}
-
-	var memorySwap int64
-	if copts.memorySwap != "" {
-		if copts.memorySwap == "-1" {
-			memorySwap = -1
-		} else {
-			memorySwap, err = units.RAMInBytes(copts.memorySwap)
-			if err != nil {
-				return nil, nil, nil, err
-			}
-		}
-	}
-
-	var kernelMemory int64
-	if copts.kernelMemory != "" {
-		kernelMemory, err = units.RAMInBytes(copts.kernelMemory)
-		if err != nil {
-			return nil, nil, nil, err
-		}
-	}
-
-	swappiness := copts.swappiness
-	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
-		return nil, nil, nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
-	}
-
-	var shmSize int64
-	if copts.shmSize != "" {
-		shmSize, err = units.RAMInBytes(copts.shmSize)
-		if err != nil {
-			return nil, nil, nil, err
-		}
-	}
-
-	// TODO FIXME units.RAMInBytes should have a uint64 version
-	var maxIOBandwidth int64
-	if copts.ioMaxBandwidth != "" {
-		maxIOBandwidth, err = units.RAMInBytes(copts.ioMaxBandwidth)
-		if err != nil {
-			return nil, nil, nil, err
-		}
-		if maxIOBandwidth < 0 {
-			return nil, nil, nil, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", copts.ioMaxBandwidth)
-		}
-	}
-
-	var binds []string
-	volumes := copts.volumes.GetMap()
-	// add any bind targets to the list of container volumes
-	for bind := range copts.volumes.GetMap() {
-		if arr := volumeSplitN(bind, 2); len(arr) > 1 {
-			// after creating the bind mount we want to delete it from the copts.volumes values because
-			// we do not want bind mounts being committed to image configs
-			binds = append(binds, bind)
-			// We should delete from the map (`volumes`) here, as deleting from copts.volumes will not work if
-			// there are duplicates entries.
-			delete(volumes, bind)
-		}
-	}
-
-	// Can't evaluate options passed into --tmpfs until we actually mount
-	tmpfs := make(map[string]string)
-	for _, t := range copts.tmpfs.GetAll() {
-		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
-			tmpfs[arr[0]] = arr[1]
-		} else {
-			tmpfs[arr[0]] = ""
-		}
-	}
-
-	var (
-		runCmd     strslice.StrSlice
-		entrypoint strslice.StrSlice
-	)
-
-	if len(copts.Args) > 0 {
-		runCmd = strslice.StrSlice(copts.Args)
-	}
-
-	if copts.entrypoint != "" {
-		entrypoint = strslice.StrSlice{copts.entrypoint}
-	} else if flags.Changed("entrypoint") {
-		// if `--entrypoint=` is parsed then Entrypoint is reset
-		entrypoint = []string{""}
-	}
-
-	ports, portBindings, err := nat.ParsePortSpecs(copts.publish.GetAll())
-	if err != nil {
-		return nil, nil, nil, err
-	}
-
-	// Merge in exposed ports to the map of published ports
-	for _, e := range copts.expose.GetAll() {
-		if strings.Contains(e, ":") {
-			return nil, nil, nil, fmt.Errorf("invalid port format for --expose: %s", e)
-		}
-		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
-		proto, port := nat.SplitProtoPort(e)
-		//parse the start and end port and create a sequence of ports to expose
-		//if expose a port, the start and end port are the same
-		start, end, err := nat.ParsePortRange(port)
-		if err != nil {
-			return nil, nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err)
-		}
-		for i := start; i <= end; i++ {
-			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
-			if err != nil {
-				return nil, nil, nil, err
-			}
-			if _, exists := ports[p]; !exists {
-				ports[p] = struct{}{}
-			}
-		}
-	}
-
-	// parse device mappings
-	deviceMappings := []container.DeviceMapping{}
-	for _, device := range copts.devices.GetAll() {
-		deviceMapping, err := ParseDevice(device)
-		if err != nil {
-			return nil, nil, nil, err
-		}
-		deviceMappings = append(deviceMappings, deviceMapping)
-	}
-
-	// collect all the environment variables for the container
-	envVariables, err := ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll())
-	if err != nil {
-		return nil, nil, nil, err
-	}
-
-	// collect all the labels for the container
-	labels, err := ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll())
-	if err != nil {
-		return nil, nil, nil, err
-	}
-
-	ipcMode := container.IpcMode(copts.ipcMode)
-	if !ipcMode.Valid() {
-		return nil, nil, nil, fmt.Errorf("--ipc: invalid IPC mode")
-	}
-
-	pidMode := container.PidMode(copts.pidMode)
-	if !pidMode.Valid() {
-		return nil, nil, nil, fmt.Errorf("--pid: invalid PID mode")
-	}
-
-	utsMode := container.UTSMode(copts.utsMode)
-	if !utsMode.Valid() {
-		return nil, nil, nil, fmt.Errorf("--uts: invalid UTS mode")
-	}
-
-	usernsMode := container.UsernsMode(copts.usernsMode)
-	if !usernsMode.Valid() {
-		return nil, nil, nil, fmt.Errorf("--userns: invalid USER mode")
-	}
-
-	restartPolicy, err := ParseRestartPolicy(copts.restartPolicy)
-	if err != nil {
-		return nil, nil, nil, err
-	}
-
-	loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll())
-	if err != nil {
-		return nil, nil, nil, err
-	}
-
-	securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll())
-	if err != nil {
-		return nil, nil, nil, err
-	}
-
-	storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll())
-	if err != nil {
-		return nil, nil, nil, err
-	}
-
-	// Healthcheck
-	var healthConfig *container.HealthConfig
-	haveHealthSettings := copts.healthCmd != "" ||
-		copts.healthInterval != 0 ||
-		copts.healthTimeout != 0 ||
-		copts.healthRetries != 0
-	if copts.noHealthcheck {
-		if haveHealthSettings {
-			return nil, nil, nil, fmt.Errorf("--no-healthcheck conflicts with --health-* options")
-		}
-		test := strslice.StrSlice{"NONE"}
-		healthConfig = &container.HealthConfig{Test: test}
-	} else if haveHealthSettings {
-		var probe strslice.StrSlice
-		if copts.healthCmd != "" {
-			args := []string{"CMD-SHELL", copts.healthCmd}
-			probe = strslice.StrSlice(args)
-		}
-		if copts.healthInterval < 0 {
-			return nil, nil, nil, fmt.Errorf("--health-interval cannot be negative")
-		}
-		if copts.healthTimeout < 0 {
-			return nil, nil, nil, fmt.Errorf("--health-timeout cannot be negative")
-		}
-
-		healthConfig = &container.HealthConfig{
-			Test:     probe,
-			Interval: copts.healthInterval,
-			Timeout:  copts.healthTimeout,
-			Retries:  copts.healthRetries,
-		}
-	}
-
-	resources := container.Resources{
-		CgroupParent:         copts.cgroupParent,
-		Memory:               memory,
-		MemoryReservation:    memoryReservation,
-		MemorySwap:           memorySwap,
-		MemorySwappiness:     &copts.swappiness,
-		KernelMemory:         kernelMemory,
-		OomKillDisable:       &copts.oomKillDisable,
-		NanoCPUs:             copts.cpus.Value(),
-		CPUCount:             copts.cpuCount,
-		CPUPercent:           copts.cpuPercent,
-		CPUShares:            copts.cpuShares,
-		CPUPeriod:            copts.cpuPeriod,
-		CpusetCpus:           copts.cpusetCpus,
-		CpusetMems:           copts.cpusetMems,
-		CPUQuota:             copts.cpuQuota,
-		CPURealtimePeriod:    copts.cpuRealtimePeriod,
-		CPURealtimeRuntime:   copts.cpuRealtimeRuntime,
-		PidsLimit:            copts.pidsLimit,
-		BlkioWeight:          copts.blkioWeight,
-		BlkioWeightDevice:    copts.blkioWeightDevice.GetList(),
-		BlkioDeviceReadBps:   copts.deviceReadBps.GetList(),
-		BlkioDeviceWriteBps:  copts.deviceWriteBps.GetList(),
-		BlkioDeviceReadIOps:  copts.deviceReadIOps.GetList(),
-		BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(),
-		IOMaximumIOps:        copts.ioMaxIOps,
-		IOMaximumBandwidth:   uint64(maxIOBandwidth),
-		Ulimits:              copts.ulimits.GetList(),
-		Devices:              deviceMappings,
-	}
-
-	config := &container.Config{
-		Hostname:     copts.hostname,
-		ExposedPorts: ports,
-		User:         copts.user,
-		Tty:          copts.tty,
-		// TODO: deprecated, it comes from -n, --networking
-		// it's still needed internally to set the network to disabled
-		// if e.g. bridge is none in daemon opts, and in inspect
-		NetworkDisabled: false,
-		OpenStdin:       copts.stdin,
-		AttachStdin:     attachStdin,
-		AttachStdout:    attachStdout,
-		AttachStderr:    attachStderr,
-		Env:             envVariables,
-		Cmd:             runCmd,
-		Image:           copts.Image,
-		Volumes:         volumes,
-		MacAddress:      copts.macAddress,
-		Entrypoint:      entrypoint,
-		WorkingDir:      copts.workingDir,
-		Labels:          ConvertKVStringsToMap(labels),
-		Healthcheck:     healthConfig,
-	}
-	if flags.Changed("stop-signal") {
-		config.StopSignal = copts.stopSignal
-	}
-	if flags.Changed("stop-timeout") {
-		config.StopTimeout = &copts.stopTimeout
-	}
-
-	hostConfig := &container.HostConfig{
-		Binds:           binds,
-		ContainerIDFile: copts.containerIDFile,
-		OomScoreAdj:     copts.oomScoreAdj,
-		AutoRemove:      copts.autoRemove,
-		Privileged:      copts.privileged,
-		PortBindings:    portBindings,
-		Links:           copts.links.GetAll(),
-		PublishAllPorts: copts.publishAll,
-		// Make sure the dns fields are never nil.
-		// New containers don't ever have those fields nil,
-		// but pre created containers can still have those nil values.
-		// See https://github.com/docker/docker/pull/17779
-		// for a more detailed explanation on why we don't want that.
-		DNS:            copts.dns.GetAllOrEmpty(),
-		DNSSearch:      copts.dnsSearch.GetAllOrEmpty(),
-		DNSOptions:     copts.dnsOptions.GetAllOrEmpty(),
-		ExtraHosts:     copts.extraHosts.GetAll(),
-		VolumesFrom:    copts.volumesFrom.GetAll(),
-		NetworkMode:    container.NetworkMode(copts.netMode),
-		IpcMode:        ipcMode,
-		PidMode:        pidMode,
-		UTSMode:        utsMode,
-		UsernsMode:     usernsMode,
-		CapAdd:         strslice.StrSlice(copts.capAdd.GetAll()),
-		CapDrop:        strslice.StrSlice(copts.capDrop.GetAll()),
-		GroupAdd:       copts.groupAdd.GetAll(),
-		RestartPolicy:  restartPolicy,
-		SecurityOpt:    securityOpts,
-		StorageOpt:     storageOpts,
-		ReadonlyRootfs: copts.readonlyRootfs,
-		LogConfig:      container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts},
-		VolumeDriver:   copts.volumeDriver,
-		Isolation:      container.Isolation(copts.isolation),
-		ShmSize:        shmSize,
-		Resources:      resources,
-		Tmpfs:          tmpfs,
-		Sysctls:        copts.sysctls.GetAll(),
-		Runtime:        copts.runtime,
-	}
-
-	// only set this value if the user provided the flag, else it should default to nil
-	if flags.Changed("init") {
-		hostConfig.Init = &copts.init
-	}
-
-	// When allocating stdin in attached mode, close stdin at client disconnect
-	if config.OpenStdin && config.AttachStdin {
-		config.StdinOnce = true
-	}
-
-	networkingConfig := &networktypes.NetworkingConfig{
-		EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
-	}
-
-	if copts.ipv4Address != "" || copts.ipv6Address != "" || copts.linkLocalIPs.Len() > 0 {
-		epConfig := &networktypes.EndpointSettings{}
-		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
-
-		epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{
-			IPv4Address: copts.ipv4Address,
-			IPv6Address: copts.ipv6Address,
-		}
-
-		if copts.linkLocalIPs.Len() > 0 {
-			epConfig.IPAMConfig.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len())
-			copy(epConfig.IPAMConfig.LinkLocalIPs, copts.linkLocalIPs.GetAll())
-		}
-	}
-
-	if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 {
-		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
-		if epConfig == nil {
-			epConfig = &networktypes.EndpointSettings{}
-		}
-		epConfig.Links = make([]string, len(hostConfig.Links))
-		copy(epConfig.Links, hostConfig.Links)
-		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
-	}
-
-	if copts.aliases.Len() > 0 {
-		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
-		if epConfig == nil {
-			epConfig = &networktypes.EndpointSettings{}
-		}
-		epConfig.Aliases = make([]string, copts.aliases.Len())
-		copy(epConfig.Aliases, copts.aliases.GetAll())
-		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
-	}
-
-	return config, hostConfig, networkingConfig, nil
-}
-
 // ReadKVStrings reads a file of line terminated key=value pairs, and overrides any keys
 // ReadKVStrings reads a file of line terminated key=value pairs, and overrides any keys
 // present in the file with additional pairs specified in the override parameter
 // present in the file with additional pairs specified in the override parameter
 func ReadKVStrings(files []string, override []string) ([]string, error) {
 func ReadKVStrings(files []string, override []string) ([]string, error) {
@@ -724,55 +59,6 @@ func ConvertKVStringsToMapWithNil(values []string) map[string]*string {
 	return result
 	return result
 }
 }
 
 
-func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
-	loggingOptsMap := ConvertKVStringsToMap(loggingOpts)
-	if loggingDriver == "none" && len(loggingOpts) > 0 {
-		return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver)
-	}
-	return loggingOptsMap, nil
-}
-
-// takes a local seccomp daemon, reads the file contents for sending to the daemon
-func parseSecurityOpts(securityOpts []string) ([]string, error) {
-	for key, opt := range securityOpts {
-		con := strings.SplitN(opt, "=", 2)
-		if len(con) == 1 && con[0] != "no-new-privileges" {
-			if strings.Contains(opt, ":") {
-				con = strings.SplitN(opt, ":", 2)
-			} else {
-				return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt)
-			}
-		}
-		if con[0] == "seccomp" && con[1] != "unconfined" {
-			f, err := ioutil.ReadFile(con[1])
-			if err != nil {
-				return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
-			}
-			b := bytes.NewBuffer(nil)
-			if err := json.Compact(b, f); err != nil {
-				return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err)
-			}
-			securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
-		}
-	}
-
-	return securityOpts, nil
-}
-
-// parses storage options per container into a map
-func parseStorageOpts(storageOpts []string) (map[string]string, error) {
-	m := make(map[string]string)
-	for _, option := range storageOpts {
-		if strings.Contains(option, "=") {
-			opt := strings.SplitN(option, "=", 2)
-			m[opt[0]] = opt[1]
-		} else {
-			return nil, fmt.Errorf("invalid storage option")
-		}
-	}
-	return m, nil
-}
-
 // ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect
 // ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect
 func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
 func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
 	p := container.RestartPolicy{}
 	p := container.RestartPolicy{}
@@ -799,195 +85,3 @@ func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
 
 
 	return p, nil
 	return p, nil
 }
 }
-
-// ParseDevice parses a device mapping string to a container.DeviceMapping struct
-func ParseDevice(device string) (container.DeviceMapping, error) {
-	src := ""
-	dst := ""
-	permissions := "rwm"
-	arr := strings.Split(device, ":")
-	switch len(arr) {
-	case 3:
-		permissions = arr[2]
-		fallthrough
-	case 2:
-		if ValidDeviceMode(arr[1]) {
-			permissions = arr[1]
-		} else {
-			dst = arr[1]
-		}
-		fallthrough
-	case 1:
-		src = arr[0]
-	default:
-		return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device)
-	}
-
-	if dst == "" {
-		dst = src
-	}
-
-	deviceMapping := container.DeviceMapping{
-		PathOnHost:        src,
-		PathInContainer:   dst,
-		CgroupPermissions: permissions,
-	}
-	return deviceMapping, nil
-}
-
-// ParseLink parses and validates the specified string as a link format (name:alias)
-func ParseLink(val string) (string, string, error) {
-	if val == "" {
-		return "", "", fmt.Errorf("empty string specified for links")
-	}
-	arr := strings.Split(val, ":")
-	if len(arr) > 2 {
-		return "", "", fmt.Errorf("bad format for links: %s", val)
-	}
-	if len(arr) == 1 {
-		return val, val, nil
-	}
-	// This is kept because we can actually get a HostConfig with links
-	// from an already created container and the format is not `foo:bar`
-	// but `/foo:/c1/bar`
-	if strings.HasPrefix(arr[0], "/") {
-		_, alias := path.Split(arr[1])
-		return arr[0][1:], alias, nil
-	}
-	return arr[0], arr[1], nil
-}
-
-// ValidateLink validates that the specified string has a valid link format (containerName:alias).
-func ValidateLink(val string) (string, error) {
-	_, _, err := ParseLink(val)
-	return val, err
-}
-
-// ValidDeviceMode checks if the mode for device is valid or not.
-// Valid mode is a composition of r (read), w (write), and m (mknod).
-func ValidDeviceMode(mode string) bool {
-	var legalDeviceMode = map[rune]bool{
-		'r': true,
-		'w': true,
-		'm': true,
-	}
-	if mode == "" {
-		return false
-	}
-	for _, c := range mode {
-		if !legalDeviceMode[c] {
-			return false
-		}
-		legalDeviceMode[c] = false
-	}
-	return true
-}
-
-// ValidateDevice validates a path for devices
-// It will make sure 'val' is in the form:
-//    [host-dir:]container-path[:mode]
-// It also validates the device mode.
-func ValidateDevice(val string) (string, error) {
-	return validatePath(val, ValidDeviceMode)
-}
-
-func validatePath(val string, validator func(string) bool) (string, error) {
-	var containerPath string
-	var mode string
-
-	if strings.Count(val, ":") > 2 {
-		return val, fmt.Errorf("bad format for path: %s", val)
-	}
-
-	split := strings.SplitN(val, ":", 3)
-	if split[0] == "" {
-		return val, fmt.Errorf("bad format for path: %s", val)
-	}
-	switch len(split) {
-	case 1:
-		containerPath = split[0]
-		val = path.Clean(containerPath)
-	case 2:
-		if isValid := validator(split[1]); isValid {
-			containerPath = split[0]
-			mode = split[1]
-			val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
-		} else {
-			containerPath = split[1]
-			val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
-		}
-	case 3:
-		containerPath = split[1]
-		mode = split[2]
-		if isValid := validator(split[2]); !isValid {
-			return val, fmt.Errorf("bad mode specified: %s", mode)
-		}
-		val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
-	}
-
-	if !path.IsAbs(containerPath) {
-		return val, fmt.Errorf("%s is not an absolute path", containerPath)
-	}
-	return val, nil
-}
-
-// volumeSplitN splits raw into a maximum of n parts, separated by a separator colon.
-// A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
-// In Windows driver letter appears in two situations:
-// a. `^[a-zA-Z]:` (A colon followed  by `^[a-zA-Z]:` is OK as colon is the separator in volume option)
-// b. A string in the format like `\\?\C:\Windows\...` (UNC).
-// Therefore, a driver letter can only follow either a `:` or `\\`
-// This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`.
-func volumeSplitN(raw string, n int) []string {
-	var array []string
-	if len(raw) == 0 || raw[0] == ':' {
-		// invalid
-		return nil
-	}
-	// numberOfParts counts the number of parts separated by a separator colon
-	numberOfParts := 0
-	// left represents the left-most cursor in raw, updated at every `:` character considered as a separator.
-	left := 0
-	// right represents the right-most cursor in raw incremented with the loop. Note this
-	// starts at index 1 as index 0 is already handle above as a special case.
-	for right := 1; right < len(raw); right++ {
-		// stop parsing if reached maximum number of parts
-		if n >= 0 && numberOfParts >= n {
-			break
-		}
-		if raw[right] != ':' {
-			continue
-		}
-		potentialDriveLetter := raw[right-1]
-		if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
-			if right > 1 {
-				beforePotentialDriveLetter := raw[right-2]
-				// Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`)
-				if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' {
-					// e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
-					array = append(array, raw[left:right])
-					left = right + 1
-					numberOfParts++
-				}
-				// else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing.
-			}
-			// if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing.
-		} else {
-			// if `:` is not preceded by a potential drive letter, then consider it as a delimiter.
-			array = append(array, raw[left:right])
-			left = right + 1
-			numberOfParts++
-		}
-	}
-	// need to take care of the last part
-	if left < len(raw) {
-		if n >= 0 && numberOfParts >= n {
-			// if the maximum number of parts is reached, just append the rest to the last part
-			// left-1 is at the last `:` that needs to be included since not considered a separator.
-			array[n-1] += raw[left-1:]
-		} else {
-			array = append(array, raw[left:])
-		}
-	}
-	return array
-}