Skip to content

Commit 7797ce6

Browse files
authored
Merge pull request #50 from lukaszbudnik/better-error-handling-and-db-mock
Better error handling, db mock, changes in internal API
2 parents 30270bf + 9c3a0cd commit 7797ce6

23 files changed

+1382
-417
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ go:
1616
- "1.9"
1717
- "1.10"
1818
- "1.11"
19-
- "tip"
2019

2120
before_script:
2221
- sh -c "if [ '$DB' = 'postgresql' ]; then psql -U postgres -c 'create database migrator_test'; fi"

README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ migrator can run as a HTTP REST service. Further, there is a ready-to-go migrato
88

99
# Usage
1010

11+
Important: Migrator since its inception supported both CLI and REST API. However, CLI is deprecated as of v2.2 and will be removed in migrator v3.0. Starting v3.0 only REST API will be supported.
12+
1113
Short and sweet.
1214

1315
```
@@ -57,14 +59,6 @@ create table if not exists {schema}.modules ( k int, v text );
5759
insert into {schema}.modules values ( 123, '123' );
5860
```
5961

60-
# DB Schemas
61-
62-
When using migrator please remember about these:
63-
64-
* migrator creates `migrator` schema (where `migrator_migrations` and `migrator_tenants` tables reside) automatically
65-
* when adding a new tenant migrator creates a new schema automatically
66-
* single schemas are not created automatically, for this you must add initial migration with `create schema` SQL statement (see example above)
67-
6862
# Server mode
6963

7064
When migrator is run with `-mode server` it starts a HTTP service and exposes simple REST API which you can use to invoke migrator actions remotely.
@@ -84,13 +78,21 @@ Some curl examples to get you started:
8478
curl http://localhost:8080/
8579
curl http://localhost:8080/diskMigrations
8680
curl http://localhost:8080/tenants
87-
curl -X POST -H "Content-Type: application/json" -d '{"name": "new_tenant"}' http://localhost:8080/tenants
8881
curl http://localhost:8080/migrations
8982
curl -X POST http://localhost:8080/migrations
83+
curl -X POST -H "Content-Type: application/json" -d '{"name": "new_tenant"}' http://localhost:8080/tenants
9084
```
9185

9286
Port is configurable in `migrator.yaml` and defaults to 8080. Should you need HTTPS capabilities I encourage you to use nginx/apache/haproxy for TLS offloading.
9387

88+
# DB Schemas
89+
90+
When using migrator please remember about these:
91+
92+
* migrator creates `migrator` schema (where `migrator_migrations` and `migrator_tenants` tables reside) automatically
93+
* when adding a new tenant migrator creates a new schema automatically
94+
* single schemas are not created automatically, for this you must add initial migration with `create schema` SQL statement (see example above)
95+
9496
# Supported databases
9597

9698
Currently migrator supports the following databases and their flavours:
@@ -167,7 +169,7 @@ cd migrator
167169
./setup.sh
168170
```
169171

170-
Migrator supports the following Go versions: 1.8, 1.9, 1.10, 1.11, and tip (all built on Travis).
172+
Migrator supports the following Go versions: 1.8, 1.9, 1.10, and 1.11 (all built on Travis).
171173

172174
# Contributing, code style, running unit & integration tests
173175

config/config_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ package config
22

33
import (
44
"fmt"
5-
"github.com/stretchr/testify/assert"
6-
"gopkg.in/validator.v2"
7-
"gopkg.in/yaml.v2"
85
"io/ioutil"
96
"log"
107
"os"
118
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"gopkg.in/validator.v2"
12+
"gopkg.in/yaml.v2"
1213
)
1314

1415
func noopLogger() *log.Logger {
@@ -63,13 +64,13 @@ slackWebHook: https://hooks.slack.com/services/TTT/BBB/XXX`
6364
assert.Equal(t, expected, actual)
6465
}
6566

66-
func TestConfigPanicFromEmptyFile(t *testing.T) {
67+
func TestConfigReadFromEmptyFileError(t *testing.T) {
6768
config, err := FromFile("../test/empty.yaml")
6869
assert.Nil(t, config)
6970
assert.IsType(t, (validator.ErrorMap)(nil), err, "Should error because of validation errors")
7071
}
7172

72-
func TestConfigPanicFromNonExistingFile(t *testing.T) {
73+
func TestConfigReadFromNonExistingFileError(t *testing.T) {
7374
config, err := FromFile("abcxyz.yaml")
7475
assert.Nil(t, config)
7576
assert.IsType(t, (*os.PathError)(nil), err, "Should error because non-existing file")

core/core.go

Lines changed: 133 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package core
22

33
import (
4+
"errors"
45
"fmt"
56
"log"
67

@@ -43,152 +44,217 @@ type ExecuteFlags struct {
4344

4445
// GetDiskMigrations is a function which loads all migrations from disk as defined in config passed as first argument
4546
// and using loader created by a function passed as second argument
46-
func GetDiskMigrations(config *config.Config, createLoader func(*config.Config) loader.Loader) []types.Migration {
47+
func GetDiskMigrations(config *config.Config, createLoader func(*config.Config) loader.Loader) ([]types.Migration, error) {
4748
loader := createLoader(config)
48-
diskMigrations := loader.GetDiskMigrations()
49-
return diskMigrations
49+
return loader.GetDiskMigrations()
5050
}
5151

5252
// GetDBTenants is a function which loads all tenants for multi-tenant schemas from DB as defined in config passed as first argument
5353
// and using connector created by a function passed as second argument
54-
func GetDBTenants(config *config.Config, createConnector func(*config.Config) db.Connector) []string {
55-
connector := createConnector(config)
56-
connector.Init()
54+
func GetDBTenants(config *config.Config, newConnector func(*config.Config) (db.Connector, error)) ([]string, error) {
55+
connector, err := newConnector(config)
56+
if err != nil {
57+
return nil, err
58+
}
59+
if err := connector.Init(); err != nil {
60+
return nil, err
61+
}
5762
defer connector.Dispose()
58-
dbTenants := connector.GetTenants()
59-
return dbTenants
63+
return connector.GetTenants()
6064
}
6165

6266
// GetDBMigrations is a function which loads all DB migrations for multi-tenant schemas from DB as defined in config passed as first argument
6367
// and using connector created by a function passed as second argument
64-
func GetDBMigrations(config *config.Config, createConnector func(*config.Config) db.Connector) []types.MigrationDB {
65-
connector := createConnector(config)
66-
connector.Init()
68+
func GetDBMigrations(config *config.Config, newConnector func(*config.Config) (db.Connector, error)) ([]types.MigrationDB, error) {
69+
connector, err := newConnector(config)
70+
if err != nil {
71+
return nil, err
72+
}
73+
if err := connector.Init(); err != nil {
74+
return nil, err
75+
}
6776
defer connector.Dispose()
68-
dbMigrations := connector.GetDBMigrations()
69-
return dbMigrations
77+
return connector.GetDBMigrations()
7078
}
7179

7280
// ApplyMigrations is a function which applies disk migrations to DB as defined in config passed as first argument
7381
// and using connector created by a function passed as second argument and disk loader created by a function passed as third argument
74-
func ApplyMigrations(config *config.Config, createConnector func(*config.Config) db.Connector, createLoader func(*config.Config) loader.Loader) []types.Migration {
75-
diskMigrations := GetDiskMigrations(config, createLoader)
82+
func ApplyMigrations(config *config.Config, newConnector func(*config.Config) (db.Connector, error), createLoader func(*config.Config) loader.Loader) (migrationsToApply []types.Migration, err error) {
83+
diskMigrations, err := GetDiskMigrations(config, createLoader)
84+
if err != nil {
85+
return
86+
}
7687
log.Printf("Read disk migrations: %d", len(diskMigrations))
7788

78-
dbMigrations := GetDBMigrations(config, createConnector)
89+
dbMigrations, err := GetDBMigrations(config, newConnector)
90+
if err != nil {
91+
return
92+
}
7993
log.Printf("Read DB migrations: %d", len(dbMigrations))
8094

81-
migrationsToApply := migrations.ComputeMigrationsToApply(diskMigrations, dbMigrations)
95+
migrationsToApply = migrations.ComputeMigrationsToApply(diskMigrations, dbMigrations)
8296
log.Printf("Found migrations to apply: %d", len(migrationsToApply))
8397

84-
doApplyMigrations(migrationsToApply, config, createConnector)
85-
86-
notifier := notifications.CreateNotifier(config)
87-
text := fmt.Sprintf("Migrations applied: %d", len(migrationsToApply))
88-
resp, err := notifier.Notify(text)
89-
98+
err = doApplyMigrations(migrationsToApply, config, newConnector)
9099
if err != nil {
91-
log.Printf("Notifier err: %v", err)
92-
} else {
93-
log.Printf("Notifier response: %v", resp)
100+
return
94101
}
95102

96-
return migrationsToApply
103+
text := fmt.Sprintf("Migrations applied: %d", len(migrationsToApply))
104+
sendNotification(config, text)
105+
106+
return
97107
}
98108

99109
// AddTenant creates new tenant in DB and applies all tenant migrations
100-
func AddTenant(tenant string, config *config.Config, createConnector func(*config.Config) db.Connector, createLoader func(*config.Config) loader.Loader) []types.Migration {
110+
func AddTenant(tenant string, config *config.Config, newConnector func(*config.Config) (db.Connector, error), createLoader func(*config.Config) loader.Loader) (migrationsToApply []types.Migration, err error) {
101111

102-
diskMigrations := GetDiskMigrations(config, createLoader)
112+
diskMigrations, err := GetDiskMigrations(config, createLoader)
113+
if err != nil {
114+
return
115+
}
103116
log.Printf("Read disk migrations: %d", len(diskMigrations))
104117

105118
// filter only tenant schemas
106-
// var migrationsToApply []types.Migration
107-
migrationsToApply := migrations.FilterTenantMigrations(diskMigrations)
119+
migrationsToApply = migrations.FilterTenantMigrations(diskMigrations)
108120
log.Printf("Found migrations to apply: %d", len(migrationsToApply))
109121

110-
doAddTenantAndApplyMigrations(tenant, migrationsToApply, config, createConnector)
111-
112-
notifier := notifications.CreateNotifier(config)
113-
text := fmt.Sprintf("Tenant %q added, migrations applied: %d", tenant, len(migrationsToApply))
114-
resp, err := notifier.Notify(text)
115-
122+
err = doAddTenantAndApplyMigrations(tenant, migrationsToApply, config, newConnector)
116123
if err != nil {
117-
log.Printf("Notifier err: %v", err)
118-
} else {
119-
log.Printf("Notifier response: %v", resp)
124+
return
120125
}
121126

122-
return diskMigrations
127+
text := fmt.Sprintf("Tenant %q added, migrations applied: %d", tenant, len(migrationsToApply))
128+
sendNotification(config, text)
129+
130+
return
123131
}
124132

125133
// VerifyMigrations loads disk and db migrations and verifies their checksums
126134
// see migrations.VerifyCheckSums for more information
127-
func VerifyMigrations(config *config.Config, createConnector func(*config.Config) db.Connector, createLoader func(*config.Config) loader.Loader) (bool, []types.Migration) {
128-
diskMigrations := GetDiskMigrations(config, createLoader)
129-
dbMigrations := GetDBMigrations(config, createConnector)
130-
return migrations.VerifyCheckSums(diskMigrations, dbMigrations)
135+
func VerifyMigrations(config *config.Config, newConnector func(*config.Config) (db.Connector, error), createLoader func(*config.Config) loader.Loader) (bool, []types.Migration, error) {
136+
diskMigrations, err := GetDiskMigrations(config, createLoader)
137+
if err != nil {
138+
return false, []types.Migration{}, err
139+
}
140+
141+
dbMigrations, err := GetDBMigrations(config, newConnector)
142+
if err != nil {
143+
return false, []types.Migration{}, err
144+
}
145+
verified, offendingMigrations := migrations.VerifyCheckSums(diskMigrations, dbMigrations)
146+
return verified, offendingMigrations, nil
131147
}
132148

133149
// ExecuteMigrator is a function which executes actions on resources defined in config passed as first argument action defined as second argument
134150
// and using connector created by a function passed as third argument and disk loader created by a function passed as fourth argument
135151
func ExecuteMigrator(config *config.Config, executeFlags ExecuteFlags) {
136-
doExecuteMigrator(config, executeFlags, db.CreateConnector, loader.CreateLoader)
152+
err := doExecuteMigrator(config, executeFlags, db.NewConnector, loader.NewLoader)
153+
if err != nil {
154+
log.Printf("Error encountered: %v", err)
155+
}
137156
}
138157

139-
func doExecuteMigrator(config *config.Config, executeFlags ExecuteFlags, createConnector func(*config.Config) db.Connector, createLoader func(*config.Config) loader.Loader) {
158+
func doExecuteMigrator(config *config.Config, executeFlags ExecuteFlags, newConnector func(*config.Config) (db.Connector, error), createLoader func(*config.Config) loader.Loader) error {
140159
switch executeFlags.Action {
141160
case PrintConfigAction:
142161
log.Printf("Configuration file ==>\n%v\n", config)
143162
case GetDiskMigrationsAction:
144-
diskMigrations := GetDiskMigrations(config, createLoader)
163+
diskMigrations, err := GetDiskMigrations(config, createLoader)
164+
if err != nil {
165+
return err
166+
}
145167
if len(diskMigrations) > 0 {
146168
log.Printf("List of disk migrations\n%v", utils.MigrationArrayToString(diskMigrations))
147169
}
148170
case GetDBMigrationsAction:
149-
dbMigrations := GetDBMigrations(config, createConnector)
171+
dbMigrations, err := GetDBMigrations(config, newConnector)
172+
if err != nil {
173+
return err
174+
}
150175
log.Printf("Read DB migrations: %d", len(dbMigrations))
151176
if len(dbMigrations) > 0 {
152177
log.Printf("List of db migrations\n%v", utils.MigrationDBArrayToString(dbMigrations))
153178
}
154179
case AddTenantAction:
155-
verified, offendingMigrations := VerifyMigrations(config, createConnector, createLoader)
180+
verified, offendingMigrations, err := VerifyMigrations(config, newConnector, createLoader)
181+
if err != nil {
182+
return err
183+
}
156184
if !verified {
157185
log.Printf("Checksum verification failed.")
158186
log.Printf("List of offending disk migrations\n%v", utils.MigrationArrayToString(offendingMigrations))
159-
} else {
160-
AddTenant(executeFlags.Tenant, config, createConnector, createLoader)
187+
return errors.New("Checksum verification failed")
188+
}
189+
190+
migrationsApplied, err := AddTenant(executeFlags.Tenant, config, newConnector, createLoader)
191+
if err != nil {
192+
return err
193+
}
194+
if len(migrationsApplied) > 0 {
195+
log.Printf("List of migrations applied\n%v", utils.MigrationArrayToString(migrationsApplied))
161196
}
162197
case GetDBTenantsAction:
163-
dbTenants := GetDBTenants(config, createConnector)
198+
dbTenants, err := GetDBTenants(config, newConnector)
199+
if err != nil {
200+
return err
201+
}
164202
log.Printf("Read DB tenants: %d", len(dbTenants))
165203
if len(dbTenants) > 0 {
166204
log.Printf("List of db tenants\n%v", utils.TenantArrayToString(dbTenants))
167205
}
168206
case ApplyAction:
169-
verified, offendingMigrations := VerifyMigrations(config, createConnector, createLoader)
207+
verified, offendingMigrations, err := VerifyMigrations(config, newConnector, createLoader)
208+
if err != nil {
209+
return err
210+
}
170211
if !verified {
171212
log.Printf("Checksum verification failed.")
172213
log.Printf("List of offending disk migrations\n%v", utils.MigrationArrayToString(offendingMigrations))
173-
} else {
174-
migrationsApplied := ApplyMigrations(config, createConnector, createLoader)
175-
if len(migrationsApplied) > 0 {
176-
log.Printf("List of migrations applied\n%v", utils.MigrationArrayToString(migrationsApplied))
177-
}
214+
return errors.New("Checksum verification failed")
215+
}
216+
migrationsApplied, err := ApplyMigrations(config, newConnector, createLoader)
217+
if err != nil {
218+
return err
219+
}
220+
if len(migrationsApplied) > 0 {
221+
log.Printf("List of migrations applied\n%v", utils.MigrationArrayToString(migrationsApplied))
178222
}
179223
}
224+
return nil
180225
}
181226

182-
func doApplyMigrations(migrationsToApply []types.Migration, config *config.Config, createConnector func(*config.Config) db.Connector) {
183-
connector := createConnector(config)
184-
connector.Init()
227+
func doApplyMigrations(migrationsToApply []types.Migration, config *config.Config, newConnector func(*config.Config) (db.Connector, error)) error {
228+
connector, err := newConnector(config)
229+
if err != nil {
230+
return err
231+
}
232+
if err := connector.Init(); err != nil {
233+
return err
234+
}
185235
defer connector.Dispose()
186-
connector.ApplyMigrations(migrationsToApply)
236+
return connector.ApplyMigrations(migrationsToApply)
187237
}
188238

189-
func doAddTenantAndApplyMigrations(tenant string, migrationsToApply []types.Migration, config *config.Config, createConnector func(*config.Config) db.Connector) {
190-
connector := createConnector(config)
191-
connector.Init()
239+
func doAddTenantAndApplyMigrations(tenant string, migrationsToApply []types.Migration, config *config.Config, newConnector func(*config.Config) (db.Connector, error)) error {
240+
connector, err := newConnector(config)
241+
if err != nil {
242+
return err
243+
}
244+
if err := connector.Init(); err != nil {
245+
return err
246+
}
192247
defer connector.Dispose()
193-
connector.AddTenantAndApplyMigrations(tenant, migrationsToApply)
248+
return connector.AddTenantAndApplyMigrations(tenant, migrationsToApply)
249+
}
250+
251+
func sendNotification(config *config.Config, text string) {
252+
notifier := notifications.CreateNotifier(config)
253+
resp, err := notifier.Notify(text)
254+
255+
if err != nil {
256+
log.Printf("Notifier err: %v", err)
257+
} else {
258+
log.Printf("Notifier response: %v", resp)
259+
}
194260
}

0 commit comments

Comments
 (0)