以前、checkovをreviewdogに対応させた話でterraform
のコードリポジトリで静的解析を実行する仕組みを作りましたが、それをGithub Actions
として世に公開していこうと思います。
Github Actionsの種類
Github Actions
にはPrivate Action
とPublic Action
の2種類があります。
Private Action
はCIを実行するリポジトリのなかに直接埋め込まれているタイプで、以下のようなディレクトリ構成になっています。
├ .github/
├ actions/
└ action.yml
└ Dockerfile
└ entrypoint.sh
├ workflows/
└ main.yml
... 以下略
Private Action
は./github/workflows/main.yaml
で以下のように相対パスで呼び出すことができます。
- uses: ./.github/actions/tflint-reviewdog
with:
github_token: ${{ secrets.github_token }}
reporter: github-pr-check
fail_on_error: "true"
filter_mode: "nofilter"
一方でPublic Action
は専用のリポジトリとして作成され、別のリポジトリから呼び出すことができます。
Public Action
は一度作ってしまえば複数のリポジトリで使いまわしが出来きるという点で便利です。今回はPublic Action
の作り方を説明していきます。
ディレクトリ構成
今回作成するPublic Action
のディレクトリ構成は以下の通りです。
├ .github/
├ workflows/
└ test.yml
├ testdata/
└ example.tf
└ action.yml
└ Dockerfile
└ entrypoint.sh
└ parse.py
└ requirements.txt
action.yml
Github Action
を作るえうで必ず必要になるファイルです。とは言え設定はとても簡単です。
設定項目は主に引数と動作環境の指定です。
以下の内容ですと、引数としてリポジトリを操作するために必要なgithub_token
に加えて、checkov
とreviewdog
というツールのオプション値を引数として受け取れるように設定しています。
また、using: 'docker'
でコンテナとして動作するように指定しています(コンテナ以外にもJavascript
などで動作させることもできます)。
name: 'Checkov with Reviewdog GitHub Action'
author: 'Ishii1648'
description: 'Run Checkov with Reviewdog against Terraform/CloudFormation infrastructure code, as a pre-packaged GitHub Action.'
inputs:
github_token:
description: 'GITHUB_TOKEN'
required: true
reporter:
description: |
Reporter of reviewdog command [github-pr-check,github-pr-review].
Default is github-pr-check.
default: 'github-pr-check'
filter_mode:
description: |
Filtering for the reviewdog command [added,diff_context,file,nofilter].
Default is added.
default: 'added'
fail_on_error:
description: |
Exit code for reviewdog when errors are found [true,false]
Default is `false`.
default: 'false'
working_directory:
description: |
Directory to run the action on, from the repo root.
Default is . ( root of the repository)
default: '.'
skip_check:
description: 'Run scan on all checks but a specific check identifier (comma separated)'
required: false
runs:
using: 'docker'
image: 'Dockerfile'
env:
INPUT_GITHUB_TOKEN: ${{ inputs.github_token }}
INPUT_REPORTER: ${{ inputs.reporter }}
INPUT_FILTER_MODE: ${{ inputs.filter_mode }}
INPUT_FAIL_ON_ERROR: ${{ inputs.fail_on_error }}
INPUT_WORKING_DIRECTORY: ${{ inputs.working_directory }}
INPUT_SKIP_CHECK: ${{ inputs.skip_check }}
Dockerfile
Dockerfileではreviewdog
とcheckov
をインストールしています。加えてcheckov
の出力結果をreviewdog
で受取可能な形式に変換するparse.py
という自作スクリプトも入れています。
FROM python:slim
ARG REVIEWDOG_VERSION="v0.11.0"
ENV PATH $PATH:/usr/local/bin
RUN apt-get update && \
apt install -y git curl
RUN curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh | sh -s -- -b "/usr/local/bin/" "${REVIEWDOG_VERSION}" 2>&1
COPY requirements.txt /requirements.txt
COPY parse.py /parse.py
COPY entrypoint.sh /entrypoint.sh
RUN pip install -r /requirements.txt
ENTRYPOINT ["/entrypoint.sh"]
entrypoint.sh
コンテナ起動時に実行されるスクリプトです。特徴としてはパイプで連結している3つのスクリプト(checkov, parse.py, reviewdog)の返り値をそれぞれ変数に格納している点です。
何故わざわざこのような事をしているのかと言うと、後述するテストで各スクリプトの返り値を元にテスト結果の成否を判定するためです。
# !/bin/bash
[[ ! -z "$INPUT_SKIP_CHECK" ]] && SKIP_CHECK_FLAG="--skip-check $INPUT_SKIP_CHECK"
export REVIEWDOG_GITHUB_API_TOKEN="${INPUT_GITHUB_TOKEN}"
checkov -d $INPUT_WORKING_DIRECTORY $SKIP_CHECK_FLAG -o json \
| python3 /parse.py \
| reviewdog -efm="%f:%l: %m" -name="checkov" -reporter="${INPUT_REPORTER}" -fail-on-error="${INPUT_FAIL_ON_ERROR}" -filter-mode="${INPUT_FILTER_MODE}"
checkov_return="${PIPESTATUS[0]}" reviewdog_return="${PIPESTATUS[2]}" exit_code=$?
echo ::set-output name=checkov-return-code::"${checkov_return}"
echo ::set-output name=reviewdog-return-code::"${reviewdog_return}"
exit $exit_code
.github/workflows/test.yml
テストのためにPR作成時とmainブランチへのマージ時にアクションが実行されるよう設定しています。
ここではreviewdog
の3つの出力パターンをそれぞれ実行するよう設定しており、name: Check return codes
で成否を判定しています。
name: Test
on:
push:
branches:
- main
pull_request:
jobs:
test-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./
id: test
continue-on-error: true
with:
github_token: ${{ secrets.github_token }}
reporter: github-pr-check
working_directory: testdata
# The check is expected to fail on the test data
- name: Check return codes
if: success() || failure ()
run: |
checkov_return="${{ steps.test.outputs.checkov-return-code }}"
reviewdog_return="${{ steps.test.outputs.reviewdog-return-code }}"
if [ "$checkov_return" -eq 1 ]; then
echo "checkov correctly returned failure ${checkov_return}"
else
echo "checkov returned ${checkov_return}, expected '1'. Failing..."
exit 1
fi
if [ "$reviewdog_return" -eq 0 ]; then
echo "reviewdog correctly returned success: ${reviewdog_return}"
else
echo "reviewdog returned ${reviewdog_return}, expected '0'. Failing..."
exit 1
fi
test-pr-check:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./
continue-on-error: true
id: test
with:
github_token: ${{ secrets.github_token }}
reporter: github-pr-check
working_directory: testdata
# The check is expected to fail on the test data
- name: Check return codes
if: success() || failure ()
run: |
checkov_return="${{ steps.test.outputs.checkov-return-code }}"
reviewdog_return="${{ steps.test.outputs.reviewdog-return-code }}"
if [ "$checkov_return" -eq 1 ]; then
echo "checkov correctly returned failure ${checkov_return}"
else
echo "checkov returned ${checkov_return}, expected '1'. Failing..."
exit 1
fi
if [ "$reviewdog_return" -eq 0 ]; then
echo "reviewdog correctly returned success: ${reviewdog_return}"
else
echo "reviewdog returned ${reviewdog_return}, expected '0'. Failing..."
exit 1
fi
test-pr-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./
continue-on-error: true
id: test
with:
github_token: ${{ secrets.github_token }}
reporter: github-pr-review
working_directory: testdata
# The check is expected to fail on the test data
- name: Check return codes
if: success() || failure ()
run: |
checkov_return="${{ steps.test.outputs.checkov-return-code }}"
reviewdog_return="${{ steps.test.outputs.reviewdog-return-code }}"
if [ "$checkov_return" -eq 1 ]; then
echo "checkov correctly returned failure ${checkov_return}"
else
echo "checkov returned ${checkov_return}, expected '1'. Failing..."
exit 1
fi
if [ "$reviewdog_return" -eq 0 ]; then
echo "reviewdog correctly returned success: ${reviewdog_return}"
else
echo "reviewdog returned ${reviewdog_return}, expected '0'. Failing..."
exit 1
fi
以上がGithub Actionsの作り方(Docker編)
になります。
Github Actions
はとても簡単に作成することができ、かつCIとして必要な機能も充分に備えているのでこれからどんどん使っていこうと思います。