From 74eb8b779d93977a3f231abe321150711d311519 Mon Sep 17 00:00:00 2001 From: OscarBell <34867484+OscarBell@users.noreply.github.com> Date: Wed, 25 May 2022 22:39:12 +0200 Subject: [PATCH] Blockinfile multiline search (#75090) --- ...multiline-flag-support-for-blockinfile.yml | 2 + lib/ansible/modules/blockinfile.py | 28 ++++++-- .../targets/blockinfile/tasks/main.yml | 1 + .../blockinfile/tasks/multiline_search.yml | 70 +++++++++++++++++++ 4 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/75090-multiline-flag-support-for-blockinfile.yml create mode 100644 test/integration/targets/blockinfile/tasks/multiline_search.yml diff --git a/changelogs/fragments/75090-multiline-flag-support-for-blockinfile.yml b/changelogs/fragments/75090-multiline-flag-support-for-blockinfile.yml new file mode 100644 index 00000000000..69a43760963 --- /dev/null +++ b/changelogs/fragments/75090-multiline-flag-support-for-blockinfile.yml @@ -0,0 +1,2 @@ +minor_changes: + - blockinfile - The presence of the multiline flag (?m) in the regular expression for insertafter opr insertbefore controls whether the match is done line by line or with multiple lines (https://github.com/ansible/ansible/pull/75090). \ No newline at end of file diff --git a/lib/ansible/modules/blockinfile.py b/lib/ansible/modules/blockinfile.py index 45b80084ac8..2f914418477 100644 --- a/lib/ansible/modules/blockinfile.py +++ b/lib/ansible/modules/blockinfile.py @@ -50,6 +50,8 @@ options: - If specified and no begin/ending C(marker) lines are found, the block will be inserted after the last match of specified regular expression. - A special value is available; C(EOF) for inserting the block at the end of the file. - If specified regular expression has no matches, C(EOF) will be used instead. + - The presence of the multiline flag (?m) in the regular expression controls whether the match is done line by line or with multiple lines. + This behaviour was added in ansible-core 2.14. type: str choices: [ EOF, '*regex*' ] default: EOF @@ -58,6 +60,8 @@ options: - If specified and no begin/ending C(marker) lines are found, the block will be inserted before the last match of specified regular expression. - A special value is available; C(BOF) for inserting the block at the beginning of the file. - If specified regular expression has no matches, the block will be inserted at the end of the file. + - The presence of the multiline flag (?m) in the regular expression controls whether the match is done line by line or with multiple lines. + This behaviour was added in ansible-core 2.14. type: str choices: [ BOF, '*regex*' ] create: @@ -158,6 +162,14 @@ EXAMPLES = r''' - { name: host1, ip: 10.10.1.10 } - { name: host2, ip: 10.10.1.11 } - { name: host3, ip: 10.10.1.12 } + +- name: Search with a multiline search flags regex and if found insert after + blockinfile: + path: listener.ora + block: "{{ listener_line | indent(width=8, first=True) }}" + insertafter: '(?m)SID_LIST_LISTENER_DG =\n.*\(SID_LIST =' + marker: " " + ''' import re @@ -165,7 +177,7 @@ import os import tempfile from ansible.module_utils.six import b from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_bytes +from ansible.module_utils._text import to_bytes, to_native def write_changes(module, contents, path): @@ -292,9 +304,17 @@ def main(): if None in (n0, n1): n0 = None if insertre is not None: - for i, line in enumerate(lines): - if insertre.search(line): - n0 = i + if insertre.flags & re.MULTILINE: + match = insertre.search(original) + if match: + if insertafter: + n0 = to_native(original).count('\n', 0, match.end()) + elif insertbefore: + n0 = to_native(original).count('\n', 0, match.start()) + else: + for i, line in enumerate(lines): + if insertre.search(line): + n0 = i if n0 is None: n0 = len(lines) elif insertafter is not None: diff --git a/test/integration/targets/blockinfile/tasks/main.yml b/test/integration/targets/blockinfile/tasks/main.yml index 03507684db1..054e55497af 100644 --- a/test/integration/targets/blockinfile/tasks/main.yml +++ b/test/integration/targets/blockinfile/tasks/main.yml @@ -38,3 +38,4 @@ - import_tasks: validate.yml - import_tasks: insertafter.yml - import_tasks: insertbefore.yml +- import_tasks: multiline_search.yml diff --git a/test/integration/targets/blockinfile/tasks/multiline_search.yml b/test/integration/targets/blockinfile/tasks/multiline_search.yml new file mode 100644 index 00000000000..8eb94f579d1 --- /dev/null +++ b/test/integration/targets/blockinfile/tasks/multiline_search.yml @@ -0,0 +1,70 @@ +- name: Create multiline_search test file + copy: + dest: "{{ remote_tmp_dir }}/listener.ora" + content: | + LISTENER=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=IPC)(KEY=LISTENER)))) # line added by Agent + ENABLE_GLOBAL_DYNAMIC_ENDPOINT_LISTENER=ON # line added by Agent + + SID_LIST_LISTENER_DG = + (SID_LIST = + (SID_DESC = + (GLOBAL_DBNAME = DB01_DG) + (ORACLE_HOME = /u01/app/oracle/product/12.1.0.1/db_1) + (SID_NAME = DB011) + ) + ) + + SID_LIST_LISTENER = + (SID_LIST = + (SID_DESC = + (GLOBAL_DBNAME = DB02) + (ORACLE_HOME = /u01/app/oracle/product/12.1.0.1/db_1) + (SID_NAME = DB021) + ) + ) + +- name: Set fact listener_line + set_fact: + listener_line: | + (SID_DESC = + (GLOBAL_DBNAME = DB03 + (ORACLE_HOME = /u01/app/oracle/product/12.1.0.1/db_1) + (SID_NAME = DB031) + ) + +- name: Add block using multiline_search enabled + blockinfile: + path: "{{ remote_tmp_dir }}/listener.ora" + block: "{{ listener_line }}" + insertafter: '(?m)SID_LIST_LISTENER_DG =\n.*\(SID_LIST =' + marker: " " + register: multiline_search1 + +- name: Add block using multiline_search enabled again + blockinfile: + path: "{{ remote_tmp_dir }}/listener.ora" + block: "{{ listener_line }}" + insertafter: '(?m)SID_LIST_LISTENER_DG =\n.*\(SID_LIST =' + marker: " " + register: multiline_search2 + +- name: Try to add block using without multiline flag in regex should add block add end of file + blockinfile: + path: "{{ remote_tmp_dir }}/listener.ora" + block: "{{ listener_line }}" + insertafter: 'SID_LIST_LISTENER_DG =\n.*\(SID_LIST =' + marker: " " + register: multiline_search3 + +- name: Stat the listener.ora file + stat: + path: "{{ remote_tmp_dir }}/listener.ora" + register: listener_ora_file + +- name: Ensure insertafter worked correctly + assert: + that: + - multiline_search1 is changed + - multiline_search2 is not changed + - multiline_search3 is changed + - listener_ora_file.stat.checksum == '5a8010ac4a2fad7c822e6aeb276931657cee75c0'