HHR’s diary

日記は無理でも週記…いや、月記でがんばろう。

CircleCIで{maven,github}-release-pluginを使ってGitHub Releaseに自動登録

一発ネタやちょっとしたオモチャならばともかく、プログラムを継続的に維持したいとなると、CIやCDの導入を検討したくなりm(ry

まぁ、長ったらしい前置きはさておき、CircleCIを使い始めました。

今回は、CircleCIを使用してMavenビルドを行い、作成したjarをGitHub Releaseへ自動登録するというネタです。

ところで、CIサービスの有名所と言えばもう一つ、Travis CIがありますね。

このTravis CI、ビルドマトリクスというCircleCIには無い機能を備えていて、ざっくり言うとJDK7と8を同時にビルドするなどができるようです。

この機能には少し惹かれるものがあったんですが、まぁ、ライブラリ提供する訳でも無いし、最新のJDKでビルドができれば良いかな、複雑な物を管理したくないしー、という感じで今回はCircleCIにしてみました。

あとは、CircleCIのv2がちょっぱやと言う話を耳にしていて気になっていたという点もあったり。

しかし、後述しますがCircleCIには無いとある機能がTravis CIにありそうなのでちょっち失敗したかなぁと思っています…

さて、CI&CDやるぞと言ってもなんか漠然としていますし、ダレないためにもまずはどこまでやるかを決めるべきかなと思いました。

さしあたって、まず何がしたいんだっけと考えた結果、ざっくり以下の3点ができればいいかなとなりました。

  1. 前回記事でテスト書かねば、と宣言したので継続化できる環境がほしい。
  2. バージョンアップ作業はルーチンワークなので自動化したい、オペミスするとカッコ悪いので。
  3. Mavenなのでビルド成果物も作成される、バージョン毎に自動で保存しておきたいでもMavenレポジトリ持ってないとりまGitHub Releaseで。

ということで、まずはこの3点が達成されることを目的にします。

まぁ、1.はCIサービスを使い始めた時点でほぼ達成かなと、コードをGitHubにcommitすれば勝手にテストを実施してくれるので。後はテストを書くだけですね、書いていませんが…

2.と3.が今回の本題です。

試行錯誤の結果、maven-release-plugingithub-release-pluginを使用することによって達成することができました。

これからその詳細を載せるのですが、これまた長ったらしいので、先に出来上がったシステム概要を載っけます。

  • CircleCI v2
  • build docker imageはcircleci/openjdk:8-jdk
  • ビルドツールはMaven
  • 連携先はGitHub
  • master以外のブランチにcommitすることをトリガーにmvn package
    • 要するに、packagingできることの確認
  • masterブランチにcommitすることをトリガーにmvn deploy
    • つまり、masterブランチへのPull requestをmergeでmvn deployされる
    • その際、バージョンアップ等が自動で行われる
    • ついでにGitHub Releaseにdraftとして登録される
      • なぜdraftかと言うと、単純に直公開にビビった

ちゃんとやろうとするとgit-flowで言うところのreleaseブランチとdevelopブランチが最低限は欲しいかなと思いましたが、まぁ必要に迫られたときでいいやとしました。

ではでは、詳細に記していこうと思います。

目次。

  • バージョンアップの自動化(maven-release-plugin
    • maven-release-pluginの設定
    • CircleCIでmaven-release-pluginを実行
  • GitHub Releaseへのjar自動登録(github-release-plugin
    • github-release-pluginの設定
    • maven-release-plugingithub-release-pluginの連携
    • CircleCIでgithub-release-pluginを実行
  • ブランチ毎のMavenフェーズ選択

バージョンアップの自動化(maven-release-plugin)

バージョンアップ作業って面倒くさいです。

Mavenの場合、SNAPSHOTを外してテストを通してjarとかにパッケージングして出来上がったパッケージをどっかに登録してgitのtag打って次の開発用にversionをインクリメントしつつSNAPSHOT付けてみたいな。

ミスるともっと面倒くさいことになります。しかも、恥ずかしい><
例えばv1.2.3にしたつもりがv12.3にインフレしちゃったりとかあるあるネタっすね。ヤヴァイ…

なので自動化したいわけです。

Mavenの場合、maven-release-pluginを使うことで大体解決することができます。

大体というかほぼ全て解決してくれます。逆にmaven-release-pluginで解決できないことは、パッケージの登録だけかと思います。

そんでもってパッケージの登録は後述するgithub-release-pluginの領分です。

さて、やりたいこととしてはよくある事例かなぁとおもむろにググるCircleCIの公式ドキュメントがヒット!

これは勝つるとふんすふんすしますが、よく見るとv1のドキュメントやん…orz

しかもよくよく読むとCircleCIのREST APImvn release:prepareを実行しているだけです。

これでは、maven-release-pluginをローカルで実行するかCircleCIで実行するかぐらいの差しかありません。

まぁ、ビルド環境が統一されるのでビルドできないそれおまかん的な問題は回避できます。

が、そうじゃない。

やりたいことは、ある条件をトリガーに(ここでは、masterブランチのmergeなどでcommitが発生したら)自動でバージョンアップ作業を開始したい、なので、テストのビルドが通ったことを自分で確認してからAPIを呼び出すというのはちょっと今回のケースには合わないなぁという感じです。

他にも色々とインターネッツを漁りますが良い記事が見つかりません…

と、いうことで自力で頑張ります。

maven-release-pluginの設定

まず、maven-release-pluginの設定です。

公式ドキュメントを参考にpomファイルを編集します。

projectタグ直下にscmタグを追加して、そこに連動させたいGitHubレポジトリ情報を記載。

こんな感じ。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <scm>
    <connection>scm:git:git@github.com:hoge/fuga.git</connection>
    <tag>HEAD</tag>
  </scm>
  :
  :

そして、project/build/pluginsタグ内にmaven-release-plugin用のpluginタグを追加します。

こんな感じ。

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-release-plugin</artifactId>
        <version>2.5.3</version>
        <configuration>
          <tagNameFormat>v@{project.version}</tagNameFormat>
          <scmCommentPrefix>'[maven-release-plugin][ci skip] '</scmCommentPrefix>
        </configuration>
      </plugin>

tagNameFormatはお好みです。
上記の設定だとprojectタグ直下のversionタグの値が参照されて、v1.0みたいな感じになります。

注意すべきはscmCommentPrefixです。
ここに[ci skip]を含めたほうがいいと思います
表現が曖昧なのはちゃんと試していないからですすみません。
これが無いと無限ループします、たぶん

pomファイルの編集が終わったらGitHubにpushする前にローカルで動作確認します。

masterのcommit履歴が汚れるのは嫌なので適当なブランチを切って動作確認を行います。

git checkout -b hoge
mvn --batch-mode -DskipTests=true release:prepare release:perform

maven-release-pluginの動作確認が目的なのでskipTestsを指定しています。

--batch-modeを指定しないとインタラクティブにバージョン番号とかを聞かれます。

ローカルで実行する場合は対話式に実行しても良いのですが、CircleCIで実行することを想定している以上、対話式では無理なので--batch-mode指定で実行します。
その場合、バージョン番号は最小位の値をひとつインクリメントするようです。

動作確認が済んだらおk。次はCircleCI上で動作させます。

CircleCIでmaven-release-pluginを実行

唐突、というか今更ですが、ここでmaven-release-pluginの動作についてざっくり説明します。

maven-release-pluginで使用するのはおおよそ2つのgoalだけです。1つはprepareでもう1つはperform

それぞれざっくり何をしているかというと、prepareはpomファイルのversionタグのSNAPSHOTを外してそれをcommitして(!)git tagを打って(!)次の開発用にversionタグをインクリメントしつつSNAPSHOT付けてcommitし(!)、それをpushします(!)。

実際にprepareで作成される1回目のcommitを貼っつけましょう。こんな感じです。

commit 0123456789abcdef........................
Author: circleci <circleci@circleci.com>
Date:   Sat Sep 2 00:00:00 2017 +0000

    [maven-release-plugin][ci skip] prepare release v1.2

diff --git a/pom.xml b/pom.xml
index 0123456..789abcd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@

   <groupId>io.github.hoge</groupId>
   <artifactId>fuga</artifactId>
-  <version>1.2-SNAPSHOT</version>
+  <version>1.2</version>
   <packaging>jar</packaging>

   <name>fuga</name>
@@ -11,7 +11,7 @@

   <scm>
     <connection>scm:git:git@github.com:hoge/fuga.git</connection>
-    <tag>HEAD</tag>
+    <tag>v1.2</tag>
   </scm>

   <properties>

一方performは何をしているかというと、prepareで作成されたtagでcheckoutして、(デフォルト設定の場合)mvn deployを実施します。

maven-release-pluginの動作概要おわりっ!

まぁ、何が言いたいかというと、!したように自動でcommit&push等を行うということです。

CircleCIでmaven-release-pluginを使う場合、当然ながらCircleCIから上記のcommit&push等を行います。ところが、CircleCIのGitHub連携ではRead-onlyの秘密鍵しか作成されません。

つまり、Writableな秘密鍵を新規作成してCircleCIに登録、そしてその公開鍵を連携するレポジトリに登録する必要があります。

詳細はCircleCIのドキュメントを参照。

これでCircleCIはWritableな秘密鍵を使用してGitHubにpushできるようになります。

最後にconfig.ymlを修正してprepareperformを実行するようにします。

こんな感じ。

version: 2
jobs:
  build:
    docker:
      - image: circleci/openjdk:8-jdk
    steps:
      - checkout
      - add_ssh_keys:
          fingerprints:
            - "xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"
      - run: git config --global user.email "circleci@circleci.com"; git config --global user.name "circleci"; mvn --batch-mode release:prepare release:perform

gitのuser設定はお好みです。

これをGitHubにpushしてCircleCIで動作確認。うまくいったらおk。

めでたしめでたし。

しかし、セキュリティしっかりしているとメンドイ…

GitHub Releaseへのjar自動登録(github-release-plugin)

Mavenpackageフェーズでjarを作成します。

せっかくなので、このjarをどこか手軽な場所に自動で保存しておきたいと思ったりします。

まぁ、CircleCIにもjarをArtifactsとして置いておくことは出来るのですが、ログインしないとArtifactsへのリンクを辿れないようなので手軽な場所とは言えません。

Mavenを使っているので、一般的にはどこかのMavenリポジトリに対してdeployフェーズでjarを登録するべきでしょうが、Mavenリポジトリなんて持ってないし、今回の対象は前回記事で作成した実行可能形式fat jarなのでMavenリポジトリに登録するメリットがそんな無いっす。

ということで、どうしよっかなーと考えていたらGitHub Releaseに置けばいいじゃんむふーとなりました。

maven-release-plugin同様、やりたいこととしてはよくある事例かなぁとおもむろにググるCircleCIの公式ブログがヒット!

これは勝つるとふんすふんすしますが、よく見るとpackagecloudにデプロイするやり方やん…orz(デジャヴ)

気を取り直して、そういえば、GitHub ReleaseにアップロードするMavenビルドプラグインがあったなぁということを思い出します。

ようするにGitHub APIを使用してGitHub Releaseへの登録をMavenビルドに組み込めるプラグインですね。

それが表題のgithub-release-pluginです。

公式プラグインでは無く、おそらく個人が公開しているサードパーティプラグインなので、メンテナンスされなくなったりする点が怖いですが、コードをみた感じシンプルなのでいざという時は自分でなんとかできる範囲です。

ということで、github-release-pluginを使うことにしました。

github-release-pluginの設定

以下の4つが必要です。

  • scmタグの追加
  • pluginタグの追加
  • maven-deploy-pluginのskip設定
  • settings.xmlの作成

scmタグはデプロイ先のGitHubの解決に使用しているようです。こいつはmaven-release-pluginを設定する際に既に記述してあるので割愛。

次のpluginタグはこんな感じ。

      <plugin>
        <groupId>de.jutzig</groupId>
        <artifactId>github-release-plugin</artifactId>
        <version>1.2.0</version>
        <executions>
          <execution>
            <id>github-upload</id>
            <phase>deploy</phase>
            <goals>
              <goal>release</goal>
            </goals>
            <configuration>
              <releaseName>v${project.version}</releaseName>
              <tag>v${project.version}</tag>
              <artifact>${project.build.directory}/${project.artifactId}-${project.version}.jar</artifact>
            </configuration>
          </execution>
        </executions>
      </plugin>

github-release-pluginは自身を使用してGitHub Releaseに登録しているようなのでその設定をほぼ丸パクリです。

そしてmaven-deploy-pluginのskip設定です。以下のpluginタグを追記します。

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-deploy-plugin</artifactId>
        <version>2.7</version>
        <configuration>
          <skip>true</skip>
        </configuration>
      </plugin>

こいつを設定しないとmvn deploy実行時にこんな感じで怒られます。

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy (default-deploy) on project hoge: Deployment failed: repository element was not specified in the POM inside distributionManagement element or in -DaltDeploymentRepository=id::layout::url parameter -> [Help 1]

ようするにデプロイ先を示せということなんですが、前述の通りGitHub Releaseに登録できれば良いのでmaven-deploy-pluginさんはskip設定にして黙っていてもらいますm(_ _)m

最後のsettings.xmlはこんな感じ。

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <servers>
    <server>
      <id>github</id>
      <privateKey>${env.GITHUB_API_TOKEN}</privateKey>
    </server>
  </servers>
</settings>

こいつを適当なファイル名で保存します。自分は.circleci.settings.xmlにしました。

idgithub決め打ちです。

privateKeyにはGitHubのPersonal access tokensを記述します。Personal access tokensの発行方法はGitHubのドキュメントを参照。

ただし、べた書き記述してしまうとGitHubにpushできなくなってしまいます。pushしちゃうとAccess tokenがバレてしまうので。なので環境変数経由で渡すようにします。

実は認証にユーザ名とパスワードを記述するやり方もあります。が、自分はGitHubの2段階認証を有効にしているのでユーザ名とパスワードを記述するやり方は使えないっす。

話を戻して、動作確認はこんな感じ。

export GITHUB_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
mvn -s ./.circleci.settings.xml deploy -Dgithub.draft=true -DskipTests=true

以下のようしてgithub-release-plugin単独の動作確認もできます。

export GITHUB_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
mvn -s ./.circleci.settings.xml de.jutzig:github-release-plugin:release -Dgithub.draft=true -DskipTests=true

GitHub Releaseを確認して登録できていればおk。

maven-release-pluginとgithub-release-pluginの連携

今回はmaven-release-pluginと連携させたいので、そのための設定をmaven-release-pluginに1つ追加します。

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-release-plugin</artifactId>
        <version>2.5.3</version>
        <configuration>
          <tagNameFormat>v@{project.version}</tagNameFormat>
          <scmCommentPrefix>'[maven-release-plugin][ci skip] '</scmCommentPrefix>
          <!-- vvv ココ vvv -->
          <arguments>-Dgithub.draft=true</arguments>
        </configuration>
      </plugin>

maven-release-pluginはその実行過程で別プロセスとしてmvn deployを実行します。argumentsタグはその引数を指定するタグです。こいつに-Dgithub.draft=trueを設定します。
先述のとおり、自分の場合はいきなりの公開にビビっているのでdraft公開です。

動作確認はこんな感じ。前述のようにcommitが発生するので、一時的なブランチを作成してそのブランチで確認すると良いと思います。

export GITHUB_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
mvn -s ./.circleci.settings.xml --batch-mode release:prepare release:perform

さっきと同様、GitHub Releaseを確認して登録できていればおk。

お次はCircleCIで動作させます。

CircleCIでgithub-release-pluginを実行

前述の通り、github-release-pluginを利用するにあたりGitHubのPersonal access tokensを使用します。

こいつを環境変数経由で渡すように組んだので、CircleCIのビルドSettingsからEnvironment Variablesに今回の環境変数名であるGITHUB_API_TOKENを追加します。

やり方は察してください(雑w

疲れてきたンゴ…

だがあと少しなので頑張る。

最後に前述の.circleci.settings.xmlを読み込むようconfig.ymlを修正して完了。

version: 2
jobs:
  build:
    docker:
      - image: circleci/openjdk:8-jdk
    steps:
      - checkout
      - add_ssh_keys:
          fingerprints:
            - "xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"
      # vvv ココ vvv
      - run: git config --global user.email "circleci@circleci.com"; git config --global user.name "circleci"; mvn -s ./.circleci.settings.xml --batch-mode release:prepare release:perform

めでたしめでたし。

ちなみにですが、Travis CIではGitHub Releaseへのアップロードは標準機能っぽいです。これがCircleCIでちょっち失敗したかなぁと思ったやつです。

隣の芝生は青い案件であることを祈るンゴ。

ブランチ毎のMavenフェーズ選択

CircleCIはどのブランチでcommitが生じてもビルドが開始します。

それ自体は良いのですが、GitHub Releaseの自動登録はmasterブランチのcommitをトリガーにしたビルド時のみに実施してほしいです。

ということで、master以外のブランチのcommitをトリガーにしたビルドはpackageフェーズまでに留めるようにします。

これは簡単。環境変数CIRCLE_BRANCHに実行中ビルドのブランチが格納されるので、これを使って条件分岐するだけです。

version: 2
jobs:
  build:
    docker:
      - image: circleci/openjdk:8-jdk
    steps:
      - checkout
      - add_ssh_keys:
          fingerprints:
            - "xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"
      # if文で分岐するだけ
      - run: if [ $CIRCLE_BRANCH = 'master' ]; then git config --global user.email "circleci@circleci.com"; git config --global user.name "circleci"; mvn -s ./.circleci.settings.xml --batch-mode release:prepare release:perform; else mvn package; fi

めでたしめでたし。

むすび

(ヽ´ω`)

記事書くのは大変ですね…

気を取り直して、実際にCircleCIを使っての感想です。

キャッシュはえー。ローカル実行よりずば抜けてはえーよ!
Maven Central Repositoryは日本からだと遠いんですかね。

逆にCircleCIへのアクセスはラグりまくりですw
CircleCIにはデバックビルド機能が有り、これを有効にしてビルドを実施するとビルドコンテナにssh接続できるようになります。が、遅いおw

何はともあれ、これでやっとacceptanceテスト書けるぞっと思ったら、initiatorは対応していなかったっていう…

(´・ω・`)

継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化

継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化