client_(attach,commit,create,diff): Wrap result and options

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski
2025-10-23 14:20:05 +02:00
parent 1d8c8e192f
commit bd31b8b1c7
32 changed files with 460 additions and 238 deletions

View File

@@ -12,7 +12,6 @@ import (
"github.com/moby/moby/api/types/registry" "github.com/moby/moby/api/types/registry"
"github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/api/types/system" "github.com/moby/moby/api/types/system"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
// APIClient is an interface that clients that talk with a docker server must implement. // APIClient is an interface that clients that talk with a docker server must implement.
@@ -58,10 +57,10 @@ type HijackDialer interface {
// ContainerAPIClient defines API client methods for the containers // ContainerAPIClient defines API client methods for the containers
type ContainerAPIClient interface { type ContainerAPIClient interface {
ContainerAttach(ctx context.Context, container string, options ContainerAttachOptions) (HijackedResponse, error) ContainerAttach(ctx context.Context, container string, options ContainerAttachOptions) (ContainerAttachResult, error)
ContainerCommit(ctx context.Context, container string, options ContainerCommitOptions) (container.CommitResponse, error) ContainerCommit(ctx context.Context, container string, options ContainerCommitOptions) (ContainerCommitResult, error)
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error)
ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error) ContainerDiff(ctx context.Context, container string, options ContainerDiffOptions) (ContainerDiffResult, error)
ExecAPIClient ExecAPIClient
ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) ContainerExport(ctx context.Context, container string) (io.ReadCloser, error)
ContainerInspect(ctx context.Context, container string) (container.InspectResponse, error) ContainerInspect(ctx context.Context, container string) (container.InspectResponse, error)

View File

@@ -16,6 +16,11 @@ type ContainerAttachOptions struct {
Logs bool Logs bool
} }
// ContainerAttachResult is the result from attaching to a container.
type ContainerAttachResult struct {
HijackedResponse
}
// ContainerAttach attaches a connection to a container in the server. // ContainerAttach attaches a connection to a container in the server.
// It returns a [HijackedResponse] with the hijacked connection // It returns a [HijackedResponse] with the hijacked connection
// and a reader to get output. It's up to the called to close // and a reader to get output. It's up to the called to close
@@ -44,10 +49,10 @@ type ContainerAttachOptions struct {
// [stdcopy.StdType]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdType // [stdcopy.StdType]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdType
// [Stdout]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stdout // [Stdout]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stdout
// [Stderr]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stderr // [Stderr]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stderr
func (cli *Client) ContainerAttach(ctx context.Context, containerID string, options ContainerAttachOptions) (HijackedResponse, error) { func (cli *Client) ContainerAttach(ctx context.Context, containerID string, options ContainerAttachOptions) (ContainerAttachResult, error) {
containerID, err := trimID("container", containerID) containerID, err := trimID("container", containerID)
if err != nil { if err != nil {
return HijackedResponse{}, err return ContainerAttachResult{}, err
} }
query := url.Values{} query := url.Values{}
@@ -70,7 +75,12 @@ func (cli *Client) ContainerAttach(ctx context.Context, containerID string, opti
query.Set("logs", "1") query.Set("logs", "1")
} }
return cli.postHijacked(ctx, "/containers/"+containerID+"/attach", query, nil, http.Header{ hijacked, err := cli.postHijacked(ctx, "/containers/"+containerID+"/attach", query, nil, http.Header{
"Content-Type": {"text/plain"}, "Content-Type": {"text/plain"},
}) })
if err != nil {
return ContainerAttachResult{}, err
}
return ContainerAttachResult{HijackedResponse: hijacked}, nil
} }

View File

@@ -20,22 +20,27 @@ type ContainerCommitOptions struct {
Config *container.Config Config *container.Config
} }
// ContainerCommitResult is the result from committing a container.
type ContainerCommitResult struct {
ID string
}
// ContainerCommit applies changes to a container and creates a new tagged image. // ContainerCommit applies changes to a container and creates a new tagged image.
func (cli *Client) ContainerCommit(ctx context.Context, containerID string, options ContainerCommitOptions) (container.CommitResponse, error) { func (cli *Client) ContainerCommit(ctx context.Context, containerID string, options ContainerCommitOptions) (ContainerCommitResult, error) {
containerID, err := trimID("container", containerID) containerID, err := trimID("container", containerID)
if err != nil { if err != nil {
return container.CommitResponse{}, err return ContainerCommitResult{}, err
} }
var repository, tag string var repository, tag string
if options.Reference != "" { if options.Reference != "" {
ref, err := reference.ParseNormalizedNamed(options.Reference) ref, err := reference.ParseNormalizedNamed(options.Reference)
if err != nil { if err != nil {
return container.CommitResponse{}, err return ContainerCommitResult{}, err
} }
if _, ok := ref.(reference.Digested); ok { if _, ok := ref.(reference.Digested); ok {
return container.CommitResponse{}, errors.New("refusing to create a tag with a digest reference") return ContainerCommitResult{}, errors.New("refusing to create a tag with a digest reference")
} }
ref = reference.TagNameOnly(ref) ref = reference.TagNameOnly(ref)
@@ -62,9 +67,9 @@ func (cli *Client) ContainerCommit(ctx context.Context, containerID string, opti
resp, err := cli.post(ctx, "/commit", query, options.Config, nil) resp, err := cli.post(ctx, "/commit", query, options.Config, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return response, err return ContainerCommitResult{}, err
} }
err = json.NewDecoder(resp.Body).Decode(&response) err = json.NewDecoder(resp.Body).Decode(&response)
return response, err return ContainerCommitResult{ID: response.ID}, err
} }

View File

@@ -10,49 +10,48 @@ import (
cerrdefs "github.com/containerd/errdefs" cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
// ContainerCreate creates a new container based on the given configuration. // ContainerCreate creates a new container based on the given configuration.
// It can be associated with a name, but it's not mandatory. // It can be associated with a name, but it's not mandatory.
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) { func (cli *Client) ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error) {
if config == nil { if options.Config == nil {
return container.CreateResponse{}, cerrdefs.ErrInvalidArgument.WithMessage("config is nil") return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("config is nil")
} }
var response container.CreateResponse var response container.CreateResponse
if hostConfig != nil { if options.HostConfig != nil {
hostConfig.CapAdd = normalizeCapabilities(hostConfig.CapAdd) options.HostConfig.CapAdd = normalizeCapabilities(options.HostConfig.CapAdd)
hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop) options.HostConfig.CapDrop = normalizeCapabilities(options.HostConfig.CapDrop)
} }
query := url.Values{} query := url.Values{}
if platform != nil { if options.Platform != nil {
if p := formatPlatform(*platform); p != "unknown" { if p := formatPlatform(*options.Platform); p != "unknown" {
query.Set("platform", p) query.Set("platform", p)
} }
} }
if containerName != "" { if options.ContainerName != "" {
query.Set("name", containerName) query.Set("name", options.ContainerName)
} }
body := container.CreateRequest{ body := container.CreateRequest{
Config: config, Config: options.Config,
HostConfig: hostConfig, HostConfig: options.HostConfig,
NetworkingConfig: networkingConfig, NetworkingConfig: options.NetworkingConfig,
} }
resp, err := cli.post(ctx, "/containers/create", query, body, nil) resp, err := cli.post(ctx, "/containers/create", query, body, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return response, err return ContainerCreateResult{}, err
} }
err = json.NewDecoder(resp.Body).Decode(&response) err = json.NewDecoder(resp.Body).Decode(&response)
return response, err return ContainerCreateResult{ID: response.ID, Warnings: response.Warnings}, err
} }
// formatPlatform returns a formatted string representing platform (e.g., "linux/arm/v7"). // formatPlatform returns a formatted string representing platform (e.g., "linux/arm/v7").

View File

@@ -0,0 +1,22 @@
package client
import (
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ContainerCreateOptions holds parameters to create a container.
type ContainerCreateOptions struct {
Config *container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
Platform *ocispec.Platform
ContainerName string
}
// ContainerCreateResult is the result from creating a container.
type ContainerCreateResult struct {
ID string
Warnings []string
}

View File

@@ -22,11 +22,11 @@ func TestContainerCreateError(t *testing.T) {
) )
assert.NilError(t, err) assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), nil, nil, nil, nil, "nothing") _, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: nil, ContainerName: "nothing"})
assert.Error(t, err, "config is nil") assert.Error(t, err, "config is nil")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
_, err = client.ContainerCreate(context.Background(), &container.Config{}, nil, nil, nil, "nothing") _, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, ContainerName: "nothing"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
// 404 doesn't automatically means an unknown image // 404 doesn't automatically means an unknown image
@@ -35,7 +35,7 @@ func TestContainerCreateError(t *testing.T) {
) )
assert.NilError(t, err) assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), &container.Config{}, nil, nil, nil, "nothing") _, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, ContainerName: "nothing"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
} }
@@ -45,7 +45,7 @@ func TestContainerCreateImageNotFound(t *testing.T) {
) )
assert.NilError(t, err) assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), &container.Config{Image: "unknown_image"}, nil, nil, nil, "unknown") _, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "unknown_image"}, ContainerName: "unknown"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
} }
@@ -74,7 +74,7 @@ func TestContainerCreateWithName(t *testing.T) {
) )
assert.NilError(t, err) assert.NilError(t, err)
r, err := client.ContainerCreate(context.Background(), &container.Config{}, nil, nil, nil, "container_name") r, err := client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, ContainerName: "container_name"})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(r.ID, "container_id")) assert.Check(t, is.Equal(r.ID, "container_id"))
} }
@@ -103,7 +103,7 @@ func TestContainerCreateAutoRemove(t *testing.T) {
) )
assert.NilError(t, err) assert.NilError(t, err)
resp, err := client.ContainerCreate(context.Background(), &container.Config{}, &container.HostConfig{AutoRemove: true}, nil, nil, "") resp, err := client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, HostConfig: &container.HostConfig{AutoRemove: true}})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(resp.ID, "container_id")) assert.Check(t, is.Equal(resp.ID, "container_id"))
} }
@@ -116,7 +116,7 @@ func TestContainerCreateConnectionError(t *testing.T) {
client, err := NewClientWithOpts(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid")) client, err := NewClientWithOpts(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err) assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), &container.Config{}, nil, nil, nil, "") _, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}})
assert.Check(t, is.ErrorType(err, IsErrConnectionFailed)) assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
} }
@@ -165,6 +165,6 @@ func TestContainerCreateCapabilities(t *testing.T) {
) )
assert.NilError(t, err) assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), &container.Config{}, &container.HostConfig{CapAdd: inputCaps, CapDrop: inputCaps}, nil, nil, "") _, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, HostConfig: &container.HostConfig{CapAdd: inputCaps, CapDrop: inputCaps}})
assert.NilError(t, err) assert.NilError(t, err)
} }

View File

@@ -9,22 +9,22 @@ import (
) )
// ContainerDiff shows differences in a container filesystem since it was started. // ContainerDiff shows differences in a container filesystem since it was started.
func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]container.FilesystemChange, error) { func (cli *Client) ContainerDiff(ctx context.Context, containerID string, options ContainerDiffOptions) (ContainerDiffResult, error) {
containerID, err := trimID("container", containerID) containerID, err := trimID("container", containerID)
if err != nil { if err != nil {
return nil, err return ContainerDiffResult{}, err
} }
resp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil) resp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return nil, err return ContainerDiffResult{}, err
} }
var changes []container.FilesystemChange var changes []container.FilesystemChange
err = json.NewDecoder(resp.Body).Decode(&changes) err = json.NewDecoder(resp.Body).Decode(&changes)
if err != nil { if err != nil {
return nil, err return ContainerDiffResult{}, err
} }
return changes, err return ContainerDiffResult{Changes: changes}, err
} }

View File

@@ -0,0 +1,13 @@
package client
import "github.com/moby/moby/api/types/container"
// ContainerDiffOptions holds parameters to show differences in a container filesystem.
type ContainerDiffOptions struct {
// Currently no options, but this allows for future extensibility
}
// ContainerDiffResult is the result from showing differences in a container filesystem.
type ContainerDiffResult struct {
Changes []container.FilesystemChange
}

View File

@@ -20,14 +20,14 @@ func TestContainerDiffError(t *testing.T) {
) )
assert.NilError(t, err) assert.NilError(t, err)
_, err = client.ContainerDiff(context.Background(), "nothing") _, err = client.ContainerDiff(context.Background(), "nothing", ContainerDiffOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerDiff(context.Background(), "") _, err = client.ContainerDiff(context.Background(), "", ContainerDiffOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty")) assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerDiff(context.Background(), " ") _, err = client.ContainerDiff(context.Background(), " ", ContainerDiffOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty")) assert.Check(t, is.ErrorContains(err, "value is empty"))
} }
@@ -67,7 +67,7 @@ func TestContainerDiff(t *testing.T) {
) )
assert.NilError(t, err) assert.NilError(t, err)
changes, err := client.ContainerDiff(context.Background(), "container_id") result, err := client.ContainerDiff(context.Background(), "container_id", ContainerDiffOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.DeepEqual(changes, expected)) assert.Check(t, is.DeepEqual(result.Changes, expected))
} }

View File

@@ -130,12 +130,12 @@ func (s *DockerAPISuite) TestContainerAPIGetChanges(c *testing.T) {
assert.NilError(c, err) assert.NilError(c, err)
defer apiClient.Close() defer apiClient.Close()
changes, err := apiClient.ContainerDiff(testutil.GetContext(c), name) result, err := apiClient.ContainerDiff(testutil.GetContext(c), name, client.ContainerDiffOptions{})
assert.NilError(c, err) assert.NilError(c, err)
// Check the changelog for removal of /etc/passwd // Check the changelog for removal of /etc/passwd
success := false success := false
for _, elem := range changes { for _, elem := range result.Changes {
if elem.Path == "/etc/passwd" && elem.Kind == 2 { if elem.Path == "/etc/passwd" && elem.Kind == 2 {
success = true success = true
} }
@@ -517,7 +517,11 @@ func (s *DockerAPISuite) TestContainerAPIBadPort(c *testing.T) {
assert.NilError(c, err) assert.NilError(c, err)
defer apiClient.Close() defer apiClient.Close()
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") _, err = apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &hostConfig,
NetworkingConfig: &network.NetworkingConfig{},
})
assert.ErrorContains(c, err, `invalid port specification: "aa80"`) assert.ErrorContains(c, err, `invalid port specification: "aa80"`)
} }
@@ -531,7 +535,11 @@ func (s *DockerAPISuite) TestContainerAPICreate(c *testing.T) {
assert.NilError(c, err) assert.NilError(c, err)
defer apiClient.Close() defer apiClient.Close()
ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &container.HostConfig{},
NetworkingConfig: &network.NetworkingConfig{},
})
assert.NilError(c, err) assert.NilError(c, err)
out := cli.DockerCmd(c, "start", "-a", ctr.ID).Stdout() out := cli.DockerCmd(c, "start", "-a", ctr.ID).Stdout()
@@ -543,7 +551,11 @@ func (s *DockerAPISuite) TestContainerAPICreateEmptyConfig(c *testing.T) {
assert.NilError(c, err) assert.NilError(c, err)
defer apiClient.Close() defer apiClient.Close()
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &container.Config{}, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") _, err = apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &container.Config{},
HostConfig: &container.HostConfig{},
NetworkingConfig: &network.NetworkingConfig{},
})
assert.ErrorContains(c, err, "no command specified") assert.ErrorContains(c, err, "no command specified")
} }
@@ -574,7 +586,11 @@ func UtilCreateNetworkMode(t *testing.T, networkMode container.NetworkMode) {
assert.NilError(t, err) assert.NilError(t, err)
defer apiClient.Close() defer apiClient.Close()
ctr, err := apiClient.ContainerCreate(testutil.GetContext(t), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") ctr, err := apiClient.ContainerCreate(testutil.GetContext(t), client.ContainerCreateOptions{
Config: &config,
HostConfig: &hostConfig,
NetworkingConfig: &network.NetworkingConfig{},
})
assert.NilError(t, err) assert.NilError(t, err)
containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(t), ctr.ID) containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(t), ctr.ID)
@@ -601,7 +617,11 @@ func (s *DockerAPISuite) TestContainerAPICreateWithCpuSharesCpuset(c *testing.T)
assert.NilError(c, err) assert.NilError(c, err)
defer apiClient.Close() defer apiClient.Close()
ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &hostConfig,
NetworkingConfig: &network.NetworkingConfig{},
})
assert.NilError(c, err) assert.NilError(c, err)
containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID) containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
@@ -746,7 +766,12 @@ func (s *DockerAPISuite) TestContainerAPIStart(c *testing.T) {
assert.NilError(c, err) assert.NilError(c, err)
defer apiClient.Close() defer apiClient.Close()
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name) _, err = apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &container.HostConfig{},
NetworkingConfig: &network.NetworkingConfig{},
ContainerName: name,
})
assert.NilError(c, err) assert.NilError(c, err)
err = apiClient.ContainerStart(testutil.GetContext(c), name, client.ContainerStartOptions{}) err = apiClient.ContainerStart(testutil.GetContext(c), name, client.ContainerStartOptions{})
@@ -989,7 +1014,12 @@ func (s *DockerAPISuite) TestPostContainersCreateWithWrongCpusetValues(c *testin
} }
const name = "wrong-cpuset-cpus" const name = "wrong-cpuset-cpus"
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig1, &network.NetworkingConfig{}, nil, name) _, err = apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &hostConfig1,
NetworkingConfig: &network.NetworkingConfig{},
ContainerName: name,
})
expected := "Invalid value 1-42,, for cpuset cpus" expected := "Invalid value 1-42,, for cpuset cpus"
assert.ErrorContains(c, err, expected) assert.ErrorContains(c, err, expected)
@@ -999,7 +1029,12 @@ func (s *DockerAPISuite) TestPostContainersCreateWithWrongCpusetValues(c *testin
}, },
} }
const name2 = "wrong-cpuset-mems" const name2 = "wrong-cpuset-mems"
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig2, &network.NetworkingConfig{}, nil, name2) _, err = apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &hostConfig2,
NetworkingConfig: &network.NetworkingConfig{},
ContainerName: name2,
})
expected = "Invalid value 42-3,1-- for cpuset mems" expected = "Invalid value 42-3,1-- for cpuset mems"
assert.ErrorContains(c, err, expected) assert.ErrorContains(c, err, expected)
} }
@@ -1015,7 +1050,11 @@ func (s *DockerAPISuite) TestPostContainersCreateMemorySwappinessHostConfigOmitt
assert.NilError(c, err) assert.NilError(c, err)
defer apiClient.Close() defer apiClient.Close()
ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &container.HostConfig{},
NetworkingConfig: &network.NetworkingConfig{},
})
assert.NilError(c, err) assert.NilError(c, err)
containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID) containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
@@ -1042,7 +1081,12 @@ func (s *DockerAPISuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *
defer apiClient.Close() defer apiClient.Close()
const name = "oomscoreadj-over" const name = "oomscoreadj-over"
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, name) _, err = apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &hostConfig,
NetworkingConfig: &network.NetworkingConfig{},
ContainerName: name,
})
expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]" expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]"
assert.ErrorContains(c, err, expected) assert.ErrorContains(c, err, expected)
@@ -1052,7 +1096,12 @@ func (s *DockerAPISuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *
} }
const name2 = "oomscoreadj-low" const name2 = "oomscoreadj-low"
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, name2) _, err = apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &hostConfig,
NetworkingConfig: &network.NetworkingConfig{},
ContainerName: name2,
})
expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]" expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]"
assert.ErrorContains(c, err, expected) assert.ErrorContains(c, err, expected)
@@ -1085,7 +1134,12 @@ func (s *DockerAPISuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T)
assert.NilError(c, err) assert.NilError(c, err)
defer apiClient.Close() defer apiClient.Close()
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name) _, err = apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &container.HostConfig{},
NetworkingConfig: &network.NetworkingConfig{},
ContainerName: name,
})
assert.NilError(c, err) assert.NilError(c, err)
err = apiClient.ContainerStart(testutil.GetContext(c), name, client.ContainerStartOptions{}) err = apiClient.ContainerStart(testutil.GetContext(c), name, client.ContainerStartOptions{})
@@ -1421,7 +1475,11 @@ func (s *DockerAPISuite) TestContainersAPICreateMountsValidation(c *testing.T) {
// TODO add checks for statuscode returned by API // TODO add checks for statuscode returned by API
for i, tc := range tests { for i, tc := range tests {
c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) { c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) {
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &tc.config, &tc.hostConfig, &network.NetworkingConfig{}, nil, "") _, err = apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &tc.config,
HostConfig: &tc.hostConfig,
NetworkingConfig: &network.NetworkingConfig{},
})
if tc.msg != "" { if tc.msg != "" {
assert.ErrorContains(c, err, tc.msg, "%v", tests[i].config) assert.ErrorContains(c, err, tc.msg, "%v", tests[i].config)
} else { } else {
@@ -1454,7 +1512,12 @@ func (s *DockerAPISuite) TestContainerAPICreateMountsBindRead(c *testing.T) {
assert.NilError(c, err) assert.NilError(c, err)
defer apiClient.Close() defer apiClient.Close()
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "test") _, err = apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &hostConfig,
NetworkingConfig: &network.NetworkingConfig{},
ContainerName: "test",
})
assert.NilError(c, err) assert.NilError(c, err)
out := cli.DockerCmd(c, "start", "-a", "test").Combined() out := cli.DockerCmd(c, "start", "-a", "test").Combined()
@@ -1590,11 +1653,11 @@ func (s *DockerAPISuite) TestContainersAPICreateMountsCreate(c *testing.T) {
c.Run(fmt.Sprintf("%d config: %v", i, tc.spec), func(c *testing.T) { c.Run(fmt.Sprintf("%d config: %v", i, tc.spec), func(c *testing.T) {
ctr, err := apiclient.ContainerCreate( ctr, err := apiclient.ContainerCreate(
ctx, ctx,
&container.Config{Image: testImg}, client.ContainerCreateOptions{
&container.HostConfig{Mounts: []mount.Mount{tc.spec}}, Config: &container.Config{Image: testImg},
&network.NetworkingConfig{}, HostConfig: &container.HostConfig{Mounts: []mount.Mount{tc.spec}},
nil, NetworkingConfig: &network.NetworkingConfig{},
"") })
assert.NilError(c, err) assert.NilError(c, err)
containerInspect, err := apiclient.ContainerInspect(ctx, ctr.ID) containerInspect, err := apiclient.ContainerInspect(ctx, ctr.ID)
@@ -1705,7 +1768,12 @@ func (s *DockerAPISuite) TestContainersAPICreateMountsTmpfs(c *testing.T) {
Mounts: []mount.Mount{x.cfg}, Mounts: []mount.Mount{x.cfg},
} }
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, cName) _, err = apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &hostConfig,
NetworkingConfig: &network.NetworkingConfig{},
ContainerName: cName,
})
assert.NilError(c, err) assert.NilError(c, err)
out := cli.DockerCmd(c, "start", "-a", cName).Combined() out := cli.DockerCmd(c, "start", "-a", cName).Combined()
for _, option := range x.expectedOptions { for _, option := range x.expectedOptions {

View File

@@ -12,6 +12,7 @@ import (
"github.com/Microsoft/go-winio" "github.com/Microsoft/go-winio"
"github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/mount" "github.com/moby/moby/api/types/mount"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client" "github.com/moby/moby/client"
"github.com/moby/moby/v2/internal/testutil" "github.com/moby/moby/v2/internal/testutil"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -51,19 +52,23 @@ func (s *DockerAPISuite) TestContainersAPICreateMountsBindNamedPipe(c *testing.T
ctx := testutil.GetContext(c) ctx := testutil.GetContext(c)
apiClient := testEnv.APIClient() apiClient := testEnv.APIClient()
_, err = apiClient.ContainerCreate(ctx, _, err = apiClient.ContainerCreate(ctx,
&container.Config{ client.ContainerCreateOptions{
Image: testEnv.PlatformDefaults.BaseImage, Config: &container.Config{
Cmd: []string{"cmd", "/c", cmd}, Image: testEnv.PlatformDefaults.BaseImage,
}, &container.HostConfig{ Cmd: []string{"cmd", "/c", cmd},
Mounts: []mount.Mount{ },
{ HostConfig: &container.HostConfig{
Type: "npipe", Mounts: []mount.Mount{
Source: hostPipeName, {
Target: containerPipeName, Type: "npipe",
Source: hostPipeName,
Target: containerPipeName,
},
}, },
}, },
}, NetworkingConfig: &network.NetworkingConfig{},
nil, nil, name) ContainerName: name,
})
assert.NilError(c, err) assert.NilError(c, err)
err = apiClient.ContainerStart(ctx, name, client.ContainerStartOptions{}) err = apiClient.ContainerStart(ctx, name, client.ContainerStartOptions{})

View File

@@ -596,7 +596,12 @@ func (s *DockerCLIVolumeSuite) TestDuplicateMountpointsForVolumesFromAndMounts(c
}, },
}, },
} }
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "app") _, err = apiClient.ContainerCreate(testutil.GetContext(c), client.ContainerCreateOptions{
Config: &config,
HostConfig: &hostConfig,
NetworkingConfig: &network.NetworkingConfig{},
ContainerName: "app",
})
assert.NilError(c, err) assert.NilError(c, err)

View File

@@ -41,17 +41,15 @@ func TestAttach(t *testing.T) {
t.Parallel() t.Parallel()
ctx := testutil.StartSpan(ctx, t) ctx := testutil.StartSpan(ctx, t)
resp, err := apiClient.ContainerCreate(ctx, resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
&container.Config{ Config: &container.Config{
Image: "busybox", Image: "busybox",
Cmd: []string{"echo", "hello"}, Cmd: []string{"echo", "hello"},
Tty: tc.tty, Tty: tc.tty,
}, },
&container.HostConfig{}, HostConfig: &container.HostConfig{},
&network.NetworkingConfig{}, NetworkingConfig: &network.NetworkingConfig{},
nil, })
"",
)
assert.NilError(t, err) assert.NilError(t, err)
attach, err := apiClient.ContainerAttach(ctx, resp.ID, client.ContainerAttachOptions{ attach, err := apiClient.ContainerAttach(ctx, resp.ID, client.ContainerAttachOptions{
Stdout: true, Stdout: true,
@@ -81,16 +79,14 @@ func TestAttachDisconnectLeak(t *testing.T) {
apiClient := d.NewClientT(t) apiClient := d.NewClientT(t)
resp, err := apiClient.ContainerCreate(ctx, resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
&container.Config{ Config: &container.Config{
Image: "busybox", Image: "busybox",
Cmd: []string{"/bin/sh", "-c", "while true; usleep 100000; done"}, Cmd: []string{"/bin/sh", "-c", "while true; usleep 100000; done"},
}, },
&container.HostConfig{}, HostConfig: &container.HostConfig{},
&network.NetworkingConfig{}, NetworkingConfig: &network.NetworkingConfig{},
nil, })
"",
)
assert.NilError(t, err) assert.NilError(t, err)
cID := resp.ID cID := resp.ID
defer apiClient.ContainerRemove(ctx, cID, client.ContainerRemoveOptions{ defer apiClient.ContainerRemove(ctx, cID, client.ContainerRemoveOptions{

View File

@@ -59,13 +59,11 @@ func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) {
t.Run(tc.doc, func(t *testing.T) { t.Run(tc.doc, func(t *testing.T) {
t.Parallel() t.Parallel()
ctx := testutil.StartSpan(ctx, t) ctx := testutil.StartSpan(ctx, t)
_, err := apiClient.ContainerCreate(ctx, _, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
&container.Config{Image: tc.image}, Config: &container.Config{Image: tc.image},
&container.HostConfig{}, HostConfig: &container.HostConfig{},
&network.NetworkingConfig{}, NetworkingConfig: &network.NetworkingConfig{},
nil, })
"",
)
assert.Check(t, is.ErrorContains(err, tc.expectedError)) assert.Check(t, is.ErrorContains(err, tc.expectedError))
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}) })
@@ -125,15 +123,11 @@ func TestCreateByImageID(t *testing.T) {
t.Run(tc.doc, func(t *testing.T) { t.Run(tc.doc, func(t *testing.T) {
t.Parallel() t.Parallel()
ctx := testutil.StartSpan(ctx, t) ctx := testutil.StartSpan(ctx, t)
resp, err := apiClient.ContainerCreate(ctx, resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
&container.Config{Image: tc.image}, Config: &container.Config{Image: tc.image},
&container.HostConfig{}, })
&network.NetworkingConfig{},
nil,
"",
)
if tc.expectedErr != "" { if tc.expectedErr != "" {
assert.Check(t, is.DeepEqual(resp, container.CreateResponse{})) assert.Check(t, is.DeepEqual(resp, client.ContainerCreateResult{}))
assert.Check(t, is.Error(err, tc.expectedErr)) assert.Check(t, is.Error(err, tc.expectedErr))
assert.Check(t, is.ErrorType(err, tc.expectedErrType)) assert.Check(t, is.ErrorType(err, tc.expectedErrType))
} else { } else {
@@ -154,17 +148,14 @@ func TestCreateLinkToNonExistingContainer(t *testing.T) {
ctx := setupTest(t) ctx := setupTest(t)
apiClient := testEnv.APIClient() apiClient := testEnv.APIClient()
_, err := apiClient.ContainerCreate(ctx, _, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
&container.Config{ Config: &container.Config{
Image: "busybox", Image: "busybox",
}, },
&container.HostConfig{ HostConfig: &container.HostConfig{
Links: []string{"no-such-container"}, Links: []string{"no-such-container"},
}, },
&network.NetworkingConfig{}, })
nil,
"",
)
assert.Check(t, is.ErrorContains(err, "could not get container for no-such-container")) assert.Check(t, is.ErrorContains(err, "could not get container for no-such-container"))
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
} }
@@ -195,16 +186,12 @@ func TestCreateWithInvalidEnv(t *testing.T) {
t.Run(strconv.Itoa(index), func(t *testing.T) { t.Run(strconv.Itoa(index), func(t *testing.T) {
t.Parallel() t.Parallel()
ctx := testutil.StartSpan(ctx, t) ctx := testutil.StartSpan(ctx, t)
_, err := apiClient.ContainerCreate(ctx, _, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
&container.Config{ Config: &container.Config{
Image: "busybox", Image: "busybox",
Env: []string{tc.env}, Env: []string{tc.env},
}, },
&container.HostConfig{}, })
&network.NetworkingConfig{},
nil,
"",
)
assert.Check(t, is.ErrorContains(err, tc.expectedError)) assert.Check(t, is.ErrorContains(err, tc.expectedError))
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
}) })
@@ -241,17 +228,14 @@ func TestCreateTmpfsMountsTarget(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
_, err := apiClient.ContainerCreate(ctx, _, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
&container.Config{ Config: &container.Config{
Image: "busybox", Image: "busybox",
}, },
&container.HostConfig{ HostConfig: &container.HostConfig{
Tmpfs: map[string]string{tc.target: ""}, Tmpfs: map[string]string{tc.target: ""},
}, },
&network.NetworkingConfig{}, })
nil,
"",
)
assert.Check(t, is.ErrorContains(err, tc.expectedError)) assert.Check(t, is.ErrorContains(err, tc.expectedError))
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
} }
@@ -298,19 +282,17 @@ func TestCreateWithCustomMaskedPaths(t *testing.T) {
t.Parallel() t.Parallel()
// Create the container. // Create the container.
ctr, err := apiClient.ContainerCreate(ctx, ctr, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
&container.Config{ Config: &container.Config{
Image: "busybox", Image: "busybox",
Cmd: []string{"true"}, Cmd: []string{"true"},
}, },
&container.HostConfig{ HostConfig: &container.HostConfig{
Privileged: tc.privileged, Privileged: tc.privileged,
MaskedPaths: tc.maskedPaths, MaskedPaths: tc.maskedPaths,
}, },
nil, ContainerName: fmt.Sprintf("create-masked-paths-%d", i),
nil, })
fmt.Sprintf("create-masked-paths-%d", i),
)
assert.NilError(t, err) assert.NilError(t, err)
ctrInspect, err := apiClient.ContainerInspect(ctx, ctr.ID) ctrInspect, err := apiClient.ContainerInspect(ctx, ctr.ID)
@@ -371,19 +353,17 @@ func TestCreateWithCustomReadonlyPaths(t *testing.T) {
for i, tc := range testCases { for i, tc := range testCases {
t.Run(tc.doc, func(t *testing.T) { t.Run(tc.doc, func(t *testing.T) {
t.Parallel() t.Parallel()
ctr, err := apiClient.ContainerCreate(ctx, ctr, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
&container.Config{ Config: &container.Config{
Image: "busybox", Image: "busybox",
Cmd: []string{"true"}, Cmd: []string{"true"},
}, },
&container.HostConfig{ HostConfig: &container.HostConfig{
Privileged: tc.privileged, Privileged: tc.privileged,
ReadonlyPaths: tc.readonlyPaths, ReadonlyPaths: tc.readonlyPaths,
}, },
nil, ContainerName: fmt.Sprintf("create-readonly-paths-%d", i),
nil, })
fmt.Sprintf("create-readonly-paths-%d", i),
)
assert.NilError(t, err) assert.NilError(t, err)
ctrInspect, err := apiClient.ContainerInspect(ctx, ctr.ID) ctrInspect, err := apiClient.ContainerInspect(ctx, ctr.ID)
@@ -482,7 +462,9 @@ func TestCreateWithInvalidHealthcheckParams(t *testing.T) {
cfg.Healthcheck.StartPeriod = tc.startPeriod cfg.Healthcheck.StartPeriod = tc.startPeriod
} }
resp, err := apiClient.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, nil, "") resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &cfg,
})
assert.Check(t, is.Equal(len(resp.Warnings), 0)) assert.Check(t, is.Equal(len(resp.Warnings), 0))
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.ErrorContains(t, err, tc.expectedErr) assert.ErrorContains(t, err, tc.expectedErr)
@@ -553,7 +535,10 @@ func TestCreateDifferentPlatform(t *testing.T) {
Architecture: img.Architecture, Architecture: img.Architecture,
Variant: img.Variant, Variant: img.Variant,
} }
_, err := apiClient.ContainerCreate(ctx, &container.Config{Image: "busybox:latest"}, &container.HostConfig{}, nil, &p, "") _, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &container.Config{Image: "busybox:latest"},
Platform: &p,
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}) })
t.Run("different cpu arch", func(t *testing.T) { t.Run("different cpu arch", func(t *testing.T) {
@@ -563,7 +548,10 @@ func TestCreateDifferentPlatform(t *testing.T) {
Architecture: img.Architecture + "DifferentArch", Architecture: img.Architecture + "DifferentArch",
Variant: img.Variant, Variant: img.Variant,
} }
_, err := apiClient.ContainerCreate(ctx, &container.Config{Image: "busybox:latest"}, &container.HostConfig{}, nil, &p, "") _, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &container.Config{Image: "busybox:latest"},
Platform: &p,
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}) })
} }
@@ -574,12 +562,10 @@ func TestCreateVolumesFromNonExistingContainer(t *testing.T) {
_, err := apiClient.ContainerCreate( _, err := apiClient.ContainerCreate(
ctx, ctx,
&container.Config{Image: "busybox"}, client.ContainerCreateOptions{
&container.HostConfig{VolumesFrom: []string{"nosuchcontainer"}}, Config: &container.Config{Image: "busybox"},
nil, HostConfig: &container.HostConfig{VolumesFrom: []string{"nosuchcontainer"}},
nil, })
"",
)
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
} }
@@ -594,12 +580,9 @@ func TestCreatePlatformSpecificImageNoPlatform(t *testing.T) {
_, err := apiClient.ContainerCreate( _, err := apiClient.ContainerCreate(
ctx, ctx,
&container.Config{Image: "arm32v7/hello-world"}, client.ContainerCreateOptions{
&container.HostConfig{}, Config: &container.Config{Image: "arm32v7/hello-world"},
nil, })
nil,
"",
)
assert.NilError(t, err) assert.NilError(t, err)
} }
@@ -653,7 +636,10 @@ func TestCreateInvalidHostConfig(t *testing.T) {
cfg := container.Config{ cfg := container.Config{
Image: "busybox", Image: "busybox",
} }
resp, err := apiClient.ContainerCreate(ctx, &cfg, &tc.hc, nil, nil, "") resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &cfg,
HostConfig: &tc.hc,
})
assert.Check(t, is.Equal(len(resp.Warnings), 0)) assert.Check(t, is.Equal(len(resp.Warnings), 0))
assert.Check(t, cerrdefs.IsInvalidArgument(err), "got: %T", err) assert.Check(t, cerrdefs.IsInvalidArgument(err), "got: %T", err)
assert.Error(t, err, tc.expectedErr) assert.Error(t, err, tc.expectedErr)
@@ -760,7 +746,10 @@ func TestCreateWithMultipleEndpointSettings(t *testing.T) {
"net3": {}, "net3": {},
}, },
} }
_, err = apiClient.ContainerCreate(ctx, &config, &container.HostConfig{}, &networkingConfig, nil, "") _, err = apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &config,
NetworkingConfig: &networkingConfig,
})
if tc.expectedErr == "" { if tc.expectedErr == "" {
assert.NilError(t, err) assert.NilError(t, err)
} else { } else {

View File

@@ -5,6 +5,7 @@ import (
"time" "time"
containertypes "github.com/moby/moby/api/types/container" containertypes "github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/moby/moby/v2/integration/internal/container" "github.com/moby/moby/v2/integration/internal/container"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
"gotest.tools/v3/poll" "gotest.tools/v3/poll"
@@ -24,9 +25,9 @@ func TestDiff(t *testing.T) {
} }
poll.WaitOn(t, container.IsStopped(ctx, apiClient, cID)) poll.WaitOn(t, container.IsStopped(ctx, apiClient, cID))
items, err := apiClient.ContainerDiff(ctx, cID) result, err := apiClient.ContainerDiff(ctx, cID, client.ContainerDiffOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.DeepEqual(t, expected, items) assert.DeepEqual(t, expected, result.Changes)
} }
func TestDiffStoppedContainer(t *testing.T) { func TestDiffStoppedContainer(t *testing.T) {
@@ -51,7 +52,7 @@ func TestDiffStoppedContainer(t *testing.T) {
} }
} }
items, err := apiClient.ContainerDiff(ctx, cID) result, err := apiClient.ContainerDiff(ctx, cID, client.ContainerDiffOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.DeepEqual(t, expected, items) assert.DeepEqual(t, expected, result.Changes)
} }

View File

@@ -64,7 +64,10 @@ func testIpcNonePrivateShareable(t *testing.T, mode string, mustBeMounted bool,
} }
apiClient := testEnv.APIClient() apiClient := testEnv.APIClient()
resp, err := apiClient.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "") resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &cfg,
HostConfig: &hostCfg,
})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(len(resp.Warnings), 0)) assert.Check(t, is.Equal(len(resp.Warnings), 0))
@@ -135,7 +138,10 @@ func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
apiClient := testEnv.APIClient() apiClient := testEnv.APIClient()
// create and start the "donor" container // create and start the "donor" container
resp, err := apiClient.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "") resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &cfg,
HostConfig: &hostCfg,
})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(len(resp.Warnings), 0)) assert.Check(t, is.Equal(len(resp.Warnings), 0))
name1 := resp.ID name1 := resp.ID
@@ -145,7 +151,10 @@ func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
// create and start the second container // create and start the second container
hostCfg.IpcMode = containertypes.IpcMode("container:" + name1) hostCfg.IpcMode = containertypes.IpcMode("container:" + name1)
resp, err = apiClient.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "") resp, err = apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &cfg,
HostConfig: &hostCfg,
})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(len(resp.Warnings), 0)) assert.Check(t, is.Equal(len(resp.Warnings), 0))
name2 := resp.ID name2 := resp.ID
@@ -201,7 +210,10 @@ func TestAPIIpcModeHost(t *testing.T) {
ctx := testutil.StartSpan(baseContext, t) ctx := testutil.StartSpan(baseContext, t)
apiClient := testEnv.APIClient() apiClient := testEnv.APIClient()
resp, err := apiClient.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "") resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &cfg,
HostConfig: &hostCfg,
})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(len(resp.Warnings), 0)) assert.Check(t, is.Equal(len(resp.Warnings), 0))
name := resp.ID name := resp.ID
@@ -237,7 +249,10 @@ func testDaemonIpcPrivateShareable(t *testing.T, mustBeShared bool, arg ...strin
Cmd: []string{"top"}, Cmd: []string{"top"},
} }
resp, err := c.ContainerCreate(ctx, &cfg, &containertypes.HostConfig{}, nil, nil, "") resp, err := c.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &cfg,
HostConfig: &containertypes.HostConfig{},
})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(len(resp.Warnings), 0)) assert.Check(t, is.Equal(len(resp.Warnings), 0))

View File

@@ -67,7 +67,11 @@ func TestContainerNetworkMountsNoChown(t *testing.T) {
assert.NilError(t, err) assert.NilError(t, err)
defer cli.Close() defer cli.Close()
ctrCreate, err := cli.ContainerCreate(ctx, &config, &hostConfig, &network.NetworkingConfig{}, nil, "") ctrCreate, err := cli.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &config,
HostConfig: &hostConfig,
NetworkingConfig: &network.NetworkingConfig{},
})
assert.NilError(t, err) assert.NilError(t, err)
// container will exit immediately because of no tty, but we only need the start sequence to test the condition // container will exit immediately because of no tty, but we only need the start sequence to test the condition
err = cli.ContainerStart(ctx, ctrCreate.ID, client.ContainerStartOptions{}) err = cli.ContainerStart(ctx, ctrCreate.ID, client.ContainerStartOptions{})
@@ -179,10 +183,13 @@ func TestMountDaemonRoot(t *testing.T) {
ctx := testutil.StartSpan(ctx, t) ctx := testutil.StartSpan(ctx, t)
c, err := apiClient.ContainerCreate(ctx, &containertypes.Config{ c, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Image: "busybox", Config: &containertypes.Config{
Cmd: []string{"true"}, Image: "busybox",
}, hc, nil, nil, "") Cmd: []string{"true"},
},
HostConfig: hc,
})
if err != nil { if err != nil {
if test.expected != "" { if test.expected != "" {
t.Fatal(err) t.Fatal(err)
@@ -430,7 +437,13 @@ func TestContainerVolumeAnonymous(t *testing.T) {
}, },
}, },
})) }))
_, err := apiClient.ContainerCreate(ctx, config.Config, config.HostConfig, config.NetworkingConfig, config.Platform, config.Name) _, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: config.Config,
HostConfig: config.HostConfig,
NetworkingConfig: config.NetworkingConfig,
Platform: config.Platform,
ContainerName: config.Name,
})
// We use [testNonExistingPlugin] for this, which produces an error // We use [testNonExistingPlugin] for this, which produces an error
// when used, which we use as indicator that the driver was passed // when used, which we use as indicator that the driver was passed
// through. We should have a cleaner way for this, but that would // through. We should have a cleaner way for this, but that would

View File

@@ -28,7 +28,7 @@ func TestNoOverlayfsWarningsAboutUndefinedBehaviors(t *testing.T) {
operation func(t *testing.T) error operation func(t *testing.T) error
}{ }{
{name: "diff", operation: func(*testing.T) error { {name: "diff", operation: func(*testing.T) error {
_, err := apiClient.ContainerDiff(ctx, cID) _, err := apiClient.ContainerDiff(ctx, cID, client.ContainerDiffOptions{})
return err return err
}}, }},
{name: "export", operation: func(*testing.T) error { {name: "export", operation: func(*testing.T) error {

View File

@@ -100,7 +100,10 @@ func TestDaemonRestartKillContainers(t *testing.T) {
Interval: 60 * time.Second, Interval: 60 * time.Second,
} }
} }
resp, err := apiClient.ContainerCreate(ctx, &config, &hostConfig, nil, nil, "") resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &config,
HostConfig: &hostConfig,
})
assert.NilError(t, err) assert.NilError(t, err)
defer apiClient.ContainerRemove(ctx, resp.ID, client.ContainerRemoveOptions{Force: true}) defer apiClient.ContainerRemove(ctx, resp.ID, client.ContainerRemoveOptions{Force: true})

View File

@@ -55,10 +55,13 @@ func TestGraphDriverPersistence(t *testing.T) {
assert.Check(t, info.DriverStatus[0][1] != "io.containerd.snapshotter.v1") assert.Check(t, info.DriverStatus[0][1] != "io.containerd.snapshotter.v1")
prevDriver := info.Driver prevDriver := info.Driver
containerResp, err := c.ContainerCreate(ctx, &containertypes.Config{ containerResp, err := c.ContainerCreate(ctx, client.ContainerCreateOptions{
Image: testImage, Config: &containertypes.Config{
Cmd: []string{"echo", "test"}, Image: testImage,
}, nil, nil, nil, "test-container") Cmd: []string{"echo", "test"},
},
ContainerName: "test-container",
})
assert.NilError(t, err, "Failed to create container") assert.NilError(t, err, "Failed to create container")
containerID := containerResp.ID containerID := containerResp.ID
@@ -141,7 +144,7 @@ func TestInspectGraphDriverAPIBC(t *testing.T) {
} }
const testImage = "busybox:latest" const testImage = "busybox:latest"
ctr, err := c.ContainerCreate(ctx, &containertypes.Config{Image: testImage}, nil, nil, nil, "test-container") ctr, err := c.ContainerCreate(ctx, client.ContainerCreateOptions{Image: testImage, Name: "test-container"})
assert.NilError(t, err) assert.NilError(t, err)
defer func() { _ = c.ContainerRemove(ctx, ctr.ID, client.ContainerRemoveOptions{Force: true}) }() defer func() { _ = c.ContainerRemove(ctx, ctr.ID, client.ContainerRemoveOptions{Force: true}) }()

View File

@@ -55,7 +55,13 @@ func NewTestConfig(ops ...func(*TestContainerConfig)) *TestContainerConfig {
func Create(ctx context.Context, t *testing.T, apiClient client.APIClient, ops ...func(*TestContainerConfig)) string { func Create(ctx context.Context, t *testing.T, apiClient client.APIClient, ops ...func(*TestContainerConfig)) string {
t.Helper() t.Helper()
config := NewTestConfig(ops...) config := NewTestConfig(ops...)
c, err := apiClient.ContainerCreate(ctx, config.Config, config.HostConfig, config.NetworkingConfig, config.Platform, config.Name) c, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: config.Config,
HostConfig: config.HostConfig,
NetworkingConfig: config.NetworkingConfig,
Platform: config.Platform,
ContainerName: config.Name,
})
assert.NilError(t, err) assert.NilError(t, err)
return c.ID return c.ID
@@ -67,8 +73,14 @@ func Create(ctx context.Context, t *testing.T, apiClient client.APIClient, ops .
// //
// ctr, err := container.CreateFromConfig(ctx, apiClient, container.NewTestConfig(container.WithAutoRemove)) // ctr, err := container.CreateFromConfig(ctx, apiClient, container.NewTestConfig(container.WithAutoRemove))
// assert.Check(t, err) // assert.Check(t, err)
func CreateFromConfig(ctx context.Context, apiClient client.APIClient, config *TestContainerConfig) (container.CreateResponse, error) { func CreateFromConfig(ctx context.Context, apiClient client.APIClient, config *TestContainerConfig) (client.ContainerCreateResult, error) {
return apiClient.ContainerCreate(ctx, config.Config, config.HostConfig, config.NetworkingConfig, config.Platform, config.Name) return apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: config.Config,
HostConfig: config.HostConfig,
NetworkingConfig: config.NetworkingConfig,
Platform: config.Platform,
ContainerName: config.Name,
})
} }
// Run creates and start a container with the specified options // Run creates and start a container with the specified options
@@ -108,7 +120,7 @@ func RunAttach(ctx context.Context, t *testing.T, apiClient client.APIClient, op
err = apiClient.ContainerStart(ctx, id, client.ContainerStartOptions{}) err = apiClient.ContainerStart(ctx, id, client.ContainerStartOptions{})
assert.NilError(t, err) assert.NilError(t, err)
s, err := demultiplexStreams(ctx, aresp) s, err := demultiplexStreams(ctx, aresp.HijackedResponse)
if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) { if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) {
assert.NilError(t, err) assert.NilError(t, err)
} }

View File

@@ -963,7 +963,13 @@ func TestEmptyPortBindingsBC(t *testing.T) {
config := ctr.NewTestConfig(ctr.WithCmd("top"), config := ctr.NewTestConfig(ctr.WithCmd("top"),
ctr.WithExposedPorts("80/tcp"), ctr.WithExposedPorts("80/tcp"),
ctr.WithPortMap(networktypes.PortMap{networktypes.MustParsePort("80/tcp"): pbs})) ctr.WithPortMap(networktypes.PortMap{networktypes.MustParsePort("80/tcp"): pbs}))
c, err := apiClient.ContainerCreate(ctx, config.Config, config.HostConfig, config.NetworkingConfig, config.Platform, config.Name) c, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: config.Config,
HostConfig: config.HostConfig,
NetworkingConfig: config.NetworkingConfig,
Platform: config.Platform,
ContainerName: config.Name,
})
assert.NilError(t, err) assert.NilError(t, err)
defer apiClient.ContainerRemove(ctx, c.ID, client.ContainerRemoveOptions{Force: true}) defer apiClient.ContainerRemove(ctx, c.ID, client.ContainerRemoveOptions{Force: true})

View File

@@ -54,13 +54,12 @@ func TestReadPluginNoRead(t *testing.T) {
ctx := testutil.StartSpan(ctx, t) ctx := testutil.StartSpan(ctx, t)
d.Start(t, append([]string{"--iptables=false", "--ip6tables=false"}, test.dOpts...)...) d.Start(t, append([]string{"--iptables=false", "--ip6tables=false"}, test.dOpts...)...)
defer d.Stop(t) defer d.Stop(t)
c, err := apiclient.ContainerCreate(ctx, c, err := apiclient.ContainerCreate(ctx, client.ContainerCreateOptions{
cfg, Config: cfg,
&container.HostConfig{LogConfig: container.LogConfig{Type: "test"}}, HostConfig: &container.HostConfig{
nil, LogConfig: container.LogConfig{Type: "test"},
nil, },
"", })
)
assert.Assert(t, err) assert.Assert(t, err)
defer apiclient.ContainerRemove(ctx, c.ID, client.ContainerRemoveOptions{Force: true}) defer apiclient.ContainerRemove(ctx, c.ID, client.ContainerRemoveOptions{Force: true})

View File

@@ -80,7 +80,12 @@ func TestRunMountVolumeSubdir(t *testing.T) {
} }
ctrName := strings.ReplaceAll(t.Name(), "/", "_") ctrName := strings.ReplaceAll(t.Name(), "/", "_")
create, creatErr := apiClient.ContainerCreate(ctx, &cfg, &hostCfg, &network.NetworkingConfig{}, nil, ctrName) create, creatErr := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &cfg,
HostConfig: &hostCfg,
NetworkingConfig: &network.NetworkingConfig{},
ContainerName: ctrName,
})
id := create.ID id := create.ID
if id != "" { if id != "" {
defer apiClient.ContainerRemove(ctx, id, client.ContainerRemoveOptions{Force: true}) defer apiClient.ContainerRemove(ctx, id, client.ContainerRemoveOptions{Force: true})
@@ -175,7 +180,12 @@ func TestRunMountImage(t *testing.T) {
} }
ctrName := strings.ReplaceAll(t.Name(), "/", "_") ctrName := strings.ReplaceAll(t.Name(), "/", "_")
create, creatErr := apiClient.ContainerCreate(ctx, &cfg, &hostCfg, &network.NetworkingConfig{}, nil, ctrName) create, creatErr := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &cfg,
HostConfig: &hostCfg,
NetworkingConfig: &network.NetworkingConfig{},
ContainerName: ctrName,
})
id := create.ID id := create.ID
if id != "" { if id != "" {
defer container.Remove(ctx, t, apiClient, id, client.ContainerRemoveOptions{Force: true}) defer container.Remove(ctx, t, apiClient, id, client.ContainerRemoveOptions{Force: true})

View File

@@ -154,10 +154,11 @@ COPY . /static`); err != nil {
assert.NilError(t, err) assert.NilError(t, err)
// Start the container // Start the container
b, err := c.ContainerCreate(context.Background(), b, err := c.ContainerCreate(context.Background(), client.ContainerCreateOptions{
&containertypes.Config{Image: imgName}, Config: &containertypes.Config{Image: imgName},
&containertypes.HostConfig{PublishAllPorts: true}, HostConfig: &containertypes.HostConfig{PublishAllPorts: true},
nil, nil, ctrName) ContainerName: ctrName,
})
assert.NilError(t, err) assert.NilError(t, err)
err = c.ContainerStart(context.Background(), b.ID, client.ContainerStartOptions{}) err = c.ContainerStart(context.Background(), b.ID, client.ContainerStartOptions{})
assert.NilError(t, err) assert.NilError(t, err)

View File

@@ -12,7 +12,6 @@ import (
"github.com/moby/moby/api/types/registry" "github.com/moby/moby/api/types/registry"
"github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/api/types/system" "github.com/moby/moby/api/types/system"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
// APIClient is an interface that clients that talk with a docker server must implement. // APIClient is an interface that clients that talk with a docker server must implement.
@@ -58,10 +57,10 @@ type HijackDialer interface {
// ContainerAPIClient defines API client methods for the containers // ContainerAPIClient defines API client methods for the containers
type ContainerAPIClient interface { type ContainerAPIClient interface {
ContainerAttach(ctx context.Context, container string, options ContainerAttachOptions) (HijackedResponse, error) ContainerAttach(ctx context.Context, container string, options ContainerAttachOptions) (ContainerAttachResult, error)
ContainerCommit(ctx context.Context, container string, options ContainerCommitOptions) (container.CommitResponse, error) ContainerCommit(ctx context.Context, container string, options ContainerCommitOptions) (ContainerCommitResult, error)
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error)
ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error) ContainerDiff(ctx context.Context, container string, options ContainerDiffOptions) (ContainerDiffResult, error)
ExecAPIClient ExecAPIClient
ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) ContainerExport(ctx context.Context, container string) (io.ReadCloser, error)
ContainerInspect(ctx context.Context, container string) (container.InspectResponse, error) ContainerInspect(ctx context.Context, container string) (container.InspectResponse, error)

View File

@@ -16,6 +16,11 @@ type ContainerAttachOptions struct {
Logs bool Logs bool
} }
// ContainerAttachResult is the result from attaching to a container.
type ContainerAttachResult struct {
HijackedResponse
}
// ContainerAttach attaches a connection to a container in the server. // ContainerAttach attaches a connection to a container in the server.
// It returns a [HijackedResponse] with the hijacked connection // It returns a [HijackedResponse] with the hijacked connection
// and a reader to get output. It's up to the called to close // and a reader to get output. It's up to the called to close
@@ -44,10 +49,10 @@ type ContainerAttachOptions struct {
// [stdcopy.StdType]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdType // [stdcopy.StdType]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdType
// [Stdout]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stdout // [Stdout]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stdout
// [Stderr]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stderr // [Stderr]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stderr
func (cli *Client) ContainerAttach(ctx context.Context, containerID string, options ContainerAttachOptions) (HijackedResponse, error) { func (cli *Client) ContainerAttach(ctx context.Context, containerID string, options ContainerAttachOptions) (ContainerAttachResult, error) {
containerID, err := trimID("container", containerID) containerID, err := trimID("container", containerID)
if err != nil { if err != nil {
return HijackedResponse{}, err return ContainerAttachResult{}, err
} }
query := url.Values{} query := url.Values{}
@@ -70,7 +75,12 @@ func (cli *Client) ContainerAttach(ctx context.Context, containerID string, opti
query.Set("logs", "1") query.Set("logs", "1")
} }
return cli.postHijacked(ctx, "/containers/"+containerID+"/attach", query, nil, http.Header{ hijacked, err := cli.postHijacked(ctx, "/containers/"+containerID+"/attach", query, nil, http.Header{
"Content-Type": {"text/plain"}, "Content-Type": {"text/plain"},
}) })
if err != nil {
return ContainerAttachResult{}, err
}
return ContainerAttachResult{HijackedResponse: hijacked}, nil
} }

View File

@@ -20,22 +20,27 @@ type ContainerCommitOptions struct {
Config *container.Config Config *container.Config
} }
// ContainerCommitResult is the result from committing a container.
type ContainerCommitResult struct {
ID string
}
// ContainerCommit applies changes to a container and creates a new tagged image. // ContainerCommit applies changes to a container and creates a new tagged image.
func (cli *Client) ContainerCommit(ctx context.Context, containerID string, options ContainerCommitOptions) (container.CommitResponse, error) { func (cli *Client) ContainerCommit(ctx context.Context, containerID string, options ContainerCommitOptions) (ContainerCommitResult, error) {
containerID, err := trimID("container", containerID) containerID, err := trimID("container", containerID)
if err != nil { if err != nil {
return container.CommitResponse{}, err return ContainerCommitResult{}, err
} }
var repository, tag string var repository, tag string
if options.Reference != "" { if options.Reference != "" {
ref, err := reference.ParseNormalizedNamed(options.Reference) ref, err := reference.ParseNormalizedNamed(options.Reference)
if err != nil { if err != nil {
return container.CommitResponse{}, err return ContainerCommitResult{}, err
} }
if _, ok := ref.(reference.Digested); ok { if _, ok := ref.(reference.Digested); ok {
return container.CommitResponse{}, errors.New("refusing to create a tag with a digest reference") return ContainerCommitResult{}, errors.New("refusing to create a tag with a digest reference")
} }
ref = reference.TagNameOnly(ref) ref = reference.TagNameOnly(ref)
@@ -62,9 +67,9 @@ func (cli *Client) ContainerCommit(ctx context.Context, containerID string, opti
resp, err := cli.post(ctx, "/commit", query, options.Config, nil) resp, err := cli.post(ctx, "/commit", query, options.Config, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return response, err return ContainerCommitResult{}, err
} }
err = json.NewDecoder(resp.Body).Decode(&response) err = json.NewDecoder(resp.Body).Decode(&response)
return response, err return ContainerCommitResult{ID: response.ID}, err
} }

View File

@@ -10,49 +10,48 @@ import (
cerrdefs "github.com/containerd/errdefs" cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
// ContainerCreate creates a new container based on the given configuration. // ContainerCreate creates a new container based on the given configuration.
// It can be associated with a name, but it's not mandatory. // It can be associated with a name, but it's not mandatory.
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) { func (cli *Client) ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error) {
if config == nil { if options.Config == nil {
return container.CreateResponse{}, cerrdefs.ErrInvalidArgument.WithMessage("config is nil") return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("config is nil")
} }
var response container.CreateResponse var response container.CreateResponse
if hostConfig != nil { if options.HostConfig != nil {
hostConfig.CapAdd = normalizeCapabilities(hostConfig.CapAdd) options.HostConfig.CapAdd = normalizeCapabilities(options.HostConfig.CapAdd)
hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop) options.HostConfig.CapDrop = normalizeCapabilities(options.HostConfig.CapDrop)
} }
query := url.Values{} query := url.Values{}
if platform != nil { if options.Platform != nil {
if p := formatPlatform(*platform); p != "unknown" { if p := formatPlatform(*options.Platform); p != "unknown" {
query.Set("platform", p) query.Set("platform", p)
} }
} }
if containerName != "" { if options.ContainerName != "" {
query.Set("name", containerName) query.Set("name", options.ContainerName)
} }
body := container.CreateRequest{ body := container.CreateRequest{
Config: config, Config: options.Config,
HostConfig: hostConfig, HostConfig: options.HostConfig,
NetworkingConfig: networkingConfig, NetworkingConfig: options.NetworkingConfig,
} }
resp, err := cli.post(ctx, "/containers/create", query, body, nil) resp, err := cli.post(ctx, "/containers/create", query, body, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return response, err return ContainerCreateResult{}, err
} }
err = json.NewDecoder(resp.Body).Decode(&response) err = json.NewDecoder(resp.Body).Decode(&response)
return response, err return ContainerCreateResult{ID: response.ID, Warnings: response.Warnings}, err
} }
// formatPlatform returns a formatted string representing platform (e.g., "linux/arm/v7"). // formatPlatform returns a formatted string representing platform (e.g., "linux/arm/v7").

View File

@@ -0,0 +1,22 @@
package client
import (
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ContainerCreateOptions holds parameters to create a container.
type ContainerCreateOptions struct {
Config *container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
Platform *ocispec.Platform
ContainerName string
}
// ContainerCreateResult is the result from creating a container.
type ContainerCreateResult struct {
ID string
Warnings []string
}

View File

@@ -9,22 +9,22 @@ import (
) )
// ContainerDiff shows differences in a container filesystem since it was started. // ContainerDiff shows differences in a container filesystem since it was started.
func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]container.FilesystemChange, error) { func (cli *Client) ContainerDiff(ctx context.Context, containerID string, options ContainerDiffOptions) (ContainerDiffResult, error) {
containerID, err := trimID("container", containerID) containerID, err := trimID("container", containerID)
if err != nil { if err != nil {
return nil, err return ContainerDiffResult{}, err
} }
resp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil) resp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return nil, err return ContainerDiffResult{}, err
} }
var changes []container.FilesystemChange var changes []container.FilesystemChange
err = json.NewDecoder(resp.Body).Decode(&changes) err = json.NewDecoder(resp.Body).Decode(&changes)
if err != nil { if err != nil {
return nil, err return ContainerDiffResult{}, err
} }
return changes, err return ContainerDiffResult{Changes: changes}, err
} }

View File

@@ -0,0 +1,13 @@
package client
import "github.com/moby/moby/api/types/container"
// ContainerDiffOptions holds parameters to show differences in a container filesystem.
type ContainerDiffOptions struct {
// Currently no options, but this allows for future extensibility
}
// ContainerDiffResult is the result from showing differences in a container filesystem.
type ContainerDiffResult struct {
Changes []container.FilesystemChange
}