Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

c8d/push: Support --platform switch #47679

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

vvoland
Copy link
Contributor

@vvoland vvoland commented Apr 4, 2024

Add OCI platform fields as a parameters to the POST /images/{id}/push that allow to specify a single-platform manifest to be pushed instead of the whole image index.

- What I did

- How I did it

- How to verify it

- Description for the changelog

- containerd image store: `POST /images/{name}/push` now supports a `os`, `architecture`, `variant`, `osversion`, `osfeatures` parameters (fields of OCI Platform) that allow selecting a specific platform-manifest from the multi-platform image. 

- A picture of a cute animal (not mandatory but encouraged)

@cpuguy83
Copy link
Member

cpuguy83 commented Apr 8, 2024

Noting what we discussed on zoom:

I think the platform option needs to support something like platform=default so the client doesn't have to know the platform of the daemon.
I think buildkit already has an option like this we can just reuse.

buildx also supports --platform=local which would be the platform of the client.

@vvoland
Copy link
Contributor Author

vvoland commented Apr 9, 2024

I added the local and remote special values.

remote is handled on the server (this PR), the local is handled in the CLI PR: docker/cli#4984.

@@ -36,6 +37,14 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options image.Pu
}
}

if options.Platform != "" {
if options.Platform == "local" {
query.Set("platform", platforms.DefaultString())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @tianon

More platform strings :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oof, and generated in client code to pass to the daemon 😭

local in the client is not local to the daemon 😭

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made local mean the platform local to the client for consistency with buildx --platform local.

That's why there's also remote (although I think I'd prefer to call it host) that is handled on the daemon side and defaults to the daemon platform:

func parsePlatform(platformStr string) (ocispec.Platform, error) {
if platformStr == "remote" {
return platforms.DefaultSpec(), nil
}
return platforms.Parse(platformStr)
}

Happy to hear your suggestions if you have better ideas 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely creeping into "build" vs "host" vs "target" confusion territory here -- what's the use case for building for the client-local host's platform when talking to a different daemon?

(Some of the context for this discussion is containerd/platforms#6, which actually makes platforms.DefaultString() unsafe to pass across process/machine boundaries like this, but I think the issue here is larger.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably because I'd mentioned it in an earlier review, but more "the client shouldn't have to know the daemon's platform... also should we do the buildx thing for client platform?"
It may well be not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the use case for building for the client-local host's platform when talking to a different daemon?

Personally, --platform local was useful to me for extracting binaries out of docker images to use on my host system, like:

echo 'FROM moby/moby-bin:master' | docker buildx build  --platform local -o type=local,dest=asdadsad -

So, mostly thinking of remote daemon or, a more down-to-earth case: DD on non-Linux platforms - in the above case I really don't care about the VM, I just want to get the image for my host.

However, local might be less useful than the remote/host/daemon for the interactive case, because most of the time I know what platform my client host is running on. Still, it could be useful in scripts.

Also, in case of a remote Docker host - users might want to push the image for the platform of their host:

$ docker -c beef tag <some-image> myregistry.local/some-image
$ docker -c beef push --platform local myregistry.local/some-image
$ docker run myregistry.local/some-image

But still, I think that's mostly useful for scripts, as in the interactive case it's highly likely that user would pass the platform explicitly anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, I'm fine with dropping these for now. We can always add them later if there's a real demand for it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would advise against using a single string field to represent the platform in the API. The first issue is highlighted here that it is too tempting to override it with special values. The second issue is that the string format is now a part of the API which it is not intended for. In containerd we only use the string for CLI or config, but the API always uses the structured form (https://github.com/containerd/containerd/blob/main/api/types/platform.proto) or its embedded in json in the original form (https://github.com/opencontainers/image-spec/blob/main/specs-go/v1/descriptor.go#L53).

The point of this change is to make the API endpoint behavior more explicit which filling in a variable "remote" value doesn't really accomplish. The way the current API behaves is already "--platform remote" and I worry by adding this flag here we end up changing the API to default to only push all platforms and end up recommending users to add "--platform remote". I think its best to keep the default to push whatever what pulled for that same image, so the equivalent of "--platform remote" if only a single platform was pulled or push all platforms if we can support pull all platforms. The case of pushing a single platform which differs from the remote after pulling multiple platforms seems like an edge case and is likely better handled with a separate command to edit an image with a new manifest.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the single string and added parameters that correspond to the fields of ocispec.Platform.

Copy link
Member

@cpuguy83 cpuguy83 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get an integration test for this?

This adds the common helper functions used by the recent
multiplatform-related PRs.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Translate os.ErrNotExist into cerrdefs.ErrNotExists

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Add utility that allows to construct an image with the specified
platforms.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
@vvoland
Copy link
Contributor Author

vvoland commented May 10, 2024

I added unit tests for the descriptor candidate selection code. Also tweaked the default behavior (no explicit platform selection) to choose a single-platform manifest in some cases.

@vvoland vvoland force-pushed the c8d-multiplatform-push branch 2 times, most recently from 9db1dc3 to e8d00ce Compare May 10, 2024 12:23
Comment on lines +164 to +172
{
name: "none requested on v7 daemon, v7 not available",
check: singleManifestSelected(linuxArmv5), // Should it fail, because v5 can't be pushed?

indexPlatforms: []platforms.Platform{linuxArm64, linuxArmv7, linuxArmv5},
deletePlatforms: []ocispec.Platform{linuxArmv7},
daemonPlatform: &linuxArmv7,
requestPlatform: nil,
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not immediately obvious to me what this is testing/emulating, sorry 😅

Isn't linuxArmv7 being in indexPlatforms meaning that the image does support v7? What is deletePlatforms?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deletePlatforms are the platforms that are missing locally. So the case is:

Image index supports: linux/arm64, linux/arm/v5 and linux/arm/v7.

Daemon is running on: linux/arm/v7

User has pulled: linux/arm64, linux/arm/v5 (image index minus the deletePlatforms).

Should the arm/v5 be pushed (as the daemon can run this image), even though it's not the native variant which the original image supports?

Add a OCI platform fields as parameters to the `POST /images/{id}/push`
that allow to specify a specific-platform manifest to be pushed instead
of the whole image index.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Comment on lines +125 to +126
p.OSVersion = form.Get("osversion")
p.OSFeatures = form["osfeatures"]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we should include these... They seem to be ignored by default containerd matcher though: https://github.com/containerd/platforms/blob/c1438e911ac7596426105350652fe267d0fb8a03/platforms.go#L154-L159

On the other hand, I can image an index having multiple images for different OSVersions/Features and user wanting to push only a selected one.

So IMO, we should either:

  1. Drop these parameters from the API
  2. Use a custom platform matcher that also checks these

WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be horrible to use the canonical JSON representation in a single field instead of multiple fields?

Copy link
Contributor Author

@vvoland vvoland May 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, yes, because we pass the parameters by encoding them in url query, not as a json-encoded body 🙈

That doesn't really solve the OSVersion and OSFeatures issue though :P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants