vendor: github.com/docker/docker 5cc3f1dab895 (master, v28.0.0-rc.2)

full diff: b570831cc3...5cc3f1dab8

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2025-02-14 17:23:37 +01:00
parent 591fcb273a
commit 1d3eb6f95b
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
90 changed files with 566 additions and 396 deletions

View File

@ -16,17 +16,17 @@ import (
type fakeClient struct {
client.Client
imageTagFunc func(string, string) error
imageSaveFunc func(images []string, options image.SaveOptions) (io.ReadCloser, error)
imageSaveFunc func(images []string, options ...client.ImageSaveOption) (io.ReadCloser, error)
imageRemoveFunc func(image string, options image.RemoveOptions) ([]image.DeleteResponse, error)
imagePushFunc func(ref string, options image.PushOptions) (io.ReadCloser, error)
infoFunc func() (system.Info, error)
imagePullFunc func(ref string, options image.PullOptions) (io.ReadCloser, error)
imagesPruneFunc func(pruneFilter filters.Args) (image.PruneReport, error)
imageLoadFunc func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error)
imageLoadFunc func(input io.Reader, options ...client.ImageLoadOption) (image.LoadResponse, error)
imageListFunc func(options image.ListOptions) ([]image.Summary, error)
imageInspectFunc func(img string) (image.InspectResponse, error)
imageImportFunc func(source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
imageHistoryFunc func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error)
imageHistoryFunc func(img string, options ...client.ImageHistoryOption) ([]image.HistoryResponseItem, error)
imageBuildFunc func(context.Context, io.Reader, types.ImageBuildOptions) (types.ImageBuildResponse, error)
}
@ -37,9 +37,9 @@ func (cli *fakeClient) ImageTag(_ context.Context, img, ref string) error {
return nil
}
func (cli *fakeClient) ImageSave(_ context.Context, images []string, options image.SaveOptions) (io.ReadCloser, error) {
func (cli *fakeClient) ImageSave(_ context.Context, images []string, options ...client.ImageSaveOption) (io.ReadCloser, error) {
if cli.imageSaveFunc != nil {
return cli.imageSaveFunc(images, options)
return cli.imageSaveFunc(images, options...)
}
return io.NopCloser(strings.NewReader("")), nil
}
@ -81,9 +81,9 @@ func (cli *fakeClient) ImagesPrune(_ context.Context, pruneFilter filters.Args)
return image.PruneReport{}, nil
}
func (cli *fakeClient) ImageLoad(_ context.Context, input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
func (cli *fakeClient) ImageLoad(_ context.Context, input io.Reader, options ...client.ImageLoadOption) (image.LoadResponse, error) {
if cli.imageLoadFunc != nil {
return cli.imageLoadFunc(input, options)
return cli.imageLoadFunc(input, options...)
}
return image.LoadResponse{}, nil
}
@ -111,9 +111,9 @@ func (cli *fakeClient) ImageImport(_ context.Context, source image.ImportSource,
return io.NopCloser(strings.NewReader("")), nil
}
func (cli *fakeClient) ImageHistory(_ context.Context, img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error) {
func (cli *fakeClient) ImageHistory(_ context.Context, img string, options ...client.ImageHistoryOption) ([]image.HistoryResponseItem, error) {
if cli.imageHistoryFunc != nil {
return cli.imageHistoryFunc(img, options)
return cli.imageHistoryFunc(img, options...)
}
return []image.HistoryResponseItem{{ID: img, Created: time.Now().Unix()}}, nil
}

View File

@ -9,7 +9,7 @@ import (
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -56,16 +56,16 @@ func NewHistoryCommand(dockerCli command.Cli) *cobra.Command {
}
func runHistory(ctx context.Context, dockerCli command.Cli, opts historyOptions) error {
var options image.HistoryOptions
var options []client.ImageHistoryOption
if opts.platform != "" {
p, err := platforms.Parse(opts.platform)
if err != nil {
return errors.Wrap(err, "invalid platform")
}
options.Platform = &p
options = append(options, client.ImageHistoryWithPlatform(p))
}
history, err := dockerCli.Client().ImageHistory(ctx, opts.image, options)
history, err := dockerCli.Client().ImageHistory(ctx, opts.image, options...)
if err != nil {
return err
}

View File

@ -9,9 +9,8 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/docker/docker/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
)
@ -20,7 +19,7 @@ func TestNewHistoryCommandErrors(t *testing.T) {
name string
args []string
expectedError string
imageHistoryFunc func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error)
imageHistoryFunc func(img string, options ...client.ImageHistoryOption) ([]image.HistoryResponseItem, error)
}{
{
name: "wrong-args",
@ -31,7 +30,7 @@ func TestNewHistoryCommandErrors(t *testing.T) {
name: "client-error",
args: []string{"image:tag"},
expectedError: "something went wrong",
imageHistoryFunc: func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error) {
imageHistoryFunc: func(string, ...client.ImageHistoryOption) ([]image.HistoryResponseItem, error) {
return []image.HistoryResponseItem{{}}, errors.New("something went wrong")
},
},
@ -56,12 +55,12 @@ func TestNewHistoryCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
imageHistoryFunc func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error)
imageHistoryFunc func(img string, options ...client.ImageHistoryOption) ([]image.HistoryResponseItem, error)
}{
{
name: "simple",
args: []string{"image:tag"},
imageHistoryFunc: func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error) {
imageHistoryFunc: func(string, ...client.ImageHistoryOption) ([]image.HistoryResponseItem, error) {
return []image.HistoryResponseItem{{
ID: "1234567890123456789",
Created: time.Now().Unix(),
@ -76,7 +75,7 @@ func TestNewHistoryCommandSuccess(t *testing.T) {
{
name: "non-human",
args: []string{"--human=false", "image:tag"},
imageHistoryFunc: func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error) {
imageHistoryFunc: func(string, ...client.ImageHistoryOption) ([]image.HistoryResponseItem, error) {
return []image.HistoryResponseItem{{
ID: "abcdef",
Created: time.Date(2017, 1, 1, 12, 0, 3, 0, time.UTC).Unix(),
@ -88,7 +87,7 @@ func TestNewHistoryCommandSuccess(t *testing.T) {
{
name: "quiet-no-trunc",
args: []string{"--quiet", "--no-trunc", "image:tag"},
imageHistoryFunc: func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error) {
imageHistoryFunc: func(string, ...client.ImageHistoryOption) ([]image.HistoryResponseItem, error) {
return []image.HistoryResponseItem{{
ID: "1234567890123456789",
Created: time.Now().Unix(),
@ -98,8 +97,10 @@ func TestNewHistoryCommandSuccess(t *testing.T) {
{
name: "platform",
args: []string{"--platform", "linux/amd64", "image:tag"},
imageHistoryFunc: func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error) {
assert.Check(t, is.DeepEqual(ocispec.Platform{OS: "linux", Architecture: "amd64"}, *options.Platform))
imageHistoryFunc: func(img string, options ...client.ImageHistoryOption) ([]image.HistoryResponseItem, error) {
// FIXME(thaJeztah): need to find appropriate way to test the result of "ImageHistoryWithPlatform" being applied
assert.Check(t, len(options) > 0) // can be 1 or two depending on whether a terminal is attached :/
// assert.Check(t, is.Contains(options, client.ImageHistoryWithPlatform(ocispec.Platform{OS: "linux", Architecture: "amd64"})))
return []image.HistoryResponseItem{{
ID: "1234567890123456789",
Created: time.Now().Unix(),

View File

@ -9,7 +9,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/internal/jsonstream"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/client"
"github.com/moby/sys/sequential"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -68,9 +68,9 @@ func runLoad(ctx context.Context, dockerCli command.Cli, opts loadOptions) error
return errors.Errorf("requested load from stdin, but stdin is empty")
}
var options image.LoadOptions
var options []client.ImageLoadOption
if opts.quiet || !dockerCli.Out().IsTerminal() {
options.Quiet = true
options = append(options, client.ImageLoadWithQuiet(true))
}
if opts.platform != "" {
@ -79,10 +79,10 @@ func runLoad(ctx context.Context, dockerCli command.Cli, opts loadOptions) error
return errors.Wrap(err, "invalid platform")
}
// TODO(thaJeztah): change flag-type to support multiple platforms.
options.Platforms = append(options.Platforms, p)
options = append(options, client.ImageLoadWithPlatforms(p))
}
response, err := dockerCli.Client().ImageLoad(ctx, input, options)
response, err := dockerCli.Client().ImageLoad(ctx, input, options...)
if err != nil {
return err
}

View File

@ -9,9 +9,8 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/docker/docker/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
)
@ -21,7 +20,7 @@ func TestNewLoadCommandErrors(t *testing.T) {
args []string
isTerminalIn bool
expectedError string
imageLoadFunc func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error)
imageLoadFunc func(input io.Reader, options ...client.ImageLoadOption) (image.LoadResponse, error)
}{
{
name: "wrong-args",
@ -38,7 +37,7 @@ func TestNewLoadCommandErrors(t *testing.T) {
name: "pull-error",
args: []string{},
expectedError: "something went wrong",
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
imageLoadFunc: func(io.Reader, ...client.ImageLoadOption) (image.LoadResponse, error) {
return image.LoadResponse{}, errors.New("something went wrong")
},
},
@ -46,7 +45,7 @@ func TestNewLoadCommandErrors(t *testing.T) {
name: "invalid platform",
args: []string{"--platform", "<invalid>"},
expectedError: `invalid platform`,
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
imageLoadFunc: func(io.Reader, ...client.ImageLoadOption) (image.LoadResponse, error) {
return image.LoadResponse{}, nil
},
},
@ -78,22 +77,21 @@ func TestNewLoadCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
imageLoadFunc func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error)
imageLoadFunc func(input io.Reader, options ...client.ImageLoadOption) (image.LoadResponse, error)
}{
{
name: "simple",
args: []string{},
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
imageLoadFunc: func(io.Reader, ...client.ImageLoadOption) (image.LoadResponse, error) {
return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
},
},
{
name: "json",
args: []string{},
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
json := "{\"ID\": \"1\"}"
imageLoadFunc: func(io.Reader, ...client.ImageLoadOption) (image.LoadResponse, error) {
return image.LoadResponse{
Body: io.NopCloser(strings.NewReader(json)),
Body: io.NopCloser(strings.NewReader(`{"ID": "1"}`)),
JSON: true,
}, nil
},
@ -101,15 +99,17 @@ func TestNewLoadCommandSuccess(t *testing.T) {
{
name: "input-file",
args: []string{"--input", "testdata/load-command-success.input.txt"},
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
imageLoadFunc: func(input io.Reader, options ...client.ImageLoadOption) (image.LoadResponse, error) {
return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
},
},
{
name: "with platform",
args: []string{"--platform", "linux/amd64"},
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
assert.Check(t, is.DeepEqual([]ocispec.Platform{{OS: "linux", Architecture: "amd64"}}, options.Platforms))
imageLoadFunc: func(input io.Reader, options ...client.ImageLoadOption) (image.LoadResponse, error) {
// FIXME(thaJeztah): need to find appropriate way to test the result of "ImageHistoryWithPlatform" being applied
assert.Check(t, len(options) > 0) // can be 1 or two depending on whether a terminal is attached :/
// assert.Check(t, is.Contains(options, client.ImageHistoryWithPlatform(ocispec.Platform{OS: "linux", Architecture: "amd64"})))
return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
},
},

View File

@ -8,7 +8,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -57,17 +57,17 @@ func RunSave(ctx context.Context, dockerCli command.Cli, opts saveOptions) error
return errors.Wrap(err, "failed to save image")
}
var options image.SaveOptions
var options []client.ImageSaveOption
if opts.platform != "" {
p, err := platforms.Parse(opts.platform)
if err != nil {
return errors.Wrap(err, "invalid platform")
}
// TODO(thaJeztah): change flag-type to support multiple platforms.
options.Platforms = append(options.Platforms, p)
options = append(options, client.ImageSaveWithPlatforms(p))
}
responseBody, err := dockerCli.Client().ImageSave(ctx, opts.images, options)
responseBody, err := dockerCli.Client().ImageSave(ctx, opts.images, options...)
if err != nil {
return err
}

View File

@ -8,8 +8,7 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/docker/docker/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@ -20,7 +19,7 @@ func TestNewSaveCommandErrors(t *testing.T) {
args []string
isTerminal bool
expectedError string
imageSaveFunc func(images []string, options image.SaveOptions) (io.ReadCloser, error)
imageSaveFunc func(images []string, options ...client.ImageSaveOption) (io.ReadCloser, error)
}{
{
name: "wrong args",
@ -38,7 +37,7 @@ func TestNewSaveCommandErrors(t *testing.T) {
args: []string{"arg1"},
isTerminal: false,
expectedError: "error saving image",
imageSaveFunc: func(images []string, options image.SaveOptions) (io.ReadCloser, error) {
imageSaveFunc: func([]string, ...client.ImageSaveOption) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("")), errors.New("error saving image")
},
},
@ -75,13 +74,13 @@ func TestNewSaveCommandSuccess(t *testing.T) {
testCases := []struct {
args []string
isTerminal bool
imageSaveFunc func(images []string, options image.SaveOptions) (io.ReadCloser, error)
imageSaveFunc func(images []string, options ...client.ImageSaveOption) (io.ReadCloser, error)
deferredFunc func()
}{
{
args: []string{"-o", "save_tmp_file", "arg1"},
isTerminal: true,
imageSaveFunc: func(images []string, _ image.SaveOptions) (io.ReadCloser, error) {
imageSaveFunc: func(images []string, _ ...client.ImageSaveOption) (io.ReadCloser, error) {
assert.Assert(t, is.Len(images, 1))
assert.Check(t, is.Equal("arg1", images[0]))
return io.NopCloser(strings.NewReader("")), nil
@ -93,7 +92,7 @@ func TestNewSaveCommandSuccess(t *testing.T) {
{
args: []string{"arg1", "arg2"},
isTerminal: false,
imageSaveFunc: func(images []string, _ image.SaveOptions) (io.ReadCloser, error) {
imageSaveFunc: func(images []string, _ ...client.ImageSaveOption) (io.ReadCloser, error) {
assert.Assert(t, is.Len(images, 2))
assert.Check(t, is.Equal("arg1", images[0]))
assert.Check(t, is.Equal("arg2", images[1]))
@ -103,10 +102,12 @@ func TestNewSaveCommandSuccess(t *testing.T) {
{
args: []string{"--platform", "linux/amd64", "arg1"},
isTerminal: false,
imageSaveFunc: func(images []string, options image.SaveOptions) (io.ReadCloser, error) {
imageSaveFunc: func(images []string, options ...client.ImageSaveOption) (io.ReadCloser, error) {
assert.Assert(t, is.Len(images, 1))
assert.Check(t, is.Equal("arg1", images[0]))
assert.Check(t, is.DeepEqual([]ocispec.Platform{{OS: "linux", Architecture: "amd64"}}, options.Platforms))
// FIXME(thaJeztah): need to find appropriate way to test the result of "ImageHistoryWithPlatform" being applied
assert.Check(t, len(options) > 0) // can be 1 or two depending on whether a terminal is attached :/
// assert.Check(t, is.Contains(options, client.ImageHistoryWithPlatform(ocispec.Platform{OS: "linux", Architecture: "amd64"})))
return io.NopCloser(strings.NewReader("")), nil
},
},

View File

@ -13,7 +13,7 @@ require (
github.com/distribution/reference v0.6.0
github.com/docker/cli-docs-tool v0.9.0
github.com/docker/distribution v2.8.3+incompatible
github.com/docker/docker v28.0.0-rc.1.0.20250211164921-b570831cc3a3+incompatible // master (v28.0.0-rc.2)
github.com/docker/docker v28.0.0-rc.1.0.20250214181517-5cc3f1dab895+incompatible // master (v28.0.0-rc.2)
github.com/docker/docker-credential-helpers v0.8.2
github.com/docker/go-connections v0.5.0
github.com/docker/go-units v0.5.0

View File

@ -51,8 +51,8 @@ github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3T
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.0.0-rc.1.0.20250211164921-b570831cc3a3+incompatible h1:XHjzdPvMafmekjBHZDiS+aQUvfog6oSaCO/dtoZvFxM=
github.com/docker/docker v28.0.0-rc.1.0.20250211164921-b570831cc3a3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.0.0-rc.1.0.20250214181517-5cc3f1dab895+incompatible h1:jC2oKzxRv7Ji6AMyTJ/io92duH/G0NePvw6AAiSMQkc=
github.com/docker/docker v28.0.0-rc.1.0.20250214181517-5cc3f1dab895+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=

View File

@ -5109,6 +5109,17 @@ definitions:
ImageID:
description: "The ID of the image that this container was created from"
type: "string"
ImageManifestDescriptor:
$ref: "#/definitions/OCIDescriptor"
x-nullable: true
description: |
OCI descriptor of the platform-specific manifest of the image
the container was created from.
Note: Only available if the daemon provides a multi-platform
image store.
This field is not populated in the `GET /system/df` endpoint.
Command:
description: "Command to run when starting the container"
type: "string"

View File

@ -121,19 +121,20 @@ type State struct {
// Summary contains response of Engine API:
// GET "/containers/json"
type Summary struct {
ID string `json:"Id"`
Names []string
Image string
ImageID string
Command string
Created int64
Ports []Port
SizeRw int64 `json:",omitempty"`
SizeRootFs int64 `json:",omitempty"`
Labels map[string]string
State string
Status string
HostConfig struct {
ID string `json:"Id"`
Names []string
Image string
ImageID string
ImageManifestDescriptor *ocispec.Descriptor `json:"ImageManifestDescriptor,omitempty"`
Command string
Created int64
Ports []Port
SizeRw int64 `json:",omitempty"`
SizeRootFs int64 `json:",omitempty"`
Labels map[string]string
State string
Status string
HostConfig struct {
NetworkMode string `json:",omitempty"`
Annotations map[string]string `json:",omitempty"`
}
@ -183,5 +184,5 @@ type InspectResponse struct {
Config *Config
NetworkSettings *NetworkSettings
// ImageManifestDescriptor is the descriptor of a platform-specific manifest of the image used to create the container.
ImageManifestDescriptor *ocispec.Descriptor `json:",omitempty"`
ImageManifestDescriptor *ocispec.Descriptor `json:"ImageManifestDescriptor,omitempty"`
}

View File

@ -10,7 +10,7 @@ func (cli *Client) BuildCancel(ctx context.Context, id string) error {
query := url.Values{}
query.Set("id", id)
serverResp, err := cli.post(ctx, "/build/cancel", query, nil, nil)
ensureReaderClosed(serverResp)
resp, err := cli.post(ctx, "/build/cancel", query, nil, nil)
ensureReaderClosed(resp)
return err
}

View File

@ -40,15 +40,15 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePru
}
query.Set("filters", f)
serverResp, err := cli.post(ctx, "/build/prune", query, nil, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.post(ctx, "/build/prune", query, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
}
report := types.BuildCachePruneReport{}
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
return nil, errors.Wrap(err, "error retrieving disk usage")
}

View File

@ -23,6 +23,6 @@ func (cli *Client) CheckpointList(ctx context.Context, container string, options
return checkpoints, err
}
err = json.NewDecoder(resp.body).Decode(&checkpoints)
err = json.NewDecoder(resp.Body).Decode(&checkpoints)
return checkpoints, err
}

View File

@ -113,23 +113,30 @@ type ImageAPIClient interface {
BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error)
BuildCancel(ctx context.Context, id string) error
ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error)
ImageHistory(ctx context.Context, image string, opts image.HistoryOptions) ([]image.HistoryResponseItem, error)
ImageImport(ctx context.Context, source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error)
ImagePull(ctx context.Context, ref string, options image.PullOptions) (io.ReadCloser, error)
ImagePush(ctx context.Context, ref string, options image.PushOptions) (io.ReadCloser, error)
ImageRemove(ctx context.Context, image string, options image.RemoveOptions) ([]image.DeleteResponse, error)
ImageSearch(ctx context.Context, term string, options registry.SearchOptions) ([]registry.SearchResult, error)
ImageTag(ctx context.Context, image, ref string) error
ImagesPrune(ctx context.Context, pruneFilter filters.Args) (image.PruneReport, 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) (image.LoadResponse, error)
ImageSave(ctx context.Context, images []string, _ ...ImageSaveOption) (io.ReadCloser, error)
ImageAPIClientDeprecated
}
// ImageAPIClientDeprecated defines deprecated methods of the ImageAPIClient.
type ImageAPIClientDeprecated interface {
// ImageInspectWithRaw returns the image information and its raw representation.
//
// Deprecated: Use [Client.ImageInspect] instead. Raw response can be obtained using the [ImageInspectWithRawResponse] option.
ImageInspectWithRaw(ctx context.Context, image string) (image.InspectResponse, []byte, error)
ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (image.InspectResponse, error)
ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error)
ImageLoad(ctx context.Context, input io.Reader, opts image.LoadOptions) (image.LoadResponse, error)
ImagePull(ctx context.Context, ref string, options image.PullOptions) (io.ReadCloser, error)
ImagePush(ctx context.Context, ref string, options image.PushOptions) (io.ReadCloser, error)
ImageRemove(ctx context.Context, image string, options image.RemoveOptions) ([]image.DeleteResponse, error)
ImageSave(ctx context.Context, images []string, opts image.SaveOptions) (io.ReadCloser, error)
ImageSearch(ctx context.Context, term string, options registry.SearchOptions) ([]registry.SearchResult, error)
ImageTag(ctx context.Context, image, ref string) error
ImagesPrune(ctx context.Context, pruneFilter filters.Args) (image.PruneReport, error)
}
// NetworkAPIClient defines API client methods for the networks

View File

@ -20,6 +20,6 @@ func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (t
return response, err
}
err = json.NewDecoder(resp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@ -24,7 +24,7 @@ func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.C
return swarm.Config{}, nil, err
}
body, err := io.ReadAll(resp.body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return swarm.Config{}, nil, err
}

View File

@ -33,6 +33,6 @@ func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptio
}
var configs []swarm.Config
err = json.NewDecoder(resp.body).Decode(&configs)
err = json.NewDecoder(resp.Body).Decode(&configs)
return configs, err
}

View File

@ -55,6 +55,6 @@ func (cli *Client) ContainerCommit(ctx context.Context, containerID string, opti
return response, err
}
err = json.NewDecoder(resp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@ -24,12 +24,12 @@ func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path stri
query := url.Values{}
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
response, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil)
defer ensureReaderClosed(response)
resp, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return container.PathStat{}, err
}
return getContainerPathStatFromHeader(response.header)
return getContainerPathStatFromHeader(resp.Header)
}
// CopyToContainer copies content into the container filesystem.
@ -71,7 +71,7 @@ func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath s
query := make(url.Values, 1)
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
response, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil)
resp, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil)
if err != nil {
return nil, container.PathStat{}, err
}
@ -82,11 +82,11 @@ func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath s
// copy it locally. Along with the stat info about the local destination,
// we have everything we need to handle the multiple possibilities there
// can be when copying a file/dir from one location to another file/dir.
stat, err := getContainerPathStatFromHeader(response.header)
stat, err := getContainerPathStatFromHeader(resp.Header)
if err != nil {
return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err)
}
return response.body, stat, err
return resp.Body, stat, err
}
func getContainerPathStatFromHeader(header http.Header) (container.PathStat, error) {

View File

@ -79,13 +79,13 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
NetworkingConfig: networkingConfig,
}
serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.post(ctx, "/containers/create", query, body, nil)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
}
err = json.NewDecoder(serverResp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@ -15,14 +15,14 @@ func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]con
return nil, err
}
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
}
var changes []container.FilesystemChange
err = json.NewDecoder(serverResp.body).Decode(&changes)
err = json.NewDecoder(resp.Body).Decode(&changes)
if err != nil {
return nil, err
}

View File

@ -40,7 +40,7 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string,
}
var response container.ExecCreateResponse
err = json.NewDecoder(resp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
@ -75,7 +75,7 @@ func (cli *Client) ContainerExecInspect(ctx context.Context, execID string) (con
return response, err
}
err = json.NewDecoder(resp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
ensureReaderClosed(resp)
return response, err
}

View File

@ -15,10 +15,10 @@ func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.
return nil, err
}
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil)
resp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil)
if err != nil {
return nil, err
}
return serverResp.body, nil
return resp.Body, nil
}

View File

@ -17,14 +17,14 @@ func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (co
return container.InspectResponse{}, err
}
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return container.InspectResponse{}, err
}
var response container.InspectResponse
err = json.NewDecoder(serverResp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
@ -39,13 +39,13 @@ func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID stri
if getSize {
query.Set("size", "1")
}
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return container.InspectResponse{}, nil, err
}
body, err := io.ReadAll(serverResp.body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return container.InspectResponse{}, nil, err
}

View File

@ -51,6 +51,6 @@ func (cli *Client) ContainerList(ctx context.Context, options container.ListOpti
}
var containers []container.Summary
err = json.NewDecoder(resp.body).Decode(&containers)
err = json.NewDecoder(resp.Body).Decode(&containers)
return containers, err
}

View File

@ -81,5 +81,5 @@ func (cli *Client) ContainerLogs(ctx context.Context, containerID string, option
if err != nil {
return nil, err
}
return resp.body, nil
return resp.Body, nil
}

View File

@ -20,14 +20,14 @@ func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Arg
return container.PruneReport{}, err
}
serverResp, err := cli.post(ctx, "/containers/prune", query, nil, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.post(ctx, "/containers/prune", query, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return container.PruneReport{}, err
}
var report container.PruneReport
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
return container.PruneReport{}, fmt.Errorf("Error retrieving disk usage: %v", err)
}

View File

@ -27,8 +27,8 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea
}
return container.StatsResponseReader{
Body: resp.body,
OSType: getDockerOS(resp.header.Get("Server")),
Body: resp.Body,
OSType: getDockerOS(resp.Header.Get("Server")),
}, nil
}
@ -50,7 +50,7 @@ func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string
}
return container.StatsResponseReader{
Body: resp.body,
OSType: getDockerOS(resp.header.Get("Server")),
Body: resp.Body,
OSType: getDockerOS(resp.Header.Get("Server")),
}, nil
}

View File

@ -28,6 +28,6 @@ func (cli *Client) ContainerTop(ctx context.Context, containerID string, argumen
}
var response container.TopResponse
err = json.NewDecoder(resp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@ -14,13 +14,13 @@ func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, upda
return container.UpdateResponse{}, err
}
serverResp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil)
defer ensureReaderClosed(resp)
if err != nil {
return container.UpdateResponse{}, err
}
var response container.UpdateResponse
err = json.NewDecoder(serverResp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@ -67,9 +67,8 @@ func (cli *Client) ContainerWait(ctx context.Context, containerID string, condit
go func() {
defer ensureReaderClosed(resp)
body := resp.body
responseText := bytes.NewBuffer(nil)
stream := io.TeeReader(body, responseText)
stream := io.TeeReader(resp.Body, responseText)
var res container.WaitResponse
if err := json.NewDecoder(stream).Decode(&res); err != nil {
@ -111,7 +110,7 @@ func (cli *Client) legacyContainerWait(ctx context.Context, containerID string)
defer ensureReaderClosed(resp)
var res container.WaitResponse
if err := json.NewDecoder(resp.body).Decode(&res); err != nil {
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
errC <- err
return
}

View File

@ -19,14 +19,14 @@ func (cli *Client) DiskUsage(ctx context.Context, options types.DiskUsageOptions
}
}
serverResp, err := cli.get(ctx, "/system/df", query, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/system/df", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return types.DiskUsage{}, err
}
var du types.DiskUsage
if err := json.NewDecoder(serverResp.body).Decode(&du); err != nil {
if err := json.NewDecoder(resp.Body).Decode(&du); err != nil {
return types.DiskUsage{}, fmt.Errorf("Error retrieving disk usage: %v", err)
}
return du, nil

View File

@ -11,14 +11,12 @@ import (
// DistributionInspect returns the image digest with the full manifest.
func (cli *Client) DistributionInspect(ctx context.Context, imageRef, encodedRegistryAuth string) (registry.DistributionInspect, error) {
// Contact the registry to retrieve digest and platform information
var distributionInspect registry.DistributionInspect
if imageRef == "" {
return distributionInspect, objectNotFoundError{object: "distribution", id: imageRef}
return registry.DistributionInspect{}, objectNotFoundError{object: "distribution", id: imageRef}
}
if err := cli.NewVersionError(ctx, "1.30", "distribution inspect"); err != nil {
return distributionInspect, err
return registry.DistributionInspect{}, err
}
var headers http.Header
@ -28,12 +26,14 @@ func (cli *Client) DistributionInspect(ctx context.Context, imageRef, encodedReg
}
}
// Contact the registry to retrieve digest and platform information
resp, err := cli.get(ctx, "/distribution/"+imageRef+"/json", url.Values{}, headers)
defer ensureReaderClosed(resp)
if err != nil {
return distributionInspect, err
return registry.DistributionInspect{}, err
}
err = json.NewDecoder(resp.body).Decode(&distributionInspect)
var distributionInspect registry.DistributionInspect
err = json.NewDecoder(resp.Body).Decode(&distributionInspect)
return distributionInspect, err
}

View File

@ -36,9 +36,9 @@ func (cli *Client) Events(ctx context.Context, options events.ListOptions) (<-ch
errs <- err
return
}
defer resp.body.Close()
defer resp.Body.Close()
decoder := json.NewDecoder(resp.body)
decoder := json.NewDecoder(resp.Body)
close(started)
for {

View File

@ -33,14 +33,14 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
headers.Set("Content-Type", "application/x-tar")
serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
resp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
if err != nil {
return types.ImageBuildResponse{}, err
}
return types.ImageBuildResponse{
Body: serverResp.body,
OSType: getDockerOS(serverResp.header.Get("Server")),
Body: resp.Body,
OSType: getDockerOS(resp.Header.Get("Server")),
}, nil
}

View File

@ -30,10 +30,10 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti
if err != nil {
return nil, err
}
return resp.body, nil
return resp.Body, nil
}
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) {
return cli.post(ctx, "/images/create", query, nil, http.Header{
registry.AuthHeader: {registryAuth},
})

View File

@ -3,33 +3,54 @@ package client // import "github.com/docker/docker/client"
import (
"context"
"encoding/json"
"fmt"
"net/url"
"github.com/docker/docker/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageHistoryWithPlatform sets the platform for the image history operation.
func ImageHistoryWithPlatform(platform ocispec.Platform) ImageHistoryOption {
return imageHistoryOptionFunc(func(opt *imageHistoryOpts) error {
if opt.apiOptions.Platform != nil {
return fmt.Errorf("platform already set to %s", *opt.apiOptions.Platform)
}
opt.apiOptions.Platform = &platform
return nil
})
}
// ImageHistory returns the changes in an image in history format.
func (cli *Client) ImageHistory(ctx context.Context, imageID string, opts image.HistoryOptions) ([]image.HistoryResponseItem, error) {
func (cli *Client) ImageHistory(ctx context.Context, imageID string, historyOpts ...ImageHistoryOption) ([]image.HistoryResponseItem, error) {
query := url.Values{}
if opts.Platform != nil {
var opts imageHistoryOpts
for _, o := range historyOpts {
if err := o.Apply(&opts); err != nil {
return nil, err
}
}
if opts.apiOptions.Platform != nil {
if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
return nil, err
}
p, err := encodePlatform(opts.Platform)
p, err := encodePlatform(opts.apiOptions.Platform)
if err != nil {
return nil, err
}
query.Set("platform", p)
}
serverResp, err := cli.get(ctx, "/images/"+imageID+"/history", query, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/images/"+imageID+"/history", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
}
var history []image.HistoryResponseItem
err = json.NewDecoder(serverResp.body).Decode(&history)
err = json.NewDecoder(resp.Body).Decode(&history)
return history, err
}

View File

@ -0,0 +1,19 @@
package client
import (
"github.com/docker/docker/api/types/image"
)
// ImageHistoryOption is a type representing functional options for the image history operation.
type ImageHistoryOption interface {
Apply(*imageHistoryOpts) error
}
type imageHistoryOptionFunc func(opt *imageHistoryOpts) error
func (f imageHistoryOptionFunc) Apply(o *imageHistoryOpts) error {
return f(o)
}
type imageHistoryOpts struct {
apiOptions image.HistoryOptions
}

View File

@ -44,5 +44,5 @@ func (cli *Client) ImageImport(ctx context.Context, source image.ImportSource, r
if err != nil {
return nil, err
}
return resp.body, nil
return resp.Body, nil
}

View File

@ -11,49 +11,6 @@ import (
"github.com/docker/docker/api/types/image"
)
// ImageInspectOption is a type representing functional options for the image inspect operation.
type ImageInspectOption interface {
Apply(*imageInspectOpts) error
}
type imageInspectOptionFunc func(opt *imageInspectOpts) error
func (f imageInspectOptionFunc) Apply(o *imageInspectOpts) error {
return f(o)
}
// ImageInspectWithRawResponse instructs the client to additionally store the
// raw inspect response in the provided buffer.
func ImageInspectWithRawResponse(raw *bytes.Buffer) ImageInspectOption {
return imageInspectOptionFunc(func(opts *imageInspectOpts) error {
opts.raw = raw
return nil
})
}
// ImageInspectWithManifests sets manifests API option for the image inspect operation.
// This option is only available for API version 1.48 and up.
// With this option set, the image inspect operation response will have the
// [image.InspectResponse.Manifests] field populated if the server is multi-platform capable.
func ImageInspectWithManifests(manifests bool) ImageInspectOption {
return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error {
clientOpts.apiOptions.Manifests = manifests
return nil
})
}
// ImageInspectWithAPIOpts sets the API options for the image inspect operation.
func ImageInspectWithAPIOpts(opts image.InspectOptions) ImageInspectOption {
return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error {
clientOpts.apiOptions = opts
return nil
})
}
type imageInspectOpts struct {
raw *bytes.Buffer
apiOptions image.InspectOptions
}
// ImageInspect returns the image information.
func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts ...ImageInspectOption) (image.InspectResponse, error) {
if imageID == "" {
@ -75,8 +32,8 @@ func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts
query.Set("manifests", "1")
}
serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", query, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/images/"+imageID+"/json", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return image.InspectResponse{}, err
}
@ -86,7 +43,7 @@ func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts
buf = &bytes.Buffer{}
}
if _, err := io.Copy(buf, serverResp.body); err != nil {
if _, err := io.Copy(buf, resp.Body); err != nil {
return image.InspectResponse{}, err
}

View File

@ -0,0 +1,50 @@
package client
import (
"bytes"
"github.com/docker/docker/api/types/image"
)
// ImageInspectOption is a type representing functional options for the image inspect operation.
type ImageInspectOption interface {
Apply(*imageInspectOpts) error
}
type imageInspectOptionFunc func(opt *imageInspectOpts) error
func (f imageInspectOptionFunc) Apply(o *imageInspectOpts) error {
return f(o)
}
// ImageInspectWithRawResponse instructs the client to additionally store the
// raw inspect response in the provided buffer.
func ImageInspectWithRawResponse(raw *bytes.Buffer) ImageInspectOption {
return imageInspectOptionFunc(func(opts *imageInspectOpts) error {
opts.raw = raw
return nil
})
}
// ImageInspectWithManifests sets manifests API option for the image inspect operation.
// This option is only available for API version 1.48 and up.
// With this option set, the image inspect operation response will have the
// [image.InspectResponse.Manifests] field populated if the server is multi-platform capable.
func ImageInspectWithManifests(manifests bool) ImageInspectOption {
return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error {
clientOpts.apiOptions.Manifests = manifests
return nil
})
}
// ImageInspectWithAPIOpts sets the API options for the image inspect operation.
func ImageInspectWithAPIOpts(opts image.InspectOptions) ImageInspectOption {
return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error {
clientOpts.apiOptions = opts
return nil
})
}
type imageInspectOpts struct {
raw *bytes.Buffer
apiOptions image.InspectOptions
}

View File

@ -56,12 +56,12 @@ func (cli *Client) ImageList(ctx context.Context, options image.ListOptions) ([]
query.Set("manifests", "1")
}
serverResp, err := cli.get(ctx, "/images/json", query, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/images/json", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return images, err
}
err = json.NewDecoder(serverResp.body).Decode(&images)
err = json.NewDecoder(resp.Body).Decode(&images)
return images, err
}

View File

@ -16,18 +16,25 @@ import (
// Platform is an optional parameter that specifies the platform to load from
// the provided multi-platform image. This is only has effect if the input image
// is a multi-platform image.
func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, opts image.LoadOptions) (image.LoadResponse, error) {
func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, loadOpts ...ImageLoadOption) (image.LoadResponse, error) {
var opts imageLoadOpts
for _, opt := range loadOpts {
if err := opt.Apply(&opts); err != nil {
return image.LoadResponse{}, err
}
}
query := url.Values{}
query.Set("quiet", "0")
if opts.Quiet {
if opts.apiOptions.Quiet {
query.Set("quiet", "1")
}
if len(opts.Platforms) > 0 {
if len(opts.apiOptions.Platforms) > 0 {
if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
return image.LoadResponse{}, err
}
p, err := encodePlatforms(opts.Platforms...)
p, err := encodePlatforms(opts.apiOptions.Platforms...)
if err != nil {
return image.LoadResponse{}, err
}
@ -41,7 +48,7 @@ func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, opts image.Lo
return image.LoadResponse{}, err
}
return image.LoadResponse{
Body: resp.body,
JSON: resp.header.Get("Content-Type") == "application/json",
Body: resp.Body,
JSON: resp.Header.Get("Content-Type") == "application/json",
}, nil
}

View File

@ -0,0 +1,41 @@
package client
import (
"fmt"
"github.com/docker/docker/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageLoadOption is a type representing functional options for the image load operation.
type ImageLoadOption interface {
Apply(*imageLoadOpts) error
}
type imageLoadOptionFunc func(opt *imageLoadOpts) error
func (f imageLoadOptionFunc) Apply(o *imageLoadOpts) error {
return f(o)
}
type imageLoadOpts struct {
apiOptions image.LoadOptions
}
// ImageLoadWithQuiet sets the quiet option for the image load operation.
func ImageLoadWithQuiet(quiet bool) ImageLoadOption {
return imageLoadOptionFunc(func(opt *imageLoadOpts) error {
opt.apiOptions.Quiet = quiet
return nil
})
}
// ImageLoadWithPlatforms sets the platforms to be loaded from the image.
func ImageLoadWithPlatforms(platforms ...ocispec.Platform) ImageLoadOption {
return imageLoadOptionFunc(func(opt *imageLoadOpts) error {
if opt.apiOptions.Platforms != nil {
return fmt.Errorf("platforms already set to %v", opt.apiOptions.Platforms)
}
opt.apiOptions.Platforms = platforms
return nil
})
}

View File

@ -20,14 +20,14 @@ func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (
return image.PruneReport{}, err
}
serverResp, err := cli.post(ctx, "/images/prune", query, nil, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.post(ctx, "/images/prune", query, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return image.PruneReport{}, err
}
var report image.PruneReport
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
return image.PruneReport{}, fmt.Errorf("Error retrieving disk usage: %v", err)
}

View File

@ -45,7 +45,7 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options image.P
if err != nil {
return nil, err
}
return resp.body, nil
return resp.Body, nil
}
// getAPITagFromNamedRef returns a tag from the specified reference.

View File

@ -63,10 +63,10 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options image.Pu
if err != nil {
return nil, err
}
return resp.body, nil
return resp.Body, nil
}
func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (serverResponse, error) {
func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (*http.Response, error) {
return cli.post(ctx, "/images/"+imageID+"/push", query, nil, http.Header{
registry.AuthHeader: {registryAuth},
})

View File

@ -19,13 +19,13 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options imag
query.Set("noprune", "1")
}
var dels []image.DeleteResponse
resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return dels, err
return nil, err
}
err = json.NewDecoder(resp.body).Decode(&dels)
var dels []image.DeleteResponse
err = json.NewDecoder(resp.Body).Decode(&dels)
return dels, err
}

View File

@ -4,22 +4,29 @@ import (
"context"
"io"
"net/url"
"github.com/docker/docker/api/types/image"
)
// ImageSave retrieves one or more images from the docker host as an io.ReadCloser.
// It's up to the caller to store the images and close the stream.
func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, opts image.SaveOptions) (io.ReadCloser, error) {
//
// Platforms is an optional parameter that specifies the platforms to save from the image.
// This is only has effect if the input image is a multi-platform image.
func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, saveOpts ...ImageSaveOption) (io.ReadCloser, error) {
var opts imageSaveOpts
for _, opt := range saveOpts {
if err := opt.Apply(&opts); err != nil {
return nil, err
}
}
query := url.Values{
"names": imageIDs,
}
if len(opts.Platforms) > 0 {
if len(opts.apiOptions.Platforms) > 0 {
if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
return nil, err
}
p, err := encodePlatforms(opts.Platforms...)
p, err := encodePlatforms(opts.apiOptions.Platforms...)
if err != nil {
return nil, err
}
@ -30,5 +37,5 @@ func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, opts image.
if err != nil {
return nil, err
}
return resp.body, nil
return resp.Body, nil
}

View File

@ -0,0 +1,33 @@
package client
import (
"fmt"
"github.com/docker/docker/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
type ImageSaveOption interface {
Apply(*imageSaveOpts) error
}
type imageSaveOptionFunc func(opt *imageSaveOpts) error
func (f imageSaveOptionFunc) Apply(o *imageSaveOpts) error {
return f(o)
}
// ImageSaveWithPlatforms sets the platforms to be saved from the image.
func ImageSaveWithPlatforms(platforms ...ocispec.Platform) ImageSaveOption {
return imageSaveOptionFunc(func(opt *imageSaveOpts) error {
if opt.apiOptions.Platforms != nil {
return fmt.Errorf("platforms already set to %v", opt.apiOptions.Platforms)
}
opt.apiOptions.Platforms = platforms
return nil
})
}
type imageSaveOpts struct {
apiOptions image.SaveOptions
}

View File

@ -43,11 +43,11 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options registr
return results, err
}
err = json.NewDecoder(resp.body).Decode(&results)
err = json.NewDecoder(resp.Body).Decode(&results)
return results, err
}
func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) {
return cli.get(ctx, "/images/search", query, http.Header{
registry.AuthHeader: {registryAuth},
})

View File

@ -12,13 +12,13 @@ import (
// Info returns information about the docker server.
func (cli *Client) Info(ctx context.Context) (system.Info, error) {
var info system.Info
serverResp, err := cli.get(ctx, "/info", url.Values{}, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/info", url.Values{}, nil)
defer ensureReaderClosed(resp)
if err != nil {
return info, err
}
if err := json.NewDecoder(serverResp.body).Decode(&info); err != nil {
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
return info, fmt.Errorf("Error reading remote info: %v", err)
}

View File

@ -19,6 +19,6 @@ func (cli *Client) RegistryLogin(ctx context.Context, auth registry.AuthConfig)
}
var response registry.AuthenticateOKBody
err = json.NewDecoder(resp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@ -10,15 +10,13 @@ import (
// NetworkCreate creates a new network in the docker host.
func (cli *Client) NetworkCreate(ctx context.Context, name string, options network.CreateOptions) (network.CreateResponse, error) {
var response network.CreateResponse
// 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
return network.CreateResponse{}, err
}
networkCreateRequest := network.CreateRequest{
@ -30,12 +28,13 @@ func (cli *Client) NetworkCreate(ctx context.Context, name string, options netwo
networkCreateRequest.CheckDuplicate = &enabled //nolint:staticcheck // ignore SA1019: CheckDuplicate is deprecated since API v1.44.
}
serverResp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
return network.CreateResponse{}, err
}
err = json.NewDecoder(serverResp.body).Decode(&response)
var response network.CreateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@ -36,7 +36,7 @@ func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string,
return network.Inspect{}, nil, err
}
raw, err := io.ReadAll(resp.body)
raw, err := io.ReadAll(resp.Body)
if err != nil {
return network.Inspect{}, nil, err
}

View File

@ -27,6 +27,6 @@ func (cli *Client) NetworkList(ctx context.Context, options network.ListOptions)
if err != nil {
return networkResources, err
}
err = json.NewDecoder(resp.body).Decode(&networkResources)
err = json.NewDecoder(resp.Body).Decode(&networkResources)
return networkResources, err
}

View File

@ -20,14 +20,14 @@ func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args)
return network.PruneReport{}, err
}
serverResp, err := cli.post(ctx, "/networks/prune", query, nil, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.post(ctx, "/networks/prune", query, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return network.PruneReport{}, err
}
var report network.PruneReport
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
return network.PruneReport{}, fmt.Errorf("Error retrieving network prune report: %v", err)
}

View File

@ -15,13 +15,13 @@ func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm
if err != nil {
return swarm.Node{}, nil, err
}
serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.Node{}, nil, err
}
body, err := io.ReadAll(serverResp.body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return swarm.Node{}, nil, err
}

View File

@ -30,6 +30,6 @@ func (cli *Client) NodeList(ctx context.Context, options types.NodeListOptions)
}
var nodes []swarm.Node
err = json.NewDecoder(resp.body).Decode(&nodes)
err = json.NewDecoder(resp.Body).Decode(&nodes)
return nodes, err
}

View File

@ -8,7 +8,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/errdefs"
)
// Ping pings the server and returns the value of the "Docker-Experimental",
@ -28,49 +27,54 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
if err != nil {
return ping, err
}
serverResp, err := cli.doRequest(req)
if err == nil {
defer ensureReaderClosed(serverResp)
switch serverResp.statusCode {
resp, err := cli.doRequest(req)
if err != nil {
if IsErrConnectionFailed(err) {
return ping, err
}
// We managed to connect, but got some error; continue and try GET request.
} else {
defer ensureReaderClosed(resp)
switch resp.StatusCode {
case http.StatusOK, http.StatusInternalServerError:
// Server handled the request, so parse the response
return parsePingResponse(cli, serverResp)
return parsePingResponse(cli, resp)
}
} else if IsErrConnectionFailed(err) {
return ping, err
}
// HEAD failed; fallback to GET.
req.Method = http.MethodGet
serverResp, err = cli.doRequest(req)
defer ensureReaderClosed(serverResp)
resp, err = cli.doRequest(req)
defer ensureReaderClosed(resp)
if err != nil {
return ping, err
}
return parsePingResponse(cli, serverResp)
return parsePingResponse(cli, resp)
}
func parsePingResponse(cli *Client, resp serverResponse) (types.Ping, error) {
var ping types.Ping
if resp.header == nil {
err := cli.checkResponseErr(resp)
return ping, errdefs.FromStatusCode(err, resp.statusCode)
func parsePingResponse(cli *Client, resp *http.Response) (types.Ping, error) {
if resp == nil {
return types.Ping{}, nil
}
ping.APIVersion = resp.header.Get("Api-Version")
ping.OSType = resp.header.Get("Ostype")
if resp.header.Get("Docker-Experimental") == "true" {
var ping types.Ping
if resp.Header == nil {
return ping, cli.checkResponseErr(resp)
}
ping.APIVersion = resp.Header.Get("Api-Version")
ping.OSType = resp.Header.Get("Ostype")
if resp.Header.Get("Docker-Experimental") == "true" {
ping.Experimental = true
}
if bv := resp.header.Get("Builder-Version"); bv != "" {
if bv := resp.Header.Get("Builder-Version"); bv != "" {
ping.BuilderVersion = types.BuilderVersion(bv)
}
if si := resp.header.Get("Swarm"); si != "" {
if si := resp.Header.Get("Swarm"); si != "" {
state, role, _ := strings.Cut(si, "/")
ping.SwarmStatus = &swarm.Status{
NodeState: swarm.LocalNodeState(state),
ControlAvailable: role == "manager",
}
}
err := cli.checkResponseErr(resp)
return ping, errdefs.FromStatusCode(err, resp.statusCode)
return ping, cli.checkResponseErr(resp)
}

View File

@ -21,7 +21,7 @@ func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*type
return nil, nil, err
}
body, err := io.ReadAll(resp.body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}

View File

@ -35,13 +35,13 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
return nil, err
}
name = resp.header.Get("Docker-Plugin-Name")
name = resp.Header.Get("Docker-Plugin-Name")
pr, pw := io.Pipe()
go func() { // todo: the client should probably be designed more around the actual api
_, err := io.Copy(pw, resp.body)
_, err := io.Copy(pw, resp.Body)
if err != nil {
pw.CloseWithError(err)
_ = pw.CloseWithError(err)
return
}
defer func() {
@ -52,29 +52,29 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
}()
if len(options.Args) > 0 {
if err := cli.PluginSet(ctx, name, options.Args); err != nil {
pw.CloseWithError(err)
_ = pw.CloseWithError(err)
return
}
}
if options.Disabled {
pw.Close()
_ = pw.Close()
return
}
enableErr := cli.PluginEnable(ctx, name, types.PluginEnableOptions{Timeout: 0})
pw.CloseWithError(enableErr)
_ = pw.CloseWithError(enableErr)
}()
return pr, nil
}
func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) {
return cli.get(ctx, "/plugins/privileges", query, http.Header{
registry.AuthHeader: {registryAuth},
})
}
func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileges types.PluginPrivileges, registryAuth string) (serverResponse, error) {
func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileges types.PluginPrivileges, registryAuth string) (*http.Response, error) {
return cli.post(ctx, "/plugins/pull", query, privileges, http.Header{
registry.AuthHeader: {registryAuth},
})
@ -98,7 +98,7 @@ func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values,
}
var privileges types.PluginPrivileges
if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil {
if err := json.NewDecoder(resp.Body).Decode(&privileges); err != nil {
ensureReaderClosed(resp)
return nil, err
}

View File

@ -28,6 +28,6 @@ func (cli *Client) PluginList(ctx context.Context, filter filters.Args) (types.P
return plugins, err
}
err = json.NewDecoder(resp.body).Decode(&plugins)
err = json.NewDecoder(resp.Body).Decode(&plugins)
return plugins, err
}

View File

@ -20,5 +20,5 @@ func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth str
if err != nil {
return nil, err
}
return resp.body, nil
return resp.Body, nil
}

View File

@ -37,10 +37,10 @@ func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types
if err != nil {
return nil, err
}
return resp.body, nil
return resp.Body, nil
}
func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privileges types.PluginPrivileges, name, registryAuth string) (serverResponse, error) {
func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privileges types.PluginPrivileges, name, registryAuth string) (*http.Response, error) {
return cli.post(ctx, "/plugins/"+name+"/upgrade", query, privileges, http.Header{
registry.AuthHeader: {registryAuth},
})

View File

@ -19,47 +19,39 @@ import (
"github.com/pkg/errors"
)
// serverResponse is a wrapper for http API responses.
type serverResponse struct {
body io.ReadCloser
header http.Header
statusCode int
reqURL *url.URL
}
// head sends an http request to the docker API using the method HEAD.
func (cli *Client) head(ctx context.Context, path string, query url.Values, headers http.Header) (serverResponse, error) {
func (cli *Client) head(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error) {
return cli.sendRequest(ctx, http.MethodHead, path, query, nil, headers)
}
// get sends an http request to the docker API using the method GET with a specific Go context.
func (cli *Client) get(ctx context.Context, path string, query url.Values, headers http.Header) (serverResponse, error) {
func (cli *Client) get(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error) {
return cli.sendRequest(ctx, http.MethodGet, path, query, nil, headers)
}
// post sends an http request to the docker API using the method POST with a specific Go context.
func (cli *Client) post(ctx context.Context, path string, query url.Values, obj interface{}, headers http.Header) (serverResponse, error) {
func (cli *Client) post(ctx context.Context, path string, query url.Values, obj interface{}, headers http.Header) (*http.Response, error) {
body, headers, err := encodeBody(obj, headers)
if err != nil {
return serverResponse{}, err
return nil, err
}
return cli.sendRequest(ctx, http.MethodPost, path, query, body, headers)
}
func (cli *Client) postRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers http.Header) (serverResponse, error) {
func (cli *Client) postRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers http.Header) (*http.Response, error) {
return cli.sendRequest(ctx, http.MethodPost, path, query, body, headers)
}
func (cli *Client) put(ctx context.Context, path string, query url.Values, obj interface{}, headers http.Header) (serverResponse, error) {
func (cli *Client) put(ctx context.Context, path string, query url.Values, obj interface{}, headers http.Header) (*http.Response, error) {
body, headers, err := encodeBody(obj, headers)
if err != nil {
return serverResponse{}, err
return nil, err
}
return cli.putRaw(ctx, path, query, body, headers)
}
// putRaw sends an http request to the docker API using the method PUT.
func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers http.Header) (serverResponse, error) {
func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers http.Header) (*http.Response, error) {
// PUT requests are expected to always have a body (apparently)
// so explicitly pass an empty body to sendRequest to signal that
// it should set the Content-Type header if not already present.
@ -70,7 +62,7 @@ func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, bo
}
// delete sends an http request to the docker API using the method DELETE.
func (cli *Client) delete(ctx context.Context, path string, query url.Values, headers http.Header) (serverResponse, error) {
func (cli *Client) delete(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error) {
return cli.sendRequest(ctx, http.MethodDelete, path, query, nil, headers)
}
@ -116,42 +108,40 @@ func (cli *Client) buildRequest(ctx context.Context, method, path string, body i
return req, nil
}
func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers http.Header) (serverResponse, error) {
func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers http.Header) (*http.Response, error) {
req, err := cli.buildRequest(ctx, method, cli.getAPIPath(ctx, path, query), body, headers)
if err != nil {
return serverResponse{}, err
return nil, err
}
resp, err := cli.doRequest(req)
switch {
case errors.Is(err, context.Canceled):
return serverResponse{}, errdefs.Cancelled(err)
return nil, errdefs.Cancelled(err)
case errors.Is(err, context.DeadlineExceeded):
return serverResponse{}, errdefs.Deadline(err)
return nil, errdefs.Deadline(err)
case err == nil:
err = cli.checkResponseErr(resp)
return resp, cli.checkResponseErr(resp)
default:
return resp, err
}
return resp, errdefs.FromStatusCode(err, resp.statusCode)
}
// FIXME(thaJeztah): Should this actually return a serverResp when a connection error occurred?
func (cli *Client) doRequest(req *http.Request) (serverResponse, error) {
serverResp := serverResponse{statusCode: -1, reqURL: req.URL}
func (cli *Client) doRequest(req *http.Request) (*http.Response, error) {
resp, err := cli.client.Do(req)
if err != nil {
if cli.scheme != "https" && strings.Contains(err.Error(), "malformed HTTP response") {
return serverResp, errConnectionFailed{fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)}
return nil, errConnectionFailed{fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)}
}
if cli.scheme == "https" && strings.Contains(err.Error(), "bad certificate") {
return serverResp, errConnectionFailed{errors.Wrap(err, "the server probably has client authentication (--tlsverify) enabled; check your TLS client certification settings")}
return nil, errConnectionFailed{errors.Wrap(err, "the server probably has client authentication (--tlsverify) enabled; check your TLS client certification settings")}
}
// Don't decorate context sentinel errors; users may be comparing to
// them directly.
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return serverResp, err
return nil, err
}
var uErr *url.Error
@ -159,7 +149,7 @@ func (cli *Client) doRequest(req *http.Request) (serverResponse, error) {
var nErr *net.OpError
if errors.As(uErr.Err, &nErr) {
if os.IsPermission(nErr.Err) {
return serverResp, errConnectionFailed{errors.Wrapf(err, "permission denied while trying to connect to the Docker daemon socket at %v", cli.host)}
return nil, errConnectionFailed{errors.Wrapf(err, "permission denied while trying to connect to the Docker daemon socket at %v", cli.host)}
}
}
}
@ -168,10 +158,10 @@ func (cli *Client) doRequest(req *http.Request) (serverResponse, error) {
if errors.As(err, &nErr) {
// FIXME(thaJeztah): any net.Error should be considered a connection error (but we should include the original error)?
if nErr.Timeout() {
return serverResp, connectionFailed(cli.host)
return nil, connectionFailed(cli.host)
}
if strings.Contains(nErr.Error(), "connection refused") || strings.Contains(nErr.Error(), "dial unix") {
return serverResp, connectionFailed(cli.host)
return nil, connectionFailed(cli.host)
}
}
@ -195,28 +185,37 @@ func (cli *Client) doRequest(req *http.Request) (serverResponse, error) {
}
}
return serverResp, errConnectionFailed{errors.Wrap(err, "error during connect")}
return nil, errConnectionFailed{errors.Wrap(err, "error during connect")}
}
if resp != nil {
serverResp.statusCode = resp.StatusCode
serverResp.body = resp.Body
serverResp.header = resp.Header
}
return serverResp, nil
return resp, nil
}
func (cli *Client) checkResponseErr(serverResp serverResponse) error {
if serverResp.statusCode >= 200 && serverResp.statusCode < 400 {
func (cli *Client) checkResponseErr(serverResp *http.Response) (retErr error) {
if serverResp == nil {
return nil
}
if serverResp.StatusCode >= 200 && serverResp.StatusCode < 400 {
return nil
}
defer func() {
retErr = errdefs.FromStatusCode(retErr, serverResp.StatusCode)
}()
var body []byte
var err error
if serverResp.body != nil {
var reqURL string
if serverResp.Request != nil {
reqURL = serverResp.Request.URL.String()
}
statusMsg := serverResp.Status
if statusMsg == "" {
statusMsg = http.StatusText(serverResp.StatusCode)
}
if serverResp.Body != nil {
bodyMax := 1 * 1024 * 1024 // 1 MiB
bodyR := &io.LimitedReader{
R: serverResp.body,
R: serverResp.Body,
N: int64(bodyMax),
}
body, err = io.ReadAll(bodyR)
@ -224,15 +223,21 @@ func (cli *Client) checkResponseErr(serverResp serverResponse) error {
return err
}
if bodyR.N == 0 {
return fmt.Errorf("request returned %s with a message (> %d bytes) for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), bodyMax, serverResp.reqURL)
if reqURL != "" {
return fmt.Errorf("request returned %s with a message (> %d bytes) for API route and version %s, check if the server supports the requested API version", statusMsg, bodyMax, reqURL)
}
return fmt.Errorf("request returned %s with a message (> %d bytes); check if the server supports the requested API version", statusMsg, bodyMax)
}
}
if len(body) == 0 {
return fmt.Errorf("request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), serverResp.reqURL)
if reqURL != "" {
return fmt.Errorf("request returned %s for API route and version %s, check if the server supports the requested API version", statusMsg, reqURL)
}
return fmt.Errorf("request returned %s; check if the server supports the requested API version", statusMsg)
}
var daemonErr error
if serverResp.header.Get("Content-Type") == "application/json" && (cli.version == "" || versions.GreaterThan(cli.version, "1.23")) {
if serverResp.Header.Get("Content-Type") == "application/json" && (cli.version == "" || versions.GreaterThan(cli.version, "1.23")) {
var errorResponse types.ErrorResponse
if err := json.Unmarshal(body, &errorResponse); err != nil {
return errors.Wrap(err, "Error reading JSON")
@ -255,8 +260,8 @@ func (cli *Client) checkResponseErr(serverResp serverResponse) error {
//
// TODO(thaJeztah): consider adding a log.Debug to allow clients to debug the actual response when enabling debug logging.
daemonErr = fmt.Errorf(`API returned a %d (%s) but provided no error-message`,
serverResp.statusCode,
http.StatusText(serverResp.statusCode),
serverResp.StatusCode,
http.StatusText(serverResp.StatusCode),
)
} else {
daemonErr = errors.New(strings.TrimSpace(errorResponse.Message))
@ -305,10 +310,16 @@ func encodeData(data interface{}) (*bytes.Buffer, error) {
return params, nil
}
func ensureReaderClosed(response serverResponse) {
if response.body != nil {
func ensureReaderClosed(response *http.Response) {
if response != nil && response.Body != nil {
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
_, _ = io.CopyN(io.Discard, response.body, 512)
_ = response.body.Close()
// see https://github.com/google/go-github/pull/317/files#r57536827
//
// TODO(thaJeztah): see if this optimization is still needed, or already implemented in stdlib,
// and check if context-cancellation should handle this as well. If still needed, consider
// wrapping response.Body, or returning a "closer()" from [Client.sendRequest] and related
// methods.
_, _ = io.CopyN(io.Discard, response.Body, 512)
_ = response.Body.Close()
}
}

View File

@ -10,16 +10,16 @@ import (
// SecretCreate creates a new secret.
func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) {
var response types.SecretCreateResponse
if err := cli.NewVersionError(ctx, "1.25", "secret create"); err != nil {
return response, err
return types.SecretCreateResponse{}, err
}
resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
return types.SecretCreateResponse{}, err
}
err = json.NewDecoder(resp.body).Decode(&response)
var response types.SecretCreateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@ -24,7 +24,7 @@ func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.S
return swarm.Secret{}, nil, err
}
body, err := io.ReadAll(resp.body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return swarm.Secret{}, nil, err
}

View File

@ -33,6 +33,6 @@ func (cli *Client) SecretList(ctx context.Context, options types.SecretListOptio
}
var secrets []swarm.Secret
err = json.NewDecoder(resp.body).Decode(&secrets)
err = json.NewDecoder(resp.Body).Decode(&secrets)
return secrets, err
}

View File

@ -73,7 +73,7 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
return response, err
}
err = json.NewDecoder(resp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
if resolveWarning != "" {
response.Warnings = append(response.Warnings, resolveWarning)
}

View File

@ -21,13 +21,13 @@ func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string,
query := url.Values{}
query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults))
serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/services/"+serviceID, query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.Service{}, nil, err
}
body, err := io.ReadAll(serverResp.body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return swarm.Service{}, nil, err
}

View File

@ -34,6 +34,6 @@ func (cli *Client) ServiceList(ctx context.Context, options types.ServiceListOpt
}
var services []swarm.Service
err = json.NewDecoder(resp.body).Decode(&services)
err = json.NewDecoder(resp.Body).Decode(&services)
return services, err
}

View File

@ -53,5 +53,5 @@ func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options co
if err != nil {
return nil, err
}
return resp.body, nil
return resp.Body, nil
}

View File

@ -80,8 +80,8 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
return swarm.ServiceUpdateResponse{}, err
}
response := swarm.ServiceUpdateResponse{}
err = json.NewDecoder(resp.body).Decode(&response)
var response swarm.ServiceUpdateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
if resolveWarning != "" {
response.Warnings = append(response.Warnings, resolveWarning)
}

View File

@ -9,13 +9,13 @@ import (
// SwarmGetUnlockKey retrieves the swarm's unlock key.
func (cli *Client) SwarmGetUnlockKey(ctx context.Context) (types.SwarmUnlockKeyResponse, error) {
serverResp, err := cli.get(ctx, "/swarm/unlockkey", nil, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/swarm/unlockkey", nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return types.SwarmUnlockKeyResponse{}, err
}
var response types.SwarmUnlockKeyResponse
err = json.NewDecoder(serverResp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@ -9,13 +9,13 @@ import (
// SwarmInit initializes the swarm.
func (cli *Client) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) {
serverResp, err := cli.post(ctx, "/swarm/init", nil, req, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.post(ctx, "/swarm/init", nil, req, nil)
defer ensureReaderClosed(resp)
if err != nil {
return "", err
}
var response string
err = json.NewDecoder(serverResp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@ -9,13 +9,13 @@ import (
// SwarmInspect inspects the swarm.
func (cli *Client) SwarmInspect(ctx context.Context) (swarm.Swarm, error) {
serverResp, err := cli.get(ctx, "/swarm", nil, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/swarm", nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.Swarm{}, err
}
var response swarm.Swarm
err = json.NewDecoder(serverResp.body).Decode(&response)
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@ -8,7 +8,7 @@ import (
// SwarmUnlock unlocks locked swarm.
func (cli *Client) SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error {
serverResp, err := cli.post(ctx, "/swarm/unlock", nil, req, nil)
ensureReaderClosed(serverResp)
resp, err := cli.post(ctx, "/swarm/unlock", nil, req, nil)
ensureReaderClosed(resp)
return err
}

View File

@ -16,13 +16,13 @@ func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm
return swarm.Task{}, nil, err
}
serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.Task{}, nil, err
}
body, err := io.ReadAll(serverResp.body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return swarm.Task{}, nil, err
}

View File

@ -30,6 +30,6 @@ func (cli *Client) TaskList(ctx context.Context, options types.TaskListOptions)
}
var tasks []swarm.Task
err = json.NewDecoder(resp.body).Decode(&tasks)
err = json.NewDecoder(resp.Body).Decode(&tasks)
return tasks, err
}

View File

@ -47,5 +47,5 @@ func (cli *Client) TaskLogs(ctx context.Context, taskID string, options containe
if err != nil {
return nil, err
}
return resp.body, nil
return resp.Body, nil
}

View File

@ -16,6 +16,6 @@ func (cli *Client) ServerVersion(ctx context.Context) (types.Version, error) {
}
var server types.Version
err = json.NewDecoder(resp.body).Decode(&server)
err = json.NewDecoder(resp.Body).Decode(&server)
return server, err
}

View File

@ -9,12 +9,13 @@ import (
// VolumeCreate creates a volume in the docker host.
func (cli *Client) VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error) {
var vol volume.Volume
resp, err := cli.post(ctx, "/volumes/create", nil, options, nil)
defer ensureReaderClosed(resp)
if err != nil {
return vol, err
return volume.Volume{}, err
}
err = json.NewDecoder(resp.body).Decode(&vol)
var vol volume.Volume
err = json.NewDecoder(resp.Body).Decode(&vol)
return vol, err
}

View File

@ -22,17 +22,18 @@ func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (v
return volume.Volume{}, nil, err
}
var vol volume.Volume
resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return vol, nil, err
return volume.Volume{}, nil, err
}
body, err := io.ReadAll(resp.body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return vol, nil, err
return volume.Volume{}, nil, err
}
var vol volume.Volume
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&vol)
return vol, body, err

View File

@ -11,23 +11,23 @@ import (
// VolumeList returns the volumes configured in the docker host.
func (cli *Client) VolumeList(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error) {
var volumes volume.ListResponse
query := url.Values{}
if options.Filters.Len() > 0 {
//nolint:staticcheck // ignore SA1019 for old code
filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
if err != nil {
return volumes, err
return volume.ListResponse{}, err
}
query.Set("filters", filterJSON)
}
resp, err := cli.get(ctx, "/volumes", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return volumes, err
return volume.ListResponse{}, err
}
err = json.NewDecoder(resp.body).Decode(&volumes)
var volumes volume.ListResponse
err = json.NewDecoder(resp.Body).Decode(&volumes)
return volumes, err
}

View File

@ -20,14 +20,14 @@ func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args)
return volume.PruneReport{}, err
}
serverResp, err := cli.post(ctx, "/volumes/prune", query, nil, nil)
defer ensureReaderClosed(serverResp)
resp, err := cli.post(ctx, "/volumes/prune", query, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return volume.PruneReport{}, err
}
var report volume.PruneReport
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
return volume.PruneReport{}, fmt.Errorf("Error retrieving volume prune report: %v", err)
}

View File

@ -1,11 +1,11 @@
package archive
import (
"bytes"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"syscall"
"unsafe"
@ -143,7 +143,7 @@ func (w *walker) walk(path string, i1, i2 os.FileInfo) (err error) {
ni1 := names1[ix1]
ni2 := names2[ix2]
switch bytes.Compare([]byte(ni1.name), []byte(ni2.name)) {
switch strings.Compare(ni1.name, ni2.name) {
case -1: // ni1 < ni2 -- advance ni1
// we will not encounter ni1 in names2
names = append(names, ni1.name)

View File

@ -53,10 +53,9 @@ type JSONProgress struct {
func (p *JSONProgress) String() string {
var (
width = p.width()
pbBox string
numbersBox string
timeLeftBox string
width = p.width()
pbBox string
numbersBox string
)
if p.Current <= 0 && p.Total <= 0 {
return ""
@ -104,14 +103,14 @@ func (p *JSONProgress) String() string {
}
}
if p.Current > 0 && p.Start > 0 && percentage < 50 {
fromStart := p.now().Sub(time.Unix(p.Start, 0))
perEntry := fromStart / time.Duration(p.Current)
left := time.Duration(p.Total-p.Current) * perEntry
left = (left / time.Second) * time.Second
if width > 50 {
timeLeftBox = " " + left.String()
// Show approximation of remaining time if there's enough width.
var timeLeftBox string
if width > 50 {
if p.Current > 0 && p.Start > 0 && percentage < 50 {
fromStart := p.now().Sub(time.Unix(p.Start, 0))
perEntry := fromStart / time.Duration(p.Current)
left := time.Duration(p.Total-p.Current) * perEntry
timeLeftBox = " " + left.Round(time.Second).String()
}
}
return pbBox + numbersBox + timeLeftBox

2
vendor/modules.txt vendored
View File

@ -55,7 +55,7 @@ github.com/docker/distribution/registry/client/transport
github.com/docker/distribution/registry/storage/cache
github.com/docker/distribution/registry/storage/cache/memory
github.com/docker/distribution/uuid
# github.com/docker/docker v28.0.0-rc.1.0.20250211164921-b570831cc3a3+incompatible
# github.com/docker/docker v28.0.0-rc.1.0.20250214181517-5cc3f1dab895+incompatible
## explicit
github.com/docker/docker/api
github.com/docker/docker/api/types