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