LoginSignup
4
1

More than 3 years have passed since last update.

AnsibleでJSON形式のデータをマージする

Last updated at Posted at 2019-12-21

はじめに

以前の記事で、事前に定義したJSONデータと、設定後のJSONデータを差分比較し、想定通り設定が反映されているか確認しました。
今回は2つのJSONデータを、Ansibleを使ってマージしたいと思います。具体的には、Cisco IOS-XEのインターフェース設定をRESTCONFで行う前に、既存設定と追加設定をマージしてみます。

1. 用意した環境

Cisco DevNet SandboxのIOS XE on CSR Recommended CodeのCSR1000v 16.9.3を利用させて頂きました。
Python3.6.8のvenv仮想環境上で、Ansible2.9.2をインストールして使いました。

2. フィルタープラグイン

JSONMergeをAnsibleフィルタープラグインとして取り込みました。こちらはPythonベースのモジュールで、base(元々のJSONデータ)に対し、head(追加のJSONデータ)をマージして結果を出力してくれます。
Merge Strategiesでマージ方法を指定できます。特に指定しない場合はoverwriteとなり、古い値が上書されてしまいます。実際には、配列データ[ 〇, △ ]は要素を追加したいケースが多いと思います。その場合は、append(単純に追加)やarrayMergeById(要素内の特定のkeyの値が同じものはマージ、新しいものは追加)等を選択可能です。
実例は後ほどご紹介しますが、どのデータをどう処理するかは、JSONスキーマと同じ要領で指定できます。

2-1. 事前準備

JSONMergeのインストールを行います。

$ pip install jsonmerge

2-2. Pythonコード(抜粋)

こちらの記事と同様、filter_pluginsディレクトリ内に以下のファイルを格納しました。(実際には、他のカスタムフィルターも同ファイル内に書いています。)
やっている事は以下の2点です。

  • HAS_JSONMERGEで、JSONMergeがインストールされているか判定
  • JSONスキーマに従い、2つのJSONデータをマージしたものを返す
custom_filters1.py
from __future__ import absolute_import, division, print_function
__metaclass__ = type

from ansible.module_utils.six import PY3, string_types
from ansible.errors import AnsibleError, AnsibleFilterError

try:
    from jsonmerge import Merger
    HAS_JSONMERGE = True
except ImportError:
    HAS_JSONMERGE = False


class FilterModule(object):

    def jsonmerge(self, output1, output2, schema):
        if not HAS_JSONMERGE:
            raise AnsibleFilterError("JSONMerge not found. Run 'pip install jsonmerge'")
        merger = Merger(schema)
        merge_result = merger.merge(output1, output2)
        return merge_result

    def filters(self):
        return {
            'jsonmerge': self.jsonmerge
        }

3. Inventoryファイル

以前の記事と同じものを使いました。

4. Playbook

各タスクの内容は以下の通りです。

  1. restconf_getモジュールで、既存のインターフェース設定を取得(GigabieEthernet1~3の内、1は設定済み)
  2. タスク1の結果を出力
  3. Playbook変数content_data内に、GigabitEthernet2のDescription、閉塞解除(no shutdown)、IPアドレス設定を定義。
    タスク1の結果と、content_dataをフィルタープラグインjsonmergeでマージ。
    この時、タスク変数schemaで、インターフェース設定(interface:)のMerge StrategyをarrayMergeByIdに指定。インターフェース名をIDとして(idRef: name)、同じIDは設定をマージするよう指定。
playbook_restconf_int_merge1.yml
---

- hosts: cisco
  gather_facts: no

  vars:
    content_data:
      ietf-interfaces:interfaces:
        interface:
          - description: "For TEST"
            enabled: true
            ietf-ip:ipv4:
              address:
                - ip: 192.168.1.1
                  netmask: 255.255.255.0
            name: GigabitEthernet2

  tasks:
    - name: get interface info   # (1)
      restconf_get:
        path: /data/ietf-interfaces:interfaces
      register: result_before

    - name: display output data   # (2)
      debug:
        msg: "{{ result_before.response }}"

    - name: merge existing and setup interface settings   # (3)
      debug:
        msg: "{{ result_before.response | jsonmerge(content_data, schema) }}"
      vars:
        schema:
          properties:
            ietf-interfaces:interfaces:
              properties:
                interface:
                  mergeStrategy: arrayMergeById
                  mergeOptions:
                    idRef: name

5. 出力結果

タスク3の結果を見ると、問題なくマージされている事が分かります。

$ ansible-playbook -i inventory_restconf1.ini playbook_restconf_int_merge1.yml

PLAY [cisco] **********************************************************************************************************

TASK [get interface info] *********************************************************************************************
ok: [csr1000v-1]

TASK [display output data] ********************************************************************************************
ok: [csr1000v-1] => {
    "msg": {
        "ietf-interfaces:interfaces": {
            "interface": [
                {
                    "description": "MANAGEMENT INTERFACE - DON'T TOUCH ME",
                    "enabled": true,
                    "ietf-ip:ipv4": {
                        "address": [
                            {
                                "ip": "10.10.20.48",
                                "netmask": "255.255.255.0"
                            }
                        ]
                    },
                    "ietf-ip:ipv6": {},
                    "name": "GigabitEthernet1",
                    "type": "iana-if-type:ethernetCsmacd"
                },
                {
                    "enabled": false,
                    "ietf-ip:ipv4": {},
                    "ietf-ip:ipv6": {},
                    "name": "GigabitEthernet2",
                    "type": "iana-if-type:ethernetCsmacd"
                },
                {
                    "description": "Network Interface",
                    "enabled": false,
                    "ietf-ip:ipv4": {},
                    "ietf-ip:ipv6": {},
                    "name": "GigabitEthernet3",
                    "type": "iana-if-type:ethernetCsmacd"
                }
            ]
        }
    }
}

TASK [merge existing and setup interface settings] ********************************************************************
ok: [csr1000v-1] => {
    "msg": {
        "ietf-interfaces:interfaces": {
            "interface": [
                {
                    "description": "MANAGEMENT INTERFACE - DON'T TOUCH ME",
                    "enabled": true,
                    "ietf-ip:ipv4": {
                        "address": [
                            {
                                "ip": "10.10.20.48",
                                "netmask": "255.255.255.0"
                            }
                        ]
                    },
                    "ietf-ip:ipv6": {},
                    "name": "GigabitEthernet1",
                    "type": "iana-if-type:ethernetCsmacd"
                },
                {
                    "description": "For TEST",
                    "enabled": true,
                    "ietf-ip:ipv4": {
                        "address": [
                            {
                                "ip": "192.168.1.1",
                                "netmask": "255.255.255.0"
                            }
                        ]
                    },
                    "ietf-ip:ipv6": {},
                    "name": "GigabitEthernet2",
                    "type": "iana-if-type:ethernetCsmacd"
                },
                {
                    "description": "Network Interface",
                    "enabled": false,
                    "ietf-ip:ipv4": {},
                    "ietf-ip:ipv6": {},
                    "name": "GigabitEthernet3",
                    "type": "iana-if-type:ethernetCsmacd"
                }
            ]
        }
    }
}

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

(参考) インターフェース設定変更

ご参考までに、uriモジュールで実際にインターフェース設定を行った結果を貼り付けておきます。

(参考1) 設定変更のPlaybook

playbook_restconf_int_merge2.yml
---

- hosts: cisco
  gather_facts: no

  vars:
    content_data:
      ietf-interfaces:interfaces:
        interface:
          - description: "For TEST"
            enabled: true
            ietf-ip:ipv4:
              address:
                - ip: 192.168.1.1
                  netmask: 255.255.255.0
            name: GigabitEthernet2

  tasks:
    - name: merge interface settings
      uri:
        url: https://{{ansible_host}}:{{ansible_httpapi_port}}/restconf/data/ietf-interfaces:interfaces
        method: PATCH
        headers:
          Content-Type: application/yang-data+json
          Accept: application/yang-data+json
        body_format: json
        body: "{{ content_data | to_json }}"
        status_code:
          - 200
          - 204
        url_username: "{{ ansible_user }}"
        url_password: "{{ ansible_password }}"
        force_basic_auth: yes
        validate_certs: no

    - name: get interface info
      restconf_get:
        path: /data/ietf-interfaces:interfaces
      register: result

    - name: display output data
      debug:
        msg: "{{ result.response }}"

(参考2) 設定変更の出力結果

$ ansible-playbook -i inventory_restconf1.ini playbook_restconf_int_merge2.yml

PLAY [cisco] **********************************************************************************************************

TASK [merge interface settings] ***************************************************************************************
ok: [csr1000v-1]

TASK [get interface info] *********************************************************************************************
ok: [csr1000v-1]

TASK [display output data] ********************************************************************************************
ok: [csr1000v-1] => {
    "msg": {
        "ietf-interfaces:interfaces": {
            "interface": [
                {
                    "description": "MANAGEMENT INTERFACE - DON'T TOUCH ME",
                    "enabled": true,
                    "ietf-ip:ipv4": {
                        "address": [
                            {
                                "ip": "10.10.20.48",
                                "netmask": "255.255.255.0"
                            }
                        ]
                    },
                    "ietf-ip:ipv6": {},
                    "name": "GigabitEthernet1",
                    "type": "iana-if-type:ethernetCsmacd"
                },
                {
                    "description": "For TEST",
                    "enabled": true,
                    "ietf-ip:ipv4": {
                        "address": [
                            {
                                "ip": "192.168.1.1",
                                "netmask": "255.255.255.0"
                            }
                        ]
                    },
                    "ietf-ip:ipv6": {},
                    "name": "GigabitEthernet2",
                    "type": "iana-if-type:ethernetCsmacd"
                },
                {
                    "description": "Network Interface",
                    "enabled": false,
                    "ietf-ip:ipv4": {},
                    "ietf-ip:ipv6": {},
                    "name": "GigabitEthernet3",
                    "type": "iana-if-type:ethernetCsmacd"
                }
            ]
        }
    }
}

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

最後に

今回のケースでは想定通りのマージ結果が得られました。ただし、ネストの関係にある2つ以上の配列データを上書きではなく追加したい場合、JSONスキーマが競合してうまく行きませんでした。何かやり方があるのかもしれませんが、気力の限界に達したので、また今度トライしたいと思います:sweat:

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1