diff --git a/.gitignore b/.gitignore index 740c811..4e54cab 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,6 @@ dmypy.json # Pyre type checker .pyre/ + +# ide +.vscode diff --git a/README.md b/README.md index 7f3162f..f76b280 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,12 @@ Name | Description [ansible.utils.get_path](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.get_path_filter.rst)|Retrieve the value in a variable using a path [ansible.utils.hwaddr](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.hwaddr_filter.rst)|HWaddr / MAC address filters [ansible.utils.index_of](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.index_of_filter.rst)|Find the indices of items in a list matching some criteria +[ansible.utils.keep_keys](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.keep_keys_filter.rst)|Keep specific keys from a data recursively. +[ansible.utils.nthhost](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.nthhost_filter.rst)|This filter returns the nth host within a network described by value. +[ansible.utils.ipv4](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.ipv4_filter.rst)|To filter only Ipv4 addresses Ipv4 filter is used. +[ansible.utils.param_list_compare](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.param_list_compare_filter.rst)|Generate the final param list combining/comparing base and provided parameters. +[ansible.utils.remove_keys](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.remove_keys_filter.rst)|Remove specific keys from a data recursively. +[ansible.utils.replace_keys](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.replace_keys_filter.rst)|Replaces specific keys with their after value from a data recursively. [ansible.utils.macaddr](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.macaddr_filter.rst)|macaddr / MAC address filters [ansible.utils.slaac](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.slaac_filter.rst)|This filter returns the SLAAC address within a network for a given HW/MAC address. [ansible.utils.ipaddr](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.ipaddr_filter.rst)|This filter is designed to return the input value if a query is True, else False. diff --git a/changelogs/fragments/recursive_filter_plugins.yaml b/changelogs/fragments/recursive_filter_plugins.yaml new file mode 100644 index 0000000..bf942fd --- /dev/null +++ b/changelogs/fragments/recursive_filter_plugins.yaml @@ -0,0 +1,5 @@ +--- +minor_changes: + - "'keep_keys' filter plugin added." + - "'remove_keys' filter plugin added." + - "'replace_keys' filter plugin added." diff --git a/docs/ansible.utils.keep_keys_filter.rst b/docs/ansible.utils.keep_keys_filter.rst new file mode 100644 index 0000000..78071cf --- /dev/null +++ b/docs/ansible.utils.keep_keys_filter.rst @@ -0,0 +1,374 @@ +.. _ansible.utils.keep_keys_filter: + + +*********************** +ansible.utils.keep_keys +*********************** + +**Keep specific keys from a data recursively.** + + +Version added: 2.5.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This plugin keep only specified keys from a provided data recursively. +- Matching parameter defaults to equals unless ``matching_parameter`` is explicitly mentioned. +- Using the parameters below- ``data|ansible.utils.keep_keys(target([....]``)) + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsConfigurationComments
+
+ data + +
+ raw + / required +
+
+ + +
This option represents a list of dictionaries or a dictionary with any level of nesting data.
+
For example config_data|ansible.utils.keep_keys(target([....])), in this case config_data represents this option.
+
+
+ matching_parameter + +
+ string +
+
+
    Choices: +
  • starts_with
  • +
  • ends_with
  • +
  • regex
  • +
+
+ +
Specify the matching configuration of target keys and data attributes.
+
+
+ target + +
+ list + / elements=string + / required +
+
+ + +
Specify the target keys to keep in list format.
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + ##example.yaml + interfaces: + - name: eth0 + enabled: true + duplex: auto + speed: auto + note: + - Connected green wire + - name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + enabled: true + note: + - Connected blue wire + - Configured by Paul + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + enabled: true + comment: Needs reconfiguration + - vlan_id: 101 + description: Eth1 - VIF 101 + enabled: true + - name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + enabled: false + + ##Playbook + vars_files: + - "example.yaml" + tasks: + - name: keep selective keys from dict/list of dict data + ansible.builtin.set_fact: + data: "{{ interfaces }}" + + - debug: + msg: "{{ data|ansible.utils.keep_keys(target=['description', 'name', 'mtu', 'duplex', 'enabled', 'vifs', 'vlan_id']) }}" + + ##Output + # TASK [keep selective keys from python dict/list of dict] **************************************************************************************** + # ok: [localhost] => { + # "ansible_facts": { + # "data": [ + # { + # "duplex": "auto", + # "enabled": true, + # "name": "eth0", + # "note": [ + # "Connected green wire" + # ], + # "speed": "auto" + # }, + # { + # "description": "Configured by Ansible - Interface 1", + # "duplex": "auto", + # "enabled": true, + # "mtu": 1500, + # "name": "eth1", + # "note": [ + # "Connected blue wire", + # "Configured by Paul" + # ], + # "speed": "auto", + # "vifs": [ + # { + # "comment": "Needs reconfiguration", + # "description": "Eth1 - VIF 100", + # "enabled": true, + # "mtu": 400, + # "vlan_id": 100 + # }, + # { + # "description": "Eth1 - VIF 101", + # "enabled": true, + # "vlan_id": 101 + # } + # ] + # }, + # { + # "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + # "enabled": false, + # "mtu": 600, + # "name": "eth2" + # } + # ] + # }, + # "changed": false + # } + # Read vars_file 'example.yaml' + + # TASK [debug] ************************************************************************************************************* + # ok: [localhost] => { + # "msg": [ + # { + # "duplex": "auto", + # "enabled": true, + # "name": "eth0" + # }, + # { + # "description": "Configured by Ansible - Interface 1", + # "duplex": "auto", + # "enabled": true, + # "mtu": 1500, + # "name": "eth1", + # "vifs": [ + # { + # "description": "Eth1 - VIF 100", + # "enabled": true, + # "mtu": 400, + # "vlan_id": 100 + # }, + # { + # "description": "Eth1 - VIF 101", + # "enabled": true, + # "vlan_id": 101 + # } + # ] + # }, + # { + # "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + # "enabled": false, + # "mtu": 600, + # "name": "eth2" + # } + # ] + # } + + ##example.yaml + interfaces: + - name: eth0 + enabled: true + duplex: auto + speed: auto + note: + - Connected green wire + - name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + enabled: true + note: + - Connected blue wire + - Configured by Paul + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + enabled: true + comment: Needs reconfiguration + - vlan_id: 101 + description: Eth1 - VIF 101 + enabled: true + - name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + enabled: false + + ##Playbook + vars_files: + - "example.yaml" + tasks: + - name: keep selective keys from dict/list of dict data + ansible.builtin.set_fact: + data: "{{ interfaces }}" + + - debug: + msg: "{{ data|ansible.utils.keep_keys(target=['desc', 'name'], matching_parameter= 'starts_with') }}" + + ##Output + # TASK [keep selective keys from python dict/list of dict] ************************** + # ok: [localhost] => { + # "ansible_facts": { + # "data": [ + # { + # "duplex": "auto", + # "enabled": true, + # "name": "eth0", + # "note": [ + # "Connected green wire" + # ], + # "speed": "auto" + # }, + # { + # "description": "Configured by Ansible - Interface 1", + # "duplex": "auto", + # "enabled": true, + # "mtu": 1500, + # "name": "eth1", + # "note": [ + # "Connected blue wire", + # "Configured by Paul" + # ], + # "speed": "auto", + # "vifs": [ + # { + # "comment": "Needs reconfiguration", + # "description": "Eth1 - VIF 100", + # "enabled": true, + # "mtu": 400, + # "vlan_id": 100 + # }, + # { + # "description": "Eth1 - VIF 101", + # "enabled": true, + # "vlan_id": 101 + # } + # ] + # }, + # { + # "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + # "enabled": false, + # "mtu": 600, + # "name": "eth2" + # } + # ] + # }, + # "changed": false + # } + # Read vars_file 'example.yaml' + + # TASK [debug] ********************************************************************************** + # ok: [localhost] => { + # "msg": [ + # { + # "name": "eth0" + # }, + # { + # "description": "Configured by Ansible - Interface 1", + # "name": "eth1", + # "vifs": [ + # { + # "description": "Eth1 - VIF 100" + # }, + # { + # "description": "Eth1 - VIF 101" + # } + # ] + # }, + # { + # "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + # "name": "eth2" + # } + # ] + # } + + + + +Status +------ + + +Authors +~~~~~~~ + +- Sagar Paul (@KB-perByte) + + +.. hint:: + Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up. diff --git a/docs/ansible.utils.remove_keys_filter.rst b/docs/ansible.utils.remove_keys_filter.rst new file mode 100644 index 0000000..2491517 --- /dev/null +++ b/docs/ansible.utils.remove_keys_filter.rst @@ -0,0 +1,390 @@ +.. _ansible.utils.remove_keys_filter: + + +************************* +ansible.utils.remove_keys +************************* + +**Remove specific keys from a data recursively.** + + +Version added: 2.5.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This plugin removes specific keys from a provided data recursively. +- Matching parameter defaults to equals unless ``matching_parameter`` is explicitly mentioned. +- Using the parameters below- ``data|ansible.utils.remove_keys(target([....]``)) + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsConfigurationComments
+
+ data + +
+ raw + / required +
+
+ + +
This option represents a list of dictionaries or a dictionary with any level of nesting data.
+
For example config_data|ansible.utils.remove_keys(target([....])), in this case config_data represents this option.
+
+
+ matching_parameter + +
+ string +
+
+
    Choices: +
  • starts_with
  • +
  • ends_with
  • +
  • regex
  • +
+
+ +
Specify the matching configuration of target keys and data attributes.
+
+
+ target + +
+ list + / elements=string + / required +
+
+ + +
Specify the target keys to remove in list format.
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + ##example.yaml + interfaces: + - name: eth0 + enabled: true + duplex: auto + speed: auto + note: + - Connected green wire + - name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + enabled: true + note: + - Connected blue wire + - Configured by Paul + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + enabled: true + comment: Needs reconfiguration + - vlan_id: 101 + description: Eth1 - VIF 101 + enabled: true + - name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + enabled: false + + ##Playbook + vars_files: + - "example.yaml" + tasks: + - name: remove multiple keys from a provided data + ansible.builtin.set_fact: + data: "{{ interfaces }}" + + - debug: + msg: "{{ data|ansible.utils.remove_keys(target=['note', 'comment']) }}" + + ##Output + # TASK [remove multiple keys from a provided data] *************************************** + # ok: [localhost] => { + # "ansible_facts": { + # "data": [ + # { + # "duplex": "auto", + # "enabled": true, + # "name": "eth0", + # "note": [ + # "Connected green wire" + # ], + # "speed": "auto" + # }, + # { + # "description": "Configured by Ansible - Interface 1", + # "duplex": "auto", + # "enabled": true, + # "mtu": 1500, + # "name": "eth1", + # "note": [ + # "Connected blue wire", + # "Configured by Paul" + # ], + # "speed": "auto", + # "vifs": [ + # { + # "comment": "Needs reconfiguration", + # "description": "Eth1 - VIF 100", + # "enabled": true, + # "mtu": 400, + # "vlan_id": 100 + # }, + # { + # "description": "Eth1 - VIF 101", + # "enabled": true, + # "vlan_id": 101 + # } + # ] + # }, + # { + # "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + # "enabled": false, + # "mtu": 600, + # "name": "eth2" + # } + # ] + # }, + # "changed": false + # } + # Read vars_file 'example.yaml' + + # TASK [debug] ******************************************** + # ok: [localhost] => { + # "msg": [ + # { + # "duplex": "auto", + # "enabled": true, + # "name": "eth0", + # "speed": "auto" + # }, + # { + # "description": "Configured by Ansible - Interface 1", + # "duplex": "auto", + # "enabled": true, + # "mtu": 1500, + # "name": "eth1", + # "speed": "auto", + # "vifs": [ + # { + # "description": "Eth1 - VIF 100", + # "enabled": true, + # "mtu": 400, + # "vlan_id": 100 + # }, + # { + # "description": "Eth1 - VIF 101", + # "enabled": true, + # "vlan_id": 101 + # } + # ] + # }, + # { + # "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + # "enabled": false, + # "mtu": 600, + # "name": "eth2" + # } + # ] + # } + + ##example.yaml + interfaces: + - name: eth0 + enabled: true + duplex: auto + speed: auto + note: + - Connected green wire + - name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + enabled: true + note: + - Connected blue wire + - Configured by Paul + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + enabled: true + comment: Needs reconfiguration + - vlan_id: 101 + description: Eth1 - VIF 101 + enabled: true + - name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + enabled: false + + ##Playbook + vars_files: + - "example.yaml" + tasks: + - name: remove multiple keys from a provided data + ansible.builtin.set_fact: + data: "{{ interfaces }}" + + - debug: + msg: "{{ data|ansible.utils.remove_keys(target=['^note$', '^comment'], matching_parameter= 'regex') }}" + + ##Output + # TASK [remove multiple keys from a provided data] *********************** + # ok: [localhost] => { + # "ansible_facts": { + # "data": [ + # { + # "duplex": "auto", + # "enabled": true, + # "name": "eth0", + # "note": [ + # "Connected green wire" + # ], + # "speed": "auto" + # }, + # { + # "description": "Configured by Ansible - Interface 1", + # "duplex": "auto", + # "enabled": true, + # "mtu": 1500, + # "name": "eth1", + # "note": [ + # "Connected blue wire", + # "Configured by Paul" + # ], + # "speed": "auto", + # "vifs": [ + # { + # "comment": "Needs reconfiguration", + # "description": "Eth1 - VIF 100", + # "enabled": true, + # "mtu": 400, + # "vlan_id": 100 + # }, + # { + # "description": "Eth1 - VIF 101", + # "enabled": true, + # "vlan_id": 101 + # } + # ] + # }, + # { + # "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + # "enabled": false, + # "mtu": 600, + # "name": "eth2" + # } + # ] + # }, + # "changed": false + # } + # Read vars_file 'example.yaml' + + # TASK [debug] ***************************************** + # ok: [localhost] => { + # "msg": [ + # { + # "duplex": "auto", + # "enabled": true, + # "name": "eth0", + # "speed": "auto" + # }, + # { + # "description": "Configured by Ansible - Interface 1", + # "duplex": "auto", + # "enabled": true, + # "mtu": 1500, + # "name": "eth1", + # "speed": "auto", + # "vifs": [ + # { + # "description": "Eth1 - VIF 100", + # "enabled": true, + # "mtu": 400, + # "vlan_id": 100 + # }, + # { + # "description": "Eth1 - VIF 101", + # "enabled": true, + # "vlan_id": 101 + # } + # ] + # }, + # { + # "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + # "enabled": false, + # "mtu": 600, + # "name": "eth2" + # } + # ] + # } + + + + +Status +------ + + +Authors +~~~~~~~ + +- Sagar Paul (@KB-perByte) + + +.. hint:: + Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up. diff --git a/docs/ansible.utils.replace_keys_filter.rst b/docs/ansible.utils.replace_keys_filter.rst new file mode 100644 index 0000000..555acc8 --- /dev/null +++ b/docs/ansible.utils.replace_keys_filter.rst @@ -0,0 +1,398 @@ +.. _ansible.utils.replace_keys_filter: + + +************************** +ansible.utils.replace_keys +************************** + +**Replaces specific keys with their after value from a data recursively.** + + +Version added: 2.5.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This plugin replaces specific keys with their after value from a data recursively. +- Matching parameter defaults to equals unless ``matching_parameter`` is explicitly mentioned. +- Using the parameters below- ``data|ansible.utils.replace_keys(target([....]``)) + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsConfigurationComments
+
+ data + +
+ raw + / required +
+
+ + +
This option represents a list of dictionaries or a dictionary with any level of nesting data.
+
For example config_data|ansible.utils.replace_keys(target([....])), in this case config_data represents this option.
+
+
+ matching_parameter + +
+ string +
+
+
    Choices: +
  • starts_with
  • +
  • ends_with
  • +
  • regex
  • +
+
+ +
Specify the matching configuration of target keys and data attributes.
+
+
+ target + +
+ list + / elements=dictionary + / required +
+
+ + +
Specify the target keys to replace in list of dictionaries format containing before and after key value.
+
+
+ after + +
+ string +
+
+ + +
after attribute key [change to]
+
+
+ before + +
+ string +
+
+ + +
before attribute key [to change]
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + ##example.yaml + interfaces: + - interface_name: eth0 + enabled: true + duplex: auto + speed: auto + - interface_name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + is_enabled: true + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + is_enabled: true + - vlan_id: 101 + description: Eth1 - VIF 101 + is_enabled: true + - interface_name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + is_enabled: false + + ##Playbook + vars_files: + - "example.yaml" + tasks: + - name: replace keys with specified keys dict/list to dict + ansible.builtin.set_fact: + data: "{{ interfaces }}" + + - debug: + msg: "{{ data|ansible.utils.replace_keys(target=[{'before':'interface_name', 'after':'name'}, {'before':'is_enabled', 'after':'enabled'}]) }}" + + ##Output + # TASK [replace keys with specified keys dict/list to dict] ************************* + # ok: [localhost] => { + # "ansible_facts": { + # "data": [ + # { + # "duplex": "auto", + # "enabled": true, + # "interface_name": "eth0", + # "speed": "auto" + # }, + # { + # "description": "Configured by Ansible - Interface 1", + # "duplex": "auto", + # "interface_name": "eth1", + # "is_enabled": true, + # "mtu": 1500, + # "speed": "auto", + # "vifs": [ + # { + # "description": "Eth1 - VIF 100", + # "is_enabled": true, + # "mtu": 400, + # "vlan_id": 100 + # }, + # { + # "description": "Eth1 - VIF 101", + # "is_enabled": true, + # "vlan_id": 101 + # } + # ] + # }, + # { + # "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + # "interface_name": "eth2", + # "is_enabled": false, + # "mtu": 600 + # } + # ] + # }, + # "changed": false + # } + + # TASK [debug] ********************************************************************** + # ok: [localhost] => { + # "msg": [ + # { + # "duplex": "auto", + # "enabled": true, + # "name": "eth0", + # "speed": "auto" + # }, + # { + # "description": "Configured by Ansible - Interface 1", + # "duplex": "auto", + # "enabled": true, + # "mtu": 1500, + # "name": "eth1", + # "speed": "auto", + # "vifs": [ + # { + # "description": "Eth1 - VIF 100", + # "enabled": true, + # "mtu": 400, + # "vlan_id": 100 + # }, + # { + # "description": "Eth1 - VIF 101", + # "enabled": true, + # "vlan_id": 101 + # } + # ] + # }, + # { + # "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + # "enabled": false, + # "mtu": 600, + # "name": "eth2" + # } + # ] + # } + + ##example.yaml + interfaces: + - interface_name: eth0 + enabled: true + duplex: auto + speed: auto + - interface_name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + is_enabled: true + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + is_enabled: true + - vlan_id: 101 + description: Eth1 - VIF 101 + is_enabled: true + - interface_name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + is_enabled: false + + ##Playbook + vars_files: + - "example.yaml" + tasks: + - name: replace keys with specified keys dict/list to dict + ansible.builtin.set_fact: + data: "{{ interfaces }}" + + - debug: + msg: "{{ data|ansible.utils.replace_keys(target=[{'before':'name', 'after':'name'}, {'before':'enabled', 'after':'enabled'}], + matching_parameter= 'ends_with') }}" + + ##Output + # TASK [replace keys with specified keys dict/list to dict] ********************************* + # ok: [localhost] => { + # "ansible_facts": { + # "data": [ + # { + # "duplex": "auto", + # "enabled": true, + # "interface_name": "eth0", + # "speed": "auto" + # }, + # { + # "description": "Configured by Ansible - Interface 1", + # "duplex": "auto", + # "interface_name": "eth1", + # "is_enabled": true, + # "mtu": 1500, + # "speed": "auto", + # "vifs": [ + # { + # "description": "Eth1 - VIF 100", + # "is_enabled": true, + # "mtu": 400, + # "vlan_id": 100 + # }, + # { + # "description": "Eth1 - VIF 101", + # "is_enabled": true, + # "vlan_id": 101 + # } + # ] + # }, + # { + # "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + # "interface_name": "eth2", + # "is_enabled": false, + # "mtu": 600 + # } + # ] + # }, + # "changed": false + # } + + # TASK [debug] *************************************************************************** + # ok: [localhost] => { + # "msg": [ + # { + # "duplex": "auto", + # "enabled": true, + # "name": "eth0", + # "speed": "auto" + # }, + # { + # "description": "Configured by Ansible - Interface 1", + # "duplex": "auto", + # "enabled": true, + # "mtu": 1500, + # "name": "eth1", + # "speed": "auto", + # "vifs": [ + # { + # "description": "Eth1 - VIF 100", + # "enabled": true, + # "mtu": 400, + # "vlan_id": 100 + # }, + # { + # "description": "Eth1 - VIF 101", + # "enabled": true, + # "vlan_id": 101 + # } + # ] + # }, + # { + # "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + # "enabled": false, + # "mtu": 600, + # "name": "eth2" + # } + # ] + # } + + + + +Status +------ + + +Authors +~~~~~~~ + +- Sagar Paul (@KB-perByte) + + +.. hint:: + Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up. diff --git a/plugins/filter/keep_keys.py b/plugins/filter/keep_keys.py new file mode 100644 index 0000000..8276d87 --- /dev/null +++ b/plugins/filter/keep_keys.py @@ -0,0 +1,331 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +""" +The keep_keys filter plugin +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ + name: keep_keys + author: Sagar Paul (@KB-perByte) + version_added: "2.5.0" + short_description: Keep specific keys from a data recursively. + description: + - This plugin keep only specified keys from a provided data recursively. + - Matching parameter defaults to equals unless C(matching_parameter) is explicitly mentioned. + - Using the parameters below- C(data|ansible.utils.keep_keys(target([....]))) + options: + data: + description: + - This option represents a list of dictionaries or a dictionary with any level of nesting data. + - For example C(config_data|ansible.utils.keep_keys(target([....]))), in this case C(config_data) represents this option. + type: raw + required: True + target: + description: Specify the target keys to keep in list format. + type: list + elements: str + required: True + matching_parameter: + description: Specify the matching configuration of target keys and data attributes. + type: str + choices: ["starts_with","ends_with","regex"] +""" + +EXAMPLES = r""" + +##example.yaml +interfaces: + - name: eth0 + enabled: true + duplex: auto + speed: auto + note: + - Connected green wire + - name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + enabled: true + note: + - Connected blue wire + - Configured by Paul + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + enabled: true + comment: Needs reconfiguration + - vlan_id: 101 + description: Eth1 - VIF 101 + enabled: true + - name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + enabled: false + +##Playbook +vars_files: + - "example.yaml" +tasks: + - name: keep selective keys from dict/list of dict data + ansible.builtin.set_fact: + data: "{{ interfaces }}" + + - debug: + msg: "{{ data|ansible.utils.keep_keys(target=['description', 'name', 'mtu', 'duplex', 'enabled', 'vifs', 'vlan_id']) }}" + +##Output +# TASK [keep selective keys from python dict/list of dict] **************************************************************************************** +# ok: [localhost] => { +# "ansible_facts": { +# "data": [ +# { +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "note": [ +# "Connected green wire" +# ], +# "speed": "auto" +# }, +# { +# "description": "Configured by Ansible - Interface 1", +# "duplex": "auto", +# "enabled": true, +# "mtu": 1500, +# "name": "eth1", +# "note": [ +# "Connected blue wire", +# "Configured by Paul" +# ], +# "speed": "auto", +# "vifs": [ +# { +# "comment": "Needs reconfiguration", +# "description": "Eth1 - VIF 100", +# "enabled": true, +# "mtu": 400, +# "vlan_id": 100 +# }, +# { +# "description": "Eth1 - VIF 101", +# "enabled": true, +# "vlan_id": 101 +# } +# ] +# }, +# { +# "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", +# "enabled": false, +# "mtu": 600, +# "name": "eth2" +# } +# ] +# }, +# "changed": false +# } +# Read vars_file 'example.yaml' + +# TASK [debug] ************************************************************************************************************* +# ok: [localhost] => { +# "msg": [ +# { +# "duplex": "auto", +# "enabled": true, +# "name": "eth0" +# }, +# { +# "description": "Configured by Ansible - Interface 1", +# "duplex": "auto", +# "enabled": true, +# "mtu": 1500, +# "name": "eth1", +# "vifs": [ +# { +# "description": "Eth1 - VIF 100", +# "enabled": true, +# "mtu": 400, +# "vlan_id": 100 +# }, +# { +# "description": "Eth1 - VIF 101", +# "enabled": true, +# "vlan_id": 101 +# } +# ] +# }, +# { +# "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", +# "enabled": false, +# "mtu": 600, +# "name": "eth2" +# } +# ] +# } + +##example.yaml +interfaces: + - name: eth0 + enabled: true + duplex: auto + speed: auto + note: + - Connected green wire + - name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + enabled: true + note: + - Connected blue wire + - Configured by Paul + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + enabled: true + comment: Needs reconfiguration + - vlan_id: 101 + description: Eth1 - VIF 101 + enabled: true + - name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + enabled: false + +##Playbook +vars_files: + - "example.yaml" +tasks: + - name: keep selective keys from dict/list of dict data + ansible.builtin.set_fact: + data: "{{ interfaces }}" + + - debug: + msg: "{{ data|ansible.utils.keep_keys(target=['desc', 'name'], matching_parameter= 'starts_with') }}" + +##Output +# TASK [keep selective keys from python dict/list of dict] ************************** +# ok: [localhost] => { +# "ansible_facts": { +# "data": [ +# { +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "note": [ +# "Connected green wire" +# ], +# "speed": "auto" +# }, +# { +# "description": "Configured by Ansible - Interface 1", +# "duplex": "auto", +# "enabled": true, +# "mtu": 1500, +# "name": "eth1", +# "note": [ +# "Connected blue wire", +# "Configured by Paul" +# ], +# "speed": "auto", +# "vifs": [ +# { +# "comment": "Needs reconfiguration", +# "description": "Eth1 - VIF 100", +# "enabled": true, +# "mtu": 400, +# "vlan_id": 100 +# }, +# { +# "description": "Eth1 - VIF 101", +# "enabled": true, +# "vlan_id": 101 +# } +# ] +# }, +# { +# "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", +# "enabled": false, +# "mtu": 600, +# "name": "eth2" +# } +# ] +# }, +# "changed": false +# } +# Read vars_file 'example.yaml' + +# TASK [debug] ********************************************************************************** +# ok: [localhost] => { +# "msg": [ +# { +# "name": "eth0" +# }, +# { +# "description": "Configured by Ansible - Interface 1", +# "name": "eth1", +# "vifs": [ +# { +# "description": "Eth1 - VIF 100" +# }, +# { +# "description": "Eth1 - VIF 101" +# } +# ] +# }, +# { +# "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", +# "name": "eth2" +# } +# ] +# } +""" + +from ansible.errors import AnsibleFilterError +from ansible_collections.ansible.utils.plugins.plugin_utils.keep_keys import ( + keep_keys, +) +from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( + AnsibleArgSpecValidator, +) + +try: + from jinja2.filters import pass_environment +except ImportError: + from jinja2.filters import environmentfilter as pass_environment + + +@pass_environment +def _keep_keys(*args, **kwargs): + """keep specific keys from a data recursively""" + + keys = ["data", "target", "matching_parameter"] + data = dict(zip(keys, args[1:])) + data.update(kwargs) + aav = AnsibleArgSpecValidator( + data=data, schema=DOCUMENTATION, name="keep_keys" + ) + valid, errors, updated_data = aav.validate() + if not valid: + raise AnsibleFilterError(errors) + return keep_keys(**updated_data) + + +class FilterModule(object): + """keep_keys""" + + def filters(self): + + """a mapping of filter names to functions""" + return {"keep_keys": _keep_keys} diff --git a/plugins/filter/remove_keys.py b/plugins/filter/remove_keys.py new file mode 100644 index 0000000..318f179 --- /dev/null +++ b/plugins/filter/remove_keys.py @@ -0,0 +1,348 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +""" +The remove_keys filter plugin +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ + name: remove_keys + author: Sagar Paul (@KB-perByte) + version_added: "2.5.0" + short_description: Remove specific keys from a data recursively. + description: + - This plugin removes specific keys from a provided data recursively. + - Matching parameter defaults to equals unless C(matching_parameter) is explicitly mentioned. + - Using the parameters below- C(data|ansible.utils.remove_keys(target([....]))) + options: + data: + description: + - This option represents a list of dictionaries or a dictionary with any level of nesting data. + - For example C(config_data|ansible.utils.remove_keys(target([....]))), in this case C(config_data) represents this option. + type: raw + required: True + target: + description: Specify the target keys to remove in list format. + type: list + elements: str + required: True + matching_parameter: + description: Specify the matching configuration of target keys and data attributes. + type: str + choices: ["starts_with","ends_with","regex"] +""" + +EXAMPLES = r""" + +##example.yaml +interfaces: + - name: eth0 + enabled: true + duplex: auto + speed: auto + note: + - Connected green wire + - name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + enabled: true + note: + - Connected blue wire + - Configured by Paul + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + enabled: true + comment: Needs reconfiguration + - vlan_id: 101 + description: Eth1 - VIF 101 + enabled: true + - name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + enabled: false + +##Playbook +vars_files: + - "example.yaml" +tasks: + - name: remove multiple keys from a provided data + ansible.builtin.set_fact: + data: "{{ interfaces }}" + + - debug: + msg: "{{ data|ansible.utils.remove_keys(target=['note', 'comment']) }}" + +##Output +# TASK [remove multiple keys from a provided data] *************************************** +# ok: [localhost] => { +# "ansible_facts": { +# "data": [ +# { +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "note": [ +# "Connected green wire" +# ], +# "speed": "auto" +# }, +# { +# "description": "Configured by Ansible - Interface 1", +# "duplex": "auto", +# "enabled": true, +# "mtu": 1500, +# "name": "eth1", +# "note": [ +# "Connected blue wire", +# "Configured by Paul" +# ], +# "speed": "auto", +# "vifs": [ +# { +# "comment": "Needs reconfiguration", +# "description": "Eth1 - VIF 100", +# "enabled": true, +# "mtu": 400, +# "vlan_id": 100 +# }, +# { +# "description": "Eth1 - VIF 101", +# "enabled": true, +# "vlan_id": 101 +# } +# ] +# }, +# { +# "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", +# "enabled": false, +# "mtu": 600, +# "name": "eth2" +# } +# ] +# }, +# "changed": false +# } +# Read vars_file 'example.yaml' + +# TASK [debug] ******************************************** +# ok: [localhost] => { +# "msg": [ +# { +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "speed": "auto" +# }, +# { +# "description": "Configured by Ansible - Interface 1", +# "duplex": "auto", +# "enabled": true, +# "mtu": 1500, +# "name": "eth1", +# "speed": "auto", +# "vifs": [ +# { +# "description": "Eth1 - VIF 100", +# "enabled": true, +# "mtu": 400, +# "vlan_id": 100 +# }, +# { +# "description": "Eth1 - VIF 101", +# "enabled": true, +# "vlan_id": 101 +# } +# ] +# }, +# { +# "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", +# "enabled": false, +# "mtu": 600, +# "name": "eth2" +# } +# ] +# } + +##example.yaml +interfaces: + - name: eth0 + enabled: true + duplex: auto + speed: auto + note: + - Connected green wire + - name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + enabled: true + note: + - Connected blue wire + - Configured by Paul + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + enabled: true + comment: Needs reconfiguration + - vlan_id: 101 + description: Eth1 - VIF 101 + enabled: true + - name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + enabled: false + +##Playbook +vars_files: + - "example.yaml" +tasks: + - name: remove multiple keys from a provided data + ansible.builtin.set_fact: + data: "{{ interfaces }}" + + - debug: + msg: "{{ data|ansible.utils.remove_keys(target=['^note$', '^comment'], matching_parameter= 'regex') }}" + +##Output +# TASK [remove multiple keys from a provided data] *********************** +# ok: [localhost] => { +# "ansible_facts": { +# "data": [ +# { +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "note": [ +# "Connected green wire" +# ], +# "speed": "auto" +# }, +# { +# "description": "Configured by Ansible - Interface 1", +# "duplex": "auto", +# "enabled": true, +# "mtu": 1500, +# "name": "eth1", +# "note": [ +# "Connected blue wire", +# "Configured by Paul" +# ], +# "speed": "auto", +# "vifs": [ +# { +# "comment": "Needs reconfiguration", +# "description": "Eth1 - VIF 100", +# "enabled": true, +# "mtu": 400, +# "vlan_id": 100 +# }, +# { +# "description": "Eth1 - VIF 101", +# "enabled": true, +# "vlan_id": 101 +# } +# ] +# }, +# { +# "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", +# "enabled": false, +# "mtu": 600, +# "name": "eth2" +# } +# ] +# }, +# "changed": false +# } +# Read vars_file 'example.yaml' + +# TASK [debug] ***************************************** +# ok: [localhost] => { +# "msg": [ +# { +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "speed": "auto" +# }, +# { +# "description": "Configured by Ansible - Interface 1", +# "duplex": "auto", +# "enabled": true, +# "mtu": 1500, +# "name": "eth1", +# "speed": "auto", +# "vifs": [ +# { +# "description": "Eth1 - VIF 100", +# "enabled": true, +# "mtu": 400, +# "vlan_id": 100 +# }, +# { +# "description": "Eth1 - VIF 101", +# "enabled": true, +# "vlan_id": 101 +# } +# ] +# }, +# { +# "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", +# "enabled": false, +# "mtu": 600, +# "name": "eth2" +# } +# ] +# } + +""" + +from ansible.errors import AnsibleFilterError +from ansible_collections.ansible.utils.plugins.plugin_utils.remove_keys import ( + remove_keys, +) +from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( + AnsibleArgSpecValidator, +) + +try: + from jinja2.filters import pass_environment +except ImportError: + from jinja2.filters import environmentfilter as pass_environment + + +@pass_environment +def _remove_keys(*args, **kwargs): + """remove specific keys from a data recursively""" + + keys = ["data", "target", "matching_parameter"] + data = dict(zip(keys, args[1:])) + data.update(kwargs) + aav = AnsibleArgSpecValidator( + data=data, schema=DOCUMENTATION, name="remove_keys" + ) + valid, errors, updated_data = aav.validate() + if not valid: + raise AnsibleFilterError(errors) + return remove_keys(**updated_data) + + +class FilterModule(object): + """remove_keys""" + + def filters(self): + + """a mapping of filter names to functions""" + return {"remove_keys": _remove_keys} diff --git a/plugins/filter/replace_keys.py b/plugins/filter/replace_keys.py new file mode 100644 index 0000000..edfd1fb --- /dev/null +++ b/plugins/filter/replace_keys.py @@ -0,0 +1,325 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +""" +The replace_keys filter plugin +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ + name: replace_keys + author: Sagar Paul (@KB-perByte) + version_added: "2.5.0" + short_description: Replaces specific keys with their after value from a data recursively. + description: + - This plugin replaces specific keys with their after value from a data recursively. + - Matching parameter defaults to equals unless C(matching_parameter) is explicitly mentioned. + - Using the parameters below- C(data|ansible.utils.replace_keys(target([....]))) + options: + data: + description: + - This option represents a list of dictionaries or a dictionary with any level of nesting data. + - For example C(config_data|ansible.utils.replace_keys(target([....]))), in this case C(config_data) represents this option. + type: raw + required: True + target: + description: Specify the target keys to replace in list of dictionaries format containing + before and after key value. + type: list + elements: dict + required: True + suboptions: + before: + description: before attribute key [to change] + type: str + after: + description: after attribute key [change to] + type: str + matching_parameter: + description: Specify the matching configuration of target keys and data attributes. + type: str + choices: ["starts_with","ends_with","regex"] +""" + +EXAMPLES = r""" +##example.yaml +interfaces: + - interface_name: eth0 + enabled: true + duplex: auto + speed: auto + - interface_name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + is_enabled: true + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + is_enabled: true + - vlan_id: 101 + description: Eth1 - VIF 101 + is_enabled: true + - interface_name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + is_enabled: false + +##Playbook +vars_files: + - "example.yaml" +tasks: + - name: replace keys with specified keys dict/list to dict + ansible.builtin.set_fact: + data: "{{ interfaces }}" + + - debug: + msg: "{{ data|ansible.utils.replace_keys(target=[{'before':'interface_name', 'after':'name'}, {'before':'is_enabled', 'after':'enabled'}]) }}" + +##Output +# TASK [replace keys with specified keys dict/list to dict] ************************* +# ok: [localhost] => { +# "ansible_facts": { +# "data": [ +# { +# "duplex": "auto", +# "enabled": true, +# "interface_name": "eth0", +# "speed": "auto" +# }, +# { +# "description": "Configured by Ansible - Interface 1", +# "duplex": "auto", +# "interface_name": "eth1", +# "is_enabled": true, +# "mtu": 1500, +# "speed": "auto", +# "vifs": [ +# { +# "description": "Eth1 - VIF 100", +# "is_enabled": true, +# "mtu": 400, +# "vlan_id": 100 +# }, +# { +# "description": "Eth1 - VIF 101", +# "is_enabled": true, +# "vlan_id": 101 +# } +# ] +# }, +# { +# "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", +# "interface_name": "eth2", +# "is_enabled": false, +# "mtu": 600 +# } +# ] +# }, +# "changed": false +# } + +# TASK [debug] ********************************************************************** +# ok: [localhost] => { +# "msg": [ +# { +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "speed": "auto" +# }, +# { +# "description": "Configured by Ansible - Interface 1", +# "duplex": "auto", +# "enabled": true, +# "mtu": 1500, +# "name": "eth1", +# "speed": "auto", +# "vifs": [ +# { +# "description": "Eth1 - VIF 100", +# "enabled": true, +# "mtu": 400, +# "vlan_id": 100 +# }, +# { +# "description": "Eth1 - VIF 101", +# "enabled": true, +# "vlan_id": 101 +# } +# ] +# }, +# { +# "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", +# "enabled": false, +# "mtu": 600, +# "name": "eth2" +# } +# ] +# } + +##example.yaml +interfaces: + - interface_name: eth0 + enabled: true + duplex: auto + speed: auto + - interface_name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + is_enabled: true + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + is_enabled: true + - vlan_id: 101 + description: Eth1 - VIF 101 + is_enabled: true + - interface_name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + is_enabled: false + +##Playbook +vars_files: + - "example.yaml" +tasks: + - name: replace keys with specified keys dict/list to dict + ansible.builtin.set_fact: + data: "{{ interfaces }}" + + - debug: + msg: "{{ data|ansible.utils.replace_keys(target=[{'before':'name', 'after':'name'}, {'before':'enabled', 'after':'enabled'}], + matching_parameter= 'ends_with') }}" + +##Output +# TASK [replace keys with specified keys dict/list to dict] ********************************* +# ok: [localhost] => { +# "ansible_facts": { +# "data": [ +# { +# "duplex": "auto", +# "enabled": true, +# "interface_name": "eth0", +# "speed": "auto" +# }, +# { +# "description": "Configured by Ansible - Interface 1", +# "duplex": "auto", +# "interface_name": "eth1", +# "is_enabled": true, +# "mtu": 1500, +# "speed": "auto", +# "vifs": [ +# { +# "description": "Eth1 - VIF 100", +# "is_enabled": true, +# "mtu": 400, +# "vlan_id": 100 +# }, +# { +# "description": "Eth1 - VIF 101", +# "is_enabled": true, +# "vlan_id": 101 +# } +# ] +# }, +# { +# "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", +# "interface_name": "eth2", +# "is_enabled": false, +# "mtu": 600 +# } +# ] +# }, +# "changed": false +# } + +# TASK [debug] *************************************************************************** +# ok: [localhost] => { +# "msg": [ +# { +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "speed": "auto" +# }, +# { +# "description": "Configured by Ansible - Interface 1", +# "duplex": "auto", +# "enabled": true, +# "mtu": 1500, +# "name": "eth1", +# "speed": "auto", +# "vifs": [ +# { +# "description": "Eth1 - VIF 100", +# "enabled": true, +# "mtu": 400, +# "vlan_id": 100 +# }, +# { +# "description": "Eth1 - VIF 101", +# "enabled": true, +# "vlan_id": 101 +# } +# ] +# }, +# { +# "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", +# "enabled": false, +# "mtu": 600, +# "name": "eth2" +# } +# ] +# } +""" + +from ansible.errors import AnsibleFilterError +from ansible_collections.ansible.utils.plugins.plugin_utils.replace_keys import ( + replace_keys, +) +from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( + AnsibleArgSpecValidator, +) + +try: + from jinja2.filters import pass_environment +except ImportError: + from jinja2.filters import environmentfilter as pass_environment + + +@pass_environment +def _replace_keys(*args, **kwargs): + """replaces specific keys with their after value from a data recursively""" + + keys = ["data", "target", "matching_parameter"] + data = dict(zip(keys, args[1:])) + data.update(kwargs) + aav = AnsibleArgSpecValidator( + data=data, schema=DOCUMENTATION, name="replace_keys" + ) + valid, errors, updated_data = aav.validate() + if not valid: + raise AnsibleFilterError(errors) + return replace_keys(**updated_data) + + +class FilterModule(object): + """replace_keys""" + + def filters(self): + + """a mapping of filter names to functions""" + return {"replace_keys": _replace_keys} diff --git a/plugins/plugin_utils/keep_keys.py b/plugins/plugin_utils/keep_keys.py new file mode 100644 index 0000000..3893c05 --- /dev/null +++ b/plugins/plugin_utils/keep_keys.py @@ -0,0 +1,83 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +""" +The keep_keys plugin code +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import re +from ansible.errors import AnsibleFilterError + + +def _raise_error(msg): + """Raise an error message, prepend with filter name + :param msg: The message + :type msg: str + :raises: AnsibleError + """ + error = "Error when using plugin 'keep_keys': {msg}".format(msg=msg) + raise AnsibleFilterError(error) + + +def keep_keys_from_dict_n_list(data, target, matching_parameter): + if isinstance(data, list): + list_data = [ + keep_keys_from_dict_n_list(each, target, matching_parameter) + for each in data + ] + return list_data + if isinstance(data, dict): + keep = {} + for k, val in data.items(): + for key in target: + match = None + if not isinstance(val, (list, dict)): + if matching_parameter == "regex": + match = re.match(key, k) + if match: + keep[k] = val + elif matching_parameter == "starts_with": + if k.startswith(key): + keep[k], match = val, True + elif matching_parameter == "ends_with": + if k.endswith(key): + keep[k], match = val, True + else: + if k == key: + keep[k], match = val, True + else: + list_data = keep_keys_from_dict_n_list( + val, target, matching_parameter + ) + if isinstance(list_data, list) and not match: + list_data = [r for r in list_data if r not in ([], {})] + if all(isinstance(s, str) for s in list_data): + continue + if list_data in ([], {}): + continue + keep[k] = list_data + return keep + return data + + +def keep_keys(data, target, matching_parameter="equality"): + """keep selective keys recursively from a given data" + :param data: The data passed in (data|keep_keys(...)) + :type data: raw + :param target: List of keys on with operation is to be performed + :type data: list + :type elements: string + :param matching_parameter: matching type of the target keys with data keys + :type data: str + """ + if not isinstance(data, (list, dict)): + _raise_error("Input is not valid for keep operation") + data = keep_keys_from_dict_n_list(data, target, matching_parameter) + return data diff --git a/plugins/plugin_utils/remove_keys.py b/plugins/plugin_utils/remove_keys.py new file mode 100644 index 0000000..4fc6706 --- /dev/null +++ b/plugins/plugin_utils/remove_keys.py @@ -0,0 +1,83 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +""" +The remove_keys plugin code +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import re +from ansible.errors import AnsibleFilterError + + +def _raise_error(msg): + """Raise an error message, prepend with filter name + :param msg: The message + :type msg: str + :raises: AnsibleError + """ + error = "Error when using plugin 'remove_keys': {msg}".format(msg=msg) + raise AnsibleFilterError(error) + + +def remove_keys_from_dict_n_list(data, target, matching_parameter): + if isinstance(data, dict): + for key in set(target): + for k in list(data.keys()): + if matching_parameter == "regex": + if re.match(key, k): + del data[k] + elif matching_parameter == "starts_with": + if k.startswith(key): + del data[k] + elif matching_parameter == "ends_with": + if k.endswith(key): + del data[k] + else: + if k == key: + del data[k] + for k, v in data.items(): + remove_keys_from_dict_n_list(v, target, matching_parameter) + elif isinstance(data, list): + for i in data: + remove_keys_from_dict_n_list(i, target, matching_parameter) + return data + + +def clear_empty_data(data): + if isinstance(data, dict): + # for k in list(data.keys()): + # if not data.get(k, {}): + # del data[k] + for k, v in data.items(): + data[k] = clear_empty_data(v) + if isinstance(data, list): + temp = [] + for i in data: + if i: + temp.append(clear_empty_data(i)) + return temp + return data + + +def remove_keys(data, target, matching_parameter="equality"): + """Remove unwanted keys recursively from a given data" + :param data: The data passed in (data|remove_keys(...)) + :type data: raw + :param target: List of keys on with operation is to be performed + :type data: list + :type elements: string + :param matching_parameter: matching type of the target keys with data keys + :type data: str + """ + if not isinstance(data, (list, dict)): + _raise_error("Input is not valid for attribute removal") + data = remove_keys_from_dict_n_list(data, target, matching_parameter) + data = clear_empty_data(data) + return data diff --git a/plugins/plugin_utils/replace_keys.py b/plugins/plugin_utils/replace_keys.py new file mode 100644 index 0000000..90f4059 --- /dev/null +++ b/plugins/plugin_utils/replace_keys.py @@ -0,0 +1,67 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +""" +The replace_keys plugin code +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import re +from ansible.errors import AnsibleFilterError + + +def _raise_error(msg): + """Raise an error message, prepend with filter name + :param msg: The message + :type msg: str + :raises: AnsibleError + """ + error = "Error when using plugin 'replace_keys': {msg}".format(msg=msg) + raise AnsibleFilterError(error) + + +def replace_keys_from_dict_n_list(data, target, matching_parameter): + if isinstance(data, dict): + for key in target: + for k in list(data.keys()): + if matching_parameter == "regex": + if re.match(key.get("before"), k): + data[key.get("after")] = data.pop(k) + elif matching_parameter == "starts_with": + if k.startswith(key.get("before")): + data[key.get("after")] = data.pop(k) + elif matching_parameter == "ends_with": + if k.endswith(key.get("before")): + data[key.get("after")] = data.pop(k) + else: + if k == key.get("before"): + data[key.get("after")] = data.pop(k) + for k, v in data.items(): + replace_keys_from_dict_n_list(v, target, matching_parameter) + elif isinstance(data, list): + for i in data: + replace_keys_from_dict_n_list(i, target, matching_parameter) + return data + + +def replace_keys(data, target, matching_parameter="equality"): + """replaces specific keys with mentioned after data" + :param data: The data passed in (data|replace_keys(...)) + :type data: raw + :param target: List of keys on with operation is to be performed + :type data: list + :type elements: string + :param matching_parameter: matching type of the target keys with data keys + :type data: list + :type elements: dict + """ + if not isinstance(data, (list, dict)): + _raise_error("Input is not valid for replace operation") + data = replace_keys_from_dict_n_list(data, target, matching_parameter) + return data diff --git a/tests/integration/targets/utils_keep_keys/tasks/main.yaml b/tests/integration/targets/utils_keep_keys/tasks/main.yaml new file mode 100644 index 0000000..eb1a94b --- /dev/null +++ b/tests/integration/targets/utils_keep_keys/tasks/main.yaml @@ -0,0 +1,14 @@ +--- +- name: Recursively find all test files + find: + file_type: file + paths: "{{ role_path }}/tasks" + recurse: false + use_regex: true + patterns: + - '^(?!_|main).+$' + delegate_to: localhost + register: found + +- include: "{{ item.path }}" + loop: "{{ found.files }}" diff --git a/tests/integration/targets/utils_keep_keys/tasks/simple.yaml b/tests/integration/targets/utils_keep_keys/tasks/simple.yaml new file mode 100644 index 0000000..72535d7 --- /dev/null +++ b/tests/integration/targets/utils_keep_keys/tasks/simple.yaml @@ -0,0 +1,70 @@ +--- +- name: Setup data as facts + ansible.builtin.set_fact: + data: + - interface_name: eth0 + enabled: true + duplex: auto + speed: auto + - interface_name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + is_enabled: true + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + is_enabled: true + - vlan_id: 101 + description: Eth1 - VIF 101 + is_enabled: true + - interface_name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + is_enabled: false + +- debug: + msg: "{{ data|ansible.utils.keep_keys(target=['desc', 'interface_'], matching_parameter= 'starts_with') }}" + register: result + +- name: Assert result dicts + assert: + that: + - keep['starts_with'] == result['msg'] + +- name: Setup data as facts for equivalent + ansible.builtin.set_fact: + data: + - interface_name: eth0 + enabled: true + duplex: auto + speed: auto + - interface_name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + is_enabled: true + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + is_enabled: true + - vlan_id: 101 + description: Eth1 - VIF 101 + is_enabled: true + - interface_name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + is_enabled: false + +- debug: + msg: "{{ data|ansible.utils.keep_keys(target=['interface_name', 'is_enabled', 'vlan_id']) }}" + register: result + +- name: Assert result dicts + assert: + that: + - keep_default['default'] == result['msg'] diff --git a/tests/integration/targets/utils_keep_keys/vars/main.yaml b/tests/integration/targets/utils_keep_keys/vars/main.yaml new file mode 100644 index 0000000..f72e028 --- /dev/null +++ b/tests/integration/targets/utils_keep_keys/vars/main.yaml @@ -0,0 +1,24 @@ +--- +keep: + starts_with: + - interface_name: eth0 + - interface_name: eth1 + description: Configured by Ansible - Interface 1 + vifs: + - description: Eth1 - VIF 100 + - description: Eth1 - VIF 101 + - interface_name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + +keep_default: + default: + - interface_name: eth0 + - interface_name: eth1 + is_enabled: true + vifs: + - vlan_id: 100 + is_enabled: true + - vlan_id: 101 + is_enabled: true + - interface_name: eth2 + is_enabled: false diff --git a/tests/integration/targets/utils_remove_keys/tasks/main.yaml b/tests/integration/targets/utils_remove_keys/tasks/main.yaml new file mode 100644 index 0000000..eb1a94b --- /dev/null +++ b/tests/integration/targets/utils_remove_keys/tasks/main.yaml @@ -0,0 +1,14 @@ +--- +- name: Recursively find all test files + find: + file_type: file + paths: "{{ role_path }}/tasks" + recurse: false + use_regex: true + patterns: + - '^(?!_|main).+$' + delegate_to: localhost + register: found + +- include: "{{ item.path }}" + loop: "{{ found.files }}" diff --git a/tests/integration/targets/utils_remove_keys/tasks/simple.yaml b/tests/integration/targets/utils_remove_keys/tasks/simple.yaml new file mode 100644 index 0000000..5e3b4c4 --- /dev/null +++ b/tests/integration/targets/utils_remove_keys/tasks/simple.yaml @@ -0,0 +1,39 @@ +--- +- name: Setup data as facts for remove integration test + ansible.builtin.set_fact: + data: + - name: eth0 + enabled: true + duplex: auto + speed: auto + comment: test interface + - name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + enabled: true + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + enabled: true + notes: + - note 1 + - note 2 + - vlan_id: 101 + description: Eth1 - VIF 101 + enabled: true + - name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + enabled: false + +- debug: + msg: "{{ data|ansible.utils.remove_keys(target=['notes', 'comment']) }}" + register: result + +- name: Assert result dicts + assert: + that: + - remove['default'] == result['msg'] diff --git a/tests/integration/targets/utils_remove_keys/vars/main.yaml b/tests/integration/targets/utils_remove_keys/vars/main.yaml new file mode 100644 index 0000000..7967f36 --- /dev/null +++ b/tests/integration/targets/utils_remove_keys/vars/main.yaml @@ -0,0 +1,25 @@ +--- +remove: + default: + - name: eth0 + enabled: true + duplex: auto + speed: auto + - name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + enabled: true + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + enabled: true + - vlan_id: 101 + description: Eth1 - VIF 101 + enabled: true + - name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + enabled: false diff --git a/tests/integration/targets/utils_replace_keys/tasks/main.yaml b/tests/integration/targets/utils_replace_keys/tasks/main.yaml new file mode 100644 index 0000000..eb1a94b --- /dev/null +++ b/tests/integration/targets/utils_replace_keys/tasks/main.yaml @@ -0,0 +1,14 @@ +--- +- name: Recursively find all test files + find: + file_type: file + paths: "{{ role_path }}/tasks" + recurse: false + use_regex: true + patterns: + - '^(?!_|main).+$' + delegate_to: localhost + register: found + +- include: "{{ item.path }}" + loop: "{{ found.files }}" diff --git a/tests/integration/targets/utils_replace_keys/tasks/simple.yaml b/tests/integration/targets/utils_replace_keys/tasks/simple.yaml new file mode 100644 index 0000000..3e9e405 --- /dev/null +++ b/tests/integration/targets/utils_replace_keys/tasks/simple.yaml @@ -0,0 +1,35 @@ +--- +- name: Setup data as facts for replace integration test + ansible.builtin.set_fact: + data: + - interface_name: eth0 + enabled: true + duplex: auto + speed: auto + - interface_name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + enabled: true + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + enabled: true + - vlan_id: 101 + description: Eth1 - VIF 101 + enabled: true + - interface_name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + enabled: false + +- debug: + msg: "{{ data|ansible.utils.replace_keys(target=[{'before':'interface_name', 'after':'name'}]) }}" + register: result + +- name: Assert result dicts + assert: + that: + - replace['default'] == result['msg'] diff --git a/tests/integration/targets/utils_replace_keys/vars/main.yaml b/tests/integration/targets/utils_replace_keys/vars/main.yaml new file mode 100644 index 0000000..dd7083c --- /dev/null +++ b/tests/integration/targets/utils_replace_keys/vars/main.yaml @@ -0,0 +1,25 @@ +--- +replace: + default: + - name: eth0 + enabled: true + duplex: auto + speed: auto + - name: eth1 + description: Configured by Ansible - Interface 1 + mtu: 1500 + speed: auto + duplex: auto + enabled: true + vifs: + - vlan_id: 100 + description: Eth1 - VIF 100 + mtu: 400 + enabled: true + - vlan_id: 101 + description: Eth1 - VIF 101 + enabled: true + - name: eth2 + description: Configured by Ansible - Interface 2 (ADMIN DOWN) + mtu: 600 + enabled: false diff --git a/tests/unit/plugins/filter/test_keep_keys.py b/tests/unit/plugins/filter/test_keep_keys.py new file mode 100644 index 0000000..33ff685 --- /dev/null +++ b/tests/unit/plugins/filter/test_keep_keys.py @@ -0,0 +1,233 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import unittest +from ansible.errors import AnsibleFilterError +from ansible_collections.ansible.utils.plugins.filter.keep_keys import ( + _keep_keys, +) + + +class TestKeepKeys(unittest.TestCase): + def setUp(self): + pass + + def test_keep_filter_plugin(self): + data = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "interface_name": "eth1", + "is_enabled": True, + "mtu": 1500, + "speed": "auto", + "vifs": [ + { + "description": "Eth1 - VIF 100", + "is_enabled": True, + "mtu": 400, + "vlan_id": 100, + }, + { + "description": "Eth1 - VIF 101", + "is_enabled": True, + "vlan_id": 101, + }, + ], + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "interface_name": "eth2", + "is_enabled": True, + "mtu": 600, + }, + ] + target = ["interface_name", "is_enabled"] + output = [ + {"interface_name": "eth0"}, + { + "is_enabled": True, + "vifs": [{"is_enabled": True}, {"is_enabled": True}], + "interface_name": "eth1", + }, + {"is_enabled": True, "interface_name": "eth2"}, + ] + args = ["", data, target] + + result = _keep_keys(*args) + self.assertEqual(result, output) + + def test_keep_filter_match_starts_with_plugin(self): + data = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "interface_name": "eth1", + "is_enabled": True, + "mtu": 1500, + "speed": "auto", + "vifs": [ + { + "description": "Eth1 - VIF 100", + "is_enabled": True, + "mtu": 400, + "vlan_id": 100, + }, + { + "description": "Eth1 - VIF 101", + "is_enabled": True, + "vlan_id": 101, + }, + ], + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "interface_name": "eth2", + "is_enabled": True, + "mtu": 600, + }, + ] + target = ["interface", "is_"] + output = [ + {"interface_name": "eth0"}, + { + "is_enabled": True, + "vifs": [{"is_enabled": True}, {"is_enabled": True}], + "interface_name": "eth1", + }, + {"is_enabled": True, "interface_name": "eth2"}, + ] + args = ["", data, target, "starts_with"] + + result = _keep_keys(*args) + self.assertEqual(result, output) + + def test_keep_filter_match_ends_with_plugin(self): + data = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "interface_name": "eth1", + "is_enabled": True, + "mtu": 1500, + "speed": "auto", + "vifs": [ + { + "description": "Eth1 - VIF 100", + "is_enabled": True, + "mtu": 400, + "vlan_id": 100, + }, + { + "description": "Eth1 - VIF 101", + "is_enabled": True, + "vlan_id": 101, + }, + ], + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "interface_name": "eth2", + "is_enabled": True, + "mtu": 600, + }, + ] + target = ["_name", "_enabled"] + output = [ + {"interface_name": "eth0"}, + { + "is_enabled": True, + "vifs": [{"is_enabled": True}, {"is_enabled": True}], + "interface_name": "eth1", + }, + {"is_enabled": True, "interface_name": "eth2"}, + ] + args = ["", data, target, "ends_with"] + + result = _keep_keys(*args) + self.assertEqual(result, output) + + def test_keep_filter_match_regex_plugin(self): + data = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "interface_name": "eth1", + "is_enabled": True, + "mtu": 1500, + "speed": "auto", + "vifs": [ + { + "description": "Eth1 - VIF 100", + "is_enabled": True, + "mtu": 400, + "vlan_id": 100, + }, + { + "description": "Eth1 - VIF 101", + "is_enabled": True, + "vlan_id": 101, + }, + ], + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "interface_name": "eth2", + "is_enabled": True, + "mtu": 600, + }, + ] + target = ["^interface_name$", "is_enabled"] + output = [ + {"interface_name": "eth0"}, + { + "is_enabled": True, + "vifs": [{"is_enabled": True}, {"is_enabled": True}], + "interface_name": "eth1", + }, + {"is_enabled": True, "interface_name": "eth2"}, + ] + args = ["", data, target, "regex"] + + result = _keep_keys(*args) + self.assertEqual(result, output) + + def test_invalid_data(self): + self.maxDiff = None + target = ["a", "b"] + args = ["", "string data", target] + with self.assertRaises(AnsibleFilterError) as error: + _keep_keys(*args) + self.assertIn( + "Error when using plugin 'keep_keys'", str(error.exception) + ) diff --git a/tests/unit/plugins/filter/test_remove_keys.py b/tests/unit/plugins/filter/test_remove_keys.py new file mode 100644 index 0000000..2b81a0f --- /dev/null +++ b/tests/unit/plugins/filter/test_remove_keys.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import unittest +from ansible.errors import AnsibleFilterError +from ansible_collections.ansible.utils.plugins.filter.remove_keys import ( + _remove_keys, +) + + +class TestReplaceKeys(unittest.TestCase): + def setUp(self): + pass + + def test_remove_filter_plugin(self): + data = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + "extra": "remove extra", + }, + { + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "interface_name": "eth1", + "is_enabled": True, + "mtu": 1500, + "speed": "auto", + "vifs": [ + { + "description": "Eth1 - VIF 100", + "is_enabled": True, + "mtu": 400, + "vlan_id": 100, + "comment": ["comment A", "comment B"], + }, + { + "description": "Eth1 - VIF 101", + "is_enabled": True, + "vlan_id": 101, + }, + ], + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "interface_name": "eth2", + "is_enabled": True, + "mtu": 600, + }, + ] + target = ["extra", "comment"] + output = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "is_enabled": True, + "vifs": [ + { + "is_enabled": True, + "description": "Eth1 - VIF 100", + "vlan_id": 100, + "mtu": 400, + }, + { + "is_enabled": True, + "description": "Eth1 - VIF 101", + "vlan_id": 101, + }, + ], + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "mtu": 1500, + "interface_name": "eth1", + "speed": "auto", + }, + { + "is_enabled": True, + "interface_name": "eth2", + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "mtu": 600, + }, + ] + args = ["", data, target] + + result = _remove_keys(*args) + self.assertEqual(result, output) + + def test_remove_filter_match_starts_with_plugin(self): + data = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "interface_name": "eth1", + "is_enabled": True, + "mtu": 1500, + "speed": "auto", + "vifs": [ + { + "description": "Eth1 - VIF 100", + "is_enabled": True, + "mtu": 400, + "vlan_id": 100, + }, + { + "description": "Eth1 - VIF 101", + "is_enabled": True, + "vlan_id": 101, + }, + ], + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "interface_name": "eth2", + "is_enabled": True, + "mtu": 600, + }, + ] + target = ["is_"] + output = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "vifs": [ + { + "description": "Eth1 - VIF 100", + "vlan_id": 100, + "mtu": 400, + }, + {"description": "Eth1 - VIF 101", "vlan_id": 101}, + ], + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "mtu": 1500, + "interface_name": "eth1", + "speed": "auto", + }, + { + "interface_name": "eth2", + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "mtu": 600, + }, + ] + args = ["", data, target, "starts_with"] + + result = _remove_keys(*args) + self.assertEqual(result, output) + + def test_replace_filter_match_ends_with_plugin(self): + data = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "interface_name": "eth1", + "is_enabled": True, + "mtu": 1500, + "speed": "auto", + "vifs": [ + { + "description": "Eth1 - VIF 100", + "is_enabled": True, + "mtu": 400, + "vlan_id": 100, + }, + { + "description": "Eth1 - VIF 101", + "is_enabled": True, + "vlan_id": 101, + }, + ], + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "interface_name": "eth2", + "is_enabled": True, + "mtu": 600, + }, + ] + target = ["_enabled"] + output = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "vifs": [ + { + "description": "Eth1 - VIF 100", + "vlan_id": 100, + "mtu": 400, + }, + {"description": "Eth1 - VIF 101", "vlan_id": 101}, + ], + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "mtu": 1500, + "interface_name": "eth1", + "speed": "auto", + }, + { + "interface_name": "eth2", + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "mtu": 600, + }, + ] + args = ["", data, target, "ends_with"] + + result = _remove_keys(*args) + self.assertEqual(result, output) + + def test_replace_filter_match_regex_plugin(self): + data = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "interface_name": "eth1", + "is_enabled": True, + "mtu": 1500, + "speed": "auto", + "vifs": [ + { + "description": "Eth1 - VIF 100", + "is_enabled": True, + "mtu": 400, + "vlan_id": 100, + }, + { + "description": "Eth1 - VIF 101", + "is_enabled": True, + "vlan_id": 101, + }, + ], + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "interface_name": "eth2", + "is_enabled": True, + "mtu": 600, + }, + ] + target = ["^desc"] + output = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "is_enabled": True, + "vifs": [ + {"is_enabled": True, "vlan_id": 100, "mtu": 400}, + {"is_enabled": True, "vlan_id": 101}, + ], + "duplex": "auto", + "mtu": 1500, + "interface_name": "eth1", + "speed": "auto", + }, + {"is_enabled": True, "interface_name": "eth2", "mtu": 600}, + ] + args = ["", data, target, "regex"] + + result = _remove_keys(*args) + self.assertEqual(result, output) + + def test_invalid_data(self): + self.maxDiff = None + target = [{"before": "pre", "after": "post"}] + args = ["", "string data", target] + with self.assertRaises(AnsibleFilterError) as error: + _remove_keys(*args) + self.assertIn( + "Error when using plugin 'remove_keys'", str(error.exception) + ) diff --git a/tests/unit/plugins/filter/test_replace_keys.py b/tests/unit/plugins/filter/test_replace_keys.py new file mode 100644 index 0000000..bc6eda8 --- /dev/null +++ b/tests/unit/plugins/filter/test_replace_keys.py @@ -0,0 +1,349 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import unittest +from ansible.errors import AnsibleFilterError +from ansible_collections.ansible.utils.plugins.filter.replace_keys import ( + _replace_keys, +) + + +class TestReplaceKeys(unittest.TestCase): + def setUp(self): + pass + + def test_replace_filter_plugin(self): + data = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "interface_name": "eth1", + "is_enabled": True, + "mtu": 1500, + "speed": "auto", + "vifs": [ + { + "description": "Eth1 - VIF 100", + "is_enabled": True, + "mtu": 400, + "vlan_id": 100, + }, + { + "description": "Eth1 - VIF 101", + "is_enabled": True, + "vlan_id": 101, + }, + ], + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "interface_name": "eth2", + "is_enabled": True, + "mtu": 600, + }, + ] + target = [ + {"before": "interface_name", "after": "name"}, + {"before": "is_enabled", "after": "enabled"}, + ] + output = [ + { + "duplex": "auto", + "enabled": True, + "speed": "auto", + "name": "eth0", + }, + { + "vifs": [ + { + "enabled": True, + "description": "Eth1 - VIF 100", + "vlan_id": 100, + "mtu": 400, + }, + { + "enabled": True, + "description": "Eth1 - VIF 101", + "vlan_id": 101, + }, + ], + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "enabled": True, + "mtu": 1500, + "speed": "auto", + "name": "eth1", + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "enabled": True, + "mtu": 600, + "name": "eth2", + }, + ] + args = ["", data, target] + + result = _replace_keys(*args) + self.assertEqual(result, output) + + def test_replace_filter_match_starts_with_plugin(self): + data = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "interface_name": "eth1", + "is_enabled": True, + "mtu": 1500, + "speed": "auto", + "vifs": [ + { + "description": "Eth1 - VIF 100", + "is_enabled": True, + "mtu": 400, + "vlan_id": 100, + }, + { + "description": "Eth1 - VIF 101", + "is_enabled": True, + "vlan_id": 101, + }, + ], + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "interface_name": "eth2", + "is_enabled": True, + "mtu": 600, + }, + ] + target = [ + {"before": "interface", "after": "name"}, + {"before": "is", "after": "enabled"}, + ] + output = [ + { + "duplex": "auto", + "enabled": True, + "speed": "auto", + "name": "eth0", + }, + { + "vifs": [ + { + "enabled": True, + "description": "Eth1 - VIF 100", + "vlan_id": 100, + "mtu": 400, + }, + { + "enabled": True, + "description": "Eth1 - VIF 101", + "vlan_id": 101, + }, + ], + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "enabled": True, + "mtu": 1500, + "speed": "auto", + "name": "eth1", + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "enabled": True, + "mtu": 600, + "name": "eth2", + }, + ] + args = ["", data, target, "starts_with"] + + result = _replace_keys(*args) + self.assertEqual(result, output) + + def test_replace_filter_match_ends_with_plugin(self): + data = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "interface_name": "eth1", + "is_enabled": True, + "mtu": 1500, + "speed": "auto", + "vifs": [ + { + "description": "Eth1 - VIF 100", + "is_enabled": True, + "mtu": 400, + "vlan_id": 100, + }, + { + "description": "Eth1 - VIF 101", + "is_enabled": True, + "vlan_id": 101, + }, + ], + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "interface_name": "eth2", + "is_enabled": True, + "mtu": 600, + }, + ] + target = [ + {"before": "ame", "after": "name"}, + {"before": "enabled", "after": "enabled"}, + ] + output = [ + { + "duplex": "auto", + "enabled": True, + "speed": "auto", + "name": "eth0", + }, + { + "vifs": [ + { + "enabled": True, + "description": "Eth1 - VIF 100", + "vlan_id": 100, + "mtu": 400, + }, + { + "enabled": True, + "description": "Eth1 - VIF 101", + "vlan_id": 101, + }, + ], + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "enabled": True, + "mtu": 1500, + "speed": "auto", + "name": "eth1", + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "enabled": True, + "mtu": 600, + "name": "eth2", + }, + ] + args = ["", data, target, "ends_with"] + + result = _replace_keys(*args) + self.assertEqual(result, output) + + def test_replace_filter_match_regex_plugin(self): + data = [ + { + "duplex": "auto", + "enabled": True, + "interface_name": "eth0", + "speed": "auto", + }, + { + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "interface_name": "eth1", + "is_enabled": True, + "mtu": 1500, + "speed": "auto", + "vifs": [ + { + "description": "Eth1 - VIF 100", + "is_enabled": True, + "mtu": 400, + "vlan_id": 100, + }, + { + "description": "Eth1 - VIF 101", + "is_enabled": True, + "vlan_id": 101, + }, + ], + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "interface_name": "eth2", + "is_enabled": True, + "mtu": 600, + }, + ] + target = [ + {"before": "^interface_name$", "after": "name"}, + {"before": "is_enabled", "after": "enabled"}, + ] + output = [ + { + "duplex": "auto", + "enabled": True, + "speed": "auto", + "name": "eth0", + }, + { + "vifs": [ + { + "enabled": True, + "description": "Eth1 - VIF 100", + "vlan_id": 100, + "mtu": 400, + }, + { + "enabled": True, + "description": "Eth1 - VIF 101", + "vlan_id": 101, + }, + ], + "description": "Configured by Ansible - Interface 1", + "duplex": "auto", + "enabled": True, + "mtu": 1500, + "speed": "auto", + "name": "eth1", + }, + { + "description": "Configured by Ansible - Interface 2 (ADMIN DOWN)", + "enabled": True, + "mtu": 600, + "name": "eth2", + }, + ] + args = ["", data, target, "regex"] + + result = _replace_keys(*args) + self.assertEqual(result, output) + + def test_invalid_data(self): + self.maxDiff = None + target = [{"before": "pre", "after": "post"}] + args = ["", "string data", target] + with self.assertRaises(AnsibleFilterError) as error: + _replace_keys(*args) + self.assertIn( + "Error when using plugin 'replace_keys'", str(error.exception) + )