RareJob Tech Blog

レアジョブテクノロジーズのエンジニア・デザイナーによる技術ブログです

Renovate on Self-Hosted GitLab

はじめに

こんにちは、DevOps グループの中島です。

依存ライブラリのバージョンアップは厄介な問題です。
更新して動かなくなったら面倒だという思いのもと放置されがちですが、
いざ脆弱性が見つかったときに一気にアップデートすると、大量の修正やテストが必要になる場合があり対処が遅れてしまいます。
そこで、定期的にアップデートすることで、一度の修正範囲を少なくなるように保つという方法が取られます。

今回のポストでは Go で書かれたアプリケーションのリポジトリに、依存関係の自動更新ツールである Renovate を導入しようとしてハマった時の記録を公開します。

セットアップ

以下の要領で実験用のリポジトリをセットアップします。

1.Renovate 用の設定ファイル (renovate.json) を作成します。

renovate.json

{
    "extends": [
        "config:base"
    ]
}

2.依存関係ファイルを作成します。
以下のように適当な go ファイルを書いて、コマンドを実行すると go.mod, go.sum が作成されます。

main.go

package main

import (
        "github.com/aws/aws-cdk-go/awscdk/v2"
)

コマンド

go get github.com/aws/aws-cdk-go/awscdk/v2

3.GitLab CI/CD のスケジュール機能で Renovate を実行するため、以下のようにジョブを構成します。
対象リポジトリGitHub 以外に存在する場合は、renovate コマンドに対してプラットフォームの種類とエンドポイントの指定が必要です。

.gitlab-ci.yml

renovate:
  image:
    name: renovate/renovate:latest
  script:
    - renovate
        --platform gitlab
        --endpoint https://${GITLAB_URL}/api/v4
        $CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME
  only:
    refs:
      - schedules

動作確認

RENOVATE_TOKEN という環境変数に GitLab へのアクセストークンを設定した上で、 GitLab CI/CD のスケジュール画面からジョブを手動実行します。

以上で最小構成にて実行が行えるはずでした。

依存関係の取得に失敗

おもむろに実行してみると依存関係の取得に失敗しています。

有用なログが出ていないので デバッグ用の環境変数をセットして実行 してみます。

export LOG_LEVEL=debug

すると、 401 でどうやら認証に失敗していることが分かりました。

DEBUG: Datasource unauthorized (repository=xxx/yyy, baseBranch=main)
       "datasource": "go",
       "packageName": "github.com/aws/aws-cdk-go/awscdk/v2",
       "url": "https://api.github.com/graphql"
DEBUG: Failed to look up go dependency github.com/aws/aws-cdk-go/awscdk/v2
...
DEBUG: http statistics (repository=xxx/yyy)
       "urls": {
         "https://api.github.com/graphql (POST,401)": 13,
       },

Renovate は依存している Go モジュールの更新を確認するため GitHub 上を探しに行きます。この際に GitHub GraphQL API を利用しているようです。 この API を利用するためには GitHub のアクセストークンが必要となりますが、特に何も設定していないため 401 になったと考えられます。

そこで、Renovate の設定ドキュメント を auth などで検索してみると、 ホストごとにトークンを記載することが可能 なことが分かりました。Renovate の設定ファイルに hostRules を追加してみます。

renovate.json

"hostRules": [
    {
        "matchHost": "api.github.com",
        "token": "***"
    }
]

また、設定が有効になっているかを確認するため、printConfig オプションも有効にしてみます。

renovate.json

"printConfig": true

実行してみると以下のような内部設定がログに表示され、有効となっていることが分かります。

 "hostRules": [
 {"timeout": 60000},
 {
   "matchHost": "api.github.com",
   "token": "***********",
   "resolvedHost": "api.github.com"
 },

この方法でも確かに動作はするのですが、Renovate はリポジトリ上に存在する設定ファイルを見に行くため、アクセストークンがリポジトリ上に登録されることになり、これはよくありません。
renovate.jsonhostRules を書かない方法を探す必要があります。

hostRules を環境変数で定義

もう少し調べてみると 環境変数で hostRules が設定可能 であることが分かります。

環境変数を規定のフォーマット (dataSource_matchHost_fieldName) に従い値を設定することで、 設定ファイルに記載したのと同様の効果が得られます。

デバッグログに "datasource": "go" と出力されていたので

export GO_API_GITHUB_COM_TOKEN=xxx

トークンを設定した上で、--detectHostRulesFromEnv true をつけて実行してみますが、これでも同様に 401 となってしまいました。

内部設定の出力は以下のようになっています。 うまくいった時との違いは、データソース (=hostType) が明示的に指定されてしまっている点です。

"hostRules": [
{"timeout": 60000},
{
  "hostType": "go",
  "matchHost": "api.github.com",
  "token": "***********",
  "resolvedHost": "api.github.com"
},

hostRules をグローバルに登録する

それではデータソースを指定しない(=グローバルに登録する)方法はあるのかを探してみますが、ドキュメントには記載がありません。 そこで、環境変数から設定をしているコードを読みに行きます。

このパース処理 を見ると、事前定義されたデータソース ID から始まる環境変数を取得し、内部設定に登録していることが分かります。
ということは、データソース ID が指定されていないと環境変数からパースされないため、グローバルに登録することはできないということが分かります。

const datasources = new Set(getDatasourceList());
...

for (const envName of Object.keys(env).sort()) {
  const splitEnv = envName.toLowerCase().replace(/__/g, '-').split('_');
  const hostType = splitEnv.shift()!;
  if (datasources.has(hostType)) {
      // パースした結果を追加する処理
  }
}

エラーとなるデータソースを特定する

グローバルに登録することが出来ないなら、必要なデータソース ID を特定して設定すれば良いはずです。
データソース ID の一覧は ここ から取ってきているので、それっぽいデータソースに当たりをつけて、これを環境変数に設定して試してみます。

api.ts

api.set(GithubReleasesDatasource.id, new GithubReleasesDatasource());
api.set(GithubTagsDatasource.id, new GithubTagsDatasource());

環境変数に設定

export GITHUB__RELEASES_API_GITHUB_COM=xxx
export GITHUB__TAGS_API_GITHUB_COM=xxx

内部設定は以下のようになり、401 は回避することが出来ました。
(なお、実際には github-tags の設定のみで依存関係の情報を取得可能でした)

"hostRules": [
  {"timeout": 60000},
  {
    "hostType": "github-releases",
    "matchHost": "api.github.com",
    "token": "***********",
    "resolvedHost": "api.github.com"
  },
  {
    "hostType": "github-tags",
    "matchHost": "api.github.com",
    "token": "***********",
    "resolvedHost": "api.github.com"
  },

リリースノートの取得に失敗

依存関係の情報は取得できましたが、今度はリリースノートが取得できません。
ログには以下の WARNING が表示されています。

WARN: No github.com token has been configured. Skipping release notes retrieval (repository=xxx/yyy, baseBranch=main, branch=renovate/go-module-dependencies)
      "manager": "gomod",
      "depName": "github.com/aws/aws-cdk-go/awscdk/v2",
      "sourceUrl": "https://github.com/aws/aws-cdk-go"

エラーメッセージでコードを grep してみるとこの処理hostType: githubhostRules を検索し、トークンが存在しない場合にエラーメッセージを表示するようになっていました。

const { token } = hostRules.find({
  hostType: 'github',
  url,
});
if (!token) {
  ...
    logger.warn(
      { manager, depName, sourceUrl },
      'No github.com token has been configured. Skipping release notes retrieval'
    );
  ...
}

しかし、hostType: github はデータソース ID の一覧に存在しないため、こちらも環境変数で設定することができません。

解決

途方に暮れてドキュメントを読みなおしていたところ、GITHUB_COM_TOKEN なる環境変数を指定できることが分かり、上記の hostRules などの設定は全て不要であっさりと正常に動作してくれました。

it's important to also configure the environment variable GITHUB_COM_TOKEN ...
This account can actually be any account on GitHub, ...

蛇足

実際の運用に合わせて GitLab CI/CD で Renovate を実行するような手順を紹介していますが、実験には Local Development を参考にローカルのコンテナで実行しています。
また、/tmp/renovate をキャッシュに利用しているのに気づかず、設定を変えても認証の挙動が変わるときと変わらないときがあり、より深みにハマっていたことを付け加えておきます。

終わりに

結局のところ、ドキュメントをしっかり読め、という教訓を思い出す形となりました。
この記事が同じような環境でハマった人の助けになることを願っています。
最後までご覧いただきありがとうございました。

We're hiring!

rarejob-tech.co.jp