Github Actions入門その5-式と関数

Git,GitHub,Github Actions

はじめに

以下のエントリに引き続き、Github Actions を学習して学んだことをまとめて見ました。

今回は Github Actions の学習シリーズの第 5 弾として、ワークフローの YAML 設定ファイル内で使用可能な式について解説します。
その後、式の種類や使い方について説明していきます。

式について

環境変数の設定や実行中のワークフローの状態を取得するために「式」を利用できます。

式を使うと、以下のようなことができます。

  • リテラルの結合
  • 実行中の環境情報の取得 ( 実行者、仮想マシン、等 )
  • 関数呼び出し ( 値の加工等 )

算術計算のために演算子を使うこともできます。

式は if キーワードと一緒に利用されることが多いです。
プログラミング言語のように、 if で指定した式の値が true となったときに、該当のステップが実行されます。

「文字列」ではなく「式」であると Github Actions に認識させるためには、以下の構文を使います。

${{ <式> }}

if キーワードの値として式を使用した場合は、 ${{ ... }} の構文を指定しないこともあります。
というのも、Github Actions は if: 以降に指定した記述を「式」として評価・処理するためです。
if: 以降の記述に関しては、冗長かもしれませんが ${{ ... }} の形式で記述しておくのが間違いないでしょうし、チームメンバにとってもわかりやすいでしょう。

以下のサンプルコードでは、 if と環境変数へのセットを行っています。

文字列リテラル部分には注意が必要です。
ダブルクォーテーション (") は利用できず、 シングルクォーテーション(')を使用する必要があります。

---
name: if-example
run-name: if-example
on:
  - push
jobs:
  if-example:
    runs-on: ubuntu-22.04
    steps:
      - run: echo "hello false"
        if: ${{ false }}
      - run: echo "hello true"
        if: ${{ true }}
      - run: echo "hello $MY_ENV_VAR"
        env:
          MY_ENV_VAR: ${{ 'development' }}
$ gh run view --log 4841370653 | grep 'Run '
if-example      Run echo "hello true"   2023-04-29T23:53:03.4496319Z ##[group]Run echo "hello true"
if-example      Run echo "hello true"   2023-04-29T23:53:03.4496794Z echo "hello true"
if-example      Run echo "hello true"   2023-04-29T23:53:03.4958971Z shell: /usr/bin/bash -e {0}
if-example      Run echo "hello true"   2023-04-29T23:53:03.4959442Z ##[endgroup]
if-example      Run echo "hello true"   2023-04-29T23:53:03.5502239Z hello true
if-example      Run echo "hello $MY_ENV_VAR"    2023-04-29T23:53:03.5692289Z ##[group]Run echo "hello $MY_ENV_VAR"
if-example      Run echo "hello $MY_ENV_VAR"    2023-04-29T23:53:03.5692775Z echo "hello $MY_ENV_VAR"
if-example      Run echo "hello $MY_ENV_VAR"    2023-04-29T23:53:03.5744301Z shell: /usr/bin/bash -e {0}
if-example      Run echo "hello $MY_ENV_VAR"    2023-04-29T23:53:03.5744685Z env:
if-example      Run echo "hello $MY_ENV_VAR"    2023-04-29T23:53:03.5745102Z   MY_ENV_VAR: development
if-example      Run echo "hello $MY_ENV_VAR"    2023-04-29T23:53:03.5745390Z ##[endgroup]
if-example      Run echo "hello $MY_ENV_VAR"    2023-04-29T23:53:03.5978957Z hello development

リテラル

式 ( ${{ ... }} ) の中では、以下のリテラルが利用できます。

typevalue
booleantrue / false
nullnull
numberJSON でサポートされているフォーマット
stringシングルクォーテーション ' で囲む。

${{ 'value' }} の表記の場合、 ${{ ... }} で囲む必要はないです。

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Print variables
        run: |
          echo $myNull
          echo $myBoolean
          echo $myIntegerNumber
          echo $myFloatNumber
          echo $myHexNumber
          echo $myExponentialNumber
          echo $myString
          echo $myStringInBraces
        env:
          myNull: ${{ null }}
          myBoolean: ${{ false }}
          myIntegerNumber: ${{ 711 }}
          myFloatNumber: ${{ -9.2 }}
          myHexNumber: ${{ 0xff }}
          myExponentialNumber: ${{ -2.99e-2 }}
          myString: Mona the Octocat
          myStringInBraces: ${{ 'It''s open source!' }}

実行結果は以下のようになります。


 false
 711
 -9.2
 255
 -0.0299
 Mona the Octocat
 It's open source!

関数

Github Actions では組み込み関数が提供されています。
関数は式の中で利用可能です。
いくつかの関数は、値を文字列に変換します。
関数の文字列変換のルールは型ごとに異なります。
以下のルールが適用されます。

結果
Null''
Boolean'true' or 'false'
Number10 進数表現 または 値の大きな数値の場合は指数表現
Array文字列に変換されない
Object文字列に変換されない

contains

contains( search, item )

配列 search の中に item という要素が含まれていた場合に true を返します。
search が string 型の場合は、 item という文字列を含んでいた場合に true を返します。

大文字小文字は区別されません

例 1

contains( 'Hello world', 'llo' )true を返します。

例 2

以下の例では、もしイベントが発生した対象のイシューに "bug" というラベルが付与されていた場合に true を返します。

contains( github.event.issue.labels.*.name, 'bug' )

例 3

github.event_name == "push" || github.event_name == "pull_request" と記述する代わりに、 contains() 関数と fromJSON() 関数を組み合わせて記述できます。

contains(fromJSON('["push", "pull_requrest"]', github.event.name))

startsWith

startsWith( searchString, searchValue )

saarchString で指定した文字列が searchValue で指定した文字列で始まっている場合は true となります。

大文字小文字は区別されません

例 1

startWtih('Hello world', 'He')true を返します。

endWtith

endWith(searchString, searchValue)

searchString の文字列が searchValue で終わっていた場合、 true を返します。

大文字小文字は区別されません

例 1

endsWith('Hello world', 'ld')true を返します。

format

format( string, replaceValue0, replaceValue1, ..., replaceValueN)

string で指定した文字列を、 replaceValue0 ... replaceValueN で指定した値で置換します。
置換対象の文字列は string 文字列の中で {N} の形式で指定されます。
N の部分は数値を指定します。
replaceValueN は 1 つ以上指定する必要がありますが、上限はありません。
波括弧をエスケープしたいときは、 {{ ... }} のように二重に重ねます。

例 1

format('Hello {0} {1} {2}', 'Mona', 'the', 'Octocat')Hello Mona the Octocat を返します。

例 2

format('{{Hello {0} {1} {2}!}}', 'Mona', 'the', 'Octocat'){Hello Mona the Octocat!} を返します。

join

join( array, optionalSeparator )

array は配列か文字列を指定できます。
array の要素をつなげて1つの文字列にします。
もし optionalSeparator が指定されたときは、連結された値の間に optionalSeparator の値が挿入されます。
optionalSeparator が指定されたなかった場合は、 , が指定されたものとして動作します。

例 1

join(github.event.issue.labels.*.name, ', ') の実行結果は 'bug, help wanted' のようになります。
ここではラベルとして bughelp wanted が指定されているものとします。

toJSON

toJSON(value)

value で指定した値を JSON 文字列表現に変換します。
デバッグ用に利用することが多いでしょう。

例 1

toJSON(job){"status":"success"} を返します。

fromJSON

fromJSON(value)

value で指定された JSON の文字列表現を解析し、オブジェクトを返却します。

文字列リテラルや環境変数を Object、Boolean、Number に変換する場合にも利用されます。

例 1

job1 ジョブで実行した結果を JSON 文字列として出力し、 job2 ジョブでパースします。

name: build
on: push
jobs:
  job1:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - id: set-matrix
        run: echo "matrix={\"include\":[{\"project\":\"foo\",\"config\":\"Debug\"},{\"project\":\"bar\",\"config\":\"Release\"}]}" >> $GITHUB_OUTPUT
  job2:
    needs: job1
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{ fromJSON(needs.job1.outputs.matrix) }}
    steps:
      - run: build

例 2

このワークフローでは、環境変数の値を Boolean や Integer に変換するために fromJSON 関数を使用しています。

name: print
on: push
env:
  continue: true
  time: 3
jobs:
  job1:
    runs-on: ubuntu-latest
    steps:
      - continue-on-error: ${{ fromJSON(env.continue) }}
        timeout-minutes: ${{ fromJSON(env.time) }}
        run: echo ...

hashFiles

hashFiles(path)

指定したファイルパス ( ファイル OR ディレクトリ ) からハッシュ値を算出し返します。
pathはパターン形式で指定できます。
また、カンマ区切りで文字列を複数指定できます。
pathGITHUB_WORKSPACE 環境変数にセットされているディレクトリからの相対パスで指定します。
必然的に、 GITHUB_WORKSPACE 環境変数のディレクトリ以下のファイルだけしか指定できないこととなります。
ハッシュ値の計算には SHA-256 アルゴリズムが利用されます。

大文字小文字は「Windows 環境でのみ」区別されません

例 1

package-lock.json ファイルをすべて探し、ハッシュ値を算出します。

hashFiles('**/package-lock.json')

例 2

例 1 に加えて、 Gemfile.lock を加えたハッシュ値を算出します。

hashFiles(' **/package-lock.json', '** /Gemfile.lock')

ステータスチェック用の関数

ここからはステータスチェック用の関数について触れていきます。

多くの場合、 if フィールドの「式」中で利用されます。

if フィールドが指定されていなかったとしても、各ステップは if: ${{ success() }} が設定されたものとして動作します。
つまり、1 つ前のステップが成功しなかった場合には後続のステップはスキップされます。

success

1つ前のステップの実行結果が失敗やキャンセルされなかった場合、 true を返します。

steps:
  ...
  - name: The job has succeeded
    if: ${{ success() }}

always

前段ステップの実行結果に関わらず、常に実行されます。
後始末や終了ログを送信するために使用されます。

if: ${{ always() }}

cancelled

ワークフローがキャンセルされたときに true を返します。

if: ${{ cancelled() }}

failure

前段のステップの実行が失敗した場合に true を返します。

更に複雑な失敗のチェック

失敗したステップを明確に特定したい場合は以下のような if フィールドを記述します。

ここでは、失敗を特定したい事前ステップに demo という名前を付与し、 steps.demo.conclusion 変数をチェックしています。

steps:
  ...
  - name: Failing step
    id: demo
    run: exit 1
  - name: The demo step has failed
    if: ${{ failure() && steps.demo.conclusion == 'failure' }}

ひとこと

Github Actions で式を使うことで、より効率的な自動化が可能となります。
学習コストがかかるかもしれませんが、慣れてしまえば非常に使いやすく、大量の手作業を減らすことができます。
ぜひ、挑戦してみてください。