mirror of https://github.com/hashicorp/boundary
provide db.Open() and db.Migrate() as part of icu-64 (#34)
parent
e8b5384b7a
commit
c43e05be2e
@ -0,0 +1,106 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/ory/dockertest"
|
||||
)
|
||||
|
||||
type DbType int
|
||||
|
||||
const (
|
||||
UnknownDB DbType = 0
|
||||
Postgres DbType = 1
|
||||
)
|
||||
|
||||
func (db DbType) String() string {
|
||||
return [...]string{
|
||||
"unknown",
|
||||
"postgres",
|
||||
}[db]
|
||||
}
|
||||
|
||||
// Open a database connection which is long-lived.
|
||||
// You need to call Close() on the returned gorm.DB
|
||||
func Open(dbType DbType, connectionUrl string) (*gorm.DB, error) {
|
||||
db, err := gorm.Open(dbType.String(), connectionUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open database: %w", err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Migrate a database schema
|
||||
func Migrate(connectionUrl string, migrationsDirectory string) error {
|
||||
if connectionUrl == "" {
|
||||
return errors.New("connection url is unset")
|
||||
}
|
||||
if _, err := os.Stat(migrationsDirectory); os.IsNotExist(err) {
|
||||
return errors.New("error migrations directory does not exist")
|
||||
}
|
||||
// run migrations
|
||||
m, err := migrate.New(fmt.Sprintf("file://%s", migrationsDirectory), connectionUrl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create migrations: %w", err)
|
||||
}
|
||||
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
|
||||
return fmt.Errorf("unable to run migrations: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartDbInDocker
|
||||
func StartDbInDocker() (cleanup func() error, retURL string, err error) {
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
return func() error { return nil }, "", fmt.Errorf("could not connect to docker: %w", err)
|
||||
}
|
||||
|
||||
resource, err := pool.Run("postgres", "latest", []string{"POSTGRES_PASSWORD=secret", "POSTGRES_DB=watchtower"})
|
||||
if err != nil {
|
||||
return func() error { return nil }, "", fmt.Errorf("could not start resource: %w", err)
|
||||
}
|
||||
|
||||
c := func() error {
|
||||
return cleanupDockerResource(pool, resource)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("postgres://postgres:secret@localhost:%s?sslmode=disable", resource.GetPort("5432/tcp"))
|
||||
|
||||
if err := pool.Retry(func() error {
|
||||
db, err := sql.Open("postgres", url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening postgres dev container: %w", err)
|
||||
}
|
||||
|
||||
if err := db.Ping(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
return nil
|
||||
}); err != nil {
|
||||
return func() error { return nil }, "", fmt.Errorf("could not connect to docker: %w", err)
|
||||
}
|
||||
return c, url, nil
|
||||
}
|
||||
|
||||
// cleanupDockerResource will clean up the dockertest resources (postgres)
|
||||
func cleanupDockerResource(pool *dockertest.Pool, resource *dockertest.Resource) error {
|
||||
var err error
|
||||
for i := 0; i < 10; i++ {
|
||||
err = pool.Purge(resource)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if strings.Contains(err.Error(), "No such container") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Failed to cleanup local container: %s", err)
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOpen(t *testing.T) {
|
||||
cleanup, url, err := StartDbInDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
type args struct {
|
||||
dbType DbType
|
||||
connectionUrl string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
args: args{
|
||||
dbType: Postgres,
|
||||
connectionUrl: url,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
args: args{
|
||||
dbType: Postgres,
|
||||
connectionUrl: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Open(tt.args.dbType, tt.args.connectionUrl)
|
||||
defer func() {
|
||||
if err == nil {
|
||||
got.Close()
|
||||
}
|
||||
}()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Open() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if tt.wantErr && got != nil {
|
||||
t.Error("Open() wanted error and got != nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrate(t *testing.T) {
|
||||
cleanup, url, err := StartDbInDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
type args struct {
|
||||
connectionUrl string
|
||||
migrationsDirectory string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
args: args{
|
||||
connectionUrl: url,
|
||||
migrationsDirectory: "migrations/postgres",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "bad-url",
|
||||
args: args{
|
||||
connectionUrl: "",
|
||||
migrationsDirectory: "migrations/postgres",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "bad-dir",
|
||||
args: args{
|
||||
connectionUrl: url,
|
||||
migrationsDirectory: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := Migrate(tt.args.connectionUrl, tt.args.migrationsDirectory); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Migrate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue