1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00
community.general/plugins/connection/lxd.py

169 lines
6 KiB
Python
Raw Normal View History

2021-08-07 15:02:21 +02: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 10:11:07 +01:00
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
author: Matt Clay (@mattclay) <matt@mystile.com>
2021-01-12 07:12:03 +01:00
name: lxd
2020-03-09 10:11:07 +01:00
short_description: Run tasks in lxc containers via lxc CLI
description:
- Run commands or put/fetch files to an existing lxc container using lxc CLI
options:
remote_addr:
description:
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 08:47:27 +02:00
- 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.
2020-03-09 10:11:07 +01:00
default: inventory_hostname
vars:
- name: inventory_hostname
2020-03-09 10:11:07 +01:00
- name: ansible_host
- name: ansible_lxd_host
executable:
description:
- shell to use for execution inside container
default: /bin/sh
vars:
- name: ansible_executable
- name: ansible_lxd_executable
remote:
description:
- Name of the LXD remote to use.
default: local
vars:
- name: ansible_lxd_remote
version_added: 2.0.0
project:
description:
- Name of the LXD project to use.
vars:
- name: ansible_lxd_project
version_added: 2.0.0
2020-03-09 10:11:07 +01: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 10:11:07 +01:00
from ansible.plugins.connection import ConnectionBase
class Connection(ConnectionBase):
""" lxd based connections """
transport = 'community.general.lxd'
has_pipelining = True
default_user = 'root'
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 10:11:07 +01:00
raise AnsibleError("lxc command not found in PATH")
if self._play_context.remote_user is not None and self._play_context.remote_user != 'root':
self._display.warning('lxd does not support remote_user, using container default: root')
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 08:47:27 +02:00
def _host(self):
""" translate remote_addr to lxd (short) hostname """
return self.get_option("remote_addr").split(".", 1)[0]
2020-03-09 10:11:07 +01:00
def _connect(self):
"""connect to lxd (nothing to do here) """
super(Connection, self)._connect()
if not self._connected:
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 08:47:27 +02:00
self._display.vvv(u"ESTABLISH LXD CONNECTION FOR USER: root", host=self._host())
2020-03-09 10:11:07 +01:00
self._connected = True
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)
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 08:47:27 +02:00
self._display.vvv(u"EXEC {0}".format(cmd), host=self._host())
2020-03-09 10:11:07 +01:00
local_cmd = [self._lxc_cmd]
if self.get_option("project"):
local_cmd.extend(["--project", self.get_option("project")])
local_cmd.extend([
"exec",
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 08:47:27 +02:00
"%s:%s" % (self.get_option("remote"), self._host()),
"--",
self.get_option("executable"), "-c", cmd
])
2020-03-09 10:11:07 +01: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)
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 08:47:27 +02:00
if "is not running" in stderr:
raise AnsibleConnectionFailure("instance not running: %s" % self._host())
2020-03-09 10:11:07 +01:00
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 08:47:27 +02:00
if "not found" in stderr:
raise AnsibleConnectionFailure("instance not found: %s" % self._host())
2020-03-09 10:11:07 +01:00
return process.returncode, stdout, stderr
def put_file(self, in_path, out_path):
""" put a file from local to lxd """
super(Connection, self).put_file(in_path, out_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 08:47:27 +02:00
self._display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self._host())
2020-03-09 10:11:07 +01:00
if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')):
raise AnsibleFileNotFound("input path is not a file: %s" % in_path)
local_cmd = [self._lxc_cmd]
if self.get_option("project"):
local_cmd.extend(["--project", self.get_option("project")])
local_cmd.extend([
"file", "push",
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 08:47:27 +02:00
"%s:%s/%s" % (self.get_option("remote"), self._host(), out_path)
])
2020-03-09 10:11:07 +01: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)
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 08:47:27 +02:00
self._display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self._host())
2020-03-09 10:11:07 +01:00
local_cmd = [self._lxc_cmd]
if self.get_option("project"):
local_cmd.extend(["--project", self.get_option("project")])
local_cmd.extend([
"file", "pull",
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 08:47:27 +02:00
"%s:%s/%s" % (self.get_option("remote"), self._host(), in_path),
out_path
])
2020-03-09 10:11:07 +01: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