goの静的解析ツールをGithub Actionsのv2で動かしてみた

github actionsのバージョンが上がったので、自分が作ったアクションも書き換えてみました。

## 作ったもの

https://github.com/grandcolline/golang-github-actions

⬇こんな感じで通知される⬇
<img width="531" alt="スクリーンショット.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77782/e221ba1b-b551-64f8-0b18-0e4d69ea06f0.png">

対応している静的解析ツールは以下のものです。

| もの | original |
| :-------: | :-- |
| `fmt`     | https://golang.org/cmd/gofmt/ |
| `vet`     | https://golang.org/cmd/vet/ |
| `imports` | https://godoc.org/golang.org/x/tools/cmd/goimports |
| `lint`    | https://godoc.org/golang.org/x/lint/golint |
| `shadow`  | https://godoc.org/golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow |
| `staticcheck` | https://godoc.org/honnef.co/go/tools/cmd/staticcheck |
| `errcheck` | https://godoc.org/github.com/kisielk/errcheck |
| `sec`      | https://godoc.org/github.com/securego/gosec/cmd/gosec |


READMEに書いてある通りなのですが、下記の感じでymlファイルを作ってもらえれば動きます。

```yaml:.github/workflows/static.yml
name: static check
on: pull_request

jobs:
  fmt:
    name: Fmt
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - uses: grandcolline/golang-github-actions@v1.0.0
      with:
        run: fmt
        token: ${{ secrets.GITHUB_TOKEN }}

  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - uses: grandcolline/golang-github-actions@v1.0.0
      with:
        run: lint
        token: ${{ secrets.GITHUB_TOKEN }}

```

withで指定できる変数は以下の通りです。

| 変数名          | デフォルト | なに |
| :------------: | :------: | :-- |
| `run`          | N/A      | 実行するコマンド(上の表から選ぶ) |
| `directory`    | `.`      | 静的解析を実行する場所の相対パス |
| `comment`      | `true`   | PRにコメントするか否か |
| `token`        | `""`     | GitHub Token |
| `flags`        | `""`     | コマンド実行時に追加するフラグ |
| `ignore-defer` | `false`  | errcheck実行時に deferのエラーチェックを無視するか否か|


## いか、アクション作成する際にやったこと

アクションには、2種類のパターンがあるそうです。

* [type script actions](https://github.com/actions/typescript-action)
* [container actions](https://github.com/actions/container-template)

今回はgoを使いたかったので、container actionsにしました。
[公式のテンプレート](https://github.com/actions/container-template)通りに以下の構成にしました。

```go
.
├── Dockerfile
├── LICENSE
├── README.md
├── action.yml
└── entrypoint.sh
```

Dockerfileは使いたいツールをインストールして、最終的に`entrypoint.sh`を実行するだけのシンプルなもの。
やりたい処理などは、`entrypoint.sh`にゴリゴリ書いていく。

```Dockerfile:Dockerfile
FROM golang::1.12.9

ENV GO111MODULE=on

RUN apt-get update && \
	apt-get -y install jq && \
	go get -u \
		github.com/kisielk/errcheck \
		golang.org/x/tools/cmd/goimports \
		golang.org/x/lint/golint \
		github.com/securego/gosec/cmd/gosec \
		golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow \
		honnef.co/go/tools/cmd/staticcheck

COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
```

action.yamlは、[公式のドキュメント](https://help.github.com/en/articles/metadata-syntax-for-github-actions)を参考に作成。


```yaml:action.yml
name: "static analysis checker"
description: "static code analysis checker for golang"
author: "grandcolline"
inputs:
  run:
    description: "excute command. [errcheck/fmt/imports/lint/sec/shadow/staticcheck/vet]"
    required: true
  directory:
    description: "action wroking directory."
    default: "."
    required: false
  comment:
    description: "send comment to PR if true."
    default: true
    required: false
  token:
    description: "github token. this is require when comment is true."
    default: ""
    required: false
  flags:
    description: "add flags"
    default: ""
    required: false
  ignore-defer:
    description: "if this is true, do not detect 'defer' with error. This is only valid when run is errcheck."
    default: false
    required: false
runs:
  using: "docker"
  image: "Dockerfile"
  args:
    - ${{ inputs.run }}
    - ${{ inputs.directory }}
    - ${{ inputs.comment }}
    - ${{ inputs.token }}
    - ${{ inputs.flags }}
    - ${{ inputs.ignore-defer }}
branding:
  icon: "alert-triangle"
  color: "yellow"
```

`args`以外にも`environment`でも渡せるので、どちらにするかは迷いどころだと思いました。どのようにかくのが綺麗なんだろう🤔
あと、Dockerのビルド時に渡すARGもここから指定できれば、golang自体のバージョン管理もアクション側でしなくて良くなりそうで良さそうだなぁと感じました。（ドキュメントを読み込めてないので、もしかしたらできるのかもしれない・・・）

最後に、`entrypoint.sh`はシェルを書く。
長いので一部だけ書くとこんな感じ。全体は[こちら](https://github.com/grandcolline/golang-github-actions/blob/master/entrypoint.sh)から。

```shell:entrypoint.sh
#!/bin/sh
set -e

# ------------------------
#  Environments
# ------------------------
RUN=$1
WORKING_DIR=$2
SEND_COMMNET=$3
GITHUB_TOKEN=$4
FLAGS=$5
IGNORE_DEFER_ERR=$6

COMMENT=""
SUCCESS=0


# ------------------------
#  Functions
# ------------------------
send_comment() {
	PAYLOAD=$(echo '{}' | jq --arg body "${COMMENT}" '.body = $body')
	COMMENTS_URL=$(cat ${GITHUB_EVENT_PATH} | jq -r .pull_request.comments_url)
	curl -s -S -H "Authorization: token ${GITHUB_TOKEN}" --header "Content-Type: application/json" --data "${PAYLOAD}" "${COMMENTS_URL}" > /dev/null
}

mod_download() {
	if [ ! -e go.mod ]; then go mod init; fi
	go mod download
	if [ $? -ne 0 ]; then exit 1; fi
}

check_errcheck() {
	if [ "${IGNORE_DEFER_ERR}" = "true" ]; then
		IGNORE_COMMAND="| grep -v defer"
	fi

	set +e
	OUTPUT=$(sh -c "errcheck ${FLAGS} ./... ${IGNORE_COMMAND} $*" 2>&1)
	test -z "${OUTPUT}"
	SUCCESS=$?

	set -e
	if [ ${SUCCESS} -eq 0 ]; then
		return
	fi

	if [ "${SEND_COMMNET}" = "true" ]; then
		COMMENT="## ⚠ errcheck Failed
\`\`\`
${OUTPUT}
\`\`\`
"
	fi
}

# ------------------------
#  Main Flow
# ------------------------
cd ${GITHUB_WORKSPACE}/${WORKING_DIR}

case ${RUN} in
	"errcheck" )
		mod_download
		check_errcheck
		;;
	* )
		echo "Invalid command"
		exit 1

esac

if [ ${SUCCESS} -ne 0 ]; then
	echo "Check Failed!!"
	echo ${COMMENT}
	if [ "${SEND_COMMNET}" = "true" ]; then
		send_comment
	fi
fi

exit ${SUCCESS}
```


## 最後に

アクションの書き方などのページがなくて辛かったですが、このあたりの記事がとても参考になりました。
感謝:pray:

https://qiita.com/homines22/items/0bc6c17e038b35fc8113
https://github.com/actions/container-template