Repository: ansible-collections/community.general

Issue:
to_ini filter fail to load value with `%` due to default interpolation option.
### Summary

When trying to write an ini file from a dictionary including `%` char in some field, the Filter fail with the following error:
```
invalid interpolation syntax in '<Value>' at position X
```
Looking at the work [here](https://github.com/ansible-collections/community.general/pull/7744), the [default interpolation option](https://docs.python.org/3/library/configparser.html#interpolation-of-values) of `configparser` is still enabled and you would need to escape each `%`.

I feel that this should not be the case, because you are not always in control of the input directory, but also because importing a file using ` | from_ini() ` and writing it back again using ` | to_ini()` is inconsistent.

Additionally, escaping the % (by doubling them) don't produce a single % but a double, making escape impossible.

A simple fix is to disable the interpolation when creating the parent class [here](https://github.com/ansible-collections/community.general/blob/e4e091accaa1d8ecbd7370db6c433bae7a81ce6d/plugins/filter/to_ini.py#L61C1-L67C31) like so:
``` Python3
class IniParser(ConfigParser):
    ''' Implements a configparser which sets the correct optionxform and disable interpolation'''

    def __init__(self):
        super().__init__(interpolation=None)
        self.optionxform = str
```
A more complex fix would require to add an option to enable or not the interpolation.



### Issue Type

Bug Report

### Component Name

filter to_ini

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.15.9]
  config file = /home/vaarlion/.ansible.cfg
  configured module search path = ['/home/vaarlion/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/vaarlion/.local/lib/python3.10/site-packages/ansible
  ansible collection location = /home/vaarlion/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/vaarlion/.local/bin/ansible
  python version = 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] (/usr/bin/python3)
  jinja version = 3.1.2
  libyaml = True
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
# /home/vaarlion/.ansible/collections/ansible_collections
Collection        Version
----------------- -------
community.general 8.3.0 
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
CONFIG_FILE() = /home/vaarlion/.ansible.cfg
DISPLAY_SKIPPED_HOSTS(/home/vaarlion/.ansible.cfg) = False
EDITOR(env: EDITOR) = /usr/bin/vim
PAGER(env: PAGER) = less
```


### OS / Environment

Ubuntu 22.04.4 LTS

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
---
- name: Test % with ini
  hosts: all

  tasks:
    - name: Create a dict with a double % in a value
      ansible.builtin.set_fact:
        test_dict_1:
          test_section:
            a: value a
            b: value %% b

    - name: Display the ini build from the dict using to_ini()
      ansible.builtin.debug:
        msg: "{{ test_dict_1 | community.general.to_ini }}"

    - name: Create a dict with a single % in a value
      ansible.builtin.set_fact:
        test_dict_2:
          test_section:
            a: value a
            b: value % b

    - name: Display the ini build from the dict using to_ini()
      ansible.builtin.debug:
        msg: "{{ test_dict_2 | community.general.to_ini }}"

```


### Expected Results

```console
PLAY [Test % with ini] *******************************************************************

TASK [Gathering Facts] *******************************************************************
ok: [localhost]

TASK [Create a dict with a double % in a value] ******************************************
ok: [localhost]

TASK [Display the ini build from the dict using to_ini()] ********************************
ok: [localhost] => {
    "msg": "[test_section]\na = value a\nb = value %% b\n"
}

TASK [Create a dict with a single % in a value] ******************************************
ok: [localhost]

TASK [Display the ini build from the dict using to_ini()] ********************************
ok: [localhost] => {
    "msg": "[test_section]\na = value a\nb = value % b\n"
}

PLAY RECAP *******************************************************************************
localhost                       : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```

### Actual Results

```console (paste below)
PLAY [Test % with ini] *******************************************************************

TASK [Gathering Facts] *******************************************************************
ok: [localhost]

TASK [Create a dict with a double % in a value] ******************************************
ok: [localhost]

TASK [Display the ini build from the dict using to_ini()] ********************************
ok: [localhost] => {
    "msg": "[test_section]\na = value a\nb = value %% b\n"
}

TASK [Create a dict with a single % in a value] ******************************************
ok: [localhost]

TASK [Display the ini build from the dict using to_ini()] ********************************
fatal: [localhost]: FAILED! => {"msg": "to_ini failed to parse given dict:invalid interpolation syntax in 'value % b' at position 6. invalid interpolation syntax in 'value % b' at position 6"}

PLAY RECAP *******************************************************************************
localhost                       : ok=4    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`tests/integration/targets/filter_to_ini/tasks/main.yml`](https://github.com/ansible-collections/community.general/blob/main/tests/integration/targets/filter_to_ini/tasks/main.yml)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
Files identified in the description:
* [`plugins/filter/to_ini.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/filter/to_ini.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @sscheib
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
I agree, interpolation should be turned off.
I don't think interpolation is of any use in Ansible's context, so I too agree, turning it off (and not providing a way of enabling it), is the way forward. I am currently preparing a PR for it.

I'll also add test coverage for the `%` case.

Resolving patch:
diff --git a/plugins/filter/from_ini.py b/plugins/filter/from_ini.py
--- a/plugins/filter/from_ini.py
+++ b/plugins/filter/from_ini.py
@@ -57,7 +57,7 @@ class IniParser(ConfigParser):
     ''' Implements a configparser which is able to return a dict '''
 
     def __init__(self):
-        super().__init__()
+        super().__init__(interpolation=None)
         self.optionxform = str
 
     def as_dict(self):
diff --git a/plugins/filter/to_ini.py b/plugins/filter/to_ini.py
--- a/plugins/filter/to_ini.py
+++ b/plugins/filter/to_ini.py
@@ -63,7 +63,7 @@ class IniParser(ConfigParser):
     ''' Implements a configparser which sets the correct optionxform '''
 
     def __init__(self):
-        super().__init__()
+        super().__init__(interpolation=None)
         self.optionxform = str
 
 


Test patch:
diff --git a/tests/integration/targets/filter_from_ini/tasks/main.yml b/tests/integration/targets/filter_from_ini/tasks/main.yml
--- a/tests/integration/targets/filter_from_ini/tasks/main.yml
+++ b/tests/integration/targets/filter_from_ini/tasks/main.yml
@@ -12,15 +12,21 @@
       another_section:
         connection: 'ssh'
 
+      interpolate_test:
+        interpolate_test_key: '%'
+
 - 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
+      key_name = key value
 
       [another_section]
-      connection=ssh
+      connection = ssh
+
+      [interpolate_test]
+      interpolate_test_key = %
 
 - name: 'Slurp the test file: {{ ini_test_file }}'
   ansible.builtin.slurp:
diff --git a/tests/integration/targets/filter_to_ini/tasks/main.yml b/tests/integration/targets/filter_to_ini/tasks/main.yml
--- a/tests/integration/targets/filter_to_ini/tasks/main.yml
+++ b/tests/integration/targets/filter_to_ini/tasks/main.yml
@@ -16,6 +16,9 @@
       another_section:
         connection: 'ssh'
 
+      interpolate_test:
+        interpolate_test_key: '%'
+
 - name: 'Write INI file manually to {{ ini_test_file }}'
   ansible.builtin.copy:
     dest: '{{ ini_test_file }}'
@@ -26,6 +29,9 @@
       [another_section]
       connection = ssh
 
+      [interpolate_test]
+      interpolate_test_key = %
+
 - name: 'Slurp the manually created test file: {{ ini_test_file }}'
   ansible.builtin.slurp:
     src: '{{ ini_test_file }}'