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