1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-02-04 07:51:50 +00:00

incus connection: fix regex (#11347)

* incus connection: fix regex

* updates

* Apply suggestions from code review

* expand regexp capture

* add changelog frag

* Update plugins/connection/incus.py

* split arguments after command option

* Update plugins/connection/incus.py

* remove *() and split from the last command

* add tests, make small adjustments

* remove redundant strip()

* add more tests

* adjusted changelog fragment
This commit is contained in:
Alexei Znamensky 2026-01-10 09:07:31 +13:00 committed by GitHub
parent 91efa27cb9
commit e790b95067
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 152 additions and 17 deletions

View file

@ -0,0 +1,6 @@
bugfixes:
- >
incus connection plugin - fix parsing of commands for Windows, enforcing a ``\`` after the drive letter and colon symbol
(https://github.com/ansible-collections/community.general/pull/11347).
minor_changes:
- incus connection plugin - simplify regular expression matching commands (https://github.com/ansible-collections/community.general/pull/11347).

View file

@ -104,11 +104,11 @@ class Connection(ConnectionBase):
if getattr(self._shell, "_IS_WINDOWS", False): if getattr(self._shell, "_IS_WINDOWS", False):
# Initializing regular expression patterns to match on a PowerShell or cmd command line. # Initializing regular expression patterns to match on a PowerShell or cmd command line.
self.powershell_regex_pattern = re.compile( self.powershell_regex_pattern = re.compile(
r"^(?P<executable>(\"?([a-z]:)?[a-z0-9 ()\\.]+)?powershell(\.exe)?\"?|(([a-z]:)?[a-z0-9()\\.]+)?powershell(\.exe)?)\s+.*(?P<command>-c(ommand)?)\s+", # noqa: E501 r'^"?(?P<executable>(?:[a-z]:\\)?[a-z0-9 ()\\.]*powershell(?:\.exe)?)"?\s+(?P<args>.*)(?P<command>-c(?:ommand)?)\s+(?P<post_args>.*(\n.*)*)',
re.IGNORECASE, re.IGNORECASE,
) )
self.cmd_regex_pattern = re.compile( self.cmd_regex_pattern = re.compile(
r"^(?P<executable>(\"?([a-z]:)?[a-z0-9 ()\\.]+)?cmd(\.exe)?\"?|(([a-z]:)?[a-z0-9()\\.]+)?cmd(\.exe)?)\s+.*(?P<command>/c)\s+", r'^"?(?P<executable>(?:[a-z]:\\)?[a-z0-9 ()\\.]*cmd(?:\.exe)?)"?\s+(?P<args>.*)(?P<command>/c)\s+(?P<post_args>.*)',
re.IGNORECASE, re.IGNORECASE,
) )
@ -153,21 +153,14 @@ class Connection(ConnectionBase):
host=self._instance(), host=self._instance(),
) )
# Split the command on the argument -c(ommand) for PowerShell or /c for cmd. # To avoid splitting on a space contained in the path, set the executable as the first argument.
before_command_argument, after_command_argument = cmd.split(regex_match.group("command"), 1) exec_cmd.append(regex_match.group("executable"))
if args := regex_match.group("args"):
exec_cmd.extend( exec_cmd.extend(args.strip().split(" "))
[ # Set the command argument depending on cmd or powershell and the rest of it
# To avoid splitting on a space contained in the path, set the executable as the first argument. exec_cmd.append(regex_match.group("command"))
regex_match.group("executable").strip('"'), if post_args := regex_match.group("post_args"):
# Remove the executable path and split the rest by space. exec_cmd.append(post_args.strip())
*(before_command_argument[len(regex_match.group("executable")) :].lstrip().split(" ")),
# Set the command argument depending on cmd or powershell.
regex_match.group("command"),
# Add the rest of the command at the end.
after_command_argument,
]
)
else: else:
# For anything else using -EncodedCommand or else, just split on space. # For anything else using -EncodedCommand or else, just split on space.
exec_cmd.extend(cmd.split(" ")) exec_cmd.extend(cmd.split(" "))

View file

@ -0,0 +1,136 @@
# Copyright (c) 2026, Alexei Znamensky <russoz@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import annotations
import pytest
import typing as t
from io import StringIO
from ansible.playbook.play_context import PlayContext
from ansible.plugins.loader import connection_loader
BUILD_CMD_TEST_CASES: list[dict[str, t.Any]] = [
dict(
id="sh simple",
input=dict(
cmd="""echo 123""",
shell="sh",
),
output=["/bin/sh", "-c", "echo 123"],
),
dict(
id="powershell simple",
input=dict(
cmd="""powershell -c My-Command1 -Param 'param' """,
shell="powershell",
),
output=["powershell", "-c", "My-Command1 -Param 'param'"],
),
dict(
id=r"D:\my path\powershell.exe simple",
input=dict(
cmd=r"""D:\my path\powershell.exe -c My-Command1 -Param 'param' """,
shell="powershell",
),
output=[r"D:\my path\powershell.exe", "-c", "My-Command1 -Param 'param'"],
),
dict(
id=r"\\127.0.0.1\share\powershell simple",
input=dict(
cmd=r"""\\127.0.0.1\share\powershell -c My-Command1 -Param 'param' """,
shell="powershell",
),
output=[r"\\127.0.0.1\share\powershell", "-c", "My-Command1 -Param 'param'"],
),
dict(
id=r"C:\powershell simple",
input=dict(
cmd=r"""C:\powershell -c My-Command1 -Param 'param' """,
shell="powershell",
),
output=[r"C:\powershell", "-c", "My-Command1 -Param 'param'"],
),
dict(
id=r'"C:\powershell" simple',
input=dict(
cmd=r""""C:\powershell" -c My-Command1 -Param 'param' """,
shell="cmd", # the plugins does not care the shell type, as long as it is windows
),
output=[r"C:\powershell", "-c", "My-Command1 -Param 'param'"],
),
dict(
id="powershell multiline",
input=dict(
cmd=(
"powershell -ExecutionPolicy bypass -Command\n"
"My-Command1 \\\n"
" -Param 'param';\n"
"My-Command2 \\\n"
" -Param 'param'"
),
shell="powershell",
),
output=[
"powershell",
"-ExecutionPolicy",
"bypass",
"-Command",
"My-Command1 \\\n -Param 'param';\nMy-Command2 \\\n -Param 'param'",
],
),
dict(
id="cmd simple",
input=dict(
cmd="""CMD.EXE /C some-command /flag1 /flag2""",
shell="powershell",
),
output=["CMD.EXE", "/C", "some-command /flag1 /flag2"],
),
dict(
id=r"C:\cmd simple",
input=dict(
cmd=r"""C:\CMD.EXE /C some-command /flag1 /flag2""",
shell="powershell",
),
output=[r"C:\CMD.EXE", "/C", "some-command /flag1 /flag2"],
),
dict(
id=r'"C:\cmd" simple',
input=dict(
cmd=r""""C:\CMD" /c some-command /flag1 /flag2""",
shell="powershell",
),
output=[r"C:\CMD", "/c", "some-command /flag1 /flag2"],
),
]
BUILD_CMD_TEST_CASE_IDS: list[str] = [tc["id"] for tc in BUILD_CMD_TEST_CASES]
@pytest.mark.parametrize("testcase", BUILD_CMD_TEST_CASES, ids=BUILD_CMD_TEST_CASE_IDS)
def test_build_command(mocker, testcase):
mocker.patch("ansible.module_utils.common.process.get_bin_path").return_value = "/test/bin/incus"
play_context = PlayContext()
play_context.shell = testcase["input"].get("shell", "sh")
in_stream = StringIO()
conn = connection_loader.get("community.general.incus", play_context, in_stream)
conn.set_option("remote_addr", "server1")
conn.set_option("remote_user", "root")
cli_preamble = [
"/test/bin/incus",
"--project",
"default",
"exec",
*(["-T"] if play_context.shell in ["cmd", "powershell"] else []),
"local:server1",
"--",
]
built = conn._build_command(testcase["input"]["cmd"])
tc_cmd = cli_preamble + testcase["output"]
assert built == tc_cmd, f"\n built = {built}\ntestcase = {tc_cmd}"