Home > Software engineering >  Backup once when using lineinfile with loop in Ansible
Backup once when using lineinfile with loop in Ansible

Time:12-15

I'm trying to update a file with lineinfile in a loop, and it makes two backup file at once. Is there a way to not make the second one?

- name: disable kernelhints on apt install
  lineinfile:
    path: /etc/needrestart/needrestart.conf
    regexp: '^.*\$nrconf{{"{"}}{{ item }}{{"}"}}.*$'
    line: '$nrconf{{"{"}}{{ item }}{{"}"}} = 0;'
    backup: true
  loop:
    - 'kernelhints'
    - 'ucodehints'

CodePudding user response:

You can track the loop index and calculate a boolean for backup

Note that this solution is far from perfect as you won't get any backup if the first iteration did not change anything or failed.

- name: Disable kernelhints on apt install
  ansible.builtin.lineinfile:
    path: /etc/needrestart/needrestart.conf
    regexp: '^.*\$nrconf{{"{"}}{{ item }}{{"}"}}.*$'
    line: '$nrconf{{"{"}}{{ item }}{{"}"}} = 0;'
    backup: "{{ it | int == 0 }}"
  loop:
    - 'kernelhints'
    - 'ucodehints'
  loop_control:
    index_var: it

CodePudding user response:

As a workaround, you can 'preen' your backup files. For example, create a list of your files

  my_backups:
    - /tmp/test.conf

Find all backups and delete all but the first one

    - name: Find my backups
      find:
        paths: "{{ item|dirname }}"
        patterns: "{{ item|basename }}.*~"
      register: out
      loop: "{{ my_backups }}"
    - name: Keep the first backup only
      file:
        state: "{{ ansible_loop.last|ternary('file', 'absent') }}"
        path: "{{ item.1.path }}"
      with_subelements:
        - "{{ out.results }}"
        - files
      loop_control:
        label: "{{ item.1.path }}"
        extended: true

Notes

  • Example of a complete playbook for testing
- hosts: localhost
  gather_facts: false

  vars:
    my_backups:
      - /tmp/test.conf

  tasks:

    - lineinfile:
        path: /tmp/test.conf
        line: "{{ item }}"
        backup: true
      loop:
        - line2
        - line3

    - name: Find my backups
      find:
        paths: "{{ item|dirname }}"
        patterns: "{{ item|basename }}.*~"
      register: out
      loop: "{{ my_backups }}"
    - name: Keep the first backup only
      file:
        state: "{{ ansible_loop.last|ternary('file', 'absent') }}"
        path: "{{ item.1.path }}"
      with_subelements:
        - "{{ out.results }}"
        - files
      loop_control:
        label: "{{ item.1.path }}"
        extended: true
  • Example of the playbook's output
PLAY [localhost] *****************************************************************************

TASK [lineinfile] ****************************************************************************
changed: [localhost] => (item=line2)
changed: [localhost] => (item=line3)

TASK [Find my backups] ***********************************************************************
ok: [localhost] => (item=/tmp/test.conf)

TASK [Keep the first backup only] ************************************************************
changed: [localhost] => (item=/tmp/test.conf.174273.2022-12-14@08:08:01~)
changed: [localhost] => (item=/tmp/test.conf.179001.2022-12-14@08:38:36~)
changed: [localhost] => (item=/tmp/test.conf.178970.2022-12-14@08:38:36~)
ok: [localhost] => (item=/tmp/test.conf.174242.2022-12-14@08:08:01~)

PLAY RECAP ***********************************************************************************
localhost: ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  • The result will be
shell> cat /tmp/test.conf
line1
line2
line3
  • Only the first backup will be present
shell> cat /tmp/test.conf.174242.2022-12-14@08\:08\:01~ 
line1
  • The loop_control doesn't work properly in controlling the backup. For example, given the file
shell> cat /tmp/test.conf 
line1

The task below

    - lineinfile:
        path: /tmp/test.conf
        line: "{{ item }}"
        backup: "{{ ansible_loop.index == 1 }}"
      loop:
        - line2
        - line3
      loop_control:
        extended: true

works as expected

TASK [lineinfile] ****************************************************************************
changed: [localhost] => (item=line2)
changed: [localhost] => (item=line3)

, creates the file

shell> cat /tmp/test.conf 
line1
line2
line3

, and also creates the backup on the first iteration

shell> cat /tmp/test.conf.170212.2022-12-14@07\:09\:41~ 
line1

But when you are given the file

shell> cat /tmp/test.conf 
line1
line2

the task also creates the file as expected

TASK [lineinfile] ****************************************************************************
ok: [localhost] => (item=line2)
changed: [localhost] => (item=line3)
shell> cat /tmp/test.conf 
line1
line2
line3

but no backup is created because the first iteration changes nothing.

  • Related