🍨はじめに
GitHub Actions ワークフロー では YAML のアンカーやエイリアスが使えないため同じ設定を何度も記述しなくてはならない場合がある。
とてもつらい。設定を変更したいときには全ての箇所を修正する必要があってつらい。
そこで ytt
を使えばテンプレートファイルから YAML ドキュメントを生成できるので楽ができそう。
と思って試してみたサンプルを備忘録として残しておく。
本記事はバージョン 0.37.0 での情報。 |
GitHub では2021年8月から uses キーワードが複合ステップアクション(Composite Action)で使えるようになったので、ローカルアクションを作成するほうが簡単かもしれない。
|
使ってみた感想
- メリット
-
YAML ドキュメントのある部分を変数化して使い回すような簡単な共通化であれば、さくっと複数ファイルへの適用もできてうれしい。
Overlays がすごい便利。 - デメリット
-
複雑な共通化をしようとすると、Starlark 言語を学ぶ必要があってしんどい。
Overlays の挙動がぜんぜんわからん。ふんいきで使ってる。 - 結論
-
複数ファイルに対して簡単な共通化をしたいときにとてもうれしい。
GitHub Actions ワークフロー を複数作成する場合、同じ設定を繰り返し記述することが多いので助かる。
🚀 ytt
(YAML Templating Tool)
YAML 用のテンプレートエンジン。
ytt
は テンプレート および Data Values や Overlays ファイルを受け取って YAML ドキュメントを出力する。
-
Python っぽいプログラミング言語である Starlark 言語 を内蔵している。
-
YAML 構造を把握している。
-
環境変数やアノテーションを使うことで、設定の一部上書きができる。
-
Go 言語製なのでシングルバイナリで利用できる。使いやすい。
YAML 構造を把握しているので、テンプレートなどを書く際には YAML ドキュメントにアノテーションを付け加える形になる。
このため新たに覚えることが少ないので、さっと使えて便利。
他テンプレートエンジン(Jinja2 とか)との比較
ytt vs x に記載されている。
|
インストール
GitHub のリリースページからバイナリファイルをダウンロードして Path を通すだけ。
Go 言語製のツールはシングルバイナリで利用できるのが素敵。
コマンドライン(CLI)での実行方法
メインのテンプレートファイルだけでなく、後述する Data Values や Overlays のファイルも一緒に読み込ませる。
ytt template \
--file main.template.yml \
--file common_data.yml
ytt template \
--file path/to/template/dir \ (1)
--file-mark '**/*ignore.yml:exclude=true' (2)
1 | 指定したディレクトリ以下(サブディレクトリも含む)にある全てのファイルが読み込まれる。 |
2 | 除外したいファイルパスを指定。詳しくは File Marks を参照。 |
ytt template \
--file config.yml \
--output-files path/to/outputs/dir
📔 ytt
の文法についてのサンプル集
とりあえずよく使いそうな分だけ(実装コストが高くなる制御構文(if文やfor文)などは除外)。
ytt
を試せる online playground が公式で用意されているため、そこで試してみるといい。
YAMLドキュメント
YAML ドキュメントは1ファイルに複数ふくめることができる(--- 区切り)。よって ファイル と ドキュメント の呼び方の違いに気をつける。 |
外部の変数( Data Values )
別ファイルに記述した YAML ドキュメントの key:value
は、 Data Values として宣言すれば変数として参照することができる。
YAML ドキュメントに @data/values
アノテーションをつければ Data Values として宣言したことになる。
また、テンプレートファイルから Data Values を参照するには、ファイル先頭に @ load("@ytt:data", "data")
アノテーションをつける。
#@data/values (1)
---
hoge: hoge value (2)
foo:
fuga: fugafuga
bar: old value
1 | Data Values ファイルの宣言。 |
2 | それぞれの値を記述。 |
#@data/values
---
bar: new value (1)
#@overlay/remove (2)
hoge:
#@overlay/match missing_ok=True (3)
baz: add new key
1 | 既存の値を上書き。 |
2 | 既存の key を削除。 |
3 | 新しい key とその値を追加。 |
#@ load("@ytt:data", "data") (1)
---
var1: #@ data.values.bar (2)
var2: #@ data.values.foo.fuga
var3: #@ data.values.foo
var4: #@ data.values.baz
1 | Data Values を読み込み。 |
2 | 登録した Data Values の各値を参照。 |
ytt template \ (1)
--file template.yml \
--file var_1.yml \
--file var_2.yml
var1: new value
var2: fugafuga
var3:
fuga: fugafuga
var4: add new key
1 | テンプレートと使用する Data Values のファイルも一緒に指定する。 |
Data Values のキーについて キーは snake_case 形式が推奨(つまり ハイフン(- )は非推奨)。これは参照時に . を使った参照ができなくなるため。
|
アノテーションのスペースについて
ということらしい(詳細)。 |
JSON データを利用する
コマンドラインオプションの --data-value-file
オプションでファイル内容を文字列として読み込み、それを JSON データとしてオブジェクトに変換すればいい。
{
"version": "1.2.3",
"levels": ["info", "warn", "error"]
}
#@ load("@ytt:data", "data")
#@ load("@ytt:json", "json") (1)
#@ load("@ytt:struct", "struct") (1)
---
#@ config = struct.encode(\ (2)
#@ json.decode(data.values.config)\ (3)
#@ )
json:
version: #@ config.version
levels: #@ config.levels
1 | JSON や struct 型のモジュールを読み込み。 |
2 | プロパティを . から参照できるように dict 型の値を struct 型に変換。(末尾の \ は Starlark 言語における改行のエスケープ) |
3 | --data-value-file で読み込んだ JSON ファイルの内容(文字列)を dict 型の値として変換。 |
ytt template \
--file template.yml \
--data-value-file config=conf.json (1)
json:
version: 1.2.3
levels:
- info
- warn
- error
1 | <key>=</path/to/file> の書式。指定した key の値にファイル内容を文字列として読み込む。 |
Data Values の型定義(Data Values Schema)
version 0.35.0 で正式実装された機能で、Data Values の型定義を宣言する。
YAML ドキュメントに @data/values-schema
アノテーションをつけて宣言する。
この Data Values Schema で定義した値はデフォルト値として機能し、 Data Values に定義された値で上書きマージされる。
よって、
-
Data Values Schema による汎用的な型定義を行い、
-
実際に扱う値を Data Values で実装する
という形にするのがよさそう。
使い所として、
などに利用するとよさそう。 |
#@data/values-schema (1)
---
cache:
name: Caching
id: cache
uses: actions/cache@v2
with:
path: /tmp/cache
key: ${{ runner.os }}-caching-${{ hashFiles('.lock') }}
restore-keys: ""
1 | Data Values Schema としての宣言。 ここで定義した値はデフォルト値になる。 |
#@data/values (1)
---
cache:
name: Cache Docker Layer
with:
key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }}
restore-keys: |
${{ runner.os }}-docker-
1 | Data Values で定義した値は一緒に読み込んだ Schema の値を上書きする。 上書きしたいプロパティだけを記述すればOK。 |
#@ load("@ytt:data", "data")
---
loading: #@ data.values.cache
ytt template --file=config
loading:
name: Cache Docker Layer
id: cache
uses: actions/cache@v2
with:
path: /tmp/cache
key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }}
restore-keys: |
${{ runner.os }}-docker-
複数ファイルに分割して Schema を定義する場合は、Overlays の機能(#@overlay/match missing_ok=True )を利用する必要があるので注意。 |
Schema の拡張
Schema で定義した型に新しくプロパティを追加したい場合は、
-
その Schema ファイルを修正する
-
別の Schema ファイルを作成して Overlays による上書きをする
のどちらかを行う必要がある。
(横着して Data Values で追加しようとすると怒られる。)
#@data/values-schema
---
cache:
#@overlay/match missing_ok=True (1)
env:
HOGE: ""
1 | Overlays を利用して新しいプロパティを追加する。 |
#@data/values
---
cache:
name: Cache add env
env:
HOGE: hogehoge
ytt template --file=config
loading:
name: Cache add env
id: cache
uses: actions/cache@v2
with:
path: /tmp/cache
key: ${{ runner.os }}-caching-${{ hashFiles('.lock') }}
restore-keys: ""
env:
HOGE: hogehoge
オプショナルなプロパティをもつ型を定義したい場合は #@schema/type any=True を利用する方法がある。 |
パッチの適用( Overlays )
テンプレートや Data Values の設定の一部だけを変更したり、共通の設定を適用したりすることができる。
Overlays として宣言するには、YAML ドキュメントに @overlay/match
アノテーションをつける。
また Overlays の関数などを使うには、ファイルの先頭で #@ load("@ytt:overlay", "overlay")
アノテーションを記述しておく。
Overlays は YAML テンプレートが描画されたあとに適用される。
他、Overlays についての詳細はこちらの公式ドキュメントを参照。
name: overlay sample
version: 1.2.3
metadata:
- name: example-ingress1
tag:
- "hoge"
annotations:
message: removed this message
overrides:
- hoge
- foo
- name: example2
tag:
- foo
annotations:
message: left message
overrides:
- yoho
#@ load("@ytt:overlay", "overlay") (1)
#@overlay/match by=overlay.all (2)
---
metadata:
#@overlay/match by=overlay.subset({"name": "example-ingress1"}) (3)
- tag:
- "fuga"
annotations:
#@overlay/remove (4)
message:
#@overlay/match missing_ok=True (5)
config:
var1: hoge
var2: fuga
#@overlay/match by=overlay.subset({"metadata": []}) (6)
---
metadata:
#@overlay/match by=overlay.all, expects="1+" (7)
- overrides:
- add value
1 | Overlays ライブラリを読み込み。 |
2 | Overlays 用のドキュメントであることを宣言。 かつ Overlays を適用する YAML 要素のパターンマッチ方法を指定( by )。 |
3 | 指定した YAML 要素と一致する要素を上書き対象とする。 |
4 | 下記のキーを削除する。 |
5 | 既存のキーが存在しないときは新しく追加したい場合、missing_ok=True を指定する。 |
6 | 詳細な値を指定したくないときは、空の値([] , {} )を指定すればいい。が、こういう場合は overlay.all() を使ったほうがいい。 |
7 | expects でマッチすべき回数を指定。この回数に該当しなければエラーとなる。 |
ytt template --file config.yml --file patch.yml
name: overlay sample
version: 1.2.3
metadata:
- name: example-ingress1
tag:
- hoge
- fuga
annotations: {}
overrides:
- hoge
- foo
- add value
config:
var1: hoge
var2: fuga
- name: example2
tag:
- foo
annotations:
message: left message
overrides:
- yoho
- add value
配列の値の置換について 配列の値を追加したり空にしたりすることは簡単にできるが、置換することは難しい。ある程度あきらめたほうがよさそう。 |
アノテーション | 説明 |
---|---|
|
どの要素を修正・上書きするかを指定する。 |
|
|
|
一致した要素を削除する。 |
|
一致した要素の値を置換する。 |
関数 | 説明 |
---|---|
|
記述した要素をすべて含む要素を検索する。 |
|
|
overlay.map_key(…) は理解が不十分なせいで期待通りの動作をしてくれないことが多かった。なので少し冗長でも overlay.subset(…) を使ったほうがイライラせずにすんだ。
|
共通の設定項目を定義する
runs-on
とかの共通設定は1回の記述で済ませたい。
そういう場合にも Overlays が有効。
runs-on
の値と、ステップの最初に行う uses: actions/checkout@v2
を共通化。
#@ load("@ytt:overlay", "overlay")
#@overlay/match by=overlay.all, expects="1+"
---
jobs:
#@overlay/match by=overlay.all, expects="1+"
_: (1)
#@overlay/match missing_ok=True
runs-on: ubuntu-20.04
steps:
#@overlay/match by=overlay.index(0) (2)
#@overlay/insert before=True (3)
- name: Checkout code
uses: actions/checkout@v2
1 | 任意のキー名としたい場合、_ と指定する。 |
2 | 最初の配列要素を検索。 |
3 | 上記で検索された要素の前に、この YAML 要素を挿入させる。 |
もしあるファイルでは別の設定にしたい場合、それ用の Overlays を設定して上書きさせればいい。
Template モジュール
既存の YAML 要素をまるごと置換する template.replace()
関数が利用できる。
例として、GitHub Actions ワークフロー においては steps
キーが配列を格納するので、ここでよく使いそう。
#@data/values
---
setup:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
#@ load("@ytt:data", "data")
#@ load("@ytt:template", "template") (1)
---
...
jobs:
docker:
name: docker / build
runs-on: ubuntu-20.04
steps:
- #@ template.replace(data.values.setup) (2)
- ...
1 | Template ライブラリを読み込み。 |
2 | ここの配列要素を setup の YAML 要素で置換している。これによって二次元配列にならずに済む。 |
ytt template --file workflow-template.yml --file vars.yml
...
jobs:
docker:
name: docker / build
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- ...
文字列のテンプレート(Text Templating )
#@yaml/text-templated-strings
アノテーションをつける。
テンプレート文字列(文字列補間)を使いたいときに使う。
@yaml/text-templated-strings
アノテーションをつけた上で (@
と @)
で囲んだ部分が変数展開される。
#@ val1 = "value1" (1)
#@ val2 = "value2"
#@yaml/text-templated-strings (2)
---
normal: "val1 is (@= val1 @) and val2 is (@= val2 @)" (3)
no_output: "val1 is (@ val1 @) and val2 is (@ val2 @)" (4)
trim_spaces: "val1 is (@-= val1 -@) and val2 is (@-= val2 @)" (5)
key_(@= val1 @): used in key (6)
1 | 変数を定義。 |
2 | テンプレート文字列を使うためのアノテーション。 |
3 | 通常使用。変数展開された値は文字列として扱われる。 |
4 | = をつけない場合、変数展開されない。 |
5 | - をつけた側のスペースが除去される。 |
6 | キーにも変数展開が使える。 |
ytt template --file config.yml
normal: val1 is value1 and val2 is value2
no_output: 'val1 is and val2 is '
trim_spaces: val1 isvalue1and val2 isvalue2
key_value1: used in key
コメント
#!
で始めるとコメントとして使われる。
---
#! This is comment
hoge: sample-for-comment
ytt template --file comment.yml
hoge: sample-for-comment
🍣FAQ
anchor/alias は使えるの?
普通に使える。
その上 ytt
で出力された YAML ドキュメントは alias が展開されるので、anchor/alias が使えない GitHub Actions ワークフロー でも安心して利用できる。
ただまあ、ytt
を使うなら anchor/alias の代わりに関数を利用したほうが筋はいいかも。
data/values
で定義した変数を別の data/values
から参照したい
現状では data/values
のファイルが全て読み込まれるまでは参照できないため、無理らしい(issues #309)。
なので代わりに Overlays でパッチをあてたり、Library を使って参照したりしてみる。
on
キーが true
に変換されてしまう
YAML Version 1.1 の仕様のせい。
ダブルクォーテーション "
で囲み、文字列として扱わせるといい。
"on":
push: {}
Error: use of reserved keyword 'with' is not allowed
ある GitHub Action を Data Value にして参照しようとしたときに発生したエラー。
- 原因
-
Starlark の予約語(参照)に
with
が入っているため、ドット表記(~~.with
)による利用はできない。 - 対策
-
ブラケット表記(
~~["with"]
)で参照すればいい。✖️NG(ドット表記)action_name: #@ data.values.<value>.with.name
✔️OK(ブラケット表記)action_name: #@ data.values.<value>["with"].name
with キーワード以外でもこのエラーは発生するが、同じようにブラケット表記を使えばいい。
|
😎おわりに
ytt
のサンプルがいまいちわからなかったので実際に試してみたのを書いた。
ただ Overlays はすごい便利なんだけど match
条件が全然わからん。ふんいきで使ってる。
なお、作成した ytt
用のテンプレートのままでは GitHub で使えないので、
-
Git Hooks で
git push
前にコンパイル -
別の GitHub Actions ワークフロー でコンパイルさせてコミットを追加[1]
したりするのがよさそう。