GitHub ActionsでDockerコンテナ上でGitを操作する際の罠

# 前提

- GitHub ActionsのセルフホステッドランナーではGitHubホステッドランナーとは異なり、ジョブ毎に隔離された仮想マシンが起動し、その中でジョブが実行されるわけではない
- そのため、ジョブ内で行ったサーバの設定変更がセルフホステッドランナーを起動しているホストマシンに直接影響してしまう
- これではジョブ間で実行される環境が変わってしまい、イミュータブルでなくなってしまう
- そのため、各ジョブは可能な限りGitHub Actionsが提供するDockerコンテナ上で動作させる機能を使用し、セルフホステッドランナーを起動しているホストマシンへの影響を最小化させることが好ましい

# 問題点

- GitHub Actionsのジョブにおいて`git push`を行ったり、`git log`を取得したりといったGitの操作は比較的よく行われる
- しかしDockerコンテナ上でジョブを実行した場合、直接ホストマシン上でジョブを実行する場合と比べ、様々な罠が存在する

# 検証

- 上記の問題点を踏まえ、以下にその検証の過程と解決策についてまとめた

## 検証１

- Dockerコンテナ上で`git push`を行う最低限のコードを実行
- 設定

```yaml:.github/workflows/example.yml
name: Git example

on:
  push

jobs:
  git-push:
    runs-on:
      - ubuntu-latest

    # Dockerコンテナイメージの指定
    container:
      image: debian:stable-slim

    steps:
      # Gitリポジトリにチェックアウト
      - name: Checkout repository
        uses: actions/checkout@v3

      # git commit、git pushするための最低限の設定
      - name: Commit & push
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          # 今回は例なので --allow-empty オプションを付与して空のコミットを作成している
          git commit --allow-empty -m "Test commit"
          git push
```

- 実行結果

```
/__w/_temp/09fdd765-0703-40c9-80b8-2c04c2e0eef1.sh: 1: git: not found
Error: Process completed with exit code 127.
```

- このDockerイメージにはGitがインストールされていないという旨のエラーが発生している

## 検証2

- 検証1の設定にGitをインストールするステップを追加
- 設定

```diff_yaml:.github/workflows/example.yml
...

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

+     - name: Install git
+       run: |
+         apt-get update
+         apt-get install git -y

      - name: Commit & push
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git commit --allow-empty -m "Test commit"
          git push
```

- 実行結果

```
fatal: not in a git directory
Error: Process completed with exit code 128.
```

- 1つ目のステップでGitリポジトリをチェックアウトしているはずだが、Gitリポジトリではないという旨のエラーが発生している

## 検証3

- チェックアウトしたGitリポジトリにどんなファイルとディレクトリが格納されているのか調査する
- 設定

```diff_yaml:.github/workflows/example.yml
...

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

+     - name: Display files
+       run: ls -la

      - name: Install git
        run: |
          apt-get update
          apt-get install git -y

...
```

- 実行結果

```
total 12
drwxr-xr-x 3 1001  123 4096 Jun  1 22:31 .
drwxr-xr-x 3 1001  123 4096 Jun  1 22:31 ..
drwxrwxr-x 3 root root 4096 Jun  1 22:31 .github
```

- Gitリポジトリの場合、本来`.git`ディレクトリが作成されているはずだが存在していない
- そのせいでGitリポジトリではないという旨のエラーが発生しているということが分かった
- どうやら`actions/checkout@v3`はGitが存在しない場合、GitHub REST APIを使用してGitリポジトリにチェックアウトしている模様

## 検証4

- GitのインストールをGitリポジトリのチェックアウトよりも先に実行してみる
- 設定

```diff_yaml:.github/workflows/example.yml
...

    steps:
+     - name: Install git
+       run: |
+         apt-get update
+         apt-get install git -y

      - name: Checkout repository
        uses: actions/checkout@v3

...
```

- 実行結果

```
total 16
drwxr-xr-x 4 1001  123 4096 Jun  1 22:34 .
drwxr-xr-x 3 1001  123 4096 Jun  1 22:34 ..
drwxr-xr-x 8 root root 4096 Jun  1 22:34 .git
drwxr-xr-x 3 root root 4096 Jun  1 22:34 .github
```

- `.git`ディレクトリが作成されていることが確認できた

```
fatal: not in a git directory
Error: Process completed with exit code 128.
```

- しかし依然としてGitリポジトリではないという旨のエラーが発生している

## 検証5

- `actions/checkout@v3`ではホストマシンにクローンしたリポジトリをDockerコンテナ上にボリュームマウントするような挙動をしている
- そのため、Dockerコンテナの現在のユーザからGitで操作できないファイルやディレクトリのパーミッションが設定されている可能性があるという仮説を立てた
- 調査すると、`git 2.35.2`で`safe.directory`という設定が導入されており、ここに設定されていないディレクトリでかつ現在のユーザが所有者でないGitリポジトリは安全ではないとみなしてGitから操作できないようになっている模様
- そのため、`git config --global --add safe.directory $PWD`という設定を追加
- 設定

```yaml:.github/workflows/example.yml
...

    steps:
      - name: Install git
        run: |
          apt-get update
          apt-get install git -y

      - name: Checkout repository
        uses: actions/checkout@v3

+     - name: Add safe directory
+       run: git config --global --add safe.directory $PWD

      - name: Commit & push
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git commit --allow-empty -m "Test commit"
          git push
```

- 実行結果

```
[master 0f925af] Test commit
remote: Permission to XXXXXXXX/XXXXXXXX.git denied to github-actions[bot].
fatal: unable to access 'https://github.com/XXXXXXXX/XXXXXXXX/': The requested URL returned error: 403
Error: Process completed with exit code 128.
```

- どうやらGitリポジトリではないという旨のエラーは解決できた模様
- しかし今度は`git push`を行うためのパーミッションが無いという旨のエラーが発生している

## 検証6

- GitHub Actionsに書き込み権限を付与する

```diff_yaml:.github/workflows/example.yml
name: Git example

on:
  push

+ permissions:
+   contents: write

jobs:
  git-push:
    runs-on:
      - ubuntu-latest

...
```

- こうすることで無事、GitHub Actionsから`git push`を行うことができた