Search code examples
ansibleassert

assert module: Is it possible to fail if string is found only once?


I have some output from a text file on a Windows system (using winrm) that I am parsing. I am trying to make the playbook fail if it does not see the word 'test' more than once.

This is what I have that works just for continuing the playbook if it sees the word 'test' at least one time:

- name: "Windows Output | Parse file"
  win_shell: |
    (Get-Content C:\TEST\output.txt)
  register: 'testing_parse'

- name: "File output | verification"
  assert:
    that:
      - "'test' in testing_parse.stdout"
    fail_msg: "Exiting now."
    success_msg: "Proceeding with task."

I thought that the following would work and it did not:

- name: "File output | verification"
  assert:
    that:
      - "'test' in testing_parse.stdout >= '2'"
    fail_msg: "Exiting now."
    success_msg: "Proceeding with task."

How can I fix my assert task so that it meets my requirement?


Solution

  • TL;DR

    - name: "File output | verification"
      vars:
        watch_regex: '(?:^|\W)(test)(?:$|\W)'
      assert:
        that:
          - testing_parse.stdout | regex_findall(watch_regex) | length > 1
        fail_msg: "Exiting now."
        success_msg: "Proceeding with task."
    

    Why the in test is not appropriate

    in returns a boolean

    Let's first see what we get back from the in test in several situations

    $ ansible localhost -m debug -a "msg={{ 'test' in my_test }}" \
        -e "my_test='a test'"
    
    localhost | SUCCESS => {
        "msg": true
    }
    
    $ ansible localhost -m debug -a "msg={{ 'test' in my_test }}" \
        -e "my_test='a test and an other test'"
    
    localhost | SUCCESS => {
        "msg": true
    }
    
    $ ansible localhost -m debug -a "msg={{ 'test' in my_test }}" \
        -e "my_test='no word we look for'"
    
    localhost | SUCCESS => {
        "msg": false
    }
    

    As you can see, it will always return a boolean depending on the presence or not of the needle in the haystack.

    in does not find words

    Note also that in is not very good at finding words (since you mentioned that) as demonstrated below:

    # It will match a substring in a middle of an other word
    $ ansible localhost -m debug -a "msg={{ 'test' in my_test }}" \
        -e my_test="blahtesttoto"
    
    localhost | SUCCESS => {
        "msg": true
    }
    
    # It will not match a word inside an element of a list...
    $ ansible localhost -m debug -a "msg={{ 'test' in my_test }}" \
        -e '{"my_test":["this is", "a test"]}'
    
    localhost | SUCCESS => {
        "msg": false
    }
    
    # ... but only an exact match of an element of a list
    $ ansible localhost -m debug -a "msg={{ 'test' in my_test }}" \
        -e '{"my_test":["a", "test"]}'
    
    localhost | SUCCESS => {
        "msg": true
    }
    

    Your expression is wrong anyway

    To end with, let's look at your try when you wrote the expression:

    'test' in testing_parse.stdout >= '2'
    

    This means:

    1. check if the string testing_parse.stdout is lexically superior or equal to the string '2'.
    2. now check if the string 'test' is found inside the preceding boolean result.

    As you can now guess with the explanation, there is absolutely no chance this will ever return true.

    regex_findall to the rescue

    A way to look for a specific word is to use a regex. The following will look for the test word i.e. the "test" string preceded and followed by any non-word character (end of line, beginning of line, white space, tab, punctuation....).

    (?:^|\W)(test)(?:$|\W)
    

    If you are not familiar with regular expressions, see this answer for a specific explanation and https://www.regextutorial.org/ for a general resource (I'm not affiliated, you can find others using your favorite search engine).

    The regex_find_all filter can return all matches of a regex against a string into a list

    $ ansible localhost -m debug \
        -a "msg={{ my_test | regex_findall('(?:^|\\W)(test)(?:$|\\W)') }}" \
        -e "my_test='test'"
    
    localhost | SUCCESS => {
        "msg": [
            "test"
        ]
    }
    
    $ ansible localhost -m debug \
        -a "msg={{ my_test | regex_findall('(?:^|\\W)(test)(?:$|\\W)') }}" \
        -e "my_test='test and an other test but not blahtesttoto yet test'"
    
    localhost | SUCCESS => {
        "msg": [
            "test",
            "test",
            "test"
        ]
    }
    
    $ ansible localhost -m debug \
        -a "msg={{ my_test | regex_findall('(?:^|\\W)(test)(?:$|\\W)') }}" \
        -e "my_test='notest'"
    
    localhost | SUCCESS => {
        "msg": []
    }
    

    Once we have that, we only need to count the number of elements in the returned list whith the length filter.

    $ ansible localhost -m debug \
        -a "msg={{ my_test | regex_findall('(?:^|\\W)(test)(?:$|\\W)') | length }}" \
        -e "my_test='test and an other test but not blahtesttoto yet test'"
    
    localhost | SUCCESS => {
        "msg": "3"
    }
    

    And finally we can fix your assert task according to your requirement:

    - name: "File output | verification"
      vars:
        watch_regex: '(?:^|\W)(test)(?:$|\W)'
      assert:
        that:
          - testing_parse.stdout | regex_findall(watch_regex) | length > 1
        fail_msg: "Exiting now."
        success_msg: "Proceeding with task."