🐙

Check! GitHub Actions: Maven リポジトリにデプロイする

2020/12/13に公開

Prologue

前回に引き続き、とあるプロジェクトで、 Maven リポジトリへのデプロイを GitHub Actions に移行することになったお話です。

まず前回は、Java を利用した経験が乏しく不慣れだったので、手始めとして手動で Maven リポジトリへデプロイする手順を確認し、記事にまとめました。今回の作業環境もこの記事がベースになっています。

https://zenn.dev/dzeyelid/articles/76d61c51522594f9a8c7

そして、本記事では、GitHub Actions で Maven リポジトリにデプロイする方法を整理していきます💡

Maven リポジトリにデプロイする GitHub Actions のワークフローを作成する

実は、ほとんどのことが、こちらの公式ドキュメントにとてもわかりやすく記載されています😳

https://docs.github.com/ja/free-pro-team@latest/actions/guides/publishing-java-packages-with-maven

ざっくりとした流れは、 setup-java アクションを使ってJava のセットアップをし、 runmvn コマンドを実行するだけです。

このドキュメントでは GPG 署名について触れられていなかったので、その点を中心に補足していきます。

  • Maven プロジェクトの設定を確認する
  • setup-java アクションに GPG 秘密鍵をインポートさせる
  • シークレットを登録する
  • GitHub Actions のワークフローを書く

Maven プロジェクトの設定を確認する

まず、ここで扱うプロジェクトについて記載します。

pom.xml の構成

プロジェクトの pom.xml には、下記のように記述しています。

<version> は、mvn コマンドを実行する際に上書きして指定できるようにプロパティをあてました。

pom.xml 抜粋
<project>
  <groupId>com.example.your-project<groupId>
  <artifactId>your-artifact</artifactId>
  <version>${revision}${changelist}</version>
  ...
  <distributionManagement>
    <repository>
      <id>your-repository</id>
      <url>http://<your server>:8081/nexus/content/repositories/your-repository</url>
    </repository>
  </distributionManagement>
  ...

  <!-- GPG sign -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-gpg-plugin</artifactId>
        <version>1.6</version>
        <executions>
          <execution>
            <id>sign-artifacts</id>
            <phase>verify</phase>
            <goals>
              <goal>sign</goal>
            </goals>
            <!-- To make reference to the passphrase on settings.xml instead of GUI dialog -->
            <configuration>
              <gpgArguments>
                <arg>--pinentry-mode</arg>
                <arg>loopback</arg>
              </gpgArguments>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...

  <properties>
    <revision>1.0.0</revision>
    <changelist>-SNAPSHOT</changelist>
  </properties>
</project>

settings.xml の設定

settings.xml は、後述のGitHub Actions のワークフローの中で setup-java アクションに指示して自動で作成させます。

setup-java アクションに GPG 秘密鍵をインポートさせる

この setup-java アクションは、Java の環境セットアップとともに、settings.xml の生成や GPG キーのインポートまで実施してくれます。詳細は下記をご参照ください。

https://github.com/marketplace/actions/setup-java-jdk

setup-java アクションはこのように記載します。

- name: Set up Java with importing GPG
  uses: actions/setup-java@v1
  with: # running setup-java again overwrites the settings.xml
    java-version: 1.8
    server-id: maven # Value of the distributionManagement/repository/id field of the pom.xml
    server-username: MAVEN_USERNAME # env variable for username in deploy
    server-password: MAVEN_USER_PASSWORD # env variable for token in deploy
    gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
    gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import

まず、server-username, server-password, gpg-passphrase に指定するのは、このアクションが生成する settings.xml のそれぞれの値で参照させる環境変数の変数名(文字列)です。

生成される settings.xml
<settings>
  <servers>
    <server>
      <id>maven</id>
      <username>${env.MAVEN_USERNAME}</username>
      <password>${env.MAVEN_USER_PASSWORD}</password>
    </server>
    <server>
      <id>gpg.passphrase</id>
      <passphrase>${env.MAVEN_GPG_PASSPHRASE}</passphrase>
    </server>
  </servers>
</settings>

そして gpg-private-key に指定する値は、GPG秘密鍵の内容(value)です。

GPG秘密鍵のエクスポートは gpg --export-secret-key でできますが、このままだとバイナリデータで出力されます。バイナリでは GitHub のシークレットで扱えないため、テキスト形式で出力する必要があります。 --armor オプションを指定することで、テキスト形式で出力することができます。

gpg --export-secret-key --armor 'your-key' > private.pem
gpg --help

 -a, --armor                 create ascii armored output

ここで出力した private.pem の中身をシークレットに登録し、 gpg-private-key に渡します。すると、このアクションが GPGインポートを実行してくれ、以後のステップで利用できるようになります。

シークレットを登録する

それでは、ワークフローで利用するシークレット情報を登録していきましょう。

https://docs.github.com/ja/free-pro-team@latest/actions/reference/encrypted-secrets

GitHub リポジトリの「Settings」>「Secrets」をひらき、「New repository secret」を選択してシークレットを登録します。

ここでは、4つのシークレットを登録します。

シークレット名 説明
MAVEN_USERNAME Maven リポジトリに接続するユーザー名
MAVEN_USER_PASSWORD Maven リポジトリに接続するためのパスワードまたはトークン
MAVEN_GPG_PASSPHRASE GPGキーのパスフレーズ
MAVEN_GPG_PRIVATE_KEY 前述で取得した GPG 秘密鍵の中身

GitHub Actions のワークフローを書く

さて、百聞は一見に如かずなので、コードを見てみましょう。このコードは下記の動きをします。

  • トリガは、リリースが公開されたときの発動を想定(後述の解説参照)
  • Java のバージョンは 1.8、アーキテクチャは x64 対象

ジョブは、 2つ。まずビルドを行い、成功したらデプロイを行います。

  • ビルド
    • JDK のセットアップ
    • mvn clean package でビルド
  • デプロイ
    • GPG キーのインポートを含む、 JDK のセットアップ
    • mvn clean deploy でデプロイ
relese.yml
name: Release artifacts

on:
  release:
    types:
    #  - released
      - published

env:
  JAVA_VERSION: '1.8'
  JAVA_PACKAGE: jdk
  JAVA_ARCHITECTURE: x64

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Setup JDK
      uses: actions/setup-java@v1
      with:
        java-version: ${{ env.JAVA_VERSION }}
        java-package: ${{ env.JAVA_PACKAGE }}
        architecture: ${{ env.JAVA_ARCHITECTURE }}

    - name: Package
      run: |
        mvn clean package --batch-mode --no-transfer-progress

  deploy:
    runs-on: ubuntu-latest
    needs: build

    steps:
    - uses: actions/checkout@v2
    - name: Setup JDK
      uses: actions/setup-java@v1
      with:
        java-version: ${{ env.JAVA_VERSION }}
        java-package: ${{ env.JAVA_PACKAGE }}
        architecture: ${{ env.JAVA_ARCHITECTURE }}
        server-id: dev
        server-username: MAVEN_USERNAME
        server-password: MAVEN_USER_PASSWORD
        gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
        gpg-passphrase: MAVEN_GPG_PASSPHRASE

    - name: Deploy
      run: |
        mvn clean deploy --batch-mode --no-transfer-progress -DskipTests=true -Dchangelist= -Drevision=${{ github.event.release.tag_name }}
      env:
        MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
        MAVEN_USER_PASSWORD: ${{ secrets.MAVEN_USER_PASSWORD }}
        MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}

解説

ポイントは、デプロイ時の run ステップの環境変数の指定です。それぞれの環境変数は、setup-java アクションの server-username, server-password, gpg-passphrase で指定した文字列を変数名とし値を渡すことで、 settings.xml で読み込まれ、デプロイ処理に反映されます。

また、 mvn clean deploy のオプションで、revisionchangelist プロパティを操作しています。これは、pom.xml で定義したプロパティで、revision プロパティにリリースタグ ${{ github.event.release.tag_name }} を渡し、スナップショットの指定を外すため changelist プロパティを空にしています。

細かい補足はこちらをご参照ください。

動作確認

ワークフローを push したら、実行させてみましょう!

不必要なリリースの作成はしたくないですが、作業用と割り切って、プレリリースを作成して公開します。このとき、リリースタグの設定は、作業ブランチに対して行ってください。(リリースタグが設定されたブランチに対してワークフローを参照しに行くためです。)

そして、アクションを見てみましょう。

無事にワークフローの実行が完了していれば成功です!👏

補足

補足: mvn deploy 中にパスフレーズを入力させる GUI が出てしまう現象の対処

前述の pom.xmlmaven-gpg-plugin の設定にあるこの部分、

pom.xml 抜粋
<configuration>
    <gpgArguments>
        <arg>--pinentry-mode</arg>
        <arg>loopback</arg>
    </gpgArguments>
</configuration>

これは、前の記事 でも記載しましたが、 mvn deploy で GPG 署名を行う際、パスフレーズの入力を促される(※)現象を回避するための記述です。

※ 実際には、下記のような gpg: signing failed: Inappropriate ioctl for device というエラーメッセージでダイアログを表示できないエラーが発生します。

[INFO] --- maven-gpg-plugin:1.6:sign (sign-artifacts) @ your-artifact ---
gpg: signing failed: Inappropriate ioctl for device
gpg: signing failed: Inappropriate ioctl for device
[INFO] ------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------
[INFO] Total time:  29.957 s
[INFO] Finished at: 2020-12-09T15:54:50Z
[INFO] ------------------------------------------------------------------
Error:  Failed to execute goal org.apache.maven.plugins:maven-gpg-plugin:1.6:sign (sign-artifacts) on project your-artifact: Exit code: 2 -> [Help 1]
Error:  
Error:  To see the full stack trace of the errors, re-run Maven with the -e switch.
Error:  Re-run Maven using the -X switch to enable full debug logging.
Error:  
Error:  For more information about the errors and possible solutions, please read the following articles:
Error:  [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
Error: Process completed with exit code 1.

こちらの issue でも話題になってました。

https://github.com/actions/setup-java/issues/91

補足: リリースのトリガについて

実際、ワークフローの作成・編集を行っていると、released のトリガでは扱いにくいです。(作業したいだけなのにリリース打たないとならないのはちょっと…)

なので、今回は下記の方法で作業しました。

  • トリガには on.release.typespublished を指定しておく
  • 作業ブランチに対してリリースタグを設定し、作業用のプレリリースを作り公開する → ワークフローが作動する
  • 再度ワークフローを実行させたいときは、git push -d origin <tag name> でタグを消去すると ↑のプレリリースがドラフトに戻るので、再度公開することで発火させる

edited を指定して、プレリリースの description などを適宜変更して保存する方法でもよいかもしれません。

リリースのトリガについては細部を確認したいので、下記のスクラップで検証状況を掲載しつつ、記事にまとめようと思います。

https://zenn.dev/dzeyelid/scraps/d34c67e2f7d9b5#comment-032ab5701533c1

補足: run ステップを指定したディレクトリで実行したい場合

GitHub Actions のワークフローで run ステップを指定したディレクトリで実行したい場合は、 working-directory で指定します。

- name: Package
  run: |
    mvn clean package
  working-directory: ./path-to-your-project

https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun

また、ワークフロー全体で同じディレクトリを指定したい場合は defaults.run.working-directory で、ジョブ全体で指定したい場合は jobs.<job_id>.defaults.run.working-directory も利用できます。

defaults:
  run:
    working-directory: ./path-to-your-project
jobs:
  job1:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./path-to-your-project

https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#defaultsrun

https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_iddefaultsrun

補足: mvn コマンドのオプション( --batch-mode, --no-transfer-progress

用途に寄りますが、mvn コマンドの下記のオプションが便利そうです。

mvn --help

 -B,--batch-mode                        Run in non-interactive (batch)
                                        mode (disables output color)

 -ntp,--no-transfer-progress            Do not display transfer progress
                                        when downloading or uploading

Epilogue

ようやく、長かった Java Maven リポジトリへのデプロイの自動化が確立できました!

知らない分野の作業するのは、初動はどうしてもキャッチアップに時間がかかってしまいますが、できるようになると楽しいですね!✨

Discussion