Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions modules/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,40 @@ func WithDatabase(dbName string) testcontainers.CustomizeRequestOption {
}
}

// WithInitScripts sets the init scripts to be run when the container starts
// WithInitScripts sets the init scripts to be run when the container starts.
// These init scripts will be executed in sorted name order as defined by the container's current locale, which defaults to en_US.utf8.
// If you need to run your scripts in a specific order, consider using `WithOrderedInitScripts` instead.
func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) error {
initScripts := []testcontainers.ContainerFile{}
containerFiles := []testcontainers.ContainerFile{}
for _, script := range scripts {
cf := testcontainers.ContainerFile{
HostFilePath: script,
ContainerFilePath: "/docker-entrypoint-initdb.d/" + filepath.Base(script),
FileMode: 0o755,
}
initScripts = append(initScripts, cf)
initScript := buildInitScriptContainerFile(script, filepath.Base(script))
containerFiles = append(containerFiles, initScript)
}
req.Files = append(req.Files, initScripts...)
return testcontainers.WithFiles(containerFiles...)(req)
}
}

return nil
// WithOrderedInitScripts sets the init scripts to be run when the container starts.
// The scripts will be run in the order that they are provided in this function.
func WithOrderedInitScripts(scripts ...string) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) error {
containerFiles := []testcontainers.ContainerFile{}
for idx, script := range scripts {
containerFilePath := fmt.Sprintf("%03d-%s", idx, filepath.Base(script))
initScript := buildInitScriptContainerFile(script, containerFilePath)
containerFiles = append(containerFiles, initScript)
}
return testcontainers.WithFiles(containerFiles...)(req)
}
}

// Adds the file located at hostFilePath as an init script that postgres will launch
func buildInitScriptContainerFile(hostFilePath string, containerFilePath string) testcontainers.ContainerFile {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: do we need this simple function that just returns the struct? I'd consider removing it as it does not do anything special inside. But I'll let you decide

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You asked to share some code between WithOrderedInitScripts and WithInitScripts, so this function does just that. The only alternative I can think of is duplicating the ContainerFile declaration in the 2 functions, so I believe this is better?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I asked that before having the new WithFiles options, which now removes the duplication. I'm fine having it as a function, although with the current implementation I see it as a pass-through function. In any case, it's not a blocker to me.

Once the docs are added, I'm happy to merge this 😊

return testcontainers.ContainerFile{
HostFilePath: hostFilePath,
ContainerFilePath: "/docker-entrypoint-initdb.d/" + containerFilePath,
FileMode: 0o755,
}
}

Expand Down
57 changes: 57 additions & 0 deletions modules/postgres/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"database/sql"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
"time"

Expand All @@ -19,6 +21,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/testcontainers/testcontainers-go"
tcexec "github.com/testcontainers/testcontainers-go/exec"
"github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/testcontainers/testcontainers-go/wait"
)
Expand Down Expand Up @@ -288,6 +291,60 @@ func TestWithInitScript(t *testing.T) {
require.NotNil(t, result)
}

func TestWithOrderedInitScript(t *testing.T) {
ctx := context.Background()

ctr, err := postgres.Run(ctx,
"postgres:15.2-alpine",
// Executes first the init-user-db shell-script, then the do-insert-user SQL script
// Using WithInitScripts, this would not work.
// This is because aaaa-insert-user would get executed first, but requires init-user-db to be executed before.
postgres.WithOrderedInitScripts(
filepath.Join("testdata", "init-user-db.sh"),
filepath.Join("testdata", "aaaa-insert-user.sql"),
),
postgres.WithDatabase(dbname),
postgres.WithUsername(user),
postgres.WithPassword(password),
postgres.BasicWaitStrategies(),
)
testcontainers.CleanupContainer(t, ctr)
require.NoError(t, err)

// Test that init scripts have been correctly renamed
c, reader, err := ctr.Exec(ctx, []string{"ls", "-l", "/docker-entrypoint-initdb.d"}, tcexec.Multiplexed())
require.NoError(t, err)
require.Equal(t, 0, c, "Expected to read init scripts from the container")

buf := new(strings.Builder)
_, err = io.Copy(buf, reader)
require.NoError(t, err)

initScripts := buf.String()
strings.Contains(initScripts, "000-init-user-db.sh")
strings.Contains(initScripts, "001-aaaa-insert-user.sql")

// explicitly set sslmode=disable because the container is not configured to use TLS
connStr, err := ctr.ConnectionString(ctx, "sslmode=disable")
require.NoError(t, err)

db, err := sql.Open("postgres", connStr)
require.NoError(t, err)
require.NotNil(t, db)
defer db.Close()

// database created in init script. See testdata/init-user-db.sh
rows, err := db.Query("SELECT COUNT(*) FROM testdb;")
require.NoError(t, err)
require.NotNil(t, rows)
for rows.Next() {
var count int
err := rows.Scan(&count)
require.NoError(t, err)
require.Equal(t, 2, count)
}
}

func TestSnapshot(t *testing.T) {
tests := []struct {
name string
Expand Down
5 changes: 5 additions & 0 deletions modules/postgres/testdata/aaaa-insert-user.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Do not rename this file, it is named like this
-- to test correct ordering behavior.
--
-- See TestWithOrderedInitScripts.
INSERT INTO testdb (id, name) VALUES (2, 'second user')