The parse.go file is used almost exclusively in the client. The few small
functions that are used outside of the client could easily be copied out
when the client is extracted, allowing this runconfig/opts package to
move to the client.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
| ... | ... |
@@ -12,7 +12,7 @@ import ( |
| 12 | 12 |
"github.com/docker/docker/pkg/jsonmessage" |
| 13 | 13 |
"github.com/docker/docker/reference" |
| 14 | 14 |
"github.com/docker/docker/registry" |
| 15 |
- "github.com/docker/docker/runconfig" |
|
| 15 |
+ runconfigopts "github.com/docker/docker/runconfig/opts" |
|
| 16 | 16 |
) |
| 17 | 17 |
|
| 18 | 18 |
func (cli *DockerCli) pullImage(image string) error {
|
| ... | ... |
@@ -156,7 +156,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
|
| 156 | 156 |
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
|
| 157 | 157 |
) |
| 158 | 158 |
|
| 159 |
- config, hostConfig, cmd, err := runconfig.Parse(cmd, args) |
|
| 159 |
+ config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args) |
|
| 160 | 160 |
if err != nil {
|
| 161 | 161 |
cmd.ReportError(err.Error(), true) |
| 162 | 162 |
os.Exit(1) |
| ... | ... |
@@ -14,7 +14,7 @@ import ( |
| 14 | 14 |
"github.com/docker/docker/opts" |
| 15 | 15 |
"github.com/docker/docker/pkg/promise" |
| 16 | 16 |
"github.com/docker/docker/pkg/signal" |
| 17 |
- "github.com/docker/docker/runconfig" |
|
| 17 |
+ runconfigopts "github.com/docker/docker/runconfig/opts" |
|
| 18 | 18 |
"github.com/docker/libnetwork/resolvconf/dns" |
| 19 | 19 |
) |
| 20 | 20 |
|
| ... | ... |
@@ -82,7 +82,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
| 82 | 82 |
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
|
| 83 | 83 |
) |
| 84 | 84 |
|
| 85 |
- config, hostConfig, cmd, err := runconfig.Parse(cmd, args) |
|
| 85 |
+ config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args) |
|
| 86 | 86 |
// just in case the Parse does not exit |
| 87 | 87 |
if err != nil {
|
| 88 | 88 |
cmd.ReportError(err.Error(), true) |
| ... | ... |
@@ -25,6 +25,7 @@ import ( |
| 25 | 25 |
"github.com/docker/docker/pkg/signal" |
| 26 | 26 |
"github.com/docker/docker/pkg/system" |
| 27 | 27 |
"github.com/docker/docker/runconfig" |
| 28 |
+ runconfigopts "github.com/docker/docker/runconfig/opts" |
|
| 28 | 29 |
"github.com/docker/go-connections/nat" |
| 29 | 30 |
) |
| 30 | 31 |
|
| ... | ... |
@@ -337,7 +338,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string) |
| 337 | 337 |
// of RUN, without leaking it to the final image. It also aids cache |
| 338 | 338 |
// lookup for same image built with same build time environment. |
| 339 | 339 |
cmdBuildEnv := []string{}
|
| 340 |
- configEnv := runconfig.ConvertKVStringsToMap(b.runConfig.Env) |
|
| 340 |
+ configEnv := runconfigopts.ConvertKVStringsToMap(b.runConfig.Env) |
|
| 341 | 341 |
for key, val := range b.BuildArgs {
|
| 342 | 342 |
if !b.isBuildArgAllowed(key) {
|
| 343 | 343 |
// skip build-args that are not in allowed list, meaning they have |
| ... | ... |
@@ -23,6 +23,7 @@ import ( |
| 23 | 23 |
"github.com/docker/docker/pkg/sysinfo" |
| 24 | 24 |
"github.com/docker/docker/reference" |
| 25 | 25 |
"github.com/docker/docker/runconfig" |
| 26 |
+ runconfigopts "github.com/docker/docker/runconfig/opts" |
|
| 26 | 27 |
"github.com/docker/libnetwork" |
| 27 | 28 |
nwconfig "github.com/docker/libnetwork/config" |
| 28 | 29 |
"github.com/docker/libnetwork/drivers/bridge" |
| ... | ... |
@@ -681,7 +682,7 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig * |
| 681 | 681 |
} |
| 682 | 682 |
|
| 683 | 683 |
for _, l := range hostConfig.Links {
|
| 684 |
- name, alias, err := runconfig.ParseLink(l) |
|
| 684 |
+ name, alias, err := runconfigopts.ParseLink(l) |
|
| 685 | 685 |
if err != nil {
|
| 686 | 686 |
return err |
| 687 | 687 |
} |
| 688 | 688 |
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 |
-} |
| 765 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,32 @@ |
| 0 |
+package runconfig |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+var ( |
|
| 7 |
+ // ErrConflictContainerNetworkAndLinks conflict between --net=container and links |
|
| 8 |
+ ErrConflictContainerNetworkAndLinks = fmt.Errorf("Conflicting options: container type network can't be used with links. This would result in undefined behavior")
|
|
| 9 |
+ // ErrConflictUserDefinedNetworkAndLinks conflict between --net=<NETWORK> and links |
|
| 10 |
+ ErrConflictUserDefinedNetworkAndLinks = fmt.Errorf("Conflicting options: networking can't be used with links. This would result in undefined behavior")
|
|
| 11 |
+ // ErrConflictSharedNetwork conflict between private and other networks |
|
| 12 |
+ ErrConflictSharedNetwork = fmt.Errorf("Container sharing network namespace with another container or host cannot be connected to any other network")
|
|
| 13 |
+ // ErrConflictHostNetwork conflict from being disconnected from host network or connected to host network. |
|
| 14 |
+ ErrConflictHostNetwork = fmt.Errorf("Container cannot be disconnected from host network or connected to host network")
|
|
| 15 |
+ // ErrConflictNoNetwork conflict between private and other networks |
|
| 16 |
+ ErrConflictNoNetwork = fmt.Errorf("Container cannot be connected to multiple networks with one of the networks in private (none) mode")
|
|
| 17 |
+ // ErrConflictNetworkAndDNS conflict between --dns and the network mode |
|
| 18 |
+ ErrConflictNetworkAndDNS = fmt.Errorf("Conflicting options: dns and the network mode")
|
|
| 19 |
+ // ErrConflictNetworkHostname conflict between the hostname and the network mode |
|
| 20 |
+ ErrConflictNetworkHostname = fmt.Errorf("Conflicting options: hostname and the network mode")
|
|
| 21 |
+ // ErrConflictHostNetworkAndLinks conflict between --net=host and links |
|
| 22 |
+ ErrConflictHostNetworkAndLinks = fmt.Errorf("Conflicting options: host type networking can't be used with links. This would result in undefined behavior")
|
|
| 23 |
+ // ErrConflictContainerNetworkAndMac conflict between the mac address and the network mode |
|
| 24 |
+ ErrConflictContainerNetworkAndMac = fmt.Errorf("Conflicting options: mac-address and the network mode")
|
|
| 25 |
+ // ErrConflictNetworkHosts conflict between add-host and the network mode |
|
| 26 |
+ ErrConflictNetworkHosts = fmt.Errorf("Conflicting options: custom host-to-IP mapping and the network mode")
|
|
| 27 |
+ // ErrConflictNetworkPublishPorts conflict between the publish options and the network mode |
|
| 28 |
+ ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type network mode")
|
|
| 29 |
+ // ErrConflictNetworkExposePorts conflict between the expose option and the network mode |
|
| 30 |
+ ErrConflictNetworkExposePorts = fmt.Errorf("Conflicting options: port exposing and the container type network mode")
|
|
| 31 |
+) |
| 0 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,623 @@ |
| 0 |
+package opts |
|
| 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 |
+ "github.com/docker/docker/volume" |
|
| 15 |
+ "github.com/docker/go-connections/nat" |
|
| 16 |
+ "github.com/docker/go-units" |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+// Parse parses the specified args for the specified command and generates a Config, |
|
| 20 |
+// a HostConfig and returns them with the specified command. |
|
| 21 |
+// If the specified args are not valid, it will return an error. |
|
| 22 |
+func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
|
|
| 23 |
+ var ( |
|
| 24 |
+ // FIXME: use utils.ListOpts for attach and volumes? |
|
| 25 |
+ flAttach = opts.NewListOpts(opts.ValidateAttach) |
|
| 26 |
+ flVolumes = opts.NewListOpts(nil) |
|
| 27 |
+ flTmpfs = opts.NewListOpts(nil) |
|
| 28 |
+ flBlkioWeightDevice = NewWeightdeviceOpt(ValidateWeightDevice) |
|
| 29 |
+ flDeviceReadBps = NewThrottledeviceOpt(ValidateThrottleBpsDevice) |
|
| 30 |
+ flDeviceWriteBps = NewThrottledeviceOpt(ValidateThrottleBpsDevice) |
|
| 31 |
+ flLinks = opts.NewListOpts(ValidateLink) |
|
| 32 |
+ flDeviceReadIOps = NewThrottledeviceOpt(ValidateThrottleIOpsDevice) |
|
| 33 |
+ flDeviceWriteIOps = NewThrottledeviceOpt(ValidateThrottleIOpsDevice) |
|
| 34 |
+ flEnv = opts.NewListOpts(opts.ValidateEnv) |
|
| 35 |
+ flLabels = opts.NewListOpts(opts.ValidateEnv) |
|
| 36 |
+ flDevices = opts.NewListOpts(ValidateDevice) |
|
| 37 |
+ |
|
| 38 |
+ flUlimits = NewUlimitOpt(nil) |
|
| 39 |
+ |
|
| 40 |
+ flPublish = opts.NewListOpts(nil) |
|
| 41 |
+ flExpose = opts.NewListOpts(nil) |
|
| 42 |
+ flDNS = opts.NewListOpts(opts.ValidateIPAddress) |
|
| 43 |
+ flDNSSearch = opts.NewListOpts(opts.ValidateDNSSearch) |
|
| 44 |
+ flDNSOptions = opts.NewListOpts(nil) |
|
| 45 |
+ flExtraHosts = opts.NewListOpts(opts.ValidateExtraHost) |
|
| 46 |
+ flVolumesFrom = opts.NewListOpts(nil) |
|
| 47 |
+ flEnvFile = opts.NewListOpts(nil) |
|
| 48 |
+ flCapAdd = opts.NewListOpts(nil) |
|
| 49 |
+ flCapDrop = opts.NewListOpts(nil) |
|
| 50 |
+ flGroupAdd = opts.NewListOpts(nil) |
|
| 51 |
+ flSecurityOpt = opts.NewListOpts(nil) |
|
| 52 |
+ flLabelsFile = opts.NewListOpts(nil) |
|
| 53 |
+ flLoggingOpts = opts.NewListOpts(nil) |
|
| 54 |
+ flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to this container")
|
|
| 55 |
+ flPidMode = cmd.String([]string{"-pid"}, "", "PID namespace to use")
|
|
| 56 |
+ flUTSMode = cmd.String([]string{"-uts"}, "", "UTS namespace to use")
|
|
| 57 |
+ flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
|
|
| 58 |
+ flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
|
|
| 59 |
+ flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
|
|
| 60 |
+ flOomKillDisable = cmd.Bool([]string{"-oom-kill-disable"}, false, "Disable OOM Killer")
|
|
| 61 |
+ flOomScoreAdj = cmd.Int([]string{"-oom-score-adj"}, 0, "Tune host's OOM preferences (-1000 to 1000)")
|
|
| 62 |
+ flContainerIDFile = cmd.String([]string{"-cidfile"}, "", "Write the container ID to the file")
|
|
| 63 |
+ flEntrypoint = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
|
|
| 64 |
+ flHostname = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
|
|
| 65 |
+ flMemoryString = cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
|
| 66 |
+ flMemoryReservation = cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit")
|
|
| 67 |
+ flMemorySwap = cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
|
|
| 68 |
+ flKernelMemory = cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit")
|
|
| 69 |
+ flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
|
|
| 70 |
+ flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
|
|
| 71 |
+ flCPUShares = cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
|
|
| 72 |
+ flCPUPeriod = cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period")
|
|
| 73 |
+ flCPUQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
|
|
| 74 |
+ flCpusetCpus = cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
|
| 75 |
+ flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
|
|
| 76 |
+ flBlkioWeight = cmd.Uint16([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
|
|
| 77 |
+ flSwappiness = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
|
|
| 78 |
+ flNetMode = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
|
|
| 79 |
+ flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
|
|
| 80 |
+ flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
|
|
| 81 |
+ flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
|
|
| 82 |
+ flReadonlyRootfs = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
|
|
| 83 |
+ flLoggingDriver = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
|
|
| 84 |
+ flCgroupParent = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
|
| 85 |
+ flVolumeDriver = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
|
|
| 86 |
+ flStopSignal = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
|
|
| 87 |
+ flIsolation = cmd.String([]string{"-isolation"}, "", "Container isolation level")
|
|
| 88 |
+ flShmSize = cmd.String([]string{"-shm-size"}, "", "Size of /dev/shm, default value is 64MB")
|
|
| 89 |
+ ) |
|
| 90 |
+ |
|
| 91 |
+ cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
|
|
| 92 |
+ cmd.Var(&flBlkioWeightDevice, []string{"-blkio-weight-device"}, "Block IO weight (relative device weight)")
|
|
| 93 |
+ cmd.Var(&flDeviceReadBps, []string{"-device-read-bps"}, "Limit read rate (bytes per second) from a device")
|
|
| 94 |
+ cmd.Var(&flDeviceWriteBps, []string{"-device-write-bps"}, "Limit write rate (bytes per second) to a device")
|
|
| 95 |
+ cmd.Var(&flDeviceReadIOps, []string{"-device-read-iops"}, "Limit read rate (IO per second) from a device")
|
|
| 96 |
+ cmd.Var(&flDeviceWriteIOps, []string{"-device-write-iops"}, "Limit write rate (IO per second) to a device")
|
|
| 97 |
+ cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
|
|
| 98 |
+ cmd.Var(&flTmpfs, []string{"-tmpfs"}, "Mount a tmpfs directory")
|
|
| 99 |
+ cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
|
|
| 100 |
+ cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
|
|
| 101 |
+ cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
|
|
| 102 |
+ cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
|
|
| 103 |
+ cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
|
|
| 104 |
+ cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
|
|
| 105 |
+ cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
|
|
| 106 |
+ cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports")
|
|
| 107 |
+ cmd.Var(&flDNS, []string{"-dns"}, "Set custom DNS servers")
|
|
| 108 |
+ cmd.Var(&flDNSSearch, []string{"-dns-search"}, "Set custom DNS search domains")
|
|
| 109 |
+ cmd.Var(&flDNSOptions, []string{"-dns-opt"}, "Set DNS options")
|
|
| 110 |
+ cmd.Var(&flExtraHosts, []string{"-add-host"}, "Add a custom host-to-IP mapping (host:ip)")
|
|
| 111 |
+ cmd.Var(&flVolumesFrom, []string{"-volumes-from"}, "Mount volumes from the specified container(s)")
|
|
| 112 |
+ cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities")
|
|
| 113 |
+ cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities")
|
|
| 114 |
+ cmd.Var(&flGroupAdd, []string{"-group-add"}, "Add additional groups to join")
|
|
| 115 |
+ cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options")
|
|
| 116 |
+ cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
|
|
| 117 |
+ cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options")
|
|
| 118 |
+ |
|
| 119 |
+ cmd.Require(flag.Min, 1) |
|
| 120 |
+ |
|
| 121 |
+ if err := cmd.ParseFlags(args, true); err != nil {
|
|
| 122 |
+ return nil, nil, cmd, err |
|
| 123 |
+ } |
|
| 124 |
+ |
|
| 125 |
+ var ( |
|
| 126 |
+ attachStdin = flAttach.Get("stdin")
|
|
| 127 |
+ attachStdout = flAttach.Get("stdout")
|
|
| 128 |
+ attachStderr = flAttach.Get("stderr")
|
|
| 129 |
+ ) |
|
| 130 |
+ |
|
| 131 |
+ // Validate the input mac address |
|
| 132 |
+ if *flMacAddress != "" {
|
|
| 133 |
+ if _, err := opts.ValidateMACAddress(*flMacAddress); err != nil {
|
|
| 134 |
+ return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
|
|
| 135 |
+ } |
|
| 136 |
+ } |
|
| 137 |
+ if *flStdin {
|
|
| 138 |
+ attachStdin = true |
|
| 139 |
+ } |
|
| 140 |
+ // If -a is not set attach to the output stdio |
|
| 141 |
+ if flAttach.Len() == 0 {
|
|
| 142 |
+ attachStdout = true |
|
| 143 |
+ attachStderr = true |
|
| 144 |
+ } |
|
| 145 |
+ |
|
| 146 |
+ var err error |
|
| 147 |
+ |
|
| 148 |
+ var flMemory int64 |
|
| 149 |
+ if *flMemoryString != "" {
|
|
| 150 |
+ flMemory, err = units.RAMInBytes(*flMemoryString) |
|
| 151 |
+ if err != nil {
|
|
| 152 |
+ return nil, nil, cmd, err |
|
| 153 |
+ } |
|
| 154 |
+ } |
|
| 155 |
+ |
|
| 156 |
+ var MemoryReservation int64 |
|
| 157 |
+ if *flMemoryReservation != "" {
|
|
| 158 |
+ MemoryReservation, err = units.RAMInBytes(*flMemoryReservation) |
|
| 159 |
+ if err != nil {
|
|
| 160 |
+ return nil, nil, cmd, err |
|
| 161 |
+ } |
|
| 162 |
+ } |
|
| 163 |
+ |
|
| 164 |
+ var memorySwap int64 |
|
| 165 |
+ if *flMemorySwap != "" {
|
|
| 166 |
+ if *flMemorySwap == "-1" {
|
|
| 167 |
+ memorySwap = -1 |
|
| 168 |
+ } else {
|
|
| 169 |
+ memorySwap, err = units.RAMInBytes(*flMemorySwap) |
|
| 170 |
+ if err != nil {
|
|
| 171 |
+ return nil, nil, cmd, err |
|
| 172 |
+ } |
|
| 173 |
+ } |
|
| 174 |
+ } |
|
| 175 |
+ |
|
| 176 |
+ var KernelMemory int64 |
|
| 177 |
+ if *flKernelMemory != "" {
|
|
| 178 |
+ KernelMemory, err = units.RAMInBytes(*flKernelMemory) |
|
| 179 |
+ if err != nil {
|
|
| 180 |
+ return nil, nil, cmd, err |
|
| 181 |
+ } |
|
| 182 |
+ } |
|
| 183 |
+ |
|
| 184 |
+ swappiness := *flSwappiness |
|
| 185 |
+ if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
|
|
| 186 |
+ return nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
|
|
| 187 |
+ } |
|
| 188 |
+ |
|
| 189 |
+ var parsedShm *int64 |
|
| 190 |
+ if *flShmSize != "" {
|
|
| 191 |
+ shmSize, err := units.RAMInBytes(*flShmSize) |
|
| 192 |
+ if err != nil {
|
|
| 193 |
+ return nil, nil, cmd, err |
|
| 194 |
+ } |
|
| 195 |
+ parsedShm = &shmSize |
|
| 196 |
+ } |
|
| 197 |
+ |
|
| 198 |
+ var binds []string |
|
| 199 |
+ // add any bind targets to the list of container volumes |
|
| 200 |
+ for bind := range flVolumes.GetMap() {
|
|
| 201 |
+ if arr := volume.SplitN(bind, 2); len(arr) > 1 {
|
|
| 202 |
+ // after creating the bind mount we want to delete it from the flVolumes values because |
|
| 203 |
+ // we do not want bind mounts being committed to image configs |
|
| 204 |
+ binds = append(binds, bind) |
|
| 205 |
+ flVolumes.Delete(bind) |
|
| 206 |
+ } |
|
| 207 |
+ } |
|
| 208 |
+ |
|
| 209 |
+ // Can't evaluate options passed into --tmpfs until we actually mount |
|
| 210 |
+ tmpfs := make(map[string]string) |
|
| 211 |
+ for _, t := range flTmpfs.GetAll() {
|
|
| 212 |
+ if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
|
|
| 213 |
+ if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
|
|
| 214 |
+ return nil, nil, cmd, err |
|
| 215 |
+ } |
|
| 216 |
+ tmpfs[arr[0]] = arr[1] |
|
| 217 |
+ } else {
|
|
| 218 |
+ tmpfs[arr[0]] = "" |
|
| 219 |
+ } |
|
| 220 |
+ } |
|
| 221 |
+ |
|
| 222 |
+ var ( |
|
| 223 |
+ parsedArgs = cmd.Args() |
|
| 224 |
+ runCmd *strslice.StrSlice |
|
| 225 |
+ entrypoint *strslice.StrSlice |
|
| 226 |
+ image = cmd.Arg(0) |
|
| 227 |
+ ) |
|
| 228 |
+ if len(parsedArgs) > 1 {
|
|
| 229 |
+ runCmd = strslice.New(parsedArgs[1:]...) |
|
| 230 |
+ } |
|
| 231 |
+ if *flEntrypoint != "" {
|
|
| 232 |
+ entrypoint = strslice.New(*flEntrypoint) |
|
| 233 |
+ } |
|
| 234 |
+ |
|
| 235 |
+ var ( |
|
| 236 |
+ domainname string |
|
| 237 |
+ hostname = *flHostname |
|
| 238 |
+ parts = strings.SplitN(hostname, ".", 2) |
|
| 239 |
+ ) |
|
| 240 |
+ if len(parts) > 1 {
|
|
| 241 |
+ hostname = parts[0] |
|
| 242 |
+ domainname = parts[1] |
|
| 243 |
+ } |
|
| 244 |
+ |
|
| 245 |
+ ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll()) |
|
| 246 |
+ if err != nil {
|
|
| 247 |
+ return nil, nil, cmd, err |
|
| 248 |
+ } |
|
| 249 |
+ |
|
| 250 |
+ // Merge in exposed ports to the map of published ports |
|
| 251 |
+ for _, e := range flExpose.GetAll() {
|
|
| 252 |
+ if strings.Contains(e, ":") {
|
|
| 253 |
+ return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
|
|
| 254 |
+ } |
|
| 255 |
+ //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] |
|
| 256 |
+ proto, port := nat.SplitProtoPort(e) |
|
| 257 |
+ //parse the start and end port and create a sequence of ports to expose |
|
| 258 |
+ //if expose a port, the start and end port are the same |
|
| 259 |
+ start, end, err := nat.ParsePortRange(port) |
|
| 260 |
+ if err != nil {
|
|
| 261 |
+ return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
|
|
| 262 |
+ } |
|
| 263 |
+ for i := start; i <= end; i++ {
|
|
| 264 |
+ p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) |
|
| 265 |
+ if err != nil {
|
|
| 266 |
+ return nil, nil, cmd, err |
|
| 267 |
+ } |
|
| 268 |
+ if _, exists := ports[p]; !exists {
|
|
| 269 |
+ ports[p] = struct{}{}
|
|
| 270 |
+ } |
|
| 271 |
+ } |
|
| 272 |
+ } |
|
| 273 |
+ |
|
| 274 |
+ // parse device mappings |
|
| 275 |
+ deviceMappings := []container.DeviceMapping{}
|
|
| 276 |
+ for _, device := range flDevices.GetAll() {
|
|
| 277 |
+ deviceMapping, err := ParseDevice(device) |
|
| 278 |
+ if err != nil {
|
|
| 279 |
+ return nil, nil, cmd, err |
|
| 280 |
+ } |
|
| 281 |
+ deviceMappings = append(deviceMappings, deviceMapping) |
|
| 282 |
+ } |
|
| 283 |
+ |
|
| 284 |
+ // collect all the environment variables for the container |
|
| 285 |
+ envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll()) |
|
| 286 |
+ if err != nil {
|
|
| 287 |
+ return nil, nil, cmd, err |
|
| 288 |
+ } |
|
| 289 |
+ |
|
| 290 |
+ // collect all the labels for the container |
|
| 291 |
+ labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll()) |
|
| 292 |
+ if err != nil {
|
|
| 293 |
+ return nil, nil, cmd, err |
|
| 294 |
+ } |
|
| 295 |
+ |
|
| 296 |
+ ipcMode := container.IpcMode(*flIpcMode) |
|
| 297 |
+ if !ipcMode.Valid() {
|
|
| 298 |
+ return nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
|
|
| 299 |
+ } |
|
| 300 |
+ |
|
| 301 |
+ pidMode := container.PidMode(*flPidMode) |
|
| 302 |
+ if !pidMode.Valid() {
|
|
| 303 |
+ return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
|
|
| 304 |
+ } |
|
| 305 |
+ |
|
| 306 |
+ utsMode := container.UTSMode(*flUTSMode) |
|
| 307 |
+ if !utsMode.Valid() {
|
|
| 308 |
+ return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
|
|
| 309 |
+ } |
|
| 310 |
+ |
|
| 311 |
+ restartPolicy, err := ParseRestartPolicy(*flRestartPolicy) |
|
| 312 |
+ if err != nil {
|
|
| 313 |
+ return nil, nil, cmd, err |
|
| 314 |
+ } |
|
| 315 |
+ |
|
| 316 |
+ loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll()) |
|
| 317 |
+ if err != nil {
|
|
| 318 |
+ return nil, nil, cmd, err |
|
| 319 |
+ } |
|
| 320 |
+ |
|
| 321 |
+ resources := container.Resources{
|
|
| 322 |
+ CgroupParent: *flCgroupParent, |
|
| 323 |
+ Memory: flMemory, |
|
| 324 |
+ MemoryReservation: MemoryReservation, |
|
| 325 |
+ MemorySwap: memorySwap, |
|
| 326 |
+ MemorySwappiness: flSwappiness, |
|
| 327 |
+ KernelMemory: KernelMemory, |
|
| 328 |
+ OomKillDisable: *flOomKillDisable, |
|
| 329 |
+ CPUShares: *flCPUShares, |
|
| 330 |
+ CPUPeriod: *flCPUPeriod, |
|
| 331 |
+ CpusetCpus: *flCpusetCpus, |
|
| 332 |
+ CpusetMems: *flCpusetMems, |
|
| 333 |
+ CPUQuota: *flCPUQuota, |
|
| 334 |
+ BlkioWeight: *flBlkioWeight, |
|
| 335 |
+ BlkioWeightDevice: flBlkioWeightDevice.GetList(), |
|
| 336 |
+ BlkioDeviceReadBps: flDeviceReadBps.GetList(), |
|
| 337 |
+ BlkioDeviceWriteBps: flDeviceWriteBps.GetList(), |
|
| 338 |
+ BlkioDeviceReadIOps: flDeviceReadIOps.GetList(), |
|
| 339 |
+ BlkioDeviceWriteIOps: flDeviceWriteIOps.GetList(), |
|
| 340 |
+ Ulimits: flUlimits.GetList(), |
|
| 341 |
+ Devices: deviceMappings, |
|
| 342 |
+ } |
|
| 343 |
+ |
|
| 344 |
+ config := &container.Config{
|
|
| 345 |
+ Hostname: hostname, |
|
| 346 |
+ Domainname: domainname, |
|
| 347 |
+ ExposedPorts: ports, |
|
| 348 |
+ User: *flUser, |
|
| 349 |
+ Tty: *flTty, |
|
| 350 |
+ // TODO: deprecated, it comes from -n, --networking |
|
| 351 |
+ // it's still needed internally to set the network to disabled |
|
| 352 |
+ // if e.g. bridge is none in daemon opts, and in inspect |
|
| 353 |
+ NetworkDisabled: false, |
|
| 354 |
+ OpenStdin: *flStdin, |
|
| 355 |
+ AttachStdin: attachStdin, |
|
| 356 |
+ AttachStdout: attachStdout, |
|
| 357 |
+ AttachStderr: attachStderr, |
|
| 358 |
+ Env: envVariables, |
|
| 359 |
+ Cmd: runCmd, |
|
| 360 |
+ Image: image, |
|
| 361 |
+ Volumes: flVolumes.GetMap(), |
|
| 362 |
+ MacAddress: *flMacAddress, |
|
| 363 |
+ Entrypoint: entrypoint, |
|
| 364 |
+ WorkingDir: *flWorkingDir, |
|
| 365 |
+ Labels: ConvertKVStringsToMap(labels), |
|
| 366 |
+ StopSignal: *flStopSignal, |
|
| 367 |
+ } |
|
| 368 |
+ |
|
| 369 |
+ hostConfig := &container.HostConfig{
|
|
| 370 |
+ Binds: binds, |
|
| 371 |
+ ContainerIDFile: *flContainerIDFile, |
|
| 372 |
+ OomScoreAdj: *flOomScoreAdj, |
|
| 373 |
+ Privileged: *flPrivileged, |
|
| 374 |
+ PortBindings: portBindings, |
|
| 375 |
+ Links: flLinks.GetAll(), |
|
| 376 |
+ PublishAllPorts: *flPublishAll, |
|
| 377 |
+ // Make sure the dns fields are never nil. |
|
| 378 |
+ // New containers don't ever have those fields nil, |
|
| 379 |
+ // but pre created containers can still have those nil values. |
|
| 380 |
+ // See https://github.com/docker/docker/pull/17779 |
|
| 381 |
+ // for a more detailed explanation on why we don't want that. |
|
| 382 |
+ DNS: flDNS.GetAllOrEmpty(), |
|
| 383 |
+ DNSSearch: flDNSSearch.GetAllOrEmpty(), |
|
| 384 |
+ DNSOptions: flDNSOptions.GetAllOrEmpty(), |
|
| 385 |
+ ExtraHosts: flExtraHosts.GetAll(), |
|
| 386 |
+ VolumesFrom: flVolumesFrom.GetAll(), |
|
| 387 |
+ NetworkMode: container.NetworkMode(*flNetMode), |
|
| 388 |
+ IpcMode: ipcMode, |
|
| 389 |
+ PidMode: pidMode, |
|
| 390 |
+ UTSMode: utsMode, |
|
| 391 |
+ CapAdd: strslice.New(flCapAdd.GetAll()...), |
|
| 392 |
+ CapDrop: strslice.New(flCapDrop.GetAll()...), |
|
| 393 |
+ GroupAdd: flGroupAdd.GetAll(), |
|
| 394 |
+ RestartPolicy: restartPolicy, |
|
| 395 |
+ SecurityOpt: flSecurityOpt.GetAll(), |
|
| 396 |
+ ReadonlyRootfs: *flReadonlyRootfs, |
|
| 397 |
+ LogConfig: container.LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
|
|
| 398 |
+ VolumeDriver: *flVolumeDriver, |
|
| 399 |
+ Isolation: container.IsolationLevel(*flIsolation), |
|
| 400 |
+ ShmSize: parsedShm, |
|
| 401 |
+ Resources: resources, |
|
| 402 |
+ Tmpfs: tmpfs, |
|
| 403 |
+ } |
|
| 404 |
+ |
|
| 405 |
+ // When allocating stdin in attached mode, close stdin at client disconnect |
|
| 406 |
+ if config.OpenStdin && config.AttachStdin {
|
|
| 407 |
+ config.StdinOnce = true |
|
| 408 |
+ } |
|
| 409 |
+ return config, hostConfig, cmd, nil |
|
| 410 |
+} |
|
| 411 |
+ |
|
| 412 |
+// reads a file of line terminated key=value pairs and override that with override parameter |
|
| 413 |
+func readKVStrings(files []string, override []string) ([]string, error) {
|
|
| 414 |
+ envVariables := []string{}
|
|
| 415 |
+ for _, ef := range files {
|
|
| 416 |
+ parsedVars, err := opts.ParseEnvFile(ef) |
|
| 417 |
+ if err != nil {
|
|
| 418 |
+ return nil, err |
|
| 419 |
+ } |
|
| 420 |
+ envVariables = append(envVariables, parsedVars...) |
|
| 421 |
+ } |
|
| 422 |
+ // parse the '-e' and '--env' after, to allow override |
|
| 423 |
+ envVariables = append(envVariables, override...) |
|
| 424 |
+ |
|
| 425 |
+ return envVariables, nil |
|
| 426 |
+} |
|
| 427 |
+ |
|
| 428 |
+// ConvertKVStringsToMap converts ["key=value"] to {"key":"value"}
|
|
| 429 |
+func ConvertKVStringsToMap(values []string) map[string]string {
|
|
| 430 |
+ result := make(map[string]string, len(values)) |
|
| 431 |
+ for _, value := range values {
|
|
| 432 |
+ kv := strings.SplitN(value, "=", 2) |
|
| 433 |
+ if len(kv) == 1 {
|
|
| 434 |
+ result[kv[0]] = "" |
|
| 435 |
+ } else {
|
|
| 436 |
+ result[kv[0]] = kv[1] |
|
| 437 |
+ } |
|
| 438 |
+ } |
|
| 439 |
+ |
|
| 440 |
+ return result |
|
| 441 |
+} |
|
| 442 |
+ |
|
| 443 |
+func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
|
|
| 444 |
+ loggingOptsMap := ConvertKVStringsToMap(loggingOpts) |
|
| 445 |
+ if loggingDriver == "none" && len(loggingOpts) > 0 {
|
|
| 446 |
+ return map[string]string{}, fmt.Errorf("Invalid logging opts for driver %s", loggingDriver)
|
|
| 447 |
+ } |
|
| 448 |
+ return loggingOptsMap, nil |
|
| 449 |
+} |
|
| 450 |
+ |
|
| 451 |
+// ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect |
|
| 452 |
+func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
|
|
| 453 |
+ p := container.RestartPolicy{}
|
|
| 454 |
+ |
|
| 455 |
+ if policy == "" {
|
|
| 456 |
+ return p, nil |
|
| 457 |
+ } |
|
| 458 |
+ |
|
| 459 |
+ var ( |
|
| 460 |
+ parts = strings.Split(policy, ":") |
|
| 461 |
+ name = parts[0] |
|
| 462 |
+ ) |
|
| 463 |
+ |
|
| 464 |
+ p.Name = name |
|
| 465 |
+ switch name {
|
|
| 466 |
+ case "always", "unless-stopped": |
|
| 467 |
+ if len(parts) > 1 {
|
|
| 468 |
+ return p, fmt.Errorf("maximum restart count not valid with restart policy of \"%s\"", name)
|
|
| 469 |
+ } |
|
| 470 |
+ case "no": |
|
| 471 |
+ // do nothing |
|
| 472 |
+ case "on-failure": |
|
| 473 |
+ if len(parts) > 2 {
|
|
| 474 |
+ return p, fmt.Errorf("restart count format is not valid, usage: 'on-failure:N' or 'on-failure'")
|
|
| 475 |
+ } |
|
| 476 |
+ if len(parts) == 2 {
|
|
| 477 |
+ count, err := strconv.Atoi(parts[1]) |
|
| 478 |
+ if err != nil {
|
|
| 479 |
+ return p, err |
|
| 480 |
+ } |
|
| 481 |
+ |
|
| 482 |
+ p.MaximumRetryCount = count |
|
| 483 |
+ } |
|
| 484 |
+ default: |
|
| 485 |
+ return p, fmt.Errorf("invalid restart policy %s", name)
|
|
| 486 |
+ } |
|
| 487 |
+ |
|
| 488 |
+ return p, nil |
|
| 489 |
+} |
|
| 490 |
+ |
|
| 491 |
+// ParseDevice parses a device mapping string to a container.DeviceMapping struct |
|
| 492 |
+func ParseDevice(device string) (container.DeviceMapping, error) {
|
|
| 493 |
+ src := "" |
|
| 494 |
+ dst := "" |
|
| 495 |
+ permissions := "rwm" |
|
| 496 |
+ arr := strings.Split(device, ":") |
|
| 497 |
+ switch len(arr) {
|
|
| 498 |
+ case 3: |
|
| 499 |
+ permissions = arr[2] |
|
| 500 |
+ fallthrough |
|
| 501 |
+ case 2: |
|
| 502 |
+ if ValidDeviceMode(arr[1]) {
|
|
| 503 |
+ permissions = arr[1] |
|
| 504 |
+ } else {
|
|
| 505 |
+ dst = arr[1] |
|
| 506 |
+ } |
|
| 507 |
+ fallthrough |
|
| 508 |
+ case 1: |
|
| 509 |
+ src = arr[0] |
|
| 510 |
+ default: |
|
| 511 |
+ return container.DeviceMapping{}, fmt.Errorf("Invalid device specification: %s", device)
|
|
| 512 |
+ } |
|
| 513 |
+ |
|
| 514 |
+ if dst == "" {
|
|
| 515 |
+ dst = src |
|
| 516 |
+ } |
|
| 517 |
+ |
|
| 518 |
+ deviceMapping := container.DeviceMapping{
|
|
| 519 |
+ PathOnHost: src, |
|
| 520 |
+ PathInContainer: dst, |
|
| 521 |
+ CgroupPermissions: permissions, |
|
| 522 |
+ } |
|
| 523 |
+ return deviceMapping, nil |
|
| 524 |
+} |
|
| 525 |
+ |
|
| 526 |
+// ParseLink parses and validates the specified string as a link format (name:alias) |
|
| 527 |
+func ParseLink(val string) (string, string, error) {
|
|
| 528 |
+ if val == "" {
|
|
| 529 |
+ return "", "", fmt.Errorf("empty string specified for links")
|
|
| 530 |
+ } |
|
| 531 |
+ arr := strings.Split(val, ":") |
|
| 532 |
+ if len(arr) > 2 {
|
|
| 533 |
+ return "", "", fmt.Errorf("bad format for links: %s", val)
|
|
| 534 |
+ } |
|
| 535 |
+ if len(arr) == 1 {
|
|
| 536 |
+ return val, val, nil |
|
| 537 |
+ } |
|
| 538 |
+ // This is kept because we can actually get an HostConfig with links |
|
| 539 |
+ // from an already created container and the format is not `foo:bar` |
|
| 540 |
+ // but `/foo:/c1/bar` |
|
| 541 |
+ if strings.HasPrefix(arr[0], "/") {
|
|
| 542 |
+ _, alias := path.Split(arr[1]) |
|
| 543 |
+ return arr[0][1:], alias, nil |
|
| 544 |
+ } |
|
| 545 |
+ return arr[0], arr[1], nil |
|
| 546 |
+} |
|
| 547 |
+ |
|
| 548 |
+// ValidateLink validates that the specified string has a valid link format (containerName:alias). |
|
| 549 |
+func ValidateLink(val string) (string, error) {
|
|
| 550 |
+ if _, _, err := ParseLink(val); err != nil {
|
|
| 551 |
+ return val, err |
|
| 552 |
+ } |
|
| 553 |
+ return val, nil |
|
| 554 |
+} |
|
| 555 |
+ |
|
| 556 |
+// ValidDeviceMode checks if the mode for device is valid or not. |
|
| 557 |
+// Valid mode is a composition of r (read), w (write), and m (mknod). |
|
| 558 |
+func ValidDeviceMode(mode string) bool {
|
|
| 559 |
+ var legalDeviceMode = map[rune]bool{
|
|
| 560 |
+ 'r': true, |
|
| 561 |
+ 'w': true, |
|
| 562 |
+ 'm': true, |
|
| 563 |
+ } |
|
| 564 |
+ if mode == "" {
|
|
| 565 |
+ return false |
|
| 566 |
+ } |
|
| 567 |
+ for _, c := range mode {
|
|
| 568 |
+ if !legalDeviceMode[c] {
|
|
| 569 |
+ return false |
|
| 570 |
+ } |
|
| 571 |
+ legalDeviceMode[c] = false |
|
| 572 |
+ } |
|
| 573 |
+ return true |
|
| 574 |
+} |
|
| 575 |
+ |
|
| 576 |
+// ValidateDevice validates a path for devices |
|
| 577 |
+// It will make sure 'val' is in the form: |
|
| 578 |
+// [host-dir:]container-path[:mode] |
|
| 579 |
+// It also validates the device mode. |
|
| 580 |
+func ValidateDevice(val string) (string, error) {
|
|
| 581 |
+ return validatePath(val, ValidDeviceMode) |
|
| 582 |
+} |
|
| 583 |
+ |
|
| 584 |
+func validatePath(val string, validator func(string) bool) (string, error) {
|
|
| 585 |
+ var containerPath string |
|
| 586 |
+ var mode string |
|
| 587 |
+ |
|
| 588 |
+ if strings.Count(val, ":") > 2 {
|
|
| 589 |
+ return val, fmt.Errorf("bad format for path: %s", val)
|
|
| 590 |
+ } |
|
| 591 |
+ |
|
| 592 |
+ split := strings.SplitN(val, ":", 3) |
|
| 593 |
+ if split[0] == "" {
|
|
| 594 |
+ return val, fmt.Errorf("bad format for path: %s", val)
|
|
| 595 |
+ } |
|
| 596 |
+ switch len(split) {
|
|
| 597 |
+ case 1: |
|
| 598 |
+ containerPath = split[0] |
|
| 599 |
+ val = path.Clean(containerPath) |
|
| 600 |
+ case 2: |
|
| 601 |
+ if isValid := validator(split[1]); isValid {
|
|
| 602 |
+ containerPath = split[0] |
|
| 603 |
+ mode = split[1] |
|
| 604 |
+ val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
|
|
| 605 |
+ } else {
|
|
| 606 |
+ containerPath = split[1] |
|
| 607 |
+ val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
|
|
| 608 |
+ } |
|
| 609 |
+ case 3: |
|
| 610 |
+ containerPath = split[1] |
|
| 611 |
+ mode = split[2] |
|
| 612 |
+ if isValid := validator(split[2]); !isValid {
|
|
| 613 |
+ return val, fmt.Errorf("bad mode specified: %s", mode)
|
|
| 614 |
+ } |
|
| 615 |
+ val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
|
|
| 616 |
+ } |
|
| 617 |
+ |
|
| 618 |
+ if !path.IsAbs(containerPath) {
|
|
| 619 |
+ return val, fmt.Errorf("%s is not an absolute path", containerPath)
|
|
| 620 |
+ } |
|
| 621 |
+ return val, nil |
|
| 622 |
+} |
| 0 | 623 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,765 @@ |
| 0 |
+package opts |
|
| 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/docker/runconfig" |
|
| 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 := runconfig.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 = runconfig.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 |
+} |