Mount volumes before copying into a container

This solves several problems with copying into volumes on a
container that is not running.

The first, and most obvious, is that we were previously entirely
unable to copy into a volume that required mounting - like
image volumes, volume plugins, and volumes that specified mount
options.

The second is that this fixed several permissions and content
issues with a fresh volume and a container that has not been run
before. A copy-up will not have occurred, so permissions on the
volume root will not have been set and content will not have been
copied into the volume.

If the container is running, this is very low cost - we maintain
a mount counter for named volumes, so it's just an increment in
the DB if the volume actually needs mounting, and a no-op if it
doesn't.

Unfortunately, we also have to fix permissions, and that is
rather more complicated. This involves an ugly set of manual
edits to the volume state to ensure that the permissions fixes
actually worked, as the code was never meant to be used in this
way. It's really ugly, but necessary to reach full Docker
compatibility.

Fixes #24405

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
This commit is contained in:
Matthew Heon
2024-11-22 11:45:58 -05:00
committed by Matt Heon
parent 44b0c24ca5
commit e66b788a51
7 changed files with 194 additions and 22 deletions

View File

@ -33,8 +33,8 @@ func (c *Container) pathAbs(path string) string {
// It returns a bool, indicating whether containerPath resolves outside of
// mountPoint (e.g., via a mount or volume), the resolved root (e.g., container
// mount, bind mount or volume) and the resolved path on the root (absolute to
// the host).
func (c *Container) resolvePath(mountPoint string, containerPath string) (string, string, error) {
// the host). If the path is on a named volume, the volume is returned.
func (c *Container) resolvePath(mountPoint string, containerPath string) (string, string, *Volume, error) {
// Let's first make sure we have a path relative to the mount point.
pathRelativeToContainerMountPoint := c.pathAbs(containerPath)
resolvedPathOnTheContainerMountPoint := filepath.Join(mountPoint, pathRelativeToContainerMountPoint)
@ -54,21 +54,17 @@ func (c *Container) resolvePath(mountPoint string, containerPath string) (string
for {
volume, err := findVolume(c, searchPath)
if err != nil {
return "", "", err
return "", "", nil, err
}
if volume != nil {
logrus.Debugf("Container path %q resolved to volume %q on path %q", containerPath, volume.Name(), searchPath)
// TODO: We really need to force the volume to mount
// before doing this, but that API is not exposed
// externally right now and doing so is beyond the scope
// of this commit.
mountPoint, err := volume.MountPoint()
if err != nil {
return "", "", err
return "", "", nil, err
}
if mountPoint == "" {
return "", "", fmt.Errorf("volume %s is not mounted, cannot copy into it", volume.Name())
return "", "", nil, fmt.Errorf("volume %s is not mounted, cannot copy into it", volume.Name())
}
// We found a matching volume for searchPath. We now
@ -78,9 +74,9 @@ func (c *Container) resolvePath(mountPoint string, containerPath string) (string
pathRelativeToVolume := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
absolutePathOnTheVolumeMount, err := securejoin.SecureJoin(mountPoint, pathRelativeToVolume)
if err != nil {
return "", "", err
return "", "", nil, err
}
return mountPoint, absolutePathOnTheVolumeMount, nil
return mountPoint, absolutePathOnTheVolumeMount, volume, nil
}
if mount := findBindMount(c, searchPath); mount != nil {
@ -92,9 +88,9 @@ func (c *Container) resolvePath(mountPoint string, containerPath string) (string
pathRelativeToBindMount := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
absolutePathOnTheBindMount, err := securejoin.SecureJoin(mount.Source, pathRelativeToBindMount)
if err != nil {
return "", "", err
return "", "", nil, err
}
return mount.Source, absolutePathOnTheBindMount, nil
return mount.Source, absolutePathOnTheBindMount, nil, nil
}
if searchPath == "/" {
@ -106,7 +102,7 @@ func (c *Container) resolvePath(mountPoint string, containerPath string) (string
}
// No volume, no bind mount but just a normal path on the container.
return mountPoint, resolvedPathOnTheContainerMountPoint, nil
return mountPoint, resolvedPathOnTheContainerMountPoint, nil, nil
}
// findVolume checks if the specified containerPath matches the destination