Skip to content

Commit f908248

Browse files
authored
Merge pull request moby#51278 from vvoland/client-container-opts2
client_(attach,commit,create,diff): Wrap result and options
2 parents ea59a8d + 8fb561c commit f908248

32 files changed

+498
-249
lines changed

client/client_interfaces.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"github.com/moby/moby/api/types/registry"
1313
"github.com/moby/moby/api/types/swarm"
1414
"github.com/moby/moby/api/types/system"
15-
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
1615
)
1716

1817
// APIClient is an interface that clients that talk with a docker server must implement.
@@ -58,10 +57,10 @@ type HijackDialer interface {
5857

5958
// ContainerAPIClient defines API client methods for the containers
6059
type ContainerAPIClient interface {
61-
ContainerAttach(ctx context.Context, container string, options ContainerAttachOptions) (HijackedResponse, error)
62-
ContainerCommit(ctx context.Context, container string, options ContainerCommitOptions) (container.CommitResponse, error)
63-
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
64-
ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error)
60+
ContainerAttach(ctx context.Context, container string, options ContainerAttachOptions) (ContainerAttachResult, error)
61+
ContainerCommit(ctx context.Context, container string, options ContainerCommitOptions) (ContainerCommitResult, error)
62+
ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error)
63+
ContainerDiff(ctx context.Context, container string, options ContainerDiffOptions) (ContainerDiffResult, error)
6564
ExecAPIClient
6665
ContainerExport(ctx context.Context, container string) (io.ReadCloser, error)
6766
ContainerInspect(ctx context.Context, container string) (container.InspectResponse, error)

client/container_attach.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ type ContainerAttachOptions struct {
1616
Logs bool
1717
}
1818

19+
// ContainerAttachResult is the result from attaching to a container.
20+
type ContainerAttachResult struct {
21+
HijackedResponse
22+
}
23+
1924
// ContainerAttach attaches a connection to a container in the server.
2025
// It returns a [HijackedResponse] with the hijacked connection
2126
// and a reader to get output. It's up to the called to close
@@ -44,10 +49,10 @@ type ContainerAttachOptions struct {
4449
// [stdcopy.StdType]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdType
4550
// [Stdout]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stdout
4651
// [Stderr]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stderr
47-
func (cli *Client) ContainerAttach(ctx context.Context, containerID string, options ContainerAttachOptions) (HijackedResponse, error) {
52+
func (cli *Client) ContainerAttach(ctx context.Context, containerID string, options ContainerAttachOptions) (ContainerAttachResult, error) {
4853
containerID, err := trimID("container", containerID)
4954
if err != nil {
50-
return HijackedResponse{}, err
55+
return ContainerAttachResult{}, err
5156
}
5257

5358
query := url.Values{}
@@ -70,7 +75,12 @@ func (cli *Client) ContainerAttach(ctx context.Context, containerID string, opti
7075
query.Set("logs", "1")
7176
}
7277

73-
return cli.postHijacked(ctx, "/containers/"+containerID+"/attach", query, nil, http.Header{
78+
hijacked, err := cli.postHijacked(ctx, "/containers/"+containerID+"/attach", query, nil, http.Header{
7479
"Content-Type": {"text/plain"},
7580
})
81+
if err != nil {
82+
return ContainerAttachResult{}, err
83+
}
84+
85+
return ContainerAttachResult{HijackedResponse: hijacked}, nil
7686
}

client/container_commit.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,27 @@ type ContainerCommitOptions struct {
2020
Config *container.Config
2121
}
2222

23+
// ContainerCommitResult is the result from committing a container.
24+
type ContainerCommitResult struct {
25+
ID string
26+
}
27+
2328
// ContainerCommit applies changes to a container and creates a new tagged image.
24-
func (cli *Client) ContainerCommit(ctx context.Context, containerID string, options ContainerCommitOptions) (container.CommitResponse, error) {
29+
func (cli *Client) ContainerCommit(ctx context.Context, containerID string, options ContainerCommitOptions) (ContainerCommitResult, error) {
2530
containerID, err := trimID("container", containerID)
2631
if err != nil {
27-
return container.CommitResponse{}, err
32+
return ContainerCommitResult{}, err
2833
}
2934

3035
var repository, tag string
3136
if options.Reference != "" {
3237
ref, err := reference.ParseNormalizedNamed(options.Reference)
3338
if err != nil {
34-
return container.CommitResponse{}, err
39+
return ContainerCommitResult{}, err
3540
}
3641

3742
if _, ok := ref.(reference.Digested); ok {
38-
return container.CommitResponse{}, errors.New("refusing to create a tag with a digest reference")
43+
return ContainerCommitResult{}, errors.New("refusing to create a tag with a digest reference")
3944
}
4045
ref = reference.TagNameOnly(ref)
4146

@@ -62,9 +67,9 @@ func (cli *Client) ContainerCommit(ctx context.Context, containerID string, opti
6267
resp, err := cli.post(ctx, "/commit", query, options.Config, nil)
6368
defer ensureReaderClosed(resp)
6469
if err != nil {
65-
return response, err
70+
return ContainerCommitResult{}, err
6671
}
6772

6873
err = json.NewDecoder(resp.Body).Decode(&response)
69-
return response, err
74+
return ContainerCommitResult{ID: response.ID}, err
7075
}

client/container_create.go

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,49 +10,63 @@ import (
1010

1111
cerrdefs "github.com/containerd/errdefs"
1212
"github.com/moby/moby/api/types/container"
13-
"github.com/moby/moby/api/types/network"
1413
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
1514
)
1615

1716
// ContainerCreate creates a new container based on the given configuration.
1817
// It can be associated with a name, but it's not mandatory.
19-
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
20-
if config == nil {
21-
return container.CreateResponse{}, cerrdefs.ErrInvalidArgument.WithMessage("config is nil")
18+
func (cli *Client) ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error) {
19+
cfg := options.Config
20+
21+
if cfg == nil {
22+
cfg = &container.Config{}
23+
}
24+
25+
if options.Image != "" {
26+
if cfg.Image != "" {
27+
return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("either Image or config.Image should be set")
28+
}
29+
newCfg := *cfg
30+
newCfg.Image = options.Image
31+
cfg = &newCfg
32+
}
33+
34+
if cfg.Image == "" {
35+
return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("config.Image or Image is required")
2236
}
2337

2438
var response container.CreateResponse
2539

26-
if hostConfig != nil {
27-
hostConfig.CapAdd = normalizeCapabilities(hostConfig.CapAdd)
28-
hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop)
40+
if options.HostConfig != nil {
41+
options.HostConfig.CapAdd = normalizeCapabilities(options.HostConfig.CapAdd)
42+
options.HostConfig.CapDrop = normalizeCapabilities(options.HostConfig.CapDrop)
2943
}
3044

3145
query := url.Values{}
32-
if platform != nil {
33-
if p := formatPlatform(*platform); p != "unknown" {
46+
if options.Platform != nil {
47+
if p := formatPlatform(*options.Platform); p != "unknown" {
3448
query.Set("platform", p)
3549
}
3650
}
3751

38-
if containerName != "" {
39-
query.Set("name", containerName)
52+
if options.Name != "" {
53+
query.Set("name", options.Name)
4054
}
4155

4256
body := container.CreateRequest{
43-
Config: config,
44-
HostConfig: hostConfig,
45-
NetworkingConfig: networkingConfig,
57+
Config: cfg,
58+
HostConfig: options.HostConfig,
59+
NetworkingConfig: options.NetworkingConfig,
4660
}
4761

4862
resp, err := cli.post(ctx, "/containers/create", query, body, nil)
4963
defer ensureReaderClosed(resp)
5064
if err != nil {
51-
return response, err
65+
return ContainerCreateResult{}, err
5266
}
5367

5468
err = json.NewDecoder(resp.Body).Decode(&response)
55-
return response, err
69+
return ContainerCreateResult{ID: response.ID, Warnings: response.Warnings}, err
5670
}
5771

5872
// formatPlatform returns a formatted string representing platform (e.g., "linux/arm/v7").

client/container_create_opts.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package client
2+
3+
import (
4+
"github.com/moby/moby/api/types/container"
5+
"github.com/moby/moby/api/types/network"
6+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
7+
)
8+
9+
// ContainerCreateOptions holds parameters to create a container.
10+
type ContainerCreateOptions struct {
11+
Config *container.Config
12+
HostConfig *container.HostConfig
13+
NetworkingConfig *network.NetworkingConfig
14+
Platform *ocispec.Platform
15+
Name string
16+
17+
// Image is a shortcut for Config.Image - only one of Image or Config.Image should be set.
18+
Image string
19+
}
20+
21+
// ContainerCreateResult is the result from creating a container.
22+
type ContainerCreateResult struct {
23+
ID string
24+
Warnings []string
25+
}

client/container_create_test.go

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,12 @@ func TestContainerCreateError(t *testing.T) {
2020
)
2121
assert.NilError(t, err)
2222

23-
_, err = client.ContainerCreate(context.Background(), nil, nil, nil, nil, "nothing")
24-
assert.Error(t, err, "config is nil")
23+
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: nil, Name: "nothing"})
24+
assert.Error(t, err, "config.Image or Image is required")
2525
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
2626

27-
_, err = client.ContainerCreate(context.Background(), &container.Config{}, nil, nil, nil, "nothing")
28-
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
29-
30-
// 404 doesn't automatically means an unknown image
31-
client, err = NewClientWithOpts(
32-
WithMockClient(errorMock(http.StatusNotFound, "Server error")),
33-
)
34-
assert.NilError(t, err)
35-
36-
_, err = client.ContainerCreate(context.Background(), &container.Config{}, nil, nil, nil, "nothing")
37-
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
27+
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, Name: "nothing"})
28+
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
3829
}
3930

4031
func TestContainerCreateImageNotFound(t *testing.T) {
@@ -43,7 +34,7 @@ func TestContainerCreateImageNotFound(t *testing.T) {
4334
)
4435
assert.NilError(t, err)
4536

46-
_, err = client.ContainerCreate(context.Background(), &container.Config{Image: "unknown_image"}, nil, nil, nil, "unknown")
37+
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "unknown_image"}, Name: "unknown"})
4738
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
4839
}
4940

@@ -65,7 +56,7 @@ func TestContainerCreateWithName(t *testing.T) {
6556
)
6657
assert.NilError(t, err)
6758

68-
r, err := client.ContainerCreate(context.Background(), &container.Config{}, nil, nil, nil, "container_name")
59+
r, err := client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, Name: "container_name"})
6960
assert.NilError(t, err)
7061
assert.Check(t, is.Equal(r.ID, "container_id"))
7162
}
@@ -87,7 +78,7 @@ func TestContainerCreateAutoRemove(t *testing.T) {
8778
)
8879
assert.NilError(t, err)
8980

90-
resp, err := client.ContainerCreate(context.Background(), &container.Config{}, &container.HostConfig{AutoRemove: true}, nil, nil, "")
81+
resp, err := client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, HostConfig: &container.HostConfig{AutoRemove: true}})
9182
assert.NilError(t, err)
9283
assert.Check(t, is.Equal(resp.ID, "container_id"))
9384
}
@@ -100,7 +91,7 @@ func TestContainerCreateConnectionError(t *testing.T) {
10091
client, err := NewClientWithOpts(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
10192
assert.NilError(t, err)
10293

103-
_, err = client.ContainerCreate(context.Background(), &container.Config{}, nil, nil, nil, "")
94+
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}})
10495
assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
10596
}
10697

@@ -142,6 +133,6 @@ func TestContainerCreateCapabilities(t *testing.T) {
142133
)
143134
assert.NilError(t, err)
144135

145-
_, err = client.ContainerCreate(context.Background(), &container.Config{}, &container.HostConfig{CapAdd: inputCaps, CapDrop: inputCaps}, nil, nil, "")
136+
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, HostConfig: &container.HostConfig{CapAdd: inputCaps, CapDrop: inputCaps}})
146137
assert.NilError(t, err)
147138
}

client/container_diff.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,22 @@ import (
99
)
1010

1111
// ContainerDiff shows differences in a container filesystem since it was started.
12-
func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]container.FilesystemChange, error) {
12+
func (cli *Client) ContainerDiff(ctx context.Context, containerID string, options ContainerDiffOptions) (ContainerDiffResult, error) {
1313
containerID, err := trimID("container", containerID)
1414
if err != nil {
15-
return nil, err
15+
return ContainerDiffResult{}, err
1616
}
1717

1818
resp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil)
1919
defer ensureReaderClosed(resp)
2020
if err != nil {
21-
return nil, err
21+
return ContainerDiffResult{}, err
2222
}
2323

2424
var changes []container.FilesystemChange
2525
err = json.NewDecoder(resp.Body).Decode(&changes)
2626
if err != nil {
27-
return nil, err
27+
return ContainerDiffResult{}, err
2828
}
29-
return changes, err
29+
return ContainerDiffResult{Changes: changes}, err
3030
}

client/container_diff_opts.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package client
2+
3+
import "github.com/moby/moby/api/types/container"
4+
5+
// ContainerDiffOptions holds parameters to show differences in a container filesystem.
6+
type ContainerDiffOptions struct {
7+
// Currently no options, but this allows for future extensibility
8+
}
9+
10+
// ContainerDiffResult is the result from showing differences in a container filesystem.
11+
type ContainerDiffResult struct {
12+
Changes []container.FilesystemChange
13+
}

client/container_diff_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ func TestContainerDiffError(t *testing.T) {
1717
)
1818
assert.NilError(t, err)
1919

20-
_, err = client.ContainerDiff(context.Background(), "nothing")
20+
_, err = client.ContainerDiff(context.Background(), "nothing", ContainerDiffOptions{})
2121
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
2222

23-
_, err = client.ContainerDiff(context.Background(), "")
23+
_, err = client.ContainerDiff(context.Background(), "", ContainerDiffOptions{})
2424
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
2525
assert.Check(t, is.ErrorContains(err, "value is empty"))
2626

27-
_, err = client.ContainerDiff(context.Background(), " ")
27+
_, err = client.ContainerDiff(context.Background(), " ", ContainerDiffOptions{})
2828
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
2929
assert.Check(t, is.ErrorContains(err, "value is empty"))
3030
}
@@ -57,7 +57,7 @@ func TestContainerDiff(t *testing.T) {
5757
)
5858
assert.NilError(t, err)
5959

60-
changes, err := client.ContainerDiff(context.Background(), "container_id")
60+
result, err := client.ContainerDiff(context.Background(), "container_id", ContainerDiffOptions{})
6161
assert.NilError(t, err)
62-
assert.Check(t, is.DeepEqual(changes, expected))
62+
assert.Check(t, is.DeepEqual(result.Changes, expected))
6363
}

0 commit comments

Comments
 (0)