Why does the blockinfile module only add one line to the file?

Viewed 161

I'm trying to use blockinfile module of ansible to write block text into file.

test purpose to /tmp/hosts, here is the content of blockinfile-test.yaml

---
- hosts: web
  gather_facts: false
  tasks:
  - name: Add mappings to /tmp/hosts
    blockinfile:
      path: /tmp/hosts
      create: yes
      block: |
        {{ item.ip }} {{ item.name }}
    loop:
      - { name: host1, ip: 10.180.104.100 }
      - { name: host2, ip: 10.180.104.101 }

This is inventory file

[myself]
ansible-main.local

[web]
10.180.104.101

Then ran command to apply

ansible-playbook -i hosts  blockinfile-test.yaml

TASK [Add mappings to /tmp/hosts] *****************************
changed: [10.180.104.101] => (item={'name': 'host1', 'ip': '10.180.104.100'})
changed: [10.180.104.101] => (item={'name': 'host2', 'ip': '10.180.104.101'})

PLAY RECAP ********************
10.180.104.101 : ok=1  changed=1 unreachable=0 failed=0 skipped=0 rescued=0  ignored=0

Then, I checked that effort but got only one line was appended in file /tmp/hosts

# BEGIN ANSIBLE MANAGED BLOCK
10.180.104.101 host2
# END ANSIBLE MANAGED BLOCK

Why is only one line written to the file?

Is there missing something to blockinfile?

1 Answers

Create unique marker of the blocks. Otherwise, the blocks will overwrite each other, e.g.

  - name: Add mappings to /tmp/hosts
    blockinfile:
      marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.name }}"
      path: /tmp/hosts
      create: true
      block: |
        {{ item.ip }} {{ item.name }}
    loop: "{{ data }}"
    vars:
      data:
        - {name: host1, ip: 10.180.104.100}
        - {name: host2, ip: 10.180.104.101}

will create the file

shell> cat /tmp/hosts 
# BEGIN ANSIBLE MANAGED BLOCK host1
10.180.104.100 host1
# END ANSIBLE MANAGED BLOCK host1
# BEGIN ANSIBLE MANAGED BLOCK host2
10.180.104.101 host2
# END ANSIBLE MANAGED BLOCK host2

A next option would be iterating the list in the block, e.g.

  - name: Add mappings to /tmp/hosts
    blockinfile:
      marker: "# {mark} ANSIBLE MANAGED BLOCK data"
      path: /tmp/hosts
      create: true
      block: |
        {% for item in data %}
        {{ item.ip }} {{ item.name }}
        {% endfor %}
    vars:
      data:
        - {name: host1, ip: 10.180.104.100}
        - {name: host2, ip: 10.180.104.101}

will create the file with a single block. This would make the file easier to read

shell> cat /tmp/hosts 
# BEGIN ANSIBLE MANAGED BLOCK data
10.180.104.100 host1
10.180.104.101 host2
# END ANSIBLE MANAGED BLOCK data

The disadvantage is that you'll have to update the whole block if anything changes. In the first case, you'll be able to update single hosts.


You can use lineinfile to update the blocks, e.g.

  - name: Update mappings in /tmp/hosts
    lineinfile:
      path: /tmp/hosts
      create: true
      regexp: '^\s*(.*?)\s+{{ item.name }}\s*$'
      line: "{{ item.ip }} {{ item.name }}"
    loop: "{{ data }}"
    vars:
      data:
        - {name: host1, ip: 10.180.104.100}
        - {name: host2, ip: 10.180.104.101}

is idempotent

TASK [Update mappings in /tmp/hosts] ******************************
ok: [localhost] => (item={'name': 'host1', 'ip': '10.180.104.100'})
ok: [localhost] => (item={'name': 'host2', 'ip': '10.180.104.101'})

Notes

  • See hosts.yml on how to validate the data and create the file from a template

  • See example of a template

  • And example of data for this template.

Related