😸

GitLab CIで社内複数プロジェクトをrenovateする

2022/12/12に公開

この記事は GitLab Advent Calendar 2022 の 12 日目の記事です。


フォルシア株式会社エンジニアの籏野です。
先日、社内にホスティングしている GitLab 上のプロジェクトのライブラリ更新を目的にrenovateを導入しました。
複数のプロジェクトで手軽に導入できるように工夫をしたので紹介します。

renovate とは

詳細は検索すればかなり出てくるので省略しますが、プロジェクトが利用するライブラリの更新を自動化するためのツールになります。
GitHub であれば GitHub App によって renovate を利用できるため、かなり気軽に導入できます。

近年、フォルシアではメインの開発に Node.js を利用しており、npm モジュールの利用も盛んです。
npm モジュールの開発・更新はかなりのペースで行われています。
renovate を導入することでライブラリの更新があれば自動でマージリクエストを作成してくれるため、最新のバージョンへの追従がやりやすくなります。

renovate 導入に向けて

GitHub であれば容易に renovate を導入できますが、フォルシアでは社内のサーバー上に GitLab をホスティングして利用しているため、renovate を導入・運用していくための仕組みを自分たちで用意する必要がありました。
今回は以下のような状態を目標として renovate 導入を試みました。

  • 社内複数プロジェクトへの導入が容易であること(ブラウザ上での設定 1 つに収まるくらいが理想)
  • 数分おきに renovate を実行して、各プロジェクトの依存モジュールの更新をチェックすること

2 つ目に関しては GitLab CI のスケジュール機能を利用することですぐに実現可能です。
1 つ目の目標達成に向けて取り組んだ内容を紹介していきます。

GitLab CI で renovate を実行する

GitLab CI での renovate 実行は公式のドキュメントを参考にしました。
今回は社内複数プロジェクトへの renovate 導入を目的にしていたので、Parallel Renovate jobs per projectの項を参照しています。

基本的には記載の通りに gitlab-ci.yml と template/.gitlab-ci.yml を用意すればよかったです。
renovate が実行できる Docker イメージが配布されており、これを利用したジョブが定義されています。
処理の流れは以下のとおりです。

  • renovate 実行対象のプロジェクトを取得する
    • RENOVATE_AUTODISCOVER を true にすることで、実行対象のプロジェクトを自動的に探索
    • RENOVATE_AUTODISCOVER_FILTER を設定し、実行対象のプロジェクトを絞る
  • 取得したプロジェクトのリストを template/.gitlab-ci.yml に埋め込むことで、renovate の実行ジョブを複数動的に生成する
  • 生成したジョブを並列実行

概ね上記の流れで問題ありませんでしたが、実行対象プロジェクトの探索について課題が上がってきました。
フォルシアではアプリごとにグループを作成しているため、複数のプロジェクトに renovate を導入しようとすると RENOVATE_AUTODISCOVER_FILTER がどんどん長く複雑になってしまうことが考えられました。
また、renovate を導入しようとしたら gitlab-ci.yml を編集してリポジトリに登録しないといけないのも面倒です。
そこで今回は renovate 実行対象のプロジェクトを取得する機能を自分たちで実装することにしました。

renovate 実行対象プロジェクトの取得

renovate 実行対象を取得するまでの流れは以下のようにしました。

  • renovate グループを作成する
  • 作成したグループをrenovate を実行したいプロジェクトに招待し、Shared Project とする。
  • renovate グループ配下の全プロジェクト(Shared Project を含む)に対して renovate を実行する

※renovate グループの Shared Project に設定しただけで、 RENOVATE_AUTODISCOVER_FILTER による探索対象になってくれると嬉しかったのですが、そううまくは行きませんでした。

「renovate グループ配下の全プロジェクト」を取得する処理は TypeScript で実装しました。
@gitbeaker/nodeを利用すると GitLab API へのリクエストも簡単に実装できます。

import { Gitlab, Types } from "@gitbeaker/node";
import fs from "fs-extra";

const main = async () => {
  const config = {
    // GitLabのホスト名
    host: process.env["GITLAB_HOST"],
    // APIリクエストに必要なトークン
    token: process.env["GITLAB_TOKEN"],
    // 実行対象のグループ
    targetGroup: process.env["AUTO_DISCOVERY_TARGET_GROUP"],
    // 対象プロジェクト一覧の出力先パス
    writeDiscoveredRepos: process.env["WRITE_DISCOVERED_REPOS"],
  };

  const gitlabApi = new Gitlab({
    host: config.host,
    token: config.token,
  });

  // 探索対象グループの取得
  const groups = (await gitlabApi.Groups.search(
    config.targetGroup
  )) as unknown as Types.GroupSchema[];
  const groupId = groups.find((group) => group.name === config.targetGroup)?.id;
  if (!groupId) {
    throw new Error(`Group '${config.targetGroup}' was not found.`);
  }

  console.log(
    `Search projects in '${config.targetGroup}' group.(ID: ${groupId})`
  );

  // renovate実行対象プロジェクトの取得
  const projects = await gitlabApi.Groups.projects(groupId);
  const discovered = projects.map((project) => project.path_with_namespace);
  console.log(`INFO: Autodiscovered repositories
  "length": ${discovered.length},
  "repositories":
    ${discovered.map((name) => `- ${name}`).join("\n\t\t")}`);

  // 実行対象プロジェクトをJSONファイルに出力
  const content = JSON.stringify(discovered);
  await fs.writeFile(config.writeDiscoveredRepos, content);
  console.log(
    `INFO: Written discovered repositories to ${writeDiscoveredRepos}`
  );
};

main();

renovate での実行対象探索と同様に、対象プロジェクトを JSON ファイルに書き出しています。
このスクリプトを利用する形で gitlab-ci.yml を作成すると以下のようになりました。
※include 部分は、renovate-runner 以下のファイルを参照できるよう各人の環境に合わせて書き換える必要があります。

include:
  - project: "renovate-bot/renovate-runner"
    file: "/templates/renovate-dind.gitlab-ci.yml"
    ref: v8.81.6

renovate:
  stage: build
  cache:
    key: "${CI_COMMIT_REF_SLUG}"
    paths:
      - .npm/
  variables:
    GITLAB_HOST: ${CI_SERVER_URL}
    GITLAB_TOKEN: ${RENOVATE_TOKEN}
    AUTO_DISCOVERY_TARGET_GROUP: ${CI_PROJECT_ROOT_NAMESPACE}
    WRITE_DISCOVERED_REPOS: template/renovate-repos.json
  script:
    - npm ci --cache .npm --prefer-offline
    # renovate 実行対象プロジェクトの探索を行う
    - npx ts-node src/index.ts
    - sed "s~###RENOVATE_REPOS###~$(cat ${WRITE_DISCOVERED_REPOS})~" template/.gitlab-ci.yml > .gitlab-renovate-repos.yml
  artifacts:
    paths:
      - ${WRITE_DISCOVERED_REPOS}
      - .gitlab-renovate-repos.yml

renovate:repos:
  stage: deploy
  needs:
    - renovate
  inherit:
    variables: false
  trigger:
    include:
      - job: renovate
        artifact: .gitlab-renovate-repos.yml

上記 yml ファイル作成後、renovate 実行に必要なトークンを CI/CD の Variables として登録します。
必須なのはRENOVATE_TOKENで、GitLab ユーザーのプロジェクト用のトークンを発行し登録しておきます。
※read/write、api の実行権限が必要です。
その他 renovate で設定可能な項目は公式のドキュメントを参照してください。

最後に CI のスケジュール実行を設定することで理想の動きを実現できました。
画像のように、renovate 実行対象プロジェクトを探索し、子プロセスが 4 個生成されていることが確認できます。
他プロジェクトチームへの導入もグループ追加だけで終わるので好評いただいています。

gitlab-ci

最後に

今回の導入でDownstream pipelinesの存在を初めて知りましたが、動的に CI ジョブを生成できたり並列実行ができたりとかなり便利に感じました。
GitLab CI には他にもいろいろな便利機能がありますので、これを読んだ皆さんもぜひ色々試してみてください!

FORCIA Tech Blog

Discussion