1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-04-19 02:11:32 +00:00

Reformat everything.

This commit is contained in:
Felix Fontein 2025-11-01 12:08:41 +01:00
parent 3f2213791a
commit 340ff8586d
1008 changed files with 61301 additions and 58309 deletions

View file

@ -87,16 +87,16 @@ display = Display()
class Connection(ConnectionBase):
""" Local chroot based connections """
"""Local chroot based connections"""
transport = 'community.general.chroot'
transport = "community.general.chroot"
has_pipelining = True
# su currently has an undiagnosed issue with calculating the file
# checksums (so copy, for instance, doesn't work right)
# Have to look into that before re-enabling this
has_tty = False
default_user = 'root'
default_user = "root"
def __init__(self, play_context, new_stdin, *args, **kwargs):
super().__init__(play_context, new_stdin, *args, **kwargs)
@ -107,7 +107,7 @@ class Connection(ConnectionBase):
if not os.path.isdir(self.chroot):
raise AnsibleError(f"{self.chroot} is not a directory")
chrootsh = os.path.join(self.chroot, 'bin/sh')
chrootsh = os.path.join(self.chroot, "bin/sh")
# Want to check for a usable bourne shell inside the chroot.
# is_executable() == True is sufficient. For symlinks it
# gets really complicated really fast. So we punt on finding that
@ -116,17 +116,18 @@ class Connection(ConnectionBase):
raise AnsibleError(f"{self.chroot} does not look like a chrootable dir (/bin/sh missing)")
def _connect(self):
""" connect to the chroot """
if not self.get_option('disable_root_check') and os.geteuid() != 0:
"""connect to the chroot"""
if not self.get_option("disable_root_check") and os.geteuid() != 0:
raise AnsibleError(
"chroot connection requires running as root. "
"You can override this check with the `disable_root_check` option.")
"You can override this check with the `disable_root_check` option."
)
if os.path.isabs(self.get_option('chroot_exe')):
self.chroot_cmd = self.get_option('chroot_exe')
if os.path.isabs(self.get_option("chroot_exe")):
self.chroot_cmd = self.get_option("chroot_exe")
else:
try:
self.chroot_cmd = get_bin_path(self.get_option('chroot_exe'))
self.chroot_cmd = get_bin_path(self.get_option("chroot_exe"))
except ValueError as e:
raise AnsibleError(str(e))
@ -136,25 +137,24 @@ class Connection(ConnectionBase):
self._connected = True
def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE):
""" run a command on the chroot. This is only needed for implementing
"""run a command on the chroot. This is only needed for implementing
put_file() get_file() so that we don't have to read the whole file
into memory.
compared to exec_command() it looses some niceties like being able to
return the process's exit code immediately.
"""
executable = self.get_option('executable')
local_cmd = [self.chroot_cmd, self.chroot, executable, '-c', cmd]
executable = self.get_option("executable")
local_cmd = [self.chroot_cmd, self.chroot, executable, "-c", cmd]
display.vvv(f"EXEC {local_cmd}", host=self.chroot)
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return p
def exec_command(self, cmd, in_data=None, sudoable=False):
""" run a command on the chroot """
"""run a command on the chroot"""
super().exec_command(cmd, in_data=in_data, sudoable=sudoable)
p = self._buffered_exec_command(cmd)
@ -164,33 +164,33 @@ class Connection(ConnectionBase):
@staticmethod
def _prefix_login_path(remote_path):
""" Make sure that we put files into a standard path
"""Make sure that we put files into a standard path
If a path is relative, then we need to choose where to put it.
ssh chooses $HOME but we aren't guaranteed that a home dir will
exist in any given chroot. So for now we're choosing "/" instead.
This also happens to be the former default.
If a path is relative, then we need to choose where to put it.
ssh chooses $HOME but we aren't guaranteed that a home dir will
exist in any given chroot. So for now we're choosing "/" instead.
This also happens to be the former default.
Can revisit using $HOME instead if it is a problem
Can revisit using $HOME instead if it is a problem
"""
if not remote_path.startswith(os.path.sep):
remote_path = os.path.join(os.path.sep, remote_path)
return os.path.normpath(remote_path)
def put_file(self, in_path, out_path):
""" transfer a file from local to chroot """
"""transfer a file from local to chroot"""
super().put_file(in_path, out_path)
display.vvv(f"PUT {in_path} TO {out_path}", host=self.chroot)
out_path = shlex_quote(self._prefix_login_path(out_path))
try:
with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file:
with open(to_bytes(in_path, errors="surrogate_or_strict"), "rb") as in_file:
if not os.fstat(in_file.fileno()).st_size:
count = ' count=0'
count = " count=0"
else:
count = ''
count = ""
try:
p = self._buffered_exec_command(f'dd of={out_path} bs={BUFSIZE}{count}', stdin=in_file)
p = self._buffered_exec_command(f"dd of={out_path} bs={BUFSIZE}{count}", stdin=in_file)
except OSError:
raise AnsibleError("chroot connection requires dd command in the chroot")
try:
@ -204,17 +204,17 @@ class Connection(ConnectionBase):
raise AnsibleError(f"file or module does not exist at: {in_path}")
def fetch_file(self, in_path, out_path):
""" fetch a file from chroot to local """
"""fetch a file from chroot to local"""
super().fetch_file(in_path, out_path)
display.vvv(f"FETCH {in_path} TO {out_path}", host=self.chroot)
in_path = shlex_quote(self._prefix_login_path(in_path))
try:
p = self._buffered_exec_command(f'dd if={in_path} bs={BUFSIZE}')
p = self._buffered_exec_command(f"dd if={in_path} bs={BUFSIZE}")
except OSError:
raise AnsibleError("chroot connection requires dd command in the chroot")
with open(to_bytes(out_path, errors='surrogate_or_strict'), 'wb+') as out_file:
with open(to_bytes(out_path, errors="surrogate_or_strict"), "wb+") as out_file:
try:
chunk = p.stdout.read(BUFSIZE)
while chunk:
@ -228,6 +228,6 @@ class Connection(ConnectionBase):
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}:\n{stdout}\n{stderr}")
def close(self):
""" terminate the connection; nothing to do here """
"""terminate the connection; nothing to do here"""
super().close()
self._connected = False

View file

@ -29,6 +29,7 @@ options:
HAVE_FUNC = False
try:
import func.overlord.client as fc
HAVE_FUNC = True
except ImportError:
pass
@ -45,7 +46,7 @@ display = Display()
class Connection(ConnectionBase):
""" Func-based connections """
"""Func-based connections"""
has_pipelining = False
@ -64,7 +65,7 @@ class Connection(ConnectionBase):
return self
def exec_command(self, cmd, in_data=None, sudoable=True):
""" run a command on the remote minion """
"""run a command on the remote minion"""
if in_data:
raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
@ -82,16 +83,16 @@ class Connection(ConnectionBase):
return os.path.join(prefix, normpath[1:])
def put_file(self, in_path, out_path):
""" transfer a file from local to remote """
"""transfer a file from local to remote"""
out_path = self._normalize_path(out_path, '/')
out_path = self._normalize_path(out_path, "/")
display.vvv(f"PUT {in_path} TO {out_path}", host=self.host)
self.client.local.copyfile.send(in_path, out_path)
def fetch_file(self, in_path, out_path):
""" fetch a file from remote to local """
"""fetch a file from remote to local"""
in_path = self._normalize_path(in_path, '/')
in_path = self._normalize_path(in_path, "/")
display.vvv(f"FETCH {in_path} TO {out_path}", host=self.host)
# need to use a tmp dir due to difference of semantic for getfile
# ( who take a # directory as destination) and fetch_file, who
@ -102,5 +103,5 @@ class Connection(ConnectionBase):
shutil.rmtree(tmpdir)
def close(self):
""" terminate the connection; nothing to do here """
"""terminate the connection; nothing to do here"""
pass

View file

@ -84,7 +84,7 @@ from ansible.plugins.connection import ConnectionBase
class Connection(ConnectionBase):
""" Incus based connections """
"""Incus based connections"""
transport = "incus"
has_pipelining = True
@ -98,12 +98,13 @@ class Connection(ConnectionBase):
raise AnsibleError("incus command not found in PATH")
def _connect(self):
"""connect to Incus (nothing to do here) """
"""connect to Incus (nothing to do here)"""
super()._connect()
if not self._connected:
self._display.vvv(f"ESTABLISH Incus CONNECTION FOR USER: {self.get_option('remote_user')}",
host=self._instance())
self._display.vvv(
f"ESTABLISH Incus CONNECTION FOR USER: {self.get_option('remote_user')}", host=self._instance()
)
self._connected = True
def _build_command(self, cmd) -> list[str]:
@ -111,10 +112,12 @@ class Connection(ConnectionBase):
exec_cmd: list[str] = [
self._incus_cmd,
"--project", self.get_option("project"),
"--project",
self.get_option("project"),
"exec",
f"{self.get_option('remote')}:{self._instance()}",
"--"]
"--",
]
if self.get_option("remote_user") != "root":
self._display.vvv(
@ -122,9 +125,7 @@ class Connection(ConnectionBase):
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("incus_become_method"), self.get_option("remote_user"), "-c"])
exec_cmd.extend([self.get_option("executable"), "-c", cmd])
@ -133,20 +134,19 @@ class Connection(ConnectionBase):
def _instance(self):
# Return only the leading part of the FQDN as the instance name
# as Incus instance names cannot be a FQDN.
return self.get_option('remote_addr').split(".")[0]
return self.get_option("remote_addr").split(".")[0]
def exec_command(self, cmd, in_data=None, sudoable=True):
""" execute a command on the Incus host """
"""execute a command on the Incus host"""
super().exec_command(cmd, in_data=in_data, sudoable=sudoable)
self._display.vvv(f"EXEC {cmd}",
host=self._instance())
self._display.vvv(f"EXEC {cmd}", host=self._instance())
local_cmd = self._build_command(cmd)
self._display.vvvvv(f"EXEC {local_cmd}", host=self._instance())
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
in_data = to_bytes(in_data, errors='surrogate_or_strict', nonstring='passthru')
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
in_data = to_bytes(in_data, errors="surrogate_or_strict", nonstring="passthru")
process = Popen(local_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = process.communicate(in_data)
@ -154,32 +154,22 @@ class Connection(ConnectionBase):
stdout = to_text(stdout)
stderr = to_text(stderr)
if stderr.startswith("Error: ") and stderr.rstrip().endswith(
": Instance is not running"
):
if stderr.startswith("Error: ") and stderr.rstrip().endswith(": 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("Error: ") and stderr.rstrip().endswith(": 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("Error: ") and ": 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("Error: ") and ": 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')})"
)
@ -191,28 +181,23 @@ class Connection(ConnectionBase):
rc, uid_out, err = self.exec_command("/bin/id -u")
if rc != 0:
raise AnsibleError(
f"Failed to get remote uid for user {self.get_option('remote_user')}: {err}"
)
raise AnsibleError(f"Failed to get remote uid for user {self.get_option('remote_user')}: {err}")
uid = uid_out.strip()
rc, gid_out, err = self.exec_command("/bin/id -g")
if rc != 0:
raise AnsibleError(
f"Failed to get remote gid for user {self.get_option('remote_user')}: {err}"
)
raise AnsibleError(f"Failed to get remote gid for user {self.get_option('remote_user')}: {err}")
gid = gid_out.strip()
return int(uid), int(gid)
def put_file(self, in_path, out_path):
""" put a file from local to Incus """
"""put a file from local to Incus"""
super().put_file(in_path, out_path)
self._display.vvv(f"PUT {in_path} TO {out_path}",
host=self._instance())
self._display.vvv(f"PUT {in_path} TO {out_path}", host=self._instance())
if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')):
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":
@ -245,30 +230,33 @@ class Connection(ConnectionBase):
self._display.vvvvv(f"PUT {local_cmd}", host=self._instance())
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
call(local_cmd)
def fetch_file(self, in_path, out_path):
""" fetch a file from Incus to local """
"""fetch a file from Incus to local"""
super().fetch_file(in_path, out_path)
self._display.vvv(f"FETCH {in_path} TO {out_path}",
host=self._instance())
self._display.vvv(f"FETCH {in_path} TO {out_path}", host=self._instance())
local_cmd = [
self._incus_cmd,
"--project", self.get_option("project"),
"file", "pull", "--quiet",
"--project",
self.get_option("project"),
"file",
"pull",
"--quiet",
f"{self.get_option('remote')}:{self._instance()}/{in_path}",
out_path]
out_path,
]
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
call(local_cmd)
def close(self):
""" close the connection (nothing to do here) """
"""close the connection (nothing to do here)"""
super().close()
self._connected = False

View file

@ -42,31 +42,33 @@ display = Display()
class Connection(Jail):
""" Local iocage based connections """
"""Local iocage based connections"""
transport = 'community.general.iocage'
transport = "community.general.iocage"
def __init__(self, play_context, new_stdin, *args, **kwargs):
self.ioc_jail = play_context.remote_addr
self.iocage_cmd = Jail._search_executable('iocage')
self.iocage_cmd = Jail._search_executable("iocage")
jail_uuid = self.get_jail_uuid()
kwargs[Jail.modified_jailname_key] = f'ioc-{jail_uuid}'
kwargs[Jail.modified_jailname_key] = f"ioc-{jail_uuid}"
display.vvv(
f"Jail {self.ioc_jail} has been translated to {kwargs[Jail.modified_jailname_key]}",
host=kwargs[Jail.modified_jailname_key]
host=kwargs[Jail.modified_jailname_key],
)
super().__init__(play_context, new_stdin, *args, **kwargs)
def get_jail_uuid(self):
p = subprocess.Popen([self.iocage_cmd, 'get', 'host_hostuuid', self.ioc_jail],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
p = subprocess.Popen(
[self.iocage_cmd, "get", "host_hostuuid", self.ioc_jail],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
stdout, stderr = p.communicate()
@ -82,4 +84,4 @@ class Connection(Jail):
if p.returncode != 0:
raise AnsibleError(f"iocage returned an error: {stdout}")
return stdout.strip('\n')
return stdout.strip("\n")

View file

@ -49,11 +49,11 @@ display = Display()
class Connection(ConnectionBase):
""" Local BSD Jail based connections """
"""Local BSD Jail based connections"""
modified_jailname_key = 'conn_jail_name'
modified_jailname_key = "conn_jail_name"
transport = 'community.general.jail'
transport = "community.general.jail"
# Pipelining may work. Someone needs to test by setting this to True and
# having pipelining=True in their ansible.cfg
has_pipelining = True
@ -69,8 +69,8 @@ class Connection(ConnectionBase):
if os.geteuid() != 0:
raise AnsibleError("jail connection requires running as root")
self.jls_cmd = self._search_executable('jls')
self.jexec_cmd = self._search_executable('jexec')
self.jls_cmd = self._search_executable("jls")
self.jexec_cmd = self._search_executable("jexec")
if self.jail not in self.list_jails():
raise AnsibleError(f"incorrect jail name {self.jail}")
@ -83,23 +83,23 @@ class Connection(ConnectionBase):
raise AnsibleError(f"{executable} command not found in PATH")
def list_jails(self):
p = subprocess.Popen([self.jls_cmd, '-q', 'name'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(
[self.jls_cmd, "-q", "name"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
stdout, stderr = p.communicate()
return to_text(stdout, errors='surrogate_or_strict').split()
return to_text(stdout, errors="surrogate_or_strict").split()
def _connect(self):
""" connect to the jail; nothing to do here """
"""connect to the jail; nothing to do here"""
super()._connect()
if not self._connected:
display.vvv(f"ESTABLISH JAIL CONNECTION FOR USER: {self._play_context.remote_user}", host=self.jail)
self._connected = True
def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE):
""" run a command on the jail. This is only needed for implementing
"""run a command on the jail. This is only needed for implementing
put_file() get_file() so that we don't have to read the whole file
into memory.
@ -108,24 +108,23 @@ class Connection(ConnectionBase):
"""
local_cmd = [self.jexec_cmd]
set_env = ''
set_env = ""
if self._play_context.remote_user is not None:
local_cmd += ['-U', self._play_context.remote_user]
local_cmd += ["-U", self._play_context.remote_user]
# update HOME since -U does not update the jail environment
set_env = f"HOME=~{self._play_context.remote_user} "
local_cmd += [self.jail, self._play_context.executable, '-c', set_env + cmd]
local_cmd += [self.jail, self._play_context.executable, "-c", set_env + cmd]
display.vvv(f"EXEC {local_cmd}", host=self.jail)
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return p
def exec_command(self, cmd, in_data=None, sudoable=False):
""" run a command on the jail """
"""run a command on the jail"""
super().exec_command(cmd, in_data=in_data, sudoable=sudoable)
p = self._buffered_exec_command(cmd)
@ -135,33 +134,33 @@ class Connection(ConnectionBase):
@staticmethod
def _prefix_login_path(remote_path):
""" Make sure that we put files into a standard path
"""Make sure that we put files into a standard path
If a path is relative, then we need to choose where to put it.
ssh chooses $HOME but we aren't guaranteed that a home dir will
exist in any given chroot. So for now we're choosing "/" instead.
This also happens to be the former default.
If a path is relative, then we need to choose where to put it.
ssh chooses $HOME but we aren't guaranteed that a home dir will
exist in any given chroot. So for now we're choosing "/" instead.
This also happens to be the former default.
Can revisit using $HOME instead if it is a problem
Can revisit using $HOME instead if it is a problem
"""
if not remote_path.startswith(os.path.sep):
remote_path = os.path.join(os.path.sep, remote_path)
return os.path.normpath(remote_path)
def put_file(self, in_path, out_path):
""" transfer a file from local to jail """
"""transfer a file from local to jail"""
super().put_file(in_path, out_path)
display.vvv(f"PUT {in_path} TO {out_path}", host=self.jail)
out_path = shlex_quote(self._prefix_login_path(out_path))
try:
with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file:
with open(to_bytes(in_path, errors="surrogate_or_strict"), "rb") as in_file:
if not os.fstat(in_file.fileno()).st_size:
count = ' count=0'
count = " count=0"
else:
count = ''
count = ""
try:
p = self._buffered_exec_command(f'dd of={out_path} bs={BUFSIZE}{count}', stdin=in_file)
p = self._buffered_exec_command(f"dd of={out_path} bs={BUFSIZE}{count}", stdin=in_file)
except OSError:
raise AnsibleError("jail connection requires dd command in the jail")
try:
@ -170,22 +169,24 @@ class Connection(ConnectionBase):
traceback.print_exc()
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}")
if p.returncode != 0:
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}:\n{to_native(stdout)}\n{to_native(stderr)}")
raise AnsibleError(
f"failed to transfer file {in_path} to {out_path}:\n{to_native(stdout)}\n{to_native(stderr)}"
)
except IOError:
raise AnsibleError(f"file or module does not exist at: {in_path}")
def fetch_file(self, in_path, out_path):
""" fetch a file from jail to local """
"""fetch a file from jail to local"""
super().fetch_file(in_path, out_path)
display.vvv(f"FETCH {in_path} TO {out_path}", host=self.jail)
in_path = shlex_quote(self._prefix_login_path(in_path))
try:
p = self._buffered_exec_command(f'dd if={in_path} bs={BUFSIZE}')
p = self._buffered_exec_command(f"dd if={in_path} bs={BUFSIZE}")
except OSError:
raise AnsibleError("jail connection requires dd command in the jail")
with open(to_bytes(out_path, errors='surrogate_or_strict'), 'wb+') as out_file:
with open(to_bytes(out_path, errors="surrogate_or_strict"), "wb+") as out_file:
try:
chunk = p.stdout.read(BUFSIZE)
while chunk:
@ -196,9 +197,11 @@ class Connection(ConnectionBase):
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}")
stdout, stderr = p.communicate()
if p.returncode != 0:
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}:\n{to_native(stdout)}\n{to_native(stderr)}")
raise AnsibleError(
f"failed to transfer file {in_path} to {out_path}:\n{to_native(stdout)}\n{to_native(stderr)}"
)
def close(self):
""" terminate the connection; nothing to do here """
"""terminate the connection; nothing to do here"""
super().close()
self._connected = False

View file

@ -41,6 +41,7 @@ import errno
HAS_LIBLXC = False
try:
import lxc as _lxc
HAS_LIBLXC = True
except ImportError:
pass
@ -51,11 +52,11 @@ from ansible.plugins.connection import ConnectionBase
class Connection(ConnectionBase):
""" Local lxc based connections """
"""Local lxc based connections"""
transport = 'community.general.lxc'
transport = "community.general.lxc"
has_pipelining = True
default_user = 'root'
default_user = "root"
def __init__(self, play_context, new_stdin, *args, **kwargs):
super().__init__(play_context, new_stdin, *args, **kwargs)
@ -64,14 +65,14 @@ class Connection(ConnectionBase):
self.container = None
def _connect(self):
""" connect to the lxc; nothing to do here """
"""connect to the lxc; nothing to do here"""
super()._connect()
if not HAS_LIBLXC:
msg = "lxc python bindings are not installed"
raise errors.AnsibleError(msg)
container_name = self.get_option('remote_addr')
container_name = self.get_option("remote_addr")
if self.container and self.container_name == container_name:
return
@ -98,7 +99,7 @@ class Connection(ConnectionBase):
continue
raise
for fd in ready_writes:
in_data = in_data[os.write(fd, in_data):]
in_data = in_data[os.write(fd, in_data) :]
if len(in_data) == 0:
write_fds.remove(fd)
for fd in ready_reads:
@ -117,12 +118,12 @@ class Connection(ConnectionBase):
return fd
def exec_command(self, cmd, in_data=None, sudoable=False):
""" run a command on the chroot """
"""run a command on the chroot"""
super().exec_command(cmd, in_data=in_data, sudoable=sudoable)
# python2-lxc needs bytes. python3-lxc needs text.
executable = to_native(self.get_option('executable'), errors='surrogate_or_strict')
local_cmd = [executable, '-c', to_native(cmd, errors='surrogate_or_strict')]
executable = to_native(self.get_option("executable"), errors="surrogate_or_strict")
local_cmd = [executable, "-c", to_native(cmd, errors="surrogate_or_strict")]
read_stdout, write_stdout = None, None
read_stderr, write_stderr = None, None
@ -133,14 +134,14 @@ class Connection(ConnectionBase):
read_stderr, write_stderr = os.pipe()
kwargs = {
'stdout': self._set_nonblocking(write_stdout),
'stderr': self._set_nonblocking(write_stderr),
'env_policy': _lxc.LXC_ATTACH_CLEAR_ENV
"stdout": self._set_nonblocking(write_stdout),
"stderr": self._set_nonblocking(write_stderr),
"env_policy": _lxc.LXC_ATTACH_CLEAR_ENV,
}
if in_data:
read_stdin, write_stdin = os.pipe()
kwargs['stdin'] = self._set_nonblocking(read_stdin)
kwargs["stdin"] = self._set_nonblocking(read_stdin)
self._display.vvv(f"EXEC {local_cmd}", host=self.container_name)
pid = self.container.attach(_lxc.attach_run_command, local_cmd, **kwargs)
@ -153,28 +154,19 @@ class Connection(ConnectionBase):
if read_stdin:
read_stdin = os.close(read_stdin)
return self._communicate(pid,
in_data,
write_stdin,
read_stdout,
read_stderr)
return self._communicate(pid, in_data, write_stdin, read_stdout, read_stderr)
finally:
fds = [read_stdout,
write_stdout,
read_stderr,
write_stderr,
read_stdin,
write_stdin]
fds = [read_stdout, write_stdout, read_stderr, write_stderr, read_stdin, write_stdin]
for fd in fds:
if fd:
os.close(fd)
def put_file(self, in_path, out_path):
''' transfer a file from local to lxc '''
"""transfer a file from local to lxc"""
super().put_file(in_path, out_path)
self._display.vvv(f"PUT {in_path} TO {out_path}", host=self.container_name)
in_path = to_bytes(in_path, errors='surrogate_or_strict')
out_path = to_bytes(out_path, errors='surrogate_or_strict')
in_path = to_bytes(in_path, errors="surrogate_or_strict")
out_path = to_bytes(out_path, errors="surrogate_or_strict")
if not os.path.exists(in_path):
msg = f"file or module does not exist: {in_path}"
@ -185,9 +177,11 @@ class Connection(ConnectionBase):
traceback.print_exc()
raise errors.AnsibleError(f"failed to open input file to {in_path}")
try:
def write_file(args):
with open(out_path, 'wb+') as dst_file:
with open(out_path, "wb+") as dst_file:
shutil.copyfileobj(src_file, dst_file)
try:
self.container.attach_wait(write_file, None)
except IOError:
@ -198,11 +192,11 @@ class Connection(ConnectionBase):
src_file.close()
def fetch_file(self, in_path, out_path):
''' fetch a file from lxc to local '''
"""fetch a file from lxc to local"""
super().fetch_file(in_path, out_path)
self._display.vvv(f"FETCH {in_path} TO {out_path}", host=self.container_name)
in_path = to_bytes(in_path, errors='surrogate_or_strict')
out_path = to_bytes(out_path, errors='surrogate_or_strict')
in_path = to_bytes(in_path, errors="surrogate_or_strict")
out_path = to_bytes(out_path, errors="surrogate_or_strict")
try:
dst_file = open(out_path, "wb")
@ -211,14 +205,16 @@ class Connection(ConnectionBase):
msg = f"failed to open output file {out_path}"
raise errors.AnsibleError(msg)
try:
def write_file(args):
try:
with open(in_path, 'rb') as src_file:
with open(in_path, "rb") as src_file:
shutil.copyfileobj(src_file, dst_file)
finally:
# this is needed in the lxc child process
# to flush internal python buffers
dst_file.close()
try:
self.container.attach_wait(write_file, None)
except IOError:
@ -229,6 +225,6 @@ class Connection(ConnectionBase):
dst_file.close()
def close(self):
''' terminate the connection; nothing to do here '''
"""terminate the connection; nothing to do here"""
super().close()
self._connected = False

View file

@ -83,9 +83,9 @@ from ansible.plugins.connection import ConnectionBase
class Connection(ConnectionBase):
""" lxd based connections """
"""lxd based connections"""
transport = 'community.general.lxd'
transport = "community.general.lxd"
has_pipelining = True
def __init__(self, play_context, new_stdin, *args, **kwargs):
@ -97,11 +97,11 @@ class Connection(ConnectionBase):
raise AnsibleError("lxc command not found in PATH")
def _host(self):
""" translate remote_addr to lxd (short) hostname """
"""translate remote_addr to lxd (short) hostname"""
return self.get_option("remote_addr").split(".", 1)[0]
def _connect(self):
"""connect to lxd (nothing to do here) """
"""connect to lxd (nothing to do here)"""
super()._connect()
if not self._connected:
@ -124,16 +124,14 @@ class Connection(ConnectionBase):
trying to run 'lxc exec' with become method: {self.get_option('lxd_become_method')}",
host=self._host(),
)
exec_cmd.extend(
[self.get_option("lxd_become_method"), self.get_option("remote_user"), "-c"]
)
exec_cmd.extend([self.get_option("lxd_become_method"), self.get_option("remote_user"), "-c"])
exec_cmd.extend([self.get_option("executable"), "-c", cmd])
return exec_cmd
def exec_command(self, cmd, in_data=None, sudoable=True):
""" execute a command on the lxd host """
"""execute a command on the lxd host"""
super().exec_command(cmd, in_data=in_data, sudoable=sudoable)
self._display.vvv(f"EXEC {cmd}", host=self._host())
@ -141,8 +139,8 @@ class Connection(ConnectionBase):
local_cmd = self._build_command(cmd)
self._display.vvvvv(f"EXEC {local_cmd}", host=self._host())
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
in_data = to_bytes(in_data, errors='surrogate_or_strict', nonstring='passthru')
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
in_data = to_bytes(in_data, errors="surrogate_or_strict", nonstring="passthru")
process = Popen(local_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = process.communicate(in_data)
@ -165,27 +163,23 @@ class Connection(ConnectionBase):
rc, uid_out, err = self.exec_command("/bin/id -u")
if rc != 0:
raise AnsibleError(
f"Failed to get remote uid for user {self.get_option('remote_user')}: {err}"
)
raise AnsibleError(f"Failed to get remote uid for user {self.get_option('remote_user')}: {err}")
uid = uid_out.strip()
rc, gid_out, err = self.exec_command("/bin/id -g")
if rc != 0:
raise AnsibleError(
f"Failed to get remote gid for user {self.get_option('remote_user')}: {err}"
)
raise AnsibleError(f"Failed to get remote gid for user {self.get_option('remote_user')}: {err}")
gid = gid_out.strip()
return int(uid), int(gid)
def put_file(self, in_path, out_path):
""" put a file from local to lxd """
"""put a file from local to lxd"""
super().put_file(in_path, out_path)
self._display.vvv(f"PUT {in_path} TO {out_path}", host=self._host())
if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')):
if not os.path.isfile(to_bytes(in_path, errors="surrogate_or_strict")):
raise AnsibleFileNotFound(f"input path is not a file: {in_path}")
local_cmd = [self._lxc_cmd]
@ -218,13 +212,13 @@ class Connection(ConnectionBase):
self._display.vvvvv(f"PUT {local_cmd}", host=self._host())
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
process = Popen(local_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
process.communicate()
def fetch_file(self, in_path, out_path):
""" fetch a file from lxd to local """
"""fetch a file from lxd to local"""
super().fetch_file(in_path, out_path)
self._display.vvv(f"FETCH {in_path} TO {out_path}", host=self._host())
@ -232,19 +226,15 @@ class Connection(ConnectionBase):
local_cmd = [self._lxc_cmd]
if self.get_option("project"):
local_cmd.extend(["--project", self.get_option("project")])
local_cmd.extend([
"file", "pull",
f"{self.get_option('remote')}:{self._host()}/{in_path}",
out_path
])
local_cmd.extend(["file", "pull", f"{self.get_option('remote')}:{self._host()}/{in_path}", out_path])
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
process = Popen(local_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
process.communicate()
def close(self):
""" close the connection (nothing to do here) """
"""close the connection (nothing to do here)"""
super().close()
self._connected = False

View file

@ -53,7 +53,7 @@ class Connection(ConnectionBase):
"""This is a connection plugin for qubes: it uses qubes-run-vm binary to interact with the containers."""
# String used to identify this Connection class from other classes
transport = 'community.general.qubes'
transport = "community.general.qubes"
has_pipelining = True
def __init__(self, play_context, new_stdin, *args, **kwargs):
@ -88,16 +88,17 @@ class Connection(ConnectionBase):
local_cmd.append(shell)
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
display.vvvv("Local cmd: ", local_cmd)
display.vvv(f"RUN {local_cmd}", host=self._remote_vmname)
p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(
local_cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
# Here we are writing the actual command to the remote bash
p.stdin.write(to_bytes(cmd, errors='surrogate_or_strict'))
p.stdin.write(to_bytes(cmd, errors="surrogate_or_strict"))
stdout, stderr = p.communicate(input=in_data)
return p.returncode, stdout, stderr
@ -108,7 +109,7 @@ class Connection(ConnectionBase):
@ensure_connect # type: ignore # TODO: for some reason, the type infos for ensure_connect suck...
def exec_command(self, cmd, in_data=None, sudoable=False):
"""Run specified command in a running QubesVM """
"""Run specified command in a running QubesVM"""
super().exec_command(cmd, in_data=in_data, sudoable=sudoable)
display.vvvv(f"CMD IS: {cmd}")
@ -119,24 +120,24 @@ class Connection(ConnectionBase):
return rc, stdout, stderr
def put_file(self, in_path, out_path):
""" Place a local file located in 'in_path' inside VM at 'out_path' """
"""Place a local file located in 'in_path' inside VM at 'out_path'"""
super().put_file(in_path, out_path)
display.vvv(f"PUT {in_path} TO {out_path}", host=self._remote_vmname)
with open(in_path, "rb") as fobj:
source_data = fobj.read()
retcode, dummy, dummy = self._qubes(f'cat > "{out_path}\"\n', source_data, "qubes.VMRootShell")
retcode, dummy, dummy = self._qubes(f'cat > "{out_path}"\n', source_data, "qubes.VMRootShell")
# if qubes.VMRootShell service not supported, fallback to qubes.VMShell and
# hope it will have appropriate permissions
if retcode == 127:
retcode, dummy, dummy = self._qubes(f'cat > "{out_path}\"\n', source_data)
retcode, dummy, dummy = self._qubes(f'cat > "{out_path}"\n', source_data)
if retcode != 0:
raise AnsibleConnectionFailure(f'Failed to put_file to {out_path}')
raise AnsibleConnectionFailure(f"Failed to put_file to {out_path}")
def fetch_file(self, in_path, out_path):
"""Obtain file specified via 'in_path' from the container and place it at 'out_path' """
"""Obtain file specified via 'in_path' from the container and place it at 'out_path'"""
super().fetch_file(in_path, out_path)
display.vvv(f"FETCH {in_path} TO {out_path}", host=self._remote_vmname)
@ -146,9 +147,9 @@ class Connection(ConnectionBase):
p = subprocess.Popen(cmd_args_list, shell=False, stdout=fobj)
p.communicate()
if p.returncode != 0:
raise AnsibleConnectionFailure(f'Failed to fetch file to {out_path}')
raise AnsibleConnectionFailure(f"Failed to fetch file to {out_path}")
def close(self):
""" Closing the connection """
"""Closing the connection"""
super().close()
self._connected = False

View file

@ -25,18 +25,19 @@ from ansible.plugins.connection import ConnectionBase
HAVE_SALTSTACK = False
try:
import salt.client as sc
HAVE_SALTSTACK = True
except ImportError:
pass
class Connection(ConnectionBase):
""" Salt-based connections """
"""Salt-based connections"""
has_pipelining = False
# while the name of the product is salt, naming that module salt cause
# trouble with module import
transport = 'community.general.saltstack'
transport = "community.general.saltstack"
def __init__(self, play_context, new_stdin, *args, **kwargs):
super().__init__(play_context, new_stdin, *args, **kwargs)
@ -51,7 +52,7 @@ class Connection(ConnectionBase):
return self
def exec_command(self, cmd, in_data=None, sudoable=False):
""" run a command on the remote minion """
"""run a command on the remote minion"""
super().exec_command(cmd, in_data=in_data, sudoable=sudoable)
if in_data:
@ -59,12 +60,14 @@ class Connection(ConnectionBase):
self._display.vvv(f"EXEC {cmd}", host=self.host)
# need to add 'true;' to work around https://github.com/saltstack/salt/issues/28077
res = self.client.cmd(self.host, 'cmd.exec_code_all', ['bash', f"true;{cmd}"])
res = self.client.cmd(self.host, "cmd.exec_code_all", ["bash", f"true;{cmd}"])
if self.host not in res:
raise errors.AnsibleError(f"Minion {self.host} didn't answer, check if salt-minion is running and the name is correct")
raise errors.AnsibleError(
f"Minion {self.host} didn't answer, check if salt-minion is running and the name is correct"
)
p = res[self.host]
return p['retcode'], p['stdout'], p['stderr']
return p["retcode"], p["stdout"], p["stderr"]
@staticmethod
def _normalize_path(path, prefix):
@ -74,27 +77,27 @@ class Connection(ConnectionBase):
return os.path.join(prefix, normpath[1:])
def put_file(self, in_path, out_path):
""" transfer a file from local to remote """
"""transfer a file from local to remote"""
super().put_file(in_path, out_path)
out_path = self._normalize_path(out_path, '/')
out_path = self._normalize_path(out_path, "/")
self._display.vvv(f"PUT {in_path} TO {out_path}", host=self.host)
with open(in_path, 'rb') as in_fh:
with open(in_path, "rb") as in_fh:
content = in_fh.read()
self.client.cmd(self.host, 'hashutil.base64_decodefile', [base64.b64encode(content), out_path])
self.client.cmd(self.host, "hashutil.base64_decodefile", [base64.b64encode(content), out_path])
# TODO test it
def fetch_file(self, in_path, out_path):
""" fetch a file from remote to local """
"""fetch a file from remote to local"""
super().fetch_file(in_path, out_path)
in_path = self._normalize_path(in_path, '/')
in_path = self._normalize_path(in_path, "/")
self._display.vvv(f"FETCH {in_path} TO {out_path}", host=self.host)
content = self.client.cmd(self.host, 'cp.get_file_str', [in_path])[self.host]
open(out_path, 'wb').write(content)
content = self.client.cmd(self.host, "cp.get_file_str", [in_path])[self.host]
open(out_path, "wb").write(content)
def close(self):
""" terminate the connection; nothing to do here """
"""terminate the connection; nothing to do here"""
pass

View file

@ -336,6 +336,7 @@ PARAMIKO_IMPORT_ERR: str | None
try:
import paramiko
from paramiko import MissingHostKeyPolicy
PARAMIKO_IMPORT_ERR = None
except ImportError:
PARAMIKO_IMPORT_ERR = traceback.format_exc()
@ -369,24 +370,22 @@ class MyAddPolicy(MissingHostKeyPolicy):
self._options = connection._options
def missing_host_key(self, client: paramiko.SSHClient, hostname: str, key: paramiko.PKey) -> None:
if all((self.connection.get_option('host_key_checking'), not self.connection.get_option('host_key_auto_add'))):
if all((self.connection.get_option("host_key_checking"), not self.connection.get_option("host_key_auto_add"))):
fingerprint = hexlify(key.get_fingerprint())
ktype = key.get_name()
if self.connection.get_option('use_persistent_connections') or self.connection.force_persistence:
if self.connection.get_option("use_persistent_connections") or self.connection.force_persistence:
# don't print the prompt string since the user cannot respond
# to the question anyway
raise AnsibleError(authenticity_msg(hostname, ktype, fingerprint)[1:92])
inp = to_text(
display.prompt_until(authenticity_msg(hostname, ktype, fingerprint), private=False),
errors='surrogate_or_strict'
errors="surrogate_or_strict",
)
if inp.lower() not in ['yes', 'y', '']:
raise AnsibleError('host connection rejected by user')
if inp.lower() not in ["yes", "y", ""]:
raise AnsibleError("host connection rejected by user")
key._added_by_ansible_this_time = True # type: ignore
@ -398,88 +397,96 @@ class MyAddPolicy(MissingHostKeyPolicy):
class Connection(ConnectionBase):
""" SSH based connections (paramiko) to WSL """
"""SSH based connections (paramiko) to WSL"""
transport = 'community.general.wsl'
transport = "community.general.wsl"
_log_channel: str | None = None
def __init__(self, play_context: PlayContext, new_stdin: io.TextIOWrapper | None = None, *args: t.Any, **kwargs: t.Any):
def __init__(
self, play_context: PlayContext, new_stdin: io.TextIOWrapper | None = None, *args: t.Any, **kwargs: t.Any
):
super().__init__(play_context, new_stdin, *args, **kwargs)
def _set_log_channel(self, name: str) -> None:
""" Mimic paramiko.SSHClient.set_log_channel """
"""Mimic paramiko.SSHClient.set_log_channel"""
self._log_channel = name
def _parse_proxy_command(self, port: int = 22) -> dict[str, t.Any]:
proxy_command = self.get_option('proxy_command') or None
proxy_command = self.get_option("proxy_command") or None
sock_kwarg = {}
if proxy_command:
replacers: t.Dict[str, str] = {
'%h': self.get_option('remote_addr'),
'%p': str(port),
'%r': self.get_option('remote_user')
"%h": self.get_option("remote_addr"),
"%p": str(port),
"%r": self.get_option("remote_user"),
}
for find, replace in replacers.items():
proxy_command = proxy_command.replace(find, replace)
try:
sock_kwarg = {'sock': paramiko.ProxyCommand(proxy_command)}
display.vvv(f'CONFIGURE PROXY COMMAND FOR CONNECTION: {proxy_command}', host=self.get_option('remote_addr'))
sock_kwarg = {"sock": paramiko.ProxyCommand(proxy_command)}
display.vvv(
f"CONFIGURE PROXY COMMAND FOR CONNECTION: {proxy_command}", host=self.get_option("remote_addr")
)
except AttributeError:
display.warning('Paramiko ProxyCommand support unavailable. '
'Please upgrade to Paramiko 1.9.0 or newer. '
'Not using configured ProxyCommand')
display.warning(
"Paramiko ProxyCommand support unavailable. "
"Please upgrade to Paramiko 1.9.0 or newer. "
"Not using configured ProxyCommand"
)
return sock_kwarg
def _connect(self) -> Connection:
""" activates the connection object """
"""activates the connection object"""
if PARAMIKO_IMPORT_ERR is not None:
raise AnsibleError(f'paramiko is not installed: {to_native(PARAMIKO_IMPORT_ERR)}')
raise AnsibleError(f"paramiko is not installed: {to_native(PARAMIKO_IMPORT_ERR)}")
port = self.get_option('port')
display.vvv(f'ESTABLISH PARAMIKO SSH CONNECTION FOR USER: {self.get_option("remote_user")} on PORT {to_text(port)} TO {self.get_option("remote_addr")}',
host=self.get_option('remote_addr'))
port = self.get_option("port")
display.vvv(
f"ESTABLISH PARAMIKO SSH CONNECTION FOR USER: {self.get_option('remote_user')} on PORT {to_text(port)} TO {self.get_option('remote_addr')}",
host=self.get_option("remote_addr"),
)
ssh = paramiko.SSHClient()
# Set pubkey and hostkey algorithms to disable, the only manipulation allowed currently
# is keeping or omitting rsa-sha2 algorithms
# default_keys: t.Tuple[str] = ()
paramiko_preferred_pubkeys = getattr(paramiko.Transport, '_preferred_pubkeys', ())
paramiko_preferred_hostkeys = getattr(paramiko.Transport, '_preferred_keys', ())
use_rsa_sha2_algorithms = self.get_option('use_rsa_sha2_algorithms')
paramiko_preferred_pubkeys = getattr(paramiko.Transport, "_preferred_pubkeys", ())
paramiko_preferred_hostkeys = getattr(paramiko.Transport, "_preferred_keys", ())
use_rsa_sha2_algorithms = self.get_option("use_rsa_sha2_algorithms")
disabled_algorithms: t.Dict[str, t.Iterable[str]] = {}
if not use_rsa_sha2_algorithms:
if paramiko_preferred_pubkeys:
disabled_algorithms['pubkeys'] = tuple(a for a in paramiko_preferred_pubkeys if 'rsa-sha2' in a)
disabled_algorithms["pubkeys"] = tuple(a for a in paramiko_preferred_pubkeys if "rsa-sha2" in a)
if paramiko_preferred_hostkeys:
disabled_algorithms['keys'] = tuple(a for a in paramiko_preferred_hostkeys if 'rsa-sha2' in a)
disabled_algorithms["keys"] = tuple(a for a in paramiko_preferred_hostkeys if "rsa-sha2" in a)
# override paramiko's default logger name
if self._log_channel is not None:
ssh.set_log_channel(self._log_channel)
self.keyfile = os.path.expanduser(self.get_option('user_known_hosts_file'))
self.keyfile = os.path.expanduser(self.get_option("user_known_hosts_file"))
if self.get_option('host_key_checking'):
for ssh_known_hosts in ('/etc/ssh/ssh_known_hosts', '/etc/openssh/ssh_known_hosts', self.keyfile):
if self.get_option("host_key_checking"):
for ssh_known_hosts in ("/etc/ssh/ssh_known_hosts", "/etc/openssh/ssh_known_hosts", self.keyfile):
try:
ssh.load_system_host_keys(ssh_known_hosts)
break
except IOError:
pass # file was not found, but not required to function
except paramiko.hostkeys.InvalidHostKey as e:
raise AnsibleConnectionFailure(f'Invalid host key: {to_text(e.line)}')
raise AnsibleConnectionFailure(f"Invalid host key: {to_text(e.line)}")
try:
ssh.load_system_host_keys()
except paramiko.hostkeys.InvalidHostKey as e:
raise AnsibleConnectionFailure(f'Invalid host key: {to_text(e.line)}')
raise AnsibleConnectionFailure(f"Invalid host key: {to_text(e.line)}")
ssh_connect_kwargs = self._parse_proxy_command(port)
ssh.set_missing_host_key_policy(MyAddPolicy(self))
conn_password = self.get_option('password')
conn_password = self.get_option("password")
allow_agent = True
if conn_password is not None:
@ -487,42 +494,42 @@ class Connection(ConnectionBase):
try:
key_filename = None
if self.get_option('private_key_file'):
key_filename = os.path.expanduser(self.get_option('private_key_file'))
if self.get_option("private_key_file"):
key_filename = os.path.expanduser(self.get_option("private_key_file"))
# paramiko 2.2 introduced auth_timeout parameter
if LooseVersion(paramiko.__version__) >= LooseVersion('2.2.0'):
ssh_connect_kwargs['auth_timeout'] = self.get_option('timeout')
if LooseVersion(paramiko.__version__) >= LooseVersion("2.2.0"):
ssh_connect_kwargs["auth_timeout"] = self.get_option("timeout")
# paramiko 1.15 introduced banner timeout parameter
if LooseVersion(paramiko.__version__) >= LooseVersion('1.15.0'):
ssh_connect_kwargs['banner_timeout'] = self.get_option('banner_timeout')
if LooseVersion(paramiko.__version__) >= LooseVersion("1.15.0"):
ssh_connect_kwargs["banner_timeout"] = self.get_option("banner_timeout")
ssh.connect(
self.get_option('remote_addr').lower(),
username=self.get_option('remote_user'),
self.get_option("remote_addr").lower(),
username=self.get_option("remote_user"),
allow_agent=allow_agent,
look_for_keys=self.get_option('look_for_keys'),
look_for_keys=self.get_option("look_for_keys"),
key_filename=key_filename,
password=conn_password,
timeout=self.get_option('timeout'),
timeout=self.get_option("timeout"),
port=port,
disabled_algorithms=disabled_algorithms,
**ssh_connect_kwargs,
)
except paramiko.ssh_exception.BadHostKeyException as e:
raise AnsibleConnectionFailure(f'host key mismatch for {to_text(e.hostname)}')
raise AnsibleConnectionFailure(f"host key mismatch for {to_text(e.hostname)}")
except paramiko.ssh_exception.AuthenticationException as e:
msg = f'Failed to authenticate: {e}'
msg = f"Failed to authenticate: {e}"
raise AnsibleAuthenticationFailure(msg)
except Exception as e:
msg = to_text(e)
if 'PID check failed' in msg:
raise AnsibleError('paramiko version issue, please upgrade paramiko on the machine running ansible')
elif 'Private key file is encrypted' in msg:
if "PID check failed" in msg:
raise AnsibleError("paramiko version issue, please upgrade paramiko on the machine running ansible")
elif "Private key file is encrypted" in msg:
msg = (
f'ssh {self.get_option("remote_user")}@{self.get_options("remote_addr")}:{port} : '
f'{msg}\nTo connect as a different user, use -u <username>.'
f"ssh {self.get_option('remote_user')}@{self.get_options('remote_addr')}:{port} : "
f"{msg}\nTo connect as a different user, use -u <username>."
)
raise AnsibleConnectionFailure(msg)
else:
@ -534,7 +541,7 @@ class Connection(ConnectionBase):
def _any_keys_added(self) -> bool:
for hostname, keys in self.ssh._host_keys.items(): # type: ignore[attr-defined] # TODO: figure out what _host_keys is!
for keytype, key in keys.items():
added_this_time = getattr(key, '_added_by_ansible_this_time', False)
added_this_time = getattr(key, "_added_by_ansible_this_time", False)
if added_this_time:
return True
return False
@ -548,42 +555,42 @@ class Connection(ConnectionBase):
if not self._any_keys_added():
return
path = os.path.expanduser('~/.ssh')
path = os.path.expanduser("~/.ssh")
makedirs_safe(path)
with open(filename, 'w') as f:
with open(filename, "w") as f:
for hostname, keys in self.ssh._host_keys.items(): # type: ignore[attr-defined] # TODO: figure out what _host_keys is!
for keytype, key in keys.items():
# was f.write
added_this_time = getattr(key, '_added_by_ansible_this_time', False)
added_this_time = getattr(key, "_added_by_ansible_this_time", False)
if not added_this_time:
f.write(f'{hostname} {keytype} {key.get_base64()}\n')
f.write(f"{hostname} {keytype} {key.get_base64()}\n")
for hostname, keys in self.ssh._host_keys.items(): # type: ignore[attr-defined] # TODO: figure out what _host_keys is!
for keytype, key in keys.items():
added_this_time = getattr(key, '_added_by_ansible_this_time', False)
added_this_time = getattr(key, "_added_by_ansible_this_time", False)
if added_this_time:
f.write(f'{hostname} {keytype} {key.get_base64()}\n')
f.write(f"{hostname} {keytype} {key.get_base64()}\n")
def _build_wsl_command(self, cmd: str) -> str:
wsl_distribution = self.get_option('wsl_distribution')
become = self.get_option('become')
become_user = self.get_option('become_user')
wsl_distribution = self.get_option("wsl_distribution")
become = self.get_option("become")
become_user = self.get_option("become_user")
if become and become_user:
wsl_user = become_user
else:
wsl_user = self.get_option('wsl_user')
args = ['wsl.exe', '--distribution', wsl_distribution]
wsl_user = self.get_option("wsl_user")
args = ["wsl.exe", "--distribution", wsl_distribution]
if wsl_user:
args.extend(['--user', wsl_user])
args.extend(['--'])
args.extend(["--user", wsl_user])
args.extend(["--"])
args.extend(shlex.split(cmd))
if os.getenv('_ANSIBLE_TEST_WSL_CONNECTION_PLUGIN_Waeri5tepheeSha2fae8'):
if os.getenv("_ANSIBLE_TEST_WSL_CONNECTION_PLUGIN_Waeri5tepheeSha2fae8"):
return shlex.join(args)
return list2cmdline(args) # see https://github.com/python/cpython/blob/3.11/Lib/subprocess.py#L576
return list2cmdline(args) # see https://github.com/python/cpython/blob/3.11/Lib/subprocess.py#L576
def exec_command(self, cmd: str, in_data: bytes | None = None, sudoable: bool = True) -> tuple[int, bytes, bytes]:
""" run a command on inside a WSL distribution """
"""run a command on inside a WSL distribution"""
cmd = self._build_wsl_command(cmd)
@ -599,18 +606,18 @@ class Connection(ConnectionBase):
chan = transport.open_session()
except Exception as e:
text_e = to_text(e)
msg = 'Failed to open session'
msg = "Failed to open session"
if text_e:
msg += f': {text_e}'
msg += f": {text_e}"
raise AnsibleConnectionFailure(to_native(msg))
display.vvv(f'EXEC {cmd}', host=self.get_option('remote_addr'))
display.vvv(f"EXEC {cmd}", host=self.get_option("remote_addr"))
cmd = to_bytes(cmd, errors='surrogate_or_strict')
cmd = to_bytes(cmd, errors="surrogate_or_strict")
no_prompt_out = b''
no_prompt_err = b''
become_output = b''
no_prompt_out = b""
no_prompt_err = b""
become_output = b""
try:
chan.exec_command(cmd)
@ -618,14 +625,14 @@ class Connection(ConnectionBase):
password_prompt = False
become_success = False
while not (become_success or password_prompt):
display.debug('Waiting for Privilege Escalation input')
display.debug("Waiting for Privilege Escalation input")
chunk = chan.recv(bufsize)
display.debug(f'chunk is: {to_text(chunk)}')
display.debug(f"chunk is: {to_text(chunk)}")
if not chunk:
if b'unknown user' in become_output:
n_become_user = to_native(self.become.get_option('become_user'))
raise AnsibleError(f'user {n_become_user} does not exist')
if b"unknown user" in become_output:
n_become_user = to_native(self.become.get_option("become_user"))
raise AnsibleError(f"user {n_become_user} does not exist")
else:
break
# raise AnsibleError('ssh connection closed waiting for password prompt')
@ -643,80 +650,78 @@ class Connection(ConnectionBase):
if password_prompt:
if self.become:
become_pass = self.become.get_option('become_pass')
chan.sendall(to_bytes(f"{become_pass}\n", errors='surrogate_or_strict'))
become_pass = self.become.get_option("become_pass")
chan.sendall(to_bytes(f"{become_pass}\n", errors="surrogate_or_strict"))
else:
raise AnsibleError('A password is required but none was supplied')
raise AnsibleError("A password is required but none was supplied")
else:
no_prompt_out += become_output
no_prompt_err += become_output
if in_data:
for i in range(0, len(in_data), bufsize):
chan.send(in_data[i:i + bufsize])
chan.send(in_data[i : i + bufsize])
chan.shutdown_write()
elif in_data == b'':
elif in_data == b"":
chan.shutdown_write()
except socket.timeout:
raise AnsibleError(f'ssh timed out waiting for privilege escalation.\n{to_text(become_output)}')
raise AnsibleError(f"ssh timed out waiting for privilege escalation.\n{to_text(become_output)}")
stdout = b''.join(chan.makefile('rb', bufsize))
stderr = b''.join(chan.makefile_stderr('rb', bufsize))
stdout = b"".join(chan.makefile("rb", bufsize))
stderr = b"".join(chan.makefile_stderr("rb", bufsize))
returncode = chan.recv_exit_status()
# NB the full english error message is:
# 'wsl.exe' is not recognized as an internal or external command,
# operable program or batch file.
if "'wsl.exe' is not recognized" in stderr.decode('utf-8'):
raise AnsibleError(
f'wsl.exe not found in path of host: {to_text(self.get_option("remote_addr"))}')
if "'wsl.exe' is not recognized" in stderr.decode("utf-8"):
raise AnsibleError(f"wsl.exe not found in path of host: {to_text(self.get_option('remote_addr'))}")
return (returncode, no_prompt_out + stdout, no_prompt_out + stderr)
def put_file(self, in_path: str, out_path: str) -> None:
""" transfer a file from local to remote """
"""transfer a file from local to remote"""
display.vvv(f'PUT {in_path} TO {out_path}', host=self.get_option('remote_addr'))
display.vvv(f"PUT {in_path} TO {out_path}", host=self.get_option("remote_addr"))
try:
with open(in_path, 'rb') as f:
with open(in_path, "rb") as f:
data = f.read()
returncode, stdout, stderr = self.exec_command(
f"{self._shell.executable} -c {self._shell.quote(f'cat > {out_path}')}",
in_data=data,
sudoable=False)
sudoable=False,
)
if returncode != 0:
if 'cat: not found' in stderr.decode('utf-8'):
if "cat: not found" in stderr.decode("utf-8"):
raise AnsibleError(
f'cat not found in path of WSL distribution: {to_text(self.get_option("wsl_distribution"))}')
raise AnsibleError(
f'{to_text(stdout)}\n{to_text(stderr)}')
f"cat not found in path of WSL distribution: {to_text(self.get_option('wsl_distribution'))}"
)
raise AnsibleError(f"{to_text(stdout)}\n{to_text(stderr)}")
except Exception as e:
raise AnsibleError(
f'error occurred while putting file from {in_path} to {out_path}!\n{to_text(e)}')
raise AnsibleError(f"error occurred while putting file from {in_path} to {out_path}!\n{to_text(e)}")
def fetch_file(self, in_path: str, out_path: str) -> None:
""" save a remote file to the specified path """
"""save a remote file to the specified path"""
display.vvv(f'FETCH {in_path} TO {out_path}', host=self.get_option('remote_addr'))
display.vvv(f"FETCH {in_path} TO {out_path}", host=self.get_option("remote_addr"))
try:
returncode, stdout, stderr = self.exec_command(
f"{self._shell.executable} -c {self._shell.quote(f'cat {in_path}')}",
sudoable=False)
f"{self._shell.executable} -c {self._shell.quote(f'cat {in_path}')}", sudoable=False
)
if returncode != 0:
if 'cat: not found' in stderr.decode('utf-8'):
if "cat: not found" in stderr.decode("utf-8"):
raise AnsibleError(
f'cat not found in path of WSL distribution: {to_text(self.get_option("wsl_distribution"))}')
raise AnsibleError(
f'{to_text(stdout)}\n{to_text(stderr)}')
with open(out_path, 'wb') as f:
f"cat not found in path of WSL distribution: {to_text(self.get_option('wsl_distribution'))}"
)
raise AnsibleError(f"{to_text(stdout)}\n{to_text(stderr)}")
with open(out_path, "wb") as f:
f.write(stdout)
except Exception as e:
raise AnsibleError(
f'error occurred while fetching file from {in_path} to {out_path}!\n{to_text(e)}')
raise AnsibleError(f"error occurred while fetching file from {in_path} to {out_path}!\n{to_text(e)}")
def reset(self) -> None:
""" reset the connection """
"""reset the connection"""
if not self._connected:
return
@ -724,9 +729,9 @@ class Connection(ConnectionBase):
self._connect()
def close(self) -> None:
""" terminate the connection """
"""terminate the connection"""
if self.get_option('host_key_checking') and self.get_option('record_host_keys') and self._any_keys_added():
if self.get_option("host_key_checking") and self.get_option("record_host_keys") and self._any_keys_added():
# add any new SSH host keys -- warning -- this could be slow
# (This doesn't acquire the connection lock because it needs
# to exclude only other known_hosts writers, not connections
@ -736,7 +741,7 @@ class Connection(ConnectionBase):
makedirs_safe(dirname)
tmp_keyfile_name = None
try:
with FileLock().lock_file(lockfile, dirname, self.get_option('lock_file_timeout')):
with FileLock().lock_file(lockfile, dirname, self.get_option("lock_file_timeout")):
# just in case any were added recently
self.ssh.load_system_host_keys()
@ -769,14 +774,14 @@ class Connection(ConnectionBase):
os.rename(tmp_keyfile_name, self.keyfile)
except LockTimeout:
raise AnsibleError(
f'writing lock file for {self.keyfile} ran in to the timeout of {self.get_option("lock_file_timeout")}s')
f"writing lock file for {self.keyfile} ran in to the timeout of {self.get_option('lock_file_timeout')}s"
)
except paramiko.hostkeys.InvalidHostKey as e:
raise AnsibleConnectionFailure(f'Invalid host key: {e.line}')
raise AnsibleConnectionFailure(f"Invalid host key: {e.line}")
except Exception as e:
# unable to save keys, including scenario when key was invalid
# and caught earlier
raise AnsibleError(
f'error occurred while writing SSH host keys!\n{to_text(e)}')
raise AnsibleError(f"error occurred while writing SSH host keys!\n{to_text(e)}")
finally:
if tmp_keyfile_name is not None:
pathlib.Path(tmp_keyfile_name).unlink(missing_ok=True)

View file

@ -42,9 +42,9 @@ display = Display()
class Connection(ConnectionBase):
""" Local zone based connections """
"""Local zone based connections"""
transport = 'community.general.zone'
transport = "community.general.zone"
has_pipelining = True
has_tty = False
@ -56,8 +56,8 @@ class Connection(ConnectionBase):
if os.geteuid() != 0:
raise AnsibleError("zone connection requires running as root")
self.zoneadm_cmd = to_bytes(self._search_executable('zoneadm'))
self.zlogin_cmd = to_bytes(self._search_executable('zlogin'))
self.zoneadm_cmd = to_bytes(self._search_executable("zoneadm"))
self.zlogin_cmd = to_bytes(self._search_executable("zlogin"))
if self.zone not in self.list_zones():
raise AnsibleError(f"incorrect zone name {self.zone}")
@ -70,15 +70,15 @@ class Connection(ConnectionBase):
raise AnsibleError(f"{executable} command not found in PATH")
def list_zones(self):
process = subprocess.Popen([self.zoneadm_cmd, 'list', '-ip'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
process = subprocess.Popen(
[self.zoneadm_cmd, "list", "-ip"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
zones = []
for line in process.stdout.readlines():
# 1:work:running:/zones/work:3126dc59-9a07-4829-cde9-a816e4c5040e:native:shared
s = line.split(':')
if s[1] != 'global':
s = line.split(":")
if s[1] != "global":
zones.append(s[1])
return zones
@ -86,23 +86,26 @@ class Connection(ConnectionBase):
def get_zone_path(self):
# solaris10vm# zoneadm -z cswbuild list -p
# -:cswbuild:installed:/zones/cswbuild:479f3c4b-d0c6-e97b-cd04-fd58f2c0238e:native:shared
process = subprocess.Popen([self.zoneadm_cmd, '-z', to_bytes(self.zone), 'list', '-p'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
process = subprocess.Popen(
[self.zoneadm_cmd, "-z", to_bytes(self.zone), "list", "-p"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
# stdout, stderr = p.communicate()
path = process.stdout.readlines()[0].split(':')[3]
path = process.stdout.readlines()[0].split(":")[3]
return f"{path}/root"
def _connect(self):
""" connect to the zone; nothing to do here """
"""connect to the zone; nothing to do here"""
super()._connect()
if not self._connected:
display.vvv("THIS IS A LOCAL ZONE DIR", host=self.zone)
self._connected = True
def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE):
""" run a command on the zone. This is only needed for implementing
"""run a command on the zone. This is only needed for implementing
put_file() get_file() so that we don't have to read the whole file
into memory.
@ -116,13 +119,12 @@ class Connection(ConnectionBase):
local_cmd = map(to_bytes, local_cmd)
display.vvv(f"EXEC {local_cmd}", host=self.zone)
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return p
def exec_command(self, cmd, in_data=None, sudoable=False):
""" run a command on the zone """
"""run a command on the zone"""
super().exec_command(cmd, in_data=in_data, sudoable=sudoable)
p = self._buffered_exec_command(cmd)
@ -131,33 +133,33 @@ class Connection(ConnectionBase):
return p.returncode, stdout, stderr
def _prefix_login_path(self, remote_path):
""" Make sure that we put files into a standard path
"""Make sure that we put files into a standard path
If a path is relative, then we need to choose where to put it.
ssh chooses $HOME but we aren't guaranteed that a home dir will
exist in any given chroot. So for now we're choosing "/" instead.
This also happens to be the former default.
If a path is relative, then we need to choose where to put it.
ssh chooses $HOME but we aren't guaranteed that a home dir will
exist in any given chroot. So for now we're choosing "/" instead.
This also happens to be the former default.
Can revisit using $HOME instead if it is a problem
Can revisit using $HOME instead if it is a problem
"""
if not remote_path.startswith(os.path.sep):
remote_path = os.path.join(os.path.sep, remote_path)
return os.path.normpath(remote_path)
def put_file(self, in_path, out_path):
""" transfer a file from local to zone """
"""transfer a file from local to zone"""
super().put_file(in_path, out_path)
display.vvv(f"PUT {in_path} TO {out_path}", host=self.zone)
out_path = shlex_quote(self._prefix_login_path(out_path))
try:
with open(in_path, 'rb') as in_file:
with open(in_path, "rb") as in_file:
if not os.fstat(in_file.fileno()).st_size:
count = ' count=0'
count = " count=0"
else:
count = ''
count = ""
try:
p = self._buffered_exec_command(f'dd of={out_path} bs={BUFSIZE}{count}', stdin=in_file)
p = self._buffered_exec_command(f"dd of={out_path} bs={BUFSIZE}{count}", stdin=in_file)
except OSError:
raise AnsibleError("jail connection requires dd command in the jail")
try:
@ -171,17 +173,17 @@ class Connection(ConnectionBase):
raise AnsibleError(f"file or module does not exist at: {in_path}")
def fetch_file(self, in_path, out_path):
""" fetch a file from zone to local """
"""fetch a file from zone to local"""
super().fetch_file(in_path, out_path)
display.vvv(f"FETCH {in_path} TO {out_path}", host=self.zone)
in_path = shlex_quote(self._prefix_login_path(in_path))
try:
p = self._buffered_exec_command(f'dd if={in_path} bs={BUFSIZE}')
p = self._buffered_exec_command(f"dd if={in_path} bs={BUFSIZE}")
except OSError:
raise AnsibleError("zone connection requires dd command in the zone")
with open(out_path, 'wb+') as out_file:
with open(out_path, "wb+") as out_file:
try:
chunk = p.stdout.read(BUFSIZE)
while chunk:
@ -195,6 +197,6 @@ class Connection(ConnectionBase):
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}:\n{stdout}\n{stderr}")
def close(self):
""" terminate the connection; nothing to do here """
"""terminate the connection; nothing to do here"""
super().close()
self._connected = False