community.general/plugins/connection/lxd.py

252 lines
8.3 KiB
Python
Raw Normal View History

2021-08-07 13:02:21 +00:00
# -*- coding: utf-8 -*-
# Copyright (c) 2016 Matt Clay <matt@mystile.com>
# Copyright (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
2020-03-09 09:11:07 +00:00
from __future__ import annotations
2020-03-09 09:11:07 +00:00
DOCUMENTATION = r"""
author: Matt Clay (@mattclay) <matt@mystile.com>
name: lxd
short_description: Run tasks in LXD instances using C(lxc) CLI
description:
- Run commands or put/fetch files to an existing instance using C(lxc) CLI.
options:
remote_addr:
2020-03-09 09:11:07 +00:00
description:
- Instance (container/VM) identifier.
- Since community.general 8.0.0, a FQDN can be provided; in that case, the first component (the part before C(.)) is
used as the instance identifier.
type: string
default: inventory_hostname
vars:
- name: inventory_hostname
- name: ansible_host
- name: ansible_lxd_host
executable:
description:
- Shell to use for execution inside instance.
type: string
default: /bin/sh
vars:
- name: ansible_executable
- name: ansible_lxd_executable
lxd_connection: Allow non-root users to connect to an instance (#9659) * fix: add support for non-root user * fix: show correct info for connection * fix: use build_exec_command to execute as nonroot * unset default user * feat: add options for setting remote user and become method * fix: add root as default remote_user * fix: remove ansible_ssh_user from remote_user vars * fix: use single quotes inside f-string * fix: ensure lxc exec comes first * fix: line length * fix: use -c flag with su * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * doc: add changelog fragment * fix: use underscore for module name in fragment * Update 9659-lxd_connection-nonroot-user.yml Co-authored-by: Felix Fontein <felix@fontein.de> * fix: add put command * feat: add get_remote_uid_gid placeholder function * feat: complete placeholder _get_remote_uid_gid function * fix: better logging * fix: ensure default values are of type str * fix: use ints for uid and gid * fix: print put command * fix: format * fix: display msg for PUT * fix: add comment about defaults * fix: format * fix: use os module to get uid and gid * Revert "fix: use os module to get uid and gid" This reverts commit bb2ba14b8fde1639f1053fc0e0a52349b22deee3. * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * fix: omit uid, gid args in lxd file push if root --------- Co-authored-by: Felix Fontein <felix@fontein.de>
2025-02-15 12:00:14 +00:00
lxd_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: lxd_become_method
version_added: 10.4.0
remote:
description:
- Name of the LXD remote to use.
type: string
default: local
vars:
- name: ansible_lxd_remote
version_added: 2.0.0
lxd_connection: Allow non-root users to connect to an instance (#9659) * fix: add support for non-root user * fix: show correct info for connection * fix: use build_exec_command to execute as nonroot * unset default user * feat: add options for setting remote user and become method * fix: add root as default remote_user * fix: remove ansible_ssh_user from remote_user vars * fix: use single quotes inside f-string * fix: ensure lxc exec comes first * fix: line length * fix: use -c flag with su * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * doc: add changelog fragment * fix: use underscore for module name in fragment * Update 9659-lxd_connection-nonroot-user.yml Co-authored-by: Felix Fontein <felix@fontein.de> * fix: add put command * feat: add get_remote_uid_gid placeholder function * feat: complete placeholder _get_remote_uid_gid function * fix: better logging * fix: ensure default values are of type str * fix: use ints for uid and gid * fix: print put command * fix: format * fix: display msg for PUT * fix: add comment about defaults * fix: format * fix: use os module to get uid and gid * Revert "fix: use os module to get uid and gid" This reverts commit bb2ba14b8fde1639f1053fc0e0a52349b22deee3. * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * fix: omit uid, gid args in lxd file push if root --------- Co-authored-by: Felix Fontein <felix@fontein.de>
2025-02-15 12:00:14 +00:00
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:
description:
- Name of the LXD project to use.
type: string
vars:
- name: ansible_lxd_project
version_added: 2.0.0
"""
2020-03-09 09:11:07 +00:00
import os
from subprocess import Popen, PIPE
from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound
from ansible.module_utils.common.process import get_bin_path
from ansible.module_utils.common.text.converters import to_bytes, to_text
2020-03-09 09:11:07 +00:00
from ansible.plugins.connection import ConnectionBase
class Connection(ConnectionBase):
""" lxd based connections """
transport = 'community.general.lxd'
has_pipelining = True
def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
try:
self._lxc_cmd = get_bin_path("lxc")
except ValueError:
2020-03-09 09:11:07 +00:00
raise AnsibleError("lxc command not found in PATH")
plugins/connection/lxd: convert FQDN to instance name (#7360) * plugins/connection/lxd: convert FQDN to instance name This allows to use FQDNs in the inventory and have the connection driver do the translation when talking to LXD that uses hostnames (no ".") for instance names. Those are either globally unique or unique per network/ project in LXD. ``` all: # Groups and hosts children: lxd_dmz: vars: ansible_lxd_project: dmz hosts: www01.dmz.example.com: www02.dmz.example.com: ``` ``` $ lxc list --project dmz +---------+---------+----------------+------+-----------+-----------+----------+ | NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS | LOCATION | +-------+---------+------------------+------+-----------+-----------+----------+ | www01 | RUNNING | 192.0.2.1 (eth0) | | CONTAINER | 0 | t1 | +-------+---------+------------------+------+-----------+-----------+----------+ | www02 | RUNNING | 192.0.2.2 (eth0) | | CONTAINER | 0 | t3 | +-------+---------+------------------+------+-----------+-----------+----------+ ``` Signed-off-by: Simon Deziel <simon.deziel@canonical.com> * plugins/connection/lxd: VMs/containers are called instances Update error string parsing to support the new format: $ lxc stop c1 -- true $ lxc exec c1 -- true Error: Instance is not running $ lxc exec does-not-exist -- true Error: Instance not found Signed-off-by: Simon Deziel <simon.deziel@canonical.com> * plugins/connection/lxd: add changelog fragment Signed-off-by: Simon Deziel <simon.deziel@canonical.com> --------- Signed-off-by: Simon Deziel <simon.deziel@canonical.com>
2023-10-25 06:47:27 +00:00
def _host(self):
""" translate remote_addr to lxd (short) hostname """
return self.get_option("remote_addr").split(".", 1)[0]
2020-03-09 09:11:07 +00:00
def _connect(self):
"""connect to lxd (nothing to do here) """
super(Connection, self)._connect()
if not self._connected:
lxd_connection: Allow non-root users to connect to an instance (#9659) * fix: add support for non-root user * fix: show correct info for connection * fix: use build_exec_command to execute as nonroot * unset default user * feat: add options for setting remote user and become method * fix: add root as default remote_user * fix: remove ansible_ssh_user from remote_user vars * fix: use single quotes inside f-string * fix: ensure lxc exec comes first * fix: line length * fix: use -c flag with su * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * doc: add changelog fragment * fix: use underscore for module name in fragment * Update 9659-lxd_connection-nonroot-user.yml Co-authored-by: Felix Fontein <felix@fontein.de> * fix: add put command * feat: add get_remote_uid_gid placeholder function * feat: complete placeholder _get_remote_uid_gid function * fix: better logging * fix: ensure default values are of type str * fix: use ints for uid and gid * fix: print put command * fix: format * fix: display msg for PUT * fix: add comment about defaults * fix: format * fix: use os module to get uid and gid * Revert "fix: use os module to get uid and gid" This reverts commit bb2ba14b8fde1639f1053fc0e0a52349b22deee3. * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * fix: omit uid, gid args in lxd file push if root --------- Co-authored-by: Felix Fontein <felix@fontein.de>
2025-02-15 12:00:14 +00:00
self._display.vvv(f"ESTABLISH LXD CONNECTION FOR USER: {self.get_option('remote_user')}", host=self._host())
2020-03-09 09:11:07 +00:00
self._connected = True
lxd_connection: Allow non-root users to connect to an instance (#9659) * fix: add support for non-root user * fix: show correct info for connection * fix: use build_exec_command to execute as nonroot * unset default user * feat: add options for setting remote user and become method * fix: add root as default remote_user * fix: remove ansible_ssh_user from remote_user vars * fix: use single quotes inside f-string * fix: ensure lxc exec comes first * fix: line length * fix: use -c flag with su * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * doc: add changelog fragment * fix: use underscore for module name in fragment * Update 9659-lxd_connection-nonroot-user.yml Co-authored-by: Felix Fontein <felix@fontein.de> * fix: add put command * feat: add get_remote_uid_gid placeholder function * feat: complete placeholder _get_remote_uid_gid function * fix: better logging * fix: ensure default values are of type str * fix: use ints for uid and gid * fix: print put command * fix: format * fix: display msg for PUT * fix: add comment about defaults * fix: format * fix: use os module to get uid and gid * Revert "fix: use os module to get uid and gid" This reverts commit bb2ba14b8fde1639f1053fc0e0a52349b22deee3. * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * fix: omit uid, gid args in lxd file push if root --------- Co-authored-by: Felix Fontein <felix@fontein.de>
2025-02-15 12:00:14 +00:00
def _build_command(self, cmd) -> str:
"""build the command to execute on the lxd host"""
exec_cmd = [self._lxc_cmd]
if self.get_option("project"):
exec_cmd.extend(["--project", self.get_option("project")])
exec_cmd.extend(["exec", f"{self.get_option('remote')}:{self._host()}", "--"])
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 '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("executable"), "-c", cmd])
return exec_cmd
2020-03-09 09:11:07 +00:00
def exec_command(self, cmd, in_data=None, sudoable=True):
""" execute a command on the lxd host """
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
self._display.vvv(f"EXEC {cmd}", host=self._host())
2020-03-09 09:11:07 +00:00
lxd_connection: Allow non-root users to connect to an instance (#9659) * fix: add support for non-root user * fix: show correct info for connection * fix: use build_exec_command to execute as nonroot * unset default user * feat: add options for setting remote user and become method * fix: add root as default remote_user * fix: remove ansible_ssh_user from remote_user vars * fix: use single quotes inside f-string * fix: ensure lxc exec comes first * fix: line length * fix: use -c flag with su * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * doc: add changelog fragment * fix: use underscore for module name in fragment * Update 9659-lxd_connection-nonroot-user.yml Co-authored-by: Felix Fontein <felix@fontein.de> * fix: add put command * feat: add get_remote_uid_gid placeholder function * feat: complete placeholder _get_remote_uid_gid function * fix: better logging * fix: ensure default values are of type str * fix: use ints for uid and gid * fix: print put command * fix: format * fix: display msg for PUT * fix: add comment about defaults * fix: format * fix: use os module to get uid and gid * Revert "fix: use os module to get uid and gid" This reverts commit bb2ba14b8fde1639f1053fc0e0a52349b22deee3. * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * fix: omit uid, gid args in lxd file push if root --------- Co-authored-by: Felix Fontein <felix@fontein.de>
2025-02-15 12:00:14 +00:00
local_cmd = self._build_command(cmd)
self._display.vvvvv(f"EXEC {local_cmd}", host=self._host())
2020-03-09 09:11:07 +00:00
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)
stdout = to_text(stdout)
stderr = to_text(stderr)
self._display.vvvvv(f"EXEC lxc output: {stdout} {stderr}", host=self._host())
plugins/connection/lxd: convert FQDN to instance name (#7360) * plugins/connection/lxd: convert FQDN to instance name This allows to use FQDNs in the inventory and have the connection driver do the translation when talking to LXD that uses hostnames (no ".") for instance names. Those are either globally unique or unique per network/ project in LXD. ``` all: # Groups and hosts children: lxd_dmz: vars: ansible_lxd_project: dmz hosts: www01.dmz.example.com: www02.dmz.example.com: ``` ``` $ lxc list --project dmz +---------+---------+----------------+------+-----------+-----------+----------+ | NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS | LOCATION | +-------+---------+------------------+------+-----------+-----------+----------+ | www01 | RUNNING | 192.0.2.1 (eth0) | | CONTAINER | 0 | t1 | +-------+---------+------------------+------+-----------+-----------+----------+ | www02 | RUNNING | 192.0.2.2 (eth0) | | CONTAINER | 0 | t3 | +-------+---------+------------------+------+-----------+-----------+----------+ ``` Signed-off-by: Simon Deziel <simon.deziel@canonical.com> * plugins/connection/lxd: VMs/containers are called instances Update error string parsing to support the new format: $ lxc stop c1 -- true $ lxc exec c1 -- true Error: Instance is not running $ lxc exec does-not-exist -- true Error: Instance not found Signed-off-by: Simon Deziel <simon.deziel@canonical.com> * plugins/connection/lxd: add changelog fragment Signed-off-by: Simon Deziel <simon.deziel@canonical.com> --------- Signed-off-by: Simon Deziel <simon.deziel@canonical.com>
2023-10-25 06:47:27 +00:00
if "is not running" in stderr:
raise AnsibleConnectionFailure(f"instance not running: {self._host()}")
2020-03-09 09:11:07 +00:00
if stderr.strip() == "Error: Instance not found" or stderr.strip() == "error: not found":
raise AnsibleConnectionFailure(f"instance not found: {self._host()}")
2020-03-09 09:11:07 +00:00
return process.returncode, stdout, stderr
lxd_connection: Allow non-root users to connect to an instance (#9659) * fix: add support for non-root user * fix: show correct info for connection * fix: use build_exec_command to execute as nonroot * unset default user * feat: add options for setting remote user and become method * fix: add root as default remote_user * fix: remove ansible_ssh_user from remote_user vars * fix: use single quotes inside f-string * fix: ensure lxc exec comes first * fix: line length * fix: use -c flag with su * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * doc: add changelog fragment * fix: use underscore for module name in fragment * Update 9659-lxd_connection-nonroot-user.yml Co-authored-by: Felix Fontein <felix@fontein.de> * fix: add put command * feat: add get_remote_uid_gid placeholder function * feat: complete placeholder _get_remote_uid_gid function * fix: better logging * fix: ensure default values are of type str * fix: use ints for uid and gid * fix: print put command * fix: format * fix: display msg for PUT * fix: add comment about defaults * fix: format * fix: use os module to get uid and gid * Revert "fix: use os module to get uid and gid" This reverts commit bb2ba14b8fde1639f1053fc0e0a52349b22deee3. * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * fix: omit uid, gid args in lxd file push if root --------- Co-authored-by: Felix Fontein <felix@fontein.de>
2025-02-15 12:00:14 +00:00
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)
2020-03-09 09:11:07 +00:00
def put_file(self, in_path, out_path):
""" put a file from local to lxd """
super(Connection, self).put_file(in_path, out_path)
self._display.vvv(f"PUT {in_path} TO {out_path}", host=self._host())
2020-03-09 09:11:07 +00:00
if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')):
raise AnsibleFileNotFound(f"input path is not a file: {in_path}")
2020-03-09 09:11:07 +00:00
local_cmd = [self._lxc_cmd]
if self.get_option("project"):
local_cmd.extend(["--project", self.get_option("project")])
lxd_connection: Allow non-root users to connect to an instance (#9659) * fix: add support for non-root user * fix: show correct info for connection * fix: use build_exec_command to execute as nonroot * unset default user * feat: add options for setting remote user and become method * fix: add root as default remote_user * fix: remove ansible_ssh_user from remote_user vars * fix: use single quotes inside f-string * fix: ensure lxc exec comes first * fix: line length * fix: use -c flag with su * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * doc: add changelog fragment * fix: use underscore for module name in fragment * Update 9659-lxd_connection-nonroot-user.yml Co-authored-by: Felix Fontein <felix@fontein.de> * fix: add put command * feat: add get_remote_uid_gid placeholder function * feat: complete placeholder _get_remote_uid_gid function * fix: better logging * fix: ensure default values are of type str * fix: use ints for uid and gid * fix: print put command * fix: format * fix: display msg for PUT * fix: add comment about defaults * fix: format * fix: use os module to get uid and gid * Revert "fix: use os module to get uid and gid" This reverts commit bb2ba14b8fde1639f1053fc0e0a52349b22deee3. * Update plugins/connection/lxd.py Co-authored-by: Felix Fontein <felix@fontein.de> * fix: omit uid, gid args in lxd file push if root --------- Co-authored-by: Felix Fontein <felix@fontein.de>
2025-02-15 12:00:14 +00:00
if self.get_option("remote_user") != "root":
uid, gid = self._get_remote_uid_gid()
local_cmd.extend(
[
"file",
"push",
"--uid",
str(uid),
"--gid",
str(gid),
in_path,
f"{self.get_option('remote')}:{self._host()}/{out_path}",
]
)
else:
local_cmd.extend(
[
"file",
"push",
in_path,
f"{self.get_option('remote')}:{self._host()}/{out_path}",
]
)
self._display.vvvvv(f"PUT {local_cmd}", host=self._host())
2020-03-09 09:11:07 +00:00
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 """
super(Connection, self).fetch_file(in_path, out_path)
self._display.vvv(f"FETCH {in_path} TO {out_path}", host=self._host())
2020-03-09 09:11:07 +00:00
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
])
2020-03-09 09:11:07 +00:00
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) """
super(Connection, self).close()
self._connected = False