github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/daemon/containerd/image.go (about) 1 package containerd 2 3 import ( 4 "context" 5 "fmt" 6 "regexp" 7 "sort" 8 "strconv" 9 "strings" 10 "sync/atomic" 11 "time" 12 13 imagetype "github.com/Prakhar-Agarwal-byte/moby/api/types/image" 14 "github.com/Prakhar-Agarwal-byte/moby/daemon/images" 15 "github.com/Prakhar-Agarwal-byte/moby/errdefs" 16 "github.com/Prakhar-Agarwal-byte/moby/image" 17 imagespec "github.com/Prakhar-Agarwal-byte/moby/image/spec/specs-go/v1" 18 "github.com/Prakhar-Agarwal-byte/moby/pkg/platforms" 19 cerrdefs "github.com/containerd/containerd/errdefs" 20 containerdimages "github.com/containerd/containerd/images" 21 cplatforms "github.com/containerd/containerd/platforms" 22 "github.com/containerd/log" 23 "github.com/distribution/reference" 24 "github.com/opencontainers/go-digest" 25 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 26 "github.com/pkg/errors" 27 "golang.org/x/sync/semaphore" 28 ) 29 30 var truncatedID = regexp.MustCompile(`^(sha256:)?([a-f0-9]{4,64})$`) 31 32 // GetImage returns an image corresponding to the image referred to by refOrID. 33 func (i *ImageService) GetImage(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*image.Image, error) { 34 desc, err := i.resolveImage(ctx, refOrID) 35 if err != nil { 36 return nil, err 37 } 38 39 platform := platforms.AllPlatformsWithPreference(cplatforms.Default()) 40 if options.Platform != nil { 41 platform = cplatforms.OnlyStrict(*options.Platform) 42 } 43 44 cs := i.client.ContentStore() 45 46 var presentImages []imagespec.DockerOCIImage 47 err = i.walkImageManifests(ctx, desc, func(img *ImageManifest) error { 48 conf, err := img.Config(ctx) 49 if err != nil { 50 if cerrdefs.IsNotFound(err) { 51 log.G(ctx).WithFields(log.Fields{ 52 "manifestDescriptor": img.Target(), 53 }).Debug("manifest was present, but accessing its config failed, ignoring") 54 return nil 55 } 56 return errdefs.System(fmt.Errorf("failed to get config descriptor: %w", err)) 57 } 58 59 var ociimage imagespec.DockerOCIImage 60 if err := readConfig(ctx, cs, conf, &ociimage); err != nil { 61 if cerrdefs.IsNotFound(err) { 62 log.G(ctx).WithFields(log.Fields{ 63 "manifestDescriptor": img.Target(), 64 "configDescriptor": conf, 65 }).Debug("manifest present, but its config is missing, ignoring") 66 return nil 67 } 68 return errdefs.System(fmt.Errorf("failed to read config of the manifest %v: %w", img.Target().Digest, err)) 69 } 70 presentImages = append(presentImages, ociimage) 71 return nil 72 }) 73 if err != nil { 74 return nil, err 75 } 76 if len(presentImages) == 0 { 77 ref, _ := reference.ParseAnyReference(refOrID) 78 return nil, images.ErrImageDoesNotExist{Ref: ref} 79 } 80 81 sort.SliceStable(presentImages, func(i, j int) bool { 82 return platform.Less(presentImages[i].Platform, presentImages[j].Platform) 83 }) 84 ociimage := presentImages[0] 85 86 img := dockerOciImageToDockerImagePartial(image.ID(desc.Target.Digest), ociimage) 87 if options.Details { 88 lastUpdated := time.Unix(0, 0) 89 size, err := i.size(ctx, desc.Target, platform) 90 if err != nil { 91 return nil, err 92 } 93 94 tagged, err := i.client.ImageService().List(ctx, "target.digest=="+desc.Target.Digest.String()) 95 if err != nil { 96 return nil, err 97 } 98 99 // Usually each image will result in 2 references (named and digested). 100 refs := make([]reference.Named, 0, len(tagged)*2) 101 for _, i := range tagged { 102 if i.UpdatedAt.After(lastUpdated) { 103 lastUpdated = i.UpdatedAt 104 } 105 if isDanglingImage(i) { 106 if len(tagged) > 1 { 107 // This is unexpected - dangling image should be deleted 108 // as soon as another image with the same target is created. 109 // Log a warning, but don't error out the whole operation. 110 log.G(ctx).WithField("refs", tagged).Warn("multiple images have the same target, but one of them is still dangling") 111 } 112 continue 113 } 114 115 name, err := reference.ParseNamed(i.Name) 116 if err != nil { 117 // This is inconsistent with `docker image ls` which will 118 // still include the malformed name in RepoTags. 119 log.G(ctx).WithField("name", name).WithError(err).Error("failed to parse image name as reference") 120 continue 121 } 122 refs = append(refs, name) 123 124 if _, ok := name.(reference.Digested); ok { 125 // Image name already contains a digest, so no need to create a digested reference. 126 continue 127 } 128 129 digested, err := reference.WithDigest(reference.TrimNamed(name), desc.Target.Digest) 130 if err != nil { 131 // This could only happen if digest is invalid, but considering that 132 // we get it from the Descriptor it's highly unlikely. 133 // Log error just in case. 134 log.G(ctx).WithError(err).Error("failed to create digested reference") 135 continue 136 } 137 refs = append(refs, digested) 138 } 139 140 img.Details = &image.Details{ 141 References: refs, 142 Size: size, 143 Metadata: nil, 144 Driver: i.snapshotter, 145 LastUpdated: lastUpdated, 146 } 147 } 148 149 return img, nil 150 } 151 152 func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*ocispec.Descriptor, error) { 153 platform := platforms.AllPlatformsWithPreference(cplatforms.Default()) 154 if options.Platform != nil { 155 platform = cplatforms.Only(*options.Platform) 156 } 157 158 cs := i.client.ContentStore() 159 160 img, err := i.resolveImage(ctx, refOrID) 161 if err != nil { 162 return nil, err 163 } 164 165 desc := img.Target 166 if containerdimages.IsManifestType(desc.MediaType) { 167 plat := desc.Platform 168 if plat == nil { 169 config, err := img.Config(ctx, cs, platform) 170 if err != nil { 171 return nil, err 172 } 173 var configPlatform ocispec.Platform 174 if err := readConfig(ctx, cs, config, &configPlatform); err != nil { 175 return nil, err 176 } 177 178 plat = &configPlatform 179 } 180 181 if options.Platform != nil { 182 if plat == nil { 183 return nil, errdefs.NotFound(errors.Errorf("image with reference %s was found but does not match the specified platform: wanted %s, actual: nil", refOrID, cplatforms.Format(*options.Platform))) 184 } else if !platform.Match(*plat) { 185 return nil, errdefs.NotFound(errors.Errorf("image with reference %s was found but does not match the specified platform: wanted %s, actual: %s", refOrID, cplatforms.Format(*options.Platform), cplatforms.Format(*plat))) 186 } 187 } 188 189 return &desc, nil 190 } 191 192 if containerdimages.IsIndexType(desc.MediaType) { 193 childManifests, err := containerdimages.LimitManifests(containerdimages.ChildrenHandler(cs), platform, 1)(ctx, desc) 194 if err != nil { 195 if cerrdefs.IsNotFound(err) { 196 return nil, errdefs.NotFound(err) 197 } 198 return nil, errdefs.System(err) 199 } 200 201 // len(childManifests) == 1 since we requested 1 and if none 202 // were found LimitManifests would have thrown an error 203 if !containerdimages.IsManifestType(childManifests[0].MediaType) { 204 return nil, errdefs.NotFound(fmt.Errorf("manifest has incorrect mediatype: %s", childManifests[0].MediaType)) 205 } 206 207 return &childManifests[0], nil 208 } 209 210 return nil, errdefs.NotFound(errors.New("failed to find manifest")) 211 } 212 213 // size returns the total size of the image's packed resources. 214 func (i *ImageService) size(ctx context.Context, desc ocispec.Descriptor, platform cplatforms.MatchComparer) (int64, error) { 215 var size int64 216 217 cs := i.client.ContentStore() 218 handler := containerdimages.LimitManifests(containerdimages.ChildrenHandler(cs), platform, 1) 219 220 var wh containerdimages.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 221 children, err := handler(ctx, desc) 222 if err != nil { 223 if !cerrdefs.IsNotFound(err) { 224 return nil, err 225 } 226 } 227 228 atomic.AddInt64(&size, desc.Size) 229 230 return children, nil 231 } 232 233 l := semaphore.NewWeighted(3) 234 if err := containerdimages.Dispatch(ctx, wh, l, desc); err != nil { 235 return 0, err 236 } 237 238 return size, nil 239 } 240 241 // resolveDescriptor searches for a descriptor based on the given 242 // reference or identifier. Returns the descriptor of 243 // the image, which could be a manifest list, manifest, or config. 244 func (i *ImageService) resolveDescriptor(ctx context.Context, refOrID string) (ocispec.Descriptor, error) { 245 img, err := i.resolveImage(ctx, refOrID) 246 if err != nil { 247 return ocispec.Descriptor{}, err 248 } 249 250 return img.Target, nil 251 } 252 253 func (i *ImageService) resolveImage(ctx context.Context, refOrID string) (containerdimages.Image, error) { 254 parsed, err := reference.ParseAnyReference(refOrID) 255 if err != nil { 256 return containerdimages.Image{}, errdefs.InvalidParameter(err) 257 } 258 259 is := i.client.ImageService() 260 261 digested, ok := parsed.(reference.Digested) 262 if ok { 263 imgs, err := is.List(ctx, "target.digest=="+digested.Digest().String()) 264 if err != nil { 265 return containerdimages.Image{}, errors.Wrap(err, "failed to lookup digest") 266 } 267 if len(imgs) == 0 { 268 return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed} 269 } 270 271 // If reference is both Named and Digested, make sure we don't match 272 // images with a different repository even if digest matches. 273 // For example, busybox@sha256:abcdef..., shouldn't match asdf@sha256:abcdef... 274 if parsedNamed, ok := parsed.(reference.Named); ok { 275 for _, img := range imgs { 276 imgNamed, err := reference.ParseNormalizedNamed(img.Name) 277 if err != nil { 278 log.G(ctx).WithError(err).WithField("image", img.Name).Warn("image with invalid name encountered") 279 continue 280 } 281 282 if parsedNamed.Name() == imgNamed.Name() { 283 return img, nil 284 } 285 } 286 return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed} 287 } 288 289 return imgs[0], nil 290 } 291 292 ref := reference.TagNameOnly(parsed.(reference.Named)).String() 293 img, err := is.Get(ctx, ref) 294 if err == nil { 295 return img, nil 296 } else { 297 // TODO(containerd): error translation can use common function 298 if !cerrdefs.IsNotFound(err) { 299 return containerdimages.Image{}, err 300 } 301 } 302 303 // If the identifier could be a short ID, attempt to match 304 if truncatedID.MatchString(refOrID) { 305 idWithoutAlgo := strings.TrimPrefix(refOrID, "sha256:") 306 filters := []string{ 307 fmt.Sprintf("name==%q", ref), // Or it could just look like one. 308 "target.digest~=" + strconv.Quote(fmt.Sprintf(`^sha256:%s[0-9a-fA-F]{%d}$`, regexp.QuoteMeta(idWithoutAlgo), 64-len(idWithoutAlgo))), 309 } 310 imgs, err := is.List(ctx, filters...) 311 if err != nil { 312 return containerdimages.Image{}, err 313 } 314 315 if len(imgs) == 0 { 316 return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed} 317 } 318 if len(imgs) > 1 { 319 digests := map[digest.Digest]struct{}{} 320 for _, img := range imgs { 321 if img.Name == ref { 322 return img, nil 323 } 324 digests[img.Target.Digest] = struct{}{} 325 } 326 327 if len(digests) > 1 { 328 return containerdimages.Image{}, errdefs.NotFound(errors.New("ambiguous reference")) 329 } 330 } 331 332 return imgs[0], nil 333 } 334 335 return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed} 336 } 337 338 // getAllImagesWithRepository returns a slice of images which name is a reference 339 // pointing to the same repository as the given reference. 340 func (i *ImageService) getAllImagesWithRepository(ctx context.Context, ref reference.Named) ([]containerdimages.Image, error) { 341 nameFilter := "^" + regexp.QuoteMeta(ref.Name()) + ":" + reference.TagRegexp.String() + "$" 342 return i.client.ImageService().List(ctx, "name~="+strconv.Quote(nameFilter)) 343 }