てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] エスケープされた JSON を正しくディクショナリにする from_json フィルター

はじめに

Ansible では、結果が JSON になるタスクを実行すると、(少なくとも表示上は)エスケープされた JSON が返ってくることがあります。

この場合、構造化データのように見えて文字列なので、ディクショナリ(構造化データ)として正しく扱えません。

from_json フィルターをかけると、うまくいきます。

この記事では簡単なサンプルをもとに説明します。

  • 動作確認環境
    • Ansible 2.9.7


エスケープされた JSON とは?

以下は、nclu モジュールを利用して、Cumulus Linux に対して、show bgp summary json コマンドを実行した結果を、debug モジュールで表示した結果です。

msg 内に \ によるエスケープが入っています。JSON のように見えてただの文字列です。

TASK [debug show bgp summary] *************************************************************************
ok: [leaf01] => {
    "msg": {
        "changed": false,
        "failed": false,
        "msg": "{\n    \"ipv4 unicast\": {\n        \"as\": 65011, \n        \"bestPath\": {\n            \"multiPathRelax\": \"true\"\n        }, \n        \"dynamicPeers\": 0, \n        \"peerCount\": 2, \n        \"peerGroupCount\": 1, \n        \"peerGroupMemory\": 64, \n        \"peerMemory\": 42352, 
        ...(略)...
    }
}

このままでは、result.msg['ipv4 unicast'] のように、内部のキーを指定しても、'dict object' has no attribute 'ipv4 unicast' のような、キーの参照エラーになってしまいます。

ちょっとハマりやすいのは、上記例でいうと result.msg を参照したときは、以下のように、いかにも正しい JSON っぽく表示される点です。

TASK [debug show bgp summary] *************************************************************************
ok: [leaf01] => {
    "msg": {
        "ipv4 unicast": {
            "as": 65011,
            "bestPath": {
                "multiPathRelax": "true"
            },
            "dynamicPeers": 0,
            "peerCount": 2,
            ...(略)...

どう見てもディクショナリとして扱えそうですが、{{ result.msg | type_debug }} の結果は AnsibleUnsafeText です。どうして・・。


from_json フィルターでディクショナリに変換

正しくディクショナリとして扱うために、from_json フィルターをかけます。

"{{ result_bgp_summary_raw | from_json }}"

こでれで、JSON、ディクショナリになります。

TASK [debug show bgp summary] *************************************************************************
ok: [leaf01] => {
    "msg": {
        "ipv4 unicast": {
            "as": 65011,
            "bestPath": {
                "multiPathRelax": "true"
            },
            "dynamicPeers": 0,
            "peerCount": 2,
            ...(略)...

"{{ result_bgp_summary_raw.msg | from_json | type_debug }}" の結果は dict です。

ここまでくれば、通常のディクショナリと同じく、特定のキーを参照したりするだけです。

タスク例

    - name: debug show bgp summary
      debug:
        msg: "{{ bgp_summary['ipv4 unicast']['peerCount'] }}"
      vars:
        bgp_summary: "{{ result_bgp_summary_raw.msg | from_json }}"

(キーの指定を .ipv4 unicast ではなく ['ipv4 unicast'] としているのは、今回対象のデータに、たまたまスペース入りのキー名があったためです)

実行例

TASK [debug show bgp summary] *************************************************************************
ok: [leaf01] => {
    "msg": "2"
}


■ おわりに

エスケープされた JSON に、from_json フィルターをかけて、ディクショナリにする方法をご紹介しました。

私だけかもしれませんが、Ansible で構造化データの扱いで悩むことが結構あります。

あるはずのキーが has no attribute となってハマったときは、エスケープされてないか、type_debugの結果はどうなるか、などを確認するのがよさそうです。