5

So I am deploying a small 3 node cluster and I need to add the public IP addresses as defined in my inventory to all of the /etc/hosts files of all of the nodes. I am trying to use the following, but it is giving me an error:

- name: Add IP address of all hosts to all hosts
  lineinfile: 
    dest: /etc/hosts
    line: '{{ hostvars[item]["ansible_host"] }} {{ hostvars[item]["ansible_hostname"] }} {{ hostvars[item]["ansible_nodename"] }}'
    state: present
  with_items: groups['all']

The error is:

fatal: [app1.domain.com]: FAILED! => {"failed": true, "msg": "the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'ansible.vars.hostvars.HostVars object' has no attribute u\"groups['all']\"\n\nThe error appears to have been in '/Users/k/Projects/Ansible/roles/common/tasks/main.yml': line 29, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: Add IP address of all hosts to all hosts\n ^ here\n"}

I'm not sure what's going on. I'm still new to Ansible and was happy with my progress until just now. Any ideas on what I'm missing?

8

The previous answer simply does not work because it adds a new line for the same host instead of modifying the existing line when an IP Address for a host changes.

The following solution takes into account when the ip address changes for a specific server and handles it well by only modifying the line instead of adding duplicate entries.

---
- name: Add IP address of all hosts to all hosts
  lineinfile:
    dest: /etc/hosts
    regexp: '.*{{ item }}$'
    line: "{{ hostvars[item].ansible_host }} {{item}}"
    state: present
  when: hostvars[item].ansible_host is defined
  with_items: "{{ groups.all }}"
  • 1
    Hi Basil, this is a better solution. I haven't considered IP changing. Thanks – gbolo Oct 25 '18 at 14:31
13

Looks like you have errors in your syntax. Also what version of ansible are you using? The variable names may be different. On version 2.2 this works for me:

- name: Add IP address of all hosts to all hosts
  lineinfile:
    dest: /etc/hosts
    line: "{{ hostvars[item].ansible_host }} {{ hostvars[item].inventory_hostname }} {{ hostvars[item].inventory_hostname_short }}"
    state: present
  with_items: "{{ groups.all }}"

UPDATE

Basil has thought about situations when the IP changes. In that case it is better to use his suggested solution:

- name: Add IP address of all hosts to all hosts
  lineinfile:
    dest: /etc/hosts
    regexp: '.*{{ item }}$'
    line: "{{ hostvars[item].ansible_host }} {{item}}"
    state: present
  when: hostvars[item].ansible_host is defined
  with_items: "{{ groups.all }}"
  • My problem was needing quotes around the groups['all'] / groups.all section. Every example I'd found online didn't have quotes around it, but that was definitely my issue. Thanks – Kevin Feb 16 '17 at 13:22
  • yea when using variables you need double quotes (except in when clause). Out of curiosity, what version of ansible are you using? also what does your inventory file look like? – gbolo Feb 16 '17 at 14:24
  • 1
    had to add following: unsafe_writes: true otherwise got error on docker-provisioned hosts – Peter Butkovic Apr 29 '18 at 19:40
  • 1
    This answer does not take into account when the ip address changes. – Basil A Oct 15 '18 at 11:09
  • 1
    on regexp: '.*{{ item }}$', what if we have two hosts named s1 and ss1? – ahmadali shafiee Aug 6 at 6:00
1

In Ansible2.5 this is a working solution and I have used ansible_env.SSH_CONNECTION variable to get the remote server ip and ansible_host is not defined in the gathered facts.

- name: Update the /etc/hosts file with node name
      tags: etchostsupdate
      become: yes
      become_user: root
      lineinfile:
        path: "/etc/hosts"
        regexp: "{{ hostvars[item]['ansible_env'].SSH_CONNECTION.split(' ')[2] }}\t{{ hostvars[item]['ansible_hostname']}}\t{{ hostvars[item]['ansible_hostname']}}"
        line: "{{ hostvars[item]['ansible_env'].SSH_CONNECTION.split(' ')[2] }}\t{{ hostvars[item]['ansible_hostname']}}\t{{ hostvars[item]['ansible_hostname']}}"
        state: present
        backup: yes
      register: etchostsupdate
      when: ansible_hostname != "{{ item }}"
      with_items: "{{groups['app']}}"
  • why are you duplicating hostvars[item]['ansible_hostname']? – ahmadali shafiee Aug 6 at 7:55
  • also you need to remove hostvars[item]['ansible_env'].SSH_CONNECTION.split(' ')[2] from regexp section in order to handle ip changes – ahmadali shafiee Aug 6 at 7:56
1

I've combined gbolo's solution and this solution with addition to add local hostname to /etc/hosts and also make sure not to mistake hostnames like s1 and ss1 with each other

- name: add hostname to /etc/hosts
  vars:
    comment: '# added by ansible'
  lineinfile:
    dest: /etc/hosts
    regexp: "127[.]0[.]0[.]1.*"
    line: "127.0.0.1 localhost.localdomain localhost {{ ansible_hostname }} {{ comment }}"
    state: present
    backup: yes
- name: add IP address of all hosts to /etc/hosts
  vars:
    comment: '# added by ansible'
  lineinfile:
    dest: /etc/hosts
    regexp: ".* {{ item }} {{ comment }}"
    line: "{{ hostvars[item]['ansible_env'].SSH_CONNECTION.split(' ')[2] }} {{ item }} {{ comment }}"
    state: present
    backup: yes
  when: ansible_hostname != item
  loop: "{{ query('inventory_hostnames', 'all') }}"
0

I had the same issue and here is my solution for anyone who is interested.

hosts/dev.ini

[controller]
controller1 ansible_ssh_host=10.11.11.10
controller2 ansible_ssh_host=10.11.11.11
controller3 ansible_ssh_host=10.11.11.12

[compute]
compute1 ansible_ssh_host=10.11.11.13
compute2 ansible_ssh_host=10.11.11.14
compute3 ansible_ssh_host=10.11.11.15

[block]
block1 ansible_ssh_host=10.11.11.16
block2 ansible_ssh_host=10.11.11.17

[haproxy]
haproxy1 ansible_ssh_host=10.11.11.18

[nginx]
nginx1 ansible_ssh_host=10.11.11.19

[deployment]
deployment ansible_ssh_host=10.11.11.20

[all:vars]
ansible_python_interpreter=/usr/bin/python3

tasks/main.yml

---
- name: Update /etc/hosts
  become: true
  blockinfile:
      path: /etc/hosts
      create: yes
      block: |
        127.0.0.1 localhost

        # The following lines are desirable for IPv6 capable hosts
        ::1 ip6-localhost ip6-loopback
        fe00::0 ip6-localnet
        ff00::0 ip6-mcastprefix
        ff02::1 ip6-allnodes
        ff02::2 ip6-allrouters
        ff02::3 ip6-allhosts

        {% for item in ansible_play_batch %}
        {{ hostvars[item].ansible_ssh_host }}   {{ item }}    
        {% endfor %}

Notes:

  • python 3.7.5 ansible 2.9.0
  • I decided to go with blockinfile instead of using templates because hostvars context was not getting updated inside the template. Plus I was in a hurry :-).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.