opts_test.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. package container
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io/ioutil"
  7. "os"
  8. "runtime"
  9. "strings"
  10. "testing"
  11. "time"
  12. "github.com/docker/docker/api/types/container"
  13. networktypes "github.com/docker/docker/api/types/network"
  14. "github.com/docker/docker/runconfig"
  15. "github.com/docker/go-connections/nat"
  16. "github.com/spf13/pflag"
  17. )
  18. func TestValidateAttach(t *testing.T) {
  19. valid := []string{
  20. "stdin",
  21. "stdout",
  22. "stderr",
  23. "STDIN",
  24. "STDOUT",
  25. "STDERR",
  26. }
  27. if _, err := validateAttach("invalid"); err == nil {
  28. t.Fatalf("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing")
  29. }
  30. for _, attach := range valid {
  31. value, err := validateAttach(attach)
  32. if err != nil {
  33. t.Fatal(err)
  34. }
  35. if value != strings.ToLower(attach) {
  36. t.Fatalf("Expected [%v], got [%v]", attach, value)
  37. }
  38. }
  39. }
  40. func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
  41. flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
  42. flags.SetOutput(ioutil.Discard)
  43. flags.Usage = nil
  44. copts := addFlags(flags)
  45. if err := flags.Parse(args); err != nil {
  46. return nil, nil, nil, err
  47. }
  48. return parse(flags, copts)
  49. }
  50. func parsetest(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
  51. config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
  52. return config, hostConfig, err
  53. }
  54. func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
  55. config, hostConfig, err := parsetest(t, args)
  56. if err != nil {
  57. t.Fatal(err)
  58. }
  59. return config, hostConfig
  60. }
  61. func TestParseRunLinks(t *testing.T) {
  62. if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
  63. t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
  64. }
  65. if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
  66. t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
  67. }
  68. if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
  69. t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
  70. }
  71. }
  72. func TestParseRunAttach(t *testing.T) {
  73. if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
  74. t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
  75. }
  76. if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
  77. 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)
  78. }
  79. if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
  80. t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
  81. }
  82. if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
  83. t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
  84. }
  85. if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
  86. t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
  87. }
  88. if _, _, err := parsetest(t, "-a"); err == nil {
  89. t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
  90. }
  91. if _, _, err := parsetest(t, "-a invalid"); err == nil {
  92. t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
  93. }
  94. if _, _, err := parsetest(t, "-a invalid -a stdout"); err == nil {
  95. t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
  96. }
  97. if _, _, err := parsetest(t, "-a stdout -a stderr -d"); err == nil {
  98. t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
  99. }
  100. if _, _, err := parsetest(t, "-a stdin -d"); err == nil {
  101. t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
  102. }
  103. if _, _, err := parsetest(t, "-a stdout -d"); err == nil {
  104. t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
  105. }
  106. if _, _, err := parsetest(t, "-a stderr -d"); err == nil {
  107. t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
  108. }
  109. if _, _, err := parsetest(t, "-d --rm"); err == nil {
  110. t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not")
  111. }
  112. }
  113. func TestParseRunVolumes(t *testing.T) {
  114. // A single volume
  115. arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
  116. if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
  117. t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
  118. } else if _, exists := config.Volumes[arr[0]]; !exists {
  119. t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes)
  120. }
  121. // Two volumes
  122. arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
  123. if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
  124. t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
  125. } else if _, exists := config.Volumes[arr[0]]; !exists {
  126. t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes)
  127. } else if _, exists := config.Volumes[arr[1]]; !exists {
  128. t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes)
  129. }
  130. // A single bind-mount
  131. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`})
  132. if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
  133. 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)
  134. }
  135. // Two bind-mounts.
  136. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`})
  137. if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
  138. t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
  139. }
  140. // Two bind-mounts, first read-only, second read-write.
  141. // TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4
  142. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`})
  143. if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
  144. t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
  145. }
  146. // Similar to previous test but with alternate modes which are only supported by Linux
  147. if runtime.GOOS != "windows" {
  148. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{})
  149. if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
  150. t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
  151. }
  152. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{})
  153. if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
  154. t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
  155. }
  156. }
  157. // One bind mount and one volume
  158. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`})
  159. if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
  160. 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)
  161. } else if _, exists := config.Volumes[arr[1]]; !exists {
  162. t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes)
  163. }
  164. // Root to non-c: drive letter (Windows specific)
  165. if runtime.GOOS == "windows" {
  166. arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`})
  167. if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
  168. t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
  169. }
  170. }
  171. }
  172. // setupPlatformVolume takes two arrays of volume specs - a Unix style
  173. // spec and a Windows style spec. Depending on the platform being unit tested,
  174. // it returns one of them, along with a volume string that would be passed
  175. // on the docker CLI (e.g. -v /bar -v /foo).
  176. func setupPlatformVolume(u []string, w []string) ([]string, string) {
  177. var a []string
  178. if runtime.GOOS == "windows" {
  179. a = w
  180. } else {
  181. a = u
  182. }
  183. s := ""
  184. for _, v := range a {
  185. s = s + "-v " + v + " "
  186. }
  187. return a, s
  188. }
  189. // check if (a == c && b == d) || (a == d && b == c)
  190. // because maps are randomized
  191. func compareRandomizedStrings(a, b, c, d string) error {
  192. if a == c && b == d {
  193. return nil
  194. }
  195. if a == d && b == c {
  196. return nil
  197. }
  198. return fmt.Errorf("strings don't match")
  199. }
  200. // Simple parse with MacAddress validation
  201. func TestParseWithMacAddress(t *testing.T) {
  202. invalidMacAddress := "--mac-address=invalidMacAddress"
  203. validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
  204. if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
  205. t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
  206. }
  207. if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
  208. t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress)
  209. }
  210. }
  211. func TestParseWithMemory(t *testing.T) {
  212. invalidMemory := "--memory=invalid"
  213. validMemory := "--memory=1G"
  214. if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
  215. t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
  216. }
  217. if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
  218. t.Fatalf("Expected the config to have '1G' as Memory, got '%v'", hostconfig.Memory)
  219. }
  220. }
  221. func TestParseWithMemorySwap(t *testing.T) {
  222. invalidMemory := "--memory-swap=invalid"
  223. validMemory := "--memory-swap=1G"
  224. anotherValidMemory := "--memory-swap=-1"
  225. if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
  226. t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
  227. }
  228. if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
  229. t.Fatalf("Expected the config to have '1073741824' as MemorySwap, got '%v'", hostconfig.MemorySwap)
  230. }
  231. if _, hostconfig := mustParse(t, anotherValidMemory); hostconfig.MemorySwap != -1 {
  232. t.Fatalf("Expected the config to have '-1' as MemorySwap, got '%v'", hostconfig.MemorySwap)
  233. }
  234. }
  235. func TestParseHostname(t *testing.T) {
  236. validHostnames := map[string]string{
  237. "hostname": "hostname",
  238. "host-name": "host-name",
  239. "hostname123": "hostname123",
  240. "123hostname": "123hostname",
  241. "hostname-of-63-bytes-long-should-be-valid-and-without-any-error": "hostname-of-63-bytes-long-should-be-valid-and-without-any-error",
  242. }
  243. hostnameWithDomain := "--hostname=hostname.domainname"
  244. hostnameWithDomainTld := "--hostname=hostname.domainname.tld"
  245. for hostname, expectedHostname := range validHostnames {
  246. if config, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname {
  247. t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
  248. }
  249. }
  250. if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname.domainname" && config.Domainname != "" {
  251. t.Fatalf("Expected the config to have 'hostname' as hostname.domainname, got '%v'", config.Hostname)
  252. }
  253. if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname.domainname.tld" && config.Domainname != "" {
  254. t.Fatalf("Expected the config to have 'hostname' as hostname.domainname.tld, got '%v'", config.Hostname)
  255. }
  256. }
  257. func TestParseWithExpose(t *testing.T) {
  258. invalids := map[string]string{
  259. ":": "invalid port format for --expose: :",
  260. "8080:9090": "invalid port format for --expose: 8080:9090",
  261. "/tcp": "invalid range format for --expose: /tcp, error: Empty string specified for ports.",
  262. "/udp": "invalid range format for --expose: /udp, error: Empty string specified for ports.",
  263. "NaN/tcp": `invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
  264. "NaN-NaN/tcp": `invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
  265. "8080-NaN/tcp": `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
  266. "1234567890-8080/tcp": `invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
  267. }
  268. valids := map[string][]nat.Port{
  269. "8080/tcp": {"8080/tcp"},
  270. "8080/udp": {"8080/udp"},
  271. "8080/ncp": {"8080/ncp"},
  272. "8080-8080/udp": {"8080/udp"},
  273. "8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
  274. }
  275. for expose, expectedError := range invalids {
  276. if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
  277. t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
  278. }
  279. }
  280. for expose, exposedPorts := range valids {
  281. config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
  282. if err != nil {
  283. t.Fatal(err)
  284. }
  285. if len(config.ExposedPorts) != len(exposedPorts) {
  286. t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts))
  287. }
  288. for _, port := range exposedPorts {
  289. if _, ok := config.ExposedPorts[port]; !ok {
  290. t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts)
  291. }
  292. }
  293. }
  294. // Merge with actual published port
  295. config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
  296. if err != nil {
  297. t.Fatal(err)
  298. }
  299. if len(config.ExposedPorts) != 2 {
  300. t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
  301. }
  302. ports := []nat.Port{"80/tcp", "81/tcp"}
  303. for _, port := range ports {
  304. if _, ok := config.ExposedPorts[port]; !ok {
  305. t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
  306. }
  307. }
  308. }
  309. func TestParseDevice(t *testing.T) {
  310. valids := map[string]container.DeviceMapping{
  311. "/dev/snd": {
  312. PathOnHost: "/dev/snd",
  313. PathInContainer: "/dev/snd",
  314. CgroupPermissions: "rwm",
  315. },
  316. "/dev/snd:rw": {
  317. PathOnHost: "/dev/snd",
  318. PathInContainer: "/dev/snd",
  319. CgroupPermissions: "rw",
  320. },
  321. "/dev/snd:/something": {
  322. PathOnHost: "/dev/snd",
  323. PathInContainer: "/something",
  324. CgroupPermissions: "rwm",
  325. },
  326. "/dev/snd:/something:rw": {
  327. PathOnHost: "/dev/snd",
  328. PathInContainer: "/something",
  329. CgroupPermissions: "rw",
  330. },
  331. }
  332. for device, deviceMapping := range valids {
  333. _, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
  334. if err != nil {
  335. t.Fatal(err)
  336. }
  337. if len(hostconfig.Devices) != 1 {
  338. t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices)
  339. }
  340. if hostconfig.Devices[0] != deviceMapping {
  341. t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
  342. }
  343. }
  344. }
  345. func TestParseModes(t *testing.T) {
  346. // ipc ko
  347. if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
  348. t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
  349. }
  350. // ipc ok
  351. _, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
  352. if err != nil {
  353. t.Fatal(err)
  354. }
  355. if !hostconfig.IpcMode.Valid() {
  356. t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
  357. }
  358. // pid ko
  359. if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
  360. t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
  361. }
  362. // pid ok
  363. _, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
  364. if err != nil {
  365. t.Fatal(err)
  366. }
  367. if !hostconfig.PidMode.Valid() {
  368. t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
  369. }
  370. // uts ko
  371. if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
  372. t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
  373. }
  374. // uts ok
  375. _, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
  376. if err != nil {
  377. t.Fatal(err)
  378. }
  379. if !hostconfig.UTSMode.Valid() {
  380. t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
  381. }
  382. // shm-size ko
  383. expectedErr := `invalid argument "a128m" for --shm-size=a128m: invalid size: 'a128m'`
  384. if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != expectedErr {
  385. t.Fatalf("Expected an error with message '%v', got %v", expectedErr, err)
  386. }
  387. // shm-size ok
  388. _, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
  389. if err != nil {
  390. t.Fatal(err)
  391. }
  392. if hostconfig.ShmSize != 134217728 {
  393. t.Fatalf("Expected a valid ShmSize, got %d", hostconfig.ShmSize)
  394. }
  395. }
  396. func TestParseRestartPolicy(t *testing.T) {
  397. invalids := map[string]string{
  398. "always:2:3": "invalid restart policy format",
  399. "on-failure:invalid": "maximum retry count must be an integer",
  400. }
  401. valids := map[string]container.RestartPolicy{
  402. "": {},
  403. "always": {
  404. Name: "always",
  405. MaximumRetryCount: 0,
  406. },
  407. "on-failure:1": {
  408. Name: "on-failure",
  409. MaximumRetryCount: 1,
  410. },
  411. }
  412. for restart, expectedError := range invalids {
  413. if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
  414. t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
  415. }
  416. }
  417. for restart, expected := range valids {
  418. _, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
  419. if err != nil {
  420. t.Fatal(err)
  421. }
  422. if hostconfig.RestartPolicy != expected {
  423. t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy)
  424. }
  425. }
  426. }
  427. func TestParseRestartPolicyAutoRemove(t *testing.T) {
  428. expected := "Conflicting options: --restart and --rm"
  429. _, _, _, err := parseRun([]string{"--rm", "--restart=always", "img", "cmd"})
  430. if err == nil || err.Error() != expected {
  431. t.Fatalf("Expected error %v, but got none", expected)
  432. }
  433. }
  434. func TestParseHealth(t *testing.T) {
  435. checkOk := func(args ...string) *container.HealthConfig {
  436. config, _, _, err := parseRun(args)
  437. if err != nil {
  438. t.Fatalf("%#v: %v", args, err)
  439. }
  440. return config.Healthcheck
  441. }
  442. checkError := func(expected string, args ...string) {
  443. config, _, _, err := parseRun(args)
  444. if err == nil {
  445. t.Fatalf("Expected error, but got %#v", config)
  446. }
  447. if err.Error() != expected {
  448. t.Fatalf("Expected %#v, got %#v", expected, err)
  449. }
  450. }
  451. health := checkOk("--no-healthcheck", "img", "cmd")
  452. if health == nil || len(health.Test) != 1 || health.Test[0] != "NONE" {
  453. t.Fatalf("--no-healthcheck failed: %#v", health)
  454. }
  455. health = checkOk("--health-cmd=/check.sh -q", "img", "cmd")
  456. if len(health.Test) != 2 || health.Test[0] != "CMD-SHELL" || health.Test[1] != "/check.sh -q" {
  457. t.Fatalf("--health-cmd: got %#v", health.Test)
  458. }
  459. if health.Timeout != 0 {
  460. t.Fatalf("--health-cmd: timeout = %f", health.Timeout)
  461. }
  462. checkError("--no-healthcheck conflicts with --health-* options",
  463. "--no-healthcheck", "--health-cmd=/check.sh -q", "img", "cmd")
  464. health = checkOk("--health-timeout=2s", "--health-retries=3", "--health-interval=4.5s", "img", "cmd")
  465. if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond {
  466. t.Fatalf("--health-*: got %#v", health)
  467. }
  468. }
  469. func TestParseLoggingOpts(t *testing.T) {
  470. // logging opts ko
  471. if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "invalid logging opts for driver none" {
  472. t.Fatalf("Expected an error with message 'invalid logging opts for driver none', got %v", err)
  473. }
  474. // logging opts ok
  475. _, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
  476. if err != nil {
  477. t.Fatal(err)
  478. }
  479. if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 {
  480. t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy)
  481. }
  482. }
  483. func TestParseEnvfileVariables(t *testing.T) {
  484. e := "open nonexistent: no such file or directory"
  485. if runtime.GOOS == "windows" {
  486. e = "open nonexistent: The system cannot find the file specified."
  487. }
  488. // env ko
  489. if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
  490. t.Fatalf("Expected an error with message '%s', got %v", e, err)
  491. }
  492. // env ok
  493. config, _, _, err := parseRun([]string{"--env-file=testdata/valid.env", "img", "cmd"})
  494. if err != nil {
  495. t.Fatal(err)
  496. }
  497. if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
  498. t.Fatalf("Expected a config with [ENV1=value1], got %v", config.Env)
  499. }
  500. config, _, _, err = parseRun([]string{"--env-file=testdata/valid.env", "--env=ENV2=value2", "img", "cmd"})
  501. if err != nil {
  502. t.Fatal(err)
  503. }
  504. if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" {
  505. t.Fatalf("Expected a config with [ENV1=value1 ENV2=value2], got %v", config.Env)
  506. }
  507. }
  508. func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) {
  509. // UTF8 with BOM
  510. config, _, _, err := parseRun([]string{"--env-file=testdata/utf8.env", "img", "cmd"})
  511. if err != nil {
  512. t.Fatal(err)
  513. }
  514. env := []string{"FOO=BAR", "HELLO=" + string([]byte{0xe6, 0x82, 0xa8, 0xe5, 0xa5, 0xbd}), "BAR=FOO"}
  515. if len(config.Env) != len(env) {
  516. t.Fatalf("Expected a config with %d env variables, got %v: %v", len(env), len(config.Env), config.Env)
  517. }
  518. for i, v := range env {
  519. if config.Env[i] != v {
  520. t.Fatalf("Expected a config with [%s], got %v", v, []byte(config.Env[i]))
  521. }
  522. }
  523. // UTF16 with BOM
  524. e := "contains invalid utf8 bytes at line"
  525. if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
  526. t.Fatalf("Expected an error with message '%s', got %v", e, err)
  527. }
  528. // UTF16BE with BOM
  529. if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16be.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
  530. t.Fatalf("Expected an error with message '%s', got %v", e, err)
  531. }
  532. }
  533. func TestParseLabelfileVariables(t *testing.T) {
  534. e := "open nonexistent: no such file or directory"
  535. if runtime.GOOS == "windows" {
  536. e = "open nonexistent: The system cannot find the file specified."
  537. }
  538. // label ko
  539. if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
  540. t.Fatalf("Expected an error with message '%s', got %v", e, err)
  541. }
  542. // label ok
  543. config, _, _, err := parseRun([]string{"--label-file=testdata/valid.label", "img", "cmd"})
  544. if err != nil {
  545. t.Fatal(err)
  546. }
  547. if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
  548. t.Fatalf("Expected a config with [LABEL1:value1], got %v", config.Labels)
  549. }
  550. config, _, _, err = parseRun([]string{"--label-file=testdata/valid.label", "--label=LABEL2=value2", "img", "cmd"})
  551. if err != nil {
  552. t.Fatal(err)
  553. }
  554. if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" {
  555. t.Fatalf("Expected a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels)
  556. }
  557. }
  558. func TestParseEntryPoint(t *testing.T) {
  559. config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
  560. if err != nil {
  561. t.Fatal(err)
  562. }
  563. if len(config.Entrypoint) != 1 && config.Entrypoint[0] != "anything" {
  564. t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint)
  565. }
  566. }
  567. // This tests the cases for binds which are generated through
  568. // DecodeContainerConfig rather than Parse()
  569. func TestDecodeContainerConfigVolumes(t *testing.T) {
  570. // Root to root
  571. bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
  572. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  573. t.Fatalf("binds %v should have failed", bindsOrVols)
  574. }
  575. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  576. t.Fatalf("volume %v should have failed", bindsOrVols)
  577. }
  578. // No destination path
  579. bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
  580. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  581. t.Fatalf("binds %v should have failed", bindsOrVols)
  582. }
  583. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  584. t.Fatalf("volume %v should have failed", bindsOrVols)
  585. }
  586. // // No destination path or mode
  587. bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
  588. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  589. t.Fatalf("binds %v should have failed", bindsOrVols)
  590. }
  591. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  592. t.Fatalf("volume %v should have failed", bindsOrVols)
  593. }
  594. // A whole lot of nothing
  595. bindsOrVols = []string{`:`}
  596. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  597. t.Fatalf("binds %v should have failed", bindsOrVols)
  598. }
  599. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  600. t.Fatalf("volume %v should have failed", bindsOrVols)
  601. }
  602. // A whole lot of nothing with no mode
  603. bindsOrVols = []string{`::`}
  604. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  605. t.Fatalf("binds %v should have failed", bindsOrVols)
  606. }
  607. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  608. t.Fatalf("volume %v should have failed", bindsOrVols)
  609. }
  610. // Too much including an invalid mode
  611. wTmp := os.Getenv("TEMP")
  612. bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
  613. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  614. t.Fatalf("binds %v should have failed", bindsOrVols)
  615. }
  616. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  617. t.Fatalf("volume %v should have failed", bindsOrVols)
  618. }
  619. // Windows specific error tests
  620. if runtime.GOOS == "windows" {
  621. // Volume which does not include a drive letter
  622. bindsOrVols = []string{`\tmp`}
  623. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  624. t.Fatalf("binds %v should have failed", bindsOrVols)
  625. }
  626. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  627. t.Fatalf("volume %v should have failed", bindsOrVols)
  628. }
  629. // Root to C-Drive
  630. bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
  631. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  632. t.Fatalf("binds %v should have failed", bindsOrVols)
  633. }
  634. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  635. t.Fatalf("volume %v should have failed", bindsOrVols)
  636. }
  637. // Container path that does not include a drive letter
  638. bindsOrVols = []string{`c:\windows:\somewhere`}
  639. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  640. t.Fatalf("binds %v should have failed", bindsOrVols)
  641. }
  642. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  643. t.Fatalf("volume %v should have failed", bindsOrVols)
  644. }
  645. }
  646. // Linux-specific error tests
  647. if runtime.GOOS != "windows" {
  648. // Just root
  649. bindsOrVols = []string{`/`}
  650. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  651. t.Fatalf("binds %v should have failed", bindsOrVols)
  652. }
  653. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  654. t.Fatalf("volume %v should have failed", bindsOrVols)
  655. }
  656. // A single volume that looks like a bind mount passed in Volumes.
  657. // This should be handled as a bind mount, not a volume.
  658. vols := []string{`/foo:/bar`}
  659. if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
  660. t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
  661. } else if hostConfig.Binds != nil {
  662. t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
  663. } else if _, exists := config.Volumes[vols[0]]; !exists {
  664. t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
  665. }
  666. }
  667. }
  668. // callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes
  669. // to call DecodeContainerConfig. It effectively does what a client would
  670. // do when calling the daemon by constructing a JSON stream of a
  671. // ContainerConfigWrapper which is populated by the set of volume specs
  672. // passed into it. It returns a config and a hostconfig which can be
  673. // validated to ensure DecodeContainerConfig has manipulated the structures
  674. // correctly.
  675. func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) {
  676. var (
  677. b []byte
  678. err error
  679. c *container.Config
  680. h *container.HostConfig
  681. )
  682. w := runconfig.ContainerConfigWrapper{
  683. Config: &container.Config{
  684. Volumes: map[string]struct{}{},
  685. },
  686. HostConfig: &container.HostConfig{
  687. NetworkMode: "none",
  688. Binds: binds,
  689. },
  690. }
  691. for _, v := range volumes {
  692. w.Config.Volumes[v] = struct{}{}
  693. }
  694. if b, err = json.Marshal(w); err != nil {
  695. return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
  696. }
  697. c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
  698. if err != nil {
  699. return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
  700. }
  701. if c == nil || h == nil {
  702. return nil, nil, fmt.Errorf("Empty config or hostconfig")
  703. }
  704. return c, h, err
  705. }
  706. func TestVolumeSplitN(t *testing.T) {
  707. for _, x := range []struct {
  708. input string
  709. n int
  710. expected []string
  711. }{
  712. {`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}},
  713. {`:C:\foo:d:`, -1, nil},
  714. {`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}},
  715. {`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}},
  716. {`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}},
  717. {`d:\`, -1, []string{`d:\`}},
  718. {`d:`, -1, []string{`d:`}},
  719. {`d:\path`, -1, []string{`d:\path`}},
  720. {`d:\path with space`, -1, []string{`d:\path with space`}},
  721. {`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}},
  722. {`c:\:d:\`, -1, []string{`c:\`, `d:\`}},
  723. {`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}},
  724. {`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}},
  725. {`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}},
  726. {`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`}},
  727. {`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}},
  728. {`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}},
  729. {`name:D:`, -1, []string{`name`, `D:`}},
  730. {`name:D::rW`, -1, []string{`name`, `D:`, `rW`}},
  731. {`name:D::RW`, -1, []string{`name`, `D:`, `RW`}},
  732. {`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}},
  733. {`c:\Windows`, -1, []string{`c:\Windows`}},
  734. {`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}},
  735. {``, -1, nil},
  736. {`.`, -1, []string{`.`}},
  737. {`..\`, -1, []string{`..\`}},
  738. {`c:\:..\`, -1, []string{`c:\`, `..\`}},
  739. {`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}},
  740. // Cover directories with one-character name
  741. {`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}},
  742. } {
  743. res := volumeSplitN(x.input, x.n)
  744. if len(res) < len(x.expected) {
  745. t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
  746. }
  747. for i, e := range res {
  748. if e != x.expected[i] {
  749. t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
  750. }
  751. }
  752. }
  753. }
  754. func TestValidateDevice(t *testing.T) {
  755. valid := []string{
  756. "/home",
  757. "/home:/home",
  758. "/home:/something/else",
  759. "/with space",
  760. "/home:/with space",
  761. "relative:/absolute-path",
  762. "hostPath:/containerPath:r",
  763. "/hostPath:/containerPath:rw",
  764. "/hostPath:/containerPath:mrw",
  765. }
  766. invalid := map[string]string{
  767. "": "bad format for path: ",
  768. "./": "./ is not an absolute path",
  769. "../": "../ is not an absolute path",
  770. "/:../": "../ is not an absolute path",
  771. "/:path": "path is not an absolute path",
  772. ":": "bad format for path: :",
  773. "/tmp:": " is not an absolute path",
  774. ":test": "bad format for path: :test",
  775. ":/test": "bad format for path: :/test",
  776. "tmp:": " is not an absolute path",
  777. ":test:": "bad format for path: :test:",
  778. "::": "bad format for path: ::",
  779. ":::": "bad format for path: :::",
  780. "/tmp:::": "bad format for path: /tmp:::",
  781. ":/tmp::": "bad format for path: :/tmp::",
  782. "path:ro": "ro is not an absolute path",
  783. "path:rr": "rr is not an absolute path",
  784. "a:/b:ro": "bad mode specified: ro",
  785. "a:/b:rr": "bad mode specified: rr",
  786. }
  787. for _, path := range valid {
  788. if _, err := validateDevice(path); err != nil {
  789. t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
  790. }
  791. }
  792. for path, expectedError := range invalid {
  793. if _, err := validateDevice(path); err == nil {
  794. t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
  795. } else {
  796. if err.Error() != expectedError {
  797. t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
  798. }
  799. }
  800. }
  801. }