I'm trying to put together a line with the ip addresses of my cluster in a loop:
set_fact: nodelist={%for host in groups['proxmox-cluster']%}"{{hostvars[host].ansible_all_ipv4_addresses|string}}"{% if not loop.last %},{% endif %}{% endfor %}
But when I try to put this line into a file with
lineinfile:
path: "/etc/security/access.conf"
line: "+:root:{{ nodelist }}"
I get:
"[u'192.168.31.238']","[u'192.168.31.248']","[u'192.168.31.252']","[u'192.168.31.250', u'192.168.1.250', u'192.168.32.250', u'192.168.33.250']"
instead of
192.168.31.238,192.168.31.248,192.168.31.252,192.168.31.250,192.168.1.250,192.168.32.250,192.168.33.250
The reason for this is that hostvars[host].ansible_all_ipv4_addresses is an array. That is why you're printing a joined list of strings inside arrays "[u'192.168.31.238']","[u'192.168.31.248']"...
If you only want to print the default ipv4 addresses you can replace your hostvars expression with:
hostvars[host].ansible_default_ipv4.address
If you want to print the first address in the total list of addresses you can replace your hostvars expression with:
hostvars[host].ansible_all_ipv4_addresses | first
If you want to include all ip addresses from all hosts you will need two loops. This can either be done with Jinja2 template syntax, clever filter chains or both. For example replacing the entire expression with a combination of a jinja2 loop with a filter will give you:
{% for host in groups['proxmox-cluster'] %}"{{ hostvars[host].ansible_all_ipv4_addresses | join(',') }}"{% if not loop.last %},{% endif %}{% endfor %}
Personally I try to avoid mixing Jinja2 template syntax inside ansible tasks/playbooks. There is usually a cleaner way using just filter chains. In your case for example you might do something like this:
- name: Print ips in access.conf
lineinfile:
path: /etc/security/access.conf
line: "{{ groups['proxmox-cluster']
| map('extract', hostvars, 'ansible_default_ipv4')
| map(attribute='address')
| join(',') }}"
Try simpler version below
- set_fact:
nodelist: "{{ nodelist|default([]) + hostvars[item].ansible_all_ipv4_addresses }}"
loop: "{{ groups['proxmox-cluster'] }}"
Try with a filter on nodelist variable in lineinfile module:
lineinfile:
path: "/etc/security/access.conf"
line: "{{ nodelist | join(',') }}"
Related
I need to read a csv file with diferent IPs and make a dictionary with a jinja2 filter for modificate the IP depending the IPNumber value. The yml file is like:
- read_csv:
path: vms.csv
key: Number
fieldnames: Name,IP1,IP2,IP3,IP4,IPNumber
delimiter: ';'
register: vms
- name: vms to dict
debug:
msg:
- {{'Name':{{ item.value.Name }},
{% if item.value.IPNumber == "1" %}
'IP':{{ item.value.IP1 }},
{% endif %}
{% if item.value.IPNumber == "2"%}
'IP':{{ item.value.IP2 }},
{% endif %}
{% if item.value.IPNumber == "3"%}
'IP':{{ item.value.IP3 }},
{% endif %}
{% if item.value.IPNumber == "4"%}
'IP':{{ item.value.IP4 }},
{% endif %}}}
loop: "{{ vms.dict | dict2items }}"
register: vms2
But I get the error:
The error appears to be in '/etc/ansible/roles/vms.yml': line 17, column 16, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
'Name':{{ item.value.Name}},
{% if item.value.IPNumber == "1" %}
^ here
I know is a syntax problem but I dont guess where the problem is.
I need some help.
The following task should create your dictionary as per your requirement inside a var you can reuse elsewhere. Rename my_ip_dict to whatever suits your project better.
- name: Create my IP dictionary
set_fact:
my_ip_dict: >-
{{
my_ip_dict | default({})
| combine({item.value.Name: item.value['IP' + item.value.IPNumber]})
}}
loop: "{{ vms.dict | dict2items }}"
- name: Check the result:
debug:
var: my_ip_dict
Note that I have dropped all the if/else structures by calling directly the correct field depending on IPNumber. I took for granted it always has a value in the valid scope or the other existing IP* fields. If this is not the case, you can always default that value e.g. item.value['IP' + item.value.IPNumber] | default('N/A')
You should put only variables/expressions within {{ or {%. To me 'Name' looks like normal text and should be outside.
Example:
# Notice the quotes `"` symbol at the beginning and end of debug message
- debug:
msg:
- "Name: {{ item.value.Name }},
{% if item.value.IPNumber == "1" %}
IP: {{ item.value.IP1 }}
# and so on...
{% endif %}"
This at least should address the error message.
I have an ansible inventory with this format:
[servers:children]
s1
s2
s3
[s1]
10.10.0.80 hostname=s1.example.com
[s2]
10.10.1.80 hostname=s2.example.com
[s3]
jail_s3 ansible_host=10.10.2.80 ansible_port=2222 hostname=s3.example.com
I would like to get a comma-separated of all the ansible hosts belonging to the group servers, for this, I have been using:
ips = "{{ query('inventory_hostnames', 'sites') | map('ipaddr') | join(',') }}"
But the problem I have now with this inventory is that query('inventory_hostnames') don't return the IP for s3 instead returns a string jail_3 the output of the list is like:
'10.10.0.80'
'10.10.1.80'
'jail_s3'
I found that I could use a loop and filter the output something like this:
debug:
msg: "{{ item if (item| ipaddr) else hostvars[item].ansible_host }}"
loop: "{{ query('inventory_hostnames','servers') }}"
Or in a template within a loop:
{% for host in query('inventory_hostnames', 'servers') %}
{{ hostvars[host].ansible_host|default(hostvars[host].inventory_hostname) }}
{% endfor %}
In this case, the filter ipaddr returns false for jail_s3 and then I fall back to hostvars[item].ansible_host.
This works but I would like to know if it could be done in "one-liner" probably by using a map, or something that could take the list of query('inventory_hostnames','servers') | ???
Any ideas?
I would like to filter hosts by a variable set on them. For example:
I have a group of hosts, one is master, the rest are slaves. On the master the variable replica_type: master is set. I would now like to get the master server dynamically.
My working aproach is:
- set_fact:
master_server_string: >-
{% for server in groups.my_servers %}
{% if hostvars[server]['replica_type']=='master' -%}
{{ server }}
{% endif %}
{% endfor %}
- set_fact:
master_server: "{{ master_server_string|trim }}"
Is there a way to perform the same with a filter (or at least in a single task)?
e.g. something like
"{{ server for server in groups.my_servers if hostvars[server]['replica_type'] == 'master' }}"
I believe you are looking for the group_by module.
https://docs.ansible.com/ansible/latest/modules/group_by_module.html
- group_by:
key: {{ replica_type }}
You can pull a value out of a group of hosts by a property by looping the group and checking for a matching value like this too:
- set_fact:
master_server: "{{ item }}"
with_items: "{{ groups.my_servers }}"
when: hostvars[item].replica_type == 'master'
I have the following and keep in mind I do not know how many ips will be in this incoming variable but I am starting with 2 for simplicity.
vars:
host_ips: ['10.0.0.100', '10.0.0.200']
I'm trying to format them in a file using a template with Ansible.
- targets: ['10.0.0.100:9090', '10.0.0.200:9090']
What syntax in Jinja2 do I use to make the host ips look like the above targets line? I KNOW I have to iterate for sure.
-targets: [{% for ip in host_ips %}'{{ ip }}:5051'{{ "," if not loop.last else "" }} {% endfor %}]
-targets: [{% for ip in host_ips %}'{{ ip }}:5051',{% endfor %}]
test.yml playbook:
vars:
host_ips: ['10.0.0.100', '10.0.0.200','10.0.0.300']
tasks:
- debug: msg=[{% for ip in host_ips %}'{{ ip }}:5051',{% endfor %}]
ansible-playbook -i localhost test.yml
TASK [debug] *******************************************************************************************
ok: [localhost] => {
"msg": [
"10.0.0.100:5051",
"10.0.0.200:5051",
"10.0.0.300:5051"
]
}
There is no need to struggle with Jinja2 loops here. All you need is to apply a transformation for the list elements (for example with map and regex_replace filters):
host_ips | map('regex_replace', '(.*)', '\\1:9090')
With the above construct you can:
use it to set a new variable in Ansible:
- set_fact:
targets: "{{ host_ips | map('regex_replace', '(.*)', '\\1:9090') | list }}"
or "format them in a file using a template" which per your request is a JSON representation of a list:
With double quotes in the output:
- targets: {{ host_ips | map('regex_replace', '(.*)', '\\1:9090') | list | to_json }}
procudes:
- targets: ["10.0.0.100:9090", "10.0.0.200:9090"]
If you really need single quotes in the output simply replace them:
- targets: {{ host_ips | map('regex_replace', '(.*)', '\\1:9090') | list | to_json | regex_replace('"', '\'') }}
produces:
- targets: ['10.0.0.100:9090', '10.0.0.200:9090']
I merged two lists from an Ansible inventory:
set_fact:
fact1: "{{ groups['group1'] + groups[group2']|list }}
The output is:
fact1:
- server01
- server02
- server03
With the above results, I need to append https:// to the front, and a port number to the back of each element.
Then I need to convert it to a comma delimited list for a server config.
In this example I want: https://server01:8000,https://server02:8000,https://server03:8000.
I tried using a join:
set_fact:
fact2: "{{ fact1|join(':8000,') }}"
which partly worked but it left the last server without a port.
How can I achieve my goal?
Solution
set_fact:
fact2: "{{ fact1 | map('regex_replace', '(.*)', 'https://\\1:8000') | join(',') }}"
Explanation
map filter applies a filter (regex_replace) to individual elements of the list;
regex_replace filter (with the following regular expression) adds a prefix and suffix to a string;
current_list | map('regex_replace', '(.*)', 'prefix\\1suffix')
join filter converts the list to comma-delimited string in the output.
Alternative
Another possible solution (builds on what you already know) would be to use Jinja2 to directly for the target string:
set_fact:
fact2: "{{ 'https://' + fact1|join(':8000,https://') + ':8000' }}"
Also you can use ternary filter:
set_fact:
fact2: "{% for it in fact1 %}https://{{ it }}:8000{{ loop.last | ternary('', ',') }}{% endfor %}"
i have this list of inventory i want to do like this
['servers']
server01
server02
server03
server04
in this example I want:
https://server01:8000;https://server02:8000],https://server03:8000;https://server04:8000]