vendor: github.com/moby/moby/api master, moby/client master

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-10-30 09:41:58 +01:00
parent b8b4f54a89
commit 8767904ae8
41 changed files with 369 additions and 204 deletions

View File

@ -30,7 +30,7 @@ type EndpointSettings struct {
// MacAddress may be used to specify a MAC address when the container is created.
// Once the container is running, it becomes operational data (it may contain a
// generated address).
MacAddress string
MacAddress HardwareAddr
IPPrefixLen int
IPv6Gateway netip.Addr
GlobalIPv6Address netip.Addr

View File

@ -24,7 +24,7 @@ type EndpointResource struct {
// mac address
// Example: 02:42:ac:13:00:02
MacAddress string `json:"MacAddress"`
MacAddress HardwareAddr `json:"MacAddress"`
// IPv4 address
// Example: 172.19.0.2/16

View File

@ -0,0 +1,37 @@
package network
import (
"encoding"
"fmt"
"net"
)
// A HardwareAddr represents a physical hardware address.
// It implements [encoding.TextMarshaler] and [encoding.TextUnmarshaler]
// in the absence of go.dev/issue/29678.
type HardwareAddr net.HardwareAddr
var _ encoding.TextMarshaler = (HardwareAddr)(nil)
var _ encoding.TextUnmarshaler = (*HardwareAddr)(nil)
var _ fmt.Stringer = (HardwareAddr)(nil)
func (m *HardwareAddr) UnmarshalText(text []byte) error {
if len(text) == 0 {
*m = nil
return nil
}
hw, err := net.ParseMAC(string(text))
if err != nil {
return err
}
*m = HardwareAddr(hw)
return nil
}
func (m HardwareAddr) MarshalText() ([]byte, error) {
return []byte(net.HardwareAddr(m).String()), nil
}
func (m HardwareAddr) String() string {
return net.HardwareAddr(m).String()
}

View File

@ -0,0 +1,58 @@
package system
// VersionResponse contains information about the Docker server host.
// GET "/version"
type VersionResponse struct {
// Platform is the platform (product name) the server is running on.
Platform PlatformInfo `json:",omitempty"`
// Version is the version of the daemon.
Version string
// APIVersion is the highest API version supported by the server.
APIVersion string `json:"ApiVersion"`
// MinAPIVersion is the minimum API version the server supports.
MinAPIVersion string `json:"MinAPIVersion,omitempty"`
// Os is the operating system the server runs on.
Os string
// Arch is the hardware architecture the server runs on.
Arch 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 []ComponentVersion `json:",omitempty"`
// The following fields are deprecated, they relate to the Engine component and are kept for backwards compatibility
GitCommit string `json:",omitempty"`
GoVersion string `json:",omitempty"`
KernelVersion string `json:",omitempty"`
Experimental bool `json:",omitempty"`
BuildTime string `json:",omitempty"`
}
// 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
}
// ComponentVersion describes the version information for a specific component.
type ComponentVersion struct {
Name string
Version string
// Details contains Key/value pairs of strings with additional information
// about the component. These values are intended for informational purposes
// only, and their content is not defined, and not part of the API
// specification.
//
// These messages can be printed by the client as information to the user.
Details map[string]string `json:",omitempty"`
}

View File

@ -16,39 +16,3 @@ const (
// MediaTypeJSONSequence is the MIME-Type for JSON Text Sequences (RFC7464).
MediaTypeJSONSequence = "application/json-seq"
)
// ComponentVersion describes the version information for a specific component.
type ComponentVersion struct {
Name string
Version string
Details map[string]string `json:",omitempty"`
}
// Version contains response of Engine API:
// GET "/version"
type Version struct {
Platform struct{ Name string } `json:",omitempty"`
Components []ComponentVersion `json:",omitempty"`
// The following fields are deprecated, they relate to the Engine component and are kept for backwards compatibility
Version string
APIVersion string `json:"ApiVersion"`
MinAPIVersion string `json:"MinAPIVersion,omitempty"`
GitCommit string
GoVersion string
Os string
Arch string
KernelVersion string `json:",omitempty"`
Experimental bool `json:",omitempty"`
BuildTime string `json:",omitempty"`
}
// PushResult contains the tag, manifest digest, and manifest size from the
// push. It's used to signal this information to the trust code in the client
// so it can sign the manifest if necessary.
type PushResult struct {
Tag string
Digest string
Size int
}

View File

@ -23,7 +23,7 @@ import (
)
func main() {
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
apiClient, err := client.New(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
}

View File

@ -6,7 +6,7 @@ https://docs.docker.com/reference/api/engine/
# Usage
You use the library by constructing a client object using [NewClientWithOpts]
You use the library by constructing a client object using [New]
and calling methods on it. The client can be configured from environment
variables by passing the [FromEnv] option, and the [WithAPIVersionNegotiation]
option to allow downgrading the API version used when connecting with an older
@ -30,7 +30,7 @@ For example, to list running containers (the equivalent of "docker ps"):
// for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does
// API-version negotiation to allow downgrading the API version
// when connecting with an older daemon version.
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
apiClient, err := client.New(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
log.Fatal(err)
}
@ -160,7 +160,14 @@ func CheckRedirect(_ *http.Request, via []*http.Request) error {
return ErrRedirect
}
// NewClientWithOpts initializes a new API client with a default HTTPClient, and
// NewClientWithOpts initializes a new API client.
//
// Deprecated: use New. This function will be removed in the next release.
func NewClientWithOpts(ops ...Opt) (*Client, error) {
return New(ops...)
}
// New initializes a new API client with a default HTTPClient, and
// default API host and version. It also initializes the custom HTTP headers to
// add to each request.
//
@ -170,11 +177,11 @@ func CheckRedirect(_ *http.Request, via []*http.Request) error {
// itself with values from environment variables ([FromEnv]), and has automatic
// API version negotiation enabled ([WithAPIVersionNegotiation]).
//
// cli, err := client.NewClientWithOpts(
// cli, err := client.New(
// client.FromEnv,
// client.WithAPIVersionNegotiation(),
// )
func NewClientWithOpts(ops ...Opt) (*Client, error) {
func New(ops ...Opt) (*Client, error) {
hostURL, err := ParseHostURL(DefaultDockerHost)
if err != nil {
return nil, err

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

@ -8,8 +8,8 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
)
@ -154,8 +154,12 @@ func (cli *Client) imageBuildOptionsToQuery(_ context.Context, options ImageBuil
if options.SessionID != "" {
query.Set("session", options.SessionID)
}
if options.Platform != "" {
query.Set("platform", strings.ToLower(options.Platform))
if len(options.Platforms) > 0 {
if len(options.Platforms) > 1 {
// TODO(thaJeztah): update API spec and add equivalent check on the daemon. We need this still for older daemons, which would ignore it.
return query, cerrdefs.ErrInvalidArgument.WithMessage("specifying multiple platforms is not yet supported")
}
query.Set("platform", formatPlatform(options.Platforms[0]))
}
if options.BuildID != "" {
query.Set("buildid", options.BuildID)

View File

@ -6,6 +6,7 @@ import (
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/registry"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageBuildOptions holds the information
@ -50,7 +51,9 @@ type ImageBuildOptions struct {
ExtraHosts []string // List of extra hosts
Target string
SessionID string
Platform string
// Platforms selects the platforms to build the image for. Multiple platforms
// can be provided if the daemon supports multi-platform builds.
Platforms []ocispec.Platform
// Version specifies the version of the underlying builder to use
Version build.BuilderVersion
// BuildID is an optional identifier that can be passed together with the

View File

@ -4,8 +4,8 @@ import (
"context"
"net/http"
"net/url"
"strings"
cerrdefs "github.com/containerd/errdefs"
"github.com/distribution/reference"
"github.com/moby/moby/api/types/registry"
)
@ -21,8 +21,12 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti
query := url.Values{}
query.Set("fromImage", ref.Name())
query.Set("tag", getAPITagFromNamedRef(ref))
if options.Platform != "" {
query.Set("platform", strings.ToLower(options.Platform))
if len(options.Platforms) > 0 {
if len(options.Platforms) > 1 {
// TODO(thaJeztah): update API spec and add equivalent check on the daemon. We need this still for older daemons, which would ignore it.
return ImageCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("specifying multiple platforms is not yet supported")
}
query.Set("platform", formatPlatform(options.Platforms[0]))
}
resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth))
if err != nil {

View File

@ -1,11 +1,18 @@
package client
import "io"
import (
"io"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageCreateOptions holds information to create images.
type ImageCreateOptions struct {
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
Platform string // Platform is the target platform of the image if it needs to be pulled from the registry.
// Platforms specifies the platforms to platform of the image if it needs
// to be pulled from the registry. Multiple platforms can be provided
// if the daemon supports multi-platform pulls.
Platforms []ocispec.Platform
}
// ImageCreateResult holds the response body returned by the daemon for image create.

View File

@ -3,7 +3,6 @@ package client
import (
"context"
"net/url"
"strings"
"github.com/distribution/reference"
)
@ -31,8 +30,9 @@ func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, re
if options.Message != "" {
query.Set("message", options.Message)
}
if options.Platform != "" {
query.Set("platform", strings.ToLower(options.Platform))
if p := formatPlatform(options.Platform); p != "unknown" {
// TODO(thaJeztah): would we ever support mutiple platforms here? (would require multiple rootfs tars as well?)
query.Set("platform", p)
}
for _, change := range options.Changes {
query.Add("changes", change)

View File

@ -2,6 +2,8 @@ package client
import (
"io"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageImportSource holds source information for ImageImport
@ -12,10 +14,10 @@ type ImageImportSource struct {
// ImageImportOptions holds information to import images from the client host.
type ImageImportOptions struct {
Tag string // Tag is the name to tag this image with. This attribute is deprecated.
Message string // Message is the message to tag the image with
Changes []string // Changes are the raw changes to apply to this image
Platform string // Platform is the target platform of the image
Tag string // Tag is the name to tag this image with. This attribute is deprecated.
Message string // Message is the message to tag the image with
Changes []string // Changes are the raw changes to apply to this image
Platform ocispec.Platform // Platform is the target platform of the image
}
// ImageImportResult holds the response body returned by the daemon for image import.

View File

@ -5,17 +5,16 @@ import (
"io"
"iter"
"net/url"
"strings"
cerrdefs "github.com/containerd/errdefs"
"github.com/distribution/reference"
"github.com/moby/moby/api/types/jsonstream"
"github.com/moby/moby/client/internal"
"github.com/moby/moby/client/pkg/jsonmessage"
)
type ImagePullResponse interface {
io.ReadCloser
JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error]
JSONMessages(ctx context.Context) iter.Seq2[jsonstream.Message, error]
Wait(ctx context.Context) error
}
@ -44,10 +43,13 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePu
if !options.All {
query.Set("tag", getAPITagFromNamedRef(ref))
}
if options.Platform != "" {
query.Set("platform", strings.ToLower(options.Platform))
if len(options.Platforms) > 0 {
if len(options.Platforms) > 1 {
// TODO(thaJeztah): update API spec and add equivalent check on the daemon. We need this still for older daemons, which would ignore it.
return nil, cerrdefs.ErrInvalidArgument.WithMessage("specifying multiple platforms is not yet supported")
}
query.Set("platform", formatPlatform(options.Platforms[0]))
}
resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth))
if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil {
resp, err = cli.tryImageCreate(ctx, query, options.PrivilegeFunc)

View File

@ -2,6 +2,8 @@ package client
import (
"context"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImagePullOptions holds information to pull images.
@ -16,5 +18,8 @@ type ImagePullOptions struct {
//
// For details, refer to [github.com/moby/moby/api/types/registry.RequestAuthConfig].
PrivilegeFunc func(context.Context) (string, error)
Platform string
// Platforms selects the platforms to pull. Multiple platforms can be
// specified if the image ia a multi-platform image.
Platforms []ocispec.Platform
}

View File

@ -12,14 +12,14 @@ import (
cerrdefs "github.com/containerd/errdefs"
"github.com/distribution/reference"
"github.com/moby/moby/api/types/jsonstream"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/client/internal"
"github.com/moby/moby/client/pkg/jsonmessage"
)
type ImagePushResponse interface {
io.ReadCloser
JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error]
JSONMessages(ctx context.Context) iter.Seq2[jsonstream.Message, error]
Wait(ctx context.Context) error
}

View File

@ -8,7 +8,7 @@ import (
"iter"
"sync"
"github.com/moby/moby/client/pkg/jsonmessage"
"github.com/moby/moby/api/types/jsonstream"
)
func NewJSONMessageStream(rc io.ReadCloser) stream {
@ -44,15 +44,15 @@ func (r stream) Close() error {
// JSONMessages decodes the response stream as a sequence of JSONMessages.
// if stream ends or context is cancelled, the underlying [io.Reader] is closed.
func (r stream) JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error] {
func (r stream) JSONMessages(ctx context.Context) iter.Seq2[jsonstream.Message, error] {
context.AfterFunc(ctx, func() {
_ = r.Close()
})
dec := json.NewDecoder(r)
return func(yield func(jsonmessage.JSONMessage, error) bool) {
return func(yield func(jsonstream.Message, error) bool) {
defer r.Close()
for {
var jm jsonmessage.JSONMessage
var jm jsonstream.Message
err := dec.Decode(&jm)
if errors.Is(err, io.EOF) {
break

View File

@ -14,28 +14,14 @@ import (
"github.com/moby/term"
)
var timeNow = time.Now // For overriding in tests.
// RFC3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to
// ensure the formatted time isalways the same number of characters.
const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
// JSONProgress describes a progress message in a JSON stream.
type JSONProgress struct {
jsonstream.Progress
// terminalFd is the fd of the current terminal, if any. It is used
// to get the terminal width.
terminalFd uintptr
// nowFunc is used to override the current time in tests.
nowFunc func() time.Time
// winSize is used to override the terminal width in tests.
winSize int
}
func (p *JSONProgress) String() string {
func RenderTUIProgress(p jsonstream.Progress, width uint16) string {
var (
width = p.width()
pbBox string
numbersBox string
)
@ -89,7 +75,7 @@ func (p *JSONProgress) String() string {
var timeLeftBox string
if width > 50 {
if p.Current > 0 && p.Start > 0 && percentage < 50 {
fromStart := p.now().Sub(time.Unix(p.Start, 0))
fromStart := timeNow().UTC().Sub(time.Unix(p.Start, 0))
perEntry := fromStart / time.Duration(p.Current)
left := time.Duration(p.Total-p.Current) * perEntry
timeLeftBox = " " + left.Round(time.Second).String()
@ -98,40 +84,6 @@ func (p *JSONProgress) String() string {
return pbBox + numbersBox + timeLeftBox
}
// now returns the current time in UTC, but can be overridden in tests
// by setting JSONProgress.nowFunc to a custom function.
func (p *JSONProgress) now() time.Time {
if p.nowFunc != nil {
return p.nowFunc()
}
return time.Now().UTC()
}
// width returns the current terminal's width, but can be overridden
// in tests by setting JSONProgress.winSize to a non-zero value.
func (p *JSONProgress) width() int {
if p.winSize != 0 {
return p.winSize
}
ws, err := term.GetWinsize(p.terminalFd)
if err == nil {
return int(ws.Width)
}
return 200
}
// JSONMessage defines a message struct. It describes
// the created time, where it from, status, ID of the
// message. It's used for docker events.
type JSONMessage struct {
Stream string `json:"stream,omitempty"`
Status string `json:"status,omitempty"`
Progress *JSONProgress `json:"progressDetail,omitempty"`
ID string `json:"id,omitempty"`
Error *jsonstream.Error `json:"errorDetail,omitempty"`
Aux *json.RawMessage `json:"aux,omitempty"` // Aux contains out-of-band data, such as digests for push signing and image id after building.
}
// We can probably use [aec.EmptyBuilder] for managing the output, but
// currently we're doing it all manually, so defining some consts for
// the basics we use.
@ -164,7 +116,7 @@ func cursorDown(out io.Writer, l uint) {
// Display prints the JSONMessage to out. If isTerminal is true, it erases
// the entire current line when displaying the progressbar. It returns an
// error if the [JSONMessage.Error] field is non-nil.
func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
func Display(jm jsonstream.Message, out io.Writer, isTerminal bool, width uint16) error {
if jm.Error != nil {
return jm.Error
}
@ -173,14 +125,17 @@ func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
clearLine(out)
endl = "\r"
_, _ = fmt.Fprint(out, endl)
} else if jm.Progress != nil && jm.Progress.String() != "" { // disable progressbar in non-terminal
} else if jm.Progress != nil && (jm.Progress.Current > 0 || jm.Progress.Total > 0) { // disable progressbar in non-terminal
return nil
}
if jm.ID != "" {
_, _ = fmt.Fprintf(out, "%s: ", jm.ID)
}
if jm.Progress != nil && isTerminal {
_, _ = fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl)
if width == 0 {
width = 200
}
_, _ = fmt.Fprintf(out, "%s %s%s", jm.Status, RenderTUIProgress(*jm.Progress, width), endl)
} else if jm.Stream != "" {
_, _ = fmt.Fprintf(out, "%s%s", jm.Stream, endl)
} else {
@ -189,16 +144,16 @@ func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
return nil
}
type JSONMessagesStream iter.Seq2[JSONMessage, error]
type JSONMessagesStream iter.Seq2[jsonstream.Message, error]
// DisplayJSONMessagesStream reads a JSON message stream from in, and writes
// each [JSONMessage] to out.
// see DisplayJSONMessages for details
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(JSONMessage)) error {
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(jsonstream.Message)) error {
dec := json.NewDecoder(in)
var f JSONMessagesStream = func(yield func(JSONMessage, error) bool) {
var f JSONMessagesStream = func(yield func(jsonstream.Message, error) bool) {
for {
var jm JSONMessage
var jm jsonstream.Message
err := dec.Decode(&jm)
if errors.Is(err, io.EOF) {
break
@ -228,8 +183,15 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr,
// - auxCallback allows handling the [JSONMessage.Aux] field. It is
// called if a JSONMessage contains an Aux field, in which case
// DisplayJSONMessagesStream does not present the JSONMessage.
func DisplayJSONMessages(messages JSONMessagesStream, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(JSONMessage)) error {
func DisplayJSONMessages(messages JSONMessagesStream, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(jsonstream.Message)) error {
ids := make(map[string]uint)
var width uint16 = 200
if isTerminal {
ws, err := term.GetWinsize(terminalFd)
if err == nil {
width = ws.Width
}
}
for jm, err := range messages {
var diff uint
@ -244,9 +206,6 @@ func DisplayJSONMessages(messages JSONMessagesStream, out io.Writer, terminalFd
continue
}
if jm.Progress != nil {
jm.Progress.terminalFd = terminalFd
}
if jm.ID != "" && jm.Progress != nil {
line, ok := ids[jm.ID]
if !ok {
@ -274,7 +233,7 @@ func DisplayJSONMessages(messages JSONMessagesStream, out io.Writer, terminalFd
// with multiple tags).
ids = make(map[string]uint)
}
err := jm.Display(out, isTerminal)
err := Display(jm, out, isTerminal, width)
if jm.ID != "" && isTerminal {
cursorDown(out, diff)
}

View File

@ -4,18 +4,78 @@ import (
"context"
"encoding/json"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/system"
)
// 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
// Version is the version of the daemon.
Version string
// APIVersion is the highest API version supported by the server.
APIVersion string
// MinAPIVersion is the minimum API version the server supports.
MinAPIVersion string
// Os is the operating system the server runs on.
Os string
// Arch is the hardware architecture the server runs on.
Arch string
// Experimental indicates that the daemon runs with experimental
// features enabled.
//
// Deprecated: this field will be removed in the next version.
Experimental bool
// 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 []system.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 system.VersionResponse
err = json.NewDecoder(resp.Body).Decode(&v)
if err != nil {
return ServerVersionResult{}, err
}
return ServerVersionResult{
Platform: PlatformInfo{
Name: v.Platform.Name,
},
Version: v.Version,
APIVersion: v.APIVersion,
MinAPIVersion: v.MinAPIVersion,
Os: v.Os,
Arch: v.Arch,
Experimental: v.Experimental, //nolint:staticcheck // ignore deprecated field.
Components: v.Components,
}, nil
}