[+] args
[+] MultiClone
This commit is contained in:
96
src/main.py
96
src/main.py
@@ -8,6 +8,7 @@ import string
|
||||
import sys
|
||||
import textwrap
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime
|
||||
from getpass import getpass
|
||||
from pathlib import Path
|
||||
@@ -30,9 +31,30 @@ workdir = Path.home() / ".vmker"
|
||||
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<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)
|
||||
|
||||
_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")
|
||||
|
||||
|
||||
@@ -94,10 +116,13 @@ class VMKer:
|
||||
|
||||
def _load_config(self):
|
||||
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():
|
||||
logger.info(f"Init before using program: {sys.argv[0]} init")
|
||||
exit(0)
|
||||
with open(workdir / "config.json", "r") as f:
|
||||
with open(cf, "r") as f:
|
||||
self.config = json.load(f)
|
||||
|
||||
def _connect(self):
|
||||
@@ -255,17 +280,22 @@ class VMKer:
|
||||
pass
|
||||
|
||||
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)
|
||||
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
|
||||
logger.success(f"[{new_name}] Cloned")
|
||||
return task.info.result
|
||||
|
||||
def _configure(self, vm):
|
||||
_name = vm.name
|
||||
user = 'root'
|
||||
key_file = 'rsa'
|
||||
|
||||
logger.info("Waiting VirtualMachine..")
|
||||
t = 0
|
||||
while True:
|
||||
ipv4, ipv6 = self._get_vm_ip(vm)
|
||||
@@ -276,8 +306,8 @@ class VMKer:
|
||||
if t > 120:
|
||||
return logger.error("No IPv4 address found.")
|
||||
|
||||
logger.info(f"IPv4: {', '.join(ipv4)}")
|
||||
logger.info(f"IPv6: {', '.join(ipv6)}")
|
||||
logger.info(f"[{_name}] IPv4: {', '.join(ipv4)}")
|
||||
logger.info(f"[{_name}] IPv6: {', '.join(ipv6)}")
|
||||
|
||||
try:
|
||||
# TODO: Default user
|
||||
@@ -285,20 +315,21 @@ class VMKer:
|
||||
ssh = ConnectHandler(
|
||||
device_type='linux',
|
||||
host=ipv4[0],
|
||||
username="root",
|
||||
username=user,
|
||||
use_keys=True,
|
||||
key_file="rsa"
|
||||
key_file=key_file
|
||||
)
|
||||
except netmiko.exceptions.NetmikoAuthenticationException as e:
|
||||
return logger.error(f"SSH Error: Authentication. ({e})")
|
||||
logger.success(f"[SSH] [{_name}] Connected to {ipv4[0]} as {user} ({key_file=})")
|
||||
# TODO: length
|
||||
_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}")
|
||||
ssh.send_command(f'echo {new_name} > /etc/hostname')
|
||||
ssh.send_command(f'echo {_name} > /etc/hostname')
|
||||
|
||||
# TODO: yml
|
||||
logger.info("Executing %.yml")
|
||||
logger.info(f"[{vm.name}] Executing %.yml")
|
||||
|
||||
ssh.disconnect()
|
||||
|
||||
@@ -319,13 +350,13 @@ class VMKer:
|
||||
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"
|
||||
_logfile = logdir / "by-new_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"
|
||||
_logfile = logdir / "by-new_name" / f"{new_name} ({i}).log"
|
||||
logger.add(_logfile, level=0)
|
||||
self._load_config()
|
||||
if not self._connect():
|
||||
@@ -339,7 +370,10 @@ class VMKer:
|
||||
logger.debug("datacenter found")
|
||||
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:
|
||||
if n in self.vm:
|
||||
return logger.error(f"Error: {n!r} already exist.")
|
||||
@@ -372,15 +406,23 @@ class VMKer:
|
||||
clone_spec = vim.vm.CloneSpec()
|
||||
clone_spec.location = relocate_spec
|
||||
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:
|
||||
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)")
|
||||
|
||||
def main(self):
|
||||
logger.debug(f"{sys.argv=}")
|
||||
logger.debug(f"{args=}")
|
||||
try:
|
||||
if len(sys.argv) > 1:
|
||||
match sys.argv[1]:
|
||||
@@ -400,19 +442,5 @@ class VMKer:
|
||||
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__":
|
||||
VMKer().main()
|
||||
|
||||
Reference in New Issue
Block a user