// Copyright IBM Corp. 2020, 2025 // SPDX-License-Identifier: BUSL-1.1 package dbtest import ( "context" "encoding/base64" "fmt" "math/rand" "os" // postgres dialect "github.com/hashicorp/boundary/internal/db/common" wrapping "github.com/hashicorp/go-kms-wrapping/v2" "github.com/hashicorp/go-kms-wrapping/v2/aead" _ "github.com/jackc/pgx/v5/stdlib" ) var ( testDBPort = "5432" testDBHost = "127.0.0.1" ) func init() { p := os.Getenv("TEST_DB_PORT") if p != "" { testDBPort = p } h := os.Getenv("TEST_DB_HOST") if h != "" { testDBHost = h } } // Dialects const ( Postgres = "postgres" ) // BoundaryBenchmarksUserPassword is the password used for all users // created by the boundary benchmarks dump generator. const BoundaryBenchmarksUserPassword = "testpassword" // Template Database Names const ( BoundaryTemplate = "boundary_template" Template1 = "template1" Boundary1000Sessions10ConnsPerSession10Template = "boundary_session_1000_10_10_template" Boundary1000Sessions10ConnsPerSession25Template = "boundary_session_1000_10_25_template" Boundary1000Sessions10ConnsPerSession50Template = "boundary_session_1000_10_50_template" Boundary1000Sessions10ConnsPerSession75Template = "boundary_session_1000_10_75_template" Boundary1000Sessions10ConnsPerSession100Template = "boundary_session_1000_10_100_template" Boundary1000Sessions10ConnsPerSession500Template = "boundary_session_1000_10_500_template" ) var supportedDialects = map[string]struct{}{ Postgres: {}, } var supportedTemplates = map[string]struct{}{ BoundaryTemplate: {}, Template1: {}, Boundary1000Sessions10ConnsPerSession10Template: {}, Boundary1000Sessions10ConnsPerSession25Template: {}, Boundary1000Sessions10ConnsPerSession50Template: {}, Boundary1000Sessions10ConnsPerSession75Template: {}, Boundary1000Sessions10ConnsPerSession100Template: {}, Boundary1000Sessions10ConnsPerSession500Template: {}, } const letterBytes = "abcdefghijklmnopqrstuvwxyz" const ( letterIdxBits = 6 // 6 bits to represent a letter index letterIdxMask = 1<= 0; { if remain == 0 { cache, remain = rand.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx < len(letterBytes) { b[i] = letterBytes[idx] i-- } cache >>= letterIdxBits remain-- } return string(b) } // StartUsingTemplate creates a new test database from a postgres template database. func StartUsingTemplate(dialect string, opt ...Option) (cleanup func() error, retURL, dbname string, err error) { if _, ok := supportedDialects[dialect]; !ok { return func() error { return nil }, "", "", fmt.Errorf("unsupported dialect: %s", dialect) } if dialect == "postgres" { dialect = "pgx" } db, err := common.SqlOpen(dialect, fmt.Sprintf("postgres://boundary:boundary@%s:%s/boundary?sslmode=disable", testDBHost, testDBPort)) if err != nil { return func() error { return nil }, "", "", fmt.Errorf("could not connect to source database: %w", err) } defer db.Close() dbname = fmt.Sprintf("boundary_test_%s", randStr(16)) template := BoundaryTemplate opts := GetOpts(opt...) if opts.withTemplate != "" { template = opts.withTemplate } if _, ok := supportedTemplates[template]; !ok { return func() error { return nil }, "", "", fmt.Errorf("unsupported database template: %s", template) } _, err = db.Exec(fmt.Sprintf("create database %s template %s", dbname, template)) if err != nil { return func() error { return nil }, "", "", fmt.Errorf("could not create test database: %w", err) } url := fmt.Sprintf("postgres://boundary:boundary@127.0.0.1:%s/%s?sslmode=disable", testDBPort, dbname) cleanup = func() error { return dropDatabase(dialect, dbname) } tdb, err := common.SqlOpen(dialect, url) if err != nil { return func() error { return nil }, "", "", fmt.Errorf("could not create test database: %w", err) } defer tdb.Close() if err := tdb.Ping(); err != nil { return func() error { return nil }, "", "", fmt.Errorf("could not ping test database: %w", err) } return cleanup, url, dbname, nil } const killconns = ` select pg_terminate_backend(pg_stat_activity.pid) from pg_stat_activity where pg_stat_activity.datname = $1 and pid <> pg_backend_pid(); ` func dropDatabase(dialect, dbname string) error { if dbname == "" { return fmt.Errorf("empty dbname") } db, err := common.SqlOpen(dialect, fmt.Sprintf("postgres://boundary:boundary@127.0.0.1:%s/boundary?sslmode=disable", testDBPort)) if err != nil { return err } defer db.Close() _, err = db.Exec(killconns, dbname) if err != nil { return err } _, err = db.Exec(fmt.Sprintf("drop database %s", dbname)) if err != nil { return err } return nil }