Open19

GitHub ActionsのTipsやベストプラクティスを淡々と記録する

tmknomtmknom

Bookmarks

Learn GitHub Actions

Using workflows

Using jobs

Managing workflow runs

Automating builds and tests

Deployment

Using containerized services

Publishing packages

Managing issues and pull requests

Monitoring and troubleshooting workflows

Using GitHub-hosted runners

Security guides

Creating actions

tmknomtmknom

Workflows

YAMLの書き方が分からなくなったら下記を参照する。

https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions

Trigger workflows

pull_request

on:
  pull_request:
    paths:
      - "**.yml"
      - "**.yaml"
      - "!.github/workflows/test.yml"

push

on:
  push:
    paths:
      - ".github/workflows/test.yml"

workflow_dispatch

on:
  workflow_dispatch:
    inputs:
      push:
        type: choice
        required: true
        description: "The flag to docker push."
        default: "false"
        options:
          - "true"
          - "false"

https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows

フィルタリング設定などは下記参照。

https://docs.github.com/en/actions/using-workflows/triggering-a-workflow

tmknomtmknom

Permissions

超重要なので熟読必須。ジョブ単位でも設定できるらしい。

https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs

全権限のはく奪

permissions: {}

公式ドキュメントにシレッと書いてあるが、意外と知られてない気がする。

まとめて設定

permissions: read-all|write-all

権限一覧

permissions:
  actions: read|write|none
  checks: read|write|none
  contents: read|write|none
  deployments: read|write|none
  id-token: read|write|none
  issues: read|write|none
  discussions: read|write|none
  packages: read|write|none
  pages: read|write|none
  pull-requests: read|write|none
  repository-projects: read|write|none
  security-events: read|write|none
  statuses: read|write|none

Reusable workflowでの挙動検証

  1. 呼び出し側:未指定
    1. Reusable workflow:{} => OK/Metadata: read
    2. Reusable workflow:contents: read => OK/Contents: read、Metadata: read
  2. 呼び出し側:contents: read
    1. Reusable workflow:{} => OK/Metadata: read
    2. Reusable workflow:未指定 => OK/Contents: read、Metadata: read
  3. 呼び出し側:{}
    1. Reusable workflow:未指定 => OK/Metadata: read
    2. Reusable workflow:contents: read => NG/contents: none

3.2のエラーメッセージ

Invalid workflow file: .github/workflows/lint.yml#L19
The workflow is not valid. .github/workflows/lint-yaml.yml (Line: 19, Col: 3):
Error calling workflow 'foo/bar/.github/workflows/lint.yml@v0'. The workflow
'foo/bar/.github/workflows/lint.yml@v0' is requesting 'contents: read',
but is only allowed 'contents: none'.

関連

tmknomtmknom

Jobs

9割この書き出し。

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout
        uses: actions/checkout@v3

https://docs.github.com/en/actions/using-jobs/using-jobs-in-a-workflow

timeout-minutes

超重要。デフォルトタイムアウトが6時間とかいうエグい設定なので、必ず明示する。無料枠を使い潰さないためのセーフティネット。

runs-on

ubuntu-latest 以外使ったことがないが、macOSやWindowsも動く模様。ただしmacOSやWindは高いので、本当に必要なとき以外は使わない。

https://docs.github.com/en/actions/using-jobs/choosing-the-runner-for-a-job

outputs

ジョブ間での値の受け渡しができる。ステップで echo "::set-output name=key::value" して、 outputs でステップの値を参照するという二段階が必要でややメンドウ。また参照時は needs とセットで使う。

jobs:
  job1:
    runs-on: ubuntu-latest
    # Map a step output to a job output
    outputs:
      output1: ${{ steps.step1.outputs.test }}
      output2: ${{ steps.step2.outputs.test }}
    steps:
      - id: step1
        run: echo "::set-output name=test::hello"
      - id: step2
        run: echo "::set-output name=test::world"
  job2:
    runs-on: ubuntu-latest
    needs: job1
    steps:
      - run: echo ${{needs.job1.outputs.output1}} ${{needs.job1.outputs.output2}}

if

ジョブの実行をするか条件定義可能。

name: example-workflow
on: [push]
jobs:
  production-deploy:
    if: github.repository == 'octo-org/octo-repo-prod'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

https://docs.github.com/en/actions/using-jobs/using-conditions-to-control-job-execution

matrix

マトリックスビルドがサポートされている。

runs-on: ${{ matrix.os }}
strategy:
  matrix:
    os: [macos-latest, windows-latest, ubuntu-18.04]
    node: [8, 10, 12, 14]
    include:
      # includes a new variable of npm with a value of 6
      # for the matrix leg matching the os and version
      - os: windows-latest
        node: 8
        npm: 6

https://docs.github.com/en/actions/using-jobs/using-a-build-matrix-for-your-jobs

Running jobs in a container

どうもコンテナ内でジョブを実行できるらしい。

jobs:
  my_job:
    container:
      image: node:14.16
      env:
        NODE_ENV: development
      ports:
        - 80
      volumes:
        - my_docker_volume:/volume_mount
      options: --cpus 1

https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container

横にDBなどもたてれる模様。

https://docs.github.com/en/actions/using-containerized-services/about-service-containers

tmknomtmknom

Steps

jobs の下に書く。GitHub Actionsの花形。

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Echo
        run: |
          echo "hello"

複数行対応アレコレ

run

run: | の部分は、YAMLに複数行書くヤツ。詳細は下記。

https://qiita.com/jerrywdlee/items/d5d31c10617ec7342d56

outputs

複数行のoutputsは頑張ってエスケープする。

    steps:
      - name: Run yamllint
        id: run
        run: |
          MESSAGE=$(yamllint --strict --format standard .) || EXIT_CODE="$?" && \
          MESSAGE="${MESSAGE//'%'/'%25'}" && \
          MESSAGE="${MESSAGE//$'\n'/'%0A'}" && \
          MESSAGE="${MESSAGE//$'\r'/'%0D'}" && \
          echo "::set-output name=message::${MESSAGE}" && \
          exit "${EXIT_CODE}"
      - name: Echo
        env:
          MESSAGE: ${{ steps.run.outputs.message }}
        run: echo "${MESSAGE}"

https://trstringer.com/github-actions-multiline-strings/

用途によっては↓だけでいいかもしれない。

MESSAGE="${MESSAGE//$'\n'/\\n}"

https://qiita.com/chanhama/items/415a0a26bbb186efc47a

if

if文も複数行に書ける。

https://zenn.dev/snowcait/articles/9bf729f0f823df

tmknomtmknom

Expressions

if文やenvなどで使える。書いたことないが、リテラルも記述できるらしい。

steps:
  - uses: actions/hello-world-javascript-action@v1.1
    if: ${{ <expression> }}

Operators

よくある条件式はこんな感じ。

if: ${{ github.event.pull_request.merged && github.head_ref == 'main' }}

booleanを判定しようとしたら、YAML的に文字列のtrue/falseとして扱われていて動かないことがある。その場合は文字列比較(foo == 'true'など)を使うこと。

Functions

あまり多くないが関数が使える。

  • contains( search, item ):if文でたまに使う
  • startsWith( searchString, searchValue ):if文でたまに使う
  • endsWith( searchString, searchValue ):if文でたまに使う
  • format( string, replaceValue0, replaceValue1, ..., replaceValueN)
  • join( array, optionalSeparator )
  • toJSON(value)
  • fromJSON(value):matrixビルドで使う/文字列からbooleanや数値への変換にも使えるらしい
  • hashFiles(path):キャッシュ関連で使う

Status check functions

ステップの実行に失敗した場合も継続したい、みたいなニーズに応える。

  • if: ${{ always() }}
  • if: ${{ success() }}
  • if: ${{ cancelled() }}
  • if: ${{ failure() }}

使い方はこんな感じ。

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

Object filters

ややトリッキーな機能。使ったことない。たとえば次のYAMLがあるとする。

[
  { "name": "apple", "quantity": 1 },
  { "name": "orange", "quantity": 2 },
  { "name": "pear", "quantity": 1 }
]

このとき fruits.*.name と書くと配列の [ "apple", "orange", "pear" ] が取れるらしい。

https://docs.github.com/en/actions/learn-github-actions/expressions

tmknomtmknom

Contexts

https://docs.github.com/en/actions/learn-github-actions/contexts

github

よく使うヤツ。

  • github.base_ref:マージ先となるターゲットブランチ
  • github.head_ref:マージ元となるソースブランチ
  • github.event:イベントのペイロード全体が取れる
  • github.event_name:イベント名だけ欲しい場合はこっち
  • github.ref:ブランチやタグが取れる refs/heads/<branch_name> refs/tags/<tag_name>
  • github.ref_name:ブランチやタグの名前だけが取れる
  • github.repositoryCodertocat/Hello-World形式のリポジトリ名
  • github.repository_owner:リポジトリ所有者
  • github. actor:ワークフロー実行者/dependabotとか取りたい場合に使う
  • github.run_id:ワークフローの識別子
  • github.sha:commit SHA
  • github.workflow:ワークフロー名
{
  "token": "***",
  "job": "dump_contexts_to_log",
  "ref": "refs/heads/my_branch",
  "sha": "c27d339ee6075c1f744c5d4b200f7901aad2c369",
  "repository": "octocat/hello-world",
  "repository_owner": "octocat",
  "repositoryUrl": "git://github.com/octocat/hello-world.git",
  "run_id": "1536140711",
  "run_number": "314",
  "retention_days": "90",
  "run_attempt": "1",
  "actor": "octocat",
  "workflow": "Context testing",
  "head_ref": "",
  "base_ref": "",
  "event_name": "push",
  "event": {
    ...
  },
  "server_url": "https://github.com",
  "api_url": "https://api.github.com",
  "graphql_url": "https://api.github.com/graphql",
  "ref_name": "my_branch",
  "ref_protected": false,
  "ref_type": "branch",
  "secret_source": "Actions",
  "workspace": "/home/runner/work/hello-world/hello-world",
  "action": "github_step",
  "event_path": "/home/runner/work/_temp/_github_workflow/event.json",
  "action_repository": "",
  "action_ref": "",
  "path": "/home/runner/work/_temp/_runner_file_commands/add_path_b037e7b5-1c88-48e2-bf78-eaaab5e02602",
  "env": "/home/runner/work/_temp/_runner_file_commands/set_env_b037e7b5-1c88-48e2-bf78-eaaab5e02602"
}

詳細はgithub contextを参照すること。

steps context

よく使う。

  • steps.<step_id>.outputs.<output_name>:ステップ間での値の受け渡しに使う
  • steps.<step_id>. outcome continue-on-error実行のステータス
  • steps.<step_id>.conclusioncontinue-on-error実行のステータス

env context

環境変数をシェル以外の場所で参照する場合に使う。たまに使えない場所がある。

secrets context

自分で登録したシークレットを参照できる。GITHUB_TOKEN はデフォルトで使える。ログ出力時に自動でマスクするようになっているが、完全ではないのでそもそもログに吐かないこと。

needs context

他のジョブの待ち合わせで使う。

inputs context

Reusable workflowsの入力パラメータ。

その他

使ったことないが他にもある。

  • job context
  • runner context
  • strategy context
  • matrix context
tmknomtmknom

Workflow commands

echoコマンドを通して、特殊な命令が発行できる。

https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions

Setting an output parameter

ステップ間の値の受け渡しなどで使う。

      - name: Set selected color
        run: echo '::set-output name=SELECTED_COLOR::green'
        id: random-color-generator
      - name: Get color
        run: echo "The selected color is ${{ steps.random-color-generator.outputs.SELECTED_COLOR }}"

デバッグログ

Secretsに ACTIONS_STEP_DEBUGtrue で保存すると、デバッグログが吐けるらしい。要検証。

echo "::debug::Set the Octocat variable"

デバッグログの詳細は下記。

https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging

ロギング

ログ出力とともに、アノテーションも作成してくれる。アノテーションを見落としがちという欠点がある。

echo "::notice file=app.js,line=1,col=5,endColumn=7::Missing semicolon"
echo "::warning file=app.js,line=1,col=5,endColumn=7::Missing semicolon"
echo "::error file=app.js,line=1,col=5,endColumn=7::Missing semicolon"

Grouping log lines

jobs:
  bash-example:
    runs-on: ubuntu-latest
    steps:
      - name: Group of log lines
        run: |
            echo "::group::My title"
            echo "Inside group"
            echo "::endgroup::"

Masking a value in log

ログ出力時にマスクしてくれる。

jobs:
  bash-example:
    runs-on: ubuntu-latest
    env:
      MY_NAME: "Mona The Octocat"
    steps:
      - name: bash-version
        run: echo "::add-mask::$MY_NAME"

Echoing command outputs

なぜかGitHub Actionsではデフォルトでechoが無効化されている。が、明示的に有効化可能。

jobs:
  workflow-command-job:
    runs-on: ubuntu-latest
    steps:
      - name: toggle workflow command echoing
        run: |
          echo '::set-output name=action_echo::disabled'
          echo '::echo::on'
          echo '::set-output name=action_echo::enabled'
          echo '::echo::off'
          echo '::set-output name=action_echo::disabled'

Setting an environment variable

echo "{environment_variable_name}={value}" >> $GITHUB_ENV で環境変数に値をセットできる。

steps:
  - name: Set the value
    id: step_one
    run: |
      echo "action_state=yellow" >> $GITHUB_ENV
  - name: Use the value
    id: step_two
    run: |
      echo "${{ env.action_state }}" # This will output 'yellow'

頑張ると複数行書けるらしい。これは読みづらい。

steps:
  - name: Set the value in bash
    id: step_one
    run: |
      echo 'JSON_RESPONSE<<EOF' >> $GITHUB_ENV
      curl https://example.lab >> $GITHUB_ENV
      echo 'EOF' >> $GITHUB_ENV

Adding a system path

PATHの追加コマンドが用意されている。

echo "$HOME/.local/bin" >> $GITHUB_PATH