<p>For example, enable <em>backup</em> and <em>register</em> the results. As a last task create a record in the <em>journal</em> (see the declaration of the variables in the details under the line) and keep it in the cache</p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; 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: &quot;{{ journal|d({})|combine({timestamp: journal_config_dict}) }}&quot;
    cacheable: true
  vars:
    timestamp: &quot;{{ '%Y-%m-%d_%H_%M_%S'|strftime }}&quot;
</code></pre>
<p>If you want to rollback the changes set the <em>rollback_journal_record</em></p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat roles/test_rollback/rollback/config.yml 
- copy:
    remote_src: true
    src: &quot;{{ item.value.backup_file }}&quot;
    dest: &quot;{{ item.value.dest }}&quot;
  loop: &quot;{{ journal[rollback_journal_record]|dict2items|selectattr('value.changed') }}&quot;
  loop_control:
    label: &quot;{{ item.key }}&quot;
</code></pre>
<p>Let's test it. Given the file on the remote host</p>
<pre class="lang-ini prettyprint-override"><code>shell&gt; ssh admin@test_11 cat /tmp/test.ini
test = X
</code></pre>
<p>Run the playbook</p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; ansible-playbook pb.yml
  ...
TASK [test_rollback : config_01] *************************************************************
changed: [test_11]

TASK [test_rollback : Write journal] *********************************************************
ok: [test_11]
  ...
</code></pre>
<p>The file changed</p>
<pre class="lang-ini prettyprint-override"><code>shell&gt; ssh admin@test_11 cat /tmp/test.ini
test = A
</code></pre>
<p>and the record was added to the journal</p>
<sup>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat /tmp/ansible_cache.json/ansible_facts_test_11 
{
    &quot;journal&quot;: {
        &quot;2023-03-08_02_33_48&quot;: {
            &quot;config_01&quot;: {
                &quot;backup_file&quot;: &quot;/tmp/test.ini.50045.2023-03-08@01:33:48~&quot;,
                &quot;changed&quot;: true,
                &quot;checksum&quot;: &quot;6676ed8ea4caa804d3c5f0247c03ece8b7a80b0b&quot;,
                &quot;dest&quot;: &quot;/tmp/test.ini&quot;,
                &quot;diff&quot;: [],
                &quot;failed&quot;: false,
                &quot;gid&quot;: 0,
                &quot;group&quot;: &quot;wheel&quot;,
                &quot;md5sum&quot;: &quot;0740a0962b43fa7bedf3dcd97e96697f&quot;,
                &quot;mode&quot;: &quot;0644&quot;,
                &quot;owner&quot;: &quot;root&quot;,
                &quot;size&quot;: 9,
                &quot;src&quot;: &quot;/home/admin/.ansible/tmp/ansible-tmp-1678239225.046748-1753646-178984183659153/source&quot;,
                &quot;state&quot;: &quot;file&quot;,
                &quot;uid&quot;: 0
            }
        }
    }
}
</code></pre>
</sup>
<p>See the records in the journal</p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt;ansible-playbook pb.yml -t info -e debug_journal_records=true
  ...
TASK [test_rollback : debug] *****************************************************************
ok: [test_11] =&gt; 
  msg: |-
    Journal records:
    - 2023-03-08_02_33_48
  ...
</code></pre>
<p>See the changed file(s) in the record</p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; ansible-playbook pb.yml -t info -e debug_journal_record=2023-03-08_02_33_48
  ...
TASK [test_rollback : debug] *****************************************************************
ok: [test_11] =&gt; 
  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
  ...
</code></pre>
<p>Rollback the changed file(s)</p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; ansible-playbook pb.yml -t rollback -e rollback_journal_record=2023-03-08_02_33_48
  ...
TASK [test_rollback : copy] ******************************************************************
changed: [test_11] =&gt; (item=config_01)
  ...
</code></pre>
<p>The file was restored from the backup</p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; ssh admin@test_11 cat /tmp/test.ini
test = X
</code></pre>
<hr>
<sup>
<p>Example of a complete project for testing</p>
<pre class="lang-bash prettyprint-override"><code>shell&gt; 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
</code></pre>
<p>Enable caching</p>
<pre class="lang-ini prettyprint-override"><code>shell&gt; 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
</code></pre>
<pre class="lang-ini prettyprint-override"><code>shell&gt; cat hosts 
test_11
test_12
test_13
</code></pre>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat pb.yml 
- hosts: test_11

  roles:
    - test_rollback
</code></pre>
<p>The role</p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; 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
</code></pre>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; 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
</code></pre>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat roles/test_rollback/tasks/rollback.yml 
- import_tasks: rollback/main.yml
  
- meta: end_play
</code></pre>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; 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: &quot;{{ journal|d({})|combine({timestamp: journal_config_dict}) }}&quot;
    cacheable: true
  vars:
    timestamp: &quot;{{ '%Y-%m-%d_%H_%M_%S'|strftime }}&quot;
</code></pre>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; 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
</code></pre>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat roles/test_rollback/rollback/config.yml 
- copy:
    remote_src: true
    src: &quot;{{ item.value.backup_file }}&quot;
    dest: &quot;{{ item.value.dest }}&quot;
  loop: &quot;{{ journal[rollback_journal_record]|dict2items|selectattr('value.changed') }}&quot;
  loop_control:
    label: &quot;{{ item.key }}&quot;
</code></pre>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat roles/test_rollback/defaults/main.yml 
# Journal
journal_config_varnames: &quot;{{ q('varnames', '^config_*') }}&quot;
journal_config_vars: &quot;{{ q('vars', *q('varnames', '^config_*')) }}&quot;
journal_config_dict: &quot;{{ dict(journal_config_varnames|zip(journal_config_vars)) }}&quot;
</code></pre>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat roles/test_rollback/templates/test.ini.j2 
test = A
</code></pre>
</sup>

CC BY-SA 4.0
2023-03-08T01:53:32.137Z
2023-03-08T02:07:00.333Z
2023-03-08T02:07:00.333Z
Vladimir Botka
<p>I would like to define rescue or rollback executions within my Ansible structure. I am using this recommended structure:</p>
<pre><code>roles
  rolename
    files
    tasks
    templates
    handlers
    ...
</code></pre>
<p>I know that I can set blocks and within them a rescue section. But this seems a little clumsy to me and I would like to define all the rollback/rescue options within its own folder like</p>
<pre><code>roles
  rolename
    ...
    tasks
    rollback/rescue
    handlers
    ...
</code></pre>
<p>This would make much more sense to me. I am speaking here about rollback, because I would like to be able to rollback a setup that I have created once with this role. I understand that the concept of rescue is a little bit different because it aims to fire when there is an error approached in executing a role. But I am hoping that I could use the concepts together while defining an anti-step to each step that I have defined in the role. And these anti-steps should have their own place and not included in the tasks files.</p>

CC BY-SA 4.0
2023-03-07T23:00:27.893Z
What prevents you from "defining an anti-step to each step" in *rollback* and including them whenever you want?
Vladimir Botka
CC BY-SA 4.0
2023-03-08T06:58:30.403Z
Maybe an unpopular opinion, but your rollback should be to a previous state. This means versioning your roles and applying the desired version of the role to the target system, not implementing "anti-tasks" in the role.
Bruce Becker
<ansible>
How to define rollback or rescue in ansible folder structure?
Vietna
# How to define rollback or rescue in ansible folder structure?

(Asked by: username_0 on 2023-03-07)

I would like to define rescue or rollback executions within my Ansible structure. I am using this recommended structure:
roles
  rolename
    files
    tasks
    templates
    handlers
    ...

I know that I can set blocks and within them a rescue section. But this seems a little clumsy to me and I would like to define all the rollback/rescue options within its own folder like
roles
  rolename
    ...
    tasks
    rollback/rescue
    handlers
    ...

This would make much more sense to me. I am speaking here about rollback, because I would like to be able to rollback a setup that I have created once with this role. I understand that the concept of rescue is a little bit different because it aims to fire when there is an error approached in executing a role. But I am hoping that I could use the concepts together while defining an anti-step to each step that I have defined in the role. And these anti-steps should have their own place and not included in the tasks files.

**Tags:** ansible

> **Comments:**
> **username_1**: What prevents you from "defining an anti-step to each step" in *rollback* and including them whenever you want?  
> **username_2**: Maybe an unpopular opinion, but your rollback should be to a previous state. This means versioning your roles and applying the desired version of the role to the target system, not implementing "anti-tasks" in the role.  

---

## 1 answers

### Answer by username_1 on 2023-03-08. Score: 1

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


---

