From 56b67cccc52312366b9ceed02a6906452864e04d Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Thu, 15 Jun 2023 16:15:11 -0500 Subject: [PATCH] Resolve issues on python pre-3.10.6 with collection dirs longer than 100 characters (#81061) --- .../fragments/long-collection-paths-fix.yml | 2 ++ lib/ansible/galaxy/collection/__init__.py | 27 +++++++++---------- .../cli/galaxy/test_collection_extract_tar.py | 15 ++--------- 3 files changed, 16 insertions(+), 28 deletions(-) create mode 100644 changelogs/fragments/long-collection-paths-fix.yml diff --git a/changelogs/fragments/long-collection-paths-fix.yml b/changelogs/fragments/long-collection-paths-fix.yml new file mode 100644 index 00000000000..47a8c5c25a8 --- /dev/null +++ b/changelogs/fragments/long-collection-paths-fix.yml @@ -0,0 +1,2 @@ +bugfixes: +- ansible-galaxy - Fix issue installing collections containing directories with more than 100 characters on python versions before 3.10.6 diff --git a/lib/ansible/galaxy/collection/__init__.py b/lib/ansible/galaxy/collection/__init__.py index d591f909984..672c65795aa 100644 --- a/lib/ansible/galaxy/collection/__init__.py +++ b/lib/ansible/galaxy/collection/__init__.py @@ -1561,6 +1561,13 @@ def install_artifact(b_coll_targz_path, b_collection_path, b_temp_path, signatur """ try: with tarfile.open(b_coll_targz_path, mode='r') as collection_tar: + # Remove this once py3.11 is our controller minimum + # Workaround for https://bugs.python.org/issue47231 + # See _extract_tar_dir + collection_tar._ansible_normalized_cache = { + m.name.removesuffix(os.path.sep): m for m in collection_tar.getmembers() + } # deprecated: description='TarFile member index' core_version='2.18' python_version='3.11' + # Verify the signature on the MANIFEST.json before extracting anything else _extract_tar_file(collection_tar, MANIFEST_FILENAME, b_collection_path, b_temp_path) @@ -1641,22 +1648,12 @@ def install_src(collection, b_collection_path, b_collection_output_path, artifac def _extract_tar_dir(tar, dirname, b_dest): """ Extracts a directory from a collection tar. """ - member_names = [to_native(dirname, errors='surrogate_or_strict')] - - # Create list of members with and without trailing separator - if not member_names[-1].endswith(os.path.sep): - member_names.append(member_names[-1] + os.path.sep) + dirname = to_native(dirname, errors='surrogate_or_strict').removesuffix(os.path.sep) - # Try all of the member names and stop on the first one that are able to successfully get - for member in member_names: - try: - tar_member = tar.getmember(member) - except KeyError: - continue - break - else: - # If we still can't find the member, raise a nice error. - raise AnsibleError("Unable to extract '%s' from collection" % to_native(member, errors='surrogate_or_strict')) + try: + tar_member = tar._ansible_normalized_cache[dirname] + except KeyError: + raise AnsibleError("Unable to extract '%s' from collection" % dirname) b_dir_path = os.path.join(b_dest, to_bytes(dirname, errors='surrogate_or_strict')) diff --git a/test/units/cli/galaxy/test_collection_extract_tar.py b/test/units/cli/galaxy/test_collection_extract_tar.py index 526442cc9de..b84f60b619f 100644 --- a/test/units/cli/galaxy/test_collection_extract_tar.py +++ b/test/units/cli/galaxy/test_collection_extract_tar.py @@ -14,6 +14,7 @@ from ansible.galaxy.collection import _extract_tar_dir @pytest.fixture def fake_tar_obj(mocker): m_tarfile = mocker.Mock() + m_tarfile._ansible_normalized_cache = {'/some/dir': mocker.Mock()} m_tarfile.type = mocker.Mock(return_value=b'99') m_tarfile.SYMTYPE = mocker.Mock(return_value=b'22') @@ -22,23 +23,11 @@ def fake_tar_obj(mocker): def test_extract_tar_member_trailing_sep(mocker): m_tarfile = mocker.Mock() - m_tarfile.getmember = mocker.Mock(side_effect=KeyError) + m_tarfile._ansible_normalized_cache = {} with pytest.raises(AnsibleError, match='Unable to extract'): _extract_tar_dir(m_tarfile, '/some/dir/', b'/some/dest') - assert m_tarfile.getmember.call_count == 1 - - -def test_extract_tar_member_no_trailing_sep(mocker): - m_tarfile = mocker.Mock() - m_tarfile.getmember = mocker.Mock(side_effect=KeyError) - - with pytest.raises(AnsibleError, match='Unable to extract'): - _extract_tar_dir(m_tarfile, '/some/dir', b'/some/dest') - - assert m_tarfile.getmember.call_count == 2 - def test_extract_tar_dir_exists(mocker, fake_tar_obj): mocker.patch('os.makedirs', return_value=None)