Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: move host-config and endpoint settings to a specific modifiers #633

Merged
merged 41 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
19d7296
feat: support preconfiguring Docker's host config and endpoint settin…
mdelapenya Nov 22, 2022
2d5ce5c
chore: deprecate ExtraHosts from the container request
mdelapenya Nov 22, 2022
0150814
chore: deprecate Binds from the container request
mdelapenya Nov 22, 2022
894bcee
chore: deprecate Tmpfs from the container request
mdelapenya Nov 22, 2022
0748a89
chore: deprecate AutoRemove from the container request
mdelapenya Nov 22, 2022
d70b26a
chore: deprecate Privileged from the container request
mdelapenya Nov 22, 2022
064467d
chore: deprecate Resources from the container request
mdelapenya Nov 22, 2022
793cd61
chore: deprecate ShmSize from the container request
mdelapenya Nov 22, 2022
feaec02
chore: deprecate CapAdd from the container request
mdelapenya Nov 22, 2022
c327e65
chore: deprecate CapDrop from the container request
mdelapenya Nov 22, 2022
27e1ae6
chore: deprecate NetworkMode from the container request
mdelapenya Nov 22, 2022
dbfb237
chore: consistent name for endpointSettings variable
mdelapenya Nov 22, 2022
595a0b5
chore: rename callback to hook
mdelapenya Nov 22, 2022
6dfd1fd
chore: extract default pre-creation hook to a function
mdelapenya Nov 22, 2022
55f98c9
fix: wording
mdelapenya Nov 22, 2022
7b2bcdf
chore: extract preCreation code to a function
mdelapenya Nov 22, 2022
8a67f21
chore: rename methods to use modifier
mdelapenya Nov 22, 2022
ec4ba50
chore: extract life cycle to a separate file
mdelapenya Nov 22, 2022
8342a00
fix: update comments
mdelapenya Nov 22, 2022
dd9bd20
chore: push Tmpfs back to layer 1
mdelapenya Nov 22, 2022
0a09cf6
chore: push Privileged back to layer 1
mdelapenya Nov 22, 2022
35849bb
Merge branch 'main' into pre-creation-hook
mdelapenya Nov 29, 2022
b437b88
fix: remove outdated comments
mdelapenya Nov 29, 2022
dac0df0
chore: bring ShmSize back to the first layer
mdelapenya Nov 29, 2022
3cbf64e
Merge branch 'main' into pre-creation-hook
mdelapenya Jan 3, 2023
17b8e1b
chore: separate concerns for modifiers
mdelapenya Jan 4, 2023
66c5f4a
fix: typo in variable
mdelapenya Jan 4, 2023
f2cb127
Merge branch 'main' into pre-creation-hook
mdelapenya Jan 24, 2023
a15e33d
chore: add modifier for Docker config
mdelapenya Feb 2, 2023
cc267c1
chore: unit tests for the preCreateHook
mdelapenya Feb 2, 2023
70f89e3
fix: check for the existence of aliases when there are multiple networks
mdelapenya Feb 3, 2023
4fb482b
chore: adjust error messages in tests
mdelapenya Feb 3, 2023
74e32d2
chore: include mounts in the unit tests
mdelapenya Feb 3, 2023
d7d38ea
fix: handle error in tests
mdelapenya Feb 3, 2023
e802e51
fix: handle error in tests
mdelapenya Feb 3, 2023
668ad6d
Merge branch 'main' into pre-creation-hook
mdelapenya Feb 3, 2023
f1b6e26
chore: execute modifiers the last
mdelapenya Feb 14, 2023
586b0e2
chore: rename file
mdelapenya Feb 14, 2023
b511560
Revert "chore: execute modifiers the last"
mdelapenya Feb 14, 2023
e1854a8
docs: document the modifiers
mdelapenya Feb 14, 2023
43998c8
fix: typo
mdelapenya Feb 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 34 additions & 30 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/pkg/archive"
"github.com/docker/go-connections/nat"

Expand Down Expand Up @@ -94,36 +95,39 @@ type ContainerFile struct {
// ContainerRequest represents the parameters used to get a running container
type ContainerRequest struct {
FromDockerfile
Image string
Entrypoint []string
Env map[string]string
ExposedPorts []string // allow specifying protocol info
Cmd []string
Labels map[string]string
Mounts ContainerMounts
Tmpfs map[string]string
RegistryCred string
WaitingFor wait.Strategy
Name string // for specifying container name
Hostname string
ExtraHosts []string
Privileged bool // for starting privileged container
Networks []string // for specifying network names
NetworkAliases map[string][]string // for specifying network aliases
NetworkMode container.NetworkMode
Resources container.Resources
Files []ContainerFile // files which will be copied when container starts
User string // for specifying uid:gid
SkipReaper bool // indicates whether we skip setting up a reaper for this
ReaperImage string // Deprecated: use WithImageName ContainerOption instead. Alternative reaper image
ReaperOptions []ContainerOption // options for the reaper
AutoRemove bool // if set to true, the container will be removed from the host when stopped
AlwaysPullImage bool // Always pull image
ImagePlatform string // ImagePlatform describes the platform which the image runs on.
Binds []string
ShmSize int64 // Amount of memory shared with the host (in bytes)
CapAdd []string // Add Linux capabilities
CapDrop []string // Drop Linux capabilities
Image string
Entrypoint []string
Env map[string]string
ExposedPorts []string // allow specifying protocol info
Cmd []string
Labels map[string]string
Mounts ContainerMounts
Tmpfs map[string]string
RegistryCred string
WaitingFor wait.Strategy
Name string // for specifying container name
Hostname string
ExtraHosts []string // Deprecated: Use HostConfigModifier instead
Privileged bool // For starting privileged container
Networks []string // for specifying network names
NetworkAliases map[string][]string // for specifying network aliases
NetworkMode container.NetworkMode // Deprecated: Use HostConfigModifier instead
Resources container.Resources // Deprecated: Use HostConfigModifier instead
Files []ContainerFile // files which will be copied when container starts
User string // for specifying uid:gid
SkipReaper bool // indicates whether we skip setting up a reaper for this
ReaperImage string // Deprecated: use WithImageName ContainerOption instead. Alternative reaper image
ReaperOptions []ContainerOption // options for the reaper
AutoRemove bool // Deprecated: Use HostConfigModifier instead. If set to true, the container will be removed from the host when stopped
AlwaysPullImage bool // Always pull image
ImagePlatform string // ImagePlatform describes the platform which the image runs on.
Binds []string // Deprecated: Use HostConfigModifier instead
ShmSize int64 // Amount of memory shared with the host (in bytes)
CapAdd []string // Deprecated: Use HostConfigModifier instead. Add Linux capabilities
CapDrop []string // Deprecated: Use HostConfigModifier instead. Drop Linux capabilities
ConfigModifier func(*container.Config) // Modifier for the config before container creation
HostConfigModifier func(*container.HostConfig) // Modifier for the host config before container creation
EnpointSettingsModifier func(map[string]*network.EndpointSettings) // Modifier for the network settings before container creation
}

type (
Expand Down
78 changes: 16 additions & 62 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1055,76 +1055,30 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
}
}

exposedPorts := req.ExposedPorts
if len(exposedPorts) == 0 && !req.NetworkMode.IsContainer() {
image, _, err := p.client.ImageInspectWithRaw(ctx, tag)
if err != nil {
return nil, err
}
for p := range image.ContainerConfig.ExposedPorts {
exposedPorts = append(exposedPorts, string(p))
}
}

exposedPortSet, exposedPortMap, err := nat.ParsePortSpecs(exposedPorts)
if err != nil {
return nil, err
}

dockerInput := &container.Config{
Entrypoint: req.Entrypoint,
Image: tag,
Env: env,
ExposedPorts: exposedPortSet,
Labels: req.Labels,
Cmd: req.Cmd,
Hostname: req.Hostname,
User: req.User,
Entrypoint: req.Entrypoint,
Image: tag,
Env: env,
Labels: req.Labels,
Cmd: req.Cmd,
Hostname: req.Hostname,
User: req.User,
}

// prepare mounts
mounts := mapToDockerMounts(req.Mounts)

hostConfig := &container.HostConfig{
ExtraHosts: req.ExtraHosts,
PortBindings: exposedPortMap,
Binds: req.Binds,
Mounts: mounts,
Tmpfs: req.Tmpfs,
AutoRemove: req.AutoRemove,
Privileged: req.Privileged,
NetworkMode: req.NetworkMode,
Resources: req.Resources,
ShmSize: req.ShmSize,
CapAdd: req.CapAdd,
CapDrop: req.CapDrop,
}

endpointConfigs := map[string]*network.EndpointSettings{}

// #248: Docker allows only one network to be specified during container creation
// If there is more than one network specified in the request container should be attached to them
// once it is created. We will take a first network if any specified in the request and use it to create container
if len(req.Networks) > 0 {
attachContainerTo := req.Networks[0]

nw, err := p.GetNetwork(ctx, NetworkRequest{
Name: attachContainerTo,
})
if err == nil {
endpointSetting := network.EndpointSettings{
Aliases: req.NetworkAliases[attachContainerTo],
NetworkID: nw.ID,
}
endpointConfigs[attachContainerTo] = &endpointSetting
}
Privileged: req.Privileged,
ShmSize: req.ShmSize,
Tmpfs: req.Tmpfs,
}

networkingConfig := network.NetworkingConfig{
EndpointsConfig: endpointConfigs,
networkingConfig := &network.NetworkingConfig{}

err = p.preCreateContainerHook(ctx, req, dockerInput, hostConfig, networkingConfig)
if err != nil {
return nil, err
}

resp, err := p.client.ContainerCreate(ctx, dockerInput, hostConfig, &networkingConfig, platform, req.Name)
resp, err := p.client.ContainerCreate(ctx, dockerInput, hostConfig, networkingConfig, platform, req.Name)
if err != nil {
return nil, err
}
Expand Down
74 changes: 45 additions & 29 deletions docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,17 @@ func TestContainerWithHostNetworkOptions(t *testing.T) {
gcr := GenericContainerRequest{
ProviderType: providerType,
ContainerRequest: ContainerRequest{
Image: nginxAlpineImage,
Privileged: true,
SkipReaper: true,
NetworkMode: "host",
Mounts: Mounts(BindMount(absPath, "/etc/nginx/conf.d/default.conf")),
Image: nginxAlpineImage,
SkipReaper: true,
Mounts: Mounts(BindMount(absPath, "/etc/nginx/conf.d/default.conf")),
ExposedPorts: []string{
nginxHighPort,
},
Privileged: true,
WaitingFor: wait.ForListeningPort(nginxHighPort),
HostConfigModifier: func(hc *container.HostConfig) {
hc.NetworkMode = "host"
},
},
Started: true,
}
Expand Down Expand Up @@ -208,10 +210,12 @@ func TestContainerWithNetworkModeAndNetworkTogether(t *testing.T) {
gcr := GenericContainerRequest{
ProviderType: providerType,
ContainerRequest: ContainerRequest{
Image: nginxImage,
SkipReaper: true,
NetworkMode: "host",
Networks: []string{"new-network"},
Image: nginxImage,
SkipReaper: true,
Networks: []string{"new-network"},
HostConfigModifier: func(hc *container.HostConfig) {
hc.NetworkMode = "host"
},
},
Started: true,
}
Expand All @@ -235,11 +239,13 @@ func TestContainerWithHostNetworkOptionsAndWaitStrategy(t *testing.T) {
gcr := GenericContainerRequest{
ProviderType: providerType,
ContainerRequest: ContainerRequest{
Image: nginxAlpineImage,
SkipReaper: true,
NetworkMode: "host",
WaitingFor: wait.ForListeningPort(nginxHighPort),
Mounts: Mounts(BindMount(absPath, "/etc/nginx/conf.d/default.conf")),
Image: nginxAlpineImage,
SkipReaper: true,
WaitingFor: wait.ForListeningPort(nginxHighPort),
Mounts: Mounts(BindMount(absPath, "/etc/nginx/conf.d/default.conf")),
HostConfigModifier: func(hc *container.HostConfig) {
hc.NetworkMode = "host"
},
},
Started: true,
}
Expand Down Expand Up @@ -271,11 +277,13 @@ func TestContainerWithHostNetworkAndEndpoint(t *testing.T) {
gcr := GenericContainerRequest{
ProviderType: providerType,
ContainerRequest: ContainerRequest{
Image: nginxAlpineImage,
SkipReaper: true,
NetworkMode: "host",
WaitingFor: wait.ForListeningPort(nginxHighPort),
Mounts: Mounts(BindMount(absPath, "/etc/nginx/conf.d/default.conf")),
Image: nginxAlpineImage,
SkipReaper: true,
WaitingFor: wait.ForListeningPort(nginxHighPort),
Mounts: Mounts(BindMount(absPath, "/etc/nginx/conf.d/default.conf")),
HostConfigModifier: func(hc *container.HostConfig) {
hc.NetworkMode = "host"
},
},
Started: true,
}
Expand Down Expand Up @@ -308,11 +316,13 @@ func TestContainerWithHostNetworkAndPortEndpoint(t *testing.T) {
gcr := GenericContainerRequest{
ProviderType: providerType,
ContainerRequest: ContainerRequest{
Image: nginxAlpineImage,
SkipReaper: true,
NetworkMode: "host",
WaitingFor: wait.ForListeningPort(nginxHighPort),
Mounts: Mounts(BindMount(absPath, "/etc/nginx/conf.d/default.conf")),
Image: nginxAlpineImage,
SkipReaper: true,
WaitingFor: wait.ForListeningPort(nginxHighPort),
Mounts: Mounts(BindMount(absPath, "/etc/nginx/conf.d/default.conf")),
HostConfigModifier: func(hc *container.HostConfig) {
hc.NetworkMode = "host"
},
},
Started: true,
}
Expand Down Expand Up @@ -2302,8 +2312,10 @@ func TestDockerContainerResources(t *testing.T) {
Image: nginxAlpineImage,
ExposedPorts: []string{nginxDefaultPort},
WaitingFor: wait.ForListeningPort(nginxDefaultPort),
Resources: container.Resources{
Ulimits: expected,
HostConfigModifier: func(hc *container.HostConfig) {
hc.Resources = container.Resources{
Ulimits: expected,
}
},
},
Started: true,
Expand Down Expand Up @@ -2388,7 +2400,9 @@ func TestContainerCapAdd(t *testing.T) {
Image: nginxAlpineImage,
ExposedPorts: []string{nginxDefaultPort},
WaitingFor: wait.ForListeningPort(nginxDefaultPort),
CapAdd: []string{expected},
HostConfigModifier: func(hc *container.HostConfig) {
hc.CapAdd = []string{expected}
},
},
Started: true,
})
Expand Down Expand Up @@ -2532,8 +2546,10 @@ func TestNetworkModeWithContainerReference(t *testing.T) {
nginxB, err := GenericContainer(ctx, GenericContainerRequest{
ProviderType: providerType,
ContainerRequest: ContainerRequest{
Image: nginxAlpineImage,
NetworkMode: container.NetworkMode(networkMode),
Image: nginxAlpineImage,
HostConfigModifier: func(hc *container.HostConfig) {
hc.NetworkMode = container.NetworkMode(networkMode)
},
},
Started: true,
})
Expand Down
88 changes: 88 additions & 0 deletions lifecycle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package testcontainers

import (
"context"

"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/go-connections/nat"
)

func (p *DockerProvider) preCreateContainerHook(ctx context.Context, req ContainerRequest, dockerInput *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) error {
// prepare mounts
hostConfig.Mounts = mapToDockerMounts(req.Mounts)

endpointSettings := map[string]*network.EndpointSettings{}

// #248: Docker allows only one network to be specified during container creation
// If there is more than one network specified in the request container should be attached to them
// once it is created. We will take a first network if any specified in the request and use it to create container
if len(req.Networks) > 0 {
attachContainerTo := req.Networks[0]

nw, err := p.GetNetwork(ctx, NetworkRequest{
Name: attachContainerTo,
})
if err == nil {
aliases := []string{}
if _, ok := req.NetworkAliases[attachContainerTo]; ok {
aliases = req.NetworkAliases[attachContainerTo]
}
endpointSetting := network.EndpointSettings{
Aliases: aliases,
NetworkID: nw.ID,
}
endpointSettings[attachContainerTo] = &endpointSetting
}
}

if req.ConfigModifier != nil {
req.ConfigModifier(dockerInput)
}

if req.HostConfigModifier == nil {
req.HostConfigModifier = defaultHostConfigModifier(req)
}
req.HostConfigModifier(hostConfig)

if req.EnpointSettingsModifier != nil {
req.EnpointSettingsModifier(endpointSettings)
}

networkingConfig.EndpointsConfig = endpointSettings

exposedPorts := req.ExposedPorts
// this check must be done after the pre-creation Modifiers are called, so the network mode is already set
if len(exposedPorts) == 0 && !hostConfig.NetworkMode.IsContainer() {
image, _, err := p.client.ImageInspectWithRaw(ctx, dockerInput.Image)
if err != nil {
return err
}
for p := range image.ContainerConfig.ExposedPorts {
exposedPorts = append(exposedPorts, string(p))
}
}

exposedPortSet, exposedPortMap, err := nat.ParsePortSpecs(exposedPorts)
if err != nil {
return err
}

dockerInput.ExposedPorts = exposedPortSet
hostConfig.PortBindings = exposedPortMap

return nil
}

// defaultHostConfigModifier provides a default modifier including the deprecated fields
func defaultHostConfigModifier(req ContainerRequest) func(hostConfig *container.HostConfig) {
return func(hostConfig *container.HostConfig) {
hostConfig.AutoRemove = req.AutoRemove
hostConfig.CapAdd = req.CapAdd
hostConfig.CapDrop = req.CapDrop
hostConfig.Binds = req.Binds
hostConfig.ExtraHosts = req.ExtraHosts
hostConfig.NetworkMode = req.NetworkMode
hostConfig.Resources = req.Resources
}
}
Loading