diff options
Diffstat (limited to 'vendor/github.com')
6 files changed, 508 insertions, 0 deletions
diff --git a/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/LICENSE b/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/LICENSE new file mode 100644 index 0000000..607a9c3 --- /dev/null +++ b/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017-2019 Gianluca Arbezzano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/Makefile b/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/Makefile new file mode 100644 index 0000000..225f0c4 --- /dev/null +++ b/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/Makefile @@ -0,0 +1,5 @@ +include ../../commons-test.mk + +.PHONY: test +test: + $(MAKE) test-postgres diff --git a/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/options.go b/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/options.go new file mode 100644 index 0000000..5779f85 --- /dev/null +++ b/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/options.go @@ -0,0 +1,39 @@ +package postgres + +import ( + "github.com/testcontainers/testcontainers-go" +) + +type options struct { + // SQLDriverName is the name of the SQL driver to use. + SQLDriverName string + Snapshot string +} + +func defaultOptions() options { + return options{ + SQLDriverName: "postgres", + Snapshot: defaultSnapshotName, + } +} + +// Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface. +var _ testcontainers.ContainerCustomizer = (Option)(nil) + +// Option is an option for the Redpanda container. +type Option func(*options) + +// Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. +func (o Option) Customize(*testcontainers.GenericContainerRequest) error { + // NOOP to satisfy interface. + return nil +} + +// WithSQLDriver sets the SQL driver to use for the container. +// It is passed to sql.Open() to connect to the database when making or restoring snapshots. +// This can be set if your app imports a different postgres driver, f.ex. "pgx" +func WithSQLDriver(driver string) Option { + return func(o *options) { + o.SQLDriverName = driver + } +} diff --git a/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/postgres.go b/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/postgres.go new file mode 100644 index 0000000..f03adc7 --- /dev/null +++ b/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/postgres.go @@ -0,0 +1,391 @@ +package postgres + +import ( + "context" + "database/sql" + _ "embed" + "errors" + "fmt" + "io" + "path/filepath" + "strings" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/log" +) + +const ( + defaultUser = "postgres" + defaultPassword = "postgres" + defaultSnapshotName = "migrated_template" +) + +//go:embed resources/customEntrypoint.sh +var embeddedCustomEntrypoint string + +// PostgresContainer represents the postgres container type used in the module +type PostgresContainer struct { + testcontainers.Container + dbName string + user string + password string + snapshotName string + // sqlDriverName is passed to sql.Open() to connect to the database when making or restoring snapshots. + // This can be set if your app imports a different postgres driver, f.ex. "pgx" + sqlDriverName string +} + +// MustConnectionString panics if the address cannot be determined. +func (c *PostgresContainer) MustConnectionString(ctx context.Context, args ...string) string { + addr, err := c.ConnectionString(ctx, args...) + if err != nil { + panic(err) + } + return addr +} + +// ConnectionString returns the connection string for the postgres container, using the default 5432 port, and +// obtaining the host and exposed port from the container. It also accepts a variadic list of extra arguments +// which will be appended to the connection string. The format of the extra arguments is the same as the +// connection string format, e.g. "connect_timeout=10" or "application_name=myapp" +func (c *PostgresContainer) ConnectionString(ctx context.Context, args ...string) (string, error) { + endpoint, err := c.PortEndpoint(ctx, "5432/tcp", "") + if err != nil { + return "", err + } + + extraArgs := strings.Join(args, "&") + connStr := fmt.Sprintf("postgres://%s:%s@%s/%s?%s", c.user, c.password, endpoint, c.dbName, extraArgs) + return connStr, nil +} + +// WithConfigFile sets the config file to be used for the postgres container +// It will also set the "config_file" parameter to the path of the config file +// as a command line argument to the container +func WithConfigFile(cfg string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + cfgFile := testcontainers.ContainerFile{ + HostFilePath: cfg, + ContainerFilePath: "/etc/postgresql.conf", + FileMode: 0o755, + } + + req.Files = append(req.Files, cfgFile) + req.Cmd = append(req.Cmd, "-c", "config_file=/etc/postgresql.conf") + + return nil + } +} + +// WithDatabase sets the initial database to be created when the container starts +// It can be used to define a different name for the default database that is created when the image is first started. +// If it is not specified, then the value of WithUser will be used. +func WithDatabase(dbName string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Env["POSTGRES_DB"] = dbName + + return nil + } +} + +// 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 { + containerFiles := []testcontainers.ContainerFile{} + for _, script := range scripts { + initScript := testcontainers.ContainerFile{ + HostFilePath: script, + ContainerFilePath: "/docker-entrypoint-initdb.d/" + filepath.Base(script), + FileMode: 0o755, + } + containerFiles = append(containerFiles, initScript) + } + + 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 +// It is required for you to use the PostgreSQL image. It must not be empty or undefined. +// This environment variable sets the superuser password for PostgreSQL. +func WithPassword(password string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Env["POSTGRES_PASSWORD"] = password + + return nil + } +} + +// WithUsername sets the initial username to be created when the container starts +// It is used in conjunction with WithPassword to set a user and its password. +// It will create the specified user with superuser power and a database with the same name. +// If it is not specified, then the default user of postgres will be used. +func WithUsername(user string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + if user == "" { + user = defaultUser + } + + req.Env["POSTGRES_USER"] = user + + return nil + } +} + +// Deprecated: use Run instead +// RunContainer creates an instance of the Postgres container type +func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*PostgresContainer, error) { + return Run(ctx, "postgres:16-alpine", opts...) +} + +// Run creates an instance of the Postgres container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*PostgresContainer, error) { + req := testcontainers.ContainerRequest{ + Image: img, + Env: map[string]string{ + "POSTGRES_USER": defaultUser, + "POSTGRES_PASSWORD": defaultPassword, + "POSTGRES_DB": defaultUser, // defaults to the user name + }, + ExposedPorts: []string{"5432/tcp"}, + Cmd: []string{"postgres", "-c", "fsync=off"}, + } + + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + + // Gather all config options (defaults and then apply provided options) + settings := defaultOptions() + for _, opt := range opts { + if apply, ok := opt.(Option); ok { + apply(&settings) + } + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } + } + + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *PostgresContainer + if container != nil { + c = &PostgresContainer{ + Container: container, + dbName: req.Env["POSTGRES_DB"], + password: req.Env["POSTGRES_PASSWORD"], + user: req.Env["POSTGRES_USER"], + sqlDriverName: settings.SQLDriverName, + snapshotName: settings.Snapshot, + } + } + + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } + + return c, nil +} + +type snapshotConfig struct { + snapshotName string +} + +// SnapshotOption is the type for passing options to the snapshot function of the database +type SnapshotOption func(container *snapshotConfig) *snapshotConfig + +// WithSnapshotName adds a specific name to the snapshot database created from the main database defined on the +// container. The snapshot must not have the same name as your main database, otherwise it will be overwritten +func WithSnapshotName(name string) SnapshotOption { + return func(cfg *snapshotConfig) *snapshotConfig { + cfg.snapshotName = name + return cfg + } +} + +// WithSSLSettings configures the Postgres server to run with the provided CA Chain +// This will not function if the corresponding postgres conf is not correctly configured. +// Namely the paths below must match what is set in the conf file +func WithSSLCert(caCertFile string, certFile string, keyFile string) testcontainers.CustomizeRequestOption { + const defaultPermission = 0o600 + + return func(req *testcontainers.GenericContainerRequest) error { + const entrypointPath = "/usr/local/bin/docker-entrypoint-ssl.bash" + + req.Files = append(req.Files, + testcontainers.ContainerFile{ + HostFilePath: caCertFile, + ContainerFilePath: "/tmp/testcontainers-go/postgres/ca_cert.pem", + FileMode: defaultPermission, + }, + testcontainers.ContainerFile{ + HostFilePath: certFile, + ContainerFilePath: "/tmp/testcontainers-go/postgres/server.cert", + FileMode: defaultPermission, + }, + testcontainers.ContainerFile{ + HostFilePath: keyFile, + ContainerFilePath: "/tmp/testcontainers-go/postgres/server.key", + FileMode: defaultPermission, + }, + testcontainers.ContainerFile{ + Reader: strings.NewReader(embeddedCustomEntrypoint), + ContainerFilePath: entrypointPath, + FileMode: defaultPermission, + }, + ) + req.Entrypoint = []string{"sh", entrypointPath} + + return nil + } +} + +// Snapshot takes a snapshot of the current state of the database as a template, which can then be restored using +// the Restore method. By default, the snapshot will be created under a database called migrated_template, you can +// customize the snapshot name with the options. +// If a snapshot already exists under the given/default name, it will be overwritten with the new snapshot. +func (c *PostgresContainer) Snapshot(ctx context.Context, opts ...SnapshotOption) error { + snapshotName, err := c.checkSnapshotConfig(opts) + if err != nil { + return err + } + + // execute the commands to create the snapshot, in order + if err := c.execCommandsSQL(ctx, + // Update pg_database to remove the template flag, then drop the database if it exists. + // This is needed because dropping a template database will fail. + // https://www.postgresql.org/docs/current/manage-ag-templatedbs.html + fmt.Sprintf(`UPDATE pg_database SET datistemplate = FALSE WHERE datname = '%s'`, snapshotName), + fmt.Sprintf(`DROP DATABASE IF EXISTS "%s"`, snapshotName), + // Create a copy of the database to another database to use as a template now that it was fully migrated + fmt.Sprintf(`CREATE DATABASE "%s" WITH TEMPLATE "%s" OWNER "%s"`, snapshotName, c.dbName, c.user), + // Snapshot the template database so we can restore it onto our original database going forward + fmt.Sprintf(`ALTER DATABASE "%s" WITH is_template = TRUE`, snapshotName), + ); err != nil { + return err + } + + c.snapshotName = snapshotName + return nil +} + +// Restore will restore the database to a specific snapshot. By default, it will restore the last snapshot taken on the +// database by the Snapshot method. If a snapshot name is provided, it will instead try to restore the snapshot by name. +func (c *PostgresContainer) Restore(ctx context.Context, opts ...SnapshotOption) error { + snapshotName, err := c.checkSnapshotConfig(opts) + if err != nil { + return err + } + + // execute the commands to restore the snapshot, in order + return c.execCommandsSQL(ctx, + // Drop the entire database by connecting to the postgres global database + fmt.Sprintf(`DROP DATABASE "%s" with (FORCE)`, c.dbName), + // Then restore the previous snapshot + fmt.Sprintf(`CREATE DATABASE "%s" WITH TEMPLATE "%s" OWNER "%s"`, c.dbName, snapshotName, c.user), + ) +} + +func (c *PostgresContainer) checkSnapshotConfig(opts []SnapshotOption) (string, error) { + config := &snapshotConfig{} + for _, opt := range opts { + config = opt(config) + } + + snapshotName := c.snapshotName + if config.snapshotName != "" { + snapshotName = config.snapshotName + } + + if c.dbName == "postgres" { + return "", errors.New("cannot restore the postgres system database as it cannot be dropped to be restored") + } + return snapshotName, nil +} + +func (c *PostgresContainer) execCommandsSQL(ctx context.Context, cmds ...string) error { + conn, cleanup, err := c.snapshotConnection(ctx) + if err != nil { + log.Printf("Could not connect to database to restore snapshot, falling back to `docker exec psql`: %v", err) + return c.execCommandsFallback(ctx, cmds) + } + if cleanup != nil { + defer cleanup() + } + for _, cmd := range cmds { + if _, err := conn.ExecContext(ctx, cmd); err != nil { + return fmt.Errorf("could not execute restore command %s: %w", cmd, err) + } + } + return nil +} + +// snapshotConnection connects to the actual database using the "postgres" sql.DB driver, if it exists. +// The returned function should be called as a defer() to close the pool. +// No need to close the individual connection, that is done as part of the pool close. +// Also, no need to cache the connection pool, since it is a single connection which is very fast to establish. +func (c *PostgresContainer) snapshotConnection(ctx context.Context) (*sql.Conn, func(), error) { + // Connect to the database "postgres" instead of the app one + c2 := &PostgresContainer{ + Container: c.Container, + dbName: "postgres", + user: c.user, + password: c.password, + sqlDriverName: c.sqlDriverName, + } + + // Try to use an actual postgres connection, if the driver is loaded + connStr := c2.MustConnectionString(ctx, "sslmode=disable") + pool, err := sql.Open(c.sqlDriverName, connStr) + if err != nil { + return nil, nil, fmt.Errorf("sql.Open for snapshot connection failed: %w", err) + } + + cleanupPool := func() { + if err := pool.Close(); err != nil { + log.Printf("Could not close database connection pool after restoring snapshot: %v", err) + } + } + + conn, err := pool.Conn(ctx) + if err != nil { + cleanupPool() + return nil, nil, fmt.Errorf("DB.Conn for snapshot connection failed: %w", err) + } + return conn, cleanupPool, nil +} + +func (c *PostgresContainer) execCommandsFallback(ctx context.Context, cmds []string) error { + for _, cmd := range cmds { + exitCode, reader, err := c.Exec(ctx, []string{"psql", "-v", "ON_ERROR_STOP=1", "-U", c.user, "-d", "postgres", "-c", cmd}) + if err != nil { + return err + } + if exitCode != 0 { + buf := new(strings.Builder) + _, err := io.Copy(buf, reader) + if err != nil { + return fmt.Errorf("non-zero exit code for restore command, could not read command output: %w", err) + } + + return fmt.Errorf("non-zero exit code for restore command: %s", buf.String()) + } + } + return nil +} diff --git a/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/resources/customEntrypoint.sh b/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/resources/customEntrypoint.sh new file mode 100644 index 0000000..ff4ffa4 --- /dev/null +++ b/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/resources/customEntrypoint.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -Eeo pipefail + + +pUID=$(id -u postgres) +pGID=$(id -g postgres) + +if [ -z "$pUID" ] +then + echo "Unable to find postgres user id, required in order to chown key material" + exit 1 +fi + +if [ -z "$pGID" ] +then + echo "Unable to find postgres group id, required in order to chown key material" + exit 1 +fi + +chown "$pUID":"$pGID" \ + /tmp/testcontainers-go/postgres/ca_cert.pem \ + /tmp/testcontainers-go/postgres/server.cert \ + /tmp/testcontainers-go/postgres/server.key + +/usr/local/bin/docker-entrypoint.sh "$@" diff --git a/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/wait_strategies.go b/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/wait_strategies.go new file mode 100644 index 0000000..92dc3f6 --- /dev/null +++ b/vendor/github.com/testcontainers/testcontainers-go/modules/postgres/wait_strategies.go @@ -0,0 +1,27 @@ +package postgres + +import ( + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +// BasicWaitStrategies is a simple but reliable way to wait for postgres to start. +// It returns a two-step wait strategy: +// +// - It will wait for the container to log `database system is ready to accept connections` twice, because it will restart itself after the first startup. +// - It will then wait for docker to actually serve the port on localhost. +// For non-linux OSes like Mac and Windows, Docker or Rancher Desktop will have to start a separate proxy. +// Without this, the tests will be flaky on those OSes! +func BasicWaitStrategies() testcontainers.CustomizeRequestOption { + // waitStrategy { + return testcontainers.WithAdditionalWaitStrategy( + // First, we wait for the container to log readiness twice. + // This is because it will restart itself after the first startup. + wait.ForLog("database system is ready to accept connections").WithOccurrence(2), + // Then, we wait for docker to actually serve the port on localhost. + // For non-linux OSes like Mac and Windows, Docker or Rancher Desktop will have to start a separate proxy. + // Without this, the tests will be flaky on those OSes! + wait.ForListeningPort("5432/tcp"), + ) + // } +} |
