Issue 1:
I have quite a large number of hosts to manage and they can be categorised into different groups depending on how you work on them. So I'd like to be able to do the following which currently seems impossible:

[dc1-frontend-slaves]

f-slave-[01:10].dc1.domain.com

[dc1-backend-slaves]

b-slave-[01:10].dc1.domain.com

[dc2-frontend-slaves]

f-slave-[01:10].dc2.domain.com

[dc2-backend-slaves]

b-slave-[01:10].dc2.domain.com

[dc1-slaves]

dc1-frontend-slaves

dc1-backend-slaves

[dc2-slaves]

dc2-frontend-slaves

dc2-backend-slaves

[all-slaves]

dc1-slaves

dc2-slaves

or

[all-slaves]

dc1-frontend-slaves

dc1-backend-slaves

dc2-frontend-slaves

dc2-backend-slaves

which would have the same result.

This avoids duplication of names anywhere and also allows a more hierarchical definition.

The example may look far fetched but real-life usage, at least where I work, would allow me to simplify somewhat the rather large ansible file I'm currently using.

Issue 2:
Issue Type:

Bug Report

Ansible Version:

ansible 1.8.4

configured module search path = None

Environment:

OSX 10.10.2, using homebrew to install ansible.

Summary:

When a playlist has two plays with hosts: 127.0.0.1, facts or registered variables from the first play fall out of scope for the second play.

Steps To Reproduce:

Evaluate this playbook test.yml:

# test.yml
- hosts: 127.0.0.1
tasks:
- set_fact: hostname_test='fubar'
- add_host: name='{{ hostname_test }}'
register: resp
- debug: msg='{{ resp }}'

- hosts: 127.0.0.1
tasks:
- debug: msg='resp={{ resp|default("None") }}'
- debug: msg='hostname_test={{ hostname_test|default("None") }}'
- debug: msg='resp={{ hostvars["127.0.0.1"]["resp"]|default("None") }}'
- debug:
msg='hostname_test={{ hostvars["127.0.0.1"]["hostname_test"]
|default("None") }}'
# end
$ ansible-playbook test.yml

The ansible inventory may also be relevant:

localhost ansible_connection=local ansible_python_interpreter=/usr/local/bin/python

Expected Results:

PLAY [127.0.0.1] **************************************************************

GATHERING FACTS ***************************************************************
ok: [127.0.0.1]

TASK: [set_fact hostname_test='fubar'] ****************************************
ok: [127.0.0.1]

TASK: [add_host name='{{ hostname_test }}'] ***********************************
ok: [127.0.0.1]

TASK: [debug msg='{{ resp }}'] ************************************************
ok: [127.0.0.1] => {
"msg": "{'invocation': {'module_name': u'add_host', 'module_args': u\"name='fubar'\"}, 'new_host': u'fubar'}"
}

PLAY [127.0.0.1] **************************************************************

GATHERING FACTS ***************************************************************
ok: [127.0.0.1]

TASK: [debug msg='resp=None'] *************************************************
ok: [127.0.0.1] => {
"msg": "resp={'invocation': {'module_name': u'add_host', 'module_args': u\"name='fubar'\"}, 'new_host': u'fubar'}"
}

TASK: [debug msg='hostname_test=None'] ****************************************
ok: [127.0.0.1] => {
"msg": "hostname_test=fubar"
}

TASK: [debug msg='resp={{ hostvars["127.0.0.1"]["resp"]|default("None") }}'] ***
ok: [127.0.0.1] => {
"msg": "resp={'invocation': {'module_name': u'add_host', 'module_args': u\"name='fubar'\"}, 'new_host': u'fubar'}"
}

TASK: [debug msg='hostname_test={{ hostvars["127.0.0.1"]["hostname_test"] |default("None") }}'] ***
ok: [127.0.0.1] => {
"msg": "hostname_test=fubar"
}

PLAY RECAP ********************************************************************
127.0.0.1 : ok=4 changed=0 unreachable=0 failed=0
127.0.0.1 : ok=5 changed=0 unreachable=0 failed=0

Actual Results:

PLAY [127.0.0.1] **************************************************************

GATHERING FACTS ***************************************************************
ok: [127.0.0.1]

TASK: [set_fact hostname_test='fubar'] ****************************************
ok: [127.0.0.1]

TASK: [add_host name='{{ hostname_test }}'] ***********************************
ok: [127.0.0.1]

TASK: [debug msg='{{ resp }}'] ************************************************
ok: [127.0.0.1] => {
"msg": "{'invocation': {'module_name': u'add_host', 'module_args': u\"name='fubar'\"}, 'new_host': u'fubar'}"
}

PLAY [127.0.0.1] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [debug msg='resp=None'] *************************************************
ok: [localhost] => {
"msg": "resp=None"
}

TASK: [debug msg='hostname_test=None'] ****************************************
ok: [localhost] => {
"msg": "hostname_test=None"
}

TASK: [debug msg='resp={{ hostvars["127.0.0.1"]["resp"]|default("None") }}'] ***
ok: [localhost] => {
"msg": "resp={'invocation': {'module_name': u'add_host', 'module_args': u\"name='fubar'\"}, 'new_host': u'fubar'}"
}

TASK: [debug msg='hostname_test={{ hostvars["127.0.0.1"]["hostname_test"] |default("None") }}'] ***
ok: [localhost] => {
"msg": "hostname_test=fubar"
}

PLAY RECAP ********************************************************************
127.0.0.1 : ok=4 changed=0 unreachable=0 failed=0
localhost : ok=5 changed=0 unreachable=0 failed=0

Both plays are declared as host: 127.0.0.1 but the second play runs as if it were hosts: localhost. Possibly this is done after a reverse lookup somewhere inside ansible? Because of this the variable scope appears to change for the second play. This causes the variables resp and hostname_test to be undefined.

Possible workarounds: Use hostvars["127.0.0.1"] as in the test case, or declare hosts: localhost for both plays. However there may be similar situations where ansible quietly remaps a hostname to some other value. If the value is not easily determined, that may be difficult to work around.