Hit a number of to-do comments in unified volumes code

As part of this, move bind mount option validity parsing and
modification (adding e.g. rbind on bind mounts that are missing
it), which requires test changes (expected values have changed).

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
This commit is contained in:
Matthew Heon 2019-04-23 16:08:48 -04:00
parent 70beb57faa
commit 13451cab5c
2 changed files with 47 additions and 28 deletions

View File

@ -79,6 +79,7 @@ func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount,
} }
// Unify mounts from --mount, --volume, --tmpfs. // Unify mounts from --mount, --volume, --tmpfs.
// Also add mounts + volumes directly from createconfig.
// Start with --volume. // Start with --volume.
for dest, mount := range volumeMounts { for dest, mount := range volumeMounts {
if _, ok := unifiedMounts[dest]; ok { if _, ok := unifiedMounts[dest]; ok {
@ -99,8 +100,21 @@ func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount,
} }
unifiedMounts[dest] = tmpfs unifiedMounts[dest] = tmpfs
} }
// Now spec mounts and volumes
// TODO: Check for conflicts between volumes and named volumes. for _, mount := range config.Mounts {
dest := mount.Destination
if _, ok := unifiedMounts[dest]; ok {
return nil, nil, errors.Wrapf(errDuplicateDest, dest)
}
unifiedMounts[dest] = mount
}
for _, volume := range config.NamedVolumes {
dest := volume.Dest
if _, ok := unifiedVolumes[dest]; ok {
return nil, nil, errors.Wrapf(errDuplicateDest, dest)
}
unifiedVolumes[dest] = volume
}
// If requested, add container init binary // If requested, add container init binary
if config.Init { if config.Init {
@ -131,9 +145,17 @@ func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount,
baseVolumes[dest] = volume baseVolumes[dest] = volume
} }
// TODO: Allow an explicit list of named volumes and spec mounts in the // Check for conflicts between named volumes and mounts
// createconfig. Podman doesn't need them, but other libpod consumers for dest := range unifiedMounts {
// might want them. if _, ok := unifiedVolumes[dest]; ok {
return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
}
}
for dest := range unifiedVolumes {
if _, ok := unifiedMounts[dest]; ok {
return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
}
}
// Final step: maps to arrays // Final step: maps to arrays
finalMounts := make([]spec.Mount, 0, len(baseMounts)) finalMounts := make([]spec.Mount, 0, len(baseMounts))
@ -164,19 +186,11 @@ func (config *CreateConfig) getVolumesFrom(runtime *libpod.Runtime) (map[string]
// Both of these are maps of mount destination to mount type. // Both of these are maps of mount destination to mount type.
// We ensure that each destination is only mounted to once in this way. // We ensure that each destination is only mounted to once in this way.
// We tiebreak only within each category; we'll let libpod tiebreak
// between mounts and named volumes.
// TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by
// one mount, and we already have /tmp/a and /tmp/b, should we remove
// the /tmp/a and /tmp/b mounts in favor of the more general /tmp?
finalMounts := make(map[string]spec.Mount) finalMounts := make(map[string]spec.Mount)
finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume) finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume)
for _, vol := range config.VolumesFrom { for _, vol := range config.VolumesFrom {
options := []string{} options := []string{}
// TODO: now that container names may contain colons this is no
// longer safe.
// I don't know if there's much we can do here...
splitVol := strings.SplitN(vol, ":", 2) splitVol := strings.SplitN(vol, ":", 2)
if len(splitVol) == 2 { if len(splitVol) == 2 {
if strings.Contains(splitVol[1], "Z") || if strings.Contains(splitVol[1], "Z") ||
@ -185,10 +199,10 @@ func (config *CreateConfig) getVolumesFrom(runtime *libpod.Runtime) (map[string]
strings.Contains(splitVol[1], "shared") { strings.Contains(splitVol[1], "shared") {
return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z", splitVol[1]) return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z", splitVol[1])
} }
if err := ValidateVolumeOpts(splitVol[1]); err != nil { options = strings.Split(splitVol[1], ",")
if err := ValidateVolumeOpts(options); err != nil {
return nil, nil, err return nil, nil, err
} }
options = strings.Split(splitVol[1], ",")
} }
ctr, err := runtime.LookupContainer(splitVol[0]) ctr, err := runtime.LookupContainer(splitVol[0])
if err != nil { if err != nil {
@ -339,14 +353,12 @@ func getBindMount(args []string) (spec.Mount, error) {
kv := strings.Split(val, "=") kv := strings.Split(val, "=")
switch kv[0] { switch kv[0] {
case "ro", "nosuid", "nodev", "noexec": case "ro", "nosuid", "nodev", "noexec":
// TODO: detect duplication of these options // TODO: detect duplication of these options.
// (Is this necessary?)
newMount.Options = append(newMount.Options, kv[0]) newMount.Options = append(newMount.Options, kv[0])
case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z": case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z":
// TODO: detect duplicate mount propagation options
newMount.Options = append(newMount.Options, kv[0]) newMount.Options = append(newMount.Options, kv[0])
case "bind-propagation": case "bind-propagation":
// TODO: this should conflict with the mount propagation
// options above.
if len(kv) == 1 { if len(kv) == 1 {
return newMount, errors.Wrapf(optionArgError, kv[0]) return newMount, errors.Wrapf(optionArgError, kv[0])
} }
@ -382,6 +394,10 @@ func getBindMount(args []string) (spec.Mount, error) {
newMount.Source = newMount.Destination newMount.Source = newMount.Destination
} }
if err := ValidateVolumeOpts(newMount.Options); err != nil {
return newMount, err
}
return newMount, nil return newMount, nil
} }
@ -509,28 +525,27 @@ func ValidateVolumeCtrDir(ctrDir string) error {
} }
// ValidateVolumeOpts validates a volume's options // ValidateVolumeOpts validates a volume's options
func ValidateVolumeOpts(option string) error { func ValidateVolumeOpts(options []string) error {
var foundRootPropagation, foundRWRO, foundLabelChange int var foundRootPropagation, foundRWRO, foundLabelChange int
options := strings.Split(option, ",")
for _, opt := range options { for _, opt := range options {
switch opt { switch opt {
case "rw", "ro": case "rw", "ro":
foundRWRO++ foundRWRO++
if foundRWRO > 1 { if foundRWRO > 1 {
return errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", option) return errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", strings.Join(options, ", "))
} }
case "z", "Z": case "z", "Z":
foundLabelChange++ foundLabelChange++
if foundLabelChange > 1 { if foundLabelChange > 1 {
return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", option) return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", strings.Join(options, ", "))
} }
case "private", "rprivate", "shared", "rshared", "slave", "rslave": case "private", "rprivate", "shared", "rshared", "slave", "rslave":
foundRootPropagation++ foundRootPropagation++
if foundRootPropagation > 1 { if foundRootPropagation > 1 {
return errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", option) return errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", strings.Join(options, ", "))
} }
default: default:
return errors.Errorf("invalid option type %q", option) return errors.Errorf("invalid option type %q", opt)
} }
} }
return nil return nil
@ -562,10 +577,10 @@ func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string
dest = splitVol[1] dest = splitVol[1]
} }
if len(splitVol) > 2 { if len(splitVol) > 2 {
if err := ValidateVolumeOpts(splitVol[2]); err != nil { options = strings.Split(splitVol[2], ",")
if err := ValidateVolumeOpts(options); err != nil {
return nil, nil, err return nil, nil, err
} }
options = strings.Split(splitVol[2], ",")
} }
if err := ValidateVolumeHostDir(src); err != nil { if err := ValidateVolumeHostDir(src); err != nil {
@ -724,6 +739,10 @@ func processOptions(options []string) []string {
return options return options
} }
// Supercede existing mounts in the spec with new, user-specified mounts.
// TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by
// one mount, and we already have /tmp/a and /tmp/b, should we remove
// the /tmp/a and /tmp/b mounts in favor of the more general /tmp?
func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount { func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
if len(mounts) > 0 { if len(mounts) > 0 {
// If we have overlappings mounts, remove them from the spec in favor of // If we have overlappings mounts, remove them from the spec in favor of

View File

@ -13,7 +13,7 @@ func TestGetVolumeMountsOneVolume(t *testing.T) {
Destination: "/foobar", Destination: "/foobar",
Type: "bind", Type: "bind",
Source: "/tmp", Source: "/tmp",
Options: []string{"ro", "rbind", "rprivate"}, Options: []string{"ro"},
} }
config := CreateConfig{ config := CreateConfig{
Volumes: []string{"/tmp:/foobar:ro"}, Volumes: []string{"/tmp:/foobar:ro"},