小技巧 - 如何在迴圈裡註冊變數

單一個模組執行結果,可以使用 register 把結果放到變數裡。

如果這模組要搭配迴圈執行多次,又要把結果放到變數裡時,要怎麼寫呢?因為之前沒有用過,腦袋不禁打結了,心裡開始在盤算該怎麼處理比較好?但好在,早有人提出解答。

StackOverflow 上的說明:Register variables in with_items loop in Ansible playbook

簡單的說,不需要特別做什麼處理,照寫就可以,只是取得的變數格式有些不同。取得的變數會把每次執行的結果放到 .results 裡,存為一個 list。舉個例子來說明,在沒有使用 loop 的情況是這樣:

---
- name: Register without loop
  hosts: all
  tasks:
  - name: shell
    shell: hostname
    register: shell_result
  - name: display
    debug:
      var: shell_result

輸出結果如下(摘錄)

TASK [display] ************************************************************************************************
ok: [localhost] => {
    "shell_result": {
        "changed": true,
        "cmd": "hostname",
        "delta": "0:00:00.004216",
        "end": "2022-09-03 05:59:29.924106",
        "failed": false,
        "rc": 0,
        "start": "2022-09-03 05:59:29.919890",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "example-host",
        "stdout_lines": [
            "example-host"
        ]
    }
}

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

這時可以看到 shell_result 是個 dict 型態的變數。

再加上 loop 以後,同樣,舉個例子來看。

---
- name: Register with loop
  hosts: all
  vars:
    cmd_list:
    - hostname
    - date
    - uptime
  tasks:
  - name: shell
    shell: "{{ item }}"
    register: shell_result
    loop: "{{ cmd_list }}"
  - name: display
    debug:
      var: shell_result

輸出結果如下(摘錄),這時就會看到 shell_result 多了一個 list 型態的 results 屬性。

TASK [display] ************************************************************************************************
ok: [localhost] => {
    "shell_result": {
        "changed": true,
        "msg": "All items completed",
        "results": [
            {
                "ansible_loop_var": "item",
                "changed": true,
                "cmd": "hostname",
                "delta": "0:00:00.003129",
                "end": "2022-09-03 06:12:38.139077",
                "failed": false,
                "invocation": {
                    "module_args": {
                        "_raw_params": "hostname",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "stdin_add_newline": true,
                        "strip_empty_ends": true,
                        "warn": true
                    }
                },
                "item": "hostname",
                "rc": 0,
                "start": "2022-09-03 06:12:38.135948",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "example-host",
                "stdout_lines": [
                    "example-host"
                ]
            },
            {
                "ansible_loop_var": "item",
                "changed": true,
                "cmd": "date",
                "delta": "0:00:00.009256",
                "end": "2022-09-03 06:12:38.286830",
                "failed": false,
                "invocation": {
                    "module_args": {
                        "_raw_params": "date",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "stdin_add_newline": true,
                        "strip_empty_ends": true,
                        "warn": true
                    }
                },
                "item": "date",
                "rc": 0,
                "start": "2022-09-03 06:12:38.277574",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "西元2022年09月03日 (週六) 06時12分38秒 CST",
                "stdout_lines": [
                    "西元2022年09月03日 (週六) 06時12分38秒 CST"
                ]
            },
            {
                "ansible_loop_var": "item",
                "changed": true,
                "cmd": "uptime",
                "delta": "0:00:00.004768",
                "end": "2022-09-03 06:12:38.433260",
                "failed": false,
                "invocation": {
                    "module_args": {
                        "_raw_params": "uptime",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "stdin_add_newline": true,
                        "strip_empty_ends": true,
                        "warn": true
                    }
                },
                "item": "uptime",
                "rc": 0,
                "start": "2022-09-03 06:12:38.428492",
                "stderr": "",
                "stderr_lines": [],
                "stdout": " 06:12:38 up 26 days, 15:24,  1 user,  load average: 0.43, 0.45, 0.60",
                "stdout_lines": [
                    " 06:12:38 up 26 days, 15:24,  1 user,  load average: 0.43, 0.45, 0.60"
                ]
            }
        ]
    }
}

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

知道結果會是這樣以後,就可以再用 loop 來處理了。在上面的 playbook 最後加入以下 task,就可以只印出輸出結果了。

  - name: Display only stdout
    debug:
      msg: "{{ item.stdout }}"
    loop: "{{ shell_result.results }}"
    loop_control:
      label: "{{ item.stdout }}"

輸出結果如下(摘錄)

TASK [Display only stdout] ************************************************************************************
ok: [localhost] => (item=example-host) => {
    "msg": "example-host"
}
ok: [localhost] => (item=西元2022年09月03日 (週六) 06時17分05秒 CST) => {
    "msg": "西元2022年09月03日 (週六) 06時17分05秒 CST"
}
ok: [localhost] => (item= 06:17:05 up 26 days, 15:28,  1 user,  load average: 0.47, 0.49, 0.58) => {
    "msg": " 06:17:05 up 26 days, 15:28,  1 user,  load average: 0.47, 0.49, 0.58"
}

這邊有使用到另外一個小技巧,避免輸出過多的資訊,就是 loop_control,這是在 2.2 以後所提供的語法。如果不使用,輸出結果會是像下面這樣子,可以看到,非常的冗長,導致不容易閱讀。

TASK [Display only stdout] ************************************************************************************
ok: [localhost] => (item={'cmd': 'hostname', 'stdout': 'example-host', 'stderr': '', 'rc': 0, 'start': '2022-09-03 06:21:09.487145', 'end': '2022-09-03 06:21:09.490120', 'delta': '0:00:00.002975', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'hostname', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['example-host'], 'stderr_lines': [], 'failed': False, 'item': 'hostname', 'ansible_loop_var': 'item'}) => {
    "msg": "example-host"
}
ok: [localhost] => (item={'cmd': 'date', 'stdout': '西元2022年09月03日 (週六) 06時21分09秒 CST', 'stderr': '', 'rc': 0, 'start': '2022-09-03 06:21:09.627735', 'end': '2022-09-03 06:21:09.630569', 'delta': '0:00:00.002834', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'date', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['西元2022年09月03日 (週六) 06時21分09秒 CST'], 'stderr_lines': [], 'failed': False, 'item': 'date', 'ansible_loop_var': 'item'}) => {
    "msg": "西元2022年09月03日 (週六) 06時21分09秒 CST"
}
ok: [localhost] => (item={'cmd': 'uptime', 'stdout': ' 06:21:09 up 26 days, 15:32,  1 user,  load average: 0.53, 0.50, 0.55', 'stderr': '', 'rc': 0, 'start': '2022-09-03 06:21:09.769529', 'end': '2022-09-03 06:21:09.773419', 'delta': '0:00:00.003890', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'uptime', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': [' 06:21:09 up 26 days, 15:32,  1 user,  load average: 0.53, 0.50, 0.55'], 'stderr_lines': [], 'failed': False, 'item': 'uptime', 'ansible_loop_var': 'item'}) => {
    "msg": " 06:21:09 up 26 days, 15:32,  1 user,  load average: 0.53, 0.50, 0.55"
}

最後整理一下,這篇分享了兩個技巧:

  1. register 是可以搭配 loop 使用的,使用時,會多出一個 results 屬性,後面可以對 results 做處理就好。
  2. 可以使用 loop_control 來避免顯示過多資訊,提升可閱讀性。