[Ansible] Registered variable について調べてみた


ansibleでは、コマンド実行結果をRegistered variableに突っ込んで便利に使えます :smiley: 

# Registered Variablesとは？
ターゲットホストで実行したコマンドの結果を任意の変数に入れることができます。

使い方を見ていきます。ansibleのバージョンは `1.9.4` です。

サンプルとして、簡単なplaybookを作成しました。

```yaml
---
- hosts: 127.0.0.1
  connection: local
  tasks:
    - name: exec whoami
      shell: whoami
      register: result
    - name: debug result var
      debug: var=result
```

whoamiコマンドの実行結果をregisterキーワードで設定した `result` 変数に受け、次の`debug`タスクでハンドリングします。

## 変数の中身はどうなっている？

では、resultの中身はどうなっているのでしょうか？　playbookの実行結果から見ていきます。

```bash
[ec2-user@ip-10-0-0-83 ~]$ ansible-playbook playbook.yml

PLAY [127.0.0.1] **************************************************************

GATHERING FACTS ***************************************************************
ok: [127.0.0.1]

TASK: [exec whoami] ***********************************************************
changed: [127.0.0.1]

TASK: [debug result var] ******************************************************
ok: [127.0.0.1] => {
    "var": {
        "result": {
            "changed": true,
            "cmd": "whoami",
            "delta": "0:00:00.002182",
            "end": "2016-01-08 04:26:38.950255",
            "invocation": {
                "module_args": "whoami",
                "module_complex_args": {},
                "module_name": "shell"
            },
            "rc": 0,
            "start": "2016-01-08 04:26:38.948073",
            "stderr": "",
            "stdout": "ec2-user",
            "stdout_lines": [
                "ec2-user"
            ],
            "warnings": []
        }
    }
}

PLAY RECAP ********************************************************************
127.0.0.1                  : ok=3    changed=1    unreachable=0    failed=0
```

おそらく、この中でよく使うのは、rc、 stdout、stderr、stdout_linesの4つだと思います。

stdoutが複数行の場合、`\n`を改行コードとしてstdoutに値がセットされます。
そして、その改行を1行づつバラしたのがstdout_linesです。
ディレクトリ一覧などを取って、ぐるぐる回す時など便利ですね。

## 複数回実行されるタスクの場合は？

サンプルとして、以下のようなplaybookを用意しました。
lsコマンドの結果を、statコマンドに渡しています。

```yaml
---
- hosts: 127.0.0.1
  connection: local
  tasks:
    - name: ls dir
      shell: ls -U /tmp/dir
      register: file_list
    - name: stat file in dir
      shell: stat /tmp/dir/{{ item }}
      register: stat_list
      with_items: file_list.stdout_lines
    - debug: var=stat_list
```

結果は、こんな感じです。
変数stat_listの中に、resultsというキーで配列として実行結果がセットされています。

```bash
[ec2-user@ip-10-0-0-83 ~]$ ansible-playbook playbook.yml

PLAY [127.0.0.1] **************************************************************

GATHERING FACTS ***************************************************************
ok: [127.0.0.1]

TASK: [ls dir] ****************************************************************
changed: [127.0.0.1]

TASK: [shell stat /tmp/dir/{{ item }}] ****************************************
changed: [127.0.0.1] => (item=foo)
changed: [127.0.0.1] => (item=bar)

TASK: [debug var=stat_list] ***************************************************
ok: [127.0.0.1] => {
    "var": {
        "stat_list": {
            "changed": true,
            "msg": "All items completed",
            "results": [
                {
                    "changed": true,
                    "cmd": "stat /tmp/dir/foo",
                    "delta": "0:00:00.002497",
                    "end": "2016-01-08 05:15:36.943282",
                    "invocation": {
                        "module_args": "stat /tmp/dir/foo",
                        "module_complex_args": {},
                        "module_name": "shell"
                    },
                    "item": "foo",
                    "rc": 0,
                    "start": "2016-01-08 05:15:36.940785",
                    "stderr": "",
                    "stdout": "  File: ‘/tmp/dir/foo’\n  Size: 0         \tBlocks: 0          IO Block: 4096   regular empty file\nDevice: ca01h/51713d\tInode: 1794        Links: 1\nAccess: (0664/-rw-rw-r--)  Uid: (  500/ec2-user)   Gid: (  500/ec2-user)\nAccess: 2016-01-08 04:58:45.291452795 +0000\nModify: 2016-01-08 04:58:45.291452795 +0000\nChange: 2016-01-08 04:58:45.291452795 +0000\n Birth: -",
                    "stdout_lines": [
                        "  File: ‘/tmp/dir/foo’",
                        "  Size: 0         \tBlocks: 0          IO Block: 4096   regular empty file",
                        "Device: ca01h/51713d\tInode: 1794        Links: 1",
                        "Access: (0664/-rw-rw-r--)  Uid: (  500/ec2-user)   Gid: (  500/ec2-user)",
                        "Access: 2016-01-08 04:58:45.291452795 +0000",
                        "Modify: 2016-01-08 04:58:45.291452795 +0000",
                        "Change: 2016-01-08 04:58:45.291452795 +0000",
                        " Birth: -"
                    ],
                    "warnings": []
                },
                {
                    "changed": true,
                    "cmd": "stat /tmp/dir/bar",
                    "delta": "0:00:00.002559",
                    "end": "2016-01-08 05:15:36.999742",
                    "invocation": {
                        "module_args": "stat /tmp/dir/bar",
                        "module_complex_args": {},
                        "module_name": "shell"
                    },
                    "item": "bar",
                    "rc": 0,
                    "start": "2016-01-08 05:15:36.997183",
                    "stderr": "",
                    "stdout": "  File: ‘/tmp/dir/bar’\n  Size: 0         \tBlocks: 0          IO Block: 4096   regular empty file\nDevice: ca01h/51713d\tInode: 1795        Links: 1\nAccess: (0664/-rw-rw-r--)  Uid: (  500/ec2-user)   Gid: (  500/ec2-user)\nAccess: 2016-01-08 04:58:45.291452795 +0000\nModify: 2016-01-08 04:58:45.291452795 +0000\nChange: 2016-01-08 04:58:45.291452795 +0000\n Birth: -",
                    "stdout_lines": [
                        "  File: ‘/tmp/dir/bar’",
                        "  Size: 0         \tBlocks: 0          IO Block: 4096   regular empty file",
                        "Device: ca01h/51713d\tInode: 1795        Links: 1",
                        "Access: (0664/-rw-rw-r--)  Uid: (  500/ec2-user)   Gid: (  500/ec2-user)",
                        "Access: 2016-01-08 04:58:45.291452795 +0000",
                        "Modify: 2016-01-08 04:58:45.291452795 +0000",
                        "Change: 2016-01-08 04:58:45.291452795 +0000",
                        " Birth: -"
                    ],
                    "warnings": []
                }
            ]
        }
    }
}
```

# タスクが失敗したときは？
タスクが失敗した時にRegistered variable はどうなるでしょうか？
こんな playbook を用意しました。fooというコマンドは存在しないので、エラーになります。

```yaml
---
- hosts: 127.0.0.1
  connection: local
  tasks:
     - shell: /usr/bin/foo
       register: foo_result
       ignore_errors: True
     - debug: var=foo_result
```

実行結果です。rcに0以外が入り、stderrにエラーメッセージが入っていることが確認できます。

```bash
[ec2-user@ip-10-0-0-83 ~]$ ansible-playbook foo.yml

PLAY [127.0.0.1] **************************************************************

GATHERING FACTS ***************************************************************
ok: [127.0.0.1]

TASK: [shell /usr/bin/foo] ****************************************************
failed: [127.0.0.1] => {"changed": true, "cmd": "/usr/bin/foo", "delta": "0:00:00.001851", "end": "2016-01-08 05:42:46.712188", "rc": 127, "start": "2016-01-08 05:42:46.710337", "warnings": []}
stderr: /bin/sh: /usr/bin/foo: No such file or directory
...ignoring

TASK: [debug var=foo_result] **************************************************
ok: [127.0.0.1] => {
    "var": {
        "foo_result": {
            "changed": true,
            "cmd": "/usr/bin/foo",
            "delta": "0:00:00.001851",
            "end": "2016-01-08 05:42:46.712188",
            "invocation": {
                "module_args": "/usr/bin/foo",
                "module_complex_args": {},
                "module_name": "shell"
            },
            "rc": 127,
            "start": "2016-01-08 05:42:46.710337",
            "stderr": "/bin/sh: /usr/bin/foo: No such file or directory",
            "stdout": "",
            "stdout_lines": [],
            "warnings": []
        }
    }
}

PLAY RECAP ********************************************************************
127.0.0.1                  : ok=3    changed=1    unreachable=0    failed=0
```
ちなみに、公式サイトにNoteとして以下のような記述があります。

> If a task fails or is skipped, the variable still is registered with a failure or skipped status, the only way to avoid registering a variable is using tags.

タスクが失敗したり、スキップしたりした場合も変数は設定されます。失敗(or スキップ)したからと言って変数が設定されないわけではないので、playbookの中で、whenキーワードで変数の宣言自体をハンドリングする(is defined)ようなtaskには注意が必要かもしれません。


# まとめ
Registered variableを使うと、ターゲットホストの状態をハンドリングしながらplaybookが書けるので、**とても便利です** :laughing: 

rcをハンドリングして細かい条件分岐をしたり、複数回実行される時のパターンのように直接with_itemsに突っ込んだり、用途はいろいろ考えられます。

実際によくやる手としては、ファイルやディレクトリをチェックしてその後の処理を分岐させたりすることなどが多いです。

そのうち、よくやるTips集などをまとめてみたいと思います。


# 参考
http://docs.ansible.com/ansible/playbooks_variables.html#registered-variables
http://docs.ansible.com/ansible/playbooks_conditionals.html#register-variables