diff --git a/config_data_import.py b/config_data_import.py index 70f4bf7..f21fce6 100644 --- a/config_data_import.py +++ b/config_data_import.py @@ -16,7 +16,9 @@ def config_menu(base_url, api_key, config_relative_path): \n[gold bold][1] [grey53 italic]Show current configuration\n[/] \ \n[gold bold][2] [grey53 italic]Setup new config file[/]\n \ \n[gold bold][3] [grey53 italic]Change selected data pool[/]\n\ -\n[gold bold][4] [grey53 italic]Change selected VMs[/]\ +\n[gold bold][4] [grey53 italic]Change selected VMs[/]\n\ +\n[gold bold][5] [grey53 italic]Change ISO UUID (auto-mount)[/]\n\ +\n[gold bold][6] [grey53 italic]Skip start-up splash[/]\n\ \n\n[green_yellow bold]ENTER - return to Main Menu[/]" config_menu_options=Align.center(config_menu_options, vertical="middle") console = Console() @@ -33,10 +35,15 @@ def config_menu(base_url, api_key, config_relative_path): change_data_pool(base_url, api_key, config_relative_path) if sub_choice == "4": change_vm_uuids(config_relative_path) + if sub_choice == "5": + change_iso_uuid(config_relative_path) + if sub_choice == "6": + change_startup_option(config_relative_path) def config_show_example(): conf_example= """ [General] +skip_startup_splash = no #Master Controller IP of your cluster #Has to be accessible for a machine, which will be executing this Utility controller_ip = 10.20.30.44 @@ -61,6 +68,20 @@ data_pool_uuid = uuid_1 = uuid_2 = +[VM_Options] +#Select interface which will be used in virtual disk creation. +#Available options: virtio / ide / scsi / sata +disk_interface = virtio + +#Select allocation type for virtual disks +#Available options: none / falloc / full / metadata +preallocation = falloc + +#Specify uuid of iso you wish to automatically mount to Virtual Machines during operations (Courses) +#This step is skipped if "none" provided +iso_uuid = none + + [Courses-Space-VM] #Set vDisk size for "Prepare VMs for Courses™" option disk1 = 10 @@ -74,6 +95,7 @@ disk3 = 20 Prompt.ask("[green_yellow bold]ENTER - return to Utility Configuration.. :right_arrow_curving_down:") def config_show(config_relative_path): + cls() console.rule(title = "Current configuration" , align="center" , style="yellow") with open(config_relative_path, "r") as f: @@ -85,15 +107,38 @@ def config_import(config_relative_path): config = configparser.ConfigParser() config.read(config_relative_path) + skip_startup_splash = config.get('General', 'skip_startup_splash') base_url = config.get('General', 'controller_ip') api_key = "jwt " + config.get('General', 'api_key') #That was realy obvious DACOM >:C data_pool_uuid = config.get('Data_Pool', 'data_pool_uuid') + vm_list = [] if 'VM_List' in config: for key, value in config['VM_List'].items(): vm_list.append(value) + #importing VM_Options + if config.has_section('VM_Options'): + iso_uuid = config.get('VM_Options' , 'iso_uuid') + disk_interface = config.get('VM_Options' , 'disk_interface') + preallocation = config.get('VM_Options' , 'preallocation') + iso_name=get_iso_name(base_url, api_key, iso_uuid) + else: + console.print("[bold yellow]Applying default values to Virtual Machine Options") + iso_uuid = "none" + disk_interface = "virtio" + preallocation = "falloc" + iso_name= "none" + config = configparser.ConfigParser() #writing default values to config + config["VM_Options"] = { + "disk_interface": "virtio", + "preallocation": "falloc", + "iso_uuid": "none", + } + with open(config_relative_path, "a") as configfile: # appending to existing config file + config.write(configfile) + #importing disk sizes for SpaceVM courses if config.has_section('Courses-Space-VM'): disk1_size = config.get('Courses-Space-VM', 'disk1') @@ -117,7 +162,22 @@ def config_import(config_relative_path): vm_names=[] for x in vm_list: vm_names.append(get_vm_name(base_url, api_key, x)) - return base_url, api_key, data_pool_uuid, data_pool_name, vm_list, vm_names, disk1_size, disk2_size, disk3_size + + return skip_startup_splash, base_url, api_key, data_pool_uuid, data_pool_name, vm_list, vm_names, disk1_size, disk2_size, disk3_size, disk_interface, preallocation, iso_uuid, iso_name + +def change_startup_option(config_relative_path): + cls() + console.print("[yellow bold]Skip start-up splash ?") + new_value = Prompt.ask("Type your choice", choices=["yes", "no"], default="no") + config = configparser.ConfigParser() + config.read(config_relative_path) + if config.has_section('General'): + config.set('General', 'skip_startup_splash', new_value) + with open(config_relative_path, 'w') as config_file: + config.write(config_file) + console.print(f"[green bold]Option set to: {new_value}") + else: + console.print("[red bold]No section 'General' in config file") def change_data_pool(base_url, api_key, config_relative_path): #change selected data pool in config cls() @@ -133,6 +193,18 @@ def change_data_pool(base_url, api_key, config_relative_path): #change selected print("No 'Data_Pool' section in config file..") config_show(config_relative_path) +def change_iso_uuid(config_relative_path): + cls() + new_iso_uuid = input("Type ISO UUID: ") + config = configparser.ConfigParser() + config.read(config_relative_path) + if config.has_section('VM_Options'): + config.set('VM_Options', 'iso_uuid', new_iso_uuid) + with open(config_relative_path, 'w') as config_file: + config.write(config_file) + else: + print("No 'VM_Options' section in config file..") + config_show(config_relative_path) def change_vm_uuids(config_relative_path): #change selected VM uuids in config config = configparser.ConfigParser() @@ -177,10 +249,20 @@ def config_edit(config_relative_path): config["General"] = { "controller_ip": base_url, "api_key": api_key, + "skip_startup_splash": "no", } config["Data_Pool"] = {"data_pool_uuid": data_pool_uuid} - with open(config_relative_path, "w") as configfile: + #disk_interface=input("Specify preffered disk interface (virtio / ide / scsi / sata): ") + #preallocation=input("Specify allocation type for virtual disks (none / falloc / full / metadata): ") + #iso_uuid=input("Specify ISO uuid you wish to auto-mount during operations(none - skip this step): ") + #config["VM_Options"] = { + # "disk_interface": disk_interface, + # "preallocation": preallocation, + # "iso_uuid": iso_uuid + #} + + with open(config_relative_path, "w") as configfile: #writing everything from above to config file config.write(configfile) print("Type VM UUIDs one by one (input ENTER to stop)") diff --git a/disk_edit_mode.py b/disk_edit_mode.py index 7a93d50..9174be2 100644 --- a/disk_edit_mode.py +++ b/disk_edit_mode.py @@ -4,7 +4,7 @@ from domain_api import * from rich.prompt import Prompt from rich.console import Console , Align -def disk_edit_mode(base_url , api_key , data_pool_uuid , vm_uuids, disk1_size, disk2_size, disk3_size): +def disk_edit_mode(base_url , api_key , data_pool_uuid , vm_uuids, disk1_size, disk2_size, disk3_size, disk_interface, preallocation, iso_uuid): os.system('cls' if os.name=='nt' else 'clear') diks_edit_menu_options="[gold bold][1] [grey53 italic]Delete vDisk by UUID\n[/grey53 italic] \ \n[gold bold][2] [grey53 italic]Delete ALL vDisks on selected Virtual Machine[/grey53 italic]\n \ @@ -35,7 +35,7 @@ def disk_edit_mode(base_url , api_key , data_pool_uuid , vm_uuids, disk1_size, d print(vm_uuids) select_uuids=int(input("Select VM to attach new disk. \n Type VM uuid index number (from list above) to select: ")) - 1 print(f"{vm_uuids[select_uuids]} - {data_pool_uuid} - {vdisk_size} ") - create_and_attach_disk(base_url , api_key , vm_uuids[select_uuids] , data_pool_uuid , vdisk_size , "falloc") + create_and_attach_disk(base_url , api_key , vm_uuids[select_uuids] , data_pool_uuid , vdisk_size , disk_interface, preallocation) if sub_choice == "4": os.system('cls' if os.name=='nt' else 'clear') @@ -55,13 +55,19 @@ def disk_edit_mode(base_url , api_key , data_pool_uuid , vm_uuids, disk1_size, d for z in vm_uuids: # only for creating disks domain_uuid = z.strip('\n') vm_name = get_vm_name(base_url, api_key, domain_uuid) - console.print(f"\n[bold underline yellow]Creating and attaching disk to[/] [bright_cyan]{vm_name}:") + console.print(f"\n[bold underline yellow]Creating and attaching disks to[/] [bright_cyan]{vm_name}:") domain_info = get_domain_info(base_url , api_key , domain_uuid) - domain_all_content = get_domain_all_content(base_url , api_key , domain_uuid) + #domain_all_content = get_domain_all_content(base_url , api_key , domain_uuid) if domain_info: - create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, disk1_size, "falloc") - create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, disk2_size, "falloc") - create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, disk3_size, "falloc") + #iso_uuid="b95241c1-6134-4263-9da5-013459612eeb" + create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, disk1_size, disk_interface, preallocation) + create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, disk2_size, disk_interface, preallocation) + create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, disk3_size, disk_interface, preallocation) + if iso_uuid == 'none': + console.print("[grey53 italic]iso_uuid was not specified. Skipping ISO auto-mount..[/]") + else: + attach_iso(base_url, api_key, domain_uuid, iso_uuid) + console.print("[bold green]\nDone. Happy virtualization :thumbs_up::thumbs_up:") Prompt.ask("[green_yellow bold]ENTER - return to Main Menu.. :right_arrow_curving_down:") os.system('cls' if os.name=='nt' else 'clear') \ No newline at end of file diff --git a/domain_api.py b/domain_api.py index cca2d3a..8c5423b 100644 --- a/domain_api.py +++ b/domain_api.py @@ -108,7 +108,26 @@ def get_disk_info(domain_all_content): console.print(Columns(disk_info_renderables)) +def get_cdrom_uuid(domain_all_content): + if 'cdroms' in domain_all_content: + cdrom_ids = [item['id'] for item in domain_all_content['cdroms']] + #print("CD-ROM UUIDs:") + for cdrom_id in cdrom_ids: + #print(cdrom_id) + return (cdrom_id) + else: + console.print("[bold yellow]No 'cdroms' field in recieved data. \nProbably VM does not have any CD-ROMs?") + +def get_iso_name(base_url, api_key, iso_uuid): + url = f"http://{base_url}//api/iso/{iso_uuid}/" + response = requests.get(url, headers={'Authorization': api_key}) + if response.status_code == 200: + iso_name = response.json() + return (f"{iso_name['filename']}") + + def get_vm_name(base_url, api_key, vm_uuids): + console = Console() url = f"http://{base_url}//api/domains/{vm_uuids}/" response = requests.get(url, headers={'Authorization': api_key}) if response.status_code == 200: @@ -129,9 +148,10 @@ def vm_info(base_url, api_key, vm_uuids): console.print(vm_info_renderable) console.rule(title = "[bold yellow]vDisks Info" , style="grey53" , align="center") get_disk_info(domain_all_content) + #console.rule(title = "[bold yellow]CD-ROM UUIDs" , style="grey53" , align="center") + #print(get_cdrom_uuid(domain_all_content)) console.rule(style="yellow") - def vm_info_short(base_url, api_key): url = f"http://{base_url}/api/domains/" response = requests.get(url, headers={'Authorization': api_key}) @@ -157,7 +177,7 @@ def vm_info_short(base_url, api_key): os.system('cls' if os.name=='nt' else 'clear') -def create_and_attach_disk(base_url , api_key , vm_id, data_pool_uuid, vdisk_size, preallocation): +def create_and_attach_disk(base_url , api_key , vm_id, data_pool_uuid, vdisk_size, disk_interface, preallocation): domain_name=get_domain_info(base_url , api_key , vm_id) disk_name=domain_name["verbose_name"] + "_" + secrets.token_hex(5) #generates unique hex id. this method can generate ~million unique ids url = f"http://{base_url}/api/domains/{vm_id}/create-attach-vdisk/" @@ -170,7 +190,7 @@ def create_and_attach_disk(base_url , api_key , vm_id, data_pool_uuid, vdisk_siz "preallocation": preallocation, "size": vdisk_size, "datapool": data_pool_uuid, - "target_bus": "virtio", + "target_bus": disk_interface, #"virtio" } with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress: task = progress.add_task("Creating and attaching vDisk...", total=None) @@ -235,6 +255,59 @@ def select_vm_by_tags(base_url, api_key, config_relative_path): Prompt.ask("[green_yellow bold]ENTER - return to Main Menu.... :right_arrow_curving_down:") os.system('cls' if os.name=='nt' else 'clear') return(vm_id_list) + + +def attach_iso(base_url, api_key, vm_id, iso_uuid): + url = f"http://{base_url}/api/domains/{vm_id}/attach-iso/" + domain_all_content = get_domain_all_content(base_url, api_key, vm_id) + cdrom_uuid=get_cdrom_uuid(domain_all_content) + headers={ + "Authorization" : api_key, + "Content-Type" : "application/json", + } + payload= { + "iso": iso_uuid, + "cdrom": cdrom_uuid + } + + with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress: + task = progress.add_task("Attaching selected ISO to VM...", total=None) + response = requests.post(url , headers=headers, json=payload) + progress.remove_task(task) + if response.status_code == 200: + iso_name = get_iso_name(base_url, api_key, iso_uuid) + console.print(f"[grey53 italic]ISO {iso_name} attached[/] :white_check_mark:") + return True + else: + console.print(f"[bold yellow]ERROR {response.status_code} attaching ISO \nProbably VM does not have any CD-ROMs?") + return False + + + + + + + + + + + + + + + + + + + + + + + + + + + def vm_menu(base_url, api_key, vm_uuids, config_relative_path): os.system('cls' if os.name=='nt' else 'clear') diff --git a/main.py b/main.py index b1b6e6c..4354c6f 100644 --- a/main.py +++ b/main.py @@ -7,7 +7,7 @@ from data_pools_api import * from disk_edit_mode import * from rich.panel import Panel from rich.console import Console , Align -SVMU_ver="0.3-dev" +SVMU_ver="0.35-dev" config_relative_path = os.path.join(os.getcwd() , 'SpaceVM_Utility.conf') #config in the same directory with main.py @@ -18,10 +18,11 @@ if not os.path.exists(config_relative_path): menu_choice=0 console = Console() os.system('cls' if os.name=='nt' else 'clear') -show_startup_logo(SVMU_ver) #shows startup splash (ASCII art) +skip_startup_splash = get_skip_startup_splash(config_relative_path) +show_startup_logo(skip_startup_splash, SVMU_ver) #shows startup splash (ASCII art) while(menu_choice != ""): #main menu loop check_config(config_relative_path) - base_url, api_key, data_pool_uuid, data_pool_name, vm_uuids, vm_names, disk1_size, disk2_size, disk3_size = config_import(config_relative_path) #importing API-KEY / IP / DATA POOL UUID / VM-UUIDs from config + skip_startup_splash, base_url, api_key, data_pool_uuid, data_pool_name, vm_uuids, vm_names, disk1_size, disk2_size, disk3_size, disk_interface, preallocation, iso_uuid, iso_name = config_import(config_relative_path) #importing API-KEY / IP / DATA POOL UUID / VM-UUIDs from config menu_options=f"[gold bold][1] [grey53 italic]Manage utility config\n[/grey53 italic] \ \n[gold bold][2] [grey53 italic]Enter disk edit mode[/grey53 italic]\n \ \n[gold bold][3] [grey53 italic]Show breif cluster overview[/grey53 italic]\n \ @@ -29,15 +30,15 @@ while(menu_choice != ""): #main menu loop \n[gold bold][5] [grey53 italic]Show data pools[/grey53 italic]\n \ \n\n[green_yellow bold]ENTER - exit Utility[/]\n\n \ [underline bold grey53]Currently imported config:[/]\n \ -[bold grey53]Connected to Controller: [bright_yellow]{base_url}[/]\n Selected Data Pool: [bright_yellow]{data_pool_name}[/]\n Selected VMs:\n [bright_yellow]{vm_names}" +[bold grey53]Connected to Controller: [bright_yellow]{base_url}[/]\n Selected Data Pool: [bright_yellow]{data_pool_name}[/]\n Selected VMs:\n [bright_yellow]{vm_names}[/]\n Auto-mount ISO: [bright_cyan]{iso_name}[/]" menu_options=Align.center(menu_options, vertical="middle") - menu_subtitle = "[blue bold][link=https://github.com/OVERLORD7F/SpaceVM_VM_Utility]:wrench: Project_GitHub[/link] [yellow]| [magenta bold][link=https://spacevm.ru/docs/]:books: SpaceVM_Docs[/link] [yellow]| [red bold][link=https://comptek.ru]:briefcase: Comptek[/link]" + menu_subtitle = "[blue bold][link=https://github.com/OVERLORD7F/SVMU]:wrench: Project_GitHub[/link] [yellow]| [magenta bold][link=https://spacevm.ru/docs/]:books: SpaceVM_Docs[/link] [yellow]| [red bold][link=https://comptek.ru]:briefcase: Comptek[/link]" console.print(Panel(menu_options, title=f"[bold magenta]SpaceVM Utility {SVMU_ver} - Main Menu" , subtitle = menu_subtitle, subtitle_align="right" , style="yellow" , width=150 , padding = 2)) menu_choice=str(input("\n>>> ")) if menu_choice == "1": config_menu(base_url, api_key, config_relative_path) if menu_choice == "2": - disk_edit_mode(base_url , api_key , data_pool_uuid , vm_uuids, disk1_size, disk2_size, disk3_size) + disk_edit_mode(base_url , api_key , data_pool_uuid , vm_uuids, disk1_size, disk2_size, disk3_size, disk_interface, preallocation, iso_uuid) if menu_choice == "3": cluster_info(base_url , api_key) if menu_choice == "4": diff --git a/splash_screen.py b/splash_screen.py index fd239bf..b2bda44 100644 --- a/splash_screen.py +++ b/splash_screen.py @@ -7,7 +7,21 @@ from rich.progress import Progress, SpinnerColumn, TextColumn console = Console() -def show_startup_logo(SVMU_ver): +def get_skip_startup_splash(config_path): + """Read only skip_startup_splash from config file.""" + try: + with open(config_path, "r", encoding="utf-8") as f: + for line in f: + if line.strip().startswith("skip_startup_splash"): + # Example: skip_startup_splash = yes + return line.split("=")[-1].strip().lower() + except Exception: + pass + return "no" # Default if not found + +def show_startup_logo(skip_startup_splash, SVMU_ver): + if skip_startup_splash == "yes": + return splash_file = os.path.join(os.path.dirname(__file__), "splash-screens.txt") if not os.path.exists(splash_file): console.print("[bold red]Splash screens file not found![/bold red]") @@ -43,5 +57,4 @@ def show_startup_logo(SVMU_ver): os.system('cls' if os.name=='nt' else 'clear') #clears screen before returning -# Example usage: # show_startup_logo()