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:
parent
e3816514a8
commit
10f86cff19
3 changed files with 186 additions and 11 deletions
|
|
@ -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)."
|
||||
|
|
@ -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')})"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
"",
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue