Best practice would be to use host-vars. host-vars work exactly the same as group-vars but define variables per host.
So you could have a file host_vars/host_a
with the content
cert: a.my.endpoint.com.pem
...and use cert
as the variable in your task.
But your problem stays the same:
I primarily don't like it because it forces you to remember to update hosts in two places (the hostfile and the vars file).
But you as well can define host vars in your inventory. This gets ugly if you have more variables to define but if that's the only one and your goal is to manage all defintions in one place, you can do it like this:
# hostfile
[prod]
host_a cert=a.my.endpoint.com.pem
host_b cert=b.my.endpoint.com.pem
host_c cert=c.my.endpoint.com.pem
In both cases your task would reduce to this:
- name: upload haproxy server certificates
copy:
src: "{{ cert }}"
dest: "/etc/haproxy/ssl/{{ cert }}"
backup: yes
notify:
- restart haproxy
tags:
- haproxy
I'm new to ansible
I'm listing here some different options, so you can learn a bit more than just the ideal solution (Option 5)
Option 1: Use YAML anchors and references
This is completely unrelated to Ansible but since the files are in YAML format you're able to do something like this:
- name: Create real users
user: name="{{item.user_name}}" comment="{{item.user_description}}" home="/home/{{item.user_name}}" shell="/bin/bash" uid="{{item.user_id}}"
with_items: &my_items
- user_name: user1
user_description: user 1
user_id: 2000
- user_name: user2
user_description: user 2
user_id: 2001
- name: Copy SSH keys
copy:
src: "keys/{{ item.user_name }}.key"
dest: "/home/{{ item.user_name }}/.ssh/authorized_keys"
owner: "{{ item.user_name }}"
group: "{{ item.user_name }}"
mode: 0600
with_items: *my_items
Option 2: Variables in blocks
Blocks are a feature introduced in Ansible 2. You can define variables for blocks and use them in the contained tasks
- vars:
userlist:
- user_name: user1
user_description: user 1
user_id: 2000
- user_name: user2
user_description: user 2
user_id: 2001
block:
- name: Create real users
user: name="{{item.user_name}}" comment="{{item.user_description}}" home="/home/{{item.user_name}}" shell="/bin/bash" uid="{{item.user_id}}"
with_items: "{{ userlist }}"
- name: Copy SSH keys
copy:
src: "keys/{{ item.user_name }}.key"
dest: "/home/{{ item.user_name }}/.ssh/authorized_keys"
owner: "{{ item.user_name }}"
group: "{{ item.user_name }}"
mode: 0600
with_items: "{{ userlist }}"
Option 3: Apply the loop to an include
task and have your tasks in the included file
- include: other_file.yml
with_items:
- user_name: user1
user_description: user 1
user_id: 2000
- user_name: user2
user_description: user 2
user_id: 2001
In the included file you will be able to access the item and its properties, e.g. item.user_name
, just like you already had it:
- name: Create real users
user: name="{{item.user_name}}" comment="{{item.user_description}}" home="/home/{{item.user_name}}" shell="/bin/bash" uid="{{item.user_id}}"
- name: Copy SSH keys
copy:
src: "keys/{{ item.user_name }}.key"
dest: "/home/{{ item.user_name }}/.ssh/authorized_keys"
owner: "{{ item.user_name }}"
group: "{{ item.user_name }}"
mode: 0600
Option 4: set a fact containing your user list in a separate task
- set_fact:
userlist:
- user_name: user1
user_description: user 1
user_id: 2000
- user_name: user2
user_description: user 2
user_id: 2001
- name: Create real users
user: name="{{item.user_name}}" comment="{{item.user_description}}" home="/home/{{item.user_name}}" shell="/bin/bash" uid="{{item.user_id}}"
with_items: "{{ userlist }}"
- name: Copy SSH keys
copy:
src: "keys/{{ item.user_name }}.key"
dest: "/home/{{ item.user_name }}/.ssh/authorized_keys"
owner: "{{ item.user_name }}"
group: "{{ item.user_name }}"
mode: 0600
with_items: "{{ userlist }}"
Option 5: Use group_vars
group_vars probably make the most sense here. I guess your hosts are in some group in your inventory file, let's call it foo
.
Create a file group_vars/foo
relative to your playbook with the content:
userlist:
- user_name: user1
user_description: user 1
user_id: 2000
- user_name: user2
user_description: user 2
user_id: 2001
All hosts which belong to the group foo
will now automatically have access to the userlist
variable. And you can just use it in your tasks:
- name: Create real users
user: name="{{item.user_name}}" comment="{{item.user_description}}" home="/home/{{item.user_name}}" shell="/bin/bash" uid="{{item.user_id}}"
with_items: "{{ userlist }}"
- name: Copy SSH keys
copy:
src: "keys/{{ item.user_name }}.key"
dest: "/home/{{ item.user_name }}/.ssh/authorized_keys"
owner: "{{ item.user_name }}"
group: "{{ item.user_name }}"
mode: 0600
with_items: "{{ userlist }}"
If you don't have groups or don't want to limit it to certain groups, you can store the vars file as group_vars/all
where all hosts have access to.
Best Answer
(Thanks to @michael-hampton for leading to this answer.)
As described in Ansible issue#8603, the configuration parser is reading variable values and immediately attempting to render templates it encounters while defining the variables. This causes the parsing to fail when a template references a variable not yet completely defined.
A comment by ‘rquelibari’ gives a good analysis:
and explains in detail how this happens.
A subsequent comment by ‘cmpunches’ directly states the solution needed:
So, until the YAML parser in Ansible is corrected to read variable values as plain text without attempting to immediately render templates (and so postpone rendering until all variables are defined), this cross-reference in values can't yet be done in Ansible variables.