Merge pull request #51233 from austinvazquez/refactor-client-version

client: refactor ServerVersion to return ServerVersionResult
This commit is contained in:
Sebastiaan van Stijn
2025-10-30 17:59:05 +01:00
committed by GitHub
10 changed files with 156 additions and 56 deletions

View File

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

View File

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

View File

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

View File

@@ -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)
}

View File

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

View File

@@ -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)
}

View File

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

View File

@@ -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"
}

View File

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

View File

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