てくなべ

ansible / network automation / 学習メモ / 思考メモ

[Ansible] レジスター変数を取り回しやすくなる Register Projections (ansible-core 2.21 から)

はじめに

2026/05/18 にリリースされた ansible-core 2.21.0 で、Register Projections という機能が追加されました。

1つのタスクで、レジスター(register)変数を複数定義できたり、タスク内であれば register を定義しなくても _task.result で参照できるようになったりします。

総合的には「レジスター変数が取り回しやすくなり、Playbook をシンプルに書けるようになる」と表現できるかなと思います。

この記事では、 Register Projections そのものと、付随して導入される暗黙的なタスクオブジェクト(task implicit object)の2つに分けてまとめます。

  • 検証環境
    • ansible-core 2.21.0

※ その他の ansible-core 2.21.0 での変更点は [Ansible] ansible-core 2.21.0 がリリース。便利そうな点と注意点。注目は Register Projections - てくなべ を参考にしてください。

1. レジスタ変数の取り回しがしやすくなる Register Projections

タスクの実行結果を register ディレクティブ で指定した変数に格納して、後続のタスクで利用する方法は、よく利用します。

ただ、レジスタ変数の値をそのまま使うことは個人的にはあまりなく、大抵は抽出や変換などの加工が伴います。この加工のために、例えば ansible.builtin.set_fact モジュールのタスクをワンクッション挟んだり、参照するタスク側の vars ディレクティブで加工したりします。

今回追加された、Register Projections という機能によって register ディレクティブを定義する側のタスクで予め加工できるようになります。うまく言えないのですが、私は「レジスタ変数を取り回しやすくなる」と表現しています。

この Register Projections 機能は、fallible による実験的な実装の段階で、@stopendy0122 さんが検証してまとめられていてとても参考になります(はやい!)。

endy-tech.hatenablog.jp

実装のプルリクにもすっきり分かりやすい例が掲載されています。

Register Projections を利用したサンプル Playbook

晴れて ansible-core 側にも実装されたということで、私も以下の Playbook を試してみます。

---
- name: Test Play
  hosts: ios
  gather_facts: false

  tasks:
    - name: Execute show commands
      cisco.ios.ios_command:
        commands:
          - show version
          - show ip route
      register:
        result_show_version: _task.result.stdout_lines[0]   # ポイント
        result_show_ip_route: _task.result.stdout_lines[1]  # ポイント

    - name: Debug result_show_version
      ansible.builtin.debug:
        msg: "{{ result_show_version }}"

    - name: Debug result_show_ip_route
      ansible.builtin.debug:
        msg: "{{ result_show_ip_route }}"

Playbook を見慣れた方ほど違和感があるかもしれません。注目は 1つ目のタスクの register です。ポイントは 2 つあります。

ポイント1: 1つのタスクで複数のレジスタ変数を定義可能に

1つめのポイントは、1つのタスクで複数のレジスタ変数をいっぺんに定義できるようになったことです。

これまでであれば、

      register: result_show_commands

のように、register には変数名を1つ指定する形でした。

今回から、以下のように register をディクショナリで指定することで、複数の変数に入れることができるようになりました。

      register:
        result_show_version: _task.result.stdout[0]
        result_show_ip_route: _task.result.stdout[1]

なお、_task は暗黙的に定義されているタスクオブジェクトです。_task.result でタスクの結果を丸ごと参照できます。なので、

      register: result

      register:
        result: _task.result

は同じです。

_task については別の使い道もあるので、本記事の後で触れます。

ポイント2: 値に Jinja2 書式が利用できる

2つ目のポイントは、Jinja2 書式を指定できることです。

これまでの、

      register: result_show_commands

result_show_commands は、あくまで文字列として変数名を定義しているだけです。そのため、register: result_show_commands.stdout[0] のような書式で抽出や加工の指定はできません。変数名として不正というエラーになってしまいます。

一方、今回から利用できる、

      register:
        result_show_version: _task.result.stdout[0]
        result_show_ip_route: _task.result.stdout[1]

といった書式の _task.result.stdout[0] の箇所は、Jinja2 書式が指定できます。そのため、上記例のように JSON 的な抽出をしたり、他にも各種フィルターも使えます。

なお、ここの Jinja2 書式は "{{ }}" のような囲い方はせずに直接指定します。when ディレクティブなどと同じです。

Register Projections を利用しない場合(比較用)

もし仮に、今まで通り register で丸ごと1つのレジスタ変数に入れる場合は、以下のように変数をばらすためのタスク(以下例では ansible.builtin.set_fact)を挟むことになります。

    - name: Execute show commands
      cisco.ios.ios_command:
        commands:
          - show version
          - show ip route
      register: result_show_commands  # 丸ごと1つのレジスタ変数に入れる場合(ansible-core 2.20 までも可)

    # set_fact で丸ごとのレジスタ変数をばらして仕分ける
    - name: set_fact
      ansible.builtin.set_fact:
        result_show_version: "{{ result_show_commands.stdout_lines[0] }}"
        result_show_ip_route: "{{ result_show_commands.stdout_lines[1] }}"

    - name: Debug result_show_version
      ansible.builtin.debug:
        msg: "{{ result_show_version }}"

    - name: Debug result_show_ip_route
      ansible.builtin.debug:
        msg: "{{ result_show_ip_route }}"

ばらした変数を1回しか参照しないのであれば、参照する側のタスクで抽出する形でもよいでしょう。

もちろん、あえて1つのレジスタ変数に丸ごと入れて、後続のタスクで処理するほうが都合がいいケースもありますが、そこは使い分けですね。

参考

(補足)上記 サンプル Playbook 実行時に [DEPRECATION WARNING] がいくつか表示されました。おそらくは ansible-core 2.21.0 と、今回利用した cisco.ios コレクション 11.4.1 との相性なのかと思います。今後の cisco.ios コレクション側のアップデートで解消されるかもしれません。

2. 暗黙的なタスクオブジェクト _task による register の省略

前述の Register Projections で「_task は暗黙的に定義されているオブジェクトです」と書きましたが、これはこれで1つの機能追加とも言えます。changelog 上は「task implicit object」と表現されています。

これは、register を指定しなくても、そのタスク内であれば _task を通じてタスクの実行結果を参照できる機能です。

例えば、failed_when で、タスクの実行結果を failed として扱うかどうかの条件の指定は、以下のようにシンプルに書けます。

    # ansible-core 2.21 からできる方法
    - name: Test command
      ansible.builtin.command:
        cmd: diff test1.txt test2.txt
      failed_when: _task.result.rc >= 2   # register の代わりに暗黙の _task を利用

failed_when だけでなく、changed_whenuntil などでも _task の参照ができます。when については、タスクの実行前に実行するかどうかを評価するため _task は参照できません(それはそう)。

比較のため、 ansible-core 2.20 まででもできる方法で書くと、以下のように register が必要です。もちろん ansible-core 2.21 でも利用できます。

    # ansible-core 2.20 まででもできる方法(比較用に掲載)
    - name: Test command
      ansible.builtin.command:
        cmd: diff test1.txt test2.txt
      register: my_result             # register を定義
      failed_when: my_result.rc >= 2  # 明示的に定義したレジスタ変数を利用

loop と併用する場合

loop を併用する場合は、少し固有の事情があります。

まず、_task.result は直近のループ 1 回分の結果で毎回上書きされます。

例えば以下のようなタスクの場合、ループの2回目だけ failed になります。

    - name: Test command
      ansible.builtin.command:
        cmd: "echo {{ item }}"
      loop:
        - 1
        - 2
        - 3
      failed_when: _task.result.stdout == "2"

もう一点押さえておきたいのが、ループ全体の結果は _task.loop_results に入るという点です。例えば、1回目のループの結果であれば _task.loop_results[0] に入ります。

ただ、当然ですが、例えば 1回目のループのタイミングでは _task.loop_results[1] は参照できません。正直、現状は使い道が思いつきませんが。

実装PRに掲載されている以下の例は、手元の ansible-core 2.21.0 で試す限り second_echo_stdout の値は 0 になります。意図通りなのかもちょっと判断できていません・・。コメントに書かれてること自体の趣旨は分かるのですが。

- shell: echo {{ item }}
  loop:
    - 1
    - 2
  register:
    second_echo_stdout: _task.loop_results[1].stdout | default(0)  # using default here is necessary as when the loop is on the first item, it will still try and access `loop_results[1]` which doesn't exist yet

参考

  • 関連PR: Register projections and action plugin dynamic host/group/var API by nitzmahone · Pull Request #86241 · ansible/ansible · GitHub
  • changelog:

    task implicit object - A new task implicit object is available for use in register and task conditional expressions (e.g., failed_when). The result of the current task can be accessed via the task.result property, without the use of register. Under a loop, task.result is the most recently completed result and task.loop_result provides access to accumulated loop results. The _task.polymorphic_result property provides compatibility with classic name-only register in loops. The value is the result of the most recent loop iteration, then becomes the final list loop result once the loop is complete.

[2026/05/30 追記]

現状、これに対応したドキュメントは見当たりませんが、未マージのプルリクはありました。

github.com

おわりに

Register Projections という機能についてまとめました。

特に 1つのタスクで、レジスタ変数を複数定義できたり、Jinja2 書式が使えるのは、Playbook がシンプルに書けるようになりそうで、便利だなと思いました。