628 lines
22 KiB
Go
628 lines
22 KiB
Go
|
package testcontainers
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/cenkalti/backoff/v4"
|
||
|
"github.com/docker/docker/api/types/container"
|
||
|
"github.com/docker/docker/api/types/network"
|
||
|
"github.com/docker/go-connections/nat"
|
||
|
)
|
||
|
|
||
|
// ContainerRequestHook is a hook that will be called before a container is created.
|
||
|
// It can be used to modify container configuration before it is created,
|
||
|
// using the different lifecycle hooks that are available:
|
||
|
// - Creating
|
||
|
// For that, it will receive a ContainerRequest, modify it and return an error if needed.
|
||
|
type ContainerRequestHook func(ctx context.Context, req ContainerRequest) error
|
||
|
|
||
|
// ContainerHook is a hook that will be called after a container is created
|
||
|
// It can be used to modify the state of the container after it is created,
|
||
|
// using the different lifecycle hooks that are available:
|
||
|
// - Created
|
||
|
// - Starting
|
||
|
// - Started
|
||
|
// - Readied
|
||
|
// - Stopping
|
||
|
// - Stopped
|
||
|
// - Terminating
|
||
|
// - Terminated
|
||
|
// For that, it will receive a Container, modify it and return an error if needed.
|
||
|
type ContainerHook func(ctx context.Context, container Container) error
|
||
|
|
||
|
// ContainerLifecycleHooks is a struct that contains all the hooks that can be used
|
||
|
// to modify the container lifecycle. All the container lifecycle hooks except the PreCreates hooks
|
||
|
// will be passed to the container once it's created
|
||
|
type ContainerLifecycleHooks struct {
|
||
|
PreCreates []ContainerRequestHook
|
||
|
PostCreates []ContainerHook
|
||
|
PreStarts []ContainerHook
|
||
|
PostStarts []ContainerHook
|
||
|
PostReadies []ContainerHook
|
||
|
PreStops []ContainerHook
|
||
|
PostStops []ContainerHook
|
||
|
PreTerminates []ContainerHook
|
||
|
PostTerminates []ContainerHook
|
||
|
}
|
||
|
|
||
|
// DefaultLoggingHook is a hook that will log the container lifecycle events
|
||
|
var DefaultLoggingHook = func(logger Logging) ContainerLifecycleHooks {
|
||
|
shortContainerID := func(c Container) string {
|
||
|
return c.GetContainerID()[:12]
|
||
|
}
|
||
|
|
||
|
return ContainerLifecycleHooks{
|
||
|
PreCreates: []ContainerRequestHook{
|
||
|
func(ctx context.Context, req ContainerRequest) error {
|
||
|
logger.Printf("🐳 Creating container for image %s", req.Image)
|
||
|
return nil
|
||
|
},
|
||
|
},
|
||
|
PostCreates: []ContainerHook{
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
logger.Printf("✅ Container created: %s", shortContainerID(c))
|
||
|
return nil
|
||
|
},
|
||
|
},
|
||
|
PreStarts: []ContainerHook{
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
logger.Printf("🐳 Starting container: %s", shortContainerID(c))
|
||
|
return nil
|
||
|
},
|
||
|
},
|
||
|
PostStarts: []ContainerHook{
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
logger.Printf("✅ Container started: %s", shortContainerID(c))
|
||
|
return nil
|
||
|
},
|
||
|
},
|
||
|
PostReadies: []ContainerHook{
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
logger.Printf("🔔 Container is ready: %s", shortContainerID(c))
|
||
|
return nil
|
||
|
},
|
||
|
},
|
||
|
PreStops: []ContainerHook{
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
logger.Printf("🐳 Stopping container: %s", shortContainerID(c))
|
||
|
return nil
|
||
|
},
|
||
|
},
|
||
|
PostStops: []ContainerHook{
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
logger.Printf("✅ Container stopped: %s", shortContainerID(c))
|
||
|
return nil
|
||
|
},
|
||
|
},
|
||
|
PreTerminates: []ContainerHook{
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
logger.Printf("🐳 Terminating container: %s", shortContainerID(c))
|
||
|
return nil
|
||
|
},
|
||
|
},
|
||
|
PostTerminates: []ContainerHook{
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
logger.Printf("🚫 Container terminated: %s", shortContainerID(c))
|
||
|
return nil
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// defaultPreCreateHook is a hook that will apply the default configuration to the container
|
||
|
var defaultPreCreateHook = func(p *DockerProvider, dockerInput *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) ContainerLifecycleHooks {
|
||
|
return ContainerLifecycleHooks{
|
||
|
PreCreates: []ContainerRequestHook{
|
||
|
func(ctx context.Context, req ContainerRequest) error {
|
||
|
return p.preCreateContainerHook(ctx, req, dockerInput, hostConfig, networkingConfig)
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// defaultCopyFileToContainerHook is a hook that will copy files to the container after it's created
|
||
|
// but before it's started
|
||
|
var defaultCopyFileToContainerHook = func(files []ContainerFile) ContainerLifecycleHooks {
|
||
|
return ContainerLifecycleHooks{
|
||
|
PostCreates: []ContainerHook{
|
||
|
// copy files to container after it's created
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
for _, f := range files {
|
||
|
if err := f.validate(); err != nil {
|
||
|
return fmt.Errorf("invalid file: %w", err)
|
||
|
}
|
||
|
|
||
|
var err error
|
||
|
// Bytes takes precedence over HostFilePath
|
||
|
if f.Reader != nil {
|
||
|
bs, ioerr := io.ReadAll(f.Reader)
|
||
|
if ioerr != nil {
|
||
|
return fmt.Errorf("can't read from reader: %w", ioerr)
|
||
|
}
|
||
|
|
||
|
err = c.CopyToContainer(ctx, bs, f.ContainerFilePath, f.FileMode)
|
||
|
} else {
|
||
|
err = c.CopyFileToContainer(ctx, f.HostFilePath, f.ContainerFilePath, f.FileMode)
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("can't copy %s to container: %w", f.HostFilePath, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// defaultLogConsumersHook is a hook that will start log consumers after the container is started
|
||
|
var defaultLogConsumersHook = func(cfg *LogConsumerConfig) ContainerLifecycleHooks {
|
||
|
return ContainerLifecycleHooks{
|
||
|
PostStarts: []ContainerHook{
|
||
|
// Produce logs sending details to the log consumers.
|
||
|
// See combineContainerHooks for the order of execution.
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
if cfg == nil || len(cfg.Consumers) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
dockerContainer := c.(*DockerContainer)
|
||
|
dockerContainer.consumers = dockerContainer.consumers[:0]
|
||
|
for _, consumer := range cfg.Consumers {
|
||
|
dockerContainer.followOutput(consumer)
|
||
|
}
|
||
|
|
||
|
return dockerContainer.startLogProduction(ctx, cfg.Opts...)
|
||
|
},
|
||
|
},
|
||
|
PostStops: []ContainerHook{
|
||
|
// Stop the log production.
|
||
|
// See combineContainerHooks for the order of execution.
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
if cfg == nil || len(cfg.Consumers) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
dockerContainer := c.(*DockerContainer)
|
||
|
|
||
|
return dockerContainer.stopLogProduction()
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func checkPortsMapped(exposedAndMappedPorts nat.PortMap, exposedPorts []string) error {
|
||
|
portMap, _, err := nat.ParsePortSpecs(exposedPorts)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("parse exposed ports: %w", err)
|
||
|
}
|
||
|
|
||
|
for exposedPort := range portMap {
|
||
|
// having entries in exposedAndMappedPorts, where the key is the exposed port,
|
||
|
// and the value is the mapped port, means that the port has been already mapped.
|
||
|
if _, ok := exposedAndMappedPorts[exposedPort]; ok {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// check if the port is mapped with the protocol (default is TCP)
|
||
|
if strings.Contains(string(exposedPort), "/") {
|
||
|
return fmt.Errorf("port %s is not mapped yet", exposedPort)
|
||
|
}
|
||
|
|
||
|
// Port didn't have a type, default to tcp and retry.
|
||
|
exposedPort += "/tcp"
|
||
|
if _, ok := exposedAndMappedPorts[exposedPort]; !ok {
|
||
|
return fmt.Errorf("port %s is not mapped yet", exposedPort)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// defaultReadinessHook is a hook that will wait for the container to be ready
|
||
|
var defaultReadinessHook = func() ContainerLifecycleHooks {
|
||
|
return ContainerLifecycleHooks{
|
||
|
PostStarts: []ContainerHook{
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
// wait until all the exposed ports are mapped:
|
||
|
// it will be ready when all the exposed ports are mapped,
|
||
|
// checking every 50ms, up to 1s, and failing if all the
|
||
|
// exposed ports are not mapped in 5s.
|
||
|
dockerContainer := c.(*DockerContainer)
|
||
|
|
||
|
b := backoff.NewExponentialBackOff()
|
||
|
|
||
|
b.InitialInterval = 50 * time.Millisecond
|
||
|
b.MaxElapsedTime = 5 * time.Second
|
||
|
b.MaxInterval = time.Duration(float64(time.Second) * backoff.DefaultRandomizationFactor)
|
||
|
|
||
|
err := backoff.RetryNotify(
|
||
|
func() error {
|
||
|
jsonRaw, err := dockerContainer.inspectRawContainer(ctx)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return checkPortsMapped(jsonRaw.NetworkSettings.Ports, dockerContainer.exposedPorts)
|
||
|
},
|
||
|
b,
|
||
|
func(err error, duration time.Duration) {
|
||
|
dockerContainer.logger.Printf("All requested ports were not exposed: %v", err)
|
||
|
},
|
||
|
)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("all exposed ports, %s, were not mapped in 5s: %w", dockerContainer.exposedPorts, err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
},
|
||
|
// wait for the container to be ready
|
||
|
func(ctx context.Context, c Container) error {
|
||
|
dockerContainer := c.(*DockerContainer)
|
||
|
|
||
|
// if a Wait Strategy has been specified, wait before returning
|
||
|
if dockerContainer.WaitingFor != nil {
|
||
|
dockerContainer.logger.Printf(
|
||
|
"⏳ Waiting for container id %s image: %s. Waiting for: %+v",
|
||
|
dockerContainer.ID[:12], dockerContainer.Image, dockerContainer.WaitingFor,
|
||
|
)
|
||
|
if err := dockerContainer.WaitingFor.WaitUntilReady(ctx, c); err != nil {
|
||
|
return fmt.Errorf("wait until ready: %w", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dockerContainer.isRunning = true
|
||
|
|
||
|
return nil
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// creatingHook is a hook that will be called before a container is created.
|
||
|
func (req ContainerRequest) creatingHook(ctx context.Context) error {
|
||
|
errs := make([]error, len(req.LifecycleHooks))
|
||
|
for i, lifecycleHooks := range req.LifecycleHooks {
|
||
|
errs[i] = lifecycleHooks.Creating(ctx)(req)
|
||
|
}
|
||
|
|
||
|
return errors.Join(errs...)
|
||
|
}
|
||
|
|
||
|
// createdHook is a hook that will be called after a container is created.
|
||
|
func (c *DockerContainer) createdHook(ctx context.Context) error {
|
||
|
return c.applyLifecycleHooks(ctx, false, func(lifecycleHooks ContainerLifecycleHooks) []ContainerHook {
|
||
|
return lifecycleHooks.PostCreates
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// startingHook is a hook that will be called before a container is started.
|
||
|
func (c *DockerContainer) startingHook(ctx context.Context) error {
|
||
|
return c.applyLifecycleHooks(ctx, true, func(lifecycleHooks ContainerLifecycleHooks) []ContainerHook {
|
||
|
return lifecycleHooks.PreStarts
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// startedHook is a hook that will be called after a container is started.
|
||
|
func (c *DockerContainer) startedHook(ctx context.Context) error {
|
||
|
return c.applyLifecycleHooks(ctx, true, func(lifecycleHooks ContainerLifecycleHooks) []ContainerHook {
|
||
|
return lifecycleHooks.PostStarts
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// readiedHook is a hook that will be called after a container is ready.
|
||
|
func (c *DockerContainer) readiedHook(ctx context.Context) error {
|
||
|
return c.applyLifecycleHooks(ctx, true, func(lifecycleHooks ContainerLifecycleHooks) []ContainerHook {
|
||
|
return lifecycleHooks.PostReadies
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// printLogs is a helper function that will print the logs of a Docker container
|
||
|
// We are going to use this helper function to inform the user of the logs when an error occurs
|
||
|
func (c *DockerContainer) printLogs(ctx context.Context, cause error) {
|
||
|
reader, err := c.Logs(ctx)
|
||
|
if err != nil {
|
||
|
c.logger.Printf("failed accessing container logs: %v\n", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
b, err := io.ReadAll(reader)
|
||
|
if err != nil {
|
||
|
c.logger.Printf("failed reading container logs: %v\n", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
c.logger.Printf("container logs (%s):\n%s", cause, b)
|
||
|
}
|
||
|
|
||
|
// stoppingHook is a hook that will be called before a container is stopped.
|
||
|
func (c *DockerContainer) stoppingHook(ctx context.Context) error {
|
||
|
return c.applyLifecycleHooks(ctx, false, func(lifecycleHooks ContainerLifecycleHooks) []ContainerHook {
|
||
|
return lifecycleHooks.PreStops
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// stoppedHook is a hook that will be called after a container is stopped.
|
||
|
func (c *DockerContainer) stoppedHook(ctx context.Context) error {
|
||
|
return c.applyLifecycleHooks(ctx, false, func(lifecycleHooks ContainerLifecycleHooks) []ContainerHook {
|
||
|
return lifecycleHooks.PostStops
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// terminatingHook is a hook that will be called before a container is terminated.
|
||
|
func (c *DockerContainer) terminatingHook(ctx context.Context) error {
|
||
|
return c.applyLifecycleHooks(ctx, false, func(lifecycleHooks ContainerLifecycleHooks) []ContainerHook {
|
||
|
return lifecycleHooks.PreTerminates
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// terminatedHook is a hook that will be called after a container is terminated.
|
||
|
func (c *DockerContainer) terminatedHook(ctx context.Context) error {
|
||
|
return c.applyLifecycleHooks(ctx, false, func(lifecycleHooks ContainerLifecycleHooks) []ContainerHook {
|
||
|
return lifecycleHooks.PostTerminates
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// applyLifecycleHooks applies all lifecycle hooks reporting the container logs on error if logError is true.
|
||
|
func (c *DockerContainer) applyLifecycleHooks(ctx context.Context, logError bool, hooks func(lifecycleHooks ContainerLifecycleHooks) []ContainerHook) error {
|
||
|
errs := make([]error, len(c.lifecycleHooks))
|
||
|
for i, lifecycleHooks := range c.lifecycleHooks {
|
||
|
errs[i] = containerHookFn(ctx, hooks(lifecycleHooks))(c)
|
||
|
}
|
||
|
|
||
|
if err := errors.Join(errs...); err != nil {
|
||
|
if logError {
|
||
|
select {
|
||
|
case <-ctx.Done():
|
||
|
// Context has timed out so need a new context to get logs.
|
||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||
|
defer cancel()
|
||
|
c.printLogs(ctx, err)
|
||
|
default:
|
||
|
c.printLogs(ctx, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Creating is a hook that will be called before a container is created.
|
||
|
func (c ContainerLifecycleHooks) Creating(ctx context.Context) func(req ContainerRequest) error {
|
||
|
return func(req ContainerRequest) error {
|
||
|
for _, hook := range c.PreCreates {
|
||
|
if err := hook(ctx, req); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// containerHookFn is a helper function that will create a function to be returned by all the different
|
||
|
// container lifecycle hooks. The created function will iterate over all the hooks and call them one by one.
|
||
|
func containerHookFn(ctx context.Context, containerHook []ContainerHook) func(container Container) error {
|
||
|
return func(container Container) error {
|
||
|
errs := make([]error, len(containerHook))
|
||
|
for i, hook := range containerHook {
|
||
|
errs[i] = hook(ctx, container)
|
||
|
}
|
||
|
|
||
|
return errors.Join(errs...)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Created is a hook that will be called after a container is created
|
||
|
func (c ContainerLifecycleHooks) Created(ctx context.Context) func(container Container) error {
|
||
|
return containerHookFn(ctx, c.PostCreates)
|
||
|
}
|
||
|
|
||
|
// Starting is a hook that will be called before a container is started
|
||
|
func (c ContainerLifecycleHooks) Starting(ctx context.Context) func(container Container) error {
|
||
|
return containerHookFn(ctx, c.PreStarts)
|
||
|
}
|
||
|
|
||
|
// Started is a hook that will be called after a container is started
|
||
|
func (c ContainerLifecycleHooks) Started(ctx context.Context) func(container Container) error {
|
||
|
return containerHookFn(ctx, c.PostStarts)
|
||
|
}
|
||
|
|
||
|
// Readied is a hook that will be called after a container is ready
|
||
|
func (c ContainerLifecycleHooks) Readied(ctx context.Context) func(container Container) error {
|
||
|
return containerHookFn(ctx, c.PostReadies)
|
||
|
}
|
||
|
|
||
|
// Stopping is a hook that will be called before a container is stopped
|
||
|
func (c ContainerLifecycleHooks) Stopping(ctx context.Context) func(container Container) error {
|
||
|
return containerHookFn(ctx, c.PreStops)
|
||
|
}
|
||
|
|
||
|
// Stopped is a hook that will be called after a container is stopped
|
||
|
func (c ContainerLifecycleHooks) Stopped(ctx context.Context) func(container Container) error {
|
||
|
return containerHookFn(ctx, c.PostStops)
|
||
|
}
|
||
|
|
||
|
// Terminating is a hook that will be called before a container is terminated
|
||
|
func (c ContainerLifecycleHooks) Terminating(ctx context.Context) func(container Container) error {
|
||
|
return containerHookFn(ctx, c.PreTerminates)
|
||
|
}
|
||
|
|
||
|
// Terminated is a hook that will be called after a container is terminated
|
||
|
func (c ContainerLifecycleHooks) Terminated(ctx context.Context) func(container Container) error {
|
||
|
return containerHookFn(ctx, c.PostTerminates)
|
||
|
}
|
||
|
|
||
|
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.Config.ExposedPorts {
|
||
|
exposedPorts = append(exposedPorts, string(p))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
exposedPortSet, exposedPortMap, err := nat.ParsePortSpecs(exposedPorts)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
dockerInput.ExposedPorts = exposedPortSet
|
||
|
|
||
|
// only exposing those ports automatically if the container request exposes zero ports and the container does not run in a container network
|
||
|
if len(exposedPorts) == 0 && !hostConfig.NetworkMode.IsContainer() {
|
||
|
hostConfig.PortBindings = exposedPortMap
|
||
|
} else {
|
||
|
hostConfig.PortBindings = mergePortBindings(hostConfig.PortBindings, exposedPortMap, req.ExposedPorts)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// combineContainerHooks it returns just one ContainerLifecycle hook, as the result of combining
|
||
|
// the default hooks with the user-defined hooks. The function will loop over all the default hooks,
|
||
|
// storing each of the hooks in a slice, and then it will loop over all the user-defined hooks,
|
||
|
// appending or prepending them to the slice of hooks. The order of hooks is the following:
|
||
|
// - for Pre-hooks, always run the default hooks first, then append the user-defined hooks
|
||
|
// - for Post-hooks, always run the user-defined hooks first, then the default hooks
|
||
|
func combineContainerHooks(defaultHooks, userDefinedHooks []ContainerLifecycleHooks) ContainerLifecycleHooks {
|
||
|
preCreates := []ContainerRequestHook{}
|
||
|
postCreates := []ContainerHook{}
|
||
|
preStarts := []ContainerHook{}
|
||
|
postStarts := []ContainerHook{}
|
||
|
postReadies := []ContainerHook{}
|
||
|
preStops := []ContainerHook{}
|
||
|
postStops := []ContainerHook{}
|
||
|
preTerminates := []ContainerHook{}
|
||
|
postTerminates := []ContainerHook{}
|
||
|
|
||
|
for _, defaultHook := range defaultHooks {
|
||
|
preCreates = append(preCreates, defaultHook.PreCreates...)
|
||
|
preStarts = append(preStarts, defaultHook.PreStarts...)
|
||
|
preStops = append(preStops, defaultHook.PreStops...)
|
||
|
preTerminates = append(preTerminates, defaultHook.PreTerminates...)
|
||
|
}
|
||
|
|
||
|
// append the user-defined hooks after the default pre-hooks
|
||
|
// and because the post hooks are still empty, the user-defined post-hooks
|
||
|
// will be the first ones to be executed
|
||
|
for _, userDefinedHook := range userDefinedHooks {
|
||
|
preCreates = append(preCreates, userDefinedHook.PreCreates...)
|
||
|
postCreates = append(postCreates, userDefinedHook.PostCreates...)
|
||
|
preStarts = append(preStarts, userDefinedHook.PreStarts...)
|
||
|
postStarts = append(postStarts, userDefinedHook.PostStarts...)
|
||
|
postReadies = append(postReadies, userDefinedHook.PostReadies...)
|
||
|
preStops = append(preStops, userDefinedHook.PreStops...)
|
||
|
postStops = append(postStops, userDefinedHook.PostStops...)
|
||
|
preTerminates = append(preTerminates, userDefinedHook.PreTerminates...)
|
||
|
postTerminates = append(postTerminates, userDefinedHook.PostTerminates...)
|
||
|
}
|
||
|
|
||
|
// finally, append the default post-hooks
|
||
|
for _, defaultHook := range defaultHooks {
|
||
|
postCreates = append(postCreates, defaultHook.PostCreates...)
|
||
|
postStarts = append(postStarts, defaultHook.PostStarts...)
|
||
|
postReadies = append(postReadies, defaultHook.PostReadies...)
|
||
|
postStops = append(postStops, defaultHook.PostStops...)
|
||
|
postTerminates = append(postTerminates, defaultHook.PostTerminates...)
|
||
|
}
|
||
|
|
||
|
return ContainerLifecycleHooks{
|
||
|
PreCreates: preCreates,
|
||
|
PostCreates: postCreates,
|
||
|
PreStarts: preStarts,
|
||
|
PostStarts: postStarts,
|
||
|
PostReadies: postReadies,
|
||
|
PreStops: preStops,
|
||
|
PostStops: postStops,
|
||
|
PreTerminates: preTerminates,
|
||
|
PostTerminates: postTerminates,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func mergePortBindings(configPortMap, exposedPortMap nat.PortMap, exposedPorts []string) nat.PortMap {
|
||
|
if exposedPortMap == nil {
|
||
|
exposedPortMap = make(map[nat.Port][]nat.PortBinding)
|
||
|
}
|
||
|
|
||
|
mappedPorts := make(map[string]struct{}, len(exposedPorts))
|
||
|
for _, p := range exposedPorts {
|
||
|
p = strings.Split(p, "/")[0]
|
||
|
mappedPorts[p] = struct{}{}
|
||
|
}
|
||
|
|
||
|
for k, v := range configPortMap {
|
||
|
if _, ok := mappedPorts[k.Port()]; ok {
|
||
|
exposedPortMap[k] = v
|
||
|
}
|
||
|
}
|
||
|
return exposedPortMap
|
||
|
}
|
||
|
|
||
|
// 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
|
||
|
}
|
||
|
}
|