From 860307c4ea5349514a8a3f25decd3e027a16d4ee Mon Sep 17 00:00:00 2001 From: Austin Vazquez Date: Mon, 20 Oct 2025 14:04:38 -0500 Subject: [PATCH] client: refactor ServerVersion to return ServerVersionResult Co-Authored-By: Claude Signed-off-by: Austin Vazquez Signed-off-by: Sebastiaan van Stijn --- client/client_interfaces.go | 3 +- client/version.go | 53 ++++++++++++++++--- integration-cli/requirements_test.go | 4 +- .../network/bridge/bridge_linux_test.go | 2 +- integration/plugin/authz/authz_plugin_test.go | 14 ++--- integration/system/version_test.go | 38 +++++++++---- internal/testutil/environment/environment.go | 40 +++++++------- internal/testutil/fakestorage/fixtures.go | 2 +- .../moby/moby/client/client_interfaces.go | 3 +- vendor/github.com/moby/moby/client/version.go | 53 ++++++++++++++++--- 10 files changed, 156 insertions(+), 56 deletions(-) diff --git a/client/client_interfaces.go b/client/client_interfaces.go index 9e7d4c1641..b599b595bb 100644 --- a/client/client_interfaces.go +++ b/client/client_interfaces.go @@ -5,7 +5,6 @@ import ( "io" "net" - "github.com/moby/moby/api/types" "github.com/moby/moby/api/types/network" "github.com/moby/moby/api/types/system" ) @@ -27,7 +26,7 @@ type stableAPIClient interface { VolumeAPIClient ClientVersion() string DaemonHost() string - ServerVersion(ctx context.Context) (types.Version, error) + ServerVersion(ctx context.Context, options ServerVersionOptions) (ServerVersionResult, error) HijackDialer Dialer() func(context.Context) (net.Conn, error) Close() error diff --git a/client/version.go b/client/version.go index 46c70b8ad5..acc82fe594 100644 --- a/client/version.go +++ b/client/version.go @@ -7,15 +7,56 @@ import ( "github.com/moby/moby/api/types" ) -// ServerVersion returns information of the docker client and server host. -func (cli *Client) ServerVersion(ctx context.Context) (types.Version, error) { +// ServerVersionOptions specifies options for the server version request. +type ServerVersionOptions struct { + // Currently no options are supported. +} + +// ServerVersionResult contains information about the Docker server host. +type ServerVersionResult struct { + // Platform is the platform (product name) the server is running on. + Platform PlatformInfo + + // APIVersion is the highest API version supported by the server. + APIVersion string + + // MinAPIVersion is the minimum API version the server supports. + MinAPIVersion string + + // Components contains version information for the components making + // up the server. Information in this field is for informational + // purposes, and not part of the API contract. + Components []types.ComponentVersion +} + +// PlatformInfo holds information about the platform (product name) the +// server is running on. +type PlatformInfo struct { + // Name is the name of the platform (for example, "Docker Engine - Community", + // or "Docker Desktop 4.49.0 (208003)") + Name string +} + +// ServerVersion returns information of the Docker server host. +func (cli *Client) ServerVersion(ctx context.Context, _ ServerVersionOptions) (ServerVersionResult, error) { resp, err := cli.get(ctx, "/version", nil, nil) defer ensureReaderClosed(resp) if err != nil { - return types.Version{}, err + return ServerVersionResult{}, err } - var server types.Version - err = json.NewDecoder(resp.Body).Decode(&server) - return server, err + var v types.Version + err = json.NewDecoder(resp.Body).Decode(&v) + if err != nil { + return ServerVersionResult{}, err + } + + return ServerVersionResult{ + Platform: PlatformInfo{ + Name: v.Platform.Name, + }, + APIVersion: v.APIVersion, + MinAPIVersion: v.MinAPIVersion, + Components: v.Components, + }, nil } diff --git a/integration-cli/requirements_test.go b/integration-cli/requirements_test.go index fc4ed832be..afc78ae498 100644 --- a/integration-cli/requirements_test.go +++ b/integration-cli/requirements_test.go @@ -43,11 +43,11 @@ func OnlyDefaultNetworks(ctx context.Context) bool { } func IsAmd64() bool { - return testEnv.DaemonVersion.Arch == "amd64" + return testEnv.DaemonInfo.Architecture == "amd64" } func NotPpc64le() bool { - return testEnv.DaemonVersion.Arch != "ppc64le" + return testEnv.DaemonInfo.Architecture != "ppc64le" } func UnixCli() bool { diff --git a/integration/network/bridge/bridge_linux_test.go b/integration/network/bridge/bridge_linux_test.go index b55f79a8db..ba38f47cbe 100644 --- a/integration/network/bridge/bridge_linux_test.go +++ b/integration/network/bridge/bridge_linux_test.go @@ -953,7 +953,7 @@ func TestEmptyPortBindingsBC(t *testing.T) { // Skip this subtest if the daemon doesn't support the client version. // TODO(aker): drop this once the Engine supports API version >= 1.53 - _, err := apiClient.ServerVersion(ctx) + _, err := apiClient.ServerVersion(ctx, client.ServerVersionOptions{}) if err != nil && strings.Contains(err.Error(), fmt.Sprintf("client version %s is too new", version)) { t.Skipf("requires API %s", version) } diff --git a/integration/plugin/authz/authz_plugin_test.go b/integration/plugin/authz/authz_plugin_test.go index 2787093930..516228f814 100644 --- a/integration/plugin/authz/authz_plugin_test.go +++ b/integration/plugin/authz/authz_plugin_test.go @@ -106,7 +106,7 @@ func TestAuthZPluginAllowRequest(t *testing.T) { assertURIRecorded(t, ctrl.requestsURIs, "/containers/create") assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", cID)) - _, err := c.ServerVersion(ctx) + _, err := c.ServerVersion(ctx, client.ServerVersionOptions{}) assert.NilError(t, err) assert.Equal(t, 1, ctrl.versionReqCount) assert.Equal(t, 1, ctrl.versionResCount) @@ -137,7 +137,7 @@ func TestAuthZPluginTLS(t *testing.T) { c, err := newTLSAPIClient(testDaemonHTTPSAddr, cacertPath, clientCertPath, clientKeyPath) assert.NilError(t, err) - _, err = c.ServerVersion(ctx) + _, err = c.ServerVersion(ctx, client.ServerVersionOptions{}) assert.NilError(t, err) assert.Equal(t, "client", ctrl.reqUser) @@ -165,7 +165,7 @@ func TestAuthZPluginDenyRequest(t *testing.T) { c := d.NewClientT(t) // Ensure command is blocked - _, err := c.ServerVersion(ctx) + _, err := c.ServerVersion(ctx, client.ServerVersionOptions{}) assert.Assert(t, err != nil) assert.Equal(t, 1, ctrl.versionReqCount) assert.Equal(t, 0, ctrl.versionResCount) @@ -211,7 +211,7 @@ func TestAuthZPluginDenyResponse(t *testing.T) { c := d.NewClientT(t) // Ensure command is blocked - _, err := c.ServerVersion(ctx) + _, err := c.ServerVersion(ctx, client.ServerVersionOptions{}) assert.Assert(t, err != nil) assert.Equal(t, 1, ctrl.versionReqCount) assert.Equal(t, 1, ctrl.versionResCount) @@ -304,7 +304,7 @@ func TestAuthZPluginErrorResponse(t *testing.T) { c := d.NewClientT(t) // Ensure command is blocked - _, err := c.ServerVersion(ctx) + _, err := c.ServerVersion(ctx, client.ServerVersionOptions{}) assert.Assert(t, err != nil) assert.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage), err.Error()) } @@ -317,7 +317,7 @@ func TestAuthZPluginErrorRequest(t *testing.T) { c := d.NewClientT(t) // Ensure command is blocked - _, err := c.ServerVersion(ctx) + _, err := c.ServerVersion(ctx, client.ServerVersionOptions{}) assert.Assert(t, err != nil) assert.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage), err.Error()) } @@ -331,7 +331,7 @@ func TestAuthZPluginEnsureNoDuplicatePluginRegistration(t *testing.T) { c := d.NewClientT(t) - _, err := c.ServerVersion(ctx) + _, err := c.ServerVersion(ctx, client.ServerVersionOptions{}) assert.NilError(t, err) // assert plugin is only called once.. diff --git a/integration/system/version_test.go b/integration/system/version_test.go index 53abc9cb83..0c3a3e7680 100644 --- a/integration/system/version_test.go +++ b/integration/system/version_test.go @@ -6,6 +6,7 @@ import ( "strings" "testing" + "github.com/moby/moby/api/types" "github.com/moby/moby/client" "github.com/moby/moby/v2/internal/testutil/request" "gotest.tools/v3/assert" @@ -16,26 +17,45 @@ func TestVersion(t *testing.T) { ctx := setupTest(t) apiClient := testEnv.APIClient() - version, err := apiClient.ServerVersion(ctx) + version, err := apiClient.ServerVersion(ctx, client.ServerVersionOptions{}) assert.NilError(t, err) + assert.Check(t, len(version.Components) > 0, "expected at least one component in version.Components") - assert.Check(t, version.APIVersion != "") - assert.Check(t, version.Version != "") - assert.Check(t, version.MinAPIVersion != "") - assert.Check(t, is.Equal(testEnv.DaemonInfo.ExperimentalBuild, version.Experimental)) - assert.Check(t, is.Equal(testEnv.DaemonInfo.OSType, version.Os)) + var engine types.ComponentVersion + var found bool + + for _, comp := range version.Components { + if comp.Name == "Engine" { + engine = comp + found = true + break + } + } + + assert.Check(t, found, "Engine component not found in version.Components") + assert.Equal(t, engine.Name, "Engine") + assert.Check(t, engine.Version != "") + assert.Equal(t, engine.Details["ApiVersion"], version.APIVersion) + assert.Equal(t, engine.Details["MinAPIVersion"], version.MinAPIVersion) + assert.Check(t, is.Equal(testEnv.DaemonInfo.OSType, engine.Details["Os"])) + + experimentalStr := engine.Details["Experimental"] + experimentalBool, err := strconv.ParseBool(experimentalStr) + assert.NilError(t, err, "Experimental field in Engine details is not a valid boolean string") + assert.Equal(t, testEnv.DaemonInfo.ExperimentalBuild, experimentalBool) } func TestAPIClientVersionOldNotSupported(t *testing.T) { ctx := setupTest(t) - major, minor, _ := strings.Cut(testEnv.DaemonVersion.MinAPIVersion, ".") + minApiVersion := testEnv.DaemonMinAPIVersion + major, minor, _ := strings.Cut(minApiVersion, ".") vMinInt, err := strconv.Atoi(minor) assert.NilError(t, err) vMinInt-- version := fmt.Sprintf("%s.%d", major, vMinInt) apiClient := request.NewAPIClient(t, client.WithVersion(version)) - expectedErrorMessage := fmt.Sprintf("Error response from daemon: client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", version, testEnv.DaemonVersion.MinAPIVersion) - _, err = apiClient.ServerVersion(ctx) + expectedErrorMessage := fmt.Sprintf("Error response from daemon: client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", version, minApiVersion) + _, err = apiClient.ServerVersion(ctx, client.ServerVersionOptions{}) assert.Error(t, err, expectedErrorMessage) } diff --git a/internal/testutil/environment/environment.go b/internal/testutil/environment/environment.go index 98695269ad..0ba3ea3d51 100644 --- a/internal/testutil/environment/environment.go +++ b/internal/testutil/environment/environment.go @@ -9,7 +9,6 @@ import ( "strings" "testing" - "github.com/moby/moby/api/types" "github.com/moby/moby/api/types/system" "github.com/moby/moby/client" "github.com/moby/moby/v2/internal/testutil/fixtures/load" @@ -20,11 +19,11 @@ import ( // Execution contains information about the current test execution and daemon // under test type Execution struct { - client client.APIClient - DaemonInfo system.Info - DaemonVersion types.Version - PlatformDefaults PlatformDefaults - protectedElements protectedElements + client client.APIClient + DaemonInfo system.Info + DaemonMinAPIVersion string + PlatformDefaults PlatformDefaults + protectedElements protectedElements } // PlatformDefaults are defaults values for the platform of the daemon under test @@ -46,22 +45,27 @@ func New(ctx context.Context) (*Execution, error) { // FromClient creates a new Execution environment from the passed in client func FromClient(ctx context.Context, c *client.Client) (*Execution, error) { + _, err := c.Ping(ctx, client.PingOptions{NegotiateAPIVersion: true}) + if err != nil { + return nil, errors.Wrapf(err, "failed to ping daemon to negotiate api version") + } + result, err := c.Info(ctx, client.InfoOptions{}) if err != nil { return nil, errors.Wrapf(err, "failed to get info from daemon") } - info := result.Info - v, err := c.ServerVersion(context.Background()) + + version, err := c.ServerVersion(ctx, client.ServerVersionOptions{}) if err != nil { - return nil, errors.Wrapf(err, "failed to get version info from daemon") + return nil, errors.Wrapf(err, "failed to get version from daemon") } return &Execution{ - client: c, - DaemonInfo: info, - DaemonVersion: v, - PlatformDefaults: getPlatformDefaults(info), - protectedElements: newProtectedElements(), + client: c, + DaemonInfo: result.Info, + DaemonMinAPIVersion: version.MinAPIVersion, + PlatformDefaults: getPlatformDefaults(result.Info), + protectedElements: newProtectedElements(), }, nil } @@ -129,11 +133,7 @@ func (e *Execution) IsRemoteDaemon() bool { // DaemonAPIVersion returns the negotiated daemon api version func (e *Execution) DaemonAPIVersion() string { - version, err := e.APIClient().ServerVersion(context.TODO()) - if err != nil { - return "" - } - return version.APIVersion + return e.APIClient().ClientVersion() } // Print the execution details to stdout @@ -228,7 +228,7 @@ func (e *Execution) GitHubActions() bool { // NotAmd64 returns true if the daemon's architecture is not amd64 func (e *Execution) NotAmd64() bool { - return e.DaemonVersion.Arch != "amd64" + return e.DaemonInfo.Architecture != "amd64" } // FirewallBackendDriver returns the value of FirewallBackend.Driver from diff --git a/internal/testutil/fakestorage/fixtures.go b/internal/testutil/fakestorage/fixtures.go index 9e55ea8663..63ec44db8d 100644 --- a/internal/testutil/fakestorage/fixtures.go +++ b/internal/testutil/fakestorage/fixtures.go @@ -31,7 +31,7 @@ func ensureHTTPServerImage(t testing.TB) { if goos == "" { goos = "linux" } - goarch := testEnv.DaemonVersion.Arch + goarch := testEnv.DaemonInfo.Architecture if goarch == "" { goarch = "amd64" } diff --git a/vendor/github.com/moby/moby/client/client_interfaces.go b/vendor/github.com/moby/moby/client/client_interfaces.go index 9e7d4c1641..b599b595bb 100644 --- a/vendor/github.com/moby/moby/client/client_interfaces.go +++ b/vendor/github.com/moby/moby/client/client_interfaces.go @@ -5,7 +5,6 @@ import ( "io" "net" - "github.com/moby/moby/api/types" "github.com/moby/moby/api/types/network" "github.com/moby/moby/api/types/system" ) @@ -27,7 +26,7 @@ type stableAPIClient interface { VolumeAPIClient ClientVersion() string DaemonHost() string - ServerVersion(ctx context.Context) (types.Version, error) + ServerVersion(ctx context.Context, options ServerVersionOptions) (ServerVersionResult, error) HijackDialer Dialer() func(context.Context) (net.Conn, error) Close() error diff --git a/vendor/github.com/moby/moby/client/version.go b/vendor/github.com/moby/moby/client/version.go index 46c70b8ad5..acc82fe594 100644 --- a/vendor/github.com/moby/moby/client/version.go +++ b/vendor/github.com/moby/moby/client/version.go @@ -7,15 +7,56 @@ import ( "github.com/moby/moby/api/types" ) -// ServerVersion returns information of the docker client and server host. -func (cli *Client) ServerVersion(ctx context.Context) (types.Version, error) { +// ServerVersionOptions specifies options for the server version request. +type ServerVersionOptions struct { + // Currently no options are supported. +} + +// ServerVersionResult contains information about the Docker server host. +type ServerVersionResult struct { + // Platform is the platform (product name) the server is running on. + Platform PlatformInfo + + // APIVersion is the highest API version supported by the server. + APIVersion string + + // MinAPIVersion is the minimum API version the server supports. + MinAPIVersion string + + // Components contains version information for the components making + // up the server. Information in this field is for informational + // purposes, and not part of the API contract. + Components []types.ComponentVersion +} + +// PlatformInfo holds information about the platform (product name) the +// server is running on. +type PlatformInfo struct { + // Name is the name of the platform (for example, "Docker Engine - Community", + // or "Docker Desktop 4.49.0 (208003)") + Name string +} + +// ServerVersion returns information of the Docker server host. +func (cli *Client) ServerVersion(ctx context.Context, _ ServerVersionOptions) (ServerVersionResult, error) { resp, err := cli.get(ctx, "/version", nil, nil) defer ensureReaderClosed(resp) if err != nil { - return types.Version{}, err + return ServerVersionResult{}, err } - var server types.Version - err = json.NewDecoder(resp.Body).Decode(&server) - return server, err + var v types.Version + err = json.NewDecoder(resp.Body).Decode(&v) + if err != nil { + return ServerVersionResult{}, err + } + + return ServerVersionResult{ + Platform: PlatformInfo{ + Name: v.Platform.Name, + }, + APIVersion: v.APIVersion, + MinAPIVersion: v.MinAPIVersion, + Components: v.Components, + }, nil }