I'm trying to dynamically read file content inside for loop in template. But it fails as soon as i try to use host variable inside file path.
Some background:
There're three entries in groups.docker_hosts.
All files it's trying to read exist and are readable.
Each file has content of file content xx where xx is host index
For instance this example works fine:
template.j2
{% for host in groups.docker_hosts %}
"{{lookup('file', '{{inventory_dir}}/pki/dev-docker01/test')}}"
{% endfor %}
result
"file content 01"
"file content 01"
"file content 01"
This one renders each host correctly as well
template.j2
{% for host in groups.docker_hosts %}
"{{host}}"
{% endfor %}
result:
"dev-docker01"
"dev-docker02"
"dev-docker03"
But as soon as i try using host inside file path i'm getting this error "msg": "AnsibleUndefinedVariable: 'host' is undefined"
{% for host in groups.mongo_hosts %}
"{{lookup('file', '{{inventory_dir}}/pki/{{host}}/test')}}"
{% endfor %}
Seems like it's running in new isolate context which only has access to global variables. Any idea how to actually make it see host variable correctly?
You are passing the value {{ host }} as name to the host. just use host as a variable.
{% for host in groups['webservers'] %}
"{{ lookup('file', 'test/' + host + '/foo.txt') }}"
{% endfor %}
Related
I'm using Ansible to retrieve a list of website we host over 50+ servers. I'm using a SQL query to store in a text file named as hostname_websites.txt using Ansible.
The next step is to build a prometheus (blackbox exporter) configuration file using that data.
Each line of each text file represent an URL. For the sake of representation I'll declare that the URL is $URL and the hostname is $HOSTNAME.
I need some jinja2 magic to generate a single yaml file as :
{% for site in *** each text file *** %}
- targets: ['{{ $URL }}']
labels: {'node': '{{ $HOSTNAME }}'}
{% endfor %}
Given that in the end I need to have a config line containing each time the URL which is a line in text file, and the hostname as a label which is in the txt filename.
Every hostname is in a group called production in Ansible, so I tried looping over that group using jinja2 as follow:
{% for host in group['production'] %}
{{ lookup('file', '{{ host }}_websites.txt).splitlines()' }}
{% endfor %}
That gave me some jinja2 parsing error, as if it wasn't rendering the for loop at all.
I stopped here, in the spirit that I would build by configuration skel around this statement.
There are two problems with your template that jump out immediately:
There is no variable group in Ansible (unless you've created one explicitly); you probably want groups.
You never nest Jinja {{...}} markers. If you want to generate a string containing a variable value, you can use string concatenation:
lookup('file', host ~ '_websites.txt')
Or string formatting:
lookup('file', '%s_websites.txt' % host)
Assuming that our inventory has a host node0 in the group production and that there exists a file node0_websites.txt with the following content:
http://stackoverflow.com/
http://ansible.com/
http://stackexchange.com/
Then running this playbook:
- hosts: localhost
gather_facts: false
tasks:
- copy:
dest: output.txt
content: |
{% for host in groups['production'] %}
# Host: {{ host }}
{% for url in lookup('file', host ~ '_websites.txt').splitlines() %}
- {{ url }}
{% endfor %}
{% endfor %}
Generates the following content in output.txt:
# Host: node0
- http://stackoverflow.com/
- http://ansible.com/
- http://stackexchange.com/
I have a template named foo.yml.j2 used in an Ansible task to generate a foo.yml file:
{% for host in ansible_play_hosts_all %}
{{ host }},
{% endfor %}
Everything works fine except I need something like the following statement: For every host in ansible_play_hosts_all except for host==bar do this or that.
Is this achievable or the only way to do this is to categorize my hosts in different groups and use ansible_play_hosts_group?
You can use the reject filter to take your host out of the list so that it is not part of your loop:
{% for host in ansible_play_hosts_all | reject('==', 'bar') %}
{{ host }},
{% endfor %}
There are more options. The trivial one is using the condition in Jinja
{% for host in ansible_play_hosts_all %}
{% if host != 'bar' %}
{{ host }},
{% endif %}
{% endfor %}
Fit the format to your needs.
Example of a complete playbook for testing
- hosts: host_1,host_2,host_3
gather_facts: false
tasks:
- debug:
msg: |
{% for host in ansible_play_hosts_all %}
{% if host != 'host_2' %}
{{ host }},
{% endif %}
{% endfor %}
run_once: true
gives (abridged)
msg: |-
host_1,
host_3,
The next option is to remove blacklisted hosts from the loop, e.g.
blacklist: [host_2]
The template below gives the same result
{% for host in ansible_play_hosts_all|difference(blacklist) %}
{{ host }},
{% endfor %}
I need to create a string in a template that can change between hosts, and it needs to be in the form of:
"cores": "0,1,2,3"
And the reason the string is "0,1,2,3" in this example is because the host has 4 processor cores.
So I got stuck with something which seems too convoluted to me and I'm not even sure how to use this core_count variable in my template file.
{% set core_count = '' %}
{% for i in range(ansible_processor_cores) %}
{% set core_count = core_count ~ i %}
{% if not loop.last %}
{% set core_count = core_count ~ ',' %}
{% endif %}
{% endfor %}
There are many handy lookup plugins in Ansible. Take sequence:
- hosts: localhost
gather_facts: yes
tasks:
- debug:
msg: '"cores": "{{ lookup("sequence","start=0 count="+(ansible_processor_cores|string)) }}"'
This article from Ansible.com shows how you can iterate over a group inside of a template file: https://support.ansible.com/hc/en-us/articles/201957887-How-to-loop-over-a-list-of-hosts-in-a-group-inside-of-a-template-
It shows the following code:
{% for host in groups['db_servers'] %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}
It works beautifully, but the servers I want to iterate over are defined by being in multiple groups. So imagine that I want to iterate over all of the servers that are in BOTH the db_servers and qa groups. How can I do this?
I tried to specify the intersection of the group in the same manner I do in my playbook, but that doesn't work. So, when I try:
{% for host in groups['db_servers:&qa'] %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}
I get the following error:
fatal: [54.173.247.115] => {'msg': "AnsibleUndefinedVariable: One or more undefined variables: 'dict object' has no attribute 'db_servers:&qa'", 'failed': True}
Any suggestions on how to iterate over multiple groups in a template file?
Ansible has the intersect filter. See Set Theory Filters.
{% for host in groups['db_servers'] | intersect(groups['qa']) %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}
You could wrap your loop in another for the two server groups:
{% for svrs in ['db_servers', 'qa'] %}
{% for host in groups[svrs] %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}
{% endfor %}
I am trying to render a template. I have to create a list as
host:
- '111.222.333.444'
- '555.666.777.888'
which has to be taken from another host file.
I am using something like this:
{% for host in groups['hostgroup'] %}
host: {{ host }}
{% endfor %}
What is the correct way to achieve the result?
Assuming you wish to declare a variable in your inventory called host which contains a list of ip addresses, you can try:
host: {{ groups['hostgroup'] }}
Or you could skip declaring this variable and use {{ groups['hostgroup'] }} directly wherever you plan to use {{ host }}
Look at add-quotes-join thread and this filter plugin if you want quotes.
EDIT:
Assuming you are rendering a template, using the ansible template module
host:
{% for host in groups['hostgroup'] %}
- '{{ host }}'
{% endfor %}