はじめに
Ansible にはロールの実行に必要な変数のバリデーションができる「Role argument validation」という機能があります。ansible-core 2.11 から利用できます。
どのような変数が必須か、どういう値を取りうるか(選択肢)を、各ロールの meta/argument_specs.yml
に定義しておくことで、ロールの呼び出す際、実際の処理をする前に仕様に応じたバリデーションができます。
いくつか注意が必要かなと思う点があるので、この記事でまとめます。
環境は以下の通りです。今後のバージョンアップで変更されるかもしれません。
- 前提環境
- ansible-core 2.16.6
ansible.builtin.import_role
モジュールによるロール呼び出し
注意が必要なポイント
型の判定がゆるめ
type
で str
、int
、bool
、list
、dict
、などの型を指定できます。デフォルトは str
です。
--- argument_specs: main: options: myvar: type: int # 例
この型の指定で、バシッと型のバリデーションまでできそうですが、実際は割とゆるめです。
傾向としてはキャストができればバリデーションもOKとみなされる雰囲気です。例えば type: int
に対しては 100
でも "100"
でも OK です。さすがに hoge
は NG です。
ほか、定義済みだけど値がない場合はバリデーションが OK になる傾向です。required: true
とセットの場合は NG になります。
以下検証結果です。
1. type: str
の場合(デフォルト)
定義
--- argument_specs: main: options: mystr: type: str
結果
変数の値 | バリデーション結果 | エラーメッセージ抜粋 |
---|---|---|
100 |
OK | |
"100" |
OK | |
100.0 |
OK | |
hoge |
OK | |
true |
OK | |
0 |
OK | |
値なし | OK | |
[100] |
OK | |
{num: 100} |
OK |
「値なし」が OK となっていますが、required: true
とセットにした場合は、'None' is not a string and conversion is not allowed
というエラーで NG となりました。
2. type: int
の場合
定義
--- argument_specs: main: options: myint: type: int
結果
変数の値 | バリデーション結果 | エラーメッセージ抜粋 |
---|---|---|
100 |
OK | |
"100" |
OK | |
100.0 |
NG | <class 'float'> cannot be converted to an int |
hoge |
NG | <class 'ansible.parsing.yaml.objects.AnsibleUnicode'> cannot be converted to an int |
true |
OK | |
0 |
OK | |
値なし | OK | |
[100] |
NG | <class 'list'> cannot be converted to an int |
{num: 100} |
NG | <class 'dict'> cannot be converted to an int |
「値なし」が OK となっていますが、required: true
とセットにした場合は、<class 'NoneType'> cannot be converted to an int
というエラーで NG となりました。
3. type: bool
の場合
定義
--- argument_specs: main: options: mybool: type: bool
結果
変数の値 | バリデーション結果 | エラーメッセージ抜粋 |
---|---|---|
100 |
NG | 'mybool' is of type <class 'int'> and we were unable to convert to bool: The value '100' is not a valid boolean. Valid booleans include: 0, 1, '0', 'yes', 'on', 'n', 'no', 'f', 't', 'off', 'false', 'y', 'true', '1' |
"100" |
NG | 'mybool' is of type <class 'ansible.parsing.yaml.objects.AnsibleUnicode'> and we were unable to convert to bool: The value '100' is not a valid boolean. Valid booleans include: 0, 1, '0', 'yes', 'on', 'n', 'no', 'f', 't', 'off', 'false', 'y', 'true', '1' |
100.0 |
NG | 'mybool' is of type <class 'float'> and we were unable to convert to bool: The value '100.0' is not a valid boolean. Valid booleans include: 0, 1, '0', 'yes', 'on', 'n', 'no', 'f', 't', 'off', 'false', 'y', 'true', '1' |
hoge |
NG | 'mybool' is of type <class 'ansible.parsing.yaml.objects.AnsibleUnicode'> and we were unable to convert to bool: The value 'hoge' is not a valid boolean. Valid booleans include: 0, 1, '0', 'yes', 'on', 'n', 'no', 'f', 't', 'off', 'false', 'y', 'true', '1' |
true |
OK | |
0 |
OK | |
値なし | OK | |
[100] |
NG | <class 'list'> cannot be converted to a bool |
{num: 100} |
NG | <class 'dict'> cannot be converted to a bool |
「値なし」が OK となっていますが、required: true
とセットにした場合は、<class 'NoneType'> cannot be converted to a bool
というエラーで NG となりました。
4. type: list
と elements: int
の場合
リストの変数の場合で、ここでは int の値を持つ仕様を想定します。
定義
--- argument_specs: main: options: mylist: type: list elements: int
結果
変数の値 | バリデーション結果 | エラーメッセージ抜粋 |
---|---|---|
100 |
OK | |
"100" |
OK | |
100.0 |
NG | <class 'str'> cannot be converted to an int |
hoge |
NG | <class 'str'> cannot be converted to an int |
true |
NG | <class 'str'> cannot be converted to an int |
0 |
OK | |
値なし | OK | |
[100] |
OK | |
{num: 100} |
NG | <class 'dict'> cannot be converted to a list |
「値なし」が OK となっていますが、required: true
とセットにした場合は、<class 'NoneType'> cannot be converted to a list
というエラーで NG となりました。
5. type: dcit
の場合
ディクショナリの変数の場合で、ここではさらに中に num
というキーで int の値を持つ仕様を想定します。
定義
--- argument_specs: main: options: mydict: type: dict options: num: type: int
結果
変数の値 | バリデーション結果 | エラーメッセージ抜粋 |
---|---|---|
100 |
NG | argument of type 'int' is not iterable |
"100" |
NG | dictionary requested, could not parse JSON or key=value |
100.0 |
NG | argument of type 'float' is not iterable |
hoge |
NG | dictionary requested, could not parse JSON or key=value |
true |
NG | argument of type 'bool' is not iterable |
0 |
NG | argument of type 'int' is not iterable |
値なし | OK | |
[100] |
NG | argument of type 'int' is not iterable |
{num: 100} |
OK |
「値なし」が OK となっていますが、required: true
とセットにした場合は、<class 'NoneType'> cannot be converted to a dict
というエラーで NG となりました。
複雑なチェックはできない
たとえば正規表現によるパターンマッチングや、数値の範囲、他のタスクの実行結果を利用したバリデーションなど、複雑なバリデーションはできません。
モジュールの argument_specs
ほどの機能はない
モジュールのコードを読んだり書いたりしたこがある方であれば、argument_spec
という言葉を見かけたことがあるかもしれません。(ロールではなく)モジュールのパラメーターの仕様を定義するディクショナリで、内部的にバリデーション的な処理がされます。
argument_spec
という名前や、含まれるキーの名前が「Role argument validation」のものと似ていますが、一部異なります。比較すると「Role argument validation」によるバリデーションのほうができることが少ないです。
たとえば「Role argument validation」では、以下のような排他や、条件付きの必須の指定はできないようです。
mutually_exclusive
required_if
required_together
required_one_of
required_by
VS Code の Ansible の拡張を使って meta/argument_specs.yml
を書いていると補完されてしまいますが。
あくまで、対応しているのは meta/argument_specs.yml
内の argument_specs
のフォーマットにあるもの(required
、choices
)のみと捉えておくのが分かりやすいと思います。
関連 Issue:
- Role Argument Spec Validation Improvements - String conversion, int ranges, Module validation features · Issue #77159 · ansible/ansible · GitHub
- Role argument specification should support "mutually_exclusive", "required_if" and the other Module argument specification features · Issue #74995 · ansible/ansible · GitHub
default
の指定は動作には関係ない
ドキュメントにも記載がありますが、default
で指定した値は机上の値であり、実際に適用されるデフォルト値は defaults/main.yml
での指定によります。
バリデーションタスクには always
タグが暗黙的につく
バリデーションは1つのタスクのような扱われ方をします。
ドキュメントに記載ありますが、バリデーションのタスクには always
タグ 扱いです。
たとえば、meta/argument_specs.yml
を定義しているロールを呼びだす以下のPlaybookがあった場合。
--- - name: Test Play int hosts: localhost connection: local gather_facts: false tasks: - name: Import role1 ansible.builtin.import_role: # static な import name: role1 vars: myint: 100
ansible-playbook
コマンドを --list-tasks
オプション付きで実行すると、バリデーション用のタスクに always
タグが付いていることが分かります。
$ ansible-playbook -i localhost, playbook.yml --list-tasks playbook: playbook.yml play #1 (localhost): Test Play int TAGS: [] tasks: role1 : Validating arguments against arg spec 'main' TAGS: [always]
--list-tags
オプション付きの実行でも always
タグの存在を確認できます。
$ ansible-playbook -i localhost, playbook.yml --list-tags playbook: playbook.yml play #1 (localhost): Test Play int TAGS: [] TASK TAGS: [always]
そのため、タグを指定した Playbook の実行時には少し注意が必要です。
たとえば、以下の Playbook を -t role2
で実行すると、role1
のバリデーション実行、role2
のバリデーション実行、role2
の処理実行、となります。role1
のバリデーション実行がされるところがポイントです。
--- - name: Test Play hosts: localhost connection: local gather_facts: false tasks: - name: Import role1 ansible.builtin.import_role: name: role1 tags: - role1 - name: Import role2 ansible.builtin.import_role: name: role2 tags: - role2
$ ansible-playbook -i localhost, playbook2.yml -t role2 PLAY [Test Play] ****************************************************************************************************** TASK [role1 : Validating arguments against arg spec 'main'] *********************************************************** ok: [localhost] TASK [role2 : Validating arguments against arg spec 'main'] *********************************************************** ok: [localhost] TASK [role2 : Debug] ************************************************************************************************** ok: [localhost] => { "msg": "In role2" }
--skip-tags=always
を追加すれば、(role1
も role2
も)バリデーションが行われなくなります。ただ、この辺りは凝りだすと複雑になり、思わぬ副作用を誘発しかねないので、ほどほどにした方が良いかなといます。
バリデーションを無効にしたい場合は、ansible.builtin.import_role
モジュールであれば rolespec_validate
を false
に指定するというのも手です。
Role argument validation 以外の選択肢
複雑なチェックには、ansible.utils.validate
モジュールと JSON Schema の組み合わせや、ansible.builtin.assert
モジュール によるチェックが向いていると思います。
おわりに
meta/argument_specs.yml による Role argument validation の注意点をまとめました。
特性を理解したうえで、複雑でないバリデーションであれば有用かなとおもいました。