diff --git a/compose.go b/compose.go index 7dc52f570d..ee5bb7c6a3 100644 --- a/compose.go +++ b/compose.go @@ -3,6 +3,7 @@ package testcontainers import ( "context" "errors" + "io" "path/filepath" "runtime" "strings" @@ -94,6 +95,10 @@ func WithStackFiles(filePaths ...string) ComposeStackOption { return ComposeStackFiles(filePaths) } +func WithStackReaders(readers ...io.Reader) ComposeStackOption { + return ComposeStackReaders(readers) +} + func NewDockerCompose(filePaths ...string) (*dockerCompose, error) { return NewDockerComposeWith(WithStackFiles(filePaths...)) } diff --git a/compose_api.go b/compose_api.go index c75453091c..289619a3cc 100644 --- a/compose_api.go +++ b/compose_api.go @@ -3,6 +3,9 @@ package testcontainers import ( "context" "fmt" + "io" + "os" + "path/filepath" "sort" "strings" "sync" @@ -14,6 +17,7 @@ import ( types2 "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" + "github.com/google/uuid" "golang.org/x/sync/errgroup" "github.com/testcontainers/testcontainers-go/wait" @@ -95,6 +99,35 @@ func (f StackIdentifier) String() string { return string(f) } +type ComposeStackReaders []io.Reader + +func (r ComposeStackReaders) applyToComposeStack(o *composeStackOptions) { + // choose directory to keep temporary files + // like + // /tmp/00000000-00000000-00000000-00000000/ + composeID := uuid.New().String() + tmpDir := filepath.Join(os.TempDir(), composeID) + if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { + panic(err) + } + + // write temporary files and put to files list + filePaths := make([]string, 0, len(r)) + for idx, src := range r { + content, err := io.ReadAll(src) + if err != nil { + panic(err) + } + filename := filepath.Join(tmpDir, fmt.Sprintf("docker-compose-%d.yaml", idx)) + if err := os.WriteFile(filename, content, os.ModePerm); err != nil { + panic(err) + } + filePaths = append(filePaths, filename) + } + + o.Paths = filePaths +} + const ( // RemoveImagesAll - remove all images used by the stack RemoveImagesAll RemoveImages = iota diff --git a/compose_api_test.go b/compose_api_test.go index ab6401f658..bd75165b67 100644 --- a/compose_api_test.go +++ b/compose_api_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "hash/fnv" + "strings" "testing" "time" @@ -409,6 +410,48 @@ func TestDockerComposeApiWithWaitForShortLifespanService(t *testing.T) { assert.Contains(t, services, "tzatziki") } +func TestNewDockerComposeWithReaders(t *testing.T) { + var ( + specOne = strings.NewReader(` +version: "3" +services: + nginx: + image: nginx:1.22.0-alpine +`) + specTwo = strings.NewReader(` +version: "3" +services: + php: + image: php:8.1.7-alpine3.15 +`) + specTree = strings.NewReader(` +version: "3" +services: + redis: + image: redis:alpine3.15 +`) + ) + compose, err := NewDockerComposeWith(WithStackReaders(specOne, specTwo, specTree)) + assert.NoError(t, err, "NewDockerComposeWith(WithStackReaders(...))") + assert.Equal(t, 3, len(compose.configs)) + + t.Cleanup(func() { + assert.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") + }) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err = compose.Up(ctx) + assert.NoError(t, err, "compose.Up()") + + serviceNames := compose.Services() + assert.Equal(t, 3, len(serviceNames)) + assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "php") + assert.Contains(t, serviceNames, "redis") +} + func testNameHash(name string) StackIdentifier { return StackIdentifier(fmt.Sprintf("%x", fnv.New32a().Sum([]byte(name)))) }