Repository: ansible-collections/community.general

Issue:
community.general.one_vm: creating fails with "Parse error: syntax error"
### Summary

When we try to create a VM in our OpenNebula environment using an Ansible task it fails with an error message.
Notably this worked before on `community.general` 6.2.0 or lower and thus is a **regression**.

We suspect that the bug appeared in 8818a6f2421d50980229db464a710a84e4748eff, especially since it relates to new ONE features (we are still on an old ONE version).

### Issue Type

Bug Report

### Component Name

one_vm

### Ansible Version

```console (paste below)
ansible [core 2.14.3]
  config file = /root/projects/server-automation/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.9/site-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.9.13 (main, Nov 16 2022, 10:51:39) [GCC 8.5.0 20210514 (Red Hat 8.5.0-15)] (/usr/bin/python3.9)
  jinja version = 3.1.2
  libyaml = True
```


### Community.general Version

```console (paste below)
# /root/.ansible/collections/ansible_collections
Collection        Version
----------------- -------
community.general 6.4.0
```


### Configuration

no special configuration

### OS / Environment

OpenNebula installation: version 6.0.0.2

### Steps to Reproduce

```yaml
  - community.general.one_vm:
      api_url: "{{ one_api }}"
      api_username: "{{ one_user }}"
      api_password: "{{ one_token }}" 
      template_id: 577
      disk_size: "5 GB"
      memory: "2048 MB"
      cpu: 1
      vcpu: 1
      networks:
        - NETWORK_ID: 6
      attributes:
        name: "testvm123"
        HYPERVISOR: kvm

```

### Expected Results

VM in OpenNebula successfully created

### Actual Results

```console
TASK [community.general.one_vm] ****************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "[one.template.instantiate] Parse error: syntax error, unexpected VARIABLE, expecting EQUAL or EQUAL_EMPTY at line 32643, columns 735:739"}
```

Depending on which vm template is used, there is sometimes a different error:
```console
TASK [community.general.one_vm] ****************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "[one.template.instantiate] Error allocating a new virtual machine template. Wrong OS/BOOT value: \"None\" should be a comma-separated list of disk# or nic#"}
```

In either case the exact same playbook worked before.

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/one_vm.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/one_vm.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 @ilicmilan @meerkampdvv @nilsding @rsmontero @xorel
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
CC @sk4zuzu @russoz

Resolving patch:
diff --git a/plugins/module_utils/opennebula.py b/plugins/module_utils/opennebula.py
--- a/plugins/module_utils/opennebula.py
+++ b/plugins/module_utils/opennebula.py
@@ -45,6 +45,8 @@ def render(to_render):
     """Converts dictionary to OpenNebula template."""
     def recurse(to_render):
         for key, value in sorted(to_render.items()):
+            if value is None:
+                continue
             if isinstance(value, dict):
                 yield '{0:}=[{1:}]'.format(key, ','.join(recurse(value)))
                 continue
@@ -52,6 +54,9 @@ def recurse(to_render):
                 for item in value:
                     yield '{0:}=[{1:}]'.format(key, ','.join(recurse(item)))
                 continue
+            if isinstance(value, str):
+                yield '{0:}="{1:}"'.format(key, value.replace('\\', '\\\\').replace('"', '\\"'))
+                continue
             yield '{0:}="{1:}"'.format(key, value)
     return '\n'.join(recurse(to_render))
 


Test patch:
diff --git a/tests/unit/plugins/module_utils/test_opennebula.py b/tests/unit/plugins/module_utils/test_opennebula.py
--- a/tests/unit/plugins/module_utils/test_opennebula.py
+++ b/tests/unit/plugins/module_utils/test_opennebula.py
@@ -75,6 +75,18 @@
             NIC=[NAME="NIC1",NETWORK_ID="1"]
         ''').strip()
     ),
+    (
+        {
+            'EMPTY_VALUE': None,
+            'SCHED_REQUIREMENTS': 'CLUSTER_ID="100"',
+            'BACKSLASH_ESCAPED': "this is escaped: \\n; this isn't: \"\nend",
+        },
+        textwrap.dedent('''
+            BACKSLASH_ESCAPED="this is escaped: \\\\n; this isn't: \\"
+            end"
+            SCHED_REQUIREMENTS="CLUSTER_ID=\\"100\\""
+        ''').strip()
+    ),
 ]