mirror of https://github.com/pressly/goose.git
e2e: move db container helpers to internal/testdb (#371)
parent
bfd4286c0f
commit
d04c088d09
|
@ -66,6 +66,10 @@ func newClickHouse(opts ...OptionsFunc) (*sql.DB, func(), error) {
|
|||
return nil, nil, err
|
||||
}
|
||||
cleanup := func() {
|
||||
if option.debug {
|
||||
// User must manually delete the Docker container.
|
||||
return
|
||||
}
|
||||
if err := pool.Purge(container); err != nil {
|
||||
log.Printf("failed to purge resource: %v", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package testdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ory/dockertest/v3"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
)
|
||||
|
||||
const (
|
||||
// https://hub.docker.com/_/mariadb
|
||||
MARIADB_IMAGE = "mariadb"
|
||||
MARIADB_VERSION = "10"
|
||||
|
||||
MARIADB_DB = "testdb"
|
||||
MARIADB_USER = "tester"
|
||||
MARIADB_PASSWORD = "password1"
|
||||
)
|
||||
|
||||
func newMariaDB(opts ...OptionsFunc) (*sql.DB, func(), error) {
|
||||
option := &options{}
|
||||
for _, f := range opts {
|
||||
f(option)
|
||||
}
|
||||
// Uses a sensible default on windows (tcp/http) and linux/osx (socket).
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to connect to docker: %v", err)
|
||||
}
|
||||
options := &dockertest.RunOptions{
|
||||
Repository: MARIADB_IMAGE,
|
||||
Tag: MARIADB_VERSION,
|
||||
Env: []string{
|
||||
"MARIADB_USER=" + MARIADB_USER,
|
||||
"MARIADB_PASSWORD=" + MARIADB_PASSWORD,
|
||||
"MARIADB_ROOT_PASSWORD=" + MARIADB_PASSWORD,
|
||||
"MARIADB_DATABASE=" + MARIADB_DB,
|
||||
},
|
||||
Labels: map[string]string{"goose_test": "1"},
|
||||
PortBindings: make(map[docker.Port][]docker.PortBinding),
|
||||
}
|
||||
if option.bindPort > 0 {
|
||||
options.PortBindings[docker.Port("3306/tcp")] = []docker.PortBinding{
|
||||
{HostPort: strconv.Itoa(option.bindPort)},
|
||||
}
|
||||
}
|
||||
container, err := pool.RunWithOptions(
|
||||
options,
|
||||
func(config *docker.HostConfig) {
|
||||
// Set AutoRemove to true so that stopped container goes away by itself.
|
||||
config.AutoRemove = true
|
||||
// config.PortBindings = options.PortBindings
|
||||
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create docker container: %v", err)
|
||||
}
|
||||
cleanup := func() {
|
||||
if option.debug {
|
||||
// User must manually delete the Docker container.
|
||||
return
|
||||
}
|
||||
if err := pool.Purge(container); err != nil {
|
||||
log.Printf("failed to purge resource: %v", err)
|
||||
}
|
||||
}
|
||||
// MySQL DSN: username:password@protocol(address)/dbname?param=value
|
||||
dsn := fmt.Sprintf("%s:%s@(%s:%s)/%s?parseTime=true&multiStatements=true",
|
||||
MARIADB_USER,
|
||||
MARIADB_PASSWORD,
|
||||
"localhost",
|
||||
container.GetPort("3306/tcp"), // Fetch port dynamically assigned to container
|
||||
MARIADB_DB,
|
||||
)
|
||||
var db *sql.DB
|
||||
// Exponential backoff-retry, because the application in the container
|
||||
// might not be ready to accept connections yet. Add an extra sleep
|
||||
// because mariadb containers take much longer to startup.
|
||||
time.Sleep(5 * time.Second)
|
||||
if err := pool.Retry(func() error {
|
||||
var err error
|
||||
db, err = sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Ping()
|
||||
},
|
||||
); err != nil {
|
||||
return nil, cleanup, fmt.Errorf("could not connect to docker database: %v", err)
|
||||
}
|
||||
return db, cleanup, nil
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package testdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/ory/dockertest/v3"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
)
|
||||
|
||||
const (
|
||||
// https://hub.docker.com/_/postgres
|
||||
POSTGRES_IMAGE = "postgres"
|
||||
POSTGRES_VERSION = "14-alpine"
|
||||
|
||||
POSTGRES_DB = "testdb"
|
||||
POSTGRES_USER = "postgres"
|
||||
POSTGRES_PASSWORD = "password1"
|
||||
)
|
||||
|
||||
func newPostgres(opts ...OptionsFunc) (*sql.DB, func(), error) {
|
||||
option := &options{}
|
||||
for _, f := range opts {
|
||||
f(option)
|
||||
}
|
||||
// Uses a sensible default on windows (tcp/http) and linux/osx (socket).
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to connect to docker: %v", err)
|
||||
}
|
||||
options := &dockertest.RunOptions{
|
||||
Repository: POSTGRES_IMAGE,
|
||||
Tag: POSTGRES_VERSION,
|
||||
Env: []string{
|
||||
"POSTGRES_USER=" + POSTGRES_USER,
|
||||
"POSTGRES_PASSWORD=" + POSTGRES_PASSWORD,
|
||||
"POSTGRES_DB=" + POSTGRES_DB,
|
||||
"listen_addresses = '*'",
|
||||
},
|
||||
Labels: map[string]string{"goose_test": "1"},
|
||||
PortBindings: make(map[docker.Port][]docker.PortBinding),
|
||||
}
|
||||
if option.bindPort > 0 {
|
||||
options.PortBindings[docker.Port("5432/tcp")] = []docker.PortBinding{
|
||||
{HostPort: strconv.Itoa(option.bindPort)},
|
||||
}
|
||||
}
|
||||
container, err := pool.RunWithOptions(
|
||||
options,
|
||||
func(config *docker.HostConfig) {
|
||||
// Set AutoRemove to true so that stopped container goes away by itself.
|
||||
config.AutoRemove = true
|
||||
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create docker container: %v", err)
|
||||
}
|
||||
cleanup := func() {
|
||||
if option.debug {
|
||||
// User must manually delete the Docker container.
|
||||
return
|
||||
}
|
||||
if err := pool.Purge(container); err != nil {
|
||||
log.Printf("failed to purge resource: %v", err)
|
||||
}
|
||||
}
|
||||
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
||||
"localhost",
|
||||
container.GetPort("5432/tcp"), // Fetch port dynamically assigned to container
|
||||
POSTGRES_USER,
|
||||
POSTGRES_PASSWORD,
|
||||
POSTGRES_DB,
|
||||
)
|
||||
var db *sql.DB
|
||||
// Exponential backoff-retry, because the application in the container
|
||||
// might not be ready to accept connections yet.
|
||||
if err := pool.Retry(
|
||||
func() error {
|
||||
var err error
|
||||
db, err = sql.Open("postgres", psqlInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Ping()
|
||||
},
|
||||
); err != nil {
|
||||
return nil, cleanup, fmt.Errorf("could not connect to docker database: %v", err)
|
||||
}
|
||||
return db, cleanup, nil
|
||||
}
|
|
@ -2,9 +2,17 @@ package testdb
|
|||
|
||||
import "database/sql"
|
||||
|
||||
// NewClickHouse starts a ClickHouse docker container, and returns
|
||||
// a connection and a cleanup function.
|
||||
// If bindPort is 0,b a random port will be used.
|
||||
func NewClickHouse(options ...OptionsFunc) (_ *sql.DB, cleanup func(), _ error) {
|
||||
// NewClickHouse starts a ClickHouse docker container. Returns db connection and a docker cleanup function.
|
||||
func NewClickHouse(options ...OptionsFunc) (db *sql.DB, cleanup func(), err error) {
|
||||
return newClickHouse(options...)
|
||||
}
|
||||
|
||||
// NewPostgres starts a PostgreSQL docker container. Returns db connection and a docker cleanup function.
|
||||
func NewPostgres(options ...OptionsFunc) (db *sql.DB, cleanup func(), err error) {
|
||||
return newPostgres(options...)
|
||||
}
|
||||
|
||||
// NewMariaDB starts a MariaDB docker container. Returns a db connection and a docker cleanup function.
|
||||
func NewMariaDB(options ...OptionsFunc) (db *sql.DB, cleanup func(), err error) {
|
||||
return newMariaDB(options...)
|
||||
}
|
||||
|
|
|
@ -8,16 +8,13 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"github.com/ory/dockertest/v3"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
"github.com/pressly/goose/v3/internal/check"
|
||||
"github.com/pressly/goose/v3/internal/testdb"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -96,170 +93,24 @@ func TestMain(m *testing.M) {
|
|||
|
||||
// newDockerDB starts a database container and returns a usable SQL connection.
|
||||
func newDockerDB(t *testing.T) (*sql.DB, error) {
|
||||
options := []testdb.OptionsFunc{
|
||||
testdb.WithBindPort(*bindPort),
|
||||
testdb.WithDebug(*debug),
|
||||
}
|
||||
var (
|
||||
db *sql.DB
|
||||
cleanup func()
|
||||
err error
|
||||
)
|
||||
switch *dialect {
|
||||
case dialectPostgres:
|
||||
return newDockerPostgresDB(t, *bindPort)
|
||||
db, cleanup, err = testdb.NewPostgres(options...)
|
||||
case dialectMySQL:
|
||||
return newDockerMariaDB(t, *bindPort)
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported dialect: %q", *dialect)
|
||||
}
|
||||
|
||||
func newDockerPostgresDB(t *testing.T, bindPort int) (*sql.DB, error) {
|
||||
const (
|
||||
dbUsername = "postgres"
|
||||
dbPassword = "password1"
|
||||
dbHost = "localhost"
|
||||
dbName = "testdb"
|
||||
)
|
||||
// Uses a sensible default on windows (tcp/http) and linux/osx (socket).
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to docker: %v", err)
|
||||
}
|
||||
options := &dockertest.RunOptions{
|
||||
Repository: "postgres",
|
||||
Tag: "14-alpine",
|
||||
Env: []string{
|
||||
"POSTGRES_USER=" + dbUsername,
|
||||
"POSTGRES_PASSWORD=" + dbPassword,
|
||||
"POSTGRES_DB=" + dbName,
|
||||
"listen_addresses = '*'",
|
||||
},
|
||||
Labels: map[string]string{"goose_test": "1"},
|
||||
PortBindings: make(map[docker.Port][]docker.PortBinding),
|
||||
}
|
||||
if bindPort > 0 {
|
||||
options.PortBindings[docker.Port("5432/tcp")] = []docker.PortBinding{
|
||||
{HostPort: strconv.Itoa(bindPort)},
|
||||
}
|
||||
}
|
||||
|
||||
container, err := pool.RunWithOptions(
|
||||
options,
|
||||
func(config *docker.HostConfig) {
|
||||
// Set AutoRemove to true so that stopped container goes away by itself.
|
||||
config.AutoRemove = true
|
||||
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create docker container: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if *debug {
|
||||
// User must manually delete the Docker container.
|
||||
return
|
||||
}
|
||||
if err := pool.Purge(container); err != nil {
|
||||
log.Printf("failed to purge resource: %v", err)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start resource: %v", err)
|
||||
}
|
||||
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
||||
dbHost,
|
||||
container.GetPort("5432/tcp"), // Fetch port dynamically assigned to container
|
||||
dbUsername,
|
||||
dbPassword,
|
||||
dbName,
|
||||
)
|
||||
var db *sql.DB
|
||||
// Exponential backoff-retry, because the application in the container
|
||||
// might not be ready to accept connections yet.
|
||||
if err := pool.Retry(
|
||||
func() error {
|
||||
var err error
|
||||
db, err = sql.Open(dialectPostgres, psqlInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Ping()
|
||||
},
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("could not connect to docker database: %v", err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func newDockerMariaDB(t *testing.T, bindPort int) (*sql.DB, error) {
|
||||
const (
|
||||
dbUsername = "tester"
|
||||
dbPassword = "password1"
|
||||
dbHost = "localhost"
|
||||
dbName = "testdb"
|
||||
)
|
||||
// Uses a sensible default on windows (tcp/http) and linux/osx (socket).
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to docker: %v", err)
|
||||
}
|
||||
options := &dockertest.RunOptions{
|
||||
Repository: "mariadb",
|
||||
Tag: "10",
|
||||
Env: []string{
|
||||
"MARIADB_USER=" + dbUsername,
|
||||
"MARIADB_PASSWORD=" + dbPassword,
|
||||
"MARIADB_ROOT_PASSWORD=" + dbPassword,
|
||||
"MARIADB_DATABASE=" + dbName,
|
||||
},
|
||||
Labels: map[string]string{"goose_test": "1"},
|
||||
// PortBindings: make(map[docker.Port][]docker.PortBinding),
|
||||
}
|
||||
if bindPort > 0 {
|
||||
options.PortBindings[docker.Port("3306/tcp")] = []docker.PortBinding{
|
||||
{HostPort: strconv.Itoa(bindPort)},
|
||||
}
|
||||
}
|
||||
|
||||
container, err := pool.RunWithOptions(
|
||||
options,
|
||||
func(config *docker.HostConfig) {
|
||||
// Set AutoRemove to true so that stopped container goes away by itself.
|
||||
config.AutoRemove = true
|
||||
// config.PortBindings = options.PortBindings
|
||||
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create docker container: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if *debug {
|
||||
// User must manually delete the Docker container.
|
||||
return
|
||||
}
|
||||
if err := pool.Purge(container); err != nil {
|
||||
log.Printf("failed to purge resource: %v", err)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start resource: %v", err)
|
||||
}
|
||||
// MySQL DSN: username:password@protocol(address)/dbname?param=value
|
||||
dsn := fmt.Sprintf("%s:%s@(%s:%s)/%s?parseTime=true&multiStatements=true",
|
||||
dbUsername,
|
||||
dbPassword,
|
||||
dbHost,
|
||||
container.GetPort("3306/tcp"), // Fetch port dynamically assigned to container
|
||||
dbName,
|
||||
)
|
||||
var db *sql.DB
|
||||
// Exponential backoff-retry, because the application in the container
|
||||
// might not be ready to accept connections yet. Add an extra sleep
|
||||
// because mariadb containers take much longer to startup.
|
||||
time.Sleep(5 * time.Second)
|
||||
if err := pool.Retry(func() error {
|
||||
var err error
|
||||
db, err = sql.Open(dialectMySQL, dsn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Ping()
|
||||
},
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("could not connect to docker database: %v", err)
|
||||
db, cleanup, err = testdb.NewMariaDB(options...)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported dialect: %q", *dialect)
|
||||
}
|
||||
check.NoError(t, err)
|
||||
t.Cleanup(cleanup)
|
||||
return db, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue