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


## 追記

バージョンアップしたので、その記事も書きました。
https://qiita.com/grandcolline/items/04c168844dfca3a8ede7

以下は、v1の頃の古い記事になります。


---

golangの静的解析ツールをGitHub Actionsで動かして、PRに通知してみた。GitHubTokenとかあまり考えなくていいからその辺の管理が楽そうですごくいい印象を受けた。

ただ、まだGithubActionsが理解できていない部分が多く、不備は多々ありそう・・・

TerraformのGithubActionsを**かなり**参考にした。（ほぼgolangに書き換えただけ）
[hashicorp/terraform-github-actions](https://github.com/hashicorp/terraform-github-actions)
ほんと、ありがとうございます:bow:

## TL;DL

結論から言うと、こんな感じファイルを `.github/main.workflow` に置くだけ。

```hcl:.github/main.workflow
workflow "Golang Test Workflow" {
  on = "pull_request"
  resolves = [
    "go imports",
    "go vet",
    "staticcheck",
  ]
}

action "filter to pr open synced" {
  uses = "actions/bin/filter@master"
  args = "action 'opened|synchronize'"
}

action "go imports" {
  uses = "grandcolline/golang-github-actions/imports@v0.1.2"
  needs = "filter to pr open synced"
  secrets = ["GITHUB_TOKEN"]
}

action "go vet" {
  uses = "grandcolline/golang-github-actions/vet@v0.1.2"
  needs = "filter to pr open synced"
  secrets = ["GITHUB_TOKEN"]
  env = {
    FLAGS = "-shadow"
  }
}

action "staticcheck" {
  uses = "grandcolline/golang-github-actions/staticcheck@v0.1.2"
  needs = "filter to pr open synced"
  secrets = ["GITHUB_TOKEN"]
}
```

これで、こんな感じでPRに通知してくれる。

<img width="600" alt="imports.png" src="https://qiita-image-store.s3.amazonaws.com/0/77782/e3aaac54-c63c-ef9c-6a02-5801cb83d4ec.png">
<img width="600" alt="staticcheck.png" src="https://qiita-image-store.s3.amazonaws.com/0/77782/e10d5bc7-3b3c-fdd2-8bba-792b27b6437c.png">


## 少し詳細

上記のワークフローの中で、下記のレポジトリを呼び出して、そのレポジトリ内にあるDockerfileから、Dockerコンテナを起動し、テストを実行している。

[grandcolline/golang-github-actions](https://github.com/grandcolline/golang-github-actions)


なので実際は、上記のレポジトリの部分を実装していった。
（ただ、実装といっても、静的解析ツールを入れたDockerfileと、テスト実行のシェルスクリプトのentrypoint.shを用意するだけ・・・）

例えば、goimportsを動かすジョブは、

```Dockerfile:goimports/Dockerfile
FROM golang:latest

LABEL "com.github.actions.name"="go imports"
LABEL "com.github.actions.description"="Run goimports"
LABEL "com.github.actions.icon"="terminal"
LABEL "com.github.actions.color"="purple"

LABEL "repository"="https://github.com/grandcolline/golang-github-actions"
LABEL "homepage"="https://github.com/grandcolline/golang-github-actions"
LABEL "maintainer"="grandcolline <grandcolline@gmail.com>"

RUN apt-get update && \
	apt-get -y install jq && \
	go get -u golang.org/x/tools/cmd/goimports

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

`jq`と`goimports`を取得するDockerfileを用意する。
LABELなどの仕様は、GitHubActionsの仕様に従う。詳しくは、[ここ](https://developer.github.com/actions/creating-github-actions/creating-a-docker-container/#label)。

実行するシェルは、こんな感じ。

```shell:goimports/entrypoint.sh
#!/bin/sh
set -e
cd "${GO_ACTION_WORKING_DIR:-.}"

set +e
UNFMT_FILES=$(sh -c "goimports -l . $*" 2>&1)
test -z "${UNFMT_FILES}"
SUCCESS=$?
echo "${UNFMT_FILES}"
set -e

if [ ${SUCCESS} -eq 0 ]; then
    exit 0
fi

if [ "${GO_ACTION_COMMENT}" = "1" ] || [ "${GO_ACTION_COMMENT}" = "false" ]; then
    exit ${SUCCESS}
fi

FMT_OUTPUT=""
for file in ${UNFMT_FILES}; do
FILE_DIFF=$(goimports -d -e "${file}" | sed -n '/@@.*/,//{/@@.*/d;p}')
FMT_OUTPUT="${FMT_OUTPUT}
<details><summary><code>${file}</code></summary>

\`\`\`diff
${FILE_DIFF}
\`\`\`
</details>

"
done

COMMENT="## goimports Failed
${FMT_OUTPUT}
"
PAYLOAD=$(echo '{}' | jq --arg body "${COMMENT}" '.body = $body')
COMMENTS_URL=$(cat /github/workflow/event.json | 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

exit ${SUCCESS}
```

自動でカレントディレクトリ（`/github/workspace`）にソースコードがcheckoutさてれいるので、`goimports -l .` でフォーマットがおかしいファイル一覧を取得し、ファイル毎にdiffを`${FMT_OUTPUT}`に整形しながら入れてあげて、最後にcurlでPRに通知する。
`${GITHUB_TOKEN}`などは、workflow側から入れてあげれば、自然な形で環境変数として使える。

環境変数などは[ここ](https://developer.github.com/actions/creating-github-actions/accessing-the-runtime-environment/)をみながら。