Add httpfs DB migrations and generate the input (#35)

This commit is contained in:
Jeff Mitchell authored and GitHub committed 2020-05-11 12:42:10 -04:00
1 parent bd5ddea6e4
commit 7f2aa5ca8f
21 files changed
+576 -139

No files matched your search

+13 -50
View File
@@ -4,7 +4,6 @@ import (
"context"
"crypto/rand"
"crypto/tls"
"database/sql"
"errors"
"fmt"
"io"
@@ -18,7 +17,6 @@ import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-hclog"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/vault/internalshared/configutil"
"github.com/hashicorp/vault/internalshared/gatedwriter"
"github.com/hashicorp/vault/internalshared/reloadutil"
@@ -26,9 +24,9 @@ import (
"github.com/hashicorp/vault/sdk/helper/mlock"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/watchtower/globals"
"github.com/hashicorp/watchtower/internal/db"
"github.com/hashicorp/watchtower/version"
"github.com/mitchellh/cli"
"github.com/ory/dockertest/v3"
"golang.org/x/net/http/httpproxy"
"google.golang.org/grpc/grpclog"
@@ -60,12 +58,8 @@ type Server struct {
Listeners []*ServerListener
DevDatabasePassword string
DevDatabaseName string
DevDatabasePort string
dockertestPool *dockertest.Pool
dockertestResource *dockertest.Resource
DevDatabaseUrl string
DevDatabaseCleanupFunc func() error
}
func NewServer() *Server {
@@ -372,54 +366,23 @@ func (b *Server) RunShutdownFuncs(ui cli.Ui) {
}
func (b *Server) CreateDevDatabase() error {
pool, err := dockertest.NewPool("")
c, url, err := db.InitDbInDocker("postgres")
if err != nil {
return fmt.Errorf("could not connect to docker: %w", err)
c()
return fmt.Errorf("unable to start dev database: %w", err)
}
resource, err := pool.Run("postgres", "latest", []string{"POSTGRES_PASSWORD=secret", "POSTGRES_DB=watchtower"})
if err != nil {
return fmt.Errorf("could not start resource: %w", err)
}
b.DevDatabaseCleanupFunc = c
b.DevDatabaseUrl = url
if err := pool.Retry(func() error {
db, err := sql.Open("postgres", fmt.Sprintf("postgres://postgres:secret@localhost:%s/watchtower?sslmode=disable", resource.GetPort("5432/tcp")))
if err != nil {
return fmt.Errorf("error opening postgres dev container: %w", err)
}
var mErr *multierror.Error
if err := db.Ping(); err != nil {
mErr = multierror.Append(fmt.Errorf("error pinging dev database container: %w", err))
}
if err := db.Close(); err != nil {
mErr = multierror.Append(fmt.Errorf("error closing dev database container: %w", err))
}
return mErr.ErrorOrNil()
}); err != nil {
return fmt.Errorf("could not connect to docker: %w", err)
}
b.dockertestPool = pool
b.dockertestResource = resource
b.DevDatabaseName = "watchtower"
b.DevDatabasePassword = "secret"
b.DevDatabasePort = resource.GetPort("5432/tcp")
b.InfoKeys = append(b.InfoKeys, "dev database name", "dev database password", "dev database port")
b.Info["dev database name"] = "watchtower"
b.Info["dev database password"] = "secret"
b.Info["dev database port"] = b.DevDatabasePort
b.InfoKeys = append(b.InfoKeys, "dev database url")
b.Info["dev database url"] = b.DevDatabaseUrl
return nil
}
func (b *Server) DestroyDevDatabase() error {
if b.dockertestPool == nil {
return nil
if b.DevDatabaseCleanupFunc != nil {
return b.DevDatabaseCleanupFunc()
}
if b.dockertestResource == nil {
return errors.New("found a pool for dev database container but no resource")
}
return b.dockertestPool.Purge(b.dockertestResource)
return errors.New("no dev database cleanup function found")
}
+59 -7
View File
@@ -8,6 +8,7 @@ import (
"strings"
"github.com/golang-migrate/migrate/v4"
"github.com/hashicorp/watchtower/internal/db/migrations"
"github.com/jinzhu/gorm"
"github.com/ory/dockertest"
)
@@ -55,28 +56,57 @@ func Migrate(connectionUrl string, migrationsDirectory string) error {
return nil
}
// InitDbInDocker initializes the data store within docker or an existing PG_URL
func InitDbInDocker(dialect string) (cleanup func() error, retURL string, err error) {
switch dialect {
case "postgres":
if os.Getenv("PG_URL") != "" {
if err := InitStore(dialect, func() error { return nil }, os.Getenv("PG_URL")); err != nil {
return func() error { return nil }, os.Getenv("PG_URL"), fmt.Errorf("error initializing store: %w", err)
}
return func() error { return nil }, os.Getenv("PG_URL"), nil
}
}
c, url, err := StartDbInDocker(dialect)
if err != nil {
return func() error { return nil }, "", fmt.Errorf("could not start docker: %w", err)
}
if err := InitStore(dialect, c, url); err != nil {
return func() error { return nil }, "", fmt.Errorf("error initializing store: %w", err)
}
return c, url, nil
}
// StartDbInDocker
func StartDbInDocker() (cleanup func() error, retURL string, err error) {
func StartDbInDocker(dialect string) (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"})
var resource *dockertest.Resource
var url string
switch dialect {
case "postgres":
resource, err = pool.Run("postgres", "latest", []string{"POSTGRES_PASSWORD=secret", "POSTGRES_DB=watchtower"})
url = "postgres://postgres:secret@localhost:%s?sslmode=disable"
default:
panic(fmt.Sprintf("unknown dialect %q", dialect))
}
if err != nil {
return func() error { return nil }, "", fmt.Errorf("could not start resource: %w", err)
}
c := func() error {
cleanup = func() error {
return cleanupDockerResource(pool, resource)
}
url := fmt.Sprintf("postgres://postgres:secret@localhost:%s?sslmode=disable", resource.GetPort("5432/tcp"))
url = fmt.Sprintf(url, resource.GetPort("5432/tcp"))
if err := pool.Retry(func() error {
db, err := sql.Open("postgres", url)
db, err := sql.Open(dialect, url)
if err != nil {
return fmt.Errorf("error opening postgres dev container: %w", err)
return fmt.Errorf("error opening %s dev container: %w", dialect, err)
}
if err := db.Ping(); err != nil {
@@ -87,7 +117,29 @@ func StartDbInDocker() (cleanup func() error, retURL string, err error) {
}); err != nil {
return func() error { return nil }, "", fmt.Errorf("could not connect to docker: %w", err)
}
return c, url, nil
return cleanup, url, nil
}
// InitStore will execute the migrations needed to initialize the store for tests
func InitStore(dialect string, cleanup func() error, url string) error {
// run migrations
source, err := migrations.NewMigrationSource(dialect)
if err != nil {
cleanup()
return fmt.Errorf("error creating migration driver: %w", err)
}
m, err := migrate.NewWithSourceInstance("httpfs", source, url)
if err != nil {
cleanup()
return fmt.Errorf("error creating migrations: %w", err)
}
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
cleanup()
return fmt.Errorf("error running migrations: %w", err)
}
return nil
}
// cleanupDockerResource will clean up the dockertest resources (postgres)
+2 -2
View File
@@ -5,7 +5,7 @@ import (
)
func TestOpen(t *testing.T) {
cleanup, url, err := StartDbInDocker()
cleanup, url, err := StartDbInDocker("postgres")
if err != nil {
t.Fatal(err)
}
@@ -56,7 +56,7 @@ func TestOpen(t *testing.T) {
}
func TestMigrate(t *testing.T) {
cleanup, url, err := StartDbInDocker()
cleanup, url, err := StartDbInDocker("postgres")
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -21,7 +21,7 @@ returning id;
`
)
cleanup, conn := TestSetup(t, "migrations/postgres")
cleanup, conn := TestSetup(t, "postgres")
defer cleanup()
defer conn.Close()
+121
View File
@@ -0,0 +1,121 @@
package migrations
import (
"bytes"
"fmt"
"net/http"
"os"
"sort"
"strings"
"time"
"github.com/golang-migrate/migrate/v4/source"
"github.com/golang-migrate/migrate/v4/source/httpfs"
)
// migrationDriver satisfies the remaining need of the Driver interface, since
// the package uses PartialDriver under the hood
type migrationDriver struct {
dialect string
}
// Open returns the given "file"
func (m *migrationDriver) Open(name string) (http.File, error) {
var ff *fakeFile
switch m.dialect {
case "postgres":
ff = postgresMigrations[name]
}
if ff == nil {
return nil, os.ErrNotExist
}
ff.name = strings.TrimPrefix(name, "migrations/")
ff.reader = bytes.NewReader(ff.bytes)
ff.dialect = m.dialect
return ff, nil
}
// NewMigrationSource creates a source.Driver using httpfs with the given dialect
func NewMigrationSource(dialect string) (source.Driver, error) {
switch dialect {
case "postgres":
default:
return nil, fmt.Errorf("unknown migrations dialect %s", dialect)
}
return httpfs.New(&migrationDriver{dialect}, "migrations")
}
// fakeFile is used to satisfy the http.File interface
type fakeFile struct {
name string
bytes []byte
reader *bytes.Reader
dialect string
}
func (f *fakeFile) Read(p []byte) (n int, err error) {
return f.reader.Read(p)
}
func (f *fakeFile) Seek(offset int64, whence int) (int64, error) {
return f.reader.Seek(offset, whence)
}
func (f *fakeFile) Close() error { return nil }
// Readdir returns os.FileInfo values, in sorted order, and eliding the
// migrations "dir"
func (f *fakeFile) Readdir(count int) ([]os.FileInfo, error) {
// Get the right map
var migrationsMap map[string]*fakeFile
switch f.dialect {
case "postgres":
migrationsMap = postgresMigrations
default:
return nil, fmt.Errorf("unknown database dialect %s", f.dialect)
}
// Sort the keys. May not be necessary but feels nice.
keys := make([]string, 0, len(migrationsMap))
for k := range migrationsMap {
keys = append(keys, k)
}
sort.Strings(keys)
// Create the slice of fileinfo objects to return
ret := make([]os.FileInfo, 0, len(migrationsMap))
for _, v := range keys {
// We need "migrations" in the map for the initial Open call but we
// should not return it as part of the "directory"'s "files".
if v == "migrations" {
continue
}
stat, err := migrationsMap[v].Stat()
if err != nil {
return nil, err
}
ret = append(ret, stat)
}
return ret, nil
}
// Stat returns a new fakeFileInfo object with the necessary bits
func (f *fakeFile) Stat() (os.FileInfo, error) {
return &fakeFileInfo{
name: f.name,
size: int64(len(f.bytes)),
}, nil
}
// fakeFileInfo satisfies os.FileInfo but represents our fake "files"
type fakeFileInfo struct {
name string
size int64
}
func (f *fakeFileInfo) Name() string { return f.name }
func (f *fakeFileInfo) Size() int64 { return f.size }
func (f *fakeFileInfo) Mode() os.FileMode { return os.ModePerm }
func (f *fakeFileInfo) ModTime() time.Time { return time.Now() }
func (f *fakeFileInfo) IsDir() bool { return false }
func (f *fakeFileInfo) Sys() interface{} { return nil }
@@ -0,0 +1,8 @@
# Be sure to place this BEFORE `include` directives, if any.
THIS_FILE := $(lastword $(MAKEFILE_LIST))
migrations:
go run -tags genmigrations .
goimports -w ${GEN_BASEPATH}/internal/db/migrations
.PHONY: migrations
@@ -0,0 +1,88 @@
// +build genmigrations
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"sort"
"strings"
"text/template"
)
// generate looks for migration sql in a directory for the given dialect and
// applies the templates below to the contents of the files, building up a
// migrations map for the dialect
func generate(dialect string) {
baseDir := os.Getenv("GEN_BASEPATH") + fmt.Sprint("/internal/db/migrations")
dir, err := os.Open(fmt.Sprintf("%s/%s", baseDir, dialect))
if err != nil {
fmt.Printf("error opening dir with dialect %s: %v\n", dialect, err)
os.Exit(1)
}
names, err := dir.Readdirnames(0)
if err != nil {
fmt.Printf("error reading dir names with dialect %s: %v\n", dialect, err)
os.Exit(1)
}
outBuf := bytes.NewBuffer(nil)
valuesBuf := bytes.NewBuffer(nil)
sort.Strings(names)
for _, name := range names {
if !strings.HasSuffix(name, ".sql") {
continue
}
contents, err := ioutil.ReadFile(fmt.Sprintf("%s/%s/%s", baseDir, dialect, name))
if err != nil {
fmt.Printf("error opening file %s with dialect %s: %v", name, dialect, err)
os.Exit(1)
}
migrationsValueTemplate.Execute(valuesBuf, struct {
Name string
Contents string
}{
Name: name,
Contents: string(contents),
})
}
migrationsTemplate.Execute(outBuf, struct {
Type string
Values string
}{
Type: dialect,
Values: valuesBuf.String(),
})
outFile := fmt.Sprintf("%s/%s.gen.go", baseDir, dialect)
if err := ioutil.WriteFile(outFile, outBuf.Bytes(), 0644); err != nil {
fmt.Printf("error writing file %q: %v\n", outFile, err)
os.Exit(1)
}
}
var migrationsTemplate = template.Must(template.New("").Parse(
`// Code generated by "make migrations"; DO NOT EDIT.
package migrations
import (
"bytes"
)
var {{ .Type }}Migrations = map[string]*fakeFile{
"migrations": {
name: "migrations",
},
{{ .Values }}
}
`))
var migrationsValueTemplate = template.Must(template.New("").Parse(
`"migrations/{{ .Name }}": {
name: "{{ .Name }}",
bytes: []byte(` + "`\n{{ .Contents }}\n`" + `),
},
`))
@@ -0,0 +1,7 @@
// +build genmigrations
package main
func main() {
generate("postgres")
}
+225
View File
@@ -0,0 +1,225 @@
// Code generated by "make migrations"; DO NOT EDIT.
package migrations
var postgresMigrations = map[string]*fakeFile{
"migrations": {
name: "migrations",
},
"migrations/01_oplog.up.sql": {
name: "01_oplog.up.sql",
bytes: []byte(`
CREATE TABLE if not exists oplog_entry (
id bigint generated always as identity primary key,
create_time timestamp with time zone default current_timestamp,
update_time timestamp with time zone default current_timestamp,
version text NOT NULL,
aggregate_name text NOT NULL,
"data" bytea NOT NULL
);
CREATE TABLE if not exists oplog_ticket (
id bigint generated always as identity primary key,
create_time timestamp with time zone default current_timestamp,
update_time timestamp with time zone default current_timestamp,
"name" text NOT NULL UNIQUE,
"version" bigint NOT NULL
);
CREATE TABLE if not exists oplog_metadata (
id bigint generated always as identity primary key,
create_time timestamp with time zone default current_timestamp,
entry_id bigint NOT NULL REFERENCES oplog_entry(id) ON DELETE CASCADE ON UPDATE CASCADE,
"key" text NOT NULL,
value text NULL
);
create index if not exists idx_oplog_metatadata_key on oplog_metadata(key);
create index if not exists idx_oplog_metatadata_value on oplog_metadata(value);
INSERT INTO oplog_ticket (name, version)
values
('default', 1),
('iam_scope', 1),
('iam_user', 1),
('iam_auth_method', 1),
('iam_group', 1),
('iam_group_member_user', 1),
('iam_role', 1),
('iam_role_grant', 1),
('iam_role_group', 1),
('iam_role_user', 1),
('db_test_user', 1),
('db_test_car', 1),
('db_test_rental', 1);
`),
},
"migrations/02_domain_types.down.sql": {
name: "02_domain_types.down.sql",
bytes: []byte(`
begin;
drop domain wt_public_id;
commit;
`),
},
"migrations/02_domain_types.up.sql": {
name: "02_domain_types.up.sql",
bytes: []byte(`
begin;
create domain wt_public_id as text
check(
length(trim(value)) > 10
);
comment on domain wt_public_id is
'Random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
commit;
`),
},
"migrations/03_db.down.sql": {
name: "03_db.down.sql",
bytes: []byte(`
drop table if exists db_test_user;
drop table if exists db_test_car;
drop table if exists db_test_rental;
`),
},
"migrations/03_db.up.sql": {
name: "03_db.up.sql",
bytes: []byte(`
-- create test tables used in the unit tests for the internal/db package
-- these tables (db_test_user, db_test_car, db_test_rental) are not part
-- of the Watchtower domain model... they are simply used for testing the internal/db package
CREATE TABLE if not exists db_test_user (
id bigint generated always as identity primary key,
create_time timestamp with time zone default current_timestamp,
update_time timestamp with time zone default current_timestamp,
public_id text NOT NULL UNIQUE,
name text UNIQUE,
phone_number text,
email text
);
CREATE TABLE if not exists db_test_car (
id bigint generated always as identity primary key,
create_time timestamp with time zone default current_timestamp,
update_time timestamp with time zone default current_timestamp,
public_id text NOT NULL UNIQUE,
name text UNIQUE,
model text,
mpg smallint
);
CREATE TABLE if not exists db_test_rental (
id bigint generated always as identity primary key,
create_time timestamp with time zone default current_timestamp,
update_time timestamp with time zone default current_timestamp,
public_id text NOT NULL UNIQUE,
name text UNIQUE,
user_id bigint not null REFERENCES db_test_user(id),
car_id bigint not null REFERENCES db_test_car(id)
);
`),
},
"migrations/04_iam.down.sql": {
name: "04_iam.down.sql",
bytes: []byte(`
BEGIN;
drop table if exists iam_scope CASCADE;
drop trigger if exists iam_scope_insert;
drop function if exists iam_sub_scopes_func;
COMMIT;
`),
},
"migrations/04_iam.up.sql": {
name: "04_iam.up.sql",
bytes: []byte(`
BEGIN;
CREATE TABLE if not exists iam_scope_type_enm (
string text NOT NULL primary key CHECK(string IN ('unknown', 'organization', 'project'))
);
INSERT INTO iam_scope_type_enm (string)
values
('unknown'),
('organization'),
('project');
CREATE TABLE if not exists iam_scope (
public_id wt_public_id primary key,
create_time timestamp with time zone default current_timestamp,
update_time timestamp with time zone default current_timestamp,
name text,
type text NOT NULL REFERENCES iam_scope_type_enm(string) CHECK(
(
type = 'organization'
and parent_id = NULL
)
or (
type = 'project'
and parent_id IS NOT NULL
)
),
description text,
parent_id text REFERENCES iam_scope(public_id) ON DELETE CASCADE ON UPDATE CASCADE
);
create table if not exists iam_scope_organization (
scope_id wt_public_id NOT NULL UNIQUE REFERENCES iam_scope(public_id) ON DELETE CASCADE ON UPDATE CASCADE,
name text UNIQUE,
primary key(scope_id)
);
create table if not exists iam_scope_project (
scope_id wt_public_id NOT NULL REFERENCES iam_scope(public_id) ON DELETE CASCADE ON UPDATE CASCADE,
parent_id wt_public_id NOT NULL REFERENCES iam_scope_organization(scope_id) ON DELETE CASCADE ON UPDATE CASCADE,
name text,
unique(parent_id, name),
primary key(scope_id, parent_id)
);
CREATE
OR REPLACE FUNCTION iam_sub_scopes_func() RETURNS TRIGGER
SET SCHEMA
'public' LANGUAGE plpgsql AS $$ DECLARE parent_type INT;
BEGIN IF new.type = 'organization' THEN
insert into iam_scope_organization (scope_id, name)
values
(new.public_id, new.name);
return NEW;
END IF;
IF new.type = 'project' THEN
insert into iam_scope_project (scope_id, parent_id, name)
values
(new.public_id, new.parent_id, new.name);
return NEW;
END IF;
RAISE EXCEPTION 'unknown scope type';
END;
$$;
CREATE TRIGGER iam_scope_insert
AFTER
insert ON iam_scope FOR EACH ROW EXECUTE PROCEDURE iam_sub_scopes_func();
CREATE
OR REPLACE FUNCTION iam_immutable_scope_type_func() RETURNS TRIGGER
SET SCHEMA
'public' LANGUAGE plpgsql AS $$ DECLARE parent_type INT;
BEGIN IF new.type != old.type THEN
RAISE EXCEPTION 'scope type cannot be updated';
END IF;
return NEW;
END;
$$;
CREATE TRIGGER iam_scope_update
BEFORE
update ON iam_scope FOR EACH ROW EXECUTE PROCEDURE iam_immutable_scope_type_func();
COMMIT;
`),
},
}
+10 -10
View File
@@ -15,7 +15,7 @@ import (
func TestGormReadWriter_Update(t *testing.T) {
// intentionally not run with t.Parallel so we don't need to use DoTx for the Update tests
cleanup, db := TestSetup(t, "migrations/postgres")
cleanup, db := TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer db.Close()
@@ -163,7 +163,7 @@ func TestGormReadWriter_Update(t *testing.T) {
func TestGormReadWriter_Create(t *testing.T) {
// intentionally not run with t.Parallel so we don't need to use DoTx for the Create tests
cleanup, db := TestSetup(t, "migrations/postgres")
cleanup, db := TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer db.Close()
@@ -271,7 +271,7 @@ func TestGormReadWriter_Create(t *testing.T) {
func TestGormReadWriter_LookupByName(t *testing.T) {
t.Parallel()
cleanup, db := TestSetup(t, "migrations/postgres")
cleanup, db := TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer db.Close()
@@ -326,7 +326,7 @@ func TestGormReadWriter_LookupByName(t *testing.T) {
func TestGormReadWriter_LookupByPublicId(t *testing.T) {
t.Parallel()
cleanup, db := TestSetup(t, "migrations/postgres")
cleanup, db := TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer db.Close()
@@ -381,7 +381,7 @@ func TestGormReadWriter_LookupByPublicId(t *testing.T) {
func TestGormReadWriter_LookupWhere(t *testing.T) {
t.Parallel()
cleanup, db := TestSetup(t, "migrations/postgres")
cleanup, db := TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer db.Close()
@@ -431,7 +431,7 @@ func TestGormReadWriter_LookupWhere(t *testing.T) {
func TestGormReadWriter_SearchWhere(t *testing.T) {
t.Parallel()
cleanup, db := TestSetup(t, "migrations/postgres")
cleanup, db := TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer db.Close()
@@ -481,7 +481,7 @@ func TestGormReadWriter_SearchWhere(t *testing.T) {
func TestGormReadWriter_DB(t *testing.T) {
t.Parallel()
cleanup, db := TestSetup(t, "migrations/postgres")
cleanup, db := TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer db.Close()
@@ -504,7 +504,7 @@ func TestGormReadWriter_DB(t *testing.T) {
func TestGormReadWriter_DoTx(t *testing.T) {
t.Parallel()
cleanup, db := TestSetup(t, "migrations/postgres")
cleanup, db := TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer db.Close()
@@ -604,7 +604,7 @@ func TestGormReadWriter_DoTx(t *testing.T) {
func TestGormReadWriter_Delete(t *testing.T) {
// intentionally not run with t.Parallel so we don't need to use DoTx for the Create tests
cleanup, db := TestSetup(t, "migrations/postgres")
cleanup, db := TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer db.Close()
@@ -781,7 +781,7 @@ func TestGormReadWriter_Delete(t *testing.T) {
func TestGormReadWriter_ScanRows(t *testing.T) {
t.Parallel()
cleanup, db := TestSetup(t, "migrations/postgres")
cleanup, db := TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer db.Close()
+3 -37
View File
@@ -2,11 +2,8 @@ package db
import (
"crypto/rand"
"fmt"
"os"
"testing"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
wrapping "github.com/hashicorp/go-kms-wrapping"
@@ -15,19 +12,15 @@ import (
)
// setup the tests (initialize the database one-time and intialized testDatabaseURL)
func TestSetup(t *testing.T, migrationsDirectory string) (func() error, *gorm.DB) {
if _, err := os.Stat(migrationsDirectory); os.IsNotExist(err) {
t.Fatal("error migrationsDirectory does not exist")
}
func TestSetup(t *testing.T, dialect string) (func() error, *gorm.DB) {
cleanup := func() error { return nil }
var url string
var err error
cleanup, url, err = initDbInDocker(t, migrationsDirectory)
cleanup, url, err = InitDbInDocker(dialect)
if err != nil {
t.Fatal(err)
}
db, err := gorm.Open("postgres", url)
db, err := gorm.Open(dialect, url)
if err != nil {
t.Fatal(err)
}
@@ -51,30 +44,3 @@ func TestWrapper(t *testing.T) wrapping.Wrapper {
return root
}
// initDbInDocker initializes postgres within dockertest for the unit tests
func initDbInDocker(t *testing.T, migrationsDirectory string) (cleanup func() error, retURL string, err error) {
if os.Getenv("PG_URL") != "" {
TestInitStore(t, func() error { return nil }, os.Getenv("PG_URL"), migrationsDirectory)
return func() error { return nil }, os.Getenv("PG_URL"), nil
}
c, url, err := StartDbInDocker()
if err != nil {
return func() error { return nil }, "", fmt.Errorf("could not start docker: %w", err)
}
TestInitStore(t, c, url, migrationsDirectory)
return c, url, nil
}
// TestInitStore will execute the migrations needed to initialize the store for tests
func TestInitStore(t *testing.T, cleanup func() error, url string, migrationsDirectory string) {
// run migrations
m, err := migrate.New(fmt.Sprintf("file://%s", migrationsDirectory), url)
if err != nil {
cleanup()
t.Fatalf("Error creating migrations: %s", err)
}
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
cleanup()
t.Fatalf("Error running migrations: %s", err)
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ import (
func Test_Utils(t *testing.T) {
t.Parallel()
cleanup, conn := TestSetup(t, "migrations/postgres")
cleanup, conn := TestSetup(t, "postgres")
defer cleanup()
defer conn.Close()
t.Run("nothing", func(t *testing.T) {
+2 -2
View File
@@ -13,7 +13,7 @@ import (
func Test_Repository_CreateScope(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
cleanup, conn := db.TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
@@ -52,7 +52,7 @@ func Test_Repository_CreateScope(t *testing.T) {
func Test_Repository_UpdateScope(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
cleanup, conn := db.TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
+3 -3
View File
@@ -15,7 +15,7 @@ import (
func TestNewRepository(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
cleanup, conn := db.TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
@@ -100,7 +100,7 @@ func TestNewRepository(t *testing.T) {
}
func Test_Repository_create(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
cleanup, conn := db.TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
@@ -149,7 +149,7 @@ func Test_Repository_create(t *testing.T) {
func Test_dbRepository_update(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
cleanup, conn := db.TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
+5 -5
View File
@@ -12,7 +12,7 @@ import (
func Test_NewScope(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
cleanup, conn := db.TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
@@ -49,7 +49,7 @@ func Test_NewScope(t *testing.T) {
}
func Test_ScopeCreate(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
cleanup, conn := db.TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
@@ -89,7 +89,7 @@ func Test_ScopeCreate(t *testing.T) {
func Test_ScopeUpdate(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
cleanup, conn := db.TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
@@ -125,7 +125,7 @@ func Test_ScopeUpdate(t *testing.T) {
}
func Test_ScopeGetScope(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
cleanup, conn := db.TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
@@ -176,7 +176,7 @@ func TestScope_ResourceType(t *testing.T) {
func TestScope_Clone(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
cleanup, conn := db.TestSetup(t, "postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()