mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
c7150dd818
* 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>
168 lines
6 KiB
Python
168 lines
6 KiB
Python
# -*- 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
|
|
|
|
from __future__ import (absolute_import, division, print_function)
|
|
__metaclass__ = type
|
|
|
|
DOCUMENTATION = '''
|
|
author: Matt Clay (@mattclay) <matt@mystile.com>
|
|
name: lxd
|
|
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:
|
|
- 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.
|
|
default: inventory_hostname
|
|
vars:
|
|
- name: inventory_hostname
|
|
- 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
|
|
'''
|
|
|
|
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
|
|
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:
|
|
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')
|
|
|
|
def _host(self):
|
|
""" 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) """
|
|
super(Connection, self)._connect()
|
|
|
|
if not self._connected:
|
|
self._display.vvv(u"ESTABLISH LXD CONNECTION FOR USER: root", host=self._host())
|
|
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)
|
|
|
|
self._display.vvv(u"EXEC {0}".format(cmd), host=self._host())
|
|
|
|
local_cmd = [self._lxc_cmd]
|
|
if self.get_option("project"):
|
|
local_cmd.extend(["--project", self.get_option("project")])
|
|
local_cmd.extend([
|
|
"exec",
|
|
"%s:%s" % (self.get_option("remote"), self._host()),
|
|
"--",
|
|
self.get_option("executable"), "-c", 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')
|
|
|
|
process = Popen(local_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
|
stdout, stderr = process.communicate(in_data)
|
|
|
|
stdout = to_text(stdout)
|
|
stderr = to_text(stderr)
|
|
|
|
if "is not running" in stderr:
|
|
raise AnsibleConnectionFailure("instance not running: %s" % self._host())
|
|
|
|
if "not found" in stderr:
|
|
raise AnsibleConnectionFailure("instance not found: %s" % self._host())
|
|
|
|
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)
|
|
|
|
self._display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self._host())
|
|
|
|
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,
|
|
"%s:%s/%s" % (self.get_option("remote"), self._host(), out_path)
|
|
])
|
|
|
|
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(u"FETCH {0} TO {1}".format(in_path, out_path), host=self._host())
|
|
|
|
local_cmd = [self._lxc_cmd]
|
|
if self.get_option("project"):
|
|
local_cmd.extend(["--project", self.get_option("project")])
|
|
local_cmd.extend([
|
|
"file", "pull",
|
|
"%s:%s/%s" % (self.get_option("remote"), self._host(), in_path),
|
|
out_path
|
|
])
|
|
|
|
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
|