github.com/rawahars/moby@v24.0.4+incompatible/daemon/images/image.go (about) 1 package images // import "github.com/docker/docker/daemon/images" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 9 "github.com/containerd/containerd/content" 10 cerrdefs "github.com/containerd/containerd/errdefs" 11 "github.com/containerd/containerd/images" 12 "github.com/containerd/containerd/leases" 13 "github.com/containerd/containerd/platforms" 14 "github.com/docker/distribution/reference" 15 imagetypes "github.com/docker/docker/api/types/image" 16 "github.com/docker/docker/errdefs" 17 "github.com/docker/docker/image" 18 "github.com/docker/docker/layer" 19 "github.com/opencontainers/go-digest" 20 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 21 "github.com/pkg/errors" 22 "github.com/sirupsen/logrus" 23 ) 24 25 // ErrImageDoesNotExist is error returned when no image can be found for a reference. 26 type ErrImageDoesNotExist struct { 27 Ref reference.Reference 28 } 29 30 func (e ErrImageDoesNotExist) Error() string { 31 ref := e.Ref 32 if named, ok := ref.(reference.Named); ok { 33 ref = reference.TagNameOnly(named) 34 } 35 return fmt.Sprintf("No such image: %s", reference.FamiliarString(ref)) 36 } 37 38 // NotFound implements the NotFound interface 39 func (e ErrImageDoesNotExist) NotFound() {} 40 41 type manifestList struct { 42 Manifests []ocispec.Descriptor `json:"manifests"` 43 } 44 45 type manifest struct { 46 Config ocispec.Descriptor `json:"config"` 47 } 48 49 func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, image string, platform *ocispec.Platform) error { 50 // Only makes sense when conatinerd image store is used 51 panic("not implemented") 52 } 53 54 func (i *ImageService) manifestMatchesPlatform(ctx context.Context, img *image.Image, platform ocispec.Platform) (bool, error) { 55 logger := logrus.WithField("image", img.ID).WithField("desiredPlatform", platforms.Format(platform)) 56 57 ls, leaseErr := i.leases.ListResources(ctx, leases.Lease{ID: imageKey(img.ID().String())}) 58 if leaseErr != nil { 59 logger.WithError(leaseErr).Error("Error looking up image leases") 60 return false, leaseErr 61 } 62 63 // Note we are comparing against manifest lists here, which we expect to always have a CPU variant set (where applicable). 64 // So there is no need for the fallback matcher here. 65 comparer := platforms.Only(platform) 66 67 var ( 68 ml manifestList 69 m manifest 70 ) 71 72 makeRdr := func(ra content.ReaderAt) io.Reader { 73 return io.LimitReader(io.NewSectionReader(ra, 0, ra.Size()), 1e6) 74 } 75 76 for _, r := range ls { 77 logger := logger.WithField("resourceID", r.ID).WithField("resourceType", r.Type) 78 logger.Debug("Checking lease resource for platform match") 79 if r.Type != "content" { 80 continue 81 } 82 83 ra, err := i.content.ReaderAt(ctx, ocispec.Descriptor{Digest: digest.Digest(r.ID)}) 84 if err != nil { 85 if cerrdefs.IsNotFound(err) { 86 continue 87 } 88 logger.WithError(err).Error("Error looking up referenced manifest list for image") 89 continue 90 } 91 92 data, err := io.ReadAll(makeRdr(ra)) 93 ra.Close() 94 95 if err != nil { 96 logger.WithError(err).Error("Error reading manifest list for image") 97 continue 98 } 99 100 ml.Manifests = nil 101 102 if err := json.Unmarshal(data, &ml); err != nil { 103 logger.WithError(err).Error("Error unmarshalling content") 104 continue 105 } 106 107 for _, md := range ml.Manifests { 108 switch md.MediaType { 109 case ocispec.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest: 110 default: 111 continue 112 } 113 114 p := ocispec.Platform{ 115 Architecture: md.Platform.Architecture, 116 OS: md.Platform.OS, 117 Variant: md.Platform.Variant, 118 } 119 if !comparer.Match(p) { 120 logger.WithField("otherPlatform", platforms.Format(p)).Debug("Manifest is not a match") 121 continue 122 } 123 124 // Here we have a platform match for the referenced manifest, let's make sure the manifest is actually for the image config we are using. 125 126 ra, err := i.content.ReaderAt(ctx, ocispec.Descriptor{Digest: md.Digest}) 127 if err != nil { 128 logger.WithField("otherDigest", md.Digest).WithError(err).Error("Could not get reader for manifest") 129 continue 130 } 131 132 data, err := io.ReadAll(makeRdr(ra)) 133 ra.Close() 134 if err != nil { 135 logger.WithError(err).Error("Error reading manifest for image") 136 continue 137 } 138 139 if err := json.Unmarshal(data, &m); err != nil { 140 logger.WithError(err).Error("Error desserializing manifest") 141 continue 142 } 143 144 if m.Config.Digest == img.ID().Digest() { 145 logger.WithField("manifestDigest", md.Digest).Debug("Found matching manifest for image") 146 return true, nil 147 } 148 149 logger.WithField("otherDigest", md.Digest).Debug("Skipping non-matching manifest") 150 } 151 } 152 153 return false, nil 154 } 155 156 // GetImage returns an image corresponding to the image referred to by refOrID. 157 func (i *ImageService) GetImage(ctx context.Context, refOrID string, options imagetypes.GetImageOpts) (*image.Image, error) { 158 img, err := i.getImage(ctx, refOrID, options) 159 if err != nil { 160 return nil, err 161 } 162 if options.Details { 163 var size int64 164 var layerMetadata map[string]string 165 layerID := img.RootFS.ChainID() 166 if layerID != "" { 167 l, err := i.layerStore.Get(layerID) 168 if err != nil { 169 return nil, err 170 } 171 defer layer.ReleaseAndLog(i.layerStore, l) 172 size = l.Size() 173 layerMetadata, err = l.Metadata() 174 if err != nil { 175 return nil, err 176 } 177 } 178 179 lastUpdated, err := i.imageStore.GetLastUpdated(img.ID()) 180 if err != nil { 181 return nil, err 182 } 183 img.Details = &image.Details{ 184 References: i.referenceStore.References(img.ID().Digest()), 185 Size: size, 186 Metadata: layerMetadata, 187 Driver: i.layerStore.DriverName(), 188 LastUpdated: lastUpdated, 189 } 190 } 191 return img, nil 192 } 193 194 func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, options imagetypes.GetImageOpts) (*ocispec.Descriptor, error) { 195 panic("not implemented") 196 } 197 198 func (i *ImageService) getImage(ctx context.Context, refOrID string, options imagetypes.GetImageOpts) (retImg *image.Image, retErr error) { 199 defer func() { 200 if retErr != nil || retImg == nil || options.Platform == nil { 201 return 202 } 203 204 imgPlat := ocispec.Platform{ 205 OS: retImg.OS, 206 Architecture: retImg.Architecture, 207 Variant: retImg.Variant, 208 } 209 p := *options.Platform 210 // Note that `platforms.Only` will fuzzy match this for us 211 // For example: an armv6 image will run just fine on an armv7 CPU, without emulation or anything. 212 if OnlyPlatformWithFallback(p).Match(imgPlat) { 213 return 214 } 215 // In some cases the image config can actually be wrong (e.g. classic `docker build` may not handle `--platform` correctly) 216 // So we'll look up the manifest list that corresponds to this image to check if at least the manifest list says it is the correct image. 217 var matches bool 218 matches, retErr = i.manifestMatchesPlatform(ctx, retImg, p) 219 if matches || retErr != nil { 220 return 221 } 222 223 // This allows us to tell clients that we don't have the image they asked for 224 // Where this gets hairy is the image store does not currently support multi-arch images, e.g.: 225 // An image `foo` may have a multi-arch manifest, but the image store only fetches the image for a specific platform 226 // The image store does not store the manifest list and image tags are assigned to architecture specific images. 227 // So we can have a `foo` image that is amd64 but the user requested armv7. If the user looks at the list of images. 228 // This may be confusing. 229 // The alternative to this is to return an errdefs.Conflict error with a helpful message, but clients will not be 230 // able to automatically tell what causes the conflict. 231 retErr = errdefs.NotFound(errors.Errorf("image with reference %s was found but does not match the specified platform: wanted %s, actual: %s", refOrID, platforms.Format(p), platforms.Format(imgPlat))) 232 }() 233 ref, err := reference.ParseAnyReference(refOrID) 234 if err != nil { 235 return nil, errdefs.InvalidParameter(err) 236 } 237 namedRef, ok := ref.(reference.Named) 238 if !ok { 239 digested, ok := ref.(reference.Digested) 240 if !ok { 241 return nil, ErrImageDoesNotExist{ref} 242 } 243 if img, err := i.imageStore.Get(image.ID(digested.Digest())); err == nil { 244 return img, nil 245 } 246 return nil, ErrImageDoesNotExist{ref} 247 } 248 249 if dgst, err := i.referenceStore.Get(namedRef); err == nil { 250 // Search the image stores to get the operating system, defaulting to host OS. 251 if img, err := i.imageStore.Get(image.ID(dgst)); err == nil { 252 return img, nil 253 } 254 } 255 256 // Search based on ID 257 if id, err := i.imageStore.Search(refOrID); err == nil { 258 img, err := i.imageStore.Get(id) 259 if err != nil { 260 return nil, ErrImageDoesNotExist{ref} 261 } 262 return img, nil 263 } 264 265 return nil, ErrImageDoesNotExist{ref} 266 } 267 268 // OnlyPlatformWithFallback uses `platforms.Only` with a fallback to handle the case where the platform 269 // being matched does not have a CPU variant. 270 // 271 // The reason for this is that CPU variant is not even if the official image config spec as of this writing. 272 // See: https://github.com/opencontainers/image-spec/pull/809 273 // Since Docker tends to compare platforms from the image config, we need to handle this case. 274 func OnlyPlatformWithFallback(p ocispec.Platform) platforms.Matcher { 275 return &onlyFallbackMatcher{only: platforms.Only(p), p: platforms.Normalize(p)} 276 } 277 278 type onlyFallbackMatcher struct { 279 only platforms.Matcher 280 p ocispec.Platform 281 } 282 283 func (m *onlyFallbackMatcher) Match(other ocispec.Platform) bool { 284 if m.only.Match(other) { 285 // It matches, no reason to fallback 286 return true 287 } 288 if other.Variant != "" { 289 // If there is a variant then this fallback does not apply, and there is no match 290 return false 291 } 292 otherN := platforms.Normalize(other) 293 otherN.Variant = "" // normalization adds a default variant... which is the whole problem with `platforms.Only` 294 295 return m.p.OS == otherN.OS && 296 m.p.Architecture == otherN.Architecture 297 }