opts_test.go 34 KB

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