github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/internal/pkg/containerized/snapshot.go (about)

     1  package containerized
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/containerd/containerd"
    11  	"github.com/containerd/containerd/containers"
    12  	"github.com/containerd/containerd/diff/apply"
    13  	"github.com/containerd/containerd/mount"
    14  	"github.com/containerd/containerd/rootfs"
    15  	"github.com/containerd/containerd/snapshots"
    16  	"github.com/opencontainers/image-spec/identity"
    17  )
    18  
    19  const (
    20  	gcRoot           = "containerd.io/gc.root"
    21  	timestampFormat  = "01-02-2006-15:04:05"
    22  	previousRevision = "docker.com/revision.previous"
    23  	imageLabel       = "docker.com/revision.image"
    24  )
    25  
    26  // ErrNoPreviousRevision returned if the container has to previous revision
    27  var ErrNoPreviousRevision = errors.New("no previous revision")
    28  
    29  // WithNewSnapshot creates a new snapshot managed by containerized
    30  func WithNewSnapshot(i containerd.Image) containerd.NewContainerOpts {
    31  	return func(ctx context.Context, client *containerd.Client, c *containers.Container) error {
    32  		if c.Snapshotter == "" {
    33  			c.Snapshotter = containerd.DefaultSnapshotter
    34  		}
    35  		r, err := create(ctx, client, i, c.ID, "")
    36  		if err != nil {
    37  			return err
    38  		}
    39  		c.SnapshotKey = r.Key
    40  		c.Image = i.Name()
    41  		return nil
    42  	}
    43  }
    44  
    45  // WithUpgrade upgrades an existing container's image to a new one
    46  func WithUpgrade(i containerd.Image) containerd.UpdateContainerOpts {
    47  	return func(ctx context.Context, client *containerd.Client, c *containers.Container) error {
    48  		revision, err := save(ctx, client, i, c)
    49  		if err != nil {
    50  			return err
    51  		}
    52  		c.Image = i.Name()
    53  		c.SnapshotKey = revision.Key
    54  		return nil
    55  	}
    56  }
    57  
    58  // WithRollback rolls back to the previous container's revision
    59  func WithRollback(ctx context.Context, client *containerd.Client, c *containers.Container) error {
    60  	prev, err := previous(ctx, client, c)
    61  	if err != nil {
    62  		return err
    63  	}
    64  	ss := client.SnapshotService(c.Snapshotter)
    65  	sInfo, err := ss.Stat(ctx, prev.Key)
    66  	if err != nil {
    67  		return err
    68  	}
    69  	snapshotImage, ok := sInfo.Labels[imageLabel]
    70  	if !ok {
    71  		return fmt.Errorf("snapshot %s does not have a service image label", prev.Key)
    72  	}
    73  	if snapshotImage == "" {
    74  		return fmt.Errorf("snapshot %s has an empty service image label", prev.Key)
    75  	}
    76  	c.Image = snapshotImage
    77  	c.SnapshotKey = prev.Key
    78  	return nil
    79  }
    80  
    81  func newRevision(id string) *revision {
    82  	now := time.Now()
    83  	return &revision{
    84  		Timestamp: now,
    85  		Key:       fmt.Sprintf("boss.io.%s.%s", id, now.Format(timestampFormat)),
    86  	}
    87  }
    88  
    89  type revision struct {
    90  	Timestamp time.Time
    91  	Key       string
    92  	mounts    []mount.Mount
    93  }
    94  
    95  // nolint: interfacer
    96  func create(ctx context.Context, client *containerd.Client, i containerd.Image, id string, previous string) (*revision, error) {
    97  	diffIDs, err := i.RootFS(ctx)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	var (
   102  		parent = identity.ChainID(diffIDs).String()
   103  		r      = newRevision(id)
   104  	)
   105  	labels := map[string]string{
   106  		gcRoot:     r.Timestamp.Format(time.RFC3339),
   107  		imageLabel: i.Name(),
   108  	}
   109  	if previous != "" {
   110  		labels[previousRevision] = previous
   111  	}
   112  	mounts, err := client.SnapshotService(containerd.DefaultSnapshotter).Prepare(ctx, r.Key, parent, snapshots.WithLabels(labels))
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	r.mounts = mounts
   117  	return r, nil
   118  }
   119  
   120  func save(ctx context.Context, client *containerd.Client, updatedImage containerd.Image, c *containers.Container) (*revision, error) {
   121  	snapshot, err := create(ctx, client, updatedImage, c.ID, c.SnapshotKey)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	service := client.SnapshotService(c.Snapshotter)
   126  	// create a diff from the existing snapshot
   127  	diff, err := rootfs.CreateDiff(ctx, c.SnapshotKey, service, client.DiffService())
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	applier := apply.NewFileSystemApplier(client.ContentStore())
   132  	if _, err := applier.Apply(ctx, diff, snapshot.mounts); err != nil {
   133  		return nil, err
   134  	}
   135  	return snapshot, nil
   136  }
   137  
   138  // nolint: interfacer
   139  func previous(ctx context.Context, client *containerd.Client, c *containers.Container) (*revision, error) {
   140  	service := client.SnapshotService(c.Snapshotter)
   141  	sInfo, err := service.Stat(ctx, c.SnapshotKey)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	key := sInfo.Labels[previousRevision]
   146  	if key == "" {
   147  		return nil, ErrNoPreviousRevision
   148  	}
   149  	parts := strings.Split(key, ".")
   150  	timestamp, err := time.Parse(timestampFormat, parts[len(parts)-1])
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	return &revision{
   155  		Timestamp: timestamp,
   156  		Key:       key,
   157  	}, nil
   158  }