Repository: ansible/ansible

Issue:
`add_host` tracebacks for dynamically generated input
Edit: happening also for valid input, hence changing the issue a bit
Edit2: well, it fails for me for any input; is it completely borked?
Edit3: I don't follow, works now, but still fails on the dynamic invocation; am reverting to my first report

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
add_host

##### ANSIBLE VERSION
```
ansible 2.4.3.0
  config file = /home/tt/g/s2i-container-image-template/tests/ansible.cfg
  configured module search path = [u'/home/tt/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /bin/ansible
  python version = 2.7.14 (default, Jan 31 2018, 14:27:00) [GCC 8.0.1 20180127 (Red Hat 8.0.1-0.6)]
```

##### CONFIGURATION
```
DEFAULT_HOST_LIST(/home/tt/g/s2i-container-image-template/tests/ansible.cfg) = [u'/home/tt/g/s2i-container-image-template/tests/inventory']
RETRY_FILES_ENABLED(/home/tt/g/s2i-container-image-template/tests/ansible.cfg) = False
```

##### OS / ENVIRONMENT
Fedora 28 (rawhide)

##### SUMMARY
add_host tracebacks for dynamically generated input

##### STEPS TO REPRODUCE
vm_provision_data is populated [by a dynamic inventory script](https://pagure.io/standard-test-roles/blob/master/f/inventory/standard-inventory-qcow2)
which I executed earlier and recorded its output
```yaml
- name: prepare inventory data for add_host
  set_fact:
    inventory_data: '{{ (vm_provision_data.stdout | from_json)._meta.hostvars[vm_image] | combine({"name": "vm"}) }}'
- debug:
    msg: "{{ inventory_data }}"
- add_host: "{{ inventory_data }}"
```

##### ACTUAL RESULTS
```
TASK [prepare inventory data for add_host] ************************************************
ok: [localhost]

TASK [debug] ******************************************************************************
ok: [localhost] => {
    "msg": {
        "ansible_ssh_common_args": "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no",
        "ansible_ssh_host": "127.0.0.3", 
        "ansible_ssh_pass": "foobar", 
        "ansible_ssh_port": "2222", 
        "ansible_ssh_private_key_file": "/tmp/inventory-cloudWa7Mwu/identity", 
        "ansible_ssh_user": "root", 
        "name": "vm"
    }
}

TASK [add_host] ***************************************************************************
creating host via 'add_host': hostname=None
ERROR! Unexpected Exception, this is probably a bug: 'NoneType' object has no attribute 'split'
the full traceback was:

Traceback (most recent call last):
  File "/bin/ansible-playbook", line 106, in <module>
    exit_code = cli.run()
  File "/usr/lib/python2.7/site-packages/ansible/cli/playbook.py", line 122, in run
    results = pbex.run()
  File "/usr/lib/python2.7/site-packages/ansible/executor/playbook_executor.py", line 154, in run
    result = self._tqm.run(play=play)
  File "/usr/lib/python2.7/site-packages/ansible/executor/task_queue_manager.py", line 290, in run
    play_return = strategy.run(iterator, play_context)
  File "/usr/lib/python2.7/site-packages/ansible/plugins/strategy/linear.py", line 292, in run
    results += self._wait_on_pending_results(iterator)
  File "/usr/lib/python2.7/site-packages/ansible/plugins/strategy/__init__.py", line 620, in _wait_on_pending_results
    results = self._process_pending_results(iterator)
  File "/usr/lib/python2.7/site-packages/ansible/plugins/strategy/__init__.py", line 107, in inner
    results = func(self, iterator, one_pass=one_pass, max_passes=max_passes)
  File "/usr/lib/python2.7/site-packages/ansible/plugins/strategy/__init__.py", line 527, in _process_pending_results
    self._add_host(new_host_info, iterator)
  File "/usr/lib/python2.7/site-packages/ansible/plugins/strategy/__init__.py", line 643, in _add_host
    new_host.vars = combine_vars(new_host.get_vars(), host_info.get('host_vars', dict()))
  File "/usr/lib/python2.7/site-packages/ansible/inventory/host.py", line 149, in get_vars
    return combine_vars(self.vars, self.get_magic_vars())
  File "/usr/lib/python2.7/site-packages/ansible/inventory/host.py", line 143, in get_magic_vars
    results['inventory_hostname_short'] = self.name.split('.')[0]
AttributeError: 'NoneType' object has no attribute 'split'
```



Discussion:
Files identified in the description:
* [lib/ansible/modules/inventory/add_host.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/inventory/add_host.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 --->
This is because the module does not accept templated kwargs like ...

```
- add_host: "{{ dictionary }}"
```

instead we expect ...

```
- add_host:
     name: "{{ dictionary.key }}"
```

We need to capture the traceback and produce an AnsibleError instead that helps inform the user of the incorrect usage. We should also set add_host to require a `name` or `hostname` and error out if not given.
Should also mention that this will allow you to send in arbitrary kwargs, although we are also deprecating the feature and will throw a warning ...

```  1 - hosts: el6host
  2   connection: local
  3   gather_facts: False
  4   vars:
  5       inventory_data:
  6           ansible_ssh_host: 127.0.0.2
  7           name: testvm
  8   tasks:
  9       - add_host:
 10         args: "{{ inventory_data }}"
```

```
TASK [add_host] **********************************************************************************
task path: /home/jtanner/workspace/issues/AP-36045/site.yml:9
[DEPRECATION WARNING]: Using variables for task params is unsafe, especially if the variables 
come from an external source like facts. This feature will be removed in version 2.6. Deprecation
 warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
creating host via 'add_host': hostname=testvm
changed: [el6host] => {
    "add_host": {
        "groups": [], 
        "host_name": "testvm", 
        "host_vars": {
            "ansible_ssh_host": "127.0.0.2"
        }
    }, 
    "changed": true, 
    "failed": false
}```
Can reproduce with https://github.com/ansible/ansible/pull/36054

```

TASK [add_host : set_fact] *****************************************************
task path: /root/ansible/test/integration/targets/add_host/tasks/main.yml:20
ok: [testhost] => {
    "ansible_facts": {
        "inventory_data": {
            "ansible_host": "127.0.0.3", 
            "ansible_port": "2222", 
            "ansible_ssh_common_args": "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no", 
            "ansible_ssh_pass": "foobar", 
            "ansible_ssh_private_key_file": "/tmp/inventory-cloudj9cGz5/identity", 
            "ansible_ssh_user": "root", 
            "hostname": "newdynamichost2"
        }
    }, 
    "changed": false, 
    "failed": false
}

TASK [add_host : Show inventory_data for 36045] ********************************
task path: /root/ansible/test/integration/targets/add_host/tasks/main.yml:32
ok: [testhost] => {
    "failed": false, 
    "msg": {
        "ansible_host": "127.0.0.3", 
        "ansible_port": "2222", 
        "ansible_ssh_common_args": "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no", 
        "ansible_ssh_pass": "foobar", 
        "ansible_ssh_private_key_file": "/tmp/inventory-cloudj9cGz5/identity", 
        "ansible_ssh_user": "root", 
        "hostname": "newdynamichost2"
    }
}

TASK [add_host : Add host from dict 36045] *************************************
task path: /root/ansible/test/integration/targets/add_host/tasks/main.yml:36
creating host via 'add_host': hostname=None
ERROR! Unexpected Exception, this is probably a bug: 'NoneType' object has no attribute 'split'
the full traceback was:

Traceback (most recent call last):
  File "/root/ansible/bin/ansible-playbook", line 118, in <module>
    exit_code = cli.run()
  File "/root/ansible/lib/ansible/cli/playbook.py", line 122, in run
    results = pbex.run()
  File "/root/ansible/lib/ansible/executor/playbook_executor.py", line 159, in run
    result = self._tqm.run(play=play)
  File "/root/ansible/lib/ansible/executor/task_queue_manager.py", line 290, in run
    play_return = strategy.run(iterator, play_context)
  File "/root/ansible/lib/ansible/plugins/strategy/linear.py", line 291, in run
    results += self._wait_on_pending_results(iterator)
  File "/root/ansible/lib/ansible/plugins/strategy/__init__.py", line 680, in _wait_on_pending_results
    results = self._process_pending_results(iterator)
  File "/root/ansible/lib/ansible/plugins/strategy/__init__.py", line 117, in inner
    results = func(self, iterator, one_pass=one_pass, max_passes=max_passes)
  File "/root/ansible/lib/ansible/plugins/strategy/__init__.py", line 588, in _process_pending_results
    self._add_host(new_host_info, iterator)
  File "/root/ansible/lib/ansible/plugins/strategy/__init__.py", line 703, in _add_host
    new_host.vars = combine_vars(new_host.get_vars(), host_info.get('host_vars', dict()))
  File "/root/ansible/lib/ansible/inventory/host.py", line 149, in get_vars
    return combine_vars(self.vars, self.get_magic_vars())
  File "/root/ansible/lib/ansible/inventory/host.py", line 143, in get_magic_vars
    results['inventory_hostname_short'] = self.name.split('.')[0]
AttributeError: 'NoneType' object has no attribute 'split'
```

Resolving patch:
diff --git a/lib/ansible/plugins/action/add_host.py b/lib/ansible/plugins/action/add_host.py
--- a/lib/ansible/plugins/action/add_host.py
+++ b/lib/ansible/plugins/action/add_host.py
@@ -48,6 +48,12 @@ def run(self, tmp=None, task_vars=None):
 
         # Parse out any hostname:port patterns
         new_name = self._task.args.get('name', self._task.args.get('hostname', self._task.args.get('host', None)))
+
+        if new_name is None:
+            result['failed'] = True
+            result['msg'] = 'name or hostname arg needs to be provided'
+            return result
+
         display.vv("creating host via 'add_host': hostname=%s" % new_name)
 
         try:


Test patch: