Multiple Conditions by using 'and' Boolean in RegEx Search

Hello,
I wanted to use print a message when two string are met in a regular expression.
However, it seems that only the first string is hit.

Extract - Playbook
- name: Task2 - sh run
ios_command:
commands:
- sh run | sec prefix-list
become: true
register: output2

:
:
- name: Result2.2 pre-check if prefix is NOT oonfigured
debug:
msg: " Prefix is configured @ {{inventory_hostname}}: {{ item.network }} permit {{ item.prefix }}"
when: output2.stdout is not search (item.network) and
output2.stdout is not search (item.prefix)
loop:
- { network: Test-nets, prefix: “10.0.0.0/25” }
- { network: Test-nets, prefix: “10.0.2.0/25” }
- { network: Test-nets, prefix: “10.0.5.0/25” }
I would appreciate any advice.

Thanks.

Okay. First, put your Ansible code between “triple-back-ticks” lines, like this:

```yaml
# Your Ansible code here
```

so we can see the indentation. That alone will greatly improve your chances of (a) your questions being understood and (b) someone jumping in with an answer.

By the way, you can edit a prior post by clicking on its little pencil icon (hint, hint, please!).

Second, the second line of your when: ends with a “}” - a closing brace - where I’m reasonably sure you meant “)” - a closing parenthesis.

But the biggest problem for me is lack of clarity. The message you’re printing seems opposite of the intent expressed in the debug task’s name:. Furthermore, in your opening sentence you say you want to print a message when two strings are in your output, but your tests both contain “not” — that is, you’re testing to ensure neither string is in your registered output.

Maybe the solution is as simple as changing both “is not search” instances to “is search” (and fixing the closing character on your when: condition). But it’s hard to say for sure, as we don’t see the indentation, we don’t see what’s in your registered output, and the descriptions of what the code is suppose to do seem opposite to the code we’ve been shown.

1 Like

Thank you utoddl for your comprehensive comment.
This is only an excerpt of YAML playbook focusing on task identified by Task2.
And we apologize to did not have identified the indents clearer to you and having had the mix up in the parenthesis.

Basically, the question remains what syntax to use in case of multiple conditions using Boolean “and”, and in conjunction with loop variables.

In essence:
If the network id AND the prefix cannot be found in output 2 then print: “Prefix is not configured…”
The loop variables are “network” and “prefix”.

Playbook excerpt:

  • name: Ansible Prefix removal on Cisco IOS XE
    hosts: iosxe
    vars_files:
    - /home/wolfm/Ansible/vault_password.yml

    vars:
    ansible_become_pass: “{{ vault_sudo_password }}”
    ansible_python_interpreter: /usr/bin/python3

    tasks:

    • name: Task2 - sh run
      ios_command:
      commands:
      - sh run | sec prefix-list

      become: true
      register: output2

    • name: Result2.2 pre-check if prefix is NOT configured
      debug:
      msg: " Prefix is NOT configured @ {{inventory_hostname}}: {{ item.network }} permit {{ item.prefix }}"
      when: output2.stdout is not search (item.network) and (item.prefix)
      loop:
      - { network: Test-nets, prefix: “10.0.0.0/25” }
      - { network: Test-nets, prefix: “10.0.2.0/25” }
      - { network: Test-nets, prefix: “10.0.5.0/25” }

The outut2 has the following format:

ip prefix-list Test-nets seq 20 permit 10.0.0.0/25
ip prefix-list Test-nets seq 25 permit 10.0.2.0/25
ip prefix-list Test-nets seq 25 permit 10.0.5.0/25

Running the current YAML book, the “when” expression meets only the first condition "(item.network), but seems to ignore second one “(item.prefix)”.

I hope this response makes it a bit clear to you.
I would also appreciate to consider respectful greetings in an exchange, like “Hello” and “Regards” or “Thanks”.
To me this is a matter of manners and respect.

Regards,
netmart

This problem is loaded with traps. It would be easy to construct some expressions that seem to work but don’t. For example,

when:
  - output2.stdout is not search(item.network)
  - output2.stdout is not search(item.prefix)

(Note: the list form above is equivalent to “when: expr1 and expr2”.)

This seems to work, but only because Test-nets shows up in all your lines, and that’s the only value this code looks for. So the first expression fails on the first line every time, and the second expression never executes. (Perhaps this is what you meant by “However, it seems that only the first string is hit.” in your initial post?)

Below is a simple (is that ever true?) playbook that demonstrates four ways to do this, all of which are rather obvious, and half of them don’t work! I’ll break it up and intersperse the output of each task into the body to reduce scrolling back and forth during discussion. I’ve modified your original stdout text stream to make the problem areas more obvious. Here we go:

- name: Testing multi-search
  hosts: localhost
  gather_facts: false
  vars:
    output2:
      stdout: |
        ip prefix-list Test-nets-0 seq 20 permit 10.0.0.0/25
        ip prefix-list Test-nets-2 seq 25 permit 10.0.2.0/25
        ip prefix-list Test-nets-5 seq 25 permit 10.0.5.0/25
      stdout_lines:
        - ip prefix-list Test-nets-0 seq 20 permit 10.0.0.0/25
        - ip prefix-list Test-nets-2 seq 25 permit 10.0.2.0/25
        - ip prefix-list Test-nets-5 seq 25 permit 10.0.5.0/25
    networks:
      - {"network": "Test-nets-0"}
      - {"network": "Test-nets-2"}
      - {"network": "Test-nets-5"}
    prefixes:
      - {"prefix": "10.0.0.0/25"}
      - {"prefix": "10.0.2.0/25"}
      - {"prefix": "10.0.5.0/25"}
    loop_items: '{{ networks | product(prefixes) | map("combine") }}'
  tasks:
    - name: Show Cartesian product of networks and prefixes
      ansible.builtin.debug:
        msg: '{{ loop_items }}'

The variables above create a modified version of your registered output2.stdout and output2.stdout_lines (the list version of .stdout, stripped of new-lines). The loop_items variable contains a list of all combinations of networks and prefixes, a few of which actually show up in output2.*. The output of the debug task looks like this (almost; I added a couple of line breaks to make it more obvious what’s going on):

TASK [Show Cartesian product of networks and prefixes] *******************************
ok: [localhost] => 
  msg:
  - network: Test-nets-0
    prefix: 10.0.0.0/25
  - network: Test-nets-0
    prefix: 10.0.2.0/25
  - network: Test-nets-0
    prefix: 10.0.5.0/25

  - network: Test-nets-2
    prefix: 10.0.0.0/25
  - network: Test-nets-2
    prefix: 10.0.2.0/25
  - network: Test-nets-2
    prefix: 10.0.5.0/25

  - network: Test-nets-5
    prefix: 10.0.0.0/25
  - network: Test-nets-5
    prefix: 10.0.2.0/25
  - network: Test-nets-5
    prefix: 10.0.5.0/25

We’ll use this list in our loop: in each of the four following tasks.
In these tasks, instead of skipping with a when:, we’ll print each combination of network and prefix we’re testing, and indicate whether the test method “sees” the data in output2.

    - name: Check configured network::prefix combinations on stdout with "search() and search()" -- FAILS
      debug:
         msg: "{{inventory_hostname}}: {{ item.network }} permit {{ item.prefix }} configured: {{ is_configured }}"
      vars:
        is_configured: '{{ output2.stdout is search(item.network | regex_escape()) and
                           output2.stdout is search(item.prefix  | regex_escape()) }}'
      loop: '{{ loop_items }}'

This task FAILS, because it finds each item.network and each item.prefix in our output2. It’s totally oblivious to the values in question being on the same line in only one third of our tests.

One thing it does do right is to regex_escape() the strings it’s searching for. It isn’t an issue for item.network, but item.prefix has these nasty “.” characters that happily match any character. That’s actually okay-ish for this data, but data changes; better to get in the habit of being careful with searches.
Anyway, here’s the output:

TASK [Check configured network::prefix combinations on stdout with "search() and search()" -- FAILS] ***
ok: [localhost] => (item={'network': 'Test-nets-0', 'prefix': '10.0.0.0/25'}) => 
  msg: 'localhost: Test-nets-0 permit 10.0.0.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-0', 'prefix': '10.0.2.0/25'}) => 
  msg: 'localhost: Test-nets-0 permit 10.0.2.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-0', 'prefix': '10.0.5.0/25'}) => 
  msg: 'localhost: Test-nets-0 permit 10.0.5.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-2', 'prefix': '10.0.0.0/25'}) => 
  msg: 'localhost: Test-nets-2 permit 10.0.0.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-2', 'prefix': '10.0.2.0/25'}) => 
  msg: 'localhost: Test-nets-2 permit 10.0.2.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-2', 'prefix': '10.0.5.0/25'}) => 
  msg: 'localhost: Test-nets-2 permit 10.0.5.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-5', 'prefix': '10.0.0.0/25'}) => 
  msg: 'localhost: Test-nets-5 permit 10.0.0.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-5', 'prefix': '10.0.2.0/25'}) => 
  msg: 'localhost: Test-nets-5 permit 10.0.2.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-5', 'prefix': '10.0.5.0/25'}) => 
  msg: 'localhost: Test-nets-5 permit 10.0.5.0/25 configured: True'

You see it claimed them all “configured: True”? But only the 1st, 5th, and 9th combinations actually exist in our input.

Let’s try again with a single “constructed” regex:

    - name: Check configured network:prefix combinations on stdout with one constructed search() -- SUCCEEDS
      debug:
         msg: "{{inventory_hostname}}: {{ item.network }} permit {{ item.prefix }} configured: {{ is_configured }}"
      vars:
        is_configured: '{{ output2.stdout is search((item.network | regex_escape()) ~ ".*" ~ (item.prefix | regex_escape())
      loop: '{{ loop_items }}'

This works, but only because multi-line search is off by default. That is, the constructed regular expression won’t span across new-lines in the input. So the fact that it succeeds is a sort of “happy accidental side effect” of searches. It would be better to be explicit about that. Anyway, here’s its output:

TASK [Check configured network:prefix combinations on stdout with one constructed search() -- SUCCEEDS] ***
ok: [localhost] => (item={'network': 'Test-nets-0', 'prefix': '10.0.0.0/25'}) => 
  msg: 'localhost: Test-nets-0 permit 10.0.0.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-0', 'prefix': '10.0.2.0/25'}) => 
  msg: 'localhost: Test-nets-0 permit 10.0.2.0/25 configured: False'
ok: [localhost] => (item={'network': 'Test-nets-0', 'prefix': '10.0.5.0/25'}) => 
  msg: 'localhost: Test-nets-0 permit 10.0.5.0/25 configured: False'
ok: [localhost] => (item={'network': 'Test-nets-2', 'prefix': '10.0.0.0/25'}) => 
  msg: 'localhost: Test-nets-2 permit 10.0.0.0/25 configured: False'
ok: [localhost] => (item={'network': 'Test-nets-2', 'prefix': '10.0.2.0/25'}) => 
  msg: 'localhost: Test-nets-2 permit 10.0.2.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-2', 'prefix': '10.0.5.0/25'}) => 
  msg: 'localhost: Test-nets-2 permit 10.0.5.0/25 configured: False'
ok: [localhost] => (item={'network': 'Test-nets-5', 'prefix': '10.0.0.0/25'}) => 
  msg: 'localhost: Test-nets-5 permit 10.0.0.0/25 configured: False'
ok: [localhost] => (item={'network': 'Test-nets-5', 'prefix': '10.0.2.0/25'}) => 
  msg: 'localhost: Test-nets-5 permit 10.0.2.0/25 configured: False'
ok: [localhost] => (item={'network': 'Test-nets-5', 'prefix': '10.0.5.0/25'}) => 
  msg: 'localhost: Test-nets-5 permit 10.0.5.0/25 configured: True'

Here you can see that only tests 1, 5, and 9 set “configured: True”.

Here’s another take on the same data. This uses the Jinja2 “in()” test. It looks at items in a list, or (as in this case) substrings within strings. Again, easy to implement, and it gets it wrong two thirds of the time, in basically the same way the first task did:

    - name: Check configured network::prefix combinations on stdout with Jinja2 "in" test -- FAILS
      debug:
         msg: "{{inventory_hostname}}: {{ item.network }} permit {{ item.prefix }} configured: {{ is_configured }}"
      vars:
        is_configured: '{{ item.network is in output2.stdout and
                           item.prefix is in output2.stdout }}'
      loop: '{{ loop_items }}'

and its erroneous output:

TASK [Check configured network::prefix combinations on stdout with Jinja2 "in" test -- FAILS] ***
ok: [localhost] => (item={'network': 'Test-nets-0', 'prefix': '10.0.0.0/25'}) => 
  msg: 'localhost: Test-nets-0 permit 10.0.0.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-0', 'prefix': '10.0.2.0/25'}) => 
  msg: 'localhost: Test-nets-0 permit 10.0.2.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-0', 'prefix': '10.0.5.0/25'}) => 
  msg: 'localhost: Test-nets-0 permit 10.0.5.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-2', 'prefix': '10.0.0.0/25'}) => 
  msg: 'localhost: Test-nets-2 permit 10.0.0.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-2', 'prefix': '10.0.2.0/25'}) => 
  msg: 'localhost: Test-nets-2 permit 10.0.2.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-2', 'prefix': '10.0.5.0/25'}) => 
  msg: 'localhost: Test-nets-2 permit 10.0.5.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-5', 'prefix': '10.0.0.0/25'}) => 
  msg: 'localhost: Test-nets-5 permit 10.0.0.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-5', 'prefix': '10.0.2.0/25'}) => 
  msg: 'localhost: Test-nets-5 permit 10.0.2.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-5', 'prefix': '10.0.5.0/25'}) => 
  msg: 'localhost: Test-nets-5 permit 10.0.5.0/25 configured: True'

Finally, here’s an example that looks at the input stream in list form: output2.stdout_lines. It uses filters to select only the lines that “contain” the network, then selects from that subset only the lines that match the corresponding prefix, and declares success if the result contains at least one item. Personally, I like this better because it is more applicable to structured data and avoids some pitfalls with text streams. But really it comes down to what you’re more comfortable with.

    - name: Check configured network::prefix combinations on stdout_lines with filtering -- SUCCEEDS
      debug:
         msg: "{{inventory_hostname}}: {{ item.network }} permit {{ item.prefix }} configured: {{ is_configured }}"
      vars:
        is_configured: '{{ output2.stdout_lines
                           | select("contains", item.network)
                           | select("contains", item.prefix)
                           | length > 0 | bool }}'
      loop: '{{ loop_items }}'

And here’s its corresponding - and correct - output:

TASK [Check configured network::prefix combinations on stdout_lines with filtering -- SUCCEEDS] ***
ok: [localhost] => (item={'network': 'Test-nets-0', 'prefix': '10.0.0.0/25'}) => 
  msg: 'localhost: Test-nets-0 permit 10.0.0.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-0', 'prefix': '10.0.2.0/25'}) => 
  msg: 'localhost: Test-nets-0 permit 10.0.2.0/25 configured: False'
ok: [localhost] => (item={'network': 'Test-nets-0', 'prefix': '10.0.5.0/25'}) => 
  msg: 'localhost: Test-nets-0 permit 10.0.5.0/25 configured: False'
ok: [localhost] => (item={'network': 'Test-nets-2', 'prefix': '10.0.0.0/25'}) => 
  msg: 'localhost: Test-nets-2 permit 10.0.0.0/25 configured: False'
ok: [localhost] => (item={'network': 'Test-nets-2', 'prefix': '10.0.2.0/25'}) => 
  msg: 'localhost: Test-nets-2 permit 10.0.2.0/25 configured: True'
ok: [localhost] => (item={'network': 'Test-nets-2', 'prefix': '10.0.5.0/25'}) => 
  msg: 'localhost: Test-nets-2 permit 10.0.5.0/25 configured: False'
ok: [localhost] => (item={'network': 'Test-nets-5', 'prefix': '10.0.0.0/25'}) => 
  msg: 'localhost: Test-nets-5 permit 10.0.0.0/25 configured: False'
ok: [localhost] => (item={'network': 'Test-nets-5', 'prefix': '10.0.2.0/25'}) => 
  msg: 'localhost: Test-nets-5 permit 10.0.2.0/25 configured: False'
ok: [localhost] => (item={'network': 'Test-nets-5', 'prefix': '10.0.5.0/25'}) => 
  msg: 'localhost: Test-nets-5 permit 10.0.5.0/25 configured: True'

It should be reasonably straightforward to adapt any of these examples (preferably one of the working ones!) to your needs.

But as always, if anything is not clear, just ask. We’re all here to help each other.
Cheers!

1 Like