diff --git a/src/main.py b/src/main.py index bcbf600..d693ed3 100644 --- a/src/main.py +++ b/src/main.py @@ -13,39 +13,37 @@ from getpass import getpass from pathlib import Path from typing import Callable +import netmiko from cryptography.fernet import Fernet, InvalidToken from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -from easydict import EasyDict as edict from loguru import logger from netmiko import ConnectHandler from pyVim.connect import SmartConnect, Disconnect from pyVmomi import vim from ruamel.yaml import YAML -try: - from pyVmomi.vim import ServiceInstanceContent, ServiceInstance -except ImportError: - pass - -chars = string.ascii_letters + string.digits + string.punctuation +chars = string.ascii_letters + string.digits yaml = YAML() workdir = Path.home() / ".vmker" -os.makedirs(workdir / "logs", exist_ok=True) +logdir = workdir / "logs" +os.makedirs(logdir / "by-new_name", exist_ok=True) logger.remove() logger.add(sys.stdout, level="INFO", backtrace=False, diagnose=False, format="\r{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {message}") -logger.add(workdir / "logs" / f"debug-{datetime.now().strftime('%d.%m.%Y_%H-%M.%S')}.log", level=0) +logger.add(logdir / f"debug-{datetime.now().strftime('%d.%m.%Y_%H-%M.%S')}.log", level=0) logger.debug("Logger initialized") class VMKer: def __init__(self): - self.si: ServiceInstance = None - self.content: ServiceInstanceContent = None + self.si = None + self.content = None self.config = None + self.vm = set() + self.template = None @staticmethod def generate_key_from_password(password: str, salt: bytes) -> bytes: @@ -104,12 +102,12 @@ class VMKer: def _connect(self): try: - cfg = edict(self.config) - self.si = SmartConnect(host=cfg.server, user=cfg.user, pwd=self._decrypt_pass(), - disableSslCertValidation=cfg.insecure) + cfg = self.config + self.si = SmartConnect(host=cfg['server'], user=cfg['user'], pwd=self._decrypt_pass(), + disableSslCertValidation=cfg['insecure']) atexit.register(Disconnect, self.si) self.content = self.si.RetrieveContent() - logger.success(f"Connected to {self.content.about.fullName}({cfg.server}) as {cfg.user}") + logger.success(f"Connected to {self.content.about.fullName}({cfg['server']}) as {cfg['user']}") return True except vim.fault.VimFault as e: # noinspection PyUnresolvedReferences @@ -240,20 +238,6 @@ class VMKer: print("Unknown command.") logger.info("Exited from configuration mode.") - - def _search_vm(self, _template_name, _new_name, datacenter): - logger.debug("Checking VMs.") - vm = None - new = None - container_view = self.content.viewManager.CreateContainerView(datacenter.vmFolder, [vim.VirtualMachine], True) - for _vm in container_view.view: - if _vm.name == _template_name: - vm = _vm - if _vm.name == _new_name: - new = _vm - container_view.Destroy() - return vm, new - @staticmethod def _get_vm_ip(vm): ipv4 = [] @@ -267,11 +251,80 @@ class VMKer: ipv6.append(ip.ipAddress) return ipv4, ipv6 - def _parse_yml(self): + def _parse_yml(self, ssh): pass + def _clone(self, new_name, destfolder, clone_spec): + logger.info(f"Cloning: {self.template.name!r} > {new_name!r}") + task = self.template.Clone(name=new_name, folder=destfolder, spec=clone_spec) + while task.info.state == vim.TaskInfo.State.running: + time.sleep(1) + if task.info.state != vim.TaskInfo.State.success: + logger.error(f"Error while cloning") + logger.exception(task.info.error) + logger.success("Cloned") + logger.info("Waiting IP VirtualMachine..") + vm = task.info.result + + t = 0 + while True: + ipv4, ipv6 = self._get_vm_ip(vm) + if len(ipv4) > 0: + break + time.sleep(1) + t += 1 + if t > 120: + return logger.error("No IPv4 address found.") + + logger.info(f"IPv4: {', '.join(ipv4)}") + logger.info(f"IPv6: {', '.join(ipv6)}") + + try: + # TODO: Default user + # TODO: key file + ssh = ConnectHandler( + device_type='linux', + host=ipv4[0], + username="root", + use_keys=True, + key_file="rsa" + ) + except netmiko.exceptions.NetmikoAuthenticationException as e: + return logger.error(f"SSH Error: Authentication. ({e})") + # TODO: length + _pwd = ''.join(random.choice(chars) for _ in range(12)) + ssh.send_command(f'echo -e "{_pwd}\n{_pwd}" | passwd root') + logger.info(f"Password for root: {_pwd}") + ssh.send_command(f'echo {new_name} > /etc/hostname') + + # TODO: yml + logger.info("Executing %.yml") + + ssh.disconnect() + + def _cache_vms(self, template_name, datacenter): + logger.debug("Checking VMs.") + self.vm = set() + self.template = None + container_view = self.content.viewManager.CreateContainerView(datacenter.vmFolder, [vim.VirtualMachine], True) + for _vm in container_view.view: + if _vm.name == template_name: + self.template = _vm + self.vm.add(_vm.name) + container_view.Destroy() + def _create(self): if len(sys.argv) > 3: + new_name = sys.argv[3] + template_name = f"{sys.argv[2]}-template" + _logfile = logdir / "by-new_vm_name" / f"{new_name}.log" + i = 0 + while True: + i += 1 + if not _logfile.exists(): + break + _logfile = logdir / "by-new_vm_name" / f"{new_name} ({i}).log" + logger.add(_logfile, level=0) self._load_config() if not self._connect(): return @@ -282,13 +335,13 @@ class VMKer: return logger.error(f"Datacenter not found: {self.config['datacenter']}") datacenter = datacenter[0] logger.debug("datacenter found") + self._cache_vms(template_name, datacenter) - template_name = f"{sys.argv[2]}-template" - new_name = sys.argv[3] - template, _new = self._search_vm(template_name, new_name, datacenter) - if _new: - return logger.error(f"Error: {new_name!r} already exist.") - if not template: + new_names = [f'{new_name}-{i+1}' for i in range(1)] + for n in new_names: + if n in self.vm: + return logger.error(f"Error: {n!r} already exist.") + if not template_name not in self.vm: return logger.error(f"Template not found: {template_name}") logger.debug("template found") @@ -310,7 +363,6 @@ class VMKer: datastore = datastore[0] logger.debug("datastore found") - logger.info(f"Cloning: {template_name!r} > {new_name!r}") destfolder = datacenter.vmFolder relocate_spec = vim.vm.RelocateSpec() relocate_spec.datastore = datastore @@ -318,47 +370,8 @@ class VMKer: clone_spec = vim.vm.CloneSpec() clone_spec.location = relocate_spec clone_spec.powerOn = True - task = template.Clone(name=new_name, folder=destfolder, spec=clone_spec) - while task.info.state == vim.TaskInfo.State.running: - time.sleep(1) - if task.info.state != vim.TaskInfo.State.success: - return logger.error(f"Error while cloning: {task.info.error}") - logger.success("Cloned") - - vm = task.info.result - logger.info("Waiting to PowerOn..") - - t = 0 - while True: - ipv4, ipv6 = self._get_vm_ip(vm) - if len(ipv4) > 0: - break - time.sleep(1) - t += 1 - if t > 120: - return logger.error("No IPv4 address found.") - - logger.info(f"IPv4: {', '.join(ipv4)}") - logger.info(f"IPv6: {', '.join(ipv6)}") - - # TODO: length - _pwd1 = ''.join(random.choice(chars) for _ in range(12)) - - # TODO: Default user - ssh = ConnectHandler( - device_type='linux', - host=ipv4[0], - username="root", - password="toor", - ) - ssh.send_command(f'echo -e "{_pwd1}\n{_pwd1}" | passwd root') - logger.info(f"Password for root: {_pwd1}") - - # TODO: yml - logger.info("Executing %.yml") - - ssh.disconnect() - + for n in new_names: + self._clone(n, destfolder, clone_spec) else: logger.info( f"Usage: {sys.argv[0]} create [commands.yml | commands1.yml commands2.yml | ...]")