diff options
28 files changed, 296 insertions, 56 deletions
@@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ansible-core -Version: 2.14.9 +Version: 2.14.10 Summary: Radically simple IT automation Home-page: https://ansible.com/ Author: Ansible, Inc. @@ -31,6 +31,11 @@ Classifier: Topic :: Utilities Requires-Python: >=3.9 Description-Content-Type: text/markdown License-File: COPYING +Requires-Dist: jinja2>=3.0.0 +Requires-Dist: PyYAML>=5.1 +Requires-Dist: cryptography +Requires-Dist: packaging +Requires-Dist: resolvelib<0.9.0,>=0.5.3 [![PyPI version](https://img.shields.io/pypi/v/ansible-core.svg)](https://pypi.org/project/ansible-core) [![Docs badge](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://docs.ansible.com/ansible/latest/) diff --git a/changelogs/CHANGELOG-v2.14.rst b/changelogs/CHANGELOG-v2.14.rst index 1bf32299..da0b3266 100644 --- a/changelogs/CHANGELOG-v2.14.rst +++ b/changelogs/CHANGELOG-v2.14.rst @@ -5,6 +5,29 @@ ansible-core 2.14 "C'mon Everybody" Release Notes .. contents:: Topics +v2.14.10 +======== + +Release Summary +--------------- + +| Release Date: 2023-09-11 +| `Porting Guide <https://docs.ansible.com/ansible-core/2.14/porting_guides/porting_guide_core_2.14.html>`__ + + +Minor Changes +------------- + +- ansible-test — Replaced `freebsd/12.3` remote with `freebsd/12.4`. The former is no longer functional. + +Bugfixes +-------- + +- PowerShell - Remove some code which is no longer valid for dotnet 5+ +- ansible-galaxy - Enabled the ``data`` tarfile filter during role installation for Python versions that support it. A probing mechanism is used to avoid Python versions with a broken implementation. +- ansible-test - Always use ansible-test managed entry points for ansible-core CLI tools when not running from source. This fixes issues where CLI entry points created during install are not compatible with ansible-test. +- tarfile - handle data filter deprecation warning message for extract and extractall (https://github.com/ansible/ansible/issues/80832). + v2.14.9 ======= diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 1276d1a6..484c4100 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -857,6 +857,45 @@ releases: - fork_safe_stdio.yml - v2.14.1_summary.yaml release_date: '2022-12-06' + 2.14.10: + changes: + release_summary: '| Release Date: 2023-09-11 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.14/porting_guides/porting_guide_core_2.14.html>`__ + + ' + codename: C'mon Everybody + fragments: + - 2.14.10_summary.yaml + release_date: '2023-09-11' + 2.14.10rc1: + changes: + bugfixes: + - PowerShell - Remove some code which is no longer valid for dotnet 5+ + - ansible-galaxy - Enabled the ``data`` tarfile filter during role installation + for Python versions that support it. A probing mechanism is used to avoid + Python versions with a broken implementation. + - ansible-test - Always use ansible-test managed entry points for ansible-core + CLI tools when not running from source. This fixes issues where CLI entry + points created during install are not compatible with ansible-test. + - tarfile - handle data filter deprecation warning message for extract and extractall + (https://github.com/ansible/ansible/issues/80832). + minor_changes: + - "ansible-test \u2014 Replaced `freebsd/12.3` remote with `freebsd/12.4`. The + former is no longer functional." + release_summary: '| Release Date: 2023-09-05 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.14/porting_guides/porting_guide_core_2.14.html>`__ + + ' + codename: C'mon Everybody + fragments: + - 2.14.10rc1_summary.yaml + - ansible-test-entry-points.yml + - dotnet-preparation.yml + - freebsd-12.3-replacement.yml + - tarfile_extract_warn.yml + release_date: '2023-09-05' 2.14.1rc1: changes: bugfixes: diff --git a/lib/ansible/galaxy/role.py b/lib/ansible/galaxy/role.py index 99bb525e..0915adfa 100644 --- a/lib/ansible/galaxy/role.py +++ b/lib/ansible/galaxy/role.py @@ -24,6 +24,7 @@ __metaclass__ = type import errno import datetime +import functools import os import tarfile import tempfile @@ -45,6 +46,32 @@ from ansible.utils.display import Display display = Display() +@functools.cache +def _check_working_data_filter() -> bool: + """ + Check if tarfile.data_filter implementation is working + for the current Python version or not + """ + + # Implemented the following code to circumvent broken implementation of data_filter + # in tarfile. See for more information - https://github.com/python/cpython/issues/107845 + # deprecated: description='probing broken data filter implementation' python_version='3.11' + ret = False + if hasattr(tarfile, 'data_filter'): + # We explicitly check if tarfile.data_filter is broken or not + ti = tarfile.TarInfo('docs/README.md') + ti.type = tarfile.SYMTYPE + ti.linkname = '../README.md' + + try: + tarfile.data_filter(ti, '/foo') + except tarfile.LinkOutsideDestinationError: + pass + else: + ret = True + return ret + + class GalaxyRole(object): SUPPORTED_SCMS = set(['git', 'hg']) @@ -379,7 +406,12 @@ class GalaxyRole(object): if n_part != '..' and not n_part.startswith('~') and '$' not in n_part: n_final_parts.append(n_part) member.name = os.path.join(*n_final_parts) - role_tar_file.extract(member, to_native(self.path)) + + if _check_working_data_filter(): + # deprecated: description='extract fallback without filter' python_version='3.11' + role_tar_file.extract(member, to_native(self.path), filter='data') # type: ignore[call-arg] + else: + role_tar_file.extract(member, to_native(self.path)) # write out the install info file for later use self._write_galaxy_install_info() diff --git a/lib/ansible/module_utils/ansible_release.py b/lib/ansible/module_utils/ansible_release.py index 7c9e0dfa..591e60b7 100644 --- a/lib/ansible/module_utils/ansible_release.py +++ b/lib/ansible/module_utils/ansible_release.py @@ -19,6 +19,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -__version__ = '2.14.9' +__version__ = '2.14.10' __author__ = 'Ansible, Inc.' __codename__ = "C'mon Everybody" diff --git a/lib/ansible/module_utils/csharp/Ansible.AccessToken.cs b/lib/ansible/module_utils/csharp/Ansible.AccessToken.cs index 48c4a197..49fba4e5 100644 --- a/lib/ansible/module_utils/csharp/Ansible.AccessToken.cs +++ b/lib/ansible/module_utils/csharp/Ansible.AccessToken.cs @@ -2,7 +2,6 @@ using Microsoft.Win32.SafeHandles; using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security.Principal; using System.Text; @@ -123,7 +122,6 @@ namespace Ansible.AccessToken base.SetHandle(handle); } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { Marshal.FreeHGlobal(handle); @@ -247,7 +245,6 @@ namespace Ansible.AccessToken public SafeNativeHandle() : base(true) { } public SafeNativeHandle(IntPtr handle) : base(true) { this.handle = handle; } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return NativeMethods.CloseHandle(handle); diff --git a/lib/ansible/module_utils/csharp/Ansible.Become.cs b/lib/ansible/module_utils/csharp/Ansible.Become.cs index a6f645ca..d3bb1564 100644 --- a/lib/ansible/module_utils/csharp/Ansible.Become.cs +++ b/lib/ansible/module_utils/csharp/Ansible.Become.cs @@ -4,7 +4,6 @@ using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Security.Principal; @@ -175,7 +174,6 @@ namespace Ansible.Become { public SafeLsaHandle() : base(true) { } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { UInt32 res = NativeMethods.LsaDeregisterLogonProcess(handle); @@ -187,7 +185,6 @@ namespace Ansible.Become { public SafeLsaMemoryBuffer() : base(true) { } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { UInt32 res = NativeMethods.LsaFreeReturnBuffer(handle); @@ -200,7 +197,6 @@ namespace Ansible.Become public NoopSafeHandle() : base(IntPtr.Zero, false) { } public override bool IsInvalid { get { return false; } } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return true; } } diff --git a/lib/ansible/module_utils/csharp/Ansible.Privilege.cs b/lib/ansible/module_utils/csharp/Ansible.Privilege.cs index 2c0b266b..9d5c0b17 100644 --- a/lib/ansible/module_utils/csharp/Ansible.Privilege.cs +++ b/lib/ansible/module_utils/csharp/Ansible.Privilege.cs @@ -3,7 +3,6 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security.Principal; using System.Text; @@ -92,7 +91,6 @@ namespace Ansible.Privilege { base.SetHandle(handle); } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { Marshal.FreeHGlobal(handle); @@ -104,7 +102,7 @@ namespace Ansible.Privilege { public SafeNativeHandle() : base(true) { } public SafeNativeHandle(IntPtr handle) : base(true) { this.handle = handle; } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + protected override bool ReleaseHandle() { return NativeMethods.CloseHandle(handle); diff --git a/lib/ansible/module_utils/csharp/Ansible.Process.cs b/lib/ansible/module_utils/csharp/Ansible.Process.cs index f4c68f05..fc156b7a 100644 --- a/lib/ansible/module_utils/csharp/Ansible.Process.cs +++ b/lib/ansible/module_utils/csharp/Ansible.Process.cs @@ -3,7 +3,6 @@ using System; using System.Collections; using System.IO; using System.Linq; -using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Text; using System.Threading; @@ -176,7 +175,6 @@ namespace Ansible.Process base.SetHandle(handle); } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { Marshal.FreeHGlobal(handle); diff --git a/lib/ansible/release.py b/lib/ansible/release.py index 7c9e0dfa..591e60b7 100644 --- a/lib/ansible/release.py +++ b/lib/ansible/release.py @@ -19,6 +19,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -__version__ = '2.14.9' +__version__ = '2.14.10' __author__ = 'Ansible, Inc.' __codename__ = "C'mon Everybody" diff --git a/lib/ansible_core.egg-info/PKG-INFO b/lib/ansible_core.egg-info/PKG-INFO index e930dbfc..6ed4167b 100644 --- a/lib/ansible_core.egg-info/PKG-INFO +++ b/lib/ansible_core.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ansible-core -Version: 2.14.9 +Version: 2.14.10 Summary: Radically simple IT automation Home-page: https://ansible.com/ Author: Ansible, Inc. @@ -31,6 +31,11 @@ Classifier: Topic :: Utilities Requires-Python: >=3.9 Description-Content-Type: text/markdown License-File: COPYING +Requires-Dist: jinja2>=3.0.0 +Requires-Dist: PyYAML>=5.1 +Requires-Dist: cryptography +Requires-Dist: packaging +Requires-Dist: resolvelib<0.9.0,>=0.5.3 [![PyPI version](https://img.shields.io/pypi/v/ansible-core.svg)](https://pypi.org/project/ansible-core) [![Docs badge](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://docs.ansible.com/ansible/latest/) diff --git a/lib/ansible_core.egg-info/SOURCES.txt b/lib/ansible_core.egg-info/SOURCES.txt index 59f6c3e9..83bc701d 100644 --- a/lib/ansible_core.egg-info/SOURCES.txt +++ b/lib/ansible_core.egg-info/SOURCES.txt @@ -968,6 +968,10 @@ test/integration/targets/ansible-test-git/collection-tests/git-at-collection-roo test/integration/targets/ansible-test-git/collection-tests/git-common.bash test/integration/targets/ansible-test-git/collection-tests/install-git.yml test/integration/targets/ansible-test-git/collection-tests/uninstall-git.yml +test/integration/targets/ansible-test-installed/aliases +test/integration/targets/ansible-test-installed/runme.sh +test/integration/targets/ansible-test-installed/ansible_collections/ns/col/tests/integration/targets/installed/aliases +test/integration/targets/ansible-test-installed/ansible_collections/ns/col/tests/integration/targets/installed/runme.sh test/integration/targets/ansible-test-integration/aliases test/integration/targets/ansible-test-integration/runme.sh test/integration/targets/ansible-test-integration-constraints/aliases diff --git a/packaging/release.py b/packaging/release.py index 05c29faa..97c58a74 100755 --- a/packaging/release.py +++ b/packaging/release.py @@ -42,7 +42,7 @@ from packaging.version import Version, InvalidVersion # region CLI Framework -C = t.TypeVar("C", bound=t.Callable[[...], None]) +C = t.TypeVar("C", bound=t.Callable[..., None]) def path_to_str(value: t.Any) -> str: @@ -50,12 +50,27 @@ def path_to_str(value: t.Any) -> str: return f"{value}/" if isinstance(value, pathlib.Path) and value.is_dir() else str(value) +@t.overload +def run(*args: t.Any, env: dict[str, t.Any] | None, cwd: pathlib.Path | str, capture_output: t.Literal[True]) -> CompletedProcess: + ... + + +@t.overload +def run(*args: t.Any, env: dict[str, t.Any] | None, cwd: pathlib.Path | str, capture_output: t.Literal[False]) -> None: + ... + + +@t.overload +def run(*args: t.Any, env: dict[str, t.Any] | None, cwd: pathlib.Path | str) -> None: + ... + + def run( *args: t.Any, env: dict[str, t.Any] | None, cwd: pathlib.Path | str, capture_output: bool = False, -) -> CompletedProcess: +) -> CompletedProcess | None: """Run the specified command.""" args = [arg.relative_to(cwd) if isinstance(arg, pathlib.Path) else arg for arg in args] @@ -76,16 +91,18 @@ def run( stderr=ex.stderr, ) from None + if not capture_output: + return None + # improve type hinting return CompletedProcess( - args=str_args, stdout=p.stdout, stderr=p.stderr, ) @contextlib.contextmanager -def suppress_when(error_as_warning: bool) -> None: +def suppress_when(error_as_warning: bool) -> t.Generator[None, None, None]: """Conditionally convert an ApplicationError in the provided context to a warning.""" if error_as_warning: try: @@ -122,9 +139,8 @@ class CalledProcessError(Exception): class CompletedProcess: """Results from a completed process.""" - args: tuple[str, ...] - stdout: str | None - stderr: str | None + stdout: str + stderr: str class Display: @@ -167,7 +183,7 @@ class CommandFramework: """ def __init__(self, **kwargs: dict[str, t.Any] | None) -> None: - self.commands: list[C] = [] + self.commands: list[t.Callable[..., None]] = [] self.arguments = kwargs self.parsed_arguments: argparse.Namespace | None = None @@ -176,7 +192,7 @@ class CommandFramework: self.commands.append(func) return func - def run(self, *args: C, **kwargs) -> None: + def run(self, *args: t.Callable[..., None], **kwargs) -> None: """Run the specified command(s), using any provided internal args.""" for arg in args: self._run(arg, **kwargs) @@ -203,6 +219,9 @@ class CommandFramework: arguments = arguments.copy() exclusive = arguments.pop("exclusive", None) + # noinspection PyProtectedMember, PyUnresolvedReferences + command_parser: argparse._ActionsContainer + if exclusive: if exclusive not in exclusive_groups: exclusive_groups[exclusive] = func_parser.add_mutually_exclusive_group() @@ -234,7 +253,7 @@ class CommandFramework: display.fatal(ex) sys.exit(1) - def _run(self, func: C, **kwargs) -> None: + def _run(self, func: t.Callable[..., None], **kwargs) -> None: """Run the specified command, using any provided internal args.""" signature = inspect.signature(func) func_args = {name: getattr(self.parsed_arguments, name) for name in signature.parameters if hasattr(self.parsed_arguments, name)} @@ -253,7 +272,7 @@ class CommandFramework: display.show(f"<== {label}", color=Display.BLUE) @staticmethod - def _format_command_name(func: C) -> str: + def _format_command_name(func: t.Callable[..., None]) -> str: """Return the friendly name of the given command.""" return func.__name__.replace("_", "-") @@ -441,7 +460,22 @@ class VersionMode(enum.Enum): raise NotImplementedError(self) -def git(*args: t.Any, capture_output: bool = False) -> CompletedProcess: +@t.overload +def git(*args: t.Any, capture_output: t.Literal[True]) -> CompletedProcess: + ... + + +@t.overload +def git(*args: t.Any, capture_output: t.Literal[False]) -> None: + ... + + +@t.overload +def git(*args: t.Any) -> None: + ... + + +def git(*args: t.Any, capture_output: t.Literal[True] | t.Literal[False] = False) -> CompletedProcess | None: """Run the specified git command.""" return run("git", *args, env=None, cwd=CHECKOUT_DIR, capture_output=capture_output) @@ -534,10 +568,6 @@ def create_pull_request_body(title: str) -> str: ##### ISSUE TYPE Feature Pull Request - -##### COMPONENT NAME - -ansible """ return body.lstrip() @@ -629,7 +659,7 @@ def get_git_state(version: Version, allow_stale: bool) -> GitState: @functools.cache -def ensure_venv() -> dict[str, str]: +def ensure_venv() -> dict[str, t.Any]: """Ensure the release venv is ready and return the env vars needed to use it.""" # TODO: consider freezing the ansible and release requirements along with their dependencies @@ -943,7 +973,7 @@ def create_github_release_notes(upstream: Remote, repository: str, version: Vers variables = dict( version=version, releases=get_release_artifact_details(repository, version, validate), - changelog=f"https://github.com/{upstream.user}/{upstream.repo}/blob/v{ version }/changelogs/CHANGELOG-v{ version.major }.{ version.minor }.rst", + changelog=f"https://github.com/{upstream.user}/{upstream.repo}/blob/v{version}/changelogs/CHANGELOG-v{version.major}.{version.minor}.rst", ) release_notes = template.render(**variables).strip() @@ -1274,7 +1304,7 @@ def build(allow_dirty: bool = False) -> None: commit_time = int(git("show", "-s", "--format=%ct", capture_output=True).stdout) env.update( - SOURCE_DATE_EPOCH=str(commit_time), + SOURCE_DATE_EPOCH=commit_time, ) git("worktree", "add", "-d", temp_dir) @@ -1312,7 +1342,11 @@ def test_sdist() -> None: except FileNotFoundError: raise ApplicationError(f"Missing sdist: {sdist_file.relative_to(CHECKOUT_DIR)}") from None - sdist.extractall(temp_dir) + # deprecated: description='extractall fallback without filter' python_version='3.11' + if hasattr(tarfile, 'data_filter'): + sdist.extractall(temp_dir, filter='data') # type: ignore[call-arg] + else: + sdist.extractall(temp_dir) pyc_glob = "*.pyc*" pyc_files = sorted(path.relative_to(temp_dir) for path in temp_dir.rglob(pyc_glob)) diff --git a/test/integration/targets/ansible-galaxy-collection/library/setup_collections.py b/test/integration/targets/ansible-galaxy-collection/library/setup_collections.py index 35b18dec..f4a51c4b 100644 --- a/test/integration/targets/ansible-galaxy-collection/library/setup_collections.py +++ b/test/integration/targets/ansible-galaxy-collection/library/setup_collections.py @@ -152,7 +152,12 @@ def publish_collection(module, collection): # Extract the tarfile to sign the MANIFEST.json with tarfile.open(collection_path, mode='r') as collection_tar: - collection_tar.extractall(path=os.path.join(collection_dir, '%s-%s-%s' % (namespace, name, version))) + # deprecated: description='extractall fallback without filter' python_version='3.11' + # Replace 'tar_filter' with 'data_filter' and 'filter=tar' with 'filter=data' once Python 3.12 is minimum requirement. + if hasattr(tarfile, 'tar_filter'): + collection_tar.extractall(path=os.path.join(collection_dir, '%s-%s-%s' % (namespace, name, version)), filter='tar') + else: + collection_tar.extractall(path=os.path.join(collection_dir, '%s-%s-%s' % (namespace, name, version))) manifest_path = os.path.join(collection_dir, '%s-%s-%s' % (namespace, name, version), 'MANIFEST.json') signature_path = os.path.join(module.params['signature_dir'], '%s-%s-%s-MANIFEST.json.asc' % (namespace, name, version)) diff --git a/test/integration/targets/ansible-test-installed/aliases b/test/integration/targets/ansible-test-installed/aliases new file mode 100644 index 00000000..7741d444 --- /dev/null +++ b/test/integration/targets/ansible-test-installed/aliases @@ -0,0 +1,4 @@ +shippable/posix/group3 # runs in the distro test containers +shippable/generic/group1 # runs in the default test container +context/controller +needs/target/collection diff --git a/test/integration/targets/ansible-test-installed/ansible_collections/ns/col/tests/integration/targets/installed/aliases b/test/integration/targets/ansible-test-installed/ansible_collections/ns/col/tests/integration/targets/installed/aliases new file mode 100644 index 00000000..1af1cf90 --- /dev/null +++ b/test/integration/targets/ansible-test-installed/ansible_collections/ns/col/tests/integration/targets/installed/aliases @@ -0,0 +1 @@ +context/controller diff --git a/test/integration/targets/ansible-test-installed/ansible_collections/ns/col/tests/integration/targets/installed/runme.sh b/test/integration/targets/ansible-test-installed/ansible_collections/ns/col/tests/integration/targets/installed/runme.sh new file mode 100755 index 00000000..9de3820a --- /dev/null +++ b/test/integration/targets/ansible-test-installed/ansible_collections/ns/col/tests/integration/targets/installed/runme.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# This test ensures that the bin entry points created by ansible-test work +# when ansible-test is running from an install instead of from source. + +set -eux + +# The third PATH entry is the injected bin directory created by ansible-test. +bin_dir="$(python -c 'import os; print(os.environ["PATH"].split(":")[2])')" + +while IFS= read -r name +do + bin="${bin_dir}/${name}" + + entry_point="${name//ansible-/}" + entry_point="${entry_point//ansible/adhoc}" + + echo "=== ${name} (${entry_point})=${bin} ===" + + if [ "${name}" == "ansible-test" ]; then + echo "skipped - ansible-test does not support self-testing from an install" + else + "${bin}" --version | tee /dev/stderr | grep -Eo "(^${name}\ \[core\ .*|executable location = ${bin}$)" + fi +done < entry-points.txt diff --git a/test/integration/targets/ansible-test-installed/runme.sh b/test/integration/targets/ansible-test-installed/runme.sh new file mode 100755 index 00000000..8315357e --- /dev/null +++ b/test/integration/targets/ansible-test-installed/runme.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +base_dir="$(dirname "$(dirname "$(dirname "$(dirname "${OUTPUT_DIR}")")")")" +bin_dir="${base_dir}/bin" + +source ../collection/setup.sh +source virtualenv.sh + +unset PYTHONPATH + +# find the bin entry points to test +ls "${bin_dir}" > tests/integration/targets/installed/entry-points.txt + +# deps are already installed, using --no-deps to avoid re-installing them +pip install "${base_dir}" --disable-pip-version-check --no-deps + +# verify entry point generation without delegation +ansible-test integration --color --truncate 0 "${@}" + +# verify entry point generation with same-host delegation +ansible-test integration --venv --color --truncate 0 "${@}" diff --git a/test/integration/targets/module_utils_Ansible.Become/library/ansible_become_tests.ps1 b/test/integration/targets/module_utils_Ansible.Become/library/ansible_become_tests.ps1 index 6e363211..163d035a 100644 --- a/test/integration/targets/module_utils_Ansible.Become/library/ansible_become_tests.ps1 +++ b/test/integration/targets/module_utils_Ansible.Become/library/ansible_become_tests.ps1 @@ -48,7 +48,6 @@ $test_whoami = { Add-Type -TypeDefinition @' using Microsoft.Win32.SafeHandles; using System; -using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security.Principal; using System.Text; @@ -212,7 +211,6 @@ namespace Ansible { public SafeLsaMemoryBuffer() : base(true) { } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { UInt32 res = NativeMethods.LsaFreeReturnBuffer(handle); @@ -232,7 +230,6 @@ namespace Ansible base.SetHandle(handle); } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { Marshal.FreeHGlobal(handle); @@ -245,7 +242,6 @@ namespace Ansible public SafeNativeHandle() : base(true) { } public SafeNativeHandle(IntPtr handle) : base(true) { this.handle = handle; } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return NativeMethods.CloseHandle(handle); diff --git a/test/lib/ansible_test/_data/completion/remote.txt b/test/lib/ansible_test/_data/completion/remote.txt index 192298bf..4e607b7a 100644 --- a/test/lib/ansible_test/_data/completion/remote.txt +++ b/test/lib/ansible_test/_data/completion/remote.txt @@ -2,7 +2,7 @@ alpine/3.16 python=3.10 become=doas_sudo provider=aws arch=x86_64 alpine become=doas_sudo provider=aws arch=x86_64 fedora/36 python=3.10 become=sudo provider=aws arch=x86_64 fedora become=sudo provider=aws arch=x86_64 -freebsd/12.3 python=3.8 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64 +freebsd/12.4 python=3.9 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64 freebsd/13.1 python=3.8,3.7,3.9,3.10 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64 freebsd python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64 macos/12.0 python=3.10 python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64 diff --git a/test/lib/ansible_test/_internal/ansible_util.py b/test/lib/ansible_test/_internal/ansible_util.py index be88ccd8..885489f4 100644 --- a/test/lib/ansible_test/_internal/ansible_util.py +++ b/test/lib/ansible_test/_internal/ansible_util.py @@ -3,9 +3,11 @@ from __future__ import annotations import json import os +import shutil import typing as t from .constants import ( + ANSIBLE_BIN_SYMLINK_MAP, SOFT_RLIMIT_NOFILE, ) @@ -17,12 +19,15 @@ from .util import ( common_environment, ApplicationError, ANSIBLE_LIB_ROOT, + ANSIBLE_TEST_ROOT, ANSIBLE_TEST_DATA_ROOT, - ANSIBLE_BIN_PATH, + ANSIBLE_ROOT, ANSIBLE_SOURCE_ROOT, ANSIBLE_TEST_TOOLS_ROOT, + MODE_FILE_EXECUTE, get_ansible_version, raw_command, + verified_chmod, ) from .util_common import ( @@ -78,8 +83,10 @@ def ansible_environment(args: CommonConfig, color: bool = True, ansible_config: env = common_environment() path = env['PATH'] - if not path.startswith(ANSIBLE_BIN_PATH + os.path.pathsep): - path = ANSIBLE_BIN_PATH + os.path.pathsep + path + ansible_bin_path = get_ansible_bin_path(args) + + if not path.startswith(ansible_bin_path + os.path.pathsep): + path = ansible_bin_path + os.path.pathsep + path if not ansible_config: # use the default empty configuration unless one has been provided @@ -197,6 +204,52 @@ def configure_plugin_paths(args: CommonConfig) -> dict[str, str]: @mutex +def get_ansible_bin_path(args: CommonConfig) -> str: + """ + Return a directory usable for PATH, containing only the ansible entry points. + If a temporary directory is required, it will be cached for the lifetime of the process and cleaned up at exit. + """ + try: + return get_ansible_bin_path.bin_path # type: ignore[attr-defined] + except AttributeError: + pass + + if ANSIBLE_SOURCE_ROOT: + # when running from source there is no need for a temporary directory since we already have known entry point scripts + bin_path = os.path.join(ANSIBLE_ROOT, 'bin') + else: + # when not running from source the installed entry points cannot be relied upon + # doing so would require using the interpreter specified by those entry points, which conflicts with using our interpreter and injector + # instead a temporary directory is created which contains only ansible entry points + # symbolic links cannot be used since the files are likely not executable + bin_path = create_temp_dir(prefix='ansible-test-', suffix='-bin') + bin_links = {os.path.join(bin_path, name): get_cli_path(path) for name, path in ANSIBLE_BIN_SYMLINK_MAP.items()} + + if not args.explain: + for dst, src in bin_links.items(): + shutil.copy(src, dst) + verified_chmod(dst, MODE_FILE_EXECUTE) + + get_ansible_bin_path.bin_path = bin_path # type: ignore[attr-defined] + + return bin_path + + +def get_cli_path(path: str) -> str: + """Return the absolute path to the CLI script from the given path which is relative to the `bin` directory of the original source tree layout.""" + path_rewrite = { + '../lib/ansible/': ANSIBLE_LIB_ROOT, + '../test/lib/ansible_test/': ANSIBLE_TEST_ROOT, + } + + for prefix, destination in path_rewrite.items(): + if path.startswith(prefix): + return os.path.join(destination, path[len(prefix):]) + + raise RuntimeError(path) + + +@mutex def get_ansible_python_path(args: CommonConfig) -> str: """ Return a directory usable for PYTHONPATH, containing only the ansible package. diff --git a/test/lib/ansible_test/_internal/commands/sanity/bin_symlinks.py b/test/lib/ansible_test/_internal/commands/sanity/bin_symlinks.py index 8f4fe8a4..6c7618d1 100644 --- a/test/lib/ansible_test/_internal/commands/sanity/bin_symlinks.py +++ b/test/lib/ansible_test/_internal/commands/sanity/bin_symlinks.py @@ -32,7 +32,7 @@ from ...payload import ( ) from ...util import ( - ANSIBLE_BIN_PATH, + ANSIBLE_SOURCE_ROOT, ) @@ -52,7 +52,7 @@ class BinSymlinksTest(SanityVersionNeutral): return True def test(self, args: SanityConfig, targets: SanityTargets) -> TestResult: - bin_root = ANSIBLE_BIN_PATH + bin_root = os.path.join(ANSIBLE_SOURCE_ROOT, 'bin') bin_names = os.listdir(bin_root) bin_paths = sorted(os.path.join(bin_root, path) for path in bin_names) diff --git a/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py b/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py index 72616e77..3153bc99 100644 --- a/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py +++ b/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py @@ -154,7 +154,11 @@ class ValidateModulesTest(SanitySingleVersion): temp_dir = process_scoped_temporary_directory(args) with tarfile.open(path) as file: - file.extractall(temp_dir) + # deprecated: description='extractall fallback without filter' python_version='3.11' + if hasattr(tarfile, 'data_filter'): + file.extractall(temp_dir, filter='data') # type: ignore[call-arg] + else: + file.extractall(temp_dir) cmd.extend([ '--original-plugins', temp_dir, diff --git a/test/lib/ansible_test/_internal/constants.py b/test/lib/ansible_test/_internal/constants.py index b6072fbe..fdf2d954 100644 --- a/test/lib/ansible_test/_internal/constants.py +++ b/test/lib/ansible_test/_internal/constants.py @@ -33,6 +33,7 @@ SECCOMP_CHOICES = [ # This bin symlink map must exactly match the contents of the bin directory. # It is necessary for payload creation to reconstruct the bin directory when running ansible-test from an installed version of ansible. # It is also used to construct the injector directory at runtime. +# It is also used to construct entry points when not running ansible-test from source. ANSIBLE_BIN_SYMLINK_MAP = { 'ansible': '../lib/ansible/cli/adhoc.py', 'ansible-config': '../lib/ansible/cli/config.py', diff --git a/test/lib/ansible_test/_internal/delegation.py b/test/lib/ansible_test/_internal/delegation.py index 7114f2ab..f9e54455 100644 --- a/test/lib/ansible_test/_internal/delegation.py +++ b/test/lib/ansible_test/_internal/delegation.py @@ -33,7 +33,6 @@ from .util import ( SubprocessError, display, filter_args, - ANSIBLE_BIN_PATH, ANSIBLE_LIB_ROOT, ANSIBLE_TEST_ROOT, OutputStream, @@ -44,6 +43,10 @@ from .util_common import ( process_scoped_temporary_directory, ) +from .ansible_util import ( + get_ansible_bin_path, +) + from .containers import ( support_container_context, ContainerDatabase, @@ -145,7 +148,7 @@ def delegate_command(args: EnvironmentConfig, host_state: HostState, exclude: li con.extract_archive(chdir=working_directory, src=payload_file) else: content_root = working_directory - ansible_bin_path = ANSIBLE_BIN_PATH + ansible_bin_path = get_ansible_bin_path(args) command = generate_command(args, host_state.controller_profile.python, ansible_bin_path, content_root, exclude, require) diff --git a/test/lib/ansible_test/_internal/util.py b/test/lib/ansible_test/_internal/util.py index a5a9faba..1859be5b 100644 --- a/test/lib/ansible_test/_internal/util.py +++ b/test/lib/ansible_test/_internal/util.py @@ -74,14 +74,12 @@ ANSIBLE_TEST_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # assume running from install ANSIBLE_ROOT = os.path.dirname(ANSIBLE_TEST_ROOT) -ANSIBLE_BIN_PATH = os.path.dirname(os.path.abspath(sys.argv[0])) ANSIBLE_LIB_ROOT = os.path.join(ANSIBLE_ROOT, 'ansible') ANSIBLE_SOURCE_ROOT = None if not os.path.exists(ANSIBLE_LIB_ROOT): # running from source ANSIBLE_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(ANSIBLE_TEST_ROOT))) - ANSIBLE_BIN_PATH = os.path.join(ANSIBLE_ROOT, 'bin') ANSIBLE_LIB_ROOT = os.path.join(ANSIBLE_ROOT, 'lib', 'ansible') ANSIBLE_SOURCE_ROOT = ANSIBLE_ROOT diff --git a/test/lib/ansible_test/_util/target/setup/bootstrap.sh b/test/lib/ansible_test/_util/target/setup/bootstrap.sh index f2e82fbc..ea17dad3 100644 --- a/test/lib/ansible_test/_util/target/setup/bootstrap.sh +++ b/test/lib/ansible_test/_util/target/setup/bootstrap.sh @@ -163,6 +163,8 @@ bootstrap_remote_freebsd() # Declare platform/python version combinations which do not have supporting OS packages available. # For these combinations ansible-test will use pip to install the requirements instead. case "${platform_version}/${python_version}" in + "12.4/3.9") + ;; *) jinja2_pkg="" # not available cryptography_pkg="" # not available diff --git a/test/support/windows-integration/plugins/module_utils/Ansible.Service.cs b/test/support/windows-integration/plugins/module_utils/Ansible.Service.cs index be0f3db3..4b963a9d 100644 --- a/test/support/windows-integration/plugins/module_utils/Ansible.Service.cs +++ b/test/support/windows-integration/plugins/module_utils/Ansible.Service.cs @@ -2,7 +2,6 @@ using Microsoft.Win32.SafeHandles; using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security.Principal; using System.Text; @@ -274,7 +273,6 @@ namespace Ansible.Service base.SetHandle(handle); } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { Marshal.FreeHGlobal(handle); @@ -287,7 +285,6 @@ namespace Ansible.Service public SafeServiceHandle() : base(true) { } public SafeServiceHandle(IntPtr handle) : base(true) { this.handle = handle; } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return NativeMethods.CloseServiceHandle(handle); |