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

Remove containerd "platform" dependency from client #42623

Merged
merged 2 commits into from
Jul 31, 2021

Conversation

thaJeztah
Copy link
Member

Note: once / if #42464 is merged/accepted, we should consider replacing github.com/opencontainers/image-spec/specs-go/v1.Platform with our own ImagePlatform, to remove the OCI spec from the API (if possible). If we do so, we can also remove the formatPlatform function here (as it would be provided by ImagePlatform.String()

This removes some of the containerd dependencies from the client:

  • client: remove unused Platform field from configWrapper
    This field was added in 7a9cb29, but appears to be unused, so removing it.
  • client: remove containerd "platform" dependency

After this, there's no direct dependency on containerd, but the errdefs package still depends on containerd's errdefs. package. I'll have a look if we can refactor so that that doesn't end up in the client package.

@thaJeztah thaJeztah added area/api status/2-code-review kind/refactor PR's that refactor, or clean-up code labels Jul 12, 2021
@thaJeztah thaJeztah changed the title Remove containerd from client Remove containerd "platform" dependency from client Jul 12, 2021
@thaJeztah
Copy link
Member Author

@cpuguy83 @dims @AkihiroSuda PTAL

@dims
Copy link
Contributor

dims commented Jul 12, 2021

LGTM @thaJeztah thanks!

@thaJeztah
Copy link
Member Author

Added the cherry-pick label; I tried to keep this diff small/localised so that we are able to cherry-pick (helps kubernetes / cAdvisor)

// instead of "unknown" if no OS is present: https://github.com/containerd/containerd/blob/v1.5.2/platforms/platforms.go#L243-L263
func formatPlatform(platform *specs.Platform) string {
if platform == nil || platform.OS == "" {
return ""
Copy link
Member Author

Choose a reason for hiding this comment

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

Let me call out here that containerd's format would return "unknown" in this case. As far as I can see, we have no use for the "unknown" value, and would treat it equivalent to "" / not set, so I went for "don't set the parameter if we don't have anything useful". but perhaps I overlooked a specific scenario 👀 👀 👀 👀

Copy link
Member

Choose a reason for hiding this comment

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

So, buildkit supports just setting arch... weird that we'd return empty for empty OS.

Copy link
Member

Choose a reason for hiding this comment

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

Any thoughts on this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Looked up the parsing on the daemon side, and looks indeed that that should be supported; if only arch is specified, the parsing on the daemon side should handle that;

case 1:
// in this case, we will test that the value might be an OS, then look
// it up. If it is not known, we'll treat it as an architecture. Since
// we have very little information about the platform here, we are
// going to be a little more strict if we don't know about the argument
// value.
p.OS = normalizeOS(parts[0])
if isKnownOS(p.OS) {
// picks a default architecture
p.Architecture = runtime.GOARCH
if p.Architecture == "arm" && cpuVariant() != "v7" {
p.Variant = cpuVariant()
}
return p, nil
}
p.Architecture, p.Variant = normalizeArch(parts[0], "")
if p.Architecture == "arm" && p.Variant == "v7" {
p.Variant = ""
}
if isKnownArch(p.Architecture) {
p.OS = runtime.GOOS
return p, nil
}

@thaJeztah thaJeztah added this to the 21.xx milestone Jul 13, 2021
@tonistiigi
Copy link
Member

Why are we duplicating this code? Same for #42464 . If for example windows os-version starts to get encoded in the string value(there is an issue for that somewhere) there are two incompatible implementations. I don't see an issue with depending on the specs repo.

//
// Similar to containerd's platforms.Format(), but returns an empty string
// instead of "unknown" if no OS is present: https://github.com/containerd/containerd/blob/v1.5.2/platforms/platforms.go#L243-L263
func formatPlatform(platform *specs.Platform) string {
Copy link
Member

Choose a reason for hiding this comment

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

Seems like we really want path.Join(platform.OS, platform.Arch....)
This will handle empty values as desired, even for empty values.
https://play.golang.org/p/ztICsyudOrB

Not sure if you want to validate that is always arch is set if variant is also set.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, nice! TIL path.Join skips the empty values (I assumed it would keep those in). Yes, I like that.

Not sure if you want to validate that is always arch is set if variant is also set.

Yeah, perhaps stop at the first empty value (in which case path.Join would not be needed 🤔). We could keep it "simple" (for now), and just let the daemon handle this; wdyt?

I was writing down notes on serializing / deserialising and normalising of oci.Platform (to start a discussion on the OCI spec), because currently it looks like there's no standard approach anywhere.

So add to the "silliness"; I was looking at the CLI code YesterdayI: https://github.com/docker/cli/blob/v20.10.7/cli/command/container/create.go#L244-L249

	if opts.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") {
		p, err := platforms.Parse(opts.platform)
		if err != nil {
			return nil, errors.Wrap(err, "error parsing specified platform")
		}
		platform = &p

So, we:

  1. pass a string representation of platform (through the --platform flag)
  2. parse it on the CLI to convert it to an oci.Platform{}
  3. pass it to the client as argument
  4. convert it to a string
  5. send it over the API as a string
  6. parse the string and convert it to an oci.Platform{}
  7. pass it to the backend as argument

Looking at that, I think we should consider to either have the client (and cli) to not bother with oci.Platform{} at all (pass a string, and let the daemon deal with it), or make the API accept a oci.Platform{} (or equivalent) in the request.

@thaJeztah thaJeztah force-pushed the remove_containerd_from_client branch from db3e1e0 to 33a0513 Compare July 15, 2021 17:42
@thaJeztah
Copy link
Member Author

@cpuguy83 updated to use path.Join()

@thaJeztah
Copy link
Member Author

@cpuguy83 @tianon @tonistiigi ptal

This field was added in 7a9cb29,
but appears to be unused, so removing it.

Signed-off-by: Sebastiaan van Stijn <[email protected]>
@thaJeztah thaJeztah force-pushed the remove_containerd_from_client branch from 33a0513 to 9a27002 Compare July 29, 2021 21:30
@thaJeztah
Copy link
Member Author

@tianon @cpuguy83 updated, as discussed. PTAL

@thaJeztah thaJeztah force-pushed the remove_containerd_from_client branch from 9a27002 to 5f0703c Compare July 29, 2021 21:32
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.

LGTM

@dims
Copy link
Contributor

dims commented Jul 29, 2021

LGTM ... is that an unrelated flake in the failing test?

[2021-07-29T21:45:41.311Z] === FAIL: github.com/docker/docker/libnetwork/networkdb TestNetworkDBNodeJoinLeaveIteration (5.12s)
[2021-07-29T21:45:41.311Z]     networkdb_test.go:511: Network existence verification failed
[2021-07-29T21:45:41.311Z]     networkdb_test.go:513: The networkNodes list has to have be 2 instead of 1 - [cb9502840039]

@thaJeztah
Copy link
Member Author

Yes, should be unrelated (but looks like it's become more flaky); #42698

@tianon
Copy link
Member

tianon commented Jul 29, 2021

Generally seems OK to me, but I am concerned that this particular code path probably doesn't have very much coverage in any of our tests and this formatting is slightly different from what containerd does (so I guess there's some regression potential here?). 😕

Not sure there's a good solution to this, though.

@thaJeztah
Copy link
Member Author

Generally seems OK to me, but I am concerned that this particular code path probably doesn't have very much coverage in any of our tests and this formatting is slightly different from what containerd does

So, looking at the differences again, I think the only difference is that containerd returns unknown for an empty OS, and we create an empty string ("no platform passed").

The "unknown" case would produce an error if sent to the daemon (platforms.Parse() will fail;

package main

import (
        "fmt"
        "github.com/containerd/containerd/platforms"
)

func main() {
        p, err := platforms.Parse("unknown")
        if err != nil {
                fmt.Println(err)
        }
        fmt.Println(platforms.Format(p))

        p, err = platforms.Parse("armhf")
        if err != nil {
                fmt.Println(err)
        }
        fmt.Println(platforms.Format(p))
}
go run main.go
"unknown": unknown operating system or architecture: invalid argument
unknown
linux/arm

However (see #42623 (comment)), in the docker cli at least, the string as passed by the user is already parsed with platforms.Parse(), which means that (currently) OS will always be propagated, even if omitted by the user, and invalid values would already error before we reach this code (not taking other consumers into account).

This brings of course question: "should it?" (that's what my comment above was about: #42623 (comment)). Because currently, omitting the OS will result in the OS of the cli to be used as default 😂;

$ docker run --rm --platform=arm64 hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
109db8fad215: Pull complete
Digest: sha256:df5f5184104426b65967e016ff2ac0bfcd44ad7899ca3bbcf8e44e4461491a9e
Status: Downloaded newer image for hello-world:latest
docker: Error response from daemon: image with reference hello-world was found but does not match the specified platform: wanted darwin/arm64, actual: linux/arm64/v8.
See 'docker run --help'.

Something to look at in a follow-up; I think we should consider passing what's provided by the user as-is. The daemon (API) will produce an error if it's invalid, and the daemon can (if needed) fill in defaults.

@tianon
Copy link
Member

tianon commented Jul 30, 2021 via email

@thaJeztah thaJeztah merged commit 0b39cc2 into moby:master Jul 31, 2021
@thaJeztah thaJeztah deleted the remove_containerd_from_client branch July 31, 2021 16:45
@dims
Copy link
Contributor

dims commented Jul 31, 2021

YESSS!!! thanks @thaJeztah

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.

5 participants