Jsonnet で YAML を生成

JsonnetJSON を拡張したような DSL で、変数や関数を定義できたり内包表記が使えたりと、それなりに便利になっています。

JSON 以外にも YAML や INI ファイルなども生成できるようになっているようなので、YAML の生成を試してみました。

なお、Jsonnet の事は Kubernetesksonnet で知ったのですが、KubernetesYAML に関しては JavaScriptPython 等のプログラミング言語を使う Pulumi の方が(現時点では)良さそうな気がしています。

はじめに

今回は Jsonnet ファイルの処理に Go 言語で実装された下記ツールを使います。

インストールは go get するだけです。

インストール
> go get github.com/google/go-jsonnet/cmd/jsonnet

これで jsonnet コマンドが使えるようになります。

> jsonnet --version

Jsonnet commandline interpreter v0.13.0

JSON 生成

まずは JSON を生成してみます。 以下のような機能を使った jsonnet ファイルを用意してみました。

  • local で変数・関数を定義
  • self でカレントのオブジェクトを参照
  • $ でルートのオブジェクトを参照
  • std.関数名 で標準ライブラリの関数を呼び出す
  • []{} の内包表記
sample.jsonnet
local items = [ { id: i, name: 'item-' + i } for i in std.range(1, 3) ];
local last(xs) = xs[std.length(xs) - 1];

{
    base_items: items,
    products: [ { name: it.name, qty: 1 } for it in items ],
    first: self.products[0],
    last: last(self.products),
    ref: {
        local ps = $['products'],
        products_md5: { [p.name]: std.md5(p.name) for p in ps }
    },
}

jsonnet コマンドで JSON 変換すると以下のようになります。 要素の並びは名前順にソートされるようです。

JSON 生成結果
> jsonnet sample.jsonnet

{
   "base_items": [
      {
         "id": 1,
         "name": "item-1"
      },
      {
         "id": 2,
         "name": "item-2"
      },
      {
         "id": 3,
         "name": "item-3"
      }
   ],
   "first": {
      "name": "item-1",
      "qty": 1
   },
   "last": {
      "name": "item-3",
      "qty": 1
   },
   "products": [
      {
         "name": "item-1",
         "qty": 1
      },
      {
         "name": "item-2",
         "qty": 1
      },
      {
         "name": "item-3",
         "qty": 1
      }
   ],
   "ref": {
      "products_md5": {
         "item-1": "761ff52b8e6dd373fdf291a1a70df20c",
         "item-2": "38d3f385ea8d8bb2dcfc759ac85af6ef",
         "item-3": "65910a0fd76f69a08e6faa142384f327"
      }
   }
}

YAML 生成

YAML で出力したい場合は std.manifestYamlDoc() を使うだけです。

sample_yaml.jsonnet
local items = [ { id: i, name: 'item-' + i } for i in std.range(1, 3) ];
local last(xs) = xs[std.length(xs) - 1];

std.manifestYamlDoc({
    base_items: items,
    products: [ { name: it.name, qty: 1 } for it in items ],
    first: self.products[0],
    last: last(self.products),
    ref: {
        local ps = $['products'],
        products_md5: { [p.name]: std.md5(p.name) for p in ps }
    },
})

YAML を出力する場合は、-S オプションを指定して jsonnet コマンドを実行します。

YAML 生成結果
> jsonnet -S sample_yaml.jsonnet

"base_items":
- "id": 1
  "name": "item-1"
- "id": 2
  "name": "item-2"
- "id": 3
  "name": "item-3"
"first":
  "name": "item-1"
  "qty": 1
"last":
  "name": "item-3"
  "qty": 1
"products":
- "name": "item-1"
  "qty": 1
- "name": "item-2"
  "qty": 1
- "name": "item-3"
  "qty": 1
"ref":
  "products_md5":
    "item-1": "761ff52b8e6dd373fdf291a1a70df20c"
    "item-2": "38d3f385ea8d8bb2dcfc759ac85af6ef"
    "item-3": "65910a0fd76f69a08e6faa142384f327"

-S オプションを指定しないとエスケープされた文字列として出力されてしまいます。

YAML 生成結果(-S を指定しなかった場合)
> jsonnet sample_yaml.jsonnet

"\"base_items\":\n- \"id\": 1\n  \"name\": \"item-1\"\n- \"id\": 2\n  \"name\": \"item-2\"\n- \"id\": 3\n  \"name\": \"item-3\"\n\"first\":\n  \"name\": \"item-1\"\n  \"qty\": 1\n\"last\":\n  \"name\": \"item-3\"\n  \"qty\": 1\n\"products\":\n- \"name\": \"item-1\"\n  \"qty\": 1\n- \"name\": \"item-2\"\n  \"qty\": 1\n- \"name\": \"item-3\"\n  \"qty\": 1\n\"ref\":\n  \"products_md5\":\n    \"item-1\": \"761ff52b8e6dd373fdf291a1a70df20c\"\n    \"item-2\": \"38d3f385ea8d8bb2dcfc759ac85af6ef\"\n    \"item-3\": \"65910a0fd76f69a08e6faa142384f327\""