GitHub App installation access token + GitHub ActionsによるSelf-Hosted Renovateで、プライベートリポジトリのgemやnpmパッケージが含まれるGemfileを更新する

GitHubでソースコードを管理しているアプリにおいて、利用ライブラリのアップデートを自動で行う方法として Dependabot や Renovate などがあります。

Renovateを使う場合、Mend社が提供している Renovate GitHub App (Mend-hosted GitHub App)を使うのが簡単です。

 
また、Renovateでは、Gemfileなどに記載されているライブラリの更新とともに、Gemfile.lockなどのロックファイルも更新します。
Use Cases - Renovate Docs

ただし、ロックファイルの更新方法はパッケージマネージャによって異なります。例えば、 プライベートリポジトリなどのアクセスできないところにライブラリがある場合です。

npmでは、アクセスできないライブラリを無視してロックファイルを更新します。

一方、Bundlerではエラーとみなします。その結果、ロックファイルを更新するRenovateの処理 renovate/artifacts が失敗してしまい、次のプルリクのようになります。
https://github.com/thinkami-renovate-sandbox/main-app-with-renovate/pull/13

 
renovate/artifacts のエラーを解消するには、プライベートリポジトリにアクセスするための情報をMend Cloudに渡せば良さそうでした。
Configure Mend for GitHub.com to resolve your private dependencies

他にも方法がないか調べたところ、GitHub Actionによる Self-Hosted Renovate がありました。GitHub の中で色々完結できそうなので、GitHub Action方式でいくことにしました。

 

次に、その環境で Renovate に渡すためのトークンとして

  • GitHub App installation access token
  • 個人アカウントのPersonal Access token (PAT)

のいずれかを使う方法がありました。

PATの場合、個人アカウントを用意する必要があり、かつ、個人アカウントに紐づく、有効期限の設定が必要など、GitHub Actionsでは使い勝手が悪いです。一方、GitHub App installation access tokenはGitHub Actionsで使うのにちょうど良さそうです。詳細は以下の記事が参考になりました。
GitHub Appsトークン解体新書:GitHub ActionsからPATを駆逐する技術

 
そこで、GitHub App installation access token + GitHub ActionsによるSelf-Hosted Renovateで、プライベートリポジトリのgemやnpmパッケージが含まれるGemfileの更新を試してみたことから、その内容をメモします。

 
目次

 

環境

以下の内容を1つのGitHub Organizationで管理しているとします。

  • プライベートリポジトリで管理している次のパッケージ
    • npmパッケージ
    • gem
  • パブリックリポジトリで管理しているアプリ
    • 上記のパッケージを以下で管理
      • package.json
        • パッケージマネージャは yarn 1.x 系
      • Gemfile
        • パッケージマネージャは Bundler

 
なお、今回の各パッケージはソースコードからインストールするものとします。

GitHub Package によるプライベートパッケージ管理の環境からインストールする場合は、別の方法になりそうです。詳しくはRenovateのドキュメントを参照してください。
Private Packages - Renovate Docs

 

検証で使うパッケージとアプリについて

今回は次のようなパッケージとアプリを用意しました。なお、完成したらそれぞれGitHubへpushしておきます。

npmパッケージ

今回はインストールできるかどうかだけが分かればよいので、ミニマムなnpmパッケージとして index.js と package.json を用意します。

index.js

export const sayHello = () => {
  console.log('Hello World!');
};

 
package.json

{
  "name": "private_npm_lib",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module"
}

 

gem

gemもミニマムなものを用意します。まず bundle gem private-bundler-app でgemを生成します。

% bundle gem private-bundler-app 
Creating gem 'private-bundler-app'...
Initializing git repo in /path/to/private-bundler-app
      create  private-bundler-app/Gemfile
      create  private-bundler-app/lib/private/bundler/app.rb
      create  private-bundler-app/lib/private/bundler/app/version.rb
      create  private-bundler-app/sig/private/bundler/app.rbs
      create  private-bundler-app/private-bundler-app.gemspec
      create  private-bundler-app/Rakefile
      create  private-bundler-app/README.md
      create  private-bundler-app/bin/console
      create  private-bundler-app/bin/setup
      create  private-bundler-app/.gitignore

 
続いて、 lib/private/bundler/app.rb ファイルを追加します。

puts 'Hello, World!'

 
最後に、 private_bundler_app.gemspec をいい感じに書いて完成です。

 

アプリ

今回は以下の組み合わせが更新できればOKとします。

  • Gemfile と Gemfile.lock
  • package.json と yarn.lock

そのため、アプリ用のファイルはつくらず、Gemfileとpackage.jsonだけ用意します。

 
Gemfile

パブリックgemとして colorize を、プライベートgemとして上記で作成した private_bundler_app をそれぞれ Gemfile に定義します。

source "https://rubygems.org"

gem 'private_bundler_app', git: 'https://github.com/thinkami-renovate-sandbox/private-bundler-app.git'

gem 'colorize', '0.5.5'

 
package.json

こちらもパブリックパッケージとして uuid を、プライベートパッケージとして private-npm-app をそれぞれ package.json に定義します。

{
  "name": "my-app-with-self-hosted-renovate",
  "version": "1.0.0",
  "main": "index.js",

  "dependencies": {
    "private_npm_lib": "https://github.com/thinkami-renovate-sandbox/private-npm-app.git",
    "uuid": "1.4.0"
  }
}

 
最後にインストールです。

まずは、gemです。

% bundle install
Fetching https://github.com/thinkami-renovate-sandbox/private-bundler-app.git
Fetching gem metadata from https://rubygems.org/.......
Resolving dependencies...
Bundle complete! 2 Gemfile dependencies, 5 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

 

続いて、npmパッケージをyarnでインストールします。 uuidのバージョンが古いためwarningが出ていますが、今回は気にしないことにします。

% yarn install
yarn install v1.22.22
info No lockfile found.
[1/4] 🔍  Resolving packages...
warning uuid@1.4.0: Please upgrade to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 3.94s.

 

全体像

Self-HostedなRenovateをGitHub Actionで作る場合、以下が必要です。

 
GitHub Appでは、以下の権限を管理します。

 
GitHub Actionでは以下を行います。

  • GitHub Appのインストールアクセストークンを取得
  • インストールアクセストークンを使ってRenovateを実行

 
それぞれ見ていきます。

GitHub App の用意

Organizationで GitHub App を作成

Organizationの Settings > Developer settings > GitHub App から、 New GitHub App ボタンを押します。

以下の項目をデフォルトから変更します。

項目 値
GitHub App name 任意の名前
Homepage URL 任意のURL
Indentifying ... - Expire user authorization tokens チェックを外す
Webhook - Active チェックを外す

 

また、Permissionsには、GitHubでのSelf-Hosted Renovateに必要なものを選択します。
GitHub and GitHub Enterprise Server - Renovate Docs

なお、上記の公式ドキュメントとは異なり、今回は workflow の更新は行わないため、workflowへのPermissionは与えていません。

項目 権限
Administration Read-only
Checks Read and write
Commit statuses Read and write
Contents Read and write
Dependabot alerts Read-only
Issues Read and write
Metadata Read-only
Pull requests Read and write

 
最後に保存して完了です。

 

App IDの確認と、Private keyの生成

GitHub ActionでGitHub Appからインストールアクセストークンを取得する際、GitHub Appの App ID と Private key が必要です。
GitHub Actions ワークフローで GitHub App を使用して認証済み API 要求を作成する - GitHub Docs

そこで、作成したGitHub Appの Generalページから、以下の作業を行います。

 

App IDの確認

上の方の About の近くにある App ID を確認し、値を控えておきます。

 

Private keyの生成

Generate a private key からPrivate keyを生成します。

生成したキーはダウンロードしておきます。

 

作成したGitHub Appをリポジトリへ割り当て

次に、作成したGitHub Appをリポジトリへと割り当てます。

割当先は

の各リポジトリです。Gemfileを持つアプリのリポジトリの他、Gemfileから参照されるリポジトリも指定する必要があります。

ここで指定しておくことで、あとで作成するGitHub App のインストールアクセストークンでプライベートリポジトリへアクセスできます。

 

まず、Organizationの Settings > Third-party Access GitHub Apps より、上記で作成したGitHub AppをOrganizationへインストールします。

インストールする際のRepository accessでは Only select repositories を選択し、

の各リポジトリを指定して保存します。

 

アプリリポジトリでの作業

Secretsの設定

Secretsを設定するため、アプリのリポジトリの Settings > Secrets and variables > Actions を開きます。

Repository secretsに以下の2つを追加します。なお、追加して保存すると値は再確認できなくなります。

Name 値
RENOVATE_APP_ID 上記で確認したGitHub Appの App ID
RENOVATE_APP_PRIVATE_KEY 上記で生成したGitHub AppのPrivate keyの中身

 

Self-HostedなRenovateの設定ファイルを作成

Self-HostedなRenovateでは、以下の3ファイルが必要です。

  • config.js
    • Renovateの設定のうち、Self-Hostedで必要な設定
  • renovate.json5
    • Renovateの設定
  • .github/workflows/renovate.yml
    • GitHub ActionsでRenovateを動かすための設定

 

ちなみに、 config.js と renovate.json5 は両方ともRenovate向けの設定ファイルです。

両者の違いは以下です。

 
今回のGitHub ActionsによるSelf-hosted Renovateでは、1つのGitHub Actionを1つのリポジトリでしか使いません。そのため、設定を config.js に集約することもできます。

ただ、「Renovate GitHub Appを利用している環境は renovate.json5 へ設定し、Self-hosted Renovateの場合は config.js へ設定する」だと分かりづらいため、今回は config.js の設定は必要最低限とし、基本は renovate.json5 ファイルに書くことにします。

 
Renovateには他にも設定できるものがあります。詳しくは以下の公式ドキュメントを参照してください。
Overview - Renovate Docs

 
では、それぞれのファイルについて詳しく見ていきます。

 

config.js

renovate.json5ファイルに書いても有効にならない設定を、この config.js へ設定します。今回の場合、renovate.json5ファイルでは「BundlerがGitHubのプライベートリポジトリへアクセスするときのトークン(BUNDLE_GITHUB__COM)を環境変数に設定する」ができませんでした。

そこで、 env オプションのキー BUNDLE_GITHUB__COM に対して、GitHub Actionから渡されてきた値 process.env.BUNDLE_GITHUB__COM を設定しています。

なお、 BUNDLE_GITHUB__COM については、 Bundlerのbundle configのページに詳細が記載されています。
Bundler: bundle config

env: {
  BUNDLE_GITHUB__COM: process.env.BUNDLE_GITHUB__COM,
},

 
config.js の全体はこちら。

module.exports = {
  $schema: "https://docs.renovatebot.com/renovate-schema.json",
  
  // renovate.json5 があれば、そちらの設定も利用する
  // https://docs.renovatebot.com/self-hosted-configuration/#requireconfig
  requireConfig: "optional",
  
  // Git設定
  gitAuthor: "Renovate Bot <renovate@example.com>",
  
  // Bundler用の環境変数設定。
  env: {
    BUNDLE_GITHUB__COM: process.env.BUNDLE_GITHUB__COM,
  },
};

 

renovate.json5

今回はマイナー・パッチアップデートのプルリクを作る感じで設定します。

{
  // Renovateの基本設定
  $schema: "https://docs.renovatebot.com/renovate-schema.json",
  
  // すべて実行する
  mode: "full",

  // ベース設定を継承
  extends: [
    "config:recommended"
  ],

  // automergeを無効化
  automerge: false,
  
  // パッケージルール
  packageRules: [
    {
      // すべてのパッケージに対して
      matchPackageNames: ["*"],
      
      // メジャーアップデートを無効化
      matchUpdateTypes: ["major"],
      enabled: false
    },
    {
      // すべてのパッケージに対して
      matchPackageNames: ["*"],
      
      // マイナー・パッチアップデートのみ有効
      matchUpdateTypes: ["minor", "patch"],
      enabled: true
    }
  ],

  // 作成するプルリクは、いったん無制限
  prConcurrentLimit: 0,
  prHourlyLimit: 0,
  
  // タイムゾーン設定(日本時間)
  timezone: "Asia/Tokyo",
}

 

.github/workflows/renovate.yml

GitHub ActionsでRenovateを動かすため、GitHub Action用の設定を用意します。  
Jobのステップとしては以下です。

  1. actions/checkout でチェックアウト
  2. actions/create-github-app-token でGitHub Appからトークンを生成
  3. renovatebot/github-action でSelf-hostedなRenovateを実行

 
ここでは、ステップ2と3を詳しく見ていきます。

 

actions/create-github-app-token でGitHub Appからトークンを生成

公式のGitHub Actions actions/create-github-app-token を使い、GitHub Appからインストールトークンを取得します。
actions/create-github-app-token: GitHub Action for creating a GitHub App Installation Access Token

READMEにはサンプルコードも掲載されています。今回は、GitHub Appがインストールされた全リポジトリに対してアクセスを可能としたいため、「Create a token for all repositories in the current owner's installation」を参考にします。

ちなみに、よりセキュアにするには、アクセスするリポジトリを絞れる「Create a token for multiple repositories in the current owner's installation」のほうがよいかもしれませんが、今回は上記の設定とします。

 
使い方としてはサンプルコードにある通り、環境変数に設定した App IDとSecret keyの他、リポジトリのオーナー(ここではorganization)を指定します。

なお、今回はわかりやすさのためGitHub Actionは @v2.0.6 のようにバージョン指定していますが、セキュアにするにはハッシュを指定する方が良いでしょう。

- name: Generate token
  id: generate-token
  uses: actions/create-github-app-token@v2.0.6
  with:
    app-id: ${{ secrets.RENOVATE_APP_ID }}
    private-key: ${{ secrets.RENOVATE_APP_PRIVATE_KEY }}
    owner: ${{ github.repository_owner }}

 

renovatebot/github-action でSelf-hostedなRenovateを実行

RenovateのGitHub ActionにあるREADMEを参考に設定します。 renovatebot/github-action

今回は環境変数まわりで色々設定するため、READMEにある Environment Variables | renovatebot/github-actioを参考にしながら設定していきます。

- name: Self-hosted Renovate
  # https://github.com/renovatebot/github-action
  uses: renovatebot/github-action@v42.0.5
  env:
    LOG_LEVEL: ${{ github.event.inputs.logLevel || 'info' }}
    RENOVATE_DRY_RUN: ${{ github.event.inputs.dryRun || 'false' }}
    RENOVATE_REPOSITORIES: ${{ github.repository }}
    BUNDLE_GITHUB__COM: "x-access-token:${{ steps.generate-token.outputs.token }}"
  with:
    configurationFile: config.js
    token: ${{ steps.generate-token.outputs.token }}
    env-regex: "^(?:RENOVATE_\\w+|LOG_LEVEL|GITHUB_COM_TOKEN|BUNDLE_GITHUB__COM)$"

 
まず、 env で BUNDLE_GITHUB__COM に値をセットしています。この env でセットした値が config.js の process.env で取得できるためです。

GitHub App でインストールアクセストークンを使ってリポジトリへとアクセスするには、 x-access-token: というprefixが必要です。
GitHub App インストールとしての認証 - GitHub Docs

そこで、一つ前のステップで取得したインストールアクセストークン steps.generate-token.outputs.token に対してprefixを設定し、 BUNDLE_GITHUB__COM の値としています。

 
また、Renovate (config.js) に BUNDLE_GITHUB__COM というキーの環境変数を渡すため、 with で env-regex を指定しています。
Passing other environment variables | renovatebot/github-action

 
他に、設定ファイルとしてリポジトリルートにある config.js を渡すよう、 configurationFile へ指定しています。
configurationFile | renovatebot/github-action

 

ファイル全体

GitHubリポジトリの Action タブから手動実行できるよう、 workflow_dispatch を定義しています。

実運用で使う場合は、スケジューリングすることになるでしょう。

name: Renovate
on:
  # 手動実行
  workflow_dispatch:
    inputs:
      dryRun:
        description: 'Dry Run'
        type: boolean
        required: false
        default: false
      logLevel:
        description: 'Log Level'
        type: choice
        required: false
        default: 'info'
        options:
          - debug
          - info
          - warn
          - error

jobs:
  renovate:
    runs-on: ubuntu-24.04
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Generate token
        id: generate-token
        # https://github.com/actions/create-github-app-token
        uses: actions/create-github-app-token@v2.0.6
        with:
          app-id: ${{ secrets.RENOVATE_APP_ID }}
          private-key: ${{ secrets.RENOVATE_APP_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}

      - name: Self-hosted Renovate
        # https://github.com/renovatebot/github-action
        uses: renovatebot/github-action@v42.0.5
        env:
          LOG_LEVEL: ${{ github.event.inputs.logLevel || 'info' }}
          RENOVATE_DRY_RUN: ${{ github.event.inputs.dryRun || 'false' }}
          RENOVATE_REPOSITORIES: ${{ github.repository }}
          BUNDLE_GITHUB__COM: "x-access-token:${{ steps.generate-token.outputs.token }}"
        with:
          configurationFile: config.js
          token: ${{ steps.generate-token.outputs.token }}
          env-regex: "^(?:RENOVATE_\\w+|LOG_LEVEL|GITHUB_COM_TOKEN|BUNDLE_GITHUB__COM)$"

 

動作確認

ここまでで作成してきた各ファイルをコミット、GitHubへpush、 main ブランチへマージした上で、動作確認を行います。

Actionsから作成したworkflowを動かすと、成功しました。
https://github.com/thinkami-renovate-sandbox/my-app-with-self-hosted-renovate/actions/runs/15598133809

また、Renovateが作成したプルリクも、プライベートリポジトリにアクセスできたようで、エラーになることなく作成されています。
https://github.com/thinkami-renovate-sandbox/my-app-with-self-hosted-renovate/pull/3

 

ソースコード

アプリのリポジトリをGitHubに上げました。動作確認の都合上、プライベートリポジトリは公開していません。
https://github.com/thinkami-renovate-sandbox/my-app-with-self-hosted-renovate

設定ファイルを作成したときのプルリクはこちら。
https://github.com/thinkami-renovate-sandbox/my-app-with-self-hosted-renovate/pull/1

Renovateが作成した、プライベートリポジトリにあるgemを含むGemfileを更新するプルリクはこちら。
https://github.com/thinkami-renovate-sandbox/my-app-with-self-hosted-renovate/pull/3

なお、色々試行錯誤したリポジトリも紹介します。あくまでいろいろ試したときの内容なので、参考まで。
https://github.com/thinkami-renovate-sandbox/main-app-with-self-hosted-renovate

 
他に、Mend-hosted Renovateを使っているリポジトリはこちら。
https://github.com/thinkami-renovate-sandbox/main-app-with-renovate