1
0
mirror of https://github.com/docker/cli.git synced 2026-01-13 18:22:35 +03:00

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-10-22 15:37:39 +02:00
parent 7b4cde6967
commit aeb78091a0
100 changed files with 1451 additions and 792 deletions

View File

@@ -28,8 +28,8 @@ require (
github.com/google/uuid v1.6.0
github.com/mattn/go-runewidth v0.0.17
github.com/moby/go-archive v0.1.0
github.com/moby/moby/api v1.52.0-beta.2.0.20251017201131-ec83dd46ed6c // master
github.com/moby/moby/client v0.1.0-beta.2.0.20251017201131-ec83dd46ed6c // master
github.com/moby/moby/api v1.52.0-beta.2.0.20251023134003-dcd668d5796b // master
github.com/moby/moby/client v0.1.0-beta.2.0.20251023134003-dcd668d5796b // master
github.com/moby/patternmatcher v0.6.0
github.com/moby/swarmkit/v2 v2.1.0
github.com/moby/sys/atomicwriter v0.1.0

View File

@@ -170,10 +170,10 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/moby/api v1.52.0-beta.2.0.20251017201131-ec83dd46ed6c h1:H7R4PXQj39EaRxCrBjN+DRDFdyy+53TdDgo5iWmhXKQ=
github.com/moby/moby/api v1.52.0-beta.2.0.20251017201131-ec83dd46ed6c/go.mod h1:/ou52HkRydg4+odrUR3vFsGgjIyHvprrpEQEkweL10s=
github.com/moby/moby/client v0.1.0-beta.2.0.20251017201131-ec83dd46ed6c h1:qFRyxx446aKHvUht2DUXzSPNwzZUgCaunUHH8TTTBgY=
github.com/moby/moby/client v0.1.0-beta.2.0.20251017201131-ec83dd46ed6c/go.mod h1:sxVfwGqVgh7n+tdxA4gFToQ/lf+bM7zATnvQjVnsKT4=
github.com/moby/moby/api v1.52.0-beta.2.0.20251023134003-dcd668d5796b h1:2y00KCjD3Dt6CFi8Zr7kh0ew32S2784EWbyKAXqQw2Q=
github.com/moby/moby/api v1.52.0-beta.2.0.20251023134003-dcd668d5796b/go.mod h1:/ou52HkRydg4+odrUR3vFsGgjIyHvprrpEQEkweL10s=
github.com/moby/moby/client v0.1.0-beta.2.0.20251023134003-dcd668d5796b h1:+JMltOoDeSwWJv4AMXE9e3dSe1h4LD4+bt6tqNO5h0s=
github.com/moby/moby/client v0.1.0-beta.2.0.20251023134003-dcd668d5796b/go.mod h1:sxVfwGqVgh7n+tdxA4gFToQ/lf+bM7zATnvQjVnsKT4=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/swarmkit/v2 v2.1.0 h1:u+cJ5hSyF3HnzsyI+NtegYxdIPQIuibk7IbpXNxuISM=

View File

@@ -42,13 +42,9 @@ type Config struct {
WorkingDir string // Current directory (PWD) in the command will be launched
Entrypoint []string // Entrypoint to run when starting the container
NetworkDisabled bool `json:",omitempty"` // Is network disabled
// Mac Address of the container.
//
// Deprecated: this field is deprecated since API v1.44 and obsolete since v1.52. Use EndpointSettings.MacAddress instead.
MacAddress string `json:",omitempty"`
OnBuild []string `json:",omitempty"` // ONBUILD metadata that were defined on the image Dockerfile
Labels map[string]string // List of labels set to this container
StopSignal string `json:",omitempty"` // Signal to stop a container
StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container
Shell []string `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT
OnBuild []string `json:",omitempty"` // ONBUILD metadata that were defined on the image Dockerfile
Labels map[string]string // List of labels set to this container
StopSignal string `json:",omitempty"` // Signal to stop a container
StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container
Shell []string `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT
}

View File

@@ -35,12 +35,7 @@ type ServiceSpec struct {
Mode ServiceMode `json:",omitempty"`
UpdateConfig *UpdateConfig `json:",omitempty"`
RollbackConfig *UpdateConfig `json:",omitempty"`
// Networks specifies which networks the service should attach to.
//
// Deprecated: This field is deprecated since v1.44. The Networks field in TaskSpec should be used instead.
Networks []NetworkAttachmentConfig `json:",omitempty"`
EndpointSpec *EndpointSpec `json:",omitempty"`
EndpointSpec *EndpointSpec `json:",omitempty"`
}
// ServiceMode represents the mode of a service.

View File

@@ -214,16 +214,6 @@ type Info struct {
Warnings []string `json:",omitempty"`
}
// Status provides information about the current swarm status and role,
// obtained from the "Swarm" header in the API response.
type Status struct {
// NodeState represents the state of the node.
NodeState LocalNodeState
// ControlAvailable indicates if the node is a swarm manager.
ControlAvailable bool
}
// Peer represents a peer.
type Peer struct {
NodeID string

View File

@@ -1,45 +1,22 @@
package types
import (
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/swarm"
)
const (
// MediaTypeRawStream is vendor specific MIME-Type set for raw TTY streams
// MediaTypeRawStream is vendor specific MIME-Type set for raw TTY streams.
MediaTypeRawStream = "application/vnd.docker.raw-stream"
// MediaTypeMultiplexedStream is vendor specific MIME-Type set for stdin/stdout/stderr multiplexed streams
// MediaTypeMultiplexedStream is vendor specific MIME-Type set for stdin/stdout/stderr multiplexed streams.
MediaTypeMultiplexedStream = "application/vnd.docker.multiplexed-stream"
// MediaTypeJSON is the MIME-Type for JSON objects
// MediaTypeJSON is the MIME-Type for JSON objects.
MediaTypeJSON = "application/json"
// MediaTypeNDJson is the MIME-Type for Newline Delimited JSON objects streams
// MediaTypeNDJSON is the MIME-Type for Newline Delimited JSON objects streams.
MediaTypeNDJSON = "application/x-ndjson"
// MediaTypeJsonSequence is the MIME-Type for JSON Text Sequences (RFC7464)
// MediaTypeJSONSequence is the MIME-Type for JSON Text Sequences (RFC7464).
MediaTypeJSONSequence = "application/json-seq"
)
// Ping contains response of Engine API:
// GET "/_ping"
type Ping struct {
APIVersion string
OSType string
Experimental bool
BuilderVersion build.BuilderVersion
// SwarmStatus provides information about the current swarm status of the
// engine, obtained from the "Swarm" header in the API response.
//
// It can be a nil struct if the API version does not provide this header
// in the ping response, or if an error occurred, in which case the client
// should use other ways to get the current swarm status, such as the /swarm
// endpoint.
SwarmStatus *swarm.Status
}
// ComponentVersion describes the version information for a specific component.
type ComponentVersion struct {
Name string

View File

@@ -5,12 +5,12 @@ package volume
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// CreateOptions VolumeConfig
// CreateRequest VolumeConfig
//
// # Volume configuration
//
// swagger:model CreateOptions
type CreateOptions struct {
// swagger:model CreateRequest
type CreateRequest struct {
// cluster volume spec
ClusterVolumeSpec *ClusterVolumeSpec `json:"ClusterVolumeSpec,omitempty"`

View File

@@ -7,13 +7,15 @@ import (
type BuildCancelOptions struct{}
type BuildCancelResult struct{}
// BuildCancel requests the daemon to cancel the ongoing build request
// with the given id.
func (cli *Client) BuildCancel(ctx context.Context, id string, _ BuildCancelOptions) error {
func (cli *Client) BuildCancel(ctx context.Context, id string, _ BuildCancelOptions) (BuildCancelResult, error) {
query := url.Values{}
query.Set("id", id)
resp, err := cli.post(ctx, "/build/cancel", query, nil, nil)
defer ensureReaderClosed(resp)
return err
return BuildCancelResult{}, err
}

View File

@@ -56,7 +56,6 @@ import (
cerrdefs "github.com/containerd/errdefs"
"github.com/docker/go-connections/sockets"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/versions"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
@@ -270,7 +269,7 @@ func (cli *Client) checkVersion(ctx context.Context) error {
return nil
}
ping, err := cli.Ping(ctx)
ping, err := cli.Ping(ctx, PingOptions{})
if err != nil {
return err
}
@@ -317,7 +316,7 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
cli.negotiateLock.Lock()
defer cli.negotiateLock.Unlock()
ping, err := cli.Ping(ctx)
ping, err := cli.Ping(ctx, PingOptions{})
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
@@ -338,7 +337,8 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
//
// 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) {
func (cli *Client) NegotiateAPIVersionPing(pingResponse PingResult) {
// TODO(thaJeztah): should this take a "Ping" option? It only consumes the version. This method should be removed overall and not be exported.
if !cli.manualOverride {
// Avoid concurrent modification of version-related fields
cli.negotiateLock.Lock()
@@ -452,11 +452,3 @@ func (cli *Client) dialer() func(context.Context) (net.Conn, error) {
}
}
}
// transportFunc allows us to inject a mock transport for testing. We define it
// here so we can detect the tlsconfig and return nil for only this type.
type transportFunc func(*http.Request) (*http.Response, error)
func (tf transportFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return tf(req)
}

View File

@@ -8,13 +8,10 @@ import (
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/events"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/plugin"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/api/types/volume"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
@@ -37,7 +34,7 @@ type stableAPIClient interface {
DaemonHost() string
ServerVersion(ctx context.Context) (types.Version, error)
NegotiateAPIVersion(ctx context.Context)
NegotiateAPIVersionPing(types.Ping)
NegotiateAPIVersionPing(PingResult)
HijackDialer
Dialer() func(context.Context) (net.Conn, error)
Close() error
@@ -92,38 +89,38 @@ type ContainerAPIClient interface {
}
type ExecAPIClient interface {
ContainerExecCreate(ctx context.Context, container string, options ExecCreateOptions) (container.ExecCreateResponse, error)
ContainerExecStart(ctx context.Context, execID string, options ExecStartOptions) error
ContainerExecAttach(ctx context.Context, execID string, options ExecAttachOptions) (HijackedResponse, error)
ContainerExecInspect(ctx context.Context, execID string) (ExecInspect, error)
ContainerExecResize(ctx context.Context, execID string, options ContainerResizeOptions) error
ExecCreate(ctx context.Context, container string, options ExecCreateOptions) (ExecCreateResult, error)
ExecStart(ctx context.Context, execID string, options ExecStartOptions) (ExecStartResult, error)
ExecAttach(ctx context.Context, execID string, options ExecAttachOptions) (ExecAttachResult, error)
ExecInspect(ctx context.Context, execID string, options ExecInspectOptions) (ExecInspectResult, error)
ExecResize(ctx context.Context, execID string, options ExecResizeOptions) (ExecResizeResult, error)
}
// DistributionAPIClient defines API client methods for the registry
type DistributionAPIClient interface {
DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registry.DistributionInspect, error)
DistributionInspect(ctx context.Context, image string, options DistributionInspectOptions) (DistributionInspectResult, error)
}
// ImageAPIClient defines API client methods for the images
type ImageAPIClient interface {
ImageBuild(ctx context.Context, context io.Reader, options ImageBuildOptions) (ImageBuildResponse, error)
ImageBuild(ctx context.Context, context io.Reader, options ImageBuildOptions) (ImageBuildResult, error)
BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (BuildCachePruneResult, error)
BuildCancel(ctx context.Context, id string, opts BuildCancelOptions) error
ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (io.ReadCloser, error)
ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (io.ReadCloser, error)
BuildCancel(ctx context.Context, id string, opts BuildCancelOptions) (BuildCancelResult, error)
ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (ImageCreateResult, error)
ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (ImageImportResult, error)
ImageList(ctx context.Context, options ImageListOptions) ([]image.Summary, error)
ImageList(ctx context.Context, options ImageListOptions) (ImageListResult, error)
ImagePull(ctx context.Context, ref string, options ImagePullOptions) (ImagePullResponse, error)
ImagePush(ctx context.Context, ref string, options ImagePushOptions) (io.ReadCloser, error)
ImageRemove(ctx context.Context, image string, options ImageRemoveOptions) ([]image.DeleteResponse, error)
ImageSearch(ctx context.Context, term string, options ImageSearchOptions) ([]registry.SearchResult, error)
ImageTag(ctx context.Context, image, ref string) error
ImagePush(ctx context.Context, ref string, options ImagePushOptions) (ImagePushResponse, error)
ImageRemove(ctx context.Context, image string, options ImageRemoveOptions) (ImageRemoveResult, error)
ImageSearch(ctx context.Context, term string, options ImageSearchOptions) (ImageSearchResult, error)
ImageTag(ctx context.Context, options ImageTagOptions) (ImageTagResult, error)
ImagesPrune(ctx context.Context, opts ImagePruneOptions) (ImagePruneResult, error)
ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (image.InspectResponse, error)
ImageHistory(ctx context.Context, image string, _ ...ImageHistoryOption) ([]image.HistoryResponseItem, error)
ImageLoad(ctx context.Context, input io.Reader, _ ...ImageLoadOption) (LoadResponse, error)
ImageSave(ctx context.Context, images []string, _ ...ImageSaveOption) (io.ReadCloser, error)
ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (ImageInspectResult, error)
ImageHistory(ctx context.Context, image string, _ ...ImageHistoryOption) (ImageHistoryResult, error)
ImageLoad(ctx context.Context, input io.Reader, _ ...ImageLoadOption) (ImageLoadResult, error)
ImageSave(ctx context.Context, images []string, _ ...ImageSaveOption) (ImageSaveResult, error)
}
// NetworkAPIClient defines API client methods for the networks
@@ -131,57 +128,56 @@ type NetworkAPIClient interface {
NetworkConnect(ctx context.Context, network, container string, config *network.EndpointSettings) error
NetworkCreate(ctx context.Context, name string, options NetworkCreateOptions) (network.CreateResponse, error)
NetworkDisconnect(ctx context.Context, network, container string, force bool) error
NetworkInspect(ctx context.Context, network string, options NetworkInspectOptions) (network.Inspect, error)
NetworkInspectWithRaw(ctx context.Context, network string, options NetworkInspectOptions) (network.Inspect, []byte, error)
NetworkList(ctx context.Context, options NetworkListOptions) ([]network.Summary, error)
NetworkInspect(ctx context.Context, network string, options NetworkInspectOptions) (NetworkInspectResult, error)
NetworkList(ctx context.Context, options NetworkListOptions) (NetworkListResult, error)
NetworkRemove(ctx context.Context, network string) error
NetworksPrune(ctx context.Context, opts NetworkPruneOptions) (NetworkPruneResult, error)
}
// NodeAPIClient defines API client methods for the nodes
type NodeAPIClient interface {
NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error)
NodeList(ctx context.Context, options NodeListOptions) ([]swarm.Node, error)
NodeRemove(ctx context.Context, nodeID string, options NodeRemoveOptions) error
NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error
NodeInspect(ctx context.Context, nodeID string, options NodeInspectOptions) (NodeInspectResult, error)
NodeList(ctx context.Context, options NodeListOptions) (NodeListResult, error)
NodeRemove(ctx context.Context, nodeID string, options NodeRemoveOptions) (NodeRemoveResult, error)
NodeUpdate(ctx context.Context, nodeID string, options NodeUpdateOptions) (NodeUpdateResult, error)
}
// PluginAPIClient defines API client methods for the plugins
type PluginAPIClient interface {
PluginList(ctx context.Context, opts PluginListOptions) (plugin.ListResponse, error)
PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) error
PluginEnable(ctx context.Context, name string, options PluginEnableOptions) error
PluginDisable(ctx context.Context, name string, options PluginDisableOptions) error
PluginInstall(ctx context.Context, name string, options PluginInstallOptions) (io.ReadCloser, error)
PluginUpgrade(ctx context.Context, name string, options PluginInstallOptions) (io.ReadCloser, error)
PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error)
PluginSet(ctx context.Context, name string, args []string) error
PluginInspectWithRaw(ctx context.Context, name string) (*plugin.Plugin, []byte, error)
PluginCreate(ctx context.Context, createContext io.Reader, options PluginCreateOptions) error
PluginList(ctx context.Context, options PluginListOptions) (PluginListResult, error)
PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) (PluginRemoveResult, error)
PluginEnable(ctx context.Context, name string, options PluginEnableOptions) (PluginEnableResult, error)
PluginDisable(ctx context.Context, name string, options PluginDisableOptions) (PluginDisableResult, error)
PluginInstall(ctx context.Context, name string, options PluginInstallOptions) (PluginInstallResult, error)
PluginUpgrade(ctx context.Context, name string, options PluginUpgradeOptions) (PluginUpgradeResult, error)
PluginPush(ctx context.Context, name string, options PluginPushOptions) (PluginPushResult, error)
PluginSet(ctx context.Context, name string, options PluginSetOptions) (PluginSetResult, error)
PluginInspect(ctx context.Context, name string, options PluginInspectOptions) (PluginInspectResult, error)
PluginCreate(ctx context.Context, createContext io.Reader, options PluginCreateOptions) (PluginCreateResult, error)
}
// ServiceAPIClient defines API client methods for the services
type ServiceAPIClient interface {
ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options ServiceCreateOptions) (swarm.ServiceCreateResponse, error)
ServiceInspectWithRaw(ctx context.Context, serviceID string, options ServiceInspectOptions) (swarm.Service, []byte, error)
ServiceList(ctx context.Context, options ServiceListOptions) ([]swarm.Service, error)
ServiceRemove(ctx context.Context, serviceID string) error
ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
ServiceLogs(ctx context.Context, serviceID string, options ContainerLogsOptions) (io.ReadCloser, error)
TaskLogs(ctx context.Context, taskID string, options ContainerLogsOptions) (io.ReadCloser, error)
TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error)
TaskList(ctx context.Context, options TaskListOptions) ([]swarm.Task, error)
ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options ServiceCreateOptions) (ServiceCreateResult, error)
ServiceInspect(ctx context.Context, serviceID string, options ServiceInspectOptions) (ServiceInspectResult, error)
ServiceList(ctx context.Context, options ServiceListOptions) (ServiceListResult, error)
ServiceRemove(ctx context.Context, serviceID string, options ServiceRemoveOptions) (ServiceRemoveResult, error)
ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options ServiceUpdateOptions) (ServiceUpdateResult, error)
ServiceLogs(ctx context.Context, serviceID string, options ServiceLogsOptions) (ServiceLogsResult, error)
TaskLogs(ctx context.Context, taskID string, options TaskLogsOptions) (TaskLogsResult, error)
TaskInspect(ctx context.Context, taskID string, options TaskInspectOptions) (TaskInspectResult, error)
TaskList(ctx context.Context, options TaskListOptions) (TaskListResult, error)
}
// SwarmAPIClient defines API client methods for the swarm
type SwarmAPIClient interface {
SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error)
SwarmJoin(ctx context.Context, req swarm.JoinRequest) error
SwarmGetUnlockKey(ctx context.Context) (swarm.UnlockKeyResponse, error)
SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error
SwarmLeave(ctx context.Context, force bool) error
SwarmInspect(ctx context.Context) (swarm.Swarm, error)
SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags SwarmUpdateFlags) error
SwarmInit(ctx context.Context, options SwarmInitOptions) (SwarmInitResult, error)
SwarmJoin(ctx context.Context, options SwarmJoinOptions) (SwarmJoinResult, error)
SwarmGetUnlockKey(ctx context.Context) (SwarmGetUnlockKeyResult, error)
SwarmUnlock(ctx context.Context, options SwarmUnlockOptions) (SwarmUnlockResult, error)
SwarmLeave(ctx context.Context, options SwarmLeaveOptions) (SwarmLeaveResult, error)
SwarmInspect(ctx context.Context, options SwarmInspectOptions) (SwarmInspectResult, error)
SwarmUpdate(ctx context.Context, version swarm.Version, options SwarmUpdateOptions) (SwarmUpdateResult, error)
}
// SystemAPIClient defines API client methods for the system
@@ -190,34 +186,33 @@ type SystemAPIClient interface {
Info(ctx context.Context) (system.Info, error)
RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error)
DiskUsage(ctx context.Context, options DiskUsageOptions) (system.DiskUsage, error)
Ping(ctx context.Context) (types.Ping, error)
Ping(ctx context.Context, options PingOptions) (PingResult, error)
}
// VolumeAPIClient defines API client methods for the volumes
type VolumeAPIClient interface {
VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error)
VolumeInspect(ctx context.Context, volumeID string) (volume.Volume, error)
VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error)
VolumeList(ctx context.Context, options VolumeListOptions) (volume.ListResponse, error)
VolumeRemove(ctx context.Context, volumeID string, force bool) error
VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error)
VolumeInspect(ctx context.Context, volumeID string, options VolumeInspectOptions) (VolumeInspectResult, error)
VolumeList(ctx context.Context, options VolumeListOptions) (VolumeListResult, error)
VolumeRemove(ctx context.Context, volumeID string, options VolumeRemoveOptions) error
VolumesPrune(ctx context.Context, opts VolumePruneOptions) (VolumePruneResult, error)
VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options VolumeUpdateOptions) error
}
// SecretAPIClient defines API client methods for secrets
type SecretAPIClient interface {
SecretList(ctx context.Context, options SecretListOptions) ([]swarm.Secret, error)
SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error)
SecretRemove(ctx context.Context, id string) error
SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error)
SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error
SecretList(ctx context.Context, options SecretListOptions) (SecretListResult, error)
SecretCreate(ctx context.Context, options SecretCreateOptions) (SecretCreateResult, error)
SecretRemove(ctx context.Context, id string, options SecretRemoveOptions) (SecretRemoveResult, error)
SecretInspect(ctx context.Context, id string, options SecretInspectOptions) (SecretInspectResult, error)
SecretUpdate(ctx context.Context, id string, options SecretUpdateOptions) (SecretUpdateResult, error)
}
// ConfigAPIClient defines API client methods for configs
type ConfigAPIClient interface {
ConfigList(ctx context.Context, options ConfigListOptions) ([]swarm.Config, error)
ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (swarm.ConfigCreateResponse, error)
ConfigRemove(ctx context.Context, id string) error
ConfigInspectWithRaw(ctx context.Context, name string) (swarm.Config, []byte, error)
ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error
ConfigList(ctx context.Context, options ConfigListOptions) (ConfigListResult, error)
ConfigCreate(ctx context.Context, options ConfigCreateOptions) (ConfigCreateResult, error)
ConfigRemove(ctx context.Context, id string, options ConfigRemoveOptions) (ConfigRemoveResult, error)
ConfigInspect(ctx context.Context, id string, options ConfigInspectOptions) (ConfigInspectResult, error)
ConfigUpdate(ctx context.Context, id string, options ConfigUpdateOptions) (ConfigUpdateResult, error)
}

View File

@@ -7,15 +7,28 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// ConfigCreateOptions holds options for creating a config.
type ConfigCreateOptions struct {
Spec swarm.ConfigSpec
}
// ConfigCreateResult holds the result from the ConfigCreate method.
type ConfigCreateResult struct {
ID string
}
// ConfigCreate creates a new config.
func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (swarm.ConfigCreateResponse, error) {
resp, err := cli.post(ctx, "/configs/create", nil, config, nil)
func (cli *Client) ConfigCreate(ctx context.Context, options ConfigCreateOptions) (ConfigCreateResult, error) {
resp, err := cli.post(ctx, "/configs/create", nil, options.Spec, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.ConfigCreateResponse{}, err
return ConfigCreateResult{}, err
}
var response swarm.ConfigCreateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
var out swarm.ConfigCreateResponse
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
return ConfigCreateResult{}, err
}
return ConfigCreateResult{ID: out.ID}, nil
}

View File

@@ -1,34 +1,35 @@
package client
import (
"bytes"
"context"
"encoding/json"
"io"
"github.com/moby/moby/api/types/swarm"
)
// ConfigInspectWithRaw returns the config information with raw data
func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) {
id, err := trimID("contig", id)
// ConfigInspectOptions holds options for inspecting a config.
type ConfigInspectOptions struct {
// Add future optional parameters here
}
// ConfigInspectResult holds the result from the ConfigInspect method.
type ConfigInspectResult struct {
Config swarm.Config
Raw []byte
}
// ConfigInspect returns the config information with raw data
func (cli *Client) ConfigInspect(ctx context.Context, id string, options ConfigInspectOptions) (ConfigInspectResult, error) {
id, err := trimID("config", id)
if err != nil {
return swarm.Config{}, nil, err
return ConfigInspectResult{}, err
}
resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.Config{}, nil, err
return ConfigInspectResult{}, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return swarm.Config{}, nil, err
}
var config swarm.Config
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&config)
return config, body, err
var out ConfigInspectResult
out.Raw, err = decodeWithRaw(resp, &out.Config)
return out, err
}

View File

@@ -8,18 +8,31 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// ConfigListOptions holds parameters to list configs
type ConfigListOptions struct {
Filters Filters
}
// ConfigListResult holds the result from the [client.ConfigList] method.
type ConfigListResult struct {
Items []swarm.Config
}
// ConfigList returns the list of configs.
func (cli *Client) ConfigList(ctx context.Context, options ConfigListOptions) ([]swarm.Config, error) {
func (cli *Client) ConfigList(ctx context.Context, options ConfigListOptions) (ConfigListResult, error) {
query := url.Values{}
options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/configs", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
return ConfigListResult{}, err
}
var configs []swarm.Config
err = json.NewDecoder(resp.Body).Decode(&configs)
return configs, err
var out ConfigListResult
err = json.NewDecoder(resp.Body).Decode(&out.Items)
if err != nil {
return ConfigListResult{}, err
}
return out, nil
}

View File

@@ -2,13 +2,24 @@ package client
import "context"
type ConfigRemoveOptions struct {
// Add future optional parameters here
}
type ConfigRemoveResult struct {
// Add future fields here
}
// ConfigRemove removes a config.
func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
func (cli *Client) ConfigRemove(ctx context.Context, id string, options ConfigRemoveOptions) (ConfigRemoveResult, error) {
id, err := trimID("config", id)
if err != nil {
return err
return ConfigRemoveResult{}, err
}
resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
defer ensureReaderClosed(resp)
return err
if err != nil {
return ConfigRemoveResult{}, err
}
return ConfigRemoveResult{}, nil
}

View File

@@ -7,15 +7,26 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// ConfigUpdateOptions holds options for updating a config.
type ConfigUpdateOptions struct {
Version swarm.Version
Spec swarm.ConfigSpec
}
type ConfigUpdateResult struct{}
// ConfigUpdate attempts to update a config
func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error {
func (cli *Client) ConfigUpdate(ctx context.Context, id string, options ConfigUpdateOptions) (ConfigUpdateResult, error) {
id, err := trimID("config", id)
if err != nil {
return err
return ConfigUpdateResult{}, err
}
query := url.Values{}
query.Set("version", version.String())
resp, err := cli.post(ctx, "/configs/"+id+"/update", query, config, nil)
query.Set("version", options.Version.String())
resp, err := cli.post(ctx, "/configs/"+id+"/update", query, options.Spec, nil)
defer ensureReaderClosed(resp)
return err
if err != nil {
return ConfigUpdateResult{}, err
}
return ConfigUpdateResult{}, nil
}

View File

@@ -11,7 +11,6 @@ import (
cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/versions"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
@@ -29,25 +28,6 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop)
}
// FIXME(thaJeztah): remove this once we updated our (integration) tests;
// some integration tests depend on this to test old API versions; see https://github.com/moby/moby/pull/51120#issuecomment-3376224865
if config.MacAddress != "" { //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
if err := cli.checkVersion(ctx); err != nil {
return response, err
}
if versions.GreaterThanOrEqualTo(cli.ClientVersion(), "1.44") {
// Since API 1.44, the container-wide MacAddress is deprecated and triggers a WARNING if it's specified.
//
// FIXME(thaJeztah): remove the field from the API
config.MacAddress = "" //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
}
}
query := url.Values{}
if platform != nil {
if p := formatPlatform(*platform); p != "unknown" {
@@ -78,7 +58,7 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
// formatPlatform returns a formatted string representing platform (e.g., "linux/arm/v7").
//
// It is a fork of [platforms.Format], and does not yet support "os.version",
// as [[platforms.FormatAll] does.
// as [platforms.FormatAll] does.
//
// [platforms.Format]: https://github.com/containerd/platforms/blob/v1.0.0-rc.1/platforms.go#L309-L316
// [platforms.FormatAll]: https://github.com/containerd/platforms/blob/v1.0.0-rc.1/platforms.go#L318-L330
@@ -89,19 +69,6 @@ func formatPlatform(platform ocispec.Platform) string {
return path.Join(platform.OS, platform.Architecture, platform.Variant)
}
// hasEndpointSpecificMacAddress checks whether one of the endpoint in networkingConfig has a MacAddress defined.
func hasEndpointSpecificMacAddress(networkingConfig *network.NetworkingConfig) bool {
if networkingConfig == nil {
return false
}
for _, endpoint := range networkingConfig.EndpointsConfig {
if endpoint.MacAddress != "" {
return true
}
}
return false
}
// allCapabilities is a magic value for "all capabilities"
const allCapabilities = "ALL"

View File

@@ -24,11 +24,16 @@ type ExecCreateOptions struct {
Cmd []string // Execution commands and args
}
// ContainerExecCreate creates a new exec configuration to run an exec process.
func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options ExecCreateOptions) (container.ExecCreateResponse, error) {
// ExecCreateResult holds the result of creating a container exec.
type ExecCreateResult struct {
container.ExecCreateResponse
}
// ExecCreate creates a new exec configuration to run an exec process.
func (cli *Client) ExecCreate(ctx context.Context, containerID string, options ExecCreateOptions) (ExecCreateResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.ExecCreateResponse{}, err
return ExecCreateResult{}, err
}
req := container.ExecCreateRequest{
@@ -48,17 +53,15 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string,
resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, req, nil)
defer ensureReaderClosed(resp)
if err != nil {
return container.ExecCreateResponse{}, err
return ExecCreateResult{}, err
}
var response container.ExecCreateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
return ExecCreateResult{ExecCreateResponse: response}, err
}
// ExecStartOptions is a temp struct used by execStart
// Config fields is part of ExecConfig in runconfig package
type ExecStartOptions struct {
type execStartAttachOptions struct {
// ExecStart will first check if it's detached
Detach bool
// Check if there's a tty
@@ -67,24 +70,34 @@ type ExecStartOptions struct {
ConsoleSize *[2]uint `json:",omitempty"`
}
// ContainerExecStart starts an exec process already created in the docker host.
func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config ExecStartOptions) error {
// ExecStartOptions holds options for starting a container exec.
type ExecStartOptions execStartAttachOptions
// ExecStartResult holds the result of starting a container exec.
type ExecStartResult struct {
}
// ExecStart starts an exec process already created in the docker host.
func (cli *Client) ExecStart(ctx context.Context, execID string, options ExecStartOptions) (ExecStartResult, error) {
req := container.ExecStartRequest{
Detach: config.Detach,
Tty: config.Tty,
ConsoleSize: config.ConsoleSize,
Detach: options.Detach,
Tty: options.Tty,
ConsoleSize: options.ConsoleSize,
}
resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, req, nil)
defer ensureReaderClosed(resp)
return err
return ExecStartResult{}, err
}
// ExecAttachOptions is a temp struct used by execAttach.
//
// TODO(thaJeztah): make this a separate type; ContainerExecAttach does not use the Detach option, and cannot run detached.
type ExecAttachOptions = ExecStartOptions
// ExecAttachOptions holds options for attaching to a container exec.
type ExecAttachOptions execStartAttachOptions
// ContainerExecAttach attaches a connection to an exec process in the server.
// ExecAttachResult holds the result of attaching to a container exec.
type ExecAttachResult struct {
HijackedResponse
}
// ExecAttach attaches a connection to an exec process in the server.
//
// It returns a [HijackedResponse] with the hijacked connection
// and a reader to get output. It's up to the called to close
@@ -102,15 +115,16 @@ type ExecAttachOptions = ExecStartOptions
// [Client.ContainerAttach] for details about the multiplexed stream.
//
// [stdcopy.StdCopy]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdCopy
func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, config ExecAttachOptions) (HijackedResponse, error) {
func (cli *Client) ExecAttach(ctx context.Context, execID string, options ExecAttachOptions) (ExecAttachResult, error) {
req := container.ExecStartRequest{
Detach: config.Detach,
Tty: config.Tty,
ConsoleSize: config.ConsoleSize,
Detach: options.Detach,
Tty: options.Tty,
ConsoleSize: options.ConsoleSize,
}
return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, req, http.Header{
response, err := cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, req, http.Header{
"Content-Type": {"application/json"},
})
return ExecAttachResult{HijackedResponse: response}, err
}
// ExecInspect holds information returned by exec inspect.
@@ -126,18 +140,27 @@ type ExecInspect struct {
Pid int `json:"Pid"`
}
// ContainerExecInspect returns information about a specific exec process on the docker host.
func (cli *Client) ContainerExecInspect(ctx context.Context, execID string) (ExecInspect, error) {
// ExecInspectOptions holds options for inspecting a container exec.
type ExecInspectOptions struct {
}
// ExecInspectResult holds the result of inspecting a container exec.
type ExecInspectResult struct {
ExecInspect
}
// ExecInspect returns information about a specific exec process on the docker host.
func (cli *Client) ExecInspect(ctx context.Context, execID string, options ExecInspectOptions) (ExecInspectResult, error) {
resp, err := cli.get(ctx, "/exec/"+execID+"/json", nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return ExecInspect{}, err
return ExecInspectResult{}, err
}
var response container.ExecInspectResponse
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil {
return ExecInspect{}, err
return ExecInspectResult{}, err
}
var ec int
@@ -145,11 +168,11 @@ func (cli *Client) ContainerExecInspect(ctx context.Context, execID string) (Exe
ec = *response.ExitCode
}
return ExecInspect{
return ExecInspectResult{ExecInspect: ExecInspect{
ExecID: response.ID,
ContainerID: response.ContainerID,
Running: response.Running,
ExitCode: ec,
Pid: response.Pid,
}, nil
}}, nil
}

View File

@@ -23,13 +23,21 @@ func (cli *Client) ContainerResize(ctx context.Context, containerID string, opti
return cli.resize(ctx, "/containers/"+containerID, options.Height, options.Width)
}
// ContainerExecResize changes the size of the tty for an exec process running inside a container.
func (cli *Client) ContainerExecResize(ctx context.Context, execID string, options ContainerResizeOptions) error {
// ExecResizeOptions holds options for resizing a container exec TTY.
type ExecResizeOptions ContainerResizeOptions
// ExecResizeResult holds the result of resizing a container exec TTY.
type ExecResizeResult struct {
}
// ExecResize changes the size of the tty for an exec process running inside a container.
func (cli *Client) ExecResize(ctx context.Context, execID string, options ExecResizeOptions) (ExecResizeResult, error) {
execID, err := trimID("exec", execID)
if err != nil {
return err
return ExecResizeResult{}, err
}
return cli.resize(ctx, "/exec/"+execID, options.Height, options.Width)
err = cli.resize(ctx, "/exec/"+execID, options.Height, options.Width)
return ExecResizeResult{}, err
}
func (cli *Client) resize(ctx context.Context, basePath string, height, width uint) error {

View File

@@ -9,16 +9,26 @@ import (
"github.com/moby/moby/api/types/registry"
)
// DistributionInspectResult holds the result of the DistributionInspect operation.
type DistributionInspectResult struct {
registry.DistributionInspect
}
// DistributionInspectOptions holds options for the DistributionInspect operation.
type DistributionInspectOptions struct {
EncodedRegistryAuth string
}
// DistributionInspect returns the image digest with the full manifest.
func (cli *Client) DistributionInspect(ctx context.Context, imageRef, encodedRegistryAuth string) (registry.DistributionInspect, error) {
func (cli *Client) DistributionInspect(ctx context.Context, imageRef string, options DistributionInspectOptions) (DistributionInspectResult, error) {
if imageRef == "" {
return registry.DistributionInspect{}, objectNotFoundError{object: "distribution", id: imageRef}
return DistributionInspectResult{}, objectNotFoundError{object: "distribution", id: imageRef}
}
var headers http.Header
if encodedRegistryAuth != "" {
if options.EncodedRegistryAuth != "" {
headers = http.Header{
registry.AuthHeader: {encodedRegistryAuth},
registry.AuthHeader: {options.EncodedRegistryAuth},
}
}
@@ -26,10 +36,10 @@ func (cli *Client) DistributionInspect(ctx context.Context, imageRef, encodedReg
resp, err := cli.get(ctx, "/distribution/"+imageRef+"/json", url.Values{}, headers)
defer ensureReaderClosed(resp)
if err != nil {
return registry.DistributionInspect{}, err
return DistributionInspectResult{}, err
}
var distributionInspect registry.DistributionInspect
err = json.NewDecoder(resp.Body).Decode(&distributionInspect)
return distributionInspect, err
return DistributionInspectResult{DistributionInspect: distributionInspect}, err
}

View File

@@ -17,15 +17,15 @@ import (
// ImageBuild sends a request to the daemon to build images.
// The Body in the response implements an [io.ReadCloser] and it's up to the caller to
// close it.
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options ImageBuildOptions) (ImageBuildResponse, error) {
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options ImageBuildOptions) (ImageBuildResult, error) {
query, err := cli.imageBuildOptionsToQuery(ctx, options)
if err != nil {
return ImageBuildResponse{}, err
return ImageBuildResult{}, err
}
buf, err := json.Marshal(options.AuthConfigs)
if err != nil {
return ImageBuildResponse{}, err
return ImageBuildResult{}, err
}
headers := http.Header{}
@@ -34,10 +34,10 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
resp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
if err != nil {
return ImageBuildResponse{}, err
return ImageBuildResult{}, err
}
return ImageBuildResponse{
return ImageBuildResult{
Body: resp.Body,
}, nil
}

View File

@@ -68,9 +68,9 @@ type ImageBuildOutput struct {
Attrs map[string]string
}
// ImageBuildResponse holds information
// ImageBuildResult holds information
// returned by a server after building
// an image.
type ImageBuildResponse struct {
type ImageBuildResult struct {
Body io.ReadCloser
}

View File

@@ -2,7 +2,6 @@ package client
import (
"context"
"io"
"net/http"
"net/url"
"strings"
@@ -13,10 +12,10 @@ import (
// ImageCreate creates a new image based on the parent options.
// It returns the JSON content in the response body.
func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (io.ReadCloser, error) {
func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (ImageCreateResult, error) {
ref, err := reference.ParseNormalizedNamed(parentReference)
if err != nil {
return nil, err
return ImageCreateResult{}, err
}
query := url.Values{}
@@ -27,9 +26,9 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti
}
resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth))
if err != nil {
return nil, err
return ImageCreateResult{}, err
}
return resp.Body, nil
return ImageCreateResult{Body: resp.Body}, nil
}
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) {

View File

@@ -1,7 +1,14 @@
package client
import "io"
// 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.
}
// ImageCreateResult holds the response body returned by the daemon for image create.
type ImageCreateResult struct {
Body io.ReadCloser
}

View File

@@ -6,7 +6,6 @@ import (
"fmt"
"net/url"
"github.com/moby/moby/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
@@ -22,24 +21,24 @@ func ImageHistoryWithPlatform(platform ocispec.Platform) ImageHistoryOption {
}
// ImageHistory returns the changes in an image in history format.
func (cli *Client) ImageHistory(ctx context.Context, imageID string, historyOpts ...ImageHistoryOption) ([]image.HistoryResponseItem, error) {
func (cli *Client) ImageHistory(ctx context.Context, imageID string, historyOpts ...ImageHistoryOption) (ImageHistoryResult, error) {
query := url.Values{}
var opts imageHistoryOpts
for _, o := range historyOpts {
if err := o.Apply(&opts); err != nil {
return nil, err
return ImageHistoryResult{}, err
}
}
if opts.apiOptions.Platform != nil {
if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
return nil, err
return ImageHistoryResult{}, err
}
p, err := encodePlatform(opts.apiOptions.Platform)
if err != nil {
return nil, err
return ImageHistoryResult{}, err
}
query.Set("platform", p)
}
@@ -47,10 +46,10 @@ func (cli *Client) ImageHistory(ctx context.Context, imageID string, historyOpts
resp, err := cli.get(ctx, "/images/"+imageID+"/history", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
return ImageHistoryResult{}, err
}
var history []image.HistoryResponseItem
err = json.NewDecoder(resp.Body).Decode(&history)
var history ImageHistoryResult
err = json.NewDecoder(resp.Body).Decode(&history.Items)
return history, err
}

View File

@@ -1,6 +1,9 @@
package client
import ocispec "github.com/opencontainers/image-spec/specs-go/v1"
import (
"github.com/moby/moby/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageHistoryOption is a type representing functional options for the image history operation.
type ImageHistoryOption interface {
@@ -20,3 +23,7 @@ type imageHistoryOptions struct {
// Platform from the manifest list to use for history.
Platform *ocispec.Platform
}
type ImageHistoryResult struct {
Items []image.HistoryResponseItem
}

View File

@@ -2,7 +2,6 @@ package client
import (
"context"
"io"
"net/url"
"strings"
@@ -11,11 +10,11 @@ import (
// ImageImport creates a new image based on the source options.
// It returns the JSON content in the response body.
func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (io.ReadCloser, error) {
func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (ImageImportResult, error) {
if ref != "" {
// Check if the given image name can be resolved
if _, err := reference.ParseNormalizedNamed(ref); err != nil {
return nil, err
return ImageImportResult{}, err
}
}
@@ -41,7 +40,7 @@ func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, re
resp, err := cli.postRaw(ctx, "/images/create", query, source.Source, nil)
if err != nil {
return nil, err
return ImageImportResult{}, err
}
return resp.Body, nil
return ImageImportResult{body: resp.Body}, nil
}

View File

@@ -17,3 +17,19 @@ type ImageImportOptions struct {
Changes []string // Changes are the raw changes to apply to this image
Platform string // Platform is the target platform of the image
}
// ImageImportResult holds the response body returned by the daemon for image import.
type ImageImportResult struct {
body io.ReadCloser
}
func (r ImageImportResult) Read(p []byte) (n int, err error) {
return r.body.Read(p)
}
func (r ImageImportResult) Close() error {
if r.body == nil {
return nil
}
return r.body.Close()
}

View File

@@ -7,38 +7,36 @@ import (
"fmt"
"io"
"net/url"
"github.com/moby/moby/api/types/image"
)
// ImageInspect returns the image information.
func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts ...ImageInspectOption) (image.InspectResponse, error) {
func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts ...ImageInspectOption) (ImageInspectResult, error) {
if imageID == "" {
return image.InspectResponse{}, objectNotFoundError{object: "image", id: imageID}
return ImageInspectResult{}, objectNotFoundError{object: "image", id: imageID}
}
var opts imageInspectOpts
for _, opt := range inspectOpts {
if err := opt.Apply(&opts); err != nil {
return image.InspectResponse{}, fmt.Errorf("error applying image inspect option: %w", err)
return ImageInspectResult{}, fmt.Errorf("error applying image inspect option: %w", err)
}
}
query := url.Values{}
if opts.apiOptions.Manifests {
if err := cli.NewVersionError(ctx, "1.48", "manifests"); err != nil {
return image.InspectResponse{}, err
return ImageInspectResult{}, err
}
query.Set("manifests", "1")
}
if opts.apiOptions.Platform != nil {
if err := cli.NewVersionError(ctx, "1.49", "platform"); err != nil {
return image.InspectResponse{}, err
return ImageInspectResult{}, err
}
platform, err := encodePlatform(opts.apiOptions.Platform)
if err != nil {
return image.InspectResponse{}, err
return ImageInspectResult{}, err
}
query.Set("platform", platform)
}
@@ -46,7 +44,7 @@ func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts
resp, err := cli.get(ctx, "/images/"+imageID+"/json", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return image.InspectResponse{}, err
return ImageInspectResult{}, err
}
buf := opts.raw
@@ -55,10 +53,10 @@ func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts
}
if _, err := io.Copy(buf, resp.Body); err != nil {
return image.InspectResponse{}, err
return ImageInspectResult{}, err
}
var response image.InspectResponse
var response ImageInspectResult
err = json.Unmarshal(buf.Bytes(), &response)
return response, err
}

View File

@@ -3,6 +3,7 @@ package client
import (
"bytes"
"github.com/moby/moby/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
@@ -62,3 +63,7 @@ type imageInspectOptions struct {
// This option is only available for API version 1.49 and up.
Platform *ocispec.Platform
}
type ImageInspectResult struct {
image.InspectResponse
}

View File

@@ -15,7 +15,7 @@ import (
// to include [image.Summary.Manifests] with information about image manifests.
// This is experimental and might change in the future without any backward
// compatibility.
func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]image.Summary, error) {
func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) (ImageListResult, error) {
var images []image.Summary
query := url.Values{}
@@ -34,7 +34,7 @@ func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]i
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
if err := cli.checkVersion(ctx); err != nil {
return images, err
return ImageListResult{}, err
}
if versions.GreaterThanOrEqualTo(cli.version, "1.47") {
@@ -45,9 +45,9 @@ func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]i
resp, err := cli.get(ctx, "/images/json", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return images, err
return ImageListResult{}, err
}
err = json.NewDecoder(resp.Body).Decode(&images)
return images, err
return ImageListResult{Items: images}, err
}

View File

@@ -1,5 +1,7 @@
package client
import "github.com/moby/moby/api/types/image"
// ImageListOptions holds parameters to list images with.
type ImageListOptions struct {
// All controls whether all images in the graph are filtered, or just
@@ -15,3 +17,8 @@ type ImageListOptions struct {
// Manifests indicates whether the image manifests should be returned.
Manifests bool
}
// ImageListResult holds the result from ImageList.
type ImageListResult struct {
Items []image.Summary
}

View File

@@ -9,16 +9,16 @@ import (
// ImageLoad loads an image in the docker host from the client host.
// It's up to the caller to close the [io.ReadCloser] in the
// [image.LoadResponse] returned by this function.
// [ImageLoadResult] returned by this function.
//
// Platform is an optional parameter that specifies the platform to load from
// the provided multi-platform image. Passing a platform only has an effect
// if the input image is a multi-platform image.
func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, loadOpts ...ImageLoadOption) (LoadResponse, error) {
func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, loadOpts ...ImageLoadOption) (ImageLoadResult, error) {
var opts imageLoadOpts
for _, opt := range loadOpts {
if err := opt.Apply(&opts); err != nil {
return LoadResponse{}, err
return ImageLoadResult{}, err
}
}
@@ -29,12 +29,12 @@ func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, loadOpts ...I
}
if len(opts.apiOptions.Platforms) > 0 {
if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
return LoadResponse{}, err
return ImageLoadResult{}, err
}
p, err := encodePlatforms(opts.apiOptions.Platforms...)
if err != nil {
return LoadResponse{}, err
return ImageLoadResult{}, err
}
query["platform"] = p
}
@@ -43,10 +43,10 @@ func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, loadOpts ...I
"Content-Type": {"application/x-tar"},
})
if err != nil {
return LoadResponse{}, err
return ImageLoadResult{}, err
}
return LoadResponse{
Body: resp.Body,
return ImageLoadResult{
body: resp.Body,
JSON: resp.Header.Get("Content-Type") == "application/json",
}, nil
}
@@ -73,8 +73,19 @@ func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, loadOpts ...I
//
// We should deprecated the "quiet" option, as it's really a client
// responsibility.
type LoadResponse struct {
type ImageLoadResult struct {
// Body must be closed to avoid a resource leak
Body io.ReadCloser
body io.ReadCloser
JSON bool
}
func (r ImageLoadResult) Read(p []byte) (n int, err error) {
return r.body.Read(p)
}
func (r ImageLoadResult) Close() error {
if r.body == nil {
return nil
}
return r.body.Close()
}

View File

@@ -2,83 +2,31 @@ package client
import (
"context"
"encoding/json"
"errors"
"io"
"iter"
"net/url"
"strings"
"sync"
cerrdefs "github.com/containerd/errdefs"
"github.com/distribution/reference"
"github.com/moby/moby/client/internal"
"github.com/moby/moby/client/pkg/jsonmessage"
)
func newImagePullResponse(rc io.ReadCloser) ImagePullResponse {
if rc == nil {
panic("nil io.ReadCloser")
}
return ImagePullResponse{
rc: rc,
close: sync.OnceValue(rc.Close),
}
}
type ImagePullResponse struct {
rc io.ReadCloser
close func() error
}
// Read implements io.ReadCloser
func (r ImagePullResponse) Read(p []byte) (n int, err error) {
if r.rc == nil {
return 0, io.EOF
}
return r.rc.Read(p)
}
// Close implements io.ReadCloser
func (r ImagePullResponse) Close() error {
if r.close == nil {
return nil
}
return r.close()
}
// 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 ImagePullResponse) JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error] {
context.AfterFunc(ctx, func() {
_ = r.Close()
})
dec := json.NewDecoder(r)
return func(yield func(jsonmessage.JSONMessage, error) bool) {
defer r.Close()
for {
var jm jsonmessage.JSONMessage
err := dec.Decode(&jm)
if errors.Is(err, io.EOF) {
break
}
if ctx.Err() != nil {
yield(jm, ctx.Err())
return
}
if !yield(jm, err) {
return
}
}
}
type ImagePullResponse interface {
io.ReadCloser
JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error]
Wait(ctx context.Context) error
}
// ImagePull requests the docker host to pull an image from a remote registry.
// It executes the privileged function if the operation is unauthorized
// and it tries one more time.
// Callers can use [ImagePullResponse.JSONMessages] to monitor pull progress as
// a sequence of JSONMessages, [ImagePullResponse.Close] does not need to be
// called in this case. Or, use the [io.Reader] interface and call
// [ImagePullResponse.Close] after processing.
// Callers can:
// - use [ImagePullResponse.Wait] to wait for pull to complete
// - use [ImagePullResponse.JSONMessages] to monitor pull progress as a sequence
// of JSONMessages, [ImagePullResponse.Close] does not need to be called in this case.
// - use the [io.Reader] interface and call [ImagePullResponse.Close] after processing.
func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePullOptions) (ImagePullResponse, error) {
// FIXME(vdemeester): there is currently used in a few way in docker/docker
// - if not in trusted content, ref is used to pass the whole reference, and tag is empty
@@ -88,7 +36,7 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePu
ref, err := reference.ParseNormalizedNamed(refStr)
if err != nil {
return ImagePullResponse{}, err
return nil, err
}
query := url.Values{}
@@ -105,10 +53,10 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePu
resp, err = cli.tryImageCreate(ctx, query, options.PrivilegeFunc)
}
if err != nil {
return ImagePullResponse{}, err
return nil, err
}
return newImagePullResponse(resp.Body), nil
return internal.NewJSONMessageStream(resp.Body), nil
}
// getAPITagFromNamedRef returns a tag from the specified reference.

View File

@@ -6,19 +6,32 @@ import (
"errors"
"fmt"
"io"
"iter"
"net/http"
"net/url"
cerrdefs "github.com/containerd/errdefs"
"github.com/distribution/reference"
"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]
Wait(ctx context.Context) error
}
// ImagePush requests the docker host to push an image to a remote registry.
// It executes the privileged function if the operation is unauthorized
// and it tries one more time.
// It's up to the caller to handle the [io.ReadCloser] and close it.
func (cli *Client) ImagePush(ctx context.Context, image string, options ImagePushOptions) (io.ReadCloser, error) {
// Callers can
// - use [ImagePushResponse.Wait] to wait for push to complete
// - use [ImagePushResponse.JSONMessages] to monitor pull progress as a sequence
// of JSONMessages, [ImagePushResponse.Close] does not need to be called in this case.
// - use the [io.Reader] interface and call [ImagePushResponse.Close] after processing.
func (cli *Client) ImagePush(ctx context.Context, image string, options ImagePushOptions) (ImagePushResponse, error) {
ref, err := reference.ParseNormalizedNamed(image)
if err != nil {
return nil, err
@@ -57,7 +70,7 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options ImagePus
if err != nil {
return nil, err
}
return resp.Body, nil
return internal.NewJSONMessageStream(resp.Body), nil
}
func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) {

View File

@@ -9,7 +9,7 @@ import (
)
// ImageRemove removes an image from the docker host.
func (cli *Client) ImageRemove(ctx context.Context, imageID string, options ImageRemoveOptions) ([]image.DeleteResponse, error) {
func (cli *Client) ImageRemove(ctx context.Context, imageID string, options ImageRemoveOptions) (ImageRemoveResult, error) {
query := url.Values{}
if options.Force {
@@ -22,7 +22,7 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options Imag
if len(options.Platforms) > 0 {
p, err := encodePlatforms(options.Platforms...)
if err != nil {
return nil, err
return ImageRemoveResult{}, err
}
query["platforms"] = p
}
@@ -30,10 +30,10 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options Imag
resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
return ImageRemoveResult{}, err
}
var dels []image.DeleteResponse
err = json.NewDecoder(resp.Body).Decode(&dels)
return dels, err
return ImageRemoveResult{Deleted: dels}, err
}

View File

@@ -1,6 +1,9 @@
package client
import ocispec "github.com/opencontainers/image-spec/specs-go/v1"
import (
"github.com/moby/moby/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageRemoveOptions holds parameters to remove images.
type ImageRemoveOptions struct {
@@ -8,3 +11,8 @@ type ImageRemoveOptions struct {
Force bool
PruneChildren bool
}
// ImageRemoveResult holds the delete responses returned by the daemon.
type ImageRemoveResult struct {
Deleted []image.DeleteResponse
}

View File

@@ -2,21 +2,20 @@ package client
import (
"context"
"io"
"net/url"
)
// ImageSave retrieves one or more images from the docker host as an
// [io.ReadCloser].
// [ImageSaveResult].
//
// Platforms is an optional parameter that specifies the platforms to save
// from the image. Passing a platform only has an effect if the input image
// is a multi-platform image.
func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, saveOpts ...ImageSaveOption) (io.ReadCloser, error) {
func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, saveOpts ...ImageSaveOption) (ImageSaveResult, error) {
var opts imageSaveOpts
for _, opt := range saveOpts {
if err := opt.Apply(&opts); err != nil {
return nil, err
return ImageSaveResult{}, err
}
}
@@ -26,18 +25,18 @@ func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, saveOpts ..
if len(opts.apiOptions.Platforms) > 0 {
if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
return nil, err
return ImageSaveResult{}, err
}
p, err := encodePlatforms(opts.apiOptions.Platforms...)
if err != nil {
return nil, err
return ImageSaveResult{}, err
}
query["platform"] = p
}
resp, err := cli.get(ctx, "/images/get", query, nil)
if err != nil {
return nil, err
return ImageSaveResult{}, err
}
return resp.Body, nil
return newImageSaveResult(resp.Body), nil
}

View File

@@ -2,6 +2,8 @@ package client
import (
"fmt"
"io"
"sync"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
@@ -36,3 +38,34 @@ type imageSaveOptions struct {
// multi-platform image and has multiple variants.
Platforms []ocispec.Platform
}
func newImageSaveResult(rc io.ReadCloser) ImageSaveResult {
if rc == nil {
panic("nil io.ReadCloser")
}
return ImageSaveResult{
rc: rc,
close: sync.OnceValue(rc.Close),
}
}
type ImageSaveResult struct {
rc io.ReadCloser
close func() error
}
// Read implements io.ReadCloser
func (r ImageSaveResult) Read(p []byte) (n int, err error) {
if r.rc == nil {
return 0, io.EOF
}
return r.rc.Read(p)
}
// Close implements io.ReadCloser
func (r ImageSaveResult) Close() error {
if r.close == nil {
return nil
}
return r.close()
}

View File

@@ -13,7 +13,7 @@ import (
// ImageSearch makes the docker host search by a term in a remote registry.
// The list of results is not sorted in any fashion.
func (cli *Client) ImageSearch(ctx context.Context, term string, options ImageSearchOptions) ([]registry.SearchResult, error) {
func (cli *Client) ImageSearch(ctx context.Context, term string, options ImageSearchOptions) (ImageSearchResult, error) {
var results []registry.SearchResult
query := url.Values{}
query.Set("term", term)
@@ -28,16 +28,16 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options ImageSe
if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil {
newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx)
if privilegeErr != nil {
return results, privilegeErr
return ImageSearchResult{}, privilegeErr
}
resp, err = cli.tryImageSearch(ctx, query, newAuthHeader)
}
if err != nil {
return results, err
return ImageSearchResult{}, err
}
err = json.NewDecoder(resp.Body).Decode(&results)
return results, err
return ImageSearchResult{Items: results}, err
}
func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) {

View File

@@ -2,8 +2,15 @@ package client
import (
"context"
"github.com/moby/moby/api/types/registry"
)
// ImageSearchResult wraps results returned by ImageSearch.
type ImageSearchResult struct {
Items []registry.SearchResult
}
// ImageSearchOptions holds parameters to search images with.
type ImageSearchOptions struct {
RegistryAuth string

View File

@@ -9,19 +9,29 @@ import (
"github.com/distribution/reference"
)
type ImageTagOptions struct {
Source string
Target string
}
type ImageTagResult struct{}
// ImageTag tags an image in the docker host
func (cli *Client) ImageTag(ctx context.Context, source, target string) error {
func (cli *Client) ImageTag(ctx context.Context, options ImageTagOptions) (ImageTagResult, error) {
source := options.Source
target := options.Target
if _, err := reference.ParseAnyReference(source); err != nil {
return fmt.Errorf("error parsing reference: %q is not a valid repository/tag: %w", source, err)
return ImageTagResult{}, fmt.Errorf("error parsing reference: %q is not a valid repository/tag: %w", source, err)
}
ref, err := reference.ParseNormalizedNamed(target)
if err != nil {
return fmt.Errorf("error parsing reference: %q is not a valid repository/tag: %w", target, err)
return ImageTagResult{}, fmt.Errorf("error parsing reference: %q is not a valid repository/tag: %w", target, err)
}
if _, ok := ref.(reference.Digested); ok {
return errors.New("refusing to create a tag with a digest reference")
return ImageTagResult{}, errors.New("refusing to create a tag with a digest reference")
}
ref = reference.TagNameOnly(ref)
@@ -34,5 +44,5 @@ func (cli *Client) ImageTag(ctx context.Context, source, target string) error {
resp, err := cli.post(ctx, "/images/"+source+"/tag", query, nil, nil)
defer ensureReaderClosed(resp)
return err
return ImageTagResult{}, err
}

View File

@@ -0,0 +1,79 @@
package internal
import (
"context"
"encoding/json"
"errors"
"io"
"iter"
"sync"
"github.com/moby/moby/client/pkg/jsonmessage"
)
func NewJSONMessageStream(rc io.ReadCloser) stream {
if rc == nil {
panic("nil io.ReadCloser")
}
return stream{
rc: rc,
close: sync.OnceValue(rc.Close),
}
}
type stream struct {
rc io.ReadCloser
close func() error
}
// Read implements io.ReadCloser
func (r stream) Read(p []byte) (n int, err error) {
if r.rc == nil {
return 0, io.EOF
}
return r.rc.Read(p)
}
// Close implements io.ReadCloser
func (r stream) Close() error {
if r.close == nil {
return nil
}
return r.close()
}
// 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] {
context.AfterFunc(ctx, func() {
_ = r.Close()
})
dec := json.NewDecoder(r)
return func(yield func(jsonmessage.JSONMessage, error) bool) {
defer r.Close()
for {
var jm jsonmessage.JSONMessage
err := dec.Decode(&jm)
if errors.Is(err, io.EOF) {
break
}
if ctx.Err() != nil {
yield(jm, ctx.Err())
return
}
if !yield(jm, err) {
return
}
}
}
}
// Wait waits for operation to complete and detects errors reported as JSONMessage
func (r stream) Wait(ctx context.Context) error {
for _, err := range r.JSONMessages(ctx) {
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,26 +1,23 @@
package client
import (
"bytes"
"context"
"encoding/json"
"io"
"net/url"
"github.com/moby/moby/api/types/network"
)
// NetworkInspect returns the information for a specific network configured in the docker host.
func (cli *Client) NetworkInspect(ctx context.Context, networkID string, options NetworkInspectOptions) (network.Inspect, error) {
networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID, options)
return networkResource, err
// NetworkInspectResult contains the result of a network inspection.
type NetworkInspectResult struct {
Network network.Inspect
Raw []byte
}
// NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation.
func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, options NetworkInspectOptions) (network.Inspect, []byte, error) {
// NetworkInspect returns the information for a specific network configured in the docker host.
func (cli *Client) NetworkInspect(ctx context.Context, networkID string, options NetworkInspectOptions) (NetworkInspectResult, error) {
networkID, err := trimID("network", networkID)
if err != nil {
return network.Inspect{}, nil, err
return NetworkInspectResult{}, err
}
query := url.Values{}
if options.Verbose {
@@ -33,15 +30,10 @@ func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string,
resp, err := cli.get(ctx, "/networks/"+networkID, query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return network.Inspect{}, nil, err
return NetworkInspectResult{}, err
}
raw, err := io.ReadAll(resp.Body)
if err != nil {
return network.Inspect{}, nil, err
}
var nw network.Inspect
err = json.NewDecoder(bytes.NewReader(raw)).Decode(&nw)
return nw, raw, err
var out NetworkInspectResult
out.Raw, err = decodeWithRaw(resp, &out.Network)
return out, err
}

View File

@@ -8,16 +8,21 @@ import (
"github.com/moby/moby/api/types/network"
)
// NetworkListResult holds the result from the [Client.NetworkList] method.
type NetworkListResult struct {
Items []network.Summary
}
// NetworkList returns the list of networks configured in the docker host.
func (cli *Client) NetworkList(ctx context.Context, options NetworkListOptions) ([]network.Summary, error) {
func (cli *Client) NetworkList(ctx context.Context, options NetworkListOptions) (NetworkListResult, error) {
query := url.Values{}
options.Filters.updateURLValues(query)
var networkResources []network.Summary
resp, err := cli.get(ctx, "/networks", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return networkResources, err
return NetworkListResult{}, err
}
err = json.NewDecoder(resp.Body).Decode(&networkResources)
return networkResources, err
var res NetworkListResult
err = json.NewDecoder(resp.Body).Decode(&res.Items)
return res, err
}

View File

@@ -9,25 +9,30 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// NodeInspectWithRaw returns the node information.
func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) {
type NodeInspectResult struct {
Node swarm.Node
Raw []byte
}
// NodeInspect returns the node information.
func (cli *Client) NodeInspect(ctx context.Context, nodeID string, options NodeInspectOptions) (NodeInspectResult, error) {
nodeID, err := trimID("node", nodeID)
if err != nil {
return swarm.Node{}, nil, err
return NodeInspectResult{}, err
}
resp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.Node{}, nil, err
return NodeInspectResult{}, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return swarm.Node{}, nil, err
return NodeInspectResult{}, err
}
var response swarm.Node
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&response)
return response, body, err
return NodeInspectResult{Node: response, Raw: body}, err
}

View File

@@ -8,17 +8,21 @@ import (
"github.com/moby/moby/api/types/swarm"
)
type NodeListResult struct {
Items []swarm.Node
}
// NodeList returns the list of nodes.
func (cli *Client) NodeList(ctx context.Context, options NodeListOptions) ([]swarm.Node, error) {
func (cli *Client) NodeList(ctx context.Context, options NodeListOptions) (NodeListResult, error) {
query := url.Values{}
options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/nodes", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
return NodeListResult{}, err
}
var nodes []swarm.Node
err = json.NewDecoder(resp.Body).Decode(&nodes)
return nodes, err
return NodeListResult{Items: nodes}, err
}

View File

@@ -5,11 +5,13 @@ import (
"net/url"
)
type NodeRemoveResult struct{}
// NodeRemove removes a Node.
func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options NodeRemoveOptions) error {
func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options NodeRemoveOptions) (NodeRemoveResult, error) {
nodeID, err := trimID("node", nodeID)
if err != nil {
return err
return NodeRemoveResult{}, err
}
query := url.Values{}
@@ -19,5 +21,5 @@ func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options NodeRe
resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil)
defer ensureReaderClosed(resp)
return err
return NodeRemoveResult{}, err
}

View File

@@ -3,20 +3,20 @@ package client
import (
"context"
"net/url"
"github.com/moby/moby/api/types/swarm"
)
type NodeUpdateResult struct{}
// NodeUpdate updates a Node.
func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error {
func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, options NodeUpdateOptions) (NodeUpdateResult, error) {
nodeID, err := trimID("node", nodeID)
if err != nil {
return err
return NodeUpdateResult{}, err
}
query := url.Values{}
query.Set("version", version.String())
resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, node, nil)
query.Set("version", options.Version.String())
resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, options.Node, nil)
defer ensureReaderClosed(resp)
return err
return NodeUpdateResult{}, err
}

View File

@@ -108,15 +108,25 @@ func WithHost(host string) Opt {
if transport, ok := c.client.Transport.(*http.Transport); ok {
return sockets.ConfigureTransport(transport, c.proto, c.addr)
}
// For test transports (like transportFunc), we skip transport configuration
// but still set the host fields so that the client can use them for headers
if _, ok := c.client.Transport.(transportFunc); ok {
// For test transports, we skip transport configuration but still
// set the host fields so that the client can use them for headers
if _, ok := c.client.Transport.(testRoundTripper); ok {
return nil
}
return fmt.Errorf("cannot apply host to transport: %T", c.client.Transport)
}
}
// testRoundTripper allows us to inject a mock-transport for testing. We define it
// here so we can detect the tlsconfig and return nil for only this type.
type testRoundTripper func(*http.Request) (*http.Response, error)
func (tf testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return tf(req)
}
func (testRoundTripper) skipConfigureTransport() bool { return true }
// WithHostFromEnv overrides the client host with the host specified in the
// DOCKER_HOST ([EnvOverrideHost]) environment variable. If DOCKER_HOST is not set,
// or set to an empty value, the host is not modified.

View File

@@ -6,11 +6,42 @@ import (
"path"
"strings"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/swarm"
)
// PingOptions holds options for [client.Ping].
type PingOptions struct {
// Add future optional parameters here
}
// PingResult holds the result of a [Client.Ping] API call.
type PingResult struct {
APIVersion string
OSType string
Experimental bool
BuilderVersion build.BuilderVersion
// SwarmStatus provides information about the current swarm status of the
// engine, obtained from the "Swarm" header in the API response.
//
// It can be a nil struct if the API version does not provide this header
// in the ping response, or if an error occurred, in which case the client
// should use other ways to get the current swarm status, such as the /swarm
// endpoint.
SwarmStatus *SwarmStatus
}
// SwarmStatus provides information about the current swarm status and role,
// obtained from the "Swarm" header in the API response.
type SwarmStatus struct {
// NodeState represents the state of the node.
NodeState swarm.LocalNodeState
// ControlAvailable indicates if the node is a swarm manager.
ControlAvailable bool
}
// Ping pings the server and returns the value of the "Docker-Experimental",
// "Builder-Version", "OS-Type" & "API-Version" headers. It attempts to use
// a HEAD request on the endpoint, but falls back to GET if HEAD is not supported
@@ -18,13 +49,13 @@ import (
// may be returned if the daemon is in an unhealthy state, but returns errors
// for other non-success status codes, failing to connect to the API, or failing
// to parse the API response.
func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, error) {
// Using cli.buildRequest() + cli.doRequest() instead of cli.sendRequest()
// because ping requests are used during API version negotiation, so we want
// to hit the non-versioned /_ping endpoint, not /v1.xx/_ping
req, err := cli.buildRequest(ctx, http.MethodHead, path.Join(cli.basePath, "/_ping"), nil, nil)
if err != nil {
return types.Ping{}, err
return PingResult{}, err
}
resp, err := cli.doRequest(req)
defer ensureReaderClosed(resp)
@@ -33,7 +64,7 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
// we got a "OK" (200) status. For non-200 status-codes, we fall
// back to doing a GET request, as a HEAD request won't have a
// response-body to get error details from.
return newPingResponse(resp), nil
return newPingResult(resp), nil
}
// HEAD failed or returned a non-OK status; fallback to GET.
@@ -42,29 +73,29 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
defer ensureReaderClosed(resp)
if err != nil {
// Failed to connect.
return types.Ping{}, err
return PingResult{}, err
}
// GET request succeeded but may have returned a non-200 status.
// Return a Ping response, together with any error returned by
// the API server.
return newPingResponse(resp), checkResponseErr(resp)
return newPingResult(resp), checkResponseErr(resp)
}
func newPingResponse(resp *http.Response) types.Ping {
func newPingResult(resp *http.Response) PingResult {
if resp == nil {
return types.Ping{}
return PingResult{}
}
var swarmStatus *swarm.Status
var swarmStatus *SwarmStatus
if si := resp.Header.Get("Swarm"); si != "" {
state, role, _ := strings.Cut(si, "/")
swarmStatus = &swarm.Status{
swarmStatus = &SwarmStatus{
NodeState: swarm.LocalNodeState(state),
ControlAvailable: role == "manager",
}
}
return types.Ping{
return PingResult{
APIVersion: resp.Header.Get("Api-Version"),
OSType: resp.Header.Get("Ostype"),
Experimental: resp.Header.Get("Docker-Experimental") == "true",

View File

@@ -195,7 +195,7 @@ type JSONMessagesStream iter.Seq2[JSONMessage, error]
// each [JSONMessage] to out.
// see DisplayJSONMessages for details
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(JSONMessage)) error {
var dec = json.NewDecoder(in)
dec := json.NewDecoder(in)
var f JSONMessagesStream = func(yield func(JSONMessage, error) bool) {
for {
var jm JSONMessage
@@ -229,7 +229,7 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr,
// 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 {
var ids = make(map[string]uint)
ids := make(map[string]uint)
for jm, err := range messages {
var diff uint

View File

@@ -12,8 +12,13 @@ type PluginCreateOptions struct {
RepoName string
}
// PluginCreateResult represents the result of a plugin create operation.
type PluginCreateResult struct {
// Currently empty; can be extended in the future if needed.
}
// PluginCreate creates a plugin
func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, createOptions PluginCreateOptions) error {
func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, createOptions PluginCreateOptions) (PluginCreateResult, error) {
headers := http.Header(make(map[string][]string))
headers.Set("Content-Type", "application/x-tar")
@@ -22,5 +27,5 @@ func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, cr
resp, err := cli.postRaw(ctx, "/plugins/create", query, createContext, headers)
defer ensureReaderClosed(resp)
return err
return PluginCreateResult{}, err
}

View File

@@ -10,11 +10,16 @@ type PluginDisableOptions struct {
Force bool
}
// PluginDisableResult represents the result of a plugin disable operation.
type PluginDisableResult struct {
// Currently empty; can be extended in the future if needed.
}
// PluginDisable disables a plugin
func (cli *Client) PluginDisable(ctx context.Context, name string, options PluginDisableOptions) error {
func (cli *Client) PluginDisable(ctx context.Context, name string, options PluginDisableOptions) (PluginDisableResult, error) {
name, err := trimID("plugin", name)
if err != nil {
return err
return PluginDisableResult{}, err
}
query := url.Values{}
if options.Force {
@@ -22,5 +27,5 @@ func (cli *Client) PluginDisable(ctx context.Context, name string, options Plugi
}
resp, err := cli.post(ctx, "/plugins/"+name+"/disable", query, nil, nil)
defer ensureReaderClosed(resp)
return err
return PluginDisableResult{}, err
}

View File

@@ -11,16 +11,21 @@ type PluginEnableOptions struct {
Timeout int
}
// PluginEnableResult represents the result of a plugin enable operation.
type PluginEnableResult struct {
// Currently empty; can be extended in the future if needed.
}
// PluginEnable enables a plugin
func (cli *Client) PluginEnable(ctx context.Context, name string, options PluginEnableOptions) error {
func (cli *Client) PluginEnable(ctx context.Context, name string, options PluginEnableOptions) (PluginEnableResult, error) {
name, err := trimID("plugin", name)
if err != nil {
return err
return PluginEnableResult{}, err
}
query := url.Values{}
query.Set("timeout", strconv.Itoa(options.Timeout))
resp, err := cli.post(ctx, "/plugins/"+name+"/enable", query, nil, nil)
defer ensureReaderClosed(resp)
return err
return PluginEnableResult{}, err
}

View File

@@ -1,32 +1,35 @@
package client
import (
"bytes"
"context"
"encoding/json"
"io"
"github.com/moby/moby/api/types/plugin"
)
// PluginInspectWithRaw inspects an existing plugin
func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*plugin.Plugin, []byte, error) {
// PluginInspectOptions holds parameters to inspect a plugin.
type PluginInspectOptions struct {
// Add future optional parameters here
}
// PluginInspectResult holds the result from the [Client.PluginInspect] method.
type PluginInspectResult struct {
Raw []byte
Plugin plugin.Plugin
}
// PluginInspect inspects an existing plugin
func (cli *Client) PluginInspect(ctx context.Context, name string, options PluginInspectOptions) (PluginInspectResult, error) {
name, err := trimID("plugin", name)
if err != nil {
return nil, nil, err
return PluginInspectResult{}, err
}
resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, nil, err
return PluginInspectResult{}, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}
var p plugin.Plugin
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&p)
return &p, body, err
var out PluginInspectResult
out.Raw, err = decodeWithRaw(resp, &out.Plugin)
return out, err
}

View File

@@ -33,17 +33,23 @@ type PluginInstallOptions struct {
Args []string
}
// PluginInstallResult holds the result of a plugin install operation.
// It is an io.ReadCloser from which the caller can read installation progress or result.
type PluginInstallResult struct {
io.ReadCloser
}
// PluginInstall installs a plugin
func (cli *Client) PluginInstall(ctx context.Context, name string, options PluginInstallOptions) (_ io.ReadCloser, retErr error) {
func (cli *Client) PluginInstall(ctx context.Context, name string, options PluginInstallOptions) (_ PluginInstallResult, retErr error) {
query := url.Values{}
if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil {
return nil, fmt.Errorf("invalid remote reference: %w", err)
return PluginInstallResult{}, fmt.Errorf("invalid remote reference: %w", err)
}
query.Set("remote", options.RemoteRef)
privileges, err := cli.checkPluginPermissions(ctx, query, options)
privileges, err := cli.checkPluginPermissions(ctx, query, &options)
if err != nil {
return nil, err
return PluginInstallResult{}, err
}
// set name for plugin pull, if empty should default to remote reference
@@ -51,7 +57,7 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options Plugi
resp, err := cli.tryPluginPull(ctx, query, privileges, options.RegistryAuth)
if err != nil {
return nil, err
return PluginInstallResult{}, err
}
name = resp.Header.Get("Docker-Plugin-Name")
@@ -70,7 +76,7 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options Plugi
}
}()
if len(options.Args) > 0 {
if err := cli.PluginSet(ctx, name, options.Args); err != nil {
if _, err := cli.PluginSet(ctx, name, PluginSetOptions{Args: options.Args}); err != nil {
_ = pw.CloseWithError(err)
return
}
@@ -81,10 +87,10 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options Plugi
return
}
enableErr := cli.PluginEnable(ctx, name, PluginEnableOptions{Timeout: 0})
_, enableErr := cli.PluginEnable(ctx, name, PluginEnableOptions{Timeout: 0})
_ = pw.CloseWithError(enableErr)
}()
return pr, nil
return PluginInstallResult{pr}, nil
}
func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) {
@@ -99,17 +105,17 @@ func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileg
})
}
func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, options PluginInstallOptions) (plugin.Privileges, error) {
resp, err := cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil {
func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, options pluginOptions) (plugin.Privileges, error) {
resp, err := cli.tryPluginPrivileges(ctx, query, options.getRegistryAuth())
if cerrdefs.IsUnauthorized(err) && options.getPrivilegeFunc() != nil {
// TODO: do inspect before to check existing name before checking privileges
newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx)
newAuthHeader, privilegeErr := options.getPrivilegeFunc()(ctx)
if privilegeErr != nil {
ensureReaderClosed(resp)
return nil, privilegeErr
}
options.RegistryAuth = newAuthHeader
resp, err = cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
options.setRegistryAuth(newAuthHeader)
resp, err = cli.tryPluginPrivileges(ctx, query, options.getRegistryAuth())
}
if err != nil {
ensureReaderClosed(resp)
@@ -123,14 +129,47 @@ func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values,
}
ensureReaderClosed(resp)
if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 {
accept, err := options.AcceptPermissionsFunc(ctx, privileges)
if !options.getAcceptAllPermissions() && options.getAcceptPermissionsFunc() != nil && len(privileges) > 0 {
accept, err := options.getAcceptPermissionsFunc()(ctx, privileges)
if err != nil {
return nil, err
}
if !accept {
return nil, errors.New("permission denied while installing plugin " + options.RemoteRef)
return nil, errors.New("permission denied while installing plugin " + options.getRemoteRef())
}
}
return privileges, nil
}
type pluginOptions interface {
getRegistryAuth() string
setRegistryAuth(string)
getPrivilegeFunc() func(context.Context) (string, error)
getAcceptAllPermissions() bool
getAcceptPermissionsFunc() func(context.Context, plugin.Privileges) (bool, error)
getRemoteRef() string
}
func (o *PluginInstallOptions) getRegistryAuth() string {
return o.RegistryAuth
}
func (o *PluginInstallOptions) setRegistryAuth(auth string) {
o.RegistryAuth = auth
}
func (o *PluginInstallOptions) getPrivilegeFunc() func(context.Context) (string, error) {
return o.PrivilegeFunc
}
func (o *PluginInstallOptions) getAcceptAllPermissions() bool {
return o.AcceptAllPermissions
}
func (o *PluginInstallOptions) getAcceptPermissionsFunc() func(context.Context, plugin.Privileges) (bool, error) {
return o.AcceptPermissionsFunc
}
func (o *PluginInstallOptions) getRemoteRef() string {
return o.RemoteRef
}

View File

@@ -13,18 +13,23 @@ type PluginListOptions struct {
Filters Filters
}
// PluginListResult represents the result of a plugin list operation.
type PluginListResult struct {
Items []*plugin.Plugin
}
// PluginList returns the installed plugins
func (cli *Client) PluginList(ctx context.Context, opts PluginListOptions) (plugin.ListResponse, error) {
var plugins plugin.ListResponse
func (cli *Client) PluginList(ctx context.Context, options PluginListOptions) (PluginListResult, error) {
query := url.Values{}
opts.Filters.updateURLValues(query)
options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/plugins", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return plugins, err
return PluginListResult{}, err
}
var plugins plugin.ListResponse
err = json.NewDecoder(resp.Body).Decode(&plugins)
return plugins, err
return PluginListResult{Items: plugins}, err
}

View File

@@ -8,17 +8,27 @@ import (
"github.com/moby/moby/api/types/registry"
)
// PluginPushOptions holds parameters to push a plugin.
type PluginPushOptions struct {
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
}
// PluginPushResult is the result of a plugin push operation
type PluginPushResult struct {
io.ReadCloser
}
// PluginPush pushes a plugin to a registry
func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) {
func (cli *Client) PluginPush(ctx context.Context, name string, options PluginPushOptions) (PluginPushResult, error) {
name, err := trimID("plugin", name)
if err != nil {
return nil, err
return PluginPushResult{}, err
}
resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, http.Header{
registry.AuthHeader: {registryAuth},
registry.AuthHeader: {options.RegistryAuth},
})
if err != nil {
return nil, err
return PluginPushResult{}, err
}
return resp.Body, nil
return PluginPushResult{resp.Body}, nil
}

View File

@@ -10,11 +10,16 @@ type PluginRemoveOptions struct {
Force bool
}
// PluginRemoveResult represents the result of a plugin removal.
type PluginRemoveResult struct {
// Currently empty; can be extended in the future if needed.
}
// PluginRemove removes a plugin
func (cli *Client) PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) error {
func (cli *Client) PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) (PluginRemoveResult, error) {
name, err := trimID("plugin", name)
if err != nil {
return err
return PluginRemoveResult{}, err
}
query := url.Values{}
@@ -24,5 +29,5 @@ func (cli *Client) PluginRemove(ctx context.Context, name string, options Plugin
resp, err := cli.delete(ctx, "/plugins/"+name, query, nil)
defer ensureReaderClosed(resp)
return err
return PluginRemoveResult{}, err
}

View File

@@ -4,14 +4,24 @@ import (
"context"
)
// PluginSetOptions defines options for modifying a plugin's settings.
type PluginSetOptions struct {
Args []string
}
// PluginSetResult represents the result of a plugin set operation.
type PluginSetResult struct {
// Currently empty; can be extended in the future if needed.
}
// PluginSet modifies settings for an existing plugin
func (cli *Client) PluginSet(ctx context.Context, name string, args []string) error {
func (cli *Client) PluginSet(ctx context.Context, name string, options PluginSetOptions) (PluginSetResult, error) {
name, err := trimID("plugin", name)
if err != nil {
return err
return PluginSetResult{}, err
}
resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, args, nil)
resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, options.Args, nil)
defer ensureReaderClosed(resp)
return err
return PluginSetResult{}, err
}

View File

@@ -12,8 +12,29 @@ import (
"github.com/moby/moby/api/types/registry"
)
// PluginUpgradeOptions holds parameters to upgrade a plugin.
type PluginUpgradeOptions struct {
Disabled bool
AcceptAllPermissions bool
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
RemoteRef string // RemoteRef is the plugin name on the registry
// PrivilegeFunc is a function that clients can supply to retry operations
// after getting an authorization error. This function returns the registry
// authentication header value in base64 encoded format, or an error if the
// privilege request fails.
//
// For details, refer to [github.com/moby/moby/api/types/registry.RequestAuthConfig].
PrivilegeFunc func(context.Context) (string, error)
AcceptPermissionsFunc func(context.Context, plugin.Privileges) (bool, error)
Args []string
}
// PluginUpgradeResult holds the result of a plugin upgrade operation.
type PluginUpgradeResult io.ReadCloser
// PluginUpgrade upgrades a plugin
func (cli *Client) PluginUpgrade(ctx context.Context, name string, options PluginInstallOptions) (io.ReadCloser, error) {
func (cli *Client) PluginUpgrade(ctx context.Context, name string, options PluginUpgradeOptions) (PluginUpgradeResult, error) {
name, err := trimID("plugin", name)
if err != nil {
return nil, err
@@ -25,7 +46,7 @@ func (cli *Client) PluginUpgrade(ctx context.Context, name string, options Plugi
}
query.Set("remote", options.RemoteRef)
privileges, err := cli.checkPluginPermissions(ctx, query, options)
privileges, err := cli.checkPluginPermissions(ctx, query, &options)
if err != nil {
return nil, err
}
@@ -42,3 +63,27 @@ func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privi
registry.AuthHeader: {registryAuth},
})
}
func (o *PluginUpgradeOptions) getRegistryAuth() string {
return o.RegistryAuth
}
func (o *PluginUpgradeOptions) setRegistryAuth(auth string) {
o.RegistryAuth = auth
}
func (o *PluginUpgradeOptions) getPrivilegeFunc() func(context.Context) (string, error) {
return o.PrivilegeFunc
}
func (o *PluginUpgradeOptions) getAcceptAllPermissions() bool {
return o.AcceptAllPermissions
}
func (o *PluginUpgradeOptions) getAcceptPermissionsFunc() func(context.Context, plugin.Privileges) (bool, error) {
return o.AcceptPermissionsFunc
}
func (o *PluginUpgradeOptions) getRemoteRef() string {
return o.RemoteRef
}

View File

@@ -7,15 +7,28 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SecretCreateOptions holds options for creating a secret.
type SecretCreateOptions struct {
Spec swarm.SecretSpec
}
// SecretCreateResult holds the result from the [Client.SecretCreate] method.
type SecretCreateResult struct {
ID string
}
// SecretCreate creates a new secret.
func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error) {
resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil)
func (cli *Client) SecretCreate(ctx context.Context, options SecretCreateOptions) (SecretCreateResult, error) {
resp, err := cli.post(ctx, "/secrets/create", nil, options.Spec, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.SecretCreateResponse{}, err
return SecretCreateResult{}, err
}
var response swarm.SecretCreateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
var out swarm.ConfigCreateResponse
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
return SecretCreateResult{}, err
}
return SecretCreateResult{ID: out.ID}, nil
}

View File

@@ -1,34 +1,35 @@
package client
import (
"bytes"
"context"
"encoding/json"
"io"
"github.com/moby/moby/api/types/swarm"
)
// SecretInspectWithRaw returns the secret information with raw data
func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.Secret, []byte, error) {
// SecretInspectOptions holds options for inspecting a secret.
type SecretInspectOptions struct {
// Add future optional parameters here
}
// SecretInspectResult holds the result from the [Client.SecretInspect]. method.
type SecretInspectResult struct {
Secret swarm.Secret
Raw []byte
}
// SecretInspect returns the secret information with raw data.
func (cli *Client) SecretInspect(ctx context.Context, id string, options SecretInspectOptions) (SecretInspectResult, error) {
id, err := trimID("secret", id)
if err != nil {
return swarm.Secret{}, nil, err
return SecretInspectResult{}, err
}
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.Secret{}, nil, err
return SecretInspectResult{}, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return swarm.Secret{}, nil, err
}
var secret swarm.Secret
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&secret)
return secret, body, err
var out SecretInspectResult
out.Raw, err = decodeWithRaw(resp, &out.Secret)
return out, err
}

View File

@@ -8,18 +8,31 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SecretList returns the list of secrets.
func (cli *Client) SecretList(ctx context.Context, options SecretListOptions) ([]swarm.Secret, error) {
query := url.Values{}
// SecretListOptions holds parameters to list secrets
type SecretListOptions struct {
Filters Filters
}
// SecretListResult holds the result from the [client.SecretList] method.
type SecretListResult struct {
Items []swarm.Secret
}
// SecretList returns the list of secrets.
func (cli *Client) SecretList(ctx context.Context, options SecretListOptions) (SecretListResult, error) {
query := url.Values{}
options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/secrets", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
return SecretListResult{}, err
}
var secrets []swarm.Secret
err = json.NewDecoder(resp.Body).Decode(&secrets)
return secrets, err
var out SecretListResult
err = json.NewDecoder(resp.Body).Decode(&out.Items)
if err != nil {
return SecretListResult{}, err
}
return out, nil
}

View File

@@ -1,6 +0,0 @@
package client
// SecretListOptions holds parameters to list secrets
type SecretListOptions struct {
Filters Filters
}

View File

@@ -2,13 +2,24 @@ package client
import "context"
type SecretRemoveOptions struct {
// Add future optional parameters here
}
type SecretRemoveResult struct {
// Add future fields here
}
// SecretRemove removes a secret.
func (cli *Client) SecretRemove(ctx context.Context, id string) error {
func (cli *Client) SecretRemove(ctx context.Context, id string, options SecretRemoveOptions) (SecretRemoveResult, error) {
id, err := trimID("secret", id)
if err != nil {
return err
return SecretRemoveResult{}, err
}
resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp)
return err
if err != nil {
return SecretRemoveResult{}, err
}
return SecretRemoveResult{}, nil
}

View File

@@ -7,15 +7,26 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SecretUpdateOptions holds options for updating a secret.
type SecretUpdateOptions struct {
Version swarm.Version
Spec swarm.SecretSpec
}
type SecretUpdateResult struct{}
// SecretUpdate attempts to update a secret.
func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
func (cli *Client) SecretUpdate(ctx context.Context, id string, options SecretUpdateOptions) (SecretUpdateResult, error) {
id, err := trimID("secret", id)
if err != nil {
return err
return SecretUpdateResult{}, err
}
query := url.Values{}
query.Set("version", version.String())
resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, secret, nil)
query.Set("version", options.Version.String())
resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, options.Spec, nil)
defer ensureReaderClosed(resp)
return err
if err != nil {
return SecretUpdateResult{}, err
}
return SecretUpdateResult{}, nil
}

View File

@@ -14,35 +14,59 @@ import (
"github.com/opencontainers/go-digest"
)
// ServiceCreate creates a new service.
func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options ServiceCreateOptions) (swarm.ServiceCreateResponse, error) {
var response swarm.ServiceCreateResponse
// ServiceCreateOptions contains the options to use when creating a service.
type ServiceCreateOptions struct {
// EncodedRegistryAuth is the encoded registry authorization credentials to
// use when updating the service.
//
// This field follows the format of the X-Registry-Auth header.
EncodedRegistryAuth string
// QueryRegistry indicates whether the service update requires
// contacting a registry. A registry may be contacted to retrieve
// the image digest and manifest, which in turn can be used to update
// platform or other information about the service.
QueryRegistry bool
}
// ServiceCreateResult represents the result of creating a service.
type ServiceCreateResult struct {
// ID is the ID of the created service.
ID string
// Warnings is a list of warnings that occurred during service creation.
Warnings []string
}
// ServiceCreate creates a new service.
func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options ServiceCreateOptions) (ServiceCreateResult, error) {
// Make sure containerSpec is not nil when no runtime is set or the runtime is set to container
if service.TaskTemplate.ContainerSpec == nil && (service.TaskTemplate.Runtime == "" || service.TaskTemplate.Runtime == swarm.RuntimeContainer) {
service.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{}
}
if err := validateServiceSpec(service); err != nil {
return response, err
return ServiceCreateResult{}, err
}
// ensure that the image is tagged
var resolveWarning string
var warnings []string
switch {
case service.TaskTemplate.ContainerSpec != nil:
if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" {
service.TaskTemplate.ContainerSpec.Image = taggedImg
}
if options.QueryRegistry {
resolveWarning = resolveContainerSpecImage(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
resolveWarning := resolveContainerSpecImage(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
}
case service.TaskTemplate.PluginSpec != nil:
if taggedImg := imageWithTagString(service.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
service.TaskTemplate.PluginSpec.Remote = taggedImg
}
if options.QueryRegistry {
resolveWarning = resolvePluginSpecRemote(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
resolveWarning := resolvePluginSpecRemote(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
}
}
@@ -53,15 +77,17 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
resp, err := cli.post(ctx, "/services/create", nil, service, headers)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
return ServiceCreateResult{}, err
}
var response swarm.ServiceCreateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
if resolveWarning != "" {
response.Warnings = append(response.Warnings, resolveWarning)
}
warnings = append(warnings, response.Warnings...)
return response, err
return ServiceCreateResult{
ID: response.ID,
Warnings: warnings,
}, err
}
func resolveContainerSpecImage(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
@@ -97,7 +123,9 @@ func resolvePluginSpecRemote(ctx context.Context, cli DistributionAPIClient, tas
}
func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {
distributionInspect, err := cli.DistributionInspect(ctx, image, encodedAuth)
distributionInspect, err := cli.DistributionInspect(ctx, image, DistributionInspectOptions{
EncodedRegistryAuth: encodedAuth,
})
var platforms []swarm.Platform
if err != nil {
return "", nil, err

View File

@@ -1,38 +1,40 @@
package client
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/url"
"github.com/moby/moby/api/types/swarm"
)
// ServiceInspectWithRaw returns the service information and the raw data.
func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts ServiceInspectOptions) (swarm.Service, []byte, error) {
// ServiceInspectOptions holds parameters related to the service inspect operation.
type ServiceInspectOptions struct {
InsertDefaults bool
}
// ServiceInspectResult represents the result of a service inspect operation.
type ServiceInspectResult struct {
Service swarm.Service
Raw []byte
}
// ServiceInspect retrieves detailed information about a specific service by its ID.
func (cli *Client) ServiceInspect(ctx context.Context, serviceID string, options ServiceInspectOptions) (ServiceInspectResult, error) {
serviceID, err := trimID("service", serviceID)
if err != nil {
return swarm.Service{}, nil, err
return ServiceInspectResult{}, err
}
query := url.Values{}
query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults))
query.Set("insertDefaults", fmt.Sprintf("%v", options.InsertDefaults))
resp, err := cli.get(ctx, "/services/"+serviceID, query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.Service{}, nil, err
return ServiceInspectResult{}, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return swarm.Service{}, nil, err
}
var response swarm.Service
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&response)
return response, body, err
var out ServiceInspectResult
out.Raw, err = decodeWithRaw(resp, &out.Service)
return out, err
}

View File

@@ -8,8 +8,22 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// ServiceListOptions holds parameters to list services with.
type ServiceListOptions struct {
Filters Filters
// Status indicates whether the server should include the service task
// count of running and desired tasks.
Status bool
}
// ServiceListResult represents the result of a service list operation.
type ServiceListResult struct {
Items []swarm.Service
}
// ServiceList returns the list of services.
func (cli *Client) ServiceList(ctx context.Context, options ServiceListOptions) ([]swarm.Service, error) {
func (cli *Client) ServiceList(ctx context.Context, options ServiceListOptions) (ServiceListResult, error) {
query := url.Values{}
options.Filters.updateURLValues(query)
@@ -21,10 +35,10 @@ func (cli *Client) ServiceList(ctx context.Context, options ServiceListOptions)
resp, err := cli.get(ctx, "/services", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
return ServiceListResult{}, err
}
var services []swarm.Service
err = json.NewDecoder(resp.Body).Decode(&services)
return services, err
return ServiceListResult{Items: services}, err
}

View File

@@ -5,17 +5,37 @@ import (
"fmt"
"io"
"net/url"
"sync"
"time"
"github.com/moby/moby/client/internal/timestamp"
)
// ServiceLogs returns the logs generated by a service in an [io.ReadCloser].
// ServiceLogsOptions holds parameters to filter logs with.
type ServiceLogsOptions struct {
ShowStdout bool
ShowStderr bool
Since string
Until string
Timestamps bool
Follow bool
Tail string
Details bool
}
// ServiceLogsResult holds the result of a service logs operation.
// It implements [io.ReadCloser].
// It's up to the caller to close the stream.
func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options ContainerLogsOptions) (io.ReadCloser, error) {
type ServiceLogsResult struct {
rc io.ReadCloser
close func() error
}
// ServiceLogs returns the logs generated by a service in an [ServiceLogsResult].
func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options ServiceLogsOptions) (ServiceLogsResult, error) {
serviceID, err := trimID("service", serviceID)
if err != nil {
return nil, err
return ServiceLogsResult{}, err
}
query := url.Values{}
@@ -30,7 +50,7 @@ func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options Co
if options.Since != "" {
ts, err := timestamp.GetTimestamp(options.Since, time.Now())
if err != nil {
return nil, fmt.Errorf(`invalid value for "since": %w`, err)
return ServiceLogsResult{}, fmt.Errorf(`invalid value for "since": %w`, err)
}
query.Set("since", ts)
}
@@ -50,7 +70,33 @@ func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options Co
resp, err := cli.get(ctx, "/services/"+serviceID+"/logs", query, nil)
if err != nil {
return nil, err
return ServiceLogsResult{}, err
}
return resp.Body, nil
return newServiceLogsResult(resp.Body), nil
}
func newServiceLogsResult(rc io.ReadCloser) ServiceLogsResult {
if rc == nil {
panic("nil io.ReadCloser")
}
return ServiceLogsResult{
rc: rc,
close: sync.OnceValue(rc.Close),
}
}
// Read implements [io.ReadCloser] for LogsResult.
func (r ServiceLogsResult) Read(p []byte) (n int, err error) {
if r.rc == nil {
return 0, io.EOF
}
return r.rc.Read(p)
}
// Close implements [io.ReadCloser] for LogsResult.
func (r ServiceLogsResult) Close() error {
if r.close == nil {
return nil
}
return r.close()
}

View File

@@ -2,14 +2,24 @@ package client
import "context"
// ServiceRemoveOptions contains options for removing a service.
type ServiceRemoveOptions struct {
// No options currently; placeholder for future use
}
// ServiceRemoveResult contains the result of removing a service.
type ServiceRemoveResult struct {
// No fields currently; placeholder for future use
}
// ServiceRemove kills and removes a service.
func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error {
func (cli *Client) ServiceRemove(ctx context.Context, serviceID string, options ServiceRemoveOptions) (ServiceRemoveResult, error) {
serviceID, err := trimID("service", serviceID)
if err != nil {
return err
return ServiceRemoveResult{}, err
}
resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil)
defer ensureReaderClosed(resp)
return err
return ServiceRemoveResult{}, err
}

View File

@@ -10,18 +10,54 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// ServiceUpdateOptions contains the options to be used for updating services.
type ServiceUpdateOptions struct {
// EncodedRegistryAuth is the encoded registry authorization credentials to
// use when updating the service.
//
// This field follows the format of the X-Registry-Auth header.
EncodedRegistryAuth string
// TODO(stevvooe): Consider moving the version parameter of ServiceUpdate
// into this field. While it does open API users up to racy writes, most
// users may not need that level of consistency in practice.
// RegistryAuthFrom specifies where to find the registry authorization
// credentials if they are not given in EncodedRegistryAuth. Valid
// values are "spec" and "previous-spec".
RegistryAuthFrom string
// Rollback indicates whether a server-side rollback should be
// performed. When this is set, the provided spec will be ignored.
// The valid values are "previous" and "none". An empty value is the
// same as "none".
Rollback string
// QueryRegistry indicates whether the service update requires
// contacting a registry. A registry may be contacted to retrieve
// the image digest and manifest, which in turn can be used to update
// platform or other information about the service.
QueryRegistry bool
}
// ServiceUpdateResult represents the result of a service update.
type ServiceUpdateResult struct {
// Warnings contains any warnings that occurred during the update.
Warnings []string
}
// ServiceUpdate updates a Service. The version number is required to avoid
// conflicting writes. It must be the value as set *before* the update.
// You can find this value in the [swarm.Service.Meta] field, which can
// be found using [Client.ServiceInspectWithRaw].
func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options ServiceUpdateOptions) (ServiceUpdateResult, error) {
serviceID, err := trimID("service", serviceID)
if err != nil {
return swarm.ServiceUpdateResponse{}, err
return ServiceUpdateResult{}, err
}
if err := validateServiceSpec(service); err != nil {
return swarm.ServiceUpdateResponse{}, err
return ServiceUpdateResult{}, err
}
query := url.Values{}
@@ -36,21 +72,23 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
query.Set("version", version.String())
// ensure that the image is tagged
var resolveWarning string
var warnings []string
switch {
case service.TaskTemplate.ContainerSpec != nil:
if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" {
service.TaskTemplate.ContainerSpec.Image = taggedImg
}
if options.QueryRegistry {
resolveWarning = resolveContainerSpecImage(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
resolveWarning := resolveContainerSpecImage(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
}
case service.TaskTemplate.PluginSpec != nil:
if taggedImg := imageWithTagString(service.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
service.TaskTemplate.PluginSpec.Remote = taggedImg
}
if options.QueryRegistry {
resolveWarning = resolvePluginSpecRemote(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
resolveWarning := resolvePluginSpecRemote(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
}
}
@@ -61,14 +99,11 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.ServiceUpdateResponse{}, err
return ServiceUpdateResult{}, err
}
var response swarm.ServiceUpdateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
if resolveWarning != "" {
response.Warnings = append(response.Warnings, resolveWarning)
}
return response, err
warnings = append(warnings, response.Warnings...)
return ServiceUpdateResult{Warnings: warnings}, err
}

View File

@@ -1,6 +0,0 @@
package client
// ConfigListOptions holds parameters to list configs
type ConfigListOptions struct {
Filters Filters
}

View File

@@ -7,15 +7,20 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmGetUnlockKeyResult contains the swarm unlock key.
type SwarmGetUnlockKeyResult struct {
Key string
}
// SwarmGetUnlockKey retrieves the swarm's unlock key.
func (cli *Client) SwarmGetUnlockKey(ctx context.Context) (swarm.UnlockKeyResponse, error) {
func (cli *Client) SwarmGetUnlockKey(ctx context.Context) (SwarmGetUnlockKeyResult, error) {
resp, err := cli.get(ctx, "/swarm/unlockkey", nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.UnlockKeyResponse{}, err
return SwarmGetUnlockKeyResult{}, err
}
var response swarm.UnlockKeyResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
return SwarmGetUnlockKeyResult{Key: response.UnlockKey}, err
}

View File

@@ -3,19 +3,52 @@ package client
import (
"context"
"encoding/json"
"net/netip"
"github.com/moby/moby/api/types/swarm"
)
// SwarmInitOptions contains options for initializing a new swarm.
type SwarmInitOptions struct {
ListenAddr string
AdvertiseAddr string
DataPathAddr string
DataPathPort uint32
ForceNewCluster bool
Spec swarm.Spec
AutoLockManagers bool
Availability swarm.NodeAvailability
DefaultAddrPool []netip.Prefix
SubnetSize uint32
}
// SwarmInitResult contains the result of a SwarmInit operation.
type SwarmInitResult struct {
NodeID string
}
// SwarmInit initializes the swarm.
func (cli *Client) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) {
func (cli *Client) SwarmInit(ctx context.Context, options SwarmInitOptions) (SwarmInitResult, error) {
req := swarm.InitRequest{
ListenAddr: options.ListenAddr,
AdvertiseAddr: options.AdvertiseAddr,
DataPathAddr: options.DataPathAddr,
DataPathPort: options.DataPathPort,
ForceNewCluster: options.ForceNewCluster,
Spec: options.Spec,
AutoLockManagers: options.AutoLockManagers,
Availability: options.Availability,
DefaultAddrPool: options.DefaultAddrPool,
SubnetSize: options.SubnetSize,
}
resp, err := cli.post(ctx, "/swarm/init", nil, req, nil)
defer ensureReaderClosed(resp)
if err != nil {
return "", err
return SwarmInitResult{}, err
}
var response string
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
var nodeID string
err = json.NewDecoder(resp.Body).Decode(&nodeID)
return SwarmInitResult{NodeID: nodeID}, err
}

View File

@@ -7,15 +7,25 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmInspectOptions holds options for inspecting a swarm.
type SwarmInspectOptions struct {
// Add future optional parameters here
}
// SwarmInspectResult represents the result of a SwarmInspect operation.
type SwarmInspectResult struct {
Swarm swarm.Swarm
}
// SwarmInspect inspects the swarm.
func (cli *Client) SwarmInspect(ctx context.Context) (swarm.Swarm, error) {
func (cli *Client) SwarmInspect(ctx context.Context, options SwarmInspectOptions) (SwarmInspectResult, error) {
resp, err := cli.get(ctx, "/swarm", nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.Swarm{}, err
return SwarmInspectResult{}, err
}
var response swarm.Swarm
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
var s swarm.Swarm
err = json.NewDecoder(resp.Body).Decode(&s)
return SwarmInspectResult{Swarm: s}, err
}

View File

@@ -6,9 +6,33 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmJoinOptions specifies options for joining a swarm.
type SwarmJoinOptions struct {
ListenAddr string
AdvertiseAddr string
DataPathAddr string
RemoteAddrs []string
JoinToken string // accept by secret
Availability swarm.NodeAvailability
}
// SwarmJoinResult contains the result of joining a swarm.
type SwarmJoinResult struct {
// No fields currently; placeholder for future use
}
// SwarmJoin joins the swarm.
func (cli *Client) SwarmJoin(ctx context.Context, req swarm.JoinRequest) error {
func (cli *Client) SwarmJoin(ctx context.Context, options SwarmJoinOptions) (SwarmJoinResult, error) {
req := swarm.JoinRequest{
ListenAddr: options.ListenAddr,
AdvertiseAddr: options.AdvertiseAddr,
DataPathAddr: options.DataPathAddr,
RemoteAddrs: options.RemoteAddrs,
JoinToken: options.JoinToken,
Availability: options.Availability,
}
resp, err := cli.post(ctx, "/swarm/join", nil, req, nil)
defer ensureReaderClosed(resp)
return err
return SwarmJoinResult{}, err
}

View File

@@ -5,13 +5,21 @@ import (
"net/url"
)
// SwarmLeaveOptions contains options for leaving a swarm.
type SwarmLeaveOptions struct {
Force bool
}
// SwarmLeaveResult represents the result of a SwarmLeave operation.
type SwarmLeaveResult struct{}
// SwarmLeave leaves the swarm.
func (cli *Client) SwarmLeave(ctx context.Context, force bool) error {
func (cli *Client) SwarmLeave(ctx context.Context, options SwarmLeaveOptions) (SwarmLeaveResult, error) {
query := url.Values{}
if force {
if options.Force {
query.Set("force", "1")
}
resp, err := cli.post(ctx, "/swarm/leave", query, nil, nil)
defer ensureReaderClosed(resp)
return err
return SwarmLeaveResult{}, err
}

View File

@@ -0,0 +1,4 @@
package client
// NodeInspectOptions holds parameters to inspect nodes with.
type NodeInspectOptions struct{}

View File

@@ -0,0 +1,9 @@
package client
import "github.com/moby/moby/api/types/swarm"
// NodeUpdateOptions holds parameters to update nodes with.
type NodeUpdateOptions struct {
Version swarm.Version
Node swarm.NodeSpec
}

View File

@@ -1,16 +0,0 @@
package client
// ServiceCreateOptions contains the options to use when creating a service.
type ServiceCreateOptions struct {
// EncodedRegistryAuth is the encoded registry authorization credentials to
// use when updating the service.
//
// This field follows the format of the X-Registry-Auth header.
EncodedRegistryAuth string
// QueryRegistry indicates whether the service update requires
// contacting a registry. A registry may be contacted to retrieve
// the image digest and manifest, which in turn can be used to update
// platform or other information about the service.
QueryRegistry bool
}

View File

@@ -1,7 +0,0 @@
package client
// ServiceInspectOptions holds parameters related to the "service inspect"
// operation.
type ServiceInspectOptions struct {
InsertDefaults bool
}

View File

@@ -1,10 +0,0 @@
package client
// ServiceListOptions holds parameters to list services with.
type ServiceListOptions struct {
Filters Filters
// Status indicates whether the server should include the service task
// count of running and desired tasks.
Status bool
}

View File

@@ -1,31 +0,0 @@
package client
// ServiceUpdateOptions contains the options to be used for updating services.
type ServiceUpdateOptions struct {
// EncodedRegistryAuth is the encoded registry authorization credentials to
// use when updating the service.
//
// This field follows the format of the X-Registry-Auth header.
EncodedRegistryAuth string
// TODO(stevvooe): Consider moving the version parameter of ServiceUpdate
// into this field. While it does open API users up to racy writes, most
// users may not need that level of consistency in practice.
// RegistryAuthFrom specifies where to find the registry authorization
// credentials if they are not given in EncodedRegistryAuth. Valid
// values are "spec" and "previous-spec".
RegistryAuthFrom string
// Rollback indicates whether a server-side rollback should be
// performed. When this is set, the provided spec will be ignored.
// The valid values are "previous" and "none". An empty value is the
// same as "none".
Rollback string
// QueryRegistry indicates whether the service update requires
// contacting a registry. A registry may be contacted to retrieve
// the image digest and manifest, which in turn can be used to update
// platform or other information about the service.
QueryRegistry bool
}

View File

@@ -1,6 +0,0 @@
package client
// TaskListOptions holds parameters to list tasks with.
type TaskListOptions struct {
Filters Filters
}

View File

@@ -6,9 +6,20 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmUnlockOptions specifies options for unlocking a swarm.
type SwarmUnlockOptions struct {
Key string
}
// SwarmUnlockResult represents the result of unlocking a swarm.
type SwarmUnlockResult struct{}
// SwarmUnlock unlocks locked swarm.
func (cli *Client) SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error {
func (cli *Client) SwarmUnlock(ctx context.Context, options SwarmUnlockOptions) (SwarmUnlockResult, error) {
req := &swarm.UnlockRequest{
UnlockKey: options.Key,
}
resp, err := cli.post(ctx, "/swarm/unlock", nil, req, nil)
defer ensureReaderClosed(resp)
return err
return SwarmUnlockResult{}, err
}

View File

@@ -8,14 +8,25 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmUpdateOptions contains options for updating a swarm.
type SwarmUpdateOptions struct {
Swarm swarm.Spec
RotateWorkerToken bool
RotateManagerToken bool
RotateManagerUnlockKey bool
}
// SwarmUpdateResult represents the result of a SwarmUpdate operation.
type SwarmUpdateResult struct{}
// SwarmUpdate updates the swarm.
func (cli *Client) SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags SwarmUpdateFlags) error {
func (cli *Client) SwarmUpdate(ctx context.Context, version swarm.Version, options SwarmUpdateOptions) (SwarmUpdateResult, error) {
query := url.Values{}
query.Set("version", version.String())
query.Set("rotateWorkerToken", strconv.FormatBool(flags.RotateWorkerToken))
query.Set("rotateManagerToken", strconv.FormatBool(flags.RotateManagerToken))
query.Set("rotateManagerUnlockKey", strconv.FormatBool(flags.RotateManagerUnlockKey))
resp, err := cli.post(ctx, "/swarm/update", query, swarm, nil)
query.Set("rotateWorkerToken", strconv.FormatBool(options.RotateWorkerToken))
query.Set("rotateManagerToken", strconv.FormatBool(options.RotateManagerToken))
query.Set("rotateManagerUnlockKey", strconv.FormatBool(options.RotateManagerUnlockKey))
resp, err := cli.post(ctx, "/swarm/update", query, options.Swarm, nil)
defer ensureReaderClosed(resp)
return err
return SwarmUpdateResult{}, err
}

View File

@@ -1,8 +0,0 @@
package client
// SwarmUpdateFlags contains flags for SwarmUpdate.
type SwarmUpdateFlags struct {
RotateWorkerToken bool
RotateManagerToken bool
RotateManagerUnlockKey bool
}

View File

@@ -1,34 +1,36 @@
package client
import (
"bytes"
"context"
"encoding/json"
"io"
"github.com/moby/moby/api/types/swarm"
)
// TaskInspectWithRaw returns the task information and its raw representation.
func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) {
// TaskInspectOptions contains options for inspecting a task.
type TaskInspectOptions struct {
// Currently no options are defined.
}
// TaskInspectResult contains the result of a task inspection.
type TaskInspectResult struct {
Task swarm.Task
Raw []byte
}
// TaskInspect returns the task information and its raw representation.
func (cli *Client) TaskInspect(ctx context.Context, taskID string, options TaskInspectOptions) (TaskInspectResult, error) {
taskID, err := trimID("task", taskID)
if err != nil {
return swarm.Task{}, nil, err
return TaskInspectResult{}, err
}
resp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.Task{}, nil, err
return TaskInspectResult{}, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return swarm.Task{}, nil, err
}
var response swarm.Task
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&response)
return response, body, err
var out TaskInspectResult
out.Raw, err = decodeWithRaw(resp, &out.Task)
return out, err
}

View File

@@ -8,8 +8,18 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// TaskListOptions holds parameters to list tasks with.
type TaskListOptions struct {
Filters Filters
}
// TaskListResult contains the result of a task list operation.
type TaskListResult struct {
Items []swarm.Task
}
// TaskList returns the list of tasks.
func (cli *Client) TaskList(ctx context.Context, options TaskListOptions) ([]swarm.Task, error) {
func (cli *Client) TaskList(ctx context.Context, options TaskListOptions) (TaskListResult, error) {
query := url.Values{}
options.Filters.updateURLValues(query)
@@ -17,10 +27,10 @@ func (cli *Client) TaskList(ctx context.Context, options TaskListOptions) ([]swa
resp, err := cli.get(ctx, "/tasks", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
return TaskListResult{}, err
}
var tasks []swarm.Task
err = json.NewDecoder(resp.Body).Decode(&tasks)
return tasks, err
return TaskListResult{Items: tasks}, err
}

View File

@@ -4,14 +4,34 @@ import (
"context"
"io"
"net/url"
"sync"
"time"
"github.com/moby/moby/client/internal/timestamp"
)
// TaskLogs returns the logs generated by a task in an [io.ReadCloser].
// TaskLogsOptions holds parameters to filter logs with.
type TaskLogsOptions struct {
ShowStdout bool
ShowStderr bool
Since string
Until string
Timestamps bool
Follow bool
Tail string
Details bool
}
// TaskLogsResult holds the result of a task logs operation.
// It implements [io.ReadCloser].
type TaskLogsResult struct {
rc io.ReadCloser
close func() error
}
// TaskLogs returns the logs generated by a task.
// It's up to the caller to close the stream.
func (cli *Client) TaskLogs(ctx context.Context, taskID string, options ContainerLogsOptions) (io.ReadCloser, error) {
func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogsOptions) (TaskLogsResult, error) {
query := url.Values{}
if options.ShowStdout {
query.Set("stdout", "1")
@@ -24,7 +44,7 @@ func (cli *Client) TaskLogs(ctx context.Context, taskID string, options Containe
if options.Since != "" {
ts, err := timestamp.GetTimestamp(options.Since, time.Now())
if err != nil {
return nil, err
return TaskLogsResult{}, err
}
query.Set("since", ts)
}
@@ -44,7 +64,33 @@ func (cli *Client) TaskLogs(ctx context.Context, taskID string, options Containe
resp, err := cli.get(ctx, "/tasks/"+taskID+"/logs", query, nil)
if err != nil {
return nil, err
return TaskLogsResult{}, err
}
return resp.Body, nil
return newTaskLogsResult(resp.Body), nil
}
func newTaskLogsResult(rc io.ReadCloser) TaskLogsResult {
if rc == nil {
panic("nil io.ReadCloser")
}
return TaskLogsResult{
rc: rc,
close: sync.OnceValue(rc.Close),
}
}
// Read implements [io.ReadCloser] for LogsResult.
func (r TaskLogsResult) Read(p []byte) (n int, err error) {
if r.rc == nil {
return 0, io.EOF
}
return r.rc.Read(p)
}
// Close implements [io.ReadCloser] for LogsResult.
func (r TaskLogsResult) Close() error {
if r.close == nil {
return nil
}
return r.close()
}

View File

@@ -1,8 +1,12 @@
package client
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
cerrdefs "github.com/containerd/errdefs"
@@ -65,3 +69,18 @@ func encodePlatform(platform *ocispec.Platform) (string, error) {
}
return string(p), nil
}
func decodeWithRaw[T any](resp *http.Response, out *T) (raw []byte, _ error) {
if resp == nil || resp.Body == nil {
return nil, errors.New("empty response")
}
defer resp.Body.Close()
var buf bytes.Buffer
tr := io.TeeReader(resp.Body, &buf)
err := json.NewDecoder(tr).Decode(out)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

View File

@@ -7,15 +7,36 @@ import (
"github.com/moby/moby/api/types/volume"
)
// VolumeCreateOptions specifies the options to create a volume.
type VolumeCreateOptions struct {
Name string
Driver string
DriverOpts map[string]string
Labels map[string]string
ClusterVolumeSpec *volume.ClusterVolumeSpec
}
// VolumeCreateResult is the result of a volume creation.
type VolumeCreateResult struct {
Volume volume.Volume
}
// VolumeCreate creates a volume in the docker host.
func (cli *Client) VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error) {
resp, err := cli.post(ctx, "/volumes/create", nil, options, nil)
func (cli *Client) VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error) {
createRequest := volume.CreateRequest{
Name: options.Name,
Driver: options.Driver,
DriverOpts: options.DriverOpts,
Labels: options.Labels,
ClusterVolumeSpec: options.ClusterVolumeSpec,
}
resp, err := cli.post(ctx, "/volumes/create", nil, createRequest, nil)
defer ensureReaderClosed(resp)
if err != nil {
return volume.Volume{}, err
return VolumeCreateResult{}, err
}
var vol volume.Volume
err = json.NewDecoder(resp.Body).Decode(&vol)
return vol, err
var v volume.Volume
err = json.NewDecoder(resp.Body).Decode(&v)
return VolumeCreateResult{Volume: v}, err
}

View File

@@ -1,40 +1,36 @@
package client
import (
"bytes"
"context"
"encoding/json"
"io"
"github.com/moby/moby/api/types/volume"
)
// VolumeInspect returns the information about a specific volume in the docker host.
func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (volume.Volume, error) {
vol, _, err := cli.VolumeInspectWithRaw(ctx, volumeID)
return vol, err
// VolumeInspectOptions holds options for inspecting a volume.
type VolumeInspectOptions struct {
// Add future optional parameters here
}
// VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation
func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error) {
// VolumeInspectResult holds the result from the [Client.VolumeInspect] method.
type VolumeInspectResult struct {
Raw []byte
Volume volume.Volume
}
// VolumeInspect returns the information about a specific volume in the docker host.
func (cli *Client) VolumeInspect(ctx context.Context, volumeID string, options VolumeInspectOptions) (VolumeInspectResult, error) {
volumeID, err := trimID("volume", volumeID)
if err != nil {
return volume.Volume{}, nil, err
return VolumeInspectResult{}, err
}
resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return volume.Volume{}, nil, err
return VolumeInspectResult{}, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return volume.Volume{}, nil, err
}
var vol volume.Volume
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&vol)
return vol, body, err
var out VolumeInspectResult
out.Raw, err = decodeWithRaw(resp, &out.Volume)
return out, err
}

View File

@@ -8,18 +8,28 @@ import (
"github.com/moby/moby/api/types/volume"
)
// VolumeListOptions holds parameters to list volumes.
type VolumeListOptions struct {
Filters Filters
}
// VolumeListResult holds the result from the [Client.VolumeList] method.
type VolumeListResult struct {
Items volume.ListResponse
}
// VolumeList returns the volumes configured in the docker host.
func (cli *Client) VolumeList(ctx context.Context, options VolumeListOptions) (volume.ListResponse, error) {
func (cli *Client) VolumeList(ctx context.Context, options VolumeListOptions) (VolumeListResult, error) {
query := url.Values{}
options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/volumes", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return volume.ListResponse{}, err
return VolumeListResult{}, err
}
var volumes volume.ListResponse
err = json.NewDecoder(resp.Body).Decode(&volumes)
return volumes, err
var res VolumeListResult
err = json.NewDecoder(resp.Body).Decode(&res.Items)
return res, err
}

View File

@@ -1,6 +0,0 @@
package client
// VolumeListOptions holds parameters to list volumes.
type VolumeListOptions struct {
Filters Filters
}

View File

@@ -6,11 +6,17 @@ import (
"fmt"
"net/url"
"github.com/containerd/errdefs"
"github.com/moby/moby/api/types/volume"
)
// VolumePruneOptions holds parameters to prune networks.
// VolumePruneOptions holds parameters to prune volumes.
type VolumePruneOptions struct {
// All controls whether named volumes should also be pruned. By
// default, only anonymous volumes are pruned.
All bool
// Filters to apply when pruning.
Filters Filters
}
@@ -21,6 +27,16 @@ type VolumePruneResult struct {
// VolumesPrune requests the daemon to delete unused data
func (cli *Client) VolumesPrune(ctx context.Context, opts VolumePruneOptions) (VolumePruneResult, error) {
if opts.All {
if _, ok := opts.Filters["all"]; ok {
return VolumePruneResult{}, errdefs.ErrInvalidArgument.WithMessage(`conflicting options: cannot specify both "all" and "all" filter`)
}
if opts.Filters == nil {
opts.Filters = Filters{}
}
opts.Filters.Add("all", "true")
}
query := url.Values{}
opts.Filters.updateURLValues(query)

View File

@@ -5,15 +5,21 @@ import (
"net/url"
)
// VolumeRemoveOptions holds optional parameters for volume removal.
type VolumeRemoveOptions struct {
// Force the removal of the volume
Force bool
}
// VolumeRemove removes a volume from the docker host.
func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, options VolumeRemoveOptions) error {
volumeID, err := trimID("volume", volumeID)
if err != nil {
return err
}
query := url.Values{}
if force {
if options.Force {
query.Set("force", "1")
}
resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil)

4
vendor/modules.txt vendored
View File

@@ -168,7 +168,7 @@ github.com/moby/docker-image-spec/specs-go/v1
github.com/moby/go-archive
github.com/moby/go-archive/compression
github.com/moby/go-archive/tarheader
# github.com/moby/moby/api v1.52.0-beta.2.0.20251017201131-ec83dd46ed6c
# github.com/moby/moby/api v1.52.0-beta.2.0.20251023134003-dcd668d5796b
## explicit; go 1.23.0
github.com/moby/moby/api/pkg/authconfig
github.com/moby/moby/api/pkg/progress
@@ -193,7 +193,7 @@ github.com/moby/moby/api/types/swarm
github.com/moby/moby/api/types/system
github.com/moby/moby/api/types/versions
github.com/moby/moby/api/types/volume
# github.com/moby/moby/client v0.1.0-beta.2.0.20251017201131-ec83dd46ed6c
# github.com/moby/moby/client v0.1.0-beta.2.0.20251023134003-dcd668d5796b
## explicit; go 1.23.0
github.com/moby/moby/client
github.com/moby/moby/client/internal