241 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			241 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // +build !windows
 | |
| 
 | |
| /*
 | |
|    Copyright The containerd Authors.
 | |
| 
 | |
|    Licensed under the Apache License, Version 2.0 (the "License");
 | |
|    you may not use this file except in compliance with the License.
 | |
|    You may obtain a copy of the License at
 | |
| 
 | |
|        http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
|    Unless required by applicable law or agreed to in writing, software
 | |
|    distributed under the License is distributed on an "AS IS" BASIS,
 | |
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|    See the License for the specific language governing permissions and
 | |
|    limitations under the License.
 | |
| */
 | |
| 
 | |
| package containerd
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"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/linux/runctypes"
 | |
| 	"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.Provider, 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(ctx, 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(ctx context.Context, mounts []mount.Mount, uid, gid uint32) error {
 | |
| 	return mount.WithTempMount(ctx, mounts, func(root string) error {
 | |
| 		return filepath.Walk(root, incrementFS(root, uid, gid))
 | |
| 	})
 | |
| }
 | |
| 
 | |
| 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)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // WithNoPivotRoot instructs the runtime not to you pivot_root
 | |
| func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
 | |
| 	if info.Options == nil {
 | |
| 		info.Options = &runctypes.CreateOptions{
 | |
| 			NoPivotRoot: true,
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 	copts, ok := info.Options.(*runctypes.CreateOptions)
 | |
| 	if !ok {
 | |
| 		return errors.New("invalid options type, expected runctypes.CreateOptions")
 | |
| 	}
 | |
| 	copts.NoPivotRoot = true
 | |
| 	return nil
 | |
| }
 | 
