From 718f88d2c737c89b99a932df0cecd4139248284e Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Thu, 28 Dec 2023 09:33:18 +0100 Subject: [PATCH] [PR #7743/ec6dfe2f backport][stable-8] Adding a new filter: from_ini, which allows conversion of INI content to a dictionary (#7769) Adding a new filter: from_ini, which allows conversion of INI content to a dictionary (#7743) * Adding a new filter: from_ini, which allows conversion of INI content to a dictionary * Adding from_ini maintainers into BOTMETA * Adding error handling; Removing quotes from examples; Fixing RETURN documentation * Adding integration tests * Moving imports below documentation; Adding a more general exception handling (cherry picked from commit ec6dfe2fcdbb11acd3fed9b543b1b434b4a4a67e) Co-authored-by: Steffen Scheib <37306894+sscheib@users.noreply.github.com> --- .github/BOTMETA.yml | 2 + plugins/filter/from_ini.py | 99 +++++++++++++++++++ .../targets/filter_from_ini/tasks/main.yml | 60 +++++++++++ .../targets/filter_from_ini/vars/main.yml | 8 ++ 4 files changed, 169 insertions(+) create mode 100644 plugins/filter/from_ini.py create mode 100644 tests/integration/targets/filter_from_ini/tasks/main.yml create mode 100644 tests/integration/targets/filter_from_ini/vars/main.yml diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index fc498404ba..d94303e9fc 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -133,6 +133,8 @@ files: maintainers: giner $filters/from_csv.py: maintainers: Ajpantuso + $filters/from_ini.py: + maintainers: sscheib $filters/groupby_as_dict.py: maintainers: felixfontein $filters/hashids.py: diff --git a/plugins/filter/from_ini.py b/plugins/filter/from_ini.py new file mode 100644 index 0000000000..d68b51092e --- /dev/null +++ b/plugins/filter/from_ini.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2023, Steffen Scheib +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function + +DOCUMENTATION = r''' + name: from_ini + short_description: Converts INI text input into a dictionary + version_added: 8.2.0 + author: Steffen Scheib (@sscheib) + description: + - Converts INI text input into a dictionary. + options: + _input: + description: A string containing an INI document. + type: string + required: true +''' + +EXAMPLES = r''' + - name: Slurp an INI file + ansible.builtin.slurp: + src: /etc/rhsm/rhsm.conf + register: rhsm_conf + + - name: Display the INI file as dictionary + ansible.builtin.debug: + var: rhsm_conf.content | b64decode | community.general.from_ini + + - name: Set a new dictionary fact with the contents of the INI file + ansible.builtin.set_fact: + rhsm_dict: >- + {{ + rhsm_conf.content | b64decode | community.general.from_ini + }} +''' + +RETURN = ''' + _value: + description: A dictionary representing the INI file. + type: dictionary +''' + +__metaclass__ = type + +from ansible.errors import AnsibleFilterError +from ansible.module_utils.six import string_types +from ansible.module_utils.six.moves import StringIO +from ansible.module_utils.six.moves.configparser import ConfigParser +from ansible.module_utils.common.text.converters import to_native + + +class IniParser(ConfigParser): + ''' Implements a configparser which is able to return a dict ''' + + def __init__(self): + super().__init__() + self.optionxform = str + + def as_dict(self): + d = dict(self._sections) + for k in d: + d[k] = dict(self._defaults, **d[k]) + d[k].pop('__name__', None) + + if self._defaults: + d['DEFAULT'] = dict(self._defaults) + + return d + + +def from_ini(obj): + ''' Read the given string as INI file and return a dict ''' + + if not isinstance(obj, string_types): + raise AnsibleFilterError(f'from_ini requires a str, got {type(obj)}') + + parser = IniParser() + + try: + parser.read_file(StringIO(obj)) + except Exception as ex: + raise AnsibleFilterError(f'from_ini failed to parse given string: ' + f'{to_native(ex)}', orig_exc=ex) + + return parser.as_dict() + + +class FilterModule(object): + ''' Query filter ''' + + def filters(self): + + return { + 'from_ini': from_ini + } diff --git a/tests/integration/targets/filter_from_ini/tasks/main.yml b/tests/integration/targets/filter_from_ini/tasks/main.yml new file mode 100644 index 0000000000..a2eca36a6e --- /dev/null +++ b/tests/integration/targets/filter_from_ini/tasks/main.yml @@ -0,0 +1,60 @@ +--- +# Copyright (c) 2023, Steffen Scheib +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: 'Define ini_test_dict' + ansible.builtin.set_fact: + ini_test_dict: + section_name: + key_name: 'key value' + + another_section: + connection: 'ssh' + +- name: 'Write INI file that reflects ini_test_dict to {{ ini_test_file }}' + ansible.builtin.copy: + dest: '{{ ini_test_file }}' + content: | + [section_name] + key_name=key value + + [another_section] + connection=ssh + +- name: 'Slurp the test file: {{ ini_test_file }}' + ansible.builtin.slurp: + src: '{{ ini_test_file }}' + register: 'ini_file_content' + +- name: >- + Ensure defined ini_test_dict is the same when retrieved + from {{ ini_test_file }} + ansible.builtin.assert: + that: + - 'ini_file_content.content | b64decode | community.general.from_ini == + ini_test_dict' + +- name: 'Create a file that is not INI formatted: {{ ini_bad_file }}' + ansible.builtin.copy: + dest: '{{ ini_bad_file }}' + content: | + Testing a not INI formatted file. + +- name: 'Slurp the file that is not INI formatted: {{ ini_bad_file }}' + ansible.builtin.slurp: + src: '{{ ini_bad_file }}' + register: 'ini_bad_file_content' + +- name: 'Try parsing the bad file with from_ini: {{ ini_bad_file }}' + ansible.builtin.debug: + var: ini_bad_file_content | b64decode | community.general.from_ini + register: 'ini_bad_file_debug' + ignore_errors: true + +- name: 'Ensure from_ini raised the correct exception' + ansible.builtin.assert: + that: + - "'from_ini failed to parse given string' in ini_bad_file_debug.msg" + - "'File contains no section headers' in ini_bad_file_debug.msg" +... diff --git a/tests/integration/targets/filter_from_ini/vars/main.yml b/tests/integration/targets/filter_from_ini/vars/main.yml new file mode 100644 index 0000000000..8c4d793275 --- /dev/null +++ b/tests/integration/targets/filter_from_ini/vars/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) 2023, Steffen Scheib +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +ini_test_file: '/tmp/test.ini' +ini_bad_file: '/tmp/bad.file' +...