123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- package snapshot
- import (
- "context"
- "path/filepath"
- "strconv"
- "strings"
- "sync"
- cerrdefs "github.com/containerd/containerd/errdefs"
- "github.com/containerd/containerd/leases"
- "github.com/containerd/containerd/mount"
- "github.com/containerd/containerd/snapshots"
- "github.com/docker/docker/daemon/graphdriver"
- "github.com/docker/docker/layer"
- "github.com/docker/docker/pkg/idtools"
- "github.com/moby/buildkit/identity"
- "github.com/moby/buildkit/snapshot"
- "github.com/moby/buildkit/util/leaseutil"
- "github.com/opencontainers/go-digest"
- "github.com/pkg/errors"
- bolt "go.etcd.io/bbolt"
- )
- var (
- keyParent = []byte("parent")
- keyCommitted = []byte("committed")
- keyIsCommitted = []byte("iscommitted")
- keyChainID = []byte("chainid")
- keySize = []byte("size")
- )
- // Opt defines options for creating the snapshotter
- type Opt struct {
- GraphDriver graphdriver.Driver
- LayerStore layer.Store
- Root string
- IdentityMapping idtools.IdentityMapping
- }
- type graphIDRegistrar interface {
- RegisterByGraphID(string, layer.ChainID, layer.DiffID, string, int64) (layer.Layer, error)
- Release(layer.Layer) ([]layer.Metadata, error)
- checksumCalculator
- }
- type checksumCalculator interface {
- ChecksumForGraphID(id, parent, oldTarDataPath, newTarDataPath string) (diffID layer.DiffID, size int64, err error)
- }
- type snapshotter struct {
- opt Opt
- refs map[string]layer.Layer
- db *bolt.DB
- mu sync.Mutex
- reg graphIDRegistrar
- }
- // NewSnapshotter creates a new snapshotter
- func NewSnapshotter(opt Opt, prevLM leases.Manager, ns string) (snapshot.Snapshotter, *leaseutil.Manager, error) {
- dbPath := filepath.Join(opt.Root, "snapshots.db")
- db, err := bolt.Open(dbPath, 0o600, nil)
- if err != nil {
- return nil, nil, errors.Wrapf(err, "failed to open database file %s", dbPath)
- }
- reg, ok := opt.LayerStore.(graphIDRegistrar)
- if !ok {
- return nil, nil, errors.Errorf("layerstore doesn't support graphID registration")
- }
- s := &snapshotter{
- opt: opt,
- db: db,
- refs: map[string]layer.Layer{},
- reg: reg,
- }
- slm := newLeaseManager(s, prevLM)
- lm := leaseutil.WithNamespace(slm, ns)
- ll, err := lm.List(context.TODO())
- if err != nil {
- return nil, nil, err
- }
- for _, l := range ll {
- rr, err := lm.ListResources(context.TODO(), l)
- if err != nil {
- return nil, nil, err
- }
- for _, r := range rr {
- if r.Type == "snapshots/default" {
- slm.addRef(l.ID, r.ID)
- }
- }
- }
- return s, lm, nil
- }
- func (s *snapshotter) Name() string {
- return "default"
- }
- func (s *snapshotter) IdentityMapping() *idtools.IdentityMapping {
- // Returning a non-nil but empty *IdentityMapping breaks BuildKit:
- // https://github.com/moby/moby/pull/39444
- if s.opt.IdentityMapping.Empty() {
- return nil
- }
- return &s.opt.IdentityMapping
- }
- func (s *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) error {
- origParent := parent
- if parent != "" {
- if l, err := s.getLayer(parent, false); err != nil {
- return errors.Wrapf(err, "failed to get parent layer %s", parent)
- } else if l != nil {
- parent, err = getGraphID(l)
- if err != nil {
- return errors.Wrapf(err, "failed to get parent graphid %s", l.ChainID())
- }
- } else {
- parent, _ = s.getGraphDriverID(parent)
- }
- }
- if err := s.opt.GraphDriver.Create(key, parent, nil); err != nil {
- return err
- }
- return s.db.Update(func(tx *bolt.Tx) error {
- b, err := tx.CreateBucketIfNotExists([]byte(key))
- if err != nil {
- return err
- }
- return b.Put(keyParent, []byte(origParent))
- })
- }
- func (s *snapshotter) chainID(key string) (layer.ChainID, bool) {
- if strings.HasPrefix(key, "sha256:") {
- dgst, err := digest.Parse(key)
- if err != nil {
- return "", false
- }
- return layer.ChainID(dgst), true
- }
- return "", false
- }
- func (s *snapshotter) GetLayer(key string) (layer.Layer, error) {
- return s.getLayer(key, true)
- }
- func (s *snapshotter) getLayer(key string, withCommitted bool) (layer.Layer, error) {
- s.mu.Lock()
- l, ok := s.refs[key]
- if !ok {
- id, ok := s.chainID(key)
- if !ok {
- if !withCommitted {
- s.mu.Unlock()
- return nil, nil
- }
- if err := s.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte(key))
- if b == nil {
- return nil
- }
- v := b.Get(keyChainID)
- if v != nil {
- id = layer.ChainID(v)
- }
- return nil
- }); err != nil {
- s.mu.Unlock()
- return nil, errors.WithStack(err)
- }
- s.mu.Unlock()
- if id == "" {
- return nil, nil
- }
- return s.getLayer(string(id), withCommitted)
- }
- var err error
- l, err = s.opt.LayerStore.Get(id)
- if err != nil {
- s.mu.Unlock()
- return nil, errors.WithStack(err)
- }
- s.refs[key] = l
- if err := s.db.Update(func(tx *bolt.Tx) error {
- _, err := tx.CreateBucketIfNotExists([]byte(key))
- return errors.WithStack(err)
- }); err != nil {
- s.mu.Unlock()
- return nil, err
- }
- }
- s.mu.Unlock()
- return l, nil
- }
- func (s *snapshotter) getGraphDriverID(key string) (string, bool) {
- var gdID string
- if err := s.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte(key))
- if b == nil {
- return errors.Wrapf(cerrdefs.ErrNotFound, "key %s", key)
- }
- v := b.Get(keyCommitted)
- if v != nil {
- gdID = string(v)
- }
- return nil
- }); err != nil || gdID == "" {
- return key, false
- }
- return gdID, true
- }
- func (s *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) {
- inf := snapshots.Info{
- Kind: snapshots.KindActive,
- }
- l, err := s.getLayer(key, false)
- if err != nil {
- return snapshots.Info{}, err
- }
- if l != nil {
- if p := l.Parent(); p != nil {
- inf.Parent = p.ChainID().String()
- }
- inf.Kind = snapshots.KindCommitted
- inf.Name = key
- return inf, nil
- }
- l, err = s.getLayer(key, true)
- if err != nil {
- return snapshots.Info{}, err
- }
- id, committed := s.getGraphDriverID(key)
- if committed {
- inf.Kind = snapshots.KindCommitted
- }
- if err := s.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte(id))
- if b == nil && l == nil {
- return errors.Wrapf(cerrdefs.ErrNotFound, "snapshot %s", id)
- }
- inf.Name = key
- if b != nil {
- v := b.Get(keyParent)
- if v != nil {
- inf.Parent = string(v)
- return nil
- }
- }
- if l != nil {
- if p := l.Parent(); p != nil {
- inf.Parent = p.ChainID().String()
- }
- inf.Kind = snapshots.KindCommitted
- }
- return nil
- }); err != nil {
- return snapshots.Info{}, err
- }
- return inf, nil
- }
- func (s *snapshotter) Mounts(ctx context.Context, key string) (snapshot.Mountable, error) {
- l, err := s.getLayer(key, true)
- if err != nil {
- return nil, err
- }
- if l != nil {
- id := identity.NewID()
- var rwlayer layer.RWLayer
- return &mountable{
- idmap: s.opt.IdentityMapping,
- acquire: func() ([]mount.Mount, func() error, error) {
- rwlayer, err = s.opt.LayerStore.CreateRWLayer(id, l.ChainID(), nil)
- if err != nil {
- return nil, nil, err
- }
- rootfs, err := rwlayer.Mount("")
- if err != nil {
- return nil, nil, err
- }
- return []mount.Mount{{
- Source: rootfs,
- Type: "bind",
- Options: []string{"rbind"},
- }}, func() error {
- _, err := s.opt.LayerStore.ReleaseRWLayer(rwlayer)
- return err
- }, nil
- },
- }, nil
- }
- id, _ := s.getGraphDriverID(key)
- return &mountable{
- idmap: s.opt.IdentityMapping,
- acquire: func() ([]mount.Mount, func() error, error) {
- rootfs, err := s.opt.GraphDriver.Get(id, "")
- if err != nil {
- return nil, nil, err
- }
- return []mount.Mount{{
- Source: rootfs,
- Type: "bind",
- Options: []string{"rbind"},
- }}, func() error {
- return s.opt.GraphDriver.Put(id)
- }, nil
- },
- }, nil
- }
- func (s *snapshotter) Remove(ctx context.Context, key string) error {
- return errors.Errorf("calling snapshot.remove is forbidden")
- }
- func (s *snapshotter) remove(ctx context.Context, key string) error {
- l, err := s.getLayer(key, true)
- if err != nil {
- return err
- }
- id, _ := s.getGraphDriverID(key)
- var found bool
- var alreadyCommitted bool
- if err := s.db.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte(key))
- found = b != nil
- if b != nil {
- if b.Get(keyIsCommitted) != nil {
- alreadyCommitted = true
- return nil
- }
- }
- if found {
- tx.DeleteBucket([]byte(key))
- if id != key {
- tx.DeleteBucket([]byte(id))
- }
- }
- return nil
- }); err != nil {
- return err
- }
- if alreadyCommitted {
- return nil
- }
- if l != nil {
- s.mu.Lock()
- delete(s.refs, key)
- s.mu.Unlock()
- _, err := s.opt.LayerStore.Release(l)
- return err
- }
- if !found { // this happens when removing views
- return nil
- }
- return s.opt.GraphDriver.Remove(id)
- }
- func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error {
- return s.db.Update(func(tx *bolt.Tx) error {
- b, err := tx.CreateBucketIfNotExists([]byte(name))
- if err != nil {
- return err
- }
- if err := b.Put(keyCommitted, []byte(key)); err != nil {
- return err
- }
- b, err = tx.CreateBucketIfNotExists([]byte(key))
- if err != nil {
- return err
- }
- return b.Put(keyIsCommitted, []byte{})
- })
- }
- func (s *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) (snapshot.Mountable, error) {
- return s.Mounts(ctx, parent)
- }
- func (s *snapshotter) Walk(context.Context, snapshots.WalkFunc, ...string) error {
- return nil
- }
- func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
- // not implemented
- return s.Stat(ctx, info.Name)
- }
- func (s *snapshotter) Usage(ctx context.Context, key string) (us snapshots.Usage, retErr error) {
- usage := snapshots.Usage{}
- if l, err := s.getLayer(key, true); err != nil {
- return usage, err
- } else if l != nil {
- usage.Size = l.DiffSize()
- return usage, nil
- }
- size := int64(-1)
- if err := s.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte(key))
- if b == nil {
- return nil
- }
- v := b.Get(keySize)
- if v != nil {
- s, err := strconv.Atoi(string(v))
- if err != nil {
- return err
- }
- size = int64(s)
- }
- return nil
- }); err != nil {
- return usage, err
- }
- if size != -1 {
- usage.Size = size
- return usage, nil
- }
- id, _ := s.getGraphDriverID(key)
- info, err := s.Stat(ctx, key)
- if err != nil {
- return usage, err
- }
- var parent string
- if info.Parent != "" {
- if l, err := s.getLayer(info.Parent, false); err != nil {
- return usage, err
- } else if l != nil {
- parent, err = getGraphID(l)
- if err != nil {
- return usage, err
- }
- } else {
- parent, _ = s.getGraphDriverID(info.Parent)
- }
- }
- diffSize, err := s.opt.GraphDriver.DiffSize(id, parent)
- if err != nil {
- return usage, err
- }
- if err := s.db.Update(func(tx *bolt.Tx) error {
- b, err := tx.CreateBucketIfNotExists([]byte(key))
- if err != nil {
- return err
- }
- return b.Put(keySize, []byte(strconv.Itoa(int(diffSize))))
- }); err != nil {
- return usage, err
- }
- usage.Size = diffSize
- return usage, nil
- }
- func (s *snapshotter) Close() error {
- return s.db.Close()
- }
- type mountable struct {
- mu sync.Mutex
- mounts []mount.Mount
- acquire func() ([]mount.Mount, func() error, error)
- release func() error
- refCount int
- idmap idtools.IdentityMapping
- }
- func (m *mountable) Mount() ([]mount.Mount, func() error, error) {
- m.mu.Lock()
- defer m.mu.Unlock()
- if m.mounts != nil {
- m.refCount++
- return m.mounts, m.releaseMount, nil
- }
- mounts, release, err := m.acquire()
- if err != nil {
- return nil, nil, err
- }
- m.mounts = mounts
- m.release = release
- m.refCount = 1
- return m.mounts, m.releaseMount, nil
- }
- func (m *mountable) releaseMount() error {
- m.mu.Lock()
- defer m.mu.Unlock()
- if m.refCount > 1 {
- m.refCount--
- return nil
- }
- m.refCount = 0
- if m.release == nil {
- return nil
- }
- m.mounts = nil
- defer func() {
- m.release = nil
- }()
- return m.release()
- }
- func (m *mountable) IdentityMapping() *idtools.IdentityMapping {
- // Returning a non-nil but empty *IdentityMapping breaks BuildKit:
- // https://github.com/moby/moby/pull/39444
- if m.idmap.Empty() {
- return nil
- }
- return &m.idmap
- }
|