前編はこちら
CUE で YAML をマージするときのルールを自分の言葉で説明すると以下の通りだった
- YAML を
YAML の Leaf までのパス: 値
と捉えたときに、それらのすべての Leaf が任意の順番でマージされる - ただし、その Leaf に対するスカラー値またはシーケンス値がひとつに決まらなければエラーになる
今回は、どうしてそうなるのか?について簡単にメモを残しておこうと思う
いったん CUE 形式に変換される
YAML を マージするときに僕が使っているのは、こういうコマンド
❯ cue export a.yml b.yml --out yaml
流れはこう
コマンドの最後につけてる --out yaml
をはずすと、マージされたものが CUE 形式のまま出力される
❯ cue export a.yml b.yml { "sample": { "foo": "foo value", "bar": "bar value" } }
ここで少し CUE について見てみる
CUE は型と値を区別せずに扱う
CUE には型と値を区別せずに扱うという特徴があり、通常なら値を書く部分に、型や条件を書くこともできる
foo: { name: string count: < 10 }
この例は
foo: name:
の値は文字列でなければならないfoo: count:
の値は10未満の数値でなければならない
という制約を表している。そして、foo: name:
と foo: count:
には、これらの条件を満たす値しか設定できない
CUE では同じ要素を複数回定義することができて、その場合はお互いに矛盾しないかの検査が行われる
なので、こういう定義は条件が矛盾しないので OK で
foo: { name: string count: < 10 } foo: { name: "orange" count: 2 }
値も指定してあるので export するとこうなる
❯ cue export foo.cue { "foo": { "name": "orange", "count": 2 } }
でも、たとえば、10以上の値を count
に指定すると矛盾するのでエラーになる
foo: { name: string count: < 10 } foo: { name: "orange" count: 10 }
❯ cue export foo.cue foo.count: invalid value 10 (out of bound <10): ./foo.cue:3:9 ./foo.cue:8:9
だから、CUE を使うと、条件を指定したスキーマを用意しておいて、実際の値がそれらの条件を満たしているかを検査することができる
そして、ひとつのパスに対して、複数の異なる値が指定されると、それらの値が矛盾してしまうので、エラーになる
foo: { name: "orange" } foo: { name: "apple" }
❯ cue export foo.cue foo.name: conflicting values "apple" and "orange": ./foo.cue:2:8 ./foo.cue:6:8
CUE の仕様はなんかもっと色々あって、Lattice 束 (そく)とか出てきて、僕はよくわかってない!のだけど、YAML のことを考えるのには、もうこれだけ分かってたら十分
ちなみに、書いてある順番は関係ないので、先に値があって、その次に条件が書いてあっても動きは同じになる
YAML には値しかない
YAML の値の部分に指定されたものは、値として扱われて、型にはならないので、こう書いても↓
foo: name: string
文字列という型にはならずに "string" という値が指定されていることになる。だから、↓のような YAML を CUE に変換すると
foo: name: orange count: 5
こうなって
{ "foo": { "name": "orange", "count": 5 } }
もう、これで foo: name:
と foo: count:
の値は決定してしまい、これ以外の値を同じパスに指定することはできない。だから、↓これは CUE に変換するときにエラーになる
foo: name: orange count: 5 --- foo: count: 6
❯ cue export a.yml foo.count: conflicting values 6 and 5: ./a.yml:3:11 ./a.yml:6:11
という CUE の仕様から、YAML をマージするときは、こういう動きになるのだった
- YAML を
YAML の Leaf までのパス: 値
と捉えたときに、それらのすべての Leaf が任意の順番でマージされる - ただし、その Leaf に対するスカラー値またはシーケンス値がひとつに決まらなければエラーになる
おしまい
おまけ:CUE の機能を利用してみる
読み込むファイルは YAML と CUE を混ぜても別に問題ないので、YAML をマージしつつ、CUE で要素を差し込むなんてこともできる
a.yml
version: 2.1 jobs: service1-say-hello: steps: - checkout - run: name: "Say hello" command: "echo Hello, World!1"
b.yml
version: 2.1 jobs: service2-say-hello: steps: - checkout - run: name: "Say hello" command: "echo Hello, World!2"
これに CUE ファイルとしてこんな c.cue を混ぜると。。。
jobs: { [string]: { docker: [{image: "cimg/base:stable"}] } }
すべての job に差し込むことができる
❯ cue export a.yml b.yml c.cue --out yaml version: 2.1 jobs: service1-say-hello: steps: - checkout - run: name: Say hello command: echo Hello, World!1 docker: - image: cimg/base:stable service2-say-hello: steps: - checkout - run: name: Say hello command: echo Hello, World!2 docker: - image: cimg/base:stable
面白いね
でも、んー、僕はたぶん使わないかなぁ。YAML 同士のマージならまだ雰囲気で使えるけど、CUE の機能を使うとなると CUE の仕様を把握する必要があるし、最初に書いた人は大丈夫だけど、そのうち誰も分からないけど動いてる・・・ってなりそうだから(じゃあこんな機能を紹介しなくていいのでは!?
これで一連の CircleCI の設定ファイル分割の話と、CUE の話はおしまい!楽しかったー!
水曜日の勉強会で
このあたりのことを実際にお見せしますー!よかったら来てね