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
33 changes: 33 additions & 0 deletions compose_api.go
Expand Up @@ -3,6 +3,9 @@ package testcontainers
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"sync"
Expand All @@ -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"
Expand Down Expand Up @@ -92,6 +96,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)
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
}
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))))
}