diff --git a/docs/modules/postgres.md b/docs/modules/postgres.md index 09736f6279..a3af88e259 100644 --- a/docs/modules/postgres.md +++ b/docs/modules/postgres.md @@ -70,6 +70,14 @@ An example of a `*.sh` script that creates a user and database is shown below: [Init script content](../../modules/postgres/testdata/init-user-db.sh) +#### Ordered Init Scripts + +If you would like to run the init scripts in a specific order, you can use the `WithOrderedInitScripts` function, which copies the given scripts in the order they are provided to the container, prefixed with the order number so that Postgres executes them in the correct order. + + +[Ordered init scripts](../../modules/postgres/postgres_test.go) inside_block:orderedInitScripts + + #### Database configuration In the case you have a custom config file for Postgres, it's possible to copy that file into the container before it's started, using the `WithConfigFile(cfgPath string)` function. diff --git a/modules/postgres/postgres.go b/modules/postgres/postgres.go index 1f54add722..865fa2c94b 100644 --- a/modules/postgres/postgres.go +++ b/modules/postgres/postgres.go @@ -94,22 +94,37 @@ 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{} - for _, script := range scripts { - cf := testcontainers.ContainerFile{ - HostFilePath: script, - ContainerFilePath: "/docker-entrypoint-initdb.d/" + filepath.Base(script), - FileMode: 0o755, - } - initScripts = append(initScripts, cf) + containerFiles := []testcontainers.ContainerFile{} + for _, script := range scripts { + initScript := testcontainers.ContainerFile{ + HostFilePath: script, + ContainerFilePath: "/docker-entrypoint-initdb.d/" + filepath.Base(script), + FileMode: 0o755, } - req.Files = append(req.Files, initScripts...) + containerFiles = append(containerFiles, initScript) + } - return nil + return testcontainers.WithFiles(containerFiles...) +} + +// 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 { + containerFiles := []testcontainers.ContainerFile{} + for idx, script := range scripts { + initScript := testcontainers.ContainerFile{ + HostFilePath: script, + ContainerFilePath: "/docker-entrypoint-initdb.d/" + fmt.Sprintf("%03d-%s", idx, filepath.Base(script)), + FileMode: 0o755, + } + containerFiles = append(containerFiles, initScript) } + + return testcontainers.WithFiles(containerFiles...) } // WithPassword sets the initial password of the user to be created when the container starts diff --git a/modules/postgres/postgres_test.go b/modules/postgres/postgres_test.go index e83b8e1454..a389d68bff 100644 --- a/modules/postgres/postgres_test.go +++ b/modules/postgres/postgres_test.go @@ -5,8 +5,10 @@ import ( "database/sql" "errors" "fmt" + "io" "os" "path/filepath" + "strings" "testing" "time" @@ -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" ) @@ -288,6 +291,62 @@ 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", + // orderedInitScripts { + // 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 diff --git a/modules/postgres/testdata/aaaa-insert-user.sql b/modules/postgres/testdata/aaaa-insert-user.sql new file mode 100644 index 0000000000..687937f374 --- /dev/null +++ b/modules/postgres/testdata/aaaa-insert-user.sql @@ -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')