diff --git a/changelogs/fragments/81083-add-blockinfile-append-and-prepend-new-line-options.yml b/changelogs/fragments/81083-add-blockinfile-append-and-prepend-new-line-options.yml new file mode 100644 index 00000000000..930f434c193 --- /dev/null +++ b/changelogs/fragments/81083-add-blockinfile-append-and-prepend-new-line-options.yml @@ -0,0 +1,2 @@ +minor_changes: + - blockinfile - add append_newline and prepend_newline options (https://github.com/ansible/ansible/issues/80835). \ No newline at end of file diff --git a/lib/ansible/modules/blockinfile.py b/lib/ansible/modules/blockinfile.py index e9feb7eed6a..b48b1820602 100644 --- a/lib/ansible/modules/blockinfile.py +++ b/lib/ansible/modules/blockinfile.py @@ -90,6 +90,22 @@ options: type: str default: END version_added: '2.5' + append_newline: + required: false + description: + - Append a blank line to the inserted block, if this does not appear at the end of the file. + - Note that this attribute is not considered when C(state) is set to C(absent) + type: bool + default: no + version_added: '2.16' + prepend_newline: + required: false + description: + - Prepend a blank line to the inserted block, if this does not appear at the beginning of the file. + - Note that this attribute is not considered when C(state) is set to C(absent) + type: bool + default: no + version_added: '2.16' notes: - When using 'with_*' loops be aware that if you do not set a unique mark the block will be overwritten on each iteration. - As of Ansible 2.3, the O(dest) option has been changed to O(path) as default, but O(dest) still works as well. @@ -117,9 +133,11 @@ attributes: EXAMPLES = r''' # Before Ansible 2.3, option 'dest' or 'name' was used instead of 'path' -- name: Insert/Update "Match User" configuration block in /etc/ssh/sshd_config +- name: Insert/Update "Match User" configuration block in /etc/ssh/sshd_config prepending and appending a new line ansible.builtin.blockinfile: path: /etc/ssh/sshd_config + append_newline: true + prepend_newline: true block: | Match User ansible-agent PasswordAuthentication no @@ -231,6 +249,8 @@ def main(): validate=dict(type='str'), marker_begin=dict(type='str', default='BEGIN'), marker_end=dict(type='str', default='END'), + append_newline=dict(type='bool', default=False), + prepend_newline=dict(type='bool', default=False), ), mutually_exclusive=[['insertbefore', 'insertafter']], add_file_common_args=True, @@ -274,6 +294,7 @@ def main(): block = to_bytes(params['block']) marker = to_bytes(params['marker']) present = params['state'] == 'present' + blank_line = [b(os.linesep)] if not present and not path_exists: module.exit_json(changed=False, msg="File %s not present" % path) @@ -337,7 +358,26 @@ def main(): if not lines[n0 - 1].endswith(b(os.linesep)): lines[n0 - 1] += b(os.linesep) + # Before the block: check if we need to prepend a blank line + # If yes, we need to add the blank line if we are at the beginning of the file + # or if the previous line is not a blank line + # In both cases, we need to shift by one on the right the inserting position of the block + if params['prepend_newline'] and present: + if n0 != 0 and lines[n0 - 1] != b(os.linesep): + lines[n0:n0] = blank_line + n0 += 1 + + # Insert the block lines[n0:n0] = blocklines + + # After the block: check if we need to append a blank line + # If yes, we need to add the blank line if we are at the end of the file + # or if the line right after is not a blank line + if params['append_newline'] and present: + line_after_block = n0 + len(blocklines) + if line_after_block < len(lines) and lines[line_after_block] != b(os.linesep): + lines[line_after_block:line_after_block] = blank_line + if lines: result = b''.join(lines) else: diff --git a/test/integration/targets/blockinfile/tasks/append_newline.yml b/test/integration/targets/blockinfile/tasks/append_newline.yml new file mode 100644 index 00000000000..ae3aef81ecf --- /dev/null +++ b/test/integration/targets/blockinfile/tasks/append_newline.yml @@ -0,0 +1,119 @@ +- name: Create append_newline test file + copy: + dest: "{{ remote_tmp_dir_test }}/append_newline.txt" + content: | + line1 + line2 + line3 + +- name: add content to file appending a new line + blockinfile: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + append_newline: true + insertafter: "line1" + block: | + line1.5 + register: insert_appending_a_new_line + +- name: add content to file appending a new line (again) + blockinfile: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + append_newline: true + insertafter: "line1" + block: | + line1.5 + register: insert_appending_a_new_line_again + +- name: get file content after adding content appending a new line + stat: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + register: appended_a_new_line + +- name: check content is the expected one after inserting content appending a new line + assert: + that: + - insert_appending_a_new_line is changed + - insert_appending_a_new_line_again is not changed + - appended_a_new_line.stat.checksum == "525ffd613a0b0eb6675e506226dc2adedf621f34" + +- name: add content to file without appending a new line + blockinfile: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + marker: "#{mark} UNWRAPPED TEXT" + insertafter: "line2" + block: | + line2.5 + register: insert_without_appending_new_line + +- name: get file content after adding content without appending a new line + stat: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + register: without_appending_new_line + +- name: check content is the expected one after inserting without appending a new line + assert: + that: + - insert_without_appending_new_line is changed + - without_appending_new_line.stat.checksum == "d5f5ed1428af50b5484a5184dc7e1afda1736646" + +- name: append a new line to existing block + blockinfile: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + append_newline: true + marker: "#{mark} UNWRAPPED TEXT" + insertafter: "line2" + block: | + line2.5 + register: append_new_line_to_existing_block + +- name: get file content after appending a line to existing block + stat: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + register: new_line_appended + +- name: check content is the expected one after appending a new line to an existing block + assert: + that: + - append_new_line_to_existing_block is changed + - new_line_appended.stat.checksum == "b09dd16be73a0077027d5a324294db8a75a7b0f9" + +- name: add a block appending a new line at the end of the file + blockinfile: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + append_newline: true + marker: "#{mark} END OF FILE TEXT" + insertafter: "line3" + block: | + line3.5 + register: insert_appending_new_line_at_the_end_of_file + +- name: get file content after appending new line at the end of the file + stat: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + register: inserted_block_appending_new_line_at_the_end_of_the_file + +- name: check content is the expected one after adding a block appending a new line at the end of the file + assert: + that: + - insert_appending_new_line_at_the_end_of_file is changed + - inserted_block_appending_new_line_at_the_end_of_the_file.stat.checksum == "9b90722b84d9bdda1be781cc4bd44d8979887691" + + +- name: Removing a block with append_newline set to true does not append another line + blockinfile: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + append_newline: true + marker: "#{mark} UNWRAPPED TEXT" + state: absent + register: remove_block_appending_new_line + +- name: get file content after removing existing block appending new line + stat: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + register: removed_block_appending_new_line + +- name: check content is the expected one after removing a block appending a new line + assert: + that: + - remove_block_appending_new_line is changed + - removed_block_appending_new_line.stat.checksum == "9a40d4c0969255cd6147537b38309d69a9b10049" diff --git a/test/integration/targets/blockinfile/tasks/main.yml b/test/integration/targets/blockinfile/tasks/main.yml index 054e55497af..b7f5161c8a5 100644 --- a/test/integration/targets/blockinfile/tasks/main.yml +++ b/test/integration/targets/blockinfile/tasks/main.yml @@ -39,3 +39,5 @@ - import_tasks: insertafter.yml - import_tasks: insertbefore.yml - import_tasks: multiline_search.yml +- import_tasks: append_newline.yml +- import_tasks: prepend_newline.yml diff --git a/test/integration/targets/blockinfile/tasks/prepend_newline.yml b/test/integration/targets/blockinfile/tasks/prepend_newline.yml new file mode 100644 index 00000000000..535db017722 --- /dev/null +++ b/test/integration/targets/blockinfile/tasks/prepend_newline.yml @@ -0,0 +1,119 @@ +- name: Create prepend_newline test file + copy: + dest: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + content: | + line1 + line2 + line3 + +- name: add content to file prepending a new line at the beginning of the file + blockinfile: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + prepend_newline: true + insertbefore: "line1" + block: | + line0.5 + register: insert_prepending_a_new_line_at_the_beginning_of_the_file + +- name: get file content after adding content prepending a new line at the beginning of the file + stat: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + register: prepended_a_new_line_at_the_beginning_of_the_file + +- name: check content is the expected one after prepending a new line at the beginning of the file + assert: + that: + - insert_prepending_a_new_line_at_the_beginning_of_the_file is changed + - prepended_a_new_line_at_the_beginning_of_the_file.stat.checksum == "bfd32c880bbfadd1983c67836c46bf8ed9d50343" + +- name: add content to file prepending a new line + blockinfile: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + prepend_newline: true + marker: "#{mark} WRAPPED TEXT" + insertafter: "line1" + block: | + line1.5 + register: insert_prepending_a_new_line + +- name: add content to file prepending a new line (again) + blockinfile: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + prepend_newline: true + marker: "#{mark} WRAPPED TEXT" + insertafter: "line1" + block: | + line1.5 + register: insert_prepending_a_new_line_again + +- name: get file content after adding content prepending a new line + stat: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + register: prepended_a_new_line + +- name: check content is the expected one after inserting content prepending a new line + assert: + that: + - insert_prepending_a_new_line is changed + - insert_prepending_a_new_line_again is not changed + - prepended_a_new_line.stat.checksum == "d5b8b42690f4a38b9a040adc3240a6f81ad5f8ee" + +- name: add content to file without prepending a new line + blockinfile: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + marker: "#{mark} UNWRAPPED TEXT" + insertafter: "line3" + block: | + line3.5 + register: insert_without_prepending_new_line + +- name: get file content after adding content without prepending a new line + stat: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + register: without_prepending_new_line + +- name: check content is the expected one after inserting without prepending a new line + assert: + that: + - insert_without_prepending_new_line is changed + - without_prepending_new_line.stat.checksum == "ad06200e7ee5b22b7eff4c57075b42d038eaffb6" + +- name: prepend a new line to existing block + blockinfile: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + prepend_newline: true + marker: "#{mark} UNWRAPPED TEXT" + insertafter: "line3" + block: | + line3.5 + register: prepend_new_line_to_existing_block + +- name: get file content after prepending a new line to an existing block + stat: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + register: new_line_prepended + +- name: check content is the expected one after prepending a new line to an existing block + assert: + that: + - prepend_new_line_to_existing_block is changed + - new_line_prepended.stat.checksum == "f2dd48160fb3c7c8e02d292666a1a3f08503f6bf" + +- name: Removing a block with prepend_newline set to true does not prepend another line + blockinfile: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + prepend_newline: true + marker: "#{mark} UNWRAPPED TEXT" + state: absent + register: remove_block_prepending_new_line + +- name: get file content after removing existing block prepending new line + stat: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + register: removed_block_prepending_new_line + +- name: check content is the expected one after removing a block prepending a new line + assert: + that: + - remove_block_prepending_new_line is changed + - removed_block_prepending_new_line.stat.checksum == "c97c3da7d607acfd5d786fbb81f3d93d867c914a" \ No newline at end of file