75f3dd141c
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CI and Release / lint-frontend (push) Has been cancelled
CI and Release / dockerfile-scan (push) Has been cancelled
CI and Release / test-frontend (push) Has been cancelled
CI and Release / lint-verification-agent (push) Has been cancelled
CI and Release / test-verification-agent (push) Has been cancelled
CI and Release / e2e-verification-agent (push) Has been cancelled
CI and Release / test-backend (push) Has been cancelled
CI and Release / build-dev-image (push) Has been cancelled
CI and Release / push-dev-image (push) Has been cancelled
CI and Release / build-image (push) Has been cancelled
CI and Release / build-verification-image (push) Has been cancelled
CI and Release / determine-version (push) Has been cancelled
CI and Release / push-image (push) Has been cancelled
CI and Release / push-verification-image (12) (push) Has been cancelled
CI and Release / lint-backend (push) Has been cancelled
CI and Release / push-verification-image (17) (push) Has been cancelled
CI and Release / push-verification-image (18) (push) Has been cancelled
CI and Release / release (push) Has been cancelled
CI and Release / publish-helm-chart (push) Has been cancelled
CI and Release / push-verification-image (13) (push) Has been cancelled
CI and Release / push-verification-image (14) (push) Has been cancelled
CI and Release / push-verification-image (15) (push) Has been cancelled
CI and Release / push-verification-image (16) (push) Has been cancelled
143 lines
5.1 KiB
Go
143 lines
5.1 KiB
Go
package containers
|
||
|
||
import (
|
||
"strconv"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/testcontainers/testcontainers-go"
|
||
"github.com/testcontainers/testcontainers-go/wait"
|
||
)
|
||
|
||
// Credentials baked into every test PostgreSQL container.
|
||
const (
|
||
PostgresUsername = "testuser"
|
||
PostgresPassword = "testpassword"
|
||
PostgresDatabase = "testdb"
|
||
)
|
||
|
||
const postgresPort = "5432/tcp"
|
||
|
||
// postgresStartupTimeout is generous because go test -p=N boots many containers at once; under CPU
|
||
// contention a cold start runs far longer than its uncontended time, and a fast host returns as
|
||
// soon as the server is ready, so the high ceiling costs nothing there.
|
||
const postgresStartupTimeout = 240 * time.Second
|
||
|
||
func postgresEnv() map[string]string {
|
||
return map[string]string{
|
||
"POSTGRES_DB": PostgresDatabase,
|
||
"POSTGRES_USER": PostgresUsername,
|
||
"POSTGRES_PASSWORD": PostgresPassword,
|
||
}
|
||
}
|
||
|
||
// postgresReady waits for the network-listening server. The entrypoint starts a temporary
|
||
// socket-only server for initdb and then restarts the real one, so "ready to accept connections"
|
||
// is logged twice; waiting for the second occurrence avoids racing the socket-only server.
|
||
func postgresReady() wait.Strategy {
|
||
return wait.ForAll(
|
||
wait.ForLog("database system is ready to accept connections").
|
||
WithOccurrence(2).WithStartupTimeout(postgresStartupTimeout),
|
||
wait.ForListeningPort(postgresPort),
|
||
)
|
||
}
|
||
|
||
// postgresRequest builds the container request for image (e.g. "postgres:16"). The durability
|
||
// flags are safe for a throwaway server and a large speedup for the write-heavy e2e restore.
|
||
func postgresRequest(image string) testcontainers.ContainerRequest {
|
||
return testcontainers.ContainerRequest{
|
||
Image: image,
|
||
ExposedPorts: []string{postgresPort},
|
||
Env: postgresEnv(),
|
||
Cmd: []string{"-c", "fsync=off", "-c", "full_page_writes=off", "-c", "synchronous_commit=off"},
|
||
Tmpfs: map[string]string{postgresDataDir(image): dataDirTmpfsOptions},
|
||
WaitingFor: postgresReady(),
|
||
}
|
||
}
|
||
|
||
// postgresDataDir returns the data directory to mount on tmpfs for image. postgres 14–17 keep
|
||
// PGDATA and the data VOLUME at /var/lib/postgresql/data. postgres:18 relocated PGDATA and moved
|
||
// the volume up to /var/lib/postgresql; mounting tmpfs at the old path there leaves a stray empty
|
||
// dir the entrypoint's layout detection rejects, so the server exits before becoming ready.
|
||
// Mounting at the parent volume avoids that. An unparseable tag falls back to the pre-18 path.
|
||
func postgresDataDir(image string) string {
|
||
if postgresMajorVersion(image) >= 18 {
|
||
return "/var/lib/postgresql"
|
||
}
|
||
|
||
return "/var/lib/postgresql/data"
|
||
}
|
||
|
||
// PostgresMajorVersion returns the major version from an image like "postgres:16" or
|
||
// "postgres:17.2", or 0 when the tag is missing or not a leading integer (treated as pre-18).
|
||
func PostgresMajorVersion(image string) int {
|
||
return postgresMajorVersion(image)
|
||
}
|
||
|
||
// postgresMajorVersion returns the major version from an image like "postgres:16" or
|
||
// "postgres:17.2", or 0 when the tag is missing or not a leading integer (treated as pre-18).
|
||
func postgresMajorVersion(image string) int {
|
||
_, tag, hasTag := strings.Cut(image, ":")
|
||
if !hasTag {
|
||
return 0
|
||
}
|
||
|
||
major, _, _ := strings.Cut(tag, ".")
|
||
|
||
version, err := strconv.Atoi(major)
|
||
if err != nil {
|
||
return 0
|
||
}
|
||
|
||
return version
|
||
}
|
||
|
||
// StartPostgres boots a PostgreSQL server from image (e.g. "postgres:16").
|
||
func StartPostgres(t *testing.T, image string) Endpoint {
|
||
t.Helper()
|
||
|
||
return start(t, postgresRequest(image), postgresPort)
|
||
}
|
||
|
||
// StartPostgresSSL boots an ssl=on PostgreSQL whose pg_hba.conf rejects non-TLS TCP. The image is
|
||
// built from contextDir (Dockerfile, server cert/key, pg_hba.conf) because PostgreSQL refuses a key
|
||
// file that is group/world-readable or not owned by postgres — the Dockerfile chowns+chmods it,
|
||
// which a copied-in file cannot. The test connects with sslmode=require.
|
||
func StartPostgresSSL(t *testing.T, contextDir string) Endpoint {
|
||
t.Helper()
|
||
|
||
return startPostgresBuild(t, contextDir, "databasus-test-postgres-ssl")
|
||
}
|
||
|
||
// StartPostgresMtls boots a PostgreSQL that also requires a client certificate signed by its ca.crt
|
||
// (pg_hba clientcert=verify-ca). Same build rationale as StartPostgresSSL; the test connects with
|
||
// sslmode=verify-ca and a client cert/key.
|
||
func StartPostgresMtls(t *testing.T, contextDir string) Endpoint {
|
||
t.Helper()
|
||
|
||
return startPostgresBuild(t, contextDir, "databasus-test-postgres-mtls")
|
||
}
|
||
|
||
// startPostgresBuild builds the image from contextDir/Dockerfile and boots it. The stable repo tag
|
||
// plus KeepImage lets Docker's layer cache skip the rebuild on repeat runs; the readiness flags
|
||
// live in the Dockerfile's CMD, so the request only sets the env and wait strategy.
|
||
func startPostgresBuild(t *testing.T, contextDir, repo string) Endpoint {
|
||
t.Helper()
|
||
|
||
req := testcontainers.ContainerRequest{
|
||
FromDockerfile: testcontainers.FromDockerfile{
|
||
Context: contextDir,
|
||
Dockerfile: "Dockerfile",
|
||
Repo: repo,
|
||
Tag: "latest",
|
||
KeepImage: true,
|
||
},
|
||
ExposedPorts: []string{postgresPort},
|
||
Env: postgresEnv(),
|
||
WaitingFor: postgresReady(),
|
||
}
|
||
|
||
return start(t, req, postgresPort)
|
||
}
|