github.com/moby/docker@v26.1.3+incompatible/builder/builder-next/adapters/snapshot/snapshot.go (about)

     1  package snapshot
     2  
     3  import (
     4  	"context"
     5  	"path/filepath"
     6  	"strconv"
     7  	"strings"
     8  	"sync"
     9  
    10  	cerrdefs "github.com/containerd/containerd/errdefs"
    11  	"github.com/containerd/containerd/leases"
    12  	"github.com/containerd/containerd/mount"
    13  	"github.com/containerd/containerd/snapshots"
    14  	"github.com/docker/docker/daemon/graphdriver"
    15  	"github.com/docker/docker/layer"
    16  	"github.com/docker/docker/pkg/idtools"
    17  	"github.com/moby/buildkit/identity"
    18  	"github.com/moby/buildkit/snapshot"
    19  	"github.com/moby/buildkit/util/leaseutil"
    20  	"github.com/moby/locker"
    21  	"github.com/opencontainers/go-digest"
    22  	"github.com/pkg/errors"
    23  	bolt "go.etcd.io/bbolt"
    24  )
    25  
    26  var (
    27  	keyParent      = []byte("parent")
    28  	keyCommitted   = []byte("committed")
    29  	keyIsCommitted = []byte("iscommitted")
    30  	keyChainID     = []byte("chainid")
    31  	keySize        = []byte("size")
    32  )
    33  
    34  // Opt defines options for creating the snapshotter
    35  type Opt struct {
    36  	GraphDriver     graphdriver.Driver
    37  	LayerStore      layer.Store
    38  	Root            string
    39  	IdentityMapping idtools.IdentityMapping
    40  }
    41  
    42  type graphIDRegistrar interface {
    43  	RegisterByGraphID(string, layer.ChainID, layer.DiffID, string, int64) (layer.Layer, error)
    44  	Release(layer.Layer) ([]layer.Metadata, error)
    45  	checksumCalculator
    46  }
    47  
    48  type checksumCalculator interface {
    49  	ChecksumForGraphID(id, parent, newTarDataPath string) (diffID layer.DiffID, size int64, err error)
    50  }
    51  
    52  type snapshotter struct {
    53  	opt Opt
    54  
    55  	refs              map[string]layer.Layer
    56  	db                *bolt.DB
    57  	mu                sync.Mutex
    58  	reg               graphIDRegistrar
    59  	layerCreateLocker *locker.Locker
    60  }
    61  
    62  // NewSnapshotter creates a new snapshotter
    63  func NewSnapshotter(opt Opt, prevLM leases.Manager, ns string) (snapshot.Snapshotter, *leaseutil.Manager, error) {
    64  	dbPath := filepath.Join(opt.Root, "snapshots.db")
    65  	db, err := bolt.Open(dbPath, 0o600, nil)
    66  	if err != nil {
    67  		return nil, nil, errors.Wrapf(err, "failed to open database file %s", dbPath)
    68  	}
    69  
    70  	reg, ok := opt.LayerStore.(graphIDRegistrar)
    71  	if !ok {
    72  		return nil, nil, errors.Errorf("layerstore doesn't support graphID registration")
    73  	}
    74  
    75  	s := &snapshotter{
    76  		opt:               opt,
    77  		db:                db,
    78  		refs:              map[string]layer.Layer{},
    79  		reg:               reg,
    80  		layerCreateLocker: locker.New(),
    81  	}
    82  
    83  	slm := newLeaseManager(s, prevLM)
    84  	lm := leaseutil.WithNamespace(slm, ns)
    85  
    86  	ll, err := lm.List(context.TODO())
    87  	if err != nil {
    88  		return nil, nil, err
    89  	}
    90  	for _, l := range ll {
    91  		rr, err := lm.ListResources(context.TODO(), l)
    92  		if err != nil {
    93  			return nil, nil, err
    94  		}
    95  		for _, r := range rr {
    96  			if r.Type == "snapshots/default" {
    97  				slm.addRef(l.ID, r.ID)
    98  			}
    99  		}
   100  	}
   101  
   102  	return s, lm, nil
   103  }
   104  
   105  func (s *snapshotter) Name() string {
   106  	return "default"
   107  }
   108  
   109  func (s *snapshotter) IdentityMapping() *idtools.IdentityMapping {
   110  	// Returning a non-nil but empty *IdentityMapping breaks BuildKit:
   111  	// https://github.com/moby/moby/pull/39444
   112  	if s.opt.IdentityMapping.Empty() {
   113  		return nil
   114  	}
   115  	return &s.opt.IdentityMapping
   116  }
   117  
   118  func (s *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) error {
   119  	origParent := parent
   120  	if parent != "" {
   121  		if l, err := s.getLayer(parent, false); err != nil {
   122  			return errors.Wrapf(err, "failed to get parent layer %s", parent)
   123  		} else if l != nil {
   124  			parent, err = getGraphID(l)
   125  			if err != nil {
   126  				return errors.Wrapf(err, "failed to get parent graphid %s", l.ChainID())
   127  			}
   128  		} else {
   129  			parent, _ = s.getGraphDriverID(parent)
   130  		}
   131  	}
   132  	if err := s.opt.GraphDriver.Create(key, parent, nil); err != nil {
   133  		return err
   134  	}
   135  	return s.db.Update(func(tx *bolt.Tx) error {
   136  		b, err := tx.CreateBucketIfNotExists([]byte(key))
   137  		if err != nil {
   138  			return err
   139  		}
   140  		return b.Put(keyParent, []byte(origParent))
   141  	})
   142  }
   143  
   144  func (s *snapshotter) chainID(key string) (layer.ChainID, bool) {
   145  	if strings.HasPrefix(key, "sha256:") {
   146  		dgst, err := digest.Parse(key)
   147  		if err != nil {
   148  			return "", false
   149  		}
   150  		return layer.ChainID(dgst), true
   151  	}
   152  	return "", false
   153  }
   154  
   155  func (s *snapshotter) GetLayer(key string) (layer.Layer, error) {
   156  	return s.getLayer(key, true)
   157  }
   158  
   159  func (s *snapshotter) getLayer(key string, withCommitted bool) (layer.Layer, error) {
   160  	s.mu.Lock()
   161  	l, ok := s.refs[key]
   162  	if !ok {
   163  		id, ok := s.chainID(key)
   164  		if !ok {
   165  			if !withCommitted {
   166  				s.mu.Unlock()
   167  				return nil, nil
   168  			}
   169  			if err := s.db.View(func(tx *bolt.Tx) error {
   170  				b := tx.Bucket([]byte(key))
   171  				if b == nil {
   172  					return nil
   173  				}
   174  				v := b.Get(keyChainID)
   175  				if v != nil {
   176  					id = layer.ChainID(v)
   177  				}
   178  				return nil
   179  			}); err != nil {
   180  				s.mu.Unlock()
   181  				return nil, errors.WithStack(err)
   182  			}
   183  			s.mu.Unlock()
   184  			if id == "" {
   185  				return nil, nil
   186  			}
   187  			return s.getLayer(string(id), withCommitted)
   188  		}
   189  		var err error
   190  		l, err = s.opt.LayerStore.Get(id)
   191  		if err != nil {
   192  			s.mu.Unlock()
   193  			return nil, errors.WithStack(err)
   194  		}
   195  		s.refs[key] = l
   196  		if err := s.db.Update(func(tx *bolt.Tx) error {
   197  			_, err := tx.CreateBucketIfNotExists([]byte(key))
   198  			return errors.WithStack(err)
   199  		}); err != nil {
   200  			s.mu.Unlock()
   201  			return nil, err
   202  		}
   203  	}
   204  	s.mu.Unlock()
   205  
   206  	return l, nil
   207  }
   208  
   209  func (s *snapshotter) getGraphDriverID(key string) (string, bool) {
   210  	var gdID string
   211  	if err := s.db.View(func(tx *bolt.Tx) error {
   212  		b := tx.Bucket([]byte(key))
   213  		if b == nil {
   214  			return errors.Wrapf(cerrdefs.ErrNotFound, "key %s", key)
   215  		}
   216  		v := b.Get(keyCommitted)
   217  		if v != nil {
   218  			gdID = string(v)
   219  		}
   220  		return nil
   221  	}); err != nil || gdID == "" {
   222  		return key, false
   223  	}
   224  	return gdID, true
   225  }
   226  
   227  func (s *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) {
   228  	inf := snapshots.Info{
   229  		Kind: snapshots.KindActive,
   230  	}
   231  
   232  	l, err := s.getLayer(key, false)
   233  	if err != nil {
   234  		return snapshots.Info{}, err
   235  	}
   236  	if l != nil {
   237  		if p := l.Parent(); p != nil {
   238  			inf.Parent = p.ChainID().String()
   239  		}
   240  		inf.Kind = snapshots.KindCommitted
   241  		inf.Name = key
   242  		return inf, nil
   243  	}
   244  
   245  	l, err = s.getLayer(key, true)
   246  	if err != nil {
   247  		return snapshots.Info{}, err
   248  	}
   249  
   250  	id, committed := s.getGraphDriverID(key)
   251  	if committed {
   252  		inf.Kind = snapshots.KindCommitted
   253  	}
   254  
   255  	if err := s.db.View(func(tx *bolt.Tx) error {
   256  		b := tx.Bucket([]byte(id))
   257  		if b == nil && l == nil {
   258  			return errors.Wrapf(cerrdefs.ErrNotFound, "snapshot %s", id)
   259  		}
   260  		inf.Name = key
   261  		if b != nil {
   262  			v := b.Get(keyParent)
   263  			if v != nil {
   264  				inf.Parent = string(v)
   265  				return nil
   266  			}
   267  		}
   268  		if l != nil {
   269  			if p := l.Parent(); p != nil {
   270  				inf.Parent = p.ChainID().String()
   271  			}
   272  			inf.Kind = snapshots.KindCommitted
   273  		}
   274  		return nil
   275  	}); err != nil {
   276  		return snapshots.Info{}, err
   277  	}
   278  	return inf, nil
   279  }
   280  
   281  func (s *snapshotter) Mounts(ctx context.Context, key string) (snapshot.Mountable, error) {
   282  	l, err := s.getLayer(key, true)
   283  	if err != nil {
   284  		return nil, err
   285  	}
   286  	if l != nil {
   287  		id := identity.NewID()
   288  		var rwlayer layer.RWLayer
   289  		return &mountable{
   290  			idmap: s.opt.IdentityMapping,
   291  			acquire: func() ([]mount.Mount, func() error, error) {
   292  				rwlayer, err = s.opt.LayerStore.CreateRWLayer(id, l.ChainID(), nil)
   293  				if err != nil {
   294  					return nil, nil, err
   295  				}
   296  				rootfs, err := rwlayer.Mount("")
   297  				if err != nil {
   298  					return nil, nil, err
   299  				}
   300  				return []mount.Mount{{
   301  						Source:  rootfs,
   302  						Type:    "bind",
   303  						Options: []string{"rbind"},
   304  					}}, func() error {
   305  						_, err := s.opt.LayerStore.ReleaseRWLayer(rwlayer)
   306  						return err
   307  					}, nil
   308  			},
   309  		}, nil
   310  	}
   311  
   312  	id, _ := s.getGraphDriverID(key)
   313  
   314  	return &mountable{
   315  		idmap: s.opt.IdentityMapping,
   316  		acquire: func() ([]mount.Mount, func() error, error) {
   317  			rootfs, err := s.opt.GraphDriver.Get(id, "")
   318  			if err != nil {
   319  				return nil, nil, err
   320  			}
   321  			return []mount.Mount{{
   322  					Source:  rootfs,
   323  					Type:    "bind",
   324  					Options: []string{"rbind"},
   325  				}}, func() error {
   326  					return s.opt.GraphDriver.Put(id)
   327  				}, nil
   328  		},
   329  	}, nil
   330  }
   331  
   332  func (s *snapshotter) Remove(ctx context.Context, key string) error {
   333  	return errors.Errorf("calling snapshot.remove is forbidden")
   334  }
   335  
   336  func (s *snapshotter) remove(ctx context.Context, key string) error {
   337  	l, err := s.getLayer(key, true)
   338  	if err != nil {
   339  		return err
   340  	}
   341  
   342  	id, _ := s.getGraphDriverID(key)
   343  
   344  	var found bool
   345  	var alreadyCommitted bool
   346  	if err := s.db.Update(func(tx *bolt.Tx) error {
   347  		b := tx.Bucket([]byte(key))
   348  		found = b != nil
   349  
   350  		if b != nil {
   351  			if b.Get(keyIsCommitted) != nil {
   352  				alreadyCommitted = true
   353  				return nil
   354  			}
   355  		}
   356  		if found {
   357  			tx.DeleteBucket([]byte(key))
   358  			if id != key {
   359  				tx.DeleteBucket([]byte(id))
   360  			}
   361  		}
   362  		return nil
   363  	}); err != nil {
   364  		return err
   365  	}
   366  
   367  	if alreadyCommitted {
   368  		return nil
   369  	}
   370  
   371  	if l != nil {
   372  		s.mu.Lock()
   373  		delete(s.refs, key)
   374  		s.mu.Unlock()
   375  		_, err := s.opt.LayerStore.Release(l)
   376  		return err
   377  	}
   378  
   379  	if !found { // this happens when removing views
   380  		return nil
   381  	}
   382  
   383  	return s.opt.GraphDriver.Remove(id)
   384  }
   385  
   386  func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error {
   387  	return s.db.Update(func(tx *bolt.Tx) error {
   388  		b, err := tx.CreateBucketIfNotExists([]byte(name))
   389  		if err != nil {
   390  			return err
   391  		}
   392  		if err := b.Put(keyCommitted, []byte(key)); err != nil {
   393  			return err
   394  		}
   395  		b, err = tx.CreateBucketIfNotExists([]byte(key))
   396  		if err != nil {
   397  			return err
   398  		}
   399  		return b.Put(keyIsCommitted, []byte{})
   400  	})
   401  }
   402  
   403  func (s *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) (snapshot.Mountable, error) {
   404  	return s.Mounts(ctx, parent)
   405  }
   406  
   407  func (s *snapshotter) Walk(context.Context, snapshots.WalkFunc, ...string) error {
   408  	return nil
   409  }
   410  
   411  func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
   412  	// not implemented
   413  	return s.Stat(ctx, info.Name)
   414  }
   415  
   416  func (s *snapshotter) Usage(ctx context.Context, key string) (us snapshots.Usage, retErr error) {
   417  	usage := snapshots.Usage{}
   418  	if l, err := s.getLayer(key, true); err != nil {
   419  		return usage, err
   420  	} else if l != nil {
   421  		usage.Size = l.DiffSize()
   422  		return usage, nil
   423  	}
   424  
   425  	size := int64(-1)
   426  	if err := s.db.View(func(tx *bolt.Tx) error {
   427  		b := tx.Bucket([]byte(key))
   428  		if b == nil {
   429  			return nil
   430  		}
   431  		v := b.Get(keySize)
   432  		if v != nil {
   433  			s, err := strconv.Atoi(string(v))
   434  			if err != nil {
   435  				return err
   436  			}
   437  			size = int64(s)
   438  		}
   439  		return nil
   440  	}); err != nil {
   441  		return usage, err
   442  	}
   443  
   444  	if size != -1 {
   445  		usage.Size = size
   446  		return usage, nil
   447  	}
   448  
   449  	id, _ := s.getGraphDriverID(key)
   450  
   451  	info, err := s.Stat(ctx, key)
   452  	if err != nil {
   453  		return usage, err
   454  	}
   455  	var parent string
   456  	if info.Parent != "" {
   457  		if l, err := s.getLayer(info.Parent, false); err != nil {
   458  			return usage, err
   459  		} else if l != nil {
   460  			parent, err = getGraphID(l)
   461  			if err != nil {
   462  				return usage, err
   463  			}
   464  		} else {
   465  			parent, _ = s.getGraphDriverID(info.Parent)
   466  		}
   467  	}
   468  
   469  	diffSize, err := s.opt.GraphDriver.DiffSize(id, parent)
   470  	if err != nil {
   471  		return usage, err
   472  	}
   473  
   474  	if err := s.db.Update(func(tx *bolt.Tx) error {
   475  		b, err := tx.CreateBucketIfNotExists([]byte(key))
   476  		if err != nil {
   477  			return err
   478  		}
   479  		return b.Put(keySize, []byte(strconv.Itoa(int(diffSize))))
   480  	}); err != nil {
   481  		return usage, err
   482  	}
   483  	usage.Size = diffSize
   484  	return usage, nil
   485  }
   486  
   487  func (s *snapshotter) Close() error {
   488  	return s.db.Close()
   489  }
   490  
   491  type mountable struct {
   492  	mu       sync.Mutex
   493  	mounts   []mount.Mount
   494  	acquire  func() ([]mount.Mount, func() error, error)
   495  	release  func() error
   496  	refCount int
   497  	idmap    idtools.IdentityMapping
   498  }
   499  
   500  func (m *mountable) Mount() ([]mount.Mount, func() error, error) {
   501  	m.mu.Lock()
   502  	defer m.mu.Unlock()
   503  
   504  	if m.mounts != nil {
   505  		m.refCount++
   506  		return m.mounts, m.releaseMount, nil
   507  	}
   508  
   509  	mounts, release, err := m.acquire()
   510  	if err != nil {
   511  		return nil, nil, err
   512  	}
   513  	m.mounts = mounts
   514  	m.release = release
   515  	m.refCount = 1
   516  
   517  	return m.mounts, m.releaseMount, nil
   518  }
   519  
   520  func (m *mountable) releaseMount() error {
   521  	m.mu.Lock()
   522  	defer m.mu.Unlock()
   523  
   524  	if m.refCount > 1 {
   525  		m.refCount--
   526  		return nil
   527  	}
   528  
   529  	m.refCount = 0
   530  	if m.release == nil {
   531  		return nil
   532  	}
   533  
   534  	m.mounts = nil
   535  	defer func() {
   536  		m.release = nil
   537  	}()
   538  	return m.release()
   539  }
   540  
   541  func (m *mountable) IdentityMapping() *idtools.IdentityMapping {
   542  	// Returning a non-nil but empty *IdentityMapping breaks BuildKit:
   543  	// https://github.com/moby/moby/pull/39444
   544  	if m.idmap.Empty() {
   545  		return nil
   546  	}
   547  	return &m.idmap
   548  }