mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	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>
This commit is contained in:
		
							parent
							
								
									d1dfc1a5ef
								
							
						
					
					
						commit
						c424be21b7
					
				
					 35 changed files with 1425 additions and 1424 deletions
				
			
		| 
						 | 
				
			
			@ -18,7 +18,6 @@ import (
 | 
			
		|||
	apiclient "github.com/docker/docker/client"
 | 
			
		||||
	"github.com/docker/docker/reference"
 | 
			
		||||
	"github.com/docker/docker/registry"
 | 
			
		||||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"github.com/spf13/pflag"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +29,7 @@ type createOptions struct {
 | 
			
		|||
// NewCreateCommand creates a new cobra.Command for `docker create`
 | 
			
		||||
func NewCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 | 
			
		||||
	var opts createOptions
 | 
			
		||||
	var copts *runconfigopts.ContainerOptions
 | 
			
		||||
	var copts *containerOptions
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "create [OPTIONS] IMAGE [COMMAND] [ARG...]",
 | 
			
		||||
| 
						 | 
				
			
			@ -55,12 +54,12 @@ func NewCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 | 
			
		|||
	flags.Bool("help", false, "Print usage")
 | 
			
		||||
 | 
			
		||||
	command.AddTrustedFlags(flags, true)
 | 
			
		||||
	copts = runconfigopts.AddFlags(flags)
 | 
			
		||||
	copts = addFlags(flags)
 | 
			
		||||
	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 {
 | 
			
		||||
		reportError(dockerCli.Err(), "create", err.Error(), true)
 | 
			
		||||
		return cli.StatusError{StatusCode: 125}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,6 @@ import (
 | 
			
		|||
	apiclient "github.com/docker/docker/client"
 | 
			
		||||
	options "github.com/docker/docker/opts"
 | 
			
		||||
	"github.com/docker/docker/pkg/promise"
 | 
			
		||||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +29,7 @@ type execOptions struct {
 | 
			
		|||
func newExecOptions() *execOptions {
 | 
			
		||||
	var values []string
 | 
			
		||||
	return &execOptions{
 | 
			
		||||
		env: options.NewListOptsRef(&values, runconfigopts.ValidateEnv),
 | 
			
		||||
		env: options.NewListOptsRef(&values, options.ValidateEnv),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										899
									
								
								cli/command/container/opts.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										899
									
								
								cli/command/container/opts.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
package opts
 | 
			
		||||
package container
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
| 
						 | 
				
			
			@ -18,24 +18,48 @@ import (
 | 
			
		|||
	"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) {
 | 
			
		||||
	flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
 | 
			
		||||
	flags.SetOutput(ioutil.Discard)
 | 
			
		||||
	flags.Usage = nil
 | 
			
		||||
	copts := AddFlags(flags)
 | 
			
		||||
	copts := addFlags(flags)
 | 
			
		||||
	if err := flags.Parse(args); err != nil {
 | 
			
		||||
		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", " "))
 | 
			
		||||
	return config, hostConfig, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
		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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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")
 | 
			
		||||
	}
 | 
			
		||||
	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")
 | 
			
		||||
	}
 | 
			
		||||
	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")
 | 
			
		||||
	}
 | 
			
		||||
	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")
 | 
			
		||||
	}
 | 
			
		||||
	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")
 | 
			
		||||
	}
 | 
			
		||||
	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")
 | 
			
		||||
	}
 | 
			
		||||
	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")
 | 
			
		||||
	}
 | 
			
		||||
	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")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
// 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
 | 
			
		||||
| 
						 | 
				
			
			@ -351,6 +209,18 @@ func setupPlatformVolume(u []string, w []string) ([]string, string) {
 | 
			
		|||
	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
 | 
			
		||||
func TestParseWithMacAddress(t *testing.T) {
 | 
			
		||||
	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)
 | 
			
		||||
	}
 | 
			
		||||
	// 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 {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
 | 
			
		||||
		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 {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -668,7 +538,7 @@ func TestParseEnvfileVariables(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) {
 | 
			
		||||
	// 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 {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -684,11 +554,11 @@ func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	// UTF16 with BOM
 | 
			
		||||
	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)
 | 
			
		||||
	}
 | 
			
		||||
	// 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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -703,14 +573,14 @@ func TestParseLabelfileVariables(t *testing.T) {
 | 
			
		|||
		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 | 
			
		||||
	}
 | 
			
		||||
	// 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 {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
 | 
			
		||||
		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 {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -729,116 +599,158 @@ func TestParseEntryPoint(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestValidateLink(t *testing.T) {
 | 
			
		||||
	valid := []string{
 | 
			
		||||
		"name",
 | 
			
		||||
		"dcdfbe62ecd0:alias",
 | 
			
		||||
		"7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da",
 | 
			
		||||
		"angry_torvalds:linus",
 | 
			
		||||
// 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)
 | 
			
		||||
	}
 | 
			
		||||
	invalid := map[string]string{
 | 
			
		||||
		"":               "empty string specified for links",
 | 
			
		||||
		"too:much:of:it": "bad format for links: too:much:of:it",
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
 | 
			
		||||
		t.Fatalf("volume %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, link := range valid {
 | 
			
		||||
		if _, err := ValidateLink(link); err != nil {
 | 
			
		||||
			t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, 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 _, _, 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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
			}
 | 
			
		||||
	// 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)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseLink(t *testing.T) {
 | 
			
		||||
	name, alias, err := ParseLink("name:alias")
 | 
			
		||||
// 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 {
 | 
			
		||||
		t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
 | 
			
		||||
		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), 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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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",
 | 
			
		||||
	if c == nil || h == nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("Empty config or hostconfig")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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())
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return c, h, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVolumeSplitN(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -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())
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +18,6 @@ import (
 | 
			
		|||
	opttypes "github.com/docker/docker/opts"
 | 
			
		||||
	"github.com/docker/docker/pkg/promise"
 | 
			
		||||
	"github.com/docker/docker/pkg/signal"
 | 
			
		||||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/docker/libnetwork/resolvconf/dns"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"github.com/spf13/pflag"
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +33,7 @@ type runOptions struct {
 | 
			
		|||
// NewRunCommand create a new `docker run` command
 | 
			
		||||
func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command {
 | 
			
		||||
	var opts runOptions
 | 
			
		||||
	var copts *runconfigopts.ContainerOptions
 | 
			
		||||
	var copts *containerOptions
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
 | 
			
		||||
| 
						 | 
				
			
			@ -63,11 +62,11 @@ func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command {
 | 
			
		|||
	flags.Bool("help", false, "Print usage")
 | 
			
		||||
 | 
			
		||||
	command.AddTrustedFlags(flags, true)
 | 
			
		||||
	copts = runconfigopts.AddFlags(flags)
 | 
			
		||||
	copts = addFlags(flags)
 | 
			
		||||
	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()
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	// 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")
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
		reportError(stderr, cmdPath, err.Error(), true)
 | 
			
		||||
		return cli.StatusError{StatusCode: 125}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,8 +11,6 @@ import (
 | 
			
		|||
	"regexp"
 | 
			
		||||
	"runtime"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/api/types/container"
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +29,7 @@ import (
 | 
			
		|||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/docker/go-units"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type buildOptions struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +38,7 @@ type buildOptions struct {
 | 
			
		|||
	tags           opts.ListOpts
 | 
			
		||||
	labels         opts.ListOpts
 | 
			
		||||
	buildArgs      opts.ListOpts
 | 
			
		||||
	ulimits        *runconfigopts.UlimitOpt
 | 
			
		||||
	ulimits        *opts.UlimitOpt
 | 
			
		||||
	memory         string
 | 
			
		||||
	memorySwap     string
 | 
			
		||||
	shmSize        string
 | 
			
		||||
| 
						 | 
				
			
			@ -67,9 +66,9 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
 | 
			
		|||
	ulimits := make(map[string]*units.Ulimit)
 | 
			
		||||
	options := buildOptions{
 | 
			
		||||
		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{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,6 @@ import (
 | 
			
		|||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/docker/docker/cli/command"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +22,7 @@ type connectOptions struct {
 | 
			
		|||
 | 
			
		||||
func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := connectOptions{
 | 
			
		||||
		links: opts.NewListOpts(runconfigopts.ValidateLink),
 | 
			
		||||
		links: opts.NewListOpts(opts.ValidateLink),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ type createOptions struct {
 | 
			
		|||
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := createOptions{
 | 
			
		||||
		driverOpts: *opts.NewMapOpts(nil, nil),
 | 
			
		||||
		labels:     opts.NewListOpts(runconfigopts.ValidateEnv),
 | 
			
		||||
		labels:     opts.NewListOpts(opts.ValidateEnv),
 | 
			
		||||
		ipamAux:    *opts.NewMapOpts(nil, nil),
 | 
			
		||||
		ipamOpt:    *opts.NewMapOpts(nil, nil),
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,7 +23,7 @@ type createOptions struct {
 | 
			
		|||
 | 
			
		||||
func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 | 
			
		||||
	createOpts := createOptions{
 | 
			
		||||
		labels: opts.NewListOpts(runconfigopts.ValidateEnv),
 | 
			
		||||
		labels: opts.NewListOpts(opts.ValidateEnv),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -304,7 +304,7 @@ type logDriverOptions struct {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func newLogDriverOptions() logDriverOptions {
 | 
			
		||||
	return logDriverOptions{opts: opts.NewListOpts(runconfigopts.ValidateEnv)}
 | 
			
		||||
	return logDriverOptions{opts: opts.NewListOpts(opts.ValidateEnv)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ldo *logDriverOptions) toLogDriver() *swarm.Driver {
 | 
			
		||||
| 
						 | 
				
			
			@ -426,17 +426,17 @@ type serviceOptions struct {
 | 
			
		|||
 | 
			
		||||
func newServiceOptions() *serviceOptions {
 | 
			
		||||
	return &serviceOptions{
 | 
			
		||||
		labels:          opts.NewListOpts(runconfigopts.ValidateEnv),
 | 
			
		||||
		labels:          opts.NewListOpts(opts.ValidateEnv),
 | 
			
		||||
		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),
 | 
			
		||||
		groups:          opts.NewListOpts(nil),
 | 
			
		||||
		logDriver:       newLogDriverOptions(),
 | 
			
		||||
		dns:             opts.NewListOpts(opts.ValidateIPAddress),
 | 
			
		||||
		dnsOption:       opts.NewListOpts(nil),
 | 
			
		||||
		dnsSearch:       opts.NewListOpts(opts.ValidateDNSSearch),
 | 
			
		||||
		hosts:           opts.NewListOpts(runconfigopts.ValidateExtraHost),
 | 
			
		||||
		hosts:           opts.NewListOpts(opts.ValidateExtraHost),
 | 
			
		||||
		networks:        opts.NewListOpts(nil),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,14 +3,13 @@ package volume
 | 
			
		|||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	volumetypes "github.com/docker/docker/api/types/volume"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/docker/docker/cli/command"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type createOptions struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +22,7 @@ type createOptions struct {
 | 
			
		|||
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := createOptions{
 | 
			
		||||
		driverOpts: *opts.NewMapOpts(nil, nil),
 | 
			
		||||
		labels:     opts.NewListOpts(runconfigopts.ValidateEnv),
 | 
			
		||||
		labels:     opts.NewListOpts(opts.ValidateEnv),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ import (
 | 
			
		|||
	"github.com/docker/docker/image"
 | 
			
		||||
	"github.com/docker/docker/layer"
 | 
			
		||||
	"github.com/docker/docker/libcontainerd"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	"github.com/docker/docker/pkg/idtools"
 | 
			
		||||
	"github.com/docker/docker/pkg/ioutils"
 | 
			
		||||
	"github.com/docker/docker/pkg/promise"
 | 
			
		||||
| 
						 | 
				
			
			@ -35,7 +36,6 @@ import (
 | 
			
		|||
	"github.com/docker/docker/pkg/symlink"
 | 
			
		||||
	"github.com/docker/docker/restartmanager"
 | 
			
		||||
	"github.com/docker/docker/runconfig"
 | 
			
		||||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/docker/go-connections/nat"
 | 
			
		||||
	"github.com/docker/libnetwork"
 | 
			
		||||
| 
						 | 
				
			
			@ -815,7 +815,7 @@ func (container *Container) BuildJoinOptions(n libnetwork.Network) ([]libnetwork
 | 
			
		|||
	var joinOptions []libnetwork.EndpointOption
 | 
			
		||||
	if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
 | 
			
		||||
		for _, str := range epConfig.Links {
 | 
			
		||||
			name, alias, err := runconfigopts.ParseLink(str)
 | 
			
		||||
			name, alias, err := opts.ParseLink(str)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,6 @@ import (
 | 
			
		|||
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"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.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(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")
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ package daemon
 | 
			
		|||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	units "github.com/docker/go-units"
 | 
			
		||||
	"github.com/spf13/pflag"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +68,7 @@ func (config *Config) InstallFlags(flags *pflag.FlagSet) {
 | 
			
		|||
 | 
			
		||||
	// Then platform-specific install flags
 | 
			
		||||
	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.EnableIPForward, "ip-forward", true, "Enable net.ipv4.ip_forward")
 | 
			
		||||
	flags.BoolVar(&config.bridgeConfig.EnableIPMasq, "ip-masq", true, "Enable IP masquerading")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,10 +12,10 @@ import (
 | 
			
		|||
	"github.com/docker/docker/container"
 | 
			
		||||
	"github.com/docker/docker/daemon/network"
 | 
			
		||||
	"github.com/docker/docker/image"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	"github.com/docker/docker/pkg/signal"
 | 
			
		||||
	"github.com/docker/docker/pkg/system"
 | 
			
		||||
	"github.com/docker/docker/pkg/truncindex"
 | 
			
		||||
	"github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/docker/go-connections/nat"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,12 +23,12 @@ import (
 | 
			
		|||
	containertypes "github.com/docker/docker/api/types/container"
 | 
			
		||||
	"github.com/docker/docker/container"
 | 
			
		||||
	"github.com/docker/docker/image"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	"github.com/docker/docker/pkg/idtools"
 | 
			
		||||
	"github.com/docker/docker/pkg/parsers"
 | 
			
		||||
	"github.com/docker/docker/pkg/parsers/kernel"
 | 
			
		||||
	"github.com/docker/docker/pkg/sysinfo"
 | 
			
		||||
	"github.com/docker/docker/runconfig"
 | 
			
		||||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/docker/libnetwork"
 | 
			
		||||
	nwconfig "github.com/docker/libnetwork/config"
 | 
			
		||||
	"github.com/docker/libnetwork/drivers/bridge"
 | 
			
		||||
| 
						 | 
				
			
			@ -1098,7 +1098,7 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	for _, l := range hostConfig.Links {
 | 
			
		||||
		name, alias, err := runconfigopts.ParseLink(l)
 | 
			
		||||
		name, alias, err := opts.ParseLink(l)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										46
									
								
								opts/env.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								opts/env.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
									
								
								opts/env_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								opts/env_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ package opts
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -146,3 +147,35 @@ func TestParseInvalidUnixAddrInvalid(t *testing.T) {
 | 
			
		|||
		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
									
								
								opts/opts.go
									
										
									
									
									
								
							
							
						
						
									
										38
									
								
								opts/opts.go
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -4,6 +4,7 @@ import (
 | 
			
		|||
	"fmt"
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"net"
 | 
			
		||||
	"path"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -231,6 +232,15 @@ func ValidateIPAddress(val string) (string, error) {
 | 
			
		|||
	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.
 | 
			
		||||
// A zero length domain is represented by a dot (.).
 | 
			
		||||
func ValidateDNSSearch(val string) (string, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -364,3 +374,31 @@ func ParseCPUs(value string) (int64, error) {
 | 
			
		|||
	}
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -230,3 +230,78 @@ func TestNamedMapOpts(t *testing.T) {
 | 
			
		|||
		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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,678 +1,13 @@
 | 
			
		|||
package opts
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
	"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
 | 
			
		||||
// present in the file with additional pairs specified in the override parameter
 | 
			
		||||
func ReadKVStrings(files []string, override []string) ([]string, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -724,55 +59,6 @@ func ConvertKVStringsToMapWithNil(values []string) map[string]*string {
 | 
			
		|||
	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
 | 
			
		||||
func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
 | 
			
		||||
	p := container.RestartPolicy{}
 | 
			
		||||
| 
						 | 
				
			
			@ -799,195 +85,3 @@ func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
 | 
			
		|||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue