changed_when: in Ansible

Question:

I’m having trouble setting up the changed_when condition on a specific task on my playbook.

I used to use ignore_errors: true in the past, using the git module:

- name: Checkout in client folder
  ansible.builtin.git:
    repo: "{{ item.value.dev_repo | default('git@*****:***/****.git') }}"
    key_file: /tmp/oci_odoo.rsa
    accept_hostkey: true
    dest: "/opt/odoo/odoo_{{ item.key }}/addons"
    version: "{{ item.value.repo_branch | default('main') }}"
    force: false
  with_dict: "{{ clients }}"
  when: inventory_hostname in groups['main']
  ignore_errors: true
  tags: tag1

That code gives me an error everytime I run the playbook since there’s actually local modifications in the folder :

[11:06:42]   ↳ module : Checkout in client folder  | hostname | FAILED | 1.08s
{
  - msg: One or more items failed
  - results: [ {
    - msg: Local modifications exist in the destination: /opt/odoo/odoo/addons (force=no).
    - changed: False
    - before: 1c9a771969222f8ebbab57bd1e173b821f684a04
    - failed: True
    }
    - ansible_loop_var: item
  } ]
  - changed: False
}

I then tried to use conditional to make ansible see a change when the text "Local modifications" is present in the result with

  register: result
  changed_when: '"Local modifications" in result.results[msg]'

or

  register: result
  changed_when: '"Local modifications" in result.results.msg'

Since debugging the result var gives:

{
  - result: {
    - msg: One or more items failed
    - results: [ {
      - msg: Local modifications exist in the destination: /opt/odoo/odoo/addons (force=no).
      - changed: False
      - before: 1c9a771969222f8ebbab57bd1e173b821f684a04
      - failed: True
      - changed_when_result: The conditional check '"Local modifications" in result.results[msg]' failed. The error was: error while evaluating conditional ("Local modifications" in result.results[msg]): 'dict object' has no attribute 'results'
      }
      - ansible_loop_var: item
    } ]
    - skipped: False
    - failed: True
    - changed: False
  }
}

And with that you get to see the error:

he conditional check '"Local modifications" in result.results[msg]' failed. The error was: error while evaluating conditional ("Local modifications" in result.results[msg]): 'dict object' has no attribute 'results'

How can I make ansible thinks there’s change when "Local modifications" is returned by the git module ?

Thanks,

EDIT: dict in use :

clients:
  client1:
    odoo_version: "15"
    odoo_subnet: "22"
    port_ending_number: "02"
    odoo_passwd: "azertyuiop"
    domain: doe
    dev_repo: [email protected]
Asked By: Louis Gromb

||

Answers:

Your condition must look like this:

  register: result
  changed_when: '"Local modifications" in result.msg'

The conditions like changed_when and failed_when are evaluated for each iteration. During the execution of an iteration the result of each iteration step is exclusively available in the variable of the register (in your example result).

Only after the end of the loop the results of all itration steps are summarized in a list results. The list entries within results are therefore those results that were previously available exclusively via the variable defined by register during execution.


Here is another small example.

The following task:

- debug:
    msg: "{{ item }}"
  with_items:
    - Hello John.
    - The sun is shining.
    - John and Joe drinking a beer.
  register: result
  changed_when: '"John" in result.msg'

Results in this behavior concerning ok and changed:

TASK [debug] *********************************************************
changed: [localhost] => (item=Hello John.) => {
    "msg": "Hello John."
}
ok: [localhost] => (item=The sun is shining.) => {
    "msg": "The sun is shining."
}
changed: [localhost] => (item=John and Joe drinking a beer.) => {
    "msg": "John and Joe drinking a beer."
}

This is how the content of the variable result looks like after execution:

TASK [debug] ***********************************************
ok: [localhost] => {
    "result": {
        "changed": true,
        "msg": "All items completed",
        "results": [
            {
                "ansible_loop_var": "item",
                "changed": true,
                "failed": false,
                "item": "Hello John.",
                "msg": "Hello John."
            },
            {
                "ansible_loop_var": "item",
                "changed": false,
                "failed": false,
                "item": "The sun is shining.",
                "msg": "The sun is shining."
            },
            {
                "ansible_loop_var": "item",
                "changed": true,
                "failed": false,
                "item": "John and Joe drinking a beer.",
                "msg": "John and Joe drinking a beer."
            }
        ],
        "skipped": false
    }
}

Basically a with_dict works exactly the same as a with_items, internally the whole thing is evaluated as a loop. A loop can basically only run on objects that can be iterated, so internally the dict is automatically converted to a list of dicts with the filter dict2items.

Therefore the whole thing works the same if I replace the with_items with a with_dict like this:

- debug:
    msg: "{{ item.key }}: {{ item.value  }}"
  with_dict:
    sentence1: Hello John.
    sentence2: The sun is shining.
    sentence3: John and Joe drinking a beer.
  changed_when: '"John" in result.msg'
  register: result

However, you are trying to apply changed_when, but at the same time you have set ignore_errors: true. This will not abort on error, but the error will still be registered as such.

The following order of precedence applies:

failed > changed > ok

If I include a fail for my example task, you will see that the first iteration step is evaluated as failed, although it is also changed.

- debug:
    msg: "{{ item.key }}: {{ item.value  }}"
  with_dict:
    sentence1: Hello John.
    sentence2: The sun is shining.
    sentence3: John and Joe drinking a beer.
  changed_when: '"John" in result.msg'
  failed_when: '"Hello" in result.msg'
  ignore_errors: true
  register: result

The result looks like this:

TASK [debug] ***************************************************************************************
failed: [localhost] (item={'key': 'sentence1', 'value': 'Hello John.'}) => {
    "msg": "sentence1: Hello John."
}
ok: [localhost] => (item={'key': 'sentence2', 'value': 'The sun is shining.'}) => {
    "msg": "sentence2: The sun is shining."
}
changed: [localhost] => (item={'key': 'sentence3', 'value': 'John and Joe drinking a beer.'}) => {
    "msg": "sentence3: John and Joe drinking a beer."
}
fatal: [localhost]: FAILED! => {"msg": "One or more items failed"}
...ignoring

This is how the content of the variable result looks like after execution:

TASK [debug] ***********************************************
ok: [localhost] => {
    "result": {
        "changed": true,
        "failed": true,
        "msg": "One or more items failed",
        "results": [
            {
                "ansible_loop_var": "item",
                "changed": true,
                "failed": true,
                "failed_when_result": true,
                "item": {
                    "key": "sentence1",
                    "value": "Hello John."
                },
                "msg": "sentence1: Hello John."
            },
            {
                "ansible_loop_var": "item",
                "changed": false,
                "failed": false,
                "failed_when_result": false,
                "item": {
                    "key": "sentence2",
                    "value": "The sun is shining."
                },
                "msg": "sentence2: The sun is shining."
            },
            {
                "ansible_loop_var": "item",
                "changed": true,
                "failed": false,
                "failed_when_result": false,
                "item": {
                    "key": "sentence3",
                    "value": "John and Joe drinking a beer."
                },
                "msg": "sentence3: John and Joe drinking a beer."
            }
        ],
        "skipped": false
    }
}

In result.msg the string "Local modifications" is contained, however an error is recognized and this predominates. Therefore define either failed_when: false or find a meaningful condition for the failed_when option in the future.

By failed_when: false your task will never evaluate as failed and never switch to ignored, but this way a changed can be evaluated as such.

So try the following:

  register: result
  changed_when: '"Local modifications" in result.msg'
  failed_when: false
Answered By: phanaz