ansible jinja template hostvars can not get ansible_host - ansible

I am trying to iterate over hostvars in an ansible playbook and it appears to be working just fine to several hostvars, except the one I need: ansible_host
If I try:
{% for host in groups['all'] %}
server {{ host }} {{ hostvars[host] }}
{% endfor %}
It outputs this for each server:
server server2 {'ansible_verbosity': 0,, '... 'ansible_check_mode': False, 'ansible_run_tags': [u'all'], 'ansible_skip_tags': [], u'ansible_host': u'10.192.11.17', 'ansible_version': {'major': 2, 'full': '2.9.11', 'string': '2.9.11', 'minor': 9, 'revision': 11}}
Which holds all vars.
Now If I try
{% for host in groups['all'] %}
server {{ host }} {{ hostvars[host]['verbosity'] }}
{% endfor %}
it will get me the ansible_verbosity just fine. But I can not find a way to get the ansible_host
{% for host in groups['all'] %}
server {{ host }} {{ hostvars[host]['ansible_host'] }}
{% endfor %}
I think the problem maybe related to the fact ansible_host is a unicode var ( u'ansible_host' ). I say that because the other 2 unicode vars fails with the same problem while all the others work just fine.
Any idea how to get that var?

Q: "hostvars can not get ansible_host"
A: ansible_host is not included in hostvars if it is not explicitly declared. ansible_host is a Connection variable. It is used, for example, as a parameter of the ssh connection plugin (see ansible-doc -t connection ssh). It is needed if inventory_hostname does not resolve. Very probably, any of your hosts don't have an ansible_host explicitly set. This is causing the problem. If ansible_host is not explicitly declared its value is set to inventory_hostname.
For example
shell > grep test_11 hosts
test_11
- hosts: test_11
tasks:
- debug:
var: hostvars[inventory_hostname]['ansible_host']
- debug:
var: ansible_host
gives
TASK [debug] *************************************************************
ok: [test_11] =>
hostvars[inventory_hostname]['ansible_host']: VARIABLE IS NOT DEFINED!
TASK [debug] *************************************************************
ok: [test_11] =>
ansible_host: test_11
If you declare ansible_host, for example, in the inventory
shell > grep test_11 hosts
test_11 ansible_host=10.1.0.61
the same playbook gives
TASK [debug] *************************************************************
ok: [test_11] =>
hostvars[inventory_hostname]['ansible_host']: 10.1.0.61
TASK [debug] *************************************************************
ok: [test_11] =>
ansible_host: 10.1.0.61
See Inventory aliases
Quoting Connection variables
ansible_host: The ip/name of the target host to use instead of inventory_hostname.
See also What's the difference between inventory_hostname and ansible_hostname
A quick solution to the problem might be defaulting to inventory_hostname, assuming ansible_host is not needed, i.e. inventory_hostname resolves. For example
{% for host in groups['all'] %}
server {{ host }} {{ hostvars[host]['ansible_host']|default(host) }}
{% endfor %}

Related

Does Ansible Jinja2 Templating support list arguments in for loop?

I'm trying to dynamically create a template from a list and I'm wondering if Ansible supports something like
{% for server in [sg-bend1, sg-bend2] %}
check program {{ server }}_test
with path /home/ubuntu/t.sh {{ server }}
if status != 0 then alert
{% endfor %}
theoretically this should produce
check program sg-bend1_test
with path /home/ubuntu/t.sh sg-bend1
if status != 0 then alert
check program sg-bend2_test
with path /home/ubuntu/t.sh sg-bend2
if status != 0 then alert
According the provided description I understand your question is related to Jinja2 Templating and syntax only.
One approach you could try is the following
{% for i in range(1,3) %}
check program sg-bend{{ i }}_test
with path /home/ubuntu/t.sh sg-bend{{ i }}
if status != 0 then alert
{% endfor %}
Similar Q&A
How to make a for loop in Jinja?
... table with Jinja2 and Python
How can I test Jinja2 templates in Ansible?
Documentation
Jinja2 Template Designer Documentation - List of Control Structures
As far as I understand the documentation the solution should be to provide the list in a variable
{% for SERVER in SERVERS %}`
or an other syntax
{% for SERVER in ('test1', 'test2') %}
Example
---
- hosts: localhost
become: false
gather_facts: false
vars:
SERVERS: ['test1', 'test2']
tasks:
- name: Show result
debug:
msg: "{% for SERVER in SERVERS %}{{ SERVER }}{% endfor %}"
- name: Show result
debug:
msg: "{% for SERVER in ('test1', 'test2') %}{{ SERVER }}{% endfor %}"
will result into an output of
TASK [Show result] ******
ok: [localhost] =>
msg: test1test2
TASK [Show result] ******
ok: [localhost] =>
msg: test1test2

Is there any way to loop through Ansible list of dictionary registered variable in combination with Jinja2?

In my inventory I have 3 servers in a group. I want to be able to increase that size in the future so I can add more nodes into the network and generate the template with Jinja2.
- name: Gathering API results
shell:
cmd: "curl {{ groups['nodes'][node_index] }}/whatever/api/result "
loop: "{{ groups['nodes'] }}"
loop_control:
index_var: node_index
register: api_value
If I run some debug tasks hardcoding which list I want to use everyhing works fine
- debug: "msg={{ api_value.results.0.stdout }}"
- debug: "msg={{ api_value.results.1.stdout }}"
- debug: "msg={{ api_value.results.2.stdout }}"
output:
ok: [server-1] => {
"msg": "random-value-a"
ok: [server-2] => {
"msg": "random-value-b"
ok: [server-3] => {
"msg": "random-value-c"
The problem is when I try to increase the list number in Jinja template. I tried several for loops combination, nested for loops and many other things but nothing seems to be working.
For example I want my Jinja template look similar like this:
{% for vm in groups['nodes'] %}
NODE_{{ loop.index }}={{ api_value.results.{loop.index}.stdout }}
{% endfor %}
This way I want to achieve this output:
NODE_0=random-value-a
NODE_1=random-value-b
NODE_2=random-value-c
Is there any other way to workaround this? Or maybe is something I could do better in the "Gathering API results" task?
Given the inventory
shell> cat hosts
[nodes]
server-1
server-2
server-3
Either run it in the loop at a single host, e.g.
- hosts: localhost
gather_facts: false
vars:
whatever_api_result:
server-1: random-value-a
server-2: random-value-b
server-3: random-value-c
tasks:
- command: "echo {{ whatever_api_result[item] }}"
register: api_value
loop: "{{ groups.nodes }}"
- debug:
msg: "{{ api_value.results|json_query('[].[item, stdout]') }}"
gives
msg:
- - server-1
- random-value-a
- - server-2
- random-value-b
- - server-3
- random-value-c
Then, in the Jinja template, fix the index variable
- debug:
msg: |-
{% for vm in groups.nodes %}
NODE_{{ loop.index0 }}={{ api_value.results[loop.index0].stdout }}
{% endfor %}
gives what you want
msg: |-
NODE_0=random-value-a
NODE_1=random-value-b
NODE_2=random-value-c
Optionally, iterate api_value.results. This gives the same result
- debug:
msg: |-
{% for v in api_value.results %}
NODE_{{ loop.index0 }}={{ v.stdout }}
{% endfor %}
Or run it in the group, e.g.
- hosts: nodes
gather_facts: false
vars:
whatever_api_result:
server-1: random-value-a
server-2: random-value-b
server-3: random-value-c
tasks:
- command: "echo {{ whatever_api_result[inventory_hostname] }}"
register: api_value
delegate_to: localhost
- debug:
msg: "{{ api_value.stdout }}"
(delegate to localhost for testing)
gives
ok: [server-1] =>
msg: random-value-a
ok: [server-2] =>
msg: random-value-b
ok: [server-3] =>
msg: random-value-c
Then, in the Jinja template, use hostvars
- debug:
msg: |-
{% for vm in groups.nodes %}
NODE_{{ loop.index0 }}={{ hostvars[vm].api_value.stdout }}
{% endfor %}
run_once: true
gives also what you want
msg: |-
NODE_0=random-value-a
NODE_1=random-value-b
NODE_2=random-value-c
Optionally, iterate hostvars. This gives the same result
- debug:
msg: |-
{% for k,v in hostvars.items() %}
NODE_{{ loop.index0 }}={{ v.api_value.stdout }}
{% endfor %}
run_once: true

if else syntax in ansible playbook

I am new to Ansible and trying to understand what is wrong with my syntax.
My goal is that only one of the roles will be selected. I do not want to use 'when'.
Here is what I wrote (I am using Ansible v2.9.5):
- name: Install external DB for Cloudera Manager Server
hosts: db_server
roles:
- {{% if (databases_type == "postgresql") %} role: postgresql {% else %} {% endif %}
{% if (databases_type == "mysql") %} role: mariadb {% else %} {% endif %}
{% if (databases_type == "oracle") %} role: oracledb}
When I run the playbook I get a syntax error but it is not clear enough.
Thanks in advance.
A simple dictionary might be a cleaner option. For example
shell> cat playbook.yml
- name: Install external DB for Cloudera Manager Server
hosts: db_server
vars:
my_roles:
postgresql: postgresql
mysql: mariadb
oracle: oracledb
tasks:
- include_role:
name: "{{ my_roles[databases_type] }}"
Example
Let's create the roles
shell> cat roles/postgresql/tasks/main.yml
- debug:
var: role_name
shell> cat roles/mariadb/tasks/main.yml
- debug:
var: role_name
shell> cat roles/oracledb/tasks/main.yml
- debug:
var: role_name
Next, let's create an inventory with three servers, group_vars with default databases_type and host_vars with the variables for two hosts test_01 and test_02. The third host test_03 will use the variables from group_vars.
shell> cat hosts
[db_server]
test_01
test_02
test_03
shell> cat group_vars/db_server
databases_type: mysql
shell> cat host_vars/test_01
databases_type: postgresql
shell> cat host_vars/test_02
databases_type: oracle
Then the playbook gives (abridged)
shell> ansible-playbook -i hosts playbook.yml
PLAY [Install external DB for Cloudera Manager Server] *****************
TASK [include_role : {{ my_roles[databases_type] }}] *******************
TASK [postgresql : debug] **********************************************
ok: [test_01] =>
role_name: postgresql
TASK [oracledb : debug] ************************************************
ok: [test_02] =>
role_name: oracledb
TASK [mariadb : debug] *************************************************
ok: [test_03] =>
role_name: mariadb
I believe that this is what you are looking for. In the below example, fruit is the variable name. If fruit is equal to Apple then I like it else I do not like it. Let me know if you face any issue or you need more explanation on this.
If else syntax:
- name: "[ If Else Example ]"
command: "echo {{ 'I like it' if fruit == 'Apple' else 'I do not like it'}}"
register: if_reg
- debug:
msg: "{{ if_reg.stdout }}"
It seems you have double curly bracket:
- {{% if ...
Try removing it to make single curly bracket:
e.g.
- {% if ...
also last line could remove ending curly bracket and put endif:
{% if (databases_type == "oracle") %} role: oracledb {% endif %}

Ansible template with list of hosts excluding current

I'm super fresh to ansible and creating a playbook that in one of the tasks should copy templated file and replace values in 2 lines. First line should have current hostname, and in second semicolon separated list of all other hosts (used in the play) - it will be different group
First line is super easy, as it's just:
localnode={{ inventory_hostname }}
but I'm having problem with exclusion in the second line. I'd like something similar to:
{% for host in groups.nodes -%} # but without inventory_hostname
othernodes={{ host }}{% if not loop.last %};{% endif %}
{%- endfor %}
Given the inventory of:
nodes:
hosts:
hosta:
hostb:
hostc:
hostd:
I'd like to get following output (example for hostd):
localnode=hostd
othernodes=hosta,hostb,hostc
I'll be very grateful for all hints on possible solution
Create the list of hosts without inventory_hostname and use it in the template
- set_fact:
list_other_hosts: "{{ groups.nodes|difference([inventory_hostname]) }}"
Simplify the template
othernodes={{ list_other_hosts|join(';') }}
As an example, the inventory
shell> cat hosts
test_jails:
hosts:
test_01:
test_02:
test_03:
and the play
- hosts: test_jails
tasks:
- set_fact:
list_other_hosts: "{{ groups.test_jails|
difference([inventory_hostname]) }}"
- debug:
msg: "{{ msg.split('\n') }}"
vars:
msg: |-
localnode={{ inventory_hostname }}
othernodes={{ list_other_hosts|join(';') }}
give
TASK [debug] ********************************************************
ok: [test_01] => {
"msg": [
"localnode=test_01",
"othernodes=test_02;test_03"
]
}
ok: [test_02] => {
"msg": [
"localnode=test_02",
"othernodes=test_01;test_03"
]
}
ok: [test_03] => {
"msg": [
"localnode=test_03",
"othernodes=test_01;test_02"
]
}

How to get all inventory variables for hosts in same group (except the current host)?

Suppose we have an ansible inventory like this:
[somegroup]
host1 secondaryIp=1.0.0.0
host2 secondaryIp=1.0.0.1
host3 secondaryIp=1.0.0.2
While running a task (specifically template module) is there a way to get the list of secondaryIp's for "all other hosts" in the group [somegroup] ??
I tried searching ansible filters: http://docs.ansible.com/ansible/latest/playbooks_filters.html#list-filters
My only working idea was to do this in inventory:
[somegroup]
host1 secondaryIp=1.0.0.0 otherIps="1.0.0.1,1.0.0.2"
host2 secondaryIp=1.0.0.1 otherIps="1.0.0.0,1.0.0.2"
host3 secondaryIp=1.0.0.2 otherIps="1.0.0.0,1.0.0.1"
You can get a list of hosts in the group using groups['somegroup'] and access their variables using hostvars. To exclude the host ansible is currently running on you just need to check if the current host in the list equals inventory_hostname. Here's how it works in practice.
In a template
{% for host in groups['somegroup'] %}
{% if host != inventory_hostname %}
{{ hostvars[host]['secondaryIp'] }}
{% endif %}
{% endfor %}
In a task
- name: debug
debug:
msg: "{{ hostvars[item]['secondaryIp'] }}"
when: item != inventory_hostname
with_items: "{{ groups['somegroup'] }}"

Resources