はじめに
コマンドの出力結果などで dict らしき出力を受け取った後、その中の値から必要な値を抽出する機会がときどきあります。
ところが、たとえば、キーを指定して dictらしき結果.キー名
を指定すると、以下のようなエラーまってしまうことがあります。
ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'キー名'
前段で debug
モジュールで表示しても dict らしき値として表示されますが、よく確認するとただのテキストだったりします。
こんなケースでの確認や、対処方法をまとめます。
- 検証環境
- ansible-core 2.14.4
再現用のPlaybook
以下のPlaybookを再現に利用します。
--- - name: Test play hosts: localhost connection: local gather_facts: false tasks: # JSONファイルの中身を表示(再現用の簡易的なもの) - name: Get JSON Text ansible.builtin.command: cmd: cat out.json changed_when: false register: json_text # デバッグ用に一旦 stduout 全体を表示 - name: Debug1 stduout ansible.builtin.debug: msg: "{{ json_text.stdout }}" # JSON 内の key1 の値を表示 - name: Debug2 key specified ansible.builtin.debug: msg: "{{ json_text.stdout.key1 }}"
なお、 out.json
の内容は以下のとおりです。今回は簡単に再現させるために用意しました。
{ "key1": "val1", "key2": "val2", "key3": "val3" }
つまり最後のタスクは、 val1
が表示されることを期待しています。
再現
先ほどの Playbook を実行して再現します。
% ansible-playbook -i localhost, dict_test.yml ...(略)... TASK [Debug] *************************************************************************************************************** fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'key1'. 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'key1'\n\nThe error appears to be in '/Users/sakana/ansible/qa/dict_test.yml': line 14, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: Debug\n ^ here\n"} ...(略)...
このように、key1
というアトリビュート(キー)がないよ、というエラーになりました。
stdout
全体を表示したタスクでは、いかにも JSON らしく表示されいて、key1
もあります。そのため、個人的な経験としてはここでおやおやと思ったことが何度かあります。
調査
エラーメッセージには、ansible.utils.unsafe_proxy.AnsibleUnsafeText object
とあるため、json_text.stdout
の中身は dict ではなく、ただのテキストと扱われてることが分かります。
念のため ansible.builtin.type_debug
フィルターで型を表示させてみます。
# デバッグ用に一旦 stduout 全体を表示 - name: Debug1 stduout ansible.builtin.debug: msg: "{{ json_text.stdout | ansible.builtin.type_debug }}"
結果は以下のとおり、AnsibleUnsafeText
と表示されました。
ok: [localhost] => { "msg": "AnsibleUnsafeText" }
もし本当に dict であれば、dict
と表示されるはずです。ということで、おかしいぞ、dict
のようで dict
じゃないぞと気がつけます。
また、json_text.stdout
ではなく、json_text
だけを表示させると、stdout
は以下のように表示されます。これはこれでテキストだと判断できます。
ok: [localhost] => { "msg": { // ...(略)... "stdout": "{\n \"key1\": \"val1\",\n \"key2\": \"val2\",\n \"key3\": \"val3\"\n}", // ...(略)... }
対処
今回のようなケースの場合、dict
として扱うために、ansible.builtin.from_json
フィルターを利用します。
json_text.stdout.key1
だったところを (json_text.stdout | ansible.builtin.from_json).key1
にします。
- name: Debug2 key specified ansible.builtin.debug: msg: "{{ (json_text.stdout | ansible.builtin.from_json).key1 }}"
これで .key1
で参照できるようになります。
ok: [localhost] => { "msg": "val1" }
タスク変数でワンクッション設けてもいいかもしれません。
- name: Debug2 key specified ansible.builtin.debug: msg: "{{ converted_dict.key1 }}" vars: converted_dict: "{{ json_text.stdout | ansible.builtin.from_json }}"
参考
書いてる途中で思い出しましたが、過去に似たような記事を書いていました。
最近のバージョンの Ansible でも同じ方法が使えることを示すという意味で、今回の記事はこれはこれで書いておきます。