You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PurpleDome/plugins/default/vm_controller/vagrant/vagrant_plugin.py

206 lines
6.1 KiB
Python

#!/usr/bin/env python3
# A plugin to control vagrant machines
from plugins.base.machinery import MachineryPlugin, MachineStates
import subprocess
import vagrant
from fabric import Connection
import os
from app.exceptions import ConfigurationError
from invoke.exceptions import UnexpectedExit
# Experiment with paramiko instead of fabric. Seems fabric has some issues with the "put" command to Windows. There seems no fix (just my workarounds). Maybe paramiko is better.
class VagrantPlugin(MachineryPlugin):
# Boilerplate
name = "vagrant"
description = "A plugin for vagrant machines"
required_files = [] # Files shipped with the plugin which are needed by the machine. Will be copied to the share
def __init__(self):
super().__init__()
self.plugin_path = __file__
self.v = None
self.c = None
self.vagrantfilepath = None
self.vagrantfile = None
def process_config(self, config):
""" Machine specific processing of configuration """
self.vagrantfilepath = os.path.abspath(self.config.vagrantfilepath())
self.vagrantfile = os.path.join(self.vagrantfilepath, "Vagrantfile")
if not os.path.isfile(self.vagrantfile):
raise ConfigurationError(f"Vagrantfile not existing: {self.vagrantfile}")
self.v = vagrant.Vagrant(root=self.vagrantfilepath)
def create(self, reboot=True):
""" Create a machine
@param reboot: Reboot the VM during installation. Required if you want to install software
"""
self.up()
if reboot:
self.halt()
self.up()
def up(self):
""" Start a machine, create it if it does not exist """
try:
self.v.up(vm_name=self.config.vmname())
except subprocess.CalledProcessError as e:
if self.v.status(vm_name=self.config.vmname())[0].state == self.v.RUNNING:
return # Everything is fine
else:
raise e
def halt(self):
""" Halt a machine """
forceit = self.config.halt_needs_force()
try:
self.v.halt(vm_name=self.config.vmname(), force=forceit)
except subprocess.CalledProcessError:
self.v.halt(vm_name=self.config.vmname(), force=True)
def destroy(self):
""" Destroy a machine """
self.v.destroy(vm_name=self.config.vmname())
def connect(self):
""" Connect to a machine. If there is already a connection we keep it """
if self.c:
return self.c
if self.config.os() == "linux":
uhp = self.v.user_hostname_port(vm_name=self.config.vmname())
print(f"Connecting to {uhp}")
self.c = Connection(uhp, connect_kwargs={"key_filename": self.v.keyfile(vm_name=self.config.vmname())})
print(self.c)
return self.c
if self.config.os() == "windows":
args = {"key_filename": os.path.join(self.sysconf["abs_machinepath_external"], self.config.ssh_keyfile())}
uhp = self.get_ip()
print(uhp)
print(args)
print(self.config.ssh_user())
# breakpoint()
self.c = Connection(uhp, user=self.config.ssh_user(), connect_kwargs=args)
print(self.c)
return self.c
def remote_run(self, cmd, disown=False):
""" Connects to the machine and runs a command there
@param disown: Send the connection into background
"""
if cmd is None:
return ""
self.connect()
cmd = cmd.strip()
print("Vagrant plugin remote run: " + cmd)
print("Disown: " + str(disown))
result = None
try:
result = self.c.run(cmd, disown=disown)
print(result)
except UnexpectedExit:
return "Unexpected Exit"
if result and result.stderr:
print("Debug: Stderr: " + str(result.stderr.strip()))
if result:
return result.stdout.strip()
return ""
def put(self, src, dst):
""" Send a file to a machine
@param src: source dir
@param dst: destination
"""
self.connect()
print(f"{src} -> {dst}")
res = ""
try:
res = self.c.put(src, dst)
except UnexpectedExit:
pass
except FileNotFoundError as e:
print(e)
return res
def get(self, src, dst):
""" Get a file to a machine
@param src: source dir
@param dst: destination
"""
self.connect()
res = ""
try:
res = self.c.get(src, dst)
except UnexpectedExit:
pass
return res
def disconnect(self):
""" Disconnect from a machine """
if self.c:
self.c.close()
self.c = None
def get_state(self):
""" Get detailed state of a machine """
vstate = self.v.status(vm_name=self.config.vmname())[0].state
# mapping vagrant states to PurpleDome states
mapping = {self.v.RUNNING: MachineStates.RUNNING,
self.v.NOT_CREATED: MachineStates.NOT_CREATED,
self.v.POWEROFF: MachineStates.POWEROFF,
self.v.ABORTED: MachineStates.ABORTED,
self.v.SAVED: MachineStates.SAVED,
self.v.STOPPED: MachineStates.STOPPED,
self.v.FROZEN: MachineStates.FROZEN,
self.v.SHUTOFF: MachineStates.SHUTOFF
}
return mapping[vstate]
def get_ip(self):
""" Return the machine ip """
# TODO: Create special code to extract windows IPs
# TODO: Find a smarter way to get the ip
# ips = []
# cmd = "ifconfig"
# res = self.vm_manager.__call_remote_run__(cmd)
# for line in res.split("\n"):
# m = re.match(r".*inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*", line)
# if m:
# print(m.group(1))
# ips.append(m.group(1))
filename = os.path.join(self.sysconf["abs_machinepath_external"], "ip4.txt")
with open(filename, "rt") as fh:
return fh.readline().strip()