add support for image inspect with containerd-integration#43818
add support for image inspect with containerd-integration#43818thaJeztah merged 1 commit intomoby:masterfrom
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
fe465a1 to
7e7249c
Compare
|
Would it be possible to split the "ctx" changes into a separate commit? |
|
moved it to #43828 |
cfa4765 to
157e515
Compare
157e515 to
1184eb5
Compare
1184eb5 to
ee27455
Compare
ee27455 to
f4f6760
Compare
|
Moving this temporarily back to draft, as I'll probably update this one with changes from #44621 (or use that one instead) |
f4f6760 to
92f1d26
Compare
corhere
left a comment
There was a problem hiding this comment.
Unfortunately, there's a(nother) problem with the "image not found" error returns.
Aside: have you seen how nerdctl resolves images? It's quite elegant. There might be some inspiration to take from it.
daemon/containerd/image.go
Outdated
| // This requires the error to be prefixed with "No such image:". | ||
| // Without this prefix, the CLI prints the error message, and exits. | ||
| // | ||
| // FIXME(thaJeztah): we need to fix this; if needed, gated by API version (API < X add the prefix) |
There was a problem hiding this comment.
@thaJeztah I removed the comment since I'm not returning images.ErrImageDoesNotExist but you might want to create an issue for this
daemon/containerd/image.go
Outdated
|
|
||
| // resolveImage searches for an image based on the given | ||
| // reference or identifier. Returns the descriptor of | ||
| // the image, could be manifest list, manifest, or config. |
There was a problem hiding this comment.
| // the image, could be manifest list, manifest, or config. | |
| // the image, which could be a manifest list, manifest, or config. |
daemon/containerd/image.go
Outdated
| // If the identifier could be a short ID, attempt to match | ||
| if shortID.MatchString(refOrID) { |
There was a problem hiding this comment.
"short ID" is an unfortunate choice of name as it could be confused with a long-deprecated reference syntax. Call it a "truncated ID" or "ID prefix" instead?
daemon/containerd/image.go
Outdated
| if ok { | ||
| imgs, err := is.List(ctx, fmt.Sprintf("target.digest==%s", digested.Digest())) | ||
| if err != nil { | ||
| return containerdimages.Image{}, errors.Wrap(err, "failed to lookup digest") |
There was a problem hiding this comment.
Does err need to be translated to (wrapped in) an errdefs value?
daemon/containerd/image.go
Outdated
| fmt.Sprintf("name==%q", ref), | ||
| fmt.Sprintf(`target.digest~=/sha256:%s[0-9a-fA-F]{%d}/`, refOrID, 64-len(refOrID)), |
There was a problem hiding this comment.
| fmt.Sprintf("name==%q", ref), | |
| fmt.Sprintf(`target.digest~=/sha256:%s[0-9a-fA-F]{%d}/`, refOrID, 64-len(refOrID)), | |
| "target.digest~=" + strconv.Quote(fmt.Sprintf(`sha256:^%s[0-9a-fA-F]{%d}$`, regexp.QuoteMeta(refOrID), 64-len(refOrID))), |
What can I say, I'm a little paranoid about properly escaping user input.
Also, the digest regexp filters should probably be anchored as nerdctl does.
daemon/containerd/image.go
Outdated
| // If the identifier could be a short ID, attempt to match | ||
| if shortID.MatchString(refOrID) { | ||
| filters := []string{ | ||
| fmt.Sprintf("name==%q", ref), |
There was a problem hiding this comment.
It took me way too long to figure out what this filter is needed for.
| fmt.Sprintf("name==%q", ref), | |
| fmt.Sprintf("name==%q", ref), // Or it could just look like one. |
daemon/containerd/image.go
Outdated
| return nil, err | ||
| } | ||
|
|
||
| tagged, err := i.client.ImageService().List(ctx, fmt.Sprintf("target.digest==%s", containerdImage.Target().Digest.String())) |
There was a problem hiding this comment.
Nit: @thaJeztah has been on a rampage lately subbing trivial fmt.Sprintf calls with string concatenation. Let's be nice and not leave more work for him.
| tagged, err := i.client.ImageService().List(ctx, fmt.Sprintf("target.digest==%s", containerdImage.Target().Digest.String())) | |
| tagged, err := i.client.ImageService().List(ctx, "target.digest=="+containerdImage.Target().Digest.String())) |
| var ociimage ocispec.Image | ||
| imageConfigBytes, err := content.ReadBlob(ctx, containerdImage.ContentStore(), conf) | ||
| if err != nil { | ||
| return nil, nil, err | ||
| } | ||
|
|
||
| if err := json.Unmarshal(imageConfigBytes, &ociimage); err != nil { | ||
| return nil, nil, err | ||
| } |
There was a problem hiding this comment.
| var ociimage ocispec.Image | |
| imageConfigBytes, err := content.ReadBlob(ctx, containerdImage.ContentStore(), conf) | |
| if err != nil { | |
| return nil, nil, err | |
| } | |
| if err := json.Unmarshal(imageConfigBytes, &ociimage); err != nil { | |
| return nil, nil, err | |
| } | |
| imageConfigBytes, err := content.ReadBlob(ctx, containerdImage.ContentStore(), conf) | |
| if err != nil { | |
| return nil, nil, err | |
| } | |
| var ociimage ocispec.Image | |
| if err := json.Unmarshal(imageConfigBytes, &ociimage); err != nil { | |
| return nil, nil, err | |
| } |
daemon/containerd/image.go
Outdated
| return img, nil | ||
| } | ||
| } | ||
| return img, errdefs.NotFound(errors.Errorf("platform %s not supported", platforms.Format(*platform))) |
There was a problem hiding this comment.
| return img, errdefs.NotFound(errors.Errorf("platform %s not supported", platforms.Format(*platform))) | |
| return img, errdefs.NotFound(errors.Errorf("%s: platform %s not supported by image", refOrID, platforms.Format(*platform))) |
805e2c6 to
e35edac
Compare
daemon/containerd/image.go
Outdated
| } | ||
|
|
||
| if len(digests) > 1 { | ||
| return containerdimages.Image{}, errdefs.NotFound(errors.New("ambiguous reference")) |
There was a problem hiding this comment.
Maybe errdefs.Conflict would be more suitable?
There was a problem hiding this comment.
Conflict (409) is mostly intended if there's a conflict with another request (e.g. update a container that is in the process of being deleted); that said; "it's hard", as those status codes weren't really designed with APIs in mind. Also see the discussion on #44685
This page has some flow-charts that have been quite useful; https://www.codetinkerer.com/2015/12/04/choosing-an-http-status-code.html
Maybe a 422 would make sense, but it's not about the body (although that may be semantics)
daemon/containerd/image.go
Outdated
| "github.com/docker/docker/image" | ||
| "github.com/docker/docker/layer" | ||
| "github.com/docker/go-connections/nat" | ||
| "github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb" |
There was a problem hiding this comment.
I'm still not comfortable with having our internal types defined by another project; see the discussion on rumpl#121 (comment)
If that type is implementing the image "v1" spec defined in this repository (https://github.com/moby/moby/tree/master/image/spec), and we don't have a type for that in this repository, we should define it here
daemon/containerd/image.go
Outdated
| exposedPorts[nat.Port(k)] = v | ||
| } | ||
|
|
||
| img := image.NewImage(image.IDFromDigest(ctrdimg.Target.Digest)) |
There was a problem hiding this comment.
Just recalled #44426 - we should update this to
| img := image.NewImage(image.IDFromDigest(ctrdimg.Target.Digest)) | |
| img := image.NewImage(image.ID(ctrdimg.Target.Digest)) |
6228c7a to
1d1988c
Compare
daemon/containerd/image.go
Outdated
| if platform != nil { | ||
| cs := i.client.ContentStore() | ||
| imgPlatforms, err := containerdimages.Platforms(ctx, cs, img.Target) | ||
| if err != nil { | ||
| return img.Target, err | ||
| } | ||
|
|
||
| comparer := platforms.Only(*platform) | ||
| for _, p := range imgPlatforms { | ||
| if comparer.Match(p) { | ||
| return img.Target, nil | ||
| } | ||
| } | ||
| return img.Target, errdefs.NotFound(errors.Errorf("%s: platform %s not supported by image", refOrID, platforms.Format(*platform))) | ||
| } |
There was a problem hiding this comment.
I think this should be extracted from resolveDescriptor to GetImage. Accepting platform as an argument to this function gives an impression that the returned descriptor will be platform specific and it doesn't.
The platform is relevant only in the GetImage context, which uses it to pick the correct image config.
daemon/containerd/image.go
Outdated
|
|
||
| cs := i.client.ContentStore() | ||
|
|
||
| platform := platforms.Default() |
There was a problem hiding this comment.
Hm, this won't work, if we have image for the non-default platform only (like if we only do docker pull --platform linux/arm64 alpine on non-arm64 host).
Is this expected?
There was a problem hiding this comment.
I think it makes sense that when no --platform flag is given then we consider this as the user asking for the current platform
There was a problem hiding this comment.
That bugs me - if the user doesn't set the --platform then we don't know what he's asking for, consider this:
$ docker pull --platform linux/arm64 alpine
$ docker inspect alpine
alpine: platform linux/amd64 not supported by imageseems a bit harsh, especially for doing an inspect which should allow me to get information about an image. The user might not remember which platform the image was pulled for and currently there's no way for him to find out that.
There was a problem hiding this comment.
What should we show in this case then?
$ docker pull --platform linux/arm64 alpine
$ docker pull --platform linux/s390x alpine
$ docker inspect alpineThere was a problem hiding this comment.
The "old" behavior would be to pick the first platform that's found (but of course that would always be the only platform). Ideally we would show all platforms for the image, but that brings us to the discussion in #44582. If we must show a single architecture, but multiple architectures are present, it could follow the same method of selecting as a docker pull would do (basically, use a platform-matcher?)
There was a problem hiding this comment.
Consider the API currently shows whatever image is currently there regardless of platform.
We could error out with like ambiguous image reference: multiple platforms are available, you must specify a platform: available platforms <platform1>,<platform2>. It would be nice, though, if the API's json error could list those platforms so clients can parse it nicely (without having to parse the error string) or pick the first one... I'm inclined to error in case of ambiguity here, though.
Alternatively... we could update the API to return a list of objects
The docker CLI at least already outputs a json array.
There wouldn't be much too change to support the updated API.
... or change it to return the image index (with the appropriate media type on the response headers).
This does mean larger changes would be needed on the client.
There was a problem hiding this comment.
On the last point... we could gate this by accept header.
There was a problem hiding this comment.
Talking with @thaJeztah and we decided to go with a AllPlatformsWithPreference that returns the preferred platform first and then the others in order that they are defined by containerd.
There was a problem hiding this comment.
^^ That approach is the "first step" for this PR, to keep the behavior (mostly) consistent with what we have pre-containers, and to get us going, but we should indeed continue the discussion on UX (which could be return multiple platforms if there's multiple variations present)
There was a problem hiding this comment.
Definitely, the discussion is only getting started. Way back when we talked about snapshotters we decided that the way to go was to make everything as close as possible to the existing and after that we can shape the things in different ways
daemon/containerd/image.go
Outdated
|
|
||
| platform := platforms.Default() | ||
| if desc.Platform != nil { | ||
| platform = platforms.OnlyStrict(*desc.Platform) |
There was a problem hiding this comment.
We use OnlyStrict here, but Only when doing the previous check which will make some cases pass the check but fail on getting the config. Maybe we could get rid of the previous platform check completely, and rely on containerdimages.Config returning an error when we don't have that platform present?
|
Outside of the discussion around multiple platforms being available, the rest of the code LGTM. |
40f184f to
82430d6
Compare
| type ErrImageDoesNotExist struct { | ||
| ref reference.Reference | ||
| Ref reference.Reference | ||
| } |
There was a problem hiding this comment.
For a follow-up; we need to check if we really need this error-type, as it already implements errdefs.NotFound, perhaps we don't need it (with the possible exception of the "special" No such ... prefix, which may be needed for older CLIs).
There was a problem hiding this comment.
Yeah this one might not be needed, the only difference is that this error type formats the message with "No such image: %s", ref whereas the errdefs.NotFound doesn't have that extra data
This is a squashed version of various PRs (or related code-changes) to implement image inspect with the containerd-integration; - add support for image inspect - introduce GetImageOpts to manage image inspect data in backend - GetImage to return image tags with details - list images matching digest to discover all tags - Add ExposedPorts and Volumes to the image returned - Refactor resolving/getting images - Return the image ID on inspect - consider digest and ignore tag when both are set - docker run --platform Signed-off-by: Djordje Lukic <[email protected]> Signed-off-by: Nicolas De Loof <[email protected]> Signed-off-by: Sebastiaan van Stijn <[email protected]>
|
Let me bring this one in 👍 |
| } | ||
|
|
||
| func (c allPlatformsWithPreferenceMatcher) Less(p1, p2 ocispec.Platform) bool { | ||
| return c.preferred.Less(p1, p2) |
There was a problem hiding this comment.
If this is to live in a pkg package, we should make sure it's robust for any preferred matcher, including ones whose Less implementation may not necessarily sort platforms which Match less than platforms it would not.
| return c.preferred.Less(p1, p2) | |
| m1, m2 := c.preferred.Match(p1), c.preferred.Match(p2) | |
| if m1 && m2 { | |
| return c.preferred.Less(p1, p2) | |
| } | |
| return m1 // Not totally-ordered |
Upstreaming:
These patches were selected by looking at the history for https://github.com/rumpl/moby/commits/dd-4.15.0/daemon/containerd/image.go, and only the relevant parts for this feature were included.
Quite a few of those are touching-up previous commits, so we should consider squashing (some of) them, but keeping them separate for now to somewhat help review.
Also included rumpl#122
This is a squashed version of various PRs (or related code-changes)
to implement image inspect with the containerd-integration;
- A picture of a cute animal (not mandatory but encouraged)