LoginSignup
3
0

More than 1 year has passed since last update.

CircleCIの設定ファイルを分割する第三の方法

Posted at

概要

業務でCircleCIを使うことになったのだが、設定ファイルをいい感じに分割する方法が見つからなかったので自作したという話。前置きに興味がない人はYAML Bundlerの紹介まで読み飛ばしてOK。

問題提起

CircleCIの設定はconfig.ymlに記述するのだが、モノレポで開発している場合は肥大化しがち。このような場合にはDynamic Configurationという仕組みが使える。このDynamic Configurationというのは以下のような二段構えになっていて、

  1. config.ymlの記述に従い、第二段階用の設定ファイル(ファイル名は任意だがこの記事ではmain.yml)の生成・パラメータの受け渡しを行う
  2. 第一段階で生成した設定ファイルとパラメータに従って必要な処理を実行

第二段階用の設定ファイルは任意の方法で生成してよいから、好きなだけファイルを分割しておいてこの段階で統合すればよいという寸法。ただし任意の方法といっても、以下の二つが定番パターンのようだ。それぞれのメリット・デメリットを挙げる。

ciercleci config pack

  • メリット
    • circleci/circleci-cliなどのdockerイメージにインストール済みのコマンドなので、CIの中で使いやすい1
  • デメリット
    • FYAMLという規格に従ってファイルを分割するのだが、自由度は低め2

yq

  • メリット
    • cimg/baseなどのdockerイメージにインストール済みのコマンドなので、CIの中で使いやすい
  • デメリット
    • 自由度は高いがyqコマンド自体に習熟する必要がある

YAML Bundlerの紹介

この記事では第三の選択肢としてYaml Bundlerというorbを紹介する。これを使うとYAMLファイルの中で!includeというタグを使い、他のYAMLファイルの内容を取り込むことができる。YAMLの書き方が分かる人ならすぐ使えるし、自由度もそれなりに高いはず。

使い方

go・pyというディレクトリでそれぞれアプリケーションを開発中のモノレポを想定する。YAML以外のコードもここで見られる。

.
├── .circleci
│  ├── config.yml
│  ├── main.yml
│  ├── go.yml
│  └── py.yml
├── go
│  ├── go.mod
│  ├── main.go
│  └── main_test.go
└── py
   ├── main.py
   └── test.py

config.ymlはこんな感じ。yaml-bundler/bundleが!includeタグを処理するcommand。

.circleci/config.yml
version: 2.1
setup: true
orbs:
  yaml-bundler: dr666m1/yaml-bundler@0.0.5
  continuation: circleci/continuation@0.3.1
jobs:
  setup:
    executor: yaml-bundler/default
    steps:
      - checkout
      - yaml-bundler/bundle:  # !includeタグを処理して上書き
          filepath: .circleci/main.yml
      - continuation/continue:
          configuration_path: .circleci/main.yml
workflows:
  setup:
    jobs:
      - setup

main.ymlはこんな感じ。filepathとjsonpath3を指定して他のYAMLファイルを読み込んでいる。

.circleci/main.yml
version: 2.1
jobs: !include
  - filepath: ./go.yml
    jsonpath: $.jobs
  - filepath: ./py.yml
    jsonpath: $.jobs
workflows: !include
  - filepath: ./go.yml
    jsonpath: $.workflows
  - filepath: ./py.yml
    jsonpath: $.workflows

取り込まれる側のgo.yml py.ymlは以下の通り。YAMLファイルとして有効な形式であれば、どんなファイルでも取り込み可能4

.circleci/go.yml
jobs:
  go-test:
    docker:
      - image: cimg/go:1.18
    steps:
      - checkout
      - run:
          working_directory: go
          command: go test
workflows:
  go-test:
    jobs:
      - go-test
.circleci/py.yml
jobs:
  py-test:
    docker:
      - image: cimg/python:3.11
    steps:
      - checkout
      - run:
          command: python -m unittest ./py/test.py
workflows:
  py-test:
    jobs:
      - py-test

最終的に!includeタグの処理が完了したmain.ymlはこんな感じ。

.circleci/main.yml
version: 2.1
jobs:
  go-test:
    docker:
      - image: cimg/go:1.18
    steps:
      - checkout
      - run:
          working_directory: go
          command: go test
  py-test:
    docker:
      - image: cimg/python:3.11
    steps:
      - checkout
      - run:
          command: python -m unittest ./py/test.py
workflows:
  go-test:
    jobs:
      - go-test
  py-test:
    jobs:
      - py-test

基本的な使い方は以上!
差分があるディレクトリに応じてworkflowを実行する応用例もあった方がよい気がするから、ここにコードを置いておく。

また、ここまでで触れられなかった!includeタグの詳しい使い方も折り畳み部分に書いておく。

!includeタグの詳しい使い方
# main.yml (before)
version: !include # filepath・jsonpathを指定する最も基本的な使い方
  filepath: ./constants.yml
  jsonpath: '$.version'
executors: !include ./executors.yml # ファイル全体を読み込む場合は一行で書ける
jobs:
  main-job:
    executor: my-executor
    # 配列で指定すると結果が統合される(配列同士だと配列になる)
    steps: !include
      - filepath: ./steps1.yml
      - filepath: ./steps2.yml
  sub-job:
    executor: my-executor
    steps:
      - run: echo 'this is only step of sub-job'
# 配列で指定すると結果が統合される(map同士だとmapになる)
workflows: !include
  - filepath: ./main-workflow.yml
  - filepath: ./sub-workflow.yml

---
# constants.yml
version: 2.1

---
# executors.yml
my-executor:
  docker:
    - image: cimg/base:current

---
# steps1.yml
- run: echo "this is first step of steps1.yml"
- run: echo "this is second step of steps1.yml"

---
# steps2.yml
- run: echo "this is only step of steps2.yml"

---
# main-workflow.yml
main-workflow:
  jobs:
    - main-job

---
# sub-workflow.yml
sub-workflow:
  jobs:
    - sub-job

---
# main.yml (after)
version: 2.1
jobs:
  main-job:
    executor: my-executor
    # 配列の配列ではなくフラットな配列になる
    steps:
      - run: echo "this is first step of steps1.yml"
      - run: echo "this is second step of steps1.yml"
      - run: echo "this is only step of steps2.yml"
  sub-job:
    executor: my-executor
    steps:
      - run: echo "this is only step of sub-job"
workflows:
  main-workflow:
    jobs:
      - main-job
  sub-workflow:
    jobs:
      - sub-job

最後に

最後まで読んだけど期待していたものと違った、という方はこちらも読んでみてください。
自分より先に素敵なorbを作っていた方の記事です。

  1. circleci/circleci-cli orbを使うのが公式推奨の方法だと思う。

  2. たとえば単一のリポジトリで複数のアプリケーションを開発しているときに、app1のcommands・jobs・workflowsをapp1.ymlというファイルで管理したい、という要望には応えられないと思われ。

  3. jsonpath-ngというパッケージを利用しているので、詳しい説明はそちらを見るとよさげ。

  4. 取り込みにはPyYAMLというパッケージを活用している。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0