[PR #9743/94e15110 backport][stable-10] incus_connection: Allow non-root users to connect to an instance (#9765)
incus_connection: Allow non-root users to connect to an instance (#9743)
* feat: add remote_user option to incus connection
* feat: add changelog fragment
* fix: formatting
(cherry picked from commit 94e1511005
)
Co-authored-by: Peter Siegel <33677897+yeetypete@users.noreply.github.com>
pull/9770/head
parent
d811807e1f
commit
35d6ab10bb
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- incus connection plugin - adds ``remote_user`` and ``incus_become_method`` parameters for allowing a non-root user to connect to an Incus instance (https://github.com/ansible-collections/community.general/pull/9743).
|
|
@ -32,6 +32,15 @@ options:
|
||||||
vars:
|
vars:
|
||||||
- name: ansible_executable
|
- name: ansible_executable
|
||||||
- name: ansible_incus_executable
|
- name: ansible_incus_executable
|
||||||
|
incus_become_method:
|
||||||
|
description:
|
||||||
|
- Become command used to switch to a non-root user.
|
||||||
|
- Is only used when O(remote_user) is not V(root).
|
||||||
|
type: str
|
||||||
|
default: /bin/su
|
||||||
|
vars:
|
||||||
|
- name: incus_become_method
|
||||||
|
version_added: 10.4.0
|
||||||
remote:
|
remote:
|
||||||
description:
|
description:
|
||||||
- The name of the Incus remote to use (per C(incus remote list)).
|
- The name of the Incus remote to use (per C(incus remote list)).
|
||||||
|
@ -40,6 +49,22 @@ options:
|
||||||
default: local
|
default: local
|
||||||
vars:
|
vars:
|
||||||
- name: ansible_incus_remote
|
- name: ansible_incus_remote
|
||||||
|
remote_user:
|
||||||
|
description:
|
||||||
|
- User to login/authenticate as.
|
||||||
|
- Can be set from the CLI via the C(--user) or C(-u) options.
|
||||||
|
type: string
|
||||||
|
default: root
|
||||||
|
vars:
|
||||||
|
- name: ansible_user
|
||||||
|
env:
|
||||||
|
- name: ANSIBLE_REMOTE_USER
|
||||||
|
ini:
|
||||||
|
- section: defaults
|
||||||
|
key: remote_user
|
||||||
|
keyword:
|
||||||
|
- name: remote_user
|
||||||
|
version_added: 10.4.0
|
||||||
project:
|
project:
|
||||||
description:
|
description:
|
||||||
- The name of the Incus project to use (per C(incus project list)).
|
- The name of the Incus project to use (per C(incus project list)).
|
||||||
|
@ -64,7 +89,6 @@ class Connection(ConnectionBase):
|
||||||
|
|
||||||
transport = "incus"
|
transport = "incus"
|
||||||
has_pipelining = True
|
has_pipelining = True
|
||||||
default_user = 'root'
|
|
||||||
|
|
||||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||||
|
@ -79,10 +103,34 @@ class Connection(ConnectionBase):
|
||||||
super(Connection, self)._connect()
|
super(Connection, self)._connect()
|
||||||
|
|
||||||
if not self._connected:
|
if not self._connected:
|
||||||
self._display.vvv("ESTABLISH Incus CONNECTION FOR USER: root",
|
self._display.vvv(f"ESTABLISH Incus CONNECTION FOR USER: {self.get_option('remote_user')}",
|
||||||
host=self._instance())
|
host=self._instance())
|
||||||
self._connected = True
|
self._connected = True
|
||||||
|
|
||||||
|
def _build_command(self, cmd) -> str:
|
||||||
|
"""build the command to execute on the incus host"""
|
||||||
|
|
||||||
|
exec_cmd = [
|
||||||
|
self._incus_cmd,
|
||||||
|
"--project", self.get_option("project"),
|
||||||
|
"exec",
|
||||||
|
f"{self.get_option('remote')}:{self._instance()}",
|
||||||
|
"--"]
|
||||||
|
|
||||||
|
if self.get_option("remote_user") != "root":
|
||||||
|
self._display.vvv(
|
||||||
|
f"INFO: Running as non-root user: {self.get_option('remote_user')}, \
|
||||||
|
trying to run 'incus exec' with become method: {self.get_option('incus_become_method')}",
|
||||||
|
host=self._instance(),
|
||||||
|
)
|
||||||
|
exec_cmd.extend(
|
||||||
|
[self.get_option("incus_become_method"), self.get_option("remote_user"), "-c"]
|
||||||
|
)
|
||||||
|
|
||||||
|
exec_cmd.extend([self.get_option("executable"), "-c", cmd])
|
||||||
|
|
||||||
|
return exec_cmd
|
||||||
|
|
||||||
def _instance(self):
|
def _instance(self):
|
||||||
# Return only the leading part of the FQDN as the instance name
|
# Return only the leading part of the FQDN as the instance name
|
||||||
# as Incus instance names cannot be a FQDN.
|
# as Incus instance names cannot be a FQDN.
|
||||||
|
@ -95,13 +143,8 @@ class Connection(ConnectionBase):
|
||||||
self._display.vvv(f"EXEC {cmd}",
|
self._display.vvv(f"EXEC {cmd}",
|
||||||
host=self._instance())
|
host=self._instance())
|
||||||
|
|
||||||
local_cmd = [
|
local_cmd = self._build_command(cmd)
|
||||||
self._incus_cmd,
|
self._display.vvvvv(f"EXEC {local_cmd}", host=self._instance())
|
||||||
"--project", self.get_option("project"),
|
|
||||||
"exec",
|
|
||||||
f"{self.get_option('remote')}:{self._instance()}",
|
|
||||||
"--",
|
|
||||||
self._play_context.executable, "-c", cmd]
|
|
||||||
|
|
||||||
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]
|
||||||
in_data = to_bytes(in_data, errors='surrogate_or_strict', nonstring='passthru')
|
in_data = to_bytes(in_data, errors='surrogate_or_strict', nonstring='passthru')
|
||||||
|
@ -120,6 +163,25 @@ class Connection(ConnectionBase):
|
||||||
|
|
||||||
return process.returncode, stdout, stderr
|
return process.returncode, stdout, stderr
|
||||||
|
|
||||||
|
def _get_remote_uid_gid(self) -> tuple[int, int]:
|
||||||
|
"""Get the user and group ID of 'remote_user' from the instance."""
|
||||||
|
|
||||||
|
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}"
|
||||||
|
)
|
||||||
|
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}"
|
||||||
|
)
|
||||||
|
gid = gid_out.strip()
|
||||||
|
|
||||||
|
return int(uid), int(gid)
|
||||||
|
|
||||||
def put_file(self, in_path, out_path):
|
def put_file(self, in_path, out_path):
|
||||||
""" put a file from local to Incus """
|
""" put a file from local to Incus """
|
||||||
super(Connection, self).put_file(in_path, out_path)
|
super(Connection, self).put_file(in_path, out_path)
|
||||||
|
@ -130,12 +192,35 @@ class Connection(ConnectionBase):
|
||||||
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}")
|
raise AnsibleFileNotFound(f"input path is not a file: {in_path}")
|
||||||
|
|
||||||
|
if self.get_option("remote_user") != "root":
|
||||||
|
uid, gid = self._get_remote_uid_gid()
|
||||||
local_cmd = [
|
local_cmd = [
|
||||||
self._incus_cmd,
|
self._incus_cmd,
|
||||||
"--project", self.get_option("project"),
|
"--project",
|
||||||
"file", "push", "--quiet",
|
self.get_option("project"),
|
||||||
|
"file",
|
||||||
|
"push",
|
||||||
|
"--uid",
|
||||||
|
str(uid),
|
||||||
|
"--gid",
|
||||||
|
str(gid),
|
||||||
|
"--quiet",
|
||||||
in_path,
|
in_path,
|
||||||
f"{self.get_option('remote')}:{self._instance()}/{out_path}"]
|
f"{self.get_option('remote')}:{self._instance()}/{out_path}",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
local_cmd = [
|
||||||
|
self._incus_cmd,
|
||||||
|
"--project",
|
||||||
|
self.get_option("project"),
|
||||||
|
"file",
|
||||||
|
"push",
|
||||||
|
"--quiet",
|
||||||
|
in_path,
|
||||||
|
f"{self.get_option('remote')}:{self._instance()}/{out_path}",
|
||||||
|
]
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue