Passing condition to "failed_when" as a variable

I try to pass a condition to failed_when as a variable in a role:

my-role/defaults/main.yml:

# list of commands to execute in form [command, changed_when, failed_when]

my_commands:
  - ["myapp init", "cmd_result.rc == 0", "(cmd_result.rc != 0) and ('Non-critical error' not in cmd_result.stdout) and ('Non-critical error' not in cmd_result.stderr)"]
  - [...]

This would allow me to loop through the command list with custom changed_when and failed_when conditions:

my-role/tasks/main.yml:

- name: Execute commands
  ansible.builtin.include_tasks: my-task.yml
  loop: "{{ my_commands }}"
  loop_control:
    loop_var: my_command

my-role/tasks/my-task.yml:

- name: My task
 ansible.builtin.command: "{{ my_command[0] }}"
  register: cmd_result
  changed_when: my_command[1]
  failed_when: my_command[2]

But I face following behaviour:

  • task fails with non-zero return code :cross_mark:
  • if I copy the condition line into failed_when statement explicitly, like failed_when: "(cmd_result.rc != 0) and ('Non-critical error' not in cmd_result.stdout) and ('Non-critical error' not in cmd_result.stderr)", task works :white_check_mark:

I suppose it may be that

  • the statement is not evaluated properly
  • cmd_result variable is not injected into the conditional statement

Can anyone explain this, and maybe suggest an elegant and functional way to get this work?

Thank you!

failed_when is evaluated to literal string “(cmd_result.rc != 0) and (‘Non-critical error’ not in cmd_result.stdout) and (‘Non-critical error’ not in cmd_result.stderr)”

What you want is to have same literal string “(cmd_result.rc != 0) and (‘Non-critical error’ not in cmd_result.stdout) and (‘Non-critical error’ not in cmd_result.stderr)” to be used as condition.

In your case there is only one evaluation - my_command[2] is evaluated to string, ansible does just one evaluation.

The solution is


failed_when: "{{ my_command[2] }}"

In this case {{ my _command[2] }} is evaluated to as string (variable evaluation) and when string (condition you wrote) is evaluated as condition.

2 Likes

Create the files on the fly. For example,

    - name: Create my commands.
      run_once: true
      delegate_to: localhost
      block:

        - name: Create directory for my commands.
          file:
            state: directory
            path: /tmp/my_commands

        - name: Create my commands.
          copy:
            dest: "{{ my_task }}"
            content: |
              ---
              - name: My task
                ansible.builtin.command: "{{ item.0 }}"
                register: cmd_result
                changed_when: "{{ item.1 }}"
                failed_when: "{{ item.2 }}"
          loop: "{{ my_commands }}"
          loop_control: 
            extended: true

Declare the filename as appropriate

my_task: "/tmp/my_commands/{{ ansible_loop.index }}-{{ item.0 }}.yml"

Then, execute the commands

    - name: Execute my commands.
      ansible.builtin.include_tasks: "{{ my_task }}"
      loop: "{{ my_commands }}"
      loop_control: 
        extended: true

[source code]

2 Likes

As mentioned already, the items in my_command are all strings, not variables. If you look at changed_when_result in the output - you’ll see a message that it’s derived from a string. If you fix that by adding brackets around the 2nd and 3rd fields for every item in my_commands, you’ll run into another issue: the loop is templated before the task executes. I’d use two different variables - one for the loop, and another that is referenced by changed_when and failed_when using the loop item.

For example, the variables could look like:

my_commands:
  - myapp init
cmd_results:
  # Or, instead of a dict, you could use a list with the same indices
  # as in my_commands, and loop over "{{ range(0, my_commands|length) | list }}".
  "myapp init":
    changed_when: "{{ cmd_result.rc == 0 }}"
    failed_when:
      - "{{ cmd_result.rc != 0 }}"
      - "{{ 'Non-critical error' not in cmd_result.stdout }}"
      - "{{ 'Non-critical error' not in cmd_result.stderr }}"

And could be used in the task like:

changed_when: cmd_results[my_command].changed_when
failed_when: cmd_results[my_command].failed_when is all