linux_parser_test.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. package mounts // import "github.com/docker/docker/volume/mounts"
  2. import (
  3. "fmt"
  4. "strings"
  5. "testing"
  6. "github.com/docker/docker/api/types/mount"
  7. "gotest.tools/v3/assert"
  8. )
  9. func TestLinuxParseMountRaw(t *testing.T) {
  10. valid := []string{
  11. "/home",
  12. "/home:/home",
  13. "/home:/something/else",
  14. "/with space",
  15. "/home:/with space",
  16. "relative:/absolute-path",
  17. "hostPath:/containerPath:ro",
  18. "/hostPath:/containerPath:rw",
  19. "/rw:/ro",
  20. "/hostPath:/containerPath:shared",
  21. "/hostPath:/containerPath:rshared",
  22. "/hostPath:/containerPath:slave",
  23. "/hostPath:/containerPath:rslave",
  24. "/hostPath:/containerPath:private",
  25. "/hostPath:/containerPath:rprivate",
  26. "/hostPath:/containerPath:ro,shared",
  27. "/hostPath:/containerPath:ro,slave",
  28. "/hostPath:/containerPath:ro,private",
  29. "/hostPath:/containerPath:ro,z,shared",
  30. "/hostPath:/containerPath:ro,Z,slave",
  31. "/hostPath:/containerPath:Z,ro,slave",
  32. "/hostPath:/containerPath:slave,Z,ro",
  33. "/hostPath:/containerPath:Z,slave,ro",
  34. "/hostPath:/containerPath:slave,ro,Z",
  35. "/hostPath:/containerPath:rslave,ro,Z",
  36. "/hostPath:/containerPath:ro,rshared,Z",
  37. "/hostPath:/containerPath:ro,Z,rprivate",
  38. }
  39. invalid := map[string]string{
  40. "": "invalid volume specification",
  41. "./": "mount path must be absolute",
  42. "../": "mount path must be absolute",
  43. "/:../": "mount path must be absolute",
  44. "/:path": "mount path must be absolute",
  45. ":": "invalid volume specification",
  46. "/tmp:": "invalid volume specification",
  47. ":test": "invalid volume specification",
  48. ":/test": "invalid volume specification",
  49. "tmp:": "invalid volume specification",
  50. ":test:": "invalid volume specification",
  51. "::": "invalid volume specification",
  52. ":::": "invalid volume specification",
  53. "/tmp:::": "invalid volume specification",
  54. ":/tmp::": "invalid volume specification",
  55. "/path:rw": "invalid volume specification",
  56. "/path:ro": "invalid volume specification",
  57. "/rw:rw": "invalid volume specification",
  58. "path:ro": "invalid volume specification",
  59. "/path:/path:sw": `invalid mode`,
  60. "/path:/path:rwz": `invalid mode`,
  61. "/path:/path:ro,rshared,rslave": `invalid mode`,
  62. "/path:/path:ro,z,rshared,rslave": `invalid mode`,
  63. "/path:shared": "invalid volume specification",
  64. "/path:slave": "invalid volume specification",
  65. "/path:private": "invalid volume specification",
  66. "name:/absolute-path:shared": "invalid volume specification",
  67. "name:/absolute-path:rshared": "invalid volume specification",
  68. "name:/absolute-path:slave": "invalid volume specification",
  69. "name:/absolute-path:rslave": "invalid volume specification",
  70. "name:/absolute-path:private": "invalid volume specification",
  71. "name:/absolute-path:rprivate": "invalid volume specification",
  72. }
  73. parser := NewLinuxParser()
  74. if p, ok := parser.(*linuxParser); ok {
  75. p.fi = mockFiProvider{}
  76. }
  77. for _, path := range valid {
  78. if _, err := parser.ParseMountRaw(path, "local"); err != nil {
  79. t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
  80. }
  81. }
  82. for path, expectedError := range invalid {
  83. if mp, err := parser.ParseMountRaw(path, "local"); err == nil {
  84. t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
  85. } else {
  86. if !strings.Contains(err.Error(), expectedError) {
  87. t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
  88. }
  89. }
  90. }
  91. }
  92. func TestLinuxParseMountRawSplit(t *testing.T) {
  93. cases := []struct {
  94. bind string
  95. driver string
  96. expType mount.Type
  97. expDest string
  98. expSource string
  99. expName string
  100. expDriver string
  101. expRW bool
  102. fail bool
  103. }{
  104. {"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
  105. {"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
  106. {"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
  107. {"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
  108. {"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
  109. {"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
  110. {"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
  111. {"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
  112. {"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
  113. }
  114. parser := NewLinuxParser()
  115. if p, ok := parser.(*linuxParser); ok {
  116. p.fi = mockFiProvider{}
  117. }
  118. for i, c := range cases {
  119. c := c
  120. t.Run(fmt.Sprintf("%d_%s", i, c.bind), func(t *testing.T) {
  121. m, err := parser.ParseMountRaw(c.bind, c.driver)
  122. if c.fail {
  123. assert.ErrorContains(t, err, "", "expected an error")
  124. return
  125. }
  126. assert.NilError(t, err)
  127. assert.Equal(t, m.Destination, c.expDest)
  128. assert.Equal(t, m.Source, c.expSource)
  129. assert.Equal(t, m.Name, c.expName)
  130. assert.Equal(t, m.Driver, c.expDriver)
  131. assert.Equal(t, m.RW, c.expRW)
  132. assert.Equal(t, m.Type, c.expType)
  133. })
  134. }
  135. }
  136. // TestLinuxParseMountSpecBindWithFileinfoError makes sure that the parser returns
  137. // the error produced by the fileinfo provider.
  138. //
  139. // Some extra context for the future in case of changes and possible wtf are we
  140. // testing this for:
  141. //
  142. // Currently this "fileInfoProvider" returns (bool, bool, error)
  143. // The 1st bool is "does this path exist"
  144. // The 2nd bool is "is this path a dir"
  145. // Then of course the error is an error.
  146. //
  147. // The issue is the parser was ignoring the error and only looking at the
  148. // "does this path exist" boolean, which is always false if there is an error.
  149. // Then the error returned to the caller was a (slightly, maybe) friendlier
  150. // error string than what comes from `os.Stat`
  151. // So ...the caller was always getting an error saying the path doesn't exist
  152. // even if it does exist but got some other error (like a permission error).
  153. // This is confusing to users.
  154. func TestLinuxParseMountSpecBindWithFileinfoError(t *testing.T) {
  155. parser := NewLinuxParser()
  156. testErr := fmt.Errorf("some crazy error")
  157. if pr, ok := parser.(*linuxParser); ok {
  158. pr.fi = &mockFiProviderWithError{err: testErr}
  159. }
  160. _, err := parser.ParseMountSpec(mount.Mount{
  161. Type: mount.TypeBind,
  162. Source: `/bananas`,
  163. Target: `/bananas`,
  164. })
  165. assert.ErrorContains(t, err, testErr.Error())
  166. }
  167. func TestConvertTmpfsOptions(t *testing.T) {
  168. type testCase struct {
  169. opt mount.TmpfsOptions
  170. readOnly bool
  171. expectedSubstrings []string
  172. unexpectedSubstrings []string
  173. }
  174. cases := []testCase{
  175. {
  176. opt: mount.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0o700},
  177. readOnly: false,
  178. expectedSubstrings: []string{"size=1m", "mode=700"},
  179. unexpectedSubstrings: []string{"ro"},
  180. },
  181. {
  182. opt: mount.TmpfsOptions{},
  183. readOnly: true,
  184. expectedSubstrings: []string{"ro"},
  185. unexpectedSubstrings: []string{},
  186. },
  187. }
  188. p := NewLinuxParser()
  189. for _, c := range cases {
  190. data, err := p.ConvertTmpfsOptions(&c.opt, c.readOnly)
  191. if err != nil {
  192. t.Fatalf("could not convert %+v (readOnly: %v) to string: %v",
  193. c.opt, c.readOnly, err)
  194. }
  195. t.Logf("data=%q", data)
  196. for _, s := range c.expectedSubstrings {
  197. if !strings.Contains(data, s) {
  198. t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c)
  199. }
  200. }
  201. for _, s := range c.unexpectedSubstrings {
  202. if strings.Contains(data, s) {
  203. t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c)
  204. }
  205. }
  206. }
  207. }