Repository: ansible-collections/community.general

Issue:
solaris_zone: zone configuration fails with python3
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
<!--- Explain the problem briefly below -->

Type error when trying to create a Solaris Zone with the `solaris_zone` module:

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->

`solaris_zone`

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```paste below
ansible 2.10.1
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/jbronn/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/jbronn/.local/lib/python3.8/site-packages/ansible
  executable location = /home/jbronn/.local/bin/ansible-playbook
  python version = 3.8.5 (default, Jul 28 2020, 12:59:40) [GCC 9.3.0]
```

##### CONFIGURATION
N/A

##### OS / ENVIRONMENT
OmniOS CE r151034t (Illumos); target host Python is 3.7.5.

##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: 'solaris zones'
  hosts: all
  become: true
  tasks:
    - solaris_zone:
        name: z1
        state: installed
        path: /zones/z1
  vars:
    ansible_python_interpreter: '/usr/bin/python3'
```

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- Describe what you expected to happen when running the steps above -->
The zone, `z1`, should be configured and installed.

##### ACTUAL RESULTS
Running the playbook produces this traceback:

```
Traceback (most recent call last):
  File "/export/home/jbronn/.ansible/tmp/ansible-tmp-1602198687.9610054-1444903-128778670541170/AnsiballZ_solaris_zone.py", line 102, in <module>
    _ansiballz_main()
  File "/export/home/jbronn/.ansible/tmp/ansible-tmp-1602198687.9610054-1444903-128778670541170/AnsiballZ_solaris_zone.py", line 94, in _ansiballz_main
    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
  File "/export/home/jbronn/.ansible/tmp/ansible-tmp-1602198687.9610054-1444903-128778670541170/AnsiballZ_solaris_zone.py", line 40, in invoke_module
    runpy.run_module(mod_name='ansible_collections.community.general.plugins.modules.solaris_zone', init_globals=None, run_name='__main__', alter_sys=True)
  File "/usr/lib/python3.7/runpy.py", line 205, in run_module
    return _run_module_code(code, init_globals, run_name, mod_spec)
  File "/usr/lib/python3.7/runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "/usr/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/tmp/ansible_solaris_zone_payload_p87jcqod/ansible_solaris_zone_payload.zip/ansible_collections/community/general/plugins/modules/solaris_zone.py", line 486, in <module>
  File "/tmp/ansible_solaris_zone_payload_p87jcqod/ansible_solaris_zone_payload.zip/ansible_collections/community/general/plugins/modules/solaris_zone.py", line 468, in main
  File "/tmp/ansible_solaris_zone_payload_p87jcqod/ansible_solaris_zone_payload.zip/ansible_collections/community/general/plugins/modules/solaris_zone.py", line 395, in state_present
  File "/tmp/ansible_solaris_zone_payload_p87jcqod/ansible_solaris_zone_payload.zip/ansible_collections/community/general/plugins/modules/solaris_zone.py", line 202, in configure
  File "/usr/lib/python3.7/tempfile.py", line 481, in func_wrapper
    return func(*args, **kwargs)
TypeError: a bytes-like object is required, not 'str'
```


Discussion:
Files identified in the description:
* [`plugins/modules/system/solaris_zone.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/system/solaris_zone.py)

If these files are inaccurate, 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/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @MorrisA @bcoca @d-little @fishman @flynn1973 @gforster @jasperla @jpdasma @kairoaraujo @marvin-sinister @mator @molekuul @pmarkham @ramooncamacho @scathatheworm @troy2914 @wtcross @xen0l
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/plugins/modules/system/solaris_zone.py b/plugins/modules/system/solaris_zone.py
--- a/plugins/modules/system/solaris_zone.py
+++ b/plugins/modules/system/solaris_zone.py
@@ -193,7 +193,7 @@ def configure(self):
             self.module.fail_json(msg='Missing required argument: path')
 
         if not self.module.check_mode:
-            t = tempfile.NamedTemporaryFile(delete=False)
+            t = tempfile.NamedTemporaryFile(delete=False, mode='wt')
 
             if self.sparse:
                 t.write('create %s\n' % self.create_options)


Test patch:
diff --git a/tests/unit/plugins/modules/system/test_solaris_zone.py b/tests/unit/plugins/modules/system/test_solaris_zone.py
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/system/test_solaris_zone.py
@@ -0,0 +1,115 @@
+# Copyright (c) 2020 Justin Bronn <jbronn@gmail.com>
+# 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 json
+import platform
+
+import pytest
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.community.general.plugins.modules.system import (
+    solaris_zone
+)
+from ansible_collections.community.general.tests.unit.plugins.modules.utils import (
+    set_module_args,
+)
+
+
+ZONEADM = "/usr/sbin/zoneadm"
+
+
+def mocker_zone_set(mocker, rc=0, out="", err="", zone_exists=False, zone_status=None):
+    """
+    Configure common mocker object for Solaris Zone tests
+    """
+    exists = mocker.patch.object(solaris_zone.Zone, "exists")
+    exists.return_value = zone_exists
+    get_bin_path = mocker.patch.object(AnsibleModule, "get_bin_path")
+    get_bin_path.return_value = ZONEADM
+    run_command = mocker.patch.object(AnsibleModule, "run_command")
+    run_command.return_value = (rc, out, err)
+    platform_release = mocker.patch.object(platform, "release")
+    platform_release.return_value = "5.11"
+    platform_system = mocker.patch.object(platform, "system")
+    platform_system.return_value = "SunOS"
+    if zone_status is not None:
+        status = mocker.patch.object(solaris_zone.Zone, "status")
+        status.return_value = zone_status
+
+
+@pytest.fixture
+def mocked_zone_create(mocker):
+    mocker_zone_set(mocker)
+
+
+@pytest.fixture
+def mocked_zone_delete(mocker):
+    mocker_zone_set(mocker, zone_exists=True, zone_status="running")
+
+
+def test_zone_create(mocked_zone_create, capfd):
+    """
+    test zone creation
+    """
+    set_module_args(
+        {
+            "name": "z1",
+            "state": "installed",
+            "path": "/zones/z1",
+            "_ansible_check_mode": False,
+        }
+    )
+    with pytest.raises(SystemExit):
+        solaris_zone.main()
+
+    out, err = capfd.readouterr()
+    results = json.loads(out)
+    assert not results.get("failed")
+    assert results["changed"]
+
+
+def test_zone_delete(mocked_zone_delete, capfd):
+    """
+    test zone deletion
+    """
+    set_module_args(
+        {
+            "name": "z1",
+            "state": "absent",
+            "path": "/zones/z1",
+            "_ansible_check_mode": False,
+        }
+    )
+    with pytest.raises(SystemExit):
+        solaris_zone.main()
+
+    out, err = capfd.readouterr()
+    results = json.loads(out)
+    assert not results.get("failed")
+    assert results["changed"]
+
+
+def test_zone_create_invalid_names(mocked_zone_create, capfd):
+    """
+    test zone creation with invalid names
+    """
+    # 1. Invalid character ('!').
+    # 2. Zone name > 64 characters.
+    # 3. Zone name beginning with non-alphanumeric character.
+    for invalid_name in ('foo!bar', 'z' * 65, '_zone'):
+        set_module_args(
+            {
+                "name": invalid_name,
+                "state": "installed",
+                "path": "/zones/" + invalid_name,
+                "_ansible_check_mode": False,
+            }
+        )
+        with pytest.raises(SystemExit):
+            solaris_zone.main()
+
+        out, err = capfd.readouterr()
+        results = json.loads(out)
+        assert results.get("failed")