github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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 }