Skip to content

Commit 6c7e2c6

Browse files
authored
Merge pull request #57 from lukaszbudnik/migrator-v3
Migrator v3
2 parents 7797ce6 + 73a5aa7 commit 6c7e2c6

27 files changed

+959
-1134
lines changed

Dockerfile

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@ FROM golang:1.11.2-alpine3.8 as builder
22

33
MAINTAINER Łukasz Budnik [email protected]
44

5-
# install migrator
5+
# build migrator
66
RUN apk add git
7-
RUN go get github.com/lukaszbudnik/migrator
7+
RUN go get -d -v github.com/lukaszbudnik/migrator
8+
RUN cd /go/src/github.com/lukaszbudnik/migrator && ./setup.sh
9+
RUN cd /go/src/github.com/lukaszbudnik/migrator && \
10+
GIT_BRANCH=$(git branch | awk -v FS=' ' '/\*/{print $NF}' | sed 's|[()]||g') && \
11+
GIT_COMMIT_SHA=$(git rev-list -1 HEAD) && \
12+
go build -ldflags "-X main.GitCommitSha=$GIT_COMMIT_SHA -X main.GitBranch=$GIT_BRANCH"
813

914
FROM alpine:3.8
10-
COPY --from=builder /go/bin/migrator /bin
15+
COPY --from=builder /go/src/github.com/lukaszbudnik/migrator/migrator /bin
1116

1217
VOLUME ["/data"]
1318

README.md

Lines changed: 134 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Migrator [![Build Status](https://travis-ci.org/lukaszbudnik/migrator.svg?branch=master)](https://travis-ci.org/lukaszbudnik/migrator) [![Go Report Card](https://goreportcard.com/badge/github.com/lukaszbudnik/migrator)](https://goreportcard.com/report/github.com/lukaszbudnik/migrator) [![codecov](https://codecov.io/gh/lukaszbudnik/migrator/branch/master/graph/badge.svg)](https://codecov.io/gh/lukaszbudnik/migrator)
1+
# migrator [![Build Status](https://travis-ci.org/lukaszbudnik/migrator.svg?branch=master)](https://travis-ci.org/lukaszbudnik/migrator) [![Go Report Card](https://goreportcard.com/badge/github.com/lukaszbudnik/migrator)](https://goreportcard.com/report/github.com/lukaszbudnik/migrator) [![codecov](https://codecov.io/gh/lukaszbudnik/migrator/branch/master/graph/badge.svg)](https://codecov.io/gh/lukaszbudnik/migrator)
22

33
Super fast and lightweight DB migration & evolution tool written in go.
44

@@ -8,48 +8,83 @@ 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.
11+
migrator exposes a simple REST API which you can use to invoke different actions:
1212

13-
Short and sweet.
13+
* GET /config - returns migrator config, response is `Content-Type: application/x-yaml`
14+
* GET /diskMigrations - returns disk migrations, response is `Content-Type: application/json`
15+
* GET /tenants - returns tenants, response is `Content-Type: application/json`
16+
* POST /tenants - adds new tenant, name parameter is passed as JSON, returns applied migrations, response is `Content-Type: application/json`
17+
* GET /migrations - returns all applied migrations
18+
* POST /migrations - applies migrations, no parameters required, returns applied migrations, response is `Content-Type: application/json`
19+
20+
Some curl examples to get you started:
1421

1522
```
16-
$ Usage of ./migrator:
17-
-action string
18-
when run in tool mode, action to execute, valid actions are: ["apply" "addTenant" "config" "diskMigrations" "dbTenants" "dbMigrations"] (default "apply")
19-
-configFile string
20-
path to migrator configuration yaml file (default "migrator.yaml")
21-
-mode string
22-
migrator mode to run: ["tool" "server"] (default "tool")
23-
-tenant string
24-
when run in tool mode and action set to "addTenant", specifies new tenant name
23+
curl -v http://localhost:8080/config
24+
curl -v http://localhost:8080/diskMigrations
25+
curl -v http://localhost:8080/tenants
26+
curl -v http://localhost:8080/migrations
27+
curl -v -X POST http://localhost:8080/migrations
28+
curl -v -X POST -H "Content-Type: application/json" -d '{"name": "new_tenant"}' http://localhost:8080/tenants
2529
```
2630

27-
Migrator requires a simple `migrator.yaml` file:
31+
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.
32+
33+
There is an official docker image available on docker hub. migrator docker image is ultra lightweight and has a size of 15MB. Ideal for micro-services deployments!
34+
35+
To find out more about migrator docker container see [DOCKER.md](DOCKER.md) for more details.
36+
37+
# Configuration
38+
39+
migrator requires a simple `migrator.yaml` file:
2840

2941
```yaml
42+
# required, base directory where all migrations are stored, see singleSchemas and tenantSchemas below
3043
baseDir: test/migrations
44+
# required, SQL go driver implementation used, see section "Supported databases"
3145
driver: postgres
32-
# dataSource format is specific to DB go driver implementation - see below 'Supported databases'
46+
# required, dataSource format is specific to SQL go driver implementation used, see section "Supported databases"
3347
dataSource: "user=postgres dbname=migrator_test host=192.168.99.100 port=55432 sslmode=disable"
34-
# override only if you have a specific way of determining tenants, default is:
48+
# optional, override only if you have a specific way of determining tenants, default is:
3549
tenantSelectSQL: "select name from migrator.migrator_tenants"
36-
# override only if you have a specific way of creating tenants, default is:
50+
# optional, override only if you have a specific way of creating tenants, default is:
3751
tenantInsertSQL: "insert into migrator.migrator_tenants (name) values ($1)"
38-
# override only if you have a specific schema placeholder, default is:
52+
# optional, override only if you have a specific schema placeholder, default is:
3953
schemaPlaceHolder: {schema}
54+
# required, single schemas directories, these are subdirectories of baseDir
4055
singleSchemas:
4156
- public
4257
- ref
4358
- config
59+
# optional, tenant schemas directories, these are subdirectories of baseDir
4460
tenantSchemas:
4561
- tenants
46-
# port is used only when migrator is run in server mode, defaults to:
62+
# optional, default is:
4763
port: 8080
48-
# optional Slack Incoming Web Hook - if defined apply migrations action will post a message to Slack
49-
slackWebHook: https://hooks.slack.com/services/TTT/BBB/XXX
64+
# the webhook configuration section is optional
65+
# URL and template are required if at least one of them is empty noop notifier is used
66+
# the default content type header sent is application/json (can be overridden via webHookHeaders below)
67+
webHookURL: https://hooks.slack.com/services/TTT/BBB/XXX
68+
# the {text} placeholder is replaced by migrator with information about executed migrations or added new tenant
69+
webHookTemplate: "{\"text\": \"{text}\",\"icon_emoji\": \":white_check_mark:\"}"
70+
# should you need more control over HTTP headers use below
71+
webHookHeaders:
72+
- "Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l"
73+
- "Content-Type: application/json"
74+
- "X-CustomHeader: value1,value2"
75+
```
76+
77+
migrator supports env variables substitution in config file. All patterns matching `${NAME}` will look for env variable `NAME`. Below are some common use cases:
78+
79+
```yaml
80+
dataSource: "user=${DB_USER} password=${DB_PASSWORD} dbname=${DB_NAME} host=${DB_HOST} port=${DB_PORT}"
81+
webHookHeaders:
82+
- "X-Security-Token: ${SECURITY_TOKEN}"
5083
```
5184

52-
Migrator will scan all directories under `baseDir` directory. Migrations listed under `singleSchemas` directories will be applied once. Migrations listed under `tenantSchemas` directories will be applied for all tenants fetched using `tenantSelectSQL`.
85+
# migrator under the hood
86+
87+
migrator scans all directories under `baseDir` directory. Migrations listed under `singleSchemas` directories will be applied once. Migrations listed under `tenantSchemas` directories will be applied for all tenants fetched using `tenantSelectSQL`.
5388

5489
SQL migrations in both `singleSchemas` and `tenantsSchemas` can use `{schema}` placeholder which will be automatically replaced by migrator with a current schema. For example:
5590

@@ -59,34 +94,6 @@ create table if not exists {schema}.modules ( k int, v text );
5994
insert into {schema}.modules values ( 123, '123' );
6095
```
6196

62-
# Server mode
63-
64-
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.
65-
66-
All actions which you can invoke from command line can be invoked via REST API:
67-
68-
* GET / - returns migrator config, response is `Content-Type: application/x-yaml`
69-
* GET /diskMigrations - returns disk migrations, response is `Content-Type: application/json`
70-
* GET /tenants - returns tenants, response is `Content-Type: application/json`
71-
* POST /tenants - adds new tenant, name parameter is passed as JSON, returns applied migrations, response is `Content-Type: application/json`
72-
* GET /migrations - returns all applied migrations
73-
* POST /migrations - applies migrations, no parameters required, returns applied migrations, response is `Content-Type: application/json`
74-
75-
Some curl examples to get you started:
76-
77-
```
78-
curl http://localhost:8080/
79-
curl http://localhost:8080/diskMigrations
80-
curl http://localhost:8080/tenants
81-
curl http://localhost:8080/migrations
82-
curl -X POST http://localhost:8080/migrations
83-
curl -X POST -H "Content-Type: application/json" -d '{"name": "new_tenant"}' http://localhost:8080/tenants
84-
```
85-
86-
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.
87-
88-
# DB Schemas
89-
9097
When using migrator please remember about these:
9198

9299
* migrator creates `migrator` schema (where `migrator_migrations` and `migrator_tenants` tables reside) automatically
@@ -112,13 +119,84 @@ Currently migrator supports the following databases and their flavours:
112119
* Microsoft SQL Server 2017 - a relational database management system developed by Microsoft, driver used: https://github.com/denisenkom/go-mssqldb
113120
* Microsoft SQL Server - original Microsoft SQL Server
114121

115-
# Do you speak docker?
122+
# Quick Start Guide
116123

117-
Yes, there is an official docker image available on docker hub.
124+
You can apply your first migrations with migrator in literally a couple of minutes. There are some test migrations which are placed in `test/migrations` directory as well as some docker scripts for setting up test databases.
118125

119-
migrator docker image is ultra lightweight and has a size of approx. 15MB. Ideal for micro-services deployments!
126+
Let's start.
120127

121-
To find out more about migrator docker container see [DOCKER.md](DOCKER.md) for more details.
128+
## 1. Get the migrator project
129+
130+
For building migrator from source code `go get` is required:
131+
132+
```
133+
go get -d -v github.com/lukaszbudnik/migrator
134+
cd $GOPATH/src/github.com/lukaszbudnik/migrator
135+
```
136+
137+
migrator supports the following Go versions: 1.8, 1.9, 1.10, and 1.11 (all built on Travis).
138+
139+
For running migrator on docker Go is not required and `git clone` is enough:
140+
141+
```
142+
git clone [email protected]:lukaszbudnik/migrator.git
143+
cd migrator
144+
```
145+
146+
## 2. Setup test DB container
147+
148+
migrator comes with helper scripts to setup test DB containers. Let's use postgres (see `ultimate-coverage.sh` for all supported containers).
149+
150+
```
151+
./test/docker/create-and-setup-container.sh postgres
152+
```
153+
154+
Script will start container called `migrator-postgres`.
155+
156+
Further, apart of starting test DB container, the script also generates a ready-to-use test config file. We will use it too.
157+
158+
## 3. Build and run migrator
159+
160+
When building & running migrator from source code execute:
161+
162+
```
163+
./setup.sh
164+
go build
165+
./migrator -configFile test/migrator.yaml
166+
```
167+
168+
> Note: There are 2 git variables injected into the production build (branch/tag and commit sha). When migrator is built like above it prints empty branch/tag and commit sha. This is OK for local development. If you want to inject proper values take a look at `Dockerfile` for details.
169+
170+
When running migrator from docker we need to update `migrator.yaml` (generated in step 2) as well as provide a link to `migrator-postgres` container:
171+
172+
```
173+
sed -i "s/host=[^ ]* port=[^ ]*/host=migrator-postgres port=5432/g" test/migrator.yaml
174+
sed -i "s/baseDir: .*/baseDir: \/data\/migrations/g" test/migrator.yaml
175+
docker run -p 8080:8080 -v $PWD/test:/data -e MIGRATOR_YAML=/data/migrator.yaml -d --link migrator-postgres lukasz/migrator
176+
```
177+
178+
## 4. Play around with migrator
179+
180+
Happy path:
181+
182+
```
183+
curl -v http://localhost:8080/config
184+
curl -v http://localhost:8080/diskMigrations
185+
curl -v http://localhost:8080/tenants
186+
curl -v http://localhost:8080/migrations
187+
curl -v -X POST http://localhost:8080/migrations
188+
curl -v -X POST -H "Content-Type: application/json" -d '{"name": "new_tenant"}' http://localhost:8080/tenants
189+
```
190+
191+
And some errors. For example let's break a checksum of the first migration and try to apply migrations or add new tenant.
192+
193+
```
194+
echo " " >> test/migrations/config/201602160001.sql
195+
curl -v -X POST -H "X-Request-Id: xyzpoi098654" http://localhost:8080/migrations
196+
curl -v -X POST -H "Content-Type: application/json" -H "X-Request-Id: abcdef123456" -d '{"name": "new_tenant2"}' http://localhost:8080/tenants
197+
```
198+
199+
In above error requests we used `X-Request-Id` header. This header can be used with all requests for request tracing and/or auditing purposes.
122200
123201
# Customisation
124202
@@ -146,31 +224,19 @@ There is a performance test generator shipped with migrator (`test/performance/g
146224

147225
Execution times are following:
148226

149-
| # Tenants | # Existing Migrations | # Migrations to apply | Migrator | Ruby | Flyway |
227+
| # Tenants | # Existing Migrations | # Migrations to apply | migrator | Ruby | Flyway |
150228
|----------- |----------------------- |----------------------- |---------- |----------- |---------- |
151229
| 10 | 0 | 10001 | 154s | 670s | 2360s |
152230
| 10 | 10001 | 20 | 2s | 455s | 340s |
153231

154-
Migrator is the undisputed winner.
232+
migrator is the undisputed winner.
155233

156-
The Ruby framework has an undesired functionality of making a DB call each time to check if given migration was already applied. Migrator fetches all applied migrations at once and compares them in memory. This is the primary reason why migrator is so much better in the second test.
234+
The Ruby framework has an undesired functionality of making a DB call each time to check if given migration was already applied. migrator fetches all applied migrations at once and compares them in memory. This is the primary reason why migrator is so much better in the second test.
157235

158236
flyway results are... very surprising. I was so shocked that I had to re-run flyway as well as all other tests. Yes, flyway is 15 times slower than migrator in the first test. In the second test flyway was faster than Ruby. Still a couple orders of magnitude slower than migrator.
159237

160238
The other thing to consider is the fact that migrator is written in go which is known to be much faster than Ruby and Java.
161239

162-
# Installation and supported Go versions
163-
164-
To install migrator use:
165-
166-
```
167-
go get github.com/lukaszbudnik/migrator
168-
cd migrator
169-
./setup.sh
170-
```
171-
172-
Migrator supports the following Go versions: 1.8, 1.9, 1.10, and 1.11 (all built on Travis).
173-
174240
# Contributing, code style, running unit & integration tests
175241

176242
Contributions are most welcomed.

TestDockerfile

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
FROM golang:1.11.2-alpine3.8 as builder
2+
3+
MAINTAINER Łukasz Budnik [email protected]
4+
5+
# use "--build-arg BRANCH=migrator-v3" to override at build time
6+
# docker build -f TestDockerfile --build-arg BRANCH=migrator-v3 -t migratortest:v1 .
7+
ARG BRANCH=master
8+
9+
# git is required
10+
RUN apk add git
11+
12+
# A - install migrator from local source code
13+
#RUN mkdir -p /go/src/github.com/lukaszbudnik/migrator
14+
#COPY . /go/src/github.com/lukaszbudnik/migrator
15+
16+
# B - install migrator from $BRANCH branch
17+
RUN go get -d -v github.com/lukaszbudnik/migrator
18+
RUN cd /go/src/github.com/lukaszbudnik/migrator && git checkout $BRANCH
19+
20+
RUN cd /go/src/github.com/lukaszbudnik/migrator && ./setup.sh
21+
RUN cd /go/src/github.com/lukaszbudnik/migrator && \
22+
GIT_BRANCH=$(git branch | awk -v FS=' ' '/\*/{print $NF}' | sed 's|[()]||g') && \
23+
GIT_COMMIT_SHA=$(git rev-list -1 HEAD) && \
24+
go build -ldflags "-X main.GitCommitSha=$GIT_COMMIT_SHA -X main.GitBranch=$GIT_BRANCH"
25+
26+
FROM alpine:3.8
27+
COPY --from=builder /go/src/github.com/lukaszbudnik/migrator/migrator /bin
28+
29+
VOLUME ["/data"]
30+
31+
# copy and register entrypoint script
32+
COPY docker-entrypoint.sh /
33+
ENTRYPOINT ["/docker-entrypoint.sh"]
34+
35+
EXPOSE 8080

common/common.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package common
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
)
8+
9+
// RequestIDKey is used together with context for setting/getting X-Request-Id
10+
type RequestIDKey struct{}
11+
12+
// ActionKey is used together with context for setting/getting current action
13+
type ActionKey struct{}
14+
15+
func logLevel(ctx context.Context, level string, format string, a ...interface{}) string {
16+
requestID := ctx.Value(RequestIDKey{})
17+
action := ctx.Value(ActionKey{})
18+
message := fmt.Sprintf(format, a...)
19+
log.Printf("%v %v [%v] - %v", level, action, requestID, message)
20+
return message
21+
}
22+
23+
// LogError logs error message
24+
func LogError(ctx context.Context, format string, a ...interface{}) string {
25+
return logLevel(ctx, "ERROR", format, a...)
26+
}
27+
28+
// LogInfo logs info message
29+
func LogInfo(ctx context.Context, format string, a ...interface{}) string {
30+
return logLevel(ctx, "INFO", format, a...)
31+
}
32+
33+
// LogPanic logs error message and panics
34+
func LogPanic(ctx context.Context, format string, a ...interface{}) string {
35+
message := logLevel(ctx, "PANIC", format, a...)
36+
panic(message)
37+
}

0 commit comments

Comments
 (0)