GooseはGo製のDB Migrationツール。
こんなリポジトリを作成し、各自ブランチを切ってGoose形式のup/downのSQLを書き、終わったらPullRequestを出す。
goose/
.keep
.circleci/config.yml
create_test_table.sql
$ cat create_test_table.sql
-- +goose Up
-- SQL in this section is executed when the migration is applied.
CREATE TABLE testtable (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
n INT NOT NULL,
c VARCHAR (20) NOT NULL UNIQUE
);
-- +goose Down
-- SQL in this section is executed when the migration is rolled back.
DROP TABLE testtable;
無事Approveされ、mergeされるとCircleCIが走り、 SQLをgooseディレクトリの中にバージョンを付けて移し、 SlackにpostMessageするエンドポイントにリクエストを飛ばす。
ここでバージョンを作成することによって、並列で作業し、レビューなどの関係で適用順が前後しても修正する必要をなくしている。ただ、pushされる前に複数のブランチを連続でmergeする場合うまく動かないのでそれはなんとかする必要がある。
CircleCI 2.0ではApprovalボタンが出せるんだが、 アクセスしにいくのがちょっと面倒なのと、周知も兼ねてSlackに出したかったので使っていない。
version: 2
jobs:
build:
docker:
- image: circleci/golang:1.8
branches:
only:
- master
steps:
- checkout
- run:
name: Create new version
command: |
if [ -e *.sql ]; then
VERSION=$(ls -U1 goose | wc -l | xargs expr 1 + | xargs printf %05d)
FILENAME=$(find . -maxdepth 1 -name "*.sql" | head | xargs basename)
mv ${FILENAME} goose/${VERSION}_${FILENAME}
git config --global user.email "[email protected]"
git config --global user.name "CircleCI"
git add .
git commit -m "version ${VERSION}"
git push origin master
COMMIT=$(git rev-parse HEAD)
curl -H "Authorization: Basic $(echo -n 'foobar:dolphins' | base64)" "https://*****/auth/message?version=${VERSION}&filename=${FILENAME}&commit=${COMMIT}"
fi
goose/
.keep
00001_create_test_table.sql
.circleci/config.yml
Migrationボタンが押されると、まずボタンを消してRunning状態とし、 処理が終わったら結果を上書きするようにしている。
SlackのInteractive messagesでボタンの入力を受け付ける - sambaiz-net
slackMessages.action('migrate', (payload, respond) => {
let replacement = payload.original_message;
delete replacement.attachments[0].actions;
replacement.attachments[0].text = `start migration by ${payload.user.name} at ${moment().format()}`;
replacement.attachments[0].fields = [
{
"title": "State",
"value": "Running",
"short": false
}
];
exec(
// Attention to command injection
`rm -rf ${repositoryName} && git clone [email protected]:${repositoryPath}.git && cd ${repositoryName}/goose && goose mysql "${mySQLConf}" up`,
(err, stdout, stderr) => {
replacement.attachments[0].fields = [
{
"title": "Result",
"value": (err || stderr) ? `${stderr || err}` : "Success",
"short": false
}
];
respond(replacement);
}
);
return replacement;
});