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 }