[+] args
[+] MultiClone
This commit is contained in:
1 parent
357e7cf61e
commit
d8ca44a285
1 file changed
+62
-34
+62
-34
@@ -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()
|
||||||
Reference in new issue
Block a user