123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- // +build !windows
- package containerd
- import (
- "context"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "syscall"
- "github.com/containerd/containerd/api/types"
- "github.com/containerd/containerd/containers"
- "github.com/containerd/containerd/content"
- "github.com/containerd/containerd/errdefs"
- "github.com/containerd/containerd/images"
- "github.com/containerd/containerd/mount"
- "github.com/containerd/containerd/platforms"
- "github.com/gogo/protobuf/proto"
- protobuf "github.com/gogo/protobuf/types"
- digest "github.com/opencontainers/go-digest"
- "github.com/opencontainers/image-spec/identity"
- "github.com/opencontainers/image-spec/specs-go/v1"
- "github.com/pkg/errors"
- )
- // WithCheckpoint allows a container to be created from the checkpointed information
- // provided by the descriptor. The image, snapshot, and runtime specifications are
- // restored on the container
- func WithCheckpoint(im Image, snapshotKey string) NewContainerOpts {
- // set image and rw, and spec
- return func(ctx context.Context, client *Client, c *containers.Container) error {
- var (
- desc = im.Target()
- id = desc.Digest
- store = client.ContentStore()
- )
- index, err := decodeIndex(ctx, store, id)
- if err != nil {
- return err
- }
- var rw *v1.Descriptor
- for _, m := range index.Manifests {
- switch m.MediaType {
- case v1.MediaTypeImageLayer:
- fk := m
- rw = &fk
- case images.MediaTypeDockerSchema2Manifest, images.MediaTypeDockerSchema2ManifestList:
- config, err := images.Config(ctx, store, m, platforms.Default())
- if err != nil {
- return errors.Wrap(err, "unable to resolve image config")
- }
- diffIDs, err := images.RootFS(ctx, store, config)
- if err != nil {
- return errors.Wrap(err, "unable to get rootfs")
- }
- setSnapshotterIfEmpty(c)
- if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, snapshotKey, identity.ChainID(diffIDs).String()); err != nil {
- if !errdefs.IsAlreadyExists(err) {
- return err
- }
- }
- c.Image = index.Annotations["image.name"]
- case images.MediaTypeContainerd1CheckpointConfig:
- data, err := content.ReadBlob(ctx, store, m.Digest)
- if err != nil {
- return errors.Wrap(err, "unable to read checkpoint config")
- }
- var any protobuf.Any
- if err := proto.Unmarshal(data, &any); err != nil {
- return err
- }
- c.Spec = &any
- }
- }
- if rw != nil {
- // apply the rw snapshot to the new rw layer
- mounts, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, snapshotKey)
- if err != nil {
- return errors.Wrapf(err, "unable to get mounts for %s", snapshotKey)
- }
- if _, err := client.DiffService().Apply(ctx, *rw, mounts); err != nil {
- return errors.Wrap(err, "unable to apply rw diff")
- }
- }
- c.SnapshotKey = snapshotKey
- return nil
- }
- }
- // WithTaskCheckpoint allows a task to be created with live runtime and memory data from a
- // previous checkpoint. Additional software such as CRIU may be required to
- // restore a task from a checkpoint
- func WithTaskCheckpoint(im Image) NewTaskOpts {
- return func(ctx context.Context, c *Client, info *TaskInfo) error {
- desc := im.Target()
- id := desc.Digest
- index, err := decodeIndex(ctx, c.ContentStore(), id)
- if err != nil {
- return err
- }
- for _, m := range index.Manifests {
- if m.MediaType == images.MediaTypeContainerd1Checkpoint {
- info.Checkpoint = &types.Descriptor{
- MediaType: m.MediaType,
- Size_: m.Size,
- Digest: m.Digest,
- }
- return nil
- }
- }
- return fmt.Errorf("checkpoint not found in index %s", id)
- }
- }
- func decodeIndex(ctx context.Context, store content.Store, id digest.Digest) (*v1.Index, error) {
- var index v1.Index
- p, err := content.ReadBlob(ctx, store, id)
- if err != nil {
- return nil, err
- }
- if err := json.Unmarshal(p, &index); err != nil {
- return nil, err
- }
- return &index, nil
- }
- // WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
- // filesystem to be used by a container with user namespaces
- func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
- return withRemappedSnapshotBase(id, i, uid, gid, false)
- }
- // WithRemappedSnapshotView is similar to WithRemappedSnapshot but rootfs is mounted as read-only.
- func WithRemappedSnapshotView(id string, i Image, uid, gid uint32) NewContainerOpts {
- return withRemappedSnapshotBase(id, i, uid, gid, true)
- }
- func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool) NewContainerOpts {
- return func(ctx context.Context, client *Client, c *containers.Container) error {
- diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default())
- if err != nil {
- return err
- }
- setSnapshotterIfEmpty(c)
- var (
- snapshotter = client.SnapshotService(c.Snapshotter)
- parent = identity.ChainID(diffIDs).String()
- usernsID = fmt.Sprintf("%s-%d-%d", parent, uid, gid)
- )
- if _, err := snapshotter.Stat(ctx, usernsID); err == nil {
- if _, err := snapshotter.Prepare(ctx, id, usernsID); err == nil {
- c.SnapshotKey = id
- c.Image = i.Name()
- return nil
- } else if !errdefs.IsNotFound(err) {
- return err
- }
- }
- mounts, err := snapshotter.Prepare(ctx, usernsID+"-remap", parent)
- if err != nil {
- return err
- }
- if err := remapRootFS(mounts, uid, gid); err != nil {
- snapshotter.Remove(ctx, usernsID)
- return err
- }
- if err := snapshotter.Commit(ctx, usernsID, usernsID+"-remap"); err != nil {
- return err
- }
- if readonly {
- _, err = snapshotter.View(ctx, id, usernsID)
- } else {
- _, err = snapshotter.Prepare(ctx, id, usernsID)
- }
- if err != nil {
- return err
- }
- c.SnapshotKey = id
- c.Image = i.Name()
- return nil
- }
- }
- func remapRootFS(mounts []mount.Mount, uid, gid uint32) error {
- root, err := ioutil.TempDir("", "ctd-remap")
- if err != nil {
- return err
- }
- defer os.Remove(root)
- for _, m := range mounts {
- if err := m.Mount(root); err != nil {
- return err
- }
- }
- err = filepath.Walk(root, incrementFS(root, uid, gid))
- if uerr := mount.Unmount(root, 0); err == nil {
- err = uerr
- }
- return err
- }
- func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
- return func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- var (
- stat = info.Sys().(*syscall.Stat_t)
- u, g = int(stat.Uid + uidInc), int(stat.Gid + gidInc)
- )
- // be sure the lchown the path as to not de-reference the symlink to a host file
- return os.Lchown(path, u, g)
- }
- }
|