diff --git a/.github/workflows/ci-test-go.yml b/.github/workflows/ci-test-go.yml index a56dab64a4..36f1e6de4f 100644 --- a/.github/workflows/ci-test-go.yml +++ b/.github/workflows/ci-test-go.yml @@ -50,6 +50,8 @@ jobs: continue-on-error: ${{ !inputs.fail-fast }} env: TESTCONTAINERS_RYUK_DISABLED: "${{ inputs.ryuk-disabled }}" + RYUK_CONNECTION_TIMEOUT: "${{ inputs.project-directory == 'modules/compose' && '5m' || '60s' }}" + RYUK_RECONNECTION_TIMEOUT: "${{ inputs.project-directory == 'modules/compose' && '30s' || '10s' }}" steps: - name: Setup rootless Docker if: ${{ inputs.rootless-docker }} diff --git a/docker.go b/docker.go index 79f85b8f00..426232b229 100644 --- a/docker.go +++ b/docker.go @@ -98,6 +98,11 @@ func (c *DockerContainer) SetProvider(provider *DockerProvider) { c.provider = provider } +// SetTerminationSignal sets the termination signal for the container +func (c *DockerContainer) SetTerminationSignal(signal chan bool) { + c.terminationSignal = signal +} + func (c *DockerContainer) GetContainerID() string { return c.ID } @@ -846,6 +851,10 @@ func (n *DockerNetwork) Remove(ctx context.Context) error { return n.provider.client.NetworkRemove(ctx, n.ID) } +func (n *DockerNetwork) SetTerminationSignal(signal chan bool) { + n.terminationSignal = signal +} + // DockerProvider implements the ContainerProvider interface type DockerProvider struct { *DockerProviderOptions diff --git a/modules/compose/compose.go b/modules/compose/compose.go index 242caef501..94e2021b90 100644 --- a/modules/compose/compose.go +++ b/modules/compose/compose.go @@ -3,6 +3,7 @@ package compose import ( "context" "errors" + "fmt" "path/filepath" "runtime" "strings" @@ -121,6 +122,25 @@ func NewDockerComposeWith(opts ...ComposeStackOption) (*dockerCompose, error) { return nil, err } + reaperProvider, err := testcontainers.NewDockerProvider() + if err != nil { + return nil, fmt.Errorf("failed to create reaper provider for compose: %w", err) + } + + tcConfig := reaperProvider.Config() + + var composeReaper *testcontainers.Reaper + if !tcConfig.RyukDisabled { + // NewReaper is deprecated: we need to find a way to create the reaper for compose + // bypassing the deprecation. + r, err := testcontainers.NewReaper(context.Background(), testcontainers.SessionID(), reaperProvider, "") + if err != nil { + return nil, fmt.Errorf("failed to create reaper for compose: %w", err) + } + + composeReaper = r + } + composeAPI := &dockerCompose{ name: composeOptions.Identifier, configs: composeOptions.Paths, @@ -129,6 +149,9 @@ func NewDockerComposeWith(opts ...ComposeStackOption) (*dockerCompose, error) { dockerClient: dockerCli.Client(), waitStrategies: make(map[string]wait.Strategy), containers: make(map[string]*testcontainers.DockerContainer), + networks: make(map[string]*testcontainers.DockerNetwork), + sessionID: testcontainers.SessionID(), + reaper: composeReaper, } return composeAPI, nil diff --git a/modules/compose/compose_api.go b/modules/compose/compose_api.go index 409875537c..62a8061e97 100644 --- a/modules/compose/compose_api.go +++ b/modules/compose/compose_api.go @@ -11,6 +11,7 @@ import ( "github.com/compose-spec/compose-go/v2/types" "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/pkg/api" + dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" @@ -134,6 +135,9 @@ type dockerCompose struct { // used in ServiceContainer(...) function to avoid calls to the Docker API containers map[string]*testcontainers.DockerContainer + // cache for networks in the compose stack + networks map[string]*testcontainers.DockerNetwork + // docker/compose API service instance used to control the compose stack composeService api.Service @@ -147,6 +151,12 @@ type dockerCompose struct { // compiled compose project // can be nil if the stack wasn't started yet project *types.Project + + // sessionID is used to identify the reaper session + sessionID string + + // reaper is used to clean up containers after the stack is stopped + reaper *testcontainers.Reaper } func (d *dockerCompose) ServiceContainer(ctx context.Context, svcName string) (*testcontainers.DockerContainer, error) { @@ -235,26 +245,89 @@ func (d *dockerCompose) Up(ctx context.Context, opts ...StackUpOption) error { return err } + err = d.lookupNetworks(ctx) + if err != nil { + return err + } + + if d.reaper != nil { + for _, n := range d.networks { + termSignal, err := d.reaper.Connect() + if err != nil { + return fmt.Errorf("failed to connect to reaper: %w", err) + } + n.SetTerminationSignal(termSignal) + + // Cleanup on error, otherwise set termSignal to nil before successful return. + defer func() { + if termSignal != nil { + termSignal <- true + } + }() + } + } + + errGrpContainers, errGrpCtx := errgroup.WithContext(ctx) + + for _, srv := range d.project.Services { + // we are going to connect each container to the reaper + srv := srv + errGrpContainers.Go(func() error { + dc, err := d.lookupContainer(errGrpCtx, srv.Name) + if err != nil { + return err + } + + if d.reaper != nil { + termSignal, err := d.reaper.Connect() + if err != nil { + return fmt.Errorf("failed to connect to reaper: %w", err) + } + dc.SetTerminationSignal(termSignal) + + // Cleanup on error, otherwise set termSignal to nil before successful return. + defer func() { + if termSignal != nil { + termSignal <- true + } + }() + } + + d.containers[srv.Name] = dc + + return nil + }) + } + + // wait here for the containers lookup to finish + if err := errGrpContainers.Wait(); err != nil { + return err + } + if len(d.waitStrategies) == 0 { return nil } - errGrp, errGrpCtx := errgroup.WithContext(ctx) + errGrpWait, errGrpCtx := errgroup.WithContext(ctx) for svc, strategy := range d.waitStrategies { // pinning the variables svc := svc strategy := strategy - errGrp.Go(func() error { + errGrpWait.Go(func() error { target, err := d.lookupContainer(errGrpCtx, svc) if err != nil { return err } + + // cache all the containers on compose.up + d.containers[svc] = target + return strategy.WaitUntilReady(errGrpCtx, target) }) } - return errGrp.Wait() + return errGrpWait.Wait() } func (d *dockerCompose) WaitForService(s string, strategy wait.Strategy) ComposeStack { @@ -327,6 +400,34 @@ func (d *dockerCompose) lookupContainer(ctx context.Context, svcName string) (*t return container, nil } +func (d *dockerCompose) lookupNetworks(ctx context.Context) error { + d.containersLock.Lock() + defer d.containersLock.Unlock() + + listOptions := dockertypes.NetworkListOptions{ + Filters: filters.NewArgs( + filters.Arg("label", fmt.Sprintf("%s=%s", api.ProjectLabel, d.name)), + ), + } + + networks, err := d.dockerClient.NetworkList(ctx, listOptions) + if err != nil { + return err + } + + for _, n := range networks { + dn := &testcontainers.DockerNetwork{ + ID: n.ID, + Name: n.Name, + Driver: n.Driver, + } + + d.networks[n.ID] = dn + } + + return nil +} + func (d *dockerCompose) compileProject(ctx context.Context) (*types.Project, error) { const nameAndDefaultConfigPath = 2 projectOptions := make([]cli.ProjectOptionsFn, len(d.projectOptions), len(d.projectOptions)+nameAndDefaultConfigPath) @@ -353,6 +454,11 @@ func (d *dockerCompose) compileProject(ctx context.Context) (*types.Project, err api.ConfigFilesLabel: strings.Join(proj.ComposeFiles, ","), api.OneoffLabel: "False", // default, will be overridden by `run` command } + + for k, label := range testcontainers.GenericLabels() { + s.CustomLabels[k] = label + } + for i, envFile := range compiledOptions.EnvFiles { // add a label for each env file, indexed by its position s.CustomLabels[fmt.Sprintf("%s.%d", api.EnvironmentFileLabel, i)] = envFile @@ -361,6 +467,20 @@ func (d *dockerCompose) compileProject(ctx context.Context) (*types.Project, err proj.Services[i] = s } + for key, n := range proj.Networks { + n.Labels = map[string]string{ + api.ProjectLabel: proj.Name, + api.NetworkLabel: n.Name, + api.VersionLabel: api.ComposeVersion, + } + + for k, label := range testcontainers.GenericLabels() { + n.Labels[k] = label + } + + proj.Networks[key] = n + } + return proj, nil } diff --git a/modules/compose/compose_api_test.go b/modules/compose/compose_api_test.go index b639b87eab..0cf9fcf4f1 100644 --- a/modules/compose/compose_api_test.go +++ b/modules/compose/compose_api_test.go @@ -15,18 +15,12 @@ import ( "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/internal/config" "github.com/testcontainers/testcontainers-go/wait" ) -const ( - simpleCompose = "docker-compose-simple.yml" - complexCompose = "docker-compose-complex.yml" - composeWithVolume = "docker-compose-volume.yml" - testdataPackage = "testdata" -) - func TestDockerComposeAPI(t *testing.T) { - path := filepath.Join(testdataPackage, simpleCompose) + path, _ := RenderComposeSimple(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -41,7 +35,7 @@ func TestDockerComposeAPI(t *testing.T) { } func TestDockerComposeAPIStrategyForInvalidService(t *testing.T) { - path := filepath.Join(testdataPackage, simpleCompose) + path, _ := RenderComposeSimple(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -54,20 +48,20 @@ func TestDockerComposeAPIStrategyForInvalidService(t *testing.T) { err = compose. // Appending with _1 as given in the Java Test-Containers Example - WaitForService("mysql-1", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + WaitForService("non-existent-srv-1", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Up(ctx, Wait(true)) require.Error(t, err, "Expected error to be thrown because service with wait strategy is not running") - require.Equal(t, "no container found for service name mysql-1", err.Error()) + require.Equal(t, "no container found for service name non-existent-srv-1", err.Error()) serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") } func TestDockerComposeAPIWithWaitLogStrategy(t *testing.T) { - path := filepath.Join(testdataPackage, complexCompose) + path, _ := RenderComposeComplex(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -79,7 +73,7 @@ func TestDockerComposeAPIWithWaitLogStrategy(t *testing.T) { t.Cleanup(cancel) err = compose. - WaitForService("mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") @@ -87,12 +81,12 @@ func TestDockerComposeAPIWithWaitLogStrategy(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "nginx") - assert.Contains(t, serviceNames, "mysql") + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPIWithRunServices(t *testing.T) { - path := filepath.Join(testdataPackage, complexCompose) + path, _ := RenderComposeComplex(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -104,22 +98,121 @@ func TestDockerComposeAPIWithRunServices(t *testing.T) { t.Cleanup(cancel) err = compose. - WaitForService("nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). - Up(ctx, Wait(true), RunServices("nginx")) + WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + Up(ctx, Wait(true), RunServices("api-nginx")) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - _, err = compose.ServiceContainer(context.Background(), "mysql") + _, err = compose.ServiceContainer(context.Background(), "api-mysql") require.Error(t, err, "Make sure there is no mysql container") assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") +} + +func TestDockerComposeAPI_TestcontainersLabelsArePresent(t *testing.T) { + path, _ := RenderComposeComplex(t) + compose, err := NewDockerCompose(path) + require.NoError(t, err, "NewDockerCompose()") + + t.Cleanup(func() { + require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") + }) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err = compose. + WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + Up(ctx, Wait(true)) + + require.NoError(t, err, "compose.Up()") + + serviceNames := compose.Services() + + assert.Len(t, serviceNames, 2) + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") + + // all the services in the compose has the Testcontainers Labels + for _, serviceName := range serviceNames { + c, err := compose.ServiceContainer(context.Background(), serviceName) + require.NoError(t, err, "compose.ServiceContainer()") + + inspect, err := compose.dockerClient.ContainerInspect(ctx, c.GetContainerID()) + require.NoError(t, err, "dockerClient.ContainerInspect()") + + for key, label := range testcontainers.GenericLabels() { + assert.Contains(t, inspect.Config.Labels, key, "Label %s is not present in container %s", key, c.GetContainerID()) + assert.Equal(t, label, inspect.Config.Labels[key], "Label %s value is not correct in container %s", key, c.GetContainerID()) + } + } +} + +func TestDockerComposeAPI_WithReaper(t *testing.T) { + config.Reset() // reset the config using the internal method to avoid the sync.Once + tcConfig := config.Read() + if tcConfig.RyukDisabled { + t.Skip("Ryuk is disabled, skipping test") + } + + path, _ := RenderComposeComplex(t) + compose, err := NewDockerCompose(path) + require.NoError(t, err, "NewDockerCompose()") + + // reaper is enabled, so we don't need to manually stop the containers: Ryuk will do it for us + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err = compose. + WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + Up(ctx, Wait(true)) + + require.NoError(t, err, "compose.Up()") + + serviceNames := compose.Services() + + assert.Len(t, serviceNames, 2) + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") +} + +func TestDockerComposeAPI_WithoutReaper(t *testing.T) { + config.Reset() // reset the config using the internal method to avoid the sync.Once + tcConfig := config.Read() + if !tcConfig.RyukDisabled { + t.Skip("Ryuk is enabled, skipping test") + } + + path, _ := RenderComposeComplex(t) + compose, err := NewDockerCompose(path) + require.NoError(t, err, "NewDockerCompose()") + t.Cleanup(func() { + // because reaper is disabled, we need to manually stop the containers + require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") + }) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err = compose. + WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + Up(ctx, Wait(true)) + + require.NoError(t, err, "compose.Up()") + + serviceNames := compose.Services() + + assert.Len(t, serviceNames, 2) + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPIWithStopServices(t *testing.T) { - path := filepath.Join(testdataPackage, complexCompose) + path, _ := RenderComposeComplex(t) compose, err := NewDockerComposeWith( WithStackFiles(path), WithLogger(testcontainers.TestLogger(t))) @@ -137,11 +230,11 @@ func TestDockerComposeAPIWithStopServices(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "nginx") - assert.Contains(t, serviceNames, "mysql") + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") // close mysql container in purpose - mysqlContainer, err := compose.ServiceContainer(context.Background(), "mysql") + mysqlContainer, err := compose.ServiceContainer(context.Background(), "api-mysql") require.NoError(t, err, "Get mysql container") stopTimeout := 10 * time.Second @@ -152,11 +245,11 @@ func TestDockerComposeAPIWithStopServices(t *testing.T) { state, err := mysqlContainer.State(ctx) require.NoError(t, err) assert.False(t, state.Running) - assert.Equal(t, "exited", state.Status) + assert.Contains(t, []string{"exited", "removing"}, state.Status) } func TestDockerComposeAPIWithWaitForService(t *testing.T) { - path := filepath.Join(testdataPackage, simpleCompose) + path, _ := RenderComposeSimple(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -171,7 +264,7 @@ func TestDockerComposeAPIWithWaitForService(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WaitForService("nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") @@ -179,11 +272,11 @@ func TestDockerComposeAPIWithWaitForService(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") } func TestDockerComposeAPIWithWaitHTTPStrategy(t *testing.T) { - path := filepath.Join(testdataPackage, simpleCompose) + path, _ := RenderComposeSimple(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -198,7 +291,7 @@ func TestDockerComposeAPIWithWaitHTTPStrategy(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WaitForService("nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") @@ -206,11 +299,11 @@ func TestDockerComposeAPIWithWaitHTTPStrategy(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") } func TestDockerComposeAPIWithContainerName(t *testing.T) { - path := filepath.Join(testdataPackage, "docker-compose-container-name.yml") + path := RenderComposeWithName(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -225,7 +318,7 @@ func TestDockerComposeAPIWithContainerName(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WaitForService("nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") @@ -233,11 +326,11 @@ func TestDockerComposeAPIWithContainerName(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") } func TestDockerComposeAPIWithWaitStrategy_NoExposedPorts(t *testing.T) { - path := filepath.Join(testdataPackage, "docker-compose-no-exposed-ports.yml") + path := RenderComposeWithoutExposedPorts(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -249,7 +342,7 @@ func TestDockerComposeAPIWithWaitStrategy_NoExposedPorts(t *testing.T) { t.Cleanup(cancel) err = compose. - WaitForService("nginx", wait.ForLog("Configuration complete; ready for start up")). + WaitForService("api-nginx", wait.ForLog("Configuration complete; ready for start up")). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") @@ -257,11 +350,11 @@ func TestDockerComposeAPIWithWaitStrategy_NoExposedPorts(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") } func TestDockerComposeAPIWithMultipleWaitStrategies(t *testing.T) { - path := filepath.Join(testdataPackage, complexCompose) + path, _ := RenderComposeComplex(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -273,8 +366,8 @@ func TestDockerComposeAPIWithMultipleWaitStrategies(t *testing.T) { t.Cleanup(cancel) err = compose. - WaitForService("mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second)). - WaitForService("nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second)). + WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") @@ -282,12 +375,12 @@ func TestDockerComposeAPIWithMultipleWaitStrategies(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "nginx") - assert.Contains(t, serviceNames, "mysql") + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPIWithFailedStrategy(t *testing.T) { - path := filepath.Join(testdataPackage, simpleCompose) + path, _ := RenderComposeSimple(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -302,7 +395,7 @@ func TestDockerComposeAPIWithFailedStrategy(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WaitForService("nginx_1", wait.NewHTTPStrategy("/").WithPort("8080/tcp").WithStartupTimeout(5*time.Second)). + WaitForService("api-nginx_1", wait.NewHTTPStrategy("/").WithPort("8080/tcp").WithStartupTimeout(5*time.Second)). Up(ctx, Wait(true)) // Verify that an error is thrown and not nil @@ -312,11 +405,11 @@ func TestDockerComposeAPIWithFailedStrategy(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") } func TestDockerComposeAPIComplex(t *testing.T) { - path := filepath.Join(testdataPackage, complexCompose) + path, _ := RenderComposeComplex(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -332,14 +425,14 @@ func TestDockerComposeAPIComplex(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "nginx") - assert.Contains(t, serviceNames, "mysql") + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPIWithEnvironment(t *testing.T) { identifier := testNameHash(t.Name()) - path := filepath.Join(testdataPackage, simpleCompose) + path, _ := RenderComposeSimple(t) compose, err := NewDockerComposeWith(WithStackFiles(path), identifier) require.NoError(t, err, "NewDockerCompose()") @@ -361,20 +454,21 @@ func TestDockerComposeAPIWithEnvironment(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") present := map[string]string{ "bar": "BAR", } absent := map[string]string{} - assertContainerEnvironmentVariables(t, identifier.String(), "nginx", present, absent) + assertContainerEnvironmentVariables(t, identifier.String(), "api-nginx", present, absent) } func TestDockerComposeAPIWithMultipleComposeFiles(t *testing.T) { + simple, _ := RenderComposeSimple(t) composeFiles := ComposeStackFiles{ - filepath.Join(testdataPackage, simpleCompose), - filepath.Join(testdataPackage, "docker-compose-postgres.yml"), - filepath.Join(testdataPackage, "docker-compose-override.yml"), + simple, + RenderComposePostgres(t), + RenderComposeOverride(t), } identifier := testNameHash(t.Name()) @@ -400,20 +494,20 @@ func TestDockerComposeAPIWithMultipleComposeFiles(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 3) - assert.Contains(t, serviceNames, "nginx") - assert.Contains(t, serviceNames, "mysql") - assert.Contains(t, serviceNames, "postgres") + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") + assert.Contains(t, serviceNames, "api-postgres") present := map[string]string{ "bar": "BAR", "foo": "FOO", } absent := map[string]string{} - assertContainerEnvironmentVariables(t, identifier.String(), "nginx", present, absent) + assertContainerEnvironmentVariables(t, identifier.String(), "api-nginx", present, absent) } func TestDockerComposeAPIWithVolume(t *testing.T) { - path := filepath.Join(testdataPackage, composeWithVolume) + path := RenderComposeWithVolume(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -429,7 +523,7 @@ func TestDockerComposeAPIWithVolume(t *testing.T) { } func TestDockerComposeAPIVolumesDeletedOnDown(t *testing.T) { - path := filepath.Join(testdataPackage, composeWithVolume) + path := RenderComposeWithVolume(t) identifier := uuid.New().String() stackFiles := WithStackFiles(path) compose, err := NewDockerComposeWith(stackFiles, StackIdentifier(identifier)) @@ -456,7 +550,7 @@ func TestDockerComposeAPIVolumesDeletedOnDown(t *testing.T) { func TestDockerComposeAPIWithBuild(t *testing.T) { t.Skip("Skipping test because of the opentelemetry dependencies issue. See https://github.com/open-telemetry/opentelemetry-go/issues/4476#issuecomment-1840547010") - path := filepath.Join(testdataPackage, "docker-compose-build.yml") + path := RenderComposeWithBuild(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -468,7 +562,7 @@ func TestDockerComposeAPIWithBuild(t *testing.T) { t.Cleanup(cancel) err = compose. - WaitForService("echo", wait.ForHTTP("/env").WithPort("8080/tcp")). + WaitForService("api-echo", wait.ForHTTP("/env").WithPort("8080/tcp")). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") diff --git a/modules/compose/compose_builder_test.go b/modules/compose/compose_builder_test.go new file mode 100644 index 0000000000..fbfe37baa3 --- /dev/null +++ b/modules/compose/compose_builder_test.go @@ -0,0 +1,169 @@ +package compose + +import ( + "fmt" + "html/template" + "io" + "net" + "os" + "path/filepath" + "testing" +) + +const ( + testdataPackage = "testdata" +) + +func RenderComposeComplex(t *testing.T) (string, []int) { + t.Helper() + + ports := []int{getFreePort(t), getFreePort(t)} + + return writeTemplate(t, "docker-compose-complex.yml", ports...), ports +} + +func RenderComposeComplexForLocal(t *testing.T) (string, []int) { + t.Helper() + + ports := []int{getFreePort(t), getFreePort(t)} + + return writeTemplateWithSrvType(t, "docker-compose-complex.yml", "local", ports...), ports +} + +func RenderComposeOverride(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-override.yml", getFreePort(t)) +} + +func RenderComposeOverrideForLocal(t *testing.T) string { + t.Helper() + + return writeTemplateWithSrvType(t, "docker-compose-override.yml", "local", getFreePort(t)) +} + +func RenderComposePostgres(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-postgres.yml", getFreePort(t)) +} + +func RenderComposePostgresForLocal(t *testing.T) string { + t.Helper() + + return writeTemplateWithSrvType(t, "docker-compose-postgres.yml", "local", getFreePort(t)) +} + +func RenderComposeSimple(t *testing.T) (string, []int) { + t.Helper() + + ports := []int{getFreePort(t)} + return writeTemplate(t, "docker-compose-simple.yml", ports...), ports +} + +func RenderComposeSimpleForLocal(t *testing.T) (string, []int) { + t.Helper() + + ports := []int{getFreePort(t)} + return writeTemplateWithSrvType(t, "docker-compose-simple.yml", "local", ports...), ports +} + +func RenderComposeWithBuild(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-build.yml", getFreePort(t)) +} + +func RenderComposeWithName(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-container-name.yml", getFreePort(t)) +} + +func RenderComposeWithNameForLocal(t *testing.T) string { + t.Helper() + + return writeTemplateWithSrvType(t, "docker-compose-container-name.yml", "local", getFreePort(t)) +} + +func RenderComposeWithoutExposedPorts(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-no-exposed-ports.yml") +} + +func RenderComposeWithoutExposedPortsForLocal(t *testing.T) string { + t.Helper() + + return writeTemplateWithSrvType(t, "docker-compose-no-exposed-ports.yml", "local") +} + +func RenderComposeWithVolume(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-volume.yml", getFreePort(t)) +} + +func RenderComposeWithVolumeForLocal(t *testing.T) string { + t.Helper() + + return writeTemplateWithSrvType(t, "docker-compose-volume.yml", "local", getFreePort(t)) +} + +// getFreePort asks the kernel for a free open port that is ready to use. +func getFreePort(t *testing.T) int { + t.Helper() + + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + t.Fatalf("failed to resolve TCP address: %v", err) + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + t.Fatalf("failed to listen on TCP address: %v", err) + } + defer l.Close() + + return l.Addr().(*net.TCPAddr).Port +} + +func writeTemplate(t *testing.T, templateFile string, port ...int) string { + return writeTemplateWithSrvType(t, templateFile, "api", port...) +} + +func writeTemplateWithSrvType(t *testing.T, templateFile string, srvType string, port ...int) string { + t.Helper() + + tmpDir := t.TempDir() + composeFile := filepath.Join(tmpDir, "docker-compose.yml") + + tmpl, err := template.ParseFiles(filepath.Join(testdataPackage, templateFile)) + if err != nil { + t.Fatalf("parsing template file: %s", err) + } + + values := map[string]interface{}{} + for i, p := range port { + values[fmt.Sprintf("Port_%d", i)] = p + } + + values["ServiceType"] = srvType + + output, err := os.Create(composeFile) + if err != nil { + t.Fatalf("creating output file: %s", err) + } + defer output.Close() + + executeTemplateFile := func(templateFile *template.Template, wr io.Writer, data any) error { + return templateFile.Execute(wr, data) + } + + err = executeTemplateFile(tmpl, output, values) + if err != nil { + t.Fatalf("executing template file: %s", err) + } + + return composeFile +} diff --git a/modules/compose/compose_test.go b/modules/compose/compose_test.go index c87e204725..2453507b06 100644 --- a/modules/compose/compose_test.go +++ b/modules/compose/compose_test.go @@ -20,11 +20,6 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) -var ( - complexComposeTestFile string = filepath.Join("testdata", "docker-compose-complex.yml") - simpleComposeTestFile string = filepath.Join("testdata", "docker-compose-simple.yml") -) - func ExampleNewLocalDockerCompose() { path := "/path/to/docker-compose.yml" @@ -103,7 +98,7 @@ func ExampleLocalDockerCompose_WithEnv() { } func TestLocalDockerCompose(t *testing.T) { - path := simpleComposeTestFile + path, _ := RenderComposeSimpleForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -120,8 +115,8 @@ func TestLocalDockerCompose(t *testing.T) { checkIfError(t, err) } -func TestDockerComposeStrategyForInvalidService(t *testing.T) { - path := simpleComposeTestFile +func TestLocalDockerComposeStrategyForInvalidService(t *testing.T) { + path, ports := RenderComposeSimpleForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -135,16 +130,16 @@ func TestDockerComposeStrategyForInvalidService(t *testing.T) { err := compose. WithCommand([]string{"up", "-d"}). // Appending with _1 as given in the Java Test-Containers Example - WithExposedService(compose.Format("mysql", "1"), 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + WithExposedService(compose.Format("non-existent-srv", "1"), ports[0], wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Invoke() require.Error(t, err.Error, "Expected error to be thrown because service with wait strategy is not running") assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") } -func TestDockerComposeWithWaitLogStrategy(t *testing.T) { - path := complexComposeTestFile +func TestLocalDockerComposeWithWaitLogStrategy(t *testing.T) { + path, _ := RenderComposeComplexForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -158,17 +153,17 @@ func TestDockerComposeWithWaitLogStrategy(t *testing.T) { err := compose. WithCommand([]string{"up", "-d"}). // Appending with _1 as given in the Java Test-Containers Example - WithExposedService(compose.Format("mysql", "1"), 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + WithExposedService(compose.Format("local-mysql", "1"), 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Invoke() checkIfError(t, err) assert.Len(t, compose.Services, 2) - assert.Contains(t, compose.Services, "nginx") - assert.Contains(t, compose.Services, "mysql") + assert.Contains(t, compose.Services, "local-nginx") + assert.Contains(t, compose.Services, "local-mysql") } -func TestDockerComposeWithWaitForService(t *testing.T) { - path := simpleComposeTestFile +func TestLocalDockerComposeWithWaitForService(t *testing.T) { + path, _ := RenderComposeSimpleForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -184,16 +179,16 @@ func TestDockerComposeWithWaitForService(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WaitForService(compose.Format("nginx", "1"), wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WaitForService(compose.Format("local-nginx", "1"), wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Invoke() checkIfError(t, err) assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") } -func TestDockerComposeWithWaitForShortLifespanService(t *testing.T) { - path := filepath.Join("testdata", "docker-compose-short-lifespan.yml") +func TestLocalDockerComposeWithWaitForShortLifespanService(t *testing.T) { + path := filepath.Join(testdataPackage, "docker-compose-short-lifespan.yml") identifier := strings.ToLower(uuid.New().String()) @@ -217,8 +212,8 @@ func TestDockerComposeWithWaitForShortLifespanService(t *testing.T) { assert.Contains(t, compose.Services, "tzatziki") } -func TestDockerComposeWithWaitHTTPStrategy(t *testing.T) { - path := simpleComposeTestFile +func TestLocalDockerComposeWithWaitHTTPStrategy(t *testing.T) { + path, ports := RenderComposeSimpleForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -234,16 +229,16 @@ func TestDockerComposeWithWaitHTTPStrategy(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WithExposedService(compose.Format("nginx", "1"), 9080, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WithExposedService(compose.Format("local-nginx", "1"), ports[0], wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Invoke() checkIfError(t, err) assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") } -func TestDockerComposeWithContainerName(t *testing.T) { - path := filepath.Join("testdata", "docker-compose-container-name.yml") +func TestLocalDockerComposeWithContainerName(t *testing.T) { + path := RenderComposeWithNameForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -259,16 +254,16 @@ func TestDockerComposeWithContainerName(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WithExposedService("nginxy", 9080, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WithExposedService("local-nginxy", 9080, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Invoke() checkIfError(t, err) assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") } -func TestDockerComposeWithWaitStrategy_NoExposedPorts(t *testing.T) { - path := filepath.Join("testdata", "docker-compose-no-exposed-ports.yml") +func TestLocalDockerComposeWithWaitStrategy_NoExposedPorts(t *testing.T) { + path := RenderComposeWithoutExposedPortsForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -281,16 +276,16 @@ func TestDockerComposeWithWaitStrategy_NoExposedPorts(t *testing.T) { err := compose. WithCommand([]string{"up", "-d"}). - WithExposedService(compose.Format("nginx", "1"), 9080, wait.ForLog("Configuration complete; ready for start up")). + WithExposedService(compose.Format("local-nginx", "1"), 9080, wait.ForLog("Configuration complete; ready for start up")). Invoke() checkIfError(t, err) assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") } -func TestDockerComposeWithMultipleWaitStrategies(t *testing.T) { - path := complexComposeTestFile +func TestLocalDockerComposeWithMultipleWaitStrategies(t *testing.T) { + path, _ := RenderComposeComplexForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -303,18 +298,18 @@ func TestDockerComposeWithMultipleWaitStrategies(t *testing.T) { err := compose. WithCommand([]string{"up", "-d"}). - WithExposedService(compose.Format("mysql", "1"), 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second)). - WithExposedService(compose.Format("nginx", "1"), 9080, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WithExposedService(compose.Format("local-mysql", "1"), 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second)). + WithExposedService(compose.Format("local-nginx", "1"), 9080, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Invoke() checkIfError(t, err) assert.Len(t, compose.Services, 2) - assert.Contains(t, compose.Services, "nginx") - assert.Contains(t, compose.Services, "mysql") + assert.Contains(t, compose.Services, "local-nginx") + assert.Contains(t, compose.Services, "local-mysql") } -func TestDockerComposeWithFailedStrategy(t *testing.T) { - path := simpleComposeTestFile +func TestLocalDockerComposeWithFailedStrategy(t *testing.T) { + path, ports := RenderComposeSimpleForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -330,18 +325,18 @@ func TestDockerComposeWithFailedStrategy(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WithExposedService("nginx_1", 9080, wait.NewHTTPStrategy("/").WithPort("8080/tcp").WithStartupTimeout(5*time.Second)). + WithExposedService("local-nginx_1", ports[0], wait.NewHTTPStrategy("/").WithPort("8080/tcp").WithStartupTimeout(5*time.Second)). Invoke() // Verify that an error is thrown and not nil // A specific error message matcher is not asserted since the docker library can change the return message, breaking this test require.Error(t, err.Error, "Expected error to be thrown because of a wrong suplied wait strategy") assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") } func TestLocalDockerComposeComplex(t *testing.T) { - path := complexComposeTestFile + path, _ := RenderComposeComplexForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -358,12 +353,12 @@ func TestLocalDockerComposeComplex(t *testing.T) { checkIfError(t, err) assert.Len(t, compose.Services, 2) - assert.Contains(t, compose.Services, "nginx") - assert.Contains(t, compose.Services, "mysql") + assert.Contains(t, compose.Services, "local-nginx") + assert.Contains(t, compose.Services, "local-mysql") } func TestLocalDockerComposeWithEnvironment(t *testing.T) { - path := simpleComposeTestFile + path, _ := RenderComposeSimpleForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -383,20 +378,21 @@ func TestLocalDockerComposeWithEnvironment(t *testing.T) { checkIfError(t, err) assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") present := map[string]string{ "bar": "BAR", } absent := map[string]string{} - assertContainerEnvironmentVariables(t, compose.Identifier, "nginx", present, absent) + assertContainerEnvironmentVariables(t, compose.Identifier, "local-nginx", present, absent) } func TestLocalDockerComposeWithMultipleComposeFiles(t *testing.T) { + simple, _ := RenderComposeSimpleForLocal(t) composeFiles := []string{ - simpleComposeTestFile, - filepath.Join("testdata", "docker-compose-postgres.yml"), - filepath.Join("testdata", "docker-compose-override.yml"), + simple, + RenderComposePostgresForLocal(t), + RenderComposeOverrideForLocal(t), } identifier := strings.ToLower(uuid.New().String()) @@ -418,20 +414,20 @@ func TestLocalDockerComposeWithMultipleComposeFiles(t *testing.T) { checkIfError(t, err) assert.Len(t, compose.Services, 3) - assert.Contains(t, compose.Services, "nginx") - assert.Contains(t, compose.Services, "mysql") - assert.Contains(t, compose.Services, "postgres") + assert.Contains(t, compose.Services, "local-nginx") + assert.Contains(t, compose.Services, "local-mysql") + assert.Contains(t, compose.Services, "local-postgres") present := map[string]string{ "bar": "BAR", "foo": "FOO", } absent := map[string]string{} - assertContainerEnvironmentVariables(t, compose.Identifier, "nginx", present, absent) + assertContainerEnvironmentVariables(t, compose.Identifier, "local-nginx", present, absent) } func TestLocalDockerComposeWithVolume(t *testing.T) { - path := filepath.Join("testdata", "docker-compose-volume.yml") + path := RenderComposeWithVolumeForLocal(t) identifier := strings.ToLower(uuid.New().String()) diff --git a/modules/compose/testdata/docker-compose-build.yml b/modules/compose/testdata/docker-compose-build.yml index 1728e177bd..88c6ccda40 100644 --- a/modules/compose/testdata/docker-compose-build.yml +++ b/modules/compose/testdata/docker-compose-build.yml @@ -1,11 +1,11 @@ version: '3' services: - echo: + {{ .ServiceType }}-echo: build: dockerfile: echoserver.Dockerfile environment: FOO: "Hello, World!" ports: - target: 8080 - published: 8080 + published: {{ .Port_0 }} protocol: tcp \ No newline at end of file diff --git a/modules/compose/testdata/docker-compose-complex.yml b/modules/compose/testdata/docker-compose-complex.yml index 46a674ca04..f2eacf026a 100644 --- a/modules/compose/testdata/docker-compose-complex.yml +++ b/modules/compose/testdata/docker-compose-complex.yml @@ -1,13 +1,13 @@ version: '3' services: - nginx: + {{ .ServiceType }}-nginx: image: docker.io/nginx:stable-alpine ports: - - "9080:80" - mysql: + - "{{ .Port_0 }}:80" + {{ .ServiceType }}-mysql: image: docker.io/mysql:8.0.36 environment: - MYSQL_DATABASE=db - MYSQL_ROOT_PASSWORD=my-secret-pw ports: - - "13306:3306" \ No newline at end of file + - "{{ .Port_1 }}:3306" \ No newline at end of file diff --git a/modules/compose/testdata/docker-compose-container-name.yml b/modules/compose/testdata/docker-compose-container-name.yml index db54e1d82f..b7e497fd84 100644 --- a/modules/compose/testdata/docker-compose-container-name.yml +++ b/modules/compose/testdata/docker-compose-container-name.yml @@ -1,9 +1,9 @@ version: '3' services: - nginx: - container_name: nginxy + {{ .ServiceType }}-nginx: + container_name: {{ .ServiceType }}-nginxy image: docker.io/nginx:stable-alpine environment: bar: ${bar} ports: - - "9080:80" + - "{{ .Port_0 }}:80" diff --git a/modules/compose/testdata/docker-compose-no-exposed-ports.yml b/modules/compose/testdata/docker-compose-no-exposed-ports.yml index d1e27913e1..58da1351f4 100644 --- a/modules/compose/testdata/docker-compose-no-exposed-ports.yml +++ b/modules/compose/testdata/docker-compose-no-exposed-ports.yml @@ -1,6 +1,6 @@ version: '3' services: - nginx: + {{ .ServiceType }}-nginx: image: docker.io/nginx:stable-alpine ports: - "80" diff --git a/modules/compose/testdata/docker-compose-override.yml b/modules/compose/testdata/docker-compose-override.yml index 3fcb135947..8a44e78631 100644 --- a/modules/compose/testdata/docker-compose-override.yml +++ b/modules/compose/testdata/docker-compose-override.yml @@ -1,10 +1,10 @@ version: '3' services: - nginx: + {{ .ServiceType }}-nginx: image: docker.io/nginx:stable-alpine - mysql: + {{ .ServiceType }}-mysql: image: docker.io/mysql:8.0.36 environment: MYSQL_RANDOM_ROOT_PASSWORD: Y ports: - - "13306:3306" + - "{{ .Port_0 }}:3306" diff --git a/modules/compose/testdata/docker-compose-postgres.yml b/modules/compose/testdata/docker-compose-postgres.yml index db6720eb3e..7988a43266 100644 --- a/modules/compose/testdata/docker-compose-postgres.yml +++ b/modules/compose/testdata/docker-compose-postgres.yml @@ -1,8 +1,8 @@ version: '3' services: - postgres: + {{ .ServiceType }}-postgres: image: docker.io/postgres:14 environment: POSTGRES_PASSWORD: s3cr3t ports: - - "15432:5432" + - "{{ .Port_0 }}:5432" diff --git a/modules/compose/testdata/docker-compose-simple.yml b/modules/compose/testdata/docker-compose-simple.yml index ddb324cbf7..90b9a4be87 100644 --- a/modules/compose/testdata/docker-compose-simple.yml +++ b/modules/compose/testdata/docker-compose-simple.yml @@ -1,9 +1,9 @@ version: '3' services: - nginx: + {{ .ServiceType }}-nginx: image: docker.io/nginx:stable-alpine environment: bar: ${bar} foo: ${foo} ports: - - "9080:80" + - "{{ .Port_0 }}:80" diff --git a/modules/compose/testdata/docker-compose-volume.yml b/modules/compose/testdata/docker-compose-volume.yml index cc14090c53..81add904d0 100644 --- a/modules/compose/testdata/docker-compose-volume.yml +++ b/modules/compose/testdata/docker-compose-volume.yml @@ -1,6 +1,6 @@ version: '3' services: - nginx: + {{ .ServiceType }}-nginx: image: docker.io/nginx:stable-alpine volumes: - type: volume @@ -11,7 +11,7 @@ services: environment: bar: ${bar} ports: - - "9080:80" + - "{{ .Port_0 }}:80" volumes: mydata: diff --git a/reaper.go b/reaper.go index 859f8b76de..54feb90cbe 100644 --- a/reaper.go +++ b/reaper.go @@ -46,7 +46,8 @@ type ReaperProvider interface { } // NewReaper creates a Reaper with a sessionID to identify containers and a provider to use -// Deprecated: it's not possible to create a reaper anymore. +// Deprecated: it's not possible to create a reaper anymore. Compose module uses this method +// to create a reaper for the compose stack. func NewReaper(ctx context.Context, sessionID string, provider ReaperProvider, reaperImageName string) (*Reaper, error) { return reuseOrCreateReaper(ctx, sessionID, provider) }