Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/server/router/container/container_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,11 @@ func (c *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
// reinitialize this field.
epConfig.GwPriority = 0
}
for _, m := range hostConfig.Mounts {
if m.Type == mount.TypeImage {
return errdefs.InvalidParameter(errors.New(`Mount type "Image" needs API v1.48 or newer`))
}
}
}

var warnings []string
Expand Down
12 changes: 12 additions & 0 deletions api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,15 @@ definitions:

- `bind` a mount of a file or directory from the host into the container.
- `volume` a docker volume with the given `Name`.
- `image` a docker image
- `tmpfs` a `tmpfs`.
- `npipe` a named pipe from the host into the container.
- `cluster` a Swarm cluster volume
type: "string"
enum:
- "bind"
- "volume"
- "image"
- "tmpfs"
- "npipe"
- "cluster"
Expand Down Expand Up @@ -350,13 +352,15 @@ definitions:

- `bind` Mounts a file or directory from the host into the container. Must exist prior to creating the container.
- `volume` Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed.
- `image` Mounts an image.
- `tmpfs` Create a tmpfs with the given options. The mount source cannot be specified for tmpfs.
- `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container.
- `cluster` a Swarm cluster volume
type: "string"
enum:
- "bind"
- "volume"
- "image"
- "tmpfs"
- "npipe"
- "cluster"
Expand Down Expand Up @@ -431,6 +435,14 @@ definitions:
description: "Source path inside the volume. Must be relative without any back traversals."
type: "string"
example: "dir-inside-volume/subdirectory"
ImageOptions:
description: "Optional configuration for the `image` type."
type: "object"
properties:
Subpath:
description: "Source path inside the image. Must be relative without any back traversals."
type: "string"
example: "dir-inside-image/subdirectory"
TmpfsOptions:
description: "Optional configuration for the `tmpfs` type."
type: "object"
Expand Down
7 changes: 7 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 All @@ -34,6 +36,7 @@ type Mount struct {

BindOptions *BindOptions `json:",omitempty"`
VolumeOptions *VolumeOptions `json:",omitempty"`
ImageOptions *ImageOptions `json:",omitempty"`
TmpfsOptions *TmpfsOptions `json:",omitempty"`
ClusterOptions *ClusterOptions `json:",omitempty"`
}
Expand Down Expand Up @@ -100,6 +103,10 @@ type VolumeOptions struct {
DriverConfig *Driver `json:",omitempty"`
}

type ImageOptions struct {
Subpath string `json:",omitempty"`
}

// Driver represents a volume driver.
type Driver struct {
Name string `json:",omitempty"`
Expand Down
14 changes: 13 additions & 1 deletion daemon/containerd/image_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,19 @@ func (i *ImageService) ImageDelete(ctx context.Context, imageRef string, force,
}

using := func(c *container.Container) bool {
return c.ImageID == imgID
if c.ImageID == imgID {
return true
}

for _, mp := range c.MountPoints {
if mp.Type == "image" {
if mp.Spec.Source == string(imgID) {
return true
}
}
}

return false
}
// TODO: Should this also check parentage here?
ctr := i.containers.First(using)
Expand Down
102 changes: 70 additions & 32 deletions daemon/containerd/image_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,82 +13,118 @@ import (
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/snapshotter"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/image"
"github.com/docker/docker/layer"
"github.com/opencontainers/image-spec/identity"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)

// CreateLayer creates a new layer for a container.
// TODO(vvoland): Decouple from container
func (i *ImageService) CreateLayer(ctr *container.Container, initFunc layer.MountInit) (container.RWLayer, error) {
ctx := context.TODO()

var parentSnapshot string
var descriptor *ocispec.Descriptor
if ctr.ImageManifest != nil {
img := c8dimages.Image{
Target: *ctr.ImageManifest,
}
platformImg, err := i.NewImageManifest(ctx, img, img.Target)
if err != nil {
return nil, err
}
unpacked, err := platformImg.IsUnpacked(ctx, i.snapshotter)
if err != nil {
return nil, err
}
descriptor = ctr.ImageManifest
}

if !unpacked {
if err := platformImg.Unpack(ctx, i.snapshotter); err != nil {
return nil, err
}
}
rwLayerOpts := &layer.CreateRWLayerOpts{
StorageOpt: ctr.HostConfig.StorageOpt,
}

return i.createLayer(descriptor, ctr.ID, rwLayerOpts, initFunc)
}

// CreateLayerFromImage creates a new layer from an image
func (i *ImageService) CreateLayerFromImage(img *image.Image, layerName string, rwLayerOpts *layer.CreateRWLayerOpts) (container.RWLayer, error) {
var descriptor *ocispec.Descriptor
if img != nil {
descriptor = img.Details.ManifestDescriptor
}

diffIDs, err := platformImg.RootFS(ctx)
return i.createLayer(descriptor, layerName, rwLayerOpts, nil)
}

func (i *ImageService) createLayer(descriptor *ocispec.Descriptor, layerName string, rwLayerOpts *layer.CreateRWLayerOpts, initFunc layer.MountInit) (container.RWLayer, error) {
ctx := context.TODO()
var parentSnapshot string
if descriptor != nil {
snapshot, err := i.getImageSnapshot(ctx, descriptor)
if err != nil {
return nil, err
}

parentSnapshot = identity.ChainID(diffIDs).String()
parentSnapshot = snapshot
}

id := ctr.ID

// TODO: Consider a better way to do this. It is better to have a container directly
// reference a snapshot, however, that is not done today because a container may
// removed and recreated with nothing holding the snapshot in between. Consider
// removing this lease and only temporarily holding a lease on re-create, using
// non-expiring leases introduces the possibility of leaking resources.
ls := i.client.LeasesService()
lease, err := ls.Create(ctx, leases.WithID(id))
lease, err := ls.Create(ctx, leases.WithID(layerName))
if err != nil {
return nil, err
}
ctx = leases.WithLease(ctx, lease.ID)

if err := i.prepareInitLayer(ctx, id, parentSnapshot, initFunc); err != nil {
return nil, err
if initFunc != nil {
if err := i.prepareInitLayer(ctx, layerName, parentSnapshot, initFunc); err != nil {
return nil, err
}
parentSnapshot = layerName + "-init"
}

sn := i.client.SnapshotService(i.StorageDriver())
if !i.idMapping.Empty() {
err = i.remapSnapshot(ctx, sn, id, id+"-init")
err = i.remapSnapshot(ctx, sn, layerName, parentSnapshot)
} else {
_, err = sn.Prepare(ctx, id, id+"-init")
_, err = sn.Prepare(ctx, layerName, parentSnapshot)
}

if err != nil {
return nil, err
}

return &rwLayer{
id: id,
id: layerName,
snapshotterName: i.StorageDriver(),
snapshotter: sn,
refCountMounter: i.refCountMounter,
lease: lease,
}, nil
}

func (i *ImageService) getImageSnapshot(ctx context.Context, descriptor *ocispec.Descriptor) (string, error) {
c8dImg := c8dimages.Image{
Target: *descriptor,
}

platformImg, err := i.NewImageManifest(ctx, c8dImg, c8dImg.Target)
if err != nil {
return "", err
}

unpacked, err := platformImg.IsUnpacked(ctx, i.snapshotter)
if err != nil {
return "", err
}

if !unpacked {
if err := platformImg.Unpack(ctx, i.snapshotter); err != nil {
return "", err
}
}

diffIDs, err := platformImg.RootFS(ctx)
if err != nil {
return "", err
}

snapshot := identity.ChainID(diffIDs).String()

return snapshot, nil
}

type rwLayer struct {
id string
snapshotter snapshots.Snapshotter
Expand Down Expand Up @@ -188,7 +224,9 @@ func (l *rwLayer) Unmount() error {
}

func (l rwLayer) Metadata() (map[string]string, error) {
return nil, nil
return map[string]string{
"ID": l.id,
}, nil
}

// ReleaseLayer releases a layer allowing it to be removed
Expand Down
1 change: 1 addition & 0 deletions daemon/image_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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) (container.RWLayer, error)
CreateLayerFromImage(img *image.Image, layerName string, rwLayerOpts *layer.CreateRWLayerOpts) (container.RWLayer, error)
GetLayerByID(cid string) (container.RWLayer, error)
LayerStoreStatus() [][2]string
GetLayerMountID(cid string) (string, error)
Expand Down
14 changes: 13 additions & 1 deletion daemon/images/image_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,19 @@ func (i *ImageService) ImageDelete(ctx context.Context, imageRef string, force,
repoRefs := i.referenceStore.References(imgID.Digest())

using := func(c *container.Container) bool {
return c.ImageID == imgID
if c.ImageID == imgID {
return true
}

for _, mp := range c.MountPoints {
if mp.Type == "image" {
if mp.Spec.Source == string(imgID) {
return true
}
}
}

return false
}

var removedRepositoryRef bool
Expand Down
19 changes: 15 additions & 4 deletions daemon/images/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,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) (container.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 @@ -134,7 +134,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) (container.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
Loading