image.go 10 KB

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