For example, enable backup and register the results. As a last task create a record in the journal (see the declaration of the variables in the details under the line) and keep it in the cache
shell> cat roles/test_rollback/tasks/config.yml
- name: config_01
template:
src: test.ini.j2
dest: /tmp/test.ini
backup: true
register: config_01
- name: Write journal
set_fact:
journal: "{{ journal|d({})|combine({timestamp: journal_config_dict}) }}"
cacheable: true
vars:
timestamp: "{{ '%Y-%m-%d_%H_%M_%S'|strftime }}"
If you want to rollback the changes set the rollback_journal_record
shell> cat roles/test_rollback/rollback/config.yml
- copy:
remote_src: true
src: "{{ item.value.backup_file }}"
dest: "{{ item.value.dest }}"
loop: "{{ journal[rollback_journal_record]|dict2items|selectattr('value.changed') }}"
loop_control:
label: "{{ item.key }}"
Let's test it. Given the file on the remote host
shell> ssh admin@test_11 cat /tmp/test.ini
test = X
Run the playbook
shell> ansible-playbook pb.yml
...
TASK [test_rollback : config_01] *************************************************************
changed: [test_11]
TASK [test_rollback : Write journal] *********************************************************
ok: [test_11]
...
The file changed
shell> ssh admin@test_11 cat /tmp/test.ini
test = A
and the record was added to the journal
shell> cat /tmp/ansible_cache.json/ansible_facts_test_11
{
"journal": {
"2023-03-08_02_33_48": {
"config_01": {
"backup_file": "/tmp/test.ini.50045.2023-03-08@01:33:48~",
"changed": true,
"checksum": "6676ed8ea4caa804d3c5f0247c03ece8b7a80b0b",
"dest": "/tmp/test.ini",
"diff": [],
"failed": false,
"gid": 0,
"group": "wheel",
"md5sum": "0740a0962b43fa7bedf3dcd97e96697f",
"mode": "0644",
"owner": "root",
"size": 9,
"src": "/home/admin/.ansible/tmp/ansible-tmp-1678239225.046748-1753646-178984183659153/source",
"state": "file",
"uid": 0
}
}
}
}
See the records in the journal
shell>ansible-playbook pb.yml -t info -e debug_journal_records=true
...
TASK [test_rollback : debug] *****************************************************************
ok: [test_11] =>
msg: |-
Journal records:
- 2023-03-08_02_33_48
...
See the changed file(s) in the record
shell> ansible-playbook pb.yml -t info -e debug_journal_record=2023-03-08_02_33_48
...
TASK [test_rollback : debug] *****************************************************************
ok: [test_11] =>
msg: |-
Journal changes of 2023-03-08_02_33_48:
config_01:
backup_file: /tmp/test.ini.50045.2023-03-08@01:33:48~
changed: true
checksum: 6676ed8ea4caa804d3c5f0247c03ece8b7a80b0b
dest: /tmp/test.ini
diff: []
failed: false
gid: 0
group: wheel
md5sum: 0740a0962b43fa7bedf3dcd97e96697f
mode: '0644'
owner: root
size: 9
src: /home/admin/.ansible/tmp/ansible-tmp-1678239225.046748-1753646-178984183659153/source
state: file
uid: 0
...
Rollback the changed file(s)
shell> ansible-playbook pb.yml -t rollback -e rollback_journal_record=2023-03-08_02_33_48
...
TASK [test_rollback : copy] ******************************************************************
changed: [test_11] => (item=config_01)
...
The file was restored from the backup
shell> ssh admin@test_11 cat /tmp/test.ini
test = X
Example of a complete project for testing
shell> tree .
.
├── ansible.cfg
├── hosts
├── pb.yml
└── roles
└── test_rollback
├── defaults
│ └── main.yml
├── rollback
│ ├── config.yml
│ └── main.yml
├── tasks
│ ├── config.yml
│ ├── info.yml
│ ├── main.yml
│ └── rollback.yml
└── templates
└── test.ini.j2
6 directories, 11 files
Enable caching
shell> cat ansible.cfg
[defaults]
gathering = explicit
collections_path = $HOME/.local/lib/python3.9/site-packages/
inventory = $PWD/hosts
roles_path = $PWD/roles
ansible_managed = {file}
deprecation_warnings=true
remote_tmp = ~/.ansible/tmp
retry_files_enabled = false
stdout_callback = yaml
# fact_caching
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_cache.json
fact_caching_prefix = ansible_facts_
fact_caching_timeout = 86400
shell> cat hosts
test_11
test_12
test_13
shell> cat pb.yml
- hosts: test_11
roles:
- test_rollback
The role
shell> cat roles/test_rollback/tasks/main.yml
- import_tasks: info.yml
tags: info
- import_tasks: rollback.yml
when: rollback_journal_record is defined
tags: rollback
- import_tasks: config.yml
tags: config
shell> cat roles/test_rollback/tasks/info.yml
- debug:
msg: |
Journal records:
{{ journal.keys()|list|to_nice_yaml }}
when: debug_journal_records|d(false)|bool
- debug:
msg: |
Journal changes of {{ debug_journal_record }}:
{{ journal[debug_journal_record]|dict2items|
selectattr('value.changed')|items2dict|to_nice_yaml }}
when: debug_journal_record is defined
shell> cat roles/test_rollback/tasks/rollback.yml
- import_tasks: rollback/main.yml
- meta: end_play
shell> cat roles/test_rollback/tasks/config.yml
- name: config_01
template:
src: test.ini.j2
dest: /tmp/test.ini
backup: true
register: config_01
- name: Write journal
set_fact:
journal: "{{ journal|d({})|combine({timestamp: journal_config_dict}) }}"
cacheable: true
vars:
timestamp: "{{ '%Y-%m-%d_%H_%M_%S'|strftime }}"
shell> cat roles/test_rollback/rollback/main.yml
- debug:
msg: |
Rollback {{ rollback_journal_record }}:
{{ journal[rollback_journal_record]|dict2items|
selectattr('value.changed')|
map(attribute='value.dest')|to_nice_yaml }}
when: debug|d(false)|bool
- import_tasks: rollback/config.yml
shell> cat roles/test_rollback/rollback/config.yml
- copy:
remote_src: true
src: "{{ item.value.backup_file }}"
dest: "{{ item.value.dest }}"
loop: "{{ journal[rollback_journal_record]|dict2items|selectattr('value.changed') }}"
loop_control:
label: "{{ item.key }}"
shell> cat roles/test_rollback/defaults/main.yml
# Journal
journal_config_varnames: "{{ q('varnames', '^config_*') }}"
journal_config_vars: "{{ q('vars', *q('varnames', '^config_*')) }}"
journal_config_dict: "{{ dict(journal_config_varnames|zip(journal_config_vars)) }}"
shell> cat roles/test_rollback/templates/test.ini.j2
test = A