[+] MultiClone
This commit is contained in:
SantaSpeen committed 2024-07-18 16:36:25 +03:00
1 parent 357e7cf61e
commit d8ca44a285
1 file changed
+62 -34
+62 -34
View File
@@ -8,6 +8,7 @@ import string
import sys import sys
import textwrap import textwrap
import time import time
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime from datetime import datetime
from getpass import getpass from getpass import getpass
from pathlib import Path from pathlib import Path
@@ -30,9 +31,30 @@ workdir = Path.home() / ".vmker"
logdir = workdir / "logs" logdir = workdir / "logs"
os.makedirs(logdir / "by-new_name", exist_ok=True) os.makedirs(logdir / "by-new_name", exist_ok=True)
logger.remove() logger.remove()
logger.add(sys.stdout, level="INFO", backtrace=False, diagnose=False,
format="\r<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | {message}")
logger.add(logdir / 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)
_argv = sys.argv[1:]
args = {}
i = 0
while i < len(_argv):
arg = _argv[i]
if arg.startswith('-'):
key = arg[1:]
if i + 1 < len(_argv) and not _argv[i + 1].startswith('-'):
if not args.get(key):
args[key] = []
args[key].append(_argv[i + 1])
i += 1
else:
logger.warning(f"Ignored invalid parameter: {key}")
i += 1
if args.get("debug"):
logger.add(sys.stdout, level=0, backtrace=False, diagnose=False,
format="\r<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | {message}")
else:
logger.add(sys.stdout, level="INFO", backtrace=False, diagnose=False,
format="\r<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | {message}")
logger.debug("Logger initialized") logger.debug("Logger initialized")
@@ -94,10 +116,13 @@ class VMKer:
def _load_config(self): def _load_config(self):
cf = workdir / "config.json" cf = workdir / "config.json"
if args.get('c'):
cf = Path().resolve() / args['c'][0]
logger.debug(f"using config file: {cf}")
if not cf.exists(): if not cf.exists():
logger.info(f"Init before using program: {sys.argv[0]} init") logger.info(f"Init before using program: {sys.argv[0]} init")
exit(0) exit(0)
with open(workdir / "config.json", "r") as f: with open(cf, "r") as f:
self.config = json.load(f) self.config = json.load(f)
def _connect(self): def _connect(self):
@@ -255,17 +280,22 @@ class VMKer:
pass pass
def _clone(self, new_name, destfolder, clone_spec): def _clone(self, new_name, destfolder, clone_spec):
logger.info(f"Cloning: {self.template.name!r} > {new_name!r}") logger.info(f"[{new_name}] Cloning from {self.template.name!r}")
task = self.template.Clone(name=new_name, folder=destfolder, spec=clone_spec) task = self.template.Clone(name=new_name, folder=destfolder, spec=clone_spec)
while task.info.state == vim.TaskInfo.State.running: while task.info.state == vim.TaskInfo.State.running:
time.sleep(1) time.sleep(1)
if task.info.state != vim.TaskInfo.State.success: if task.info.state != vim.TaskInfo.State.success:
logger.error(f"Error while cloning") logger.error(f"Error while cloning")
logger.exception(task.info.error) logger.exception(task.info.error)
logger.success("Cloned") logger.success(f"[{new_name}] Cloned")
logger.info("Waiting IP VirtualMachine..") return task.info.result
vm = task.info.result
def _configure(self, vm):
_name = vm.name
user = 'root'
key_file = 'rsa'
logger.info("Waiting VirtualMachine..")
t = 0 t = 0
while True: while True:
ipv4, ipv6 = self._get_vm_ip(vm) ipv4, ipv6 = self._get_vm_ip(vm)
@@ -276,8 +306,8 @@ class VMKer:
if t > 120: if t > 120:
return logger.error("No IPv4 address found.") return logger.error("No IPv4 address found.")
logger.info(f"IPv4: {', '.join(ipv4)}") logger.info(f"[{_name}] IPv4: {', '.join(ipv4)}")
logger.info(f"IPv6: {', '.join(ipv6)}") logger.info(f"[{_name}] IPv6: {', '.join(ipv6)}")
try: try:
# TODO: Default user # TODO: Default user
@@ -285,20 +315,21 @@ class VMKer:
ssh = ConnectHandler( ssh = ConnectHandler(
device_type='linux', device_type='linux',
host=ipv4[0], host=ipv4[0],
username="root", username=user,
use_keys=True, use_keys=True,
key_file="rsa" key_file=key_file
) )
except netmiko.exceptions.NetmikoAuthenticationException as e: except netmiko.exceptions.NetmikoAuthenticationException as e:
return logger.error(f"SSH Error: Authentication. ({e})") return logger.error(f"SSH Error: Authentication. ({e})")
logger.success(f"[SSH] [{_name}] Connected to {ipv4[0]} as {user} ({key_file=})")
# TODO: length # TODO: length
_pwd = ''.join(random.choice(chars) for _ in range(12)) _pwd = ''.join(random.choice(chars) for _ in range(12))
ssh.send_command(f'echo -e "{_pwd}\n{_pwd}" | passwd root') ssh.send_command(f"echo '{user}:{_pwd}' | chpasswd")
logger.info(f"Password for root: {_pwd}") logger.info(f"Password for root: {_pwd}")
ssh.send_command(f'echo {new_name} > /etc/hostname') ssh.send_command(f'echo {_name} > /etc/hostname')
# TODO: yml # TODO: yml
logger.info("Executing %.yml") logger.info(f"[{vm.name}] Executing %.yml")
ssh.disconnect() ssh.disconnect()
@@ -319,13 +350,13 @@ class VMKer:
if len(sys.argv) > 3: if len(sys.argv) > 3:
new_name = sys.argv[3] new_name = sys.argv[3]
template_name = f"{sys.argv[2]}-template" template_name = f"{sys.argv[2]}-template"
_logfile = logdir / "by-new_vm_name" / f"{new_name}.log" _logfile = logdir / "by-new_name" / f"{new_name}.log"
i = 0 i = 0
while True: while True:
i += 1 i += 1
if not _logfile.exists(): if not _logfile.exists():
break break
_logfile = logdir / "by-new_vm_name" / f"{new_name} ({i}).log" _logfile = logdir / "by-new_name" / f"{new_name} ({i}).log"
logger.add(_logfile, level=0) logger.add(_logfile, level=0)
self._load_config() self._load_config()
if not self._connect(): if not self._connect():
@@ -339,7 +370,10 @@ class VMKer:
logger.debug("datacenter found") logger.debug("datacenter found")
self._cache_vms(template_name, datacenter) self._cache_vms(template_name, datacenter)
new_names = [f'{new_name}-{i+1}' for i in range(1)] if args.get("n"):
new_names = [f'{new_name}-{i + 1}' for i in range(int(args['n'][0]))]
else:
new_names = [new_name]
for n in new_names: for n in new_names:
if n in self.vm: if n in self.vm:
return logger.error(f"Error: {n!r} already exist.") return logger.error(f"Error: {n!r} already exist.")
@@ -372,15 +406,23 @@ class VMKer:
clone_spec = vim.vm.CloneSpec() clone_spec = vim.vm.CloneSpec()
clone_spec.location = relocate_spec clone_spec.location = relocate_spec
clone_spec.powerOn = True clone_spec.powerOn = True
for n in new_names:
self._clone(n, destfolder, clone_spec) def _caf(nn):
vm = self._clone(nn, destfolder, clone_spec)
self._configure(vm)
for new_name in new_names:
with ThreadPoolExecutor(max_workers=2) as executor:
executor.submit(_caf, new_name)
else: else:
logger.info( logger.info(
f"Usage: {sys.argv[0]} create <template_name> <new_name> [commands.yml | commands1.yml commands2.yml | ...]") f"Usage: {sys.argv[0]} create template new [-n VMs count] [[-f <file with commands>] ...]")
logger.info("Mark: template search on target host with '-template' suffix (%template_name%-template)") logger.info("Mark: template search on target host with '-template' suffix (%template_name%-template)")
def main(self): def main(self):
logger.debug(f"{sys.argv=}") logger.debug(f"{sys.argv=}")
logger.debug(f"{args=}")
try: try:
if len(sys.argv) > 1: if len(sys.argv) > 1:
match sys.argv[1]: match sys.argv[1]:
@@ -400,19 +442,5 @@ class VMKer:
logger.info("Exited by KeyboardInterrupt") logger.info("Exited by KeyboardInterrupt")
def generate_password(length=12):
chars = string.ascii_letters + string.digits + string.punctuation
return ''.join(random.choice(chars) for _ in range(length))
def get_vm_ip(vm):
for nic in vm.guest.net:
if nic.ipConfig:
for ip in nic.ipConfig.ipAddress:
if ":" not in ip.ipAddress: # Skip IPv6 addresses
return ip.ipAddress
return None
if __name__ == "__main__": if __name__ == "__main__":
VMKer().main() VMKer().main()