opts_test.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  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/pkg/errors"
  18. "github.com/spf13/pflag"
  19. )
  20. func TestValidateAttach(t *testing.T) {
  21. valid := []string{
  22. "stdin",
  23. "stdout",
  24. "stderr",
  25. "STDIN",
  26. "STDOUT",
  27. "STDERR",
  28. }
  29. if _, err := validateAttach("invalid"); err == nil {
  30. t.Fatal("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing")
  31. }
  32. for _, attach := range valid {
  33. value, err := validateAttach(attach)
  34. if err != nil {
  35. t.Fatal(err)
  36. }
  37. if value != strings.ToLower(attach) {
  38. t.Fatalf("Expected [%v], got [%v]", attach, value)
  39. }
  40. }
  41. }
  42. func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
  43. flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
  44. flags.SetOutput(ioutil.Discard)
  45. flags.Usage = nil
  46. copts := addFlags(flags)
  47. if err := flags.Parse(args); err != nil {
  48. return nil, nil, nil, err
  49. }
  50. // TODO: fix tests to accept ContainerConfig
  51. containerConfig, err := parse(flags, copts)
  52. if err != nil {
  53. return nil, nil, nil, err
  54. }
  55. return containerConfig.Config, containerConfig.HostConfig, containerConfig.NetworkingConfig, err
  56. }
  57. func parsetest(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
  58. config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
  59. return config, hostConfig, err
  60. }
  61. func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
  62. config, hostConfig, err := parsetest(t, args)
  63. if err != nil {
  64. t.Fatal(err)
  65. }
  66. return config, hostConfig
  67. }
  68. func TestParseRunLinks(t *testing.T) {
  69. if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
  70. t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
  71. }
  72. if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
  73. t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
  74. }
  75. if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
  76. t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
  77. }
  78. }
  79. func TestParseRunAttach(t *testing.T) {
  80. if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
  81. t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
  82. }
  83. if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
  84. 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)
  85. }
  86. if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
  87. t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
  88. }
  89. if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
  90. t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
  91. }
  92. if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
  93. t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
  94. }
  95. if _, _, err := parsetest(t, "-a"); err == nil {
  96. t.Fatal("Error parsing attach flags, `-a` should be an error but is not")
  97. }
  98. if _, _, err := parsetest(t, "-a invalid"); err == nil {
  99. t.Fatal("Error parsing attach flags, `-a invalid` should be an error but is not")
  100. }
  101. if _, _, err := parsetest(t, "-a invalid -a stdout"); err == nil {
  102. t.Fatal("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
  103. }
  104. if _, _, err := parsetest(t, "-a stdout -a stderr -d"); err == nil {
  105. t.Fatal("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
  106. }
  107. if _, _, err := parsetest(t, "-a stdin -d"); err == nil {
  108. t.Fatal("Error parsing attach flags, `-a stdin -d` should be an error but is not")
  109. }
  110. if _, _, err := parsetest(t, "-a stdout -d"); err == nil {
  111. t.Fatal("Error parsing attach flags, `-a stdout -d` should be an error but is not")
  112. }
  113. if _, _, err := parsetest(t, "-a stderr -d"); err == nil {
  114. t.Fatal("Error parsing attach flags, `-a stderr -d` should be an error but is not")
  115. }
  116. if _, _, err := parsetest(t, "-d --rm"); err == nil {
  117. t.Fatal("Error parsing attach flags, `-d --rm` should be an error but is not")
  118. }
  119. }
  120. func TestParseRunVolumes(t *testing.T) {
  121. // A single volume
  122. arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
  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, %q is missing from volumes. Received %v", tryit, config.Volumes)
  127. }
  128. // Two volumes
  129. arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
  130. if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
  131. t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
  132. } else if _, exists := config.Volumes[arr[0]]; !exists {
  133. t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes)
  134. } else if _, exists := config.Volumes[arr[1]]; !exists {
  135. t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes)
  136. }
  137. // A single bind-mount
  138. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`})
  139. if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
  140. 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)
  141. }
  142. // Two bind-mounts.
  143. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`})
  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. // Two bind-mounts, first read-only, second read-write.
  148. // TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4
  149. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`})
  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. // Similar to previous test but with alternate modes which are only supported by Linux
  154. if runtime.GOOS != "windows" {
  155. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{})
  156. if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
  157. t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
  158. }
  159. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{})
  160. if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
  161. t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
  162. }
  163. }
  164. // One bind mount and one volume
  165. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`})
  166. if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
  167. 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)
  168. } else if _, exists := config.Volumes[arr[1]]; !exists {
  169. t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes)
  170. }
  171. // Root to non-c: drive letter (Windows specific)
  172. if runtime.GOOS == "windows" {
  173. arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`})
  174. if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
  175. t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
  176. }
  177. }
  178. }
  179. // setupPlatformVolume takes two arrays of volume specs - a Unix style
  180. // spec and a Windows style spec. Depending on the platform being unit tested,
  181. // it returns one of them, along with a volume string that would be passed
  182. // on the docker CLI (e.g. -v /bar -v /foo).
  183. func setupPlatformVolume(u []string, w []string) ([]string, string) {
  184. var a []string
  185. if runtime.GOOS == "windows" {
  186. a = w
  187. } else {
  188. a = u
  189. }
  190. s := ""
  191. for _, v := range a {
  192. s = s + "-v " + v + " "
  193. }
  194. return a, s
  195. }
  196. // check if (a == c && b == d) || (a == d && b == c)
  197. // because maps are randomized
  198. func compareRandomizedStrings(a, b, c, d string) error {
  199. if a == c && b == d {
  200. return nil
  201. }
  202. if a == d && b == c {
  203. return nil
  204. }
  205. return errors.Errorf("strings don't match")
  206. }
  207. // Simple parse with MacAddress validation
  208. func TestParseWithMacAddress(t *testing.T) {
  209. invalidMacAddress := "--mac-address=invalidMacAddress"
  210. validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
  211. if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
  212. t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
  213. }
  214. if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
  215. t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress)
  216. }
  217. }
  218. func TestParseWithMemory(t *testing.T) {
  219. invalidMemory := "--memory=invalid"
  220. _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"})
  221. assert.Error(t, err, invalidMemory)
  222. _, hostconfig := mustParse(t, "--memory=1G")
  223. assert.Equal(t, hostconfig.Memory, int64(1073741824))
  224. }
  225. func TestParseWithMemorySwap(t *testing.T) {
  226. invalidMemory := "--memory-swap=invalid"
  227. _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"})
  228. assert.Error(t, err, invalidMemory)
  229. _, hostconfig := mustParse(t, "--memory-swap=1G")
  230. assert.Equal(t, hostconfig.MemorySwap, int64(1073741824))
  231. _, hostconfig = mustParse(t, "--memory-swap=-1")
  232. assert.Equal(t, hostconfig.MemorySwap, int64(-1))
  233. }
  234. func TestParseHostname(t *testing.T) {
  235. validHostnames := map[string]string{
  236. "hostname": "hostname",
  237. "host-name": "host-name",
  238. "hostname123": "hostname123",
  239. "123hostname": "123hostname",
  240. "hostname-of-63-bytes-long-should-be-valid-and-without-any-error": "hostname-of-63-bytes-long-should-be-valid-and-without-any-error",
  241. }
  242. hostnameWithDomain := "--hostname=hostname.domainname"
  243. hostnameWithDomainTld := "--hostname=hostname.domainname.tld"
  244. for hostname, expectedHostname := range validHostnames {
  245. if config, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname {
  246. t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
  247. }
  248. }
  249. if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname.domainname" && config.Domainname != "" {
  250. t.Fatalf("Expected the config to have 'hostname' as hostname.domainname, got '%v'", config.Hostname)
  251. }
  252. if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname.domainname.tld" && config.Domainname != "" {
  253. t.Fatalf("Expected the config to have 'hostname' as hostname.domainname.tld, got '%v'", config.Hostname)
  254. }
  255. }
  256. func TestParseWithExpose(t *testing.T) {
  257. invalids := map[string]string{
  258. ":": "invalid port format for --expose: :",
  259. "8080:9090": "invalid port format for --expose: 8080:9090",
  260. "/tcp": "invalid range format for --expose: /tcp, error: Empty string specified for ports.",
  261. "/udp": "invalid range format for --expose: /udp, error: Empty string specified for ports.",
  262. "NaN/tcp": `invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
  263. "NaN-NaN/tcp": `invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
  264. "8080-NaN/tcp": `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
  265. "1234567890-8080/tcp": `invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
  266. }
  267. valids := map[string][]nat.Port{
  268. "8080/tcp": {"8080/tcp"},
  269. "8080/udp": {"8080/udp"},
  270. "8080/ncp": {"8080/ncp"},
  271. "8080-8080/udp": {"8080/udp"},
  272. "8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
  273. }
  274. for expose, expectedError := range invalids {
  275. if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
  276. t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
  277. }
  278. }
  279. for expose, exposedPorts := range valids {
  280. config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
  281. if err != nil {
  282. t.Fatal(err)
  283. }
  284. if len(config.ExposedPorts) != len(exposedPorts) {
  285. t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts))
  286. }
  287. for _, port := range exposedPorts {
  288. if _, ok := config.ExposedPorts[port]; !ok {
  289. t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts)
  290. }
  291. }
  292. }
  293. // Merge with actual published port
  294. config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
  295. if err != nil {
  296. t.Fatal(err)
  297. }
  298. if len(config.ExposedPorts) != 2 {
  299. t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
  300. }
  301. ports := []nat.Port{"80/tcp", "81/tcp"}
  302. for _, port := range ports {
  303. if _, ok := config.ExposedPorts[port]; !ok {
  304. t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
  305. }
  306. }
  307. }
  308. func TestParseDevice(t *testing.T) {
  309. valids := map[string]container.DeviceMapping{
  310. "/dev/snd": {
  311. PathOnHost: "/dev/snd",
  312. PathInContainer: "/dev/snd",
  313. CgroupPermissions: "rwm",
  314. },
  315. "/dev/snd:rw": {
  316. PathOnHost: "/dev/snd",
  317. PathInContainer: "/dev/snd",
  318. CgroupPermissions: "rw",
  319. },
  320. "/dev/snd:/something": {
  321. PathOnHost: "/dev/snd",
  322. PathInContainer: "/something",
  323. CgroupPermissions: "rwm",
  324. },
  325. "/dev/snd:/something:rw": {
  326. PathOnHost: "/dev/snd",
  327. PathInContainer: "/something",
  328. CgroupPermissions: "rw",
  329. },
  330. }
  331. for device, deviceMapping := range valids {
  332. _, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
  333. if err != nil {
  334. t.Fatal(err)
  335. }
  336. if len(hostconfig.Devices) != 1 {
  337. t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices)
  338. }
  339. if hostconfig.Devices[0] != deviceMapping {
  340. t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
  341. }
  342. }
  343. }
  344. func TestParseModes(t *testing.T) {
  345. // ipc ko
  346. if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
  347. t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
  348. }
  349. // ipc ok
  350. _, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
  351. if err != nil {
  352. t.Fatal(err)
  353. }
  354. if !hostconfig.IpcMode.Valid() {
  355. t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
  356. }
  357. // pid ko
  358. if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
  359. t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
  360. }
  361. // pid ok
  362. _, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
  363. if err != nil {
  364. t.Fatal(err)
  365. }
  366. if !hostconfig.PidMode.Valid() {
  367. t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
  368. }
  369. // uts ko
  370. if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
  371. t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
  372. }
  373. // uts ok
  374. _, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
  375. if err != nil {
  376. t.Fatal(err)
  377. }
  378. if !hostconfig.UTSMode.Valid() {
  379. t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
  380. }
  381. // shm-size ko
  382. expectedErr := `invalid argument "a128m" for --shm-size=a128m: invalid size: 'a128m'`
  383. if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != expectedErr {
  384. t.Fatalf("Expected an error with message '%v', got %v", expectedErr, 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 = %s", 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", "--health-start-period=5s", "img", "cmd")
  464. if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond || health.StartPeriod != 5*time.Second {
  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, errors.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, errors.Errorf("Error parsing %s: %v", string(b), err)
  699. }
  700. if c == nil || h == nil {
  701. return nil, nil, errors.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. }