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 | ...]")