2222 errNoDest = errors .New ("must set volume destination" )
2323)
2424
25+ type universalMount struct {
26+ mount spec.Mount
27+ // Used only with Named Volume type mounts
28+ subPath string
29+ }
30+
2531// Parse all volume-related options in the create config into a set of mounts
2632// and named volumes to add to the container.
2733// Handles --volumes, --mount, and --tmpfs flags.
@@ -253,18 +259,18 @@ func Mounts(mountFlag []string, configMounts []string) (map[string]spec.Mount, m
253259 return finalMounts , finalNamedVolumes , finalImageVolumes , nil
254260}
255261
256- func parseMountOptions (mountType string , args []string ) (* spec. Mount , error ) {
262+ func parseMountOptions (mountType string , args []string ) (* universalMount , error ) {
257263 var setTmpcopyup , setRORW , setSuid , setDev , setExec , setRelabel , setOwnership , setSwap bool
258264
259- mnt := spec. Mount {}
265+ mnt := new ( universalMount )
260266 for _ , arg := range args {
261267 name , value , hasValue := strings .Cut (arg , "=" )
262268 switch name {
263269 case "bind-nonrecursive" :
264270 if mountType != define .TypeBind {
265271 return nil , fmt .Errorf ("%q option not supported for %q mount types" , name , mountType )
266272 }
267- mnt .Options = append (mnt .Options , define .TypeBind )
273+ mnt .mount . Options = append (mnt . mount .Options , define .TypeBind )
268274 case "bind-propagation" :
269275 if mountType != define .TypeBind {
270276 return nil , fmt .Errorf ("%q option not supported for %q mount types" , name , mountType )
@@ -278,16 +284,16 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
278284 default :
279285 return nil , fmt .Errorf ("invalid value %q" , arg )
280286 }
281- mnt .Options = append (mnt .Options , value )
287+ mnt .mount . Options = append (mnt . mount .Options , value )
282288 case "consistency" :
283289 // Often used on MACs and mistakenly on Linux platforms.
284290 // Since Docker ignores this option so shall we.
285291 continue
286292 case "idmap" :
287293 if hasValue {
288- mnt .Options = append (mnt .Options , fmt .Sprintf ("idmap=%s" , value ))
294+ mnt .mount . Options = append (mnt . mount .Options , fmt .Sprintf ("idmap=%s" , value ))
289295 } else {
290- mnt .Options = append (mnt .Options , "idmap" )
296+ mnt .mount . Options = append (mnt . mount .Options , "idmap" )
291297 }
292298 case "readonly" , "ro" , "rw" :
293299 if setRORW {
@@ -307,35 +313,35 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
307313 if hasValue {
308314 switch strings .ToLower (value ) {
309315 case "true" :
310- mnt .Options = append (mnt .Options , name )
316+ mnt .mount . Options = append (mnt . mount .Options , name )
311317 case "false" :
312318 // Set the opposite only for rw
313319 // ro's opposite is the default
314320 if name == "rw" {
315- mnt .Options = append (mnt .Options , "ro" )
321+ mnt .mount . Options = append (mnt . mount .Options , "ro" )
316322 }
317323 }
318324 } else {
319- mnt .Options = append (mnt .Options , name )
325+ mnt .mount . Options = append (mnt . mount .Options , name )
320326 }
321327 case "nodev" , "dev" :
322328 if setDev {
323329 return nil , fmt .Errorf ("cannot pass 'nodev' and 'dev' mnt.Options more than once: %w" , errOptionArg )
324330 }
325331 setDev = true
326- mnt .Options = append (mnt .Options , name )
332+ mnt .mount . Options = append (mnt . mount .Options , name )
327333 case "noexec" , "exec" :
328334 if setExec {
329335 return nil , fmt .Errorf ("cannot pass 'noexec' and 'exec' mnt.Options more than once: %w" , errOptionArg )
330336 }
331337 setExec = true
332- mnt .Options = append (mnt .Options , name )
338+ mnt .mount . Options = append (mnt . mount .Options , name )
333339 case "nosuid" , "suid" :
334340 if setSuid {
335341 return nil , fmt .Errorf ("cannot pass 'nosuid' and 'suid' mnt.Options more than once: %w" , errOptionArg )
336342 }
337343 setSuid = true
338- mnt .Options = append (mnt .Options , name )
344+ mnt .mount . Options = append (mnt . mount .Options , name )
339345 case "noswap" :
340346 if setSwap {
341347 return nil , fmt .Errorf ("cannot pass 'noswap' mnt.Options more than once: %w" , errOptionArg )
@@ -344,7 +350,7 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
344350 return nil , fmt .Errorf ("the 'noswap' option is only allowed with rootful tmpfs mounts: %w" , errOptionArg )
345351 }
346352 setSwap = true
347- mnt .Options = append (mnt .Options , name )
353+ mnt .mount . Options = append (mnt . mount .Options , name )
348354 case "relabel" :
349355 if setRelabel {
350356 return nil , fmt .Errorf ("cannot pass 'relabel' option more than once: %w" , errOptionArg )
@@ -355,19 +361,19 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
355361 }
356362 switch value {
357363 case "private" :
358- mnt .Options = append (mnt .Options , "Z" )
364+ mnt .mount . Options = append (mnt . mount .Options , "Z" )
359365 case "shared" :
360- mnt .Options = append (mnt .Options , "z" )
366+ mnt .mount . Options = append (mnt . mount .Options , "z" )
361367 default :
362368 return nil , fmt .Errorf ("%s mount option must be 'private' or 'shared': %w" , name , util .ErrBadMntOption )
363369 }
364370 case "shared" , "rshared" , "private" , "rprivate" , "slave" , "rslave" , "unbindable" , "runbindable" , "Z" , "z" , "no-dereference" :
365- mnt .Options = append (mnt .Options , name )
371+ mnt .mount . Options = append (mnt . mount .Options , name )
366372 case "src" , "source" :
367373 if mountType == define .TypeTmpfs {
368374 return nil , fmt .Errorf ("%q option not supported for %q mount types" , name , mountType )
369375 }
370- if mnt .Source != "" {
376+ if mnt .mount . Source != "" {
371377 return nil , fmt .Errorf ("cannot pass %q option more than once: %w" , name , errOptionArg )
372378 }
373379 if ! hasValue {
@@ -376,9 +382,17 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
376382 if len (value ) == 0 {
377383 return nil , fmt .Errorf ("host directory cannot be empty: %w" , errOptionArg )
378384 }
379- mnt .Source = value
385+ mnt .mount .Source = value
386+ case "subpath" , "volume-subpath" :
387+ if mountType != define .TypeVolume {
388+ return nil , fmt .Errorf ("cannot set option %q on non-volume mounts" , name )
389+ }
390+ if ! hasValue {
391+ return nil , fmt .Errorf ("%v: %w" , name , errOptionArg )
392+ }
393+ mnt .subPath = value
380394 case "target" , "dst" , "destination" :
381- if mnt .Destination != "" {
395+ if mnt .mount . Destination != "" {
382396 return nil , fmt .Errorf ("cannot pass %q option more than once: %w" , name , errOptionArg )
383397 }
384398 if ! hasValue {
@@ -387,7 +401,7 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
387401 if err := parse .ValidateVolumeCtrDir (value ); err != nil {
388402 return nil , err
389403 }
390- mnt .Destination = unixPathClean (value )
404+ mnt .mount . Destination = unixPathClean (value )
391405 case "tmpcopyup" , "notmpcopyup" :
392406 if mountType != define .TypeTmpfs {
393407 return nil , fmt .Errorf ("%q option not supported for %q mount types" , name , mountType )
@@ -396,23 +410,23 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
396410 return nil , fmt .Errorf ("cannot pass 'tmpcopyup' and 'notmpcopyup' mnt.Options more than once: %w" , errOptionArg )
397411 }
398412 setTmpcopyup = true
399- mnt .Options = append (mnt .Options , name )
413+ mnt .mount . Options = append (mnt . mount .Options , name )
400414 case "tmpfs-mode" :
401415 if mountType != define .TypeTmpfs {
402416 return nil , fmt .Errorf ("%q option not supported for %q mount types" , name , mountType )
403417 }
404418 if ! hasValue {
405419 return nil , fmt .Errorf ("%v: %w" , name , errOptionArg )
406420 }
407- mnt .Options = append (mnt .Options , fmt .Sprintf ("mode=%s" , value ))
421+ mnt .mount . Options = append (mnt . mount .Options , fmt .Sprintf ("mode=%s" , value ))
408422 case "tmpfs-size" :
409423 if mountType != define .TypeTmpfs {
410424 return nil , fmt .Errorf ("%q option not supported for %q mount types" , name , mountType )
411425 }
412426 if ! hasValue {
413427 return nil , fmt .Errorf ("%v: %w" , name , errOptionArg )
414428 }
415- mnt .Options = append (mnt .Options , fmt .Sprintf ("size=%s" , value ))
429+ mnt .mount . Options = append (mnt . mount .Options , fmt .Sprintf ("size=%s" , value ))
416430 case "U" , "chown" :
417431 if setOwnership {
418432 return nil , fmt .Errorf ("cannot pass 'U' or 'chown' option more than once: %w" , errOptionArg )
@@ -422,7 +436,7 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
422436 return nil , err
423437 }
424438 if ok {
425- mnt .Options = append (mnt .Options , "U" )
439+ mnt .mount . Options = append (mnt . mount .Options , "U" )
426440 }
427441 setOwnership = true
428442 case "volume-label" :
@@ -434,25 +448,26 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
434448 if mountType != define .TypeVolume {
435449 return nil , fmt .Errorf ("%q option not supported for %q mount types" , name , mountType )
436450 }
437- mnt .Options = append (mnt .Options , arg )
451+ mnt .mount . Options = append (mnt . mount .Options , arg )
438452 default :
439453 return nil , fmt .Errorf ("%s: %w" , name , util .ErrBadMntOption )
440454 }
441455 }
442- if mountType != "glob" && len (mnt .Destination ) == 0 {
456+ if mountType != "glob" && len (mnt .mount . Destination ) == 0 {
443457 return nil , errNoDest
444458 }
445- return & mnt , nil
459+ return mnt , nil
446460}
447461
448462// Parse glob mounts entry from the --mount flag.
449463func getGlobMounts (args []string ) ([]spec.Mount , error ) {
450464 mounts := []spec.Mount {}
451465
452- mnt , err := parseMountOptions ("glob" , args )
466+ uMnt , err := parseMountOptions ("glob" , args )
453467 if err != nil {
454468 return nil , err
455469 }
470+ mnt := uMnt .mount
456471
457472 globs , err := filepath .Glob (mnt .Source )
458473 if err != nil {
@@ -488,10 +503,11 @@ func getBindMount(args []string) (spec.Mount, error) {
488503 Type : define .TypeBind ,
489504 }
490505 var err error
491- mnt , err := parseMountOptions (newMount .Type , args )
506+ uMnt , err := parseMountOptions (newMount .Type , args )
492507 if err != nil {
493508 return newMount , err
494509 }
510+ mnt := uMnt .mount
495511
496512 if len (mnt .Destination ) == 0 {
497513 return newMount , errNoDest
@@ -519,10 +535,11 @@ func parseMemoryMount(args []string, mountType string) (spec.Mount, error) {
519535 }
520536
521537 var err error
522- mnt , err := parseMountOptions (newMount .Type , args )
538+ uMnt , err := parseMountOptions (newMount .Type , args )
523539 if err != nil {
524540 return newMount , err
525541 }
542+ mnt := uMnt .mount
526543 if len (mnt .Destination ) == 0 {
527544 return newMount , errNoDest
528545 }
@@ -576,12 +593,14 @@ func getNamedVolume(args []string) (*specgen.NamedVolume, error) {
576593 if err != nil {
577594 return nil , err
578595 }
579- if len (mnt .Destination ) == 0 {
596+ if len (mnt .mount . Destination ) == 0 {
580597 return nil , errNoDest
581598 }
582- newVolume .Options = mnt .Options
583- newVolume .Name = mnt .Source
584- newVolume .Dest = mnt .Destination
599+
600+ newVolume .Options = mnt .mount .Options
601+ newVolume .SubPath = mnt .subPath
602+ newVolume .Name = mnt .mount .Source
603+ newVolume .Dest = mnt .mount .Destination
585604 return newVolume , nil
586605}
587606
0 commit comments