gitでcommit/pushされたファイルの内容をリモートリポジトリ側でvalidationしてOKだったらdeployする

# 目的と条件

- コンフィグファイルをgit管理したい
- pushされたらdeployまで自動でやりたい
- レポジトリにpushされる前にvalidationをかけたい
- validationは強制したい
- validationツールとコンフィグは同じリポジトリで管理しない
- validationがOKだったら対象のサーバに自動でデプロイしたい
- コンフィグの編集者は必ずしもgitに精通しているわけではないので難しいことはさせない

# 動機

ネットワークやサーバの運用を行なっているとコンフィグファイルをgitで履歴管理して、リモートリポジトリにpushされたらリモートからサーバにデプロイする、みたいなことをやりたくなる。でも内容に誤りのあるコンフィグファイルをいきなりデプロイしてしまうとシステムに異常を来すので、リモートリポジトリにpushする前にかならずコンフィグ内容のvalidationを行なって、問題がなければ受け入れるようにしたい。

プログラムのコードであればテストが存在して、それらはプログラムの一部として扱われるので、同じリポジトリ内に置いて管理される。しかし管理したいのはコンフィグファイル、あくまでデータであって、validationのツールとは別に履歴を管理したい。ツールの改良履歴とコンフィグ内容の履歴が混ざるのが嫌。

また、コンフィグを編集する人は、プログラマよりはオペレータに近いので、gitに精通しているわけではない。なのでCVS以上に小難しいことをさせたくない。

# やりかた

編集者の手元(editor)にvalidationツールを置けない。作業するサーバの違いやツールのインタープリタのバージョン(rbenv等)などが問題になるからだ。それに加えて、必ずvalidationを実施してあげる必要があるため、サーバ＝リモートリポジトリ側でvalidationを行わなければならない。これにはupdateのhookが使える。また、pushが成功した後のデプロイにはpost-receiveのhookが使える。

# 環境

## リモートリポジトリ

server1というサーバの/reposに--bare --sharedで作成してある

 cd /repos/hoge.git
 git init --bare --shared

## ツール用アカウント

server1にツールのアカウントとしてhogeを作成してある。
/home/hoge/work/hogeにローカルリポジトリが作成してある。

 cd /home/hoge/work/hoge
 cat hogehoge > hoge.conf
 git init
 git remote add origin /repos/hoge.git
 git add hoge.conf
 git push origin master

## オペレータの作業ディレクトリ

オペレータは任意のサーバの個人のアカウントでhoge.confの編集作業を行う。

 cd /home/yuyarin
 git clone hoge@server1:/repos/hoge.git
 cd hoge
 vim hoge.conf
 git add hoge.conf
 git commit -m "Hogehoge"
 git push origin master
 # ここでvalidationされる

# validationツール

まずvalidationのツールを用意する。このツールは標準入力からファイル内容を受け取れるようにしてある。
試しにコンフィグ内にhogeと書かれていたらinvalidとする。invalidである場合はエラーメッセージを表示して、exit 1する。

```rb:/usr/local/bin/validate_hoge
#!ruby
fd = ARGV[0] and File.exists?(ARGV[0]) ? File::open(ARGV[0], 'r') : STDIN
while line = fd.gets
	if /hoge/ =~ line
		puts "validation ERROR"
		exit 1
	end
end
puts "validation OK"
exit 0
```

# deployツール

hogeのローカルリポジトリのファイルを各サーバにscpするスクリプトを設置。
必要があればsshでデーモンのreloadを行うなどする。

```sh:/usr/local/bin/deploy_hoge
#!/bin/sh
FILE=hoge.conf
LOCAL_DIR=/home/hoge/work/hoge
REMOTE_DIR=/etc/hoged
REMOTE_USER=hoge
REMOTE_HOSTS="hoge1 hoge2 hoge3 hoge4"
for REMOTE_HOST in $REMOTE_HOSTS
do
	CMD="scp ${LOCAL_DIR}/${FILE} ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/${FILE}"
	echo $CMD
	eval $CMD
done
```

# hookの作成

## update
。
このスクリプトはhooks/updateとして設置し、0を返せばaccept、それ以外の値を返せばrejectとなる。rejectになるとリポジトリには反映されない。

```sh:/repos/hoge.git/hooks/update
#!/bin/sh
git show $3:hoge.conf | /usr/local/bin/validate_hoge
```

pushされてきたrevisionはシェルスクリプトであれば$3で取得できる。特定のファイル内容を確認するためには git showコマンドを使う。git show #{revision}:#{path}でファイル内容を取得することができるので、これをパイプでvalidationのツールに渡す。validationの結果、invalidだったら1が返ってくるので、pushはrejectされる。

## post-receive

push内容がacceptされるとpost-receiveのhookが呼ばれるので、ここでreployツールを実行する。ここではhogeアカウントのローカルリポジトリで一度pullしたのち、別に用意した。deployツールを実行している。これでhoge.confがサーバにデプロイされる。

```sh:/repos/hoge.git/hooks/post-receive
#!/bin/sh
cd /home/hoge/work/hoge
git --git-dir=.git pull /repos/hoge.git master
/usr/local/bin/deploy-hoge
```

git pullするのがダサければ、git showした内容をデプロイのツールに食わせるのもありかなぁ。

# 実行例

```
/home/yuyarin/hoge$ git commit -am 'Add string hoge'
[master 8519468] Add string hoge
 1 file changed, 1 insertion(+)
/home/yuyarin/hoge$ git push origin master
Counting objects: 5, done.
Writing objects: 100% (3/3), 232 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: validation error
remote: error: hook declined to update refs/heads/master
To hoge@server1:/repos/hoge.git
 ! [remote rejected] master -> master (hook declined)
error: failed to push some refs to 'hoge@server1:/repos/hoge.git'
```

コンフィグ内容がinvalidだとrejectされる。

```
/home/yuyarin/hoge$ vim hoge.conf              
/home/yuyarin/hoge$ git commit -am 'Remove strings hoge'    
[master 3af7194] Remove strings hoge
 1 file changed, 1 insertion(+), 2 deletions(-)
/home/yuyarin/hoge$ git push origin master
Counting objects: 8, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (6/6), 428 bytes | 0 bytes/s, done.
Total 6 (delta 0), reused 0 (delta 0)
remote: validation OK
remote: scp /home/hoge/work/hoge.conf hoge@hoge1:/etc/hoged/hoge.conf
remote: scp /home/hoge/work/hoge.conf hoge@hoge2:/etc/hoged/hoge.conf
remote: scp /home/hoge/work/hoge.conf hoge@hoge3:/etc/hoged/hoge.conf
remote: scp /home/hoge/work/hoge.conf hoge@hoge4:/etc/hoged/hoge.conf
To hoge@server1:/repos/hoge.git
   e5aa890..3af7194  master -> master
```

コンフィグ内容に問題がないとコミット内容がacceptされて、post-receivedのhookで各サーバにファイルがデプロイされる。

# 悩みどころ

## ゴミcommit

validationがcommitの後に行われるので、validationが通るまで誤った設定内容のゴミcommitが蓄積される。オペレータにgit rebaseとかやらせたくない。作業サーバを強制した上で、commit前にもvalidationツールを手動で実施するような手順にするという運用対処するしか無いのかなぁ。