GradleからbootRunを実行する時にコマンドライン引数でパラメータを渡したい

GradleからbootRun (Spring Bootアプリケーションの起動タスク)を実行する時に、コマンドライン引数でパラメータを渡す方法についてです。

注:古めのバージョン(Spring Boot 1.4.x, Gradle 3.x)で確認しているため、最新バージョンでは動作しない可能性があります。

build.gradleで指定する場合

はじめに、 build.gradle で指定する場合は以下のように書きます。

bootRun {
   jvmArgs  ['-Dspring.profiles.active=local']
}

コマンドライン引数で渡す場合

そもそもSpring Bootでは、環境変数やプロパティファイルなど、多様で柔軟な方式で外部設定値を与えることができます。
コマンドライン引数もそれらの選択肢のうちの一つであり、プロパティファイルを書き換えたり環境変数を指定するまでもないようなケースで便利(お手軽)です。

いきなり結論ですが、コマンド実行の際は以下のようにします。

$ ./gradlew bootRun -PjvmArgs="-Dspring.profiles.active=local"

-P はプロジェクトプロパティを設定するGradleのオプションです。

合わせて、 build.gradle も以下のようにします。

bootRun {
    if (project.hasProperty('jvmArgs')) {
        jvmArgs project.jvmArgs.split('\\s+') as List
    }
}

ハイブリッド版

build.gradle でデフォルト値は決めつつ、コマンドライン引数で追加の設定を与えたいような、上記2つのハイブリッド版です。
コマンド実行方法は同じなので省略します。 build.gradle をちょっと書き換えるだけでOKです。このあたりの柔軟性はやりすぎ注意ですが、やはりGradleは便利ですね。

bootRun {
    def myJvmArgs = ['-Dfoo=aaa']
    if (project.hasProperty('jvmArgs')) {
        myJvmArgs.addAll(project.jvmArgs.split('\\s+') as List)
    }
    jvmArgs myJvmArgs
}

CodeBuildの標準イメージ(マネージド)上でdocker-composeを使ってRDBやRedisを利用する

CodeBuildの標準イメージで、docker-composeが使えるようになっていました。

docs.aws.amazon.com

でそれぞれ使えそうです(リンク先はGitHub上のDockerfile)。

これまでは、MySQLやRedisなどが絡んだテストを実行したい場合、ビルドのフェーズ内でパッケージをインストールする or 独自のイメージを用意する等の対応が必要でした(よね?)。
今では、日常的な開発環境構築に利用する docker-compose.yml をそのまま使って docker-compose up -d するだけで、使い捨てのビルド環境が構築できるということですね。

試してみて気づいた細かい注意点などをメモしておきます。

UbuntuAmazon Linuxの違い

次に挙げるように、パッケージマネージャやJavaのランタイムの指定の違いがあり、共通の buildspec.yml でどちらのOSにも対応するのはちょっと難しいかもしれません。
もっとも、どちらかのOSに対応していればほとんどのケースでは十分だと思いますが…。

パッケージインストール用のコマンド

CodeBuildの buildspec.yml を書く上では、パッケージマネージャが異なることは気をつける必要があります。

Ubuntu の場合は apt / apt-get, Amazon Linux の場合は yum を利用します。

runtime-versions で指定できるjavaの種類

aws/codebuild/standard:2.0 を使っている場合、 buildspec.yml 中で runtime-versions を指定する必要があります。

docs.aws.amazon.com

未指定の場合、以下のようなエラーが発生します。

YAML_FILE_ERROR Message: This build image requires selecting at least one runtime version.

例えば、ランタイムにJavaを使いたい場合は、以下のように指定します。

phases:
  install:
    runtime-versions:
      docker: 18
      java: openjdk8

ここで指定できるJavaの値ですが、OSによって次のような違いがあるみたいです(correttoはAWSの OpenJDKディストリビューションですね)

OS 指定可能な値
Ubuntu openjdk8, openjdk11
Amazon Linux corretto8, corretto11

coretto の指定について、(この記事を書いている時点で)日本語のドキュメントには書かれていませんが、英語のドキュメントには書かれています。

Runtime Versions in Buildspec File Sample for CodeBuild - AWS CodeBuild

高速化のためのローカルキャッシュの利用

ビルド時間の短縮は常に重要な関心事です。素直に作ったビルドジョブにおいては、ビルドの時間を大きく占めるのが、Docker イメージや依存関係のあるライブラリのダウンロードでしょう。
2017年の末頃からS3上へのキャッシュはサポートされていましたが、今ではローカルキャッシュも利用できます。ネットワーク越しでないため、2回目以降のビルドがさらに短縮できることが期待できます。

docs.aws.amazon.com

JavaでGradleを使っている場合は、

cache:
  paths:
    - '/root/.gradle/**/*'

と指定しておけばOKだと思います。
ちなみに、ローカルキャッシュに関するサイズの上限や有効期間については、AWSドキュメント上では言及がありません。キャッシュがあってもなくても、ビルドが安定するような作りにしておくことが大切ですね。

あとは、Docker18.09.0からサポートされたBuildKitなどを有効化してみるのも効果があると思います。

GitHubで2FAを有効にした時にgit pushでAuthentication failedや403エラーになった時の対応

GitHubで途中でアカウントの2FAを有効にした場合、今まで使っていたリポジトリgit push origin xxx を実行すると、

remote: Invalid username or password.
fatal: Authentication failed for 'https://github.com/hoge/some-repo.git/'

とか、

fatal: unable to access 'https://github.com/hoge/some-repo.git/': The requested URL returned error: 403

といったエラーに遭遇します。
その時の対応法です。

解決法

  1. 新しくPersonal access tokensを発行する
  2. .git/configに記載されている urlhttps://{account name}@github.com/hoge/some-repo.git の形式にする
  3. 初回push時にパスワードを聞かれるので、1.で発行したPersonal access tokenを入力(ペースト)する

参考

Spring × SpockのテストでsetupSpecにAutowiredされたコンポーネントを使いたい

今のプロジェクトではJUnit5よりも好んでSpock (Groovy)を使ってます。

Spring Boot製アプリケーションのテストを行う際に、 setupSpec (テストクラス内で一度きりの初期化)メソッド内で、 Autowired されたコンポーネントを使いたかったのですが、使えなかったので、その対応方法のメモです。

まずは前提ですが、

  • spring-spockはなるべく最新のバージョンを使いましょう
  • Spockのテストクラスには @RunWith(SpringRunner)つけない ようにしましょう

失敗ケース

  @Shared
  @Autowired
  HogeService hogeService

  def setupSpec() {
      hogeService.doSomething()
  }

成功ケース

  @Autowired
  HogeService hogeService

  @Shared
  boolean isInitialized = false

  def setup() {
      if (isInitialized) {
          return
      }

      hogeService.doSomething()
      isInitialized = true
  }

というわけで、 setupSpec を使うのをやめて、setup(テストメソッド側で毎回呼ばれる処理)に移しました。 ちなみに、 isInitialized を使った一連のコードは、なくても(テストの成否に)影響はないのですが、 hogeService.doSomething が1回だけ行えばよいような初期化処理であり、テストケースの数が多い場合、実行時間の面で有利です。

参考

インストール済みパッケージ情報をApex or SOQLで取得したい #salesforce

[設定] - [インストール済みパッケージ] から確認できる、インストール済みのパッケージとそのバージョンを、プログラム的に取得したい、というメモです。

結論から言うと、インストール済みパッケージの取得はTooling APIで提供されているみたいなので、以下のクエリでOKです。

SELECT Id, SubscriberPackageId,
  SubscriberPackage.NamespacePrefix,
  SubscriberPackage.Name, SubscriberPackageVersion.Id,
  SubscriberPackageVersion.Name, SubscriberPackageVersion.MajorVersion,
  SubscriberPackageVersion.MinorVersion,
  SubscriberPackageVersion.PatchVersion,
  SubscriberPackageVersion.BuildNumber
FROM InstalledSubscriberPackage
ORDER BY SubscriberPackageId

開発者コンソール上のQuery Editorで行う場合、「Execute」の右にある「Use Tooling API」にチェックを入れておく必要があります。

ところが、これをApexでやろうとした場合、上記のクエリ結果を List<InstalledSubscriberPackage> に代入してあげればOKかと思いきや、 InstalledSubscriberPackage がInvalid Typeとしてエラーになってしまいます。
そのため、ちょっと回りくどいですが、ApexからTooling APIREST API呼び出しを行ってあげればOKです。

String query = 'SELECT Id FROM InstalledSubscriberPackage';

Http http = new Http();
HttpRequest req = new HttpRequest();
req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId());
req.setHeader('Content-Type', 'application/json');
req.setEndpoint(URL.getSalesforceBaseUrl().toExternalForm() + '/services/data/v44.0/tooling/query/?q=' + EncodingUtil.urlEncode(query, 'UTF-8'));
req.setMethod('GET');

HttpResponse res = http.send(req);
System.debug(res);

String result = res.getBody();
System.debug(result);

Tooling API、直接触れる機会はなかなかないですね。特に最近はSalesforce CLIなどでラップされてしまっていますし。

Lightning Web Components(LWC)をVisualforceで表示してみる#salesforce

巷で話題の(?)Lightning Web Components(LWC)をVisualforceで表示できないか試してみました。

LWCをAura Componentでラップして、Lightning Outを使う

最初に思いついたのは、LWCをAura Componentでラップして、Lightning Out for Visualforceを使う方法です。
この方法で問題なく表示できました。

ソースを順に見ていきます。

LWC

Trailheadのサンプルのまんまですが…。

■ helloworld.html

<template>
  <lightning-card title="HelloWorld" icon-name="custom:custom14">
      <div class="slds-m-around_medium">
          <p>Hello, {greeting}!</p>
          <lightning-input label="Name" value={greeting} onchange={changeHandler}></lightning-input>
      </div>
  </lightning-card>
</template>

■ helloworld.js

import { LightningElement, track } from 'lwc';
export default class HelloWorld extends LightningElement {
  @track greeting = 'World';

  changeHandler(event) {
    this.greeting = event.target.value;
  }
}

Aura Component (auraHelloWorld.cmp)

LWCを呼び出すだけ。

<aura:component implements="flexipage:availableForAllPageTypes">
  <c:helloworld />
</aura:component>

参考にしたのは以下のページです:

Compose Aura Components from Lightning Web Components

Lightning Outアプリケーション(helloWorldOut.app)

上記のAura Componentを使えるように宣言します。

<aura:application extends="ltng:outApp" >
    <aura:dependency resource="c:auraHelloWorld" />
</aura:application>

Visualforce (lwcHelloWorld.vfp)

ページ名(ファイル名)は何でもよいです。

<apex:page>
    <apex:includeLightning />
    <div id="hello"></div>
   
    <script>
        $Lightning.use("c:helloWorldOut", function() {
            $Lightning.createComponent("c:auraHelloWorld", {}, "hello", function(cmp, err) {}); 
        });
    </script>
</apex:page>

プレビュー

f:id:jappy:20190121220208p:plain

できました!

Aura Componentでラップする必要ないのでは?

ところで、よくよく考えると、Aura Componentでラップする必要がないのでは?という疑問が湧きました。

というわけで、Lightning Outアプリケーションで直接LWCを宣言します。

<aura:application extends="ltng:outApp" >
    <aura:dependency resource="c:helloworld" />
</aura:application>

その後、Visualforce側 createComponent の引数も "c:helloworld" に変更します。こちらの方法でも、同じように表示できました。

まとめ

LWCをVisualforceで表示したければ、今までのAura Componentと同じ要領でLightning Outが利用できます。
注:今回は簡単なサンプルだけなので、実際にやると色んな落とし穴があるかもしれません。