diff --git a/container.go b/container.go index b0f2273a30..dc63703f3a 100644 --- a/container.go +++ b/container.go @@ -23,6 +23,7 @@ import ( "github.com/moby/patternmatcher/ignorefile" tcexec "github.com/testcontainers/testcontainers-go/exec" + "github.com/testcontainers/testcontainers-go/internal/config" "github.com/testcontainers/testcontainers-go/internal/core" "github.com/testcontainers/testcontainers-go/log" "github.com/testcontainers/testcontainers-go/wait" @@ -176,7 +177,7 @@ func (c *ContainerRequest) sessionID() string { return sessionID } - return core.SessionID() + return config.Read().SessionID } // containerOptions functional options for a container diff --git a/docker.go b/docker.go index e20026c387..ff09e6207d 100644 --- a/docker.go +++ b/docker.go @@ -924,7 +924,7 @@ func (c *DockerContainer) connectReaper(ctx context.Context) error { return nil } - reaper, err := spawner.reaper(context.WithValue(ctx, core.DockerHostContextKey, c.provider.host), core.SessionID(), c.provider) + reaper, err := spawner.reaper(context.WithValue(ctx, core.DockerHostContextKey, c.provider.host), c.provider.config.SessionID, c.provider) if err != nil { return fmt.Errorf("reaper: %w", err) } diff --git a/docker_client.go b/docker_client.go index e9eea1efeb..9605356aa8 100644 --- a/docker_client.go +++ b/docker_client.go @@ -12,7 +12,9 @@ import ( "github.com/docker/docker/client" "github.com/testcontainers/testcontainers-go/internal" + "github.com/testcontainers/testcontainers-go/internal/config" "github.com/testcontainers/testcontainers-go/internal/core" + "github.com/testcontainers/testcontainers-go/internal/core/bootstrap" "github.com/testcontainers/testcontainers-go/log" ) @@ -20,6 +22,8 @@ import ( // It implements the SystemAPIClient interface in order to cache the docker info and reuse it. type DockerClient struct { *client.Client // client is embedded into our own client + + config config.Config } var ( @@ -82,8 +86,8 @@ func (c *DockerClient) Info(ctx context.Context) (system.Info, error) { internal.Version, core.MustExtractDockerHost(ctx), core.MustExtractDockerSocket(ctx), - core.SessionID(), - core.ProcessID(), + c.config.SessionID, + bootstrap.ProcessID(), ) return dockerInfo, nil @@ -122,6 +126,7 @@ func NewDockerClientWithOpts(ctx context.Context, opt ...client.Opt) (*DockerCli tcClient := DockerClient{ Client: dockerClient, + config: config.Read(), } if _, err = tcClient.Info(ctx); err != nil { diff --git a/generic.go b/generic.go index dc5ee1ccb1..900163e3ca 100644 --- a/generic.go +++ b/generic.go @@ -8,6 +8,7 @@ import ( "strings" "sync" + "github.com/testcontainers/testcontainers-go/internal/config" "github.com/testcontainers/testcontainers-go/internal/core" "github.com/testcontainers/testcontainers-go/log" ) @@ -109,7 +110,7 @@ type GenericProvider interface { // reaper is enabled, otherwise this is excluded to prevent resources being // incorrectly reaped. func GenericLabels() map[string]string { - return core.DefaultLabels(core.SessionID()) + return core.DefaultLabels(config.Read().SessionID) } // AddGenericLabels adds the generic labels to target. diff --git a/internal/config/config.go b/internal/config/config.go index dda7e282bc..4c61dca515 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -9,6 +9,8 @@ import ( "time" "github.com/magiconair/properties" + + "github.com/testcontainers/testcontainers-go/internal/core/bootstrap" ) const ReaperDefaultImage = "testcontainers/ryuk:0.12.0" @@ -52,6 +54,13 @@ type Config struct { // Environment variable: TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX HubImageNamePrefix string `properties:"hub.image.name.prefix,default="` + // SessionID is the ID of the testing session. + // Setting this value will preclude runs from creating more than one reaper. Therefore, + // changes to ryuk settings past its creation will be ignored. + // + // Environment variable: TESTCONTAINERS_SESSION_ID + SessionID string `properties:"session.id,default="` + // RyukDisabled is a flag to enable or disable the Garbage Collector. // Setting this to true will prevent testcontainers from automatically cleaning up // resources, which is particularly important in tests which timeout as they @@ -121,6 +130,13 @@ func read() Config { config.HubImageNamePrefix = hubImageNamePrefix } + sessionID := os.Getenv("TESTCONTAINERS_SESSION_ID") + if sessionID != "" { + config.SessionID = sessionID + } else if config.SessionID == "" { + config.SessionID = bootstrap.SessionID() + } + ryukPrivilegedEnv := os.Getenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED") if parseBool(ryukPrivilegedEnv) { config.RyukPrivileged = ryukPrivilegedEnv == "true" diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 591fcff11c..b86148d214 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -8,6 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go/internal/core/bootstrap" ) const ( @@ -21,6 +23,7 @@ const ( func resetTestEnv(t *testing.T) { t.Helper() t.Setenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", "") + t.Setenv("TESTCONTAINERS_SESSION_ID", "") t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "") t.Setenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED", "") t.Setenv("RYUK_VERBOSE", "") @@ -42,6 +45,7 @@ func TestReadConfig(t *testing.T) { config := Read() expected := Config{ + SessionID: bootstrap.SessionID(), RyukDisabled: true, Host: "", // docker socket is empty at the properties file } @@ -66,7 +70,9 @@ func TestReadTCConfig(t *testing.T) { config := read() - expected := Config{} + expected := Config{ + SessionID: bootstrap.SessionID(), + } assert.Equal(t, expected, config) }) @@ -76,6 +82,7 @@ func TestReadTCConfig(t *testing.T) { t.Setenv("USERPROFILE", "") // Windows support t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") t.Setenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", defaultHubPrefix) + t.Setenv("TESTCONTAINERS_SESSION_ID", "foo") t.Setenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED", "true") t.Setenv("RYUK_RECONNECTION_TIMEOUT", "13s") t.Setenv("RYUK_CONNECTION_TIMEOUT", "12s") @@ -84,6 +91,7 @@ func TestReadTCConfig(t *testing.T) { expected := Config{ HubImageNamePrefix: defaultHubPrefix, + SessionID: "foo", RyukDisabled: true, RyukPrivileged: true, Host: "", // docker socket is empty at the properties file @@ -101,7 +109,9 @@ func TestReadTCConfig(t *testing.T) { config := read() - expected := Config{} + expected := Config{ + SessionID: bootstrap.SessionID(), + } assert.Equal(t, expected, config) }) @@ -113,7 +123,10 @@ func TestReadTCConfig(t *testing.T) { t.Setenv("DOCKER_HOST", tcpDockerHost33293) config := read() - expected := Config{} // the config does not read DOCKER_HOST, that's why it's empty + expected := Config{ + Host: "", // the config does not read env var `DOCKER_HOST`, that's why `Host` it's empty + SessionID: bootstrap.SessionID(), + } assert.Equal(t, expected, config) }) @@ -124,6 +137,7 @@ func TestReadTCConfig(t *testing.T) { t.Setenv("USERPROFILE", tmpDir) // Windows support t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") t.Setenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", defaultHubPrefix) + t.Setenv("TESTCONTAINERS_SESSION_ID", "foo") t.Setenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED", "true") t.Setenv("RYUK_VERBOSE", "true") t.Setenv("RYUK_RECONNECTION_TIMEOUT", "13s") @@ -132,6 +146,7 @@ func TestReadTCConfig(t *testing.T) { config := read() expected := Config{ HubImageNamePrefix: defaultHubPrefix, + SessionID: "foo", RyukDisabled: true, RyukPrivileged: true, RyukVerbose: true, @@ -146,6 +161,7 @@ func TestReadTCConfig(t *testing.T) { defaultRyukConnectionTimeout := 60 * time.Second defaultRyukReconnectionTimeout := 10 * time.Second defaultConfig := Config{ + SessionID: bootstrap.SessionID(), RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, } @@ -161,6 +177,7 @@ func TestReadTCConfig(t *testing.T) { "docker.host = " + tcpDockerHost33293, map[string]string{}, Config{ + SessionID: bootstrap.SessionID(), Host: tcpDockerHost33293, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -173,6 +190,7 @@ func TestReadTCConfig(t *testing.T) { `, map[string]string{}, Config{ + SessionID: bootstrap.SessionID(), Host: tcpDockerHost4711, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -187,6 +205,7 @@ func TestReadTCConfig(t *testing.T) { `, map[string]string{}, Config{ + SessionID: bootstrap.SessionID(), Host: tcpDockerHost1234, TLSVerify: 1, RyukConnectionTimeout: defaultRyukConnectionTimeout, @@ -198,6 +217,7 @@ func TestReadTCConfig(t *testing.T) { "", map[string]string{}, Config{ + SessionID: bootstrap.SessionID(), RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, @@ -209,6 +229,7 @@ func TestReadTCConfig(t *testing.T) { `, map[string]string{}, Config{ + SessionID: bootstrap.SessionID(), Host: tcpDockerHost1234, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -219,6 +240,7 @@ func TestReadTCConfig(t *testing.T) { "docker.host=" + tcpDockerHost33293, map[string]string{}, Config{ + SessionID: bootstrap.SessionID(), Host: tcpDockerHost33293, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -238,6 +260,7 @@ func TestReadTCConfig(t *testing.T) { docker.cert.path=/tmp/certs`, map[string]string{}, Config{ + SessionID: bootstrap.SessionID(), Host: tcpDockerHost1234, CertPath: "/tmp/certs", RyukConnectionTimeout: defaultRyukConnectionTimeout, @@ -249,6 +272,7 @@ func TestReadTCConfig(t *testing.T) { `ryuk.disabled=true`, map[string]string{}, Config{ + SessionID: bootstrap.SessionID(), RyukDisabled: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -259,6 +283,7 @@ func TestReadTCConfig(t *testing.T) { `ryuk.container.privileged=true`, map[string]string{}, Config{ + SessionID: bootstrap.SessionID(), RyukPrivileged: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -270,6 +295,7 @@ func TestReadTCConfig(t *testing.T) { ryuk.reconnection.timeout=13s`, map[string]string{}, Config{ + SessionID: bootstrap.SessionID(), RyukReconnectionTimeout: 13 * time.Second, RyukConnectionTimeout: 12 * time.Second, }, @@ -282,6 +308,7 @@ func TestReadTCConfig(t *testing.T) { "RYUK_CONNECTION_TIMEOUT": "12s", }, Config{ + SessionID: bootstrap.SessionID(), RyukReconnectionTimeout: 13 * time.Second, RyukConnectionTimeout: 12 * time.Second, }, @@ -295,6 +322,7 @@ func TestReadTCConfig(t *testing.T) { "RYUK_CONNECTION_TIMEOUT": "12s", }, Config{ + SessionID: bootstrap.SessionID(), RyukReconnectionTimeout: 13 * time.Second, RyukConnectionTimeout: 12 * time.Second, }, @@ -304,6 +332,7 @@ func TestReadTCConfig(t *testing.T) { `ryuk.verbose=true`, map[string]string{}, Config{ + SessionID: bootstrap.SessionID(), RyukVerbose: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -316,6 +345,7 @@ func TestReadTCConfig(t *testing.T) { "TESTCONTAINERS_RYUK_DISABLED": "true", }, Config{ + SessionID: bootstrap.SessionID(), RyukDisabled: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -328,6 +358,7 @@ func TestReadTCConfig(t *testing.T) { "TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED": "true", }, Config{ + SessionID: bootstrap.SessionID(), RyukPrivileged: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -340,6 +371,7 @@ func TestReadTCConfig(t *testing.T) { "TESTCONTAINERS_RYUK_DISABLED": "true", }, Config{ + SessionID: bootstrap.SessionID(), RyukDisabled: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -352,6 +384,7 @@ func TestReadTCConfig(t *testing.T) { "TESTCONTAINERS_RYUK_DISABLED": "true", }, Config{ + SessionID: bootstrap.SessionID(), RyukDisabled: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -380,6 +413,7 @@ func TestReadTCConfig(t *testing.T) { "RYUK_VERBOSE": "true", }, Config{ + SessionID: bootstrap.SessionID(), RyukVerbose: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -392,6 +426,7 @@ func TestReadTCConfig(t *testing.T) { "RYUK_VERBOSE": "true", }, Config{ + SessionID: bootstrap.SessionID(), RyukVerbose: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -420,6 +455,7 @@ func TestReadTCConfig(t *testing.T) { "TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED": "true", }, Config{ + SessionID: bootstrap.SessionID(), RyukPrivileged: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -432,6 +468,7 @@ func TestReadTCConfig(t *testing.T) { "TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED": "true", }, Config{ + SessionID: bootstrap.SessionID(), RyukPrivileged: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -462,6 +499,7 @@ func TestReadTCConfig(t *testing.T) { "TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED": "true", }, Config{ + SessionID: bootstrap.SessionID(), RyukDisabled: true, RyukPrivileged: true, }, @@ -487,6 +525,7 @@ func TestReadTCConfig(t *testing.T) { `hub.image.name.prefix=` + defaultHubPrefix + `/props/`, map[string]string{}, Config{ + SessionID: bootstrap.SessionID(), HubImageNamePrefix: defaultHubPrefix + "/props/", RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -499,6 +538,7 @@ func TestReadTCConfig(t *testing.T) { "TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX": defaultHubPrefix + "/env/", }, Config{ + SessionID: bootstrap.SessionID(), HubImageNamePrefix: defaultHubPrefix + "/env/", RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, @@ -511,11 +551,35 @@ func TestReadTCConfig(t *testing.T) { "TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX": defaultHubPrefix + "/env/", }, Config{ + SessionID: bootstrap.SessionID(), HubImageNamePrefix: defaultHubPrefix + "/env/", RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, + // + { + "With Session ID set as a property", + `session.id=foo`, + map[string]string{}, + Config{ + SessionID: "foo", + RyukConnectionTimeout: defaultRyukConnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + }, + }, + { + "With Session ID set using an env var and properties. Env var wins", + `session.id=bar`, + map[string]string{ + "TESTCONTAINERS_SESSION_ID": "foo", + }, + Config{ + SessionID: "foo", + RyukConnectionTimeout: defaultRyukConnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/core/bootstrap.go b/internal/core/bootstrap/bootstrap.go similarity index 99% rename from internal/core/bootstrap.go rename to internal/core/bootstrap/bootstrap.go index d249d9be31..18758c7e13 100644 --- a/internal/core/bootstrap.go +++ b/internal/core/bootstrap/bootstrap.go @@ -1,4 +1,4 @@ -package core +package bootstrap import ( "crypto/sha256" diff --git a/internal/core/client.go b/internal/core/client.go index 04a54bcbc5..9b9bea7efe 100644 --- a/internal/core/client.go +++ b/internal/core/client.go @@ -8,6 +8,7 @@ import ( "github.com/testcontainers/testcontainers-go/internal" "github.com/testcontainers/testcontainers-go/internal/config" + "github.com/testcontainers/testcontainers-go/internal/core/bootstrap" ) // NewClient returns a new docker client extracting the docker host from the different alternatives @@ -32,8 +33,8 @@ func NewClient(ctx context.Context, ops ...client.Opt) (*client.Client, error) { opts = append(opts, client.WithHTTPHeaders( map[string]string{ - "x-tc-pp": ProjectPath(), - "x-tc-sid": SessionID(), + "x-tc-pp": bootstrap.ProjectPath(), + "x-tc-sid": tcConfig.SessionID, "User-Agent": "tc-go/" + internal.Version, }), ) diff --git a/network.go b/network.go index e0cc83f510..e1ae09396e 100644 --- a/network.go +++ b/network.go @@ -5,6 +5,7 @@ import ( "github.com/docker/docker/api/types/network" + "github.com/testcontainers/testcontainers-go/internal/config" "github.com/testcontainers/testcontainers-go/internal/core" ) @@ -56,5 +57,5 @@ func (r NetworkRequest) sessionID() string { return sessionID } - return core.SessionID() + return config.Read().SessionID } diff --git a/reaper_test.go b/reaper_test.go index 59c780fa3e..9417345d94 100644 --- a/reaper_test.go +++ b/reaper_test.go @@ -126,7 +126,7 @@ func testReaperRunning(t *testing.T) { t.Helper() ctx := context.Background() - sessionID := core.SessionID() + sessionID := config.Read().SessionID reaperContainer, err := spawner.lookupContainer(ctx, sessionID) require.NoError(t, err) require.NotNil(t, reaperContainer) diff --git a/testcontainers.go b/testcontainers.go index 77ba722c74..d3e1890735 100644 --- a/testcontainers.go +++ b/testcontainers.go @@ -3,6 +3,7 @@ package testcontainers import ( "context" + "github.com/testcontainers/testcontainers-go/internal/config" "github.com/testcontainers/testcontainers-go/internal/core" ) @@ -50,5 +51,5 @@ func MustExtractDockerSocket(ctx context.Context) string { // - identify the test session, aggregating the test execution of multiple packages in the same test session. // - tag the containers created by testcontainers-go, adding a label to the container with the session ID. func SessionID() string { - return core.SessionID() + return config.Read().SessionID }