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: restructure Docker helper methods #799

Merged
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
59 changes: 17 additions & 42 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"io"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
Expand All @@ -35,6 +34,7 @@ import (

tcexec "github.com/testcontainers/testcontainers-go/exec"
"github.com/testcontainers/testcontainers-go/internal/testcontainersdocker"
"github.com/testcontainers/testcontainers-go/internal/testcontainerssession"
"github.com/testcontainers/testcontainers-go/wait"
)

Expand Down Expand Up @@ -133,7 +133,7 @@ func (c *DockerContainer) PortEndpoint(ctx context.Context, port nat.Port, proto
// Warning: this is based on your Docker host setting. Will fail if using an SSH tunnel
// You can use the "TC_HOST" env variable to set this yourself
func (c *DockerContainer) Host(ctx context.Context) (string, error) {
host, err := c.provider.daemonHost(ctx)
host, err := c.provider.DaemonHost(ctx)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -782,7 +782,7 @@ func NewDockerClient() (cli *client.Client, host string, tcConfig TestContainers

opts = append(opts, client.WithHTTPHeaders(
map[string]string{
"x-tc-sid": sessionID().String(),
"x-tc-sid": testcontainerssession.String(),
}),
)

Expand Down Expand Up @@ -971,8 +971,6 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
req.Labels = make(map[string]string)
}

sessionID := sessionID()

reaperOpts := containerOptions{
ImageName: req.ReaperImage,
}
Expand All @@ -984,7 +982,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
// the reaper does not need to start a reaper for itself
isReaperContainer := strings.EqualFold(req.Image, reaperImage(reaperOpts.ImageName))
if !req.SkipReaper && !isReaperContainer {
r, err := reuseOrCreateReaper(context.WithValue(ctx, testcontainersdocker.DockerHostContextKey, p.host), sessionID.String(), p, req.ReaperOptions...)
r, err := reuseOrCreateReaper(context.WithValue(ctx, testcontainersdocker.DockerHostContextKey, p.host), testcontainerssession.String(), p, req.ReaperOptions...)
if err != nil {
return nil, fmt.Errorf("%w: creating reaper failed", err)
}
Expand Down Expand Up @@ -1154,7 +1152,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
WaitingFor: req.WaitingFor,
Image: tag,
imageWasBuilt: req.ShouldBuildImage(),
sessionID: sessionID,
sessionID: testcontainerssession.ID(),
provider: p,
terminationSignal: termSignal,
skipReaper: req.SkipReaper,
Expand Down Expand Up @@ -1198,10 +1196,9 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain
return p.CreateContainer(ctx, req)
}

sessionID := sessionID()
var termSignal chan bool
if !req.SkipReaper {
r, err := reuseOrCreateReaper(context.WithValue(ctx, testcontainersdocker.DockerHostContextKey, p.host), sessionID.String(), p, req.ReaperOptions...)
r, err := reuseOrCreateReaper(context.WithValue(ctx, testcontainersdocker.DockerHostContextKey, p.host), testcontainerssession.String(), p, req.ReaperOptions...)
if err != nil {
return nil, fmt.Errorf("%w: creating reaper failed", err)
}
Expand All @@ -1216,7 +1213,7 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain
ID: c.ID,
WaitingFor: req.WaitingFor,
Image: c.Image,
sessionID: sessionID,
sessionID: testcontainerssession.ID(),
provider: p,
terminationSignal: termSignal,
skipReaper: req.SkipReaper,
Expand Down Expand Up @@ -1283,10 +1280,14 @@ func (p *DockerProvider) Config() TestContainersConfig {
return p.config
}

// daemonHost gets the host or ip of the Docker daemon where ports are exposed on
// DaemonHost gets the host or ip of the Docker daemon where ports are exposed on
// Warning: this is based on your Docker host setting. Will fail if using an SSH tunnel
// You can use the "TC_HOST" env variable to set this yourself
func (p *DockerProvider) daemonHost(ctx context.Context) (string, error) {
func (p *DockerProvider) DaemonHost(ctx context.Context) (string, error) {
return daemonHost(ctx, p)
}

func daemonHost(ctx context.Context, p *DockerProvider) (string, error) {
if p.hostCache != "" {
return p.hostCache, nil
}
Expand All @@ -1307,11 +1308,10 @@ func (p *DockerProvider) daemonHost(ctx context.Context) (string, error) {
case "http", "https", "tcp":
p.hostCache = url.Hostname()
case "unix", "npipe":
if inAContainer() {
if testcontainersdocker.InAContainer() {
ip, err := p.GetGatewayIP(ctx)
if err != nil {
// fallback to getDefaultGatewayIP
ip, err = getDefaultGatewayIP()
ip, err = testcontainersdocker.DefaultGatewayIP()
if err != nil {
ip = "localhost"
}
Expand All @@ -1321,7 +1321,7 @@ func (p *DockerProvider) daemonHost(ctx context.Context) (string, error) {
p.hostCache = "localhost"
}
default:
return "", errors.New("Could not determine host through env or docker host")
return "", errors.New("could not determine host through env or docker host")
}

return p.hostCache, nil
Expand Down Expand Up @@ -1355,8 +1355,7 @@ func (p *DockerProvider) CreateNetwork(ctx context.Context, req NetworkRequest)

var termSignal chan bool
if !req.SkipReaper {
sessionID := sessionID()
r, err := reuseOrCreateReaper(context.WithValue(ctx, testcontainersdocker.DockerHostContextKey, p.host), sessionID.String(), p, req.ReaperOptions...)
r, err := reuseOrCreateReaper(context.WithValue(ctx, testcontainersdocker.DockerHostContextKey, p.host), testcontainerssession.String(), p, req.ReaperOptions...)
if err != nil {
return nil, fmt.Errorf("%w: creating network reaper failed", err)
}
Expand Down Expand Up @@ -1438,30 +1437,6 @@ func (p *DockerProvider) printReaperBanner(resource string) {
p.Logger.Printf(ryukDisabledMessage)
}

func inAContainer() bool {
// see https://github.com/testcontainers/testcontainers-java/blob/3ad8d80e2484864e554744a4800a81f6b7982168/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L15
if _, err := os.Stat("/.dockerenv"); err == nil {
return true
}
return false
}

// deprecated
// see https://github.com/testcontainers/testcontainers-java/blob/main/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L46
func getDefaultGatewayIP() (string, error) {
// see https://github.com/testcontainers/testcontainers-java/blob/3ad8d80e2484864e554744a4800a81f6b7982168/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L27
cmd := exec.Command("sh", "-c", "ip route|awk '/default/ { print $3 }'")
stdout, err := cmd.Output()
if err != nil {
return "", errors.New("Failed to detect docker host")
}
ip := strings.TrimSpace(string(stdout))
if len(ip) == 0 {
return "", errors.New("Failed to parse default gateway IP")
}
return ip, nil
}

func (p *DockerProvider) getDefaultNetwork(ctx context.Context, cli client.APIClient) (string, error) {
// Get list of available networks
networkResources, err := cli.NetworkList(ctx, types.NetworkListOptions{})
Expand Down
33 changes: 33 additions & 0 deletions internal/testcontainersdocker/docker_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,33 @@ package testcontainersdocker

import (
"context"
"errors"
"net/url"
"os"
"os/exec"
"strings"
)

type dockerHostContext string

var DockerHostContextKey = dockerHostContext("docker_host")

// deprecated
// see https://github.com/testcontainers/testcontainers-java/blob/main/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L46
func DefaultGatewayIP() (string, error) {
// see https://github.com/testcontainers/testcontainers-java/blob/3ad8d80e2484864e554744a4800a81f6b7982168/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L27
cmd := exec.Command("sh", "-c", "ip route|awk '/default/ { print $3 }'")
stdout, err := cmd.Output()
if err != nil {
return "", errors.New("failed to detect docker host")
}
ip := strings.TrimSpace(string(stdout))
if len(ip) == 0 {
return "", errors.New("failed to parse default gateway IP")
}
return ip, nil
}

// Extracts the docker host from the context, or returns the default value
func ExtractDockerHost(ctx context.Context) (dockerHostPath string) {
if dockerHostPath = os.Getenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE"); dockerHostPath != "" {
Expand Down Expand Up @@ -38,3 +57,17 @@ func ExtractDockerHost(ctx context.Context) (dockerHostPath string) {
return dockerHostPath
}
}

// InAContainer returns true if the code is running inside a container
// See https://github.com/docker/docker/blob/a9fa38b1edf30b23cae3eade0be48b3d4b1de14b/daemon/initlayer/setup_unix.go#L25
func InAContainer() bool {
return inAContainer("/.dockerenv")
}

func inAContainer(path string) bool {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making it testable

// see https://github.com/testcontainers/testcontainers-java/blob/3ad8d80e2484864e554744a4800a81f6b7982168/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L15
if _, err := os.Stat(path); err == nil {
return true
}
return false
}
21 changes: 21 additions & 0 deletions internal/testcontainersdocker/docker_host_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package testcontainersdocker

import (
"context"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -45,3 +47,22 @@ func Test_ExtractDockerHost(t *testing.T) {
assert.Equal(t, "/this/is/a/sample.sock", host)
})
}

func TestInAContainer(t *testing.T) {
const dockerenvName = ".dockerenv"

t.Run("file does not exist", func(t *testing.T) {
tmpDir := t.TempDir()

assert.False(t, inAContainer(filepath.Join(tmpDir, dockerenvName)))
})

t.Run("file exists", func(t *testing.T) {
tmpDir := t.TempDir()

f := filepath.Join(tmpDir, dockerenvName)
os.Create(f)

assert.True(t, inAContainer(f))
})
}
22 changes: 22 additions & 0 deletions internal/testcontainerssession/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package testcontainerssession

import (
"sync"

"github.com/google/uuid"
)

var id uuid.UUID
var idOnce sync.Once

func ID() uuid.UUID {
idOnce.Do(func() {
id = uuid.New()
})

return id
}

func String() string {
return ID().String()
}
18 changes: 0 additions & 18 deletions session.go

This file was deleted.