Skip to content

Instantly share code, notes, and snippets.

@rothgar rothgar/main.yml

Last active Jul 26, 2020
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

njames 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

tomgoto commented Jun 19, 2014

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

@holms

This comment has been minimized.

Copy link

holms 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

rajneeshl3 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

rmetzler 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

MaxiReglisse 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

skinowski 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

goldyfruit 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

v1k0d3n 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

jhcook commented Jul 4, 2016

@goldyfruit brilliant! Copy/paste ta.

@janhh

This comment has been minimized.

Copy link

janhh 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

bbaassssiiee 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

alexsavio 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

kirillrst 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

lotux 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

tuxillo 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

rhinoceros 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

chuckwm 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

zswanson 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

tiendungitd 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

haridsv 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

dpneumo 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

rwngallego 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

zx1986 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

@ilyesAj

This comment has been minimized.

Copy link

ilyesAj commented Jan 9, 2020

this works for ansible 2.2.1

  tasks:
          - name: "generate hosts file from inventory "
            copy:
              src: "{{inventory_for_bastion}}"
              dest: /etc/ansible/hosts
              mode: 766
          - name: "Build hosts file"
            lineinfile: dest=/etc/hosts regexp='.*{{ item }}$' line="{{ hostvars[item].ansible_host}} {{item}}" state=present
            with_items: "{{ groups['internal'] }}"

make sur that the group internal is defined in your inventory located in /etc/ansible/hosts and looks like that :

[internal]
terraform-controller-0   ansible_host=10.240.0.4
@smareti

This comment has been minimized.

Copy link

smareti commented Jan 24, 2020

It works in ansible 2.9.2
Screen Shot 2020-01-23 at 7 38 14 PM

@soliverprofesional

This comment has been minimized.

Copy link

soliverprofesional commented Mar 2, 2020

Hi there, sorry for refloating. I'm sharing my update just in the case helps someone else.

Based on the solutions already posted, I've got the issue where I had hosts with multiple NICs/IPs that I wanted to add to the /etc/hosts.
e.g.:

192.168.10.11 10.0.2.15 10.10.10.11       ha-1      ha-1
192.168.10.12 10.0.2.15 10.10.10.12       ha-2      ha-2

So I changed the variable {{ hostvars[item].ansible_facts.default_ipv4.address }} for {{ hostvars[item].ansible_facts.all_ipv4_addresses | join(" ") }} that is basically the list of IP address in the host gather from the ansible facts, converted to a list of strings separated by space.

Thank you guys for adding your solutions. Were extremely helpful for me.

@dywanik

This comment has been minimized.

Copy link

dywanik commented Mar 30, 2020

Hi, I was wondering if anybody was able to test this solution using Molecule?

@enr0s

This comment has been minimized.

Copy link

enr0s commented Jun 26, 2020

@dywanik After some research, I could confirm there's no way to manipulate /etc/hosts with Molecule using docker platform. The /etc/hosts file is crucial for Docker's linking system and it should only be manipulated manually at the image level, rather than the container level.
See:
https://docs.docker.com/network/links/#updating-the-etchosts-file

William-Yeh/docker-ansible#4 (comment)

@enr0s

This comment has been minimized.

Copy link

enr0s commented Jun 26, 2020

Anyway the solution proposed here works fine.
I have simply added this code in my molecule.yml

...
provisioner:
  name: ansible
  inventory:
    group_vars:
      all:
        run_not_in_container: False
...

and then modified my tasks/main.yml

- name: Generate /etc/hosts file
  template:
    src: etc/hosts.j2
    dest: /etc/hosts
    owner: root
    group: root
    mode: 0644
  when: run_not_in_container

when you test your role remember to set the variable

[all:vars]
run_not_in_container=True
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.