Repository: ansible/ansible

Issue:
add_host no longer preserves inventory_dir
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
add_host

##### ANSIBLE VERSION
```
ansible 2.5.0 (devel cb5f2c7ac3) last updated 2017/09/26 11:45:15 (GMT +1000)
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/will/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/will/src/ansible/lib/ansible
  executable location = /home/will/src/ansible/bin/ansible
  python version = 2.7.13 (default, Sep  5 2017, 08:53:59) [GCC 7.1.1 20170622 (Red Hat 7.1.1-3)]
```

##### CONFIGURATION
N/A

##### OS / ENVIRONMENT
N/A

##### SUMMARY
With 2.4 and beyond, ansible no longer preserves `inventory_dir` for `add_host`.

Although my problem with this is specific to `inventory_dir`, and can be worked around with adding `inventory_dir: "{{ inventory_dir }}"` to the `add_host`, it makes me wonder if other variables are missing too.

##### STEPS TO REPRODUCE
```
- hosts: localhost

  tasks:
    - add_host:
        hostname: newhost
        groups: existinggroup

- hosts: newhost
  connection: local

  tasks:
    - debug:
        var: inventory_dir
```
##### EXPECTED RESULTS
With ansible 2.3.2 I get

```
$ ansible-playbook playbook.yml -i inventory
 [WARNING]: provided hosts list is empty, only localhost is available


PLAY [localhost] *********************************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [localhost]

TASK [add_host] **********************************************************************************************************************************************
changed: [localhost]

PLAY [newhost] ***********************************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [newhost]

TASK [debug] *************************************************************************************************************************************************
ok: [newhost] => {
    "inventory_dir": "/home/will/tmp/ansible/add_host/inventory"
}

PLAY RECAP ***************************************************************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0   
newhost                    : ok=2    changed=0    unreachable=0    failed=0   
```

##### ACTUAL RESULTS
With ansible 2.4.0 onwards, up to and including current devel, I get:

```
$ ansible-playbook playbook.yml -i inventory
 [WARNING]: Unable to parse /home/will/tmp/ansible/add_host/inventory as an inventory source

 [WARNING]: No inventory was parsed, only implicit localhost is available

 [WARNING]: Could not match supplied host pattern, ignoring: all

 [WARNING]: provided hosts list is empty, only localhost is available


PLAY [localhost] *********************************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [localhost]

TASK [add_host] **********************************************************************************************************************************************
changed: [localhost]

PLAY [newhost] ***********************************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [newhost]

TASK [debug] *************************************************************************************************************************************************
ok: [newhost] => {
    "inventory_dir": null
}

PLAY RECAP ***************************************************************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0   
newhost                    : ok=2    changed=0    unreachable=0    failed=0   
```



Discussion:
not related to add_host, inventory_dir used to be a global, now it is a hostvar, as it needs to vary 'per inventory source' . I have PR to porting guide to make a  note of this.
But can't add_host inherit it from global settings? The problem is, all of the rest of the hosts have inventory_dir *except* for those added through add_host.
that is the point, it is not global anymore, if you have 2 or 4 inventory sources, which dir should i use?

`- i /path1/to/hosts.yml -i /path2/from/hosts.ini` 

currently i'm leaning to set it to the playbook path
For `add_host`, inventory_dir could be the inventory_dir of the calling task, which is how I'm working around it. 

To be honest, I might just be using inventory_dir in a weird way - for me it's simply "give me a fairly fixed anchor point within my repo" - as playbooks might be at more than one level, and roles at a different level again - so saying where other files are in the repo requires some kind of constant. If "directory I invoked ansible-playbook from" is available (which is different to playbook_dir) that might be better.
This problem also affects version 2.4. A workaround is to pass inventor_dir as a argument to add_host
```
add_host:
      groups: hostgroup
      name: hostname
      inventory_dir: '{{inventory_dir}}'
```

is their an `inventory_dirs` (or similarly named) list?
inventory_dir still exists, but each host has it's own, `ansible_inventory_sources ` (in 2.5) contains 'all current sources' so  the 'dir' can be extracted from the one you want.

Im still debating on what the default for `add_host` should be, but mostly favoring "current playbook's dir"
@bcoca, my preference is to just make the workaround the default - so that add_host gets the `inventory_dir` of the host running `add_host`.

Please do not make it the current playbook dir, that would be worse than the existing situation (at least it breaks obviously right now)
I agree wih the default inheritance approach.
I use `inventory_dir` to reference my environment specific variables in my playbacks. I cannot use group variables since if a single host is part of multiple groups the variables will be overridden by the successor group variables.

I organise my inventory and variables like below:

```
inventories
            /dev
                 /vars
            /staging
                     /vars
            /production
                        /vars
```

`inventory_dir` also helps me not passing any runtime variables. This should be maintained as it is and in dynamic inventory it should inherit from parent.
At today's meeting we decided that None is the most appropriate value here as setting it to anything else is a lie that will trip up some users (for instance, setting it to inventory_dir of the host which defines the host could end up with an unexpected inventory_dir if the user is running ansible with more than one inventory source and the play has hosts defined in different inventories.)  it's better for users to encounter an error which they need to explicitly fix than for them to silently start using the wrong value.  We are going to document the workarounds to this in the add_hosts page and link to it from the porting_guide for 2.4 as this bug's resolution.

Workaround that was discussed on IRC was to use set_fact and ```{{ lookup('env', 'PWD') }}``` to set a fact with the current working directory.  Then base paths off of that.

Workaround for people who want to have inventory_dir set to that of the defining host is to have a separate play with only a single host:
```
- hosts: SPECIFIC_HOST
  gather_facts: False
  tasks:
  - add_host:
      hostname: newhost
      groups: existinggroup
      inventory_dir: '{{ inventory_dir }}'
- hosts: all
  tasks:
  # All of your normal tasks
```
Where SPECIFIC_HOST is the name of a single host whose inventory_dir you want to clone from.

Things to watch out for:
* delegate_to inside of another play won't work because delegate_to uses the new host's connection vars but not the inventory_dir setting
* implicit localhost (if localhost is not specified in any inventory file) also has inventory_dir: None so it is not a good candidate for SPECIFIC_HOST.  (localhost can be used if you specify it inside of an inventory).
* You can simply tack inventory_dir onto an existing add_host task safely if you **know** that you won't use the playbook with multiple inventories.  Since inventories can be specified at the command line, this is not a robust solution even if you find it expedient today.

Resolving patch:
diff --git a/lib/ansible/modules/inventory/add_host.py b/lib/ansible/modules/inventory/add_host.py
--- a/lib/ansible/modules/inventory/add_host.py
+++ b/lib/ansible/modules/inventory/add_host.py
@@ -35,8 +35,10 @@
 notes:
     - This module bypasses the play host loop and only runs once for all the hosts in the play, if you need it
       to iterate use a with\_ directive.
-    - This module is also supported for Windows targets.
+    - Windows targets are supported by this module.
     - The alias 'host' of the parameter 'name' is only available on >=2.4
+    - Since Ansible version 2.4, the ``inventory_dir`` variable is now set to ``None`` instead of the 'global inventory source',
+      because you can now have multiple sources.  An example was added that shows how to partially restore the previous behaviour.
 author:
     - "Ansible Core Team"
     - "Seth Vidal"
@@ -71,4 +73,9 @@
     hostname: "{{ new_ip }}"
     ansible_host: "{{ inventory_hostname }}"
     ansible_port: "{{ new_port }}"
+
+- name: Ensure inventory vars are set to the same value as the inventory_hostname has (close to pre 2.4 behaviour)
+  add_host:
+    hostname: charlie
+    inventory_dir: "{{inventory_dir}}"
 '''


Test patch: