This is a follow up to this question.
I have a play that provisions a bunch of EC2 instances. By necessity, the hosts
value is localhost
(because when it's run there are no hosts), and the play assembles a new host list called ec2hosts
, and generates a host ip to hostname map because this is the first and only time that information is made available, and saves it using set_fact
. This hosts list is then the subject of a follow-up play. The problem is that I need to use the hostname_map
dict created during the provisioning play in the second play, and I can't see how to do this.
Here's the first play:
- hosts: localhost
connection: local
gather_facts: False
tasks:
- name: Provision a set of instances
ec2:
key_name: marcus
instance_type: t2.micro
image: "{{ ami_id }}"
wait: true
exact_count: "{{ server_count }}"
count_tag:
Tutorial: "{{ tutorial_name }}"
instance_tags:
Tutorial: "{{ tutorial_name }}"
groups: ['SSH', 'Web']
register: ec2
- name: Add all instance public IPs to host group
add_host:
hostname: "{{ item.public_ip }}"
groups: ec2hosts
loop: "{{ ec2.instances }}"
- name: Build an IP to hostname map
set_fact:
hostname_map: "{{ hostname_map | combine({item.0.public_ip: (item.1 + '.' + tutorial_domain)}) }}"
loop: "{{ ec2.instances|zip(hostnames)|list }}"
- name: Debug hostname_map
debug:
msg: "{{ hostname_map }}"
At the end of this, hostname_map
contains a map like:
{
"18.184.109.70": "host1.example.com",
"18.196.135.59": "host2.example.com"
}
From reading the ansible docs on variable scope, it says that variables defined in a play are not available outside that play unless it's being applied to the same set of hosts. That's not possible in this case, so I need to use a var with global scope, and from what I've read, set_fact
is the appropriate way to do that. So I created an empty variable in /group_vars/all
, so that the variable is accessible to all plays:
hostname_map: {}
The next play connects to each newly-created instance (using the host list we created dynamically) and sets its hostname from the inside:
- hosts: ec2hosts
gather_facts: yes
tasks:
- name: Debug hostname_map
debug:
msg: "{{ hostname_map }}"
- name: Set hostnames
hostname:
name: "{{ hostname_map[ansible_host] }}"
However, this fails because hostname_map
is empty
TASK [Debug hostname_map]
ok: [18.184.109.70] => {
"msg": {}
}
ok: [18.196.135.59] => {
"msg": {}
}
so I get this error:
fatal: [18.184.109.70]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute u'18.184.109.70'
Curiously I get the same debug output and error even when I don't define the variable globally.
Other articles I've read all suggest that the values should be enumerated explicitly in vars
, but I can't do that because the data is dynamic and unknown until runtime. Similarly, I can't provide it using extra_vars
on a command line for the same reason.
How can I make this variable available within the second play? I'd like to avoid clunky solutions like writing a local file out and then reading it back in!
I'm also open to suggestions for doing this a completely different way, where "this" is: create an arbitrary number of EC2 instances and assign them hostnames taken from a static list.