image.go 9.8 KB

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