diff --git a/lib/ansible/modules/utilities/logic/wait_for.py b/lib/ansible/modules/utilities/logic/wait_for.py index 3111cc3db97..6b9e3508e6b 100644 --- a/lib/ansible/modules/utilities/logic/wait_for.py +++ b/lib/ansible/modules/utilities/logic/wait_for.py @@ -134,6 +134,14 @@ EXAMPLES = r''' path: /tmp/foo search_regex: completed +- name: Wait until regex pattern matches in the file /tmp/foo and print the matched group + wait_for: + path: /tmp/foo + search_regex: completed (?P\w+) + register: waitfor +- debug: + msg: Completed {{ waitfor['groupdict']['task'] }} + - name: Wait until the lock file is removed wait_for: path: /var/lock/file.lock @@ -176,6 +184,20 @@ elapsed: returned: always type: int sample: 23 +match_groups: + description: Tuple containing all the subgroups of the match as returned by U(https://docs.python.org/2/library/re.html#re.MatchObject.groups) + returned: always + type: list + sample: ['match 1', 'match 2'] +match_groupdict: + description: Dictionary containing all the named subgroups of the match, keyed by the subgroup name, + as returned by U(https://docs.python.org/2/library/re.html#re.MatchObject.groupdict) + returned: always + type: dict + sample: + { + 'group': 'match' + } ''' import binascii @@ -468,6 +490,9 @@ def main(): else: compiled_search_re = None + match_groupdict = {} + match_groups = () + if port and path: module.fail_json(msg="port and path parameter can not both be passed to wait_for", elapsed=0) if path and state == 'stopped': @@ -537,8 +562,13 @@ def main(): try: f = open(path) try: - if re.search(compiled_search_re, f.read()): - # String found, success! + search = re.search(compiled_search_re, f.read()) + if search: + if search.groupdict(): + match_groupdict = search.groupdict() + if search.groups(): + match_groups = search.groups() + break finally: f.close() @@ -630,7 +660,8 @@ def main(): module.fail_json(msg=msg or "Timeout when waiting for %s:%s to drain" % (host, port), elapsed=elapsed.seconds) elapsed = datetime.datetime.utcnow() - start - module.exit_json(state=state, port=port, search_regex=search_regex, path=path, elapsed=elapsed.seconds) + module.exit_json(state=state, port=port, search_regex=search_regex, match_groups=match_groups, match_groupdict=match_groupdict, path=path, + elapsed=elapsed.seconds) if __name__ == '__main__': diff --git a/test/integration/targets/wait_for/tasks/main.yml b/test/integration/targets/wait_for/tasks/main.yml index 908d2d8d039..4d1c9f319df 100644 --- a/test/integration/targets/wait_for/tasks/main.yml +++ b/test/integration/targets/wait_for/tasks/main.yml @@ -11,17 +11,17 @@ - name: setup a path file: - path: /tmp/wait_for_file + path: "{{ output_dir }}/wait_for_file" state: touch -- name: setup remove a file after 10s - shell: sleep 10 && rm /tmp/wait_for_file +- name: setup remove a file after 3s + shell: sleep 3 && rm {{ output_dir }}/wait_for_file async: 20 poll: 0 - name: test for absent path wait_for: - path: /tmp/wait_for_file + path: "{{ output_dir }}/wait_for_file" state: absent timeout: 20 register: waitfor @@ -29,47 +29,70 @@ assert: that: - waitfor is successful - - "waitfor.path == '/tmp/wait_for_file'" - - waitfor.elapsed >= 5 + - waitfor.path == "{{ output_dir | expanduser }}/wait_for_file" + - waitfor.elapsed >= 2 - waitfor.elapsed <= 15 -- name: setup create a file after 10s - shell: sleep 10 && touch /tmp/wait_for_file +- name: setup create a file after 3s + shell: sleep 3 && touch {{ output_dir }}/wait_for_file async: 20 poll: 0 - name: test for present path wait_for: - path: /tmp/wait_for_file - timeout: 20 + path: "{{ output_dir }}/wait_for_file" + timeout: 5 register: waitfor - name: verify test for absent path assert: that: - waitfor is successful - - "waitfor.path == '/tmp/wait_for_file'" - - waitfor.elapsed >= 5 + - waitfor.path == "{{ output_dir | expanduser }}/wait_for_file" + - waitfor.elapsed >= 2 - waitfor.elapsed <= 15 -- name: setup write keyword to file after 10s - shell: rm -f /tmp/wait_for_keyword && sleep 10 && echo completed > /tmp/wait_for_keyword +- name: setup write keyword to file after 3s + shell: sleep 3 && echo completed > {{output_dir}}/wait_for_keyword async: 20 poll: 0 - name: test wait for keyword in file wait_for: - path: /tmp/wait_for_keyword + path: "{{output_dir}}/wait_for_keyword" search_regex: completed - timeout: 20 + timeout: 5 register: waitfor -- name: verify test wait for port timeout + +- name: verify test wait for keyword in file assert: that: - waitfor is successful - "waitfor.search_regex == 'completed'" - - waitfor.elapsed >= 5 + - waitfor.elapsed >= 2 - waitfor.elapsed <= 15 +- name: setup write keyword to file after 3s + shell: sleep 3 && echo "completed data 123" > {{output_dir}}/wait_for_keyword + async: 20 + poll: 0 + +- name: test wait for keyword in file with match groups + wait_for: + path: "{{output_dir}}/wait_for_keyword" + search_regex: completed (?P\w+) ([0-9]+) + timeout: 5 + register: waitfor + +- name: verify test wait for keyword in file with match groups + assert: + that: + - waitfor is successful + - waitfor.elapsed >= 2 + - waitfor.elapsed <= 15 + - waitfor['match_groupdict'] | length == 1 + - waitfor['match_groupdict']['foo'] == 'data' + - waitfor['match_groups'] == ['data', '123'] + - name: test wait for port timeout wait_for: port: 12121 @@ -98,7 +121,7 @@ - "waitfor.msg == 'fail with custom message'" - name: setup start SimpleHTTPServer - shell: sleep 10 && cd {{ files_dir }} && {{ ansible_python.executable }} {{ output_dir}}/testserver.py {{ http_port }} + shell: sleep 3 && cd {{ files_dir }} && {{ ansible_python.executable }} {{ output_dir}}/testserver.py {{ http_port }} async: 120 # this test set can take ~1m to run on FreeBSD (via Shippable) poll: 0