[golang]Github ActionsでGoのCIやってみた

# はじめに
前回 Go で 簡単な API を作りました。

https://qiita.com/Ixy_194/items/7f320a9550ea28679aff


今回は Github Actions を使って CI の実装をしてみたいと思います。

## Github Actions とは？
Github が提供するビルド、テスト、デプロイのパイプラインを自動化できる継続的インテグレーションと継続的デリバリー（CI/CD）のプラットフォームのこと。
リポジトリに対するすべての pull request をビルドしてテストしたり、マージされた pull request を運用環境にデプロイしたりするワークフローを作成できます。

詳細は以下を参照してください。

https://docs.github.com/ja/actions/learn-github-actions/understanding-github-actions

# 今回のゴール
Github Actions を使って簡単な CI のワークフローを作ります。

CIのフローは以下のようになります。
1. リポジトリへソースを PUSH or PR の作成
2. Github Actions 上で CI の実行
    - 単体テストの実行
    - linter の実行
    - 結果を Slack へ通知

イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974822/a778919d-845a-4761-0fb2-d7e603f46384.png)


# ワークフロー作成
ワークフローの作成には、
`.github/workflows`配下に YAML 形式のファイル（.yml, yaml）を作成しワークフローを定義していきます。

```zsh
.
├── .github                 # ディレクトリ作成
│   └── workflows
│       └── golang-ci.yml   #  作成するワークフローのファイル
```
## 単体テスト
まずは、単体テストを実行するためのワークフローを書いていきます。
Github Action 上で Go　の単体テスト（`go test -v`）を行うための構文は以下のようになります
```yml:.github/workflows/golang-ci.yml
# ワークフローの名前
name: Go CI

# トリガーになる Github イベント
#　今回は、リポジトリへのコミット（push）と PR 作成(pull_request)をトリガーにします
on:
  push:
  pull_request:

# ジョブ定義
jobs:
  # ジョブ名
  test:
    # ジョブ実行環境の指定
    runs-on: ubuntu-latest
    # ジョブ内で実行する step を定義
    steps:
      # リポジトリからソースコードの取得
      - uses: actions/checkout@v3
      # Go の実行環境 Set up
      - uses: actions/setup-go@v4
        with:
          go-version: ^1.20
      # テストの実行
      - name: Run Test
        run: go test -v ./...
```
これだけです。簡単ですね。
では、実際にこのコードをリポジトリに push して CI を走らせます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974822/c01f2efd-922a-a658-f9ea-4147b7bb4296.png)
↑こんな感じにリポジトリの Actions タブから ワークフローの実行結果を確認することができます。
無事、テストも成功しているようです。
実際の開発現場では、PR 作成前に 単体テストを実行して、問題なければ PR 作成、問題があれば再度修正をする、という流れになると思います。

では、特に問題なさそうなので、このまま PR を作成します。
実際に作成した PR はこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974822/d2c8d792-20ae-bc33-b5aa-cb1c616c3b74.png)
PR のページでは、`All check have passed`と表示され、テストが成功している事もわかります。

リポジトリの設定で、ワークフローが失敗している場合（今回であれば単体テストの実行）、マージをブロックすることもできるので、そこはよしなに運用にあわせて設定してみてください。

では、これで PR をマージして、単体テスト実行のワークフロー作成は完了です。


## linter
続いて、 linter を実行するためのワークフローを書いていきます。
linter には `golangci-lint`を使用します。
また、補助ツールとして reviewdog を使い、 linter 指摘箇所を PR 作成時にコメントさせてみます。
### そもそも linter とは？
いわゆる静的解析を行うツールです。
ソースコードの静的解析を行ってくれるもので、構文のチェックやバグの発見をしてくれたりします。
Java であれば CheckStyle とか FindBugs なんかが有名ですね。

### golangci-lint とは？
Go の静的解析ツールを統合して実行してくれるツールです。
Golang には色々な解析ツールがあります（gofmt, goimports, govet）。
golangci-lint はこれらの様々な解析ツールを統合して実行することができます。
これ一つあれば、複数のツールを組み合わせなくていいし、実行速度も早いという優れもの。

https://github.com/golangci/golangci-lint


### reviewdog　とは？
各種 linter の解析結果を PR にコメントする形で指摘してくれる補助ツールです。
linter を使って規約違反があることがわかっても実際の該当コードを探すのが大変だったりするのですが、 reviewdog を使うと、PR に直接コメント形式で linter 指摘を書いてくれるので該当コードを確認するのがとても楽になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974822/8b8a88fe-fd51-0edb-9e3f-145c7221296e.png)


https://github.com/reviewdog/reviewdog

### golangci-lint　の設定
では、まずは golangci-lint の設定ファイルを書いていきます。
golangci-lint は、設定で、`どの linter を有効にするのか`、`解析対象のファイルの指定`や`除外対象のファイルの指定`などを行うことができます。
詳細は以下を参考にしてみてください。

https://golangci-lint.run/usage/configuration/

今回はこんな感じに設定してみました。
```yaml
# linter の設定
linters:
  # 全ての linter を無効化
  disable-all: true
  # 指定した linter のみ有効化
  enable:
    - govet
    - errcheck
    - staticcheck
    - unused
    - gosimple
    - varcheck
    - misspell
    - lll
    - gofumpt 
```

設定ファイルの書き方は大きく `black list` 形式と `white list` 形式があります。
golangci-lint　にはデフォルトで有効になっている linter があり、何も指定しないとデフォルトで有効化されている linter が実行されます。利用する linter は明示的に指定したいので、今回は全ての linter を無効化してから、使いたい linter のみ enable で指定して有効化しています。（これが white list 形式）

### reviewdog の設定
続いてreviewdog　の設定です。
PR 作成時、 golangci-lint を実行し、指摘箇所があれば PR にコメントをさせるようワークフローを設定します。
以下のファイルを追加します。
```zsh
.
├── .github                 
│   └── workflows
        ├── golang-ci.yml
　　       └── golangci-lint-reviewdog.yml   #  追加したファイル
```
追加したファイルの内容
```yml:.github/workflows/golangci-lint-reviewdog.yml 
# ワークフローの名前
name: Golangci-lint ReviewDog 

on:
  pull_request:

# ジョブ定義
jobs:
  # lint
  golangci-lint:
    # GITHUB_TOKEN への権限設定
    permissions:
      checks: write
      contents: read
      pull-requests: write 
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: reviewdog/action-golangci-lint@v2
        with:
          github_token: ${{ secrets.github_token }}
          go_version: ^1.20
          # reviewdog の動作モードの指定
          reporter: github-pr-review
          # Github Status へのレポートレベルの指定
          level: warning
          golangci_lint_flags: "--config=.golangci.yml"

```
こちらは、 PR 作成時のみ動作するようにしています。
主な設定はポイントは以下です。
<dl>
  <dt>github_token</dt>
  <dd>reviewdog が PR にコメントできるようにトークンを指定</dd>
  <dt>reporter</dt>
  <dd>reviewdog の動作設定の指定</dd>
  <dd>ここでは、PR に直接コメントをさせたいので github-pr-review を指定します。</dd>
  <dt>level</dt>
  <dd>Github Checks に報告するレベルの指定</dd>
  <dd>info, worning, error の指定が可能</dd>
  <dd>今回は指摘があってもマージができるようにしたいので warning としています</dd></dl>


### 実際に動かしてみる
動作確認用に以下のファイルを追加しました。
これを使って実際に PR を作成します。（スペルミスで指摘されるはず）
```go:cmd/go-api-sample-todo/golangci_lint_reviredog_sample.go
package main
func typo() {
	mispell := "misspell"
	println(mispell)
}
```
すると、こんな感じにコメントが付きました。いいですね！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974822/5b2c98a1-3398-5e98-e885-bd08f5455f43.png)
コードレビューする時、このレベルのチェックに時間や労力を割きたくないので、自動化できるところは自動化して、人間は大事なところにのみ労力を集中したいものです。


linter の設定は以上です。

## Slack 連携
最後に Slack 連携です。
ここまでで、Github Actions を使った CI（単体テスト、静的解析） の構築をしました。
ここでは、、その CI の実行結果を Slack に連携できるようにしてみたいと思います。
ただし、実行結果を毎回全て通知されてもうざいだけなので、ここでは `CI（単体テスト） が失敗したときのみ Slack へ通知する`ようにしてみたいと思います。


いやいや、とにかくなんでも通知が欲しいんだーって方は、
この辺使ってできるので参考にしてみてくださいｍｍ

https://github.com/integrations/slack#configuration

### Slack　連携のやり方
以下の手順でやっていきます。
1. Slack の Incoming Webhook URL の発行
1. 発行した Incoming Webhook URL を リポジトリに環境変数として登録 
1. CI が失敗した時のみ Slack 通知できるようにワークフローの修正

#### 1. Slack の Incoming Webhook URL の発行
まずは Webhook の発行です。
Slack 公式サイトより [Webhook 作成ページ](https://slack.com/services/new/incoming-webhook)を開き、ログイン、通知先のチャンネルを指定して「Incoming Webhook インテグレーションの追加」をします。
※ ここでは、`#golang-api-sample-todo`を通知先として指定しています。（各自、これは自身の環境にあわせてください）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974822/c85e9f76-7d51-50d3-5f03-2f7a4b085138.png)

すると、こんな感じに Webhook の URL が発行されます。
これは後で使うのでしっかりメモっておいてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974822/c1d42c04-429e-2bcb-2ad5-6fac3fda8947.png)

#### 2. 発行した Incoming Webhook URL を リポジトリに環境変数として登録 
次に、発行した Webhook の URL をリポジトリに環境変数として登録します。
`Settings` タブから`Secres and variables` -> `Actions`を選択し、`New repository secret`から登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974822/3eea5802-a302-4f98-9458-ac1f02d8814f.png)

Name には参照するときの変数名、Secret には実際の値を入力します。
ここでは、 Name は `SLACK_WEBHOOK_URL`としておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974822/accc453f-bbc8-eff7-03ac-e5b358165668.png)

`Add secret`で追加。こんな感じに SLACK_WEBHOOK_URL が追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974822/e29eebea-7063-3c2b-68cc-323595e9f65f.png)


#### 3. CI が失敗した時に Slack 通知できるようにワークフローの修正
これで Slack　通知のための下準備ができたので、ワークフローを修正して失敗時のみ Slack 通知するようにします。
修正したコードはこちら。
```yaml:golang-ci.yml
# ワークフローの名前
name: Go CI

# トリガーになる Github イベント
#　今回は、リポジトリへのコミットと PR 作成をトリガーにします
on:
  push:
  pull_request:

# ジョブ定義
jobs:
  # ジョブ名
  unit-test:
    # ジョブの実行環境の指定
    runs-on: ubuntu-latest
    # ジョブ内で実行する step を定義
    steps:
      # リポジトリからソースコードの取得
      - uses: actions/checkout@v3
      # Go の実行環境の Set up
      - uses: actions/setup-go@v4
        with:
          go-version: ^1.20
      # テストの実行
      - name: Run Test
        run: go test -v ./...
      # 以下を追記
      # Slack 通知用の step
      - uses: rtCamp/action-slack-notify@v2.0.2
        # テストが失敗したときのみ通知する
        if: failure()
        env:
          SLACK_TITLE: Test Failure
          SLACK_COLOR: danger
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
```
※`${{ secrets.SLACK_WEBHOOK_URL }}`は、先程登録した環境変数を参照するための記述です。

では、テストが失敗するようにコードを修正して、実際に通知されるか試してみます。
結果、こんな感じにちゃんと通知がきました。よかった
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974822/1c3e3f1f-7f3d-5bc4-bdb0-13447212f26c.png)

Slack 連携は以上です。

# 最後に
簡単ですが、 Github Actions を使った CI の構築から Slack 連携までやってみました。
Github Actions、便利ですね (´・ω・｀)
今回作ったものは以下に公開してます。参考までに。

https://github.com/Ixy-194/go-api-sample-todo-ci

次回は AWS に環境作って、 API を デプロイしていくとこもつくってみようかなーと思います。
ではでは。