CircleCIでformat、lint、buildが通るかをチェックしたい

2024/01/11に公開

経緯

本業の方で、Next.JS14(この前13 → 14にバージョン上げました🙌🏻)を用いて開発、というか既存のレガシーな作りを取っ払ってNextにreplaceしちゃおうぜ計画を行っている。
その際に技術的に気になったことをメモしていく。できれば継続的に🐟

今回は、prettierのformatと、ESlintのcheck、nextのbuildを、CircleCIを使って一括checkし、一つでも通らなかったらGitHubでプルリクを作成した際にマージできないようにしたので、採用理由や実装ポイント、詰まった点などをメモしておく。

CircleCIで行う理由

これに関しては、既存のサービスにおいて、stg環境や本番環境にデプロイする時などに、CircleCIを使用していたためというのが大きい。
ただ、普段CIの管理をしているのはSREチームなので、僕は普段触る機会がない。
今回ほぼ初めて触ったのだが、不明瞭な点があってもSREチームの方にサポートいただけたので、そういう意味でも安心感があった。

huskyは?

最近のフロントエンド開発だと、huskyとlint-stagedを組み合わせて、commit時にコード整形やlintを行う方がデファクトスタンダードなのかもしれないし、その方法も検討したのだが、現状のアプリのディレクトリ構成だと厳しかったので断念した😵

実装

今回の実装のポイントは主に以下である。

  • 普段Dockerコンテナ上で開発を行っているため、CircleCiの実行環境もDockerを使用
  • Node.jsがインストールされた Docker イメージであるcimg/nodeを使用
  • feature/~ブランチ、developブランチに対してpushが行われたことをジョブのトリガーとする

実際のコードはこちら(サービス名の箇所はour-serviceとします。)

.circleci/config.yml
version: 2.1

jobs:
  build-and-format-and-lint:
    docker:
      - image: cimg/node:lts
    steps:
      - checkout
      - setup_remote_docker:
          version: 20.10.18
      - run:
          name: Install Docker Compose
          command: |
            sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
            sudo chmod +x /usr/local/bin/docker-compose
      - run:
          name: Run Shared Network
          command: |
            docker network create shared-network
      - run:
          name: Build images of services declared in docker-compose.yml
          command: docker compose build
      - run:
          name: Run Docker Compose
          command: |
            docker compose up -d
      - run:
          name: Copy src directory to container
          command: |
            docker cp ./src/. our-service:/app
      - run:
          name: Install npm dependencies
          command: |
            docker exec our-service npm install
      - run:
          name: Run npm scripts
          command: |
            docker exec our-service npm run lint
            docker exec our-service npm run lint:prettier
            docker exec our-service npm run build

workflows:
  version: 2
  push-feature-commit:
    jobs:
      - build-and-format-and-lint:
          filters:
            branches:
              only:
                - /^feature\/.*/
                - develop

主に以下を参考にして作成した。
https://circleci.com/docs/ja/docker-compose/

実装時、特に詰まったポイント

事象:npm installのrun実行時に、app配下にpackage.jsonがないと言われる

前提として、アプリのディレクトリ構成では、src配下にpackage.jsonがあり、
docker compose.ymlで、src配下はコンテナ内の/appにバインドマウントされるように設定している。
なので、アプリの方では、コンテナのapp内にpackage.jsonが存在し、これを元にnpm installなどが走るようになっているのだが、
CircleCI上のコンテナでは、何故か/app配下にpackage.jsonが存在してないことになっていた。

appへのバインドが上手くいってないのでは?
と思い、ジョブ内でdocker-compose configを実行し、docker composeの設定を確認したのだが、上手くできてそう。

なぜだ・・?

原因:Setup_remote_dockerとアプリのdockerの差異

CI上で動作させるリモートDocker環境をアクティブ化するために、setup_remote_dockerステップを追加しているのだが、どうやらこのリモートDocker環境とアプリdockerの環境差異が原因のようだった。(これに気づくのに半日かかった泣)

よく見たら公式にもそのように記載があった。

docker を setup_remote_docker と組み合わせて使用すると、docker-machine を使用して作成した場合と同様のリモートエンジンを作成できます。 ただし、このセットアップでは、ボリュームのマウントとポート転送は同じようには機能しません。 リモート Docker デーモンは、Docker CLI や Docker Compose とは異なるシステム上で動作するため、これを機能させるにはデータの移動が必要です。

解決:CI上のリモートDockerに、アプリのdockerコンテナデータを移動

公式には続きで、

マウントは通常、Docker ボリュームでコンテンツを利用可能にすることで解決できます。 docker cp を使用して、CLI ホストから Docker リモートホスト上で実行しているコンテナにデータを取得することで、Docker ボリュームにデータをロードできます。
デプロイ用の Docker イメージをビルドする場合は、この組み合わせが必要です。

と記載あったのでこれに従い、アプリのdockerコンテナデータを、CI上のdockerの/app内にcpするrunを加えることによって、正常にnpm installが走るようになった。

docker cp ./src/. our-service:/app

GitHubでブランチ保護ルールを設定

最後にGitHubのリポジトリの、
settings → Branches → Branch protection rule
で、developに対して、ciのbuild-and-format-and-lint(作成したjob名)を、Require status checks to pass before mergingに設定してあげて完成!

これで、developに向けて作成された、feature/~のプルリクは、絶対にformat、lint、buildを通さないとマージできないようにできる。

まとめと今後の展望

初めてCIの設定をしたが難しい。。
でもjobがしっかり通るようになって実用可能になった瞬間は嬉しかった😆
ただ難しい。。dockerなどについても、もっと勉強しなくてはというモチベになったいい機会だった💪🏻

インテグレーションテストが実装出来次第、
このジョブの中に実行コマンドを加えて、テストもCIでcheckするようにしたい!🔥

Discussion