Signed-off-by: Daniel Nephin <dnephin@docker.com>
| ... | ... |
@@ -7,8 +7,8 @@ import ( |
| 7 | 7 |
"github.com/Sirupsen/logrus" |
| 8 | 8 |
"github.com/docker/docker/api/types" |
| 9 | 9 |
Cli "github.com/docker/docker/cli" |
| 10 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 10 | 11 |
"github.com/docker/docker/pkg/promise" |
| 11 |
- "github.com/docker/docker/runconfig" |
|
| 12 | 12 |
) |
| 13 | 13 |
|
| 14 | 14 |
// CmdExec runs a command in a running container. |
| ... | ... |
@@ -18,7 +18,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
| 18 | 18 |
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
|
| 19 | 19 |
detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
|
| 20 | 20 |
|
| 21 |
- execConfig, err := runconfig.ParseExec(cmd, args) |
|
| 21 |
+ execConfig, err := ParseExec(cmd, args) |
|
| 22 | 22 |
// just in case the ParseExec does not exit |
| 23 | 23 |
if execConfig.Container == "" || err != nil {
|
| 24 | 24 |
return Cli.StatusError{StatusCode: 1}
|
| ... | ... |
@@ -113,3 +113,46 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
| 113 | 113 |
|
| 114 | 114 |
return nil |
| 115 | 115 |
} |
| 116 |
+ |
|
| 117 |
+// ParseExec parses the specified args for the specified command and generates |
|
| 118 |
+// an ExecConfig from it. |
|
| 119 |
+// If the minimal number of specified args is not right or if specified args are |
|
| 120 |
+// not valid, it will return an error. |
|
| 121 |
+func ParseExec(cmd *flag.FlagSet, args []string) (*types.ExecConfig, error) {
|
|
| 122 |
+ var ( |
|
| 123 |
+ flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
|
|
| 124 |
+ flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
|
|
| 125 |
+ flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background")
|
|
| 126 |
+ flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
|
|
| 127 |
+ flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to the command")
|
|
| 128 |
+ execCmd []string |
|
| 129 |
+ container string |
|
| 130 |
+ ) |
|
| 131 |
+ cmd.Require(flag.Min, 2) |
|
| 132 |
+ if err := cmd.ParseFlags(args, true); err != nil {
|
|
| 133 |
+ return nil, err |
|
| 134 |
+ } |
|
| 135 |
+ container = cmd.Arg(0) |
|
| 136 |
+ parsedArgs := cmd.Args() |
|
| 137 |
+ execCmd = parsedArgs[1:] |
|
| 138 |
+ |
|
| 139 |
+ execConfig := &types.ExecConfig{
|
|
| 140 |
+ User: *flUser, |
|
| 141 |
+ Privileged: *flPrivileged, |
|
| 142 |
+ Tty: *flTty, |
|
| 143 |
+ Cmd: execCmd, |
|
| 144 |
+ Container: container, |
|
| 145 |
+ Detach: *flDetach, |
|
| 146 |
+ } |
|
| 147 |
+ |
|
| 148 |
+ // If -d is not set, attach to everything by default |
|
| 149 |
+ if !*flDetach {
|
|
| 150 |
+ execConfig.AttachStdout = true |
|
| 151 |
+ execConfig.AttachStderr = true |
|
| 152 |
+ if *flStdin {
|
|
| 153 |
+ execConfig.AttachStdin = true |
|
| 154 |
+ } |
|
| 155 |
+ } |
|
| 156 |
+ |
|
| 157 |
+ return execConfig, nil |
|
| 158 |
+} |
| 116 | 159 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,130 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "testing" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/api/types" |
|
| 8 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+type arguments struct {
|
|
| 12 |
+ args []string |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+func TestParseExec(t *testing.T) {
|
|
| 16 |
+ invalids := map[*arguments]error{
|
|
| 17 |
+ &arguments{[]string{"-unknown"}}: fmt.Errorf("flag provided but not defined: -unknown"),
|
|
| 18 |
+ &arguments{[]string{"-u"}}: fmt.Errorf("flag needs an argument: -u"),
|
|
| 19 |
+ &arguments{[]string{"--user"}}: fmt.Errorf("flag needs an argument: --user"),
|
|
| 20 |
+ } |
|
| 21 |
+ valids := map[*arguments]*types.ExecConfig{
|
|
| 22 |
+ &arguments{
|
|
| 23 |
+ []string{"container", "command"},
|
|
| 24 |
+ }: {
|
|
| 25 |
+ Container: "container", |
|
| 26 |
+ Cmd: []string{"command"},
|
|
| 27 |
+ AttachStdout: true, |
|
| 28 |
+ AttachStderr: true, |
|
| 29 |
+ }, |
|
| 30 |
+ &arguments{
|
|
| 31 |
+ []string{"container", "command1", "command2"},
|
|
| 32 |
+ }: {
|
|
| 33 |
+ Container: "container", |
|
| 34 |
+ Cmd: []string{"command1", "command2"},
|
|
| 35 |
+ AttachStdout: true, |
|
| 36 |
+ AttachStderr: true, |
|
| 37 |
+ }, |
|
| 38 |
+ &arguments{
|
|
| 39 |
+ []string{"-i", "-t", "-u", "uid", "container", "command"},
|
|
| 40 |
+ }: {
|
|
| 41 |
+ User: "uid", |
|
| 42 |
+ AttachStdin: true, |
|
| 43 |
+ AttachStdout: true, |
|
| 44 |
+ AttachStderr: true, |
|
| 45 |
+ Tty: true, |
|
| 46 |
+ Container: "container", |
|
| 47 |
+ Cmd: []string{"command"},
|
|
| 48 |
+ }, |
|
| 49 |
+ &arguments{
|
|
| 50 |
+ []string{"-d", "container", "command"},
|
|
| 51 |
+ }: {
|
|
| 52 |
+ AttachStdin: false, |
|
| 53 |
+ AttachStdout: false, |
|
| 54 |
+ AttachStderr: false, |
|
| 55 |
+ Detach: true, |
|
| 56 |
+ Container: "container", |
|
| 57 |
+ Cmd: []string{"command"},
|
|
| 58 |
+ }, |
|
| 59 |
+ &arguments{
|
|
| 60 |
+ []string{"-t", "-i", "-d", "container", "command"},
|
|
| 61 |
+ }: {
|
|
| 62 |
+ AttachStdin: false, |
|
| 63 |
+ AttachStdout: false, |
|
| 64 |
+ AttachStderr: false, |
|
| 65 |
+ Detach: true, |
|
| 66 |
+ Tty: true, |
|
| 67 |
+ Container: "container", |
|
| 68 |
+ Cmd: []string{"command"},
|
|
| 69 |
+ }, |
|
| 70 |
+ } |
|
| 71 |
+ for invalid, expectedError := range invalids {
|
|
| 72 |
+ cmd := flag.NewFlagSet("exec", flag.ContinueOnError)
|
|
| 73 |
+ cmd.ShortUsage = func() {}
|
|
| 74 |
+ cmd.SetOutput(ioutil.Discard) |
|
| 75 |
+ _, err := ParseExec(cmd, invalid.args) |
|
| 76 |
+ if err == nil || err.Error() != expectedError.Error() {
|
|
| 77 |
+ t.Fatalf("Expected an error [%v] for %v, got %v", expectedError, invalid, err)
|
|
| 78 |
+ } |
|
| 79 |
+ |
|
| 80 |
+ } |
|
| 81 |
+ for valid, expectedExecConfig := range valids {
|
|
| 82 |
+ cmd := flag.NewFlagSet("exec", flag.ContinueOnError)
|
|
| 83 |
+ cmd.ShortUsage = func() {}
|
|
| 84 |
+ cmd.SetOutput(ioutil.Discard) |
|
| 85 |
+ execConfig, err := ParseExec(cmd, valid.args) |
|
| 86 |
+ if err != nil {
|
|
| 87 |
+ t.Fatal(err) |
|
| 88 |
+ } |
|
| 89 |
+ if !compareExecConfig(expectedExecConfig, execConfig) {
|
|
| 90 |
+ t.Fatalf("Expected [%v] for %v, got [%v]", expectedExecConfig, valid, execConfig)
|
|
| 91 |
+ } |
|
| 92 |
+ } |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+func compareExecConfig(config1 *types.ExecConfig, config2 *types.ExecConfig) bool {
|
|
| 96 |
+ if config1.AttachStderr != config2.AttachStderr {
|
|
| 97 |
+ return false |
|
| 98 |
+ } |
|
| 99 |
+ if config1.AttachStdin != config2.AttachStdin {
|
|
| 100 |
+ return false |
|
| 101 |
+ } |
|
| 102 |
+ if config1.AttachStdout != config2.AttachStdout {
|
|
| 103 |
+ return false |
|
| 104 |
+ } |
|
| 105 |
+ if config1.Container != config2.Container {
|
|
| 106 |
+ return false |
|
| 107 |
+ } |
|
| 108 |
+ if config1.Detach != config2.Detach {
|
|
| 109 |
+ return false |
|
| 110 |
+ } |
|
| 111 |
+ if config1.Privileged != config2.Privileged {
|
|
| 112 |
+ return false |
|
| 113 |
+ } |
|
| 114 |
+ if config1.Tty != config2.Tty {
|
|
| 115 |
+ return false |
|
| 116 |
+ } |
|
| 117 |
+ if config1.User != config2.User {
|
|
| 118 |
+ return false |
|
| 119 |
+ } |
|
| 120 |
+ if len(config1.Cmd) != len(config2.Cmd) {
|
|
| 121 |
+ return false |
|
| 122 |
+ } |
|
| 123 |
+ for index, value := range config1.Cmd {
|
|
| 124 |
+ if value != config2.Cmd[index] {
|
|
| 125 |
+ return false |
|
| 126 |
+ } |
|
| 127 |
+ } |
|
| 128 |
+ return true |
|
| 129 |
+} |
| 0 | 130 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,651 @@ |
| 0 |
+package runconfig |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "path" |
|
| 5 |
+ "strconv" |
|
| 6 |
+ "strings" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/api/types/container" |
|
| 9 |
+ "github.com/docker/docker/api/types/strslice" |
|
| 10 |
+ "github.com/docker/docker/opts" |
|
| 11 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 12 |
+ "github.com/docker/docker/pkg/mount" |
|
| 13 |
+ "github.com/docker/docker/pkg/signal" |
|
| 14 |
+ runconfigopts "github.com/docker/docker/runconfig/opts" |
|
| 15 |
+ "github.com/docker/docker/volume" |
|
| 16 |
+ "github.com/docker/go-connections/nat" |
|
| 17 |
+ "github.com/docker/go-units" |
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+var ( |
|
| 21 |
+ // ErrConflictContainerNetworkAndLinks conflict between --net=container and links |
|
| 22 |
+ ErrConflictContainerNetworkAndLinks = fmt.Errorf("Conflicting options: container type network can't be used with links. This would result in undefined behavior")
|
|
| 23 |
+ // ErrConflictUserDefinedNetworkAndLinks conflict between --net=<NETWORK> and links |
|
| 24 |
+ ErrConflictUserDefinedNetworkAndLinks = fmt.Errorf("Conflicting options: networking can't be used with links. This would result in undefined behavior")
|
|
| 25 |
+ // ErrConflictSharedNetwork conflict between private and other networks |
|
| 26 |
+ ErrConflictSharedNetwork = fmt.Errorf("Container sharing network namespace with another container or host cannot be connected to any other network")
|
|
| 27 |
+ // ErrConflictHostNetwork conflict from being disconnected from host network or connected to host network. |
|
| 28 |
+ ErrConflictHostNetwork = fmt.Errorf("Container cannot be disconnected from host network or connected to host network")
|
|
| 29 |
+ // ErrConflictNoNetwork conflict between private and other networks |
|
| 30 |
+ ErrConflictNoNetwork = fmt.Errorf("Container cannot be connected to multiple networks with one of the networks in private (none) mode")
|
|
| 31 |
+ // ErrConflictNetworkAndDNS conflict between --dns and the network mode |
|
| 32 |
+ ErrConflictNetworkAndDNS = fmt.Errorf("Conflicting options: dns and the network mode")
|
|
| 33 |
+ // ErrConflictNetworkHostname conflict between the hostname and the network mode |
|
| 34 |
+ ErrConflictNetworkHostname = fmt.Errorf("Conflicting options: hostname and the network mode")
|
|
| 35 |
+ // ErrConflictHostNetworkAndLinks conflict between --net=host and links |
|
| 36 |
+ ErrConflictHostNetworkAndLinks = fmt.Errorf("Conflicting options: host type networking can't be used with links. This would result in undefined behavior")
|
|
| 37 |
+ // ErrConflictContainerNetworkAndMac conflict between the mac address and the network mode |
|
| 38 |
+ ErrConflictContainerNetworkAndMac = fmt.Errorf("Conflicting options: mac-address and the network mode")
|
|
| 39 |
+ // ErrConflictNetworkHosts conflict between add-host and the network mode |
|
| 40 |
+ ErrConflictNetworkHosts = fmt.Errorf("Conflicting options: custom host-to-IP mapping and the network mode")
|
|
| 41 |
+ // ErrConflictNetworkPublishPorts conflict between the publish options and the network mode |
|
| 42 |
+ ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type network mode")
|
|
| 43 |
+ // ErrConflictNetworkExposePorts conflict between the expose option and the network mode |
|
| 44 |
+ ErrConflictNetworkExposePorts = fmt.Errorf("Conflicting options: port exposing and the container type network mode")
|
|
| 45 |
+) |
|
| 46 |
+ |
|
| 47 |
+// Parse parses the specified args for the specified command and generates a Config, |
|
| 48 |
+// a HostConfig and returns them with the specified command. |
|
| 49 |
+// If the specified args are not valid, it will return an error. |
|
| 50 |
+func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
|
|
| 51 |
+ var ( |
|
| 52 |
+ // FIXME: use utils.ListOpts for attach and volumes? |
|
| 53 |
+ flAttach = opts.NewListOpts(opts.ValidateAttach) |
|
| 54 |
+ flVolumes = opts.NewListOpts(nil) |
|
| 55 |
+ flTmpfs = opts.NewListOpts(nil) |
|
| 56 |
+ flBlkioWeightDevice = runconfigopts.NewWeightdeviceOpt(runconfigopts.ValidateWeightDevice) |
|
| 57 |
+ flDeviceReadBps = runconfigopts.NewThrottledeviceOpt(runconfigopts.ValidateThrottleBpsDevice) |
|
| 58 |
+ flDeviceWriteBps = runconfigopts.NewThrottledeviceOpt(runconfigopts.ValidateThrottleBpsDevice) |
|
| 59 |
+ flLinks = opts.NewListOpts(ValidateLink) |
|
| 60 |
+ flDeviceReadIOps = runconfigopts.NewThrottledeviceOpt(runconfigopts.ValidateThrottleIOpsDevice) |
|
| 61 |
+ flDeviceWriteIOps = runconfigopts.NewThrottledeviceOpt(runconfigopts.ValidateThrottleIOpsDevice) |
|
| 62 |
+ flEnv = opts.NewListOpts(opts.ValidateEnv) |
|
| 63 |
+ flLabels = opts.NewListOpts(opts.ValidateEnv) |
|
| 64 |
+ flDevices = opts.NewListOpts(ValidateDevice) |
|
| 65 |
+ |
|
| 66 |
+ flUlimits = runconfigopts.NewUlimitOpt(nil) |
|
| 67 |
+ |
|
| 68 |
+ flPublish = opts.NewListOpts(nil) |
|
| 69 |
+ flExpose = opts.NewListOpts(nil) |
|
| 70 |
+ flDNS = opts.NewListOpts(opts.ValidateIPAddress) |
|
| 71 |
+ flDNSSearch = opts.NewListOpts(opts.ValidateDNSSearch) |
|
| 72 |
+ flDNSOptions = opts.NewListOpts(nil) |
|
| 73 |
+ flExtraHosts = opts.NewListOpts(opts.ValidateExtraHost) |
|
| 74 |
+ flVolumesFrom = opts.NewListOpts(nil) |
|
| 75 |
+ flEnvFile = opts.NewListOpts(nil) |
|
| 76 |
+ flCapAdd = opts.NewListOpts(nil) |
|
| 77 |
+ flCapDrop = opts.NewListOpts(nil) |
|
| 78 |
+ flGroupAdd = opts.NewListOpts(nil) |
|
| 79 |
+ flSecurityOpt = opts.NewListOpts(nil) |
|
| 80 |
+ flLabelsFile = opts.NewListOpts(nil) |
|
| 81 |
+ flLoggingOpts = opts.NewListOpts(nil) |
|
| 82 |
+ flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to this container")
|
|
| 83 |
+ flPidMode = cmd.String([]string{"-pid"}, "", "PID namespace to use")
|
|
| 84 |
+ flUTSMode = cmd.String([]string{"-uts"}, "", "UTS namespace to use")
|
|
| 85 |
+ flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
|
|
| 86 |
+ flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
|
|
| 87 |
+ flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
|
|
| 88 |
+ flOomKillDisable = cmd.Bool([]string{"-oom-kill-disable"}, false, "Disable OOM Killer")
|
|
| 89 |
+ flOomScoreAdj = cmd.Int([]string{"-oom-score-adj"}, 0, "Tune host's OOM preferences (-1000 to 1000)")
|
|
| 90 |
+ flContainerIDFile = cmd.String([]string{"-cidfile"}, "", "Write the container ID to the file")
|
|
| 91 |
+ flEntrypoint = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
|
|
| 92 |
+ flHostname = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
|
|
| 93 |
+ flMemoryString = cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
|
| 94 |
+ flMemoryReservation = cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit")
|
|
| 95 |
+ flMemorySwap = cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
|
|
| 96 |
+ flKernelMemory = cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit")
|
|
| 97 |
+ flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
|
|
| 98 |
+ flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
|
|
| 99 |
+ flCPUShares = cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
|
|
| 100 |
+ flCPUPeriod = cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period")
|
|
| 101 |
+ flCPUQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
|
|
| 102 |
+ flCpusetCpus = cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
|
| 103 |
+ flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
|
|
| 104 |
+ flBlkioWeight = cmd.Uint16([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
|
|
| 105 |
+ flSwappiness = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
|
|
| 106 |
+ flNetMode = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
|
|
| 107 |
+ flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
|
|
| 108 |
+ flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
|
|
| 109 |
+ flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
|
|
| 110 |
+ flReadonlyRootfs = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
|
|
| 111 |
+ flLoggingDriver = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
|
|
| 112 |
+ flCgroupParent = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
|
| 113 |
+ flVolumeDriver = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
|
|
| 114 |
+ flStopSignal = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
|
|
| 115 |
+ flIsolation = cmd.String([]string{"-isolation"}, "", "Container isolation level")
|
|
| 116 |
+ flShmSize = cmd.String([]string{"-shm-size"}, "", "Size of /dev/shm, default value is 64MB")
|
|
| 117 |
+ ) |
|
| 118 |
+ |
|
| 119 |
+ cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
|
|
| 120 |
+ cmd.Var(&flBlkioWeightDevice, []string{"-blkio-weight-device"}, "Block IO weight (relative device weight)")
|
|
| 121 |
+ cmd.Var(&flDeviceReadBps, []string{"-device-read-bps"}, "Limit read rate (bytes per second) from a device")
|
|
| 122 |
+ cmd.Var(&flDeviceWriteBps, []string{"-device-write-bps"}, "Limit write rate (bytes per second) to a device")
|
|
| 123 |
+ cmd.Var(&flDeviceReadIOps, []string{"-device-read-iops"}, "Limit read rate (IO per second) from a device")
|
|
| 124 |
+ cmd.Var(&flDeviceWriteIOps, []string{"-device-write-iops"}, "Limit write rate (IO per second) to a device")
|
|
| 125 |
+ cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
|
|
| 126 |
+ cmd.Var(&flTmpfs, []string{"-tmpfs"}, "Mount a tmpfs directory")
|
|
| 127 |
+ cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
|
|
| 128 |
+ cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
|
|
| 129 |
+ cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
|
|
| 130 |
+ cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
|
|
| 131 |
+ cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
|
|
| 132 |
+ cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
|
|
| 133 |
+ cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
|
|
| 134 |
+ cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports")
|
|
| 135 |
+ cmd.Var(&flDNS, []string{"-dns"}, "Set custom DNS servers")
|
|
| 136 |
+ cmd.Var(&flDNSSearch, []string{"-dns-search"}, "Set custom DNS search domains")
|
|
| 137 |
+ cmd.Var(&flDNSOptions, []string{"-dns-opt"}, "Set DNS options")
|
|
| 138 |
+ cmd.Var(&flExtraHosts, []string{"-add-host"}, "Add a custom host-to-IP mapping (host:ip)")
|
|
| 139 |
+ cmd.Var(&flVolumesFrom, []string{"-volumes-from"}, "Mount volumes from the specified container(s)")
|
|
| 140 |
+ cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities")
|
|
| 141 |
+ cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities")
|
|
| 142 |
+ cmd.Var(&flGroupAdd, []string{"-group-add"}, "Add additional groups to join")
|
|
| 143 |
+ cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options")
|
|
| 144 |
+ cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
|
|
| 145 |
+ cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options")
|
|
| 146 |
+ |
|
| 147 |
+ cmd.Require(flag.Min, 1) |
|
| 148 |
+ |
|
| 149 |
+ if err := cmd.ParseFlags(args, true); err != nil {
|
|
| 150 |
+ return nil, nil, cmd, err |
|
| 151 |
+ } |
|
| 152 |
+ |
|
| 153 |
+ var ( |
|
| 154 |
+ attachStdin = flAttach.Get("stdin")
|
|
| 155 |
+ attachStdout = flAttach.Get("stdout")
|
|
| 156 |
+ attachStderr = flAttach.Get("stderr")
|
|
| 157 |
+ ) |
|
| 158 |
+ |
|
| 159 |
+ // Validate the input mac address |
|
| 160 |
+ if *flMacAddress != "" {
|
|
| 161 |
+ if _, err := opts.ValidateMACAddress(*flMacAddress); err != nil {
|
|
| 162 |
+ return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
|
|
| 163 |
+ } |
|
| 164 |
+ } |
|
| 165 |
+ if *flStdin {
|
|
| 166 |
+ attachStdin = true |
|
| 167 |
+ } |
|
| 168 |
+ // If -a is not set attach to the output stdio |
|
| 169 |
+ if flAttach.Len() == 0 {
|
|
| 170 |
+ attachStdout = true |
|
| 171 |
+ attachStderr = true |
|
| 172 |
+ } |
|
| 173 |
+ |
|
| 174 |
+ var err error |
|
| 175 |
+ |
|
| 176 |
+ var flMemory int64 |
|
| 177 |
+ if *flMemoryString != "" {
|
|
| 178 |
+ flMemory, err = units.RAMInBytes(*flMemoryString) |
|
| 179 |
+ if err != nil {
|
|
| 180 |
+ return nil, nil, cmd, err |
|
| 181 |
+ } |
|
| 182 |
+ } |
|
| 183 |
+ |
|
| 184 |
+ var MemoryReservation int64 |
|
| 185 |
+ if *flMemoryReservation != "" {
|
|
| 186 |
+ MemoryReservation, err = units.RAMInBytes(*flMemoryReservation) |
|
| 187 |
+ if err != nil {
|
|
| 188 |
+ return nil, nil, cmd, err |
|
| 189 |
+ } |
|
| 190 |
+ } |
|
| 191 |
+ |
|
| 192 |
+ var memorySwap int64 |
|
| 193 |
+ if *flMemorySwap != "" {
|
|
| 194 |
+ if *flMemorySwap == "-1" {
|
|
| 195 |
+ memorySwap = -1 |
|
| 196 |
+ } else {
|
|
| 197 |
+ memorySwap, err = units.RAMInBytes(*flMemorySwap) |
|
| 198 |
+ if err != nil {
|
|
| 199 |
+ return nil, nil, cmd, err |
|
| 200 |
+ } |
|
| 201 |
+ } |
|
| 202 |
+ } |
|
| 203 |
+ |
|
| 204 |
+ var KernelMemory int64 |
|
| 205 |
+ if *flKernelMemory != "" {
|
|
| 206 |
+ KernelMemory, err = units.RAMInBytes(*flKernelMemory) |
|
| 207 |
+ if err != nil {
|
|
| 208 |
+ return nil, nil, cmd, err |
|
| 209 |
+ } |
|
| 210 |
+ } |
|
| 211 |
+ |
|
| 212 |
+ swappiness := *flSwappiness |
|
| 213 |
+ if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
|
|
| 214 |
+ return nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
|
|
| 215 |
+ } |
|
| 216 |
+ |
|
| 217 |
+ var parsedShm *int64 |
|
| 218 |
+ if *flShmSize != "" {
|
|
| 219 |
+ shmSize, err := units.RAMInBytes(*flShmSize) |
|
| 220 |
+ if err != nil {
|
|
| 221 |
+ return nil, nil, cmd, err |
|
| 222 |
+ } |
|
| 223 |
+ parsedShm = &shmSize |
|
| 224 |
+ } |
|
| 225 |
+ |
|
| 226 |
+ var binds []string |
|
| 227 |
+ // add any bind targets to the list of container volumes |
|
| 228 |
+ for bind := range flVolumes.GetMap() {
|
|
| 229 |
+ if arr := volume.SplitN(bind, 2); len(arr) > 1 {
|
|
| 230 |
+ // after creating the bind mount we want to delete it from the flVolumes values because |
|
| 231 |
+ // we do not want bind mounts being committed to image configs |
|
| 232 |
+ binds = append(binds, bind) |
|
| 233 |
+ flVolumes.Delete(bind) |
|
| 234 |
+ } |
|
| 235 |
+ } |
|
| 236 |
+ |
|
| 237 |
+ // Can't evaluate options passed into --tmpfs until we actually mount |
|
| 238 |
+ tmpfs := make(map[string]string) |
|
| 239 |
+ for _, t := range flTmpfs.GetAll() {
|
|
| 240 |
+ if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
|
|
| 241 |
+ if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
|
|
| 242 |
+ return nil, nil, cmd, err |
|
| 243 |
+ } |
|
| 244 |
+ tmpfs[arr[0]] = arr[1] |
|
| 245 |
+ } else {
|
|
| 246 |
+ tmpfs[arr[0]] = "" |
|
| 247 |
+ } |
|
| 248 |
+ } |
|
| 249 |
+ |
|
| 250 |
+ var ( |
|
| 251 |
+ parsedArgs = cmd.Args() |
|
| 252 |
+ runCmd *strslice.StrSlice |
|
| 253 |
+ entrypoint *strslice.StrSlice |
|
| 254 |
+ image = cmd.Arg(0) |
|
| 255 |
+ ) |
|
| 256 |
+ if len(parsedArgs) > 1 {
|
|
| 257 |
+ runCmd = strslice.New(parsedArgs[1:]...) |
|
| 258 |
+ } |
|
| 259 |
+ if *flEntrypoint != "" {
|
|
| 260 |
+ entrypoint = strslice.New(*flEntrypoint) |
|
| 261 |
+ } |
|
| 262 |
+ |
|
| 263 |
+ var ( |
|
| 264 |
+ domainname string |
|
| 265 |
+ hostname = *flHostname |
|
| 266 |
+ parts = strings.SplitN(hostname, ".", 2) |
|
| 267 |
+ ) |
|
| 268 |
+ if len(parts) > 1 {
|
|
| 269 |
+ hostname = parts[0] |
|
| 270 |
+ domainname = parts[1] |
|
| 271 |
+ } |
|
| 272 |
+ |
|
| 273 |
+ ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll()) |
|
| 274 |
+ if err != nil {
|
|
| 275 |
+ return nil, nil, cmd, err |
|
| 276 |
+ } |
|
| 277 |
+ |
|
| 278 |
+ // Merge in exposed ports to the map of published ports |
|
| 279 |
+ for _, e := range flExpose.GetAll() {
|
|
| 280 |
+ if strings.Contains(e, ":") {
|
|
| 281 |
+ return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
|
|
| 282 |
+ } |
|
| 283 |
+ //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] |
|
| 284 |
+ proto, port := nat.SplitProtoPort(e) |
|
| 285 |
+ //parse the start and end port and create a sequence of ports to expose |
|
| 286 |
+ //if expose a port, the start and end port are the same |
|
| 287 |
+ start, end, err := nat.ParsePortRange(port) |
|
| 288 |
+ if err != nil {
|
|
| 289 |
+ return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
|
|
| 290 |
+ } |
|
| 291 |
+ for i := start; i <= end; i++ {
|
|
| 292 |
+ p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) |
|
| 293 |
+ if err != nil {
|
|
| 294 |
+ return nil, nil, cmd, err |
|
| 295 |
+ } |
|
| 296 |
+ if _, exists := ports[p]; !exists {
|
|
| 297 |
+ ports[p] = struct{}{}
|
|
| 298 |
+ } |
|
| 299 |
+ } |
|
| 300 |
+ } |
|
| 301 |
+ |
|
| 302 |
+ // parse device mappings |
|
| 303 |
+ deviceMappings := []container.DeviceMapping{}
|
|
| 304 |
+ for _, device := range flDevices.GetAll() {
|
|
| 305 |
+ deviceMapping, err := ParseDevice(device) |
|
| 306 |
+ if err != nil {
|
|
| 307 |
+ return nil, nil, cmd, err |
|
| 308 |
+ } |
|
| 309 |
+ deviceMappings = append(deviceMappings, deviceMapping) |
|
| 310 |
+ } |
|
| 311 |
+ |
|
| 312 |
+ // collect all the environment variables for the container |
|
| 313 |
+ envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll()) |
|
| 314 |
+ if err != nil {
|
|
| 315 |
+ return nil, nil, cmd, err |
|
| 316 |
+ } |
|
| 317 |
+ |
|
| 318 |
+ // collect all the labels for the container |
|
| 319 |
+ labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll()) |
|
| 320 |
+ if err != nil {
|
|
| 321 |
+ return nil, nil, cmd, err |
|
| 322 |
+ } |
|
| 323 |
+ |
|
| 324 |
+ ipcMode := container.IpcMode(*flIpcMode) |
|
| 325 |
+ if !ipcMode.Valid() {
|
|
| 326 |
+ return nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
|
|
| 327 |
+ } |
|
| 328 |
+ |
|
| 329 |
+ pidMode := container.PidMode(*flPidMode) |
|
| 330 |
+ if !pidMode.Valid() {
|
|
| 331 |
+ return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
|
|
| 332 |
+ } |
|
| 333 |
+ |
|
| 334 |
+ utsMode := container.UTSMode(*flUTSMode) |
|
| 335 |
+ if !utsMode.Valid() {
|
|
| 336 |
+ return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
|
|
| 337 |
+ } |
|
| 338 |
+ |
|
| 339 |
+ restartPolicy, err := ParseRestartPolicy(*flRestartPolicy) |
|
| 340 |
+ if err != nil {
|
|
| 341 |
+ return nil, nil, cmd, err |
|
| 342 |
+ } |
|
| 343 |
+ |
|
| 344 |
+ loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll()) |
|
| 345 |
+ if err != nil {
|
|
| 346 |
+ return nil, nil, cmd, err |
|
| 347 |
+ } |
|
| 348 |
+ |
|
| 349 |
+ resources := container.Resources{
|
|
| 350 |
+ CgroupParent: *flCgroupParent, |
|
| 351 |
+ Memory: flMemory, |
|
| 352 |
+ MemoryReservation: MemoryReservation, |
|
| 353 |
+ MemorySwap: memorySwap, |
|
| 354 |
+ MemorySwappiness: flSwappiness, |
|
| 355 |
+ KernelMemory: KernelMemory, |
|
| 356 |
+ OomKillDisable: *flOomKillDisable, |
|
| 357 |
+ CPUShares: *flCPUShares, |
|
| 358 |
+ CPUPeriod: *flCPUPeriod, |
|
| 359 |
+ CpusetCpus: *flCpusetCpus, |
|
| 360 |
+ CpusetMems: *flCpusetMems, |
|
| 361 |
+ CPUQuota: *flCPUQuota, |
|
| 362 |
+ BlkioWeight: *flBlkioWeight, |
|
| 363 |
+ BlkioWeightDevice: flBlkioWeightDevice.GetList(), |
|
| 364 |
+ BlkioDeviceReadBps: flDeviceReadBps.GetList(), |
|
| 365 |
+ BlkioDeviceWriteBps: flDeviceWriteBps.GetList(), |
|
| 366 |
+ BlkioDeviceReadIOps: flDeviceReadIOps.GetList(), |
|
| 367 |
+ BlkioDeviceWriteIOps: flDeviceWriteIOps.GetList(), |
|
| 368 |
+ Ulimits: flUlimits.GetList(), |
|
| 369 |
+ Devices: deviceMappings, |
|
| 370 |
+ } |
|
| 371 |
+ |
|
| 372 |
+ config := &container.Config{
|
|
| 373 |
+ Hostname: hostname, |
|
| 374 |
+ Domainname: domainname, |
|
| 375 |
+ ExposedPorts: ports, |
|
| 376 |
+ User: *flUser, |
|
| 377 |
+ Tty: *flTty, |
|
| 378 |
+ // TODO: deprecated, it comes from -n, --networking |
|
| 379 |
+ // it's still needed internally to set the network to disabled |
|
| 380 |
+ // if e.g. bridge is none in daemon opts, and in inspect |
|
| 381 |
+ NetworkDisabled: false, |
|
| 382 |
+ OpenStdin: *flStdin, |
|
| 383 |
+ AttachStdin: attachStdin, |
|
| 384 |
+ AttachStdout: attachStdout, |
|
| 385 |
+ AttachStderr: attachStderr, |
|
| 386 |
+ Env: envVariables, |
|
| 387 |
+ Cmd: runCmd, |
|
| 388 |
+ Image: image, |
|
| 389 |
+ Volumes: flVolumes.GetMap(), |
|
| 390 |
+ MacAddress: *flMacAddress, |
|
| 391 |
+ Entrypoint: entrypoint, |
|
| 392 |
+ WorkingDir: *flWorkingDir, |
|
| 393 |
+ Labels: ConvertKVStringsToMap(labels), |
|
| 394 |
+ StopSignal: *flStopSignal, |
|
| 395 |
+ } |
|
| 396 |
+ |
|
| 397 |
+ hostConfig := &container.HostConfig{
|
|
| 398 |
+ Binds: binds, |
|
| 399 |
+ ContainerIDFile: *flContainerIDFile, |
|
| 400 |
+ OomScoreAdj: *flOomScoreAdj, |
|
| 401 |
+ Privileged: *flPrivileged, |
|
| 402 |
+ PortBindings: portBindings, |
|
| 403 |
+ Links: flLinks.GetAll(), |
|
| 404 |
+ PublishAllPorts: *flPublishAll, |
|
| 405 |
+ // Make sure the dns fields are never nil. |
|
| 406 |
+ // New containers don't ever have those fields nil, |
|
| 407 |
+ // but pre created containers can still have those nil values. |
|
| 408 |
+ // See https://github.com/docker/docker/pull/17779 |
|
| 409 |
+ // for a more detailed explanation on why we don't want that. |
|
| 410 |
+ DNS: flDNS.GetAllOrEmpty(), |
|
| 411 |
+ DNSSearch: flDNSSearch.GetAllOrEmpty(), |
|
| 412 |
+ DNSOptions: flDNSOptions.GetAllOrEmpty(), |
|
| 413 |
+ ExtraHosts: flExtraHosts.GetAll(), |
|
| 414 |
+ VolumesFrom: flVolumesFrom.GetAll(), |
|
| 415 |
+ NetworkMode: container.NetworkMode(*flNetMode), |
|
| 416 |
+ IpcMode: ipcMode, |
|
| 417 |
+ PidMode: pidMode, |
|
| 418 |
+ UTSMode: utsMode, |
|
| 419 |
+ CapAdd: strslice.New(flCapAdd.GetAll()...), |
|
| 420 |
+ CapDrop: strslice.New(flCapDrop.GetAll()...), |
|
| 421 |
+ GroupAdd: flGroupAdd.GetAll(), |
|
| 422 |
+ RestartPolicy: restartPolicy, |
|
| 423 |
+ SecurityOpt: flSecurityOpt.GetAll(), |
|
| 424 |
+ ReadonlyRootfs: *flReadonlyRootfs, |
|
| 425 |
+ LogConfig: container.LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
|
|
| 426 |
+ VolumeDriver: *flVolumeDriver, |
|
| 427 |
+ Isolation: container.IsolationLevel(*flIsolation), |
|
| 428 |
+ ShmSize: parsedShm, |
|
| 429 |
+ Resources: resources, |
|
| 430 |
+ Tmpfs: tmpfs, |
|
| 431 |
+ } |
|
| 432 |
+ |
|
| 433 |
+ // When allocating stdin in attached mode, close stdin at client disconnect |
|
| 434 |
+ if config.OpenStdin && config.AttachStdin {
|
|
| 435 |
+ config.StdinOnce = true |
|
| 436 |
+ } |
|
| 437 |
+ return config, hostConfig, cmd, nil |
|
| 438 |
+} |
|
| 439 |
+ |
|
| 440 |
+// reads a file of line terminated key=value pairs and override that with override parameter |
|
| 441 |
+func readKVStrings(files []string, override []string) ([]string, error) {
|
|
| 442 |
+ envVariables := []string{}
|
|
| 443 |
+ for _, ef := range files {
|
|
| 444 |
+ parsedVars, err := opts.ParseEnvFile(ef) |
|
| 445 |
+ if err != nil {
|
|
| 446 |
+ return nil, err |
|
| 447 |
+ } |
|
| 448 |
+ envVariables = append(envVariables, parsedVars...) |
|
| 449 |
+ } |
|
| 450 |
+ // parse the '-e' and '--env' after, to allow override |
|
| 451 |
+ envVariables = append(envVariables, override...) |
|
| 452 |
+ |
|
| 453 |
+ return envVariables, nil |
|
| 454 |
+} |
|
| 455 |
+ |
|
| 456 |
+// ConvertKVStringsToMap converts ["key=value"] to {"key":"value"}
|
|
| 457 |
+func ConvertKVStringsToMap(values []string) map[string]string {
|
|
| 458 |
+ result := make(map[string]string, len(values)) |
|
| 459 |
+ for _, value := range values {
|
|
| 460 |
+ kv := strings.SplitN(value, "=", 2) |
|
| 461 |
+ if len(kv) == 1 {
|
|
| 462 |
+ result[kv[0]] = "" |
|
| 463 |
+ } else {
|
|
| 464 |
+ result[kv[0]] = kv[1] |
|
| 465 |
+ } |
|
| 466 |
+ } |
|
| 467 |
+ |
|
| 468 |
+ return result |
|
| 469 |
+} |
|
| 470 |
+ |
|
| 471 |
+func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
|
|
| 472 |
+ loggingOptsMap := ConvertKVStringsToMap(loggingOpts) |
|
| 473 |
+ if loggingDriver == "none" && len(loggingOpts) > 0 {
|
|
| 474 |
+ return map[string]string{}, fmt.Errorf("Invalid logging opts for driver %s", loggingDriver)
|
|
| 475 |
+ } |
|
| 476 |
+ return loggingOptsMap, nil |
|
| 477 |
+} |
|
| 478 |
+ |
|
| 479 |
+// ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect |
|
| 480 |
+func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
|
|
| 481 |
+ p := container.RestartPolicy{}
|
|
| 482 |
+ |
|
| 483 |
+ if policy == "" {
|
|
| 484 |
+ return p, nil |
|
| 485 |
+ } |
|
| 486 |
+ |
|
| 487 |
+ var ( |
|
| 488 |
+ parts = strings.Split(policy, ":") |
|
| 489 |
+ name = parts[0] |
|
| 490 |
+ ) |
|
| 491 |
+ |
|
| 492 |
+ p.Name = name |
|
| 493 |
+ switch name {
|
|
| 494 |
+ case "always", "unless-stopped": |
|
| 495 |
+ if len(parts) > 1 {
|
|
| 496 |
+ return p, fmt.Errorf("maximum restart count not valid with restart policy of \"%s\"", name)
|
|
| 497 |
+ } |
|
| 498 |
+ case "no": |
|
| 499 |
+ // do nothing |
|
| 500 |
+ case "on-failure": |
|
| 501 |
+ if len(parts) > 2 {
|
|
| 502 |
+ return p, fmt.Errorf("restart count format is not valid, usage: 'on-failure:N' or 'on-failure'")
|
|
| 503 |
+ } |
|
| 504 |
+ if len(parts) == 2 {
|
|
| 505 |
+ count, err := strconv.Atoi(parts[1]) |
|
| 506 |
+ if err != nil {
|
|
| 507 |
+ return p, err |
|
| 508 |
+ } |
|
| 509 |
+ |
|
| 510 |
+ p.MaximumRetryCount = count |
|
| 511 |
+ } |
|
| 512 |
+ default: |
|
| 513 |
+ return p, fmt.Errorf("invalid restart policy %s", name)
|
|
| 514 |
+ } |
|
| 515 |
+ |
|
| 516 |
+ return p, nil |
|
| 517 |
+} |
|
| 518 |
+ |
|
| 519 |
+// ParseDevice parses a device mapping string to a container.DeviceMapping struct |
|
| 520 |
+func ParseDevice(device string) (container.DeviceMapping, error) {
|
|
| 521 |
+ src := "" |
|
| 522 |
+ dst := "" |
|
| 523 |
+ permissions := "rwm" |
|
| 524 |
+ arr := strings.Split(device, ":") |
|
| 525 |
+ switch len(arr) {
|
|
| 526 |
+ case 3: |
|
| 527 |
+ permissions = arr[2] |
|
| 528 |
+ fallthrough |
|
| 529 |
+ case 2: |
|
| 530 |
+ if ValidDeviceMode(arr[1]) {
|
|
| 531 |
+ permissions = arr[1] |
|
| 532 |
+ } else {
|
|
| 533 |
+ dst = arr[1] |
|
| 534 |
+ } |
|
| 535 |
+ fallthrough |
|
| 536 |
+ case 1: |
|
| 537 |
+ src = arr[0] |
|
| 538 |
+ default: |
|
| 539 |
+ return container.DeviceMapping{}, fmt.Errorf("Invalid device specification: %s", device)
|
|
| 540 |
+ } |
|
| 541 |
+ |
|
| 542 |
+ if dst == "" {
|
|
| 543 |
+ dst = src |
|
| 544 |
+ } |
|
| 545 |
+ |
|
| 546 |
+ deviceMapping := container.DeviceMapping{
|
|
| 547 |
+ PathOnHost: src, |
|
| 548 |
+ PathInContainer: dst, |
|
| 549 |
+ CgroupPermissions: permissions, |
|
| 550 |
+ } |
|
| 551 |
+ return deviceMapping, nil |
|
| 552 |
+} |
|
| 553 |
+ |
|
| 554 |
+// ParseLink parses and validates the specified string as a link format (name:alias) |
|
| 555 |
+func ParseLink(val string) (string, string, error) {
|
|
| 556 |
+ if val == "" {
|
|
| 557 |
+ return "", "", fmt.Errorf("empty string specified for links")
|
|
| 558 |
+ } |
|
| 559 |
+ arr := strings.Split(val, ":") |
|
| 560 |
+ if len(arr) > 2 {
|
|
| 561 |
+ return "", "", fmt.Errorf("bad format for links: %s", val)
|
|
| 562 |
+ } |
|
| 563 |
+ if len(arr) == 1 {
|
|
| 564 |
+ return val, val, nil |
|
| 565 |
+ } |
|
| 566 |
+ // This is kept because we can actually get an HostConfig with links |
|
| 567 |
+ // from an already created container and the format is not `foo:bar` |
|
| 568 |
+ // but `/foo:/c1/bar` |
|
| 569 |
+ if strings.HasPrefix(arr[0], "/") {
|
|
| 570 |
+ _, alias := path.Split(arr[1]) |
|
| 571 |
+ return arr[0][1:], alias, nil |
|
| 572 |
+ } |
|
| 573 |
+ return arr[0], arr[1], nil |
|
| 574 |
+} |
|
| 575 |
+ |
|
| 576 |
+// ValidateLink validates that the specified string has a valid link format (containerName:alias). |
|
| 577 |
+func ValidateLink(val string) (string, error) {
|
|
| 578 |
+ if _, _, err := ParseLink(val); err != nil {
|
|
| 579 |
+ return val, err |
|
| 580 |
+ } |
|
| 581 |
+ return val, nil |
|
| 582 |
+} |
|
| 583 |
+ |
|
| 584 |
+// ValidDeviceMode checks if the mode for device is valid or not. |
|
| 585 |
+// Valid mode is a composition of r (read), w (write), and m (mknod). |
|
| 586 |
+func ValidDeviceMode(mode string) bool {
|
|
| 587 |
+ var legalDeviceMode = map[rune]bool{
|
|
| 588 |
+ 'r': true, |
|
| 589 |
+ 'w': true, |
|
| 590 |
+ 'm': true, |
|
| 591 |
+ } |
|
| 592 |
+ if mode == "" {
|
|
| 593 |
+ return false |
|
| 594 |
+ } |
|
| 595 |
+ for _, c := range mode {
|
|
| 596 |
+ if !legalDeviceMode[c] {
|
|
| 597 |
+ return false |
|
| 598 |
+ } |
|
| 599 |
+ legalDeviceMode[c] = false |
|
| 600 |
+ } |
|
| 601 |
+ return true |
|
| 602 |
+} |
|
| 603 |
+ |
|
| 604 |
+// ValidateDevice validates a path for devices |
|
| 605 |
+// It will make sure 'val' is in the form: |
|
| 606 |
+// [host-dir:]container-path[:mode] |
|
| 607 |
+// It also validates the device mode. |
|
| 608 |
+func ValidateDevice(val string) (string, error) {
|
|
| 609 |
+ return validatePath(val, ValidDeviceMode) |
|
| 610 |
+} |
|
| 611 |
+ |
|
| 612 |
+func validatePath(val string, validator func(string) bool) (string, error) {
|
|
| 613 |
+ var containerPath string |
|
| 614 |
+ var mode string |
|
| 615 |
+ |
|
| 616 |
+ if strings.Count(val, ":") > 2 {
|
|
| 617 |
+ return val, fmt.Errorf("bad format for path: %s", val)
|
|
| 618 |
+ } |
|
| 619 |
+ |
|
| 620 |
+ split := strings.SplitN(val, ":", 3) |
|
| 621 |
+ if split[0] == "" {
|
|
| 622 |
+ return val, fmt.Errorf("bad format for path: %s", val)
|
|
| 623 |
+ } |
|
| 624 |
+ switch len(split) {
|
|
| 625 |
+ case 1: |
|
| 626 |
+ containerPath = split[0] |
|
| 627 |
+ val = path.Clean(containerPath) |
|
| 628 |
+ case 2: |
|
| 629 |
+ if isValid := validator(split[1]); isValid {
|
|
| 630 |
+ containerPath = split[0] |
|
| 631 |
+ mode = split[1] |
|
| 632 |
+ val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
|
|
| 633 |
+ } else {
|
|
| 634 |
+ containerPath = split[1] |
|
| 635 |
+ val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
|
|
| 636 |
+ } |
|
| 637 |
+ case 3: |
|
| 638 |
+ containerPath = split[1] |
|
| 639 |
+ mode = split[2] |
|
| 640 |
+ if isValid := validator(split[2]); !isValid {
|
|
| 641 |
+ return val, fmt.Errorf("bad mode specified: %s", mode)
|
|
| 642 |
+ } |
|
| 643 |
+ val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
|
|
| 644 |
+ } |
|
| 645 |
+ |
|
| 646 |
+ if !path.IsAbs(containerPath) {
|
|
| 647 |
+ return val, fmt.Errorf("%s is not an absolute path", containerPath)
|
|
| 648 |
+ } |
|
| 649 |
+ return val, nil |
|
| 650 |
+} |
| 0 | 651 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,764 @@ |
| 0 |
+package runconfig |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io/ioutil" |
|
| 7 |
+ "os" |
|
| 8 |
+ "runtime" |
|
| 9 |
+ "strings" |
|
| 10 |
+ "testing" |
|
| 11 |
+ |
|
| 12 |
+ "github.com/docker/docker/api/types/container" |
|
| 13 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 14 |
+ "github.com/docker/go-connections/nat" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
|
|
| 18 |
+ cmd := flag.NewFlagSet("run", flag.ContinueOnError)
|
|
| 19 |
+ cmd.SetOutput(ioutil.Discard) |
|
| 20 |
+ cmd.Usage = nil |
|
| 21 |
+ return Parse(cmd, args) |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
|
|
| 25 |
+ config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " ")) |
|
| 26 |
+ return config, hostConfig, err |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
|
|
| 30 |
+ config, hostConfig, err := parse(t, args) |
|
| 31 |
+ if err != nil {
|
|
| 32 |
+ t.Fatal(err) |
|
| 33 |
+ } |
|
| 34 |
+ return config, hostConfig |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+func TestParseRunLinks(t *testing.T) {
|
|
| 38 |
+ if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
|
|
| 39 |
+ t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
|
|
| 40 |
+ } |
|
| 41 |
+ if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
|
|
| 42 |
+ t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
|
|
| 43 |
+ } |
|
| 44 |
+ if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
|
|
| 45 |
+ t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
|
|
| 46 |
+ } |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+func TestParseRunAttach(t *testing.T) {
|
|
| 50 |
+ if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
|
| 51 |
+ t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
|
| 52 |
+ } |
|
| 53 |
+ if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
|
|
| 54 |
+ t.Fatalf("Error parsing attach flags. Expect only Stdin and Stdout enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
|
| 55 |
+ } |
|
| 56 |
+ if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
|
|
| 57 |
+ t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
|
| 58 |
+ } |
|
| 59 |
+ if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
|
|
| 60 |
+ t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
|
| 61 |
+ } |
|
| 62 |
+ if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
|
|
| 63 |
+ t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 66 |
+ if _, _, err := parse(t, "-a"); err == nil {
|
|
| 67 |
+ t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
|
|
| 68 |
+ } |
|
| 69 |
+ if _, _, err := parse(t, "-a invalid"); err == nil {
|
|
| 70 |
+ t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
|
|
| 71 |
+ } |
|
| 72 |
+ if _, _, err := parse(t, "-a invalid -a stdout"); err == nil {
|
|
| 73 |
+ t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
|
|
| 74 |
+ } |
|
| 75 |
+ if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil {
|
|
| 76 |
+ t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
|
|
| 77 |
+ } |
|
| 78 |
+ if _, _, err := parse(t, "-a stdin -d"); err == nil {
|
|
| 79 |
+ t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
|
|
| 80 |
+ } |
|
| 81 |
+ if _, _, err := parse(t, "-a stdout -d"); err == nil {
|
|
| 82 |
+ t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
|
|
| 83 |
+ } |
|
| 84 |
+ if _, _, err := parse(t, "-a stderr -d"); err == nil {
|
|
| 85 |
+ t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
|
|
| 86 |
+ } |
|
| 87 |
+ if _, _, err := parse(t, "-d --rm"); err == nil {
|
|
| 88 |
+ t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not")
|
|
| 89 |
+ } |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 92 |
+func TestParseRunVolumes(t *testing.T) {
|
|
| 93 |
+ |
|
| 94 |
+ // A single volume |
|
| 95 |
+ arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
|
|
| 96 |
+ if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
|
|
| 97 |
+ t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
|
|
| 98 |
+ } else if _, exists := config.Volumes[arr[0]]; !exists {
|
|
| 99 |
+ t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes)
|
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 102 |
+ // Two volumes |
|
| 103 |
+ arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
|
|
| 104 |
+ if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
|
|
| 105 |
+ t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
|
|
| 106 |
+ } else if _, exists := config.Volumes[arr[0]]; !exists {
|
|
| 107 |
+ t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes)
|
|
| 108 |
+ } else if _, exists := config.Volumes[arr[1]]; !exists {
|
|
| 109 |
+ t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes)
|
|
| 110 |
+ } |
|
| 111 |
+ |
|
| 112 |
+ // A single bind-mount |
|
| 113 |
+ arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`})
|
|
| 114 |
+ if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
|
|
| 115 |
+ t.Fatalf("Error parsing volume flags, %q should mount-bind the path before the colon into the path after the colon. Received %v %v", arr[0], hostConfig.Binds, config.Volumes)
|
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ // Two bind-mounts. |
|
| 119 |
+ arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`})
|
|
| 120 |
+ if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
|
| 121 |
+ t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
|
|
| 122 |
+ } |
|
| 123 |
+ |
|
| 124 |
+ // Two bind-mounts, first read-only, second read-write. |
|
| 125 |
+ // TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4 |
|
| 126 |
+ arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`})
|
|
| 127 |
+ if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
|
| 128 |
+ t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
|
|
| 129 |
+ } |
|
| 130 |
+ |
|
| 131 |
+ // Similar to previous test but with alternate modes which are only supported by Linux |
|
| 132 |
+ if runtime.GOOS != "windows" {
|
|
| 133 |
+ arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{})
|
|
| 134 |
+ if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
|
| 135 |
+ t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
|
|
| 136 |
+ } |
|
| 137 |
+ |
|
| 138 |
+ arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{})
|
|
| 139 |
+ if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
|
| 140 |
+ t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
|
|
| 141 |
+ } |
|
| 142 |
+ } |
|
| 143 |
+ |
|
| 144 |
+ // One bind mount and one volume |
|
| 145 |
+ arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`})
|
|
| 146 |
+ if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
|
|
| 147 |
+ t.Fatalf("Error parsing volume flags, %s and %s should only one and only one bind mount %s. Received %s", arr[0], arr[1], arr[0], hostConfig.Binds)
|
|
| 148 |
+ } else if _, exists := config.Volumes[arr[1]]; !exists {
|
|
| 149 |
+ t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes)
|
|
| 150 |
+ } |
|
| 151 |
+ |
|
| 152 |
+ // Root to non-c: drive letter (Windows specific) |
|
| 153 |
+ if runtime.GOOS == "windows" {
|
|
| 154 |
+ arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`})
|
|
| 155 |
+ if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
|
|
| 156 |
+ t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
|
|
| 157 |
+ } |
|
| 158 |
+ } |
|
| 159 |
+ |
|
| 160 |
+} |
|
| 161 |
+ |
|
| 162 |
+// This tests the cases for binds which are generated through |
|
| 163 |
+// DecodeContainerConfig rather than Parse() |
|
| 164 |
+func TestDecodeContainerConfigVolumes(t *testing.T) {
|
|
| 165 |
+ |
|
| 166 |
+ // Root to root |
|
| 167 |
+ bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
|
|
| 168 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 169 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 170 |
+ } |
|
| 171 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 172 |
+ t.Fatalf("volume %v should have failed", bindsOrVols)
|
|
| 173 |
+ } |
|
| 174 |
+ |
|
| 175 |
+ // No destination path |
|
| 176 |
+ bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
|
|
| 177 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 178 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 179 |
+ } |
|
| 180 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 181 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 182 |
+ } |
|
| 183 |
+ |
|
| 184 |
+ // // No destination path or mode |
|
| 185 |
+ bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
|
|
| 186 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 187 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 188 |
+ } |
|
| 189 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 190 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 191 |
+ } |
|
| 192 |
+ |
|
| 193 |
+ // A whole lot of nothing |
|
| 194 |
+ bindsOrVols = []string{`:`}
|
|
| 195 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 196 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 197 |
+ } |
|
| 198 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 199 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 200 |
+ } |
|
| 201 |
+ |
|
| 202 |
+ // A whole lot of nothing with no mode |
|
| 203 |
+ bindsOrVols = []string{`::`}
|
|
| 204 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 205 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 206 |
+ } |
|
| 207 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 208 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 209 |
+ } |
|
| 210 |
+ |
|
| 211 |
+ // Too much including an invalid mode |
|
| 212 |
+ wTmp := os.Getenv("TEMP")
|
|
| 213 |
+ bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
|
|
| 214 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 215 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 216 |
+ } |
|
| 217 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 218 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 219 |
+ } |
|
| 220 |
+ |
|
| 221 |
+ // Windows specific error tests |
|
| 222 |
+ if runtime.GOOS == "windows" {
|
|
| 223 |
+ // Volume which does not include a drive letter |
|
| 224 |
+ bindsOrVols = []string{`\tmp`}
|
|
| 225 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 226 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 227 |
+ } |
|
| 228 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 229 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 230 |
+ } |
|
| 231 |
+ |
|
| 232 |
+ // Root to C-Drive |
|
| 233 |
+ bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
|
|
| 234 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 235 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 236 |
+ } |
|
| 237 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 238 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 239 |
+ } |
|
| 240 |
+ |
|
| 241 |
+ // Container path that does not include a drive letter |
|
| 242 |
+ bindsOrVols = []string{`c:\windows:\somewhere`}
|
|
| 243 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 244 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 245 |
+ } |
|
| 246 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 247 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 248 |
+ } |
|
| 249 |
+ } |
|
| 250 |
+ |
|
| 251 |
+ // Linux-specific error tests |
|
| 252 |
+ if runtime.GOOS != "windows" {
|
|
| 253 |
+ // Just root |
|
| 254 |
+ bindsOrVols = []string{`/`}
|
|
| 255 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 256 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 257 |
+ } |
|
| 258 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 259 |
+ t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 260 |
+ } |
|
| 261 |
+ |
|
| 262 |
+ // A single volume that looks like a bind mount passed in Volumes. |
|
| 263 |
+ // This should be handled as a bind mount, not a volume. |
|
| 264 |
+ vols := []string{`/foo:/bar`}
|
|
| 265 |
+ if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
|
|
| 266 |
+ t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
|
|
| 267 |
+ } else if hostConfig.Binds != nil {
|
|
| 268 |
+ t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
|
|
| 269 |
+ } else if _, exists := config.Volumes[vols[0]]; !exists {
|
|
| 270 |
+ t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
|
|
| 271 |
+ } |
|
| 272 |
+ |
|
| 273 |
+ } |
|
| 274 |
+} |
|
| 275 |
+ |
|
| 276 |
+// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes |
|
| 277 |
+// to call DecodeContainerConfig. It effectively does what a client would |
|
| 278 |
+// do when calling the daemon by constructing a JSON stream of a |
|
| 279 |
+// ContainerConfigWrapper which is populated by the set of volume specs |
|
| 280 |
+// passed into it. It returns a config and a hostconfig which can be |
|
| 281 |
+// validated to ensure DecodeContainerConfig has manipulated the structures |
|
| 282 |
+// correctly. |
|
| 283 |
+func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) {
|
|
| 284 |
+ var ( |
|
| 285 |
+ b []byte |
|
| 286 |
+ err error |
|
| 287 |
+ c *container.Config |
|
| 288 |
+ h *container.HostConfig |
|
| 289 |
+ ) |
|
| 290 |
+ w := ContainerConfigWrapper{
|
|
| 291 |
+ Config: &container.Config{
|
|
| 292 |
+ Volumes: map[string]struct{}{},
|
|
| 293 |
+ }, |
|
| 294 |
+ HostConfig: &container.HostConfig{
|
|
| 295 |
+ NetworkMode: "none", |
|
| 296 |
+ Binds: binds, |
|
| 297 |
+ }, |
|
| 298 |
+ } |
|
| 299 |
+ for _, v := range volumes {
|
|
| 300 |
+ w.Config.Volumes[v] = struct{}{}
|
|
| 301 |
+ } |
|
| 302 |
+ if b, err = json.Marshal(w); err != nil {
|
|
| 303 |
+ return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
|
|
| 304 |
+ } |
|
| 305 |
+ c, h, err = DecodeContainerConfig(bytes.NewReader(b)) |
|
| 306 |
+ if err != nil {
|
|
| 307 |
+ return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
|
|
| 308 |
+ } |
|
| 309 |
+ if c == nil || h == nil {
|
|
| 310 |
+ return nil, nil, fmt.Errorf("Empty config or hostconfig")
|
|
| 311 |
+ } |
|
| 312 |
+ |
|
| 313 |
+ return c, h, err |
|
| 314 |
+} |
|
| 315 |
+ |
|
| 316 |
+// check if (a == c && b == d) || (a == d && b == c) |
|
| 317 |
+// because maps are randomized |
|
| 318 |
+func compareRandomizedStrings(a, b, c, d string) error {
|
|
| 319 |
+ if a == c && b == d {
|
|
| 320 |
+ return nil |
|
| 321 |
+ } |
|
| 322 |
+ if a == d && b == c {
|
|
| 323 |
+ return nil |
|
| 324 |
+ } |
|
| 325 |
+ return fmt.Errorf("strings don't match")
|
|
| 326 |
+} |
|
| 327 |
+ |
|
| 328 |
+// setupPlatformVolume takes two arrays of volume specs - a Unix style |
|
| 329 |
+// spec and a Windows style spec. Depending on the platform being unit tested, |
|
| 330 |
+// it returns one of them, along with a volume string that would be passed |
|
| 331 |
+// on the docker CLI (eg -v /bar -v /foo). |
|
| 332 |
+func setupPlatformVolume(u []string, w []string) ([]string, string) {
|
|
| 333 |
+ var a []string |
|
| 334 |
+ if runtime.GOOS == "windows" {
|
|
| 335 |
+ a = w |
|
| 336 |
+ } else {
|
|
| 337 |
+ a = u |
|
| 338 |
+ } |
|
| 339 |
+ s := "" |
|
| 340 |
+ for _, v := range a {
|
|
| 341 |
+ s = s + "-v " + v + " " |
|
| 342 |
+ } |
|
| 343 |
+ return a, s |
|
| 344 |
+} |
|
| 345 |
+ |
|
| 346 |
+// Simple parse with MacAddress validation |
|
| 347 |
+func TestParseWithMacAddress(t *testing.T) {
|
|
| 348 |
+ invalidMacAddress := "--mac-address=invalidMacAddress" |
|
| 349 |
+ validMacAddress := "--mac-address=92:d0:c6:0a:29:33" |
|
| 350 |
+ if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
|
|
| 351 |
+ t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
|
|
| 352 |
+ } |
|
| 353 |
+ if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
|
|
| 354 |
+ t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress)
|
|
| 355 |
+ } |
|
| 356 |
+} |
|
| 357 |
+ |
|
| 358 |
+func TestParseWithMemory(t *testing.T) {
|
|
| 359 |
+ invalidMemory := "--memory=invalid" |
|
| 360 |
+ validMemory := "--memory=1G" |
|
| 361 |
+ if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
|
|
| 362 |
+ t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
|
|
| 363 |
+ } |
|
| 364 |
+ if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
|
|
| 365 |
+ t.Fatalf("Expected the config to have '1G' as Memory, got '%v'", hostconfig.Memory)
|
|
| 366 |
+ } |
|
| 367 |
+} |
|
| 368 |
+ |
|
| 369 |
+func TestParseWithMemorySwap(t *testing.T) {
|
|
| 370 |
+ invalidMemory := "--memory-swap=invalid" |
|
| 371 |
+ validMemory := "--memory-swap=1G" |
|
| 372 |
+ anotherValidMemory := "--memory-swap=-1" |
|
| 373 |
+ if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
|
|
| 374 |
+ t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
|
|
| 375 |
+ } |
|
| 376 |
+ if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
|
|
| 377 |
+ t.Fatalf("Expected the config to have '1073741824' as MemorySwap, got '%v'", hostconfig.MemorySwap)
|
|
| 378 |
+ } |
|
| 379 |
+ if _, hostconfig := mustParse(t, anotherValidMemory); hostconfig.MemorySwap != -1 {
|
|
| 380 |
+ t.Fatalf("Expected the config to have '-1' as MemorySwap, got '%v'", hostconfig.MemorySwap)
|
|
| 381 |
+ } |
|
| 382 |
+} |
|
| 383 |
+ |
|
| 384 |
+func TestParseHostname(t *testing.T) {
|
|
| 385 |
+ hostname := "--hostname=hostname" |
|
| 386 |
+ hostnameWithDomain := "--hostname=hostname.domainname" |
|
| 387 |
+ hostnameWithDomainTld := "--hostname=hostname.domainname.tld" |
|
| 388 |
+ if config, _ := mustParse(t, hostname); config.Hostname != "hostname" && config.Domainname != "" {
|
|
| 389 |
+ t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
|
|
| 390 |
+ } |
|
| 391 |
+ if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname" && config.Domainname != "domainname" {
|
|
| 392 |
+ t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
|
|
| 393 |
+ } |
|
| 394 |
+ if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname" && config.Domainname != "domainname.tld" {
|
|
| 395 |
+ t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
|
|
| 396 |
+ } |
|
| 397 |
+} |
|
| 398 |
+ |
|
| 399 |
+func TestParseWithExpose(t *testing.T) {
|
|
| 400 |
+ invalids := map[string]string{
|
|
| 401 |
+ ":": "Invalid port format for --expose: :", |
|
| 402 |
+ "8080:9090": "Invalid port format for --expose: 8080:9090", |
|
| 403 |
+ "/tcp": "Invalid range format for --expose: /tcp, error: Empty string specified for ports.", |
|
| 404 |
+ "/udp": "Invalid range format for --expose: /udp, error: Empty string specified for ports.", |
|
| 405 |
+ "NaN/tcp": `Invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, |
|
| 406 |
+ "NaN-NaN/tcp": `Invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, |
|
| 407 |
+ "8080-NaN/tcp": `Invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, |
|
| 408 |
+ "1234567890-8080/tcp": `Invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`, |
|
| 409 |
+ } |
|
| 410 |
+ valids := map[string][]nat.Port{
|
|
| 411 |
+ "8080/tcp": {"8080/tcp"},
|
|
| 412 |
+ "8080/udp": {"8080/udp"},
|
|
| 413 |
+ "8080/ncp": {"8080/ncp"},
|
|
| 414 |
+ "8080-8080/udp": {"8080/udp"},
|
|
| 415 |
+ "8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
|
|
| 416 |
+ } |
|
| 417 |
+ for expose, expectedError := range invalids {
|
|
| 418 |
+ if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
|
|
| 419 |
+ t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
|
|
| 420 |
+ } |
|
| 421 |
+ } |
|
| 422 |
+ for expose, exposedPorts := range valids {
|
|
| 423 |
+ config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
|
|
| 424 |
+ if err != nil {
|
|
| 425 |
+ t.Fatal(err) |
|
| 426 |
+ } |
|
| 427 |
+ if len(config.ExposedPorts) != len(exposedPorts) {
|
|
| 428 |
+ t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts))
|
|
| 429 |
+ } |
|
| 430 |
+ for _, port := range exposedPorts {
|
|
| 431 |
+ if _, ok := config.ExposedPorts[port]; !ok {
|
|
| 432 |
+ t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts)
|
|
| 433 |
+ } |
|
| 434 |
+ } |
|
| 435 |
+ } |
|
| 436 |
+ // Merge with actual published port |
|
| 437 |
+ config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
|
|
| 438 |
+ if err != nil {
|
|
| 439 |
+ t.Fatal(err) |
|
| 440 |
+ } |
|
| 441 |
+ if len(config.ExposedPorts) != 2 {
|
|
| 442 |
+ t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
|
|
| 443 |
+ } |
|
| 444 |
+ ports := []nat.Port{"80/tcp", "81/tcp"}
|
|
| 445 |
+ for _, port := range ports {
|
|
| 446 |
+ if _, ok := config.ExposedPorts[port]; !ok {
|
|
| 447 |
+ t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
|
|
| 448 |
+ } |
|
| 449 |
+ } |
|
| 450 |
+} |
|
| 451 |
+ |
|
| 452 |
+func TestParseDevice(t *testing.T) {
|
|
| 453 |
+ valids := map[string]container.DeviceMapping{
|
|
| 454 |
+ "/dev/snd": {
|
|
| 455 |
+ PathOnHost: "/dev/snd", |
|
| 456 |
+ PathInContainer: "/dev/snd", |
|
| 457 |
+ CgroupPermissions: "rwm", |
|
| 458 |
+ }, |
|
| 459 |
+ "/dev/snd:rw": {
|
|
| 460 |
+ PathOnHost: "/dev/snd", |
|
| 461 |
+ PathInContainer: "/dev/snd", |
|
| 462 |
+ CgroupPermissions: "rw", |
|
| 463 |
+ }, |
|
| 464 |
+ "/dev/snd:/something": {
|
|
| 465 |
+ PathOnHost: "/dev/snd", |
|
| 466 |
+ PathInContainer: "/something", |
|
| 467 |
+ CgroupPermissions: "rwm", |
|
| 468 |
+ }, |
|
| 469 |
+ "/dev/snd:/something:rw": {
|
|
| 470 |
+ PathOnHost: "/dev/snd", |
|
| 471 |
+ PathInContainer: "/something", |
|
| 472 |
+ CgroupPermissions: "rw", |
|
| 473 |
+ }, |
|
| 474 |
+ } |
|
| 475 |
+ for device, deviceMapping := range valids {
|
|
| 476 |
+ _, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
|
|
| 477 |
+ if err != nil {
|
|
| 478 |
+ t.Fatal(err) |
|
| 479 |
+ } |
|
| 480 |
+ if len(hostconfig.Devices) != 1 {
|
|
| 481 |
+ t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices)
|
|
| 482 |
+ } |
|
| 483 |
+ if hostconfig.Devices[0] != deviceMapping {
|
|
| 484 |
+ t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
|
|
| 485 |
+ } |
|
| 486 |
+ } |
|
| 487 |
+ |
|
| 488 |
+} |
|
| 489 |
+ |
|
| 490 |
+func TestParseModes(t *testing.T) {
|
|
| 491 |
+ // ipc ko |
|
| 492 |
+ if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
|
|
| 493 |
+ t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
|
|
| 494 |
+ } |
|
| 495 |
+ // ipc ok |
|
| 496 |
+ _, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
|
|
| 497 |
+ if err != nil {
|
|
| 498 |
+ t.Fatal(err) |
|
| 499 |
+ } |
|
| 500 |
+ if !hostconfig.IpcMode.Valid() {
|
|
| 501 |
+ t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
|
|
| 502 |
+ } |
|
| 503 |
+ // pid ko |
|
| 504 |
+ if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
|
|
| 505 |
+ t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
|
|
| 506 |
+ } |
|
| 507 |
+ // pid ok |
|
| 508 |
+ _, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
|
|
| 509 |
+ if err != nil {
|
|
| 510 |
+ t.Fatal(err) |
|
| 511 |
+ } |
|
| 512 |
+ if !hostconfig.PidMode.Valid() {
|
|
| 513 |
+ t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
|
|
| 514 |
+ } |
|
| 515 |
+ // uts ko |
|
| 516 |
+ if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
|
|
| 517 |
+ t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
|
|
| 518 |
+ } |
|
| 519 |
+ // uts ok |
|
| 520 |
+ _, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
|
|
| 521 |
+ if err != nil {
|
|
| 522 |
+ t.Fatal(err) |
|
| 523 |
+ } |
|
| 524 |
+ if !hostconfig.UTSMode.Valid() {
|
|
| 525 |
+ t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
|
|
| 526 |
+ } |
|
| 527 |
+ // shm-size ko |
|
| 528 |
+ if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
|
|
| 529 |
+ t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
|
|
| 530 |
+ } |
|
| 531 |
+ // shm-size ok |
|
| 532 |
+ _, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
|
|
| 533 |
+ if err != nil {
|
|
| 534 |
+ t.Fatal(err) |
|
| 535 |
+ } |
|
| 536 |
+ if *hostconfig.ShmSize != 134217728 {
|
|
| 537 |
+ t.Fatalf("Expected a valid ShmSize, got %d", *hostconfig.ShmSize)
|
|
| 538 |
+ } |
|
| 539 |
+} |
|
| 540 |
+ |
|
| 541 |
+func TestParseRestartPolicy(t *testing.T) {
|
|
| 542 |
+ invalids := map[string]string{
|
|
| 543 |
+ "something": "invalid restart policy something", |
|
| 544 |
+ "always:2": "maximum restart count not valid with restart policy of \"always\"", |
|
| 545 |
+ "always:2:3": "maximum restart count not valid with restart policy of \"always\"", |
|
| 546 |
+ "on-failure:invalid": `strconv.ParseInt: parsing "invalid": invalid syntax`, |
|
| 547 |
+ "on-failure:2:5": "restart count format is not valid, usage: 'on-failure:N' or 'on-failure'", |
|
| 548 |
+ } |
|
| 549 |
+ valids := map[string]container.RestartPolicy{
|
|
| 550 |
+ "": {},
|
|
| 551 |
+ "always": {
|
|
| 552 |
+ Name: "always", |
|
| 553 |
+ MaximumRetryCount: 0, |
|
| 554 |
+ }, |
|
| 555 |
+ "on-failure:1": {
|
|
| 556 |
+ Name: "on-failure", |
|
| 557 |
+ MaximumRetryCount: 1, |
|
| 558 |
+ }, |
|
| 559 |
+ } |
|
| 560 |
+ for restart, expectedError := range invalids {
|
|
| 561 |
+ if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
|
|
| 562 |
+ t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
|
|
| 563 |
+ } |
|
| 564 |
+ } |
|
| 565 |
+ for restart, expected := range valids {
|
|
| 566 |
+ _, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
|
|
| 567 |
+ if err != nil {
|
|
| 568 |
+ t.Fatal(err) |
|
| 569 |
+ } |
|
| 570 |
+ if hostconfig.RestartPolicy != expected {
|
|
| 571 |
+ t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy)
|
|
| 572 |
+ } |
|
| 573 |
+ } |
|
| 574 |
+} |
|
| 575 |
+ |
|
| 576 |
+func TestParseLoggingOpts(t *testing.T) {
|
|
| 577 |
+ // logging opts ko |
|
| 578 |
+ if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
|
|
| 579 |
+ t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err)
|
|
| 580 |
+ } |
|
| 581 |
+ // logging opts ok |
|
| 582 |
+ _, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
|
|
| 583 |
+ if err != nil {
|
|
| 584 |
+ t.Fatal(err) |
|
| 585 |
+ } |
|
| 586 |
+ if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 {
|
|
| 587 |
+ t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy)
|
|
| 588 |
+ } |
|
| 589 |
+} |
|
| 590 |
+ |
|
| 591 |
+func TestParseEnvfileVariables(t *testing.T) {
|
|
| 592 |
+ e := "open nonexistent: no such file or directory" |
|
| 593 |
+ if runtime.GOOS == "windows" {
|
|
| 594 |
+ e = "open nonexistent: The system cannot find the file specified." |
|
| 595 |
+ } |
|
| 596 |
+ // env ko |
|
| 597 |
+ if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
|
|
| 598 |
+ t.Fatalf("Expected an error with message '%s', got %v", e, err)
|
|
| 599 |
+ } |
|
| 600 |
+ // env ok |
|
| 601 |
+ config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
|
|
| 602 |
+ if err != nil {
|
|
| 603 |
+ t.Fatal(err) |
|
| 604 |
+ } |
|
| 605 |
+ if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
|
|
| 606 |
+ t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env)
|
|
| 607 |
+ } |
|
| 608 |
+ config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
|
|
| 609 |
+ if err != nil {
|
|
| 610 |
+ t.Fatal(err) |
|
| 611 |
+ } |
|
| 612 |
+ if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" {
|
|
| 613 |
+ t.Fatalf("Expected a a config with [ENV1=value1 ENV2=value2], got %v", config.Env)
|
|
| 614 |
+ } |
|
| 615 |
+} |
|
| 616 |
+ |
|
| 617 |
+func TestParseLabelfileVariables(t *testing.T) {
|
|
| 618 |
+ e := "open nonexistent: no such file or directory" |
|
| 619 |
+ if runtime.GOOS == "windows" {
|
|
| 620 |
+ e = "open nonexistent: The system cannot find the file specified." |
|
| 621 |
+ } |
|
| 622 |
+ // label ko |
|
| 623 |
+ if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
|
|
| 624 |
+ t.Fatalf("Expected an error with message '%s', got %v", e, err)
|
|
| 625 |
+ } |
|
| 626 |
+ // label ok |
|
| 627 |
+ config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
|
|
| 628 |
+ if err != nil {
|
|
| 629 |
+ t.Fatal(err) |
|
| 630 |
+ } |
|
| 631 |
+ if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
|
|
| 632 |
+ t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels)
|
|
| 633 |
+ } |
|
| 634 |
+ config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
|
|
| 635 |
+ if err != nil {
|
|
| 636 |
+ t.Fatal(err) |
|
| 637 |
+ } |
|
| 638 |
+ if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" {
|
|
| 639 |
+ t.Fatalf("Expected a a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels)
|
|
| 640 |
+ } |
|
| 641 |
+} |
|
| 642 |
+ |
|
| 643 |
+func TestParseEntryPoint(t *testing.T) {
|
|
| 644 |
+ config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
|
|
| 645 |
+ if err != nil {
|
|
| 646 |
+ t.Fatal(err) |
|
| 647 |
+ } |
|
| 648 |
+ if config.Entrypoint.Len() != 1 && config.Entrypoint.Slice()[0] != "anything" {
|
|
| 649 |
+ t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint)
|
|
| 650 |
+ } |
|
| 651 |
+} |
|
| 652 |
+ |
|
| 653 |
+func TestValidateLink(t *testing.T) {
|
|
| 654 |
+ valid := []string{
|
|
| 655 |
+ "name", |
|
| 656 |
+ "dcdfbe62ecd0:alias", |
|
| 657 |
+ "7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da", |
|
| 658 |
+ "angry_torvalds:linus", |
|
| 659 |
+ } |
|
| 660 |
+ invalid := map[string]string{
|
|
| 661 |
+ "": "empty string specified for links", |
|
| 662 |
+ "too:much:of:it": "bad format for links: too:much:of:it", |
|
| 663 |
+ } |
|
| 664 |
+ |
|
| 665 |
+ for _, link := range valid {
|
|
| 666 |
+ if _, err := ValidateLink(link); err != nil {
|
|
| 667 |
+ t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err)
|
|
| 668 |
+ } |
|
| 669 |
+ } |
|
| 670 |
+ |
|
| 671 |
+ for link, expectedError := range invalid {
|
|
| 672 |
+ if _, err := ValidateLink(link); err == nil {
|
|
| 673 |
+ t.Fatalf("ValidateLink(`%q`) should have failed validation", link)
|
|
| 674 |
+ } else {
|
|
| 675 |
+ if !strings.Contains(err.Error(), expectedError) {
|
|
| 676 |
+ t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError)
|
|
| 677 |
+ } |
|
| 678 |
+ } |
|
| 679 |
+ } |
|
| 680 |
+} |
|
| 681 |
+ |
|
| 682 |
+func TestParseLink(t *testing.T) {
|
|
| 683 |
+ name, alias, err := ParseLink("name:alias")
|
|
| 684 |
+ if err != nil {
|
|
| 685 |
+ t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
|
|
| 686 |
+ } |
|
| 687 |
+ if name != "name" {
|
|
| 688 |
+ t.Fatalf("Link name should have been name, got %s instead", name)
|
|
| 689 |
+ } |
|
| 690 |
+ if alias != "alias" {
|
|
| 691 |
+ t.Fatalf("Link alias should have been alias, got %s instead", alias)
|
|
| 692 |
+ } |
|
| 693 |
+ // short format definition |
|
| 694 |
+ name, alias, err = ParseLink("name")
|
|
| 695 |
+ if err != nil {
|
|
| 696 |
+ t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
|
|
| 697 |
+ } |
|
| 698 |
+ if name != "name" {
|
|
| 699 |
+ t.Fatalf("Link name should have been name, got %s instead", name)
|
|
| 700 |
+ } |
|
| 701 |
+ if alias != "name" {
|
|
| 702 |
+ t.Fatalf("Link alias should have been name, got %s instead", alias)
|
|
| 703 |
+ } |
|
| 704 |
+ // empty string link definition is not allowed |
|
| 705 |
+ if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
|
|
| 706 |
+ t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
|
|
| 707 |
+ } |
|
| 708 |
+ // more than two colons are not allowed |
|
| 709 |
+ if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
|
|
| 710 |
+ t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
|
|
| 711 |
+ } |
|
| 712 |
+} |
|
| 713 |
+ |
|
| 714 |
+func TestValidateDevice(t *testing.T) {
|
|
| 715 |
+ valid := []string{
|
|
| 716 |
+ "/home", |
|
| 717 |
+ "/home:/home", |
|
| 718 |
+ "/home:/something/else", |
|
| 719 |
+ "/with space", |
|
| 720 |
+ "/home:/with space", |
|
| 721 |
+ "relative:/absolute-path", |
|
| 722 |
+ "hostPath:/containerPath:r", |
|
| 723 |
+ "/hostPath:/containerPath:rw", |
|
| 724 |
+ "/hostPath:/containerPath:mrw", |
|
| 725 |
+ } |
|
| 726 |
+ invalid := map[string]string{
|
|
| 727 |
+ "": "bad format for path: ", |
|
| 728 |
+ "./": "./ is not an absolute path", |
|
| 729 |
+ "../": "../ is not an absolute path", |
|
| 730 |
+ "/:../": "../ is not an absolute path", |
|
| 731 |
+ "/:path": "path is not an absolute path", |
|
| 732 |
+ ":": "bad format for path: :", |
|
| 733 |
+ "/tmp:": " is not an absolute path", |
|
| 734 |
+ ":test": "bad format for path: :test", |
|
| 735 |
+ ":/test": "bad format for path: :/test", |
|
| 736 |
+ "tmp:": " is not an absolute path", |
|
| 737 |
+ ":test:": "bad format for path: :test:", |
|
| 738 |
+ "::": "bad format for path: ::", |
|
| 739 |
+ ":::": "bad format for path: :::", |
|
| 740 |
+ "/tmp:::": "bad format for path: /tmp:::", |
|
| 741 |
+ ":/tmp::": "bad format for path: :/tmp::", |
|
| 742 |
+ "path:ro": "ro is not an absolute path", |
|
| 743 |
+ "path:rr": "rr is not an absolute path", |
|
| 744 |
+ "a:/b:ro": "bad mode specified: ro", |
|
| 745 |
+ "a:/b:rr": "bad mode specified: rr", |
|
| 746 |
+ } |
|
| 747 |
+ |
|
| 748 |
+ for _, path := range valid {
|
|
| 749 |
+ if _, err := ValidateDevice(path); err != nil {
|
|
| 750 |
+ t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
|
|
| 751 |
+ } |
|
| 752 |
+ } |
|
| 753 |
+ |
|
| 754 |
+ for path, expectedError := range invalid {
|
|
| 755 |
+ if _, err := ValidateDevice(path); err == nil {
|
|
| 756 |
+ t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
|
|
| 757 |
+ } else {
|
|
| 758 |
+ if err.Error() != expectedError {
|
|
| 759 |
+ t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
|
|
| 760 |
+ } |
|
| 761 |
+ } |
|
| 762 |
+ } |
|
| 763 |
+} |
| 0 | 764 |
deleted file mode 100644 |
| ... | ... |
@@ -1,49 +0,0 @@ |
| 1 |
-package runconfig |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "github.com/docker/docker/api/types" |
|
| 5 |
- flag "github.com/docker/docker/pkg/mflag" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-// ParseExec parses the specified args for the specified command and generates |
|
| 9 |
-// an ExecConfig from it. |
|
| 10 |
-// If the minimal number of specified args is not right or if specified args are |
|
| 11 |
-// not valid, it will return an error. |
|
| 12 |
-func ParseExec(cmd *flag.FlagSet, args []string) (*types.ExecConfig, error) {
|
|
| 13 |
- var ( |
|
| 14 |
- flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
|
|
| 15 |
- flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
|
|
| 16 |
- flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background")
|
|
| 17 |
- flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
|
|
| 18 |
- flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to the command")
|
|
| 19 |
- execCmd []string |
|
| 20 |
- container string |
|
| 21 |
- ) |
|
| 22 |
- cmd.Require(flag.Min, 2) |
|
| 23 |
- if err := cmd.ParseFlags(args, true); err != nil {
|
|
| 24 |
- return nil, err |
|
| 25 |
- } |
|
| 26 |
- container = cmd.Arg(0) |
|
| 27 |
- parsedArgs := cmd.Args() |
|
| 28 |
- execCmd = parsedArgs[1:] |
|
| 29 |
- |
|
| 30 |
- execConfig := &types.ExecConfig{
|
|
| 31 |
- User: *flUser, |
|
| 32 |
- Privileged: *flPrivileged, |
|
| 33 |
- Tty: *flTty, |
|
| 34 |
- Cmd: execCmd, |
|
| 35 |
- Container: container, |
|
| 36 |
- Detach: *flDetach, |
|
| 37 |
- } |
|
| 38 |
- |
|
| 39 |
- // If -d is not set, attach to everything by default |
|
| 40 |
- if !*flDetach {
|
|
| 41 |
- execConfig.AttachStdout = true |
|
| 42 |
- execConfig.AttachStderr = true |
|
| 43 |
- if *flStdin {
|
|
| 44 |
- execConfig.AttachStdin = true |
|
| 45 |
- } |
|
| 46 |
- } |
|
| 47 |
- |
|
| 48 |
- return execConfig, nil |
|
| 49 |
-} |
| 50 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,130 +0,0 @@ |
| 1 |
-package runconfig |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "io/ioutil" |
|
| 6 |
- "testing" |
|
| 7 |
- |
|
| 8 |
- "github.com/docker/docker/api/types" |
|
| 9 |
- flag "github.com/docker/docker/pkg/mflag" |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-type arguments struct {
|
|
| 13 |
- args []string |
|
| 14 |
-} |
|
| 15 |
- |
|
| 16 |
-func TestParseExec(t *testing.T) {
|
|
| 17 |
- invalids := map[*arguments]error{
|
|
| 18 |
- &arguments{[]string{"-unknown"}}: fmt.Errorf("flag provided but not defined: -unknown"),
|
|
| 19 |
- &arguments{[]string{"-u"}}: fmt.Errorf("flag needs an argument: -u"),
|
|
| 20 |
- &arguments{[]string{"--user"}}: fmt.Errorf("flag needs an argument: --user"),
|
|
| 21 |
- } |
|
| 22 |
- valids := map[*arguments]*types.ExecConfig{
|
|
| 23 |
- &arguments{
|
|
| 24 |
- []string{"container", "command"},
|
|
| 25 |
- }: {
|
|
| 26 |
- Container: "container", |
|
| 27 |
- Cmd: []string{"command"},
|
|
| 28 |
- AttachStdout: true, |
|
| 29 |
- AttachStderr: true, |
|
| 30 |
- }, |
|
| 31 |
- &arguments{
|
|
| 32 |
- []string{"container", "command1", "command2"},
|
|
| 33 |
- }: {
|
|
| 34 |
- Container: "container", |
|
| 35 |
- Cmd: []string{"command1", "command2"},
|
|
| 36 |
- AttachStdout: true, |
|
| 37 |
- AttachStderr: true, |
|
| 38 |
- }, |
|
| 39 |
- &arguments{
|
|
| 40 |
- []string{"-i", "-t", "-u", "uid", "container", "command"},
|
|
| 41 |
- }: {
|
|
| 42 |
- User: "uid", |
|
| 43 |
- AttachStdin: true, |
|
| 44 |
- AttachStdout: true, |
|
| 45 |
- AttachStderr: true, |
|
| 46 |
- Tty: true, |
|
| 47 |
- Container: "container", |
|
| 48 |
- Cmd: []string{"command"},
|
|
| 49 |
- }, |
|
| 50 |
- &arguments{
|
|
| 51 |
- []string{"-d", "container", "command"},
|
|
| 52 |
- }: {
|
|
| 53 |
- AttachStdin: false, |
|
| 54 |
- AttachStdout: false, |
|
| 55 |
- AttachStderr: false, |
|
| 56 |
- Detach: true, |
|
| 57 |
- Container: "container", |
|
| 58 |
- Cmd: []string{"command"},
|
|
| 59 |
- }, |
|
| 60 |
- &arguments{
|
|
| 61 |
- []string{"-t", "-i", "-d", "container", "command"},
|
|
| 62 |
- }: {
|
|
| 63 |
- AttachStdin: false, |
|
| 64 |
- AttachStdout: false, |
|
| 65 |
- AttachStderr: false, |
|
| 66 |
- Detach: true, |
|
| 67 |
- Tty: true, |
|
| 68 |
- Container: "container", |
|
| 69 |
- Cmd: []string{"command"},
|
|
| 70 |
- }, |
|
| 71 |
- } |
|
| 72 |
- for invalid, expectedError := range invalids {
|
|
| 73 |
- cmd := flag.NewFlagSet("exec", flag.ContinueOnError)
|
|
| 74 |
- cmd.ShortUsage = func() {}
|
|
| 75 |
- cmd.SetOutput(ioutil.Discard) |
|
| 76 |
- _, err := ParseExec(cmd, invalid.args) |
|
| 77 |
- if err == nil || err.Error() != expectedError.Error() {
|
|
| 78 |
- t.Fatalf("Expected an error [%v] for %v, got %v", expectedError, invalid, err)
|
|
| 79 |
- } |
|
| 80 |
- |
|
| 81 |
- } |
|
| 82 |
- for valid, expectedExecConfig := range valids {
|
|
| 83 |
- cmd := flag.NewFlagSet("exec", flag.ContinueOnError)
|
|
| 84 |
- cmd.ShortUsage = func() {}
|
|
| 85 |
- cmd.SetOutput(ioutil.Discard) |
|
| 86 |
- execConfig, err := ParseExec(cmd, valid.args) |
|
| 87 |
- if err != nil {
|
|
| 88 |
- t.Fatal(err) |
|
| 89 |
- } |
|
| 90 |
- if !compareExecConfig(expectedExecConfig, execConfig) {
|
|
| 91 |
- t.Fatalf("Expected [%v] for %v, got [%v]", expectedExecConfig, valid, execConfig)
|
|
| 92 |
- } |
|
| 93 |
- } |
|
| 94 |
-} |
|
| 95 |
- |
|
| 96 |
-func compareExecConfig(config1 *types.ExecConfig, config2 *types.ExecConfig) bool {
|
|
| 97 |
- if config1.AttachStderr != config2.AttachStderr {
|
|
| 98 |
- return false |
|
| 99 |
- } |
|
| 100 |
- if config1.AttachStdin != config2.AttachStdin {
|
|
| 101 |
- return false |
|
| 102 |
- } |
|
| 103 |
- if config1.AttachStdout != config2.AttachStdout {
|
|
| 104 |
- return false |
|
| 105 |
- } |
|
| 106 |
- if config1.Container != config2.Container {
|
|
| 107 |
- return false |
|
| 108 |
- } |
|
| 109 |
- if config1.Detach != config2.Detach {
|
|
| 110 |
- return false |
|
| 111 |
- } |
|
| 112 |
- if config1.Privileged != config2.Privileged {
|
|
| 113 |
- return false |
|
| 114 |
- } |
|
| 115 |
- if config1.Tty != config2.Tty {
|
|
| 116 |
- return false |
|
| 117 |
- } |
|
| 118 |
- if config1.User != config2.User {
|
|
| 119 |
- return false |
|
| 120 |
- } |
|
| 121 |
- if len(config1.Cmd) != len(config2.Cmd) {
|
|
| 122 |
- return false |
|
| 123 |
- } |
|
| 124 |
- for index, value := range config1.Cmd {
|
|
| 125 |
- if value != config2.Cmd[index] {
|
|
| 126 |
- return false |
|
| 127 |
- } |
|
| 128 |
- } |
|
| 129 |
- return true |
|
| 130 |
-} |
| 131 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,651 +0,0 @@ |
| 1 |
-package runconfig |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "path" |
|
| 6 |
- "strconv" |
|
| 7 |
- "strings" |
|
| 8 |
- |
|
| 9 |
- "github.com/docker/docker/api/types/container" |
|
| 10 |
- "github.com/docker/docker/api/types/strslice" |
|
| 11 |
- "github.com/docker/docker/opts" |
|
| 12 |
- flag "github.com/docker/docker/pkg/mflag" |
|
| 13 |
- "github.com/docker/docker/pkg/mount" |
|
| 14 |
- "github.com/docker/docker/pkg/signal" |
|
| 15 |
- runconfigopts "github.com/docker/docker/runconfig/opts" |
|
| 16 |
- "github.com/docker/docker/volume" |
|
| 17 |
- "github.com/docker/go-connections/nat" |
|
| 18 |
- "github.com/docker/go-units" |
|
| 19 |
-) |
|
| 20 |
- |
|
| 21 |
-var ( |
|
| 22 |
- // ErrConflictContainerNetworkAndLinks conflict between --net=container and links |
|
| 23 |
- ErrConflictContainerNetworkAndLinks = fmt.Errorf("Conflicting options: container type network can't be used with links. This would result in undefined behavior")
|
|
| 24 |
- // ErrConflictUserDefinedNetworkAndLinks conflict between --net=<NETWORK> and links |
|
| 25 |
- ErrConflictUserDefinedNetworkAndLinks = fmt.Errorf("Conflicting options: networking can't be used with links. This would result in undefined behavior")
|
|
| 26 |
- // ErrConflictSharedNetwork conflict between private and other networks |
|
| 27 |
- ErrConflictSharedNetwork = fmt.Errorf("Container sharing network namespace with another container or host cannot be connected to any other network")
|
|
| 28 |
- // ErrConflictHostNetwork conflict from being disconnected from host network or connected to host network. |
|
| 29 |
- ErrConflictHostNetwork = fmt.Errorf("Container cannot be disconnected from host network or connected to host network")
|
|
| 30 |
- // ErrConflictNoNetwork conflict between private and other networks |
|
| 31 |
- ErrConflictNoNetwork = fmt.Errorf("Container cannot be connected to multiple networks with one of the networks in private (none) mode")
|
|
| 32 |
- // ErrConflictNetworkAndDNS conflict between --dns and the network mode |
|
| 33 |
- ErrConflictNetworkAndDNS = fmt.Errorf("Conflicting options: dns and the network mode")
|
|
| 34 |
- // ErrConflictNetworkHostname conflict between the hostname and the network mode |
|
| 35 |
- ErrConflictNetworkHostname = fmt.Errorf("Conflicting options: hostname and the network mode")
|
|
| 36 |
- // ErrConflictHostNetworkAndLinks conflict between --net=host and links |
|
| 37 |
- ErrConflictHostNetworkAndLinks = fmt.Errorf("Conflicting options: host type networking can't be used with links. This would result in undefined behavior")
|
|
| 38 |
- // ErrConflictContainerNetworkAndMac conflict between the mac address and the network mode |
|
| 39 |
- ErrConflictContainerNetworkAndMac = fmt.Errorf("Conflicting options: mac-address and the network mode")
|
|
| 40 |
- // ErrConflictNetworkHosts conflict between add-host and the network mode |
|
| 41 |
- ErrConflictNetworkHosts = fmt.Errorf("Conflicting options: custom host-to-IP mapping and the network mode")
|
|
| 42 |
- // ErrConflictNetworkPublishPorts conflict between the publish options and the network mode |
|
| 43 |
- ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type network mode")
|
|
| 44 |
- // ErrConflictNetworkExposePorts conflict between the expose option and the network mode |
|
| 45 |
- ErrConflictNetworkExposePorts = fmt.Errorf("Conflicting options: port exposing and the container type network mode")
|
|
| 46 |
-) |
|
| 47 |
- |
|
| 48 |
-// Parse parses the specified args for the specified command and generates a Config, |
|
| 49 |
-// a HostConfig and returns them with the specified command. |
|
| 50 |
-// If the specified args are not valid, it will return an error. |
|
| 51 |
-func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
|
|
| 52 |
- var ( |
|
| 53 |
- // FIXME: use utils.ListOpts for attach and volumes? |
|
| 54 |
- flAttach = opts.NewListOpts(opts.ValidateAttach) |
|
| 55 |
- flVolumes = opts.NewListOpts(nil) |
|
| 56 |
- flTmpfs = opts.NewListOpts(nil) |
|
| 57 |
- flBlkioWeightDevice = runconfigopts.NewWeightdeviceOpt(runconfigopts.ValidateWeightDevice) |
|
| 58 |
- flDeviceReadBps = runconfigopts.NewThrottledeviceOpt(runconfigopts.ValidateThrottleBpsDevice) |
|
| 59 |
- flDeviceWriteBps = runconfigopts.NewThrottledeviceOpt(runconfigopts.ValidateThrottleBpsDevice) |
|
| 60 |
- flLinks = opts.NewListOpts(ValidateLink) |
|
| 61 |
- flDeviceReadIOps = runconfigopts.NewThrottledeviceOpt(runconfigopts.ValidateThrottleIOpsDevice) |
|
| 62 |
- flDeviceWriteIOps = runconfigopts.NewThrottledeviceOpt(runconfigopts.ValidateThrottleIOpsDevice) |
|
| 63 |
- flEnv = opts.NewListOpts(opts.ValidateEnv) |
|
| 64 |
- flLabels = opts.NewListOpts(opts.ValidateEnv) |
|
| 65 |
- flDevices = opts.NewListOpts(ValidateDevice) |
|
| 66 |
- |
|
| 67 |
- flUlimits = runconfigopts.NewUlimitOpt(nil) |
|
| 68 |
- |
|
| 69 |
- flPublish = opts.NewListOpts(nil) |
|
| 70 |
- flExpose = opts.NewListOpts(nil) |
|
| 71 |
- flDNS = opts.NewListOpts(opts.ValidateIPAddress) |
|
| 72 |
- flDNSSearch = opts.NewListOpts(opts.ValidateDNSSearch) |
|
| 73 |
- flDNSOptions = opts.NewListOpts(nil) |
|
| 74 |
- flExtraHosts = opts.NewListOpts(opts.ValidateExtraHost) |
|
| 75 |
- flVolumesFrom = opts.NewListOpts(nil) |
|
| 76 |
- flEnvFile = opts.NewListOpts(nil) |
|
| 77 |
- flCapAdd = opts.NewListOpts(nil) |
|
| 78 |
- flCapDrop = opts.NewListOpts(nil) |
|
| 79 |
- flGroupAdd = opts.NewListOpts(nil) |
|
| 80 |
- flSecurityOpt = opts.NewListOpts(nil) |
|
| 81 |
- flLabelsFile = opts.NewListOpts(nil) |
|
| 82 |
- flLoggingOpts = opts.NewListOpts(nil) |
|
| 83 |
- flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to this container")
|
|
| 84 |
- flPidMode = cmd.String([]string{"-pid"}, "", "PID namespace to use")
|
|
| 85 |
- flUTSMode = cmd.String([]string{"-uts"}, "", "UTS namespace to use")
|
|
| 86 |
- flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
|
|
| 87 |
- flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
|
|
| 88 |
- flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
|
|
| 89 |
- flOomKillDisable = cmd.Bool([]string{"-oom-kill-disable"}, false, "Disable OOM Killer")
|
|
| 90 |
- flOomScoreAdj = cmd.Int([]string{"-oom-score-adj"}, 0, "Tune host's OOM preferences (-1000 to 1000)")
|
|
| 91 |
- flContainerIDFile = cmd.String([]string{"-cidfile"}, "", "Write the container ID to the file")
|
|
| 92 |
- flEntrypoint = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
|
|
| 93 |
- flHostname = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
|
|
| 94 |
- flMemoryString = cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
|
| 95 |
- flMemoryReservation = cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit")
|
|
| 96 |
- flMemorySwap = cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
|
|
| 97 |
- flKernelMemory = cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit")
|
|
| 98 |
- flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
|
|
| 99 |
- flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
|
|
| 100 |
- flCPUShares = cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
|
|
| 101 |
- flCPUPeriod = cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period")
|
|
| 102 |
- flCPUQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
|
|
| 103 |
- flCpusetCpus = cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
|
| 104 |
- flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
|
|
| 105 |
- flBlkioWeight = cmd.Uint16([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
|
|
| 106 |
- flSwappiness = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
|
|
| 107 |
- flNetMode = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
|
|
| 108 |
- flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
|
|
| 109 |
- flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
|
|
| 110 |
- flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
|
|
| 111 |
- flReadonlyRootfs = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
|
|
| 112 |
- flLoggingDriver = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
|
|
| 113 |
- flCgroupParent = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
|
| 114 |
- flVolumeDriver = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
|
|
| 115 |
- flStopSignal = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
|
|
| 116 |
- flIsolation = cmd.String([]string{"-isolation"}, "", "Container isolation level")
|
|
| 117 |
- flShmSize = cmd.String([]string{"-shm-size"}, "", "Size of /dev/shm, default value is 64MB")
|
|
| 118 |
- ) |
|
| 119 |
- |
|
| 120 |
- cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
|
|
| 121 |
- cmd.Var(&flBlkioWeightDevice, []string{"-blkio-weight-device"}, "Block IO weight (relative device weight)")
|
|
| 122 |
- cmd.Var(&flDeviceReadBps, []string{"-device-read-bps"}, "Limit read rate (bytes per second) from a device")
|
|
| 123 |
- cmd.Var(&flDeviceWriteBps, []string{"-device-write-bps"}, "Limit write rate (bytes per second) to a device")
|
|
| 124 |
- cmd.Var(&flDeviceReadIOps, []string{"-device-read-iops"}, "Limit read rate (IO per second) from a device")
|
|
| 125 |
- cmd.Var(&flDeviceWriteIOps, []string{"-device-write-iops"}, "Limit write rate (IO per second) to a device")
|
|
| 126 |
- cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
|
|
| 127 |
- cmd.Var(&flTmpfs, []string{"-tmpfs"}, "Mount a tmpfs directory")
|
|
| 128 |
- cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
|
|
| 129 |
- cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
|
|
| 130 |
- cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
|
|
| 131 |
- cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
|
|
| 132 |
- cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
|
|
| 133 |
- cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
|
|
| 134 |
- cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
|
|
| 135 |
- cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports")
|
|
| 136 |
- cmd.Var(&flDNS, []string{"-dns"}, "Set custom DNS servers")
|
|
| 137 |
- cmd.Var(&flDNSSearch, []string{"-dns-search"}, "Set custom DNS search domains")
|
|
| 138 |
- cmd.Var(&flDNSOptions, []string{"-dns-opt"}, "Set DNS options")
|
|
| 139 |
- cmd.Var(&flExtraHosts, []string{"-add-host"}, "Add a custom host-to-IP mapping (host:ip)")
|
|
| 140 |
- cmd.Var(&flVolumesFrom, []string{"-volumes-from"}, "Mount volumes from the specified container(s)")
|
|
| 141 |
- cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities")
|
|
| 142 |
- cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities")
|
|
| 143 |
- cmd.Var(&flGroupAdd, []string{"-group-add"}, "Add additional groups to join")
|
|
| 144 |
- cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options")
|
|
| 145 |
- cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
|
|
| 146 |
- cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options")
|
|
| 147 |
- |
|
| 148 |
- cmd.Require(flag.Min, 1) |
|
| 149 |
- |
|
| 150 |
- if err := cmd.ParseFlags(args, true); err != nil {
|
|
| 151 |
- return nil, nil, cmd, err |
|
| 152 |
- } |
|
| 153 |
- |
|
| 154 |
- var ( |
|
| 155 |
- attachStdin = flAttach.Get("stdin")
|
|
| 156 |
- attachStdout = flAttach.Get("stdout")
|
|
| 157 |
- attachStderr = flAttach.Get("stderr")
|
|
| 158 |
- ) |
|
| 159 |
- |
|
| 160 |
- // Validate the input mac address |
|
| 161 |
- if *flMacAddress != "" {
|
|
| 162 |
- if _, err := opts.ValidateMACAddress(*flMacAddress); err != nil {
|
|
| 163 |
- return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
|
|
| 164 |
- } |
|
| 165 |
- } |
|
| 166 |
- if *flStdin {
|
|
| 167 |
- attachStdin = true |
|
| 168 |
- } |
|
| 169 |
- // If -a is not set attach to the output stdio |
|
| 170 |
- if flAttach.Len() == 0 {
|
|
| 171 |
- attachStdout = true |
|
| 172 |
- attachStderr = true |
|
| 173 |
- } |
|
| 174 |
- |
|
| 175 |
- var err error |
|
| 176 |
- |
|
| 177 |
- var flMemory int64 |
|
| 178 |
- if *flMemoryString != "" {
|
|
| 179 |
- flMemory, err = units.RAMInBytes(*flMemoryString) |
|
| 180 |
- if err != nil {
|
|
| 181 |
- return nil, nil, cmd, err |
|
| 182 |
- } |
|
| 183 |
- } |
|
| 184 |
- |
|
| 185 |
- var MemoryReservation int64 |
|
| 186 |
- if *flMemoryReservation != "" {
|
|
| 187 |
- MemoryReservation, err = units.RAMInBytes(*flMemoryReservation) |
|
| 188 |
- if err != nil {
|
|
| 189 |
- return nil, nil, cmd, err |
|
| 190 |
- } |
|
| 191 |
- } |
|
| 192 |
- |
|
| 193 |
- var memorySwap int64 |
|
| 194 |
- if *flMemorySwap != "" {
|
|
| 195 |
- if *flMemorySwap == "-1" {
|
|
| 196 |
- memorySwap = -1 |
|
| 197 |
- } else {
|
|
| 198 |
- memorySwap, err = units.RAMInBytes(*flMemorySwap) |
|
| 199 |
- if err != nil {
|
|
| 200 |
- return nil, nil, cmd, err |
|
| 201 |
- } |
|
| 202 |
- } |
|
| 203 |
- } |
|
| 204 |
- |
|
| 205 |
- var KernelMemory int64 |
|
| 206 |
- if *flKernelMemory != "" {
|
|
| 207 |
- KernelMemory, err = units.RAMInBytes(*flKernelMemory) |
|
| 208 |
- if err != nil {
|
|
| 209 |
- return nil, nil, cmd, err |
|
| 210 |
- } |
|
| 211 |
- } |
|
| 212 |
- |
|
| 213 |
- swappiness := *flSwappiness |
|
| 214 |
- if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
|
|
| 215 |
- return nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
|
|
| 216 |
- } |
|
| 217 |
- |
|
| 218 |
- var parsedShm *int64 |
|
| 219 |
- if *flShmSize != "" {
|
|
| 220 |
- shmSize, err := units.RAMInBytes(*flShmSize) |
|
| 221 |
- if err != nil {
|
|
| 222 |
- return nil, nil, cmd, err |
|
| 223 |
- } |
|
| 224 |
- parsedShm = &shmSize |
|
| 225 |
- } |
|
| 226 |
- |
|
| 227 |
- var binds []string |
|
| 228 |
- // add any bind targets to the list of container volumes |
|
| 229 |
- for bind := range flVolumes.GetMap() {
|
|
| 230 |
- if arr := volume.SplitN(bind, 2); len(arr) > 1 {
|
|
| 231 |
- // after creating the bind mount we want to delete it from the flVolumes values because |
|
| 232 |
- // we do not want bind mounts being committed to image configs |
|
| 233 |
- binds = append(binds, bind) |
|
| 234 |
- flVolumes.Delete(bind) |
|
| 235 |
- } |
|
| 236 |
- } |
|
| 237 |
- |
|
| 238 |
- // Can't evaluate options passed into --tmpfs until we actually mount |
|
| 239 |
- tmpfs := make(map[string]string) |
|
| 240 |
- for _, t := range flTmpfs.GetAll() {
|
|
| 241 |
- if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
|
|
| 242 |
- if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
|
|
| 243 |
- return nil, nil, cmd, err |
|
| 244 |
- } |
|
| 245 |
- tmpfs[arr[0]] = arr[1] |
|
| 246 |
- } else {
|
|
| 247 |
- tmpfs[arr[0]] = "" |
|
| 248 |
- } |
|
| 249 |
- } |
|
| 250 |
- |
|
| 251 |
- var ( |
|
| 252 |
- parsedArgs = cmd.Args() |
|
| 253 |
- runCmd *strslice.StrSlice |
|
| 254 |
- entrypoint *strslice.StrSlice |
|
| 255 |
- image = cmd.Arg(0) |
|
| 256 |
- ) |
|
| 257 |
- if len(parsedArgs) > 1 {
|
|
| 258 |
- runCmd = strslice.New(parsedArgs[1:]...) |
|
| 259 |
- } |
|
| 260 |
- if *flEntrypoint != "" {
|
|
| 261 |
- entrypoint = strslice.New(*flEntrypoint) |
|
| 262 |
- } |
|
| 263 |
- |
|
| 264 |
- var ( |
|
| 265 |
- domainname string |
|
| 266 |
- hostname = *flHostname |
|
| 267 |
- parts = strings.SplitN(hostname, ".", 2) |
|
| 268 |
- ) |
|
| 269 |
- if len(parts) > 1 {
|
|
| 270 |
- hostname = parts[0] |
|
| 271 |
- domainname = parts[1] |
|
| 272 |
- } |
|
| 273 |
- |
|
| 274 |
- ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll()) |
|
| 275 |
- if err != nil {
|
|
| 276 |
- return nil, nil, cmd, err |
|
| 277 |
- } |
|
| 278 |
- |
|
| 279 |
- // Merge in exposed ports to the map of published ports |
|
| 280 |
- for _, e := range flExpose.GetAll() {
|
|
| 281 |
- if strings.Contains(e, ":") {
|
|
| 282 |
- return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
|
|
| 283 |
- } |
|
| 284 |
- //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] |
|
| 285 |
- proto, port := nat.SplitProtoPort(e) |
|
| 286 |
- //parse the start and end port and create a sequence of ports to expose |
|
| 287 |
- //if expose a port, the start and end port are the same |
|
| 288 |
- start, end, err := nat.ParsePortRange(port) |
|
| 289 |
- if err != nil {
|
|
| 290 |
- return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
|
|
| 291 |
- } |
|
| 292 |
- for i := start; i <= end; i++ {
|
|
| 293 |
- p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) |
|
| 294 |
- if err != nil {
|
|
| 295 |
- return nil, nil, cmd, err |
|
| 296 |
- } |
|
| 297 |
- if _, exists := ports[p]; !exists {
|
|
| 298 |
- ports[p] = struct{}{}
|
|
| 299 |
- } |
|
| 300 |
- } |
|
| 301 |
- } |
|
| 302 |
- |
|
| 303 |
- // parse device mappings |
|
| 304 |
- deviceMappings := []container.DeviceMapping{}
|
|
| 305 |
- for _, device := range flDevices.GetAll() {
|
|
| 306 |
- deviceMapping, err := ParseDevice(device) |
|
| 307 |
- if err != nil {
|
|
| 308 |
- return nil, nil, cmd, err |
|
| 309 |
- } |
|
| 310 |
- deviceMappings = append(deviceMappings, deviceMapping) |
|
| 311 |
- } |
|
| 312 |
- |
|
| 313 |
- // collect all the environment variables for the container |
|
| 314 |
- envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll()) |
|
| 315 |
- if err != nil {
|
|
| 316 |
- return nil, nil, cmd, err |
|
| 317 |
- } |
|
| 318 |
- |
|
| 319 |
- // collect all the labels for the container |
|
| 320 |
- labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll()) |
|
| 321 |
- if err != nil {
|
|
| 322 |
- return nil, nil, cmd, err |
|
| 323 |
- } |
|
| 324 |
- |
|
| 325 |
- ipcMode := container.IpcMode(*flIpcMode) |
|
| 326 |
- if !ipcMode.Valid() {
|
|
| 327 |
- return nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
|
|
| 328 |
- } |
|
| 329 |
- |
|
| 330 |
- pidMode := container.PidMode(*flPidMode) |
|
| 331 |
- if !pidMode.Valid() {
|
|
| 332 |
- return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
|
|
| 333 |
- } |
|
| 334 |
- |
|
| 335 |
- utsMode := container.UTSMode(*flUTSMode) |
|
| 336 |
- if !utsMode.Valid() {
|
|
| 337 |
- return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
|
|
| 338 |
- } |
|
| 339 |
- |
|
| 340 |
- restartPolicy, err := ParseRestartPolicy(*flRestartPolicy) |
|
| 341 |
- if err != nil {
|
|
| 342 |
- return nil, nil, cmd, err |
|
| 343 |
- } |
|
| 344 |
- |
|
| 345 |
- loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll()) |
|
| 346 |
- if err != nil {
|
|
| 347 |
- return nil, nil, cmd, err |
|
| 348 |
- } |
|
| 349 |
- |
|
| 350 |
- resources := container.Resources{
|
|
| 351 |
- CgroupParent: *flCgroupParent, |
|
| 352 |
- Memory: flMemory, |
|
| 353 |
- MemoryReservation: MemoryReservation, |
|
| 354 |
- MemorySwap: memorySwap, |
|
| 355 |
- MemorySwappiness: flSwappiness, |
|
| 356 |
- KernelMemory: KernelMemory, |
|
| 357 |
- OomKillDisable: *flOomKillDisable, |
|
| 358 |
- CPUShares: *flCPUShares, |
|
| 359 |
- CPUPeriod: *flCPUPeriod, |
|
| 360 |
- CpusetCpus: *flCpusetCpus, |
|
| 361 |
- CpusetMems: *flCpusetMems, |
|
| 362 |
- CPUQuota: *flCPUQuota, |
|
| 363 |
- BlkioWeight: *flBlkioWeight, |
|
| 364 |
- BlkioWeightDevice: flBlkioWeightDevice.GetList(), |
|
| 365 |
- BlkioDeviceReadBps: flDeviceReadBps.GetList(), |
|
| 366 |
- BlkioDeviceWriteBps: flDeviceWriteBps.GetList(), |
|
| 367 |
- BlkioDeviceReadIOps: flDeviceReadIOps.GetList(), |
|
| 368 |
- BlkioDeviceWriteIOps: flDeviceWriteIOps.GetList(), |
|
| 369 |
- Ulimits: flUlimits.GetList(), |
|
| 370 |
- Devices: deviceMappings, |
|
| 371 |
- } |
|
| 372 |
- |
|
| 373 |
- config := &container.Config{
|
|
| 374 |
- Hostname: hostname, |
|
| 375 |
- Domainname: domainname, |
|
| 376 |
- ExposedPorts: ports, |
|
| 377 |
- User: *flUser, |
|
| 378 |
- Tty: *flTty, |
|
| 379 |
- // TODO: deprecated, it comes from -n, --networking |
|
| 380 |
- // it's still needed internally to set the network to disabled |
|
| 381 |
- // if e.g. bridge is none in daemon opts, and in inspect |
|
| 382 |
- NetworkDisabled: false, |
|
| 383 |
- OpenStdin: *flStdin, |
|
| 384 |
- AttachStdin: attachStdin, |
|
| 385 |
- AttachStdout: attachStdout, |
|
| 386 |
- AttachStderr: attachStderr, |
|
| 387 |
- Env: envVariables, |
|
| 388 |
- Cmd: runCmd, |
|
| 389 |
- Image: image, |
|
| 390 |
- Volumes: flVolumes.GetMap(), |
|
| 391 |
- MacAddress: *flMacAddress, |
|
| 392 |
- Entrypoint: entrypoint, |
|
| 393 |
- WorkingDir: *flWorkingDir, |
|
| 394 |
- Labels: ConvertKVStringsToMap(labels), |
|
| 395 |
- StopSignal: *flStopSignal, |
|
| 396 |
- } |
|
| 397 |
- |
|
| 398 |
- hostConfig := &container.HostConfig{
|
|
| 399 |
- Binds: binds, |
|
| 400 |
- ContainerIDFile: *flContainerIDFile, |
|
| 401 |
- OomScoreAdj: *flOomScoreAdj, |
|
| 402 |
- Privileged: *flPrivileged, |
|
| 403 |
- PortBindings: portBindings, |
|
| 404 |
- Links: flLinks.GetAll(), |
|
| 405 |
- PublishAllPorts: *flPublishAll, |
|
| 406 |
- // Make sure the dns fields are never nil. |
|
| 407 |
- // New containers don't ever have those fields nil, |
|
| 408 |
- // but pre created containers can still have those nil values. |
|
| 409 |
- // See https://github.com/docker/docker/pull/17779 |
|
| 410 |
- // for a more detailed explanation on why we don't want that. |
|
| 411 |
- DNS: flDNS.GetAllOrEmpty(), |
|
| 412 |
- DNSSearch: flDNSSearch.GetAllOrEmpty(), |
|
| 413 |
- DNSOptions: flDNSOptions.GetAllOrEmpty(), |
|
| 414 |
- ExtraHosts: flExtraHosts.GetAll(), |
|
| 415 |
- VolumesFrom: flVolumesFrom.GetAll(), |
|
| 416 |
- NetworkMode: container.NetworkMode(*flNetMode), |
|
| 417 |
- IpcMode: ipcMode, |
|
| 418 |
- PidMode: pidMode, |
|
| 419 |
- UTSMode: utsMode, |
|
| 420 |
- CapAdd: strslice.New(flCapAdd.GetAll()...), |
|
| 421 |
- CapDrop: strslice.New(flCapDrop.GetAll()...), |
|
| 422 |
- GroupAdd: flGroupAdd.GetAll(), |
|
| 423 |
- RestartPolicy: restartPolicy, |
|
| 424 |
- SecurityOpt: flSecurityOpt.GetAll(), |
|
| 425 |
- ReadonlyRootfs: *flReadonlyRootfs, |
|
| 426 |
- LogConfig: container.LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
|
|
| 427 |
- VolumeDriver: *flVolumeDriver, |
|
| 428 |
- Isolation: container.IsolationLevel(*flIsolation), |
|
| 429 |
- ShmSize: parsedShm, |
|
| 430 |
- Resources: resources, |
|
| 431 |
- Tmpfs: tmpfs, |
|
| 432 |
- } |
|
| 433 |
- |
|
| 434 |
- // When allocating stdin in attached mode, close stdin at client disconnect |
|
| 435 |
- if config.OpenStdin && config.AttachStdin {
|
|
| 436 |
- config.StdinOnce = true |
|
| 437 |
- } |
|
| 438 |
- return config, hostConfig, cmd, nil |
|
| 439 |
-} |
|
| 440 |
- |
|
| 441 |
-// reads a file of line terminated key=value pairs and override that with override parameter |
|
| 442 |
-func readKVStrings(files []string, override []string) ([]string, error) {
|
|
| 443 |
- envVariables := []string{}
|
|
| 444 |
- for _, ef := range files {
|
|
| 445 |
- parsedVars, err := opts.ParseEnvFile(ef) |
|
| 446 |
- if err != nil {
|
|
| 447 |
- return nil, err |
|
| 448 |
- } |
|
| 449 |
- envVariables = append(envVariables, parsedVars...) |
|
| 450 |
- } |
|
| 451 |
- // parse the '-e' and '--env' after, to allow override |
|
| 452 |
- envVariables = append(envVariables, override...) |
|
| 453 |
- |
|
| 454 |
- return envVariables, nil |
|
| 455 |
-} |
|
| 456 |
- |
|
| 457 |
-// ConvertKVStringsToMap converts ["key=value"] to {"key":"value"}
|
|
| 458 |
-func ConvertKVStringsToMap(values []string) map[string]string {
|
|
| 459 |
- result := make(map[string]string, len(values)) |
|
| 460 |
- for _, value := range values {
|
|
| 461 |
- kv := strings.SplitN(value, "=", 2) |
|
| 462 |
- if len(kv) == 1 {
|
|
| 463 |
- result[kv[0]] = "" |
|
| 464 |
- } else {
|
|
| 465 |
- result[kv[0]] = kv[1] |
|
| 466 |
- } |
|
| 467 |
- } |
|
| 468 |
- |
|
| 469 |
- return result |
|
| 470 |
-} |
|
| 471 |
- |
|
| 472 |
-func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
|
|
| 473 |
- loggingOptsMap := ConvertKVStringsToMap(loggingOpts) |
|
| 474 |
- if loggingDriver == "none" && len(loggingOpts) > 0 {
|
|
| 475 |
- return map[string]string{}, fmt.Errorf("Invalid logging opts for driver %s", loggingDriver)
|
|
| 476 |
- } |
|
| 477 |
- return loggingOptsMap, nil |
|
| 478 |
-} |
|
| 479 |
- |
|
| 480 |
-// ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect |
|
| 481 |
-func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
|
|
| 482 |
- p := container.RestartPolicy{}
|
|
| 483 |
- |
|
| 484 |
- if policy == "" {
|
|
| 485 |
- return p, nil |
|
| 486 |
- } |
|
| 487 |
- |
|
| 488 |
- var ( |
|
| 489 |
- parts = strings.Split(policy, ":") |
|
| 490 |
- name = parts[0] |
|
| 491 |
- ) |
|
| 492 |
- |
|
| 493 |
- p.Name = name |
|
| 494 |
- switch name {
|
|
| 495 |
- case "always", "unless-stopped": |
|
| 496 |
- if len(parts) > 1 {
|
|
| 497 |
- return p, fmt.Errorf("maximum restart count not valid with restart policy of \"%s\"", name)
|
|
| 498 |
- } |
|
| 499 |
- case "no": |
|
| 500 |
- // do nothing |
|
| 501 |
- case "on-failure": |
|
| 502 |
- if len(parts) > 2 {
|
|
| 503 |
- return p, fmt.Errorf("restart count format is not valid, usage: 'on-failure:N' or 'on-failure'")
|
|
| 504 |
- } |
|
| 505 |
- if len(parts) == 2 {
|
|
| 506 |
- count, err := strconv.Atoi(parts[1]) |
|
| 507 |
- if err != nil {
|
|
| 508 |
- return p, err |
|
| 509 |
- } |
|
| 510 |
- |
|
| 511 |
- p.MaximumRetryCount = count |
|
| 512 |
- } |
|
| 513 |
- default: |
|
| 514 |
- return p, fmt.Errorf("invalid restart policy %s", name)
|
|
| 515 |
- } |
|
| 516 |
- |
|
| 517 |
- return p, nil |
|
| 518 |
-} |
|
| 519 |
- |
|
| 520 |
-// ParseDevice parses a device mapping string to a container.DeviceMapping struct |
|
| 521 |
-func ParseDevice(device string) (container.DeviceMapping, error) {
|
|
| 522 |
- src := "" |
|
| 523 |
- dst := "" |
|
| 524 |
- permissions := "rwm" |
|
| 525 |
- arr := strings.Split(device, ":") |
|
| 526 |
- switch len(arr) {
|
|
| 527 |
- case 3: |
|
| 528 |
- permissions = arr[2] |
|
| 529 |
- fallthrough |
|
| 530 |
- case 2: |
|
| 531 |
- if ValidDeviceMode(arr[1]) {
|
|
| 532 |
- permissions = arr[1] |
|
| 533 |
- } else {
|
|
| 534 |
- dst = arr[1] |
|
| 535 |
- } |
|
| 536 |
- fallthrough |
|
| 537 |
- case 1: |
|
| 538 |
- src = arr[0] |
|
| 539 |
- default: |
|
| 540 |
- return container.DeviceMapping{}, fmt.Errorf("Invalid device specification: %s", device)
|
|
| 541 |
- } |
|
| 542 |
- |
|
| 543 |
- if dst == "" {
|
|
| 544 |
- dst = src |
|
| 545 |
- } |
|
| 546 |
- |
|
| 547 |
- deviceMapping := container.DeviceMapping{
|
|
| 548 |
- PathOnHost: src, |
|
| 549 |
- PathInContainer: dst, |
|
| 550 |
- CgroupPermissions: permissions, |
|
| 551 |
- } |
|
| 552 |
- return deviceMapping, nil |
|
| 553 |
-} |
|
| 554 |
- |
|
| 555 |
-// ParseLink parses and validates the specified string as a link format (name:alias) |
|
| 556 |
-func ParseLink(val string) (string, string, error) {
|
|
| 557 |
- if val == "" {
|
|
| 558 |
- return "", "", fmt.Errorf("empty string specified for links")
|
|
| 559 |
- } |
|
| 560 |
- arr := strings.Split(val, ":") |
|
| 561 |
- if len(arr) > 2 {
|
|
| 562 |
- return "", "", fmt.Errorf("bad format for links: %s", val)
|
|
| 563 |
- } |
|
| 564 |
- if len(arr) == 1 {
|
|
| 565 |
- return val, val, nil |
|
| 566 |
- } |
|
| 567 |
- // This is kept because we can actually get an HostConfig with links |
|
| 568 |
- // from an already created container and the format is not `foo:bar` |
|
| 569 |
- // but `/foo:/c1/bar` |
|
| 570 |
- if strings.HasPrefix(arr[0], "/") {
|
|
| 571 |
- _, alias := path.Split(arr[1]) |
|
| 572 |
- return arr[0][1:], alias, nil |
|
| 573 |
- } |
|
| 574 |
- return arr[0], arr[1], nil |
|
| 575 |
-} |
|
| 576 |
- |
|
| 577 |
-// ValidateLink validates that the specified string has a valid link format (containerName:alias). |
|
| 578 |
-func ValidateLink(val string) (string, error) {
|
|
| 579 |
- if _, _, err := ParseLink(val); err != nil {
|
|
| 580 |
- return val, err |
|
| 581 |
- } |
|
| 582 |
- return val, nil |
|
| 583 |
-} |
|
| 584 |
- |
|
| 585 |
-// ValidDeviceMode checks if the mode for device is valid or not. |
|
| 586 |
-// Valid mode is a composition of r (read), w (write), and m (mknod). |
|
| 587 |
-func ValidDeviceMode(mode string) bool {
|
|
| 588 |
- var legalDeviceMode = map[rune]bool{
|
|
| 589 |
- 'r': true, |
|
| 590 |
- 'w': true, |
|
| 591 |
- 'm': true, |
|
| 592 |
- } |
|
| 593 |
- if mode == "" {
|
|
| 594 |
- return false |
|
| 595 |
- } |
|
| 596 |
- for _, c := range mode {
|
|
| 597 |
- if !legalDeviceMode[c] {
|
|
| 598 |
- return false |
|
| 599 |
- } |
|
| 600 |
- legalDeviceMode[c] = false |
|
| 601 |
- } |
|
| 602 |
- return true |
|
| 603 |
-} |
|
| 604 |
- |
|
| 605 |
-// ValidateDevice validates a path for devices |
|
| 606 |
-// It will make sure 'val' is in the form: |
|
| 607 |
-// [host-dir:]container-path[:mode] |
|
| 608 |
-// It also validates the device mode. |
|
| 609 |
-func ValidateDevice(val string) (string, error) {
|
|
| 610 |
- return validatePath(val, ValidDeviceMode) |
|
| 611 |
-} |
|
| 612 |
- |
|
| 613 |
-func validatePath(val string, validator func(string) bool) (string, error) {
|
|
| 614 |
- var containerPath string |
|
| 615 |
- var mode string |
|
| 616 |
- |
|
| 617 |
- if strings.Count(val, ":") > 2 {
|
|
| 618 |
- return val, fmt.Errorf("bad format for path: %s", val)
|
|
| 619 |
- } |
|
| 620 |
- |
|
| 621 |
- split := strings.SplitN(val, ":", 3) |
|
| 622 |
- if split[0] == "" {
|
|
| 623 |
- return val, fmt.Errorf("bad format for path: %s", val)
|
|
| 624 |
- } |
|
| 625 |
- switch len(split) {
|
|
| 626 |
- case 1: |
|
| 627 |
- containerPath = split[0] |
|
| 628 |
- val = path.Clean(containerPath) |
|
| 629 |
- case 2: |
|
| 630 |
- if isValid := validator(split[1]); isValid {
|
|
| 631 |
- containerPath = split[0] |
|
| 632 |
- mode = split[1] |
|
| 633 |
- val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
|
|
| 634 |
- } else {
|
|
| 635 |
- containerPath = split[1] |
|
| 636 |
- val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
|
|
| 637 |
- } |
|
| 638 |
- case 3: |
|
| 639 |
- containerPath = split[1] |
|
| 640 |
- mode = split[2] |
|
| 641 |
- if isValid := validator(split[2]); !isValid {
|
|
| 642 |
- return val, fmt.Errorf("bad mode specified: %s", mode)
|
|
| 643 |
- } |
|
| 644 |
- val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
|
|
| 645 |
- } |
|
| 646 |
- |
|
| 647 |
- if !path.IsAbs(containerPath) {
|
|
| 648 |
- return val, fmt.Errorf("%s is not an absolute path", containerPath)
|
|
| 649 |
- } |
|
| 650 |
- return val, nil |
|
| 651 |
-} |
| 652 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,764 +0,0 @@ |
| 1 |
-package runconfig |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bytes" |
|
| 5 |
- "encoding/json" |
|
| 6 |
- "fmt" |
|
| 7 |
- "io/ioutil" |
|
| 8 |
- "os" |
|
| 9 |
- "runtime" |
|
| 10 |
- "strings" |
|
| 11 |
- "testing" |
|
| 12 |
- |
|
| 13 |
- "github.com/docker/docker/api/types/container" |
|
| 14 |
- flag "github.com/docker/docker/pkg/mflag" |
|
| 15 |
- "github.com/docker/go-connections/nat" |
|
| 16 |
-) |
|
| 17 |
- |
|
| 18 |
-func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
|
|
| 19 |
- cmd := flag.NewFlagSet("run", flag.ContinueOnError)
|
|
| 20 |
- cmd.SetOutput(ioutil.Discard) |
|
| 21 |
- cmd.Usage = nil |
|
| 22 |
- return Parse(cmd, args) |
|
| 23 |
-} |
|
| 24 |
- |
|
| 25 |
-func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
|
|
| 26 |
- config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " ")) |
|
| 27 |
- return config, hostConfig, err |
|
| 28 |
-} |
|
| 29 |
- |
|
| 30 |
-func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
|
|
| 31 |
- config, hostConfig, err := parse(t, args) |
|
| 32 |
- if err != nil {
|
|
| 33 |
- t.Fatal(err) |
|
| 34 |
- } |
|
| 35 |
- return config, hostConfig |
|
| 36 |
-} |
|
| 37 |
- |
|
| 38 |
-func TestParseRunLinks(t *testing.T) {
|
|
| 39 |
- if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
|
|
| 40 |
- t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
|
|
| 41 |
- } |
|
| 42 |
- if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
|
|
| 43 |
- t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
|
|
| 44 |
- } |
|
| 45 |
- if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
|
|
| 46 |
- t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
|
|
| 47 |
- } |
|
| 48 |
-} |
|
| 49 |
- |
|
| 50 |
-func TestParseRunAttach(t *testing.T) {
|
|
| 51 |
- if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
|
| 52 |
- t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
|
| 53 |
- } |
|
| 54 |
- if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
|
|
| 55 |
- t.Fatalf("Error parsing attach flags. Expect only Stdin and Stdout enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
|
| 56 |
- } |
|
| 57 |
- if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
|
|
| 58 |
- t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
|
| 59 |
- } |
|
| 60 |
- if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
|
|
| 61 |
- t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
|
| 62 |
- } |
|
| 63 |
- if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
|
|
| 64 |
- t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
|
| 65 |
- } |
|
| 66 |
- |
|
| 67 |
- if _, _, err := parse(t, "-a"); err == nil {
|
|
| 68 |
- t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
|
|
| 69 |
- } |
|
| 70 |
- if _, _, err := parse(t, "-a invalid"); err == nil {
|
|
| 71 |
- t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
|
|
| 72 |
- } |
|
| 73 |
- if _, _, err := parse(t, "-a invalid -a stdout"); err == nil {
|
|
| 74 |
- t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
|
|
| 75 |
- } |
|
| 76 |
- if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil {
|
|
| 77 |
- t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
|
|
| 78 |
- } |
|
| 79 |
- if _, _, err := parse(t, "-a stdin -d"); err == nil {
|
|
| 80 |
- t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
|
|
| 81 |
- } |
|
| 82 |
- if _, _, err := parse(t, "-a stdout -d"); err == nil {
|
|
| 83 |
- t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
|
|
| 84 |
- } |
|
| 85 |
- if _, _, err := parse(t, "-a stderr -d"); err == nil {
|
|
| 86 |
- t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
|
|
| 87 |
- } |
|
| 88 |
- if _, _, err := parse(t, "-d --rm"); err == nil {
|
|
| 89 |
- t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not")
|
|
| 90 |
- } |
|
| 91 |
-} |
|
| 92 |
- |
|
| 93 |
-func TestParseRunVolumes(t *testing.T) {
|
|
| 94 |
- |
|
| 95 |
- // A single volume |
|
| 96 |
- arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
|
|
| 97 |
- if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
|
|
| 98 |
- t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
|
|
| 99 |
- } else if _, exists := config.Volumes[arr[0]]; !exists {
|
|
| 100 |
- t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes)
|
|
| 101 |
- } |
|
| 102 |
- |
|
| 103 |
- // Two volumes |
|
| 104 |
- arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
|
|
| 105 |
- if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
|
|
| 106 |
- t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
|
|
| 107 |
- } else if _, exists := config.Volumes[arr[0]]; !exists {
|
|
| 108 |
- t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes)
|
|
| 109 |
- } else if _, exists := config.Volumes[arr[1]]; !exists {
|
|
| 110 |
- t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes)
|
|
| 111 |
- } |
|
| 112 |
- |
|
| 113 |
- // A single bind-mount |
|
| 114 |
- arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`})
|
|
| 115 |
- if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
|
|
| 116 |
- t.Fatalf("Error parsing volume flags, %q should mount-bind the path before the colon into the path after the colon. Received %v %v", arr[0], hostConfig.Binds, config.Volumes)
|
|
| 117 |
- } |
|
| 118 |
- |
|
| 119 |
- // Two bind-mounts. |
|
| 120 |
- arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`})
|
|
| 121 |
- if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
|
| 122 |
- t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
|
|
| 123 |
- } |
|
| 124 |
- |
|
| 125 |
- // Two bind-mounts, first read-only, second read-write. |
|
| 126 |
- // TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4 |
|
| 127 |
- arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`})
|
|
| 128 |
- if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
|
| 129 |
- t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
|
|
| 130 |
- } |
|
| 131 |
- |
|
| 132 |
- // Similar to previous test but with alternate modes which are only supported by Linux |
|
| 133 |
- if runtime.GOOS != "windows" {
|
|
| 134 |
- arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{})
|
|
| 135 |
- if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
|
| 136 |
- t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
|
|
| 137 |
- } |
|
| 138 |
- |
|
| 139 |
- arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{})
|
|
| 140 |
- if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
|
| 141 |
- t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
|
|
| 142 |
- } |
|
| 143 |
- } |
|
| 144 |
- |
|
| 145 |
- // One bind mount and one volume |
|
| 146 |
- arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`})
|
|
| 147 |
- if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
|
|
| 148 |
- t.Fatalf("Error parsing volume flags, %s and %s should only one and only one bind mount %s. Received %s", arr[0], arr[1], arr[0], hostConfig.Binds)
|
|
| 149 |
- } else if _, exists := config.Volumes[arr[1]]; !exists {
|
|
| 150 |
- t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes)
|
|
| 151 |
- } |
|
| 152 |
- |
|
| 153 |
- // Root to non-c: drive letter (Windows specific) |
|
| 154 |
- if runtime.GOOS == "windows" {
|
|
| 155 |
- arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`})
|
|
| 156 |
- if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
|
|
| 157 |
- t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
|
|
| 158 |
- } |
|
| 159 |
- } |
|
| 160 |
- |
|
| 161 |
-} |
|
| 162 |
- |
|
| 163 |
-// This tests the cases for binds which are generated through |
|
| 164 |
-// DecodeContainerConfig rather than Parse() |
|
| 165 |
-func TestDecodeContainerConfigVolumes(t *testing.T) {
|
|
| 166 |
- |
|
| 167 |
- // Root to root |
|
| 168 |
- bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
|
|
| 169 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 170 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 171 |
- } |
|
| 172 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 173 |
- t.Fatalf("volume %v should have failed", bindsOrVols)
|
|
| 174 |
- } |
|
| 175 |
- |
|
| 176 |
- // No destination path |
|
| 177 |
- bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
|
|
| 178 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 179 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 180 |
- } |
|
| 181 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 182 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 183 |
- } |
|
| 184 |
- |
|
| 185 |
- // // No destination path or mode |
|
| 186 |
- bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
|
|
| 187 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 188 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 189 |
- } |
|
| 190 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 191 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 192 |
- } |
|
| 193 |
- |
|
| 194 |
- // A whole lot of nothing |
|
| 195 |
- bindsOrVols = []string{`:`}
|
|
| 196 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 197 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 198 |
- } |
|
| 199 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 200 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 201 |
- } |
|
| 202 |
- |
|
| 203 |
- // A whole lot of nothing with no mode |
|
| 204 |
- bindsOrVols = []string{`::`}
|
|
| 205 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 206 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 207 |
- } |
|
| 208 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 209 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 210 |
- } |
|
| 211 |
- |
|
| 212 |
- // Too much including an invalid mode |
|
| 213 |
- wTmp := os.Getenv("TEMP")
|
|
| 214 |
- bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
|
|
| 215 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 216 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 217 |
- } |
|
| 218 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 219 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 220 |
- } |
|
| 221 |
- |
|
| 222 |
- // Windows specific error tests |
|
| 223 |
- if runtime.GOOS == "windows" {
|
|
| 224 |
- // Volume which does not include a drive letter |
|
| 225 |
- bindsOrVols = []string{`\tmp`}
|
|
| 226 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 227 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 228 |
- } |
|
| 229 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 230 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 231 |
- } |
|
| 232 |
- |
|
| 233 |
- // Root to C-Drive |
|
| 234 |
- bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
|
|
| 235 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 236 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 237 |
- } |
|
| 238 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 239 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 240 |
- } |
|
| 241 |
- |
|
| 242 |
- // Container path that does not include a drive letter |
|
| 243 |
- bindsOrVols = []string{`c:\windows:\somewhere`}
|
|
| 244 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 245 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 246 |
- } |
|
| 247 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 248 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 249 |
- } |
|
| 250 |
- } |
|
| 251 |
- |
|
| 252 |
- // Linux-specific error tests |
|
| 253 |
- if runtime.GOOS != "windows" {
|
|
| 254 |
- // Just root |
|
| 255 |
- bindsOrVols = []string{`/`}
|
|
| 256 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
| 257 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 258 |
- } |
|
| 259 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
| 260 |
- t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
| 261 |
- } |
|
| 262 |
- |
|
| 263 |
- // A single volume that looks like a bind mount passed in Volumes. |
|
| 264 |
- // This should be handled as a bind mount, not a volume. |
|
| 265 |
- vols := []string{`/foo:/bar`}
|
|
| 266 |
- if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
|
|
| 267 |
- t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
|
|
| 268 |
- } else if hostConfig.Binds != nil {
|
|
| 269 |
- t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
|
|
| 270 |
- } else if _, exists := config.Volumes[vols[0]]; !exists {
|
|
| 271 |
- t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
|
|
| 272 |
- } |
|
| 273 |
- |
|
| 274 |
- } |
|
| 275 |
-} |
|
| 276 |
- |
|
| 277 |
-// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes |
|
| 278 |
-// to call DecodeContainerConfig. It effectively does what a client would |
|
| 279 |
-// do when calling the daemon by constructing a JSON stream of a |
|
| 280 |
-// ContainerConfigWrapper which is populated by the set of volume specs |
|
| 281 |
-// passed into it. It returns a config and a hostconfig which can be |
|
| 282 |
-// validated to ensure DecodeContainerConfig has manipulated the structures |
|
| 283 |
-// correctly. |
|
| 284 |
-func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) {
|
|
| 285 |
- var ( |
|
| 286 |
- b []byte |
|
| 287 |
- err error |
|
| 288 |
- c *container.Config |
|
| 289 |
- h *container.HostConfig |
|
| 290 |
- ) |
|
| 291 |
- w := ContainerConfigWrapper{
|
|
| 292 |
- Config: &container.Config{
|
|
| 293 |
- Volumes: map[string]struct{}{},
|
|
| 294 |
- }, |
|
| 295 |
- HostConfig: &container.HostConfig{
|
|
| 296 |
- NetworkMode: "none", |
|
| 297 |
- Binds: binds, |
|
| 298 |
- }, |
|
| 299 |
- } |
|
| 300 |
- for _, v := range volumes {
|
|
| 301 |
- w.Config.Volumes[v] = struct{}{}
|
|
| 302 |
- } |
|
| 303 |
- if b, err = json.Marshal(w); err != nil {
|
|
| 304 |
- return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
|
|
| 305 |
- } |
|
| 306 |
- c, h, err = DecodeContainerConfig(bytes.NewReader(b)) |
|
| 307 |
- if err != nil {
|
|
| 308 |
- return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
|
|
| 309 |
- } |
|
| 310 |
- if c == nil || h == nil {
|
|
| 311 |
- return nil, nil, fmt.Errorf("Empty config or hostconfig")
|
|
| 312 |
- } |
|
| 313 |
- |
|
| 314 |
- return c, h, err |
|
| 315 |
-} |
|
| 316 |
- |
|
| 317 |
-// check if (a == c && b == d) || (a == d && b == c) |
|
| 318 |
-// because maps are randomized |
|
| 319 |
-func compareRandomizedStrings(a, b, c, d string) error {
|
|
| 320 |
- if a == c && b == d {
|
|
| 321 |
- return nil |
|
| 322 |
- } |
|
| 323 |
- if a == d && b == c {
|
|
| 324 |
- return nil |
|
| 325 |
- } |
|
| 326 |
- return fmt.Errorf("strings don't match")
|
|
| 327 |
-} |
|
| 328 |
- |
|
| 329 |
-// setupPlatformVolume takes two arrays of volume specs - a Unix style |
|
| 330 |
-// spec and a Windows style spec. Depending on the platform being unit tested, |
|
| 331 |
-// it returns one of them, along with a volume string that would be passed |
|
| 332 |
-// on the docker CLI (eg -v /bar -v /foo). |
|
| 333 |
-func setupPlatformVolume(u []string, w []string) ([]string, string) {
|
|
| 334 |
- var a []string |
|
| 335 |
- if runtime.GOOS == "windows" {
|
|
| 336 |
- a = w |
|
| 337 |
- } else {
|
|
| 338 |
- a = u |
|
| 339 |
- } |
|
| 340 |
- s := "" |
|
| 341 |
- for _, v := range a {
|
|
| 342 |
- s = s + "-v " + v + " " |
|
| 343 |
- } |
|
| 344 |
- return a, s |
|
| 345 |
-} |
|
| 346 |
- |
|
| 347 |
-// Simple parse with MacAddress validation |
|
| 348 |
-func TestParseWithMacAddress(t *testing.T) {
|
|
| 349 |
- invalidMacAddress := "--mac-address=invalidMacAddress" |
|
| 350 |
- validMacAddress := "--mac-address=92:d0:c6:0a:29:33" |
|
| 351 |
- if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
|
|
| 352 |
- t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
|
|
| 353 |
- } |
|
| 354 |
- if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
|
|
| 355 |
- t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress)
|
|
| 356 |
- } |
|
| 357 |
-} |
|
| 358 |
- |
|
| 359 |
-func TestParseWithMemory(t *testing.T) {
|
|
| 360 |
- invalidMemory := "--memory=invalid" |
|
| 361 |
- validMemory := "--memory=1G" |
|
| 362 |
- if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
|
|
| 363 |
- t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
|
|
| 364 |
- } |
|
| 365 |
- if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
|
|
| 366 |
- t.Fatalf("Expected the config to have '1G' as Memory, got '%v'", hostconfig.Memory)
|
|
| 367 |
- } |
|
| 368 |
-} |
|
| 369 |
- |
|
| 370 |
-func TestParseWithMemorySwap(t *testing.T) {
|
|
| 371 |
- invalidMemory := "--memory-swap=invalid" |
|
| 372 |
- validMemory := "--memory-swap=1G" |
|
| 373 |
- anotherValidMemory := "--memory-swap=-1" |
|
| 374 |
- if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
|
|
| 375 |
- t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
|
|
| 376 |
- } |
|
| 377 |
- if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
|
|
| 378 |
- t.Fatalf("Expected the config to have '1073741824' as MemorySwap, got '%v'", hostconfig.MemorySwap)
|
|
| 379 |
- } |
|
| 380 |
- if _, hostconfig := mustParse(t, anotherValidMemory); hostconfig.MemorySwap != -1 {
|
|
| 381 |
- t.Fatalf("Expected the config to have '-1' as MemorySwap, got '%v'", hostconfig.MemorySwap)
|
|
| 382 |
- } |
|
| 383 |
-} |
|
| 384 |
- |
|
| 385 |
-func TestParseHostname(t *testing.T) {
|
|
| 386 |
- hostname := "--hostname=hostname" |
|
| 387 |
- hostnameWithDomain := "--hostname=hostname.domainname" |
|
| 388 |
- hostnameWithDomainTld := "--hostname=hostname.domainname.tld" |
|
| 389 |
- if config, _ := mustParse(t, hostname); config.Hostname != "hostname" && config.Domainname != "" {
|
|
| 390 |
- t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
|
|
| 391 |
- } |
|
| 392 |
- if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname" && config.Domainname != "domainname" {
|
|
| 393 |
- t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
|
|
| 394 |
- } |
|
| 395 |
- if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname" && config.Domainname != "domainname.tld" {
|
|
| 396 |
- t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
|
|
| 397 |
- } |
|
| 398 |
-} |
|
| 399 |
- |
|
| 400 |
-func TestParseWithExpose(t *testing.T) {
|
|
| 401 |
- invalids := map[string]string{
|
|
| 402 |
- ":": "Invalid port format for --expose: :", |
|
| 403 |
- "8080:9090": "Invalid port format for --expose: 8080:9090", |
|
| 404 |
- "/tcp": "Invalid range format for --expose: /tcp, error: Empty string specified for ports.", |
|
| 405 |
- "/udp": "Invalid range format for --expose: /udp, error: Empty string specified for ports.", |
|
| 406 |
- "NaN/tcp": `Invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, |
|
| 407 |
- "NaN-NaN/tcp": `Invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, |
|
| 408 |
- "8080-NaN/tcp": `Invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, |
|
| 409 |
- "1234567890-8080/tcp": `Invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`, |
|
| 410 |
- } |
|
| 411 |
- valids := map[string][]nat.Port{
|
|
| 412 |
- "8080/tcp": {"8080/tcp"},
|
|
| 413 |
- "8080/udp": {"8080/udp"},
|
|
| 414 |
- "8080/ncp": {"8080/ncp"},
|
|
| 415 |
- "8080-8080/udp": {"8080/udp"},
|
|
| 416 |
- "8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
|
|
| 417 |
- } |
|
| 418 |
- for expose, expectedError := range invalids {
|
|
| 419 |
- if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
|
|
| 420 |
- t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
|
|
| 421 |
- } |
|
| 422 |
- } |
|
| 423 |
- for expose, exposedPorts := range valids {
|
|
| 424 |
- config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
|
|
| 425 |
- if err != nil {
|
|
| 426 |
- t.Fatal(err) |
|
| 427 |
- } |
|
| 428 |
- if len(config.ExposedPorts) != len(exposedPorts) {
|
|
| 429 |
- t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts))
|
|
| 430 |
- } |
|
| 431 |
- for _, port := range exposedPorts {
|
|
| 432 |
- if _, ok := config.ExposedPorts[port]; !ok {
|
|
| 433 |
- t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts)
|
|
| 434 |
- } |
|
| 435 |
- } |
|
| 436 |
- } |
|
| 437 |
- // Merge with actual published port |
|
| 438 |
- config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
|
|
| 439 |
- if err != nil {
|
|
| 440 |
- t.Fatal(err) |
|
| 441 |
- } |
|
| 442 |
- if len(config.ExposedPorts) != 2 {
|
|
| 443 |
- t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
|
|
| 444 |
- } |
|
| 445 |
- ports := []nat.Port{"80/tcp", "81/tcp"}
|
|
| 446 |
- for _, port := range ports {
|
|
| 447 |
- if _, ok := config.ExposedPorts[port]; !ok {
|
|
| 448 |
- t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
|
|
| 449 |
- } |
|
| 450 |
- } |
|
| 451 |
-} |
|
| 452 |
- |
|
| 453 |
-func TestParseDevice(t *testing.T) {
|
|
| 454 |
- valids := map[string]container.DeviceMapping{
|
|
| 455 |
- "/dev/snd": {
|
|
| 456 |
- PathOnHost: "/dev/snd", |
|
| 457 |
- PathInContainer: "/dev/snd", |
|
| 458 |
- CgroupPermissions: "rwm", |
|
| 459 |
- }, |
|
| 460 |
- "/dev/snd:rw": {
|
|
| 461 |
- PathOnHost: "/dev/snd", |
|
| 462 |
- PathInContainer: "/dev/snd", |
|
| 463 |
- CgroupPermissions: "rw", |
|
| 464 |
- }, |
|
| 465 |
- "/dev/snd:/something": {
|
|
| 466 |
- PathOnHost: "/dev/snd", |
|
| 467 |
- PathInContainer: "/something", |
|
| 468 |
- CgroupPermissions: "rwm", |
|
| 469 |
- }, |
|
| 470 |
- "/dev/snd:/something:rw": {
|
|
| 471 |
- PathOnHost: "/dev/snd", |
|
| 472 |
- PathInContainer: "/something", |
|
| 473 |
- CgroupPermissions: "rw", |
|
| 474 |
- }, |
|
| 475 |
- } |
|
| 476 |
- for device, deviceMapping := range valids {
|
|
| 477 |
- _, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
|
|
| 478 |
- if err != nil {
|
|
| 479 |
- t.Fatal(err) |
|
| 480 |
- } |
|
| 481 |
- if len(hostconfig.Devices) != 1 {
|
|
| 482 |
- t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices)
|
|
| 483 |
- } |
|
| 484 |
- if hostconfig.Devices[0] != deviceMapping {
|
|
| 485 |
- t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
|
|
| 486 |
- } |
|
| 487 |
- } |
|
| 488 |
- |
|
| 489 |
-} |
|
| 490 |
- |
|
| 491 |
-func TestParseModes(t *testing.T) {
|
|
| 492 |
- // ipc ko |
|
| 493 |
- if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
|
|
| 494 |
- t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
|
|
| 495 |
- } |
|
| 496 |
- // ipc ok |
|
| 497 |
- _, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
|
|
| 498 |
- if err != nil {
|
|
| 499 |
- t.Fatal(err) |
|
| 500 |
- } |
|
| 501 |
- if !hostconfig.IpcMode.Valid() {
|
|
| 502 |
- t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
|
|
| 503 |
- } |
|
| 504 |
- // pid ko |
|
| 505 |
- if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
|
|
| 506 |
- t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
|
|
| 507 |
- } |
|
| 508 |
- // pid ok |
|
| 509 |
- _, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
|
|
| 510 |
- if err != nil {
|
|
| 511 |
- t.Fatal(err) |
|
| 512 |
- } |
|
| 513 |
- if !hostconfig.PidMode.Valid() {
|
|
| 514 |
- t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
|
|
| 515 |
- } |
|
| 516 |
- // uts ko |
|
| 517 |
- if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
|
|
| 518 |
- t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
|
|
| 519 |
- } |
|
| 520 |
- // uts ok |
|
| 521 |
- _, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
|
|
| 522 |
- if err != nil {
|
|
| 523 |
- t.Fatal(err) |
|
| 524 |
- } |
|
| 525 |
- if !hostconfig.UTSMode.Valid() {
|
|
| 526 |
- t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
|
|
| 527 |
- } |
|
| 528 |
- // shm-size ko |
|
| 529 |
- if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
|
|
| 530 |
- t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
|
|
| 531 |
- } |
|
| 532 |
- // shm-size ok |
|
| 533 |
- _, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
|
|
| 534 |
- if err != nil {
|
|
| 535 |
- t.Fatal(err) |
|
| 536 |
- } |
|
| 537 |
- if *hostconfig.ShmSize != 134217728 {
|
|
| 538 |
- t.Fatalf("Expected a valid ShmSize, got %d", *hostconfig.ShmSize)
|
|
| 539 |
- } |
|
| 540 |
-} |
|
| 541 |
- |
|
| 542 |
-func TestParseRestartPolicy(t *testing.T) {
|
|
| 543 |
- invalids := map[string]string{
|
|
| 544 |
- "something": "invalid restart policy something", |
|
| 545 |
- "always:2": "maximum restart count not valid with restart policy of \"always\"", |
|
| 546 |
- "always:2:3": "maximum restart count not valid with restart policy of \"always\"", |
|
| 547 |
- "on-failure:invalid": `strconv.ParseInt: parsing "invalid": invalid syntax`, |
|
| 548 |
- "on-failure:2:5": "restart count format is not valid, usage: 'on-failure:N' or 'on-failure'", |
|
| 549 |
- } |
|
| 550 |
- valids := map[string]container.RestartPolicy{
|
|
| 551 |
- "": {},
|
|
| 552 |
- "always": {
|
|
| 553 |
- Name: "always", |
|
| 554 |
- MaximumRetryCount: 0, |
|
| 555 |
- }, |
|
| 556 |
- "on-failure:1": {
|
|
| 557 |
- Name: "on-failure", |
|
| 558 |
- MaximumRetryCount: 1, |
|
| 559 |
- }, |
|
| 560 |
- } |
|
| 561 |
- for restart, expectedError := range invalids {
|
|
| 562 |
- if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
|
|
| 563 |
- t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
|
|
| 564 |
- } |
|
| 565 |
- } |
|
| 566 |
- for restart, expected := range valids {
|
|
| 567 |
- _, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
|
|
| 568 |
- if err != nil {
|
|
| 569 |
- t.Fatal(err) |
|
| 570 |
- } |
|
| 571 |
- if hostconfig.RestartPolicy != expected {
|
|
| 572 |
- t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy)
|
|
| 573 |
- } |
|
| 574 |
- } |
|
| 575 |
-} |
|
| 576 |
- |
|
| 577 |
-func TestParseLoggingOpts(t *testing.T) {
|
|
| 578 |
- // logging opts ko |
|
| 579 |
- if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
|
|
| 580 |
- t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err)
|
|
| 581 |
- } |
|
| 582 |
- // logging opts ok |
|
| 583 |
- _, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
|
|
| 584 |
- if err != nil {
|
|
| 585 |
- t.Fatal(err) |
|
| 586 |
- } |
|
| 587 |
- if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 {
|
|
| 588 |
- t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy)
|
|
| 589 |
- } |
|
| 590 |
-} |
|
| 591 |
- |
|
| 592 |
-func TestParseEnvfileVariables(t *testing.T) {
|
|
| 593 |
- e := "open nonexistent: no such file or directory" |
|
| 594 |
- if runtime.GOOS == "windows" {
|
|
| 595 |
- e = "open nonexistent: The system cannot find the file specified." |
|
| 596 |
- } |
|
| 597 |
- // env ko |
|
| 598 |
- if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
|
|
| 599 |
- t.Fatalf("Expected an error with message '%s', got %v", e, err)
|
|
| 600 |
- } |
|
| 601 |
- // env ok |
|
| 602 |
- config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
|
|
| 603 |
- if err != nil {
|
|
| 604 |
- t.Fatal(err) |
|
| 605 |
- } |
|
| 606 |
- if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
|
|
| 607 |
- t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env)
|
|
| 608 |
- } |
|
| 609 |
- config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
|
|
| 610 |
- if err != nil {
|
|
| 611 |
- t.Fatal(err) |
|
| 612 |
- } |
|
| 613 |
- if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" {
|
|
| 614 |
- t.Fatalf("Expected a a config with [ENV1=value1 ENV2=value2], got %v", config.Env)
|
|
| 615 |
- } |
|
| 616 |
-} |
|
| 617 |
- |
|
| 618 |
-func TestParseLabelfileVariables(t *testing.T) {
|
|
| 619 |
- e := "open nonexistent: no such file or directory" |
|
| 620 |
- if runtime.GOOS == "windows" {
|
|
| 621 |
- e = "open nonexistent: The system cannot find the file specified." |
|
| 622 |
- } |
|
| 623 |
- // label ko |
|
| 624 |
- if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
|
|
| 625 |
- t.Fatalf("Expected an error with message '%s', got %v", e, err)
|
|
| 626 |
- } |
|
| 627 |
- // label ok |
|
| 628 |
- config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
|
|
| 629 |
- if err != nil {
|
|
| 630 |
- t.Fatal(err) |
|
| 631 |
- } |
|
| 632 |
- if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
|
|
| 633 |
- t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels)
|
|
| 634 |
- } |
|
| 635 |
- config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
|
|
| 636 |
- if err != nil {
|
|
| 637 |
- t.Fatal(err) |
|
| 638 |
- } |
|
| 639 |
- if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" {
|
|
| 640 |
- t.Fatalf("Expected a a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels)
|
|
| 641 |
- } |
|
| 642 |
-} |
|
| 643 |
- |
|
| 644 |
-func TestParseEntryPoint(t *testing.T) {
|
|
| 645 |
- config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
|
|
| 646 |
- if err != nil {
|
|
| 647 |
- t.Fatal(err) |
|
| 648 |
- } |
|
| 649 |
- if config.Entrypoint.Len() != 1 && config.Entrypoint.Slice()[0] != "anything" {
|
|
| 650 |
- t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint)
|
|
| 651 |
- } |
|
| 652 |
-} |
|
| 653 |
- |
|
| 654 |
-func TestValidateLink(t *testing.T) {
|
|
| 655 |
- valid := []string{
|
|
| 656 |
- "name", |
|
| 657 |
- "dcdfbe62ecd0:alias", |
|
| 658 |
- "7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da", |
|
| 659 |
- "angry_torvalds:linus", |
|
| 660 |
- } |
|
| 661 |
- invalid := map[string]string{
|
|
| 662 |
- "": "empty string specified for links", |
|
| 663 |
- "too:much:of:it": "bad format for links: too:much:of:it", |
|
| 664 |
- } |
|
| 665 |
- |
|
| 666 |
- for _, link := range valid {
|
|
| 667 |
- if _, err := ValidateLink(link); err != nil {
|
|
| 668 |
- t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err)
|
|
| 669 |
- } |
|
| 670 |
- } |
|
| 671 |
- |
|
| 672 |
- for link, expectedError := range invalid {
|
|
| 673 |
- if _, err := ValidateLink(link); err == nil {
|
|
| 674 |
- t.Fatalf("ValidateLink(`%q`) should have failed validation", link)
|
|
| 675 |
- } else {
|
|
| 676 |
- if !strings.Contains(err.Error(), expectedError) {
|
|
| 677 |
- t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError)
|
|
| 678 |
- } |
|
| 679 |
- } |
|
| 680 |
- } |
|
| 681 |
-} |
|
| 682 |
- |
|
| 683 |
-func TestParseLink(t *testing.T) {
|
|
| 684 |
- name, alias, err := ParseLink("name:alias")
|
|
| 685 |
- if err != nil {
|
|
| 686 |
- t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
|
|
| 687 |
- } |
|
| 688 |
- if name != "name" {
|
|
| 689 |
- t.Fatalf("Link name should have been name, got %s instead", name)
|
|
| 690 |
- } |
|
| 691 |
- if alias != "alias" {
|
|
| 692 |
- t.Fatalf("Link alias should have been alias, got %s instead", alias)
|
|
| 693 |
- } |
|
| 694 |
- // short format definition |
|
| 695 |
- name, alias, err = ParseLink("name")
|
|
| 696 |
- if err != nil {
|
|
| 697 |
- t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
|
|
| 698 |
- } |
|
| 699 |
- if name != "name" {
|
|
| 700 |
- t.Fatalf("Link name should have been name, got %s instead", name)
|
|
| 701 |
- } |
|
| 702 |
- if alias != "name" {
|
|
| 703 |
- t.Fatalf("Link alias should have been name, got %s instead", alias)
|
|
| 704 |
- } |
|
| 705 |
- // empty string link definition is not allowed |
|
| 706 |
- if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
|
|
| 707 |
- t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
|
|
| 708 |
- } |
|
| 709 |
- // more than two colons are not allowed |
|
| 710 |
- if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
|
|
| 711 |
- t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
|
|
| 712 |
- } |
|
| 713 |
-} |
|
| 714 |
- |
|
| 715 |
-func TestValidateDevice(t *testing.T) {
|
|
| 716 |
- valid := []string{
|
|
| 717 |
- "/home", |
|
| 718 |
- "/home:/home", |
|
| 719 |
- "/home:/something/else", |
|
| 720 |
- "/with space", |
|
| 721 |
- "/home:/with space", |
|
| 722 |
- "relative:/absolute-path", |
|
| 723 |
- "hostPath:/containerPath:r", |
|
| 724 |
- "/hostPath:/containerPath:rw", |
|
| 725 |
- "/hostPath:/containerPath:mrw", |
|
| 726 |
- } |
|
| 727 |
- invalid := map[string]string{
|
|
| 728 |
- "": "bad format for path: ", |
|
| 729 |
- "./": "./ is not an absolute path", |
|
| 730 |
- "../": "../ is not an absolute path", |
|
| 731 |
- "/:../": "../ is not an absolute path", |
|
| 732 |
- "/:path": "path is not an absolute path", |
|
| 733 |
- ":": "bad format for path: :", |
|
| 734 |
- "/tmp:": " is not an absolute path", |
|
| 735 |
- ":test": "bad format for path: :test", |
|
| 736 |
- ":/test": "bad format for path: :/test", |
|
| 737 |
- "tmp:": " is not an absolute path", |
|
| 738 |
- ":test:": "bad format for path: :test:", |
|
| 739 |
- "::": "bad format for path: ::", |
|
| 740 |
- ":::": "bad format for path: :::", |
|
| 741 |
- "/tmp:::": "bad format for path: /tmp:::", |
|
| 742 |
- ":/tmp::": "bad format for path: :/tmp::", |
|
| 743 |
- "path:ro": "ro is not an absolute path", |
|
| 744 |
- "path:rr": "rr is not an absolute path", |
|
| 745 |
- "a:/b:ro": "bad mode specified: ro", |
|
| 746 |
- "a:/b:rr": "bad mode specified: rr", |
|
| 747 |
- } |
|
| 748 |
- |
|
| 749 |
- for _, path := range valid {
|
|
| 750 |
- if _, err := ValidateDevice(path); err != nil {
|
|
| 751 |
- t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
|
|
| 752 |
- } |
|
| 753 |
- } |
|
| 754 |
- |
|
| 755 |
- for path, expectedError := range invalid {
|
|
| 756 |
- if _, err := ValidateDevice(path); err == nil {
|
|
| 757 |
- t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
|
|
| 758 |
- } else {
|
|
| 759 |
- if err.Error() != expectedError {
|
|
| 760 |
- t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
|
|
| 761 |
- } |
|
| 762 |
- } |
|
| 763 |
- } |
|
| 764 |
-} |