Skip to content

Instantly share code, notes, and snippets.

@rothgar rothgar/main.yml
Last active Aug 26, 2019

Embed
What would you like to do?
Generate /etc/hosts with Ansible
# Idempotent way to build a /etc/hosts file with Ansible using your Ansible hosts inventory for a source.
# Will include all hosts the playbook is run on.
# Inspired from http://xmeblog.blogspot.com/2013/06/ansible-dynamicaly-update-etchosts.html
- name: "Build hosts file"
lineinfile: dest=/etc/hosts regexp='.*{{ item }}$' line="{{ hostvars[item].ansible_default_ipv4.address }} {{item}}" state=present
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: groups['all']
@njames

This comment has been minimized.

Copy link

commented Mar 25, 2014

Nice one ... I used

hostvars[item]['ansible_ssh_host']

but that was defined in my hostfile

@tomgoto

This comment has been minimized.

Copy link

commented Jun 19, 2014

fantastic! Now I can easily and dynamically refresh entire machine's hosts.

@holms

This comment has been minimized.

Copy link

commented Sep 22, 2014

@rothgar https://github.com/holms/ansible-fqdn I've made role with help of this gist :) finally descent fqdn role..

@rajneeshl3

This comment has been minimized.

Copy link

commented Dec 1, 2014

I have 2 inventory files - /etc/ansible/production and /etc/ansible/staging. And hence no “/etc/ansible/hosts” file.

I have a task for setting up /etc/hosts file on the nodes as -

  • name: Create hosts file

    template: src=staging_hosts.j2 dest=/etc/hosts backup=yes

    tags: hosts

I want to modify this task so that src=staging_hosts.j2 if I’m running the ansible-playbook for the inventory file named “staging”. But I want src=production_hosts.j2 when I’m running the command against inventory file named “production” (example - ansible-playbook -i production -t hosts).

staging_hosts.j2 is a simple file -

10.x.x.x app1.abc.com

10.x.x.x web1.abc.com

Basically, I don’t want production hosts to be listed inside /etc/hosts of staging servers. Any ideas?

@rmetzler

This comment has been minimized.

Copy link

commented Jan 31, 2015

@rajneeshl3 isn't this what groups are for? You check each server, which group the server belongs to and add all other group members.

@MaxiReglisse

This comment has been minimized.

Copy link

commented Jul 28, 2015

play_hosts is available as a list of hostnames that are in scope for the current play. so it becomes:

lineinfile: dest=/etc/hosts regexp='.*{{ item }}$' line="{{ hostvars[item].ansible_default_ipv4.address }} {{ item }}" state=present
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: play_hosts
@skinowski

This comment has been minimized.

Copy link

commented Feb 3, 2016

Regex looks faulty.

It'll accidentally replace substring matches. (eg hostnames: test, tes, te, t, tester)
It will also remove IPV6 entries for those hosts.

For example, main4 or main6 hostnames will destroy localhost entries if your /etc/hosts has this:

127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6

@goldyfruit

This comment has been minimized.

Copy link

commented Feb 10, 2016

This how I generate my /etc/hosts file:


---
# tasks/hosts.yml
- name: Generate /etc/hosts file
  template:
    src=etc/hosts.j2
    dest=/etc/hosts
# {{ ansible_managed }}
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

{% for item in play_hosts %}
{% set short_name = item.split('.') %}
{{ hostvars[item]['ansible_host'] }}  {{ item }} {{ short_name[0] }}
{% endfor %}

Hope it helps.

@v1k0d3n

This comment has been minimized.

Copy link

commented Mar 20, 2016

i use something very similar (found this in a google search). if users are new to Ansible, they'll need to make sure that fact checking isn't turned off in a site.yml or none of this will work. some playbook groupings turn off fact checking for a little edge on speed. i ran into this issue when one of my teammates copied playbooks from one project to another, and didn't realize why the host generation tasks were "skipping".

@jhcook

This comment has been minimized.

Copy link

commented Jul 4, 2016

@goldyfruit brilliant! Copy/paste ta.

@janhh

This comment has been minimized.

Copy link

commented Jul 5, 2016

Used @goldyfruit great answer as a starting point. I got an IO error when trying to override /etc/hosts directly, so I write it to a separate file before overriding /etc/hosts (not sure what's up with that). Ansible v. 2.1 and docker.

sometask/templates/etc/hosts.j2

# {{ ansible_managed }}
127.0.0.1   localhost
::1         localhost ip6-localhost ip6-loopback

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

# Network nodes as generated through Ansible.
{% for host in play_hosts %}
{% if 'ansible_eth0' in hostvars[host] %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}  {{ host }}
{% endif %}
{% endfor %}

sometask/tasks/configure_etc_hosts.yml


---
- name: "generate /etc/hosts.ansible file"
  template: "src=etc/hosts.j2 dest='/etc/hosts.ansible' owner=root group=root mode=0644"
  tags: etc_hosts
  become: true

- name: "check if debian generated hosts file has a backup"
  stat: "path=/etc/hosts.debian"
  tags: etc_hosts
  register: etc_hosts_debian

- name: "backup debian generated /etc/hosts"
  command: "cp /etc/hosts /etc/hosts.debian"
  when: etc_hosts_debian.stat.islnk is not defined
  tags: etc_hosts
  become: true

- name: "install /etc/hosts.ansible file"
  command: "cp /etc/hosts.ansible /etc/hosts"
  tags: etc_hosts
  become: true
@bbaassssiiee

This comment has been minimized.

Copy link

commented Sep 27, 2016

"the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'dict object' has no attribute 'ansible_default_ipv4'

@alexsavio

This comment has been minimized.

Copy link

commented Jun 22, 2017

- name: test | Build hosts file
  lineinfile:
    dest: /etc/hosts
    regexp: '.*{{ item }}$'
    line: '{{ hostvars[item].ip_address }} {{item}}'
    state: present
  with_items: '{{ groups["kafka_hosts"] }}'
  become: yes
  become_method: sudo
@kirillrst

This comment has been minimized.

Copy link

commented Jul 2, 2017

It works (Ansible 2.3.0.0):

  - name: Build hosts file
    lineinfile:
      dest: /etc/hosts
      regexp: '.*{{ item }}$'
      line: '{{ hostvars[item].ansible_default_ipv4.address }} {{item}}'
      state: present
    with_items: '{{ groups["all"] }}'

But what about "127.0.1.1"? It's absent.

@lotux

This comment has been minimized.

Copy link

commented Sep 25, 2017

you can fix the issue of removing/replacing 127.0.0.1 as following

- name: update host file
  lineinfile:
    dest: /etc/hosts
    regexp: '{{ hostvars[item].ansible_default_ipv4.address }}.*{{ item }}$'
    line: "{{ hostvars[item].ansible_default_ipv4.address }} {{item}}"
    state: present
  become: yes
  with_items: "{{ groups.all }}"
@tuxillo

This comment has been minimized.

Copy link

commented Jan 27, 2018

Hi,

I've kind of mixed all info shared here plus some bits from a bunch of Google searches.

First you need a playbook that includes all hosts because the 'ansible_play_batch' variablle has the list of hostnames that apply to the current play. I have a 'common' role like in the 'Best practices' document from Ansible.

---
# Main site file - allhosts.yml

- hosts: all
  roles:
    - common

The hosts file task:

- name: update /etc/hosts file
  blockinfile:
    dest: /etc/hosts
    content: "{{ lookup('template', 'templates/etc/hosts.j2') }}"
    state: present

And the Jinja2 template used in the task:

{% for item in ansible_play_batch %}
{% set short_name = item.split('.') %}
{{ hostvars[item].ansible_default_ipv4.address }}       {{ item }}      {{ short_name[0] }}
{% endfor %}

By using 'blockinfile' module you don't override the already existing entries in your hosts file. The calculated entries are added in a block at the end.

@rhinoceros

This comment has been minimized.

Copy link

commented Feb 11, 2018

@tuxillo
+1
I used your way to configure /etc/hosts
thanks!

inventory file

[servers]
master ansible_ssh_host=172.18.23.69
node1 ansible_ssh_host=172.18.23.70
node2 ansible_ssh_host=172.18.23.71
node3 ansible_ssh_host=172.18.23.72

templates/etc/hosts.j2

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

playbook task

- hosts: servers
  gather_facts: False
  tasks:
  - name: update /etc/hosts file
    become: true
    blockinfile:
      dest: /etc/hosts
      content: "{{ lookup('template', 'templates/etc/hosts.j2') }}"
      state: present
@chuckwm

This comment has been minimized.

Copy link

commented Feb 22, 2018

Thanks for the templates/etc/hosts.j2 stuff, works nicely. What I can't get it to work is setting string padding using format. So basically 12 chars for IP, 10 chars for shortname, etc. Supposedly possible with:

{{ "%-12s%10" | format("192.168.244.244", "my-short-name" ) }}

But not:

{{ "%-12s%10" | format({{ hostvars[item]['ansible_host'] }}, {{ item }} ) }}

do I need something like str() in front or something? Sorry for the silly question. I just like things to line up.

@zswanson

This comment has been minimized.

Copy link

commented Mar 2, 2018

@rhinoceros note that as of ansible 2.0 the ansible_ssh_user, ansible_ssh_host, ansible_ssh_port variables are deprecated and you should just use 'ansible_host' etc.

@tiendungitd

This comment has been minimized.

Copy link

commented Mar 28, 2018

I got issue when using this way, try with ansible version 2.4 and 2.5, any idea?

fatal: [db-master]: FAILED! => {
    "msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'ansible_default_ipv4'\n\nThe error appears to have been in '/home/ubuntu/postgres_ha/tasks/install.yml': line 2, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n---\n  - name: Build hosts file\n    ^ here\n"
}

try to debug

`[root@origin-deployer postgres_ha]# ansible pgmaster -i inventory -m debug -a "var=ansible_default_ipv4" -b

db-master | SUCCESS => {
    "ansible_default_ipv4": "VARIABLE IS NOT DEFINED!",
    "changed": false
}
`
@haridsv

This comment has been minimized.

Copy link

commented Apr 8, 2018

@tiendungitd I got the same error using a 2.5 version of Ansible, not sure what happened to these variables. I just got around by using hostvars[item].ip instead.

@dpneumo

This comment has been minimized.

Copy link

commented Apr 25, 2018

@rhinoceros, @tiendungitd, @haridsv
Ansible requires that facts have been gathered before 'hostvars' can be used to obtain the desired data. So be careful with 'gather_facts: False'.

Ansible 2.5, at least, seems to require that the 'hostvars' values be referenced like this:

{{ hostvars[item].ansible_facts.default_ipv4.address }}

Notice the 'ansible_facts' reference. It was a bit painful, but I started with only 'hostvars[item]' in the template. Then in the generated 'hosts' file I 'walked' into the resultant text to find the appropriate nested fields. There are other reference paths to the desired ip address that could be followed.

etc_hosts.yml:

---
- hosts: all
  become: yes
  become_user: root
  tasks:
    - name: update /etc/hosts file
      blockinfile:
        dest: /etc/hosts
        content: "{{ lookup('template', 'hosts.j2') }}"
        state: present

template/hosts.j2:

{% for item in ansible_play_batch %}
{%- if item != 'localhost' %}
{{ hostvars[item].ansible_facts.default_ipv4.address }}       {{ item }}
{% endif %}
{% endfor %}

To solve this I initially tried:

---
- hosts: localhost
  become: yes
  become_user: root
  tasks:
    - debug: var=inventory_hostname
    - debug: var=hostvars[inventory_hostname]

to decipher the reference path to the desired ip address, but that does not show that the 'ansible_facts' reference is required in the template.

There may be someplace that documents current references to the values available in 'hostvars' and current appropriate methods to access them. But I haven't found it yet.

@rwngallego

This comment has been minimized.

Copy link

commented Jan 26, 2019

It works in Ansible 2.7.6:

  - name: Add the inventory into /etc/hosts
    lineinfile:
      dest: /etc/hosts
      regexp: '.*{{ item }}$'
      line: "{{ hostvars[item]['ansible_default_ipv4']['address'] }} {{item}}"
      state: present
    when: hostvars[item]['ansible_facts']['default_ipv4'] is defined
    with_items:
      - "{{ groups['all'] }}"
@zx1986

This comment has been minimized.

Copy link

commented Aug 26, 2019

I got:

"msg": "The conditional check 'hostvars[item].ansible_default_ipv4.address is defined' failed. 
The error was: 
error while evaluating conditional (hostvars[item].ansible_default_ipv4.address is defined): 
u\"hostvars['groups['all']']\" is undefined\n\n

The error appears to be in '/var/lib/awx/projects/_16__openpoint_ansible/playbooks/hostname.yml': line 12, column 7, 
but may\n
be elsewhere in the file depending on the exact syntax problem.
\n\n

The offending line appears to be:
\n\n  
post_tasks:
\n    - name: Build hosts file
\n      ^ here
\n",

And I fix it with @rwngallego 's codes.


https://serverfault.com/questions/832799/ansible-add-ip-of-all-hosts-to-etc-hosts-of-all-other-hosts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.