GitHub Actionsでcacheに転んでいた

# はじめに
[前回の記事](https://qiita.com/yawn_yawn_yawn_/items/2f4bf5d3ce7e5e77dfd7#yaml%E5%85%A8%E4%BD%93)のまとめを読んでからだと、雰囲気がつかみやすいかも？

# 実現したかったこと
GitHub Actionsを用いてgoの静的解析、テスト/ビルドを行うCIを構築したい。
その際、毎回モジュールのインストールが挟まると時間がかかっちゃうので、キャッシュを使って短縮したい。という話の続き。

[前回の記事](https://qiita.com/yawn_yawn_yawn_/items/2f4bf5d3ce7e5e77dfd7#yaml%E5%85%A8%E4%BD%93)の段階では、キャッシュヒット、と通知されたが、`go vet`や`go test`を行う際にモジュールのインストールが発生する。保存したはずのキャッシュが使えてねえ...


# 起きていたこと

**中身がキャッシュされていなかった事件**

キャッシュ自体はされていましたが、モジュールがキャッシュされていませんでした。なんのためのキャッシュや...

# 原因を調べる
静的解析、テスト/ビルドをわけた、問題のyamlは以下。ちょっと長め。

``` .github/workflows/work.yml
... 省略

jobs:
  # cacheが関係ないジョブ
  static-check:
    name: StaticCheck
    runs-on: ubuntu-latest
    container: golang:1.12
    steps:
      # set go version
      - name: Set up Go 1.12
        uses: actions/setup-go@v1
        with:
          go-version: 1.12
        id: go

      - name: Check out code into the Go module directory
        uses: actions/checkout@v2
      
      # cache保存用にディレクトリを作っておく
      - name: before cache
        run: |
          mkdir -p ~/go/pkg/mod

      # cache
      - name: cache
        uses: actions/cache@v1
        id: cache-go
        with:
          path: ~/go/pkg/mod
          key: ${{ env.cache-version }}-${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
          restore-keys: |
          ${{ runner.os }}-go-

      # cacheがヒットしたか確認
      - name: after cache
        run: |
          echo "-->${{steps.cache-go.outputs.cache-hit}}<--"

      # キャッシュがヒットしなかったら install module
      - name: Get dependencies
        if: steps.cache-go.outputs.cache-hit != 'true'
        run: |
          ... 省略

      # Run fmt
      ... 省略 fmt, lint, vetを行う

  build:
    name: Test-Build
    runs-on: ubuntu-latest
    needs: [static-check]
    services:
        ... 省略

    steps:
    - name: Set up Go 1.13
      uses: actions/setup-go@v1
      with:
        go-version: 1.12
      id: go

    - name: Check out code into the Go module directory
      uses: actions/checkout@v2

      ... 省略
      
    # cache保存用にディレクトリを作っておく
    - name: before cache
      run: |
        mkdir -p ~/go/pkg/mod

    # cache
    - name: cache
      uses: actions/cache@v1
      id: cache-go
      with:
        path: ~/go/pkg/mod
        key: ${{ env.cache-version }}-${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
          ${{ runner.os }}-go-

    # cacheがヒットしたか確認
    - name: after cache
      run: |
        echo "-->${{steps.cache-go.outputs.cache-hit}}<--"

    # キャッシュがヒットしなかったら install module
    - name: Get dependencies
      if: steps.cache-go.outputs.cache-hit != 'true'
      run: |
        ... 省略

    # Run test
    ... 以下省略。テストとビルドを行います。

```

原因はここ。

```
container: golang:1.12
```

はい。これだけです。`static-check`のjobには設定されていますが、`build`の方にはないですね...。

`container`指定の何があかんかったかというと、**GOPATHに違いがでてしまうこと**でした。
**`container`を使用した時と、使用なかった時で、GOPATHが変わっているんですね**...。

それぞれ、pathを比較してみました。

## container
まず、`container`を使用した時のPATH

```
container を指定していた時のPATH
// $GOPATH
/go

// $HOME
/github/home

// $PATH
/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
```

goのモジュールは、`/go/pkg/mod`に入ります。こいつをキャッシュしたいわけですね。

## actions/setup-go

次に、`actions/setup-go`を使用したのPATH。

```
actions/setup-go だけの時のPATH
// $GOPATH


// $HOME
/home/runner

// $PATH
/opt/hostedtoolcache/go/1.12.17/x64/bin:/usr/share/rust/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/runner/.dotnet/tools:/home/runner/.config/composer/vendor/bin
```

`$GOPATH`が表示されない！ですが、`go env`で確認してみると、`$GOPATH=/home/runner/go`に設定されていることが確認できます。
goのモジュールは、`/home/runner/go/pkg/mod`に入ります。

## つまり？

前日の、ディレクトリを作ったらいけた！という現象は。。。

- `~/go/pkg/mod`を作成したが、
- 実際にモジュールがインストールされているのは`/go/pkg/mod`

つまり、**空っぽのディレクトリをキャッシュしてたんですね**。なんというアホ。

# 試したこと
## 双方、containerを使用してみる
キャッシュをtarで展開するときに、`permission denied`にハマりまくった。`/go`にアクセスすることが困難なので、`chmod`なりで対抗してみましたが、流石にきびしい。。。

4/15追記: そもそもpathの設定をミスっていた気がする

## 片方だけcontainerを使用
`static-check`は`container`を指定し、`build`では指定しない、という方法。
`actions/cacheのpath`に違いが出る以外は何も変わらないが、actionsの実行結果をみていると、コンテナの初期化に時間がかかっていることがわかりました...。

## containerを使用しない
一瞬でうまくいきました...`golint`や`goimports`はPATHの関係で、少しいじりましたが、それ以外はとても快適。

## どのくらい時間に差が出るのか
`static-check`、`build`の2つのjobを完了するまでに、どれだけの時間がかかるかみてみました。

- キャッシュを一切使わない: 4m15s
- 片方だけcontainerを使用、キャッシュを使う: 3m20s
- 双方containerを使用せず、キャッシュを使う: 2m36s

containerの初期化が凄まじい。最終的にはキャッシュを使うことで、1m45sの短縮。やるじゃん...。

# まとめ
`container`を指定したのは理由が、`actions/setup-go`だと`/go/bin`にpathが通っていなかったことでした。
`golint`や、`goimports`をPATHの追加や、`~/go/bin/golint`などと書かなくてよくなるので、、、、。
いけるじゃん！って思う前に、中身がどうなってるのか確認しないとダメだなあ...

# 最終的なyaml
※ testにmysqlが必要なので環境を用意しています。

``` .github/workflows/work.yml
name: Go

on:
  pull_request:
    branches:
      - '*'

env:
  cache-version: v10

jobs:
  
  static-check:
    name: StaticCheck
    runs-on: ubuntu-latest

    steps:
      # set go version
      - name: Set up Go 1.12
        uses: actions/setup-go@v1
        with:
          go-version: 1.12
        id: go

      - name: Check out code into the Go module directory
        uses: actions/checkout@v2

      # cache store
      - name: cache
        uses: actions/cache@v1
        id: cache-go
        with:
          path: ~/go/pkg/mod
          key: ${{env.cache-version}}-${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-
      
      - name: after cache
        run: |
          echo "-->${{steps.cache-go.outputs.cache-hit}}<--"

      - name: Install go modules
        if: steps.cache-go.outputs.cache-hit != 'true'
        run: |
          echo "run install go module"
          go get -v -t -d ./...
          if [ -f Gopkg.toml ]; then
              curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
              dep ensure
          fi
        
      # Run fmt
      - name: Go fmt
        run: |
          go get -u golang.org/x/tools/cmd/goimports
          gofmt -s -w cmd/
          ~/go/bin/goimports -w cmd/

      # Run lint
      - name: Go Lint
        run: |
          go get -u golang.org/x/lint/golint
          ~/go/bin/golint ./...

      # Run vet
      - name: Go vet
        run: |
          go vet ./...

  build:
    name: Test-Build
    runs-on: ubuntu-latest
    needs: [static-check]
    services:
      mysql:
        image: mysql:5.7
        ports:
          - 3306:3306
        options: --health-cmd "mysqladmin ping -h localhost" --health-interval 20s --health-timeout 10s --health-retries 10
        env:
          MYSQL_ROOT_PASSWORD: pass
          MYSQL_DATABASE: sample

    steps:
    - name: Set up Go 1.12
      uses: actions/setup-go@v1
      with:
        go-version: 1.12
      id: go

    - name: Check out code into the Go module directory
      uses: actions/checkout@v2

    - name: SetUp ddl
      run: |
        mysql --protocol=tcp -u root -ppass sample < ./ddl.sql

    # cache store
    - name: cache
      uses: actions/cache@v1
      id: cache-go
      with:
        path: ~/go/pkg/mod
        key: ${{env.cache-version}}-${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
          ${{ runner.os }}-go-

    # cacheがヒットしたか確認
    - name: after cache
      run: |
        echo "-->${{steps.cache-go.outputs.cache-hit}}<--"
    
    - name: Get dependencies
      if: steps.cache-go.outputs.cache-hit != 'true'
      run: |
        go get -v -t -d ./...
        if [ -f Gopkg.toml ]; then
            curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
            dep ensure
        fi
    
    - name: Test
      run: go test -v ./...

    - name: Build
      run: go build -v ./cmd/main.go
```