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