blockinfile - properly insert block when no trailing new line exists (#72350)
This commit is contained in:
parent
233e7beb5b
commit
c51438312a
12 changed files with 309 additions and 142 deletions
|
@ -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)
|
|
@ -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:
|
||||||
|
|
|
@ -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'
|
|
@ -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'
|
32
test/integration/targets/blockinfile/tasks/create_file.yml
Normal file
32
test/integration/targets/blockinfile/tasks/create_file.yml
Normal 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
|
18
test/integration/targets/blockinfile/tasks/diff.yml
Normal file
18
test/integration/targets/blockinfile/tasks/diff.yml
Normal 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
|
|
@ -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'
|
37
test/integration/targets/blockinfile/tasks/insertafter.yml
Normal file
37
test/integration/targets/blockinfile/tasks/insertafter.yml
Normal 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'
|
39
test/integration/targets/blockinfile/tasks/insertbefore.yml
Normal file
39
test/integration/targets/blockinfile/tasks/insertbefore.yml
Normal 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'
|
|
@ -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'
|
|
||||||
|
|
|
@ -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"'
|
28
test/integration/targets/blockinfile/tasks/validate.yml
Normal file
28
test/integration/targets/blockinfile/tasks/validate.yml
Normal 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
|
Loading…
Reference in a new issue