client/container_create: Add Image outside of Config

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski
2025-10-23 15:13:55 +02:00
parent 3340c86db9
commit 8fb561ca9a
6 changed files with 49 additions and 22 deletions

View File

@@ -16,8 +16,23 @@ import (
// 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, options ContainerCreateOptions) (ContainerCreateResult, error) { func (cli *Client) ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error) {
if options.Config == nil { cfg := options.Config
return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("config is nil")
if cfg == nil {
cfg = &container.Config{}
}
if options.Image != "" {
if cfg.Image != "" {
return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("either Image or config.Image should be set")
}
newCfg := *cfg
newCfg.Image = options.Image
cfg = &newCfg
}
if cfg.Image == "" {
return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("config.Image or Image is required")
} }
var response container.CreateResponse var response container.CreateResponse
@@ -39,7 +54,7 @@ func (cli *Client) ContainerCreate(ctx context.Context, options ContainerCreateO
} }
body := container.CreateRequest{ body := container.CreateRequest{
Config: options.Config, Config: cfg,
HostConfig: options.HostConfig, HostConfig: options.HostConfig,
NetworkingConfig: options.NetworkingConfig, NetworkingConfig: options.NetworkingConfig,
} }

View File

@@ -13,6 +13,9 @@ type ContainerCreateOptions struct {
NetworkingConfig *network.NetworkingConfig NetworkingConfig *network.NetworkingConfig
Platform *ocispec.Platform Platform *ocispec.Platform
Name string Name string
// Image is a shortcut for Config.Image - only one of Image or Config.Image should be set.
Image string
} }
// ContainerCreateResult is the result from creating a container. // ContainerCreateResult is the result from creating a container.

View File

@@ -23,20 +23,11 @@ func TestContainerCreateError(t *testing.T) {
assert.NilError(t, err) assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: nil, Name: "nothing"}) _, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: nil, Name: "nothing"})
assert.Error(t, err, "config is nil") assert.Error(t, err, "config.Image or Image is required")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, Name: "nothing"}) _, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, Name: "nothing"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
// 404 doesn't automatically means an unknown image
client, err = NewClientWithOpts(
WithMockClient(errorMock(http.StatusNotFound, "Server error")),
)
assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, Name: "nothing"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
} }
func TestContainerCreateImageNotFound(t *testing.T) { func TestContainerCreateImageNotFound(t *testing.T) {
@@ -74,7 +65,7 @@ func TestContainerCreateWithName(t *testing.T) {
) )
assert.NilError(t, err) assert.NilError(t, err)
r, err := client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, Name: "container_name"}) r, err := client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, Name: "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 +94,7 @@ func TestContainerCreateAutoRemove(t *testing.T) {
) )
assert.NilError(t, err) assert.NilError(t, err)
resp, err := client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, HostConfig: &container.HostConfig{AutoRemove: true}}) resp, err := client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, 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 +107,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(), ContainerCreateOptions{Config: &container.Config{}}) _, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}})
assert.Check(t, is.ErrorType(err, IsErrConnectionFailed)) assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
} }
@@ -165,6 +156,6 @@ func TestContainerCreateCapabilities(t *testing.T) {
) )
assert.NilError(t, err) assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, HostConfig: &container.HostConfig{CapAdd: inputCaps, CapDrop: inputCaps}}) _, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, HostConfig: &container.HostConfig{CapAdd: inputCaps, CapDrop: inputCaps}})
assert.NilError(t, err) assert.NilError(t, err)
} }

View File

@@ -557,7 +557,7 @@ func (s *DockerAPISuite) TestContainerAPICreateEmptyConfig(c *testing.T) {
NetworkingConfig: &network.NetworkingConfig{}, NetworkingConfig: &network.NetworkingConfig{},
}) })
assert.ErrorContains(c, err, "no command specified") assert.ErrorContains(c, err, "config.Image or Image is required")
} }
func (s *DockerAPISuite) TestContainerAPICreateBridgeNetworkMode(c *testing.T) { func (s *DockerAPISuite) TestContainerAPICreateBridgeNetworkMode(c *testing.T) {

View File

@@ -16,8 +16,23 @@ import (
// 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, options ContainerCreateOptions) (ContainerCreateResult, error) { func (cli *Client) ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error) {
if options.Config == nil { cfg := options.Config
return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("config is nil")
if cfg == nil {
cfg = &container.Config{}
}
if options.Image != "" {
if cfg.Image != "" {
return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("either Image or config.Image should be set")
}
newCfg := *cfg
newCfg.Image = options.Image
cfg = &newCfg
}
if cfg.Image == "" {
return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("config.Image or Image is required")
} }
var response container.CreateResponse var response container.CreateResponse
@@ -39,7 +54,7 @@ func (cli *Client) ContainerCreate(ctx context.Context, options ContainerCreateO
} }
body := container.CreateRequest{ body := container.CreateRequest{
Config: options.Config, Config: cfg,
HostConfig: options.HostConfig, HostConfig: options.HostConfig,
NetworkingConfig: options.NetworkingConfig, NetworkingConfig: options.NetworkingConfig,
} }

View File

@@ -13,6 +13,9 @@ type ContainerCreateOptions struct {
NetworkingConfig *network.NetworkingConfig NetworkingConfig *network.NetworkingConfig
Platform *ocispec.Platform Platform *ocispec.Platform
Name string Name string
// Image is a shortcut for Config.Image - only one of Image or Config.Image should be set.
Image string
} }
// ContainerCreateResult is the result from creating a container. // ContainerCreateResult is the result from creating a container.