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:
parent
3f2213791a
commit
340ff8586d
1008 changed files with 61301 additions and 58309 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue