summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--PKG-INFO2
-rw-r--r--changelogs/CHANGELOG-v2.17.rst24
-rw-r--r--changelogs/changelog.yaml44
-rw-r--r--lib/ansible/executor/powershell/exec_wrapper.ps11
-rw-r--r--lib/ansible/executor/powershell/module_powershell_wrapper.ps113
-rw-r--r--lib/ansible/module_utils/ansible_release.py2
-rw-r--r--lib/ansible/module_utils/csharp/Ansible.Basic.cs11
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm135
-rw-r--r--lib/ansible/parsing/mod_args.py20
-rw-r--r--lib/ansible/playbook/handler.py10
-rw-r--r--lib/ansible/playbook/task.py1
-rw-r--r--lib/ansible/plugins/filter/splitext.yml8
-rw-r--r--lib/ansible/plugins/filter/win_splitdrive.yml20
-rw-r--r--lib/ansible/plugins/shell/__init__.py51
-rw-r--r--lib/ansible/plugins/shell/sh.py4
-rw-r--r--lib/ansible/plugins/strategy/__init__.py26
-rw-r--r--lib/ansible/release.py2
-rw-r--r--lib/ansible_core.egg-info/PKG-INFO2
-rw-r--r--lib/ansible_core.egg-info/SOURCES.txt3
-rw-r--r--test/integration/targets/any_errors_fatal/80981.yml2
-rwxr-xr-xtest/integration/targets/any_errors_fatal/runme.sh8
-rw-r--r--test/integration/targets/collections/test_task_resolved_plugin/unqualified_and_collections_kw.yml6
-rw-r--r--test/integration/targets/json_cleanup/library/bad_json2
-rw-r--r--test/integration/targets/json_cleanup/module_output_cleaning.yml7
-rw-r--r--test/integration/targets/module_defaults/tasks/main.yml11
-rw-r--r--test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps131
-rw-r--r--test/integration/targets/shell/meta/main.yml3
-rw-r--r--test/integration/targets/shell/tasks/command-building.yml53
-rw-r--r--test/integration/targets/shell/tasks/main.yml2
-rw-r--r--test/integration/targets/shell/test-command-building-playbook.yml4
-rw-r--r--test/integration/targets/win_exec_wrapper/tasks/main.yml55
-rw-r--r--test/lib/ansible_test/_internal/pypi_proxy.py2
-rw-r--r--test/units/playbook/test_block.py4
-rw-r--r--test/units/playbook/test_helpers.py5
-rw-r--r--test/units/utils/display/test_broken_cowsay.py3
35 files changed, 378 insertions, 99 deletions
diff --git a/PKG-INFO b/PKG-INFO
index 90ea773..5cefb3a 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: ansible-core
-Version: 2.17.0
+Version: 2.17.1
Summary: Radically simple IT automation
Home-page: https://ansible.com/
Author: Ansible, Inc.
diff --git a/changelogs/CHANGELOG-v2.17.rst b/changelogs/CHANGELOG-v2.17.rst
index dd46da0..65bd411 100644
--- a/changelogs/CHANGELOG-v2.17.rst
+++ b/changelogs/CHANGELOG-v2.17.rst
@@ -4,6 +4,30 @@ ansible-core 2.17 "Gallows Pole" Release Notes
.. contents:: Topics
+v2.17.1
+=======
+
+Release Summary
+---------------
+
+| Release Date: 2024-06-17
+| `Porting Guide <https://docs.ansible.com/ansible-core/2.17/porting_guides/porting_guide_core_2.17.html>`__
+
+Minor Changes
+-------------
+
+- ansible-test - Update ``pypi-test-container`` to version 3.1.0.
+
+Bugfixes
+--------
+
+- Fix rapid memory usage growth when notifying handlers using the ``listen`` keyword (https://github.com/ansible/ansible/issues/83392)
+- Fix the task attribute ``resolved_action`` to show the FQCN instead of ``None`` when ``action`` or ``local_action`` is used in the playbook.
+- Fix using ``module_defaults`` with ``local_action``/``action`` (https://github.com/ansible/ansible/issues/81905).
+- fixed unit test test_borken_cowsay to address mock not been properly applied when existing unix system already have cowsay installed.
+- powershell - Implement more robust deletion mechanism for C# code compilation temporary files. This should avoid scenarios where the underlying temporary directory may be temporarily locked by antivirus tools or other IO problems. A failure to delete one of these temporary directories will result in a warning rather than an outright failure.
+- shell plugin - properly quote all needed components of shell commands (https://github.com/ansible/ansible/issues/82535)
+
v2.17.0
=======
diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml
index f2e7e54..3020a6f 100644
--- a/changelogs/changelog.yaml
+++ b/changelogs/changelog.yaml
@@ -687,3 +687,47 @@ releases:
- psrp-version-req.yml
- uri_follow_redirect_bool.yml
release_date: '2024-05-13'
+ 2.17.1:
+ changes:
+ minor_changes:
+ - ansible-test - Update ``pypi-test-container`` to version 3.1.0.
+ release_summary: '| Release Date: 2024-06-17
+
+ | `Porting Guide <https://docs.ansible.com/ansible-core/2.17/porting_guides/porting_guide_core_2.17.html>`__
+
+ '
+ codename: Gallows Pole
+ fragments:
+ - 2.17.1_summary.yaml
+ - ansible-test-pypi-test-container-update.yml
+ release_date: '2024-06-17'
+ 2.17.1rc1:
+ changes:
+ bugfixes:
+ - Fix rapid memory usage growth when notifying handlers using the ``listen``
+ keyword (https://github.com/ansible/ansible/issues/83392)
+ - Fix the task attribute ``resolved_action`` to show the FQCN instead of ``None``
+ when ``action`` or ``local_action`` is used in the playbook.
+ - Fix using ``module_defaults`` with ``local_action``/``action`` (https://github.com/ansible/ansible/issues/81905).
+ - fixed unit test test_borken_cowsay to address mock not been properly applied
+ when existing unix system already have cowsay installed.
+ - powershell - Implement more robust deletion mechanism for C# code compilation
+ temporary files. This should avoid scenarios where the underlying temporary
+ directory may be temporarily locked by antivirus tools or other IO problems.
+ A failure to delete one of these temporary directories will result in a warning
+ rather than an outright failure.
+ - shell plugin - properly quote all needed components of shell commands (https://github.com/ansible/ansible/issues/82535)
+ release_summary: '| Release Date: 2024-06-10
+
+ | `Porting Guide <https://docs.ansible.com/ansible-core/2.17/porting_guides/porting_guide_core_2.17.html>`__
+
+ '
+ codename: Gallows Pole
+ fragments:
+ - 2.17.1rc1_summary.yaml
+ - 82535-properly-quote-shell.yml
+ - 83327.yml
+ - 83392-fix-memory-issues-handlers.yml
+ - PowerShell-AddType-temp.yml
+ - correct-callback-fqcn-old-style-action-invocation.yml
+ release_date: '2024-06-10'
diff --git a/lib/ansible/executor/powershell/exec_wrapper.ps1 b/lib/ansible/executor/powershell/exec_wrapper.ps1
index 0f97bdf..cce99ab 100644
--- a/lib/ansible/executor/powershell/exec_wrapper.ps1
+++ b/lib/ansible/executor/powershell/exec_wrapper.ps1
@@ -178,6 +178,7 @@ $($ErrorRecord.InvocationInfo.PositionMessage)
Write-AnsibleLog "INFO - converting json raw to a payload" "exec_wrapper"
$payload = ConvertFrom-AnsibleJson -InputObject $json_raw
+ $payload.module_args._ansible_exec_wrapper_warnings = [System.Collections.Generic.List[string]]@()
# TODO: handle binary modules
# TODO: handle persistence
diff --git a/lib/ansible/executor/powershell/module_powershell_wrapper.ps1 b/lib/ansible/executor/powershell/module_powershell_wrapper.ps1
index c35c84c..f79dd6f 100644
--- a/lib/ansible/executor/powershell/module_powershell_wrapper.ps1
+++ b/lib/ansible/executor/powershell/module_powershell_wrapper.ps1
@@ -29,7 +29,18 @@ if ($csharp_utils.Count -gt 0) {
# add any C# references so the module does not have to do so
$new_tmp = [System.Environment]::ExpandEnvironmentVariables($Payload.module_args["_ansible_remote_tmp"])
- Add-CSharpType -References $csharp_utils -TempPath $new_tmp -IncludeDebugInfo
+
+ # We use a fake module object to capture warnings
+ $fake_module = [PSCustomObject]@{
+ Tmpdir = $new_tmp
+ Verbosity = 3
+ }
+ $warning_func = New-Object -TypeName System.Management.Automation.PSScriptMethod -ArgumentList Warn, {
+ param($message)
+ $Payload.module_args._ansible_exec_wrapper_warnings.Add($message)
+ }
+ $fake_module.PSObject.Members.Add($warning_func)
+ Add-CSharpType -References $csharp_utils -AnsibleModule $fake_module
}
if ($Payload.ContainsKey("coverage") -and $null -ne $host.Runspace -and $null -ne $host.Runspace.Debugger) {
diff --git a/lib/ansible/module_utils/ansible_release.py b/lib/ansible/module_utils/ansible_release.py
index 88f7515..7ed9fdf 100644
--- a/lib/ansible/module_utils/ansible_release.py
+++ b/lib/ansible/module_utils/ansible_release.py
@@ -17,6 +17,6 @@
from __future__ import annotations
-__version__ = '2.17.0'
+__version__ = '2.17.1'
__author__ = 'Ansible, Inc.'
__codename__ = "Gallows Pole"
diff --git a/lib/ansible/module_utils/csharp/Ansible.Basic.cs b/lib/ansible/module_utils/csharp/Ansible.Basic.cs
index a042af8..0859582 100644
--- a/lib/ansible/module_utils/csharp/Ansible.Basic.cs
+++ b/lib/ansible/module_utils/csharp/Ansible.Basic.cs
@@ -1025,7 +1025,16 @@ namespace Ansible.Basic
foreach (DictionaryEntry entry in param)
{
string paramKey = (string)entry.Key;
- if (!legalInputs.Contains(paramKey, StringComparer.OrdinalIgnoreCase))
+ if (paramKey == "_ansible_exec_wrapper_warnings")
+ {
+ // Special key used in module_powershell_wrapper to pass
+ // along any warnings that should be returned back to
+ // Ansible.
+ removedParameters.Add(paramKey);
+ foreach (string warning in (IList<string>)entry.Value)
+ Warn(warning);
+ }
+ else if (!legalInputs.Contains(paramKey, StringComparer.OrdinalIgnoreCase))
unsupportedParameters.Add(paramKey);
else if (!legalInputs.Contains(paramKey))
// For backwards compatibility we do not care about the case but we need to warn the users as this will
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1
index f40c338..b18a9a1 100644
--- a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1
@@ -75,7 +75,7 @@ Function Add-CSharpType {
[Switch]$IgnoreWarnings,
[Switch]$PassThru,
[Parameter(Mandatory = $true, ParameterSetName = "Module")][Object]$AnsibleModule,
- [Parameter(ParameterSetName = "Manual")][String]$TempPath = $env:TMP,
+ [Parameter(ParameterSetName = "Manual")][String]$TempPath,
[Parameter(ParameterSetName = "Manual")][Switch]$IncludeDebugInfo,
[String[]]$CompileSymbols = @()
)
@@ -280,9 +280,11 @@ Function Add-CSharpType {
$include_debug = $AnsibleModule.Verbosity -ge 3
}
else {
- $temp_path = $TempPath
+ $temp_path = [System.IO.Path]::GetTempPath()
$include_debug = $IncludeDebugInfo.IsPresent
}
+ $temp_path = Join-Path -Path $temp_path -ChildPath ([Guid]::NewGuid().Guid)
+
$compiler_options = [System.Collections.ArrayList]@("/optimize")
if ($defined_symbols.Count -gt 0) {
$compiler_options.Add("/define:" + ([String]::Join(";", $defined_symbols.ToArray()))) > $null
@@ -304,8 +306,12 @@ Function Add-CSharpType {
)
# create a code snippet for each reference and check if we need
- # to reference any extra assemblies
- $ignore_warnings = [System.Collections.ArrayList]@()
+ # to reference any extra assemblies.
+ # CS1610 is a warning when csc.exe failed to delete temporary files.
+ # We use our own temp dir deletion mechanism so this doesn't become a
+ # fatal error.
+ # https://github.com/ansible-collections/ansible.windows/issues/598
+ $ignore_warnings = [System.Collections.ArrayList]@('1610')
$compile_units = [System.Collections.Generic.List`1[System.CodeDom.CodeSnippetCompileUnit]]@()
foreach ($reference in $References) {
# scan through code and add any assemblies that match
@@ -373,7 +379,26 @@ Function Add-CSharpType {
}
}
- $compile = $provider.CompileAssemblyFromDom($compile_parameters, $compile_units)
+ $null = New-Item -Path $temp_path -ItemType Directory -Force
+ try {
+ $compile = $provider.CompileAssemblyFromDom($compile_parameters, $compile_units)
+ }
+ finally {
+ # Try to delete the temp path, if this fails and we are running
+ # with a module object write a warning instead of failing.
+ try {
+ [System.IO.Directory]::Delete($temp_path, $true)
+ }
+ catch {
+ $msg = "Failed to cleanup temporary directory '$temp_path' used for compiling C# code."
+ if ($AnsibleModule) {
+ $AnsibleModule.Warn("$msg Files may still be present after the task is complete. Error: $_")
+ }
+ else {
+ throw "$msg Error: $_"
+ }
+ }
+ }
}
finally {
foreach ($kvp in $originalEnv.GetEnumerator()) {
diff --git a/lib/ansible/parsing/mod_args.py b/lib/ansible/parsing/mod_args.py
index 56e7e98..fb982f5 100644
--- a/lib/ansible/parsing/mod_args.py
+++ b/lib/ansible/parsing/mod_args.py
@@ -53,6 +53,17 @@ BUILTIN_TASKS = frozenset(add_internal_fqcns((
)))
+def _get_action_context(action_or_module, collection_list):
+ module_context = module_loader.find_plugin_with_context(action_or_module, collection_list=collection_list)
+ if module_context and module_context.resolved and module_context.action_plugin:
+ action_or_module = module_context.action_plugin
+
+ context = action_loader.find_plugin_with_context(action_or_module, collection_list=collection_list)
+ if not context or not context.resolved:
+ context = module_context
+ return context
+
+
class ModuleArgsParser:
"""
@@ -291,6 +302,11 @@ class ModuleArgsParser:
delegate_to = 'localhost'
action, args = self._normalize_parameters(thing, action=action, additional_args=additional_args)
+ if action is not None and not skip_action_validation:
+ context = _get_action_context(action, self._collection_list)
+ if context is not None and context.resolved:
+ self.resolved_action = context.resolved_fqcn
+
# module: <stuff> is the more new-style invocation
# filter out task attributes so we're only querying unrecognized keys as actions/modules
@@ -306,9 +322,7 @@ class ModuleArgsParser:
is_action_candidate = True
else:
try:
- context = action_loader.find_plugin_with_context(item, collection_list=self._collection_list)
- if not context.resolved:
- context = module_loader.find_plugin_with_context(item, collection_list=self._collection_list)
+ context = _get_action_context(item, self._collection_list)
except AnsibleError as e:
if e.obj is None:
e.obj = self._task_ds
diff --git a/lib/ansible/playbook/handler.py b/lib/ansible/playbook/handler.py
index 09f122e..a7b4a53 100644
--- a/lib/ansible/playbook/handler.py
+++ b/lib/ansible/playbook/handler.py
@@ -37,6 +37,16 @@ class Handler(Task):
''' returns a human-readable representation of the handler '''
return "HANDLER: %s" % self.get_name()
+ def _validate_listen(self, attr, name, value):
+ new_value = self.get_validated_value(name, attr, value, None)
+ if self._role is not None:
+ for listener in new_value.copy():
+ new_value.extend([
+ f"{self._role.get_name(include_role_fqcn=True)} : {listener}",
+ f"{self._role.get_name(include_role_fqcn=False)} : {listener}",
+ ])
+ setattr(self, name, new_value)
+
@staticmethod
def load(data, block=None, role=None, task_include=None, variable_manager=None, loader=None):
t = Handler(block=block, role=role, task_include=task_include)
diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py
index 7f54639..8345869 100644
--- a/lib/ansible/playbook/task.py
+++ b/lib/ansible/playbook/task.py
@@ -208,6 +208,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
# But if it wasn't, we can add the yaml object now to get more detail
raise AnsibleParserError(to_native(e), obj=ds, orig_exc=e)
else:
+ # Set the resolved action plugin (or if it does not exist, module) for callbacks.
self.resolved_action = args_parser.resolved_action
# the command/shell/script modules used to support the `cmd` arg,
diff --git a/lib/ansible/plugins/filter/splitext.yml b/lib/ansible/plugins/filter/splitext.yml
index 5f94692..56d5c30 100644
--- a/lib/ansible/plugins/filter/splitext.yml
+++ b/lib/ansible/plugins/filter/splitext.yml
@@ -14,14 +14,14 @@ DOCUMENTATION:
EXAMPLES: |
- # gobble => [ '/etc/make', 'conf' ]
+ # gobble => [ '/etc/make', '.conf' ]
gobble: "{{ '/etc/make.conf' | splitext }}"
- # file_n_ext => [ 'ansible', 'cfg' ]
+ # file_n_ext => [ 'ansible', '.cfg' ]
file_n_ext: "{{ 'ansible.cfg' | splitext }}"
- # hoax => ['/etc/hoasdf', '']
- hoax: '{{ "/etc//hoasdf/" | splitext }}'
+ # hoax => [ '/etc/hoasdf', '' ]
+ hoax: "{{ '/etc/hoasdf' | splitext }}"
RETURN:
_value:
diff --git a/lib/ansible/plugins/filter/win_splitdrive.yml b/lib/ansible/plugins/filter/win_splitdrive.yml
index 828d1dd..d6ad422 100644
--- a/lib/ansible/plugins/filter/win_splitdrive.yml
+++ b/lib/ansible/plugins/filter/win_splitdrive.yml
@@ -5,6 +5,8 @@ DOCUMENTATION:
short_description: Split a Windows path by the drive letter
description:
- Returns a list with the first component being the drive letter and the second, the rest of the path.
+ - If the path contains a drive letter, drive will contain everything up to and including the colon.
+ - If the path contains a UNC (Universal Naming Convention) path, drive will contain the host name and share, up to but not including the fourth separator.
options:
_input:
description: A Windows path.
@@ -13,17 +15,27 @@ DOCUMENTATION:
EXAMPLES: |
- # To get the last name of a file Windows path, like ['C', '\Users\asdf\foo.txt'] out of 'C:\Users\asdf\foo.txt'
+ # To get the last name of a file Windows path, like ['C:', '\Users\asdf\foo.txt'] out of 'C:\Users\asdf\foo.txt'
{{ mypath | win_splitdrive }}
- # just the drive letter
+ # To get path from UNC (Universal Naming Convention) path, like ['//host/computer', '/dir/a'] out of '//host/computer/dir/a'
+
+ # just the drive letter, like ['C:'] out of 'C:\Users\asdf\foo.txt'
+ {{ mypath | win_splitdrive | first }}
+
+ # path w/o drive letter, like ['\Users\asdf\foo.txt'] out of 'C:\Users\asdf\foo.txt'
+ {{ mypath | win_splitdrive | last }}
+
+ # just the hostname and share, like ['//host/computer'] out of '//host/computer/dir/a'
{{ mypath | win_splitdrive | first }}
- # path w/o drive letter
+ # path w/o hostname and share, like ['/dir/a'] out of '//host/computer/dir/a'
{{ mypath | win_splitdrive | last }}
RETURN:
_value:
- description: List in which the first element is the drive letter and the second the rest of the path.
+ description:
+ - List in which the first element is the drive letter with colon and the second the rest of the path.
+ - In case of UNC path, first element is the hostname and share and the second the rest of the path.
type: list
elements: str
diff --git a/lib/ansible/plugins/shell/__init__.py b/lib/ansible/plugins/shell/__init__.py
index 5aa0a1b..56be533 100644
--- a/lib/ansible/plugins/shell/__init__.py
+++ b/lib/ansible/plugins/shell/__init__.py
@@ -85,7 +85,7 @@ class ShellBase(AnsiblePlugin):
return 'ansible-tmp-%s-%s-%s' % (time.time(), os.getpid(), random.randint(0, 2**48))
def env_prefix(self, **kwargs):
- return ' '.join(['%s=%s' % (k, shlex.quote(text_type(v))) for k, v in kwargs.items()])
+ return ' '.join(['%s=%s' % (k, self.quote(text_type(v))) for k, v in kwargs.items()])
def join_path(self, *args):
return os.path.join(*args)
@@ -101,41 +101,33 @@ class ShellBase(AnsiblePlugin):
def chmod(self, paths, mode):
cmd = ['chmod', mode]
cmd.extend(paths)
- cmd = [shlex.quote(c) for c in cmd]
-
- return ' '.join(cmd)
+ return shlex.join(cmd)
def chown(self, paths, user):
cmd = ['chown', user]
cmd.extend(paths)
- cmd = [shlex.quote(c) for c in cmd]
-
- return ' '.join(cmd)
+ return shlex.join(cmd)
def chgrp(self, paths, group):
cmd = ['chgrp', group]
cmd.extend(paths)
- cmd = [shlex.quote(c) for c in cmd]
-
- return ' '.join(cmd)
+ return shlex.join(cmd)
def set_user_facl(self, paths, user, mode):
"""Only sets acls for users as that's really all we need"""
cmd = ['setfacl', '-m', 'u:%s:%s' % (user, mode)]
cmd.extend(paths)
- cmd = [shlex.quote(c) for c in cmd]
-
- return ' '.join(cmd)
+ return shlex.join(cmd)
def remove(self, path, recurse=False):
- path = shlex.quote(path)
+ path = self.quote(path)
cmd = 'rm -f '
if recurse:
cmd += '-r '
return cmd + "%s %s" % (path, self._SHELL_REDIRECT_ALLNULL)
def exists(self, path):
- cmd = ['test', '-e', shlex.quote(path)]
+ cmd = ['test', '-e', self.quote(path)]
return ' '.join(cmd)
def mkdtemp(self, basefile=None, system=False, mode=0o700, tmpdir=None):
@@ -194,8 +186,7 @@ class ShellBase(AnsiblePlugin):
# Check that the user_path to expand is safe
if user_home_path != '~':
if not _USER_HOME_PATH_RE.match(user_home_path):
- # shlex.quote will make the shell return the string verbatim
- user_home_path = shlex.quote(user_home_path)
+ user_home_path = self.quote(user_home_path)
elif username:
# if present the user name is appended to resolve "that user's home"
user_home_path += username
@@ -207,20 +198,20 @@ class ShellBase(AnsiblePlugin):
return 'echo %spwd%s' % (self._SHELL_SUB_LEFT, self._SHELL_SUB_RIGHT)
def build_module_command(self, env_string, shebang, cmd, arg_path=None):
- # don't quote the cmd if it's an empty string, because this will break pipelining mode
- if cmd.strip() != '':
- cmd = shlex.quote(cmd)
+ env_string = env_string.strip()
+ if env_string:
+ env_string += ' '
- cmd_parts = []
- if shebang:
- shebang = shebang.replace("#!", "").strip()
- else:
- shebang = ""
- cmd_parts.extend([env_string.strip(), shebang, cmd])
- if arg_path is not None:
- cmd_parts.append(arg_path)
- new_cmd = " ".join(cmd_parts)
- return new_cmd
+ if shebang is None:
+ shebang = ''
+
+ cmd_parts = [
+ shebang.removeprefix('#!').strip(),
+ cmd.strip(),
+ arg_path,
+ ]
+
+ return f'{env_string}%s' % shlex.join(cps for cp in cmd_parts if cp and (cps := cp.strip()))
def append_command(self, cmd, cmd_to_append):
"""Append an additional command if supported by the shell"""
diff --git a/lib/ansible/plugins/shell/sh.py b/lib/ansible/plugins/shell/sh.py
index e0412b7..ef85596 100644
--- a/lib/ansible/plugins/shell/sh.py
+++ b/lib/ansible/plugins/shell/sh.py
@@ -13,8 +13,6 @@ extends_documentation_fragment:
- shell_common
'''
-import shlex
-
from ansible.plugins.shell import ShellBase
@@ -66,7 +64,7 @@ class ShellModule(ShellBase):
# Quoting gets complex here. We're writing a python string that's
# used by a variety of shells on the remote host to invoke a python
# "one-liner".
- shell_escaped_path = shlex.quote(path)
+ shell_escaped_path = self.quote(path)
test = "rc=flag; [ -r %(p)s ] %(shell_or)s rc=2; [ -f %(p)s ] %(shell_or)s rc=1; [ -d %(p)s ] %(shell_and)s rc=3; %(i)s -V 2>/dev/null %(shell_or)s rc=4; [ x\"$rc\" != \"xflag\" ] %(shell_and)s echo \"${rc} \"%(p)s %(shell_and)s exit 0" % dict(p=shell_escaped_path, i=python_interp, shell_and=self._SHELL_AND, shell_or=self._SHELL_OR) # NOQA
csums = [
u"({0} -c 'import hashlib; BLOCKSIZE = 65536; hasher = hashlib.sha1();{2}afile = open(\"'{1}'\", \"rb\"){2}buf = afile.read(BLOCKSIZE){2}while len(buf) > 0:{2}\thasher.update(buf){2}\tbuf = afile.read(BLOCKSIZE){2}afile.close(){2}print(hasher.hexdigest())' 2>/dev/null)".format(python_interp, shell_escaped_path, self._SHELL_EMBEDDED_PY_EOL), # NOQA Python > 2.4 (including python3)
diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py
index efd69ef..a5822c3 100644
--- a/lib/ansible/plugins/strategy/__init__.py
+++ b/lib/ansible/plugins/strategy/__init__.py
@@ -549,27 +549,13 @@ class StrategyBase:
yield handler
break
- templar.available_variables = {}
- seen = []
+ seen = set()
for handler in handlers:
- if listeners := handler.listen:
- listeners = handler.get_validated_value(
- 'listen',
- handler.fattributes.get('listen'),
- listeners,
- templar,
- )
- if handler._role is not None:
- for listener in listeners.copy():
- listeners.extend([
- handler._role.get_name(include_role_fqcn=True) + ' : ' + listener,
- handler._role.get_name(include_role_fqcn=False) + ' : ' + listener
- ])
- if notification in listeners:
- if handler.name and handler.name in seen:
- continue
- seen.append(handler.name)
- yield handler
+ if notification in handler.listen:
+ if handler.name and handler.name in seen:
+ continue
+ seen.add(handler.name)
+ yield handler
@debug_closure
def _process_pending_results(self, iterator, one_pass=False, max_passes=None):
diff --git a/lib/ansible/release.py b/lib/ansible/release.py
index 88f7515..7ed9fdf 100644
--- a/lib/ansible/release.py
+++ b/lib/ansible/release.py
@@ -17,6 +17,6 @@
from __future__ import annotations
-__version__ = '2.17.0'
+__version__ = '2.17.1'
__author__ = 'Ansible, Inc.'
__codename__ = "Gallows Pole"
diff --git a/lib/ansible_core.egg-info/PKG-INFO b/lib/ansible_core.egg-info/PKG-INFO
index 90ea773..5cefb3a 100644
--- a/lib/ansible_core.egg-info/PKG-INFO
+++ b/lib/ansible_core.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: ansible-core
-Version: 2.17.0
+Version: 2.17.1
Summary: Radically simple IT automation
Home-page: https://ansible.com/
Author: Ansible, Inc.
diff --git a/lib/ansible_core.egg-info/SOURCES.txt b/lib/ansible_core.egg-info/SOURCES.txt
index 343b795..6aa9d9d 100644
--- a/lib/ansible_core.egg-info/SOURCES.txt
+++ b/lib/ansible_core.egg-info/SOURCES.txt
@@ -3452,9 +3452,12 @@ test/integration/targets/setup_win_printargv/files/PrintArgv.cs
test/integration/targets/setup_win_printargv/meta/main.yml
test/integration/targets/setup_win_printargv/tasks/main.yml
test/integration/targets/shell/aliases
+test/integration/targets/shell/test-command-building-playbook.yml
test/integration/targets/shell/action_plugins/test_shell.py
test/integration/targets/shell/connection_plugins/test_connection_default.py
test/integration/targets/shell/connection_plugins/test_connection_override.py
+test/integration/targets/shell/meta/main.yml
+test/integration/targets/shell/tasks/command-building.yml
test/integration/targets/shell/tasks/main.yml
test/integration/targets/slurp/aliases
test/integration/targets/slurp/files/bar.bin
diff --git a/test/integration/targets/any_errors_fatal/80981.yml b/test/integration/targets/any_errors_fatal/80981.yml
index 51cf8df..5654aa1 100644
--- a/test/integration/targets/any_errors_fatal/80981.yml
+++ b/test/integration/targets/any_errors_fatal/80981.yml
@@ -11,7 +11,7 @@
rescue:
- name: Rescues both hosts
debug:
- msg: rescue
+ msg: rescuedd
- name: You can recover from fatal errors by adding a rescue section to the block.
debug:
msg: recovered
diff --git a/test/integration/targets/any_errors_fatal/runme.sh b/test/integration/targets/any_errors_fatal/runme.sh
index 58f0ddf..55381a7 100755
--- a/test/integration/targets/any_errors_fatal/runme.sh
+++ b/test/integration/targets/any_errors_fatal/runme.sh
@@ -15,8 +15,6 @@ if [ "${res}" -eq 0 ] ; then
exit 1
fi
-set -ux
-
ansible-playbook -i inventory "$@" always_block.yml | tee out.txt | grep 'any_errors_fatal_always_block_start'
res=$?
cat out.txt
@@ -25,8 +23,6 @@ if [ "${res}" -ne 0 ] ; then
exit 1
fi
-set -ux
-
for test_name in test_include_role test_include_tasks; do
ansible-playbook -i inventory "$@" -e test_name=$test_name 50897.yml | tee out.txt | grep 'any_errors_fatal_this_should_never_be_reached'
res=$?
@@ -36,6 +32,8 @@ for test_name in test_include_role test_include_tasks; do
fi
done
+set -e
+
ansible-playbook -i inventory "$@" 31543.yml | tee out.txt
[ "$(grep -c 'SHOULD NOT HAPPEN' out.txt)" -eq 0 ]
@@ -47,5 +45,5 @@ ansible-playbook -i inventory "$@" 73246.yml | tee out.txt
ansible-playbook -i inventory "$@" 80981.yml | tee out.txt
[ "$(grep -c 'SHOULD NOT HAPPEN' out.txt)" -eq 0 ]
-[ "$(grep -c 'rescue' out.txt)" -eq 2 ]
+[ "$(grep -c 'rescuedd' out.txt)" -eq 2 ]
[ "$(grep -c 'recovered' out.txt)" -eq 2 ]
diff --git a/test/integration/targets/collections/test_task_resolved_plugin/unqualified_and_collections_kw.yml b/test/integration/targets/collections/test_task_resolved_plugin/unqualified_and_collections_kw.yml
index 5af4eda..ac70778 100644
--- a/test/integration/targets/collections/test_task_resolved_plugin/unqualified_and_collections_kw.yml
+++ b/test/integration/targets/collections/test_task_resolved_plugin/unqualified_and_collections_kw.yml
@@ -10,5 +10,7 @@
- ping:
- collection_action:
- collection_module:
- - formerly_action:
- - formerly_module:
+ - local_action:
+ module: formerly_action
+ - action:
+ module: formerly_module
diff --git a/test/integration/targets/json_cleanup/library/bad_json b/test/integration/targets/json_cleanup/library/bad_json
index 1df8c72..e84de04 100644
--- a/test/integration/targets/json_cleanup/library/bad_json
+++ b/test/integration/targets/json_cleanup/library/bad_json
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/bin/bash
set -eu
diff --git a/test/integration/targets/json_cleanup/module_output_cleaning.yml b/test/integration/targets/json_cleanup/module_output_cleaning.yml
index 165352a..94ae9c1 100644
--- a/test/integration/targets/json_cleanup/module_output_cleaning.yml
+++ b/test/integration/targets/json_cleanup/module_output_cleaning.yml
@@ -2,10 +2,17 @@
hosts: localhost
gather_facts: false
tasks:
+ - name: where is bash
+ shell: command -v bash
+ register: bash
+ changed_when: false
+
- name: call module that spews extra stuff
bad_json:
register: clean_json
ignore_errors: true
+ vars:
+ ansible_bash_interpreter: '{{ bash.stdout }}'
- name: all expected is there
assert:
diff --git a/test/integration/targets/module_defaults/tasks/main.yml b/test/integration/targets/module_defaults/tasks/main.yml
index 747c2f9..0483278 100644
--- a/test/integration/targets/module_defaults/tasks/main.yml
+++ b/test/integration/targets/module_defaults/tasks/main.yml
@@ -10,16 +10,23 @@
- debug:
register: foo
+ - local_action:
+ module: debug
+ register: local_action_foo
+
- name: test that 'debug' task used default 'msg' param
assert:
- that: foo.msg == "test default"
+ that:
+ - foo.msg == "test default"
+ - local_action_foo.msg == "test default"
- name: remove test file
file:
state: absent
- name: touch test file
- file:
+ local_action:
+ module: file
state: touch
- name: stat test file
diff --git a/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1 b/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1
index 625cb21..27b0d10 100644
--- a/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1
+++ b/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1
@@ -1323,6 +1323,37 @@ test_no_log - Invoked with:
$actual | Assert-DictionaryEqual -Expected $expected
}
+ "Run with exec wrapper warnings" = {
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_exec_wrapper_warnings = [System.Collections.Generic.List[string]]@(
+ 'Warning 1',
+ 'Warning 2'
+ )
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+ $m.Warn("Warning 3")
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{}
+ }
+ warnings = @("Warning 1", "Warning 2", "Warning 3")
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
"FailJson with message" = {
$m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
diff --git a/test/integration/targets/shell/meta/main.yml b/test/integration/targets/shell/meta/main.yml
new file mode 100644
index 0000000..cb6005d
--- /dev/null
+++ b/test/integration/targets/shell/meta/main.yml
@@ -0,0 +1,3 @@
+dependencies:
+ - prepare_tests
+ - setup_remote_tmp_dir
diff --git a/test/integration/targets/shell/tasks/command-building.yml b/test/integration/targets/shell/tasks/command-building.yml
new file mode 100644
index 0000000..bd45261
--- /dev/null
+++ b/test/integration/targets/shell/tasks/command-building.yml
@@ -0,0 +1,53 @@
+- vars:
+ atd: '{{ remote_tmp_dir }}/shell/t m p'
+ api: '{{ remote_tmp_dir }}/shell/p y t h o n'
+ block:
+ - name: create test dir
+ file:
+ path: '{{ atd|dirname }}'
+ state: directory
+
+ - name: create tempdir with spaces
+ file:
+ path: '{{ atd }}'
+ state: directory
+
+ - name: create symlink for ansible_python_interpreter to file with spaces
+ file:
+ dest: '{{ api }}'
+ src: '{{ ansible_facts.python.executable }}'
+ state: link
+
+ - name: run simple test playbook
+ command: >-
+ ansible-playbook -vvv -i inventory
+ -e 'ansible_python_interpreter="{{ api }}"'
+ -e 'ansible_pipelining=0'
+ "{{ role_path }}/test-command-building-playbook.yml"
+ environment:
+ ANSIBLE_REMOTE_TMP: '{{ atd }}'
+ ANSIBLE_NOCOLOR: "1"
+ ANSIBLE_FORCE_COLOR: "0"
+ register: command_building
+ delegate_to: localhost
+
+ - debug:
+ var: command_building.stdout_lines
+
+ - block:
+ - debug:
+ var: py_cmd
+
+ - debug:
+ var: tmp_dir
+
+ - assert:
+ that:
+ - py_cmd in exec_line
+ - tmp_dir in exec_line
+ vars:
+ exec_line: '{{ command_building.stdout_lines | select("search", "EXEC.*p y t h o n") | first }}'
+ py_cmd: >-
+ '"'"'{{ api }}'"'"'
+ tmp_dir: >-
+ '"'"'{{ atd }}/ansible-tmp-
diff --git a/test/integration/targets/shell/tasks/main.yml b/test/integration/targets/shell/tasks/main.yml
index d6f2a2b..7a442f4 100644
--- a/test/integration/targets/shell/tasks/main.yml
+++ b/test/integration/targets/shell/tasks/main.yml
@@ -34,3 +34,5 @@
with_items:
- powershell
- sh
+
+- import_tasks: command-building.yml
diff --git a/test/integration/targets/shell/test-command-building-playbook.yml b/test/integration/targets/shell/test-command-building-playbook.yml
new file mode 100644
index 0000000..f77cf4c
--- /dev/null
+++ b/test/integration/targets/shell/test-command-building-playbook.yml
@@ -0,0 +1,4 @@
+- hosts: testhost
+ gather_facts: false
+ tasks:
+ - ping:
diff --git a/test/integration/targets/win_exec_wrapper/tasks/main.yml b/test/integration/targets/win_exec_wrapper/tasks/main.yml
index f1342c4..75da3d6 100644
--- a/test/integration/targets/win_exec_wrapper/tasks/main.yml
+++ b/test/integration/targets/win_exec_wrapper/tasks/main.yml
@@ -194,16 +194,33 @@
become_test_username: ansible_become_test
gen_pw: "{{ 'password123!' + lookup('password', '/dev/null chars=ascii_letters,digits length=8') }}"
-- name: create unprivileged user
- win_user:
- name: "{{ become_test_username }}"
- password: "{{ gen_pw }}"
- update_password: always
- groups: Users
- register: become_test_user_result
-
- name: execute tests and ensure that test user is deleted regardless of success/failure
block:
+ - name: create unprivileged user
+ win_user:
+ name: "{{ become_test_username }}"
+ password: "{{ gen_pw }}"
+ update_password: always
+ groups: Users
+ register: become_test_user_result
+
+ - name: create tempdir for test user
+ win_file:
+ path: C:\Windows\TEMP\test-dir
+ state: directory
+
+ - name: deny delete permissions on new temp dir for test user
+ win_acl:
+ path: C:\Windows\TEMP\test-dir
+ user: '{{ become_test_user_result.sid }}'
+ type: '{{ item.type }}'
+ rights: '{{ item.rights }}'
+ loop:
+ - type: allow
+ rights: ListDirectory, CreateFiles, CreateDirectories, ReadAttributes, ReadExtendedAttributes, WriteData, WriteAttributes, WriteExtendedAttributes, Synchronize
+ - type: deny
+ rights: DeleteSubdirectoriesAndFiles, Delete
+
- name: ensure current user is not the become user
win_shell: whoami
register: whoami_out
@@ -238,6 +255,21 @@
- become_system is successful
- become_system.output == become_test_user_result.sid
+ - name: run module with tempdir with no delete access
+ win_ping:
+ register: temp_deletion_warning
+ vars:
+ <<: *become_vars
+ ansible_remote_tmp: C:\Windows\TEMP\test-dir
+
+ - name: assert warning about tmpdir deletion is present
+ assert:
+ that:
+ - temp_deletion_warning.warnings | count == 1
+ - >-
+ temp_deletion_warning.warnings[0] is
+ regex("(?i).*Failed to cleanup temporary directory 'C:\\\\Windows\\\\TEMP\\\\test-dir\\\\.*' used for compiling C# code\\. Files may still be present after the task is complete\\..*")
+
always:
- name: ensure test user is deleted
win_user:
@@ -249,7 +281,12 @@
win_shell: rmdir /S /Q {{ profile_dir_out.stdout_lines[0] }}
args:
executable: cmd.exe
- when: become_test_username in profile_dir_out.stdout_lines[0]
+ when: become_test_username in profile_dir_out.stdout_lines[0] | default("")
+
+ - name: remove test tempdir
+ win_file:
+ path: C:\Windows\TEMP\test-dir
+ state: absent
- name: test common functions in exec
test_common_functions:
diff --git a/test/lib/ansible_test/_internal/pypi_proxy.py b/test/lib/ansible_test/_internal/pypi_proxy.py
index d119efa..1c9a9aa 100644
--- a/test/lib/ansible_test/_internal/pypi_proxy.py
+++ b/test/lib/ansible_test/_internal/pypi_proxy.py
@@ -69,7 +69,7 @@ def run_pypi_proxy(args: EnvironmentConfig, targets_use_pypi: bool) -> None:
display.warning('Unable to use the PyPI proxy because Docker is not available. Installation of packages using `pip` may fail.')
return
- image = 'quay.io/ansible/pypi-test-container:2.0.0'
+ image = 'quay.io/ansible/pypi-test-container:3.1.0'
port = 3141
run_support_container(
diff --git a/test/units/playbook/test_block.py b/test/units/playbook/test_block.py
index aac5f71..3c4dfb1 100644
--- a/test/units/playbook/test_block.py
+++ b/test/units/playbook/test_block.py
@@ -20,6 +20,10 @@ from __future__ import annotations
import unittest
from ansible.playbook.block import Block
from ansible.playbook.task import Task
+from ansible.plugins.loader import init_plugin_loader
+
+
+init_plugin_loader()
class TestBlock(unittest.TestCase):
diff --git a/test/units/playbook/test_helpers.py b/test/units/playbook/test_helpers.py
index 2977b0d..fb6170c 100644
--- a/test/units/playbook/test_helpers.py
+++ b/test/units/playbook/test_helpers.py
@@ -24,13 +24,16 @@ from unittest.mock import MagicMock
from units.mock.loader import DictDataLoader
from ansible import errors
+from ansible.playbook import helpers
from ansible.playbook.block import Block
from ansible.playbook.handler import Handler
from ansible.playbook.task import Task
from ansible.playbook.task_include import TaskInclude
from ansible.playbook.role.include import RoleInclude
+from ansible.plugins.loader import init_plugin_loader
-from ansible.playbook import helpers
+
+init_plugin_loader()
class MixinForMocks(object):
diff --git a/test/units/utils/display/test_broken_cowsay.py b/test/units/utils/display/test_broken_cowsay.py
index 854b78b..50691c2 100644
--- a/test/units/utils/display/test_broken_cowsay.py
+++ b/test/units/utils/display/test_broken_cowsay.py
@@ -10,13 +10,12 @@ from unittest.mock import MagicMock
def test_display_with_fake_cowsay_binary(capsys, mocker):
- display = Display()
mocker.patch("ansible.constants.ANSIBLE_COW_PATH", "./cowsay.sh")
-
mock_popen = MagicMock()
mock_popen.return_value.returncode = 1
mocker.patch("subprocess.Popen", mock_popen)
+ display = Display()
assert not hasattr(display, "cows_available")
assert display.b_cowsay is None