diff --git a/changelogs/fragments/get_url_regex.yml b/changelogs/fragments/get_url_regex.yml new file mode 100644 index 00000000000..3f2d5259fc7 --- /dev/null +++ b/changelogs/fragments/get_url_regex.yml @@ -0,0 +1,3 @@ +--- +bugfixes: + - get_url - fix regex for GNU Digest line which is used in comparing checksums (https://github.com/ansible/ansible/issues/86132). diff --git a/lib/ansible/modules/get_url.py b/lib/ansible/modules/get_url.py index b679d08d9c4..39278fc2111 100644 --- a/lib/ansible/modules/get_url.py +++ b/lib/ansible/modules/get_url.py @@ -477,17 +477,13 @@ def is_url(checksum): return urlsplit(checksum).scheme in supported_schemes -def parse_digest_lines(filename, lines): +def parse_digest_lines(filename: str, lines: list[str]) -> list[tuple[str, str]]: """Returns a list of tuple containing the filename and digest depending upon the lines provided - - Args: - filename (str): Name of the filename, used only when the digest is one-liner - lines (list): A list of lines containing filenames and checksums """ checksum_map = [] BSD_DIGEST_LINE = re.compile(r'^(\w+) ?\((?P.+)\) ?= (?P[\w.]+)$') - GNU_DIGEST_LINE = re.compile(r'^(?P[\w.]+) ([ *])(?P.+)$') + GNU_DIGEST_LINE = re.compile(r'^(?P[\w.]+)\s+(\*|\.\/|\.)?(?P.+)$') if len(lines) == 1 and len(lines[0].split()) == 1: # Only a single line with a single string diff --git a/test/integration/targets/get_url/tasks/main.yml b/test/integration/targets/get_url/tasks/main.yml index b7debc902fe..f03fb87fca1 100644 --- a/test/integration/targets/get_url/tasks/main.yml +++ b/test/integration/targets/get_url/tasks/main.yml @@ -1,20 +1,6 @@ # Test code for the get_url module # (c) 2014, Richard Isaacson - -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - name: Determine if python looks like it will support modern ssl features like SNI command: "{{ ansible_python.executable }} -c 'from ssl import SSLContext'" @@ -332,13 +318,13 @@ - name: create src file copy: - dest: '{{ files_dir }}/27617.txt' - content: "ptux" - -- name: create duplicate src file - copy: - dest: '{{ files_dir }}/71420.txt' + dest: '{{ files_dir }}/{{ item }}.txt' content: "ptux" + loop: + - 27617 + - 71420 + - 86132 + - 86132_single_space - name: create sha1 checksum file of src copy: @@ -346,6 +332,8 @@ content: | a97e6837f60cec6da4491bab387296bbcd72bdba 27617.txt a97e6837f60cec6da4491bab387296bbcd72bdba 71420.txt + a97e6837f60cec6da4491bab387296bbcd72bdba 86132.txt + a97e6837f60cec6da4491bab387296bbcd72bdba 86132_single_space.txt 3911340502960ca33aece01129234460bfeb2791 not_target1.txt 1b4b6adf30992cedb0f6edefd6478ff0a593b2e4 not_target2.txt @@ -355,6 +343,8 @@ content: | b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. 27617.txt b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. 71420.txt + b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. 86132.txt + b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. 86132_single_space.txt 30949cc401e30ac494d695ab8764a9f76aae17c5d73c67f65e9b558f47eff892 not_target1.txt d0dbfc1945bc83bf6606b770e442035f2c4e15c886ee0c22fb3901ba19900b5b not_target2.txt @@ -364,6 +354,8 @@ content: | b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. ./27617.txt b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. ./71420.txt + b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. ./86132.txt + b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. ./86132_single_space.txt 30949cc401e30ac494d695ab8764a9f76aae17c5d73c67f65e9b558f47eff892 ./not_target1.txt d0dbfc1945bc83bf6606b770e442035f2c4e15c886ee0c22fb3901ba19900b5b ./not_target2.txt @@ -373,6 +365,8 @@ content: | b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. *27617.txt b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. *71420.txt + b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. *86132.txt + b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. *86132_single_space.txt 30949cc401e30ac494d695ab8764a9f76aae17c5d73c67f65e9b558f47eff892 *not_target1.txt d0dbfc1945bc83bf6606b770e442035f2c4e15c886ee0c22fb3901ba19900b5b *not_target2.txt @@ -551,6 +545,61 @@ path: "{{ remote_tmp_dir }}/27617.txt" register: stat_result_sha256_checksum_only +- name: download 86132.txt with sha1 checksum url + get_url: + url: 'http://localhost:{{ http_port }}/86132.txt' + dest: '{{ remote_tmp_dir }}' + checksum: 'sha1:http://localhost:{{ http_port }}/sha1sum.txt' + register: result_sha1_86132 + +- stat: + path: "{{ remote_tmp_dir }}/86132.txt" + register: stat_result_sha1_86132 + +- name: download 86132.txt with sha256 checksum url + get_url: + url: 'http://localhost:{{ http_port }}/86132.txt' + dest: '{{ remote_tmp_dir }}/86132sha256.txt' + checksum: 'sha256:http://localhost:{{ http_port }}/sha256sum.txt' + register: result_sha256_86132 + +- stat: + path: "{{ remote_tmp_dir }}/86132.txt" + register: stat_result_sha256_86132 + +- name: download 86132.txt with sha256 checksum url with dot leading paths + get_url: + url: 'http://localhost:{{ http_port }}/86132.txt' + dest: '{{ remote_tmp_dir }}/86132sha256_with_dot.txt' + checksum: 'sha256:http://localhost:{{ http_port }}/sha256sum_with_dot.txt' + register: result_sha256_with_dot_86132 + +- stat: + path: "{{ remote_tmp_dir }}/86132sha256_with_dot.txt" + register: stat_result_sha256_with_dot_86132 + +- name: download 86132.txt with sha256 checksum url with asterisk leading paths + get_url: + url: 'http://localhost:{{ http_port }}/86132.txt' + dest: '{{ remote_tmp_dir }}/86132sha256_with_asterisk.txt' + checksum: 'sha256:http://localhost:{{ http_port }}/sha256sum_with_asterisk.txt' + register: result_sha256_with_asterisk_86132 + +- stat: + path: "{{ remote_tmp_dir }}/86132sha256_with_asterisk.txt" + register: stat_result_sha256_with_asterisk_86132 + +- name: download 86132_single_space.txt with sha256 checksum url + get_url: + url: 'http://localhost:{{ http_port }}/86132_single_space.txt' + dest: '{{ remote_tmp_dir }}/86132_single_space.txt' + checksum: 'sha256:http://localhost:{{ http_port }}/sha256sum.txt' + register: result_sha256_86132_single_space + +- stat: + path: "{{ remote_tmp_dir }}/86132_single_space.txt" + register: stat_result_sha256_86132_single_space + - name: Assert that the file was downloaded assert: that: @@ -579,6 +628,16 @@ - "stat_result_sha256_with_asterisk_71420.stat.exists == true" - "stat_result_sha256_with_file_scheme_71420.stat.exists == true" - "stat_result_sha256_checksum_only.stat.exists == true" + - result_sha1_86132 is changed + - result_sha256_86132 is changed + - result_sha256_with_dot_86132 is changed + - result_sha256_with_asterisk_86132 is changed + - "stat_result_sha1_86132.stat.exists == true" + - "stat_result_sha256_86132.stat.exists == true" + - "stat_result_sha256_with_dot_86132.stat.exists == true" + - "stat_result_sha256_with_asterisk_86132.stat.exists == true" + - result_sha256_86132_single_space is changed + - "stat_result_sha256_86132_single_space.stat.exists == true" - name: Test for incomplete data read (issue 85164) get_url: diff --git a/test/units/modules/test_get_url.py b/test/units/modules/test_get_url.py index 9e096341931..459933a2f81 100644 --- a/test/units/modules/test_get_url.py +++ b/test/units/modules/test_get_url.py @@ -8,22 +8,59 @@ import pytest from ansible.modules.get_url import parse_digest_lines +FILENAME = "sample.txt" + @pytest.mark.parametrize( ("lines", "expected"), [ + pytest.param( + [ + "2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25 sample.txt", + ], + [("2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25", FILENAME)], + id="single-line-digest-single-space", + ), + pytest.param( + [ + "2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25 sample.txt", + ], + [("2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25", FILENAME)], + id="single-line-digest-multiple-spaces", + ), + pytest.param( + [ + "2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25 .sample.txt", + ], + [("2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25", FILENAME)], + id="single-line-digest-multiple-spaces-with-dot", + ), + pytest.param( + [ + "2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25 *sample.txt", + ], + [("2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25", FILENAME)], + id="single-line-digest-multiple-spaces-with-asterisk", + ), + pytest.param( + [ + "2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25 ./sample.txt", + ], + [("2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25", FILENAME)], + id="single-line-digest-multiple-spaces-with-dot-and-slash", + ), pytest.param( [ "a97e6837f60cec6da4491bab387296bbcd72bdba", ], - [("a97e6837f60cec6da4491bab387296bbcd72bdba", "sample.txt")], + [("a97e6837f60cec6da4491bab387296bbcd72bdba", FILENAME)], id="single-line-digest", ), pytest.param( [ "a97e6837f60cec6da4491bab387296bbcd72bdba sample.txt", ], - [("a97e6837f60cec6da4491bab387296bbcd72bdba", "sample.txt")], + [("a97e6837f60cec6da4491bab387296bbcd72bdba", FILENAME)], id="GNU-style-digest", ), pytest.param( @@ -33,7 +70,7 @@ from ansible.modules.get_url import parse_digest_lines [ ( "b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006.", - "sample.txt", + FILENAME, ) ], id="BSD-style-digest", @@ -41,5 +78,4 @@ from ansible.modules.get_url import parse_digest_lines ], ) def test_parse_digest_lines(lines, expected): - filename = "sample.txt" - assert parse_digest_lines(filename, lines) == expected + assert parse_digest_lines(filename=FILENAME, lines=lines) == expected