GolangのCIをGitHub Actionsで構築してみる！

# はじめに
こんにちは、H×Hのセンリツ大好きエンジニアです。

前回：[TypeScript(Next.js)のCIをGitHub Actionsで構築してみる！](https://qiita.com/Senritsu420/items/f51a7eff81a8c1372302)

今回は、Go言語のCIをGitHub Actionsで構築してみた！という内容です。
初めて作成したのでおかしな所もあると思いますが、あまり責めないでください。。。（泣きます）
# GitHub Actionsとは？
こちらの記事が非常に分かりやすいので、詳しく知りたい方は参考にしてみて下さい！（他力本願寺）
[【初心者向け】【入門】GitHub Actionsの書き方についてデバッグ設定、runs-onやcheckoutなどの仕組みや構造も含めて徹底解説](https://qiita.com/shun198/items/14cdba2d8e58ab96cf95)

簡単に言うと、GitHub上でCI/CDが構築できるよ！と言うものです。
（今までは、CircleCIなどの外部サービスと連携させる必要がありましたが、必要なくなります。一つのサービスで完結できるのって素晴らしいですね！）

# CIの内容
GoのCIでは、主に3点を行います。
1. goのテスト
1. golangci-lintを用いた静的解析
1. Dockleを用いた脆弱性検査

Dockleを用いた脆弱性検査に関しては前回と同じです。

# 実装
Github Actionsはリポジトリ直下の`.github/workflows`にあるymlファイルに記述されたワークフローを元に実行されます。
公式リファレンス：　[GitHub Actions](https://github.co.jp/features/actions)

なので、まずはリポジトリ直下に`.github/workflows`ディレクトリを作成し、その中に今回使用するymlファイルを作成します。（名前はなんでも良いので、`backend_ci.yml`とかにしておきます。）

構成図は以下になります。
```
.
├── .github
│   └── workflows
│       └── backend_ci.yml <- コレ！
├── .octocov.yml
├── backend
│   ├── .air.toml
│   ├── .golangci.yml
│   ├── Dockerfile
│   ├── go.mod
│   └── main.go
└── compose.yml
.
.
.
```
「自分はこのリポジトリではバックエンドだけ開発するからリポジトリ直下に置くよ！」という方はこの後のymlファイルの作業ディレクトリを`.`に変更して下さい。

気になるymlファイルの中身ですが、以下のようにしました！
``` backend_ci.yml
# ワークフローの名前
name: backend_ci

# トリガーになる Github イベント
on:
  pull_request:
    branches:
      - develop
    # ルート直下の場合は不要
    paths:
      - "backend/**"
  # 無くても良い（手動実行したい人だけ残す）
  workflow_dispatch:

# 環境変数を設定
env:
  DOCKER_IMAGE_TAG: "backend"

# デフォルトの作業ディレクトリ変更
# ルート直下の場合は不要
defaults:
  run:
    working-directory: backend

# ジョブ定義
jobs:
  # ジョブ名
  backend_test:
    runs-on: ubuntu-latest
    steps:
      # リポジトリからソースコードの取得
      - name: Checkout Repository
        uses: actions/checkout@v4

        # Go の実行環境 Setup
      - name: Go Setup
        uses: actions/setup-go@v4
        with:
          go-version: 1.21

      # テストの実行
      - name: Run Test
        run: go test -v ./... -coverprofile=coverage.out

      # 実行結果を出力
      - name: Run Octocov
        uses: k1LoW/octocov-action@v0

  backend_lint:
    runs-on: ubuntu-latest
    # GITHUB_TOKEN への権限設定
    permissions:
      checks: write
      contents: read
      pull-requests: write
    steps:
      # リポジトリからソースコードの取得
      - name: Checkout Repository
        uses: actions/checkout@v4

      # Reveiwdogでgolangci-lintを実行
      - name: Run golangci-lint with Reviewdog
        uses: reviewdog/action-golangci-lint@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          go_version: 1.21
          # reviewdog の動作モードの指定
          reporter: github-pr-review
          # Github Status へのレポートレベルの指定
          level: warning
          workdir: backend/
          golangci_lint_flags: "--config=.golangci.yml"

  backend_build_check:
    runs-on: ubuntu-latest
    steps:
      # リポジトリからソースコードの取得
      - name: Checkout Repository
        uses: actions/checkout@v3

      # Dockerのコンテナイメージをビルド
      - name: Docker Build
        run: |
          docker build -f Dockerfile -t ${{ env.DOCKER_IMAGE_TAG }} .

      # Dockleで脆弱性検査を行う
      - name: Run Dockle
        uses: erzz/dockle-action@v1
        with:
          image: ${{ env.DOCKER_IMAGE_TAG }}
          exit-code: 1
          failure-threshold: fatal
```
行ごとに必要な箇所はコメントしているので、所々割愛しながら説明していきます。
``` backend_ci.yml
# トリガーになる Github イベント
on:
  pull_request:
    branches:
      - develop
    # ルート直下の場合は不要
    paths:
      - "backend/**"
  # 無くても良い（手動実行したい人だけ残す）
  workflow_dispatch:
```
ここでは、GitHub Actionsを呼び出すトリガーを設定します。
今回は、以下のように設定しました。
- developブランチにPull Requestが作成された時に発火
    - pathsでbackendディレクトリの中身が編集されていれば実行するように変更
- 手動実行もしたいので`workflow_dispatch`をトリガーに追加

``` backend.yml
# 環境変数を設定
env:
  DOCKER_IMAGE_TAG: "frontend"
```
今回は、Dockerを用いてフロントエンドとバックエンドを管理しており、脆弱性検査の時にDockerfileのイメージを指定するので、ここで環境変数に設定しています。
## goのテスト
``` backend.yml
# ジョブ定義
jobs:
  # ジョブ名
  backend_test:
    runs-on: ubuntu-latest
    steps:
      # リポジトリからソースコードの取得
      - name: Checkout Repository
        uses: actions/checkout@v4

        # Go の実行環境 Setup
      - name: Go Setup
        uses: actions/setup-go@v4
        with:
          go-version: 1.21
```
`jobs`では、実行するジョブを定義します。
実行する内容を記述する前にジョブの実行を行うOSの指定をしています。

ジョブの内容ですが、まずはリポジトリからソースコードを取得します。
次に、Goの環境を準備していきます。バージョンは1.21にしてますが、ここは自由に変更して下さい！

``` backend.yml
      # テストの実行
      - name: Run Test
        run: go test -v ./... -coverprofile=coverage.out

      # 実行結果を出力
      - name: Run Octocov
        uses: k1LoW/octocov-action@v0
```
Run Testでテストを実行するとともに、カバレッジを計算します。
Octocovは、テストカバレッジ値やテストコード実行時間を管理できるツールです。
このツールを用いて、カバレッジを出力してもらいます。

また、ルート直下に`octocov.yml`ファイルを作成します。
``` octocov.yml
coverage:
  paths:
    - coverage.out
codeToTestRatio:
  code:
    - "**/*.go"
    - "!**/*_test.go"
  test:
    - "**/*_test.go"
testExecutionTime:
  if: true
comment:
  if: is_pull_request
```
これで、テストカバレッジ値をPull Requestのコメントとして投稿してくれるようになります！

## goの静的解析
``` backend_ci.yml
  backend_lint:
    runs-on: ubuntu-latest
    # GITHUB_TOKEN への権限設定
    permissions:
      checks: write
      contents: read
      pull-requests: write
```
同じようにジョブの実行を行うOSの指定をしています。
また、GitHub Actionsの権限設定ですが、golangci-lintで指摘項目があった際にPull Requestのコメントに内容を送信してもらうためです。（直す箇所が特定できるのでめためた便利です!）

``` backend_ci.yml
    steps:
      # リポジトリからソースコードの取得
      - name: Checkout Repository
        uses: actions/checkout@v4

      # Reveiwdogでgolangci-lintを実行
      - name: Run golangci-lint with Reviewdog
        uses: reviewdog/action-golangci-lint@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          go_version: 1.21
          # reviewdog の動作モードの指定
          reporter: github-pr-review
          # Github Status へのレポートレベルの指定
          level: warning
          workdir: backend/
          golangci_lint_flags: "--config=.golangci.yml"
```
ジョブの中身では、以下を実行します。
- リポジトリからソースコードを取得
- golangci-lintを実行し、結果をreviewdogでレビューコメント投稿

reviewdogという便利ツールを使います。
このツールは、各種linterの検出結果をレビューコメントとして自動的に投稿してくれるツールです。(とても便利です！)
公式リファレンス：[reviewdog/reviewdog](https://github.com/reviewdog/reviewdog)

今回はこのreviewdogのgolangci-lintバージョンで実行します。
オプションでは、以下のことを行なっています。
- `github_token`でreviewdogに権限を付与
- goのバージョン指定
- `reporter`で投稿方法を設定
    - Pull Requestのレビューコメントに設定
- `level`で警告をどのレベルで指摘するか設定
    - warningに指定
- `workdir`で作業ディレクトリ変更
- `golangci_lint_flags`でgolangci.ymlファイルから静的解析の設定を指定

`golangci.yml`は静的解析の設定を記載しているファイルです。
今回は`backend`ディレクトリに入れています。
``` backend/golangci.yml
run:
  deadline: 5m

linters-settings:
  govet:
    check-shadowing: true
  golint:
    min-confidence: 0.8
linters:
  disable-all: false
  enable:
    - govet
    - errcheck
    - staticcheck
    - unused
    - gosimple
    - misspell
    - lll
    - gofumpt

```
`golangci.yml`ファイルでは使用するLinter等を設定しています。
設定したいLinterはこちらで確認して、取捨選択して下さい！
公式リファレンス：[Linters](https://golangci-lint.run/usage/linters/)

これで実際に動かしてみましょう！
![スクリーンショット 2024-01-13 15.00.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653241/9fd07a9c-dd86-94c4-f54d-fab8a710a506.png)
きちんと指摘してくれました！
指摘があると、自分は「うっ！やばい！」と思うのですが、botに言われても心に余裕があるので素晴らしいツールですね！

## 脆弱性検査
最後に、脆弱性検査のジョブを見ていきましょう。
``` backend_ci.yml
  backend_build_check:
    runs-on: ubuntu-latest
    steps:
      # リポジトリからソースコードの取得
      - name: Checkout Repository
        uses: actions/checkout@v3

      # Dockerのコンテナイメージをビルド
      - name: Docker Build
        run: |
          docker build -f Dockerfile -t ${{ env.DOCKER_IMAGE_TAG }} .

      # Dockleで脆弱性検査を行う
      - name: Run Dockle
        uses: erzz/dockle-action@v1
        with:
          image: ${{ env.DOCKER_IMAGE_TAG }}
          exit-code: 1
          failure-threshold: fatal
```
先ほど同様リポジトリからソースコードを取得した後、Dockerイメージをビルドしていきます。
自分はDockerを使っているのですが、Dockerを使っていない場合は分かりません。。。

最後に、脆弱性検査ツールのDockleを実行します。
今回は、指摘される警告レベルをfatalに設定しました。

実際に動かしてみます！
![スクリーンショット 2024-01-13 15.05.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653241/f541d2ec-c17b-54b4-59f9-93d3caa66ad1.png)

Dockerfile上で、「`apk add`を使う場合は`--no-cache`を使えよ！」と怒られます。
botになら怒られても「すみません。。。」とはならないので、ガラスのハートを持ってる自分でもヒビが入るくらいで耐えられますね！

# さいごに
golangのCIをGitHub Actionsで構築してみました！（完全に理解した()）
ただ、自分の完全に理解した()ではカバーできない所もあるので、アドバイスも頂ければ幸いです！

ここまでご覧いただきありがとうございました！