🐙

GitHub Actionsでジョブ実行リポジトリとは別のリポジトリを修正してPRを作る

2023/06/03に公開

概要

GitOpsをしていると、アプリケーションコードとインフラコード(Kubernetesマニフェストなど)はリポジトリごと分離して、アプリケーションコードの更新に伴ってインフラコードを修正することになります。この際、デプロイにコンテナを利用していればインフラコード上の修正は使用するコンテナイメージ名の修正になり、CI/CDを使ってこの修正を自動化することも多いと思います。

ここではそんな用途のために、GitHub Actionsで実行されたジョブのリポジトリとは別のリポジトリの内容を修正して、PRを作成するための手法を説明します。なお、ここではOrganizationではなく、個人アカウントでの設定方法についてのみ説明します。

手順

現状の手順は以下の通りになります。

  1. GitHub Appの作成
  2. GitHub Appの秘密鍵作成
  3. GitHub Appのインストール
  4. GitHub Actionsの構成

GitHub Appの作成

まず、自分のアカウントの Settings を開きます。

左ペインの Developer Settings を開きます。

GitHub Apps が選択されている状態で New GitHub App を選択します。

以下のパラメータで、GitHub Appを作ります。

  • GitHub App Name: 任意の名前
  • Homepage URL: 修正先のリポジトリURL
  • Webhook: 非Active
  • Permissions
    • Repository Permissions
      • Contents: Read and write
      • Pull requests: Read and write
  • Where can this GitHub App be installed?: Only on this account

すると、こんな画面になります。App IDは後程GitHub Actionsを構成する際に必要になるので、控えておきます。

GitHub Appの秘密鍵作成

GitHub Appの作成後、下の方に秘密鍵作成用のボタンがあるので、こちらをクリックします。

すると、GitHub Appにアクセスするための秘密鍵が作成され、DLされます。このファイルの中身は後程GitHub Actionsを構成するときに使います。

GitHub Appのインストール

左ペインの Install App を選択します。

自分のアカウントが表示されると思うので、 Install をクリックします。

Only select repositories を選んで、修正先となるリポジトリを選択します。

これでこのアプリに、修正先リポジトリに対してclone・push・PR作成をする権限が付与されました。

GitHub Actionsの構成

ここまで来たら後は実際にGitHub Actionsを構成していきます。まずは最終的な設定内容を見ていきます。

  change_image_tag:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
      with:
        repository: <修正先リポジトリ>
        ref: <修正先ブランチ>
        path: <clone先の相対パス>
    - run: |
        curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
        sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
        echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
        sudo apt update && sudo apt install gh -y
    - run: <manifest内のimageタグを更新する処理>
      working-directory: <clone先の相対パス>
    - id: generate_token
      uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
      with:
        app_id: ${{ secrets.APP_ID }}
        private_key: ${{ secrets.APP_PRIVATE_KEY }}
        repository: <修正先リポジトリ>
    - run: |
        GIT_DIFF=$(git diff --name-only)
        if [ -z "$GIT_DIFF" ]; then
          echo "nothing to do."
        else
          git config --unset-all http.https://github.com/.extraheader
          git remote add from-actions https://github-actions:${GITHUB_TOKEN}@github.com/<修正先リポジトリ>
          git config user.name github-actions[bot]
          git config user.email 41898282+github-actions[bot]@users.noreply.github.com
          git checkout -b <任意のブランチ名prefix>-${{ github.sha }} &&
          git add .
          git commit -m "Update image tag to ${{ github.sha }}" &&
          git push from-actions <任意のブランチ名prefix>-${{ github.sha }} &&
          gh pr create --base <修正先ブランチ> --head <任意のブランチ名prefix>-${{ github.sha }} --title "from <修正先リポジトリ>: sha=${{ github.sha }}" --body ""
        fi
      working-directory: <clone先の相対パス>
      env:
        GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}

各ステップの詳細を見ていきます。

    - uses: actions/checkout@v3
      with:
        repository: <修正先リポジトリ>
        ref: <修正先ブランチ>
        path: <clone先の相対パス>

ここは特に不思議なことはないかと思います。manifestの入っているリポジトリをcheckoutしてきているだけです。 path のディレクトリ内にリポジトリの内容が入るので、後続のジョブでgitコマンドを叩くときは working-directorypath の値を入れておきます。

    - run: |
        curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
        sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
        echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
        sudo apt update && sudo apt install gh -y

このステップではgithub cli (ghコマンド) をインストールしています。内容はほぼ公式のこちらのままです。

    - run: <manifest内のimageタグを更新する処理>
      working-directory: <clone先の相対パス>

このステップで、実際にmanifest内のimageタグを更新していきます。実際にデプロイするインフラによってこのステップの内容は変わると思います。

    - id: generate_token
      uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
      with:
        app_id: ${{ secrets.APP_ID }}
        private_key: ${{ secrets.APP_PRIVATE_KEY }}
        repository: <修正先リポジトリ>

このステップでは事前に作成したGitHub Appに対する GITHUB_TOKEN を取得します。そのためには事前にactionsのシークレット変数として、GitHub AppのApp IdをAPP_IDに、秘密鍵の内容をAPP_PRIVATE_KEYに格納しておいてください。

    - run: |
        GIT_DIFF=$(git diff --name-only)
        if [ -z "$GIT_DIFF" ]; then
          echo "nothing to do."
        else
          git config --unset-all http.https://github.com/.extraheader
          git remote add from-actions https://github-actions:${GITHUB_TOKEN}@github.com/<修正先リポジトリ>
          git config user.name github-actions[bot]
          git config user.email 41898282+github-actions[bot]@users.noreply.github.com
          git checkout -b <任意のブランチ名prefix>-${{ github.sha }} &&
          git add .
          git commit -m "Update image tag to ${{ github.sha }}" &&
          git push from-actions <任意のブランチ名prefix>-${{ github.sha }} &&
          gh pr create --base <修正先ブランチ> --head <任意のブランチ名prefix>-${{ github.sha }} --title "from <修正先リポジトリ>: sha=${{ github.sha }}" --body ""
        fi
      working-directory: <clone先の相対パス>
      env:
        GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}

最後のステップでは、直前のジョブで作成したGITHUB_TOKENを使って修正先リポジトリにトピックブランチをpush、PRの作成を行います。
ハマりどころとして git config --unset-all http.https://github.com/.extraheader の一行がないと、actionsのデフォルトでロードされるGITHUB_TOKENが利用され、ジョブで作成した値が使われずAccess Deniedとなります。

Discussion