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

Added support for Windows VM with Incus connection. (#11199)

* Added support for Windows VM with Incus connection.

* Update changelogs/fragments/11199-incus-windows.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Attempt to fix the argument splitting.

* Only split on the first occurrence of the command argument

* Applying nox

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
Marc Olivier Bergeron 2025-11-29 09:18:02 -05:00 committed by GitHub
parent d550baacfa
commit 22a4f8e272
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 62 additions and 9 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- incus connection plugin - add support for Windows virtual machines (https://github.com/ansible-collections/community.general/pull/11199).

View file

@ -13,6 +13,9 @@ short_description: Run tasks in Incus instances using the Incus CLI
description:
- Run commands or put/fetch files to an existing Incus instance using Incus CLI.
version_added: "8.2.0"
notes:
- When using this collection for Windows virtual machines, set C(ansible_shell_type) to C(powershell) or C(cmd) as a variable to the host in
the inventory.
options:
remote_addr:
description:
@ -75,6 +78,7 @@ options:
"""
import os
import re
from subprocess import call, Popen, PIPE
from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound
@ -97,6 +101,23 @@ class Connection(ConnectionBase):
if not self._incus_cmd:
raise AnsibleError("incus command not found in PATH")
if getattr(self._shell, "_IS_WINDOWS", False):
# Initializing regular expression patterns to match on a PowerShell or cmd command line.
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
re.IGNORECASE,
)
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+",
re.IGNORECASE,
)
# Basic setup for a Windows host.
self.has_native_async = True
self.always_pipeline_modules = True
self.module_implementation_preferences = (".ps1", ".exe", "")
self.allow_executable = False
def _connect(self):
"""connect to Incus (nothing to do here)"""
super()._connect()
@ -115,19 +136,49 @@ class Connection(ConnectionBase):
"--project",
self.get_option("project"),
"exec",
*(["-T"] if getattr(self._shell, "_IS_WINDOWS", False) else []),
f"{self.get_option('remote')}:{self._instance()}",
"--",
]
if self.get_option("remote_user") != "root":
self._display.vvv(
f"INFO: Running as non-root user: {self.get_option('remote_user')}, \
trying to run 'incus exec' with become method: {self.get_option('incus_become_method')}",
host=self._instance(),
)
exec_cmd.extend([self.get_option("incus_become_method"), self.get_option("remote_user"), "-c"])
if getattr(self._shell, "_IS_WINDOWS", False):
if (
(regex_match := self.powershell_regex_pattern.match(cmd))
and (regex_pattern := self.powershell_regex_pattern)
) or ((regex_match := self.cmd_regex_pattern.match(cmd)) and (regex_pattern := self.cmd_regex_pattern)):
self._display.vvvvvv(
f'Found keyword: "{regex_match.group("command")}" based on regex: {regex_pattern.pattern}',
host=self._instance(),
)
exec_cmd.extend([self.get_option("executable"), "-c", cmd])
# Split the command on the argument -c(ommand) for PowerShell or /c for cmd.
before_command_argument, after_command_argument = cmd.split(regex_match.group("command"), 1)
exec_cmd.extend(
[
# To avoid splitting on a space contained in the path, set the executable as the first argument.
regex_match.group("executable").strip('"'),
# Remove the executable path and split the rest by space.
*(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:
# For anything else using -EncodedCommand or else, just split on space.
exec_cmd.extend(cmd.split(" "))
else:
if self.get_option("remote_user") != "root":
self._display.vvv(
f"INFO: Running as non-root user: {self.get_option('remote_user')}, \
trying to run 'incus exec' with become method: {self.get_option('incus_become_method')}",
host=self._instance(),
)
exec_cmd.extend([self.get_option("incus_become_method"), self.get_option("remote_user"), "-c"])
exec_cmd.extend([self.get_option("executable"), "-c", cmd])
return exec_cmd
@ -200,7 +251,7 @@ class Connection(ConnectionBase):
if not os.path.isfile(to_bytes(in_path, errors="surrogate_or_strict")):
raise AnsibleFileNotFound(f"input path is not a file: {in_path}")
if self.get_option("remote_user") != "root":
if not getattr(self._shell, "_IS_WINDOWS", False) and self.get_option("remote_user") != "root":
uid, gid = self._get_remote_uid_gid()
local_cmd = [
self._incus_cmd,