CircleCI の大きな config.yml を分割しちゃおう!

config.yml を分割できる Orb を作ったよー

Split Config Orb という Orb を作った

こないだからちょこちょこ試してたやつを Orb にしたのだ。この Orb を使うと config.yml を分割できる。Orb にしたから簡単に使えるよー!

config.yml が大きいから分割したいー!って場合や、モノレポで複数サービスを入れてるから各サービスごとに config.yml を書きたい!って場合に使えるかなぁって思ってる

もしちょっとでも興味があったら、実際に使ってみてフィードバックをいただけると嬉しいです。フィードバックを元にして機能をブラッシュアップできるといいなと思ってます!GitHub の Issue でも Twitter でメンションくれても OK です!

じゃ、さっそく使ってみようー!

簡単に使えるよー

こんな風に config.yml を書いておくだけ!

version: 2.1

setup: true

orbs:
  split-config: bufferings/split-config@0.1.0

workflows:
  generate-config:
    jobs:
      - split-config/generate-config:
          find-config-regex: .*/\.circleci/config\.yml

これで CircleCI が起動したときに、ここに書いてある正規表現にマッチする設定ファイルをかき集めてきて、ひとつにマージしてから、パイプラインを実行するのだ!

あ、その前に、いっこだけやっとくことがあるんだった!

ダイナミックコンフィグを有効化してね

マージした設定ファイルを使ってパイプラインを起動するのに ダイナミックコンフィグ という機能を使ってるのだけど、デフォルトではオフになってるので この手順にしたがって オンにしといてください!

おわりー!これだけー!以下は、他にこんなことができるよーとかの紹介

集めてくるファイルの指定方法

ファイルを集めてくるのには、↑の例みたいに正規表現で指定することもできるし、「そんなに変更されるわけじゃないから固定リストで指定したいなぁ」って人は↓こんな感じでも大丈夫

version: 2.1

setup: true

orbs:
  # Please specify the latest version
  split-config: bufferings/split-config@1.2.3

workflows:
  generate-config:
    jobs:
      - split-config/generate-config:
          fixed-config-paths: |
            ./common/.circleci/config.yml
            ./service1/.circleci/config.yml
            ./service2/.circleci/config.yml
            ./service3/.circleci/config.yml

パスは、リポジトリのルートからの相対パスでお願いします!

path-filtering Orb との組み合わせ

path-filtering という Orb を使うと、例えば、モノレポで複数のサービスがひとつのリポジトリに入ってて「変更があったサービスのワークフローだけ実行したいのだ!」みたいなことが実現できる。この path-filtering と組み合わせて使う場合はこんな感じ

version: 2.1

setup: true

orbs:
  # Please specify the latest version
  split-config: bufferings/split-config@1.2.3
  path-filtering: circleci/path-filtering@1.2.3

workflows:
  generate-config:
    jobs:
      - split-config/generate-config:
          find-config-regex: .*/\.circleci/.*\.yml
          generated-config-path: /tmp/generated_config.yml
          continuation: false
          post-steps:
            - persist_to_workspace:
                root: /tmp
                paths:
                  - generated_config.yml
      - path-filtering/filter:
          workspace_path: /tmp
          config-path: /tmp/generated_config.yml
          mapping: |
            service1/.* build-service1 true
            service2/.* build-service2 true
          requires:
            - split-config/generate-config

やってるのは、マージした設定ファイルを path-filtering Orb に渡すってこと。ジョブ間の受け渡しになるので ワークスペース を使って渡してる

どうやってマージしてるの?

マージには CUE (https://cuelang.org/) を使ってる

CUE は設定用の言語みたいな感じで、型の定義だったり、モジュール化だったりと、色んなことができるんだけど、その中のほんとに一部の機能を利用してマージしてるのだ。使ってる機能は

  • YAML ファイルを読み込んで CUE 形式に変換する
  • 複数の CUE 形式のデータをマージする
  • CUE 形式のデータを YAML 形式で出力する

というもの。これで、複数の YAML を取り込んでマージされた YAML を出力してる

例えば、こんな YAML

version: 2.1

jobs:
  service1-say-hello:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - run:
          name: "Say hello"
          command: "echo Hello, World!1"

workflows:
  service1-say-hello-workflow:
    jobs:
      - service1-say-hello

こんな YAML

version: 2.1

jobs:
  service2-say-hello:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - run:
          name: "Say hello"
          command: "echo Hello, World!1"

workflows:
  service2-say-hello-workflow:
    jobs:
      - service2-say-hello

マージすると、こうなる↓かしこいー!

❯ cue export service1.yml service2.yml --out yaml
version: 2.1
jobs:
  service1-say-hello:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - run:
          name: Say hello
          command: echo Hello, World!1
  service2-say-hello:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - run:
          name: Say hello
          command: echo Hello, World!1
workflows:
  service1-say-hello-workflow:
    jobs:
      - service1-say-hello
  service2-say-hello-workflow:
    jobs:
      - service2-say-hello

この CUE のマージの機能がかなり面白いので、興味ある方は、ぜひ CUE のドキュメントをチェックしてみてください!僕も、この辺りはもう少し細かく見ていきたいなって思ってる

制約

この Orb には、その仕組みに起因する制約があるので、注意してほしい

制約1: Workflow や Job の名前がかぶらないようにしないといけない

最終的には1つの YAML ファイルとして書き出す仕組みなので、名前が重複しちゃだめって制約がある

制約2: YAML のアンカーとエイリアスは、ひとつの YAML ファイル内で完結してる必要がある

CUE が YAML を読み込むときにアンカーとエイリアスを解析してしまうので、複数ファイルにまたがったアンカー・エイリアスは利用できない なので、あるファイルでアンカーを定義しておいて、別のファイルでエイリアスを使う、みたいなことはできない

興味がある人向け

この Orb は裏側で CUE を使ってはいるんだけど「使う人はできるだけ YAML のことしか考えなくていいようにしたい!」って思いながら作った

なので、YAML のことだけを考えてもらったら大丈夫なんだけど、実は CUE ファイルを渡しても読み込んでくれて、最終的には YAML で出力される。だから、実は CircleCI の設定を CUE で定義することもできてしまうのだー。こんなふうに↓

package config

version: "2.1"

jobs: {
    "service1-say-hello": {
        docker: [
            {image: "cimg/base:stable"},
        ]
        steps: [
            "checkout",
            {
                run: {
                    name:    "Say hello"
                    command: "echo Hello, World!1"
                }
            },
        ]
    }
}

workflows: {
    "service1-say-hello-workflow":
    {
        jobs: [
            "common-say-hello",
            "service1-say-hello",
        ]
    }
}

で、config.ymlcue ファイルを読み込むようにしてあげれば OK

version: 2.1

setup: true

orbs:
  # Please specify the latest version
  path-filtering: circleci/path-filtering@1.2.3

workflows:
  generate-config:
    jobs:
      - split-config/generate-config:
          find-config-regex: .*/\.circleci/config\.cue

CUE の機能を使って何か面白いことできないかなぁ?ってのにちょっとだけ興味あるから、これも遊んでみようかなぁって思ってる

Working Example

って、ここまで書いたようなことを、GitHub の README に書いてるからよかったらチェックしてみてくださいー

で、それぞれの例のところに、実際にそれを使った Example Project へのリンクがあるから、どんな感じか見てみたい方はどうぞ!

使ってみてー!

使ってみてくれたら嬉しいー!フィードバックくれたらもっと嬉しいー!

大阪のミートアップで喋りますー

来週の 7/27(水) に大阪でオフラインミートアップを開催する予定なんですけど、そこで、この Orb の紹介をしようと思いますー!よかったら来てください!(ただ、いま COVID-19 の感染者がどんどん増えている状況なので、中の人とよく相談して考えたいと思ってます

circleci.connpass.com