サーバレス（Walter + GitHook）で成果物を Nexus Repository (Maven Central) に自動登録する

この投稿は @takahi-i が投稿したブログ記事、 [Automatic Java Artifacts Upload to Nexus Repository with Walter and Git Hooks](https://walter-cd.net/2016/11/18/automatic-upload-aritfacts-to-nexus-repository/)の日本語訳です。

## TL;DR

* Maven リリースプラグインの対話的な処理はミスが起こりやすくて辛い
* [Walter](https://github.com/walter-cd/walter) を使って Nexus Repository （Maven　Central） への登録を自動化
* GitHook　を利用してバージョンアップするコミットをしたときに自動でリリース処理が走るように拡張（Jenkins や Tranvis などのサービスが必要ない）

## Maven release プラグイン

本稿の著者は [Walter](https://github.com/walter-cd/walter) の開発メンバーですが、[RedPen](http://github.com/redpen-cc/redpen) という Java で記述された校正ツールの開発メンバーでもあります。このツールの成果物（Jarファイル、Warファイル）はリリースごとに Maven Central リポジトリに追加されています。これまで Maven Central リポジトリに成果物を追加する作業には Maven の **release プラグイン** を利用していました。

Maven release プラグインを使うまでの設定（``pom.xml``　や電子署名）はかなり大変です。詳しくは[こちら](http://samuraism.jp/diary/2012/05/03/1336047480000.html)の記事を読んで下さい。とはいえ一度設定が終了すると、リリースのタイミングで ``mvn release`` コマンドを実行するだけで成果物を Maven Centaral リポジトリに追加できて便利です。

しかし最近、リリースごとに release プラグインを実行するのに煩わしさを感じています。そこで本稿ではリリース処理を自動化する方法について検討します。ただしその前に、release プラグインを利用したリリース処理について考えてみます。

# Release プラグインを利用した処理

リリースプラグインを実行すると、多用な処理を対話的に実行します。以下は、relealse プラグインの準備をする
``release:prepare`` を実行した状態です。

```
➜  redpen git:(master) mvn release:prepare
[INFO] Scanning for projects...
...
[INFO] Checking dependencies and plugins for snapshots ...
What is the release version for "redpen"? (cc.redpen:redpen) 1.7.5: :
```

はじめにリリースのバージョンを入力するように言われます。リリースプラグインは、現在のスナップショットのバージョンから、リリースバージョンを提示します（上の例では、1.7.5）。もし提示されたバージョンと、出したいバージョンが違い場合にはバージョン番号を手入力します。

次にプラグインは今後の Development Version を聞いてきます。必要であれば、手入力します。

```
What is the new development version for "redpen"? (cc.redpen:redpen)
1.7.6-SNAPSHOT: :
```

これが終わると、プラグインは git に新しいバージョン番号の変更を行い、git にコミットをします。そして、現在の状態でビルドをします。

ビルドが成功すると、プラグインはバージョン番号の変更をリモートリポジトリに push すると言いだします。

```
[INFO] Working directory: /Users/takahi-i/IdeaProjects/redpen
Username for 'https://github.com': Password for 'https://github.com':
```

GitHub のユーザ名とパスワードが要求されているのがわかります。この後``mvn release:prepare`` が成功しても、次に実行する ``release:perform``コマンドでも対話的に色々入力する必要があります。そっかしい私は間違って入力してしまい、何度も初めからやりなおしました :fearful:

# リリース処理を自動化

前節でみてきた release プラグインは、対話的な入力作業があるために使いにくくなってしまっています。

## 先行事例

そこで、この問題を解決した事例がないか Tweet したところ、スライド「[これであなたもOSS開発者！ Mavenセントラルリポジトリに自作ライブラリをアップロードするときのいろんなコツの話](http://www.slideshare.net/nabedge/20140405-maven-5java)」を教えてもらいました。スライドによると ``mvn versions:set`` と ``mvn deploy`` コマンドを代用すると、対話処理が必要なくなるらしい。

## Walter を使ってみる

上記のスライドでは Jenkins サーバを利用して、必要なコマンドを実行しています。ただ、RedPenのような小規模なツールだとサーバで実行するのもやりたいことに対してコストが大きいと考えました。モチベーションとしては、なんとかサーバ管理せずに自動化したい。また Wercker などの外部 CI サービスでも自動化でき、運用もサービスにおまかせできるので気軽です。

しかし、これらサービスでのデプロイ処理はうまく動かなかったときの、ログ分析や再実行（commitし直して、push 直す）が少々つらいので、できればローカル PC で実行したいと考えています。

そこでビルドパイプラインツール、[Walter](https://github.com/walter-cd/walter)にreleaseプラグインがやっていることを実行させてみました。Walter は YAML 形式に実行したいコマンドを列挙していきます。詳しくは[こちら](https://github.com/walter-cd/walter)を参照してください。

以下、Walter で release プラグインがやっていることをフロー化した設定です。

```
pipeline:
    - name: Set Version
      command: mvn versions:set -DnewVersion=$REDPEN_VERSION
    - name: Add Files to the Next Changes
      command: git add pom.xml; git add **/*.xml
    - name: Commit Version Changes
      command: git commit -m "Set version in pom.xml to $REDPEN_VERSION"
    - name: Delopy to Sonatype
      command: mvn clean deploy -DperformRelease --settings
~/.m2/settings.xml > redpen-distribution/target/walter.log
    - name: Create Release Tag
      command: git tag -a redpen-$REDPEN_VERSION -m "RedPen release
$REDPEN_VERSION"
    - name: Flush Next Release Version Number
      command: echo $REDPEN_VERSION | awk -F. -v OFS=. 'NF==1{print
++$NF}; NF>1{if(length($NF+1)>length($NF))$(NF-1)++;
$NF=sprintf("%0*d-SNAPSHOT", length($NF), ($NF+1)%(10^length($NF)));
print}'
    - name: Echo Next Version
      command: echo __OUT["Flush Next Release Version Number"]
    - name: Set next version
      command: mvn versions:set -DnewVersion=__OUT["Flush Next Release
Version Number"]
    - name: Git Add
      command: git add .
    - name: Commit New Snapshot version
      command: git commit -m "Update versions in pom.xml for next release"
```

はじめに ``mvn version`` で環境変数から与えられたバージョンを設定しています。次に変更を git に登録します。これで、Maven Central に成果物を登録する準備が整いました。Delopy to Sonatype では ``mvn clean deploy`` で成果物を Sonatype に登録します。

次にリリース後の development バージョンを指定します。通常 developement のバージョン名は ``-SNAPSHOT`` というサフィックスを持ちます。まず、次のバージョン番号を計算し（"Flush Next Release Version Number"）、そのバージョンを "mvn versions:set" で指定しています。__OUTは指定されたステージで使用された標準出力を再利用するのに使用します。このファイルを ``release.yml`` という名前で RedPen のリポジトリに登録します。

これで、``walter -c release.yml`` コマンドを実行するだけでリリース処理が完結します（Sonatype では手作業で成果物を close する必要はありますが）。

# Git Hook と組み合わせる

上のやり方で単一コマンドを実行するとリリース処理が完結するようにはなりました。しかし、まだ一つ問題が残っています。RedPen ではコマンドやサーバでバージョンが確認できるようになっています。RedPen で利用されるバージョンは RedPen.java ファイルの一行を書き換えると全て変更されるようになっています。例えば以下の画像は RedPen サーバ 1.7.7 の様子です。

<img width="282" alt="Screen Shot 2017-03-19 at 23.33.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/61252/36ac54bd-0f3f-8fbd-40fe-55071db33293.png">

しかし、そそっかしい私は、バージョン番号を上げ忘れてリリースしてしまったことがあります :fearful: この問題を GitHook で解決することを考えました。

GitHook は特定の git コマンドが実行されると、指定されたスクリプトが実行される仕組みです。今回は、``git commit`` のときに、``コミットメッセージが ``Bump version ...`` となっているときだけ、リリースコマンドが自動実行されるようにしてみます。

以下のファイルを ./git/hooks/post-commit に置きました。

```
#!/bin/bash

LATEST_MESSAGE=`git log -1 --pretty=%B`

if echo $LATEST_MESSAGE | grep -q "^Bump version " ; then
   export REDPEN_VERSION=`echo $LATEST_MESSAGE | grep -o "[0-9.]\+"`
   echo "Extracted Version: " $REDPEN_VERSION
    ./walter -c release.yml
else
   echo "Not changed version"
fi
```

このファイルを置いた状態で、コミットメッセージが "Bump vesrion" となっている（RedPenのバージョンを変更するコミットメッセージは Bump version なっている）と、リリース処理が実行されます。このため内部のバージョンが上がっていないのにリリース処理をしてしまうという事故が防げる（はず）。

# 今後
RedPen ではコミットがリモートリポジトリにプッシュされるごとに [Heroku上のサンプルサーバ](http://redpen.herokuapp.com/)を新しいバージョンに切り替えています。現在は Jenkins を利用していますが、今後はサンプルサーバの切り替え処理もローカルで自動化できないか検討してみます。