てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] 「つまずき Ansible 【Part35】ロールの実行に必要な変数をチェック」ふりかえり

はじめに

2022/03/05 に、YouTube Live で「つまずき Ansible 【Part35】ロールの実行に必要な変数をチェック」という配信をしました。

tekunabe.connpass.com

今回は、ロールの実行に必要な変数のバリデーションができる「Role argument validation」を試します。

ansible-core 2.11 から利用できる機能です。


動画

youtu.be

ロールと変数とチェック

ロール内で変数を利用する場合は、あらじめ必要な変数が定義されているかなどをチェックしたいところです。

古くから使える方法としては、assert モジュールがあり、柔軟なチェックができます。

一方 ansible-core 2.11 からは Role argument validation という機能が利用できます(正確な機能名はわからないですが、ドキュメントの見出しからとっています)。 assert モジュールのよいな汎用的な機能ではなく、ロールの argument (内部で利用する変数と解釈しています)専用のチェック機能です。

Role argument validation

Role argument validation では、チェックするルールをロール内の meta/argument_specs.yml 内に定義します。

必須チェック

エラーになるケース

必要な変数だと定義しているのに、定義してない場合です。

meta/argument_specs.yml を以下のようにします。myapp_intmyapp_strrequired: true で必須としています。

---
argument_specs:
  main:
    short_description: The main entry point for the myapp role
    options:
      myapp_int:
        type: "int"
        required: true
        description: "The integer value"

      myapp_str:
        type: "str"
        required: true
        description: "The string value"

以下の Playbook を実行することにします。myapp_intmyapp_str も未定義です。

---
- hosts: localhost
  gather_facts: false

  tasks:
    - name: import test
      ansible.builtin.import_role:
        name: test

ロールの処理の中身も変数を利用するだけです。

---
- name: debug test 1
  debug:
    msg: "{{ myapp_int }}"

- name: debug test 2
  debug:
    msg: "{{ myapp_str }}"

この状態で、Playbook を実行します。(エラーを改行表示するために -vvv を付けてます。)

$ ansible-playbook -i localhost, site.yml -vvv 
...(略)...

PLAYBOOK: site.yml ******************************************************************************************************************************************************************************
1 plays in site.yml

PLAY [localhost] ********************************************************************************************************************************************************************************
META: ran handlers

TASK [test : Validating arguments against arg spec 'main' - The main entry point for the myapp role] ********************************************************************************************
task path: /home/admin/ansible-ee/site.yml:2
fatal: [localhost]: FAILED! => {
    "argument_errors": [
        "missing required arguments: myapp_int, myapp_str"   # ここがポイント
    ],
    "argument_spec_data": {
        "myapp_int": {
            "description": "The integer value",
            "required": true,
            "type": "int"
        },
        "myapp_str": {
            "description": "The string value",
            "required": true,
            "type": "str"
        }
    },
    "changed": false,
    "msg": "Validation of arguments failed:\nmissing required arguments: myapp_int, myapp_str",
    "validate_args_context": {
        "argument_spec_name": "main",
        "name": "test",
        "path": "/home/admin/ansible-ee/roles/test",
        "type": "role"
    }
}

PLAY RECAP **************************************************************************************************************************************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

ポイントは "missing required arguments: myapp_int, myapp_str" です。2つの変数の分がいっぺんに表示してくれるのが嬉しいです。

validate が1つのタスクとてログに表示される点も特徴かなと思います。

正常なケース

今度は正常なケースです。myapp_intmyapp_str をきちんと定義します。

どこで定義されててもいいのですが、今回は import_role モジュールのタスクの vars で定義します。

---
- hosts: localhost
  gather_facts: false

  tasks:
    - name: import test
      ansible.builtin.import_role:
        name: test
      vars:
        myapp_int: 123
        myapp_str: hello

これで実行すると成功します。validate のタスクの状態も ok です。

TASK [test : Validating arguments against arg spec 'main' - The main entry point for the myapp role] ********************************************************************************************
task path: /home/admin/ansible-ee/site.yml:2
ok: [localhost] => {
    "changed": false,
    "msg": "The arg spec validation passed",    # 成功
    "validate_args_context": {
        "argument_spec_name": "main",
        "name": "test",
        "path": "/home/admin/ansible-ee/roles/test",
        "type": "role"
    }
}

TASK [test : debug test 1] **********************************************************************************************************************************************************************
task path: /home/admin/ansible-ee/roles/test/tasks/main.yml:2
ok: [localhost] => {
    "msg": 123
}

TASK [test : debug test 2] **********************************************************************************************************************************************************************
task path: /home/admin/ansible-ee/roles/test/tasks/main.yml:6
ok: [localhost] => {
    "msg": "hello"
}

チェックの無効化

ロールに meta/argument_specs.yml があると validate 処理がされますが、無効化もできます。

import_role モジュールで呼び出す場合は rolespec_validate オプションfalse を指定します。デフォルトは true です。

# ...(略)...
    - name: import test
      ansible.builtin.import_role:
        name: test
        rolespec_validate: false

この場合は、validate 処理はされず、ログにも出てきません。

型チェック

次は型チェックです。meta/argument_specs.yml 内では、 myapp_inttype: "int" という指定をしています。

なので、意図的に 文字に指定してエラーになるようにしてみます。

# ...(略)...
    - name: import test
      ansible.builtin.import_role:
        name: test
      vars:
        myapp_int: abc    # 文字列
        myapp_str: hello

こんなエラーになりました。

fatal: [localhost]: FAILED! => {
    "argument_errors": [
        "argument 'myapp_int' is of type <class 'ansible.parsing.yaml.objects.AnsibleUnicode'> and we were unable to convert to int: <class 'ansible.parsing.yaml.objects.AnsibleUnicode'> cannot be converted to an int"
    ],

unable to convert to int とあるので、変換しようとしてエラー、ということのようです。 なお、myapp_int: "123" という指定はエラーになりませんでした。

選択肢チェック

AかBであること、のような選択肢のチェックです。meta/argument_specs.yml に以下を追加します。

# ...(略)...
      kind:
        type: "str"
        choices:
          - kingyo
          - ugui
        required: true
        description: "kind of sakana"

この場合は、kind の値は kingyougui のいずれかであること、という指定です。

Playbook 側の vars では、意図的にエラーになるように、kind: buri を指定します。

# ...(略)...
    - name: import test
      ansible.builtin.import_role:
        name: test
      vars:
        myapp_int: 123
        myapp_str: hello
        kind: buri

こんなエラーになりました。"value of kind must be one of: kingyo, ugui, got: buri" と表示されています。

...(略)...
TASK [test : Validating arguments against arg spec 'main' - The main entry point for the myapp role] ***
task path: /home/admin/ansible-ee/site.yml:2
fatal: [localhost]: FAILED! => {
    "argument_errors": [
        "value of kind must be one of: kingyo, ugui, got: buri"  # ポイント
    ],
    "argument_spec_data": {
        "kind": {
            "choices": [
                "kingyo",
                "ugui"
            ],
            "description": "kind of sakana",
            "required": true,
            "type": "str"
        },
        "myapp_int": {
            "description": "The integer value",
            "required": true,
            "type": "int"
        },
        "myapp_str": {
            "description": "The string value",
            "required": true,
            "type": "str"
        }
    },
    "changed": false,
    "msg": "Validation of arguments failed:\nvalue of kind must be one of: kingyo, ugui, got: buri",
    "validate_args_context": {
        "argument_spec_name": "main",
        "name": "test",
        "path": "/home/admin/ansible-ee/roles/test",
        "type": "role"
    }
}

validate_argument_spec モジュール

Role argument validation は、ロール読み込み時に meta/argument_specs.yml があるとチェックするという動作でした。

似たようなものとして、ansible.builtin.validate_argument_specモジュールがあります。 これは、任意のタイミングで、 meta/argument_specs.yml のような定義(厳密には meta/argument_specs.ymloptions 配下のみ) を利用してチェックするモジュールです。

タイミングが任意の代わりに、ファイルを明示的に指定する必要があります。

[2024/01/13 追記] 別の記事で書きました。

tekunabe.hatenablog.jp

チェックモードでもvalidateされる

Role argument validation は ansible-playbook コマンドの -C または --check によるチェックモードによる実行でもvalidateしてくれます。

ansible-docによるargument情報の表示

meta/argument_specs.yml に定義した情報は、ansible-doc コマンドで表示できあます。

ロール一覧

ロール一覧の表示では、short_description に定義した情報が表示されまます。

$ ansible-doc -t role -r roles -l
...(略)...
test                                                main         The main entry point for the myapp role!!!!

ロール個別表示

ロールと指定すると書く argument のタイプ、説明なども表示されます。

$ ansible-doc -t role -r roles test
> TEST    (/home/admin/ansible-ee/roles/test)

ENTRY POINT: main - The main entry point for the myapp role

OPTIONS (= is mandatory):

= kind
        kind of sakana
        (Choices: kingyo, ugui)
        type: str

= myapp_int
        The integer value

        type: int

= myapp_str
        The string value

        type: str

まとめ

ロールの実行に必要な変数をチェックする Role argument validation 機能をためしてみました。

柔軟さでは assert や ansible.utils.validate`モジュールのほうが勝ると思いますが、meta/argument_specs.ymlドキュメンテーションも兼ねる点は便利かなと思います。


Part36にむけて

以下のネタを検討中です。気が向いたものをやります。 connpass申込時のアンケートでいただいたものも含めています。

  • ansible-builder、ansible-runner、ansible-navigator
  • connection: local ななにか
  • Windows
  • cli_parse モジュール(Part15 の続き)
  • モジュールのテスト
  • Tower / AWX
  • role と Playbook のリポジトリ分割と読み込み
  • AWXとの共存を念頭に入れたDirectory構成