summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PKG-INFO7
-rw-r--r--changelogs/CHANGELOG-v2.14.rst23
-rw-r--r--changelogs/changelog.yaml39
-rw-r--r--lib/ansible/galaxy/role.py34
-rw-r--r--lib/ansible/module_utils/ansible_release.py2
-rw-r--r--lib/ansible/module_utils/csharp/Ansible.AccessToken.cs3
-rw-r--r--lib/ansible/module_utils/csharp/Ansible.Become.cs4
-rw-r--r--lib/ansible/module_utils/csharp/Ansible.Privilege.cs4
-rw-r--r--lib/ansible/module_utils/csharp/Ansible.Process.cs2
-rw-r--r--lib/ansible/release.py2
-rw-r--r--lib/ansible_core.egg-info/PKG-INFO7
-rw-r--r--lib/ansible_core.egg-info/SOURCES.txt4
-rwxr-xr-xpackaging/release.py74
-rw-r--r--test/integration/targets/ansible-galaxy-collection/library/setup_collections.py7
-rw-r--r--test/integration/targets/ansible-test-installed/aliases4
-rw-r--r--test/integration/targets/ansible-test-installed/ansible_collections/ns/col/tests/integration/targets/installed/aliases1
-rwxr-xr-xtest/integration/targets/ansible-test-installed/ansible_collections/ns/col/tests/integration/targets/installed/runme.sh24
-rwxr-xr-xtest/integration/targets/ansible-test-installed/runme.sh21
-rw-r--r--test/integration/targets/module_utils_Ansible.Become/library/ansible_become_tests.ps14
-rw-r--r--test/lib/ansible_test/_data/completion/remote.txt2
-rw-r--r--test/lib/ansible_test/_internal/ansible_util.py59
-rw-r--r--test/lib/ansible_test/_internal/commands/sanity/bin_symlinks.py4
-rw-r--r--test/lib/ansible_test/_internal/commands/sanity/validate_modules.py6
-rw-r--r--test/lib/ansible_test/_internal/constants.py1
-rw-r--r--test/lib/ansible_test/_internal/delegation.py7
-rw-r--r--test/lib/ansible_test/_internal/util.py2
-rw-r--r--test/lib/ansible_test/_util/target/setup/bootstrap.sh2
-rw-r--r--test/support/windows-integration/plugins/module_utils/Ansible.Service.cs3
28 files changed, 296 insertions, 56 deletions
diff --git a/PKG-INFO b/PKG-INFO
index e930dbfc..6ed4167b 100644
--- a/PKG-INFO
+++ b/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/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);