68

Let's imagine an inventory file like this:

node-01 ansible_ssh_host=192.168.100.101
node-02 ansible_ssh_host=192.168.100.102
node-03 ansible_ssh_host=192.168.100.103
node-04 ansible_ssh_host=192.168.100.104
node-05 ansible_ssh_host=192.168.100.105

[mainnodes]
node-[01:04]

In my playbook I now want to create some variables containing the IP addresses of the group mainnodes:

vars:
  main_nodes_ips: "192.168.100.101,192.168.100.102,192.168.100.103,192.168.100.104"
  main_nodes_ips_with_port: "192.168.100.101:3000,192.168.100.102:3000,192.168.100.103:3000,192.168.100.104:3000"

This is what I got so far:

vars:
  main_nodes_ips: "{{groups['mainnodes']|join(',')}}"
  main_nodes_ips_with_port: "{{groups['mainnodes']|join(':3000,')}}"

but that would use the host names instead of the IP addresses.

Any ideas how this could be done?

Update:

looking at the docs for a while, I think this would allow me to loop through all the ip adresses:

{% for host in groups['mainnodes'] %}
    {{hostvars[host]['ansible_ssh_host']}}
{% endfor %}

But I just can't figure out how to create an array that holds all these IPs. So that I can use the |join() command on them.

Update2:
I just thought I had figured it out... but it turns out that you cannot use the {% %} syntax in the playbook... or can I? Well in the vars section it didn't. :/

vars:
  {% set main_nodes_ip_arr=[] %}
  {% for host in groups['mesos-slave'] %}
     {% if main_nodes_ip_arr.insert(loop.index,hostvars[host]['ansible_ssh_host']) %} {% endif %}
  {% endfor %}
  main_nodes_ips: "{{main_nodes_ip_arr|join(',')}}"
  main_nodes_ips_with_port: "{{main_nodes_ip_arr|join(':3000,')}}"
Forivin
  • 14,780
  • 27
  • 106
  • 199
  • 7
    For anyone using version 2.0+ `ansible_ssh_host` has been deprecated. This solution still works but you need to replace it with `ansible_host` – Pudding Feb 09 '17 at 10:36
  • You wrote you are not too happy with your own anser. You have been pretty close to the best practice. Please set McKelvins as the accepted one now. https://stackoverflow.com/a/39932728/2898712 Thank you. – wedi Feb 21 '19 at 09:56

10 Answers10

99

I find the magic map extract here.

main_nodes_ips: "{{ groups['mainnodes'] | map('extract', hostvars, ['ansible_host']) | join(',') }}"
main_nodes_ips_with_port: "{{ groups['mainnodes'] | map('extract', hostvars, ['ansible_host']) | join(':3000,') }}:3000"

An alternative(idea comes from here):

main_nodes_ips: "{{ groups['mainnodes'] | map('extract', hostvars, ['ansible_eth0', 'ipv4', 'address']) | join(',') }}"

(Suppose the interface is eth0)

Jason Noble
  • 3,756
  • 19
  • 21
mckelvin
  • 3,918
  • 1
  • 29
  • 22
  • Note that the alternative only works if all servers have all the main interface set to eth0 – mperrin May 25 '18 at 09:06
  • 3
    and note also that `ansible_ssh_host` has been renamed `ansible_host` since ansible 2.0 – mperrin May 25 '18 at 09:16
  • If you need to add some suffix to each item in the result list (for example, the port number) you can use: `"{{ groups['backend_servers'] | map('extract', hostvars, ['ansible_host']) | map('regex_replace', '^(.*)', '\\1:80') | list }}"` – Stepan Kokhanovskiy Dec 01 '18 at 12:35
  • 5
    For the second part `ansible_default_ipv4` might be more reliable, since interface names have become unpredictable.... – Gert van den Berg Jun 10 '19 at 12:30
15

i came across this problem a while back and this is what i came up with (not optimal, but it works)

---
# playbook.yml
  - hosts: localhost
    connection: local

    tasks:
      - name: create deploy template
        template:
          src: iplist.txt
          dest: /tmp/iplist.txt
      - include_vars: /tmp/iplist.txt

      - debug: var=ip

and the template file is

ip:
{% for h in groups['webservers'] %}
 - {{ hostvars[h].ansible_ssh_host }}
{% endfor %}
user2599522
  • 3,005
  • 2
  • 23
  • 40
9

This do the trick for me. Not relying on the interface name

- main_nodes_ips: "{{ groups['mainnodes'] | map('extract', hostvars, ['ansible_default_ipv4', 'address']) | join(',') }}"
Scandinave
  • 1,388
  • 1
  • 17
  • 41
3
- name: Create List of nodes to be added into Cluster
  set_fact: nodelist={%for host in groups['mygroup']%}"{{hostvars[host].ansible_eth0.ipv4.address}}"{% if not loop.last %},{% endif %}{% endfor %}

 - debug: msg=[{{nodelist}}]

 - name: Set Cluster node list in config file
   lineinfile:
         path: "/etc/myfonfig.cfg"
         line: "hosts: [{{ nodelist }}]"

as results you will have the following line in config file:

hosts: ["192.168.126.38","192.168.126.39","192.168.126.40"]
ADV-IT
  • 756
  • 1
  • 8
  • 10
2

I got it to work on my own now. I'm not too happy about the solution, but it will do:

main_nodes_ips: "{% set IP_ARR=[] %}{% for host in groups['mainnodes'] %}{% if IP_ARR.insert(loop.index,hostvars[host]['ansible_ssh_host']) %}{% endif %}{% endfor %}{{IP_ARR|join(',')}}"
main_nodes_ips_with_port: "{% set IP_ARR=[] %}{% for host in groups['mainnodes'] %}{% if IP_ARR.insert(loop.index,hostvars[host]['ansible_ssh_host']) %}{% endif %}{% endfor %}{{IP_ARR|join(':3000,')}
Forivin
  • 14,780
  • 27
  • 106
  • 199
  • 8
    @McKelvin's answer should be the accepted solution, because it's far superior to this one and the recommended way by the Ansible official documentation to do it. – Strahinja Kustudic Mar 24 '17 at 14:50
2

I've done this by using ansible facts in a playbook. This playbook takes ansible_all_ipv4_addresses list and ansible_nodename (which is actually fully qualified domain name), iterates through all hosts and saves the data in localpath_to_save_ips file on your localhost. You can change localpath_to_save_ips to the absolute path on your localhost.

---
- hosts: all
  become: yes
  gather_facts: yes

  tasks:
  - name: get ip
    local_action: shell echo {{ ansible_all_ipv4_addresses }} {{ ansible_nodename }} >> localpath_to_save_ips
1

I found the "only way" to acceess other group's ip's, when any of the following is true:

  • some members are not bootstrapped by ansible yet
  • using serial
  • group is not part of playbook

Is as follows:

{% set ips=[] %}{% for host in groups['othergroup'] %}{% if ips.append(lookup('dig', host)) %}{% endif %}{% endfor %}{{ ips }}

Requires dnspython on the machine running ansible, install via

sudo apt-get install python-dnspython

If anyone knows a better way given the conditions, I'd love to get rid of this abomination.

1

this is what I did in order to not be relied on eth0 (thanks to ADV-IT's answer):

- name: gathering facts
  hosts: mainnodes
  gather_facts: true

- hosts: mainnodes
  tasks:
    - name: Create List of nodes
      set_fact: nodelist={%for host in groups['mainnodes']%}"{{hostvars[host]['ansible_env'].SSH_CONNECTION.split(' ')[2]}}"{% if not loop.last %},{% endif %}{% endfor %}

ahmadali shafiee
  • 4,350
  • 12
  • 56
  • 91
1

I ran into a similar problem getting the IP address of a node in another group. Using a construct like: the_ip: "{{ hostvars[groups['master'][0]]['ansible_default_ipv4'].address }}" works only when running the group master, which was not part of my playbook (I was running on localhost). I have overcome the problem by adding an extra play to playbook, like:

- hosts: master
  gather_facts: yes
  become: no
  vars: 
    - the_master_ip: "{{ hostvars[groups['master'][0]]['ansible_default_ipv4'].address }}"
  tasks:
  - debug: var=the_master_ip
  - set_fact: the_ip={{ the_master_ip }}

After which I can use the the_ip in the next play of the playbook.

This may also solve the abomination mentioned by @Petroldrake ?

Monger39
  • 109
  • 8
  • strangely enough I was not able to get this to work elsewhere. In the end I used a solution using `hostvars` taken from https://serverfault.com/questions/962040/sharing-ansible-variable-between-plays – Monger39 Nov 17 '21 at 10:27
0

##Just fetch Ip's using -ansible_default_ipv4.address- & redirect to a local file & then use it

  • name: gathering_facts hosts: hosts gather_facts: true tasks:

    • name: Rediret to the file shell: echo "{{ansible_default_ipv4.address}}" >>ipss.txt delegate_to: localhost
Vishal
  • 1
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 03 '22 at 08:02