github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/snapshots/storage/bolt.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package storage
    18  
    19  import (
    20  	"context"
    21  	"encoding/binary"
    22  	"fmt"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/containerd/containerd/errdefs"
    27  	"github.com/containerd/containerd/filters"
    28  	"github.com/containerd/containerd/metadata/boltutil"
    29  	"github.com/containerd/containerd/snapshots"
    30  	"github.com/pkg/errors"
    31  	bolt "go.etcd.io/bbolt"
    32  )
    33  
    34  var (
    35  	bucketKeyStorageVersion = []byte("v1")
    36  	bucketKeySnapshot       = []byte("snapshots")
    37  	bucketKeyParents        = []byte("parents")
    38  
    39  	bucketKeyID     = []byte("id")
    40  	bucketKeyParent = []byte("parent")
    41  	bucketKeyKind   = []byte("kind")
    42  	bucketKeyInodes = []byte("inodes")
    43  	bucketKeySize   = []byte("size")
    44  
    45  	// ErrNoTransaction is returned when an operation is attempted with
    46  	// a context which is not inside of a transaction.
    47  	ErrNoTransaction = errors.New("no transaction in context")
    48  )
    49  
    50  // parentKey returns a composite key of the parent and child identifiers. The
    51  // parts of the key are separated by a zero byte.
    52  func parentKey(parent, child uint64) []byte {
    53  	b := make([]byte, binary.Size([]uint64{parent, child})+1)
    54  	i := binary.PutUvarint(b, parent)
    55  	j := binary.PutUvarint(b[i+1:], child)
    56  	return b[0 : i+j+1]
    57  }
    58  
    59  // parentPrefixKey returns the parent part of the composite key with the
    60  // zero byte separator.
    61  func parentPrefixKey(parent uint64) []byte {
    62  	b := make([]byte, binary.Size(parent)+1)
    63  	i := binary.PutUvarint(b, parent)
    64  	return b[0 : i+1]
    65  }
    66  
    67  // getParentPrefix returns the first part of the composite key which
    68  // represents the parent identifier.
    69  func getParentPrefix(b []byte) uint64 {
    70  	parent, _ := binary.Uvarint(b)
    71  	return parent
    72  }
    73  
    74  // GetInfo returns the snapshot Info directly from the metadata. Requires a
    75  // context with a storage transaction.
    76  func GetInfo(ctx context.Context, key string) (string, snapshots.Info, snapshots.Usage, error) {
    77  	var (
    78  		id uint64
    79  		su snapshots.Usage
    80  		si = snapshots.Info{
    81  			Name: key,
    82  		}
    83  	)
    84  	err := withSnapshotBucket(ctx, key, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
    85  		getUsage(bkt, &su)
    86  		return readSnapshot(bkt, &id, &si)
    87  	})
    88  	if err != nil {
    89  		return "", snapshots.Info{}, snapshots.Usage{}, err
    90  	}
    91  
    92  	return fmt.Sprintf("%d", id), si, su, nil
    93  }
    94  
    95  // UpdateInfo updates an existing snapshot info's data
    96  func UpdateInfo(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
    97  	updated := snapshots.Info{
    98  		Name: info.Name,
    99  	}
   100  	err := withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
   101  		sbkt := bkt.Bucket([]byte(info.Name))
   102  		if sbkt == nil {
   103  			return errors.Wrap(errdefs.ErrNotFound, "snapshot does not exist")
   104  		}
   105  		if err := readSnapshot(sbkt, nil, &updated); err != nil {
   106  			return err
   107  		}
   108  
   109  		if len(fieldpaths) > 0 {
   110  			for _, path := range fieldpaths {
   111  				if strings.HasPrefix(path, "labels.") {
   112  					if updated.Labels == nil {
   113  						updated.Labels = map[string]string{}
   114  					}
   115  
   116  					key := strings.TrimPrefix(path, "labels.")
   117  					updated.Labels[key] = info.Labels[key]
   118  					continue
   119  				}
   120  
   121  				switch path {
   122  				case "labels":
   123  					updated.Labels = info.Labels
   124  				default:
   125  					return errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on snapshot %q", path, info.Name)
   126  				}
   127  			}
   128  		} else {
   129  			// Set mutable fields
   130  			updated.Labels = info.Labels
   131  		}
   132  		updated.Updated = time.Now().UTC()
   133  		if err := boltutil.WriteTimestamps(sbkt, updated.Created, updated.Updated); err != nil {
   134  			return err
   135  		}
   136  
   137  		return boltutil.WriteLabels(sbkt, updated.Labels)
   138  	})
   139  	if err != nil {
   140  		return snapshots.Info{}, err
   141  	}
   142  	return updated, nil
   143  }
   144  
   145  // WalkInfo iterates through all metadata Info for the stored snapshots and
   146  // calls the provided function for each. Requires a context with a storage
   147  // transaction.
   148  func WalkInfo(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error {
   149  	filter, err := filters.ParseAll(fs...)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	// TODO: allow indexes (name, parent, specific labels)
   154  	return withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
   155  		return bkt.ForEach(func(k, v []byte) error {
   156  			// skip non buckets
   157  			if v != nil {
   158  				return nil
   159  			}
   160  			var (
   161  				sbkt = bkt.Bucket(k)
   162  				si   = snapshots.Info{
   163  					Name: string(k),
   164  				}
   165  			)
   166  			if err := readSnapshot(sbkt, nil, &si); err != nil {
   167  				return err
   168  			}
   169  			if !filter.Match(adaptSnapshot(si)) {
   170  				return nil
   171  			}
   172  
   173  			return fn(ctx, si)
   174  		})
   175  	})
   176  }
   177  
   178  // GetSnapshot returns the metadata for the active or view snapshot transaction
   179  // referenced by the given key. Requires a context with a storage transaction.
   180  func GetSnapshot(ctx context.Context, key string) (s Snapshot, err error) {
   181  	err = withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
   182  		sbkt := bkt.Bucket([]byte(key))
   183  		if sbkt == nil {
   184  			return errors.Wrap(errdefs.ErrNotFound, "snapshot does not exist")
   185  		}
   186  
   187  		s.ID = fmt.Sprintf("%d", readID(sbkt))
   188  		s.Kind = readKind(sbkt)
   189  
   190  		if s.Kind != snapshots.KindActive && s.Kind != snapshots.KindView {
   191  			return errors.Wrapf(errdefs.ErrFailedPrecondition, "requested snapshot %v not active or view", key)
   192  		}
   193  
   194  		if parentKey := sbkt.Get(bucketKeyParent); len(parentKey) > 0 {
   195  			spbkt := bkt.Bucket(parentKey)
   196  			if spbkt == nil {
   197  				return errors.Wrap(errdefs.ErrNotFound, "parent does not exist")
   198  			}
   199  
   200  			s.ParentIDs, err = parents(bkt, spbkt, readID(spbkt))
   201  			if err != nil {
   202  				return errors.Wrap(err, "failed to get parent chain")
   203  			}
   204  		}
   205  		return nil
   206  	})
   207  	if err != nil {
   208  		return Snapshot{}, err
   209  	}
   210  
   211  	return
   212  }
   213  
   214  // CreateSnapshot inserts a record for an active or view snapshot with the provided parent.
   215  func CreateSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts ...snapshots.Opt) (s Snapshot, err error) {
   216  	switch kind {
   217  	case snapshots.KindActive, snapshots.KindView:
   218  	default:
   219  		return Snapshot{}, errors.Wrapf(errdefs.ErrInvalidArgument, "snapshot type %v invalid; only snapshots of type Active or View can be created", kind)
   220  	}
   221  	var base snapshots.Info
   222  	for _, opt := range opts {
   223  		if err := opt(&base); err != nil {
   224  			return Snapshot{}, err
   225  		}
   226  	}
   227  
   228  	err = createBucketIfNotExists(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
   229  		var (
   230  			spbkt *bolt.Bucket
   231  		)
   232  		if parent != "" {
   233  			spbkt = bkt.Bucket([]byte(parent))
   234  			if spbkt == nil {
   235  				return errors.Wrapf(errdefs.ErrNotFound, "missing parent %q bucket", parent)
   236  			}
   237  
   238  			if readKind(spbkt) != snapshots.KindCommitted {
   239  				return errors.Wrapf(errdefs.ErrInvalidArgument, "parent %q is not committed snapshot", parent)
   240  			}
   241  		}
   242  		sbkt, err := bkt.CreateBucket([]byte(key))
   243  		if err != nil {
   244  			if err == bolt.ErrBucketExists {
   245  				err = errors.Wrapf(errdefs.ErrAlreadyExists, "snapshot %v", key)
   246  			}
   247  			return err
   248  		}
   249  
   250  		id, err := bkt.NextSequence()
   251  		if err != nil {
   252  			return errors.Wrapf(err, "unable to get identifier for snapshot %q", key)
   253  		}
   254  
   255  		t := time.Now().UTC()
   256  		si := snapshots.Info{
   257  			Parent:  parent,
   258  			Kind:    kind,
   259  			Labels:  base.Labels,
   260  			Created: t,
   261  			Updated: t,
   262  		}
   263  		if err := putSnapshot(sbkt, id, si); err != nil {
   264  			return err
   265  		}
   266  
   267  		if spbkt != nil {
   268  			pid := readID(spbkt)
   269  
   270  			// Store a backlink from the key to the parent. Store the snapshot name
   271  			// as the value to allow following the backlink to the snapshot value.
   272  			if err := pbkt.Put(parentKey(pid, id), []byte(key)); err != nil {
   273  				return errors.Wrapf(err, "failed to write parent link for snapshot %q", key)
   274  			}
   275  
   276  			s.ParentIDs, err = parents(bkt, spbkt, pid)
   277  			if err != nil {
   278  				return errors.Wrapf(err, "failed to get parent chain for snapshot %q", key)
   279  			}
   280  		}
   281  
   282  		s.ID = fmt.Sprintf("%d", id)
   283  		s.Kind = kind
   284  		return nil
   285  	})
   286  	if err != nil {
   287  		return Snapshot{}, err
   288  	}
   289  
   290  	return
   291  }
   292  
   293  // Remove removes a snapshot from the metastore. The string identifier for the
   294  // snapshot is returned as well as the kind. The provided context must contain a
   295  // writable transaction.
   296  func Remove(ctx context.Context, key string) (string, snapshots.Kind, error) {
   297  	var (
   298  		id uint64
   299  		si snapshots.Info
   300  	)
   301  
   302  	if err := withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
   303  		sbkt := bkt.Bucket([]byte(key))
   304  		if sbkt == nil {
   305  			return errors.Wrapf(errdefs.ErrNotFound, "snapshot %v", key)
   306  		}
   307  
   308  		if err := readSnapshot(sbkt, &id, &si); err != nil {
   309  			return errors.Wrapf(err, "failed to read snapshot %s", key)
   310  		}
   311  
   312  		if pbkt != nil {
   313  			k, _ := pbkt.Cursor().Seek(parentPrefixKey(id))
   314  			if getParentPrefix(k) == id {
   315  				return errors.Wrap(errdefs.ErrFailedPrecondition, "cannot remove snapshot with child")
   316  			}
   317  
   318  			if si.Parent != "" {
   319  				spbkt := bkt.Bucket([]byte(si.Parent))
   320  				if spbkt == nil {
   321  					return errors.Wrapf(errdefs.ErrNotFound, "snapshot %v", key)
   322  				}
   323  
   324  				if err := pbkt.Delete(parentKey(readID(spbkt), id)); err != nil {
   325  					return errors.Wrap(err, "failed to delete parent link")
   326  				}
   327  			}
   328  		}
   329  
   330  		if err := bkt.DeleteBucket([]byte(key)); err != nil {
   331  			return errors.Wrap(err, "failed to delete snapshot")
   332  		}
   333  
   334  		return nil
   335  	}); err != nil {
   336  		return "", 0, err
   337  	}
   338  
   339  	return fmt.Sprintf("%d", id), si.Kind, nil
   340  }
   341  
   342  // CommitActive renames the active snapshot transaction referenced by `key`
   343  // as a committed snapshot referenced by `Name`. The resulting snapshot  will be
   344  // committed and readonly. The `key` reference will no longer be available for
   345  // lookup or removal. The returned string identifier for the committed snapshot
   346  // is the same identifier of the original active snapshot. The provided context
   347  // must contain a writable transaction.
   348  func CommitActive(ctx context.Context, key, name string, usage snapshots.Usage, opts ...snapshots.Opt) (string, error) {
   349  	var (
   350  		id   uint64
   351  		base snapshots.Info
   352  	)
   353  	for _, opt := range opts {
   354  		if err := opt(&base); err != nil {
   355  			return "", err
   356  		}
   357  	}
   358  
   359  	if err := withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
   360  		dbkt, err := bkt.CreateBucket([]byte(name))
   361  		if err != nil {
   362  			if err == bolt.ErrBucketExists {
   363  				err = errdefs.ErrAlreadyExists
   364  			}
   365  			return errors.Wrapf(err, "committed snapshot %v", name)
   366  		}
   367  		sbkt := bkt.Bucket([]byte(key))
   368  		if sbkt == nil {
   369  			return errors.Wrapf(errdefs.ErrNotFound, "failed to get active snapshot %q", key)
   370  		}
   371  
   372  		var si snapshots.Info
   373  		if err := readSnapshot(sbkt, &id, &si); err != nil {
   374  			return errors.Wrapf(err, "failed to read active snapshot %q", key)
   375  		}
   376  
   377  		if si.Kind != snapshots.KindActive {
   378  			return errors.Wrapf(errdefs.ErrFailedPrecondition, "snapshot %q is not active", key)
   379  		}
   380  		si.Kind = snapshots.KindCommitted
   381  		si.Created = time.Now().UTC()
   382  		si.Updated = si.Created
   383  
   384  		// Replace labels, do not inherit
   385  		si.Labels = base.Labels
   386  
   387  		if err := putSnapshot(dbkt, id, si); err != nil {
   388  			return err
   389  		}
   390  		if err := putUsage(dbkt, usage); err != nil {
   391  			return err
   392  		}
   393  		if err := bkt.DeleteBucket([]byte(key)); err != nil {
   394  			return errors.Wrapf(err, "failed to delete active snapshot %q", key)
   395  		}
   396  		if si.Parent != "" {
   397  			spbkt := bkt.Bucket([]byte(si.Parent))
   398  			if spbkt == nil {
   399  				return errors.Wrapf(errdefs.ErrNotFound, "missing parent %q of snapshot %q", si.Parent, key)
   400  			}
   401  			pid := readID(spbkt)
   402  
   403  			// Updates parent back link to use new key
   404  			if err := pbkt.Put(parentKey(pid, id), []byte(name)); err != nil {
   405  				return errors.Wrapf(err, "failed to update parent link %q from %q to %q", pid, key, name)
   406  			}
   407  		}
   408  
   409  		return nil
   410  	}); err != nil {
   411  		return "", err
   412  	}
   413  
   414  	return fmt.Sprintf("%d", id), nil
   415  }
   416  
   417  // IDMap returns all the IDs mapped to their key
   418  func IDMap(ctx context.Context) (map[string]string, error) {
   419  	m := map[string]string{}
   420  	if err := withBucket(ctx, func(ctx context.Context, bkt, _ *bolt.Bucket) error {
   421  		return bkt.ForEach(func(k, v []byte) error {
   422  			// skip non buckets
   423  			if v != nil {
   424  				return nil
   425  			}
   426  			id := readID(bkt.Bucket(k))
   427  			m[fmt.Sprintf("%d", id)] = string(k)
   428  			return nil
   429  		})
   430  	}); err != nil {
   431  		return nil, err
   432  	}
   433  
   434  	return m, nil
   435  }
   436  
   437  func withSnapshotBucket(ctx context.Context, key string, fn func(context.Context, *bolt.Bucket, *bolt.Bucket) error) error {
   438  	tx, ok := ctx.Value(transactionKey{}).(*bolt.Tx)
   439  	if !ok {
   440  		return ErrNoTransaction
   441  	}
   442  	vbkt := tx.Bucket(bucketKeyStorageVersion)
   443  	if vbkt == nil {
   444  		return errors.Wrap(errdefs.ErrNotFound, "bucket does not exist")
   445  	}
   446  	bkt := vbkt.Bucket(bucketKeySnapshot)
   447  	if bkt == nil {
   448  		return errors.Wrap(errdefs.ErrNotFound, "snapshots bucket does not exist")
   449  	}
   450  	bkt = bkt.Bucket([]byte(key))
   451  	if bkt == nil {
   452  		return errors.Wrap(errdefs.ErrNotFound, "snapshot does not exist")
   453  	}
   454  
   455  	return fn(ctx, bkt, vbkt.Bucket(bucketKeyParents))
   456  }
   457  
   458  func withBucket(ctx context.Context, fn func(context.Context, *bolt.Bucket, *bolt.Bucket) error) error {
   459  	tx, ok := ctx.Value(transactionKey{}).(*bolt.Tx)
   460  	if !ok {
   461  		return ErrNoTransaction
   462  	}
   463  	bkt := tx.Bucket(bucketKeyStorageVersion)
   464  	if bkt == nil {
   465  		return errors.Wrap(errdefs.ErrNotFound, "bucket does not exist")
   466  	}
   467  	return fn(ctx, bkt.Bucket(bucketKeySnapshot), bkt.Bucket(bucketKeyParents))
   468  }
   469  
   470  func createBucketIfNotExists(ctx context.Context, fn func(context.Context, *bolt.Bucket, *bolt.Bucket) error) error {
   471  	tx, ok := ctx.Value(transactionKey{}).(*bolt.Tx)
   472  	if !ok {
   473  		return ErrNoTransaction
   474  	}
   475  
   476  	bkt, err := tx.CreateBucketIfNotExists(bucketKeyStorageVersion)
   477  	if err != nil {
   478  		return errors.Wrap(err, "failed to create version bucket")
   479  	}
   480  	sbkt, err := bkt.CreateBucketIfNotExists(bucketKeySnapshot)
   481  	if err != nil {
   482  		return errors.Wrap(err, "failed to create snapshots bucket")
   483  	}
   484  	pbkt, err := bkt.CreateBucketIfNotExists(bucketKeyParents)
   485  	if err != nil {
   486  		return errors.Wrap(err, "failed to create parents bucket")
   487  	}
   488  	return fn(ctx, sbkt, pbkt)
   489  }
   490  
   491  func parents(bkt, pbkt *bolt.Bucket, parent uint64) (parents []string, err error) {
   492  	for {
   493  		parents = append(parents, fmt.Sprintf("%d", parent))
   494  
   495  		parentKey := pbkt.Get(bucketKeyParent)
   496  		if len(parentKey) == 0 {
   497  			return
   498  		}
   499  		pbkt = bkt.Bucket(parentKey)
   500  		if pbkt == nil {
   501  			return nil, errors.Wrap(errdefs.ErrNotFound, "missing parent")
   502  		}
   503  
   504  		parent = readID(pbkt)
   505  	}
   506  }
   507  
   508  func readKind(bkt *bolt.Bucket) (k snapshots.Kind) {
   509  	kind := bkt.Get(bucketKeyKind)
   510  	if len(kind) == 1 {
   511  		k = snapshots.Kind(kind[0])
   512  	}
   513  	return
   514  }
   515  
   516  func readID(bkt *bolt.Bucket) uint64 {
   517  	id, _ := binary.Uvarint(bkt.Get(bucketKeyID))
   518  	return id
   519  }
   520  
   521  func readSnapshot(bkt *bolt.Bucket, id *uint64, si *snapshots.Info) error {
   522  	if id != nil {
   523  		*id = readID(bkt)
   524  	}
   525  	if si != nil {
   526  		si.Kind = readKind(bkt)
   527  		si.Parent = string(bkt.Get(bucketKeyParent))
   528  
   529  		if err := boltutil.ReadTimestamps(bkt, &si.Created, &si.Updated); err != nil {
   530  			return err
   531  		}
   532  
   533  		labels, err := boltutil.ReadLabels(bkt)
   534  		if err != nil {
   535  			return err
   536  		}
   537  		si.Labels = labels
   538  	}
   539  
   540  	return nil
   541  }
   542  
   543  func putSnapshot(bkt *bolt.Bucket, id uint64, si snapshots.Info) error {
   544  	idEncoded, err := encodeID(id)
   545  	if err != nil {
   546  		return err
   547  	}
   548  
   549  	updates := [][2][]byte{
   550  		{bucketKeyID, idEncoded},
   551  		{bucketKeyKind, []byte{byte(si.Kind)}},
   552  	}
   553  	if si.Parent != "" {
   554  		updates = append(updates, [2][]byte{bucketKeyParent, []byte(si.Parent)})
   555  	}
   556  	for _, v := range updates {
   557  		if err := bkt.Put(v[0], v[1]); err != nil {
   558  			return err
   559  		}
   560  	}
   561  	if err := boltutil.WriteTimestamps(bkt, si.Created, si.Updated); err != nil {
   562  		return err
   563  	}
   564  	return boltutil.WriteLabels(bkt, si.Labels)
   565  }
   566  
   567  func getUsage(bkt *bolt.Bucket, usage *snapshots.Usage) {
   568  	usage.Inodes, _ = binary.Varint(bkt.Get(bucketKeyInodes))
   569  	usage.Size, _ = binary.Varint(bkt.Get(bucketKeySize))
   570  }
   571  
   572  func putUsage(bkt *bolt.Bucket, usage snapshots.Usage) error {
   573  	for _, v := range []struct {
   574  		key   []byte
   575  		value int64
   576  	}{
   577  		{bucketKeyInodes, usage.Inodes},
   578  		{bucketKeySize, usage.Size},
   579  	} {
   580  		e, err := encodeSize(v.value)
   581  		if err != nil {
   582  			return err
   583  		}
   584  		if err := bkt.Put(v.key, e); err != nil {
   585  			return err
   586  		}
   587  	}
   588  	return nil
   589  }
   590  
   591  func encodeSize(size int64) ([]byte, error) {
   592  	var (
   593  		buf         [binary.MaxVarintLen64]byte
   594  		sizeEncoded = buf[:]
   595  	)
   596  	sizeEncoded = sizeEncoded[:binary.PutVarint(sizeEncoded, size)]
   597  
   598  	if len(sizeEncoded) == 0 {
   599  		return nil, fmt.Errorf("failed encoding size = %v", size)
   600  	}
   601  	return sizeEncoded, nil
   602  }
   603  
   604  func encodeID(id uint64) ([]byte, error) {
   605  	var (
   606  		buf       [binary.MaxVarintLen64]byte
   607  		idEncoded = buf[:]
   608  	)
   609  	idEncoded = idEncoded[:binary.PutUvarint(idEncoded, id)]
   610  
   611  	if len(idEncoded) == 0 {
   612  		return nil, fmt.Errorf("failed encoding id = %v", id)
   613  	}
   614  	return idEncoded, nil
   615  }
   616  
   617  func adaptSnapshot(info snapshots.Info) filters.Adaptor {
   618  	return filters.AdapterFunc(func(fieldpath []string) (string, bool) {
   619  		if len(fieldpath) == 0 {
   620  			return "", false
   621  		}
   622  
   623  		switch fieldpath[0] {
   624  		case "kind":
   625  			switch info.Kind {
   626  			case snapshots.KindActive:
   627  				return "active", true
   628  			case snapshots.KindView:
   629  				return "view", true
   630  			case snapshots.KindCommitted:
   631  				return "committed", true
   632  			}
   633  		case "name":
   634  			return info.Name, true
   635  		case "parent":
   636  			return info.Parent, true
   637  		case "labels":
   638  			if len(info.Labels) == 0 {
   639  				return "", false
   640  			}
   641  
   642  			v, ok := info.Labels[strings.Join(fieldpath[1:], ".")]
   643  			return v, ok
   644  		}
   645  
   646  		return "", false
   647  	})
   648  }