Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package ansible-core-2.19 for openSUSE:Factory checked in at 2026-04-21 12:45:49 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ansible-core-2.19 (Old) and /work/SRC/openSUSE:Factory/.ansible-core-2.19.new.11940 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ansible-core-2.19" Tue Apr 21 12:45:49 2026 rev:6 rq:1348366 version:2.19.9 Changes: -------- --- /work/SRC/openSUSE:Factory/ansible-core-2.19/ansible-core-2.19.changes 2026-03-27 06:36:06.684852122 +0100 +++ /work/SRC/openSUSE:Factory/.ansible-core-2.19.new.11940/ansible-core-2.19.changes 2026-04-21 12:47:31.489154411 +0200 @@ -1,0 +2,16 @@ +Tue Apr 21 06:09:25 UTC 2026 - Johannes Kastl <[email protected]> + +- update to 2.19.9: + https://github.com/ansible/ansible/blob/v2.19.9/changelogs/CHANGELOG-v2.19.rst + * Minor Changes + - ansible-test - Generate dist_info when running tests. + - ansible-test - Replace the parallels managed macOS provider + with a new mac provider. + - ansible-test - Switch managed macOS remotes from x86_64 to + aarch64. + * Bugfixes + - ansible-galaxy collection - Fix using the server + configuration for validate_certs when downloading + collections. (#86694) + +------------------------------------------------------------------- @@ -4,0 +21 @@ + https://github.com/ansible/ansible/blob/v2.19.8/changelogs/CHANGELOG-v2.19.rst Old: ---- ansible_core-2.19.8.tar.gz ansible_core-2.19.8.tar.gz.sha256 New: ---- ansible_core-2.19.9.tar.gz ansible_core-2.19.9.tar.gz.sha256 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ansible-core-2.19.spec ++++++ --- /var/tmp/diff_new_pack.tXrl5c/_old 2026-04-21 12:47:32.361190584 +0200 +++ /var/tmp/diff_new_pack.tXrl5c/_new 2026-04-21 12:47:32.365190750 +0200 @@ -43,7 +43,7 @@ %endif Name: ansible-core-2.19 -Version: 2.19.8 +Version: 2.19.9 Release: 0 Summary: Radically simple IT automation License: GPL-3.0-or-later ++++++ ansible_core-2.19.8.tar.gz -> ansible_core-2.19.9.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/PKG-INFO new/ansible_core-2.19.9/PKG-INFO --- old/ansible_core-2.19.8/PKG-INFO 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/PKG-INFO 2026-04-21 02:40:29.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ansible-core -Version: 2.19.8 +Version: 2.19.9 Summary: Radically simple IT automation Author: Ansible Project Project-URL: Homepage, https://ansible.com/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/ansible_core.egg-info/PKG-INFO new/ansible_core-2.19.9/ansible_core.egg-info/PKG-INFO --- old/ansible_core-2.19.8/ansible_core.egg-info/PKG-INFO 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/ansible_core.egg-info/PKG-INFO 2026-04-21 02:40:29.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ansible-core -Version: 2.19.8 +Version: 2.19.9 Summary: Radically simple IT automation Author: Ansible Project Project-URL: Homepage, https://ansible.com/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/ansible_core.egg-info/SOURCES.txt new/ansible_core-2.19.9/ansible_core.egg-info/SOURCES.txt --- old/ansible_core-2.19.8/ansible_core.egg-info/SOURCES.txt 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/ansible_core.egg-info/SOURCES.txt 2026-04-21 02:40:29.000000000 +0200 @@ -562,6 +562,7 @@ lib/ansible/plugins/callback/tree.py lib/ansible/plugins/cliconf/__init__.py lib/ansible/plugins/connection/__init__.py +lib/ansible/plugins/connection/_paramiko_ssh.py lib/ansible/plugins/connection/local.py lib/ansible/plugins/connection/paramiko_ssh.py lib/ansible/plugins/connection/psrp.py @@ -1167,6 +1168,8 @@ test/integration/targets/ansible-test-integration/ansible_collections/ns/col/plugins/modules/hello.py test/integration/targets/ansible-test-integration/ansible_collections/ns/col/tests/integration/targets/hello/aliases test/integration/targets/ansible-test-integration/ansible_collections/ns/col/tests/integration/targets/hello/tasks/main.yml +test/integration/targets/ansible-test-metadata/aliases +test/integration/targets/ansible-test-metadata/runme.sh test/integration/targets/ansible-test-no-tty/aliases test/integration/targets/ansible-test-no-tty/runme.sh test/integration/targets/ansible-test-no-tty/ansible_collections/ns/col/run-with-pty.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/changelogs/CHANGELOG-v2.19.rst new/ansible_core-2.19.9/changelogs/CHANGELOG-v2.19.rst --- old/ansible_core-2.19.8/changelogs/CHANGELOG-v2.19.rst 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/changelogs/CHANGELOG-v2.19.rst 2026-04-21 02:40:29.000000000 +0200 @@ -4,6 +4,27 @@ .. contents:: Topics +v2.19.9 +======= + +Release Summary +--------------- + +| Release Date: 2026-04-20 +| `Porting Guide <https://docs.ansible.com/ansible-core/2.19/porting_guides/porting_guide_core_2.19.html>`__ + +Minor Changes +------------- + +- ansible-test - Generate ``dist_info`` when running tests. +- ansible-test - Replace the ``parallels`` managed macOS provider with a new ``mac`` provider. +- ansible-test - Switch managed macOS remotes from x86_64 to aarch64. + +Bugfixes +-------- + +- ansible-galaxy collection - Fix using the server configuration for ``validate_certs`` when downloading collections. (https://github.com/ansible/ansible/issues/86694) + v2.19.8 ======= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/changelogs/changelog.yaml new/ansible_core-2.19.9/changelogs/changelog.yaml --- old/ansible_core-2.19.8/changelogs/changelog.yaml 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/changelogs/changelog.yaml 2026-04-21 02:40:29.000000000 +0200 @@ -1559,3 +1559,36 @@ - configlookupfix.yml - pyyaml-name.yml release_date: '2026-03-16' + 2.19.9: + changes: + release_summary: '| Release Date: 2026-04-20 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.19/porting_guides/porting_guide_core_2.19.html>`__ + + ' + codename: What Is and What Should Never Be + fragments: + - 2.19.9_summary.yaml + release_date: '2026-04-20' + 2.19.9rc1: + changes: + bugfixes: + - ansible-galaxy collection - Fix using the server configuration for ``validate_certs`` + when downloading collections. (https://github.com/ansible/ansible/issues/86694) + minor_changes: + - ansible-test - Generate ``dist_info`` when running tests. + - ansible-test - Replace the ``parallels`` managed macOS provider with a new + ``mac`` provider. + - ansible-test - Switch managed macOS remotes from x86_64 to aarch64. + release_summary: '| Release Date: 2026-04-13 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.19/porting_guides/porting_guide_core_2.19.html>`__ + + ' + codename: What Is and What Should Never Be + fragments: + - 2.19.9rc1_summary.yaml + - ansible-galaxy-validate_certs-server-config.yml + - ansible-test-dist-info.yml + - ansible-test-macos-aarch64.yml + release_date: '2026-04-13' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/lib/ansible/cli/galaxy.py new/ansible_core-2.19.9/lib/ansible/cli/galaxy.py --- old/ansible_core-2.19.8/lib/ansible/cli/galaxy.py 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/lib/ansible/cli/galaxy.py 2026-04-21 02:40:29.000000000 +0200 @@ -80,8 +80,10 @@ if 'artifacts_manager' in kwargs: return wrapped_method(*args, **kwargs) - # FIXME: use validate_certs context from Galaxy servers when downloading collections - # .get used here for when this is used in a non-CLI context + # configures validate_certs for type 'url' only + # type 'galaxy' inherits and overrides resolved_validate_certs + # type 'git' recalculates resolved_validate_certs + # NOTE: .get used here for when this is used in a non-CLI context artifacts_manager_kwargs = {'validate_certs': context.CLIARGS.get('resolved_validate_certs', True)} keyring = context.CLIARGS.get('keyring', None) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/lib/ansible/galaxy/collection/concrete_artifact_manager.py new/ansible_core-2.19.9/lib/ansible/galaxy/collection/concrete_artifact_manager.py --- old/ansible_core-2.19.8/lib/ansible/galaxy/collection/concrete_artifact_manager.py 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/lib/ansible/galaxy/collection/concrete_artifact_manager.py 2026-04-21 02:40:29.000000000 +0200 @@ -28,7 +28,7 @@ from ansible import context from ansible.errors import AnsibleError from ansible.galaxy import get_collections_galaxy_meta_info -from ansible.galaxy.api import should_retry_error +from ansible.galaxy.api import should_retry_error, CollectionVersionMetadata, GalaxyAPI from ansible.galaxy.dependency_resolution.dataclasses import _GALAXY_YAML from ansible.galaxy.user_agent import user_agent from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text @@ -68,8 +68,7 @@ self._artifact_cache = {} # type: dict[bytes, bytes] self._galaxy_artifact_cache = {} # type: dict[Candidate | Requirement, bytes] self._artifact_meta_cache = {} # type: dict[bytes, dict[str, str | list[str] | dict[str, str] | None | t.Type[Sentinel]]] - self._galaxy_collection_cache = {} # type: dict[Candidate | Requirement, tuple[str, str, GalaxyToken]] - self._galaxy_collection_origin_cache = {} # type: dict[Candidate, tuple[str, list[dict[str, str]]]] + self._galaxy_collection_cache: dict[Candidate, tuple[CollectionVersionMetadata, GalaxyAPI]] = {} self._b_working_directory = b_working_directory # type: bytes self._supplemental_signature_cache = {} # type: dict[str, str] self._keyring = keyring # type: str @@ -104,15 +103,12 @@ def get_galaxy_artifact_source_info(self, collection): # type: (Candidate) -> dict[str, t.Union[str, list[dict[str, str]]]] - server = collection.src.api_server try: - download_url = self._galaxy_collection_cache[collection][0] - signatures_url, signatures = self._galaxy_collection_origin_cache[collection] + metadata, server = self._galaxy_collection_cache[collection] except KeyError as key_err: raise RuntimeError( - 'The is no known source for {coll!s}'. - format(coll=collection), + f"There is no known source for {collection!s}" ) from key_err return { @@ -120,14 +116,13 @@ "namespace": collection.namespace, "name": collection.name, "version": collection.ver, - "server": server, - "version_url": signatures_url, - "download_url": download_url, - "signatures": signatures, + "server": server.api_server, + "version_url": metadata.signatures_url, + "download_url": metadata.download_url, + "signatures": metadata.signatures, } - def get_galaxy_artifact_path(self, collection): - # type: (t.Union[Candidate, Requirement]) -> bytes + def get_galaxy_artifact_path(self, collection: Candidate) -> bytes: """Given a Galaxy-stored collection, return a cached path. If it's not yet on disk, this method downloads the artifact first. @@ -138,11 +133,10 @@ pass try: - url, sha256_hash, token = self._galaxy_collection_cache[collection] + metadata, api = self._galaxy_collection_cache[collection] except KeyError as key_err: raise RuntimeError( - 'There is no known source for {coll!s}'. - format(coll=collection), + f'There is no known source for {collection!s}' ) from key_err display.vvvv( @@ -152,39 +146,27 @@ try: b_artifact_path = _download_file( - url, + metadata.download_url, self._b_working_directory, - expected_hash=sha256_hash, - validate_certs=self._validate_certs, - token=token, + expected_hash=metadata.artifact_sha256, + validate_certs=api.validate_certs, + token=api.token, ) # type: bytes except URLError as err: raise AnsibleError( 'Failed to download collection tar ' - "from '{coll_src!s}': {download_err!s}". - format( - coll_src=to_native(collection.src), - download_err=to_native(err), - ), + f"from '{api!s}': {err!s}" ) from err except Exception as err: raise AnsibleError( 'Failed to download collection tar ' - "from '{coll_src!s}' due to the following unforeseen error: " - '{download_err!s}'. - format( - coll_src=to_native(collection.src), - download_err=to_native(err), - ), + f"from '{api!s}' due to the following unforeseen error: " + f'{err!s}' ) from err else: display.vvv( - "Collection '{coll!s}' obtained from " - 'server {server!s} {url!s}'.format( - coll=collection, server=collection.src or 'Galaxy', - url=collection.src.api_server if collection.src is not None - else '', - ) + f"Collection '{collection!s}' obtained from server {api!s} " + f"{api.api_server!s}" ) self._galaxy_artifact_cache[collection] = b_artifact_path @@ -338,15 +320,13 @@ self._artifact_meta_cache[collection.src] = collection_meta return collection_meta - def save_collection_source(self, collection, url, sha256_hash, token, signatures_url, signatures): - # type: (Candidate, str, str, GalaxyToken, str, list[dict[str, str]]) -> None - """Store collection URL, SHA256 hash and Galaxy API token. + def save_collection_source(self, collection: Candidate, metadata: CollectionVersionMetadata, api: GalaxyAPI) -> None: + """Store collection version metadata and origin. This is a hook that is supposed to be called before attempting to download Galaxy-based collections with ``get_galaxy_artifact_path()``. """ - self._galaxy_collection_cache[collection] = url, sha256_hash, token - self._galaxy_collection_origin_cache[collection] = signatures_url, signatures + self._galaxy_collection_cache[collection] = (metadata, api) @classmethod @contextmanager diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/lib/ansible/galaxy/collection/galaxy_api_proxy.py new/ansible_core-2.19.9/lib/ansible/galaxy/collection/galaxy_api_proxy.py --- old/ansible_core-2.19.8/lib/ansible/galaxy/collection/galaxy_api_proxy.py 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/lib/ansible/galaxy/collection/galaxy_api_proxy.py 2026-04-21 02:40:29.000000000 +0200 @@ -146,11 +146,8 @@ else: self._concrete_art_mgr.save_collection_source( collection_candidate, - version_metadata.download_url, - version_metadata.artifact_sha256, - api.token, - version_metadata.signatures_url, - version_metadata.signatures, + version_metadata, + api ) return version_metadata diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/lib/ansible/module_utils/ansible_release.py new/ansible_core-2.19.9/lib/ansible/module_utils/ansible_release.py --- old/ansible_core-2.19.8/lib/ansible/module_utils/ansible_release.py 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/lib/ansible/module_utils/ansible_release.py 2026-04-21 02:40:29.000000000 +0200 @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.19.8' +__version__ = '2.19.9' __author__ = 'Ansible, Inc.' __codename__ = "What Is and What Should Never Be" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/lib/ansible/plugins/connection/_paramiko_ssh.py new/ansible_core-2.19.9/lib/ansible/plugins/connection/_paramiko_ssh.py --- old/ansible_core-2.19.8/lib/ansible/plugins/connection/_paramiko_ssh.py 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.19.9/lib/ansible/plugins/connection/_paramiko_ssh.py 2026-04-21 02:40:29.000000000 +0200 @@ -0,0 +1,713 @@ +# (c) 2012, Michael DeHaan <[email protected]> +# (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import annotations + +DOCUMENTATION = """ + author: Ansible Core Team + name: paramiko + short_description: Run tasks via Python SSH (paramiko) + deprecated: + removed_in: "2.21" + why: Paramiko versions older than 3.3.2 and 3.4.1 will be incompatible with cryptography 48.0.0. + alternatives: ansible.builtin.ssh + description: + - Use the Python SSH implementation (Paramiko) to connect to targets + - The paramiko transport is provided because many distributions, in particular EL6 and before do not support ControlPersist + in their SSH implementations. + - This is needed on the Ansible control machine to be reasonably efficient with connections. + Thus paramiko is faster for most users on these platforms. + Users with ControlPersist capability can consider using -c ssh or configuring the transport in the configuration file. + - This plugin also borrows a lot of settings from the ssh plugin as they both cover the same protocol. + version_added: "0.1" + options: + remote_addr: + description: + - Address of the remote target + default: inventory_hostname + type: string + vars: + - name: inventory_hostname + - name: ansible_host + - name: ansible_ssh_host + - name: ansible_paramiko_host + port: + description: Remote port to connect to. + type: int + default: 22 + ini: + - section: defaults + key: remote_port + - section: paramiko_connection + key: remote_port + version_added: '2.15' + env: + - name: ANSIBLE_REMOTE_PORT + - name: ANSIBLE_REMOTE_PARAMIKO_PORT + version_added: '2.15' + vars: + - name: ansible_port + - name: ansible_ssh_port + - name: ansible_paramiko_port + version_added: '2.15' + keyword: + - name: port + remote_user: + description: + - User to login/authenticate as + - Can be set from the CLI via the C(--user) or C(-u) options. + type: string + vars: + - name: ansible_user + - name: ansible_ssh_user + - name: ansible_paramiko_user + env: + - name: ANSIBLE_REMOTE_USER + - name: ANSIBLE_PARAMIKO_REMOTE_USER + version_added: '2.5' + ini: + - section: defaults + key: remote_user + - section: paramiko_connection + key: remote_user + version_added: '2.5' + keyword: + - name: remote_user + password: + description: + - Secret used to either login the ssh server or as a passphrase for ssh keys that require it + - Can be set from the CLI via the C(--ask-pass) option. + type: string + vars: + - name: ansible_password + - name: ansible_ssh_pass + - name: ansible_ssh_password + - name: ansible_paramiko_pass + - name: ansible_paramiko_password + version_added: '2.5' + use_rsa_sha2_algorithms: + description: + - Whether or not to enable RSA SHA2 algorithms for pubkeys and hostkeys + - On paramiko versions older than 2.9, this only affects hostkeys + - For behavior matching paramiko<2.9 set this to V(False) + vars: + - name: ansible_paramiko_use_rsa_sha2_algorithms + ini: + - {key: use_rsa_sha2_algorithms, section: paramiko_connection} + env: + - {name: ANSIBLE_PARAMIKO_USE_RSA_SHA2_ALGORITHMS} + default: True + type: boolean + version_added: '2.14' + host_key_auto_add: + description: 'Automatically add host keys' + env: [{name: ANSIBLE_PARAMIKO_HOST_KEY_AUTO_ADD}] + ini: + - {key: host_key_auto_add, section: paramiko_connection} + type: boolean + look_for_keys: + default: True + description: 'False to disable searching for private key files in ~/.ssh/' + env: [{name: ANSIBLE_PARAMIKO_LOOK_FOR_KEYS}] + ini: + - {key: look_for_keys, section: paramiko_connection} + type: boolean + proxy_command: + default: '' + description: + - Proxy information for running the connection via a jumphost. + type: string + env: [{name: ANSIBLE_PARAMIKO_PROXY_COMMAND}] + ini: + - {key: proxy_command, section: paramiko_connection} + vars: + - name: ansible_paramiko_proxy_command + version_added: '2.15' + pty: + default: True + description: 'SUDO usually requires a PTY, True to give a PTY and False to not give a PTY.' + env: + - name: ANSIBLE_PARAMIKO_PTY + ini: + - section: paramiko_connection + key: pty + type: boolean + record_host_keys: + default: True + description: 'Save the host keys to a file' + env: [{name: ANSIBLE_PARAMIKO_RECORD_HOST_KEYS}] + ini: + - section: paramiko_connection + key: record_host_keys + type: boolean + host_key_checking: + description: 'Set this to "False" if you want to avoid host key checking by the underlying tools Ansible uses to connect to the host' + type: boolean + default: True + env: + - name: ANSIBLE_HOST_KEY_CHECKING + - name: ANSIBLE_SSH_HOST_KEY_CHECKING + version_added: '2.5' + - name: ANSIBLE_PARAMIKO_HOST_KEY_CHECKING + version_added: '2.5' + ini: + - section: defaults + key: host_key_checking + - section: paramiko_connection + key: host_key_checking + version_added: '2.5' + vars: + - name: ansible_host_key_checking + version_added: '2.5' + - name: ansible_ssh_host_key_checking + version_added: '2.5' + - name: ansible_paramiko_host_key_checking + version_added: '2.5' + use_persistent_connections: + description: 'Toggles the use of persistence for connections' + type: boolean + default: False + env: + - name: ANSIBLE_USE_PERSISTENT_CONNECTIONS + ini: + - section: defaults + key: use_persistent_connections + banner_timeout: + type: float + default: 30 + version_added: '2.14' + description: + - Configures, in seconds, the amount of time to wait for the SSH + banner to be presented. This option is supported by paramiko + version 1.15.0 or newer. + ini: + - section: paramiko_connection + key: banner_timeout + env: + - name: ANSIBLE_PARAMIKO_BANNER_TIMEOUT + timeout: + type: int + default: 10 + description: Number of seconds until the plugin gives up on failing to establish a TCP connection. + ini: + - section: defaults + key: timeout + - section: ssh_connection + key: timeout + version_added: '2.11' + - section: paramiko_connection + key: timeout + version_added: '2.15' + env: + - name: ANSIBLE_TIMEOUT + - name: ANSIBLE_SSH_TIMEOUT + version_added: '2.11' + - name: ANSIBLE_PARAMIKO_TIMEOUT + version_added: '2.15' + vars: + - name: ansible_ssh_timeout + version_added: '2.11' + - name: ansible_paramiko_timeout + version_added: '2.15' + cli: + - name: timeout + private_key_file: + description: + - Path to private key file to use for authentication. + type: string + ini: + - section: defaults + key: private_key_file + - section: paramiko_connection + key: private_key_file + version_added: '2.15' + env: + - name: ANSIBLE_PRIVATE_KEY_FILE + - name: ANSIBLE_PARAMIKO_PRIVATE_KEY_FILE + version_added: '2.15' + vars: + - name: ansible_private_key_file + - name: ansible_ssh_private_key_file + - name: ansible_paramiko_private_key_file + version_added: '2.15' + cli: + - name: private_key_file + option: '--private-key' +""" + +import os +import socket +import tempfile +import traceback +import fcntl +import re +import typing as t + +from ansible.module_utils.compat.version import LooseVersion +from binascii import hexlify + +from ansible.errors import ( + AnsibleAuthenticationFailure, + AnsibleConnectionFailure, + AnsibleError, + AnsibleFileNotFound, +) + +from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text +from ansible.module_utils.compat.paramiko import _PARAMIKO_IMPORT_ERR as PARAMIKO_IMPORT_ERR, _paramiko as paramiko +from ansible.plugins.connection import ConnectionBase +from ansible.utils.display import Display +from ansible.utils.path import makedirs_safe +from ansible.module_utils._internal import _deprecator + +display = Display() + + +AUTHENTICITY_MSG = """ +paramiko: The authenticity of host '%s' can't be established. +The %s key fingerprint is %s. +Are you sure you want to continue connecting (yes/no)? +""" + +# SSH Options Regex +SETTINGS_REGEX = re.compile(r'(\w+)(?:\s*=\s*|\s+)(.+)') + +MissingHostKeyPolicy: type = object +if paramiko: + MissingHostKeyPolicy = paramiko.MissingHostKeyPolicy + + +class MyAddPolicy(MissingHostKeyPolicy): + """ + Based on AutoAddPolicy in paramiko so we can determine when keys are added + + and also prompt for input. + + Policy for automatically adding the hostname and new host key to the + local L{HostKeys} object, and saving it. This is used by L{SSHClient}. + """ + + def __init__(self, connection: Connection) -> None: + self.connection = connection + self._options = connection._options + + def missing_host_key(self, client, hostname, key) -> None: + + if all((self.connection.get_option('host_key_checking'), not self.connection.get_option('host_key_auto_add'))): + + fingerprint = hexlify(key.get_fingerprint()) + ktype = key.get_name() + + if self.connection.get_option('use_persistent_connections') or self.connection.force_persistence: + # don't print the prompt string since the user cannot respond + # to the question anyway + raise AnsibleError(AUTHENTICITY_MSG[1:92] % (hostname, ktype, fingerprint)) + + inp = to_text( + display.prompt_until(AUTHENTICITY_MSG % (hostname, ktype, fingerprint), private=False), + errors='surrogate_or_strict' + ) + + if inp not in ['yes', 'y', '']: + raise AnsibleError("host connection rejected by user") + + key._added_by_ansible_this_time = True + + # existing implementation below: + client._host_keys.add(hostname, key.get_name(), key) + + # host keys are actually saved in close() function below + # in order to control ordering. + + +# keep connection objects on a per host basis to avoid repeated attempts to reconnect + +SSH_CONNECTION_CACHE: dict[str, paramiko.client.SSHClient] = {} +SFTP_CONNECTION_CACHE: dict[str, paramiko.sftp_client.SFTPClient] = {} + + +class Connection(ConnectionBase): + """ SSH based connections with Paramiko """ + + transport = 'paramiko' + _log_channel: str | None = None + + def __init__(self, *args, **kwargs): + display.deprecated( # pylint: disable=ansible-deprecated-unnecessary-collection-name + msg='The paramiko connection plugin is deprecated.', + version='2.21', + deprecator=_deprecator.ANSIBLE_CORE_DEPRECATOR, # entire plugin being removed; this improves the messaging + ) + + super().__init__(*args, **kwargs) + + def _cache_key(self) -> str: + return "%s__%s__" % (self.get_option('remote_addr'), self.get_option('remote_user')) + + def _connect(self) -> Connection: + cache_key = self._cache_key() + if cache_key in SSH_CONNECTION_CACHE: + self.ssh = SSH_CONNECTION_CACHE[cache_key] + else: + self.ssh = SSH_CONNECTION_CACHE[cache_key] = self._connect_uncached() + + self._connected = True + return self + + def _set_log_channel(self, name: str) -> None: + """Mimic paramiko.SSHClient.set_log_channel""" + self._log_channel = name + + def _parse_proxy_command(self, port: int = 22) -> dict[str, t.Any]: + proxy_command = self.get_option('proxy_command') or None + + sock_kwarg = {} + if proxy_command: + replacers = { + '%h': self.get_option('remote_addr'), + '%p': port, + '%r': self.get_option('remote_user') + } + for find, replace in replacers.items(): + proxy_command = proxy_command.replace(find, str(replace)) + try: + sock_kwarg = {'sock': paramiko.ProxyCommand(proxy_command)} + display.vvv("CONFIGURE PROXY COMMAND FOR CONNECTION: %s" % proxy_command, host=self.get_option('remote_addr')) + except AttributeError: + display.warning('Paramiko ProxyCommand support unavailable. ' + 'Please upgrade to Paramiko 1.9.0 or newer. ' + 'Not using configured ProxyCommand') + + return sock_kwarg + + def _connect_uncached(self) -> paramiko.SSHClient: + """ activates the connection object """ + + if paramiko is None: + raise AnsibleError("paramiko is not installed: %s" % to_native(PARAMIKO_IMPORT_ERR)) + + port = self.get_option('port') + display.vvv("ESTABLISH PARAMIKO SSH CONNECTION FOR USER: %s on PORT %s TO %s" % (self.get_option('remote_user'), port, self.get_option('remote_addr')), + host=self.get_option('remote_addr')) + + ssh = paramiko.SSHClient() + + # Set pubkey and hostkey algorithms to disable, the only manipulation allowed currently + # is keeping or omitting rsa-sha2 algorithms + # default_keys: t.Tuple[str] = () + paramiko_preferred_pubkeys = getattr(paramiko.Transport, '_preferred_pubkeys', ()) + paramiko_preferred_hostkeys = getattr(paramiko.Transport, '_preferred_keys', ()) + use_rsa_sha2_algorithms = self.get_option('use_rsa_sha2_algorithms') + disabled_algorithms: t.Dict[str, t.Iterable[str]] = {} + if not use_rsa_sha2_algorithms: + if paramiko_preferred_pubkeys: + disabled_algorithms['pubkeys'] = tuple(a for a in paramiko_preferred_pubkeys if 'rsa-sha2' in a) + if paramiko_preferred_hostkeys: + disabled_algorithms['keys'] = tuple(a for a in paramiko_preferred_hostkeys if 'rsa-sha2' in a) + + # override paramiko's default logger name + if self._log_channel is not None: + ssh.set_log_channel(self._log_channel) + + self.keyfile = os.path.expanduser("~/.ssh/known_hosts") + + if self.get_option('host_key_checking'): + for ssh_known_hosts in ("/etc/ssh/ssh_known_hosts", "/etc/openssh/ssh_known_hosts"): + try: + # TODO: check if we need to look at several possible locations, possible for loop + ssh.load_system_host_keys(ssh_known_hosts) + break + except OSError: + pass # file was not found, but not required to function + ssh.load_system_host_keys() + + ssh_connect_kwargs = self._parse_proxy_command(port) + + ssh.set_missing_host_key_policy(MyAddPolicy(self)) + + conn_password = self.get_option('password') + + allow_agent = True + + if conn_password is not None: + allow_agent = False + + try: + key_filename = None + if self.get_option('private_key_file'): + key_filename = os.path.expanduser(self.get_option('private_key_file')) + + # paramiko 2.2 introduced auth_timeout parameter + if LooseVersion(paramiko.__version__) >= LooseVersion('2.2.0'): + ssh_connect_kwargs['auth_timeout'] = self.get_option('timeout') + + # paramiko 1.15 introduced banner timeout parameter + if LooseVersion(paramiko.__version__) >= LooseVersion('1.15.0'): + ssh_connect_kwargs['banner_timeout'] = self.get_option('banner_timeout') + + ssh.connect( + self.get_option('remote_addr').lower(), + username=self.get_option('remote_user'), + allow_agent=allow_agent, + look_for_keys=self.get_option('look_for_keys'), + key_filename=key_filename, + password=conn_password, + timeout=self.get_option('timeout'), + port=port, + disabled_algorithms=disabled_algorithms, + **ssh_connect_kwargs, + ) + except paramiko.ssh_exception.BadHostKeyException as e: + raise AnsibleConnectionFailure('host key mismatch for %s' % e.hostname) + except paramiko.ssh_exception.AuthenticationException as ex: + raise AnsibleAuthenticationFailure() from ex + except Exception as ex: + msg = str(ex) + if u"PID check failed" in msg: + raise AnsibleError("paramiko version issue, please upgrade paramiko on the machine running ansible") from ex + elif u"Private key file is encrypted" in msg: + msg = 'ssh %s@%s:%s : %s\nTo connect as a different user, use -u <username>.' % ( + self.get_option('remote_user'), self.get_options('remote_addr'), port, msg) + raise AnsibleConnectionFailure(msg) from ex + else: + raise AnsibleConnectionFailure(msg) from ex + + return ssh + + def exec_command(self, cmd: str, in_data: bytes | None = None, sudoable: bool = True) -> tuple[int, bytes, bytes]: + """ run a command on the remote host """ + + super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) + + if in_data: + raise AnsibleError("Internal Error: this module does not support optimized module pipelining") + + bufsize = 4096 + + try: + self.ssh.get_transport().set_keepalive(5) + chan = self.ssh.get_transport().open_session() + except Exception as e: + text_e = to_text(e) + msg = u"Failed to open session" + if text_e: + msg += u": %s" % text_e + raise AnsibleConnectionFailure(to_native(msg)) + + # sudo usually requires a PTY (cf. requiretty option), therefore + # we give it one by default (pty=True in ansible.cfg), and we try + # to initialise from the calling environment when sudoable is enabled + if self.get_option('pty') and sudoable: + chan.get_pty(term=os.getenv('TERM', 'vt100'), width=int(os.getenv('COLUMNS', 0)), height=int(os.getenv('LINES', 0))) + + display.vvv("EXEC %s" % cmd, host=self.get_option('remote_addr')) + + cmd = to_bytes(cmd, errors='surrogate_or_strict') + + no_prompt_out = b'' + no_prompt_err = b'' + become_output = b'' + + try: + chan.exec_command(cmd) + if self.become and self.become.expect_prompt(): + passprompt = False + become_sucess = False + while not (become_sucess or passprompt): + display.debug('Waiting for Privilege Escalation input') + + chunk = chan.recv(bufsize) + display.debug("chunk is: %r" % chunk) + if not chunk: + if b'unknown user' in become_output: + n_become_user = to_native(self.become.get_option('become_user')) + raise AnsibleError('user %s does not exist' % n_become_user) + else: + break + # raise AnsibleError('ssh connection closed waiting for password prompt') + become_output += chunk + + # need to check every line because we might get lectured + # and we might get the middle of a line in a chunk + for line in become_output.splitlines(True): + if self.become.check_success(line): + become_sucess = True + break + elif self.become.check_password_prompt(line): + passprompt = True + break + + if passprompt: + if self.become: + become_pass = self.become.get_option('become_pass') + chan.sendall(to_bytes(become_pass, errors='surrogate_or_strict') + b'\n') + else: + raise AnsibleError("A password is required but none was supplied") + else: + no_prompt_out += become_output + no_prompt_err += become_output + except socket.timeout: + raise AnsibleError('ssh timed out waiting for privilege escalation.\n' + to_text(become_output)) + + stdout = b''.join(chan.makefile('rb', bufsize)) + stderr = b''.join(chan.makefile_stderr('rb', bufsize)) + + return (chan.recv_exit_status(), no_prompt_out + stdout, no_prompt_out + stderr) + + def put_file(self, in_path: str, out_path: str) -> None: + """ transfer a file from local to remote """ + + super(Connection, self).put_file(in_path, out_path) + + display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.get_option('remote_addr')) + + if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')): + raise AnsibleFileNotFound("file or module does not exist: %s" % in_path) + + try: + self.sftp = self.ssh.open_sftp() + except Exception as e: + raise AnsibleError("failed to open a SFTP connection (%s)" % e) + + try: + self.sftp.put(to_bytes(in_path, errors='surrogate_or_strict'), to_bytes(out_path, errors='surrogate_or_strict')) + except OSError as ex: + raise AnsibleError(f"Failed to transfer file to {out_path!r}.") from ex + + def _connect_sftp(self) -> paramiko.sftp_client.SFTPClient: + + cache_key = "%s__%s__" % (self.get_option('remote_addr'), self.get_option('remote_user')) + if cache_key in SFTP_CONNECTION_CACHE: + return SFTP_CONNECTION_CACHE[cache_key] + else: + result = SFTP_CONNECTION_CACHE[cache_key] = self._connect().ssh.open_sftp() + return result + + def fetch_file(self, in_path: str, out_path: str) -> None: + """ save a remote file to the specified path """ + + super(Connection, self).fetch_file(in_path, out_path) + + display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.get_option('remote_addr')) + + try: + self.sftp = self._connect_sftp() + except Exception as e: + raise AnsibleError("failed to open a SFTP connection (%s)" % to_native(e)) + + try: + self.sftp.get(to_bytes(in_path, errors='surrogate_or_strict'), to_bytes(out_path, errors='surrogate_or_strict')) + except OSError as ex: + raise AnsibleError(f"Failed to transfer file from {in_path!r}.") from ex + + def _any_keys_added(self) -> bool: + + for hostname, keys in self.ssh._host_keys.items(): + for keytype, key in keys.items(): + added_this_time = getattr(key, '_added_by_ansible_this_time', False) + if added_this_time: + return True + return False + + def _save_ssh_host_keys(self, filename: str) -> None: + """ + not using the paramiko save_ssh_host_keys function as we want to add new SSH keys at the bottom so folks + don't complain about it :) + """ + + if not self._any_keys_added(): + return + + path = os.path.expanduser("~/.ssh") + makedirs_safe(path) + + with open(filename, 'w') as f: + + for hostname, keys in self.ssh._host_keys.items(): + + for keytype, key in keys.items(): + + # was f.write + added_this_time = getattr(key, '_added_by_ansible_this_time', False) + if not added_this_time: + f.write("%s %s %s\n" % (hostname, keytype, key.get_base64())) + + for hostname, keys in self.ssh._host_keys.items(): + + for keytype, key in keys.items(): + added_this_time = getattr(key, '_added_by_ansible_this_time', False) + if added_this_time: + f.write("%s %s %s\n" % (hostname, keytype, key.get_base64())) + + def reset(self) -> None: + if not self._connected: + return + self.close() + self._connect() + + def close(self) -> None: + """ terminate the connection """ + + cache_key = self._cache_key() + SSH_CONNECTION_CACHE.pop(cache_key, None) + SFTP_CONNECTION_CACHE.pop(cache_key, None) + + if hasattr(self, 'sftp'): + if self.sftp is not None: + self.sftp.close() + + if self.get_option('host_key_checking') and self.get_option('record_host_keys') and self._any_keys_added(): + + # add any new SSH host keys -- warning -- this could be slow + # (This doesn't acquire the connection lock because it needs + # to exclude only other known_hosts writers, not connections + # that are starting up.) + lockfile = self.keyfile.replace("known_hosts", ".known_hosts.lock") + dirname = os.path.dirname(self.keyfile) + makedirs_safe(dirname) + + KEY_LOCK = open(lockfile, 'w') + fcntl.lockf(KEY_LOCK, fcntl.LOCK_EX) + + try: + # just in case any were added recently + + self.ssh.load_system_host_keys() + self.ssh._host_keys.update(self.ssh._system_host_keys) + + # gather information about the current key file, so + # we can ensure the new file has the correct mode/owner + + key_dir = os.path.dirname(self.keyfile) + if os.path.exists(self.keyfile): + key_stat = os.stat(self.keyfile) + mode = key_stat.st_mode + uid = key_stat.st_uid + gid = key_stat.st_gid + else: + mode = 33188 + uid = os.getuid() + gid = os.getgid() + + # Save the new keys to a temporary file and move it into place + # rather than rewriting the file. We set delete=False because + # the file will be moved into place rather than cleaned up. + + tmp_keyfile = tempfile.NamedTemporaryFile(dir=key_dir, delete=False) + os.chmod(tmp_keyfile.name, mode & 0o7777) + os.chown(tmp_keyfile.name, uid, gid) + + self._save_ssh_host_keys(tmp_keyfile.name) + tmp_keyfile.close() + + os.rename(tmp_keyfile.name, self.keyfile) + + except Exception: + + # unable to save keys, including scenario when key was invalid + # and caught earlier + traceback.print_exc() + fcntl.lockf(KEY_LOCK, fcntl.LOCK_UN) + + self.ssh.close() + self._connected = False diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/lib/ansible/plugins/connection/paramiko_ssh.py new/ansible_core-2.19.9/lib/ansible/plugins/connection/paramiko_ssh.py --- old/ansible_core-2.19.8/lib/ansible/plugins/connection/paramiko_ssh.py 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/lib/ansible/plugins/connection/paramiko_ssh.py 2026-04-21 02:40:29.000000000 +0200 @@ -7,6 +7,10 @@ author: Ansible Core Team name: paramiko short_description: Run tasks via Python SSH (paramiko) + deprecated: + removed_in: "2.21" + why: Paramiko versions older than 3.3.2 and 3.4.1 will be incompatible with cryptography 48.0.0. + alternatives: ansible.builtin.ssh description: - Use the Python SSH implementation (Paramiko) to connect to targets - The paramiko transport is provided because many distributions, in particular EL6 and before do not support ControlPersist diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/lib/ansible/release.py new/ansible_core-2.19.9/lib/ansible/release.py --- old/ansible_core-2.19.8/lib/ansible/release.py 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/lib/ansible/release.py 2026-04-21 02:40:29.000000000 +0200 @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.19.8' +__version__ = '2.19.9' __author__ = 'Ansible, Inc.' __codename__ = "What Is and What Should Never Be" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/test/integration/targets/ansible-test-cloud-azure/aliases new/ansible_core-2.19.9/test/integration/targets/ansible-test-cloud-azure/aliases --- old/ansible_core-2.19.8/test/integration/targets/ansible-test-cloud-azure/aliases 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/test/integration/targets/ansible-test-cloud-azure/aliases 2026-04-21 02:40:29.000000000 +0200 @@ -1,3 +1,4 @@ cloud/azure shippable/generic/group1 context/controller +disabled/yes # Azure credential provisioner pool broken in Core CI (likely SP policy) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/test/integration/targets/ansible-test-metadata/aliases new/ansible_core-2.19.9/test/integration/targets/ansible-test-metadata/aliases --- old/ansible_core-2.19.8/test/integration/targets/ansible-test-metadata/aliases 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.19.9/test/integration/targets/ansible-test-metadata/aliases 2026-04-21 02:40:29.000000000 +0200 @@ -0,0 +1,3 @@ +shippable/posix/group3 # runs in the distro test containers +shippable/generic/group1 # runs in the default test container +context/controller diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/test/integration/targets/ansible-test-metadata/runme.sh new/ansible_core-2.19.9/test/integration/targets/ansible-test-metadata/runme.sh --- old/ansible_core-2.19.8/test/integration/targets/ansible-test-metadata/runme.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.19.9/test/integration/targets/ansible-test-metadata/runme.sh 2026-04-21 02:40:29.000000000 +0200 @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Verify that importlib.metadata can find ansible-core using the PYTHONPATH set by ansible-test. +# Regression test for https://github.com/ansible/ansible/issues/86695 + +set -eux + +VERSION=$(python -c "from importlib.metadata import version; print(version('ansible-core'))") + +test "$VERSION" = "$ANSIBLE_TEST_ANSIBLE_VERSION" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/test/lib/ansible_test/_data/completion/remote.txt new/ansible_core-2.19.9/test/lib/ansible_test/_data/completion/remote.txt --- old/ansible_core-2.19.8/test/lib/ansible_test/_data/completion/remote.txt 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/test/lib/ansible_test/_data/completion/remote.txt 2026-04-21 02:40:29.000000000 +0200 @@ -5,8 +5,8 @@ freebsd/13.5 python=3.11 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64 alias=freebsd/13 freebsd/14.2 python=3.11 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64 alias=freebsd/14,freebsd/latest freebsd python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64 -macos/15.3 python=3.13 python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64 alias=macos/15,macos/latest -macos python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64 +macos/15.3 python=3.13 python_dir=/usr/local/bin become=sudo provider=mac arch=aarch64 alias=macos/15,macos/latest +macos python_dir=/usr/local/bin become=sudo provider=mac arch=aarch64 rhel/9.7 python=3.9,3.12 become=sudo provider=aws arch=x86_64 alias=rhel/9 rhel/10.1 python=3.12 become=sudo provider=aws arch=x86_64 alias=rhel/10,rhel/latest rhel become=sudo provider=aws arch=x86_64 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/test/lib/ansible_test/_internal/ansible_util.py new/ansible_core-2.19.9/test/lib/ansible_test/_internal/ansible_util.py --- old/ansible_core-2.19.8/test/lib/ansible_test/_internal/ansible_util.py 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/test/lib/ansible_test/_internal/ansible_util.py 2026-04-21 02:40:29.000000000 +0200 @@ -4,6 +4,7 @@ import json import os +import pathlib import shutil import typing as t @@ -12,6 +13,10 @@ SOFT_RLIMIT_NOFILE, ) +from .io import ( + write_text_file, +) + from .util import ( common_environment, ApplicationError, @@ -22,6 +27,7 @@ ANSIBLE_SOURCE_ROOT, ANSIBLE_TEST_TOOLS_ROOT, MODE_FILE_EXECUTE, + get_ansible_version, raw_command, verified_chmod, ) @@ -248,15 +254,12 @@ raise RuntimeError(path) -# noinspection PyUnusedLocal @mutex def get_ansible_python_path(args: CommonConfig) -> str: """ Return a directory usable for PYTHONPATH, containing only the ansible package. If a temporary directory is required, it will be cached for the lifetime of the process and cleaned up at exit. """ - del args # not currently used - try: return get_ansible_python_path.python_path # type: ignore[attr-defined] except AttributeError: @@ -273,11 +276,38 @@ os.symlink(ANSIBLE_LIB_ROOT, os.path.join(python_path, 'ansible')) + if not args.explain: + generate_dist_info(python_path) + get_ansible_python_path.python_path = python_path # type: ignore[attr-defined] return python_path +def generate_dist_info(path: str) -> None: + """Generate a dist-info in the specified base directory.""" + version = get_ansible_version() + metadata = f'''\ +Metadata-Version: 2.1 +Name: ansible-core +Version: {version} +''' + python_path = pathlib.Path(path) + + current_dist_info = python_path / f'ansible_core-{version}.dist-info' + + for dist_info in pathlib.Path(path).glob('ansible_core-*.dist-info'): + if dist_info == current_dist_info: + continue + shutil.rmtree(dist_info, ignore_errors=True) + + metadata_path = current_dist_info / 'METADATA' + if metadata_path.is_file(): + return + + write_text_file(str(metadata_path), metadata, create_directories=True) + + class CollectionDetail: """Collection detail.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/test/lib/ansible_test/_internal/commands/integration/__init__.py new/ansible_core-2.19.9/test/lib/ansible_test/_internal/commands/integration/__init__.py --- old/ansible_core-2.19.8/test/lib/ansible_test/_internal/commands/integration/__init__.py 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/test/lib/ansible_test/_internal/commands/integration/__init__.py 2026-04-21 02:40:29.000000000 +0200 @@ -61,6 +61,7 @@ from ...util import ( ApplicationError, display, + get_ansible_version, SubprocessError, remove_tree, ) @@ -808,6 +809,7 @@ ANSIBLE_CALLBACKS_ENABLED=','.join(sorted(set(callback_plugins))), ANSIBLE_TEST_CI=args.metadata.ci_provider or get_ci_provider().code, ANSIBLE_TEST_COVERAGE='check' if args.coverage_check else ('yes' if args.coverage else ''), + ANSIBLE_TEST_ANSIBLE_VERSION=get_ansible_version(), OUTPUT_DIR=test_dir, INVENTORY_PATH=os.path.abspath(inventory_path), ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/test/lib/ansible_test/_internal/constants.py new/ansible_core-2.19.9/test/lib/ansible_test/_internal/constants.py --- old/ansible_core-2.19.8/test/lib/ansible_test/_internal/constants.py 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/test/lib/ansible_test/_internal/constants.py 2026-04-21 02:40:29.000000000 +0200 @@ -23,7 +23,7 @@ 'default', 'aws', 'azure', - 'parallels', + 'mac', ] SECCOMP_CHOICES = [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.8/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py new/ansible_core-2.19.9/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py --- old/ansible_core-2.19.8/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py 2026-03-23 18:38:39.000000000 +0100 +++ new/ansible_core-2.19.9/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py 2026-04-21 02:40:29.000000000 +0200 @@ -795,11 +795,10 @@ Required('why'): doc_string, 'alternative': doc_string, 'alternatives': doc_string, + Required('removed_from_collection'): collection_name, + 'removed': Any(True), } - if for_collection: - main_fields.update({Required('removed_from_collection'): collection_name, 'removed': Any(True)}) - date_schema = { Required('removed_at_date'): date(), } ++++++ ansible_core-2.19.8.tar.gz.sha256 -> ansible_core-2.19.9.tar.gz.sha256 ++++++ --- /work/SRC/openSUSE:Factory/ansible-core-2.19/ansible_core-2.19.8.tar.gz.sha256 2026-03-27 06:36:06.788856408 +0100 +++ /work/SRC/openSUSE:Factory/.ansible-core-2.19.new.11940/ansible_core-2.19.9.tar.gz.sha256 2026-04-21 12:47:31.557157232 +0200 @@ -1 +1 @@ -676e70925b775d2c8526b2bcea59c4b2195221bd6e4e3c0537e89387e08608bd ansible_core-2.19.8.tar.gz +74107de13d188ff579fb215bc3eb875c9198803215d6378ed588c7f35aba12f5 ansible_core-2.19.9.tar.gz
