Repository: ansible/ansible

Issue:
Traceback when "dst" is not provided to unarchive module
##### Issue Type:

Bug Report
##### Ansible Version:

ansible 1.5.3
##### Environment:

OpenSuSE
##### Summary:

Ansible produces stacktrace an simple syntax error (e.g. dst= instead of dest=).
##### Steps To Reproduce:

Use the following playbook:

```

---
- hosts: <...>
  tasks:
  - name: Unpack archive
    tags: unpack
    unarchive: "src=/tmp/file.tar.gz dst=/usr/local/src"
```
- file.tar.gz exists.
- ansible-playbook --syntax-check test.yml is fine.
- ansible-playbook --check test.yml fails.
##### Expected Results:

A simple hint that there is a syntax error (e.g. 'unknown parameter: dst)
##### Actual Results:

```
PLAY [...] ****************************************************

GATHERING FACTS ***************************************************************
ok: [...]

TASK: [Unpack archive] ********************************************************
fatal: [...] => Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/ansible/runner/__init__.py", line 530, in _executor
    exec_rc = self._executor_internal(host, new_stdin)
  File "/usr/lib/python2.7/site-packages/ansible/runner/__init__.py", line 621, in _executor_internal
    return self._executor_internal_inner(host, self.module_name, self.module_args, inject, port, complex_args=complex_args)
  File "/usr/lib/python2.7/site-packages/ansible/runner/__init__.py", line 807, in _executor_internal_inner
    result = handler.run(conn, tmp, module_name, module_args, inject, complex_args)
  File "/usr/lib/python2.7/site-packages/ansible/runner/action_plugins/unarchive.py", line 50, in run
    dest    = os.path.expanduser(options.get('dest', None))
  File "/usr/lib64/python2.7/posixpath.py", line 261, in expanduser
    if not path.startswith('~'):
AttributeError: 'NoneType' object has no attribute 'startswith'

FATAL: all hosts have already failed -- aborting

PLAY RECAP ********************************************************************
           to retry, use: --limit @...

...        : ok=1    changed=0    unreachable=1    failed=0
```



Discussion:
The error here should probably just indicate the dst is required.


Resolving patch:
diff --git a/lib/ansible/runner/action_plugins/unarchive.py b/lib/ansible/runner/action_plugins/unarchive.py
--- a/lib/ansible/runner/action_plugins/unarchive.py
+++ b/lib/ansible/runner/action_plugins/unarchive.py
@@ -46,15 +46,16 @@ def run(self, conn, tmp, module_name, module_args, inject, complex_args=None, **
         if complex_args:
             options.update(complex_args)
         options.update(utils.parse_kv(module_args))
-        source  = os.path.expanduser(options.get('src', None))
-        dest    = os.path.expanduser(options.get('dest', None))
+        source  = options.get('src', None)
+        dest    = options.get('dest', None)
         copy    = utils.boolean(options.get('copy', 'yes'))
 
         if source is None or dest is None:
             result = dict(failed=True, msg="src (or content) and dest are required")
             return ReturnData(conn=conn, result=result)
 
-        source = template.template(self.runner.basedir, source, inject)
+        dest = os.path.expanduser(dest)
+        source = template.template(self.runner.basedir, os.path.expanduser(source), inject)
         if copy:
             if '_original_file' in inject:
                 source = utils.path_dwim_relative(inject['_original_file'], 'files', source, self.runner.basedir)


Test patch: