Repository: moby/moby
File: client/client.go (Go)

Code before:
//
// If a manual override is in place, either through the "DOCKER_API_VERSION"
// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
// with a fixed version ([WithVersion]), no negotiation is performed.
//
// If the API server's ping response does not contain an API version, or if the
// client did not get a successful ping response, it assumes it is connected with
// an old daemon that does not support API version negotiation, in which case it
// downgrades to the lowest supported API version.
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
	if !cli.manualOverride {
		// Avoid concurrent modification of version-related fields
		cli.negotiateLock.Lock()
		defer cli.negotiateLock.Unlock()

		ping, err := cli.Ping(ctx)
		if err != nil {
			// FIXME(thaJeztah): Ping returns an error when failing to connect to the API; we should not swallow the error here, and instead returning it.
			return
		}
		// FIXME(thaJeztah): we should not swallow the error here, and instead returning it.
		_ = cli.negotiateAPIVersion(ping.APIVersion)
	}
}

// NegotiateAPIVersionPing downgrades the client's API version to match the
// APIVersion in the ping response. If the API version in pingResponse is higher
// than the maximum version supported by the client, it uses the client's maximum
// version.
//
// If a manual override is in place, either through the "DOCKER_API_VERSION"
// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
// with a fixed version ([WithVersion]), no negotiation is performed.
//
// If the API server's ping response does not contain an API version, it falls
// back to the oldest API version supported.
func (cli *Client) NegotiateAPIVersionPing(pingResponse types.Ping) {
	if !cli.manualOverride {
		// Avoid concurrent modification of version-related fields
		cli.negotiateLock.Lock()
		defer cli.negotiateLock.Unlock()

		// FIXME(thaJeztah): we should not swallow the error here, and instead returning it.
		_ = cli.negotiateAPIVersion(pingResponse.APIVersion)
	}
}

// negotiateAPIVersion updates the version to match the API version from
// the ping response. It falls back to the lowest version supported if the
// API version is empty, or returns an error if the API version is lower than
// the lowest supported API version, in which case the version is not modified.

Review comment (suggestion):
I'd like to get rid of this function; it's used in the CLI currently to use a "Ping" result that was triggered in some code, that uses the Ping for feature detection, then (likely only to avoid pinging "twice") reuses the ping result as optimization, but we probably shouldn't, and maybe either optimise the "ping" to only happen once for the lifecycle of the client, or have a method to query features.

Code after:
		// Avoid concurrent modification of version-related fields
		cli.negotiateLock.Lock()
		defer cli.negotiateLock.Unlock()

		ping, err := cli.Ping(ctx)
		if err != nil {
			// FIXME(thaJeztah): Ping returns an error when failing to connect to the API; we should not swallow the error here, and instead returning it.
			return
		}
		// FIXME(thaJeztah): we should not swallow the error here, and instead returning it.
		_ = cli.negotiateAPIVersion(ping.APIVersion)
	}
}

// NegotiateAPIVersionPing downgrades the client's API version to match the
// APIVersion in the ping response. If the API version in pingResponse is higher
// than the maximum version supported by the client, it uses the client's maximum
// version.
//
// If a manual override is in place, either through the "DOCKER_API_VERSION"
// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
// with a fixed version ([WithVersion]), no negotiation is performed.
//
// If the API server's ping response does not contain an API version, it falls
// back to the oldest API version supported.
func (cli *Client) NegotiateAPIVersionPing(pingResponse types.Ping) {
	if !cli.manualOverride {
		// Avoid concurrent modification of version-related fields
		cli.negotiateLock.Lock()
		defer cli.negotiateLock.Unlock()

		// FIXME(thaJeztah): we should not swallow the error here, and instead returning it.
		_ = cli.negotiateAPIVersion(pingResponse.APIVersion)
	}
}

// negotiateAPIVersion updates the version to match the API version from
// the ping response. It falls back to the lowest version supported if the
// API version is empty, or returns an error if the API version is lower than
// the lowest supported API version, in which case the version is not modified.
func (cli *Client) negotiateAPIVersion(pingVersion string) error {
	pingVersion = strings.TrimPrefix(pingVersion, "v")
	if pingVersion == "" {
		// TODO(thaJeztah): consider returning an error on empty value or not falling back; see https://github.com/moby/moby/pull/51119#discussion_r2413148487
		pingVersion = fallbackAPIVersion
	} else if versions.LessThan(pingVersion, fallbackAPIVersion) {
		return cerrdefs.ErrInvalidArgument.WithMessage(fmt.Sprintf("API version %s is not supported by this client: the minimum supported API version is %s", pingVersion, fallbackAPIVersion))
	}

	// if the client is not initialized with a version, start with the latest supported version
	if cli.version == "" {