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 11 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 `platform` parameters (JSON encoded OCI Platform type) that allows 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?

@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.

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

Copy link
Member

Choose a reason for hiding this comment

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

Could we use the string serialisation as was defined in containerd/platforms#6 ? (at least that would keep the option open to pass multiple as well I guess

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 not be against JSON in url-encoding though if parsing that makes things less ambiguous (we already do this for filters, which admitted are a bit ugly, but in the end, API is not for humans)

Copy link
Member

Choose a reason for hiding this comment

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

I think we specifically wanted to not use that string format in the API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was my first implementation, see: #47679 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to JSON encoding

@vvoland vvoland modified the milestones: v-future, 27.0.0 May 14, 2024
@vvoland vvoland force-pushed the c8d-multiplatform-push branch 3 times, most recently from 9e5950a to 827f61a Compare May 20, 2024 13:20
@vvoland
Copy link
Contributor Author

vvoland commented May 20, 2024

Current state:
image

If docker pull would also pull manifests for all platforms, pushing a full index (without all data locally) would also just work with the cross-repo mount:
image

And would fallback to the single-manifest if cross-repo mounts are not possible:
image

vvoland added 11 commits May 21, 2024 12:04
This adds the common helper functions used by the recent
multiplatform-related PRs.

Signed-off-by: Paweł Gronowski <[email protected]>
Translate os.ErrNotExist into cerrdefs.ErrNotExists

Signed-off-by: Paweł Gronowski <[email protected]>
Add utility that allows to construct an image with the specified
platforms.

Signed-off-by: Paweł Gronowski <[email protected]>
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 <[email protected]>
Signed-off-by: Paweł Gronowski <[email protected]>
When no platform was requested and pushing whole index failed, fallback
to pushing a platform-specific manifest with a best candidate (if it's
possible to choose one).

Signed-off-by: Paweł Gronowski <[email protected]>
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

6 participants