mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Adding a new filter: to_ini, which allows conversion of a dictionary to an INI formatted string (#7744)
* Adding a new filter: to_ini, which allows conversion of a dictionary to an INI formatted string * Adding to_ini maintainers into BOTMETA * Correcting filter suffix * Moving filter to correct path * Adding error handling; Removing quotes from examples; Fixing RETURN documentation * Removing the last newline char; Adding error handling for an empty dict * Adding integration tests for to_ini * Fixing F-String usage * Fixing formatting * Fixing whitespace * Moving import statements below documentation; Adding a more generic Exception handling; Removing unused imports * Removing not needed set_fact and replacing it with using vars: * Replacing MutableMapping with Mapping
This commit is contained in:
parent
f79940c415
commit
ec12422fae
4 changed files with 173 additions and 0 deletions
2
.github/BOTMETA.yml
vendored
2
.github/BOTMETA.yml
vendored
|
@ -158,6 +158,8 @@ files:
|
||||||
maintainers: resmo
|
maintainers: resmo
|
||||||
$filters/to_hours.yml:
|
$filters/to_hours.yml:
|
||||||
maintainers: resmo
|
maintainers: resmo
|
||||||
|
$filters/to_ini.py:
|
||||||
|
maintainers: sscheib
|
||||||
$filters/to_milliseconds.yml:
|
$filters/to_milliseconds.yml:
|
||||||
maintainers: resmo
|
maintainers: resmo
|
||||||
$filters/to_minutes.yml:
|
$filters/to_minutes.yml:
|
||||||
|
|
105
plugins/filter/to_ini.py
Normal file
105
plugins/filter/to_ini.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2023, Steffen Scheib <steffen@scheib.me>
|
||||||
|
# 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: to_ini
|
||||||
|
short_description: Converts a dictionary to the INI file format
|
||||||
|
version_added: 8.2.0
|
||||||
|
author: Steffen Scheib (@sscheib)
|
||||||
|
description:
|
||||||
|
- Converts a dictionary to the INI file format.
|
||||||
|
options:
|
||||||
|
_input:
|
||||||
|
description: The dictionary that should be converted to the INI format.
|
||||||
|
type: dictionary
|
||||||
|
required: true
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
- name: Define a dictionary
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
my_dict:
|
||||||
|
section_name:
|
||||||
|
key_name: 'key value'
|
||||||
|
|
||||||
|
another_section:
|
||||||
|
connection: 'ssh'
|
||||||
|
|
||||||
|
- name: Write dictionary to INI file
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /tmp/test.ini
|
||||||
|
content: '{{ my_dict | community.general.to_ini }}'
|
||||||
|
|
||||||
|
# /tmp/test.ini will look like this:
|
||||||
|
# [section_name]
|
||||||
|
# key_name = key value
|
||||||
|
#
|
||||||
|
# [another_section]
|
||||||
|
# connection = ssh
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
_value:
|
||||||
|
description: A string formatted as INI file.
|
||||||
|
type: string
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleFilterError
|
||||||
|
from ansible.module_utils.common._collections_compat import Mapping
|
||||||
|
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 sets the correct optionxform '''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.optionxform = str
|
||||||
|
|
||||||
|
|
||||||
|
def to_ini(obj):
|
||||||
|
''' Read the given dict and return an INI formatted string '''
|
||||||
|
|
||||||
|
if not isinstance(obj, Mapping):
|
||||||
|
raise AnsibleFilterError(f'to_ini requires a dict, got {type(obj)}')
|
||||||
|
|
||||||
|
ini_parser = IniParser()
|
||||||
|
|
||||||
|
try:
|
||||||
|
ini_parser.read_dict(obj)
|
||||||
|
except Exception as ex:
|
||||||
|
raise AnsibleFilterError('to_ini failed to parse given dict:'
|
||||||
|
f'{to_native(ex)}', orig_exc=ex)
|
||||||
|
|
||||||
|
# catching empty dicts
|
||||||
|
if obj == dict():
|
||||||
|
raise AnsibleFilterError('to_ini received an empty dict. '
|
||||||
|
'An empty dict cannot be converted.')
|
||||||
|
|
||||||
|
config = StringIO()
|
||||||
|
ini_parser.write(config)
|
||||||
|
|
||||||
|
# config.getvalue() returns two \n at the end
|
||||||
|
# with the below insanity, we remove the very last character of
|
||||||
|
# the resulting string
|
||||||
|
return ''.join(config.getvalue().rsplit(config.getvalue()[-1], 1))
|
||||||
|
|
||||||
|
|
||||||
|
class FilterModule(object):
|
||||||
|
''' Query filter '''
|
||||||
|
|
||||||
|
def filters(self):
|
||||||
|
|
||||||
|
return {
|
||||||
|
'to_ini': to_ini
|
||||||
|
}
|
58
tests/integration/targets/filter_to_ini/tasks/main.yml
Normal file
58
tests/integration/targets/filter_to_ini/tasks/main.yml
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
# Copyright (c) 2023, Steffen Scheib <steffen@scheib.me>
|
||||||
|
# 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: >-
|
||||||
|
Write INI file that reflects using to_ini to {{ ini_test_file_filter }}
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: '{{ ini_test_file_filter }}'
|
||||||
|
content: '{{ ini_test_dict | community.general.to_ini }}'
|
||||||
|
vars:
|
||||||
|
ini_test_dict:
|
||||||
|
section_name:
|
||||||
|
key_name: 'key value'
|
||||||
|
|
||||||
|
another_section:
|
||||||
|
connection: 'ssh'
|
||||||
|
|
||||||
|
- name: 'Write INI file manually 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 manually created test file: {{ ini_test_file }}'
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: '{{ ini_test_file }}'
|
||||||
|
register: 'ini_file_content'
|
||||||
|
|
||||||
|
- name: 'Slurp the test file created with to_ini: {{ ini_test_file_filter }}'
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: '{{ ini_test_file_filter }}'
|
||||||
|
register: 'ini_file_filter_content'
|
||||||
|
|
||||||
|
- name: >-
|
||||||
|
Ensure the manually created test file and the test file created with
|
||||||
|
to_ini are identical
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- 'ini_file_content.content | b64decode ==
|
||||||
|
ini_file_filter_content.content | b64decode'
|
||||||
|
|
||||||
|
- name: 'Try to convert an empty dictionary with to_ini'
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: '{{ {} | community.general.to_ini }}'
|
||||||
|
register: 'ini_empty_dict'
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: 'Ensure the correct exception was raised'
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- "'to_ini received an empty dict. An empty dict cannot be converted.' in
|
||||||
|
ini_empty_dict.msg"
|
||||||
|
...
|
8
tests/integration/targets/filter_to_ini/vars/main.yml
Normal file
8
tests/integration/targets/filter_to_ini/vars/main.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
# Copyright (c) 2023, Steffen Scheib <steffen@scheib.me>
|
||||||
|
# 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_test_file_filter: '/tmp/test_filter.ini'
|
||||||
|
...
|
Loading…
Reference in a new issue