1
0
mirror of https://github.com/docker/cli.git synced 2026-01-26 15:41:42 +03:00

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

Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
This commit is contained in:
Austin Vazquez
2025-11-04 17:50:21 -06:00
parent cc7275c4e5
commit 5039eee77f
56 changed files with 951 additions and 645 deletions

View File

@@ -0,0 +1,36 @@
// Code generated by go-swagger; DO NOT EDIT.
package build
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// DiskUsage represents system data usage for build cache resources.
//
// swagger:model DiskUsage
type DiskUsage struct {
// Count of active build cache records.
//
// Example: 1
ActiveBuildCacheRecords int64 `json:"ActiveBuildCacheRecords,omitempty"`
// List of build cache records.
//
Items []CacheRecord `json:"Items,omitempty"`
// Disk space that can be reclaimed by removing inactive build cache records.
//
// Example: 12345678
Reclaimable int64 `json:"Reclaimable,omitempty"`
// Count of all build cache records.
//
// Example: 4
TotalBuildCacheRecords int64 `json:"TotalBuildCacheRecords,omitempty"`
// Disk space in use by build cache records.
//
// Example: 98765432
TotalSize int64 `json:"TotalSize,omitempty"`
}

View File

@@ -0,0 +1,36 @@
// Code generated by go-swagger; DO NOT EDIT.
package container
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// DiskUsage represents system data usage information for container resources.
//
// swagger:model DiskUsage
type DiskUsage struct {
// Count of active containers.
//
// Example: 1
ActiveContainers int64 `json:"ActiveContainers,omitempty"`
// List of container summaries.
//
Items []Summary `json:"Items,omitempty"`
// Disk space that can be reclaimed by removing inactive containers.
//
// Example: 12345678
Reclaimable int64 `json:"Reclaimable,omitempty"`
// Count of all containers.
//
// Example: 4
TotalContainers int64 `json:"TotalContainers,omitempty"`
// Disk space in use by containers.
//
// Example: 98765432
TotalSize int64 `json:"TotalSize,omitempty"`
}

View File

@@ -0,0 +1,36 @@
// Code generated by go-swagger; DO NOT EDIT.
package image
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// DiskUsage represents system data usage for image resources.
//
// swagger:model DiskUsage
type DiskUsage struct {
// Count of active images.
//
// Example: 1
ActiveImages int64 `json:"ActiveImages,omitempty"`
// List of image summaries.
//
Items []Summary `json:"Items,omitempty"`
// Disk space that can be reclaimed by removing unused images.
//
// Example: 12345678
Reclaimable int64 `json:"Reclaimable,omitempty"`
// Count of all images.
//
// Example: 4
TotalImages int64 `json:"TotalImages,omitempty"`
// Disk space in use by images.
//
// Example: 98765432
TotalSize int64 `json:"TotalSize,omitempty"`
}

View File

@@ -106,18 +106,27 @@ type ReplicatedJob struct {
// This type is deliberately empty.
type GlobalJob struct{}
// FailureAction is the action to perform when updating a service fails.
type FailureAction string
const (
// UpdateFailureActionPause PAUSE
UpdateFailureActionPause = "pause"
UpdateFailureActionPause FailureAction = "pause"
// UpdateFailureActionContinue CONTINUE
UpdateFailureActionContinue = "continue"
UpdateFailureActionContinue FailureAction = "continue"
// UpdateFailureActionRollback ROLLBACK
UpdateFailureActionRollback = "rollback"
UpdateFailureActionRollback FailureAction = "rollback"
)
// UpdateOrder is the order of operations when rolling out or rolling back
// an updated tasks for a service.
type UpdateOrder string
const (
// UpdateOrderStopFirst STOP_FIRST
UpdateOrderStopFirst = "stop-first"
UpdateOrderStopFirst UpdateOrder = "stop-first"
// UpdateOrderStartFirst START_FIRST
UpdateOrderStartFirst = "start-first"
UpdateOrderStartFirst UpdateOrder = "start-first"
)
// UpdateConfig represents the update configuration.
@@ -130,7 +139,7 @@ type UpdateConfig struct {
Delay time.Duration `json:",omitempty"`
// FailureAction is the action to take when an update failures.
FailureAction string `json:",omitempty"`
FailureAction FailureAction `json:",omitempty"`
// Monitor indicates how long to monitor a task for failure after it is
// created. If the task fails by ending up in one of the states
@@ -156,7 +165,7 @@ type UpdateConfig struct {
// Order indicates the order of operations when rolling out an updated
// task. Either the old task is shut down before the new task is
// started, or the new task is started before the old task is shut down.
Order string
Order UpdateOrder
}
// ServiceStatus represents the number of running tasks in a service and the
@@ -198,8 +207,12 @@ type JobStatus struct {
LastExecution time.Time `json:",omitempty"`
}
// RegistryAuthSource defines options for the "registryAuthFrom" query parameter
// on service update.
type RegistryAuthSource string
// Values for RegistryAuthFrom in ServiceUpdateOptions
const (
RegistryAuthFromSpec = "spec"
RegistryAuthFromPreviousSpec = "previous-spec"
RegistryAuthFromSpec RegistryAuthSource = "spec"
RegistryAuthFromPreviousSpec RegistryAuthSource = "previous-spec"
)

View File

@@ -24,9 +24,27 @@ const (
// DiskUsage contains response of Engine API:
// GET "/system/df"
type DiskUsage struct {
LayersSize int64
Images []*image.Summary
Containers []*container.Summary
Volumes []*volume.Volume
BuildCache []*build.CacheRecord
LegacyDiskUsage
ImageUsage *image.DiskUsage `json:"ImageUsage,omitempty"`
ContainerUsage *container.DiskUsage `json:"ContainerUsage,omitempty"`
VolumeUsage *volume.DiskUsage `json:"VolumeUsage,omitempty"`
BuildCacheUsage *build.DiskUsage `json:"BuildCacheUsage,omitempty"`
}
type LegacyDiskUsage struct {
// Deprecated: kept to maintain backwards compatibility with API < v1.52, use [ImagesDiskUsage.TotalSize] instead.
LayersSize int64 `json:"LayersSize,omitempty"`
// Deprecated: kept to maintain backwards compatibility with API < v1.52, use [ImagesDiskUsage.Items] instead.
Images []image.Summary `json:"Images,omitzero"`
// Deprecated: kept to maintain backwards compatibility with API < v1.52, use [ContainersDiskUsage.Items] instead.
Containers []container.Summary `json:"Containers,omitzero"`
// Deprecated: kept to maintain backwards compatibility with API < v1.52, use [VolumesDiskUsage.Items] instead.
Volumes []volume.Volume `json:"Volumes,omitzero"`
// Deprecated: kept to maintain backwards compatibility with API < v1.52, use [BuildCacheDiskUsage.Items] instead.
BuildCache []build.CacheRecord `json:"BuildCache,omitzero"`
}

View File

@@ -0,0 +1,36 @@
// Code generated by go-swagger; DO NOT EDIT.
package volume
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// DiskUsage represents system data usage for volume resources.
//
// swagger:model DiskUsage
type DiskUsage struct {
// Count of active volumes.
//
// Example: 1
ActiveVolumes int64 `json:"ActiveVolumes,omitempty"`
// List of volumes.
//
Items []Volume `json:"Items,omitempty"`
// Disk space that can be reclaimed by removing inactive volumes.
//
// Example: 12345678
Reclaimable int64 `json:"Reclaimable,omitempty"`
// Disk space in use by volumes.
//
// Example: 98765432
TotalSize int64 `json:"TotalSize,omitempty"`
// Count of all volumes.
//
// Example: 4
TotalVolumes int64 `json:"TotalVolumes,omitempty"`
}

View File

@@ -4,8 +4,6 @@ import (
"context"
"io"
"net"
"github.com/moby/moby/api/types/system"
)
// APIClient is an interface that clients that talk with a docker server must implement.
@@ -74,7 +72,7 @@ type ContainerAPIClient interface {
ContainerWait(ctx context.Context, container string, options ContainerWaitOptions) ContainerWaitResult
CopyFromContainer(ctx context.Context, container string, options CopyFromContainerOptions) (CopyFromContainerResult, error)
CopyToContainer(ctx context.Context, container string, options CopyToContainerOptions) (CopyToContainerResult, error)
ContainersPrune(ctx context.Context, opts ContainerPruneOptions) (ContainerPruneResult, error)
ContainerPrune(ctx context.Context, opts ContainerPruneOptions) (ContainerPruneResult, error)
}
type ExecAPIClient interface {
@@ -103,7 +101,7 @@ type ImageAPIClient interface {
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)
ImagePrune(ctx context.Context, opts ImagePruneOptions) (ImagePruneResult, error)
ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (ImageInspectResult, error)
ImageHistory(ctx context.Context, image string, _ ...ImageHistoryOption) (ImageHistoryResult, error)
@@ -119,7 +117,7 @@ type NetworkAPIClient interface {
NetworkInspect(ctx context.Context, network string, options NetworkInspectOptions) (NetworkInspectResult, error)
NetworkList(ctx context.Context, options NetworkListOptions) (NetworkListResult, error)
NetworkRemove(ctx context.Context, network string, options NetworkRemoveOptions) (NetworkRemoveResult, error)
NetworksPrune(ctx context.Context, opts NetworkPruneOptions) (NetworkPruneResult, error)
NetworkPrune(ctx context.Context, opts NetworkPruneOptions) (NetworkPruneResult, error)
}
// NodeAPIClient defines API client methods for the nodes
@@ -173,7 +171,7 @@ type SystemAPIClient interface {
Events(ctx context.Context, options EventsListOptions) EventsResult
Info(ctx context.Context, options InfoOptions) (SystemInfoResult, error)
RegistryLogin(ctx context.Context, auth RegistryLoginOptions) (RegistryLoginResult, error)
DiskUsage(ctx context.Context, options DiskUsageOptions) (system.DiskUsage, error)
DiskUsage(ctx context.Context, options DiskUsageOptions) (DiskUsageResult, error)
Ping(ctx context.Context, options PingOptions) (PingResult, error)
}
@@ -183,7 +181,7 @@ type VolumeAPIClient interface {
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) (VolumeRemoveResult, error)
VolumesPrune(ctx context.Context, options VolumePruneOptions) (VolumePruneResult, error)
VolumePrune(ctx context.Context, options VolumePruneOptions) (VolumePruneResult, error)
VolumeUpdate(ctx context.Context, volumeID string, options VolumeUpdateOptions) (VolumeUpdateResult, error)
}

View File

@@ -12,17 +12,17 @@ import (
// ExecCreateOptions is a small subset of the Config struct that holds the configuration
// for the exec feature of docker.
type ExecCreateOptions struct {
User string // User that will run the command
Privileged bool // Is the container in privileged mode
Tty bool // Attach standard streams to a tty.
ConsoleSize *[2]uint `json:",omitempty"` // Initial console size [height, width]
AttachStdin bool // Attach the standard input, makes possible user interaction
AttachStderr bool // Attach the standard error
AttachStdout bool // Attach the standard output
DetachKeys string // Escape keys for detach
Env []string // Environment variables
WorkingDir string // Working directory
Cmd []string // Execution commands and args
User string // User that will run the command
Privileged bool // Is the container in privileged mode
TTY bool // Attach standard streams to a tty.
ConsoleSize ConsoleSize // Initial terminal size [height, width], unused if TTY == false
AttachStdin bool // Attach the standard input, makes possible user interaction
AttachStderr bool // Attach the standard error
AttachStdout bool // Attach the standard output
DetachKeys string // Escape keys for detach
Env []string // Environment variables
WorkingDir string // Working directory
Cmd []string // Execution commands and args
}
// ExecCreateResult holds the result of creating a container exec.
@@ -37,11 +37,16 @@ func (cli *Client) ExecCreate(ctx context.Context, containerID string, options E
return ExecCreateResult{}, err
}
consoleSize, err := getConsoleSize(options.TTY, options.ConsoleSize)
if err != nil {
return ExecCreateResult{}, err
}
req := container.ExecCreateRequest{
User: options.User,
Privileged: options.Privileged,
Tty: options.Tty,
ConsoleSize: options.ConsoleSize,
Tty: options.TTY,
ConsoleSize: consoleSize,
AttachStdin: options.AttachStdin,
AttachStderr: options.AttachStderr,
AttachStdout: options.AttachStdout,
@@ -73,7 +78,7 @@ type ExecStartOptions struct {
// Check if there's a tty
TTY bool
// Terminal size [height, width], unused if TTY == false
ConsoleSize ConsoleSize `json:",omitzero"`
ConsoleSize ConsoleSize
}
// ExecStartResult holds the result of starting a container exec.
@@ -82,13 +87,16 @@ 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: options.Detach,
Tty: options.TTY,
}
if err := applyConsoleSize(&req, &options.ConsoleSize); err != nil {
consoleSize, err := getConsoleSize(options.TTY, options.ConsoleSize)
if err != nil {
return ExecStartResult{}, err
}
req := container.ExecStartRequest{
Detach: options.Detach,
Tty: options.TTY,
ConsoleSize: consoleSize,
}
resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, req, nil)
defer ensureReaderClosed(resp)
return ExecStartResult{}, err
@@ -126,27 +134,29 @@ type ExecAttachResult struct {
//
// [stdcopy.StdCopy]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdCopy
func (cli *Client) ExecAttach(ctx context.Context, execID string, options ExecAttachOptions) (ExecAttachResult, error) {
req := container.ExecStartRequest{
Detach: false,
Tty: options.TTY,
}
if err := applyConsoleSize(&req, &options.ConsoleSize); err != nil {
consoleSize, err := getConsoleSize(options.TTY, options.ConsoleSize)
if err != nil {
return ExecAttachResult{}, err
}
req := container.ExecStartRequest{
Detach: false,
Tty: options.TTY,
ConsoleSize: consoleSize,
}
response, err := cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, req, http.Header{
"Content-Type": {"application/json"},
})
return ExecAttachResult{HijackedResponse: response}, err
}
func applyConsoleSize(req *container.ExecStartRequest, consoleSize *ConsoleSize) error {
func getConsoleSize(hasTTY bool, consoleSize ConsoleSize) (*[2]uint, error) {
if consoleSize.Height != 0 || consoleSize.Width != 0 {
if !req.Tty {
return errdefs.ErrInvalidArgument.WithMessage("console size is only supported when TTY is enabled")
if !hasTTY {
return nil, errdefs.ErrInvalidArgument.WithMessage("console size is only supported when TTY is enabled")
}
req.ConsoleSize = &[2]uint{consoleSize.Height, consoleSize.Width}
return &[2]uint{consoleSize.Height, consoleSize.Width}, nil
}
return nil
return nil, nil
}
// ExecInspectOptions holds options for inspecting a container exec.

View File

@@ -4,7 +4,6 @@ import (
"context"
"io"
"net/url"
"sync"
)
// ContainerExportOptions specifies options for container export operations.
@@ -13,50 +12,36 @@ type ContainerExportOptions struct {
}
// ContainerExportResult represents the result of a container export operation.
type ContainerExportResult struct {
rc io.ReadCloser
close func() error
type ContainerExportResult interface {
io.ReadCloser
}
// ContainerExport retrieves the raw contents of a container
// and returns them as an [io.ReadCloser]. It's up to the caller
// to close the stream.
//
// The underlying [io.ReadCloser] is automatically closed if the context is canceled,
func (cli *Client) ContainerExport(ctx context.Context, containerID string, options ContainerExportOptions) (ContainerExportResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return ContainerExportResult{}, err
return nil, err
}
resp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil)
if err != nil {
return ContainerExportResult{}, err
return nil, err
}
return newContainerExportResult(resp.Body), nil
return &containerExportResult{
ReadCloser: newCancelReadCloser(ctx, resp.Body),
}, nil
}
func newContainerExportResult(rc io.ReadCloser) ContainerExportResult {
if rc == nil {
panic("nil io.ReadCloser")
}
return ContainerExportResult{
rc: rc,
close: sync.OnceValue(rc.Close),
}
type containerExportResult struct {
io.ReadCloser
}
// Read implements io.ReadCloser
func (r ContainerExportResult) 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 ContainerExportResult) Close() error {
if r.close == nil {
return nil
}
return r.close()
}
var (
_ io.ReadCloser = (*containerExportResult)(nil)
_ ContainerExportResult = (*containerExportResult)(nil)
)

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"io"
"net/url"
"sync"
"time"
"github.com/moby/moby/client/internal/timestamp"
@@ -24,14 +23,15 @@ type ContainerLogsOptions struct {
}
// ContainerLogsResult is the result of a container logs operation.
type ContainerLogsResult struct {
rc io.ReadCloser
close func() error
type ContainerLogsResult interface {
io.ReadCloser
}
// ContainerLogs returns the logs generated by a container in an [io.ReadCloser].
// It's up to the caller to close the stream.
//
// The underlying [io.ReadCloser] is automatically closed if the context is canceled,
//
// The stream format on the response uses one of two formats:
//
// - If the container is using a TTY, there is only a single stream (stdout)
@@ -58,7 +58,7 @@ type ContainerLogsResult struct {
func (cli *Client) ContainerLogs(ctx context.Context, containerID string, options ContainerLogsOptions) (ContainerLogsResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return ContainerLogsResult{}, err
return nil, err
}
query := url.Values{}
@@ -73,7 +73,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, containerID string, option
if options.Since != "" {
ts, err := timestamp.GetTimestamp(options.Since, time.Now())
if err != nil {
return ContainerLogsResult{}, fmt.Errorf(`invalid value for "since": %w`, err)
return nil, fmt.Errorf(`invalid value for "since": %w`, err)
}
query.Set("since", ts)
}
@@ -81,7 +81,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, containerID string, option
if options.Until != "" {
ts, err := timestamp.GetTimestamp(options.Until, time.Now())
if err != nil {
return ContainerLogsResult{}, fmt.Errorf(`invalid value for "until": %w`, err)
return nil, fmt.Errorf(`invalid value for "until": %w`, err)
}
query.Set("until", ts)
}
@@ -101,33 +101,18 @@ func (cli *Client) ContainerLogs(ctx context.Context, containerID string, option
resp, err := cli.get(ctx, "/containers/"+containerID+"/logs", query, nil)
if err != nil {
return ContainerLogsResult{}, err
return nil, err
}
return newContainerLogsResult(resp.Body), nil
return &containerLogsResult{
ReadCloser: newCancelReadCloser(ctx, resp.Body),
}, nil
}
func newContainerLogsResult(rc io.ReadCloser) ContainerLogsResult {
if rc == nil {
panic("rc cannot be nil")
}
return ContainerLogsResult{
rc: rc,
close: sync.OnceValue(rc.Close),
}
type containerLogsResult struct {
io.ReadCloser
}
// Read implements the io.Reader interface.
func (r ContainerLogsResult) Read(p []byte) (n int, err error) {
if r.rc == nil {
return 0, io.EOF
}
return r.rc.Read(p)
}
// Close closes the underlying reader.
func (r ContainerLogsResult) Close() error {
if r.close == nil {
return nil
}
return r.close()
}
var (
_ io.ReadCloser = (*containerLogsResult)(nil)
_ ContainerLogsResult = (*containerLogsResult)(nil)
)

View File

@@ -14,13 +14,13 @@ type ContainerPruneOptions struct {
Filters Filters
}
// ContainerPruneResult holds the result from the [Client.ContainersPrune] method.
// ContainerPruneResult holds the result from the [Client.ContainerPrune] method.
type ContainerPruneResult struct {
Report container.PruneReport
}
// ContainersPrune requests the daemon to delete unused data
func (cli *Client) ContainersPrune(ctx context.Context, opts ContainerPruneOptions) (ContainerPruneResult, error) {
// ContainerPrune requests the daemon to delete unused data
func (cli *Client) ContainerPrune(ctx context.Context, opts ContainerPruneOptions) (ContainerPruneResult, error) {
query := url.Values{}
opts.Filters.updateURLValues(query)

View File

@@ -43,6 +43,8 @@ type ContainerStatsResult struct {
// ContainerStats retrieves live resource usage statistics for the specified
// container. The caller must close the [io.ReadCloser] in the returned result
// to release associated resources.
//
// The underlying [io.ReadCloser] is automatically closed if the context is canceled,
func (cli *Client) ContainerStats(ctx context.Context, containerID string, options ContainerStatsOptions) (ContainerStatsResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
@@ -68,6 +70,6 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, optio
}
return ContainerStatsResult{
Body: resp.Body,
Body: newCancelReadCloser(ctx, resp.Body),
}, nil
}

View File

@@ -2,18 +2,26 @@ package client
import (
"context"
"io"
"net/url"
"github.com/distribution/reference"
)
// ImageImport creates a new image based on the source options.
// It returns the JSON content in the response body.
// ImageImportResult holds the response body returned by the daemon for image import.
type ImageImportResult interface {
io.ReadCloser
}
// ImageImport creates a new image based on the source options. It returns the
// JSON content in the [ImageImportResult].
//
// The underlying [io.ReadCloser] is automatically closed if the context is canceled,
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 ImageImportResult{}, err
return nil, err
}
}
@@ -40,7 +48,19 @@ 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 ImageImportResult{}, err
return nil, err
}
return ImageImportResult{rc: resp.Body}, nil
return &imageImportResult{
ReadCloser: newCancelReadCloser(ctx, resp.Body),
}, nil
}
// ImageImportResult holds the response body returned by the daemon for image import.
type imageImportResult struct {
io.ReadCloser
}
var (
_ io.ReadCloser = (*imageImportResult)(nil)
_ ImageImportResult = (*imageImportResult)(nil)
)

View File

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

View File

@@ -7,18 +7,21 @@ import (
"net/url"
)
// 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
// [ImageLoadResult] returned by this function.
// ImageLoadResult returns information to the client about a load process.
// It implements [io.ReadCloser] and must be closed to avoid a resource leak.
type ImageLoadResult interface {
io.ReadCloser
}
// ImageLoad loads an image in the docker host from the client host. It's up
// to the caller to close the [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.
// The underlying [io.ReadCloser] is automatically closed if the context is canceled,
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 ImageLoadResult{}, err
return nil, err
}
}
@@ -29,12 +32,12 @@ func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, loadOpts ...I
}
if len(opts.apiOptions.Platforms) > 0 {
if err := cli.requiresVersion(ctx, "1.48", "platform"); err != nil {
return ImageLoadResult{}, err
return nil, err
}
p, err := encodePlatforms(opts.apiOptions.Platforms...)
if err != nil {
return ImageLoadResult{}, err
return nil, err
}
query["platform"] = p
}
@@ -43,26 +46,19 @@ func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, loadOpts ...I
"Content-Type": {"application/x-tar"},
})
if err != nil {
return ImageLoadResult{}, err
return nil, err
}
return ImageLoadResult{
body: resp.Body,
return &imageLoadResult{
ReadCloser: newCancelReadCloser(ctx, resp.Body),
}, nil
}
// ImageLoadResult returns information to the client about a load process.
type ImageLoadResult struct {
// Body must be closed to avoid a resource leak
body io.ReadCloser
// imageLoadResult returns information to the client about a load process.
type imageLoadResult struct {
io.ReadCloser
}
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()
}
var (
_ io.ReadCloser = (*imageLoadResult)(nil)
_ ImageLoadResult = (*imageLoadResult)(nil)
)

View File

@@ -38,6 +38,10 @@ func ImageLoadWithQuiet(quiet bool) ImageLoadOption {
}
// ImageLoadWithPlatforms sets the platforms to be loaded from the image.
//
// 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 ImageLoadWithPlatforms(platforms ...ocispec.Platform) ImageLoadOption {
return imageLoadOptionFunc(func(opt *imageLoadOpts) error {
if opt.apiOptions.Platforms != nil {

View File

@@ -14,13 +14,13 @@ type ImagePruneOptions struct {
Filters Filters
}
// ImagePruneResult holds the result from the [Client.ImagesPrune] method.
// ImagePruneResult holds the result from the [Client.ImagePrune] method.
type ImagePruneResult struct {
Report image.PruneReport
}
// ImagesPrune requests the daemon to delete unused data
func (cli *Client) ImagesPrune(ctx context.Context, opts ImagePruneOptions) (ImagePruneResult, error) {
// ImagePrune requests the daemon to delete unused data
func (cli *Client) ImagePrune(ctx context.Context, opts ImagePruneOptions) (ImagePruneResult, error) {
query := url.Values{}
opts.Filters.updateURLValues(query)

View File

@@ -2,11 +2,17 @@ package client
import (
"context"
"io"
"net/url"
)
type ImageSaveResult interface {
io.ReadCloser
}
// ImageSave retrieves one or more images from the docker host as an
// [ImageSaveResult].
// [ImageSaveResult]. Callers should close the reader, but the underlying
// [io.ReadCloser] is automatically closed if the context is canceled,
//
// 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
@@ -15,7 +21,7 @@ func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, saveOpts ..
var opts imageSaveOpts
for _, opt := range saveOpts {
if err := opt.Apply(&opts); err != nil {
return ImageSaveResult{}, err
return nil, err
}
}
@@ -25,18 +31,29 @@ func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, saveOpts ..
if len(opts.apiOptions.Platforms) > 0 {
if err := cli.requiresVersion(ctx, "1.48", "platform"); err != nil {
return ImageSaveResult{}, err
return nil, err
}
p, err := encodePlatforms(opts.apiOptions.Platforms...)
if err != nil {
return ImageSaveResult{}, err
return nil, err
}
query["platform"] = p
}
resp, err := cli.get(ctx, "/images/get", query, nil)
if err != nil {
return ImageSaveResult{}, err
return nil, err
}
return newImageSaveResult(resp.Body), nil
return &imageSaveResult{
ReadCloser: newCancelReadCloser(ctx, resp.Body),
}, nil
}
type imageSaveResult struct {
io.ReadCloser
}
var (
_ io.ReadCloser = (*imageSaveResult)(nil)
_ ImageSaveResult = (*imageSaveResult)(nil)
)

View File

@@ -2,8 +2,6 @@ package client
import (
"fmt"
"io"
"sync"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
@@ -18,8 +16,11 @@ func (f imageSaveOptionFunc) Apply(o *imageSaveOpts) error {
return f(o)
}
// ImageSaveWithPlatforms sets the platforms to be saved from the image.
// ImageSaveWithPlatforms sets the platforms to be saved from the image. It
// produces an error if platforms are already set. This option only has an
// effect if the input image is a multi-platform image.
func ImageSaveWithPlatforms(platforms ...ocispec.Platform) ImageSaveOption {
// TODO(thaJeztah): verify the GoDoc; do we produce an error for a single-platform image without the given platform?
return imageSaveOptionFunc(func(opt *imageSaveOpts) error {
if opt.apiOptions.Platforms != nil {
return fmt.Errorf("platforms already set to %v", opt.apiOptions.Platforms)
@@ -38,34 +39,3 @@ 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

@@ -14,13 +14,13 @@ type NetworkPruneOptions struct {
Filters Filters
}
// NetworkPruneResult holds the result from the [Client.NetworksPrune] method.
// NetworkPruneResult holds the result from the [Client.NetworkPrune] method.
type NetworkPruneResult struct {
Report network.PruneReport
}
// NetworksPrune requests the daemon to delete unused networks
func (cli *Client) NetworksPrune(ctx context.Context, opts NetworkPruneOptions) (NetworkPruneResult, error) {
// NetworkPrune requests the daemon to delete unused networks
func (cli *Client) NetworkPrune(ctx context.Context, opts NetworkPruneOptions) (NetworkPruneResult, error) {
query := url.Values{}
opts.Filters.updateURLValues(query)

View File

@@ -1,8 +1,6 @@
package security
import (
"errors"
"fmt"
"strings"
)
@@ -19,22 +17,14 @@ type KeyValue struct {
// DecodeOptions decodes a security options string slice to a
// type-safe [Option].
func DecodeOptions(opts []string) ([]Option, error) {
so := []Option{}
func DecodeOptions(opts []string) []Option {
so := make([]Option, 0, len(opts))
for _, opt := range opts {
// support output from a < 1.13 docker daemon
if !strings.Contains(opt, "=") {
so = append(so, Option{Name: opt})
continue
}
secopt := Option{}
for _, s := range strings.Split(opt, ",") {
k, v, ok := strings.Cut(s, "=")
if !ok {
return nil, fmt.Errorf("invalid security option %q", s)
}
if k == "" || v == "" {
return nil, errors.New("invalid empty security option")
k, v, _ := strings.Cut(s, "=")
if k == "" {
continue
}
if k == "name" {
secopt.Name = v
@@ -42,7 +32,9 @@ func DecodeOptions(opts []string) ([]Option, error) {
}
secopt.Options = append(secopt.Options, KeyValue{Key: k, Value: v})
}
so = append(so, secopt)
if secopt.Name != "" {
so = append(so, secopt)
}
}
return so, nil
return so
}

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"io"
"net/url"
"sync"
"time"
"github.com/moby/moby/client/internal/timestamp"
@@ -26,16 +25,21 @@ type ServiceLogsOptions struct {
// ServiceLogsResult holds the result of a service logs operation.
// It implements [io.ReadCloser].
// It's up to the caller to close the stream.
type ServiceLogsResult struct {
rc io.ReadCloser
close func() error
type ServiceLogsResult interface {
io.ReadCloser
}
// ServiceLogs returns the logs generated by a service in an [ServiceLogsResult].
// ServiceLogs returns the logs generated by a service in a [ServiceLogsResult].
// as an [io.ReadCloser]. Callers should close the stream.
//
// The underlying [io.ReadCloser] is automatically closed if the context is canceled,
func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options ServiceLogsOptions) (ServiceLogsResult, error) {
// TODO(thaJeztah): this function needs documentation about the format of ths stream (similar to for container logs)
// TODO(thaJeztah): migrate CLI utilities to the client where suitable; https://github.com/docker/cli/blob/v29.0.0-rc.1/cli/command/service/logs.go#L73-L348
serviceID, err := trimID("service", serviceID)
if err != nil {
return ServiceLogsResult{}, err
return nil, err
}
query := url.Values{}
@@ -50,7 +54,7 @@ func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options Se
if options.Since != "" {
ts, err := timestamp.GetTimestamp(options.Since, time.Now())
if err != nil {
return ServiceLogsResult{}, fmt.Errorf(`invalid value for "since": %w`, err)
return nil, fmt.Errorf(`invalid value for "since": %w`, err)
}
query.Set("since", ts)
}
@@ -70,33 +74,18 @@ func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options Se
resp, err := cli.get(ctx, "/services/"+serviceID+"/logs", query, nil)
if err != nil {
return ServiceLogsResult{}, err
return nil, err
}
return newServiceLogsResult(resp.Body), nil
return &serviceLogsResult{
ReadCloser: newCancelReadCloser(ctx, 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),
}
type serviceLogsResult struct {
io.ReadCloser
}
// 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()
}
var (
_ io.ReadCloser = (*serviceLogsResult)(nil)
_ ServiceLogsResult = (*serviceLogsResult)(nil)
)

View File

@@ -28,7 +28,7 @@ type ServiceUpdateOptions struct {
// 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
RegistryAuthFrom swarm.RegistryAuthSource
// Rollback indicates whether a server-side rollback should be
// performed. When this is set, the provided spec will be ignored.
@@ -65,7 +65,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, options
query := url.Values{}
if options.RegistryAuthFrom != "" {
query.Set("registryAuthFrom", options.RegistryAuthFrom)
query.Set("registryAuthFrom", string(options.RegistryAuthFrom))
}
if options.Rollback != "" {

View File

@@ -5,29 +5,315 @@ import (
"encoding/json"
"fmt"
"net/url"
"slices"
"strings"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/api/types/volume"
"github.com/moby/moby/client/pkg/versions"
)
// DiskUsage requests the current data usage from the daemon
func (cli *Client) DiskUsage(ctx context.Context, options DiskUsageOptions) (system.DiskUsage, error) {
var query url.Values
if len(options.Types) > 0 {
query = url.Values{}
for _, t := range options.Types {
query.Add("type", string(t))
// DiskUsageOptions holds parameters for [Client.DiskUsage] operations.
type DiskUsageOptions struct {
// Containers controls whether container disk usage should be computed.
Containers bool
// Images controls whether image disk usage should be computed.
Images bool
// BuildCache controls whether build cache disk usage should be computed.
BuildCache bool
// Volumes controls whether volume disk usage should be computed.
Volumes bool
// Verbose enables more detailed disk usage information.
Verbose bool
}
// DiskUsageResult is the result of [Client.DiskUsage] operations.
type DiskUsageResult struct {
// Containers holds container disk usage information.
Containers ContainersDiskUsage
// Images holds image disk usage information.
Images ImagesDiskUsage
// BuildCache holds build cache disk usage information.
BuildCache BuildCacheDiskUsage
// Volumes holds volume disk usage information.
Volumes VolumesDiskUsage
}
// ContainersDiskUsage contains disk usage information for containers.
type ContainersDiskUsage struct {
// ActiveContainers is the number of active containers.
ActiveContainers int64
// TotalContainers is the total number of containers.
TotalContainers int64
// Reclaimable is the amount of disk space that can be reclaimed.
Reclaimable int64
// TotalSize is the total disk space used by all containers.
TotalSize int64
// Items holds detailed information about each container.
Items []container.Summary
}
// ImagesDiskUsage contains disk usage information for images.
type ImagesDiskUsage struct {
// ActiveImages is the number of active images.
ActiveImages int64
// TotalImages is the total number of images.
TotalImages int64
// Reclaimable is the amount of disk space that can be reclaimed.
Reclaimable int64
// TotalSize is the total disk space used by all images.
TotalSize int64
// Items holds detailed information about each image.
Items []image.Summary
}
// VolumesDiskUsage contains disk usage information for volumes.
type VolumesDiskUsage struct {
// ActiveVolumes is the number of active volumes.
ActiveVolumes int64
// TotalVolumes is the total number of volumes.
TotalVolumes int64
// Reclaimable is the amount of disk space that can be reclaimed.
Reclaimable int64
// TotalSize is the total disk space used by all volumes.
TotalSize int64
// Items holds detailed information about each volume.
Items []volume.Volume
}
// BuildCacheDiskUsage contains disk usage information for build cache.
type BuildCacheDiskUsage struct {
// ActiveBuildCacheRecords is the number of active build cache records.
ActiveBuildCacheRecords int64
// TotalBuildCacheRecords is the total number of build cache records.
TotalBuildCacheRecords int64
// Reclaimable is the amount of disk space that can be reclaimed.
Reclaimable int64
// TotalSize is the total disk space used by all build cache records.
TotalSize int64
// Items holds detailed information about each build cache record.
Items []build.CacheRecord
}
// DiskUsage requests the current data usage from the daemon.
func (cli *Client) DiskUsage(ctx context.Context, options DiskUsageOptions) (DiskUsageResult, error) {
query := url.Values{}
for _, t := range []struct {
flag bool
sysObj system.DiskUsageObject
}{
{options.Containers, system.ContainerObject},
{options.Images, system.ImageObject},
{options.Volumes, system.VolumeObject},
{options.BuildCache, system.BuildCacheObject},
} {
if t.flag {
query.Add("type", string(t.sysObj))
}
}
if options.Verbose {
query.Set("verbose", "1")
}
resp, err := cli.get(ctx, "/system/df", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return system.DiskUsage{}, err
return DiskUsageResult{}, err
}
var du system.DiskUsage
if err := json.NewDecoder(resp.Body).Decode(&du); err != nil {
return system.DiskUsage{}, fmt.Errorf("Error retrieving disk usage: %v", err)
return DiskUsageResult{}, fmt.Errorf("Error retrieving disk usage: %v", err)
}
return du, nil
// Generate result from a legacy response.
if versions.LessThan(cli.version, "1.52") {
return diskUsageResultFromLegacyAPI(&du), nil
}
var r DiskUsageResult
if idu := du.ImageUsage; idu != nil {
r.Images = ImagesDiskUsage{
ActiveImages: idu.ActiveImages,
Reclaimable: idu.Reclaimable,
TotalImages: idu.TotalImages,
TotalSize: idu.TotalSize,
}
if options.Verbose {
r.Images.Items = slices.Clone(idu.Items)
}
}
if cdu := du.ContainerUsage; cdu != nil {
r.Containers = ContainersDiskUsage{
ActiveContainers: cdu.ActiveContainers,
Reclaimable: cdu.Reclaimable,
TotalContainers: cdu.TotalContainers,
TotalSize: cdu.TotalSize,
}
if options.Verbose {
r.Containers.Items = slices.Clone(cdu.Items)
}
}
if bdu := du.BuildCacheUsage; bdu != nil {
r.BuildCache = BuildCacheDiskUsage{
ActiveBuildCacheRecords: bdu.ActiveBuildCacheRecords,
Reclaimable: bdu.Reclaimable,
TotalBuildCacheRecords: bdu.TotalBuildCacheRecords,
TotalSize: bdu.TotalSize,
}
if options.Verbose {
r.BuildCache.Items = slices.Clone(bdu.Items)
}
}
if vdu := du.VolumeUsage; vdu != nil {
r.Volumes = VolumesDiskUsage{
ActiveVolumes: vdu.ActiveVolumes,
Reclaimable: vdu.Reclaimable,
TotalVolumes: vdu.TotalVolumes,
TotalSize: vdu.TotalSize,
}
if options.Verbose {
r.Volumes.Items = slices.Clone(vdu.Items)
}
}
return r, nil
}
func diskUsageResultFromLegacyAPI(du *system.DiskUsage) DiskUsageResult {
return DiskUsageResult{
Images: imageDiskUsageFromLegacyAPI(du),
Containers: containerDiskUsageFromLegacyAPI(du),
BuildCache: buildCacheDiskUsageFromLegacyAPI(du),
Volumes: volumeDiskUsageFromLegacyAPI(du),
}
}
func imageDiskUsageFromLegacyAPI(du *system.DiskUsage) ImagesDiskUsage {
idu := ImagesDiskUsage{
TotalSize: du.LayersSize,
TotalImages: int64(len(du.Images)),
Items: du.Images,
}
var used int64
for _, i := range idu.Items {
if i.Containers > 0 {
idu.ActiveImages++
if i.Size == -1 || i.SharedSize == -1 {
continue
}
used += (i.Size - i.SharedSize)
}
}
if idu.TotalImages > 0 {
idu.Reclaimable = idu.TotalSize - used
}
return idu
}
func containerDiskUsageFromLegacyAPI(du *system.DiskUsage) ContainersDiskUsage {
cdu := ContainersDiskUsage{
TotalContainers: int64(len(du.Containers)),
Items: du.Containers,
}
var used int64
for _, c := range cdu.Items {
cdu.TotalSize += c.SizeRw
switch strings.ToLower(c.State) {
case "running", "paused", "restarting":
cdu.ActiveContainers++
used += c.SizeRw
}
}
cdu.Reclaimable = cdu.TotalSize - used
return cdu
}
func buildCacheDiskUsageFromLegacyAPI(du *system.DiskUsage) BuildCacheDiskUsage {
bdu := BuildCacheDiskUsage{
TotalBuildCacheRecords: int64(len(du.BuildCache)),
Items: du.BuildCache,
}
var used int64
for _, b := range du.BuildCache {
if !b.Shared {
bdu.TotalSize += b.Size
}
if b.InUse {
bdu.ActiveBuildCacheRecords++
if !b.Shared {
used += b.Size
}
}
}
bdu.Reclaimable = bdu.TotalSize - used
return bdu
}
func volumeDiskUsageFromLegacyAPI(du *system.DiskUsage) VolumesDiskUsage {
vdu := VolumesDiskUsage{
TotalVolumes: int64(len(du.Volumes)),
Items: du.Volumes,
}
var used int64
for _, v := range vdu.Items {
// Ignore volumes with no usage data
if v.UsageData != nil {
if v.UsageData.RefCount > 0 {
vdu.ActiveVolumes++
used += v.UsageData.Size
}
if v.UsageData.Size > 0 {
vdu.TotalSize += v.UsageData.Size
}
}
}
vdu.Reclaimable = vdu.TotalSize - used
return vdu
}

View File

@@ -1,10 +0,0 @@
package client
import "github.com/moby/moby/api/types/system"
// DiskUsageOptions holds parameters for system disk usage query.
type DiskUsageOptions struct {
// Types specifies what object types to include in the response. If empty,
// all object types are returned.
Types []system.DiskUsageObject
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"io"
"net/url"
"sync"
"time"
"github.com/moby/moby/client/internal/timestamp"
@@ -24,14 +23,18 @@ type TaskLogsOptions struct {
// TaskLogsResult holds the result of a task logs operation.
// It implements [io.ReadCloser].
type TaskLogsResult struct {
rc io.ReadCloser
close func() error
type TaskLogsResult interface {
io.ReadCloser
}
// TaskLogs returns the logs generated by a task.
// It's up to the caller to close the stream.
// TaskLogs returns the logs generated by a service in a [TaskLogsResult].
// as an [io.ReadCloser]. Callers should close the stream.
//
// The underlying [io.ReadCloser] is automatically closed if the context is canceled,
func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogsOptions) (TaskLogsResult, error) {
// TODO(thaJeztah): this function needs documentation about the format of ths stream (similar to for container logs)
// TODO(thaJeztah): migrate CLI utilities to the client where suitable; https://github.com/docker/cli/blob/v29.0.0-rc.1/cli/command/service/logs.go#L73-L348
query := url.Values{}
if options.ShowStdout {
query.Set("stdout", "1")
@@ -44,7 +47,7 @@ func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogs
if options.Since != "" {
ts, err := timestamp.GetTimestamp(options.Since, time.Now())
if err != nil {
return TaskLogsResult{}, err
return nil, err
}
query.Set("since", ts)
}
@@ -64,33 +67,18 @@ func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogs
resp, err := cli.get(ctx, "/tasks/"+taskID+"/logs", query, nil)
if err != nil {
return TaskLogsResult{}, err
return nil, err
}
return newTaskLogsResult(resp.Body), nil
return &taskLogsResult{
ReadCloser: newCancelReadCloser(ctx, 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),
}
type taskLogsResult struct {
io.ReadCloser
}
// 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()
}
var (
_ io.ReadCloser = (*taskLogsResult)(nil)
_ ContainerLogsResult = (*taskLogsResult)(nil)
)

View File

@@ -2,12 +2,14 @@ package client
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"sync"
cerrdefs "github.com/containerd/errdefs"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -84,3 +86,22 @@ func decodeWithRaw[T any](resp *http.Response, out *T) (raw json.RawMessage, _ e
}
return buf.Bytes(), nil
}
// newCancelReadCloser wraps rc so it's automatically closed when ctx is canceled.
// Close is idempotent and returns the first error from rc.Close.
func newCancelReadCloser(ctx context.Context, rc io.ReadCloser) io.ReadCloser {
crc := &cancelReadCloser{
rc: rc,
close: sync.OnceValue(rc.Close),
}
context.AfterFunc(ctx, func() { _ = crc.Close() })
return crc
}
type cancelReadCloser struct {
rc io.ReadCloser
close func() error
}
func (c *cancelReadCloser) Read(p []byte) (int, error) { return c.rc.Read(p) }
func (c *cancelReadCloser) Close() error { return c.close() }

View File

@@ -20,13 +20,13 @@ type VolumePruneOptions struct {
Filters Filters
}
// VolumePruneResult holds the result from the [Client.VolumesPrune] method.
// VolumePruneResult holds the result from the [Client.VolumePrune] method.
type VolumePruneResult struct {
Report volume.PruneReport
}
// VolumesPrune requests the daemon to delete unused data
func (cli *Client) VolumesPrune(ctx context.Context, options VolumePruneOptions) (VolumePruneResult, error) {
// VolumePrune requests the daemon to delete unused data
func (cli *Client) VolumePrune(ctx context.Context, options VolumePruneOptions) (VolumePruneResult, error) {
if options.All {
if _, ok := options.Filters["all"]; ok {
return VolumePruneResult{}, errdefs.ErrInvalidArgument.WithMessage(`conflicting options: cannot specify both "all" and "all" filter`)