APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Azure Pipelinesのscript利用時はご注意を

f:id:turtle2005:20220318212854p:plain
Azure Pipelines

Pipelineで問題発生している、でも正常終了の怪

突然ですが、Azure Pipelines上でタスク/スクリプトを実行する際どのような方法で実行されていますか?

例えばNode.js用のPipelineを作成することを考えてみましょう。

f:id:turtle2005:20220318213058p:plain
Node.js

新規にPipelineを作成する際に上記のNode.js用Pipelineを選択すると、以下のようなYamlが作成すると思います。

pool:
  vmImage: ubuntu-latest

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '16.x'
  displayName: 'Install Node.js'

- script: |
    npm install
    npm run build
  displayName: 'npm install and build'

ビルドの前にlintを実行したい場合、以下のように修正すると思います。

- script: |
    npm install
    npm run lint
    npm run build
  displayName: 'npm install and build'

この場合の期待動作はlint時に問題を検出した場合にPipelineがエラー終了する、というものになると思います。 しかし実はそうした動作にはなりません。そこが注意しなければならないところです。

コマンドは必ずしも異常終了しない

npm run lintはなにか問題を検出しても エラー にはなりません。 今回の例ではeslintを利用していますが、eslintでは通常問題検出を ワーニング として通知します。このため、コマンドそのものは正常終了(終了コードが0)となります。

$ npn run lint
$ echo $?
0

Azure Pipelinesの標準の動作はコマンドが異常終了した場合(終了コードが0以外)を エラー と扱います。このため、上記の例では何か問題を発見したとしてもPipelineでは正常として扱ってしまうのです。最初の注意ポイントはここです。コマンドの終了コードが非常に重要なのです。

コマンドがエラーでも異常終了しない

eslintでは --max-warnings=0 を実行時引数に付与すると、1つでも問題を検出すると異常終了します(異常終了が0以外となる)

$ npn run lint
$ echo $?
1

これをPipelineで実行したら期待通りの動きになるはず。

- script: |
    npm install
    npm run lint -- --max-warnings=0
    npm run build
  displayName: 'npm install and build'

ところが結果はPipelineは正常終了となりました。ここが注意ポイントです。実は script最後に実行したコマンドの終了コードで正常か異常かを判断します。今回の例では最後に実行する npm run build が正常終了するため、Pipelineも正常と扱ってしまいます。

期待通りに動作するためには

期待通りの動作をさせる方法はいくつかあると思います。ここではそのいくつかを紹介します。

コマンド1つずつにStepを分割する。

今回の事象は複数のコマンドを1つのscript行(ブロック)内で実行していることが原因です。なので、script行(ブロック)1つに付き1つのコマンドを実行すれば問題が解決します。

- script:
    npm install
  displayName: 'npm install'
- script:
    npm run lint -- --max-warnings=0
  displayName: 'npm lint'
- script:
    npm run test
  displayName: 'npm test'

せっかくまとめて書けるにもかかわらず1つ1つに分割するのは面倒ですが、これが一番簡単な方法です。

コマンド終了コードを確認する

スクリプト内でfor loopをさせたいなど、ステップを1つ1つに分割できないケースもあると思います。この場合にも採用するのが次の方法です。 以下のようにコマンド実行時に1つ1つ終了コードを確認し、異常と判断したら exit 1 を実行すれば期待通りの動作になります。

- script: |
    npm install
    npm run lint -- --max-warnings=0
    if [ $? -ne 0 ]; then
      exit 1
    fi
    npm run build
  displayName: 'npm install and build'

簡単なことにも落とし穴

今回の例は当初簡単な修正で実現するだろうと思っていたものです。しかし実際には落とし穴にはまってしまった点です。 何がエラーで、何が正常か、簡単なことでもちゃんと確認しなければ。みなさんもこんな落とし穴にはご注意ください。

実はこのエラー判定の動作はGitHub Actionsではまた少し違います。その動作はまた別の混乱を引き起こします。この違いについては機会があれば書いてみたいと思います。