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-11-06 19:55:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ansible-core-2.19 (Old) and /work/SRC/openSUSE:Factory/.ansible-core-2.19.new.3759290 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ansible-core-2.19" Fri Nov 6 19:55:32 2026 rev:14 rq:1395744 version:2.19.15 Changes: -------- --- /work/SRC/openSUSE:Factory/ansible-core-2.19/ansible-core-2.19.changes 2026-10-18 19:25:19.513788529 +0200 +++ /work/SRC/openSUSE:Factory/.ansible-core-2.19.new.3759290/ansible-core-2.19.changes 2026-11-06 20:00:55.402592324 +0100 @@ -1,0 +2,18 @@ +Fri Nov 6 08:45:59 UTC 2026 - Johannes Kastl <[email protected]> + +- update to 2.19.15: + https://github.com/ansible/ansible/blob/v2.19.15/changelogs/CHANGELOG-v2.19.rst + * Minor Changes + - ansible-test - Remove unsupported FreeBSD 13.5 remote. + * Security Fixes + - ansible-galaxy collection install - fix validating FILES.json + entries are contained in the collection before creating them. + (CVE-2026-89091) + - ansible-galaxy role install - fix validating the role name is + contained to the configured role path. (CVE-2026-105929) + * Bugfixes + - git module - updated to avoid possible incorrect parameters + read from the repo targets. CVE-2026-84503 +- remove patch ansible-core-CVE-2026-89091.patch + +------------------------------------------------------------------- Old: ---- ansible-core-CVE-2026-89091.patch ansible_core-2.19.14.tar.gz ansible_core-2.19.14.tar.gz.sha256 New: ---- ansible_core-2.19.15.tar.gz ansible_core-2.19.15.tar.gz.sha256 ----------(Old B)---------- Old: read from the repo targets. CVE-2026-84503 - remove patch ansible-core-CVE-2026-89091.patch ----------(Old E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ansible-core-2.19.spec ++++++ --- /var/tmp/diff_new_pack.oqkkwr/_old 2026-11-06 20:00:57.253676504 +0100 +++ /var/tmp/diff_new_pack.oqkkwr/_new 2026-11-06 20:00:57.257676686 +0100 @@ -43,7 +43,7 @@ %endif Name: ansible-core-2.19 -Version: 2.19.14 +Version: 2.19.15 Release: 0 Summary: Radically simple IT automation License: GPL-3.0-or-later @@ -52,8 +52,6 @@ Source1: ansible_core-%{version}.tar.gz.sha256 # CVE-2026-16493 (bsc#1272369) Patch0: ansible-core-CVE-2026-16493.patch -# CVE-2026-89091 (bsc#1284847) -Patch1: ansible-core-CVE-2026-89091.patch BuildArch: noarch Provides: ansible-core = %{version} ++++++ ansible_core-2.19.14.tar.gz -> ansible_core-2.19.15.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/PKG-INFO new/ansible_core-2.19.15/PKG-INFO --- old/ansible_core-2.19.14/PKG-INFO 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/PKG-INFO 2026-11-02 18:10:40.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ansible-core -Version: 2.19.14 +Version: 2.19.15 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.14/ansible_core.egg-info/PKG-INFO new/ansible_core-2.19.15/ansible_core.egg-info/PKG-INFO --- old/ansible_core-2.19.14/ansible_core.egg-info/PKG-INFO 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/ansible_core.egg-info/PKG-INFO 2026-11-02 18:10:40.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ansible-core -Version: 2.19.14 +Version: 2.19.15 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.14/ansible_core.egg-info/SOURCES.txt new/ansible_core-2.19.15/ansible_core.egg-info/SOURCES.txt --- old/ansible_core-2.19.14/ansible_core.egg-info/SOURCES.txt 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/ansible_core.egg-info/SOURCES.txt 2026-11-02 18:10:40.000000000 +0100 @@ -993,11 +993,13 @@ test/integration/targets/ansible-galaxy-collection/library/setup_collections.py test/integration/targets/ansible-galaxy-collection/meta/main.yml test/integration/targets/ansible-galaxy-collection/tasks/build.yml +test/integration/targets/ansible-galaxy-collection/tasks/build_and_install_tarball.yml test/integration/targets/ansible-galaxy-collection/tasks/download.yml test/integration/targets/ansible-galaxy-collection/tasks/fail_fast_resolvelib.yml test/integration/targets/ansible-galaxy-collection/tasks/init.yml test/integration/targets/ansible-galaxy-collection/tasks/install.yml test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml +test/integration/targets/ansible-galaxy-collection/tasks/install_tarball.yml test/integration/targets/ansible-galaxy-collection/tasks/list.yml test/integration/targets/ansible-galaxy-collection/tasks/main.yml test/integration/targets/ansible-galaxy-collection/tasks/pinned_pre_releases_in_deptree.yml @@ -4897,7 +4899,6 @@ test/units/cli/test_playbook.py test/units/cli/test_vault.py test/units/cli/arguments/test_option_helpers.py -test/units/cli/galaxy/test_collection_extract_tar.py test/units/cli/galaxy/test_display_collection.py test/units/cli/galaxy/test_display_header.py test/units/cli/galaxy/test_display_role.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/changelogs/CHANGELOG-v2.19.rst new/ansible_core-2.19.15/changelogs/CHANGELOG-v2.19.rst --- old/ansible_core-2.19.14/changelogs/CHANGELOG-v2.19.rst 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/changelogs/CHANGELOG-v2.19.rst 2026-11-02 18:10:40.000000000 +0100 @@ -4,6 +4,31 @@ .. contents:: Topics +v2.19.15 +======== + +Release Summary +--------------- + +| Release Date: 2026-11-02 +| `Porting Guide <https://docs.ansible.com/ansible-core/2.19/porting_guides/porting_guide_core_2.19.html>`__ + +Minor Changes +------------- + +- ansible-test - Remove unsupported FreeBSD 13.5 remote. + +Security Fixes +-------------- + +- ``ansible-galaxy collection install`` - fix validating ``FILES.json`` entries are contained in the collection before creating them. (CVE-2026-89091) +- ``ansible-galaxy role install`` - fix validating the role name is contained to the configured role path. (CVE-2026-105929) + +Bugfixes +-------- + +- git module - updated to avoid possible incorrect parameters read from the repo targets. CVE-2026-84503 + v2.19.14 ======== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/changelogs/changelog.yaml new/ansible_core-2.19.15/changelogs/changelog.yaml --- old/ansible_core-2.19.14/changelogs/changelog.yaml 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/changelogs/changelog.yaml 2026-11-02 18:10:40.000000000 +0100 @@ -1391,6 +1391,42 @@ - ansible-test-validate-modules-secret.yml - ensure_conn_cli_dir_perms.yml release_date: '2026-09-29' + 2.19.15: + changes: + release_summary: '| Release Date: 2026-11-02 + + | `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.15_summary.yaml + release_date: '2026-11-02' + 2.19.15rc1: + changes: + bugfixes: + - git module - updated to avoid possible incorrect parameters read from the + repo targets. CVE-2026-84503 + minor_changes: + - ansible-test - Remove unsupported FreeBSD 13.5 remote. + release_summary: '| Release Date: 2026-10-27 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.19/porting_guides/porting_guide_core_2.19.html>`__ + + ' + security_fixes: + - '``ansible-galaxy collection install`` - fix validating ``FILES.json`` entries + are contained in the collection before creating them. (CVE-2026-89091)' + - '``ansible-galaxy role install`` - fix validating the role name is contained + to the configured role path. (CVE-2026-105929)' + codename: What Is and What Should Never Be + fragments: + - 2.19.15rc1_summary.yaml + - ansible-galaxy-collection-check-path-containment.yml + - ansible-galaxy-role-name-containment.yml + - ansible-test-remove-freebsd-13.5.yml + - git_fixes.yml + release_date: '2026-10-27' 2.19.1rc1: changes: bugfixes: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/lib/ansible/cli/galaxy.py new/ansible_core-2.19.15/lib/ansible/cli/galaxy.py --- old/ansible_core-2.19.14/lib/ansible/cli/galaxy.py 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/lib/ansible/cli/galaxy.py 2026-11-02 18:10:40.000000000 +0100 @@ -61,6 +61,7 @@ from ansible.template import trust_as_template from ansible.utils.collection_loader import AnsibleCollectionConfig from ansible.utils.display import Display +from ansible.utils.path import is_subpath from ansible.utils.plugin_docs import get_versioned_doclink from ansible.utils.vars import load_extra_vars @@ -1471,6 +1472,15 @@ display.vvv('Processing role %s ' % role.name) + if not any( + is_subpath(os.path.join(path, role.name), path, real=True) + for path in context.CLIARGS['roles_path'] + ): + display.warning(f"The role name '{role.name}' resolves to a path outside of the configured role directory") + self.exit_without_ignore() + display.display(f'- skipping role {role.name}') + continue + # query the galaxy API for the role data if role.install_info is not None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/lib/ansible/galaxy/collection/__init__.py new/ansible_core-2.19.15/lib/ansible/galaxy/collection/__init__.py --- old/ansible_core-2.19.14/lib/ansible/galaxy/collection/__init__.py 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/lib/ansible/galaxy/collection/__init__.py 2026-11-02 18:10:40.000000000 +0100 @@ -1685,6 +1685,9 @@ except KeyError: raise AnsibleError("Unable to extract '%s' from collection" % dirname) + if not _is_child_path(to_bytes(tar_member.name, errors='surrogate_or_strict'), b_dest): + raise AnsibleError(f"Cannot extract directory '{dirname}' in collection: path is outside of collection '{tar_member.name}'") + b_dir_path = os.path.join(b_dest, to_bytes(dirname, errors='surrogate_or_strict')) b_parent_path = os.path.dirname(b_dir_path) @@ -1789,13 +1792,16 @@ def _is_child_path(path, parent_path, link_name=None): """ Checks that path is a path within the parent_path specified. """ b_path = to_bytes(path, errors='surrogate_or_strict') + b_parent_path = to_bytes(parent_path, errors='surrogate_or_strict') if link_name and not os.path.isabs(b_path): # If link_name is specified, path is the source of the link and we need to resolve the absolute path. b_link_dir = os.path.dirname(to_bytes(link_name, errors='surrogate_or_strict')) - b_path = os.path.abspath(os.path.join(b_link_dir, b_path)) + b_path = os.path.join(b_link_dir, b_path) + elif not os.path.isabs(b_path): + b_path = os.path.join(b_parent_path, b_path) - b_parent_path = to_bytes(parent_path, errors='surrogate_or_strict') + b_path = os.path.normpath(b_path) return b_path == b_parent_path or b_path.startswith(b_parent_path + to_bytes(os.path.sep)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/lib/ansible/module_utils/ansible_release.py new/ansible_core-2.19.15/lib/ansible/module_utils/ansible_release.py --- old/ansible_core-2.19.14/lib/ansible/module_utils/ansible_release.py 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/lib/ansible/module_utils/ansible_release.py 2026-11-02 18:10:40.000000000 +0100 @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.19.14' +__version__ = '2.19.15' __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.14/lib/ansible/modules/git.py new/ansible_core-2.19.15/lib/ansible/modules/git.py --- old/ansible_core-2.19.14/lib/ansible/modules/git.py 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/lib/ansible/modules/git.py 2026-11-02 18:10:40.000000000 +0100 @@ -379,14 +379,14 @@ module.fail_json(msg="Unable to read %s" % headfile) if rawdata: try: - rawdata = rawdata.replace('refs/remotes/%s' % remote, '', 1) + rawdata = rawdata.replace(f'refs/remotes/{remote}', '', 1) refparts = rawdata.split(' ') newref = refparts[-1] nrefparts = newref.split('/', 2) res = nrefparts[-1].rstrip('\n') except Exception: if fail_on_error and module: - module.fail_json(msg="Unable to split head from '%s'" % rawdata) + module.fail_json(msg=f"Unable to split head from '{rawdata}'") return res @@ -406,7 +406,7 @@ params = [] # run a bad submodule command to get valid params - cmd = "%s submodule update --help" % (git_path) + cmd = [git_path, 'submodule', 'update', '--help'] rc, stdout, stderr = module.run_command(cmd, cwd=cwd) lines = stderr.split('\n') update_line = None @@ -519,7 +519,7 @@ def get_version(module, git_path, dest, ref="HEAD"): """ samples the version of the git repo """ - cmd = "%s rev-parse %s" % (git_path, ref) + cmd = [git_path, "rev-parse", ref] rc, stdout, stderr = module.run_command(cmd, cwd=dest) sha = to_native(stdout).rstrip('\n') return sha @@ -617,7 +617,7 @@ # git before 1.7.5 doesn't have separate-git-dir argument, do fallback needs_separate_git_dir_fallback = True else: - cmd.append('--separate-git-dir=%s' % separate_git_dir) + cmd.append(f'--separate-git-dir={separate_git_dir}') cmd.extend([repo, dest]) module.run_command(cmd, check_rc=True, cwd=dest_dirname) @@ -642,7 +642,7 @@ if bare: return False - cmd = "%s status --porcelain" % (git_path) + cmd = [git_path, "status", "--porcelain"] rc, stdout, stderr = module.run_command(cmd, cwd=dest) lines = stdout.splitlines() lines = list(filter(lambda c: not re.search('^\\?\\?.*$', c), lines)) @@ -656,7 +656,7 @@ Discards any changes to tracked files in working tree since that commit. """ - cmd = "%s reset --hard HEAD" % (git_path,) + cmd = [git_path, "reset", "--hard", "HEAD"] return module.run_command(cmd, check_rc=True, cwd=dest) @@ -668,7 +668,7 @@ # Ensure we have the object we are referring to during git diff ! git_version_used = git_version(git_path, module) fetch(git_path, module, repo, dest, after, remote, depth, bare, '', git_version_used) - cmd = '%s diff %s %s' % (git_path, before, after) + cmd = [git_path, 'diff', before, after] (rc, out, err) = module.run_command(cmd, cwd=dest) if rc == 0 and out: return {'prepared': out} @@ -685,6 +685,7 @@ cloning = False cwd = None tag = False + cmd = [git_path, 'ls-remote', remote] if remote == module.params['repo']: cloning = True elif remote == 'file://' + os.path.expanduser(module.params['repo']): @@ -694,15 +695,15 @@ if version == 'HEAD': if cloning: # cloning the repo, just get the remote's HEAD version - cmd = '%s ls-remote %s -h HEAD' % (git_path, remote) + cmd.extend(['-h', 'HEAD']) else: head_branch = get_head_branch(git_path, module, dest, remote, bare) - cmd = '%s ls-remote %s -h refs/heads/%s' % (git_path, remote, head_branch) + cmd.extend(['-h', '--', shlex.quote(f'refs/heads/{head_branch}')]) elif is_remote_branch(git_path, module, dest, remote, version): - cmd = '%s ls-remote %s -h refs/heads/%s' % (git_path, remote, version) + cmd.extend(['-h', f'refs/heads/{version}']) elif is_remote_tag(git_path, module, dest, remote, version): tag = True - cmd = '%s ls-remote %s -t refs/tags/%s*' % (git_path, remote, version) + cmd.extend(['-t', f'refs/tags/{version}*']) else: # appears to be a sha1. return as-is since it appears # cannot check for a specific sha1 on remote @@ -727,7 +728,7 @@ def is_remote_tag(git_path, module, dest, remote, version): - cmd = '%s ls-remote %s -t refs/tags/%s' % (git_path, remote, version) + cmd = [git_path, 'ls-remote', remote, '-t', f'refs/tags/{version}'] (rc, out, err) = module.run_command(cmd, check_rc=True, cwd=dest) if to_native(version, errors='surrogate_or_strict') in out: return True @@ -737,10 +738,10 @@ def get_branches(git_path, module, dest): branches = [] - cmd = '%s branch --no-color -a' % (git_path,) + cmd = [git_path, 'branch', '--no-color', '-a'] (rc, out, err) = module.run_command(cmd, cwd=dest) if rc != 0: - module.fail_json(msg="Could not determine branch data - received %s" % out, stdout=out, stderr=err) + module.fail_json(msg=f"Could not determine branch data - received {out}", stdout=out, stderr=err) for line in out.split('\n'): if line.strip(): branches.append(line.strip()) @@ -752,7 +753,7 @@ cmd = [git_path, 'for-each-ref', 'refs/tags/', '--format', '%(objecttype):%(refname:short)'] (rc, out, err) = module.run_command(cmd, cwd=dest) if rc != 0: - module.fail_json(msg="Could not determine tag data - received %s" % out, stdout=out, stderr=err) + module.fail_json(msg=f"Could not determine tag data - received {out}", stdout=out, stderr=err) for line in to_native(out).split('\n'): if line.strip(): tagtype, tagname = line.strip().split(':') @@ -762,7 +763,7 @@ def is_remote_branch(git_path, module, dest, remote, version): - cmd = '%s ls-remote %s -h refs/heads/%s' % (git_path, remote, version) + cmd = [git_path, 'ls-remote', remote, '-h', f'refs/heads/{version}'] (rc, out, err) = module.run_command(cmd, check_rc=True, cwd=dest) if to_native(version, errors='surrogate_or_strict') in out: return True @@ -772,13 +773,7 @@ def is_local_branch(git_path, module, dest, branch): branches = get_branches(git_path, module, dest) - lbranch = '%s' % branch - if lbranch in branches: - return True - elif '* %s' % branch in branches: - return True - else: - return False + return f'{branch}' in branches or f'* {branch}' in branches def is_not_a_branch(git_path, module, dest): @@ -840,6 +835,11 @@ if is_not_a_branch(git_path, module, dest): headfile = os.path.join(repo_path, 'refs', 'remotes', remote, 'HEAD') branch = head_splitter(headfile, remote, module=module, fail_on_error=True) + + if branch.startswith('-'): + # prevent branch names that can be confused as options + module.fail_json(msg=f'Invalid branch name ({branch}) from {headfile} in {remote}') + return branch @@ -963,7 +963,7 @@ # Compare against submodule HEAD # FIXME: determine this from .gitmodules version = 'master' - after = get_submodule_versions(git_path, module, dest, '%s/%s' % (remote, version)) + after = get_submodule_versions(git_path, module, dest, shlex.quote('%s/%s') % (remote, version)) if begin != after: changed = True else: @@ -1009,48 +1009,52 @@ Can't use git remote set-branches, as it is not available in git 1.7.1 (centos6) """ - branchref = "+refs/heads/%s:refs/heads/%s" % (version, version) - branchref += ' +refs/heads/%s:refs/remotes/%s/%s' % (version, remote, version) - cmd = "%s fetch --depth=%s %s %s" % (git_path, depth, remote, branchref) + cmd = [ + git_path, + "fetch", + f"--depth={depth}", + remote, + f"+refs/heads/{version}:refs/heads/{version}", + f"+refs/heads/{version}:refs/remotes/{remote}/{version}", + ] (rc, out, err) = module.run_command(cmd, cwd=dest) if rc != 0: - module.fail_json(msg="Failed to fetch branch from remote: %s" % version, stdout=out, stderr=err, rc=rc) + module.fail_json(msg=f"Failed to fetch branch from remote: {version}", stdout=out, stderr=err, rc=rc) def switch_version(git_path, module, dest, remote, version, verify_commit, depth, gpg_allowlist): cmd = '' if version == 'HEAD': branch = get_head_branch(git_path, module, dest, remote) - (rc, out, err) = module.run_command("%s checkout --force %s" % (git_path, branch), cwd=dest) + (rc, out, err) = module.run_command([git_path, "checkout", "--force", branch], cwd=dest) if rc != 0: - module.fail_json(msg="Failed to checkout branch %s" % branch, - stdout=out, stderr=err, rc=rc) - cmd = "%s reset --hard %s/%s --" % (git_path, remote, branch) + module.fail_json(msg=f"Failed to checkout branch {branch} HEAD", stdout=out, stderr=err, rc=rc) + cmd = [git_path, "reset", "--hard", shlex.quote("/".join([remote, branch]))] else: # FIXME check for local_branch first, should have been fetched already if is_remote_branch(git_path, module, dest, remote, version): + rv = "/".join([remote, version]) if depth and not is_local_branch(git_path, module, dest, version): # git clone --depth implies --single-branch, which makes # the checkout fail if the version changes # fetch the remote branch, to be able to check it out next set_remote_branch(git_path, module, dest, remote, version, depth) if not is_local_branch(git_path, module, dest, version): - cmd = "%s checkout --track -b %s %s/%s" % (git_path, version, remote, version) + cmd = [git_path, "checkout", "--track", "-b", version, rv] else: - (rc, out, err) = module.run_command("%s checkout --force %s" % (git_path, version), cwd=dest) + (rc, out, err) = module.run_command([git_path, "checkout", "--force", version], cwd=dest) if rc != 0: module.fail_json(msg="Failed to checkout branch %s" % version, stdout=out, stderr=err, rc=rc) - cmd = "%s reset --hard %s/%s" % (git_path, remote, version) + cmd = [git_path, "reset", "--hard", rv] else: - cmd = "%s checkout --force %s" % (git_path, version) + cmd = [git_path, "checkout", "--force", version] (rc, out1, err1) = module.run_command(cmd, cwd=dest) if rc != 0: - if version != 'HEAD': - module.fail_json(msg="Failed to checkout %s" % (version), - stdout=out1, stderr=err1, rc=rc, cmd=cmd) + if version == 'HEAD': + msg = f"Failed to checkout branch {branch}" else: - module.fail_json(msg="Failed to checkout branch %s" % (branch), - stdout=out1, stderr=err1, rc=rc, cmd=cmd) + msg = f"Failed to checkout {version}" + module.fail_json(msg=msg, stdout=out1, stderr=err1, rc=rc, cmd=cmd) if verify_commit: verify_commit_sign(git_path, module, dest, version, gpg_allowlist) @@ -1063,9 +1067,9 @@ git_sub = "verify-tag" else: git_sub = "verify-commit" - cmd = "%s %s %s" % (git_path, git_sub, version) + cmd = [git_path, git_sub, version] if gpg_allowlist: - cmd += " --raw" + cmd.append("--raw") (rc, out, err) = module.run_command(cmd, cwd=dest) if rc != 0: module.fail_json(msg='Failed to verify GPG signature of commit/tag "%s"' % version, stdout=out, stderr=err, rc=rc) @@ -1094,7 +1098,7 @@ def git_version(git_path, module): """return the installed version of git""" - cmd = "%s --version" % git_path + cmd = [git_path, "--version"] (rc, out, err) = module.run_command(cmd) if rc != 0: # one could fail_json here, but the version info is not that important, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/lib/ansible/release.py new/ansible_core-2.19.15/lib/ansible/release.py --- old/ansible_core-2.19.14/lib/ansible/release.py 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/lib/ansible/release.py 2026-11-02 18:10:40.000000000 +0100 @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.19.14' +__version__ = '2.19.15' __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.14/pyproject.toml new/ansible_core-2.19.15/pyproject.toml --- old/ansible_core-2.19.14/pyproject.toml 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/pyproject.toml 2026-11-02 18:10:40.000000000 +0100 @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools >= 66.1.0, <= 84.0.0", "wheel == 0.45.1"] # lower bound to support controller Python versions, upper bound for latest version tested at release +requires = ["setuptools >= 66.1.0, <= 84.2.1", "wheel == 0.45.1"] # lower bound to support controller Python versions, upper bound for latest version tested at release build-backend = "setuptools.build_meta" [project] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/test/integration/targets/ansible-galaxy-collection/files/build_bad_tar.py new/ansible_core-2.19.15/test/integration/targets/ansible-galaxy-collection/files/build_bad_tar.py --- old/ansible_core-2.19.14/test/integration/targets/ansible-galaxy-collection/files/build_bad_tar.py 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/test/integration/targets/ansible-galaxy-collection/files/build_bad_tar.py 2026-11-02 18:10:40.000000000 +0100 @@ -5,11 +5,11 @@ from __future__ import annotations +import argparse import hashlib import io import json import os -import sys import tarfile from ansible.module_utils.common.file import S_IRWXU_RXG_RXO @@ -44,9 +44,23 @@ } -def add_file(tar_file, filename, b_content, update_files=True): +def calculate_target_filename(symlink_path, linkname): + """The target is relative to the symlink.""" + symlink_dir = os.path.dirname(symlink_path) + if symlink_dir: + # tarfile will extract the linkname as {{ symlink_dir }}/{{ linkname }}, even if linkname is an absolute path + return "/".join([symlink_dir, linkname]) + return linkname + + +def add_file(tar_file, filename, b_content, update_files=True, symlink_linkname=None): tar_info = tarfile.TarInfo(filename) - tar_info.size = len(b_content) + if symlink_linkname is not None: + add_file(tar_file, calculate_target_filename(filename, symlink_linkname), b"", update_files=False) + tar_info.type = tarfile.SYMTYPE + tar_info.linkname = symlink_linkname + else: + tar_info.size = len(b_content) tar_info.mode = S_IRWXU_RXG_RXO tar_file.addfile(tarinfo=tar_info, fileobj=io.BytesIO(b_content)) @@ -58,14 +72,76 @@ 'name': filename, 'ftype': 'file', 'chksum_type': 'sha256', - 'chksum_sha256': sha256.hexdigest(), + 'chksum_sha256': sha256.hexdigest() if symlink_linkname is None else None, 'format': 1 }) -collection_tar = os.path.join(sys.argv[1], 'suspicious-test-1.0.0.tar.gz') +def add_dir(tar_file, dir_name, symlink_linkname=None, update_files=True): + if symlink_linkname: + add_dir(tar_file, symlink_linkname, update_files=False) + + tar_info = tarfile.TarInfo(dir_name) + + if symlink_linkname is None: + tar_info.type = tarfile.DIRTYPE + else: + tar_info.type = tarfile.SYMTYPE + tar_info.linkname = symlink_linkname + + tar_info.mode = 0o777 + tar_file.addfile(tar_info) + + if not update_files: + return + + files['files'].append({ + 'name': dir_name, + 'ftype': 'dir', + 'chksum_type': 'sha256', + 'chksum_sha256': None, + 'format': 1, + }) + + +parser = argparse.ArgumentParser(description="Test suspiciously crafted collection artifacts.") +parser.add_argument("dest", help="The destination directory for the collection artifact.") + +mutually_exclusive_group = parser.add_mutually_exclusive_group() +mutually_exclusive_group.add_argument( + "--file", help="A file to add to the collection artifact.", +) +mutually_exclusive_group.add_argument( + "--dir", help="A directory to add to the collection artifact.", +) +mutually_exclusive_group.add_argument( + "--symlink-linkname", help="Add a symlink linkname to the collection artifact.", +) +parser.add_argument( + "--symlink", help="The name of the symlink to use with --symlink-linkname. Defaults to 'symlink'.", +) + + +args = parser.parse_args() + +collection_tar = os.path.join(args.dest, 'suspicious-test-1.0.0.tar.gz') with tarfile.open(collection_tar, mode='w:gz') as tar_file: - add_file(tar_file, '../../outside.sh', b"#!/usr/bin/env bash\necho \"you got pwned\"") + filename = args.file + dirname = args.dir + + if args.symlink and not args.symlink_linkname: + parser.error("--symlink and --symlink-linkname must be used together") + + if args.symlink_linkname: + if args.symlink_linkname.endswith(os.path.sep): + dirname = args.symlink or "symlink" + else: + filename = args.symlink or "symlink" + + if dirname: + add_dir(tar_file, dirname, symlink_linkname=args.symlink_linkname) + elif filename: + add_file(tar_file, filename, b"#!/usr/bin/env bash\necho \"you got pwned\"", symlink_linkname=args.symlink_linkname) b_files = json.dumps(files).encode('utf-8') b_files_hash = hashlib.sha256() @@ -73,12 +149,3 @@ manifest['file_manifest_file']['chksum_sha256'] = b_files_hash.hexdigest() add_file(tar_file, 'FILES.json', b_files) add_file(tar_file, 'MANIFEST.json', json.dumps(manifest).encode('utf-8')) - - b_manifest = json.dumps(manifest).encode('utf-8') - - for name, b in [('MANIFEST.json', b_manifest), ('FILES.json', b_files)]: - b_io = io.BytesIO(b) - tar_info = tarfile.TarInfo(name) - tar_info.size = len(b) - tar_info.mode = 0o0644 - tar_file.addfile(tarinfo=tar_info, fileobj=b_io) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/test/integration/targets/ansible-galaxy-collection/tasks/build_and_install_tarball.yml new/ansible_core-2.19.15/test/integration/targets/ansible-galaxy-collection/tasks/build_and_install_tarball.yml --- old/ansible_core-2.19.14/test/integration/targets/ansible-galaxy-collection/tasks/build_and_install_tarball.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.19.15/test/integration/targets/ansible-galaxy-collection/tasks/build_and_install_tarball.yml 2026-11-02 18:10:40.000000000 +0100 @@ -0,0 +1,37 @@ +- name: Test a specially built tarball + debug: + msg: "Creating tarball with arguments: {{ tarfile_arguments }}" + +- name: Create tarball + script: build_bad_tar.py {{ galaxy_dir | quote }} {{ tarfile_arguments }} + +- name: Try to install the tarball + command: ansible-galaxy collection install '{{ galaxy_dir }}/suspicious-test-1.0.0.tar.gz' {{ galaxy_verbosity }} + register: fail_bad_tar + ignore_errors: true + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + +- name: Get the result of the install attempt + stat: + path: '{{ galaxy_dir }}/ansible_collections/suspicious' + register: fail_bad_tar_actual + +- name: Remove collection if it was installed + file: + path: '{{ galaxy_dir }}/ansible_collections' + state: absent + when: cleanup | default(True) + +- name: Assert the expected result + assert: + fail_msg: "{{ rc_msg }}{{ stderr_msg }}{{ stdout_msg }}" + that: + - fail_bad_tar.rc == expected_rc + - fail_bad_tar.stderr is search(expected_error) + - fail_bad_tar.stdout is search(expected_stdout) + - (expected_rc == 0) | ternary(fail_bad_tar_actual.stat.exists, not fail_bad_tar_actual.stat.exists) + vars: + rc_msg: "Expected rc: {{ expected_rc }}" + stderr_msg: "{{ (expected_error == '') | ternary('', ', error: ' ~ expected_error) }}" + stdout_msg: "{{ (expected_stdout == '') | ternary('', ', stdout: ' ~ expected_stdout) }}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/test/integration/targets/ansible-galaxy-collection/tasks/install.yml new/ansible_core-2.19.15/test/integration/targets/ansible-galaxy-collection/tasks/install.yml --- old/ansible_core-2.19.14/test/integration/targets/ansible-galaxy-collection/tasks/install.yml 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/test/integration/targets/ansible-galaxy-collection/tasks/install.yml 2026-11-02 18:10:40.000000000 +0100 @@ -285,26 +285,6 @@ - "{{ galaxy_dir }}/scratch/tmp_parent/" - "{{ galaxy_dir }}/tmp_parent-name-1.0.0.tar.gz" -- name: setup bad tarball - {{ test_id }} - script: build_bad_tar.py {{ galaxy_dir | quote }} - -- name: fail to install a collection from a bad tarball - {{ test_id }} - command: ansible-galaxy collection install '{{ galaxy_dir }}/suspicious-test-1.0.0.tar.gz' {{ galaxy_verbosity }} - register: fail_bad_tar - failed_when: fail_bad_tar.rc != 1 and "Cannot extract tar entry '../../outside.sh' as it will be placed outside the collection directory" not in fail_bad_tar.stderr - environment: - ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' - -- name: get result of failed collection install - {{ test_id }} - stat: - path: '{{ galaxy_dir }}/ansible_collections\suspicious' - register: fail_bad_tar_actual - -- name: assert result of failed collection install - {{ test_id }} - assert: - that: - - not fail_bad_tar_actual.stat.exists - - name: Find artifact url for namespace4.name uri: url: '{{ test_api_server }}v3/plugin/ansible/content/primary/collections/index/namespace4/name/versions/1.0.0/' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/test/integration/targets/ansible-galaxy-collection/tasks/install_tarball.yml new/ansible_core-2.19.15/test/integration/targets/ansible-galaxy-collection/tasks/install_tarball.yml --- old/ansible_core-2.19.14/test/integration/targets/ansible-galaxy-collection/tasks/install_tarball.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.19.15/test/integration/targets/ansible-galaxy-collection/tasks/install_tarball.yml 2026-11-02 18:10:40.000000000 +0100 @@ -0,0 +1,97 @@ +- name: Test unsafe tarball extraction + vars: + unsafe_rel_path: '../../outside' + unsafe_abs_path: '{{ galaxy_dir }}/ansible_collections/suspicious/test/../../outside' + safe_linkname: '{{ galaxy_dir }}/ansible_collections/suspicious/test/inside' + expected_rc: 1 + expected_stdout: "" + tarfile_arguments: "{{ item }}" + block: + - name: Test unsafe file extraction + include_tasks: build_and_install_tarball.yml + loop: + - "--file '{{ unsafe_rel_path }}.sh'" + - "--file '{{ unsafe_abs_path }}.sh'" + - "--symlink '{{ unsafe_rel_path }}.sh' --symlink-linkname '{{ safe_linkname }}.sh'" + - "--symlink '{{ unsafe_abs_path }}.sh' --symlink-linkname '{{ safe_linkname }}.sh'" + vars: + expected_error: "Cannot extract tar entry" + + - name: Test unsafe directory extraction + include_tasks: build_and_install_tarball.yml + loop: + - "--dir '{{ unsafe_rel_path }}'" + - "--dir '{{ unsafe_abs_path }}'" + - "--symlink '{{ unsafe_rel_path }}' --symlink-linkname '{{ safe_linkname }}/'" + - "--symlink '{{ unsafe_abs_path }}' --symlink-linkname '{{ safe_linkname }}/'" + vars: + expected_error: "Cannot extract directory" + + - name: Test unsafe symlink linkname + include_tasks: build_and_install_tarball.yml + loop: + - "--symlink-linkname '{{ unsafe_rel_path }}.sh'" + - "--symlink-linkname '{{ unsafe_abs_path }}.sh'" + - "--symlink-linkname '{{ unsafe_rel_path }}/'" + - "--symlink-linkname '{{ unsafe_abs_path }}/'" + - "--symlink 'some_directory/symlink' --symlink-linkname 'safe/../../../unsafe'" + vars: + expected_error: >- + Cannot extract symlink '.*symlink' in collection: path points to location outside of collection + +- name: Test safe tarball extraction + vars: + expected_rc: 0 + expected_error: "" + expected_stdout: "suspicious\\.test:1\\.0\\.0 was installed successfully" + cleanup: False + block: + - name: Test successful directory extraction + include_tasks: build_and_install_tarball.yml + loop: + - "--dir 'safe/'" + - "--dir 'safe'" + loop_control: + loop_var: tarfile_arguments + vars: + cleanup: True + + - name: Test successful relative linkname extraction + include_tasks: build_and_install_tarball.yml + vars: + tarfile_arguments: "--symlink 'some_directory/symlink' --symlink-linkname '../safe'" + + - &stat_symlink + name: Check extracted the symlink exists + stat: + path: "{{ galaxy_dir }}/ansible_collections/suspicious/test/some_directory/symlink" + register: expected_symlink + + - &realpath_symlink + name: Check extracted symlink is not broken + command: "realpath {{ galaxy_dir }}/ansible_collections/suspicious/test/some_directory/symlink" + ignore_errors: True + register: realpath_result + + - &assert_symlink + assert: + that: + - expected_symlink.stat.exists + - realpath_result is not failed + - expected_symlink.stat.lnk_source == realpath_result.stdout == galaxy_dir ~ '/ansible_collections/suspicious/test/safe' + + - &cleanup + name: Remove installed collection + file: + path: "{{ galaxy_dir }}/ansible_collections" + state: absent + + - name: Test successful absolute linkname extraction + include_tasks: build_and_install_tarball.yml + vars: + tarfile_arguments: "--symlink some_directory/symlink --symlink-linkname '{{ galaxy_dir }}/ansible_collections/suspicious/test/safe'" + + - <<: *stat_symlink + - <<: *realpath_symlink + - <<: *assert_symlink + - <<: *cleanup diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/test/integration/targets/ansible-galaxy-collection/tasks/main.yml new/ansible_core-2.19.15/test/integration/targets/ansible-galaxy-collection/tasks/main.yml --- old/ansible_core-2.19.14/test/integration/targets/ansible-galaxy-collection/tasks/main.yml 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/test/integration/targets/ansible-galaxy-collection/tasks/main.yml 2026-11-02 18:10:40.000000000 +0100 @@ -63,6 +63,9 @@ environment: ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' +- name: run ansible-galaxy collection tarball extraction tests + include_tasks: install_tarball.yml + - name: run ansible-galaxy collection publish tests for {{ test_name }} include_tasks: publish.yml args: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/test/integration/targets/ansible-galaxy-role/tasks/main.yml new/ansible_core-2.19.15/test/integration/targets/ansible-galaxy-role/tasks/main.yml --- old/ansible_core-2.19.14/test/integration/targets/ansible-galaxy-role/tasks/main.yml 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/test/integration/targets/ansible-galaxy-role/tasks/main.yml 2026-11-02 18:10:40.000000000 +0100 @@ -63,11 +63,47 @@ register: invalid - assert: - that: "invalid.rc != 0" + that: + - invalid.rc != 0 + - 'invalid.stderr is search("Invalid linkname for tarfile member: path (:?/|/root/invalid) is not a subpath of the role")' - name: Uninstall invalid role command: ansible-galaxy role remove invalid-testrole +- name: Test invalid role requirement name + vars: + abs_name: "{{ remote_tmp_dir }}/unsafe" + install_msg: "{{ abs_name }} was installed successfully" + external_msg: "The role name '{{ abs_name }}' resolves to a path outside of the configured role directory" + block: + - name: Create invalid requirements file + copy: + dest: invalid-requirements.yml + content: |- + - src: "{{ remote_tmp_dir }}/valid-role.tar" + name: "{{ abs_name }}" + + - name: Test installing the role + command: ansible-galaxy install -r invalid-requirements.yml + register: fail_external + ignore_errors: True + + - assert: + that: + - fail_external is failed + - fail_external.stderr is search(external_msg) + - fail_external.stdout is not search(install_msg) + +- name: Test --ignore-errors skips unsatisfiable invalid requirement name + command: ansible-galaxy install {{ remote_tmp_dir }}/roles/../valid-role.tar --roles-path {{ remote_tmp_dir }}/roles --ignore-errors + register: warn_external + +- assert: + that: + - warn_external is success + - warn_external.stderr is search("The role name '.*' resolves to a path outside of the configured role directory") + - warn_external.stdout is not search(".* was installed successfully") + - import_tasks: dir-traversal.yml - import_tasks: valid-role-symlinks.yml - import_tasks: git-config-injection.yml diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/test/lib/ansible_test/_data/completion/remote.txt new/ansible_core-2.19.15/test/lib/ansible_test/_data/completion/remote.txt --- old/ansible_core-2.19.14/test/lib/ansible_test/_data/completion/remote.txt 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/test/lib/ansible_test/_data/completion/remote.txt 2026-11-02 18:10:40.000000000 +0100 @@ -2,7 +2,6 @@ alpine become=doas_sudo provider=aws arch=x86_64 fedora/41 python=3.13 become=sudo provider=aws arch=x86_64 alias=fedora/latest fedora become=sudo provider=aws arch=x86_64 -freebsd/13.5 python=3.11 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64 alias=freebsd/13 freebsd/14.3 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=mac arch=aarch64 alias=macos/15,macos/latest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/test/lib/ansible_test/_util/target/setup/bootstrap.sh new/ansible_core-2.19.15/test/lib/ansible_test/_util/target/setup/bootstrap.sh --- old/ansible_core-2.19.14/test/lib/ansible_test/_util/target/setup/bootstrap.sh 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/test/lib/ansible_test/_util/target/setup/bootstrap.sh 2026-11-02 18:10:40.000000000 +0100 @@ -187,9 +187,6 @@ # 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 - 13.5/3.11) - # defaults available - ;; 14.3/3.11) # defaults available ;; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.14/test/units/cli/galaxy/test_collection_extract_tar.py new/ansible_core-2.19.15/test/units/cli/galaxy/test_collection_extract_tar.py --- old/ansible_core-2.19.14/test/units/cli/galaxy/test_collection_extract_tar.py 2026-10-05 17:06:28.000000000 +0200 +++ new/ansible_core-2.19.15/test/units/cli/galaxy/test_collection_extract_tar.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020 Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import annotations - -import pytest - -from ansible.galaxy.collection import _extract_tar_dir - - [email protected] -def fake_tar_obj(mocker): - m_tarfile = mocker.Mock() - m_tarfile.type = mocker.Mock(return_value=b'99') - m_tarfile.SYMTYPE = mocker.Mock(return_value=b'22') - - return m_tarfile - - -def test_extract_tar_dir_exists(mocker, fake_tar_obj): - mocker.patch('os.makedirs', return_value=None) - m_makedir = mocker.patch('os.mkdir', return_value=None) - mocker.patch('os.path.isdir', return_value=True) - - _extract_tar_dir(fake_tar_obj, '/some/dir', b'/some/dest') - - assert not m_makedir.called - - -def test_extract_tar_dir_does_not_exist(mocker, fake_tar_obj): - mocker.patch('os.makedirs', return_value=None) - m_makedir = mocker.patch('os.mkdir', return_value=None) - mocker.patch('os.path.isdir', return_value=False) - - _extract_tar_dir(fake_tar_obj, '/some/dir', b'/some/dest') - - assert m_makedir.called - assert m_makedir.call_args[0] == (b'/some/dir', 0o0755) ++++++ ansible_core-2.19.14.tar.gz.sha256 -> ansible_core-2.19.15.tar.gz.sha256 ++++++ --- /work/SRC/openSUSE:Factory/ansible-core-2.19/ansible_core-2.19.14.tar.gz.sha256 2026-10-08 13:28:31.235636416 +0200 +++ /work/SRC/openSUSE:Factory/.ansible-core-2.19.new.3759290/ansible_core-2.19.15.tar.gz.sha256 2026-11-06 20:00:56.157626660 +0100 @@ -1 +1 @@ -1e7612788eaa6b3c87a3670ce86c64b830eba0ae2b3fcb25a772f276704163b4 ansible_core-2.19.14.tar.gz +05fa7f0730199b8e0dd9204f258075992143bd51dae71f68965b832a50d422fe ansible_core-2.19.15.tar.gz
