… or could be in `opts` package. Having `runconfig/opts` and `opts`
doesn't really make sense and make it difficult to know where to put
some code.
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
... | ... |
@@ -18,7 +18,6 @@ import ( |
18 | 18 |
apiclient "github.com/docker/docker/client" |
19 | 19 |
"github.com/docker/docker/reference" |
20 | 20 |
"github.com/docker/docker/registry" |
21 |
- runconfigopts "github.com/docker/docker/runconfig/opts" |
|
22 | 21 |
"github.com/spf13/cobra" |
23 | 22 |
"github.com/spf13/pflag" |
24 | 23 |
) |
... | ... |
@@ -30,7 +29,7 @@ type createOptions struct { |
30 | 30 |
// NewCreateCommand creates a new cobra.Command for `docker create` |
31 | 31 |
func NewCreateCommand(dockerCli *command.DockerCli) *cobra.Command { |
32 | 32 |
var opts createOptions |
33 |
- var copts *runconfigopts.ContainerOptions |
|
33 |
+ var copts *containerOptions |
|
34 | 34 |
|
35 | 35 |
cmd := &cobra.Command{ |
36 | 36 |
Use: "create [OPTIONS] IMAGE [COMMAND] [ARG...]", |
... | ... |
@@ -55,12 +54,12 @@ func NewCreateCommand(dockerCli *command.DockerCli) *cobra.Command { |
55 | 55 |
flags.Bool("help", false, "Print usage") |
56 | 56 |
|
57 | 57 |
command.AddTrustedFlags(flags, true) |
58 |
- copts = runconfigopts.AddFlags(flags) |
|
58 |
+ copts = addFlags(flags) |
|
59 | 59 |
return cmd |
60 | 60 |
} |
61 | 61 |
|
62 |
-func runCreate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *createOptions, copts *runconfigopts.ContainerOptions) error { |
|
63 |
- config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts) |
|
62 |
+func runCreate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *createOptions, copts *containerOptions) error { |
|
63 |
+ config, hostConfig, networkingConfig, err := parse(flags, copts) |
|
64 | 64 |
if err != nil { |
65 | 65 |
reportError(dockerCli.Err(), "create", err.Error(), true) |
66 | 66 |
return cli.StatusError{StatusCode: 125} |
... | ... |
@@ -13,7 +13,6 @@ import ( |
13 | 13 |
apiclient "github.com/docker/docker/client" |
14 | 14 |
options "github.com/docker/docker/opts" |
15 | 15 |
"github.com/docker/docker/pkg/promise" |
16 |
- runconfigopts "github.com/docker/docker/runconfig/opts" |
|
17 | 16 |
"github.com/spf13/cobra" |
18 | 17 |
) |
19 | 18 |
|
... | ... |
@@ -30,7 +29,7 @@ type execOptions struct { |
30 | 30 |
func newExecOptions() *execOptions { |
31 | 31 |
var values []string |
32 | 32 |
return &execOptions{ |
33 |
- env: options.NewListOptsRef(&values, runconfigopts.ValidateEnv), |
|
33 |
+ env: options.NewListOptsRef(&values, options.ValidateEnv), |
|
34 | 34 |
} |
35 | 35 |
} |
36 | 36 |
|
37 | 37 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,899 @@ |
0 |
+package container |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "bytes" |
|
4 |
+ "encoding/json" |
|
5 |
+ "fmt" |
|
6 |
+ "io/ioutil" |
|
7 |
+ "path" |
|
8 |
+ "strconv" |
|
9 |
+ "strings" |
|
10 |
+ "time" |
|
11 |
+ |
|
12 |
+ "github.com/docker/docker/api/types/container" |
|
13 |
+ networktypes "github.com/docker/docker/api/types/network" |
|
14 |
+ "github.com/docker/docker/api/types/strslice" |
|
15 |
+ "github.com/docker/docker/opts" |
|
16 |
+ "github.com/docker/docker/pkg/signal" |
|
17 |
+ runconfigopts "github.com/docker/docker/runconfig/opts" |
|
18 |
+ "github.com/docker/go-connections/nat" |
|
19 |
+ units "github.com/docker/go-units" |
|
20 |
+ "github.com/spf13/pflag" |
|
21 |
+) |
|
22 |
+ |
|
23 |
+// containerOptions is a data object with all the options for creating a container |
|
24 |
+type containerOptions struct { |
|
25 |
+ attach opts.ListOpts |
|
26 |
+ volumes opts.ListOpts |
|
27 |
+ tmpfs opts.ListOpts |
|
28 |
+ blkioWeightDevice opts.WeightdeviceOpt |
|
29 |
+ deviceReadBps opts.ThrottledeviceOpt |
|
30 |
+ deviceWriteBps opts.ThrottledeviceOpt |
|
31 |
+ links opts.ListOpts |
|
32 |
+ aliases opts.ListOpts |
|
33 |
+ linkLocalIPs opts.ListOpts |
|
34 |
+ deviceReadIOps opts.ThrottledeviceOpt |
|
35 |
+ deviceWriteIOps opts.ThrottledeviceOpt |
|
36 |
+ env opts.ListOpts |
|
37 |
+ labels opts.ListOpts |
|
38 |
+ devices opts.ListOpts |
|
39 |
+ ulimits *opts.UlimitOpt |
|
40 |
+ sysctls *opts.MapOpts |
|
41 |
+ publish opts.ListOpts |
|
42 |
+ expose opts.ListOpts |
|
43 |
+ dns opts.ListOpts |
|
44 |
+ dnsSearch opts.ListOpts |
|
45 |
+ dnsOptions opts.ListOpts |
|
46 |
+ extraHosts opts.ListOpts |
|
47 |
+ volumesFrom opts.ListOpts |
|
48 |
+ envFile opts.ListOpts |
|
49 |
+ capAdd opts.ListOpts |
|
50 |
+ capDrop opts.ListOpts |
|
51 |
+ groupAdd opts.ListOpts |
|
52 |
+ securityOpt opts.ListOpts |
|
53 |
+ storageOpt opts.ListOpts |
|
54 |
+ labelsFile opts.ListOpts |
|
55 |
+ loggingOpts opts.ListOpts |
|
56 |
+ privileged bool |
|
57 |
+ pidMode string |
|
58 |
+ utsMode string |
|
59 |
+ usernsMode string |
|
60 |
+ publishAll bool |
|
61 |
+ stdin bool |
|
62 |
+ tty bool |
|
63 |
+ oomKillDisable bool |
|
64 |
+ oomScoreAdj int |
|
65 |
+ containerIDFile string |
|
66 |
+ entrypoint string |
|
67 |
+ hostname string |
|
68 |
+ memoryString string |
|
69 |
+ memoryReservation string |
|
70 |
+ memorySwap string |
|
71 |
+ kernelMemory string |
|
72 |
+ user string |
|
73 |
+ workingDir string |
|
74 |
+ cpuCount int64 |
|
75 |
+ cpuShares int64 |
|
76 |
+ cpuPercent int64 |
|
77 |
+ cpuPeriod int64 |
|
78 |
+ cpuRealtimePeriod int64 |
|
79 |
+ cpuRealtimeRuntime int64 |
|
80 |
+ cpuQuota int64 |
|
81 |
+ cpus opts.NanoCPUs |
|
82 |
+ cpusetCpus string |
|
83 |
+ cpusetMems string |
|
84 |
+ blkioWeight uint16 |
|
85 |
+ ioMaxBandwidth string |
|
86 |
+ ioMaxIOps uint64 |
|
87 |
+ swappiness int64 |
|
88 |
+ netMode string |
|
89 |
+ macAddress string |
|
90 |
+ ipv4Address string |
|
91 |
+ ipv6Address string |
|
92 |
+ ipcMode string |
|
93 |
+ pidsLimit int64 |
|
94 |
+ restartPolicy string |
|
95 |
+ readonlyRootfs bool |
|
96 |
+ loggingDriver string |
|
97 |
+ cgroupParent string |
|
98 |
+ volumeDriver string |
|
99 |
+ stopSignal string |
|
100 |
+ stopTimeout int |
|
101 |
+ isolation string |
|
102 |
+ shmSize string |
|
103 |
+ noHealthcheck bool |
|
104 |
+ healthCmd string |
|
105 |
+ healthInterval time.Duration |
|
106 |
+ healthTimeout time.Duration |
|
107 |
+ healthRetries int |
|
108 |
+ runtime string |
|
109 |
+ autoRemove bool |
|
110 |
+ init bool |
|
111 |
+ initPath string |
|
112 |
+ credentialSpec string |
|
113 |
+ |
|
114 |
+ Image string |
|
115 |
+ Args []string |
|
116 |
+} |
|
117 |
+ |
|
118 |
+// addFlags adds all command line flags that will be used by parse to the FlagSet |
|
119 |
+func addFlags(flags *pflag.FlagSet) *containerOptions { |
|
120 |
+ copts := &containerOptions{ |
|
121 |
+ aliases: opts.NewListOpts(nil), |
|
122 |
+ attach: opts.NewListOpts(validateAttach), |
|
123 |
+ blkioWeightDevice: opts.NewWeightdeviceOpt(opts.ValidateWeightDevice), |
|
124 |
+ capAdd: opts.NewListOpts(nil), |
|
125 |
+ capDrop: opts.NewListOpts(nil), |
|
126 |
+ dns: opts.NewListOpts(opts.ValidateIPAddress), |
|
127 |
+ dnsOptions: opts.NewListOpts(nil), |
|
128 |
+ dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), |
|
129 |
+ deviceReadBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice), |
|
130 |
+ deviceReadIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice), |
|
131 |
+ deviceWriteBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice), |
|
132 |
+ deviceWriteIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice), |
|
133 |
+ devices: opts.NewListOpts(validateDevice), |
|
134 |
+ env: opts.NewListOpts(opts.ValidateEnv), |
|
135 |
+ envFile: opts.NewListOpts(nil), |
|
136 |
+ expose: opts.NewListOpts(nil), |
|
137 |
+ extraHosts: opts.NewListOpts(opts.ValidateExtraHost), |
|
138 |
+ groupAdd: opts.NewListOpts(nil), |
|
139 |
+ labels: opts.NewListOpts(opts.ValidateEnv), |
|
140 |
+ labelsFile: opts.NewListOpts(nil), |
|
141 |
+ linkLocalIPs: opts.NewListOpts(nil), |
|
142 |
+ links: opts.NewListOpts(opts.ValidateLink), |
|
143 |
+ loggingOpts: opts.NewListOpts(nil), |
|
144 |
+ publish: opts.NewListOpts(nil), |
|
145 |
+ securityOpt: opts.NewListOpts(nil), |
|
146 |
+ storageOpt: opts.NewListOpts(nil), |
|
147 |
+ sysctls: opts.NewMapOpts(nil, opts.ValidateSysctl), |
|
148 |
+ tmpfs: opts.NewListOpts(nil), |
|
149 |
+ ulimits: opts.NewUlimitOpt(nil), |
|
150 |
+ volumes: opts.NewListOpts(nil), |
|
151 |
+ volumesFrom: opts.NewListOpts(nil), |
|
152 |
+ } |
|
153 |
+ |
|
154 |
+ // General purpose flags |
|
155 |
+ flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR") |
|
156 |
+ flags.Var(&copts.devices, "device", "Add a host device to the container") |
|
157 |
+ flags.VarP(&copts.env, "env", "e", "Set environment variables") |
|
158 |
+ flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables") |
|
159 |
+ flags.StringVar(&copts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image") |
|
160 |
+ flags.Var(&copts.groupAdd, "group-add", "Add additional groups to join") |
|
161 |
+ flags.StringVarP(&copts.hostname, "hostname", "h", "", "Container host name") |
|
162 |
+ flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached") |
|
163 |
+ flags.VarP(&copts.labels, "label", "l", "Set meta data on a container") |
|
164 |
+ flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels") |
|
165 |
+ flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only") |
|
166 |
+ flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits") |
|
167 |
+ flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal)) |
|
168 |
+ flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container") |
|
169 |
+ flags.SetAnnotation("stop-timeout", "version", []string{"1.25"}) |
|
170 |
+ flags.Var(copts.sysctls, "sysctl", "Sysctl options") |
|
171 |
+ flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY") |
|
172 |
+ flags.Var(copts.ulimits, "ulimit", "Ulimit options") |
|
173 |
+ flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])") |
|
174 |
+ flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container") |
|
175 |
+ flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits") |
|
176 |
+ |
|
177 |
+ // Security |
|
178 |
+ flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities") |
|
179 |
+ flags.Var(&copts.capDrop, "cap-drop", "Drop Linux capabilities") |
|
180 |
+ flags.BoolVar(&copts.privileged, "privileged", false, "Give extended privileges to this container") |
|
181 |
+ flags.Var(&copts.securityOpt, "security-opt", "Security Options") |
|
182 |
+ flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use") |
|
183 |
+ flags.StringVar(&copts.credentialSpec, "credentialspec", "", "Credential spec for managed service account (Windows only)") |
|
184 |
+ |
|
185 |
+ // Network and port publishing flag |
|
186 |
+ flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)") |
|
187 |
+ flags.Var(&copts.dns, "dns", "Set custom DNS servers") |
|
188 |
+ // We allow for both "--dns-opt" and "--dns-option", although the latter is the recommended way. |
|
189 |
+ // This is to be consistent with service create/update |
|
190 |
+ flags.Var(&copts.dnsOptions, "dns-opt", "Set DNS options") |
|
191 |
+ flags.Var(&copts.dnsOptions, "dns-option", "Set DNS options") |
|
192 |
+ flags.MarkHidden("dns-opt") |
|
193 |
+ flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains") |
|
194 |
+ flags.Var(&copts.expose, "expose", "Expose a port or a range of ports") |
|
195 |
+ flags.StringVar(&copts.ipv4Address, "ip", "", "Container IPv4 address (e.g. 172.30.100.104)") |
|
196 |
+ flags.StringVar(&copts.ipv6Address, "ip6", "", "Container IPv6 address (e.g. 2001:db8::33)") |
|
197 |
+ flags.Var(&copts.links, "link", "Add link to another container") |
|
198 |
+ flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses") |
|
199 |
+ flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") |
|
200 |
+ flags.VarP(&copts.publish, "publish", "p", "Publish a container's port(s) to the host") |
|
201 |
+ flags.BoolVarP(&copts.publishAll, "publish-all", "P", false, "Publish all exposed ports to random ports") |
|
202 |
+ // We allow for both "--net" and "--network", although the latter is the recommended way. |
|
203 |
+ flags.StringVar(&copts.netMode, "net", "default", "Connect a container to a network") |
|
204 |
+ flags.StringVar(&copts.netMode, "network", "default", "Connect a container to a network") |
|
205 |
+ flags.MarkHidden("net") |
|
206 |
+ // We allow for both "--net-alias" and "--network-alias", although the latter is the recommended way. |
|
207 |
+ flags.Var(&copts.aliases, "net-alias", "Add network-scoped alias for the container") |
|
208 |
+ flags.Var(&copts.aliases, "network-alias", "Add network-scoped alias for the container") |
|
209 |
+ flags.MarkHidden("net-alias") |
|
210 |
+ |
|
211 |
+ // Logging and storage |
|
212 |
+ flags.StringVar(&copts.loggingDriver, "log-driver", "", "Logging driver for the container") |
|
213 |
+ flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container") |
|
214 |
+ flags.Var(&copts.loggingOpts, "log-opt", "Log driver options") |
|
215 |
+ flags.Var(&copts.storageOpt, "storage-opt", "Storage driver options for the container") |
|
216 |
+ flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory") |
|
217 |
+ flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)") |
|
218 |
+ flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume") |
|
219 |
+ |
|
220 |
+ // Health-checking |
|
221 |
+ flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health") |
|
222 |
+ flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ns|us|ms|s|m|h) (default 0s)") |
|
223 |
+ flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy") |
|
224 |
+ flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ns|us|ms|s|m|h) (default 0s)") |
|
225 |
+ flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK") |
|
226 |
+ |
|
227 |
+ // Resource management |
|
228 |
+ flags.Uint16Var(&copts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)") |
|
229 |
+ flags.Var(&copts.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)") |
|
230 |
+ flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file") |
|
231 |
+ flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") |
|
232 |
+ flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)") |
|
233 |
+ flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)") |
|
234 |
+ flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)") |
|
235 |
+ flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period") |
|
236 |
+ flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota") |
|
237 |
+ flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds") |
|
238 |
+ flags.Int64Var(&copts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds") |
|
239 |
+ flags.Int64VarP(&copts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)") |
|
240 |
+ flags.Var(&copts.cpus, "cpus", "Number of CPUs") |
|
241 |
+ flags.Var(&copts.deviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device") |
|
242 |
+ flags.Var(&copts.deviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device") |
|
243 |
+ flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device") |
|
244 |
+ flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device") |
|
245 |
+ flags.StringVar(&copts.ioMaxBandwidth, "io-maxbandwidth", "", "Maximum IO bandwidth limit for the system drive (Windows only)") |
|
246 |
+ flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)") |
|
247 |
+ flags.StringVar(&copts.kernelMemory, "kernel-memory", "", "Kernel memory limit") |
|
248 |
+ flags.StringVarP(&copts.memoryString, "memory", "m", "", "Memory limit") |
|
249 |
+ flags.StringVar(&copts.memoryReservation, "memory-reservation", "", "Memory soft limit") |
|
250 |
+ flags.StringVar(&copts.memorySwap, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap") |
|
251 |
+ flags.Int64Var(&copts.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)") |
|
252 |
+ flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer") |
|
253 |
+ flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)") |
|
254 |
+ flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)") |
|
255 |
+ |
|
256 |
+ // Low-level execution (cgroups, namespaces, ...) |
|
257 |
+ flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container") |
|
258 |
+ flags.StringVar(&copts.ipcMode, "ipc", "", "IPC namespace to use") |
|
259 |
+ flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology") |
|
260 |
+ flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use") |
|
261 |
+ flags.StringVar(&copts.shmSize, "shm-size", "", "Size of /dev/shm, default value is 64MB") |
|
262 |
+ flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use") |
|
263 |
+ flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container") |
|
264 |
+ |
|
265 |
+ flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes") |
|
266 |
+ flags.StringVar(&copts.initPath, "init-path", "", "Path to the docker-init binary") |
|
267 |
+ return copts |
|
268 |
+} |
|
269 |
+ |
|
270 |
+// parse parses the args for the specified command and generates a Config, |
|
271 |
+// a HostConfig and returns them with the specified command. |
|
272 |
+// If the specified args are not valid, it will return an error. |
|
273 |
+func parse(flags *pflag.FlagSet, copts *containerOptions) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) { |
|
274 |
+ var ( |
|
275 |
+ attachStdin = copts.attach.Get("stdin") |
|
276 |
+ attachStdout = copts.attach.Get("stdout") |
|
277 |
+ attachStderr = copts.attach.Get("stderr") |
|
278 |
+ ) |
|
279 |
+ |
|
280 |
+ // Validate the input mac address |
|
281 |
+ if copts.macAddress != "" { |
|
282 |
+ if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil { |
|
283 |
+ return nil, nil, nil, fmt.Errorf("%s is not a valid mac address", copts.macAddress) |
|
284 |
+ } |
|
285 |
+ } |
|
286 |
+ if copts.stdin { |
|
287 |
+ attachStdin = true |
|
288 |
+ } |
|
289 |
+ // If -a is not set, attach to stdout and stderr |
|
290 |
+ if copts.attach.Len() == 0 { |
|
291 |
+ attachStdout = true |
|
292 |
+ attachStderr = true |
|
293 |
+ } |
|
294 |
+ |
|
295 |
+ var err error |
|
296 |
+ |
|
297 |
+ var memory int64 |
|
298 |
+ if copts.memoryString != "" { |
|
299 |
+ memory, err = units.RAMInBytes(copts.memoryString) |
|
300 |
+ if err != nil { |
|
301 |
+ return nil, nil, nil, err |
|
302 |
+ } |
|
303 |
+ } |
|
304 |
+ |
|
305 |
+ var memoryReservation int64 |
|
306 |
+ if copts.memoryReservation != "" { |
|
307 |
+ memoryReservation, err = units.RAMInBytes(copts.memoryReservation) |
|
308 |
+ if err != nil { |
|
309 |
+ return nil, nil, nil, err |
|
310 |
+ } |
|
311 |
+ } |
|
312 |
+ |
|
313 |
+ var memorySwap int64 |
|
314 |
+ if copts.memorySwap != "" { |
|
315 |
+ if copts.memorySwap == "-1" { |
|
316 |
+ memorySwap = -1 |
|
317 |
+ } else { |
|
318 |
+ memorySwap, err = units.RAMInBytes(copts.memorySwap) |
|
319 |
+ if err != nil { |
|
320 |
+ return nil, nil, nil, err |
|
321 |
+ } |
|
322 |
+ } |
|
323 |
+ } |
|
324 |
+ |
|
325 |
+ var kernelMemory int64 |
|
326 |
+ if copts.kernelMemory != "" { |
|
327 |
+ kernelMemory, err = units.RAMInBytes(copts.kernelMemory) |
|
328 |
+ if err != nil { |
|
329 |
+ return nil, nil, nil, err |
|
330 |
+ } |
|
331 |
+ } |
|
332 |
+ |
|
333 |
+ swappiness := copts.swappiness |
|
334 |
+ if swappiness != -1 && (swappiness < 0 || swappiness > 100) { |
|
335 |
+ return nil, nil, nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness) |
|
336 |
+ } |
|
337 |
+ |
|
338 |
+ var shmSize int64 |
|
339 |
+ if copts.shmSize != "" { |
|
340 |
+ shmSize, err = units.RAMInBytes(copts.shmSize) |
|
341 |
+ if err != nil { |
|
342 |
+ return nil, nil, nil, err |
|
343 |
+ } |
|
344 |
+ } |
|
345 |
+ |
|
346 |
+ // TODO FIXME units.RAMInBytes should have a uint64 version |
|
347 |
+ var maxIOBandwidth int64 |
|
348 |
+ if copts.ioMaxBandwidth != "" { |
|
349 |
+ maxIOBandwidth, err = units.RAMInBytes(copts.ioMaxBandwidth) |
|
350 |
+ if err != nil { |
|
351 |
+ return nil, nil, nil, err |
|
352 |
+ } |
|
353 |
+ if maxIOBandwidth < 0 { |
|
354 |
+ return nil, nil, nil, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", copts.ioMaxBandwidth) |
|
355 |
+ } |
|
356 |
+ } |
|
357 |
+ |
|
358 |
+ var binds []string |
|
359 |
+ volumes := copts.volumes.GetMap() |
|
360 |
+ // add any bind targets to the list of container volumes |
|
361 |
+ for bind := range copts.volumes.GetMap() { |
|
362 |
+ if arr := volumeSplitN(bind, 2); len(arr) > 1 { |
|
363 |
+ // after creating the bind mount we want to delete it from the copts.volumes values because |
|
364 |
+ // we do not want bind mounts being committed to image configs |
|
365 |
+ binds = append(binds, bind) |
|
366 |
+ // We should delete from the map (`volumes`) here, as deleting from copts.volumes will not work if |
|
367 |
+ // there are duplicates entries. |
|
368 |
+ delete(volumes, bind) |
|
369 |
+ } |
|
370 |
+ } |
|
371 |
+ |
|
372 |
+ // Can't evaluate options passed into --tmpfs until we actually mount |
|
373 |
+ tmpfs := make(map[string]string) |
|
374 |
+ for _, t := range copts.tmpfs.GetAll() { |
|
375 |
+ if arr := strings.SplitN(t, ":", 2); len(arr) > 1 { |
|
376 |
+ tmpfs[arr[0]] = arr[1] |
|
377 |
+ } else { |
|
378 |
+ tmpfs[arr[0]] = "" |
|
379 |
+ } |
|
380 |
+ } |
|
381 |
+ |
|
382 |
+ var ( |
|
383 |
+ runCmd strslice.StrSlice |
|
384 |
+ entrypoint strslice.StrSlice |
|
385 |
+ ) |
|
386 |
+ |
|
387 |
+ if len(copts.Args) > 0 { |
|
388 |
+ runCmd = strslice.StrSlice(copts.Args) |
|
389 |
+ } |
|
390 |
+ |
|
391 |
+ if copts.entrypoint != "" { |
|
392 |
+ entrypoint = strslice.StrSlice{copts.entrypoint} |
|
393 |
+ } else if flags.Changed("entrypoint") { |
|
394 |
+ // if `--entrypoint=` is parsed then Entrypoint is reset |
|
395 |
+ entrypoint = []string{""} |
|
396 |
+ } |
|
397 |
+ |
|
398 |
+ ports, portBindings, err := nat.ParsePortSpecs(copts.publish.GetAll()) |
|
399 |
+ if err != nil { |
|
400 |
+ return nil, nil, nil, err |
|
401 |
+ } |
|
402 |
+ |
|
403 |
+ // Merge in exposed ports to the map of published ports |
|
404 |
+ for _, e := range copts.expose.GetAll() { |
|
405 |
+ if strings.Contains(e, ":") { |
|
406 |
+ return nil, nil, nil, fmt.Errorf("invalid port format for --expose: %s", e) |
|
407 |
+ } |
|
408 |
+ //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] |
|
409 |
+ proto, port := nat.SplitProtoPort(e) |
|
410 |
+ //parse the start and end port and create a sequence of ports to expose |
|
411 |
+ //if expose a port, the start and end port are the same |
|
412 |
+ start, end, err := nat.ParsePortRange(port) |
|
413 |
+ if err != nil { |
|
414 |
+ return nil, nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err) |
|
415 |
+ } |
|
416 |
+ for i := start; i <= end; i++ { |
|
417 |
+ p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) |
|
418 |
+ if err != nil { |
|
419 |
+ return nil, nil, nil, err |
|
420 |
+ } |
|
421 |
+ if _, exists := ports[p]; !exists { |
|
422 |
+ ports[p] = struct{}{} |
|
423 |
+ } |
|
424 |
+ } |
|
425 |
+ } |
|
426 |
+ |
|
427 |
+ // parse device mappings |
|
428 |
+ deviceMappings := []container.DeviceMapping{} |
|
429 |
+ for _, device := range copts.devices.GetAll() { |
|
430 |
+ deviceMapping, err := parseDevice(device) |
|
431 |
+ if err != nil { |
|
432 |
+ return nil, nil, nil, err |
|
433 |
+ } |
|
434 |
+ deviceMappings = append(deviceMappings, deviceMapping) |
|
435 |
+ } |
|
436 |
+ |
|
437 |
+ // collect all the environment variables for the container |
|
438 |
+ envVariables, err := runconfigopts.ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll()) |
|
439 |
+ if err != nil { |
|
440 |
+ return nil, nil, nil, err |
|
441 |
+ } |
|
442 |
+ |
|
443 |
+ // collect all the labels for the container |
|
444 |
+ labels, err := runconfigopts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll()) |
|
445 |
+ if err != nil { |
|
446 |
+ return nil, nil, nil, err |
|
447 |
+ } |
|
448 |
+ |
|
449 |
+ ipcMode := container.IpcMode(copts.ipcMode) |
|
450 |
+ if !ipcMode.Valid() { |
|
451 |
+ return nil, nil, nil, fmt.Errorf("--ipc: invalid IPC mode") |
|
452 |
+ } |
|
453 |
+ |
|
454 |
+ pidMode := container.PidMode(copts.pidMode) |
|
455 |
+ if !pidMode.Valid() { |
|
456 |
+ return nil, nil, nil, fmt.Errorf("--pid: invalid PID mode") |
|
457 |
+ } |
|
458 |
+ |
|
459 |
+ utsMode := container.UTSMode(copts.utsMode) |
|
460 |
+ if !utsMode.Valid() { |
|
461 |
+ return nil, nil, nil, fmt.Errorf("--uts: invalid UTS mode") |
|
462 |
+ } |
|
463 |
+ |
|
464 |
+ usernsMode := container.UsernsMode(copts.usernsMode) |
|
465 |
+ if !usernsMode.Valid() { |
|
466 |
+ return nil, nil, nil, fmt.Errorf("--userns: invalid USER mode") |
|
467 |
+ } |
|
468 |
+ |
|
469 |
+ restartPolicy, err := runconfigopts.ParseRestartPolicy(copts.restartPolicy) |
|
470 |
+ if err != nil { |
|
471 |
+ return nil, nil, nil, err |
|
472 |
+ } |
|
473 |
+ |
|
474 |
+ loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll()) |
|
475 |
+ if err != nil { |
|
476 |
+ return nil, nil, nil, err |
|
477 |
+ } |
|
478 |
+ |
|
479 |
+ securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll()) |
|
480 |
+ if err != nil { |
|
481 |
+ return nil, nil, nil, err |
|
482 |
+ } |
|
483 |
+ |
|
484 |
+ storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll()) |
|
485 |
+ if err != nil { |
|
486 |
+ return nil, nil, nil, err |
|
487 |
+ } |
|
488 |
+ |
|
489 |
+ // Healthcheck |
|
490 |
+ var healthConfig *container.HealthConfig |
|
491 |
+ haveHealthSettings := copts.healthCmd != "" || |
|
492 |
+ copts.healthInterval != 0 || |
|
493 |
+ copts.healthTimeout != 0 || |
|
494 |
+ copts.healthRetries != 0 |
|
495 |
+ if copts.noHealthcheck { |
|
496 |
+ if haveHealthSettings { |
|
497 |
+ return nil, nil, nil, fmt.Errorf("--no-healthcheck conflicts with --health-* options") |
|
498 |
+ } |
|
499 |
+ test := strslice.StrSlice{"NONE"} |
|
500 |
+ healthConfig = &container.HealthConfig{Test: test} |
|
501 |
+ } else if haveHealthSettings { |
|
502 |
+ var probe strslice.StrSlice |
|
503 |
+ if copts.healthCmd != "" { |
|
504 |
+ args := []string{"CMD-SHELL", copts.healthCmd} |
|
505 |
+ probe = strslice.StrSlice(args) |
|
506 |
+ } |
|
507 |
+ if copts.healthInterval < 0 { |
|
508 |
+ return nil, nil, nil, fmt.Errorf("--health-interval cannot be negative") |
|
509 |
+ } |
|
510 |
+ if copts.healthTimeout < 0 { |
|
511 |
+ return nil, nil, nil, fmt.Errorf("--health-timeout cannot be negative") |
|
512 |
+ } |
|
513 |
+ |
|
514 |
+ healthConfig = &container.HealthConfig{ |
|
515 |
+ Test: probe, |
|
516 |
+ Interval: copts.healthInterval, |
|
517 |
+ Timeout: copts.healthTimeout, |
|
518 |
+ Retries: copts.healthRetries, |
|
519 |
+ } |
|
520 |
+ } |
|
521 |
+ |
|
522 |
+ resources := container.Resources{ |
|
523 |
+ CgroupParent: copts.cgroupParent, |
|
524 |
+ Memory: memory, |
|
525 |
+ MemoryReservation: memoryReservation, |
|
526 |
+ MemorySwap: memorySwap, |
|
527 |
+ MemorySwappiness: &copts.swappiness, |
|
528 |
+ KernelMemory: kernelMemory, |
|
529 |
+ OomKillDisable: &copts.oomKillDisable, |
|
530 |
+ NanoCPUs: copts.cpus.Value(), |
|
531 |
+ CPUCount: copts.cpuCount, |
|
532 |
+ CPUPercent: copts.cpuPercent, |
|
533 |
+ CPUShares: copts.cpuShares, |
|
534 |
+ CPUPeriod: copts.cpuPeriod, |
|
535 |
+ CpusetCpus: copts.cpusetCpus, |
|
536 |
+ CpusetMems: copts.cpusetMems, |
|
537 |
+ CPUQuota: copts.cpuQuota, |
|
538 |
+ CPURealtimePeriod: copts.cpuRealtimePeriod, |
|
539 |
+ CPURealtimeRuntime: copts.cpuRealtimeRuntime, |
|
540 |
+ PidsLimit: copts.pidsLimit, |
|
541 |
+ BlkioWeight: copts.blkioWeight, |
|
542 |
+ BlkioWeightDevice: copts.blkioWeightDevice.GetList(), |
|
543 |
+ BlkioDeviceReadBps: copts.deviceReadBps.GetList(), |
|
544 |
+ BlkioDeviceWriteBps: copts.deviceWriteBps.GetList(), |
|
545 |
+ BlkioDeviceReadIOps: copts.deviceReadIOps.GetList(), |
|
546 |
+ BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(), |
|
547 |
+ IOMaximumIOps: copts.ioMaxIOps, |
|
548 |
+ IOMaximumBandwidth: uint64(maxIOBandwidth), |
|
549 |
+ Ulimits: copts.ulimits.GetList(), |
|
550 |
+ Devices: deviceMappings, |
|
551 |
+ } |
|
552 |
+ |
|
553 |
+ config := &container.Config{ |
|
554 |
+ Hostname: copts.hostname, |
|
555 |
+ ExposedPorts: ports, |
|
556 |
+ User: copts.user, |
|
557 |
+ Tty: copts.tty, |
|
558 |
+ // TODO: deprecated, it comes from -n, --networking |
|
559 |
+ // it's still needed internally to set the network to disabled |
|
560 |
+ // if e.g. bridge is none in daemon opts, and in inspect |
|
561 |
+ NetworkDisabled: false, |
|
562 |
+ OpenStdin: copts.stdin, |
|
563 |
+ AttachStdin: attachStdin, |
|
564 |
+ AttachStdout: attachStdout, |
|
565 |
+ AttachStderr: attachStderr, |
|
566 |
+ Env: envVariables, |
|
567 |
+ Cmd: runCmd, |
|
568 |
+ Image: copts.Image, |
|
569 |
+ Volumes: volumes, |
|
570 |
+ MacAddress: copts.macAddress, |
|
571 |
+ Entrypoint: entrypoint, |
|
572 |
+ WorkingDir: copts.workingDir, |
|
573 |
+ Labels: runconfigopts.ConvertKVStringsToMap(labels), |
|
574 |
+ Healthcheck: healthConfig, |
|
575 |
+ } |
|
576 |
+ if flags.Changed("stop-signal") { |
|
577 |
+ config.StopSignal = copts.stopSignal |
|
578 |
+ } |
|
579 |
+ if flags.Changed("stop-timeout") { |
|
580 |
+ config.StopTimeout = &copts.stopTimeout |
|
581 |
+ } |
|
582 |
+ |
|
583 |
+ hostConfig := &container.HostConfig{ |
|
584 |
+ Binds: binds, |
|
585 |
+ ContainerIDFile: copts.containerIDFile, |
|
586 |
+ OomScoreAdj: copts.oomScoreAdj, |
|
587 |
+ AutoRemove: copts.autoRemove, |
|
588 |
+ Privileged: copts.privileged, |
|
589 |
+ PortBindings: portBindings, |
|
590 |
+ Links: copts.links.GetAll(), |
|
591 |
+ PublishAllPorts: copts.publishAll, |
|
592 |
+ // Make sure the dns fields are never nil. |
|
593 |
+ // New containers don't ever have those fields nil, |
|
594 |
+ // but pre created containers can still have those nil values. |
|
595 |
+ // See https://github.com/docker/docker/pull/17779 |
|
596 |
+ // for a more detailed explanation on why we don't want that. |
|
597 |
+ DNS: copts.dns.GetAllOrEmpty(), |
|
598 |
+ DNSSearch: copts.dnsSearch.GetAllOrEmpty(), |
|
599 |
+ DNSOptions: copts.dnsOptions.GetAllOrEmpty(), |
|
600 |
+ ExtraHosts: copts.extraHosts.GetAll(), |
|
601 |
+ VolumesFrom: copts.volumesFrom.GetAll(), |
|
602 |
+ NetworkMode: container.NetworkMode(copts.netMode), |
|
603 |
+ IpcMode: ipcMode, |
|
604 |
+ PidMode: pidMode, |
|
605 |
+ UTSMode: utsMode, |
|
606 |
+ UsernsMode: usernsMode, |
|
607 |
+ CapAdd: strslice.StrSlice(copts.capAdd.GetAll()), |
|
608 |
+ CapDrop: strslice.StrSlice(copts.capDrop.GetAll()), |
|
609 |
+ GroupAdd: copts.groupAdd.GetAll(), |
|
610 |
+ RestartPolicy: restartPolicy, |
|
611 |
+ SecurityOpt: securityOpts, |
|
612 |
+ StorageOpt: storageOpts, |
|
613 |
+ ReadonlyRootfs: copts.readonlyRootfs, |
|
614 |
+ LogConfig: container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts}, |
|
615 |
+ VolumeDriver: copts.volumeDriver, |
|
616 |
+ Isolation: container.Isolation(copts.isolation), |
|
617 |
+ ShmSize: shmSize, |
|
618 |
+ Resources: resources, |
|
619 |
+ Tmpfs: tmpfs, |
|
620 |
+ Sysctls: copts.sysctls.GetAll(), |
|
621 |
+ Runtime: copts.runtime, |
|
622 |
+ } |
|
623 |
+ |
|
624 |
+ // only set this value if the user provided the flag, else it should default to nil |
|
625 |
+ if flags.Changed("init") { |
|
626 |
+ hostConfig.Init = &copts.init |
|
627 |
+ } |
|
628 |
+ |
|
629 |
+ // When allocating stdin in attached mode, close stdin at client disconnect |
|
630 |
+ if config.OpenStdin && config.AttachStdin { |
|
631 |
+ config.StdinOnce = true |
|
632 |
+ } |
|
633 |
+ |
|
634 |
+ networkingConfig := &networktypes.NetworkingConfig{ |
|
635 |
+ EndpointsConfig: make(map[string]*networktypes.EndpointSettings), |
|
636 |
+ } |
|
637 |
+ |
|
638 |
+ if copts.ipv4Address != "" || copts.ipv6Address != "" || copts.linkLocalIPs.Len() > 0 { |
|
639 |
+ epConfig := &networktypes.EndpointSettings{} |
|
640 |
+ networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig |
|
641 |
+ |
|
642 |
+ epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{ |
|
643 |
+ IPv4Address: copts.ipv4Address, |
|
644 |
+ IPv6Address: copts.ipv6Address, |
|
645 |
+ } |
|
646 |
+ |
|
647 |
+ if copts.linkLocalIPs.Len() > 0 { |
|
648 |
+ epConfig.IPAMConfig.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len()) |
|
649 |
+ copy(epConfig.IPAMConfig.LinkLocalIPs, copts.linkLocalIPs.GetAll()) |
|
650 |
+ } |
|
651 |
+ } |
|
652 |
+ |
|
653 |
+ if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 { |
|
654 |
+ epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] |
|
655 |
+ if epConfig == nil { |
|
656 |
+ epConfig = &networktypes.EndpointSettings{} |
|
657 |
+ } |
|
658 |
+ epConfig.Links = make([]string, len(hostConfig.Links)) |
|
659 |
+ copy(epConfig.Links, hostConfig.Links) |
|
660 |
+ networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig |
|
661 |
+ } |
|
662 |
+ |
|
663 |
+ if copts.aliases.Len() > 0 { |
|
664 |
+ epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] |
|
665 |
+ if epConfig == nil { |
|
666 |
+ epConfig = &networktypes.EndpointSettings{} |
|
667 |
+ } |
|
668 |
+ epConfig.Aliases = make([]string, copts.aliases.Len()) |
|
669 |
+ copy(epConfig.Aliases, copts.aliases.GetAll()) |
|
670 |
+ networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig |
|
671 |
+ } |
|
672 |
+ |
|
673 |
+ return config, hostConfig, networkingConfig, nil |
|
674 |
+} |
|
675 |
+ |
|
676 |
+func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) { |
|
677 |
+ loggingOptsMap := runconfigopts.ConvertKVStringsToMap(loggingOpts) |
|
678 |
+ if loggingDriver == "none" && len(loggingOpts) > 0 { |
|
679 |
+ return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver) |
|
680 |
+ } |
|
681 |
+ return loggingOptsMap, nil |
|
682 |
+} |
|
683 |
+ |
|
684 |
+// takes a local seccomp daemon, reads the file contents for sending to the daemon |
|
685 |
+func parseSecurityOpts(securityOpts []string) ([]string, error) { |
|
686 |
+ for key, opt := range securityOpts { |
|
687 |
+ con := strings.SplitN(opt, "=", 2) |
|
688 |
+ if len(con) == 1 && con[0] != "no-new-privileges" { |
|
689 |
+ if strings.Contains(opt, ":") { |
|
690 |
+ con = strings.SplitN(opt, ":", 2) |
|
691 |
+ } else { |
|
692 |
+ return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt) |
|
693 |
+ } |
|
694 |
+ } |
|
695 |
+ if con[0] == "seccomp" && con[1] != "unconfined" { |
|
696 |
+ f, err := ioutil.ReadFile(con[1]) |
|
697 |
+ if err != nil { |
|
698 |
+ return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err) |
|
699 |
+ } |
|
700 |
+ b := bytes.NewBuffer(nil) |
|
701 |
+ if err := json.Compact(b, f); err != nil { |
|
702 |
+ return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err) |
|
703 |
+ } |
|
704 |
+ securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes()) |
|
705 |
+ } |
|
706 |
+ } |
|
707 |
+ |
|
708 |
+ return securityOpts, nil |
|
709 |
+} |
|
710 |
+ |
|
711 |
+// parses storage options per container into a map |
|
712 |
+func parseStorageOpts(storageOpts []string) (map[string]string, error) { |
|
713 |
+ m := make(map[string]string) |
|
714 |
+ for _, option := range storageOpts { |
|
715 |
+ if strings.Contains(option, "=") { |
|
716 |
+ opt := strings.SplitN(option, "=", 2) |
|
717 |
+ m[opt[0]] = opt[1] |
|
718 |
+ } else { |
|
719 |
+ return nil, fmt.Errorf("invalid storage option") |
|
720 |
+ } |
|
721 |
+ } |
|
722 |
+ return m, nil |
|
723 |
+} |
|
724 |
+ |
|
725 |
+// parseDevice parses a device mapping string to a container.DeviceMapping struct |
|
726 |
+func parseDevice(device string) (container.DeviceMapping, error) { |
|
727 |
+ src := "" |
|
728 |
+ dst := "" |
|
729 |
+ permissions := "rwm" |
|
730 |
+ arr := strings.Split(device, ":") |
|
731 |
+ switch len(arr) { |
|
732 |
+ case 3: |
|
733 |
+ permissions = arr[2] |
|
734 |
+ fallthrough |
|
735 |
+ case 2: |
|
736 |
+ if validDeviceMode(arr[1]) { |
|
737 |
+ permissions = arr[1] |
|
738 |
+ } else { |
|
739 |
+ dst = arr[1] |
|
740 |
+ } |
|
741 |
+ fallthrough |
|
742 |
+ case 1: |
|
743 |
+ src = arr[0] |
|
744 |
+ default: |
|
745 |
+ return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device) |
|
746 |
+ } |
|
747 |
+ |
|
748 |
+ if dst == "" { |
|
749 |
+ dst = src |
|
750 |
+ } |
|
751 |
+ |
|
752 |
+ deviceMapping := container.DeviceMapping{ |
|
753 |
+ PathOnHost: src, |
|
754 |
+ PathInContainer: dst, |
|
755 |
+ CgroupPermissions: permissions, |
|
756 |
+ } |
|
757 |
+ return deviceMapping, nil |
|
758 |
+} |
|
759 |
+ |
|
760 |
+// validDeviceMode checks if the mode for device is valid or not. |
|
761 |
+// Valid mode is a composition of r (read), w (write), and m (mknod). |
|
762 |
+func validDeviceMode(mode string) bool { |
|
763 |
+ var legalDeviceMode = map[rune]bool{ |
|
764 |
+ 'r': true, |
|
765 |
+ 'w': true, |
|
766 |
+ 'm': true, |
|
767 |
+ } |
|
768 |
+ if mode == "" { |
|
769 |
+ return false |
|
770 |
+ } |
|
771 |
+ for _, c := range mode { |
|
772 |
+ if !legalDeviceMode[c] { |
|
773 |
+ return false |
|
774 |
+ } |
|
775 |
+ legalDeviceMode[c] = false |
|
776 |
+ } |
|
777 |
+ return true |
|
778 |
+} |
|
779 |
+ |
|
780 |
+// validateDevice validates a path for devices |
|
781 |
+// It will make sure 'val' is in the form: |
|
782 |
+// [host-dir:]container-path[:mode] |
|
783 |
+// It also validates the device mode. |
|
784 |
+func validateDevice(val string) (string, error) { |
|
785 |
+ return validatePath(val, validDeviceMode) |
|
786 |
+} |
|
787 |
+ |
|
788 |
+func validatePath(val string, validator func(string) bool) (string, error) { |
|
789 |
+ var containerPath string |
|
790 |
+ var mode string |
|
791 |
+ |
|
792 |
+ if strings.Count(val, ":") > 2 { |
|
793 |
+ return val, fmt.Errorf("bad format for path: %s", val) |
|
794 |
+ } |
|
795 |
+ |
|
796 |
+ split := strings.SplitN(val, ":", 3) |
|
797 |
+ if split[0] == "" { |
|
798 |
+ return val, fmt.Errorf("bad format for path: %s", val) |
|
799 |
+ } |
|
800 |
+ switch len(split) { |
|
801 |
+ case 1: |
|
802 |
+ containerPath = split[0] |
|
803 |
+ val = path.Clean(containerPath) |
|
804 |
+ case 2: |
|
805 |
+ if isValid := validator(split[1]); isValid { |
|
806 |
+ containerPath = split[0] |
|
807 |
+ mode = split[1] |
|
808 |
+ val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) |
|
809 |
+ } else { |
|
810 |
+ containerPath = split[1] |
|
811 |
+ val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath)) |
|
812 |
+ } |
|
813 |
+ case 3: |
|
814 |
+ containerPath = split[1] |
|
815 |
+ mode = split[2] |
|
816 |
+ if isValid := validator(split[2]); !isValid { |
|
817 |
+ return val, fmt.Errorf("bad mode specified: %s", mode) |
|
818 |
+ } |
|
819 |
+ val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode) |
|
820 |
+ } |
|
821 |
+ |
|
822 |
+ if !path.IsAbs(containerPath) { |
|
823 |
+ return val, fmt.Errorf("%s is not an absolute path", containerPath) |
|
824 |
+ } |
|
825 |
+ return val, nil |
|
826 |
+} |
|
827 |
+ |
|
828 |
+// volumeSplitN splits raw into a maximum of n parts, separated by a separator colon. |
|
829 |
+// A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped). |
|
830 |
+// In Windows driver letter appears in two situations: |
|
831 |
+// a. `^[a-zA-Z]:` (A colon followed by `^[a-zA-Z]:` is OK as colon is the separator in volume option) |
|
832 |
+// b. A string in the format like `\\?\C:\Windows\...` (UNC). |
|
833 |
+// Therefore, a driver letter can only follow either a `:` or `\\` |
|
834 |
+// This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`. |
|
835 |
+func volumeSplitN(raw string, n int) []string { |
|
836 |
+ var array []string |
|
837 |
+ if len(raw) == 0 || raw[0] == ':' { |
|
838 |
+ // invalid |
|
839 |
+ return nil |
|
840 |
+ } |
|
841 |
+ // numberOfParts counts the number of parts separated by a separator colon |
|
842 |
+ numberOfParts := 0 |
|
843 |
+ // left represents the left-most cursor in raw, updated at every `:` character considered as a separator. |
|
844 |
+ left := 0 |
|
845 |
+ // right represents the right-most cursor in raw incremented with the loop. Note this |
|
846 |
+ // starts at index 1 as index 0 is already handle above as a special case. |
|
847 |
+ for right := 1; right < len(raw); right++ { |
|
848 |
+ // stop parsing if reached maximum number of parts |
|
849 |
+ if n >= 0 && numberOfParts >= n { |
|
850 |
+ break |
|
851 |
+ } |
|
852 |
+ if raw[right] != ':' { |
|
853 |
+ continue |
|
854 |
+ } |
|
855 |
+ potentialDriveLetter := raw[right-1] |
|
856 |
+ if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') { |
|
857 |
+ if right > 1 { |
|
858 |
+ beforePotentialDriveLetter := raw[right-2] |
|
859 |
+ // Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`) |
|
860 |
+ if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' { |
|
861 |
+ // e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`. |
|
862 |
+ array = append(array, raw[left:right]) |
|
863 |
+ left = right + 1 |
|
864 |
+ numberOfParts++ |
|
865 |
+ } |
|
866 |
+ // else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing. |
|
867 |
+ } |
|
868 |
+ // if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing. |
|
869 |
+ } else { |
|
870 |
+ // if `:` is not preceded by a potential drive letter, then consider it as a delimiter. |
|
871 |
+ array = append(array, raw[left:right]) |
|
872 |
+ left = right + 1 |
|
873 |
+ numberOfParts++ |
|
874 |
+ } |
|
875 |
+ } |
|
876 |
+ // need to take care of the last part |
|
877 |
+ if left < len(raw) { |
|
878 |
+ if n >= 0 && numberOfParts >= n { |
|
879 |
+ // if the maximum number of parts is reached, just append the rest to the last part |
|
880 |
+ // left-1 is at the last `:` that needs to be included since not considered a separator. |
|
881 |
+ array[n-1] += raw[left-1:] |
|
882 |
+ } else { |
|
883 |
+ array = append(array, raw[left:]) |
|
884 |
+ } |
|
885 |
+ } |
|
886 |
+ return array |
|
887 |
+} |
|
888 |
+ |
|
889 |
+// validateAttach validates that the specified string is a valid attach option. |
|
890 |
+func validateAttach(val string) (string, error) { |
|
891 |
+ s := strings.ToLower(val) |
|
892 |
+ for _, str := range []string{"stdin", "stdout", "stderr"} { |
|
893 |
+ if s == str { |
|
894 |
+ return s, nil |
|
895 |
+ } |
|
896 |
+ } |
|
897 |
+ return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR") |
|
898 |
+} |
0 | 899 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,857 @@ |
0 |
+package container |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "bytes" |
|
4 |
+ "encoding/json" |
|
5 |
+ "fmt" |
|
6 |
+ "io/ioutil" |
|
7 |
+ "os" |
|
8 |
+ "runtime" |
|
9 |
+ "strings" |
|
10 |
+ "testing" |
|
11 |
+ "time" |
|
12 |
+ |
|
13 |
+ "github.com/docker/docker/api/types/container" |
|
14 |
+ networktypes "github.com/docker/docker/api/types/network" |
|
15 |
+ "github.com/docker/docker/runconfig" |
|
16 |
+ "github.com/docker/go-connections/nat" |
|
17 |
+ "github.com/spf13/pflag" |
|
18 |
+) |
|
19 |
+ |
|
20 |
+func TestValidateAttach(t *testing.T) { |
|
21 |
+ valid := []string{ |
|
22 |
+ "stdin", |
|
23 |
+ "stdout", |
|
24 |
+ "stderr", |
|
25 |
+ "STDIN", |
|
26 |
+ "STDOUT", |
|
27 |
+ "STDERR", |
|
28 |
+ } |
|
29 |
+ if _, err := validateAttach("invalid"); err == nil { |
|
30 |
+ t.Fatalf("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing") |
|
31 |
+ } |
|
32 |
+ |
|
33 |
+ for _, attach := range valid { |
|
34 |
+ value, err := validateAttach(attach) |
|
35 |
+ if err != nil { |
|
36 |
+ t.Fatal(err) |
|
37 |
+ } |
|
38 |
+ if value != strings.ToLower(attach) { |
|
39 |
+ t.Fatalf("Expected [%v], got [%v]", attach, value) |
|
40 |
+ } |
|
41 |
+ } |
|
42 |
+} |
|
43 |
+ |
|
44 |
+func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) { |
|
45 |
+ flags := pflag.NewFlagSet("run", pflag.ContinueOnError) |
|
46 |
+ flags.SetOutput(ioutil.Discard) |
|
47 |
+ flags.Usage = nil |
|
48 |
+ copts := addFlags(flags) |
|
49 |
+ if err := flags.Parse(args); err != nil { |
|
50 |
+ return nil, nil, nil, err |
|
51 |
+ } |
|
52 |
+ return parse(flags, copts) |
|
53 |
+} |
|
54 |
+ |
|
55 |
+func parsetest(t *testing.T, args string) (*container.Config, *container.HostConfig, error) { |
|
56 |
+ config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " ")) |
|
57 |
+ return config, hostConfig, err |
|
58 |
+} |
|
59 |
+ |
|
60 |
+func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) { |
|
61 |
+ config, hostConfig, err := parsetest(t, args) |
|
62 |
+ if err != nil { |
|
63 |
+ t.Fatal(err) |
|
64 |
+ } |
|
65 |
+ return config, hostConfig |
|
66 |
+} |
|
67 |
+ |
|
68 |
+func TestParseRunLinks(t *testing.T) { |
|
69 |
+ if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" { |
|
70 |
+ t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links) |
|
71 |
+ } |
|
72 |
+ if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" { |
|
73 |
+ t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links) |
|
74 |
+ } |
|
75 |
+ if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 { |
|
76 |
+ t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links) |
|
77 |
+ } |
|
78 |
+} |
|
79 |
+ |
|
80 |
+func TestParseRunAttach(t *testing.T) { |
|
81 |
+ if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr { |
|
82 |
+ t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) |
|
83 |
+ } |
|
84 |
+ if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr { |
|
85 |
+ 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) |
|
86 |
+ } |
|
87 |
+ if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr { |
|
88 |
+ t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) |
|
89 |
+ } |
|
90 |
+ if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr { |
|
91 |
+ t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) |
|
92 |
+ } |
|
93 |
+ if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr { |
|
94 |
+ t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) |
|
95 |
+ } |
|
96 |
+ |
|
97 |
+ if _, _, err := parsetest(t, "-a"); err == nil { |
|
98 |
+ t.Fatalf("Error parsing attach flags, `-a` should be an error but is not") |
|
99 |
+ } |
|
100 |
+ if _, _, err := parsetest(t, "-a invalid"); err == nil { |
|
101 |
+ t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not") |
|
102 |
+ } |
|
103 |
+ if _, _, err := parsetest(t, "-a invalid -a stdout"); err == nil { |
|
104 |
+ t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not") |
|
105 |
+ } |
|
106 |
+ if _, _, err := parsetest(t, "-a stdout -a stderr -d"); err == nil { |
|
107 |
+ t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not") |
|
108 |
+ } |
|
109 |
+ if _, _, err := parsetest(t, "-a stdin -d"); err == nil { |
|
110 |
+ t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not") |
|
111 |
+ } |
|
112 |
+ if _, _, err := parsetest(t, "-a stdout -d"); err == nil { |
|
113 |
+ t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not") |
|
114 |
+ } |
|
115 |
+ if _, _, err := parsetest(t, "-a stderr -d"); err == nil { |
|
116 |
+ t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not") |
|
117 |
+ } |
|
118 |
+ if _, _, err := parsetest(t, "-d --rm"); err == nil { |
|
119 |
+ t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not") |
|
120 |
+ } |
|
121 |
+} |
|
122 |
+ |
|
123 |
+func TestParseRunVolumes(t *testing.T) { |
|
124 |
+ |
|
125 |
+ // A single volume |
|
126 |
+ arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`}) |
|
127 |
+ if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil { |
|
128 |
+ t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds) |
|
129 |
+ } else if _, exists := config.Volumes[arr[0]]; !exists { |
|
130 |
+ t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes) |
|
131 |
+ } |
|
132 |
+ |
|
133 |
+ // Two volumes |
|
134 |
+ arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`}) |
|
135 |
+ if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil { |
|
136 |
+ t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds) |
|
137 |
+ } else if _, exists := config.Volumes[arr[0]]; !exists { |
|
138 |
+ t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes) |
|
139 |
+ } else if _, exists := config.Volumes[arr[1]]; !exists { |
|
140 |
+ t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes) |
|
141 |
+ } |
|
142 |
+ |
|
143 |
+ // A single bind-mount |
|
144 |
+ arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`}) |
|
145 |
+ if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] { |
|
146 |
+ 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) |
|
147 |
+ } |
|
148 |
+ |
|
149 |
+ // Two bind-mounts. |
|
150 |
+ arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`}) |
|
151 |
+ if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { |
|
152 |
+ t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds) |
|
153 |
+ } |
|
154 |
+ |
|
155 |
+ // Two bind-mounts, first read-only, second read-write. |
|
156 |
+ // TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4 |
|
157 |
+ arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`}) |
|
158 |
+ if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { |
|
159 |
+ t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds) |
|
160 |
+ } |
|
161 |
+ |
|
162 |
+ // Similar to previous test but with alternate modes which are only supported by Linux |
|
163 |
+ if runtime.GOOS != "windows" { |
|
164 |
+ arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{}) |
|
165 |
+ if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { |
|
166 |
+ t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds) |
|
167 |
+ } |
|
168 |
+ |
|
169 |
+ arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{}) |
|
170 |
+ if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { |
|
171 |
+ t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds) |
|
172 |
+ } |
|
173 |
+ } |
|
174 |
+ |
|
175 |
+ // One bind mount and one volume |
|
176 |
+ arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`}) |
|
177 |
+ if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] { |
|
178 |
+ 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) |
|
179 |
+ } else if _, exists := config.Volumes[arr[1]]; !exists { |
|
180 |
+ t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes) |
|
181 |
+ } |
|
182 |
+ |
|
183 |
+ // Root to non-c: drive letter (Windows specific) |
|
184 |
+ if runtime.GOOS == "windows" { |
|
185 |
+ arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`}) |
|
186 |
+ if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 { |
|
187 |
+ t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0]) |
|
188 |
+ } |
|
189 |
+ } |
|
190 |
+ |
|
191 |
+} |
|
192 |
+ |
|
193 |
+// setupPlatformVolume takes two arrays of volume specs - a Unix style |
|
194 |
+// spec and a Windows style spec. Depending on the platform being unit tested, |
|
195 |
+// it returns one of them, along with a volume string that would be passed |
|
196 |
+// on the docker CLI (e.g. -v /bar -v /foo). |
|
197 |
+func setupPlatformVolume(u []string, w []string) ([]string, string) { |
|
198 |
+ var a []string |
|
199 |
+ if runtime.GOOS == "windows" { |
|
200 |
+ a = w |
|
201 |
+ } else { |
|
202 |
+ a = u |
|
203 |
+ } |
|
204 |
+ s := "" |
|
205 |
+ for _, v := range a { |
|
206 |
+ s = s + "-v " + v + " " |
|
207 |
+ } |
|
208 |
+ return a, s |
|
209 |
+} |
|
210 |
+ |
|
211 |
+// check if (a == c && b == d) || (a == d && b == c) |
|
212 |
+// because maps are randomized |
|
213 |
+func compareRandomizedStrings(a, b, c, d string) error { |
|
214 |
+ if a == c && b == d { |
|
215 |
+ return nil |
|
216 |
+ } |
|
217 |
+ if a == d && b == c { |
|
218 |
+ return nil |
|
219 |
+ } |
|
220 |
+ return fmt.Errorf("strings don't match") |
|
221 |
+} |
|
222 |
+ |
|
223 |
+// Simple parse with MacAddress validation |
|
224 |
+func TestParseWithMacAddress(t *testing.T) { |
|
225 |
+ invalidMacAddress := "--mac-address=invalidMacAddress" |
|
226 |
+ validMacAddress := "--mac-address=92:d0:c6:0a:29:33" |
|
227 |
+ if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" { |
|
228 |
+ t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err) |
|
229 |
+ } |
|
230 |
+ if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" { |
|
231 |
+ t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress) |
|
232 |
+ } |
|
233 |
+} |
|
234 |
+ |
|
235 |
+func TestParseWithMemory(t *testing.T) { |
|
236 |
+ invalidMemory := "--memory=invalid" |
|
237 |
+ validMemory := "--memory=1G" |
|
238 |
+ if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" { |
|
239 |
+ t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err) |
|
240 |
+ } |
|
241 |
+ if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 { |
|
242 |
+ t.Fatalf("Expected the config to have '1G' as Memory, got '%v'", hostconfig.Memory) |
|
243 |
+ } |
|
244 |
+} |
|
245 |
+ |
|
246 |
+func TestParseWithMemorySwap(t *testing.T) { |
|
247 |
+ invalidMemory := "--memory-swap=invalid" |
|
248 |
+ validMemory := "--memory-swap=1G" |
|
249 |
+ anotherValidMemory := "--memory-swap=-1" |
|
250 |
+ if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" { |
|
251 |
+ t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err) |
|
252 |
+ } |
|
253 |
+ if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 { |
|
254 |
+ t.Fatalf("Expected the config to have '1073741824' as MemorySwap, got '%v'", hostconfig.MemorySwap) |
|
255 |
+ } |
|
256 |
+ if _, hostconfig := mustParse(t, anotherValidMemory); hostconfig.MemorySwap != -1 { |
|
257 |
+ t.Fatalf("Expected the config to have '-1' as MemorySwap, got '%v'", hostconfig.MemorySwap) |
|
258 |
+ } |
|
259 |
+} |
|
260 |
+ |
|
261 |
+func TestParseHostname(t *testing.T) { |
|
262 |
+ validHostnames := map[string]string{ |
|
263 |
+ "hostname": "hostname", |
|
264 |
+ "host-name": "host-name", |
|
265 |
+ "hostname123": "hostname123", |
|
266 |
+ "123hostname": "123hostname", |
|
267 |
+ "hostname-of-63-bytes-long-should-be-valid-and-without-any-error": "hostname-of-63-bytes-long-should-be-valid-and-without-any-error", |
|
268 |
+ } |
|
269 |
+ hostnameWithDomain := "--hostname=hostname.domainname" |
|
270 |
+ hostnameWithDomainTld := "--hostname=hostname.domainname.tld" |
|
271 |
+ for hostname, expectedHostname := range validHostnames { |
|
272 |
+ if config, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname { |
|
273 |
+ t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname) |
|
274 |
+ } |
|
275 |
+ } |
|
276 |
+ if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname.domainname" && config.Domainname != "" { |
|
277 |
+ t.Fatalf("Expected the config to have 'hostname' as hostname.domainname, got '%v'", config.Hostname) |
|
278 |
+ } |
|
279 |
+ if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname.domainname.tld" && config.Domainname != "" { |
|
280 |
+ t.Fatalf("Expected the config to have 'hostname' as hostname.domainname.tld, got '%v'", config.Hostname) |
|
281 |
+ } |
|
282 |
+} |
|
283 |
+ |
|
284 |
+func TestParseWithExpose(t *testing.T) { |
|
285 |
+ invalids := map[string]string{ |
|
286 |
+ ":": "invalid port format for --expose: :", |
|
287 |
+ "8080:9090": "invalid port format for --expose: 8080:9090", |
|
288 |
+ "/tcp": "invalid range format for --expose: /tcp, error: Empty string specified for ports.", |
|
289 |
+ "/udp": "invalid range format for --expose: /udp, error: Empty string specified for ports.", |
|
290 |
+ "NaN/tcp": `invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, |
|
291 |
+ "NaN-NaN/tcp": `invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, |
|
292 |
+ "8080-NaN/tcp": `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, |
|
293 |
+ "1234567890-8080/tcp": `invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`, |
|
294 |
+ } |
|
295 |
+ valids := map[string][]nat.Port{ |
|
296 |
+ "8080/tcp": {"8080/tcp"}, |
|
297 |
+ "8080/udp": {"8080/udp"}, |
|
298 |
+ "8080/ncp": {"8080/ncp"}, |
|
299 |
+ "8080-8080/udp": {"8080/udp"}, |
|
300 |
+ "8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"}, |
|
301 |
+ } |
|
302 |
+ for expose, expectedError := range invalids { |
|
303 |
+ if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError { |
|
304 |
+ t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err) |
|
305 |
+ } |
|
306 |
+ } |
|
307 |
+ for expose, exposedPorts := range valids { |
|
308 |
+ config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}) |
|
309 |
+ if err != nil { |
|
310 |
+ t.Fatal(err) |
|
311 |
+ } |
|
312 |
+ if len(config.ExposedPorts) != len(exposedPorts) { |
|
313 |
+ t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts)) |
|
314 |
+ } |
|
315 |
+ for _, port := range exposedPorts { |
|
316 |
+ if _, ok := config.ExposedPorts[port]; !ok { |
|
317 |
+ t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts) |
|
318 |
+ } |
|
319 |
+ } |
|
320 |
+ } |
|
321 |
+ // Merge with actual published port |
|
322 |
+ config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"}) |
|
323 |
+ if err != nil { |
|
324 |
+ t.Fatal(err) |
|
325 |
+ } |
|
326 |
+ if len(config.ExposedPorts) != 2 { |
|
327 |
+ t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts) |
|
328 |
+ } |
|
329 |
+ ports := []nat.Port{"80/tcp", "81/tcp"} |
|
330 |
+ for _, port := range ports { |
|
331 |
+ if _, ok := config.ExposedPorts[port]; !ok { |
|
332 |
+ t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts) |
|
333 |
+ } |
|
334 |
+ } |
|
335 |
+} |
|
336 |
+ |
|
337 |
+func TestParseDevice(t *testing.T) { |
|
338 |
+ valids := map[string]container.DeviceMapping{ |
|
339 |
+ "/dev/snd": { |
|
340 |
+ PathOnHost: "/dev/snd", |
|
341 |
+ PathInContainer: "/dev/snd", |
|
342 |
+ CgroupPermissions: "rwm", |
|
343 |
+ }, |
|
344 |
+ "/dev/snd:rw": { |
|
345 |
+ PathOnHost: "/dev/snd", |
|
346 |
+ PathInContainer: "/dev/snd", |
|
347 |
+ CgroupPermissions: "rw", |
|
348 |
+ }, |
|
349 |
+ "/dev/snd:/something": { |
|
350 |
+ PathOnHost: "/dev/snd", |
|
351 |
+ PathInContainer: "/something", |
|
352 |
+ CgroupPermissions: "rwm", |
|
353 |
+ }, |
|
354 |
+ "/dev/snd:/something:rw": { |
|
355 |
+ PathOnHost: "/dev/snd", |
|
356 |
+ PathInContainer: "/something", |
|
357 |
+ CgroupPermissions: "rw", |
|
358 |
+ }, |
|
359 |
+ } |
|
360 |
+ for device, deviceMapping := range valids { |
|
361 |
+ _, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"}) |
|
362 |
+ if err != nil { |
|
363 |
+ t.Fatal(err) |
|
364 |
+ } |
|
365 |
+ if len(hostconfig.Devices) != 1 { |
|
366 |
+ t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices) |
|
367 |
+ } |
|
368 |
+ if hostconfig.Devices[0] != deviceMapping { |
|
369 |
+ t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices) |
|
370 |
+ } |
|
371 |
+ } |
|
372 |
+ |
|
373 |
+} |
|
374 |
+ |
|
375 |
+func TestParseModes(t *testing.T) { |
|
376 |
+ // ipc ko |
|
377 |
+ if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" { |
|
378 |
+ t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err) |
|
379 |
+ } |
|
380 |
+ // ipc ok |
|
381 |
+ _, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"}) |
|
382 |
+ if err != nil { |
|
383 |
+ t.Fatal(err) |
|
384 |
+ } |
|
385 |
+ if !hostconfig.IpcMode.Valid() { |
|
386 |
+ t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode) |
|
387 |
+ } |
|
388 |
+ // pid ko |
|
389 |
+ if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" { |
|
390 |
+ t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err) |
|
391 |
+ } |
|
392 |
+ // pid ok |
|
393 |
+ _, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"}) |
|
394 |
+ if err != nil { |
|
395 |
+ t.Fatal(err) |
|
396 |
+ } |
|
397 |
+ if !hostconfig.PidMode.Valid() { |
|
398 |
+ t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode) |
|
399 |
+ } |
|
400 |
+ // uts ko |
|
401 |
+ if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" { |
|
402 |
+ t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err) |
|
403 |
+ } |
|
404 |
+ // uts ok |
|
405 |
+ _, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"}) |
|
406 |
+ if err != nil { |
|
407 |
+ t.Fatal(err) |
|
408 |
+ } |
|
409 |
+ if !hostconfig.UTSMode.Valid() { |
|
410 |
+ t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode) |
|
411 |
+ } |
|
412 |
+ // shm-size ko |
|
413 |
+ if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" { |
|
414 |
+ t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err) |
|
415 |
+ } |
|
416 |
+ // shm-size ok |
|
417 |
+ _, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"}) |
|
418 |
+ if err != nil { |
|
419 |
+ t.Fatal(err) |
|
420 |
+ } |
|
421 |
+ if hostconfig.ShmSize != 134217728 { |
|
422 |
+ t.Fatalf("Expected a valid ShmSize, got %d", hostconfig.ShmSize) |
|
423 |
+ } |
|
424 |
+} |
|
425 |
+ |
|
426 |
+func TestParseRestartPolicy(t *testing.T) { |
|
427 |
+ invalids := map[string]string{ |
|
428 |
+ "always:2:3": "invalid restart policy format", |
|
429 |
+ "on-failure:invalid": "maximum retry count must be an integer", |
|
430 |
+ } |
|
431 |
+ valids := map[string]container.RestartPolicy{ |
|
432 |
+ "": {}, |
|
433 |
+ "always": { |
|
434 |
+ Name: "always", |
|
435 |
+ MaximumRetryCount: 0, |
|
436 |
+ }, |
|
437 |
+ "on-failure:1": { |
|
438 |
+ Name: "on-failure", |
|
439 |
+ MaximumRetryCount: 1, |
|
440 |
+ }, |
|
441 |
+ } |
|
442 |
+ for restart, expectedError := range invalids { |
|
443 |
+ if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError { |
|
444 |
+ t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err) |
|
445 |
+ } |
|
446 |
+ } |
|
447 |
+ for restart, expected := range valids { |
|
448 |
+ _, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"}) |
|
449 |
+ if err != nil { |
|
450 |
+ t.Fatal(err) |
|
451 |
+ } |
|
452 |
+ if hostconfig.RestartPolicy != expected { |
|
453 |
+ t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy) |
|
454 |
+ } |
|
455 |
+ } |
|
456 |
+} |
|
457 |
+ |
|
458 |
+func TestParseHealth(t *testing.T) { |
|
459 |
+ checkOk := func(args ...string) *container.HealthConfig { |
|
460 |
+ config, _, _, err := parseRun(args) |
|
461 |
+ if err != nil { |
|
462 |
+ t.Fatalf("%#v: %v", args, err) |
|
463 |
+ } |
|
464 |
+ return config.Healthcheck |
|
465 |
+ } |
|
466 |
+ checkError := func(expected string, args ...string) { |
|
467 |
+ config, _, _, err := parseRun(args) |
|
468 |
+ if err == nil { |
|
469 |
+ t.Fatalf("Expected error, but got %#v", config) |
|
470 |
+ } |
|
471 |
+ if err.Error() != expected { |
|
472 |
+ t.Fatalf("Expected %#v, got %#v", expected, err) |
|
473 |
+ } |
|
474 |
+ } |
|
475 |
+ health := checkOk("--no-healthcheck", "img", "cmd") |
|
476 |
+ if health == nil || len(health.Test) != 1 || health.Test[0] != "NONE" { |
|
477 |
+ t.Fatalf("--no-healthcheck failed: %#v", health) |
|
478 |
+ } |
|
479 |
+ |
|
480 |
+ health = checkOk("--health-cmd=/check.sh -q", "img", "cmd") |
|
481 |
+ if len(health.Test) != 2 || health.Test[0] != "CMD-SHELL" || health.Test[1] != "/check.sh -q" { |
|
482 |
+ t.Fatalf("--health-cmd: got %#v", health.Test) |
|
483 |
+ } |
|
484 |
+ if health.Timeout != 0 { |
|
485 |
+ t.Fatalf("--health-cmd: timeout = %f", health.Timeout) |
|
486 |
+ } |
|
487 |
+ |
|
488 |
+ checkError("--no-healthcheck conflicts with --health-* options", |
|
489 |
+ "--no-healthcheck", "--health-cmd=/check.sh -q", "img", "cmd") |
|
490 |
+ |
|
491 |
+ health = checkOk("--health-timeout=2s", "--health-retries=3", "--health-interval=4.5s", "img", "cmd") |
|
492 |
+ if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond { |
|
493 |
+ t.Fatalf("--health-*: got %#v", health) |
|
494 |
+ } |
|
495 |
+} |
|
496 |
+ |
|
497 |
+func TestParseLoggingOpts(t *testing.T) { |
|
498 |
+ // logging opts ko |
|
499 |
+ if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "invalid logging opts for driver none" { |
|
500 |
+ t.Fatalf("Expected an error with message 'invalid logging opts for driver none', got %v", err) |
|
501 |
+ } |
|
502 |
+ // logging opts ok |
|
503 |
+ _, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"}) |
|
504 |
+ if err != nil { |
|
505 |
+ t.Fatal(err) |
|
506 |
+ } |
|
507 |
+ if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 { |
|
508 |
+ t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy) |
|
509 |
+ } |
|
510 |
+} |
|
511 |
+ |
|
512 |
+func TestParseEnvfileVariables(t *testing.T) { |
|
513 |
+ e := "open nonexistent: no such file or directory" |
|
514 |
+ if runtime.GOOS == "windows" { |
|
515 |
+ e = "open nonexistent: The system cannot find the file specified." |
|
516 |
+ } |
|
517 |
+ // env ko |
|
518 |
+ if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e { |
|
519 |
+ t.Fatalf("Expected an error with message '%s', got %v", e, err) |
|
520 |
+ } |
|
521 |
+ // env ok |
|
522 |
+ config, _, _, err := parseRun([]string{"--env-file=testdata/valid.env", "img", "cmd"}) |
|
523 |
+ if err != nil { |
|
524 |
+ t.Fatal(err) |
|
525 |
+ } |
|
526 |
+ if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" { |
|
527 |
+ t.Fatalf("Expected a config with [ENV1=value1], got %v", config.Env) |
|
528 |
+ } |
|
529 |
+ config, _, _, err = parseRun([]string{"--env-file=testdata/valid.env", "--env=ENV2=value2", "img", "cmd"}) |
|
530 |
+ if err != nil { |
|
531 |
+ t.Fatal(err) |
|
532 |
+ } |
|
533 |
+ if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" { |
|
534 |
+ t.Fatalf("Expected a config with [ENV1=value1 ENV2=value2], got %v", config.Env) |
|
535 |
+ } |
|
536 |
+} |
|
537 |
+ |
|
538 |
+func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) { |
|
539 |
+ // UTF8 with BOM |
|
540 |
+ config, _, _, err := parseRun([]string{"--env-file=testdata/utf8.env", "img", "cmd"}) |
|
541 |
+ if err != nil { |
|
542 |
+ t.Fatal(err) |
|
543 |
+ } |
|
544 |
+ env := []string{"FOO=BAR", "HELLO=" + string([]byte{0xe6, 0x82, 0xa8, 0xe5, 0xa5, 0xbd}), "BAR=FOO"} |
|
545 |
+ if len(config.Env) != len(env) { |
|
546 |
+ t.Fatalf("Expected a config with %d env variables, got %v: %v", len(env), len(config.Env), config.Env) |
|
547 |
+ } |
|
548 |
+ for i, v := range env { |
|
549 |
+ if config.Env[i] != v { |
|
550 |
+ t.Fatalf("Expected a config with [%s], got %v", v, []byte(config.Env[i])) |
|
551 |
+ } |
|
552 |
+ } |
|
553 |
+ |
|
554 |
+ // UTF16 with BOM |
|
555 |
+ e := "contains invalid utf8 bytes at line" |
|
556 |
+ if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) { |
|
557 |
+ t.Fatalf("Expected an error with message '%s', got %v", e, err) |
|
558 |
+ } |
|
559 |
+ // UTF16BE with BOM |
|
560 |
+ if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16be.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) { |
|
561 |
+ t.Fatalf("Expected an error with message '%s', got %v", e, err) |
|
562 |
+ } |
|
563 |
+} |
|
564 |
+ |
|
565 |
+func TestParseLabelfileVariables(t *testing.T) { |
|
566 |
+ e := "open nonexistent: no such file or directory" |
|
567 |
+ if runtime.GOOS == "windows" { |
|
568 |
+ e = "open nonexistent: The system cannot find the file specified." |
|
569 |
+ } |
|
570 |
+ // label ko |
|
571 |
+ if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e { |
|
572 |
+ t.Fatalf("Expected an error with message '%s', got %v", e, err) |
|
573 |
+ } |
|
574 |
+ // label ok |
|
575 |
+ config, _, _, err := parseRun([]string{"--label-file=testdata/valid.label", "img", "cmd"}) |
|
576 |
+ if err != nil { |
|
577 |
+ t.Fatal(err) |
|
578 |
+ } |
|
579 |
+ if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" { |
|
580 |
+ t.Fatalf("Expected a config with [LABEL1:value1], got %v", config.Labels) |
|
581 |
+ } |
|
582 |
+ config, _, _, err = parseRun([]string{"--label-file=testdata/valid.label", "--label=LABEL2=value2", "img", "cmd"}) |
|
583 |
+ if err != nil { |
|
584 |
+ t.Fatal(err) |
|
585 |
+ } |
|
586 |
+ if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" { |
|
587 |
+ t.Fatalf("Expected a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels) |
|
588 |
+ } |
|
589 |
+} |
|
590 |
+ |
|
591 |
+func TestParseEntryPoint(t *testing.T) { |
|
592 |
+ config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"}) |
|
593 |
+ if err != nil { |
|
594 |
+ t.Fatal(err) |
|
595 |
+ } |
|
596 |
+ if len(config.Entrypoint) != 1 && config.Entrypoint[0] != "anything" { |
|
597 |
+ t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint) |
|
598 |
+ } |
|
599 |
+} |
|
600 |
+ |
|
601 |
+// This tests the cases for binds which are generated through |
|
602 |
+// DecodeContainerConfig rather than Parse() |
|
603 |
+func TestDecodeContainerConfigVolumes(t *testing.T) { |
|
604 |
+ |
|
605 |
+ // Root to root |
|
606 |
+ bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`}) |
|
607 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
608 |
+ t.Fatalf("binds %v should have failed", bindsOrVols) |
|
609 |
+ } |
|
610 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
611 |
+ t.Fatalf("volume %v should have failed", bindsOrVols) |
|
612 |
+ } |
|
613 |
+ |
|
614 |
+ // No destination path |
|
615 |
+ bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`}) |
|
616 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
617 |
+ t.Fatalf("binds %v should have failed", bindsOrVols) |
|
618 |
+ } |
|
619 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
620 |
+ t.Fatalf("volume %v should have failed", bindsOrVols) |
|
621 |
+ } |
|
622 |
+ |
|
623 |
+ // // No destination path or mode |
|
624 |
+ bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`}) |
|
625 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
626 |
+ t.Fatalf("binds %v should have failed", bindsOrVols) |
|
627 |
+ } |
|
628 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
629 |
+ t.Fatalf("volume %v should have failed", bindsOrVols) |
|
630 |
+ } |
|
631 |
+ |
|
632 |
+ // A whole lot of nothing |
|
633 |
+ bindsOrVols = []string{`:`} |
|
634 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
635 |
+ t.Fatalf("binds %v should have failed", bindsOrVols) |
|
636 |
+ } |
|
637 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
638 |
+ t.Fatalf("volume %v should have failed", bindsOrVols) |
|
639 |
+ } |
|
640 |
+ |
|
641 |
+ // A whole lot of nothing with no mode |
|
642 |
+ bindsOrVols = []string{`::`} |
|
643 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
644 |
+ t.Fatalf("binds %v should have failed", bindsOrVols) |
|
645 |
+ } |
|
646 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
647 |
+ t.Fatalf("volume %v should have failed", bindsOrVols) |
|
648 |
+ } |
|
649 |
+ |
|
650 |
+ // Too much including an invalid mode |
|
651 |
+ wTmp := os.Getenv("TEMP") |
|
652 |
+ bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp}) |
|
653 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
654 |
+ t.Fatalf("binds %v should have failed", bindsOrVols) |
|
655 |
+ } |
|
656 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
657 |
+ t.Fatalf("volume %v should have failed", bindsOrVols) |
|
658 |
+ } |
|
659 |
+ |
|
660 |
+ // Windows specific error tests |
|
661 |
+ if runtime.GOOS == "windows" { |
|
662 |
+ // Volume which does not include a drive letter |
|
663 |
+ bindsOrVols = []string{`\tmp`} |
|
664 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
665 |
+ t.Fatalf("binds %v should have failed", bindsOrVols) |
|
666 |
+ } |
|
667 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
668 |
+ t.Fatalf("volume %v should have failed", bindsOrVols) |
|
669 |
+ } |
|
670 |
+ |
|
671 |
+ // Root to C-Drive |
|
672 |
+ bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`} |
|
673 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
674 |
+ t.Fatalf("binds %v should have failed", bindsOrVols) |
|
675 |
+ } |
|
676 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
677 |
+ t.Fatalf("volume %v should have failed", bindsOrVols) |
|
678 |
+ } |
|
679 |
+ |
|
680 |
+ // Container path that does not include a drive letter |
|
681 |
+ bindsOrVols = []string{`c:\windows:\somewhere`} |
|
682 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
683 |
+ t.Fatalf("binds %v should have failed", bindsOrVols) |
|
684 |
+ } |
|
685 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
686 |
+ t.Fatalf("volume %v should have failed", bindsOrVols) |
|
687 |
+ } |
|
688 |
+ } |
|
689 |
+ |
|
690 |
+ // Linux-specific error tests |
|
691 |
+ if runtime.GOOS != "windows" { |
|
692 |
+ // Just root |
|
693 |
+ bindsOrVols = []string{`/`} |
|
694 |
+ if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
695 |
+ t.Fatalf("binds %v should have failed", bindsOrVols) |
|
696 |
+ } |
|
697 |
+ if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
698 |
+ t.Fatalf("volume %v should have failed", bindsOrVols) |
|
699 |
+ } |
|
700 |
+ |
|
701 |
+ // A single volume that looks like a bind mount passed in Volumes. |
|
702 |
+ // This should be handled as a bind mount, not a volume. |
|
703 |
+ vols := []string{`/foo:/bar`} |
|
704 |
+ if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil { |
|
705 |
+ t.Fatal("Volume /foo:/bar should have succeeded as a volume name") |
|
706 |
+ } else if hostConfig.Binds != nil { |
|
707 |
+ t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds) |
|
708 |
+ } else if _, exists := config.Volumes[vols[0]]; !exists { |
|
709 |
+ t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes) |
|
710 |
+ } |
|
711 |
+ |
|
712 |
+ } |
|
713 |
+} |
|
714 |
+ |
|
715 |
+// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes |
|
716 |
+// to call DecodeContainerConfig. It effectively does what a client would |
|
717 |
+// do when calling the daemon by constructing a JSON stream of a |
|
718 |
+// ContainerConfigWrapper which is populated by the set of volume specs |
|
719 |
+// passed into it. It returns a config and a hostconfig which can be |
|
720 |
+// validated to ensure DecodeContainerConfig has manipulated the structures |
|
721 |
+// correctly. |
|
722 |
+func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) { |
|
723 |
+ var ( |
|
724 |
+ b []byte |
|
725 |
+ err error |
|
726 |
+ c *container.Config |
|
727 |
+ h *container.HostConfig |
|
728 |
+ ) |
|
729 |
+ w := runconfig.ContainerConfigWrapper{ |
|
730 |
+ Config: &container.Config{ |
|
731 |
+ Volumes: map[string]struct{}{}, |
|
732 |
+ }, |
|
733 |
+ HostConfig: &container.HostConfig{ |
|
734 |
+ NetworkMode: "none", |
|
735 |
+ Binds: binds, |
|
736 |
+ }, |
|
737 |
+ } |
|
738 |
+ for _, v := range volumes { |
|
739 |
+ w.Config.Volumes[v] = struct{}{} |
|
740 |
+ } |
|
741 |
+ if b, err = json.Marshal(w); err != nil { |
|
742 |
+ return nil, nil, fmt.Errorf("Error on marshal %s", err.Error()) |
|
743 |
+ } |
|
744 |
+ c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b)) |
|
745 |
+ if err != nil { |
|
746 |
+ return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err) |
|
747 |
+ } |
|
748 |
+ if c == nil || h == nil { |
|
749 |
+ return nil, nil, fmt.Errorf("Empty config or hostconfig") |
|
750 |
+ } |
|
751 |
+ |
|
752 |
+ return c, h, err |
|
753 |
+} |
|
754 |
+ |
|
755 |
+func TestVolumeSplitN(t *testing.T) { |
|
756 |
+ for _, x := range []struct { |
|
757 |
+ input string |
|
758 |
+ n int |
|
759 |
+ expected []string |
|
760 |
+ }{ |
|
761 |
+ {`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}}, |
|
762 |
+ {`:C:\foo:d:`, -1, nil}, |
|
763 |
+ {`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}}, |
|
764 |
+ {`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}}, |
|
765 |
+ {`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}}, |
|
766 |
+ |
|
767 |
+ {`d:\`, -1, []string{`d:\`}}, |
|
768 |
+ {`d:`, -1, []string{`d:`}}, |
|
769 |
+ {`d:\path`, -1, []string{`d:\path`}}, |
|
770 |
+ {`d:\path with space`, -1, []string{`d:\path with space`}}, |
|
771 |
+ {`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}}, |
|
772 |
+ {`c:\:d:\`, -1, []string{`c:\`, `d:\`}}, |
|
773 |
+ {`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}}, |
|
774 |
+ {`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}}, |
|
775 |
+ {`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}}, |
|
776 |
+ {`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}}, |
|
777 |
+ {`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}}, |
|
778 |
+ {`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}}, |
|
779 |
+ {`name:D:`, -1, []string{`name`, `D:`}}, |
|
780 |
+ {`name:D::rW`, -1, []string{`name`, `D:`, `rW`}}, |
|
781 |
+ {`name:D::RW`, -1, []string{`name`, `D:`, `RW`}}, |
|
782 |
+ {`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}}, |
|
783 |
+ {`c:\Windows`, -1, []string{`c:\Windows`}}, |
|
784 |
+ {`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}}, |
|
785 |
+ |
|
786 |
+ {``, -1, nil}, |
|
787 |
+ {`.`, -1, []string{`.`}}, |
|
788 |
+ {`..\`, -1, []string{`..\`}}, |
|
789 |
+ {`c:\:..\`, -1, []string{`c:\`, `..\`}}, |
|
790 |
+ {`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}}, |
|
791 |
+ |
|
792 |
+ // Cover directories with one-character name |
|
793 |
+ {`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}}, |
|
794 |
+ } { |
|
795 |
+ res := volumeSplitN(x.input, x.n) |
|
796 |
+ if len(res) < len(x.expected) { |
|
797 |
+ t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res) |
|
798 |
+ } |
|
799 |
+ for i, e := range res { |
|
800 |
+ if e != x.expected[i] { |
|
801 |
+ t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res) |
|
802 |
+ } |
|
803 |
+ } |
|
804 |
+ } |
|
805 |
+} |
|
806 |
+ |
|
807 |
+func TestValidateDevice(t *testing.T) { |
|
808 |
+ valid := []string{ |
|
809 |
+ "/home", |
|
810 |
+ "/home:/home", |
|
811 |
+ "/home:/something/else", |
|
812 |
+ "/with space", |
|
813 |
+ "/home:/with space", |
|
814 |
+ "relative:/absolute-path", |
|
815 |
+ "hostPath:/containerPath:r", |
|
816 |
+ "/hostPath:/containerPath:rw", |
|
817 |
+ "/hostPath:/containerPath:mrw", |
|
818 |
+ } |
|
819 |
+ invalid := map[string]string{ |
|
820 |
+ "": "bad format for path: ", |
|
821 |
+ "./": "./ is not an absolute path", |
|
822 |
+ "../": "../ is not an absolute path", |
|
823 |
+ "/:../": "../ is not an absolute path", |
|
824 |
+ "/:path": "path is not an absolute path", |
|
825 |
+ ":": "bad format for path: :", |
|
826 |
+ "/tmp:": " is not an absolute path", |
|
827 |
+ ":test": "bad format for path: :test", |
|
828 |
+ ":/test": "bad format for path: :/test", |
|
829 |
+ "tmp:": " is not an absolute path", |
|
830 |
+ ":test:": "bad format for path: :test:", |
|
831 |
+ "::": "bad format for path: ::", |
|
832 |
+ ":::": "bad format for path: :::", |
|
833 |
+ "/tmp:::": "bad format for path: /tmp:::", |
|
834 |
+ ":/tmp::": "bad format for path: :/tmp::", |
|
835 |
+ "path:ro": "ro is not an absolute path", |
|
836 |
+ "path:rr": "rr is not an absolute path", |
|
837 |
+ "a:/b:ro": "bad mode specified: ro", |
|
838 |
+ "a:/b:rr": "bad mode specified: rr", |
|
839 |
+ } |
|
840 |
+ |
|
841 |
+ for _, path := range valid { |
|
842 |
+ if _, err := validateDevice(path); err != nil { |
|
843 |
+ t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err) |
|
844 |
+ } |
|
845 |
+ } |
|
846 |
+ |
|
847 |
+ for path, expectedError := range invalid { |
|
848 |
+ if _, err := validateDevice(path); err == nil { |
|
849 |
+ t.Fatalf("ValidateDevice(`%q`) should have failed validation", path) |
|
850 |
+ } else { |
|
851 |
+ if err.Error() != expectedError { |
|
852 |
+ t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error()) |
|
853 |
+ } |
|
854 |
+ } |
|
855 |
+ } |
|
856 |
+} |
... | ... |
@@ -18,7 +18,6 @@ import ( |
18 | 18 |
opttypes "github.com/docker/docker/opts" |
19 | 19 |
"github.com/docker/docker/pkg/promise" |
20 | 20 |
"github.com/docker/docker/pkg/signal" |
21 |
- runconfigopts "github.com/docker/docker/runconfig/opts" |
|
22 | 21 |
"github.com/docker/libnetwork/resolvconf/dns" |
23 | 22 |
"github.com/spf13/cobra" |
24 | 23 |
"github.com/spf13/pflag" |
... | ... |
@@ -34,7 +33,7 @@ type runOptions struct { |
34 | 34 |
// NewRunCommand create a new `docker run` command |
35 | 35 |
func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command { |
36 | 36 |
var opts runOptions |
37 |
- var copts *runconfigopts.ContainerOptions |
|
37 |
+ var copts *containerOptions |
|
38 | 38 |
|
39 | 39 |
cmd := &cobra.Command{ |
40 | 40 |
Use: "run [OPTIONS] IMAGE [COMMAND] [ARG...]", |
... | ... |
@@ -63,11 +62,11 @@ func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command { |
63 | 63 |
flags.Bool("help", false, "Print usage") |
64 | 64 |
|
65 | 65 |
command.AddTrustedFlags(flags, true) |
66 |
- copts = runconfigopts.AddFlags(flags) |
|
66 |
+ copts = addFlags(flags) |
|
67 | 67 |
return cmd |
68 | 68 |
} |
69 | 69 |
|
70 |
-func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *runconfigopts.ContainerOptions) error { |
|
70 |
+func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *containerOptions) error { |
|
71 | 71 |
stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In() |
72 | 72 |
client := dockerCli.Client() |
73 | 73 |
// TODO: pass this as an argument |
... | ... |
@@ -79,9 +78,9 @@ func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions |
79 | 79 |
ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm") |
80 | 80 |
) |
81 | 81 |
|
82 |
- config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts) |
|
82 |
+ config, hostConfig, networkingConfig, err := parse(flags, copts) |
|
83 | 83 |
|
84 |
- // just in case the Parse does not exit |
|
84 |
+ // just in case the parse does not exit |
|
85 | 85 |
if err != nil { |
86 | 86 |
reportError(stderr, cmdPath, err.Error(), true) |
87 | 87 |
return cli.StatusError{StatusCode: 125} |
... | ... |
@@ -11,8 +11,6 @@ import ( |
11 | 11 |
"regexp" |
12 | 12 |
"runtime" |
13 | 13 |
|
14 |
- "golang.org/x/net/context" |
|
15 |
- |
|
16 | 14 |
"github.com/docker/docker/api" |
17 | 15 |
"github.com/docker/docker/api/types" |
18 | 16 |
"github.com/docker/docker/api/types/container" |
... | ... |
@@ -31,6 +29,7 @@ import ( |
31 | 31 |
runconfigopts "github.com/docker/docker/runconfig/opts" |
32 | 32 |
"github.com/docker/go-units" |
33 | 33 |
"github.com/spf13/cobra" |
34 |
+ "golang.org/x/net/context" |
|
34 | 35 |
) |
35 | 36 |
|
36 | 37 |
type buildOptions struct { |
... | ... |
@@ -39,7 +38,7 @@ type buildOptions struct { |
39 | 39 |
tags opts.ListOpts |
40 | 40 |
labels opts.ListOpts |
41 | 41 |
buildArgs opts.ListOpts |
42 |
- ulimits *runconfigopts.UlimitOpt |
|
42 |
+ ulimits *opts.UlimitOpt |
|
43 | 43 |
memory string |
44 | 44 |
memorySwap string |
45 | 45 |
shmSize string |
... | ... |
@@ -67,9 +66,9 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command { |
67 | 67 |
ulimits := make(map[string]*units.Ulimit) |
68 | 68 |
options := buildOptions{ |
69 | 69 |
tags: opts.NewListOpts(validateTag), |
70 |
- buildArgs: opts.NewListOpts(runconfigopts.ValidateEnv), |
|
71 |
- ulimits: runconfigopts.NewUlimitOpt(&ulimits), |
|
72 |
- labels: opts.NewListOpts(runconfigopts.ValidateEnv), |
|
70 |
+ buildArgs: opts.NewListOpts(opts.ValidateEnv), |
|
71 |
+ ulimits: opts.NewUlimitOpt(&ulimits), |
|
72 |
+ labels: opts.NewListOpts(opts.ValidateEnv), |
|
73 | 73 |
} |
74 | 74 |
|
75 | 75 |
cmd := &cobra.Command{ |
... | ... |
@@ -7,7 +7,6 @@ import ( |
7 | 7 |
"github.com/docker/docker/cli" |
8 | 8 |
"github.com/docker/docker/cli/command" |
9 | 9 |
"github.com/docker/docker/opts" |
10 |
- runconfigopts "github.com/docker/docker/runconfig/opts" |
|
11 | 10 |
"github.com/spf13/cobra" |
12 | 11 |
) |
13 | 12 |
|
... | ... |
@@ -23,7 +22,7 @@ type connectOptions struct { |
23 | 23 |
|
24 | 24 |
func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command { |
25 | 25 |
opts := connectOptions{ |
26 |
- links: opts.NewListOpts(runconfigopts.ValidateLink), |
|
26 |
+ links: opts.NewListOpts(opts.ValidateLink), |
|
27 | 27 |
} |
28 | 28 |
|
29 | 29 |
cmd := &cobra.Command{ |
... | ... |
@@ -36,7 +36,7 @@ type createOptions struct { |
36 | 36 |
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command { |
37 | 37 |
opts := createOptions{ |
38 | 38 |
driverOpts: *opts.NewMapOpts(nil, nil), |
39 |
- labels: opts.NewListOpts(runconfigopts.ValidateEnv), |
|
39 |
+ labels: opts.NewListOpts(opts.ValidateEnv), |
|
40 | 40 |
ipamAux: *opts.NewMapOpts(nil, nil), |
41 | 41 |
ipamOpt: *opts.NewMapOpts(nil, nil), |
42 | 42 |
} |
... | ... |
@@ -23,7 +23,7 @@ type createOptions struct { |
23 | 23 |
|
24 | 24 |
func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command { |
25 | 25 |
createOpts := createOptions{ |
26 |
- labels: opts.NewListOpts(runconfigopts.ValidateEnv), |
|
26 |
+ labels: opts.NewListOpts(opts.ValidateEnv), |
|
27 | 27 |
} |
28 | 28 |
|
29 | 29 |
cmd := &cobra.Command{ |
... | ... |
@@ -304,7 +304,7 @@ type logDriverOptions struct { |
304 | 304 |
} |
305 | 305 |
|
306 | 306 |
func newLogDriverOptions() logDriverOptions { |
307 |
- return logDriverOptions{opts: opts.NewListOpts(runconfigopts.ValidateEnv)} |
|
307 |
+ return logDriverOptions{opts: opts.NewListOpts(opts.ValidateEnv)} |
|
308 | 308 |
} |
309 | 309 |
|
310 | 310 |
func (ldo *logDriverOptions) toLogDriver() *swarm.Driver { |
... | ... |
@@ -426,17 +426,17 @@ type serviceOptions struct { |
426 | 426 |
|
427 | 427 |
func newServiceOptions() *serviceOptions { |
428 | 428 |
return &serviceOptions{ |
429 |
- labels: opts.NewListOpts(runconfigopts.ValidateEnv), |
|
429 |
+ labels: opts.NewListOpts(opts.ValidateEnv), |
|
430 | 430 |
constraints: opts.NewListOpts(nil), |
431 |
- containerLabels: opts.NewListOpts(runconfigopts.ValidateEnv), |
|
432 |
- env: opts.NewListOpts(runconfigopts.ValidateEnv), |
|
431 |
+ containerLabels: opts.NewListOpts(opts.ValidateEnv), |
|
432 |
+ env: opts.NewListOpts(opts.ValidateEnv), |
|
433 | 433 |
envFile: opts.NewListOpts(nil), |
434 | 434 |
groups: opts.NewListOpts(nil), |
435 | 435 |
logDriver: newLogDriverOptions(), |
436 | 436 |
dns: opts.NewListOpts(opts.ValidateIPAddress), |
437 | 437 |
dnsOption: opts.NewListOpts(nil), |
438 | 438 |
dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), |
439 |
- hosts: opts.NewListOpts(runconfigopts.ValidateExtraHost), |
|
439 |
+ hosts: opts.NewListOpts(opts.ValidateExtraHost), |
|
440 | 440 |
networks: opts.NewListOpts(nil), |
441 | 441 |
} |
442 | 442 |
} |
... | ... |
@@ -3,14 +3,13 @@ package volume |
3 | 3 |
import ( |
4 | 4 |
"fmt" |
5 | 5 |
|
6 |
- "golang.org/x/net/context" |
|
7 |
- |
|
8 | 6 |
volumetypes "github.com/docker/docker/api/types/volume" |
9 | 7 |
"github.com/docker/docker/cli" |
10 | 8 |
"github.com/docker/docker/cli/command" |
11 | 9 |
"github.com/docker/docker/opts" |
12 | 10 |
runconfigopts "github.com/docker/docker/runconfig/opts" |
13 | 11 |
"github.com/spf13/cobra" |
12 |
+ "golang.org/x/net/context" |
|
14 | 13 |
) |
15 | 14 |
|
16 | 15 |
type createOptions struct { |
... | ... |
@@ -23,7 +22,7 @@ type createOptions struct { |
23 | 23 |
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command { |
24 | 24 |
opts := createOptions{ |
25 | 25 |
driverOpts: *opts.NewMapOpts(nil, nil), |
26 |
- labels: opts.NewListOpts(runconfigopts.ValidateEnv), |
|
26 |
+ labels: opts.NewListOpts(opts.ValidateEnv), |
|
27 | 27 |
} |
28 | 28 |
|
29 | 29 |
cmd := &cobra.Command{ |
... | ... |
@@ -28,6 +28,7 @@ import ( |
28 | 28 |
"github.com/docker/docker/image" |
29 | 29 |
"github.com/docker/docker/layer" |
30 | 30 |
"github.com/docker/docker/libcontainerd" |
31 |
+ "github.com/docker/docker/opts" |
|
31 | 32 |
"github.com/docker/docker/pkg/idtools" |
32 | 33 |
"github.com/docker/docker/pkg/ioutils" |
33 | 34 |
"github.com/docker/docker/pkg/promise" |
... | ... |
@@ -35,7 +36,6 @@ import ( |
35 | 35 |
"github.com/docker/docker/pkg/symlink" |
36 | 36 |
"github.com/docker/docker/restartmanager" |
37 | 37 |
"github.com/docker/docker/runconfig" |
38 |
- runconfigopts "github.com/docker/docker/runconfig/opts" |
|
39 | 38 |
"github.com/docker/docker/volume" |
40 | 39 |
"github.com/docker/go-connections/nat" |
41 | 40 |
"github.com/docker/libnetwork" |
... | ... |
@@ -815,7 +815,7 @@ func (container *Container) BuildJoinOptions(n libnetwork.Network) ([]libnetwork |
815 | 815 |
var joinOptions []libnetwork.EndpointOption |
816 | 816 |
if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok { |
817 | 817 |
for _, str := range epConfig.Links { |
818 |
- name, alias, err := runconfigopts.ParseLink(str) |
|
818 |
+ name, alias, err := opts.ParseLink(str) |
|
819 | 819 |
if err != nil { |
820 | 820 |
return nil, err |
821 | 821 |
} |
... | ... |
@@ -7,7 +7,6 @@ import ( |
7 | 7 |
|
8 | 8 |
"github.com/docker/docker/api/types" |
9 | 9 |
"github.com/docker/docker/opts" |
10 |
- runconfigopts "github.com/docker/docker/runconfig/opts" |
|
11 | 10 |
"github.com/spf13/pflag" |
12 | 11 |
) |
13 | 12 |
|
... | ... |
@@ -41,7 +40,7 @@ func (config *Config) InstallCommonUnixFlags(flags *pflag.FlagSet) { |
41 | 41 |
flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv6, ""), "default-gateway-v6", "Container default gateway IPv6 address") |
42 | 42 |
flags.BoolVar(&config.bridgeConfig.InterContainerCommunication, "icc", true, "Enable inter-container communication") |
43 | 43 |
flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultIP, "0.0.0.0"), "ip", "Default IP when binding container ports") |
44 |
- flags.Var(runconfigopts.NewNamedRuntimeOpt("runtimes", &config.Runtimes, stockRuntimeName), "add-runtime", "Register an additional OCI compatible runtime") |
|
44 |
+ flags.Var(opts.NewNamedRuntimeOpt("runtimes", &config.Runtimes, stockRuntimeName), "add-runtime", "Register an additional OCI compatible runtime") |
|
45 | 45 |
flags.StringVar(&config.DefaultRuntime, "default-runtime", stockRuntimeName, "Default OCI runtime for containers") |
46 | 46 |
|
47 | 47 |
} |
... | ... |
@@ -5,7 +5,7 @@ package daemon |
5 | 5 |
import ( |
6 | 6 |
"fmt" |
7 | 7 |
|
8 |
- runconfigopts "github.com/docker/docker/runconfig/opts" |
|
8 |
+ "github.com/docker/docker/opts" |
|
9 | 9 |
units "github.com/docker/go-units" |
10 | 10 |
"github.com/spf13/pflag" |
11 | 11 |
) |
... | ... |
@@ -68,7 +68,7 @@ func (config *Config) InstallFlags(flags *pflag.FlagSet) { |
68 | 68 |
|
69 | 69 |
// Then platform-specific install flags |
70 | 70 |
flags.BoolVar(&config.EnableSelinuxSupport, "selinux-enabled", false, "Enable selinux support") |
71 |
- flags.Var(runconfigopts.NewUlimitOpt(&config.Ulimits), "default-ulimit", "Default ulimits for containers") |
|
71 |
+ flags.Var(opts.NewUlimitOpt(&config.Ulimits), "default-ulimit", "Default ulimits for containers") |
|
72 | 72 |
flags.BoolVar(&config.bridgeConfig.EnableIPTables, "iptables", true, "Enable addition of iptables rules") |
73 | 73 |
flags.BoolVar(&config.bridgeConfig.EnableIPForward, "ip-forward", true, "Enable net.ipv4.ip_forward") |
74 | 74 |
flags.BoolVar(&config.bridgeConfig.EnableIPMasq, "ip-masq", true, "Enable IP masquerading") |
... | ... |
@@ -12,10 +12,10 @@ import ( |
12 | 12 |
"github.com/docker/docker/container" |
13 | 13 |
"github.com/docker/docker/daemon/network" |
14 | 14 |
"github.com/docker/docker/image" |
15 |
+ "github.com/docker/docker/opts" |
|
15 | 16 |
"github.com/docker/docker/pkg/signal" |
16 | 17 |
"github.com/docker/docker/pkg/system" |
17 | 18 |
"github.com/docker/docker/pkg/truncindex" |
18 |
- "github.com/docker/docker/runconfig/opts" |
|
19 | 19 |
"github.com/docker/go-connections/nat" |
20 | 20 |
) |
21 | 21 |
|
... | ... |
@@ -23,12 +23,12 @@ import ( |
23 | 23 |
containertypes "github.com/docker/docker/api/types/container" |
24 | 24 |
"github.com/docker/docker/container" |
25 | 25 |
"github.com/docker/docker/image" |
26 |
+ "github.com/docker/docker/opts" |
|
26 | 27 |
"github.com/docker/docker/pkg/idtools" |
27 | 28 |
"github.com/docker/docker/pkg/parsers" |
28 | 29 |
"github.com/docker/docker/pkg/parsers/kernel" |
29 | 30 |
"github.com/docker/docker/pkg/sysinfo" |
30 | 31 |
"github.com/docker/docker/runconfig" |
31 |
- runconfigopts "github.com/docker/docker/runconfig/opts" |
|
32 | 32 |
"github.com/docker/libnetwork" |
33 | 33 |
nwconfig "github.com/docker/libnetwork/config" |
34 | 34 |
"github.com/docker/libnetwork/drivers/bridge" |
... | ... |
@@ -1098,7 +1098,7 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig * |
1098 | 1098 |
} |
1099 | 1099 |
|
1100 | 1100 |
for _, l := range hostConfig.Links { |
1101 |
- name, alias, err := runconfigopts.ParseLink(l) |
|
1101 |
+ name, alias, err := opts.ParseLink(l) |
|
1102 | 1102 |
if err != nil { |
1103 | 1103 |
return err |
1104 | 1104 |
} |
1105 | 1105 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,46 @@ |
0 |
+package opts |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ "os" |
|
5 |
+ "runtime" |
|
6 |
+ "strings" |
|
7 |
+) |
|
8 |
+ |
|
9 |
+// ValidateEnv validates an environment variable and returns it. |
|
10 |
+// If no value is specified, it returns the current value using os.Getenv. |
|
11 |
+// |
|
12 |
+// As on ParseEnvFile and related to #16585, environment variable names |
|
13 |
+// are not validate what so ever, it's up to application inside docker |
|
14 |
+// to validate them or not. |
|
15 |
+// |
|
16 |
+// The only validation here is to check if name is empty, per #25099 |
|
17 |
+func ValidateEnv(val string) (string, error) { |
|
18 |
+ arr := strings.Split(val, "=") |
|
19 |
+ if arr[0] == "" { |
|
20 |
+ return "", fmt.Errorf("invalid environment variable: %s", val) |
|
21 |
+ } |
|
22 |
+ if len(arr) > 1 { |
|
23 |
+ return val, nil |
|
24 |
+ } |
|
25 |
+ if !doesEnvExist(val) { |
|
26 |
+ return val, nil |
|
27 |
+ } |
|
28 |
+ return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil |
|
29 |
+} |
|
30 |
+ |
|
31 |
+func doesEnvExist(name string) bool { |
|
32 |
+ for _, entry := range os.Environ() { |
|
33 |
+ parts := strings.SplitN(entry, "=", 2) |
|
34 |
+ if runtime.GOOS == "windows" { |
|
35 |
+ // Environment variable are case-insensitive on Windows. PaTh, path and PATH are equivalent. |
|
36 |
+ if strings.EqualFold(parts[0], name) { |
|
37 |
+ return true |
|
38 |
+ } |
|
39 |
+ } |
|
40 |
+ if parts[0] == name { |
|
41 |
+ return true |
|
42 |
+ } |
|
43 |
+ } |
|
44 |
+ return false |
|
45 |
+} |
0 | 46 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,42 @@ |
0 |
+package opts |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ "os" |
|
5 |
+ "runtime" |
|
6 |
+ "testing" |
|
7 |
+) |
|
8 |
+ |
|
9 |
+func TestValidateEnv(t *testing.T) { |
|
10 |
+ valids := map[string]string{ |
|
11 |
+ "a": "a", |
|
12 |
+ "something": "something", |
|
13 |
+ "_=a": "_=a", |
|
14 |
+ "env1=value1": "env1=value1", |
|
15 |
+ "_env1=value1": "_env1=value1", |
|
16 |
+ "env2=value2=value3": "env2=value2=value3", |
|
17 |
+ "env3=abc!qwe": "env3=abc!qwe", |
|
18 |
+ "env_4=value 4": "env_4=value 4", |
|
19 |
+ "PATH": fmt.Sprintf("PATH=%v", os.Getenv("PATH")), |
|
20 |
+ "PATH=something": "PATH=something", |
|
21 |
+ "asd!qwe": "asd!qwe", |
|
22 |
+ "1asd": "1asd", |
|
23 |
+ "123": "123", |
|
24 |
+ "some space": "some space", |
|
25 |
+ " some space before": " some space before", |
|
26 |
+ "some space after ": "some space after ", |
|
27 |
+ } |
|
28 |
+ // Environment variables are case in-sensitive on Windows |
|
29 |
+ if runtime.GOOS == "windows" { |
|
30 |
+ valids["PaTh"] = fmt.Sprintf("PaTh=%v", os.Getenv("PATH")) |
|
31 |
+ } |
|
32 |
+ for value, expected := range valids { |
|
33 |
+ actual, err := ValidateEnv(value) |
|
34 |
+ if err != nil { |
|
35 |
+ t.Fatal(err) |
|
36 |
+ } |
|
37 |
+ if actual != expected { |
|
38 |
+ t.Fatalf("Expected [%v], got [%v]", expected, actual) |
|
39 |
+ } |
|
40 |
+ } |
|
41 |
+} |
... | ... |
@@ -149,3 +149,17 @@ func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) { |
149 | 149 |
|
150 | 150 |
return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil |
151 | 151 |
} |
152 |
+ |
|
153 |
+// ValidateExtraHost validates that the specified string is a valid extrahost and returns it. |
|
154 |
+// ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6). |
|
155 |
+func ValidateExtraHost(val string) (string, error) { |
|
156 |
+ // allow for IPv6 addresses in extra hosts by only splitting on first ":" |
|
157 |
+ arr := strings.SplitN(val, ":", 2) |
|
158 |
+ if len(arr) != 2 || len(arr[0]) == 0 { |
|
159 |
+ return "", fmt.Errorf("bad format for add-host: %q", val) |
|
160 |
+ } |
|
161 |
+ if _, err := ValidateIPAddress(arr[1]); err != nil { |
|
162 |
+ return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) |
|
163 |
+ } |
|
164 |
+ return val, nil |
|
165 |
+} |
... | ... |
@@ -2,6 +2,7 @@ package opts |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"fmt" |
5 |
+ "strings" |
|
5 | 6 |
"testing" |
6 | 7 |
) |
7 | 8 |
|
... | ... |
@@ -146,3 +147,35 @@ func TestParseInvalidUnixAddrInvalid(t *testing.T) { |
146 | 146 |
t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock") |
147 | 147 |
} |
148 | 148 |
} |
149 |
+ |
|
150 |
+func TestValidateExtraHosts(t *testing.T) { |
|
151 |
+ valid := []string{ |
|
152 |
+ `myhost:192.168.0.1`, |
|
153 |
+ `thathost:10.0.2.1`, |
|
154 |
+ `anipv6host:2003:ab34:e::1`, |
|
155 |
+ `ipv6local:::1`, |
|
156 |
+ } |
|
157 |
+ |
|
158 |
+ invalid := map[string]string{ |
|
159 |
+ `myhost:192.notanipaddress.1`: `invalid IP`, |
|
160 |
+ `thathost-nosemicolon10.0.0.1`: `bad format`, |
|
161 |
+ `anipv6host:::::1`: `invalid IP`, |
|
162 |
+ `ipv6local:::0::`: `invalid IP`, |
|
163 |
+ } |
|
164 |
+ |
|
165 |
+ for _, extrahost := range valid { |
|
166 |
+ if _, err := ValidateExtraHost(extrahost); err != nil { |
|
167 |
+ t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err) |
|
168 |
+ } |
|
169 |
+ } |
|
170 |
+ |
|
171 |
+ for extraHost, expectedError := range invalid { |
|
172 |
+ if _, err := ValidateExtraHost(extraHost); err == nil { |
|
173 |
+ t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost) |
|
174 |
+ } else { |
|
175 |
+ if !strings.Contains(err.Error(), expectedError) { |
|
176 |
+ t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError) |
|
177 |
+ } |
|
178 |
+ } |
|
179 |
+ } |
|
180 |
+} |
... | ... |
@@ -4,6 +4,7 @@ import ( |
4 | 4 |
"fmt" |
5 | 5 |
"math/big" |
6 | 6 |
"net" |
7 |
+ "path" |
|
7 | 8 |
"regexp" |
8 | 9 |
"strings" |
9 | 10 |
|
... | ... |
@@ -231,6 +232,15 @@ func ValidateIPAddress(val string) (string, error) { |
231 | 231 |
return "", fmt.Errorf("%s is not an ip address", val) |
232 | 232 |
} |
233 | 233 |
|
234 |
+// ValidateMACAddress validates a MAC address. |
|
235 |
+func ValidateMACAddress(val string) (string, error) { |
|
236 |
+ _, err := net.ParseMAC(strings.TrimSpace(val)) |
|
237 |
+ if err != nil { |
|
238 |
+ return "", err |
|
239 |
+ } |
|
240 |
+ return val, nil |
|
241 |
+} |
|
242 |
+ |
|
234 | 243 |
// ValidateDNSSearch validates domain for resolvconf search configuration. |
235 | 244 |
// A zero length domain is represented by a dot (.). |
236 | 245 |
func ValidateDNSSearch(val string) (string, error) { |
... | ... |
@@ -364,3 +374,31 @@ func ParseCPUs(value string) (int64, error) { |
364 | 364 |
} |
365 | 365 |
return nano.Num().Int64(), nil |
366 | 366 |
} |
367 |
+ |
|
368 |
+// ParseLink parses and validates the specified string as a link format (name:alias) |
|
369 |
+func ParseLink(val string) (string, string, error) { |
|
370 |
+ if val == "" { |
|
371 |
+ return "", "", fmt.Errorf("empty string specified for links") |
|
372 |
+ } |
|
373 |
+ arr := strings.Split(val, ":") |
|
374 |
+ if len(arr) > 2 { |
|
375 |
+ return "", "", fmt.Errorf("bad format for links: %s", val) |
|
376 |
+ } |
|
377 |
+ if len(arr) == 1 { |
|
378 |
+ return val, val, nil |
|
379 |
+ } |
|
380 |
+ // This is kept because we can actually get a HostConfig with links |
|
381 |
+ // from an already created container and the format is not `foo:bar` |
|
382 |
+ // but `/foo:/c1/bar` |
|
383 |
+ if strings.HasPrefix(arr[0], "/") { |
|
384 |
+ _, alias := path.Split(arr[1]) |
|
385 |
+ return arr[0][1:], alias, nil |
|
386 |
+ } |
|
387 |
+ return arr[0], arr[1], nil |
|
388 |
+} |
|
389 |
+ |
|
390 |
+// ValidateLink validates that the specified string has a valid link format (containerName:alias). |
|
391 |
+func ValidateLink(val string) (string, error) { |
|
392 |
+ _, _, err := ParseLink(val) |
|
393 |
+ return val, err |
|
394 |
+} |
... | ... |
@@ -230,3 +230,78 @@ func TestNamedMapOpts(t *testing.T) { |
230 | 230 |
t.Errorf("expected map-size to be in the values, got %v", tmpMap) |
231 | 231 |
} |
232 | 232 |
} |
233 |
+ |
|
234 |
+func TestValidateMACAddress(t *testing.T) { |
|
235 |
+ if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil { |
|
236 |
+ t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err) |
|
237 |
+ } |
|
238 |
+ |
|
239 |
+ if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil { |
|
240 |
+ t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC") |
|
241 |
+ } |
|
242 |
+ |
|
243 |
+ if _, err := ValidateMACAddress(`random invalid string`); err == nil { |
|
244 |
+ t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC") |
|
245 |
+ } |
|
246 |
+} |
|
247 |
+ |
|
248 |
+func TestValidateLink(t *testing.T) { |
|
249 |
+ valid := []string{ |
|
250 |
+ "name", |
|
251 |
+ "dcdfbe62ecd0:alias", |
|
252 |
+ "7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da", |
|
253 |
+ "angry_torvalds:linus", |
|
254 |
+ } |
|
255 |
+ invalid := map[string]string{ |
|
256 |
+ "": "empty string specified for links", |
|
257 |
+ "too:much:of:it": "bad format for links: too:much:of:it", |
|
258 |
+ } |
|
259 |
+ |
|
260 |
+ for _, link := range valid { |
|
261 |
+ if _, err := ValidateLink(link); err != nil { |
|
262 |
+ t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err) |
|
263 |
+ } |
|
264 |
+ } |
|
265 |
+ |
|
266 |
+ for link, expectedError := range invalid { |
|
267 |
+ if _, err := ValidateLink(link); err == nil { |
|
268 |
+ t.Fatalf("ValidateLink(`%q`) should have failed validation", link) |
|
269 |
+ } else { |
|
270 |
+ if !strings.Contains(err.Error(), expectedError) { |
|
271 |
+ t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError) |
|
272 |
+ } |
|
273 |
+ } |
|
274 |
+ } |
|
275 |
+} |
|
276 |
+ |
|
277 |
+func TestParseLink(t *testing.T) { |
|
278 |
+ name, alias, err := ParseLink("name:alias") |
|
279 |
+ if err != nil { |
|
280 |
+ t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err) |
|
281 |
+ } |
|
282 |
+ if name != "name" { |
|
283 |
+ t.Fatalf("Link name should have been name, got %s instead", name) |
|
284 |
+ } |
|
285 |
+ if alias != "alias" { |
|
286 |
+ t.Fatalf("Link alias should have been alias, got %s instead", alias) |
|
287 |
+ } |
|
288 |
+ // short format definition |
|
289 |
+ name, alias, err = ParseLink("name") |
|
290 |
+ if err != nil { |
|
291 |
+ t.Fatalf("Expected not to error out on a valid name only format but got: %v", err) |
|
292 |
+ } |
|
293 |
+ if name != "name" { |
|
294 |
+ t.Fatalf("Link name should have been name, got %s instead", name) |
|
295 |
+ } |
|
296 |
+ if alias != "name" { |
|
297 |
+ t.Fatalf("Link alias should have been name, got %s instead", alias) |
|
298 |
+ } |
|
299 |
+ // empty string link definition is not allowed |
|
300 |
+ if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") { |
|
301 |
+ t.Fatalf("Expected error 'empty string specified for links' but got: %v", err) |
|
302 |
+ } |
|
303 |
+ // more than two colons are not allowed |
|
304 |
+ if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") { |
|
305 |
+ t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err) |
|
306 |
+ } |
|
307 |
+} |
233 | 308 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,79 @@ |
0 |
+package opts |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ "strings" |
|
5 |
+ |
|
6 |
+ "github.com/docker/docker/api/types" |
|
7 |
+) |
|
8 |
+ |
|
9 |
+// RuntimeOpt defines a map of Runtimes |
|
10 |
+type RuntimeOpt struct { |
|
11 |
+ name string |
|
12 |
+ stockRuntimeName string |
|
13 |
+ values *map[string]types.Runtime |
|
14 |
+} |
|
15 |
+ |
|
16 |
+// NewNamedRuntimeOpt creates a new RuntimeOpt |
|
17 |
+func NewNamedRuntimeOpt(name string, ref *map[string]types.Runtime, stockRuntime string) *RuntimeOpt { |
|
18 |
+ if ref == nil { |
|
19 |
+ ref = &map[string]types.Runtime{} |
|
20 |
+ } |
|
21 |
+ return &RuntimeOpt{name: name, values: ref, stockRuntimeName: stockRuntime} |
|
22 |
+} |
|
23 |
+ |
|
24 |
+// Name returns the name of the NamedListOpts in the configuration. |
|
25 |
+func (o *RuntimeOpt) Name() string { |
|
26 |
+ return o.name |
|
27 |
+} |
|
28 |
+ |
|
29 |
+// Set validates and updates the list of Runtimes |
|
30 |
+func (o *RuntimeOpt) Set(val string) error { |
|
31 |
+ parts := strings.SplitN(val, "=", 2) |
|
32 |
+ if len(parts) != 2 { |
|
33 |
+ return fmt.Errorf("invalid runtime argument: %s", val) |
|
34 |
+ } |
|
35 |
+ |
|
36 |
+ parts[0] = strings.TrimSpace(parts[0]) |
|
37 |
+ parts[1] = strings.TrimSpace(parts[1]) |
|
38 |
+ if parts[0] == "" || parts[1] == "" { |
|
39 |
+ return fmt.Errorf("invalid runtime argument: %s", val) |
|
40 |
+ } |
|
41 |
+ |
|
42 |
+ parts[0] = strings.ToLower(parts[0]) |
|
43 |
+ if parts[0] == o.stockRuntimeName { |
|
44 |
+ return fmt.Errorf("runtime name '%s' is reserved", o.stockRuntimeName) |
|
45 |
+ } |
|
46 |
+ |
|
47 |
+ if _, ok := (*o.values)[parts[0]]; ok { |
|
48 |
+ return fmt.Errorf("runtime '%s' was already defined", parts[0]) |
|
49 |
+ } |
|
50 |
+ |
|
51 |
+ (*o.values)[parts[0]] = types.Runtime{Path: parts[1]} |
|
52 |
+ |
|
53 |
+ return nil |
|
54 |
+} |
|
55 |
+ |
|
56 |
+// String returns Runtime values as a string. |
|
57 |
+func (o *RuntimeOpt) String() string { |
|
58 |
+ var out []string |
|
59 |
+ for k := range *o.values { |
|
60 |
+ out = append(out, k) |
|
61 |
+ } |
|
62 |
+ |
|
63 |
+ return fmt.Sprintf("%v", out) |
|
64 |
+} |
|
65 |
+ |
|
66 |
+// GetMap returns a map of Runtimes (name: path) |
|
67 |
+func (o *RuntimeOpt) GetMap() map[string]types.Runtime { |
|
68 |
+ if o.values != nil { |
|
69 |
+ return *o.values |
|
70 |
+ } |
|
71 |
+ |
|
72 |
+ return map[string]types.Runtime{} |
|
73 |
+} |
|
74 |
+ |
|
75 |
+// Type returns the type of the option |
|
76 |
+func (o *RuntimeOpt) Type() string { |
|
77 |
+ return "runtime" |
|
78 |
+} |
0 | 79 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,111 @@ |
0 |
+package opts |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ "strconv" |
|
5 |
+ "strings" |
|
6 |
+ |
|
7 |
+ "github.com/docker/docker/api/types/blkiodev" |
|
8 |
+ "github.com/docker/go-units" |
|
9 |
+) |
|
10 |
+ |
|
11 |
+// ValidatorThrottleFctType defines a validator function that returns a validated struct and/or an error. |
|
12 |
+type ValidatorThrottleFctType func(val string) (*blkiodev.ThrottleDevice, error) |
|
13 |
+ |
|
14 |
+// ValidateThrottleBpsDevice validates that the specified string has a valid device-rate format. |
|
15 |
+func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) { |
|
16 |
+ split := strings.SplitN(val, ":", 2) |
|
17 |
+ if len(split) != 2 { |
|
18 |
+ return nil, fmt.Errorf("bad format: %s", val) |
|
19 |
+ } |
|
20 |
+ if !strings.HasPrefix(split[0], "/dev/") { |
|
21 |
+ return nil, fmt.Errorf("bad format for device path: %s", val) |
|
22 |
+ } |
|
23 |
+ rate, err := units.RAMInBytes(split[1]) |
|
24 |
+ if err != nil { |
|
25 |
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) |
|
26 |
+ } |
|
27 |
+ if rate < 0 { |
|
28 |
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) |
|
29 |
+ } |
|
30 |
+ |
|
31 |
+ return &blkiodev.ThrottleDevice{ |
|
32 |
+ Path: split[0], |
|
33 |
+ Rate: uint64(rate), |
|
34 |
+ }, nil |
|
35 |
+} |
|
36 |
+ |
|
37 |
+// ValidateThrottleIOpsDevice validates that the specified string has a valid device-rate format. |
|
38 |
+func ValidateThrottleIOpsDevice(val string) (*blkiodev.ThrottleDevice, error) { |
|
39 |
+ split := strings.SplitN(val, ":", 2) |
|
40 |
+ if len(split) != 2 { |
|
41 |
+ return nil, fmt.Errorf("bad format: %s", val) |
|
42 |
+ } |
|
43 |
+ if !strings.HasPrefix(split[0], "/dev/") { |
|
44 |
+ return nil, fmt.Errorf("bad format for device path: %s", val) |
|
45 |
+ } |
|
46 |
+ rate, err := strconv.ParseUint(split[1], 10, 64) |
|
47 |
+ if err != nil { |
|
48 |
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val) |
|
49 |
+ } |
|
50 |
+ if rate < 0 { |
|
51 |
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val) |
|
52 |
+ } |
|
53 |
+ |
|
54 |
+ return &blkiodev.ThrottleDevice{ |
|
55 |
+ Path: split[0], |
|
56 |
+ Rate: uint64(rate), |
|
57 |
+ }, nil |
|
58 |
+} |
|
59 |
+ |
|
60 |
+// ThrottledeviceOpt defines a map of ThrottleDevices |
|
61 |
+type ThrottledeviceOpt struct { |
|
62 |
+ values []*blkiodev.ThrottleDevice |
|
63 |
+ validator ValidatorThrottleFctType |
|
64 |
+} |
|
65 |
+ |
|
66 |
+// NewThrottledeviceOpt creates a new ThrottledeviceOpt |
|
67 |
+func NewThrottledeviceOpt(validator ValidatorThrottleFctType) ThrottledeviceOpt { |
|
68 |
+ values := []*blkiodev.ThrottleDevice{} |
|
69 |
+ return ThrottledeviceOpt{ |
|
70 |
+ values: values, |
|
71 |
+ validator: validator, |
|
72 |
+ } |
|
73 |
+} |
|
74 |
+ |
|
75 |
+// Set validates a ThrottleDevice and sets its name as a key in ThrottledeviceOpt |
|
76 |
+func (opt *ThrottledeviceOpt) Set(val string) error { |
|
77 |
+ var value *blkiodev.ThrottleDevice |
|
78 |
+ if opt.validator != nil { |
|
79 |
+ v, err := opt.validator(val) |
|
80 |
+ if err != nil { |
|
81 |
+ return err |
|
82 |
+ } |
|
83 |
+ value = v |
|
84 |
+ } |
|
85 |
+ (opt.values) = append((opt.values), value) |
|
86 |
+ return nil |
|
87 |
+} |
|
88 |
+ |
|
89 |
+// String returns ThrottledeviceOpt values as a string. |
|
90 |
+func (opt *ThrottledeviceOpt) String() string { |
|
91 |
+ var out []string |
|
92 |
+ for _, v := range opt.values { |
|
93 |
+ out = append(out, v.String()) |
|
94 |
+ } |
|
95 |
+ |
|
96 |
+ return fmt.Sprintf("%v", out) |
|
97 |
+} |
|
98 |
+ |
|
99 |
+// GetList returns a slice of pointers to ThrottleDevices. |
|
100 |
+func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice { |
|
101 |
+ var throttledevice []*blkiodev.ThrottleDevice |
|
102 |
+ throttledevice = append(throttledevice, opt.values...) |
|
103 |
+ |
|
104 |
+ return throttledevice |
|
105 |
+} |
|
106 |
+ |
|
107 |
+// Type returns the option type |
|
108 |
+func (opt *ThrottledeviceOpt) Type() string { |
|
109 |
+ return "throttled-device" |
|
110 |
+} |
0 | 111 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,57 @@ |
0 |
+package opts |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ |
|
5 |
+ "github.com/docker/go-units" |
|
6 |
+) |
|
7 |
+ |
|
8 |
+// UlimitOpt defines a map of Ulimits |
|
9 |
+type UlimitOpt struct { |
|
10 |
+ values *map[string]*units.Ulimit |
|
11 |
+} |
|
12 |
+ |
|
13 |
+// NewUlimitOpt creates a new UlimitOpt |
|
14 |
+func NewUlimitOpt(ref *map[string]*units.Ulimit) *UlimitOpt { |
|
15 |
+ if ref == nil { |
|
16 |
+ ref = &map[string]*units.Ulimit{} |
|
17 |
+ } |
|
18 |
+ return &UlimitOpt{ref} |
|
19 |
+} |
|
20 |
+ |
|
21 |
+// Set validates a Ulimit and sets its name as a key in UlimitOpt |
|
22 |
+func (o *UlimitOpt) Set(val string) error { |
|
23 |
+ l, err := units.ParseUlimit(val) |
|
24 |
+ if err != nil { |
|
25 |
+ return err |
|
26 |
+ } |
|
27 |
+ |
|
28 |
+ (*o.values)[l.Name] = l |
|
29 |
+ |
|
30 |
+ return nil |
|
31 |
+} |
|
32 |
+ |
|
33 |
+// String returns Ulimit values as a string. |
|
34 |
+func (o *UlimitOpt) String() string { |
|
35 |
+ var out []string |
|
36 |
+ for _, v := range *o.values { |
|
37 |
+ out = append(out, v.String()) |
|
38 |
+ } |
|
39 |
+ |
|
40 |
+ return fmt.Sprintf("%v", out) |
|
41 |
+} |
|
42 |
+ |
|
43 |
+// GetList returns a slice of pointers to Ulimits. |
|
44 |
+func (o *UlimitOpt) GetList() []*units.Ulimit { |
|
45 |
+ var ulimits []*units.Ulimit |
|
46 |
+ for _, v := range *o.values { |
|
47 |
+ ulimits = append(ulimits, v) |
|
48 |
+ } |
|
49 |
+ |
|
50 |
+ return ulimits |
|
51 |
+} |
|
52 |
+ |
|
53 |
+// Type returns the option type |
|
54 |
+func (o *UlimitOpt) Type() string { |
|
55 |
+ return "ulimit" |
|
56 |
+} |
0 | 57 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,42 @@ |
0 |
+package opts |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "testing" |
|
4 |
+ |
|
5 |
+ "github.com/docker/go-units" |
|
6 |
+) |
|
7 |
+ |
|
8 |
+func TestUlimitOpt(t *testing.T) { |
|
9 |
+ ulimitMap := map[string]*units.Ulimit{ |
|
10 |
+ "nofile": {"nofile", 1024, 512}, |
|
11 |
+ } |
|
12 |
+ |
|
13 |
+ ulimitOpt := NewUlimitOpt(&ulimitMap) |
|
14 |
+ |
|
15 |
+ expected := "[nofile=512:1024]" |
|
16 |
+ if ulimitOpt.String() != expected { |
|
17 |
+ t.Fatalf("Expected %v, got %v", expected, ulimitOpt) |
|
18 |
+ } |
|
19 |
+ |
|
20 |
+ // Valid ulimit append to opts |
|
21 |
+ if err := ulimitOpt.Set("core=1024:1024"); err != nil { |
|
22 |
+ t.Fatal(err) |
|
23 |
+ } |
|
24 |
+ |
|
25 |
+ // Invalid ulimit type returns an error and do not append to opts |
|
26 |
+ if err := ulimitOpt.Set("notavalidtype=1024:1024"); err == nil { |
|
27 |
+ t.Fatalf("Expected error on invalid ulimit type") |
|
28 |
+ } |
|
29 |
+ expected = "[nofile=512:1024 core=1024:1024]" |
|
30 |
+ expected2 := "[core=1024:1024 nofile=512:1024]" |
|
31 |
+ result := ulimitOpt.String() |
|
32 |
+ if result != expected && result != expected2 { |
|
33 |
+ t.Fatalf("Expected %v or %v, got %v", expected, expected2, ulimitOpt) |
|
34 |
+ } |
|
35 |
+ |
|
36 |
+ // And test GetList |
|
37 |
+ ulimits := ulimitOpt.GetList() |
|
38 |
+ if len(ulimits) != 2 { |
|
39 |
+ t.Fatalf("Expected a ulimit list of 2, got %v", ulimits) |
|
40 |
+ } |
|
41 |
+} |
0 | 42 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,89 @@ |
0 |
+package opts |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ "strconv" |
|
5 |
+ "strings" |
|
6 |
+ |
|
7 |
+ "github.com/docker/docker/api/types/blkiodev" |
|
8 |
+) |
|
9 |
+ |
|
10 |
+// ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error. |
|
11 |
+type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error) |
|
12 |
+ |
|
13 |
+// ValidateWeightDevice validates that the specified string has a valid device-weight format. |
|
14 |
+func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) { |
|
15 |
+ split := strings.SplitN(val, ":", 2) |
|
16 |
+ if len(split) != 2 { |
|
17 |
+ return nil, fmt.Errorf("bad format: %s", val) |
|
18 |
+ } |
|
19 |
+ if !strings.HasPrefix(split[0], "/dev/") { |
|
20 |
+ return nil, fmt.Errorf("bad format for device path: %s", val) |
|
21 |
+ } |
|
22 |
+ weight, err := strconv.ParseUint(split[1], 10, 0) |
|
23 |
+ if err != nil { |
|
24 |
+ return nil, fmt.Errorf("invalid weight for device: %s", val) |
|
25 |
+ } |
|
26 |
+ if weight > 0 && (weight < 10 || weight > 1000) { |
|
27 |
+ return nil, fmt.Errorf("invalid weight for device: %s", val) |
|
28 |
+ } |
|
29 |
+ |
|
30 |
+ return &blkiodev.WeightDevice{ |
|
31 |
+ Path: split[0], |
|
32 |
+ Weight: uint16(weight), |
|
33 |
+ }, nil |
|
34 |
+} |
|
35 |
+ |
|
36 |
+// WeightdeviceOpt defines a map of WeightDevices |
|
37 |
+type WeightdeviceOpt struct { |
|
38 |
+ values []*blkiodev.WeightDevice |
|
39 |
+ validator ValidatorWeightFctType |
|
40 |
+} |
|
41 |
+ |
|
42 |
+// NewWeightdeviceOpt creates a new WeightdeviceOpt |
|
43 |
+func NewWeightdeviceOpt(validator ValidatorWeightFctType) WeightdeviceOpt { |
|
44 |
+ values := []*blkiodev.WeightDevice{} |
|
45 |
+ return WeightdeviceOpt{ |
|
46 |
+ values: values, |
|
47 |
+ validator: validator, |
|
48 |
+ } |
|
49 |
+} |
|
50 |
+ |
|
51 |
+// Set validates a WeightDevice and sets its name as a key in WeightdeviceOpt |
|
52 |
+func (opt *WeightdeviceOpt) Set(val string) error { |
|
53 |
+ var value *blkiodev.WeightDevice |
|
54 |
+ if opt.validator != nil { |
|
55 |
+ v, err := opt.validator(val) |
|
56 |
+ if err != nil { |
|
57 |
+ return err |
|
58 |
+ } |
|
59 |
+ value = v |
|
60 |
+ } |
|
61 |
+ (opt.values) = append((opt.values), value) |
|
62 |
+ return nil |
|
63 |
+} |
|
64 |
+ |
|
65 |
+// String returns WeightdeviceOpt values as a string. |
|
66 |
+func (opt *WeightdeviceOpt) String() string { |
|
67 |
+ var out []string |
|
68 |
+ for _, v := range opt.values { |
|
69 |
+ out = append(out, v.String()) |
|
70 |
+ } |
|
71 |
+ |
|
72 |
+ return fmt.Sprintf("%v", out) |
|
73 |
+} |
|
74 |
+ |
|
75 |
+// GetList returns a slice of pointers to WeightDevices. |
|
76 |
+func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice { |
|
77 |
+ var weightdevice []*blkiodev.WeightDevice |
|
78 |
+ for _, v := range opt.values { |
|
79 |
+ weightdevice = append(weightdevice, v) |
|
80 |
+ } |
|
81 |
+ |
|
82 |
+ return weightdevice |
|
83 |
+} |
|
84 |
+ |
|
85 |
+// Type returns the option type |
|
86 |
+func (opt *WeightdeviceOpt) Type() string { |
|
87 |
+ return "weighted-device" |
|
88 |
+} |
2 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,83 +0,0 @@ |
1 |
-package opts |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "fmt" |
|
5 |
- "net" |
|
6 |
- "os" |
|
7 |
- "runtime" |
|
8 |
- "strings" |
|
9 |
- |
|
10 |
- fopts "github.com/docker/docker/opts" |
|
11 |
-) |
|
12 |
- |
|
13 |
-// ValidateAttach validates that the specified string is a valid attach option. |
|
14 |
-func ValidateAttach(val string) (string, error) { |
|
15 |
- s := strings.ToLower(val) |
|
16 |
- for _, str := range []string{"stdin", "stdout", "stderr"} { |
|
17 |
- if s == str { |
|
18 |
- return s, nil |
|
19 |
- } |
|
20 |
- } |
|
21 |
- return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR") |
|
22 |
-} |
|
23 |
- |
|
24 |
-// ValidateEnv validates an environment variable and returns it. |
|
25 |
-// If no value is specified, it returns the current value using os.Getenv. |
|
26 |
-// |
|
27 |
-// As on ParseEnvFile and related to #16585, environment variable names |
|
28 |
-// are not validate what so ever, it's up to application inside docker |
|
29 |
-// to validate them or not. |
|
30 |
-// |
|
31 |
-// The only validation here is to check if name is empty, per #25099 |
|
32 |
-func ValidateEnv(val string) (string, error) { |
|
33 |
- arr := strings.Split(val, "=") |
|
34 |
- if arr[0] == "" { |
|
35 |
- return "", fmt.Errorf("invalid environment variable: %s", val) |
|
36 |
- } |
|
37 |
- if len(arr) > 1 { |
|
38 |
- return val, nil |
|
39 |
- } |
|
40 |
- if !doesEnvExist(val) { |
|
41 |
- return val, nil |
|
42 |
- } |
|
43 |
- return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil |
|
44 |
-} |
|
45 |
- |
|
46 |
-func doesEnvExist(name string) bool { |
|
47 |
- for _, entry := range os.Environ() { |
|
48 |
- parts := strings.SplitN(entry, "=", 2) |
|
49 |
- if runtime.GOOS == "windows" { |
|
50 |
- // Environment variable are case-insensitive on Windows. PaTh, path and PATH are equivalent. |
|
51 |
- if strings.EqualFold(parts[0], name) { |
|
52 |
- return true |
|
53 |
- } |
|
54 |
- } |
|
55 |
- if parts[0] == name { |
|
56 |
- return true |
|
57 |
- } |
|
58 |
- } |
|
59 |
- return false |
|
60 |
-} |
|
61 |
- |
|
62 |
-// ValidateExtraHost validates that the specified string is a valid extrahost and returns it. |
|
63 |
-// ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6). |
|
64 |
-func ValidateExtraHost(val string) (string, error) { |
|
65 |
- // allow for IPv6 addresses in extra hosts by only splitting on first ":" |
|
66 |
- arr := strings.SplitN(val, ":", 2) |
|
67 |
- if len(arr) != 2 || len(arr[0]) == 0 { |
|
68 |
- return "", fmt.Errorf("bad format for add-host: %q", val) |
|
69 |
- } |
|
70 |
- if _, err := fopts.ValidateIPAddress(arr[1]); err != nil { |
|
71 |
- return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) |
|
72 |
- } |
|
73 |
- return val, nil |
|
74 |
-} |
|
75 |
- |
|
76 |
-// ValidateMACAddress validates a MAC address. |
|
77 |
-func ValidateMACAddress(val string) (string, error) { |
|
78 |
- _, err := net.ParseMAC(strings.TrimSpace(val)) |
|
79 |
- if err != nil { |
|
80 |
- return "", err |
|
81 |
- } |
|
82 |
- return val, nil |
|
83 |
-} |
84 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,113 +0,0 @@ |
1 |
-package opts |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "fmt" |
|
5 |
- "os" |
|
6 |
- "runtime" |
|
7 |
- "strings" |
|
8 |
- "testing" |
|
9 |
-) |
|
10 |
- |
|
11 |
-func TestValidateAttach(t *testing.T) { |
|
12 |
- valid := []string{ |
|
13 |
- "stdin", |
|
14 |
- "stdout", |
|
15 |
- "stderr", |
|
16 |
- "STDIN", |
|
17 |
- "STDOUT", |
|
18 |
- "STDERR", |
|
19 |
- } |
|
20 |
- if _, err := ValidateAttach("invalid"); err == nil { |
|
21 |
- t.Fatalf("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing") |
|
22 |
- } |
|
23 |
- |
|
24 |
- for _, attach := range valid { |
|
25 |
- value, err := ValidateAttach(attach) |
|
26 |
- if err != nil { |
|
27 |
- t.Fatal(err) |
|
28 |
- } |
|
29 |
- if value != strings.ToLower(attach) { |
|
30 |
- t.Fatalf("Expected [%v], got [%v]", attach, value) |
|
31 |
- } |
|
32 |
- } |
|
33 |
-} |
|
34 |
- |
|
35 |
-func TestValidateEnv(t *testing.T) { |
|
36 |
- valids := map[string]string{ |
|
37 |
- "a": "a", |
|
38 |
- "something": "something", |
|
39 |
- "_=a": "_=a", |
|
40 |
- "env1=value1": "env1=value1", |
|
41 |
- "_env1=value1": "_env1=value1", |
|
42 |
- "env2=value2=value3": "env2=value2=value3", |
|
43 |
- "env3=abc!qwe": "env3=abc!qwe", |
|
44 |
- "env_4=value 4": "env_4=value 4", |
|
45 |
- "PATH": fmt.Sprintf("PATH=%v", os.Getenv("PATH")), |
|
46 |
- "PATH=something": "PATH=something", |
|
47 |
- "asd!qwe": "asd!qwe", |
|
48 |
- "1asd": "1asd", |
|
49 |
- "123": "123", |
|
50 |
- "some space": "some space", |
|
51 |
- " some space before": " some space before", |
|
52 |
- "some space after ": "some space after ", |
|
53 |
- } |
|
54 |
- // Environment variables are case in-sensitive on Windows |
|
55 |
- if runtime.GOOS == "windows" { |
|
56 |
- valids["PaTh"] = fmt.Sprintf("PaTh=%v", os.Getenv("PATH")) |
|
57 |
- } |
|
58 |
- for value, expected := range valids { |
|
59 |
- actual, err := ValidateEnv(value) |
|
60 |
- if err != nil { |
|
61 |
- t.Fatal(err) |
|
62 |
- } |
|
63 |
- if actual != expected { |
|
64 |
- t.Fatalf("Expected [%v], got [%v]", expected, actual) |
|
65 |
- } |
|
66 |
- } |
|
67 |
-} |
|
68 |
- |
|
69 |
-func TestValidateExtraHosts(t *testing.T) { |
|
70 |
- valid := []string{ |
|
71 |
- `myhost:192.168.0.1`, |
|
72 |
- `thathost:10.0.2.1`, |
|
73 |
- `anipv6host:2003:ab34:e::1`, |
|
74 |
- `ipv6local:::1`, |
|
75 |
- } |
|
76 |
- |
|
77 |
- invalid := map[string]string{ |
|
78 |
- `myhost:192.notanipaddress.1`: `invalid IP`, |
|
79 |
- `thathost-nosemicolon10.0.0.1`: `bad format`, |
|
80 |
- `anipv6host:::::1`: `invalid IP`, |
|
81 |
- `ipv6local:::0::`: `invalid IP`, |
|
82 |
- } |
|
83 |
- |
|
84 |
- for _, extrahost := range valid { |
|
85 |
- if _, err := ValidateExtraHost(extrahost); err != nil { |
|
86 |
- t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err) |
|
87 |
- } |
|
88 |
- } |
|
89 |
- |
|
90 |
- for extraHost, expectedError := range invalid { |
|
91 |
- if _, err := ValidateExtraHost(extraHost); err == nil { |
|
92 |
- t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost) |
|
93 |
- } else { |
|
94 |
- if !strings.Contains(err.Error(), expectedError) { |
|
95 |
- t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError) |
|
96 |
- } |
|
97 |
- } |
|
98 |
- } |
|
99 |
-} |
|
100 |
- |
|
101 |
-func TestValidateMACAddress(t *testing.T) { |
|
102 |
- if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil { |
|
103 |
- t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err) |
|
104 |
- } |
|
105 |
- |
|
106 |
- if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil { |
|
107 |
- t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC") |
|
108 |
- } |
|
109 |
- |
|
110 |
- if _, err := ValidateMACAddress(`random invalid string`); err == nil { |
|
111 |
- t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC") |
|
112 |
- } |
|
113 |
-} |
... | ... |
@@ -1,678 +1,13 @@ |
1 | 1 |
package opts |
2 | 2 |
|
3 | 3 |
import ( |
4 |
- "bytes" |
|
5 |
- "encoding/json" |
|
6 | 4 |
"fmt" |
7 |
- "io/ioutil" |
|
8 |
- "path" |
|
9 | 5 |
"strconv" |
10 | 6 |
"strings" |
11 |
- "time" |
|
12 | 7 |
|
13 | 8 |
"github.com/docker/docker/api/types/container" |
14 |
- networktypes "github.com/docker/docker/api/types/network" |
|
15 |
- "github.com/docker/docker/api/types/strslice" |
|
16 |
- "github.com/docker/docker/opts" |
|
17 |
- "github.com/docker/docker/pkg/signal" |
|
18 |
- "github.com/docker/go-connections/nat" |
|
19 |
- units "github.com/docker/go-units" |
|
20 |
- "github.com/spf13/pflag" |
|
21 | 9 |
) |
22 | 10 |
|
23 |
-// ContainerOptions is a data object with all the options for creating a container |
|
24 |
-type ContainerOptions struct { |
|
25 |
- attach opts.ListOpts |
|
26 |
- volumes opts.ListOpts |
|
27 |
- tmpfs opts.ListOpts |
|
28 |
- blkioWeightDevice WeightdeviceOpt |
|
29 |
- deviceReadBps ThrottledeviceOpt |
|
30 |
- deviceWriteBps ThrottledeviceOpt |
|
31 |
- links opts.ListOpts |
|
32 |
- aliases opts.ListOpts |
|
33 |
- linkLocalIPs opts.ListOpts |
|
34 |
- deviceReadIOps ThrottledeviceOpt |
|
35 |
- deviceWriteIOps ThrottledeviceOpt |
|
36 |
- env opts.ListOpts |
|
37 |
- labels opts.ListOpts |
|
38 |
- devices opts.ListOpts |
|
39 |
- ulimits *UlimitOpt |
|
40 |
- sysctls *opts.MapOpts |
|
41 |
- publish opts.ListOpts |
|
42 |
- expose opts.ListOpts |
|
43 |
- dns opts.ListOpts |
|
44 |
- dnsSearch opts.ListOpts |
|
45 |
- dnsOptions opts.ListOpts |
|
46 |
- extraHosts opts.ListOpts |
|
47 |
- volumesFrom opts.ListOpts |
|
48 |
- envFile opts.ListOpts |
|
49 |
- capAdd opts.ListOpts |
|
50 |
- capDrop opts.ListOpts |
|
51 |
- groupAdd opts.ListOpts |
|
52 |
- securityOpt opts.ListOpts |
|
53 |
- storageOpt opts.ListOpts |
|
54 |
- labelsFile opts.ListOpts |
|
55 |
- loggingOpts opts.ListOpts |
|
56 |
- privileged bool |
|
57 |
- pidMode string |
|
58 |
- utsMode string |
|
59 |
- usernsMode string |
|
60 |
- publishAll bool |
|
61 |
- stdin bool |
|
62 |
- tty bool |
|
63 |
- oomKillDisable bool |
|
64 |
- oomScoreAdj int |
|
65 |
- containerIDFile string |
|
66 |
- entrypoint string |
|
67 |
- hostname string |
|
68 |
- memoryString string |
|
69 |
- memoryReservation string |
|
70 |
- memorySwap string |
|
71 |
- kernelMemory string |
|
72 |
- user string |
|
73 |
- workingDir string |
|
74 |
- cpuCount int64 |
|
75 |
- cpuShares int64 |
|
76 |
- cpuPercent int64 |
|
77 |
- cpuPeriod int64 |
|
78 |
- cpuRealtimePeriod int64 |
|
79 |
- cpuRealtimeRuntime int64 |
|
80 |
- cpuQuota int64 |
|
81 |
- cpus opts.NanoCPUs |
|
82 |
- cpusetCpus string |
|
83 |
- cpusetMems string |
|
84 |
- blkioWeight uint16 |
|
85 |
- ioMaxBandwidth string |
|
86 |
- ioMaxIOps uint64 |
|
87 |
- swappiness int64 |
|
88 |
- netMode string |
|
89 |
- macAddress string |
|
90 |
- ipv4Address string |
|
91 |
- ipv6Address string |
|
92 |
- ipcMode string |
|
93 |
- pidsLimit int64 |
|
94 |
- restartPolicy string |
|
95 |
- readonlyRootfs bool |
|
96 |
- loggingDriver string |
|
97 |
- cgroupParent string |
|
98 |
- volumeDriver string |
|
99 |
- stopSignal string |
|
100 |
- stopTimeout int |
|
101 |
- isolation string |
|
102 |
- shmSize string |
|
103 |
- noHealthcheck bool |
|
104 |
- healthCmd string |
|
105 |
- healthInterval time.Duration |
|
106 |
- healthTimeout time.Duration |
|
107 |
- healthRetries int |
|
108 |
- runtime string |
|
109 |
- autoRemove bool |
|
110 |
- init bool |
|
111 |
- initPath string |
|
112 |
- credentialSpec string |
|
113 |
- |
|
114 |
- Image string |
|
115 |
- Args []string |
|
116 |
-} |
|
117 |
- |
|
118 |
-// AddFlags adds all command line flags that will be used by Parse to the FlagSet |
|
119 |
-func AddFlags(flags *pflag.FlagSet) *ContainerOptions { |
|
120 |
- copts := &ContainerOptions{ |
|
121 |
- aliases: opts.NewListOpts(nil), |
|
122 |
- attach: opts.NewListOpts(ValidateAttach), |
|
123 |
- blkioWeightDevice: NewWeightdeviceOpt(ValidateWeightDevice), |
|
124 |
- capAdd: opts.NewListOpts(nil), |
|
125 |
- capDrop: opts.NewListOpts(nil), |
|
126 |
- dns: opts.NewListOpts(opts.ValidateIPAddress), |
|
127 |
- dnsOptions: opts.NewListOpts(nil), |
|
128 |
- dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), |
|
129 |
- deviceReadBps: NewThrottledeviceOpt(ValidateThrottleBpsDevice), |
|
130 |
- deviceReadIOps: NewThrottledeviceOpt(ValidateThrottleIOpsDevice), |
|
131 |
- deviceWriteBps: NewThrottledeviceOpt(ValidateThrottleBpsDevice), |
|
132 |
- deviceWriteIOps: NewThrottledeviceOpt(ValidateThrottleIOpsDevice), |
|
133 |
- devices: opts.NewListOpts(ValidateDevice), |
|
134 |
- env: opts.NewListOpts(ValidateEnv), |
|
135 |
- envFile: opts.NewListOpts(nil), |
|
136 |
- expose: opts.NewListOpts(nil), |
|
137 |
- extraHosts: opts.NewListOpts(ValidateExtraHost), |
|
138 |
- groupAdd: opts.NewListOpts(nil), |
|
139 |
- labels: opts.NewListOpts(ValidateEnv), |
|
140 |
- labelsFile: opts.NewListOpts(nil), |
|
141 |
- linkLocalIPs: opts.NewListOpts(nil), |
|
142 |
- links: opts.NewListOpts(ValidateLink), |
|
143 |
- loggingOpts: opts.NewListOpts(nil), |
|
144 |
- publish: opts.NewListOpts(nil), |
|
145 |
- securityOpt: opts.NewListOpts(nil), |
|
146 |
- storageOpt: opts.NewListOpts(nil), |
|
147 |
- sysctls: opts.NewMapOpts(nil, opts.ValidateSysctl), |
|
148 |
- tmpfs: opts.NewListOpts(nil), |
|
149 |
- ulimits: NewUlimitOpt(nil), |
|
150 |
- volumes: opts.NewListOpts(nil), |
|
151 |
- volumesFrom: opts.NewListOpts(nil), |
|
152 |
- } |
|
153 |
- |
|
154 |
- // General purpose flags |
|
155 |
- flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR") |
|
156 |
- flags.Var(&copts.devices, "device", "Add a host device to the container") |
|
157 |
- flags.VarP(&copts.env, "env", "e", "Set environment variables") |
|
158 |
- flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables") |
|
159 |
- flags.StringVar(&copts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image") |
|
160 |
- flags.Var(&copts.groupAdd, "group-add", "Add additional groups to join") |
|
161 |
- flags.StringVarP(&copts.hostname, "hostname", "h", "", "Container host name") |
|
162 |
- flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached") |
|
163 |
- flags.VarP(&copts.labels, "label", "l", "Set meta data on a container") |
|
164 |
- flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels") |
|
165 |
- flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only") |
|
166 |
- flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits") |
|
167 |
- flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal)) |
|
168 |
- flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container") |
|
169 |
- flags.SetAnnotation("stop-timeout", "version", []string{"1.25"}) |
|
170 |
- flags.Var(copts.sysctls, "sysctl", "Sysctl options") |
|
171 |
- flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY") |
|
172 |
- flags.Var(copts.ulimits, "ulimit", "Ulimit options") |
|
173 |
- flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])") |
|
174 |
- flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container") |
|
175 |
- flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits") |
|
176 |
- |
|
177 |
- // Security |
|
178 |
- flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities") |
|
179 |
- flags.Var(&copts.capDrop, "cap-drop", "Drop Linux capabilities") |
|
180 |
- flags.BoolVar(&copts.privileged, "privileged", false, "Give extended privileges to this container") |
|
181 |
- flags.Var(&copts.securityOpt, "security-opt", "Security Options") |
|
182 |
- flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use") |
|
183 |
- flags.StringVar(&copts.credentialSpec, "credentialspec", "", "Credential spec for managed service account (Windows only)") |
|
184 |
- |
|
185 |
- // Network and port publishing flag |
|
186 |
- flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)") |
|
187 |
- flags.Var(&copts.dns, "dns", "Set custom DNS servers") |
|
188 |
- // We allow for both "--dns-opt" and "--dns-option", although the latter is the recommended way. |
|
189 |
- // This is to be consistent with service create/update |
|
190 |
- flags.Var(&copts.dnsOptions, "dns-opt", "Set DNS options") |
|
191 |
- flags.Var(&copts.dnsOptions, "dns-option", "Set DNS options") |
|
192 |
- flags.MarkHidden("dns-opt") |
|
193 |
- flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains") |
|
194 |
- flags.Var(&copts.expose, "expose", "Expose a port or a range of ports") |
|
195 |
- flags.StringVar(&copts.ipv4Address, "ip", "", "Container IPv4 address (e.g. 172.30.100.104)") |
|
196 |
- flags.StringVar(&copts.ipv6Address, "ip6", "", "Container IPv6 address (e.g. 2001:db8::33)") |
|
197 |
- flags.Var(&copts.links, "link", "Add link to another container") |
|
198 |
- flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses") |
|
199 |
- flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") |
|
200 |
- flags.VarP(&copts.publish, "publish", "p", "Publish a container's port(s) to the host") |
|
201 |
- flags.BoolVarP(&copts.publishAll, "publish-all", "P", false, "Publish all exposed ports to random ports") |
|
202 |
- // We allow for both "--net" and "--network", although the latter is the recommended way. |
|
203 |
- flags.StringVar(&copts.netMode, "net", "default", "Connect a container to a network") |
|
204 |
- flags.StringVar(&copts.netMode, "network", "default", "Connect a container to a network") |
|
205 |
- flags.MarkHidden("net") |
|
206 |
- // We allow for both "--net-alias" and "--network-alias", although the latter is the recommended way. |
|
207 |
- flags.Var(&copts.aliases, "net-alias", "Add network-scoped alias for the container") |
|
208 |
- flags.Var(&copts.aliases, "network-alias", "Add network-scoped alias for the container") |
|
209 |
- flags.MarkHidden("net-alias") |
|
210 |
- |
|
211 |
- // Logging and storage |
|
212 |
- flags.StringVar(&copts.loggingDriver, "log-driver", "", "Logging driver for the container") |
|
213 |
- flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container") |
|
214 |
- flags.Var(&copts.loggingOpts, "log-opt", "Log driver options") |
|
215 |
- flags.Var(&copts.storageOpt, "storage-opt", "Storage driver options for the container") |
|
216 |
- flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory") |
|
217 |
- flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)") |
|
218 |
- flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume") |
|
219 |
- |
|
220 |
- // Health-checking |
|
221 |
- flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health") |
|
222 |
- flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ns|us|ms|s|m|h) (default 0s)") |
|
223 |
- flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy") |
|
224 |
- flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ns|us|ms|s|m|h) (default 0s)") |
|
225 |
- flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK") |
|
226 |
- |
|
227 |
- // Resource management |
|
228 |
- flags.Uint16Var(&copts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)") |
|
229 |
- flags.Var(&copts.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)") |
|
230 |
- flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file") |
|
231 |
- flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") |
|
232 |
- flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)") |
|
233 |
- flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)") |
|
234 |
- flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)") |
|
235 |
- flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period") |
|
236 |
- flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota") |
|
237 |
- flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds") |
|
238 |
- flags.Int64Var(&copts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds") |
|
239 |
- flags.Int64VarP(&copts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)") |
|
240 |
- flags.Var(&copts.cpus, "cpus", "Number of CPUs") |
|
241 |
- flags.Var(&copts.deviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device") |
|
242 |
- flags.Var(&copts.deviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device") |
|
243 |
- flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device") |
|
244 |
- flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device") |
|
245 |
- flags.StringVar(&copts.ioMaxBandwidth, "io-maxbandwidth", "", "Maximum IO bandwidth limit for the system drive (Windows only)") |
|
246 |
- flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)") |
|
247 |
- flags.StringVar(&copts.kernelMemory, "kernel-memory", "", "Kernel memory limit") |
|
248 |
- flags.StringVarP(&copts.memoryString, "memory", "m", "", "Memory limit") |
|
249 |
- flags.StringVar(&copts.memoryReservation, "memory-reservation", "", "Memory soft limit") |
|
250 |
- flags.StringVar(&copts.memorySwap, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap") |
|
251 |
- flags.Int64Var(&copts.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)") |
|
252 |
- flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer") |
|
253 |
- flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)") |
|
254 |
- flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)") |
|
255 |
- |
|
256 |
- // Low-level execution (cgroups, namespaces, ...) |
|
257 |
- flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container") |
|
258 |
- flags.StringVar(&copts.ipcMode, "ipc", "", "IPC namespace to use") |
|
259 |
- flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology") |
|
260 |
- flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use") |
|
261 |
- flags.StringVar(&copts.shmSize, "shm-size", "", "Size of /dev/shm, default value is 64MB") |
|
262 |
- flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use") |
|
263 |
- flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container") |
|
264 |
- |
|
265 |
- flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes") |
|
266 |
- flags.StringVar(&copts.initPath, "init-path", "", "Path to the docker-init binary") |
|
267 |
- return copts |
|
268 |
-} |
|
269 |
- |
|
270 |
-// Parse parses the args for the specified command and generates a Config, |
|
271 |
-// a HostConfig and returns them with the specified command. |
|
272 |
-// If the specified args are not valid, it will return an error. |
|
273 |
-func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) { |
|
274 |
- var ( |
|
275 |
- attachStdin = copts.attach.Get("stdin") |
|
276 |
- attachStdout = copts.attach.Get("stdout") |
|
277 |
- attachStderr = copts.attach.Get("stderr") |
|
278 |
- ) |
|
279 |
- |
|
280 |
- // Validate the input mac address |
|
281 |
- if copts.macAddress != "" { |
|
282 |
- if _, err := ValidateMACAddress(copts.macAddress); err != nil { |
|
283 |
- return nil, nil, nil, fmt.Errorf("%s is not a valid mac address", copts.macAddress) |
|
284 |
- } |
|
285 |
- } |
|
286 |
- if copts.stdin { |
|
287 |
- attachStdin = true |
|
288 |
- } |
|
289 |
- // If -a is not set, attach to stdout and stderr |
|
290 |
- if copts.attach.Len() == 0 { |
|
291 |
- attachStdout = true |
|
292 |
- attachStderr = true |
|
293 |
- } |
|
294 |
- |
|
295 |
- var err error |
|
296 |
- |
|
297 |
- var memory int64 |
|
298 |
- if copts.memoryString != "" { |
|
299 |
- memory, err = units.RAMInBytes(copts.memoryString) |
|
300 |
- if err != nil { |
|
301 |
- return nil, nil, nil, err |
|
302 |
- } |
|
303 |
- } |
|
304 |
- |
|
305 |
- var memoryReservation int64 |
|
306 |
- if copts.memoryReservation != "" { |
|
307 |
- memoryReservation, err = units.RAMInBytes(copts.memoryReservation) |
|
308 |
- if err != nil { |
|
309 |
- return nil, nil, nil, err |
|
310 |
- } |
|
311 |
- } |
|
312 |
- |
|
313 |
- var memorySwap int64 |
|
314 |
- if copts.memorySwap != "" { |
|
315 |
- if copts.memorySwap == "-1" { |
|
316 |
- memorySwap = -1 |
|
317 |
- } else { |
|
318 |
- memorySwap, err = units.RAMInBytes(copts.memorySwap) |
|
319 |
- if err != nil { |
|
320 |
- return nil, nil, nil, err |
|
321 |
- } |
|
322 |
- } |
|
323 |
- } |
|
324 |
- |
|
325 |
- var kernelMemory int64 |
|
326 |
- if copts.kernelMemory != "" { |
|
327 |
- kernelMemory, err = units.RAMInBytes(copts.kernelMemory) |
|
328 |
- if err != nil { |
|
329 |
- return nil, nil, nil, err |
|
330 |
- } |
|
331 |
- } |
|
332 |
- |
|
333 |
- swappiness := copts.swappiness |
|
334 |
- if swappiness != -1 && (swappiness < 0 || swappiness > 100) { |
|
335 |
- return nil, nil, nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness) |
|
336 |
- } |
|
337 |
- |
|
338 |
- var shmSize int64 |
|
339 |
- if copts.shmSize != "" { |
|
340 |
- shmSize, err = units.RAMInBytes(copts.shmSize) |
|
341 |
- if err != nil { |
|
342 |
- return nil, nil, nil, err |
|
343 |
- } |
|
344 |
- } |
|
345 |
- |
|
346 |
- // TODO FIXME units.RAMInBytes should have a uint64 version |
|
347 |
- var maxIOBandwidth int64 |
|
348 |
- if copts.ioMaxBandwidth != "" { |
|
349 |
- maxIOBandwidth, err = units.RAMInBytes(copts.ioMaxBandwidth) |
|
350 |
- if err != nil { |
|
351 |
- return nil, nil, nil, err |
|
352 |
- } |
|
353 |
- if maxIOBandwidth < 0 { |
|
354 |
- return nil, nil, nil, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", copts.ioMaxBandwidth) |
|
355 |
- } |
|
356 |
- } |
|
357 |
- |
|
358 |
- var binds []string |
|
359 |
- volumes := copts.volumes.GetMap() |
|
360 |
- // add any bind targets to the list of container volumes |
|
361 |
- for bind := range copts.volumes.GetMap() { |
|
362 |
- if arr := volumeSplitN(bind, 2); len(arr) > 1 { |
|
363 |
- // after creating the bind mount we want to delete it from the copts.volumes values because |
|
364 |
- // we do not want bind mounts being committed to image configs |
|
365 |
- binds = append(binds, bind) |
|
366 |
- // We should delete from the map (`volumes`) here, as deleting from copts.volumes will not work if |
|
367 |
- // there are duplicates entries. |
|
368 |
- delete(volumes, bind) |
|
369 |
- } |
|
370 |
- } |
|
371 |
- |
|
372 |
- // Can't evaluate options passed into --tmpfs until we actually mount |
|
373 |
- tmpfs := make(map[string]string) |
|
374 |
- for _, t := range copts.tmpfs.GetAll() { |
|
375 |
- if arr := strings.SplitN(t, ":", 2); len(arr) > 1 { |
|
376 |
- tmpfs[arr[0]] = arr[1] |
|
377 |
- } else { |
|
378 |
- tmpfs[arr[0]] = "" |
|
379 |
- } |
|
380 |
- } |
|
381 |
- |
|
382 |
- var ( |
|
383 |
- runCmd strslice.StrSlice |
|
384 |
- entrypoint strslice.StrSlice |
|
385 |
- ) |
|
386 |
- |
|
387 |
- if len(copts.Args) > 0 { |
|
388 |
- runCmd = strslice.StrSlice(copts.Args) |
|
389 |
- } |
|
390 |
- |
|
391 |
- if copts.entrypoint != "" { |
|
392 |
- entrypoint = strslice.StrSlice{copts.entrypoint} |
|
393 |
- } else if flags.Changed("entrypoint") { |
|
394 |
- // if `--entrypoint=` is parsed then Entrypoint is reset |
|
395 |
- entrypoint = []string{""} |
|
396 |
- } |
|
397 |
- |
|
398 |
- ports, portBindings, err := nat.ParsePortSpecs(copts.publish.GetAll()) |
|
399 |
- if err != nil { |
|
400 |
- return nil, nil, nil, err |
|
401 |
- } |
|
402 |
- |
|
403 |
- // Merge in exposed ports to the map of published ports |
|
404 |
- for _, e := range copts.expose.GetAll() { |
|
405 |
- if strings.Contains(e, ":") { |
|
406 |
- return nil, nil, nil, fmt.Errorf("invalid port format for --expose: %s", e) |
|
407 |
- } |
|
408 |
- //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] |
|
409 |
- proto, port := nat.SplitProtoPort(e) |
|
410 |
- //parse the start and end port and create a sequence of ports to expose |
|
411 |
- //if expose a port, the start and end port are the same |
|
412 |
- start, end, err := nat.ParsePortRange(port) |
|
413 |
- if err != nil { |
|
414 |
- return nil, nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err) |
|
415 |
- } |
|
416 |
- for i := start; i <= end; i++ { |
|
417 |
- p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) |
|
418 |
- if err != nil { |
|
419 |
- return nil, nil, nil, err |
|
420 |
- } |
|
421 |
- if _, exists := ports[p]; !exists { |
|
422 |
- ports[p] = struct{}{} |
|
423 |
- } |
|
424 |
- } |
|
425 |
- } |
|
426 |
- |
|
427 |
- // parse device mappings |
|
428 |
- deviceMappings := []container.DeviceMapping{} |
|
429 |
- for _, device := range copts.devices.GetAll() { |
|
430 |
- deviceMapping, err := ParseDevice(device) |
|
431 |
- if err != nil { |
|
432 |
- return nil, nil, nil, err |
|
433 |
- } |
|
434 |
- deviceMappings = append(deviceMappings, deviceMapping) |
|
435 |
- } |
|
436 |
- |
|
437 |
- // collect all the environment variables for the container |
|
438 |
- envVariables, err := ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll()) |
|
439 |
- if err != nil { |
|
440 |
- return nil, nil, nil, err |
|
441 |
- } |
|
442 |
- |
|
443 |
- // collect all the labels for the container |
|
444 |
- labels, err := ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll()) |
|
445 |
- if err != nil { |
|
446 |
- return nil, nil, nil, err |
|
447 |
- } |
|
448 |
- |
|
449 |
- ipcMode := container.IpcMode(copts.ipcMode) |
|
450 |
- if !ipcMode.Valid() { |
|
451 |
- return nil, nil, nil, fmt.Errorf("--ipc: invalid IPC mode") |
|
452 |
- } |
|
453 |
- |
|
454 |
- pidMode := container.PidMode(copts.pidMode) |
|
455 |
- if !pidMode.Valid() { |
|
456 |
- return nil, nil, nil, fmt.Errorf("--pid: invalid PID mode") |
|
457 |
- } |
|
458 |
- |
|
459 |
- utsMode := container.UTSMode(copts.utsMode) |
|
460 |
- if !utsMode.Valid() { |
|
461 |
- return nil, nil, nil, fmt.Errorf("--uts: invalid UTS mode") |
|
462 |
- } |
|
463 |
- |
|
464 |
- usernsMode := container.UsernsMode(copts.usernsMode) |
|
465 |
- if !usernsMode.Valid() { |
|
466 |
- return nil, nil, nil, fmt.Errorf("--userns: invalid USER mode") |
|
467 |
- } |
|
468 |
- |
|
469 |
- restartPolicy, err := ParseRestartPolicy(copts.restartPolicy) |
|
470 |
- if err != nil { |
|
471 |
- return nil, nil, nil, err |
|
472 |
- } |
|
473 |
- |
|
474 |
- loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll()) |
|
475 |
- if err != nil { |
|
476 |
- return nil, nil, nil, err |
|
477 |
- } |
|
478 |
- |
|
479 |
- securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll()) |
|
480 |
- if err != nil { |
|
481 |
- return nil, nil, nil, err |
|
482 |
- } |
|
483 |
- |
|
484 |
- storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll()) |
|
485 |
- if err != nil { |
|
486 |
- return nil, nil, nil, err |
|
487 |
- } |
|
488 |
- |
|
489 |
- // Healthcheck |
|
490 |
- var healthConfig *container.HealthConfig |
|
491 |
- haveHealthSettings := copts.healthCmd != "" || |
|
492 |
- copts.healthInterval != 0 || |
|
493 |
- copts.healthTimeout != 0 || |
|
494 |
- copts.healthRetries != 0 |
|
495 |
- if copts.noHealthcheck { |
|
496 |
- if haveHealthSettings { |
|
497 |
- return nil, nil, nil, fmt.Errorf("--no-healthcheck conflicts with --health-* options") |
|
498 |
- } |
|
499 |
- test := strslice.StrSlice{"NONE"} |
|
500 |
- healthConfig = &container.HealthConfig{Test: test} |
|
501 |
- } else if haveHealthSettings { |
|
502 |
- var probe strslice.StrSlice |
|
503 |
- if copts.healthCmd != "" { |
|
504 |
- args := []string{"CMD-SHELL", copts.healthCmd} |
|
505 |
- probe = strslice.StrSlice(args) |
|
506 |
- } |
|
507 |
- if copts.healthInterval < 0 { |
|
508 |
- return nil, nil, nil, fmt.Errorf("--health-interval cannot be negative") |
|
509 |
- } |
|
510 |
- if copts.healthTimeout < 0 { |
|
511 |
- return nil, nil, nil, fmt.Errorf("--health-timeout cannot be negative") |
|
512 |
- } |
|
513 |
- |
|
514 |
- healthConfig = &container.HealthConfig{ |
|
515 |
- Test: probe, |
|
516 |
- Interval: copts.healthInterval, |
|
517 |
- Timeout: copts.healthTimeout, |
|
518 |
- Retries: copts.healthRetries, |
|
519 |
- } |
|
520 |
- } |
|
521 |
- |
|
522 |
- resources := container.Resources{ |
|
523 |
- CgroupParent: copts.cgroupParent, |
|
524 |
- Memory: memory, |
|
525 |
- MemoryReservation: memoryReservation, |
|
526 |
- MemorySwap: memorySwap, |
|
527 |
- MemorySwappiness: &copts.swappiness, |
|
528 |
- KernelMemory: kernelMemory, |
|
529 |
- OomKillDisable: &copts.oomKillDisable, |
|
530 |
- NanoCPUs: copts.cpus.Value(), |
|
531 |
- CPUCount: copts.cpuCount, |
|
532 |
- CPUPercent: copts.cpuPercent, |
|
533 |
- CPUShares: copts.cpuShares, |
|
534 |
- CPUPeriod: copts.cpuPeriod, |
|
535 |
- CpusetCpus: copts.cpusetCpus, |
|
536 |
- CpusetMems: copts.cpusetMems, |
|
537 |
- CPUQuota: copts.cpuQuota, |
|
538 |
- CPURealtimePeriod: copts.cpuRealtimePeriod, |
|
539 |
- CPURealtimeRuntime: copts.cpuRealtimeRuntime, |
|
540 |
- PidsLimit: copts.pidsLimit, |
|
541 |
- BlkioWeight: copts.blkioWeight, |
|
542 |
- BlkioWeightDevice: copts.blkioWeightDevice.GetList(), |
|
543 |
- BlkioDeviceReadBps: copts.deviceReadBps.GetList(), |
|
544 |
- BlkioDeviceWriteBps: copts.deviceWriteBps.GetList(), |
|
545 |
- BlkioDeviceReadIOps: copts.deviceReadIOps.GetList(), |
|
546 |
- BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(), |
|
547 |
- IOMaximumIOps: copts.ioMaxIOps, |
|
548 |
- IOMaximumBandwidth: uint64(maxIOBandwidth), |
|
549 |
- Ulimits: copts.ulimits.GetList(), |
|
550 |
- Devices: deviceMappings, |
|
551 |
- } |
|
552 |
- |
|
553 |
- config := &container.Config{ |
|
554 |
- Hostname: copts.hostname, |
|
555 |
- ExposedPorts: ports, |
|
556 |
- User: copts.user, |
|
557 |
- Tty: copts.tty, |
|
558 |
- // TODO: deprecated, it comes from -n, --networking |
|
559 |
- // it's still needed internally to set the network to disabled |
|
560 |
- // if e.g. bridge is none in daemon opts, and in inspect |
|
561 |
- NetworkDisabled: false, |
|
562 |
- OpenStdin: copts.stdin, |
|
563 |
- AttachStdin: attachStdin, |
|
564 |
- AttachStdout: attachStdout, |
|
565 |
- AttachStderr: attachStderr, |
|
566 |
- Env: envVariables, |
|
567 |
- Cmd: runCmd, |
|
568 |
- Image: copts.Image, |
|
569 |
- Volumes: volumes, |
|
570 |
- MacAddress: copts.macAddress, |
|
571 |
- Entrypoint: entrypoint, |
|
572 |
- WorkingDir: copts.workingDir, |
|
573 |
- Labels: ConvertKVStringsToMap(labels), |
|
574 |
- Healthcheck: healthConfig, |
|
575 |
- } |
|
576 |
- if flags.Changed("stop-signal") { |
|
577 |
- config.StopSignal = copts.stopSignal |
|
578 |
- } |
|
579 |
- if flags.Changed("stop-timeout") { |
|
580 |
- config.StopTimeout = &copts.stopTimeout |
|
581 |
- } |
|
582 |
- |
|
583 |
- hostConfig := &container.HostConfig{ |
|
584 |
- Binds: binds, |
|
585 |
- ContainerIDFile: copts.containerIDFile, |
|
586 |
- OomScoreAdj: copts.oomScoreAdj, |
|
587 |
- AutoRemove: copts.autoRemove, |
|
588 |
- Privileged: copts.privileged, |
|
589 |
- PortBindings: portBindings, |
|
590 |
- Links: copts.links.GetAll(), |
|
591 |
- PublishAllPorts: copts.publishAll, |
|
592 |
- // Make sure the dns fields are never nil. |
|
593 |
- // New containers don't ever have those fields nil, |
|
594 |
- // but pre created containers can still have those nil values. |
|
595 |
- // See https://github.com/docker/docker/pull/17779 |
|
596 |
- // for a more detailed explanation on why we don't want that. |
|
597 |
- DNS: copts.dns.GetAllOrEmpty(), |
|
598 |
- DNSSearch: copts.dnsSearch.GetAllOrEmpty(), |
|
599 |
- DNSOptions: copts.dnsOptions.GetAllOrEmpty(), |
|
600 |
- ExtraHosts: copts.extraHosts.GetAll(), |
|
601 |
- VolumesFrom: copts.volumesFrom.GetAll(), |
|
602 |
- NetworkMode: container.NetworkMode(copts.netMode), |
|
603 |
- IpcMode: ipcMode, |
|
604 |
- PidMode: pidMode, |
|
605 |
- UTSMode: utsMode, |
|
606 |
- UsernsMode: usernsMode, |
|
607 |
- CapAdd: strslice.StrSlice(copts.capAdd.GetAll()), |
|
608 |
- CapDrop: strslice.StrSlice(copts.capDrop.GetAll()), |
|
609 |
- GroupAdd: copts.groupAdd.GetAll(), |
|
610 |
- RestartPolicy: restartPolicy, |
|
611 |
- SecurityOpt: securityOpts, |
|
612 |
- StorageOpt: storageOpts, |
|
613 |
- ReadonlyRootfs: copts.readonlyRootfs, |
|
614 |
- LogConfig: container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts}, |
|
615 |
- VolumeDriver: copts.volumeDriver, |
|
616 |
- Isolation: container.Isolation(copts.isolation), |
|
617 |
- ShmSize: shmSize, |
|
618 |
- Resources: resources, |
|
619 |
- Tmpfs: tmpfs, |
|
620 |
- Sysctls: copts.sysctls.GetAll(), |
|
621 |
- Runtime: copts.runtime, |
|
622 |
- } |
|
623 |
- |
|
624 |
- // only set this value if the user provided the flag, else it should default to nil |
|
625 |
- if flags.Changed("init") { |
|
626 |
- hostConfig.Init = &copts.init |
|
627 |
- } |
|
628 |
- |
|
629 |
- // When allocating stdin in attached mode, close stdin at client disconnect |
|
630 |
- if config.OpenStdin && config.AttachStdin { |
|
631 |
- config.StdinOnce = true |
|
632 |
- } |
|
633 |
- |
|
634 |
- networkingConfig := &networktypes.NetworkingConfig{ |
|
635 |
- EndpointsConfig: make(map[string]*networktypes.EndpointSettings), |
|
636 |
- } |
|
637 |
- |
|
638 |
- if copts.ipv4Address != "" || copts.ipv6Address != "" || copts.linkLocalIPs.Len() > 0 { |
|
639 |
- epConfig := &networktypes.EndpointSettings{} |
|
640 |
- networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig |
|
641 |
- |
|
642 |
- epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{ |
|
643 |
- IPv4Address: copts.ipv4Address, |
|
644 |
- IPv6Address: copts.ipv6Address, |
|
645 |
- } |
|
646 |
- |
|
647 |
- if copts.linkLocalIPs.Len() > 0 { |
|
648 |
- epConfig.IPAMConfig.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len()) |
|
649 |
- copy(epConfig.IPAMConfig.LinkLocalIPs, copts.linkLocalIPs.GetAll()) |
|
650 |
- } |
|
651 |
- } |
|
652 |
- |
|
653 |
- if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 { |
|
654 |
- epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] |
|
655 |
- if epConfig == nil { |
|
656 |
- epConfig = &networktypes.EndpointSettings{} |
|
657 |
- } |
|
658 |
- epConfig.Links = make([]string, len(hostConfig.Links)) |
|
659 |
- copy(epConfig.Links, hostConfig.Links) |
|
660 |
- networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig |
|
661 |
- } |
|
662 |
- |
|
663 |
- if copts.aliases.Len() > 0 { |
|
664 |
- epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] |
|
665 |
- if epConfig == nil { |
|
666 |
- epConfig = &networktypes.EndpointSettings{} |
|
667 |
- } |
|
668 |
- epConfig.Aliases = make([]string, copts.aliases.Len()) |
|
669 |
- copy(epConfig.Aliases, copts.aliases.GetAll()) |
|
670 |
- networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig |
|
671 |
- } |
|
672 |
- |
|
673 |
- return config, hostConfig, networkingConfig, nil |
|
674 |
-} |
|
675 |
- |
|
676 | 11 |
// ReadKVStrings reads a file of line terminated key=value pairs, and overrides any keys |
677 | 12 |
// present in the file with additional pairs specified in the override parameter |
678 | 13 |
func ReadKVStrings(files []string, override []string) ([]string, error) { |
... | ... |
@@ -724,55 +59,6 @@ func ConvertKVStringsToMapWithNil(values []string) map[string]*string { |
724 | 724 |
return result |
725 | 725 |
} |
726 | 726 |
|
727 |
-func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) { |
|
728 |
- loggingOptsMap := ConvertKVStringsToMap(loggingOpts) |
|
729 |
- if loggingDriver == "none" && len(loggingOpts) > 0 { |
|
730 |
- return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver) |
|
731 |
- } |
|
732 |
- return loggingOptsMap, nil |
|
733 |
-} |
|
734 |
- |
|
735 |
-// takes a local seccomp daemon, reads the file contents for sending to the daemon |
|
736 |
-func parseSecurityOpts(securityOpts []string) ([]string, error) { |
|
737 |
- for key, opt := range securityOpts { |
|
738 |
- con := strings.SplitN(opt, "=", 2) |
|
739 |
- if len(con) == 1 && con[0] != "no-new-privileges" { |
|
740 |
- if strings.Contains(opt, ":") { |
|
741 |
- con = strings.SplitN(opt, ":", 2) |
|
742 |
- } else { |
|
743 |
- return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt) |
|
744 |
- } |
|
745 |
- } |
|
746 |
- if con[0] == "seccomp" && con[1] != "unconfined" { |
|
747 |
- f, err := ioutil.ReadFile(con[1]) |
|
748 |
- if err != nil { |
|
749 |
- return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err) |
|
750 |
- } |
|
751 |
- b := bytes.NewBuffer(nil) |
|
752 |
- if err := json.Compact(b, f); err != nil { |
|
753 |
- return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err) |
|
754 |
- } |
|
755 |
- securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes()) |
|
756 |
- } |
|
757 |
- } |
|
758 |
- |
|
759 |
- return securityOpts, nil |
|
760 |
-} |
|
761 |
- |
|
762 |
-// parses storage options per container into a map |
|
763 |
-func parseStorageOpts(storageOpts []string) (map[string]string, error) { |
|
764 |
- m := make(map[string]string) |
|
765 |
- for _, option := range storageOpts { |
|
766 |
- if strings.Contains(option, "=") { |
|
767 |
- opt := strings.SplitN(option, "=", 2) |
|
768 |
- m[opt[0]] = opt[1] |
|
769 |
- } else { |
|
770 |
- return nil, fmt.Errorf("invalid storage option") |
|
771 |
- } |
|
772 |
- } |
|
773 |
- return m, nil |
|
774 |
-} |
|
775 |
- |
|
776 | 727 |
// ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect |
777 | 728 |
func ParseRestartPolicy(policy string) (container.RestartPolicy, error) { |
778 | 729 |
p := container.RestartPolicy{} |
... | ... |
@@ -799,195 +85,3 @@ func ParseRestartPolicy(policy string) (container.RestartPolicy, error) { |
799 | 799 |
|
800 | 800 |
return p, nil |
801 | 801 |
} |
802 |
- |
|
803 |
-// ParseDevice parses a device mapping string to a container.DeviceMapping struct |
|
804 |
-func ParseDevice(device string) (container.DeviceMapping, error) { |
|
805 |
- src := "" |
|
806 |
- dst := "" |
|
807 |
- permissions := "rwm" |
|
808 |
- arr := strings.Split(device, ":") |
|
809 |
- switch len(arr) { |
|
810 |
- case 3: |
|
811 |
- permissions = arr[2] |
|
812 |
- fallthrough |
|
813 |
- case 2: |
|
814 |
- if ValidDeviceMode(arr[1]) { |
|
815 |
- permissions = arr[1] |
|
816 |
- } else { |
|
817 |
- dst = arr[1] |
|
818 |
- } |
|
819 |
- fallthrough |
|
820 |
- case 1: |
|
821 |
- src = arr[0] |
|
822 |
- default: |
|
823 |
- return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device) |
|
824 |
- } |
|
825 |
- |
|
826 |
- if dst == "" { |
|
827 |
- dst = src |
|
828 |
- } |
|
829 |
- |
|
830 |
- deviceMapping := container.DeviceMapping{ |
|
831 |
- PathOnHost: src, |
|
832 |
- PathInContainer: dst, |
|
833 |
- CgroupPermissions: permissions, |
|
834 |
- } |
|
835 |
- return deviceMapping, nil |
|
836 |
-} |
|
837 |
- |
|
838 |
-// ParseLink parses and validates the specified string as a link format (name:alias) |
|
839 |
-func ParseLink(val string) (string, string, error) { |
|
840 |
- if val == "" { |
|
841 |
- return "", "", fmt.Errorf("empty string specified for links") |
|
842 |
- } |
|
843 |
- arr := strings.Split(val, ":") |
|
844 |
- if len(arr) > 2 { |
|
845 |
- return "", "", fmt.Errorf("bad format for links: %s", val) |
|
846 |
- } |
|
847 |
- if len(arr) == 1 { |
|
848 |
- return val, val, nil |
|
849 |
- } |
|
850 |
- // This is kept because we can actually get a HostConfig with links |
|
851 |
- // from an already created container and the format is not `foo:bar` |
|
852 |
- // but `/foo:/c1/bar` |
|
853 |
- if strings.HasPrefix(arr[0], "/") { |
|
854 |
- _, alias := path.Split(arr[1]) |
|
855 |
- return arr[0][1:], alias, nil |
|
856 |
- } |
|
857 |
- return arr[0], arr[1], nil |
|
858 |
-} |
|
859 |
- |
|
860 |
-// ValidateLink validates that the specified string has a valid link format (containerName:alias). |
|
861 |
-func ValidateLink(val string) (string, error) { |
|
862 |
- _, _, err := ParseLink(val) |
|
863 |
- return val, err |
|
864 |
-} |
|
865 |
- |
|
866 |
-// ValidDeviceMode checks if the mode for device is valid or not. |
|
867 |
-// Valid mode is a composition of r (read), w (write), and m (mknod). |
|
868 |
-func ValidDeviceMode(mode string) bool { |
|
869 |
- var legalDeviceMode = map[rune]bool{ |
|
870 |
- 'r': true, |
|
871 |
- 'w': true, |
|
872 |
- 'm': true, |
|
873 |
- } |
|
874 |
- if mode == "" { |
|
875 |
- return false |
|
876 |
- } |
|
877 |
- for _, c := range mode { |
|
878 |
- if !legalDeviceMode[c] { |
|
879 |
- return false |
|
880 |
- } |
|
881 |
- legalDeviceMode[c] = false |
|
882 |
- } |
|
883 |
- return true |
|
884 |
-} |
|
885 |
- |
|
886 |
-// ValidateDevice validates a path for devices |
|
887 |
-// It will make sure 'val' is in the form: |
|
888 |
-// [host-dir:]container-path[:mode] |
|
889 |
-// It also validates the device mode. |
|
890 |
-func ValidateDevice(val string) (string, error) { |
|
891 |
- return validatePath(val, ValidDeviceMode) |
|
892 |
-} |
|
893 |
- |
|
894 |
-func validatePath(val string, validator func(string) bool) (string, error) { |
|
895 |
- var containerPath string |
|
896 |
- var mode string |
|
897 |
- |
|
898 |
- if strings.Count(val, ":") > 2 { |
|
899 |
- return val, fmt.Errorf("bad format for path: %s", val) |
|
900 |
- } |
|
901 |
- |
|
902 |
- split := strings.SplitN(val, ":", 3) |
|
903 |
- if split[0] == "" { |
|
904 |
- return val, fmt.Errorf("bad format for path: %s", val) |
|
905 |
- } |
|
906 |
- switch len(split) { |
|
907 |
- case 1: |
|
908 |
- containerPath = split[0] |
|
909 |
- val = path.Clean(containerPath) |
|
910 |
- case 2: |
|
911 |
- if isValid := validator(split[1]); isValid { |
|
912 |
- containerPath = split[0] |
|
913 |
- mode = split[1] |
|
914 |
- val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) |
|
915 |
- } else { |
|
916 |
- containerPath = split[1] |
|
917 |
- val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath)) |
|
918 |
- } |
|
919 |
- case 3: |
|
920 |
- containerPath = split[1] |
|
921 |
- mode = split[2] |
|
922 |
- if isValid := validator(split[2]); !isValid { |
|
923 |
- return val, fmt.Errorf("bad mode specified: %s", mode) |
|
924 |
- } |
|
925 |
- val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode) |
|
926 |
- } |
|
927 |
- |
|
928 |
- if !path.IsAbs(containerPath) { |
|
929 |
- return val, fmt.Errorf("%s is not an absolute path", containerPath) |
|
930 |
- } |
|
931 |
- return val, nil |
|
932 |
-} |
|
933 |
- |
|
934 |
-// volumeSplitN splits raw into a maximum of n parts, separated by a separator colon. |
|
935 |
-// A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped). |
|
936 |
-// In Windows driver letter appears in two situations: |
|
937 |
-// a. `^[a-zA-Z]:` (A colon followed by `^[a-zA-Z]:` is OK as colon is the separator in volume option) |
|
938 |
-// b. A string in the format like `\\?\C:\Windows\...` (UNC). |
|
939 |
-// Therefore, a driver letter can only follow either a `:` or `\\` |
|
940 |
-// This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`. |
|
941 |
-func volumeSplitN(raw string, n int) []string { |
|
942 |
- var array []string |
|
943 |
- if len(raw) == 0 || raw[0] == ':' { |
|
944 |
- // invalid |
|
945 |
- return nil |
|
946 |
- } |
|
947 |
- // numberOfParts counts the number of parts separated by a separator colon |
|
948 |
- numberOfParts := 0 |
|
949 |
- // left represents the left-most cursor in raw, updated at every `:` character considered as a separator. |
|
950 |
- left := 0 |
|
951 |
- // right represents the right-most cursor in raw incremented with the loop. Note this |
|
952 |
- // starts at index 1 as index 0 is already handle above as a special case. |
|
953 |
- for right := 1; right < len(raw); right++ { |
|
954 |
- // stop parsing if reached maximum number of parts |
|
955 |
- if n >= 0 && numberOfParts >= n { |
|
956 |
- break |
|
957 |
- } |
|
958 |
- if raw[right] != ':' { |
|
959 |
- continue |
|
960 |
- } |
|
961 |
- potentialDriveLetter := raw[right-1] |
|
962 |
- if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') { |
|
963 |
- if right > 1 { |
|
964 |
- beforePotentialDriveLetter := raw[right-2] |
|
965 |
- // Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`) |
|
966 |
- if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' { |
|
967 |
- // e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`. |
|
968 |
- array = append(array, raw[left:right]) |
|
969 |
- left = right + 1 |
|
970 |
- numberOfParts++ |
|
971 |
- } |
|
972 |
- // else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing. |
|
973 |
- } |
|
974 |
- // if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing. |
|
975 |
- } else { |
|
976 |
- // if `:` is not preceded by a potential drive letter, then consider it as a delimiter. |
|
977 |
- array = append(array, raw[left:right]) |
|
978 |
- left = right + 1 |
|
979 |
- numberOfParts++ |
|
980 |
- } |
|
981 |
- } |
|
982 |
- // need to take care of the last part |
|
983 |
- if left < len(raw) { |
|
984 |
- if n >= 0 && numberOfParts >= n { |
|
985 |
- // if the maximum number of parts is reached, just append the rest to the last part |
|
986 |
- // left-1 is at the last `:` that needs to be included since not considered a separator. |
|
987 |
- array[n-1] += raw[left-1:] |
|
988 |
- } else { |
|
989 |
- array = append(array, raw[left:]) |
|
990 |
- } |
|
991 |
- } |
|
992 |
- return array |
|
993 |
-} |
994 | 802 |
deleted file mode 100644 |
... | ... |
@@ -1,894 +0,0 @@ |
1 |
-package opts |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "bytes" |
|
5 |
- "encoding/json" |
|
6 |
- "fmt" |
|
7 |
- "io/ioutil" |
|
8 |
- "os" |
|
9 |
- "runtime" |
|
10 |
- "strings" |
|
11 |
- "testing" |
|
12 |
- "time" |
|
13 |
- |
|
14 |
- "github.com/docker/docker/api/types/container" |
|
15 |
- networktypes "github.com/docker/docker/api/types/network" |
|
16 |
- "github.com/docker/docker/runconfig" |
|
17 |
- "github.com/docker/go-connections/nat" |
|
18 |
- "github.com/spf13/pflag" |
|
19 |
-) |
|
20 |
- |
|
21 |
-func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) { |
|
22 |
- flags := pflag.NewFlagSet("run", pflag.ContinueOnError) |
|
23 |
- flags.SetOutput(ioutil.Discard) |
|
24 |
- flags.Usage = nil |
|
25 |
- copts := AddFlags(flags) |
|
26 |
- if err := flags.Parse(args); err != nil { |
|
27 |
- return nil, nil, nil, err |
|
28 |
- } |
|
29 |
- return Parse(flags, copts) |
|
30 |
-} |
|
31 |
- |
|
32 |
-func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) { |
|
33 |
- config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " ")) |
|
34 |
- return config, hostConfig, err |
|
35 |
-} |
|
36 |
- |
|
37 |
-func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) { |
|
38 |
- config, hostConfig, err := parse(t, args) |
|
39 |
- if err != nil { |
|
40 |
- t.Fatal(err) |
|
41 |
- } |
|
42 |
- return config, hostConfig |
|
43 |
-} |
|
44 |
- |
|
45 |
-func TestParseRunLinks(t *testing.T) { |
|
46 |
- if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" { |
|
47 |
- t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links) |
|
48 |
- } |
|
49 |
- if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" { |
|
50 |
- t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links) |
|
51 |
- } |
|
52 |
- if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 { |
|
53 |
- t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links) |
|
54 |
- } |
|
55 |
-} |
|
56 |
- |
|
57 |
-func TestParseRunAttach(t *testing.T) { |
|
58 |
- if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr { |
|
59 |
- t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) |
|
60 |
- } |
|
61 |
- if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr { |
|
62 |
- 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) |
|
63 |
- } |
|
64 |
- if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr { |
|
65 |
- t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) |
|
66 |
- } |
|
67 |
- if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr { |
|
68 |
- t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) |
|
69 |
- } |
|
70 |
- if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr { |
|
71 |
- t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) |
|
72 |
- } |
|
73 |
- |
|
74 |
- if _, _, err := parse(t, "-a"); err == nil { |
|
75 |
- t.Fatalf("Error parsing attach flags, `-a` should be an error but is not") |
|
76 |
- } |
|
77 |
- if _, _, err := parse(t, "-a invalid"); err == nil { |
|
78 |
- t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not") |
|
79 |
- } |
|
80 |
- if _, _, err := parse(t, "-a invalid -a stdout"); err == nil { |
|
81 |
- t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not") |
|
82 |
- } |
|
83 |
- if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil { |
|
84 |
- t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not") |
|
85 |
- } |
|
86 |
- if _, _, err := parse(t, "-a stdin -d"); err == nil { |
|
87 |
- t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not") |
|
88 |
- } |
|
89 |
- if _, _, err := parse(t, "-a stdout -d"); err == nil { |
|
90 |
- t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not") |
|
91 |
- } |
|
92 |
- if _, _, err := parse(t, "-a stderr -d"); err == nil { |
|
93 |
- t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not") |
|
94 |
- } |
|
95 |
- if _, _, err := parse(t, "-d --rm"); err == nil { |
|
96 |
- t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not") |
|
97 |
- } |
|
98 |
-} |
|
99 |
- |
|
100 |
-func TestParseRunVolumes(t *testing.T) { |
|
101 |
- |
|
102 |
- // A single volume |
|
103 |
- arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`}) |
|
104 |
- if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil { |
|
105 |
- t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds) |
|
106 |
- } else if _, exists := config.Volumes[arr[0]]; !exists { |
|
107 |
- t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes) |
|
108 |
- } |
|
109 |
- |
|
110 |
- // Two volumes |
|
111 |
- arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`}) |
|
112 |
- if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil { |
|
113 |
- t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds) |
|
114 |
- } else if _, exists := config.Volumes[arr[0]]; !exists { |
|
115 |
- t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes) |
|
116 |
- } else if _, exists := config.Volumes[arr[1]]; !exists { |
|
117 |
- t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes) |
|
118 |
- } |
|
119 |
- |
|
120 |
- // A single bind-mount |
|
121 |
- arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`}) |
|
122 |
- if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] { |
|
123 |
- 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) |
|
124 |
- } |
|
125 |
- |
|
126 |
- // Two bind-mounts. |
|
127 |
- arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`}) |
|
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 |
- // Two bind-mounts, first read-only, second read-write. |
|
133 |
- // TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4 |
|
134 |
- arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`}) |
|
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 |
- // Similar to previous test but with alternate modes which are only supported by Linux |
|
140 |
- if runtime.GOOS != "windows" { |
|
141 |
- arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{}) |
|
142 |
- if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { |
|
143 |
- t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds) |
|
144 |
- } |
|
145 |
- |
|
146 |
- arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{}) |
|
147 |
- if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { |
|
148 |
- t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds) |
|
149 |
- } |
|
150 |
- } |
|
151 |
- |
|
152 |
- // One bind mount and one volume |
|
153 |
- arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`}) |
|
154 |
- if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] { |
|
155 |
- 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) |
|
156 |
- } else if _, exists := config.Volumes[arr[1]]; !exists { |
|
157 |
- t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes) |
|
158 |
- } |
|
159 |
- |
|
160 |
- // Root to non-c: drive letter (Windows specific) |
|
161 |
- if runtime.GOOS == "windows" { |
|
162 |
- arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`}) |
|
163 |
- if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 { |
|
164 |
- t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0]) |
|
165 |
- } |
|
166 |
- } |
|
167 |
- |
|
168 |
-} |
|
169 |
- |
|
170 |
-// This tests the cases for binds which are generated through |
|
171 |
-// DecodeContainerConfig rather than Parse() |
|
172 |
-func TestDecodeContainerConfigVolumes(t *testing.T) { |
|
173 |
- |
|
174 |
- // Root to root |
|
175 |
- bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`}) |
|
176 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
177 |
- t.Fatalf("binds %v should have failed", bindsOrVols) |
|
178 |
- } |
|
179 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
180 |
- t.Fatalf("volume %v should have failed", bindsOrVols) |
|
181 |
- } |
|
182 |
- |
|
183 |
- // No destination path |
|
184 |
- bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`}) |
|
185 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
186 |
- t.Fatalf("binds %v should have failed", bindsOrVols) |
|
187 |
- } |
|
188 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
189 |
- t.Fatalf("volume %v should have failed", bindsOrVols) |
|
190 |
- } |
|
191 |
- |
|
192 |
- // // No destination path or mode |
|
193 |
- bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`}) |
|
194 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
195 |
- t.Fatalf("binds %v should have failed", bindsOrVols) |
|
196 |
- } |
|
197 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
198 |
- t.Fatalf("volume %v should have failed", bindsOrVols) |
|
199 |
- } |
|
200 |
- |
|
201 |
- // A whole lot of nothing |
|
202 |
- bindsOrVols = []string{`:`} |
|
203 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
204 |
- t.Fatalf("binds %v should have failed", bindsOrVols) |
|
205 |
- } |
|
206 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
207 |
- t.Fatalf("volume %v should have failed", bindsOrVols) |
|
208 |
- } |
|
209 |
- |
|
210 |
- // A whole lot of nothing with no mode |
|
211 |
- bindsOrVols = []string{`::`} |
|
212 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
213 |
- t.Fatalf("binds %v should have failed", bindsOrVols) |
|
214 |
- } |
|
215 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
216 |
- t.Fatalf("volume %v should have failed", bindsOrVols) |
|
217 |
- } |
|
218 |
- |
|
219 |
- // Too much including an invalid mode |
|
220 |
- wTmp := os.Getenv("TEMP") |
|
221 |
- bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp}) |
|
222 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
223 |
- t.Fatalf("binds %v should have failed", bindsOrVols) |
|
224 |
- } |
|
225 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
226 |
- t.Fatalf("volume %v should have failed", bindsOrVols) |
|
227 |
- } |
|
228 |
- |
|
229 |
- // Windows specific error tests |
|
230 |
- if runtime.GOOS == "windows" { |
|
231 |
- // Volume which does not include a drive letter |
|
232 |
- bindsOrVols = []string{`\tmp`} |
|
233 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
234 |
- t.Fatalf("binds %v should have failed", bindsOrVols) |
|
235 |
- } |
|
236 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
237 |
- t.Fatalf("volume %v should have failed", bindsOrVols) |
|
238 |
- } |
|
239 |
- |
|
240 |
- // Root to C-Drive |
|
241 |
- bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`} |
|
242 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
243 |
- t.Fatalf("binds %v should have failed", bindsOrVols) |
|
244 |
- } |
|
245 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
246 |
- t.Fatalf("volume %v should have failed", bindsOrVols) |
|
247 |
- } |
|
248 |
- |
|
249 |
- // Container path that does not include a drive letter |
|
250 |
- bindsOrVols = []string{`c:\windows:\somewhere`} |
|
251 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
252 |
- t.Fatalf("binds %v should have failed", bindsOrVols) |
|
253 |
- } |
|
254 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
255 |
- t.Fatalf("volume %v should have failed", bindsOrVols) |
|
256 |
- } |
|
257 |
- } |
|
258 |
- |
|
259 |
- // Linux-specific error tests |
|
260 |
- if runtime.GOOS != "windows" { |
|
261 |
- // Just root |
|
262 |
- bindsOrVols = []string{`/`} |
|
263 |
- if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { |
|
264 |
- t.Fatalf("binds %v should have failed", bindsOrVols) |
|
265 |
- } |
|
266 |
- if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { |
|
267 |
- t.Fatalf("volume %v should have failed", bindsOrVols) |
|
268 |
- } |
|
269 |
- |
|
270 |
- // A single volume that looks like a bind mount passed in Volumes. |
|
271 |
- // This should be handled as a bind mount, not a volume. |
|
272 |
- vols := []string{`/foo:/bar`} |
|
273 |
- if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil { |
|
274 |
- t.Fatal("Volume /foo:/bar should have succeeded as a volume name") |
|
275 |
- } else if hostConfig.Binds != nil { |
|
276 |
- t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds) |
|
277 |
- } else if _, exists := config.Volumes[vols[0]]; !exists { |
|
278 |
- t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes) |
|
279 |
- } |
|
280 |
- |
|
281 |
- } |
|
282 |
-} |
|
283 |
- |
|
284 |
-// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes |
|
285 |
-// to call DecodeContainerConfig. It effectively does what a client would |
|
286 |
-// do when calling the daemon by constructing a JSON stream of a |
|
287 |
-// ContainerConfigWrapper which is populated by the set of volume specs |
|
288 |
-// passed into it. It returns a config and a hostconfig which can be |
|
289 |
-// validated to ensure DecodeContainerConfig has manipulated the structures |
|
290 |
-// correctly. |
|
291 |
-func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) { |
|
292 |
- var ( |
|
293 |
- b []byte |
|
294 |
- err error |
|
295 |
- c *container.Config |
|
296 |
- h *container.HostConfig |
|
297 |
- ) |
|
298 |
- w := runconfig.ContainerConfigWrapper{ |
|
299 |
- Config: &container.Config{ |
|
300 |
- Volumes: map[string]struct{}{}, |
|
301 |
- }, |
|
302 |
- HostConfig: &container.HostConfig{ |
|
303 |
- NetworkMode: "none", |
|
304 |
- Binds: binds, |
|
305 |
- }, |
|
306 |
- } |
|
307 |
- for _, v := range volumes { |
|
308 |
- w.Config.Volumes[v] = struct{}{} |
|
309 |
- } |
|
310 |
- if b, err = json.Marshal(w); err != nil { |
|
311 |
- return nil, nil, fmt.Errorf("Error on marshal %s", err.Error()) |
|
312 |
- } |
|
313 |
- c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b)) |
|
314 |
- if err != nil { |
|
315 |
- return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err) |
|
316 |
- } |
|
317 |
- if c == nil || h == nil { |
|
318 |
- return nil, nil, fmt.Errorf("Empty config or hostconfig") |
|
319 |
- } |
|
320 |
- |
|
321 |
- return c, h, err |
|
322 |
-} |
|
323 |
- |
|
324 |
-// check if (a == c && b == d) || (a == d && b == c) |
|
325 |
-// because maps are randomized |
|
326 |
-func compareRandomizedStrings(a, b, c, d string) error { |
|
327 |
- if a == c && b == d { |
|
328 |
- return nil |
|
329 |
- } |
|
330 |
- if a == d && b == c { |
|
331 |
- return nil |
|
332 |
- } |
|
333 |
- return fmt.Errorf("strings don't match") |
|
334 |
-} |
|
335 |
- |
|
336 |
-// setupPlatformVolume takes two arrays of volume specs - a Unix style |
|
337 |
-// spec and a Windows style spec. Depending on the platform being unit tested, |
|
338 |
-// it returns one of them, along with a volume string that would be passed |
|
339 |
-// on the docker CLI (e.g. -v /bar -v /foo). |
|
340 |
-func setupPlatformVolume(u []string, w []string) ([]string, string) { |
|
341 |
- var a []string |
|
342 |
- if runtime.GOOS == "windows" { |
|
343 |
- a = w |
|
344 |
- } else { |
|
345 |
- a = u |
|
346 |
- } |
|
347 |
- s := "" |
|
348 |
- for _, v := range a { |
|
349 |
- s = s + "-v " + v + " " |
|
350 |
- } |
|
351 |
- return a, s |
|
352 |
-} |
|
353 |
- |
|
354 |
-// Simple parse with MacAddress validation |
|
355 |
-func TestParseWithMacAddress(t *testing.T) { |
|
356 |
- invalidMacAddress := "--mac-address=invalidMacAddress" |
|
357 |
- validMacAddress := "--mac-address=92:d0:c6:0a:29:33" |
|
358 |
- if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" { |
|
359 |
- t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err) |
|
360 |
- } |
|
361 |
- if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" { |
|
362 |
- t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress) |
|
363 |
- } |
|
364 |
-} |
|
365 |
- |
|
366 |
-func TestParseWithMemory(t *testing.T) { |
|
367 |
- invalidMemory := "--memory=invalid" |
|
368 |
- validMemory := "--memory=1G" |
|
369 |
- if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" { |
|
370 |
- t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err) |
|
371 |
- } |
|
372 |
- if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 { |
|
373 |
- t.Fatalf("Expected the config to have '1G' as Memory, got '%v'", hostconfig.Memory) |
|
374 |
- } |
|
375 |
-} |
|
376 |
- |
|
377 |
-func TestParseWithMemorySwap(t *testing.T) { |
|
378 |
- invalidMemory := "--memory-swap=invalid" |
|
379 |
- validMemory := "--memory-swap=1G" |
|
380 |
- anotherValidMemory := "--memory-swap=-1" |
|
381 |
- if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" { |
|
382 |
- t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err) |
|
383 |
- } |
|
384 |
- if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 { |
|
385 |
- t.Fatalf("Expected the config to have '1073741824' as MemorySwap, got '%v'", hostconfig.MemorySwap) |
|
386 |
- } |
|
387 |
- if _, hostconfig := mustParse(t, anotherValidMemory); hostconfig.MemorySwap != -1 { |
|
388 |
- t.Fatalf("Expected the config to have '-1' as MemorySwap, got '%v'", hostconfig.MemorySwap) |
|
389 |
- } |
|
390 |
-} |
|
391 |
- |
|
392 |
-func TestParseHostname(t *testing.T) { |
|
393 |
- validHostnames := map[string]string{ |
|
394 |
- "hostname": "hostname", |
|
395 |
- "host-name": "host-name", |
|
396 |
- "hostname123": "hostname123", |
|
397 |
- "123hostname": "123hostname", |
|
398 |
- "hostname-of-63-bytes-long-should-be-valid-and-without-any-error": "hostname-of-63-bytes-long-should-be-valid-and-without-any-error", |
|
399 |
- } |
|
400 |
- hostnameWithDomain := "--hostname=hostname.domainname" |
|
401 |
- hostnameWithDomainTld := "--hostname=hostname.domainname.tld" |
|
402 |
- for hostname, expectedHostname := range validHostnames { |
|
403 |
- if config, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname { |
|
404 |
- t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname) |
|
405 |
- } |
|
406 |
- } |
|
407 |
- if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname.domainname" && config.Domainname != "" { |
|
408 |
- t.Fatalf("Expected the config to have 'hostname' as hostname.domainname, got '%v'", config.Hostname) |
|
409 |
- } |
|
410 |
- if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname.domainname.tld" && config.Domainname != "" { |
|
411 |
- t.Fatalf("Expected the config to have 'hostname' as hostname.domainname.tld, got '%v'", config.Hostname) |
|
412 |
- } |
|
413 |
-} |
|
414 |
- |
|
415 |
-func TestParseWithExpose(t *testing.T) { |
|
416 |
- invalids := map[string]string{ |
|
417 |
- ":": "invalid port format for --expose: :", |
|
418 |
- "8080:9090": "invalid port format for --expose: 8080:9090", |
|
419 |
- "/tcp": "invalid range format for --expose: /tcp, error: Empty string specified for ports.", |
|
420 |
- "/udp": "invalid range format for --expose: /udp, error: Empty string specified for ports.", |
|
421 |
- "NaN/tcp": `invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, |
|
422 |
- "NaN-NaN/tcp": `invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, |
|
423 |
- "8080-NaN/tcp": `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, |
|
424 |
- "1234567890-8080/tcp": `invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`, |
|
425 |
- } |
|
426 |
- valids := map[string][]nat.Port{ |
|
427 |
- "8080/tcp": {"8080/tcp"}, |
|
428 |
- "8080/udp": {"8080/udp"}, |
|
429 |
- "8080/ncp": {"8080/ncp"}, |
|
430 |
- "8080-8080/udp": {"8080/udp"}, |
|
431 |
- "8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"}, |
|
432 |
- } |
|
433 |
- for expose, expectedError := range invalids { |
|
434 |
- if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError { |
|
435 |
- t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err) |
|
436 |
- } |
|
437 |
- } |
|
438 |
- for expose, exposedPorts := range valids { |
|
439 |
- config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}) |
|
440 |
- if err != nil { |
|
441 |
- t.Fatal(err) |
|
442 |
- } |
|
443 |
- if len(config.ExposedPorts) != len(exposedPorts) { |
|
444 |
- t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts)) |
|
445 |
- } |
|
446 |
- for _, port := range exposedPorts { |
|
447 |
- if _, ok := config.ExposedPorts[port]; !ok { |
|
448 |
- t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts) |
|
449 |
- } |
|
450 |
- } |
|
451 |
- } |
|
452 |
- // Merge with actual published port |
|
453 |
- config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"}) |
|
454 |
- if err != nil { |
|
455 |
- t.Fatal(err) |
|
456 |
- } |
|
457 |
- if len(config.ExposedPorts) != 2 { |
|
458 |
- t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts) |
|
459 |
- } |
|
460 |
- ports := []nat.Port{"80/tcp", "81/tcp"} |
|
461 |
- for _, port := range ports { |
|
462 |
- if _, ok := config.ExposedPorts[port]; !ok { |
|
463 |
- t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts) |
|
464 |
- } |
|
465 |
- } |
|
466 |
-} |
|
467 |
- |
|
468 |
-func TestParseDevice(t *testing.T) { |
|
469 |
- valids := map[string]container.DeviceMapping{ |
|
470 |
- "/dev/snd": { |
|
471 |
- PathOnHost: "/dev/snd", |
|
472 |
- PathInContainer: "/dev/snd", |
|
473 |
- CgroupPermissions: "rwm", |
|
474 |
- }, |
|
475 |
- "/dev/snd:rw": { |
|
476 |
- PathOnHost: "/dev/snd", |
|
477 |
- PathInContainer: "/dev/snd", |
|
478 |
- CgroupPermissions: "rw", |
|
479 |
- }, |
|
480 |
- "/dev/snd:/something": { |
|
481 |
- PathOnHost: "/dev/snd", |
|
482 |
- PathInContainer: "/something", |
|
483 |
- CgroupPermissions: "rwm", |
|
484 |
- }, |
|
485 |
- "/dev/snd:/something:rw": { |
|
486 |
- PathOnHost: "/dev/snd", |
|
487 |
- PathInContainer: "/something", |
|
488 |
- CgroupPermissions: "rw", |
|
489 |
- }, |
|
490 |
- } |
|
491 |
- for device, deviceMapping := range valids { |
|
492 |
- _, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"}) |
|
493 |
- if err != nil { |
|
494 |
- t.Fatal(err) |
|
495 |
- } |
|
496 |
- if len(hostconfig.Devices) != 1 { |
|
497 |
- t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices) |
|
498 |
- } |
|
499 |
- if hostconfig.Devices[0] != deviceMapping { |
|
500 |
- t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices) |
|
501 |
- } |
|
502 |
- } |
|
503 |
- |
|
504 |
-} |
|
505 |
- |
|
506 |
-func TestParseModes(t *testing.T) { |
|
507 |
- // ipc ko |
|
508 |
- if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" { |
|
509 |
- t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err) |
|
510 |
- } |
|
511 |
- // ipc ok |
|
512 |
- _, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"}) |
|
513 |
- if err != nil { |
|
514 |
- t.Fatal(err) |
|
515 |
- } |
|
516 |
- if !hostconfig.IpcMode.Valid() { |
|
517 |
- t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode) |
|
518 |
- } |
|
519 |
- // pid ko |
|
520 |
- if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" { |
|
521 |
- t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err) |
|
522 |
- } |
|
523 |
- // pid ok |
|
524 |
- _, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"}) |
|
525 |
- if err != nil { |
|
526 |
- t.Fatal(err) |
|
527 |
- } |
|
528 |
- if !hostconfig.PidMode.Valid() { |
|
529 |
- t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode) |
|
530 |
- } |
|
531 |
- // uts ko |
|
532 |
- if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" { |
|
533 |
- t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err) |
|
534 |
- } |
|
535 |
- // uts ok |
|
536 |
- _, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"}) |
|
537 |
- if err != nil { |
|
538 |
- t.Fatal(err) |
|
539 |
- } |
|
540 |
- if !hostconfig.UTSMode.Valid() { |
|
541 |
- t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode) |
|
542 |
- } |
|
543 |
- // shm-size ko |
|
544 |
- if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" { |
|
545 |
- t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err) |
|
546 |
- } |
|
547 |
- // shm-size ok |
|
548 |
- _, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"}) |
|
549 |
- if err != nil { |
|
550 |
- t.Fatal(err) |
|
551 |
- } |
|
552 |
- if hostconfig.ShmSize != 134217728 { |
|
553 |
- t.Fatalf("Expected a valid ShmSize, got %d", hostconfig.ShmSize) |
|
554 |
- } |
|
555 |
-} |
|
556 |
- |
|
557 |
-func TestParseRestartPolicy(t *testing.T) { |
|
558 |
- invalids := map[string]string{ |
|
559 |
- "always:2:3": "invalid restart policy format", |
|
560 |
- "on-failure:invalid": "maximum retry count must be an integer", |
|
561 |
- } |
|
562 |
- valids := map[string]container.RestartPolicy{ |
|
563 |
- "": {}, |
|
564 |
- "always": { |
|
565 |
- Name: "always", |
|
566 |
- MaximumRetryCount: 0, |
|
567 |
- }, |
|
568 |
- "on-failure:1": { |
|
569 |
- Name: "on-failure", |
|
570 |
- MaximumRetryCount: 1, |
|
571 |
- }, |
|
572 |
- } |
|
573 |
- for restart, expectedError := range invalids { |
|
574 |
- if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError { |
|
575 |
- t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err) |
|
576 |
- } |
|
577 |
- } |
|
578 |
- for restart, expected := range valids { |
|
579 |
- _, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"}) |
|
580 |
- if err != nil { |
|
581 |
- t.Fatal(err) |
|
582 |
- } |
|
583 |
- if hostconfig.RestartPolicy != expected { |
|
584 |
- t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy) |
|
585 |
- } |
|
586 |
- } |
|
587 |
-} |
|
588 |
- |
|
589 |
-func TestParseHealth(t *testing.T) { |
|
590 |
- checkOk := func(args ...string) *container.HealthConfig { |
|
591 |
- config, _, _, err := parseRun(args) |
|
592 |
- if err != nil { |
|
593 |
- t.Fatalf("%#v: %v", args, err) |
|
594 |
- } |
|
595 |
- return config.Healthcheck |
|
596 |
- } |
|
597 |
- checkError := func(expected string, args ...string) { |
|
598 |
- config, _, _, err := parseRun(args) |
|
599 |
- if err == nil { |
|
600 |
- t.Fatalf("Expected error, but got %#v", config) |
|
601 |
- } |
|
602 |
- if err.Error() != expected { |
|
603 |
- t.Fatalf("Expected %#v, got %#v", expected, err) |
|
604 |
- } |
|
605 |
- } |
|
606 |
- health := checkOk("--no-healthcheck", "img", "cmd") |
|
607 |
- if health == nil || len(health.Test) != 1 || health.Test[0] != "NONE" { |
|
608 |
- t.Fatalf("--no-healthcheck failed: %#v", health) |
|
609 |
- } |
|
610 |
- |
|
611 |
- health = checkOk("--health-cmd=/check.sh -q", "img", "cmd") |
|
612 |
- if len(health.Test) != 2 || health.Test[0] != "CMD-SHELL" || health.Test[1] != "/check.sh -q" { |
|
613 |
- t.Fatalf("--health-cmd: got %#v", health.Test) |
|
614 |
- } |
|
615 |
- if health.Timeout != 0 { |
|
616 |
- t.Fatalf("--health-cmd: timeout = %f", health.Timeout) |
|
617 |
- } |
|
618 |
- |
|
619 |
- checkError("--no-healthcheck conflicts with --health-* options", |
|
620 |
- "--no-healthcheck", "--health-cmd=/check.sh -q", "img", "cmd") |
|
621 |
- |
|
622 |
- health = checkOk("--health-timeout=2s", "--health-retries=3", "--health-interval=4.5s", "img", "cmd") |
|
623 |
- if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond { |
|
624 |
- t.Fatalf("--health-*: got %#v", health) |
|
625 |
- } |
|
626 |
-} |
|
627 |
- |
|
628 |
-func TestParseLoggingOpts(t *testing.T) { |
|
629 |
- // logging opts ko |
|
630 |
- if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "invalid logging opts for driver none" { |
|
631 |
- t.Fatalf("Expected an error with message 'invalid logging opts for driver none', got %v", err) |
|
632 |
- } |
|
633 |
- // logging opts ok |
|
634 |
- _, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"}) |
|
635 |
- if err != nil { |
|
636 |
- t.Fatal(err) |
|
637 |
- } |
|
638 |
- if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 { |
|
639 |
- t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy) |
|
640 |
- } |
|
641 |
-} |
|
642 |
- |
|
643 |
-func TestParseEnvfileVariables(t *testing.T) { |
|
644 |
- e := "open nonexistent: no such file or directory" |
|
645 |
- if runtime.GOOS == "windows" { |
|
646 |
- e = "open nonexistent: The system cannot find the file specified." |
|
647 |
- } |
|
648 |
- // env ko |
|
649 |
- if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e { |
|
650 |
- t.Fatalf("Expected an error with message '%s', got %v", e, err) |
|
651 |
- } |
|
652 |
- // env ok |
|
653 |
- config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"}) |
|
654 |
- if err != nil { |
|
655 |
- t.Fatal(err) |
|
656 |
- } |
|
657 |
- if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" { |
|
658 |
- t.Fatalf("Expected a config with [ENV1=value1], got %v", config.Env) |
|
659 |
- } |
|
660 |
- config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"}) |
|
661 |
- if err != nil { |
|
662 |
- t.Fatal(err) |
|
663 |
- } |
|
664 |
- if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" { |
|
665 |
- t.Fatalf("Expected a config with [ENV1=value1 ENV2=value2], got %v", config.Env) |
|
666 |
- } |
|
667 |
-} |
|
668 |
- |
|
669 |
-func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) { |
|
670 |
- // UTF8 with BOM |
|
671 |
- config, _, _, err := parseRun([]string{"--env-file=fixtures/utf8.env", "img", "cmd"}) |
|
672 |
- if err != nil { |
|
673 |
- t.Fatal(err) |
|
674 |
- } |
|
675 |
- env := []string{"FOO=BAR", "HELLO=" + string([]byte{0xe6, 0x82, 0xa8, 0xe5, 0xa5, 0xbd}), "BAR=FOO"} |
|
676 |
- if len(config.Env) != len(env) { |
|
677 |
- t.Fatalf("Expected a config with %d env variables, got %v: %v", len(env), len(config.Env), config.Env) |
|
678 |
- } |
|
679 |
- for i, v := range env { |
|
680 |
- if config.Env[i] != v { |
|
681 |
- t.Fatalf("Expected a config with [%s], got %v", v, []byte(config.Env[i])) |
|
682 |
- } |
|
683 |
- } |
|
684 |
- |
|
685 |
- // UTF16 with BOM |
|
686 |
- e := "contains invalid utf8 bytes at line" |
|
687 |
- if _, _, _, err := parseRun([]string{"--env-file=fixtures/utf16.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) { |
|
688 |
- t.Fatalf("Expected an error with message '%s', got %v", e, err) |
|
689 |
- } |
|
690 |
- // UTF16BE with BOM |
|
691 |
- if _, _, _, err := parseRun([]string{"--env-file=fixtures/utf16be.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) { |
|
692 |
- t.Fatalf("Expected an error with message '%s', got %v", e, err) |
|
693 |
- } |
|
694 |
-} |
|
695 |
- |
|
696 |
-func TestParseLabelfileVariables(t *testing.T) { |
|
697 |
- e := "open nonexistent: no such file or directory" |
|
698 |
- if runtime.GOOS == "windows" { |
|
699 |
- e = "open nonexistent: The system cannot find the file specified." |
|
700 |
- } |
|
701 |
- // label ko |
|
702 |
- if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e { |
|
703 |
- t.Fatalf("Expected an error with message '%s', got %v", e, err) |
|
704 |
- } |
|
705 |
- // label ok |
|
706 |
- config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"}) |
|
707 |
- if err != nil { |
|
708 |
- t.Fatal(err) |
|
709 |
- } |
|
710 |
- if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" { |
|
711 |
- t.Fatalf("Expected a config with [LABEL1:value1], got %v", config.Labels) |
|
712 |
- } |
|
713 |
- config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"}) |
|
714 |
- if err != nil { |
|
715 |
- t.Fatal(err) |
|
716 |
- } |
|
717 |
- if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" { |
|
718 |
- t.Fatalf("Expected a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels) |
|
719 |
- } |
|
720 |
-} |
|
721 |
- |
|
722 |
-func TestParseEntryPoint(t *testing.T) { |
|
723 |
- config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"}) |
|
724 |
- if err != nil { |
|
725 |
- t.Fatal(err) |
|
726 |
- } |
|
727 |
- if len(config.Entrypoint) != 1 && config.Entrypoint[0] != "anything" { |
|
728 |
- t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint) |
|
729 |
- } |
|
730 |
-} |
|
731 |
- |
|
732 |
-func TestValidateLink(t *testing.T) { |
|
733 |
- valid := []string{ |
|
734 |
- "name", |
|
735 |
- "dcdfbe62ecd0:alias", |
|
736 |
- "7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da", |
|
737 |
- "angry_torvalds:linus", |
|
738 |
- } |
|
739 |
- invalid := map[string]string{ |
|
740 |
- "": "empty string specified for links", |
|
741 |
- "too:much:of:it": "bad format for links: too:much:of:it", |
|
742 |
- } |
|
743 |
- |
|
744 |
- for _, link := range valid { |
|
745 |
- if _, err := ValidateLink(link); err != nil { |
|
746 |
- t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err) |
|
747 |
- } |
|
748 |
- } |
|
749 |
- |
|
750 |
- for link, expectedError := range invalid { |
|
751 |
- if _, err := ValidateLink(link); err == nil { |
|
752 |
- t.Fatalf("ValidateLink(`%q`) should have failed validation", link) |
|
753 |
- } else { |
|
754 |
- if !strings.Contains(err.Error(), expectedError) { |
|
755 |
- t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError) |
|
756 |
- } |
|
757 |
- } |
|
758 |
- } |
|
759 |
-} |
|
760 |
- |
|
761 |
-func TestParseLink(t *testing.T) { |
|
762 |
- name, alias, err := ParseLink("name:alias") |
|
763 |
- if err != nil { |
|
764 |
- t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err) |
|
765 |
- } |
|
766 |
- if name != "name" { |
|
767 |
- t.Fatalf("Link name should have been name, got %s instead", name) |
|
768 |
- } |
|
769 |
- if alias != "alias" { |
|
770 |
- t.Fatalf("Link alias should have been alias, got %s instead", alias) |
|
771 |
- } |
|
772 |
- // short format definition |
|
773 |
- name, alias, err = ParseLink("name") |
|
774 |
- if err != nil { |
|
775 |
- t.Fatalf("Expected not to error out on a valid name only format but got: %v", err) |
|
776 |
- } |
|
777 |
- if name != "name" { |
|
778 |
- t.Fatalf("Link name should have been name, got %s instead", name) |
|
779 |
- } |
|
780 |
- if alias != "name" { |
|
781 |
- t.Fatalf("Link alias should have been name, got %s instead", alias) |
|
782 |
- } |
|
783 |
- // empty string link definition is not allowed |
|
784 |
- if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") { |
|
785 |
- t.Fatalf("Expected error 'empty string specified for links' but got: %v", err) |
|
786 |
- } |
|
787 |
- // more than two colons are not allowed |
|
788 |
- if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") { |
|
789 |
- t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err) |
|
790 |
- } |
|
791 |
-} |
|
792 |
- |
|
793 |
-func TestValidateDevice(t *testing.T) { |
|
794 |
- valid := []string{ |
|
795 |
- "/home", |
|
796 |
- "/home:/home", |
|
797 |
- "/home:/something/else", |
|
798 |
- "/with space", |
|
799 |
- "/home:/with space", |
|
800 |
- "relative:/absolute-path", |
|
801 |
- "hostPath:/containerPath:r", |
|
802 |
- "/hostPath:/containerPath:rw", |
|
803 |
- "/hostPath:/containerPath:mrw", |
|
804 |
- } |
|
805 |
- invalid := map[string]string{ |
|
806 |
- "": "bad format for path: ", |
|
807 |
- "./": "./ is not an absolute path", |
|
808 |
- "../": "../ is not an absolute path", |
|
809 |
- "/:../": "../ is not an absolute path", |
|
810 |
- "/:path": "path is not an absolute path", |
|
811 |
- ":": "bad format for path: :", |
|
812 |
- "/tmp:": " is not an absolute path", |
|
813 |
- ":test": "bad format for path: :test", |
|
814 |
- ":/test": "bad format for path: :/test", |
|
815 |
- "tmp:": " is not an absolute path", |
|
816 |
- ":test:": "bad format for path: :test:", |
|
817 |
- "::": "bad format for path: ::", |
|
818 |
- ":::": "bad format for path: :::", |
|
819 |
- "/tmp:::": "bad format for path: /tmp:::", |
|
820 |
- ":/tmp::": "bad format for path: :/tmp::", |
|
821 |
- "path:ro": "ro is not an absolute path", |
|
822 |
- "path:rr": "rr is not an absolute path", |
|
823 |
- "a:/b:ro": "bad mode specified: ro", |
|
824 |
- "a:/b:rr": "bad mode specified: rr", |
|
825 |
- } |
|
826 |
- |
|
827 |
- for _, path := range valid { |
|
828 |
- if _, err := ValidateDevice(path); err != nil { |
|
829 |
- t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err) |
|
830 |
- } |
|
831 |
- } |
|
832 |
- |
|
833 |
- for path, expectedError := range invalid { |
|
834 |
- if _, err := ValidateDevice(path); err == nil { |
|
835 |
- t.Fatalf("ValidateDevice(`%q`) should have failed validation", path) |
|
836 |
- } else { |
|
837 |
- if err.Error() != expectedError { |
|
838 |
- t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error()) |
|
839 |
- } |
|
840 |
- } |
|
841 |
- } |
|
842 |
-} |
|
843 |
- |
|
844 |
-func TestVolumeSplitN(t *testing.T) { |
|
845 |
- for _, x := range []struct { |
|
846 |
- input string |
|
847 |
- n int |
|
848 |
- expected []string |
|
849 |
- }{ |
|
850 |
- {`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}}, |
|
851 |
- {`:C:\foo:d:`, -1, nil}, |
|
852 |
- {`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}}, |
|
853 |
- {`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}}, |
|
854 |
- {`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}}, |
|
855 |
- |
|
856 |
- {`d:\`, -1, []string{`d:\`}}, |
|
857 |
- {`d:`, -1, []string{`d:`}}, |
|
858 |
- {`d:\path`, -1, []string{`d:\path`}}, |
|
859 |
- {`d:\path with space`, -1, []string{`d:\path with space`}}, |
|
860 |
- {`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}}, |
|
861 |
- {`c:\:d:\`, -1, []string{`c:\`, `d:\`}}, |
|
862 |
- {`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}}, |
|
863 |
- {`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}}, |
|
864 |
- {`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}}, |
|
865 |
- {`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}}, |
|
866 |
- {`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}}, |
|
867 |
- {`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}}, |
|
868 |
- {`name:D:`, -1, []string{`name`, `D:`}}, |
|
869 |
- {`name:D::rW`, -1, []string{`name`, `D:`, `rW`}}, |
|
870 |
- {`name:D::RW`, -1, []string{`name`, `D:`, `RW`}}, |
|
871 |
- {`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}}, |
|
872 |
- {`c:\Windows`, -1, []string{`c:\Windows`}}, |
|
873 |
- {`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}}, |
|
874 |
- |
|
875 |
- {``, -1, nil}, |
|
876 |
- {`.`, -1, []string{`.`}}, |
|
877 |
- {`..\`, -1, []string{`..\`}}, |
|
878 |
- {`c:\:..\`, -1, []string{`c:\`, `..\`}}, |
|
879 |
- {`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}}, |
|
880 |
- |
|
881 |
- // Cover directories with one-character name |
|
882 |
- {`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}}, |
|
883 |
- } { |
|
884 |
- res := volumeSplitN(x.input, x.n) |
|
885 |
- if len(res) < len(x.expected) { |
|
886 |
- t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res) |
|
887 |
- } |
|
888 |
- for i, e := range res { |
|
889 |
- if e != x.expected[i] { |
|
890 |
- t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res) |
|
891 |
- } |
|
892 |
- } |
|
893 |
- } |
|
894 |
-} |
895 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,79 +0,0 @@ |
1 |
-package opts |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "fmt" |
|
5 |
- "strings" |
|
6 |
- |
|
7 |
- "github.com/docker/docker/api/types" |
|
8 |
-) |
|
9 |
- |
|
10 |
-// RuntimeOpt defines a map of Runtimes |
|
11 |
-type RuntimeOpt struct { |
|
12 |
- name string |
|
13 |
- stockRuntimeName string |
|
14 |
- values *map[string]types.Runtime |
|
15 |
-} |
|
16 |
- |
|
17 |
-// NewNamedRuntimeOpt creates a new RuntimeOpt |
|
18 |
-func NewNamedRuntimeOpt(name string, ref *map[string]types.Runtime, stockRuntime string) *RuntimeOpt { |
|
19 |
- if ref == nil { |
|
20 |
- ref = &map[string]types.Runtime{} |
|
21 |
- } |
|
22 |
- return &RuntimeOpt{name: name, values: ref, stockRuntimeName: stockRuntime} |
|
23 |
-} |
|
24 |
- |
|
25 |
-// Name returns the name of the NamedListOpts in the configuration. |
|
26 |
-func (o *RuntimeOpt) Name() string { |
|
27 |
- return o.name |
|
28 |
-} |
|
29 |
- |
|
30 |
-// Set validates and updates the list of Runtimes |
|
31 |
-func (o *RuntimeOpt) Set(val string) error { |
|
32 |
- parts := strings.SplitN(val, "=", 2) |
|
33 |
- if len(parts) != 2 { |
|
34 |
- return fmt.Errorf("invalid runtime argument: %s", val) |
|
35 |
- } |
|
36 |
- |
|
37 |
- parts[0] = strings.TrimSpace(parts[0]) |
|
38 |
- parts[1] = strings.TrimSpace(parts[1]) |
|
39 |
- if parts[0] == "" || parts[1] == "" { |
|
40 |
- return fmt.Errorf("invalid runtime argument: %s", val) |
|
41 |
- } |
|
42 |
- |
|
43 |
- parts[0] = strings.ToLower(parts[0]) |
|
44 |
- if parts[0] == o.stockRuntimeName { |
|
45 |
- return fmt.Errorf("runtime name '%s' is reserved", o.stockRuntimeName) |
|
46 |
- } |
|
47 |
- |
|
48 |
- if _, ok := (*o.values)[parts[0]]; ok { |
|
49 |
- return fmt.Errorf("runtime '%s' was already defined", parts[0]) |
|
50 |
- } |
|
51 |
- |
|
52 |
- (*o.values)[parts[0]] = types.Runtime{Path: parts[1]} |
|
53 |
- |
|
54 |
- return nil |
|
55 |
-} |
|
56 |
- |
|
57 |
-// String returns Runtime values as a string. |
|
58 |
-func (o *RuntimeOpt) String() string { |
|
59 |
- var out []string |
|
60 |
- for k := range *o.values { |
|
61 |
- out = append(out, k) |
|
62 |
- } |
|
63 |
- |
|
64 |
- return fmt.Sprintf("%v", out) |
|
65 |
-} |
|
66 |
- |
|
67 |
-// GetMap returns a map of Runtimes (name: path) |
|
68 |
-func (o *RuntimeOpt) GetMap() map[string]types.Runtime { |
|
69 |
- if o.values != nil { |
|
70 |
- return *o.values |
|
71 |
- } |
|
72 |
- |
|
73 |
- return map[string]types.Runtime{} |
|
74 |
-} |
|
75 |
- |
|
76 |
-// Type returns the type of the option |
|
77 |
-func (o *RuntimeOpt) Type() string { |
|
78 |
- return "runtime" |
|
79 |
-} |
80 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,111 +0,0 @@ |
1 |
-package opts |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "fmt" |
|
5 |
- "strconv" |
|
6 |
- "strings" |
|
7 |
- |
|
8 |
- "github.com/docker/docker/api/types/blkiodev" |
|
9 |
- "github.com/docker/go-units" |
|
10 |
-) |
|
11 |
- |
|
12 |
-// ValidatorThrottleFctType defines a validator function that returns a validated struct and/or an error. |
|
13 |
-type ValidatorThrottleFctType func(val string) (*blkiodev.ThrottleDevice, error) |
|
14 |
- |
|
15 |
-// ValidateThrottleBpsDevice validates that the specified string has a valid device-rate format. |
|
16 |
-func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) { |
|
17 |
- split := strings.SplitN(val, ":", 2) |
|
18 |
- if len(split) != 2 { |
|
19 |
- return nil, fmt.Errorf("bad format: %s", val) |
|
20 |
- } |
|
21 |
- if !strings.HasPrefix(split[0], "/dev/") { |
|
22 |
- return nil, fmt.Errorf("bad format for device path: %s", val) |
|
23 |
- } |
|
24 |
- rate, err := units.RAMInBytes(split[1]) |
|
25 |
- if err != nil { |
|
26 |
- return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) |
|
27 |
- } |
|
28 |
- if rate < 0 { |
|
29 |
- return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) |
|
30 |
- } |
|
31 |
- |
|
32 |
- return &blkiodev.ThrottleDevice{ |
|
33 |
- Path: split[0], |
|
34 |
- Rate: uint64(rate), |
|
35 |
- }, nil |
|
36 |
-} |
|
37 |
- |
|
38 |
-// ValidateThrottleIOpsDevice validates that the specified string has a valid device-rate format. |
|
39 |
-func ValidateThrottleIOpsDevice(val string) (*blkiodev.ThrottleDevice, error) { |
|
40 |
- split := strings.SplitN(val, ":", 2) |
|
41 |
- if len(split) != 2 { |
|
42 |
- return nil, fmt.Errorf("bad format: %s", val) |
|
43 |
- } |
|
44 |
- if !strings.HasPrefix(split[0], "/dev/") { |
|
45 |
- return nil, fmt.Errorf("bad format for device path: %s", val) |
|
46 |
- } |
|
47 |
- rate, err := strconv.ParseUint(split[1], 10, 64) |
|
48 |
- if err != nil { |
|
49 |
- return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val) |
|
50 |
- } |
|
51 |
- if rate < 0 { |
|
52 |
- return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val) |
|
53 |
- } |
|
54 |
- |
|
55 |
- return &blkiodev.ThrottleDevice{ |
|
56 |
- Path: split[0], |
|
57 |
- Rate: uint64(rate), |
|
58 |
- }, nil |
|
59 |
-} |
|
60 |
- |
|
61 |
-// ThrottledeviceOpt defines a map of ThrottleDevices |
|
62 |
-type ThrottledeviceOpt struct { |
|
63 |
- values []*blkiodev.ThrottleDevice |
|
64 |
- validator ValidatorThrottleFctType |
|
65 |
-} |
|
66 |
- |
|
67 |
-// NewThrottledeviceOpt creates a new ThrottledeviceOpt |
|
68 |
-func NewThrottledeviceOpt(validator ValidatorThrottleFctType) ThrottledeviceOpt { |
|
69 |
- values := []*blkiodev.ThrottleDevice{} |
|
70 |
- return ThrottledeviceOpt{ |
|
71 |
- values: values, |
|
72 |
- validator: validator, |
|
73 |
- } |
|
74 |
-} |
|
75 |
- |
|
76 |
-// Set validates a ThrottleDevice and sets its name as a key in ThrottledeviceOpt |
|
77 |
-func (opt *ThrottledeviceOpt) Set(val string) error { |
|
78 |
- var value *blkiodev.ThrottleDevice |
|
79 |
- if opt.validator != nil { |
|
80 |
- v, err := opt.validator(val) |
|
81 |
- if err != nil { |
|
82 |
- return err |
|
83 |
- } |
|
84 |
- value = v |
|
85 |
- } |
|
86 |
- (opt.values) = append((opt.values), value) |
|
87 |
- return nil |
|
88 |
-} |
|
89 |
- |
|
90 |
-// String returns ThrottledeviceOpt values as a string. |
|
91 |
-func (opt *ThrottledeviceOpt) String() string { |
|
92 |
- var out []string |
|
93 |
- for _, v := range opt.values { |
|
94 |
- out = append(out, v.String()) |
|
95 |
- } |
|
96 |
- |
|
97 |
- return fmt.Sprintf("%v", out) |
|
98 |
-} |
|
99 |
- |
|
100 |
-// GetList returns a slice of pointers to ThrottleDevices. |
|
101 |
-func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice { |
|
102 |
- var throttledevice []*blkiodev.ThrottleDevice |
|
103 |
- throttledevice = append(throttledevice, opt.values...) |
|
104 |
- |
|
105 |
- return throttledevice |
|
106 |
-} |
|
107 |
- |
|
108 |
-// Type returns the option type |
|
109 |
-func (opt *ThrottledeviceOpt) Type() string { |
|
110 |
- return "throttled-device" |
|
111 |
-} |
112 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,57 +0,0 @@ |
1 |
-package opts |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "fmt" |
|
5 |
- |
|
6 |
- "github.com/docker/go-units" |
|
7 |
-) |
|
8 |
- |
|
9 |
-// UlimitOpt defines a map of Ulimits |
|
10 |
-type UlimitOpt struct { |
|
11 |
- values *map[string]*units.Ulimit |
|
12 |
-} |
|
13 |
- |
|
14 |
-// NewUlimitOpt creates a new UlimitOpt |
|
15 |
-func NewUlimitOpt(ref *map[string]*units.Ulimit) *UlimitOpt { |
|
16 |
- if ref == nil { |
|
17 |
- ref = &map[string]*units.Ulimit{} |
|
18 |
- } |
|
19 |
- return &UlimitOpt{ref} |
|
20 |
-} |
|
21 |
- |
|
22 |
-// Set validates a Ulimit and sets its name as a key in UlimitOpt |
|
23 |
-func (o *UlimitOpt) Set(val string) error { |
|
24 |
- l, err := units.ParseUlimit(val) |
|
25 |
- if err != nil { |
|
26 |
- return err |
|
27 |
- } |
|
28 |
- |
|
29 |
- (*o.values)[l.Name] = l |
|
30 |
- |
|
31 |
- return nil |
|
32 |
-} |
|
33 |
- |
|
34 |
-// String returns Ulimit values as a string. |
|
35 |
-func (o *UlimitOpt) String() string { |
|
36 |
- var out []string |
|
37 |
- for _, v := range *o.values { |
|
38 |
- out = append(out, v.String()) |
|
39 |
- } |
|
40 |
- |
|
41 |
- return fmt.Sprintf("%v", out) |
|
42 |
-} |
|
43 |
- |
|
44 |
-// GetList returns a slice of pointers to Ulimits. |
|
45 |
-func (o *UlimitOpt) GetList() []*units.Ulimit { |
|
46 |
- var ulimits []*units.Ulimit |
|
47 |
- for _, v := range *o.values { |
|
48 |
- ulimits = append(ulimits, v) |
|
49 |
- } |
|
50 |
- |
|
51 |
- return ulimits |
|
52 |
-} |
|
53 |
- |
|
54 |
-// Type returns the option type |
|
55 |
-func (o *UlimitOpt) Type() string { |
|
56 |
- return "ulimit" |
|
57 |
-} |
58 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,42 +0,0 @@ |
1 |
-package opts |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "testing" |
|
5 |
- |
|
6 |
- "github.com/docker/go-units" |
|
7 |
-) |
|
8 |
- |
|
9 |
-func TestUlimitOpt(t *testing.T) { |
|
10 |
- ulimitMap := map[string]*units.Ulimit{ |
|
11 |
- "nofile": {"nofile", 1024, 512}, |
|
12 |
- } |
|
13 |
- |
|
14 |
- ulimitOpt := NewUlimitOpt(&ulimitMap) |
|
15 |
- |
|
16 |
- expected := "[nofile=512:1024]" |
|
17 |
- if ulimitOpt.String() != expected { |
|
18 |
- t.Fatalf("Expected %v, got %v", expected, ulimitOpt) |
|
19 |
- } |
|
20 |
- |
|
21 |
- // Valid ulimit append to opts |
|
22 |
- if err := ulimitOpt.Set("core=1024:1024"); err != nil { |
|
23 |
- t.Fatal(err) |
|
24 |
- } |
|
25 |
- |
|
26 |
- // Invalid ulimit type returns an error and do not append to opts |
|
27 |
- if err := ulimitOpt.Set("notavalidtype=1024:1024"); err == nil { |
|
28 |
- t.Fatalf("Expected error on invalid ulimit type") |
|
29 |
- } |
|
30 |
- expected = "[nofile=512:1024 core=1024:1024]" |
|
31 |
- expected2 := "[core=1024:1024 nofile=512:1024]" |
|
32 |
- result := ulimitOpt.String() |
|
33 |
- if result != expected && result != expected2 { |
|
34 |
- t.Fatalf("Expected %v or %v, got %v", expected, expected2, ulimitOpt) |
|
35 |
- } |
|
36 |
- |
|
37 |
- // And test GetList |
|
38 |
- ulimits := ulimitOpt.GetList() |
|
39 |
- if len(ulimits) != 2 { |
|
40 |
- t.Fatalf("Expected a ulimit list of 2, got %v", ulimits) |
|
41 |
- } |
|
42 |
-} |
43 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,89 +0,0 @@ |
1 |
-package opts |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "fmt" |
|
5 |
- "strconv" |
|
6 |
- "strings" |
|
7 |
- |
|
8 |
- "github.com/docker/docker/api/types/blkiodev" |
|
9 |
-) |
|
10 |
- |
|
11 |
-// ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error. |
|
12 |
-type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error) |
|
13 |
- |
|
14 |
-// ValidateWeightDevice validates that the specified string has a valid device-weight format. |
|
15 |
-func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) { |
|
16 |
- split := strings.SplitN(val, ":", 2) |
|
17 |
- if len(split) != 2 { |
|
18 |
- return nil, fmt.Errorf("bad format: %s", val) |
|
19 |
- } |
|
20 |
- if !strings.HasPrefix(split[0], "/dev/") { |
|
21 |
- return nil, fmt.Errorf("bad format for device path: %s", val) |
|
22 |
- } |
|
23 |
- weight, err := strconv.ParseUint(split[1], 10, 0) |
|
24 |
- if err != nil { |
|
25 |
- return nil, fmt.Errorf("invalid weight for device: %s", val) |
|
26 |
- } |
|
27 |
- if weight > 0 && (weight < 10 || weight > 1000) { |
|
28 |
- return nil, fmt.Errorf("invalid weight for device: %s", val) |
|
29 |
- } |
|
30 |
- |
|
31 |
- return &blkiodev.WeightDevice{ |
|
32 |
- Path: split[0], |
|
33 |
- Weight: uint16(weight), |
|
34 |
- }, nil |
|
35 |
-} |
|
36 |
- |
|
37 |
-// WeightdeviceOpt defines a map of WeightDevices |
|
38 |
-type WeightdeviceOpt struct { |
|
39 |
- values []*blkiodev.WeightDevice |
|
40 |
- validator ValidatorWeightFctType |
|
41 |
-} |
|
42 |
- |
|
43 |
-// NewWeightdeviceOpt creates a new WeightdeviceOpt |
|
44 |
-func NewWeightdeviceOpt(validator ValidatorWeightFctType) WeightdeviceOpt { |
|
45 |
- values := []*blkiodev.WeightDevice{} |
|
46 |
- return WeightdeviceOpt{ |
|
47 |
- values: values, |
|
48 |
- validator: validator, |
|
49 |
- } |
|
50 |
-} |
|
51 |
- |
|
52 |
-// Set validates a WeightDevice and sets its name as a key in WeightdeviceOpt |
|
53 |
-func (opt *WeightdeviceOpt) Set(val string) error { |
|
54 |
- var value *blkiodev.WeightDevice |
|
55 |
- if opt.validator != nil { |
|
56 |
- v, err := opt.validator(val) |
|
57 |
- if err != nil { |
|
58 |
- return err |
|
59 |
- } |
|
60 |
- value = v |
|
61 |
- } |
|
62 |
- (opt.values) = append((opt.values), value) |
|
63 |
- return nil |
|
64 |
-} |
|
65 |
- |
|
66 |
-// String returns WeightdeviceOpt values as a string. |
|
67 |
-func (opt *WeightdeviceOpt) String() string { |
|
68 |
- var out []string |
|
69 |
- for _, v := range opt.values { |
|
70 |
- out = append(out, v.String()) |
|
71 |
- } |
|
72 |
- |
|
73 |
- return fmt.Sprintf("%v", out) |
|
74 |
-} |
|
75 |
- |
|
76 |
-// GetList returns a slice of pointers to WeightDevices. |
|
77 |
-func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice { |
|
78 |
- var weightdevice []*blkiodev.WeightDevice |
|
79 |
- for _, v := range opt.values { |
|
80 |
- weightdevice = append(weightdevice, v) |
|
81 |
- } |
|
82 |
- |
|
83 |
- return weightdevice |
|
84 |
-} |
|
85 |
- |
|
86 |
-// Type returns the option type |
|
87 |
-func (opt *WeightdeviceOpt) Type() string { |
|
88 |
- return "weighted-device" |
|
89 |
-} |