blockinfile - properly insert block when no trailing new line exists (#72350)

This commit is contained in:
Sam Doran 2020-10-27 11:34:09 -04:00 committed by GitHub
parent 233e7beb5b
commit c51438312a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 309 additions and 142 deletions

View file

@ -0,0 +1,4 @@
bugfixes:
- >
blockinfile - properly insert a block at the end of a file that does not
have a trailing newline character (https://github.com/ansible/ansible/issues/72055)

View file

@ -209,7 +209,6 @@ def main():
add_file_common_args=True, add_file_common_args=True,
supports_check_mode=True supports_check_mode=True
) )
params = module.params params = module.params
path = params['path'] path = params['path']
@ -302,8 +301,12 @@ def main():
lines[n1:n0 + 1] = [] lines[n1:n0 + 1] = []
n0 = n1 n0 = n1
lines[n0:n0] = blocklines # Ensure there is a line separator before the block of lines to be inserted
if n0 > 0:
if not lines[n0 - 1].endswith(b(os.linesep)):
lines[n0 - 1] += b(os.linesep)
lines[n0:n0] = blocklines
if lines: if lines:
result = b''.join(lines) result = b''.join(lines)
else: else:

View file

@ -0,0 +1,47 @@
- name: copy the sshd_config to the test dir
copy:
src: sshd_config
dest: "{{ output_dir_test }}"
- name: insert/update "Match User" configuration block in sshd_config
blockinfile:
path: "{{ output_dir_test }}/sshd_config"
block: |
Match User ansible-agent
PasswordAuthentication no
backup: yes
register: blockinfile_test0
- name: check content
shell: 'grep -c -e "Match User ansible-agent" -e "PasswordAuthentication no" {{ output_dir_test }}/sshd_config'
register: blockinfile_test0_grep
- debug:
var: blockinfile_test0
verbosity: 1
- debug:
var: blockinfile_test0_grep
verbosity: 1
- name: validate first example results
assert:
that:
- 'blockinfile_test0.changed is defined'
- 'blockinfile_test0.msg is defined'
- 'blockinfile_test0.changed'
- 'blockinfile_test0.msg == "Block inserted"'
- 'blockinfile_test0_grep.stdout == "2"'
- name: check idemptotence
blockinfile:
path: "{{ output_dir_test }}/sshd_config"
block: |
Match User ansible-agent
PasswordAuthentication no
register: blockinfile_test1
- name: validate idempotence results
assert:
that:
- 'not blockinfile_test1.changed'

View file

@ -0,0 +1,30 @@
- name: Add block without trailing line separator
blockinfile:
path: "{{ output_dir_test }}/chomped_block_test.txt"
create: yes
content: |-
one
two
three
register: chomptest1
- name: Add block without trailing line separator again
blockinfile:
path: "{{ output_dir_test }}/chomped_block_test.txt"
content: |-
one
two
three
register: chomptest2
- name: Check output file
stat:
path: "{{ output_dir_test }}/chomped_block_test.txt"
register: chomptest_file
- name: Ensure chomptest results are correct
assert:
that:
- chomptest1 is changed
- chomptest2 is not changed
- chomptest_file.stat.checksum == '50d49f528a5f7147c7029ed6220c326b1ee2c4ae'

View file

@ -0,0 +1,32 @@
- name: Create a file with blockinfile
blockinfile:
path: "{{ output_dir_test }}/empty.txt"
block: |
Hey
there
state: present
create: yes
register: empty_test_1
- name: Run a task that results in an empty file
blockinfile:
path: "{{ output_dir_test }}/empty.txt"
block: |
Hey
there
state: absent
create: yes
register: empty_test_2
- stat:
path: "{{ output_dir_test }}/empty.txt"
register: empty_test_stat
- name: Ensure empty file was created
assert:
that:
- empty_test_1 is changed
- "'File created' in empty_test_1.msg"
- empty_test_2 is changed
- "'Block removed' in empty_test_2.msg"
- empty_test_stat.stat.size == 0

View file

@ -0,0 +1,18 @@
- name: Create a test file
copy:
content: diff test
dest: "{{ output_dir_test }}/diff.txt"
- name: Add block to file with diff
blockinfile:
path: "{{ output_dir_test }}/diff.txt"
block: |
line 1
line 2
register: difftest
diff: yes
- name: Ensure diff was shown
assert:
that:
- difftest.diff | length > 0

View file

@ -0,0 +1,36 @@
- name: Create file without trailing newline
copy:
content: '# File with no newline'
dest: "{{ output_dir_test }}/no_newline_at_end.txt"
register: no_newline
- name: Add block to file that does not have a newline at the end
blockinfile:
path: "{{ output_dir_test }}/no_newline_at_end.txt"
content: |
one
two
three
register: no_newline_test1
- name: Add block to file that does not have a newline at the end again
blockinfile:
path: "{{ output_dir_test }}/no_newline_at_end.txt"
content: |
one
two
three
register: no_newline_test2
- name: Stat the file
stat:
path: "{{ output_dir_test }}/no_newline_at_end.txt"
register: no_newline_file
- name: Ensure block was correctly written to file with no newline at end
assert:
that:
- no_newline_test1 is changed
- no_newline_test2 is not changed
- no_newline_file.stat.checksum == 'dab16f864025e59125e74d1498ffb2bb048224e6'

View file

@ -0,0 +1,37 @@
- name: Create insertafter test file
copy:
dest: "{{ output_dir }}/after.txt"
content: |
line1
line2
line3
- name: Add block using insertafter
blockinfile:
path: "{{ output_dir }}/after.txt"
insertafter: line2
block: |
block1
block2
register: after1
- name: Add block using insertafter again
blockinfile:
path: "{{ output_dir }}/after.txt"
insertafter: line2
block: |
block1
block2
register: after2
- name: Stat the after.txt file
stat:
path: "{{ output_dir }}/after.txt"
register: after_file
- name: Ensure insertafter worked correctly
assert:
that:
- after1 is changed
- after2 is not changed
- after_file.stat.checksum == 'a8adeb971358230a28ce554f3b8fdd1ef65fdf1c'

View file

@ -0,0 +1,39 @@
- name: Create insertbefore test file
copy:
dest: "{{ output_dir }}/before.txt"
content: |
line1
line2
line3
- name: Add block using insertbefore
blockinfile:
path: "{{ output_dir }}/before.txt"
insertbefore: line2
block: |
block1
block2
register: after1
- name: Add block using insertbefore again
blockinfile:
path: "{{ output_dir }}/before.txt"
insertbefore: line2
block: |
block1
block2
register: after2
- name: Stat the before.txt file
stat:
path: "{{ output_dir }}/before.txt"
register: after_file
- command: cat {{ output_dir }}/before.txt
- name: Ensure insertbefore worked correctly
assert:
that:
- after1 is changed
- after2 is not changed
- after_file.stat.checksum == '16681d1d7f29d173243bb951d6afb9c0824d7bf4'

View file

@ -29,143 +29,12 @@
path: "{{ output_dir_test }}" path: "{{ output_dir_test }}"
state: directory state: directory
## - import_tasks: add_block_to_existing_file.yml
## blockinfile - import_tasks: create_file.yml
## - import_tasks: preserve_line_endings.yml
- import_tasks: block_without_trailing_newline.yml
- name: copy the sshd_config to the test dir - import_tasks: file_without_trailing_newline.yml
copy: - import_tasks: diff.yml
src: sshd_config - import_tasks: validate.yml
dest: "{{ output_dir_test }}" - import_tasks: insertafter.yml
- import_tasks: insertbefore.yml
- name: insert/update "Match User" configuration block in sshd_config
blockinfile:
path: "{{ output_dir_test }}/sshd_config"
block: |
Match User ansible-agent
PasswordAuthentication no
register: blockinfile_test0
- name: check content
shell: 'grep -c -e "Match User ansible-agent" -e "PasswordAuthentication no" {{ output_dir_test }}/sshd_config'
register: blockinfile_test0_grep
- debug:
var: blockinfile_test0
verbosity: 1
- debug:
var: blockinfile_test0_grep
verbosity: 1
- name: validate first example results
assert:
that:
- 'blockinfile_test0.changed is defined'
- 'blockinfile_test0.msg is defined'
- 'blockinfile_test0.changed'
- 'blockinfile_test0.msg == "Block inserted"'
- 'blockinfile_test0_grep.stdout == "2"'
- name: check idemptotence
blockinfile:
path: "{{ output_dir_test }}/sshd_config"
block: |
Match User ansible-agent
PasswordAuthentication no
register: blockinfile_test1
- name: validate idempotence results
assert:
that:
- 'not blockinfile_test1.changed'
- name: Create a file with blockinfile
blockinfile:
path: "{{ output_dir_test }}/empty.txt"
block: |
Hey
there
state: present
create: yes
register: empty_test_1
- name: Run a task that results in an empty file
blockinfile:
path: "{{ output_dir_test }}/empty.txt"
block: |
Hey
there
state: absent
create: yes
register: empty_test_2
- stat:
path: "{{ output_dir_test }}/empty.txt"
register: empty_test_stat
- name: Ensure empty file was created
assert:
that:
- empty_test_1 is changed
- "'File created' in empty_test_1.msg"
- empty_test_2 is changed
- "'Block removed' in empty_test_2.msg"
- empty_test_stat.stat.size == 0
- name: create line_endings_test.txt in the test dir
copy:
dest: "{{ output_dir_test }}/line_endings_test.txt"
# generating the content like this instead of copying a fixture file
# prevents sanity checks from warning about mixed line endings
content: "unix\nunix\nunix\n\ndos\r\ndos\r\ndos\r\n\nunix\nunix\n# BEGIN ANSIBLE MANAGED BLOCK\ndos\r\n# END ANSIBLE MANAGED BLOCK\nunix\nunix\nunix\nunix\n"
- name: insert/update "dos" configuration block in line_endings_test.txt
blockinfile:
path: "{{ output_dir_test }}/line_endings_test.txt"
block: "dos\r\ndos\r\ndos\r\n"
register: blockinfile_test2
- name: check content
# using the more precise `grep -Pc "^dos\\r$" ...` fails on BSD/macOS
shell: 'grep -c "^dos.$" {{ output_dir_test }}/line_endings_test.txt'
register: blockinfile_test2_grep
- name: validate line_endings_test.txt results
assert:
that:
- 'blockinfile_test2 is changed'
- 'blockinfile_test2.msg == "Block inserted"'
- 'blockinfile_test2_grep.stdout == "6"'
- name: Add block without trailing line separator
blockinfile:
path: "{{ output_dir_test }}/chomped_block_test.txt"
create: yes
content: |-
one
two
three
register: chomptest1
- name: Add block without trailing line separator again
blockinfile:
path: "{{ output_dir_test }}/chomped_block_test.txt"
content: |-
one
two
three
register: chomptest2
- name: Check output file
stat:
path: "{{ output_dir_test }}/chomped_block_test.txt"
register: chomptest_file
- name: Ensure chomptest results are correct
assert:
that:
- chomptest1 is changed
- chomptest2 is not changed
- chomptest_file.stat.checksum == '50d49f528a5f7147c7029ed6220c326b1ee2c4ae'

View file

@ -0,0 +1,24 @@
- name: create line_endings_test.txt in the test dir
copy:
dest: "{{ output_dir_test }}/line_endings_test.txt"
# generating the content like this instead of copying a fixture file
# prevents sanity checks from warning about mixed line endings
content: "unix\nunix\nunix\n\ndos\r\ndos\r\ndos\r\n\nunix\nunix\n# BEGIN ANSIBLE MANAGED BLOCK\ndos\r\n# END ANSIBLE MANAGED BLOCK\nunix\nunix\nunix\nunix\n"
- name: insert/update "dos" configuration block in line_endings_test.txt
blockinfile:
path: "{{ output_dir_test }}/line_endings_test.txt"
block: "dos\r\ndos\r\ndos\r\n"
register: blockinfile_test2
- name: check content
# using the more precise `grep -Pc "^dos\\r$" ...` fails on BSD/macOS
shell: 'grep -c "^dos.$" {{ output_dir_test }}/line_endings_test.txt'
register: blockinfile_test2_grep
- name: validate line_endings_test.txt results
assert:
that:
- 'blockinfile_test2 is changed'
- 'blockinfile_test2.msg == "Block inserted"'
- 'blockinfile_test2_grep.stdout == "6"'

View file

@ -0,0 +1,28 @@
- name: EXPECTED FAILURE test improper validate
blockinfile:
path: "{{ output_dir }}/validate.txt"
block: |
line1
line2
create: yes
validate: grep
ignore_errors: yes
- name: EXPECTED FAILURE test failure to validate
blockinfile:
path: "{{ output_dir }}/validate.txt"
block: |
line1
line2
create: yes
validate: grep line47 %s
ignore_errors: yes
- name: Test proper validate
blockinfile:
path: "{{ output_dir }}/validate.txt"
block: |
line1
line2
create: yes
validate: grep line1 %s