github.com/lalkh/containerd@v1.4.3/metadata/images.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 metadata
    18  
    19  import (
    20  	"context"
    21  	"encoding/binary"
    22  	"fmt"
    23  	"strings"
    24  	"sync/atomic"
    25  	"time"
    26  
    27  	"github.com/containerd/containerd/errdefs"
    28  	"github.com/containerd/containerd/filters"
    29  	"github.com/containerd/containerd/images"
    30  	"github.com/containerd/containerd/labels"
    31  	"github.com/containerd/containerd/metadata/boltutil"
    32  	"github.com/containerd/containerd/namespaces"
    33  	digest "github.com/opencontainers/go-digest"
    34  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    35  	"github.com/pkg/errors"
    36  	bolt "go.etcd.io/bbolt"
    37  )
    38  
    39  type imageStore struct {
    40  	db *DB
    41  }
    42  
    43  // NewImageStore returns a store backed by a bolt DB
    44  func NewImageStore(db *DB) images.Store {
    45  	return &imageStore{db: db}
    46  }
    47  
    48  func (s *imageStore) Get(ctx context.Context, name string) (images.Image, error) {
    49  	var image images.Image
    50  
    51  	namespace, err := namespaces.NamespaceRequired(ctx)
    52  	if err != nil {
    53  		return images.Image{}, err
    54  	}
    55  
    56  	if err := view(ctx, s.db, func(tx *bolt.Tx) error {
    57  		bkt := getImagesBucket(tx, namespace)
    58  		if bkt == nil {
    59  			return errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
    60  		}
    61  
    62  		ibkt := bkt.Bucket([]byte(name))
    63  		if ibkt == nil {
    64  			return errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
    65  		}
    66  
    67  		image.Name = name
    68  		if err := readImage(&image, ibkt); err != nil {
    69  			return errors.Wrapf(err, "image %q", name)
    70  		}
    71  
    72  		return nil
    73  	}); err != nil {
    74  		return images.Image{}, err
    75  	}
    76  
    77  	return image, nil
    78  }
    79  
    80  func (s *imageStore) List(ctx context.Context, fs ...string) ([]images.Image, error) {
    81  	namespace, err := namespaces.NamespaceRequired(ctx)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	filter, err := filters.ParseAll(fs...)
    87  	if err != nil {
    88  		return nil, errors.Wrap(errdefs.ErrInvalidArgument, err.Error())
    89  	}
    90  
    91  	var m []images.Image
    92  	if err := view(ctx, s.db, func(tx *bolt.Tx) error {
    93  		bkt := getImagesBucket(tx, namespace)
    94  		if bkt == nil {
    95  			return nil // empty store
    96  		}
    97  
    98  		return bkt.ForEach(func(k, v []byte) error {
    99  			var (
   100  				image = images.Image{
   101  					Name: string(k),
   102  				}
   103  				kbkt = bkt.Bucket(k)
   104  			)
   105  
   106  			if err := readImage(&image, kbkt); err != nil {
   107  				return err
   108  			}
   109  
   110  			if filter.Match(adaptImage(image)) {
   111  				m = append(m, image)
   112  			}
   113  			return nil
   114  		})
   115  	}); err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	return m, nil
   120  }
   121  
   122  func (s *imageStore) Create(ctx context.Context, image images.Image) (images.Image, error) {
   123  	namespace, err := namespaces.NamespaceRequired(ctx)
   124  	if err != nil {
   125  		return images.Image{}, err
   126  	}
   127  
   128  	if err := update(ctx, s.db, func(tx *bolt.Tx) error {
   129  		if err := validateImage(&image); err != nil {
   130  			return err
   131  		}
   132  
   133  		bkt, err := createImagesBucket(tx, namespace)
   134  		if err != nil {
   135  			return err
   136  		}
   137  
   138  		ibkt, err := bkt.CreateBucket([]byte(image.Name))
   139  		if err != nil {
   140  			if err != bolt.ErrBucketExists {
   141  				return err
   142  			}
   143  
   144  			return errors.Wrapf(errdefs.ErrAlreadyExists, "image %q", image.Name)
   145  		}
   146  
   147  		image.CreatedAt = time.Now().UTC()
   148  		image.UpdatedAt = image.CreatedAt
   149  		return writeImage(ibkt, &image)
   150  	}); err != nil {
   151  		return images.Image{}, err
   152  	}
   153  
   154  	return image, nil
   155  }
   156  
   157  func (s *imageStore) Update(ctx context.Context, image images.Image, fieldpaths ...string) (images.Image, error) {
   158  	namespace, err := namespaces.NamespaceRequired(ctx)
   159  	if err != nil {
   160  		return images.Image{}, err
   161  	}
   162  
   163  	if image.Name == "" {
   164  		return images.Image{}, errors.Wrapf(errdefs.ErrInvalidArgument, "image name is required for update")
   165  	}
   166  
   167  	var updated images.Image
   168  
   169  	if err := update(ctx, s.db, func(tx *bolt.Tx) error {
   170  		bkt, err := createImagesBucket(tx, namespace)
   171  		if err != nil {
   172  			return err
   173  		}
   174  
   175  		ibkt := bkt.Bucket([]byte(image.Name))
   176  		if ibkt == nil {
   177  			return errors.Wrapf(errdefs.ErrNotFound, "image %q", image.Name)
   178  		}
   179  
   180  		if err := readImage(&updated, ibkt); err != nil {
   181  			return errors.Wrapf(err, "image %q", image.Name)
   182  		}
   183  		createdat := updated.CreatedAt
   184  		updated.Name = image.Name
   185  
   186  		if len(fieldpaths) > 0 {
   187  			for _, path := range fieldpaths {
   188  				if strings.HasPrefix(path, "labels.") {
   189  					if updated.Labels == nil {
   190  						updated.Labels = map[string]string{}
   191  					}
   192  
   193  					key := strings.TrimPrefix(path, "labels.")
   194  					updated.Labels[key] = image.Labels[key]
   195  					continue
   196  				} else if strings.HasPrefix(path, "annotations.") {
   197  					if updated.Target.Annotations == nil {
   198  						updated.Target.Annotations = map[string]string{}
   199  					}
   200  
   201  					key := strings.TrimPrefix(path, "annotations.")
   202  					updated.Target.Annotations[key] = image.Target.Annotations[key]
   203  					continue
   204  				}
   205  
   206  				switch path {
   207  				case "labels":
   208  					updated.Labels = image.Labels
   209  				case "target":
   210  					// NOTE(stevvooe): While we allow setting individual labels, we
   211  					// only support replacing the target as a unit, since that is
   212  					// commonly pulled as a unit from other sources. It often doesn't
   213  					// make sense to modify the size or digest without touching the
   214  					// mediatype, as well, for example.
   215  					updated.Target = image.Target
   216  				case "annotations":
   217  					updated.Target.Annotations = image.Target.Annotations
   218  				default:
   219  					return errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on image %q", path, image.Name)
   220  				}
   221  			}
   222  		} else {
   223  			updated = image
   224  		}
   225  
   226  		if err := validateImage(&updated); err != nil {
   227  			return err
   228  		}
   229  
   230  		updated.CreatedAt = createdat
   231  		updated.UpdatedAt = time.Now().UTC()
   232  		return writeImage(ibkt, &updated)
   233  	}); err != nil {
   234  		return images.Image{}, err
   235  	}
   236  
   237  	return updated, nil
   238  
   239  }
   240  
   241  func (s *imageStore) Delete(ctx context.Context, name string, opts ...images.DeleteOpt) error {
   242  	namespace, err := namespaces.NamespaceRequired(ctx)
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	return update(ctx, s.db, func(tx *bolt.Tx) error {
   248  		bkt := getImagesBucket(tx, namespace)
   249  		if bkt == nil {
   250  			return errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
   251  		}
   252  
   253  		if err = bkt.DeleteBucket([]byte(name)); err != nil {
   254  			if err == bolt.ErrBucketNotFound {
   255  				err = errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
   256  			}
   257  			return err
   258  		}
   259  
   260  		atomic.AddUint32(&s.db.dirty, 1)
   261  
   262  		return nil
   263  	})
   264  }
   265  
   266  func validateImage(image *images.Image) error {
   267  	if image.Name == "" {
   268  		return errors.Wrapf(errdefs.ErrInvalidArgument, "image name must not be empty")
   269  	}
   270  
   271  	for k, v := range image.Labels {
   272  		if err := labels.Validate(k, v); err != nil {
   273  			return errors.Wrapf(err, "image.Labels")
   274  		}
   275  	}
   276  
   277  	return validateTarget(&image.Target)
   278  }
   279  
   280  func validateTarget(target *ocispec.Descriptor) error {
   281  	// NOTE(stevvooe): Only validate fields we actually store.
   282  
   283  	if err := target.Digest.Validate(); err != nil {
   284  		return errors.Wrapf(errdefs.ErrInvalidArgument, "Target.Digest %q invalid: %v", target.Digest, err)
   285  	}
   286  
   287  	if target.Size <= 0 {
   288  		return errors.Wrapf(errdefs.ErrInvalidArgument, "Target.Size must be greater than zero")
   289  	}
   290  
   291  	if target.MediaType == "" {
   292  		return errors.Wrapf(errdefs.ErrInvalidArgument, "Target.MediaType must be set")
   293  	}
   294  
   295  	return nil
   296  }
   297  
   298  func readImage(image *images.Image, bkt *bolt.Bucket) error {
   299  	if err := boltutil.ReadTimestamps(bkt, &image.CreatedAt, &image.UpdatedAt); err != nil {
   300  		return err
   301  	}
   302  
   303  	labels, err := boltutil.ReadLabels(bkt)
   304  	if err != nil {
   305  		return err
   306  	}
   307  	image.Labels = labels
   308  
   309  	image.Target.Annotations, err = boltutil.ReadAnnotations(bkt)
   310  	if err != nil {
   311  		return err
   312  	}
   313  
   314  	tbkt := bkt.Bucket(bucketKeyTarget)
   315  	if tbkt == nil {
   316  		return errors.New("unable to read target bucket")
   317  	}
   318  	return tbkt.ForEach(func(k, v []byte) error {
   319  		if v == nil {
   320  			return nil // skip it? a bkt maybe?
   321  		}
   322  
   323  		// TODO(stevvooe): This is why we need to use byte values for
   324  		// keys, rather than full arrays.
   325  		switch string(k) {
   326  		case string(bucketKeyDigest):
   327  			image.Target.Digest = digest.Digest(v)
   328  		case string(bucketKeyMediaType):
   329  			image.Target.MediaType = string(v)
   330  		case string(bucketKeySize):
   331  			image.Target.Size, _ = binary.Varint(v)
   332  		}
   333  
   334  		return nil
   335  	})
   336  }
   337  
   338  func writeImage(bkt *bolt.Bucket, image *images.Image) error {
   339  	if err := boltutil.WriteTimestamps(bkt, image.CreatedAt, image.UpdatedAt); err != nil {
   340  		return err
   341  	}
   342  
   343  	if err := boltutil.WriteLabels(bkt, image.Labels); err != nil {
   344  		return errors.Wrapf(err, "writing labels for image %v", image.Name)
   345  	}
   346  
   347  	if err := boltutil.WriteAnnotations(bkt, image.Target.Annotations); err != nil {
   348  		return errors.Wrapf(err, "writing Annotations for image %v", image.Name)
   349  	}
   350  
   351  	// write the target bucket
   352  	tbkt, err := bkt.CreateBucketIfNotExists(bucketKeyTarget)
   353  	if err != nil {
   354  		return err
   355  	}
   356  
   357  	sizeEncoded, err := encodeInt(image.Target.Size)
   358  	if err != nil {
   359  		return err
   360  	}
   361  
   362  	for _, v := range [][2][]byte{
   363  		{bucketKeyDigest, []byte(image.Target.Digest)},
   364  		{bucketKeyMediaType, []byte(image.Target.MediaType)},
   365  		{bucketKeySize, sizeEncoded},
   366  	} {
   367  		if err := tbkt.Put(v[0], v[1]); err != nil {
   368  			return err
   369  		}
   370  	}
   371  
   372  	return nil
   373  }
   374  
   375  func encodeInt(i int64) ([]byte, error) {
   376  	var (
   377  		buf      [binary.MaxVarintLen64]byte
   378  		iEncoded = buf[:]
   379  	)
   380  	iEncoded = iEncoded[:binary.PutVarint(iEncoded, i)]
   381  
   382  	if len(iEncoded) == 0 {
   383  		return nil, fmt.Errorf("failed encoding integer = %v", i)
   384  	}
   385  	return iEncoded, nil
   386  }