1/* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15*/ 16 17package opts 18 19import ( 20 "context" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 25 "github.com/containerd/containerd" 26 "github.com/containerd/containerd/containers" 27 "github.com/containerd/containerd/errdefs" 28 "github.com/containerd/containerd/log" 29 "github.com/containerd/containerd/mount" 30 "github.com/containerd/containerd/snapshots" 31 "github.com/containerd/continuity/fs" 32 "github.com/pkg/errors" 33) 34 35// WithNewSnapshot wraps `containerd.WithNewSnapshot` so that if creating the 36// snapshot fails we make sure the image is actually unpacked and and retry. 37func WithNewSnapshot(id string, i containerd.Image, opts ...snapshots.Opt) containerd.NewContainerOpts { 38 f := containerd.WithNewSnapshot(id, i, opts...) 39 return func(ctx context.Context, client *containerd.Client, c *containers.Container) error { 40 if err := f(ctx, client, c); err != nil { 41 if !errdefs.IsNotFound(err) { 42 return err 43 } 44 45 if err := i.Unpack(ctx, c.Snapshotter); err != nil { 46 return errors.Wrap(err, "error unpacking image") 47 } 48 return f(ctx, client, c) 49 } 50 return nil 51 } 52} 53 54// WithVolumes copies ownership of volume in rootfs to its corresponding host path. 55// It doesn't update runtime spec. 56// The passed in map is a host path to container path map for all volumes. 57func WithVolumes(volumeMounts map[string]string) containerd.NewContainerOpts { 58 return func(ctx context.Context, client *containerd.Client, c *containers.Container) (err error) { 59 if c.Snapshotter == "" { 60 return errors.New("no snapshotter set for container") 61 } 62 if c.SnapshotKey == "" { 63 return errors.New("rootfs not created for container") 64 } 65 snapshotter := client.SnapshotService(c.Snapshotter) 66 mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) 67 if err != nil { 68 return err 69 } 70 root, err := ioutil.TempDir("", "ctd-volume") 71 if err != nil { 72 return err 73 } 74 // We change RemoveAll to Remove so that we either leak a temp dir 75 // if it fails but not RM snapshot data. 76 // refer to https://github.com/containerd/containerd/pull/1868 77 // https://github.com/containerd/containerd/pull/1785 78 defer os.Remove(root) // nolint: errcheck 79 if err := mount.All(mounts, root); err != nil { 80 return errors.Wrap(err, "failed to mount") 81 } 82 defer func() { 83 if uerr := mount.Unmount(root, 0); uerr != nil { 84 log.G(ctx).WithError(uerr).Errorf("Failed to unmount snapshot %q", c.SnapshotKey) 85 if err == nil { 86 err = uerr 87 } 88 } 89 }() 90 91 for host, volume := range volumeMounts { 92 src := filepath.Join(root, volume) 93 if _, err := os.Stat(src); err != nil { 94 if os.IsNotExist(err) { 95 // Skip copying directory if it does not exist. 96 continue 97 } 98 return errors.Wrap(err, "stat volume in rootfs") 99 } 100 if err := copyExistingContents(src, host); err != nil { 101 return errors.Wrap(err, "taking runtime copy of volume") 102 } 103 } 104 return nil 105 } 106} 107 108// copyExistingContents copies from the source to the destination and 109// ensures the ownership is appropriately set. 110func copyExistingContents(source, destination string) error { 111 dstList, err := ioutil.ReadDir(destination) 112 if err != nil { 113 return err 114 } 115 if len(dstList) != 0 { 116 return errors.Errorf("volume at %q is not initially empty", destination) 117 } 118 return fs.CopyDir(destination, source) 119} 120