1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-06-10 18:15:39 +00:00

[PR #12163/f9d4f0ad backport][stable-12] Fix incus Windows modules with ansible-core 2.21 (#12178)

Fix incus Windows modules with ansible-core 2.21 (#12163)

* Fix incus Windows modules with ansible-core 2.21

* strip wrapper quotes for payload flags (-enc, -encodedcommand, -command, -c, -file, -f) before incus exec argv handoff, added changelogs

* Fixed some edge cases for powershell parsing

* Fixed changelogs

* Fixed pep8 format

* Added warning message when modifying direct commands

* Fixed changelogs fragement

(cherry picked from commit f9d4f0ad6b)

Co-authored-by: Simon Bouchard <simon.bouchard23@gmail.com>
This commit is contained in:
patchback[bot] 2026-06-02 21:18:39 +02:00 committed by GitHub
parent e3816514a8
commit 10f86cff19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 186 additions and 11 deletions

View file

@ -0,0 +1,3 @@
bugfixes:
- "incus connection plugin - improve Windows PowerShell argv handling by stripping wrapper quotes from payload arguments for ``-enc``, ``-encodedcommand``, ``-command``, ``-c``, ``-file`` and ``-f`` (https://github.com/ansible-collections/community.general/issues/12161, https://github.com/ansible-collections/community.general/pull/12158)."
- "incus connection plugin - return ``stdout``/``stderr`` as bytes instead of strings to restore compatibility with ansible-core 2.21 module execution (https://github.com/ansible-collections/community.general/issues/12161, https://github.com/ansible-collections/community.general/pull/12158)."

View file

@ -79,11 +79,12 @@ options:
import os
import re
import shlex
from subprocess import PIPE, Popen, call
from ansible.errors import AnsibleConnectionFailure, AnsibleError, AnsibleFileNotFound
from ansible.module_utils.common.process import get_bin_path
from ansible.module_utils.common.text.converters import to_bytes, to_text
from ansible.module_utils.common.text.converters import to_bytes
from ansible.plugins.connection import ConnectionBase
@ -163,13 +164,37 @@ class Connection(ConnectionBase):
exec_cmd.append(regex_match.group("executable"))
if args := regex_match.group("args"):
exec_cmd.extend(args.strip().split(" "))
# Set the command argument depending on cmd or powershell and the rest of it
exec_cmd.append(regex_match.group("command"))
if post_args := regex_match.group("post_args"):
exec_cmd.append(post_args.strip())
post_args = post_args.strip()
# Keep quotes from becoming literal PowerShell argument content.
if len(post_args) >= 2 and post_args[0] == post_args[-1] and post_args[0] in ("'", '"'):
self._display.v(
"WARNING: PowerShell -Command argument is wrapped in outer quotes; "
"this connection plugin strips those quotes and behavior may differ "
"from a direct shell run. Prefer passing -Command without extra "
"outer quoting.",
host=self._instance(),
)
post_args = post_args[1:-1]
exec_cmd.append(post_args)
else:
# For anything else using -EncodedCommand or else, just split on space.
exec_cmd.extend(cmd.split(" "))
# Keep quotes from becoming literal PowerShell argument content.
# shlex is not a full PowerShell/Windows command-line parser,
# but with posix=False it reliably keeps quoted segments (for
# example, paths with spaces) as single argv items, which is
# what we need before passing arguments to incus exec.
parts = shlex.split(cmd, posix=False)
for i, part in enumerate(parts[:-1]):
if part.lower() in {"-enc", "-encodedcommand", "-file", "-f"}:
parts[i + 1] = parts[i + 1].strip("'\"")
break
exec_cmd.extend(parts)
else:
if self.get_option("remote_user") != "root":
self._display.vvv(
@ -203,25 +228,22 @@ class Connection(ConnectionBase):
process = Popen(local_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = process.communicate(in_data)
stdout = to_text(stdout)
stderr = to_text(stderr)
if stderr.startswith("Error: ") and stderr.rstrip().endswith(": Instance is not running"):
if stderr.startswith(b"Error: ") and stderr.rstrip().endswith(b": Instance is not running"):
raise AnsibleConnectionFailure(
f"instance not running: {self._instance()} (remote={self.get_option('remote')}, project={self.get_option('project')})"
)
if stderr.startswith("Error: ") and stderr.rstrip().endswith(": Instance not found"):
if stderr.startswith(b"Error: ") and stderr.rstrip().endswith(b": Instance not found"):
raise AnsibleConnectionFailure(
f"instance not found: {self._instance()} (remote={self.get_option('remote')}, project={self.get_option('project')})"
)
if stderr.startswith("Error: ") and ": User does not have permission " in stderr:
if stderr.startswith(b"Error: ") and b": User does not have permission " in stderr:
raise AnsibleConnectionFailure(
f"instance access denied: {self._instance()} (remote={self.get_option('remote')}, project={self.get_option('project')})"
)
if stderr.startswith("Error: ") and ": User does not have entitlement " in stderr:
if stderr.startswith(b"Error: ") and b": User does not have entitlement " in stderr:
raise AnsibleConnectionFailure(
f"instance access denied: {self._instance()} (remote={self.get_option('remote')}, project={self.get_option('project')})"
)

View file

@ -104,6 +104,156 @@ BUILD_CMD_TEST_CASES: list[dict[str, t.Any]] = [
),
output=[r"C:\CMD", "/c", "some-command /flag1 /flag2"],
),
dict(
id="powershell encoded command strips quotes",
input=dict(
cmd="""powershell -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -EncodedCommand 'cABhAHIAYQBtAA=='""",
shell="powershell",
),
output=[
"powershell",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Unrestricted",
"-EncodedCommand",
"cABhAHIAYQBtAA==",
],
),
dict(
id="powershell encoded command strips double quotes",
input=dict(
cmd='''powershell -NoProfile -EncodedCommand "cABhAHIAYQBtAA=="''',
shell="powershell",
),
output=[
"powershell",
"-NoProfile",
"-EncodedCommand",
"cABhAHIAYQBtAA==",
],
),
dict(
id="powershell encoded command case-insensitive keyword",
input=dict(
cmd="""powershell -NoProfile -eNcOdEdCoMmAnD 'cABhAHIAYQBtAA=='""",
shell="powershell",
),
output=[
"powershell",
"-NoProfile",
"-eNcOdEdCoMmAnD",
"cABhAHIAYQBtAA==",
],
),
dict(
id="powershell encoded command keeps surrounding flags",
input=dict(
cmd="""powershell -NoProfile -EncodedCommand 'cABhAHIAYQBtAA==' -InputFormat None""",
shell="powershell",
),
output=[
"powershell",
"-NoProfile",
"-EncodedCommand",
"cABhAHIAYQBtAA==",
"-InputFormat",
"None",
],
),
dict(
id="powershell -enc alias strips quotes",
input=dict(
cmd="""powershell -NoProfile -enc 'cABhAHIAYQBtAA=='""",
shell="powershell",
),
output=[
"powershell",
"-NoProfile",
"-enc",
"cABhAHIAYQBtAA==",
],
),
dict(
id="powershell -Command with spaces in path",
input=dict(
cmd="""powershell.exe -NonInteractive -Command 'Write-Host "hello"; & \\'C:\\My Scripts\\run me.ps1\\''""",
shell="powershell",
),
output=[
"powershell.exe",
"-NonInteractive",
"-Command",
"""Write-Host "hello"; & \\'C:\\My Scripts\\run me.ps1\\'""",
],
),
dict(
id="powershell -File with spaces in path",
input=dict(
cmd='''powershell.exe -NonInteractive -File "C:\\My Scripts\\run me.ps1"''',
shell="powershell",
),
output=[
"powershell.exe",
"-NonInteractive",
"-File",
r"C:\My Scripts\run me.ps1",
],
),
dict(
id="powershell -File single quoted path with trailing args",
input=dict(
cmd="""powershell.exe -NoProfile -File 'C:\\My Scripts\\run me.ps1' -Arg1 value""",
shell="powershell",
),
output=[
"powershell.exe",
"-NoProfile",
"-File",
r"C:\My Scripts\run me.ps1",
"-Arg1",
"value",
],
),
dict(
id="powershell -F alias with spaces in path",
input=dict(
cmd='''powershell.exe -NoProfile -F "C:\\My Scripts\\run me.ps1"''',
shell="powershell",
),
output=[
"powershell.exe",
"-NoProfile",
"-F",
r"C:\My Scripts\run me.ps1",
],
),
dict(
id="powershell -Command outer double quotes",
input=dict(
cmd='''powershell.exe -NoProfile -Command "Write-Host 'hello world'"''',
shell="powershell",
),
output=[
"powershell.exe",
"-NoProfile",
"-Command",
"Write-Host 'hello world'",
],
),
dict(
id="powershell -Command empty quoted payload",
input=dict(
cmd='''powershell.exe -NoProfile -Command ""''',
shell="powershell",
),
output=[
"powershell.exe",
"-NoProfile",
"-Command",
"",
],
),
]