« ^ »

Github ActionsのYAMLをactionlintで静的解析する

所要時間: 約 3分

Github ActionsのYAMLを静的解析するactionlintというツールがある1。今回はそれを使ってみる。

READMEに従い go install でインストールする。

go install github.com/rhysd/actionlint/cmd/actionlint@latest

$HOME/go/bin 配下に actionlint というコマンドが生成される。 $HOME/go/bin にはパスを通しておく。

export PATH=$HOME/go/bin:$PATH
Usage: actionlint [FLAGS] [FILES...] [-]

  actionlint is a linter for GitHub Actions workflow files.

  To check all YAML files in current repository, just run actionlint without
  arguments. It automatically finds the nearest '.github/workflows' directory:

    $ actionlint

  To check specific files, pass the file paths as arguments:

    $ actionlint file1.yaml file2.yaml

  To check content which is not saved in file yet (e.g. output from some
  command), pass - argument. It reads stdin and checks it as workflow file:

    $ actionlint -

  To serialize errors into JSON, use -format option. It allows to format error
  messages flexibly with Go template syntax.

    $ actionlint -format '{{json .}}'

Documents:

  https://github.com/rhysd/actionlint/tree/main/docs

Flags:
  -color
        Always enable colorful output. This is useful to force colorful outputs
  -config-file string
        File path to config file
  -debug
        Enable debug output (for development)
  -format string
        Custom template to format error messages in Go template syntax. See https://github.com/rhysd/actionlint/tree/main/docs/usage.md#format  -ignore value
        Regular expression matching to error messages you want to ignore. This flag is repeatable
  -init-config
        Generate default config file at .github/actionlint.yaml in current project
  -no-color
        Disable colorful output
  -oneline
        Use one line per one error. Useful for reading error messages from programs
  -pyflakes string
        Command name or file path of "pyflakes" external command. If empty, pyflakes integration will be disabled (default "pyflakes")
  -shellcheck string
        Command name or file path of "shellcheck" external command. If empty, shellcheck integration will be disabled (default "shellcheck")
  -stdin-filename string
        File name when reading input from stdin
  -verbose
        Enable verbose output
  -version
        Show version and how this binary was installed

READMEに掲載されている壊れたYAMLを使いコマンドを実行し、どのような出力となるのか確認する。

actionlint broken.yml
broken.yml:3:5: unexpected key "branch" for "push" section. expected one of "branches", "branches-ignore", "paths", "paths-ignore", "tags", "tags-ignore", "types", "workflows" [syntax-check]
  |
3 |     branch: main
  |     ^~~~~~~
broken.yml:5:11: character '\' is invalid for branch and tag names. only special characters [, ?, +, *, \ ! can be escaped with \. see `man git-check-ref-format` for more details. note that regular expression is unavailable. note: filter pattern syntax is explained at https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet [glob]
  |
5 |       - 'v\d+'
  |           ^~~~
broken.yml:10:28: label "linux-latest" is unknown. available labels are "windows-latest", "windows-2022", "windows-2019", "windows-2016", "ubuntu-latest", "ubuntu-22.04", "ubuntu-20.04", "ubuntu-18.04", "macos-latest", "macos-latest-xl", "macos-13-xl", "macos-13", "macos-13.0", "macos-12-xl", "macos-12", "macos-12.0", "macos-11", "macos-11.0", "macos-10.15", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file [runner-label]
   |
10 |         os: [macos-latest, linux-latest]
   |                            ^~~~~~~~~~~~~
broken.yml:13:41: "github.event.head_commit.message" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions for more details [expression]
   |
13 |       - run: echo "Checking commit '${{ github.event.head_commit.message }}'"
   |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
broken.yml:17:11: input "node_version" is not defined in action "actions/setup-node@v3". available inputs are "always-auth", "architecture", "cache", "cache-dependency-path", "check-latest", "node-version", "node-version-file", "registry-url", "scope", "token" [action]
   |
17 |           node_version: 16.x
   |           ^~~~~~~~~~~~~
broken.yml:21:20: property "platform" is not defined in object type {os: string} [expression]
   |
21 |           key: ${{ matrix.platform }}-node-${{ hashFiles('**/package-lock.json') }}
   |                    ^~~~~~~~~~~~~~~
broken.yml:22:17: receiver of object dereference "permissions" must be type of object but got "string" [expression]
   |
22 |         if: ${{ github.repository.permissions.admin == true }}
   |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

-no-color-oneline を使う事でプログラム的に扱い易くなりそうだ。

actionlint -no-color -oneline broken.yml
broken.yml:3:5: unexpected key "branch" for "push" section. expected one of "branches", "branches-ignore", "paths", "paths-ignore", "tags", "tags-ignore", "types", "workflows" [syntax-check]
broken.yml:5:11: character '\' is invalid for branch and tag names. only special characters [, ?, +, *, \ ! can be escaped with \. see `man git-check-ref-format` for more details. note that regular expression is unavailable. note: filter pattern syntax is explained at https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet [glob]
broken.yml:10:28: label "linux-latest" is unknown. available labels are "windows-latest", "windows-2022", "windows-2019", "windows-2016", "ubuntu-latest", "ubuntu-22.04", "ubuntu-20.04", "ubuntu-18.04", "macos-latest", "macos-latest-xl", "macos-13-xl", "macos-13", "macos-13.0", "macos-12-xl", "macos-12", "macos-12.0", "macos-11", "macos-11.0", "macos-10.15", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file [runner-label]
broken.yml:13:41: "github.event.head_commit.message" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions for more details [expression]
broken.yml:17:11: input "node_version" is not defined in action "actions/setup-node@v3". available inputs are "always-auth", "architecture", "cache", "cache-dependency-path", "check-latest", "node-version", "node-version-file", "registry-url", "scope", "token" [action]
broken.yml:21:20: property "platform" is not defined in object type {os: string} [expression]
broken.yml:22:17: receiver of object dereference "permissions" must be type of object but got "string" [expression]

これなら自分でflymakeも書けそうだが、flymake用の拡張は既に実装された flymake-actionlint というものがあるから、まずはそれを使う事にしよう2

M-x package-install RET flymake-actionlint RET

.github/workflows 配下のYAMLファイルを開き、 flymake-actionlint-load を実行すると、以下のようにエラー箇所が赤く表示される。

https://res.cloudinary.com/symdon/image/upload/v1702610524/blog.symdon.info/1702606486/error.png

良さげだ。