opts_test.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  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. if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
  384. t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
  385. }
  386. // shm-size ok
  387. _, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
  388. if err != nil {
  389. t.Fatal(err)
  390. }
  391. if hostconfig.ShmSize != 134217728 {
  392. t.Fatalf("Expected a valid ShmSize, got %d", hostconfig.ShmSize)
  393. }
  394. }
  395. func TestParseRestartPolicy(t *testing.T) {
  396. invalids := map[string]string{
  397. "always:2:3": "invalid restart policy format",
  398. "on-failure:invalid": "maximum retry count must be an integer",
  399. }
  400. valids := map[string]container.RestartPolicy{
  401. "": {},
  402. "always": {
  403. Name: "always",
  404. MaximumRetryCount: 0,
  405. },
  406. "on-failure:1": {
  407. Name: "on-failure",
  408. MaximumRetryCount: 1,
  409. },
  410. }
  411. for restart, expectedError := range invalids {
  412. if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
  413. t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
  414. }
  415. }
  416. for restart, expected := range valids {
  417. _, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
  418. if err != nil {
  419. t.Fatal(err)
  420. }
  421. if hostconfig.RestartPolicy != expected {
  422. t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy)
  423. }
  424. }
  425. }
  426. func TestParseRestartPolicyAutoRemove(t *testing.T) {
  427. expected := "Conflicting options: --restart and --rm"
  428. _, _, _, err := parseRun([]string{"--rm", "--restart=always", "img", "cmd"})
  429. if err == nil || err.Error() != expected {
  430. t.Fatalf("Expected error %v, but got none", expected)
  431. }
  432. }
  433. func TestParseHealth(t *testing.T) {
  434. checkOk := func(args ...string) *container.HealthConfig {
  435. config, _, _, err := parseRun(args)
  436. if err != nil {
  437. t.Fatalf("%#v: %v", args, err)
  438. }
  439. return config.Healthcheck
  440. }
  441. checkError := func(expected string, args ...string) {
  442. config, _, _, err := parseRun(args)
  443. if err == nil {
  444. t.Fatalf("Expected error, but got %#v", config)
  445. }
  446. if err.Error() != expected {
  447. t.Fatalf("Expected %#v, got %#v", expected, err)
  448. }
  449. }
  450. health := checkOk("--no-healthcheck", "img", "cmd")
  451. if health == nil || len(health.Test) != 1 || health.Test[0] != "NONE" {
  452. t.Fatalf("--no-healthcheck failed: %#v", health)
  453. }
  454. health = checkOk("--health-cmd=/check.sh -q", "img", "cmd")
  455. if len(health.Test) != 2 || health.Test[0] != "CMD-SHELL" || health.Test[1] != "/check.sh -q" {
  456. t.Fatalf("--health-cmd: got %#v", health.Test)
  457. }
  458. if health.Timeout != 0 {
  459. t.Fatalf("--health-cmd: timeout = %f", health.Timeout)
  460. }
  461. checkError("--no-healthcheck conflicts with --health-* options",
  462. "--no-healthcheck", "--health-cmd=/check.sh -q", "img", "cmd")
  463. health = checkOk("--health-timeout=2s", "--health-retries=3", "--health-interval=4.5s", "img", "cmd")
  464. if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond {
  465. t.Fatalf("--health-*: got %#v", health)
  466. }
  467. }
  468. func TestParseLoggingOpts(t *testing.T) {
  469. // logging opts ko
  470. if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "invalid logging opts for driver none" {
  471. t.Fatalf("Expected an error with message 'invalid logging opts for driver none', got %v", err)
  472. }
  473. // logging opts ok
  474. _, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
  475. if err != nil {
  476. t.Fatal(err)
  477. }
  478. if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 {
  479. t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy)
  480. }
  481. }
  482. func TestParseEnvfileVariables(t *testing.T) {
  483. e := "open nonexistent: no such file or directory"
  484. if runtime.GOOS == "windows" {
  485. e = "open nonexistent: The system cannot find the file specified."
  486. }
  487. // env ko
  488. if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
  489. t.Fatalf("Expected an error with message '%s', got %v", e, err)
  490. }
  491. // env ok
  492. config, _, _, err := parseRun([]string{"--env-file=testdata/valid.env", "img", "cmd"})
  493. if err != nil {
  494. t.Fatal(err)
  495. }
  496. if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
  497. t.Fatalf("Expected a config with [ENV1=value1], got %v", config.Env)
  498. }
  499. config, _, _, err = parseRun([]string{"--env-file=testdata/valid.env", "--env=ENV2=value2", "img", "cmd"})
  500. if err != nil {
  501. t.Fatal(err)
  502. }
  503. if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" {
  504. t.Fatalf("Expected a config with [ENV1=value1 ENV2=value2], got %v", config.Env)
  505. }
  506. }
  507. func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) {
  508. // UTF8 with BOM
  509. config, _, _, err := parseRun([]string{"--env-file=testdata/utf8.env", "img", "cmd"})
  510. if err != nil {
  511. t.Fatal(err)
  512. }
  513. env := []string{"FOO=BAR", "HELLO=" + string([]byte{0xe6, 0x82, 0xa8, 0xe5, 0xa5, 0xbd}), "BAR=FOO"}
  514. if len(config.Env) != len(env) {
  515. t.Fatalf("Expected a config with %d env variables, got %v: %v", len(env), len(config.Env), config.Env)
  516. }
  517. for i, v := range env {
  518. if config.Env[i] != v {
  519. t.Fatalf("Expected a config with [%s], got %v", v, []byte(config.Env[i]))
  520. }
  521. }
  522. // UTF16 with BOM
  523. e := "contains invalid utf8 bytes at line"
  524. if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
  525. t.Fatalf("Expected an error with message '%s', got %v", e, err)
  526. }
  527. // UTF16BE with BOM
  528. if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16be.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
  529. t.Fatalf("Expected an error with message '%s', got %v", e, err)
  530. }
  531. }
  532. func TestParseLabelfileVariables(t *testing.T) {
  533. e := "open nonexistent: no such file or directory"
  534. if runtime.GOOS == "windows" {
  535. e = "open nonexistent: The system cannot find the file specified."
  536. }
  537. // label ko
  538. if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
  539. t.Fatalf("Expected an error with message '%s', got %v", e, err)
  540. }
  541. // label ok
  542. config, _, _, err := parseRun([]string{"--label-file=testdata/valid.label", "img", "cmd"})
  543. if err != nil {
  544. t.Fatal(err)
  545. }
  546. if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
  547. t.Fatalf("Expected a config with [LABEL1:value1], got %v", config.Labels)
  548. }
  549. config, _, _, err = parseRun([]string{"--label-file=testdata/valid.label", "--label=LABEL2=value2", "img", "cmd"})
  550. if err != nil {
  551. t.Fatal(err)
  552. }
  553. if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" {
  554. t.Fatalf("Expected a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels)
  555. }
  556. }
  557. func TestParseEntryPoint(t *testing.T) {
  558. config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
  559. if err != nil {
  560. t.Fatal(err)
  561. }
  562. if len(config.Entrypoint) != 1 && config.Entrypoint[0] != "anything" {
  563. t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint)
  564. }
  565. }
  566. // This tests the cases for binds which are generated through
  567. // DecodeContainerConfig rather than Parse()
  568. func TestDecodeContainerConfigVolumes(t *testing.T) {
  569. // Root to root
  570. bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
  571. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  572. t.Fatalf("binds %v should have failed", bindsOrVols)
  573. }
  574. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  575. t.Fatalf("volume %v should have failed", bindsOrVols)
  576. }
  577. // No destination path
  578. bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
  579. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  580. t.Fatalf("binds %v should have failed", bindsOrVols)
  581. }
  582. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  583. t.Fatalf("volume %v should have failed", bindsOrVols)
  584. }
  585. // // No destination path or mode
  586. bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
  587. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  588. t.Fatalf("binds %v should have failed", bindsOrVols)
  589. }
  590. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  591. t.Fatalf("volume %v should have failed", bindsOrVols)
  592. }
  593. // A whole lot of nothing
  594. bindsOrVols = []string{`:`}
  595. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  596. t.Fatalf("binds %v should have failed", bindsOrVols)
  597. }
  598. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  599. t.Fatalf("volume %v should have failed", bindsOrVols)
  600. }
  601. // A whole lot of nothing with no mode
  602. bindsOrVols = []string{`::`}
  603. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  604. t.Fatalf("binds %v should have failed", bindsOrVols)
  605. }
  606. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  607. t.Fatalf("volume %v should have failed", bindsOrVols)
  608. }
  609. // Too much including an invalid mode
  610. wTmp := os.Getenv("TEMP")
  611. bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
  612. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  613. t.Fatalf("binds %v should have failed", bindsOrVols)
  614. }
  615. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  616. t.Fatalf("volume %v should have failed", bindsOrVols)
  617. }
  618. // Windows specific error tests
  619. if runtime.GOOS == "windows" {
  620. // Volume which does not include a drive letter
  621. bindsOrVols = []string{`\tmp`}
  622. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  623. t.Fatalf("binds %v should have failed", bindsOrVols)
  624. }
  625. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  626. t.Fatalf("volume %v should have failed", bindsOrVols)
  627. }
  628. // Root to C-Drive
  629. bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
  630. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  631. t.Fatalf("binds %v should have failed", bindsOrVols)
  632. }
  633. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  634. t.Fatalf("volume %v should have failed", bindsOrVols)
  635. }
  636. // Container path that does not include a drive letter
  637. bindsOrVols = []string{`c:\windows:\somewhere`}
  638. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  639. t.Fatalf("binds %v should have failed", bindsOrVols)
  640. }
  641. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  642. t.Fatalf("volume %v should have failed", bindsOrVols)
  643. }
  644. }
  645. // Linux-specific error tests
  646. if runtime.GOOS != "windows" {
  647. // Just root
  648. bindsOrVols = []string{`/`}
  649. if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
  650. t.Fatalf("binds %v should have failed", bindsOrVols)
  651. }
  652. if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
  653. t.Fatalf("volume %v should have failed", bindsOrVols)
  654. }
  655. // A single volume that looks like a bind mount passed in Volumes.
  656. // This should be handled as a bind mount, not a volume.
  657. vols := []string{`/foo:/bar`}
  658. if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
  659. t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
  660. } else if hostConfig.Binds != nil {
  661. t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
  662. } else if _, exists := config.Volumes[vols[0]]; !exists {
  663. t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
  664. }
  665. }
  666. }
  667. // callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes
  668. // to call DecodeContainerConfig. It effectively does what a client would
  669. // do when calling the daemon by constructing a JSON stream of a
  670. // ContainerConfigWrapper which is populated by the set of volume specs
  671. // passed into it. It returns a config and a hostconfig which can be
  672. // validated to ensure DecodeContainerConfig has manipulated the structures
  673. // correctly.
  674. func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) {
  675. var (
  676. b []byte
  677. err error
  678. c *container.Config
  679. h *container.HostConfig
  680. )
  681. w := runconfig.ContainerConfigWrapper{
  682. Config: &container.Config{
  683. Volumes: map[string]struct{}{},
  684. },
  685. HostConfig: &container.HostConfig{
  686. NetworkMode: "none",
  687. Binds: binds,
  688. },
  689. }
  690. for _, v := range volumes {
  691. w.Config.Volumes[v] = struct{}{}
  692. }
  693. if b, err = json.Marshal(w); err != nil {
  694. return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
  695. }
  696. c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
  697. if err != nil {
  698. return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
  699. }
  700. if c == nil || h == nil {
  701. return nil, nil, fmt.Errorf("Empty config or hostconfig")
  702. }
  703. return c, h, err
  704. }
  705. func TestVolumeSplitN(t *testing.T) {
  706. for _, x := range []struct {
  707. input string
  708. n int
  709. expected []string
  710. }{
  711. {`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}},
  712. {`:C:\foo:d:`, -1, nil},
  713. {`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}},
  714. {`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}},
  715. {`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}},
  716. {`d:\`, -1, []string{`d:\`}},
  717. {`d:`, -1, []string{`d:`}},
  718. {`d:\path`, -1, []string{`d:\path`}},
  719. {`d:\path with space`, -1, []string{`d:\path with space`}},
  720. {`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}},
  721. {`c:\:d:\`, -1, []string{`c:\`, `d:\`}},
  722. {`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}},
  723. {`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}},
  724. {`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}},
  725. {`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`}},
  726. {`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}},
  727. {`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}},
  728. {`name:D:`, -1, []string{`name`, `D:`}},
  729. {`name:D::rW`, -1, []string{`name`, `D:`, `rW`}},
  730. {`name:D::RW`, -1, []string{`name`, `D:`, `RW`}},
  731. {`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}},
  732. {`c:\Windows`, -1, []string{`c:\Windows`}},
  733. {`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}},
  734. {``, -1, nil},
  735. {`.`, -1, []string{`.`}},
  736. {`..\`, -1, []string{`..\`}},
  737. {`c:\:..\`, -1, []string{`c:\`, `..\`}},
  738. {`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}},
  739. // Cover directories with one-character name
  740. {`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}},
  741. } {
  742. res := volumeSplitN(x.input, x.n)
  743. if len(res) < len(x.expected) {
  744. t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
  745. }
  746. for i, e := range res {
  747. if e != x.expected[i] {
  748. t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
  749. }
  750. }
  751. }
  752. }
  753. func TestValidateDevice(t *testing.T) {
  754. valid := []string{
  755. "/home",
  756. "/home:/home",
  757. "/home:/something/else",
  758. "/with space",
  759. "/home:/with space",
  760. "relative:/absolute-path",
  761. "hostPath:/containerPath:r",
  762. "/hostPath:/containerPath:rw",
  763. "/hostPath:/containerPath:mrw",
  764. }
  765. invalid := map[string]string{
  766. "": "bad format for path: ",
  767. "./": "./ is not an absolute path",
  768. "../": "../ is not an absolute path",
  769. "/:../": "../ is not an absolute path",
  770. "/:path": "path is not an absolute path",
  771. ":": "bad format for path: :",
  772. "/tmp:": " is not an absolute path",
  773. ":test": "bad format for path: :test",
  774. ":/test": "bad format for path: :/test",
  775. "tmp:": " is not an absolute path",
  776. ":test:": "bad format for path: :test:",
  777. "::": "bad format for path: ::",
  778. ":::": "bad format for path: :::",
  779. "/tmp:::": "bad format for path: /tmp:::",
  780. ":/tmp::": "bad format for path: :/tmp::",
  781. "path:ro": "ro is not an absolute path",
  782. "path:rr": "rr is not an absolute path",
  783. "a:/b:ro": "bad mode specified: ro",
  784. "a:/b:rr": "bad mode specified: rr",
  785. }
  786. for _, path := range valid {
  787. if _, err := validateDevice(path); err != nil {
  788. t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
  789. }
  790. }
  791. for path, expectedError := range invalid {
  792. if _, err := validateDevice(path); err == nil {
  793. t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
  794. } else {
  795. if err.Error() != expectedError {
  796. t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
  797. }
  798. }
  799. }
  800. }