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