Skip to content

Commit

Permalink
Implement mount from image
Browse files Browse the repository at this point in the history
Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>
  • Loading branch information
LaurentGoderre committed Nov 6, 2024
1 parent 6ac445c commit b0cd75d
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 23 deletions.
2 changes: 2 additions & 0 deletions api/types/mount/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const (
TypeNamedPipe Type = "npipe"
// TypeCluster is the type for Swarm Cluster Volumes.
TypeCluster Type = "cluster"
// TypeImage is the type for mounting another image's filesystem
TypeImage Type = "image"
)

// Mount represents a mount (volume).
Expand Down
5 changes: 5 additions & 0 deletions daemon/containerd/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
dimages "github.com/docker/docker/daemon/images"
"github.com/docker/docker/daemon/snapshotter"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/image"
"github.com/docker/docker/layer"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/registry"
Expand Down Expand Up @@ -115,6 +116,10 @@ func (i *ImageService) CreateLayer(container *container.Container, initFunc laye
return nil, errdefs.NotImplemented(errdefs.NotImplemented(errors.New("not implemented")))
}

func (i *ImageService) CreateLayerFromImage(img *image.Image, layerName string, rwLayerOpts *layer.CreateRWLayerOpts) (layer.RWLayer, error) {
return nil, errdefs.NotImplemented(errdefs.NotImplemented(errors.New("not implemented")))
}

// LayerStoreStatus returns the status for each layer store
// called from info.go
func (i *ImageService) LayerStoreStatus() [][2]string {
Expand Down
1 change: 1 addition & 0 deletions daemon/image_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type ImageService interface {

GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ROLayer, error)
CreateLayer(container *container.Container, initFunc layer.MountInit) (layer.RWLayer, error)
CreateLayerFromImage(img *image.Image, layerName string, rwLayerOpts *layer.CreateRWLayerOpts) (layer.RWLayer, error)
LayerStoreStatus() [][2]string
GetLayerMountID(cid string) (string, error)
ReleaseLayer(rwlayer layer.RWLayer) error
Expand Down
19 changes: 15 additions & 4 deletions daemon/images/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ func (i *ImageService) Children(_ context.Context, id image.ID) ([]image.ID, err
// called from create.go
// TODO: accept an opt struct instead of container?
func (i *ImageService) CreateLayer(container *container.Container, initFunc layer.MountInit) (layer.RWLayer, error) {
var layerID layer.ChainID
var img *image.Image
if container.ImageID != "" {
img, err := i.imageStore.Get(container.ImageID)
containerImg, err := i.imageStore.Get(container.ImageID)
if err != nil {
return nil, err
}
layerID = img.RootFS.ChainID()
img = containerImg
}

rwLayerOpts := &layer.CreateRWLayerOpts{
Expand All @@ -133,7 +133,18 @@ func (i *ImageService) CreateLayer(container *container.Container, initFunc laye
StorageOpt: container.HostConfig.StorageOpt,
}

return i.layerStore.CreateRWLayer(container.ID, layerID, rwLayerOpts)
return i.CreateLayerFromImage(img, container.ID, rwLayerOpts)
}

// CreateLayerFromImage creates a file system from an arbitrary image
// Used to mount an image inside another
func (i *ImageService) CreateLayerFromImage(img *image.Image, layerName string, rwLayerOpts *layer.CreateRWLayerOpts) (layer.RWLayer, error) {
var layerID layer.ChainID
if img != nil {
layerID = img.RootFS.ChainID()
}

return i.layerStore.CreateRWLayer(layerName, layerID, rwLayerOpts)
}

// GetLayerByID returns a layer by ID
Expand Down
61 changes: 42 additions & 19 deletions daemon/mounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,51 @@ func (daemon *Daemon) removeMountPoints(container *container.Container, rm bool)
var rmErrors []string
ctx := context.TODO()
for _, m := range container.MountPoints {
if m.Type != mounttypes.TypeVolume || m.Volume == nil {
continue
}
daemon.volumes.Release(ctx, m.Volume.Name(), container.ID)
if !rm {
continue
}
if m.Type == mounttypes.TypeVolume {
if m.Volume == nil {
continue
}
daemon.volumes.Release(ctx, m.Volume.Name(), container.ID)
if !rm {
continue
}

// Do not remove named mountpoints
// these are mountpoints specified like `docker run -v <name>:/foo`
if m.Spec.Source != "" {
continue
// Do not remove named mountpoints
// these are mountpoints specified like `docker run -v <name>:/foo`
if m.Spec.Source != "" {
continue
}

err := daemon.volumes.Remove(ctx, m.Volume.Name())
// Ignore volume in use errors because having this
// volume being referenced by other container is
// not an error, but an implementation detail.
// This prevents docker from logging "ERROR: Volume in use"
// where there is another container using the volume.
if err != nil && !volumesservice.IsInUse(err) {
rmErrors = append(rmErrors, err.Error())
}
}

err := daemon.volumes.Remove(ctx, m.Volume.Name())
// Ignore volume in use errors because having this
// volume being referenced by other container is
// not an error, but an implementation detail.
// This prevents docker from logging "ERROR: Volume in use"
// where there is another container using the volume.
if err != nil && !volumesservice.IsInUse(err) {
rmErrors = append(rmErrors, err.Error())
if m.Type == mounttypes.TypeImage {
layerName := fmt.Sprintf("%s-%s", container.ID, m.Spec.Source)
if accessor, ok := daemon.imageService.(layerAccessor); ok {
layer, err := accessor.GetLayerByID(layerName)
if err != nil {
rmErrors = append(rmErrors, err.Error())
continue
}
err = daemon.imageService.ReleaseLayer(layer)
if err != nil {
rmErrors = append(rmErrors, err.Error())
continue
}
err = layer.Unmount()
if err != nil {
rmErrors = append(rmErrors, err.Error())
continue
}
}
}
}

Expand Down
26 changes: 26 additions & 0 deletions daemon/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@ package daemon // import "github.com/docker/docker/daemon"

import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"

"github.com/containerd/log"
"github.com/docker/docker/api/types/backend"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
mounttypes "github.com/docker/docker/api/types/mount"
volumetypes "github.com/docker/docker/api/types/volume"
"github.com/docker/docker/container"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/layer"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
"github.com/docker/docker/volume/service"
Expand Down Expand Up @@ -245,6 +248,29 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
}
}

if mp.Type == mounttypes.TypeImage {
img, err := daemon.imageService.GetImage(ctx, mp.Source, backend.GetImageOpts{})
if err != nil {
return err
}

rwLayerOpts := &layer.CreateRWLayerOpts{
StorageOpt: container.HostConfig.StorageOpt,
}

layerName := fmt.Sprintf("%s-%s", container.ID, mp.Source)
layer, err := daemon.imageService.CreateLayerFromImage(img, layerName, rwLayerOpts)
if err != nil {
return err
}
path, err := layer.Mount("")
if err != nil {
return err
}

mp.Source = path
}

binds[mp.Destination] = true
dereferenceIfExists(mp.Destination)
mountPoints[mp.Destination] = mp
Expand Down
16 changes: 16 additions & 0 deletions volume/mounts/linux_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSour
if _, err := p.ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
return &errMountConfig{mnt, err}
}
case mount.TypeImage:
if mnt.BindOptions != nil {
return &errMountConfig{mnt, errExtraField("BindOptions")}
}
if len(mnt.Source) == 0 {
return &errMountConfig{mnt, errMissingField("Source")}
}
default:
return &errMountConfig{mnt, errors.New("mount type unknown")}
}
Expand Down Expand Up @@ -353,6 +360,15 @@ func (p *linuxParser) parseMountSpec(cfg mount.Mount, validateBindSourceExists b
}
case mount.TypeTmpfs:
// NOP
case mount.TypeImage:
mp.Source = cfg.Source
if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
mp.Propagation = cfg.BindOptions.Propagation
} else {
// If user did not specify a propagation mode, get
// default propagation mode.
mp.Propagation = linuxDefaultPropagationMode
}
}
return mp, nil
}
Expand Down
11 changes: 11 additions & 0 deletions volume/mounts/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mounts // import "github.com/docker/docker/volume/mounts"

import (
"os"
"runtime"
"testing"

"github.com/docker/docker/api/types/mount"
Expand Down Expand Up @@ -78,6 +79,16 @@ func TestParseMountSpec(t *testing.T) {
},
}

if runtime.GOOS != "windows" {
cases = append(cases, struct {
input mount.Mount
expected MountPoint
}{
input: mount.Mount{Type: mount.TypeImage, Source: "alpine", Target: testDestinationPath},
expected: MountPoint{Type: mount.TypeImage, Source: "alpine", Destination: testDestinationPath, RW: true, Propagation: parser.DefaultPropagationMode()},
})
}

for _, tc := range cases {
tc := tc
t.Run("", func(t *testing.T) {
Expand Down

0 comments on commit b0cd75d

Please sign in to comment.