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

feat: support creating compose Stacks from Readers #466

5 changes: 5 additions & 0 deletions compose.go
Expand Up @@ -3,6 +3,7 @@ package testcontainers
import (
"context"
"errors"
"io"
"path/filepath"
"runtime"
"strings"
Expand Down Expand Up @@ -92,6 +93,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...))
}
Expand Down
43 changes: 43 additions & 0 deletions compose_api.go
Expand Up @@ -3,6 +3,10 @@ package testcontainers
import (
"context"
"fmt"
"hash/fnv"
"io"
"os"
"path/filepath"
"sort"
"strings"
"sync"
Expand Down Expand Up @@ -92,6 +96,45 @@ func (f StackIdentifier) String() string {
return string(f)
}

type ComposeStackReaders []io.Reader

func (r ComposeStackReaders) applyToComposeStack(o *composeStackOptions) {
currentDir, err := os.Getwd()
if err != nil {
panic(err)
}

// choose directory to keep temporary files
// like
// /tmp/testcontainers-go/my-awesome-service-2f686f6d652f676f666f7262726f6b65
projectName := filepath.Base(currentDir)
projectHash := fmt.Sprintf("%x", fnv.New32a().Sum([]byte(currentDir)))[:32]
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
tmpDir := filepath.Join(os.TempDir(), "testcontainers-go", fmt.Sprintf("%s-%s", projectName, projectHash))
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
if err := os.RemoveAll(tmpDir); err != nil {
panic(err)
}
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
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)
}
name := fmt.Sprintf("docker-compose-%d.yaml", idx)
filename := filepath.Join(tmpDir, name)
if err := os.WriteFile(filename, content, os.ModePerm); err != nil {
continue
}
filePaths = append(filePaths, filename)
}

o.Paths = filePaths
}

const (
// RemoveImagesAll - remove all images used by the stack
RemoveImagesAll RemoveImages = iota
Expand Down
43 changes: 43 additions & 0 deletions compose_api_test.go
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"hash/fnv"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -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))))
}