13 Commits

Author SHA1 Message Date
27d85f4d1a Update README.md 2025-05-30 12:19:29 +03:00
OVERLORD7F
f3734432a7 Added option to write selected VMs by tag (#10) to сonfig 2025-05-30 12:12:33 +03:00
OVERLORD7F
72dccaa206 Default values for Courses-Space-VM section (fix #18) 2025-05-30 11:28:59 +03:00
Seting-dev
daba29f208 Filter VM UUIDs #10 (#17)
* is it my?

* Issues #8 Config Edit changes

* Patch #8

* vm_tags

* Changed

* deleted

* deleted conf

* patch to domain sub menu
2025-05-29 17:19:43 +03:00
OVERLORD7F
1f4c614c28 Show example config #14 2025-05-29 15:44:01 +03:00
OVERLORD7F
7867b0ea23 Added options in config for courses #16 2025-05-29 15:20:29 +03:00
OVERLORD7F
dae4bf33a0 Merge branch 'main' of https://github.com/OVERLORD7F/SpaceVM_VM_Utility 2025-05-29 13:30:01 +03:00
OVERLORD7F
d2383dea4f - Progress bars for disk delete / create actions
- Changed output for disk edit menu
2025-05-29 13:28:48 +03:00
OVERLORD
433854b36f Update README.md
updated config usage
2025-05-28 17:09:53 +03:00
OVERLORD7F
71a7c38c27 Expanding config-related functions
- New function change_vm_uuids
- New function change_data_pool
2025-05-28 16:59:38 +03:00
OVERLORD7F
3ca5404b81 LEEETS GOOOO <50 lines !!1
- Ping / API key checks are back! #13
- Changes in config import
- Moved everything in main to menu loop because of reasons  #2
- New function check_config
- Currently selected pools / vms .. are shown in main menu #12
- Renamed a few functions
2025-05-28 12:45:07 +03:00
OVERLORD7F
2ccde25b6e - Finally, proper config file is here!
- Small changes in menus
2025-05-26 17:52:12 +03:00
Seting-dev
96c6e29c00 Config Edit changes #8 (#11)
* Issues #8 Config Edit changes

* Patch #8
2025-05-26 10:28:01 +03:00
7 changed files with 380 additions and 132 deletions

View File

@@ -20,26 +20,38 @@ Clone repository or use compiled .exe from [Releases Tab](https://github.com/OVE
Fill in the config file as stated below.
## Config File
Config file contains all necessary data for utility and has to be placed in the same directory as Utility itself.
You can create config and specify all necessary data within the Utility.
<ins>The following parameters are required:</ins>
```
1. Controller IP Address (Master)
2. API Integration Key
3. Data Pool UUID (which will be used for operations)
4. Virtual Machine UUID (List of selected VMs for operations)
5. Virtual Machine UUID
6. ...
```
## Config File (SpaceVM_Utility.conf)
_SpaceVM_Utility.conf_ contains all necessary data for utility and has to be placed in the same directory as Utility itself.
You can populate config within Utility Main Menu.
You can create config and change specific options within the Utility.
```
[General]
#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
For manual input see format below:
```
1.2.3.4
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1...
67497424-e54b-46f1-b023-4d6d65eac104
304fd7be-06a6-4f4e-9bb9-41bf532ec4fb
f840320f-cecc-4a31-a799-fd84e7baf089
#Integration API Key
(how to get your key - https://spacevm.ru/docs/latest/base/operator_guide/security/users/#_14 )
# do not specify JWT tag with your key!
api_key =
[Data_Pool]
#Data pool which will be used for utility operations
#(Targeted storage for new vDisks)
data_pool_uuid =
[VM_List]
#Selected VMs which will be used for utility operations
#How to find UUID:
#List all available VMs in Utility Main Menu (Option 6)
#Use https://spacevm.ru/docs/latest/cli/space/vm/info/ or copy UUID from web panel
uuid_1 =
uuid_2 =
[Courses-Space-VM]
#Set vDisk size for "Prepare VMs for Courses" option
disk1 =
disk2 =
disk3 =
```

View File

@@ -34,4 +34,17 @@ def cluster_info(base_url, api_key): # output short clusters overview
else:
console.print(f"[red]Failed to retrieve data {response.status_code}[/]")
Prompt.ask("[green_yellow bold]ENTER - return to Main Menu.. :right_arrow_curving_down:")
os.system('cls' if os.name == 'nt' else 'clear')
os.system('cls' if os.name == 'nt' else 'clear')
def check_api_key(base_url, api_key): # test api key and show spaceVM version
url = f"http://{base_url}/api/controllers/base-version/"
response = requests.get(url, headers={'Authorization': api_key})
console = Console()
if response.status_code == 200:
cluster_info = response.json()
version = cluster_info['version']
console.print(f"[bold green]Successfully conected to SpaceVM v{version}")
else:
console.print(f"[bold red]{response.status_code}[/]")
return response.status_code

View File

@@ -1,24 +1,77 @@
import os
import subprocess
import configparser
from cluster_api import *
from domain_api import *
from data_pools_api import *
from rich import print
from rich.panel import Panel
from rich.console import Console , Align
from rich.prompt import Prompt
console = Console()
def config_menu(config_relative_path):
def config_menu(base_url, api_key, config_relative_path):
cls()
config_menu_options="[gold bold][1] [grey53 italic]Show current configuration\n[/grey53 italic] \
\n[gold bold][2] [grey53 italic]Change configuraion[/grey53 italic]\n \
\n\n[green_yellow bold]ENTER - return to Main Menu"
config_menu_options="[gold bold][0] [grey53 italic]Show example configuration\n[/]\
\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\n[green_yellow bold]ENTER - return to Main Menu[/]"
config_menu_options=Align.center(config_menu_options, vertical="middle")
console = Console()
console.print(Panel(config_menu_options, title="[gold bold]SpaceVM Utility - Utility Configuration" , border_style="magenta" , width=150 , padding = 2))
sub_choice=str(input("\n>>> "))
if sub_choice == "0":
config_show_example()
if sub_choice == "1":
config_show(config_relative_path)
config_menu(base_url, api_key, config_relative_path)
if sub_choice == "2":
config_edit(config_relative_path)
if sub_choice == "3":
change_data_pool(base_url, api_key, config_relative_path)
if sub_choice == "4":
change_vm_uuids(config_relative_path)
def config_show_example():
conf_example= """
[General]
#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
#Integration API Key. how to get your key:
# ( https://spacevm.ru/docs/latest/base/operator_guide/security/users/#_14 )
# do not specify JWT tag with your key!
api_key =
[Data_Pool]
#Data pool which will be used for utility operations
#(Targeted storage for new vDisks)
data_pool_uuid =
[VM_List]
#Selected VMs which will be used for utility operations
#How to find UUID:
#List all available VMs in Utility Main Menu (Option 6)
#Use https://spacevm.ru/docs/latest/cli/space/vm/info/ or copy UUID from web panel
uuid_1 =
uuid_2 =
[Courses-Space-VM]
#Set vDisk size for "Prepare VMs for Courses™" option
disk1 = 10
disk2 = 20
disk3 = 20
"""
cls()
console.rule(title = "Example config file", align="center", style="yellow")
console.print(conf_example)
console.rule(style="yellow")
Prompt.ask("[green_yellow bold]ENTER - return to Utility Configuration.. :right_arrow_curving_down:")
def config_show(config_relative_path):
cls()
@@ -26,52 +79,144 @@ def config_show(config_relative_path):
with open(config_relative_path, "r") as f:
print(f.read())
console.rule(style="yellow")
Prompt.ask("[green_yellow bold]ENTER - return to Main Menu.. :right_arrow_curving_down:")
Prompt.ask("[green_yellow bold]ENTER - return to Utility Configuration.. :right_arrow_curving_down:")
def import_vm_uuid(config_relative_path):
vm_uuids = []
with open(config_relative_path, "r") as f:
for i in range(3): # ignoring 2 first lines (IP, API-KEY)
next(f)
for line in f:
line = line.strip('\n')
if line: # checks if line is empty (EOF). ESSENTIAL, DO NOT REMOVE
vm_uuids.append(line)
return vm_uuids
def config_import(config_relative_path):
config = configparser.ConfigParser()
config.read(config_relative_path)
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 disk sizes for SpaceVM courses
if config.has_section('Courses-Space-VM'):
disk1_size = config.get('Courses-Space-VM', 'disk1')
disk2_size = config.get('Courses-Space-VM', 'disk2')
disk3_size = config.get('Courses-Space-VM', 'disk3')
else:
console.print("[bold yellow]Applying default values to Disk sizes for Courses")
disk1_size, disk2_size, disk3_size = 10, 20, 20 #applying default values for courses
config = configparser.ConfigParser() #writing default values to config
config["Courses-Space-VM"] = {
"disk1": 10,
"disk2": 20,
"disk3": 20,
}
with open(config_relative_path, "a") as configfile: # appending to existing config file
config.write(configfile)
#get pretty name for selected data pool
data_pool_name = get_data_pool_name(base_url , api_key , data_pool_uuid)
#get pretty name for selected VMs
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
def change_data_pool(base_url, api_key, config_relative_path): #change selected data pool in config
cls()
show_data_pools(base_url, api_key)
new_data_pool_uuid = input("Type NEW Data Pool UUID: ")
config = configparser.ConfigParser()
config.read(config_relative_path)
if config.has_section('Data_Pool'):
config.set('Data_Pool', 'data_pool_uuid', new_data_pool_uuid)
with open(config_relative_path, 'w') as config_file:
config.write(config_file)
else:
print("No 'Data_Pool' 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()
config.read(config_relative_path)
# Remove old VM_List section if it exists, then add a fresh one
if config.has_section('VM_List'):
config.remove_section('VM_List')
config.add_section('VM_List')
cls()
console.print("[yellow bold]Type new VM UUIDs one by one (input ENTER to stop):")
x = 0
while True:
vm_input = input(">> ")
if not vm_input:
break
x += 1
config.set('VM_List', f'uuid_{x}', vm_input)
with open(config_relative_path, 'w') as configfile:
config.write(configfile)
console.print("[green bold]VM UUIDs have been updated in config :pencil:")
Prompt.ask("[green_yellow bold]Press ENTER to proceed.. :right_arrow_curving_down:")
config_show(config_relative_path)
def import_threelines(config_relative_path):
threelines = [None] * 3
with open(config_relative_path, "r") as f:
all_lines = f.readlines()
if len(all_lines) < 3:
raise ValueError("Check config. Receiving less than 3 lines!")
threelines[0] = all_lines[0].strip('\n')
threelines[1] = "jwt " + all_lines[1].strip('\n') #actual format for api_key. That was realy obvious DACOM >:C
threelines[2] = all_lines[2].strip('\n')
return threelines
def config_edit(config_relative_path):
read_input=input("Create new config file? (Y / N): ")
menu_choice=str(read_input)
read_input = input("Create new config file? (Y / N): ")
menu_choice = str(read_input)
if menu_choice == "Y" or menu_choice == "y":
base_url = input("Type SpaceVM Controller IP: ")
while check_ping(base_url) != True:
base_url = console.input("[bold red]No response.\nCheck and type SpaceVM Controller IP again: [/]")
api_key = input("Type your API Key: ")
while check_api_key(base_url, "jwt " + api_key) != 200:
api_key = console.input("[bold red]Check and type SpaceVM Controller API Key again: [/]")
show_data_pools(base_url, "jwt " + api_key)
data_pool_uuid = input("Type Data Pool UUID you wish to use: ")
lines = [base_url, api_key, data_pool_uuid]
with open(config_relative_path, "w+") as file:
for line in lines:
file.write(line + '\n')
config = configparser.ConfigParser()
config["General"] = {
"controller_ip": base_url,
"api_key": api_key,
}
config["Data_Pool"] = {"data_pool_uuid": data_pool_uuid}
with open(config_relative_path, "w") as configfile:
config.write(configfile)
print("Type VM UUIDs one by one (input ENTER to stop)")
with open(config_relative_path, "a") as file: #appends new content at the end without modifying the existing data
vm_input="test"
while (vm_input != ""):
with open(config_relative_path, "a") as file:
file.write("[VM_List]\n") #manually writing section for VMs
vm_input = []
x = 0
while vm_input != "":
vm_input = input(">> ")
file.write(vm_input + '\n')
console.print("[green bold]VM UUIDs has been written in config :pencil:")
console.print("[green bold]Configuration completed ! :white_check_mark:")
Prompt.ask("[green_yellow bold]Press ENTER to proceed.. :right_arrow_curving_down:")
cls()
if vm_input:
x += 1
file.write(f"uuid_{x} = {vm_input}\n")
console.print("[green bold]VM UUIDs have been written in config :pencil:")
console.print("[green bold]Configuration completed ! :white_check_mark:")
Prompt.ask("[green_yellow bold]Press ENTER to proceed.. :right_arrow_curving_down:")
cls()
def check_config(config_relative_path):
if os.path.exists(config_relative_path) and os.path.getsize(config_relative_path) > 0: #check if config exists and not empty
pass #do nothing
else:
console.print("[yellow bold italic]Config file was not found or empty.. ")
config_edit(config_relative_path)
def cls():
os.system('cls' if os.name=='nt' else 'clear')
os.system('cls' if os.name=='nt' else 'clear')
def check_ping(base_url):
DNULL = open(os.devnull, 'w')
if os.name == 'nt':
status = subprocess.call(["ping","-n","1",base_url],stdout = DNULL)
else:
status = subprocess.call(["ping","-c","1",base_url],stdout = DNULL)
if status == 0:
return True
else:
return False

View File

@@ -5,14 +5,14 @@ from rich.console import Console
from rich.panel import Panel
from rich.align import Align
def data_pools(base_url, api_key): # output data pool info
def show_data_pools(base_url, api_key): # output data pool info
url = f"http://{base_url}//api/data-pools/"
response = requests.get(url, headers={'Authorization': api_key})
console = Console()
if response.status_code == 200:
data_pools = response.json()
results_data_pools_info = data_pools['results']
os.system('cls' if os.name == 'nt' else 'clear')
#os.system('cls' if os.name == 'nt' else 'clear')
console.rule("[bold cyan]Data Pools Overview")
console.print(f"[bold]Data pools total:[/] {data_pools['count']}\n")
panels = []
@@ -32,5 +32,12 @@ def data_pools(base_url, api_key): # output data pool info
console.print(*panels, sep="\n")
else:
console.print(f"[red]Failed to retrieve data {response.status_code}[/]")
Prompt.ask("[green_yellow bold]ENTER - return to Main Menu.. :right_arrow_curving_down:")
os.system('cls' if os.name == 'nt' else 'clear')
Prompt.ask("[green_yellow bold]ENTER - to proceed.. :right_arrow_curving_down:")
#translates data pool uuid to verbose_name
def get_data_pool_name(base_url, api_key, data_pool_uuid):
url = f"http://{base_url}//api/data-pools/{data_pool_uuid}/"
response = requests.get(url, headers={'Authorization': api_key})
if response.status_code == 200:
data_pool_name = response.json()
return (f"{data_pool_name['verbose_name']}")

View File

@@ -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):
def disk_edit_mode(base_url , api_key , data_pool_uuid , vm_uuids, disk1_size, disk2_size, disk3_size):
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 \
@@ -52,16 +52,16 @@ def disk_edit_mode(base_url , api_key , data_pool_uuid , vm_uuids):
disk_uuids = get_disk_uuids(base_url , api_key , domain_all_content)
for y in disk_uuids:
delete_disk(base_url , api_key , y)
console.print("[bold red]All attached vDisks has been deleted!")
for z in vm_uuids: # only for creating disks
domain_uuid = z.strip('\n')
console.print(f"\n[bold underline yellow]Creating and attaching disk to VM {domain_uuid}")
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}:")
domain_info = get_domain_info(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, 10, "falloc")
create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, 20, "falloc")
create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, 20, "falloc")
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")
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')

View File

@@ -2,10 +2,13 @@
import requests
import secrets #for generating unique names
import os
import configparser
from config_data_import import *
from rich.console import Console , Align
from rich.columns import Columns
from rich.panel import Panel
from rich.prompt import Prompt
from rich.prompt import Prompt, Confirm
from rich.progress import Progress, SpinnerColumn, TextColumn
console = Console() #necessary for pretty menus & output
power_state = ["Unknown" , "Off" , "Suspend" , "On"] #3 - on; 2 - suspend; 1 - off; 0 - unknown
@@ -22,7 +25,6 @@ def get_domain_info(base_url , api_key , domain_uuid):
print(f"Failed to retrieve data {response.status_code}")
def get_domain_all_content(base_url, api_key, domain_uuid):
url= f"http://{base_url}/api/domains/{domain_uuid}/all-content"
response = requests.get(url , headers={'Authorization' : api_key})
@@ -55,24 +57,27 @@ def get_disk_uuids(base_url , api_key , domain_all_content):
def delete_disk(base_url , api_key , vdisk_uuid):
url = f"http://{base_url}/api/vdisks/{vdisk_uuid}/remove/"
headers={
url = f"http://{base_url}/api/vdisks/{vdisk_uuid}/remove/"
headers={
"Authorization" : api_key,
"Content-Type" : "application/json",
}
payload= {
}
payload= {
"force": False,
"guaranteed": False,
"clean_type": "zero",
"clean_count": 1
}
}
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress:
task = progress.add_task("Deleting vDisk...", total=None)
response = requests.post(url , headers=headers, json=payload)
if response.status_code == 200:
print(f"vDisk {vdisk_uuid} successfully deleted")
return True
else:
print(f"ERROR deleting disk {vdisk_uuid} :\n {response.status_code} - {response.text}")
return False
progress.remove_task(task)
if response.status_code == 200:
console.print(f"[grey53 italic]{vdisk_uuid}[/] :wastebasket:")
return True
else:
print(f"ERROR deleting disk {vdisk_uuid} :\n {response.status_code} - {response.text}")
return False
def get_disk_info(domain_all_content):
@@ -103,6 +108,14 @@ def get_disk_info(domain_all_content):
console.print(Columns(disk_info_renderables))
def get_vm_name(base_url, api_key, vm_uuids):
url = f"http://{base_url}//api/domains/{vm_uuids}/"
response = requests.get(url, headers={'Authorization': api_key})
if response.status_code == 200:
vm_name = response.json()
return (f"{vm_name['verbose_name']}")
def vm_info(base_url, api_key, vm_uuids):
domain_info = get_domain_info(base_url, api_key, vm_uuids)
domain_all_content = get_domain_all_content(base_url, api_key, vm_uuids)
@@ -119,13 +132,15 @@ def vm_info(base_url, api_key, vm_uuids):
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})
if response.status_code == 200:
vm_info_short = response.json()
results_vm_info_short = vm_info_short['results']
tag = vm_info_short['results'][0]['tags'][0]
print(tag)
#print(results_vm_info_short)
os.system('cls' if os.name=='nt' else 'clear')
console.print(Align.center(Panel(f"[bold magenta]Short VM overview | Total: {vm_info_short['count']}", expand=True , border_style="yellow") , vertical="middle"))
console.rule(style="grey53")
@@ -141,28 +156,32 @@ def vm_info_short(base_url, api_key):
Prompt.ask("[green_yellow bold]ENTER - return to Main Menu.... :right_arrow_curving_down:")
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):
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
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/"
headers={
"Authorization" : api_key,
"Content-Type" : "application/json",
"Authorization" : api_key,
"Content-Type" : "application/json",
}
payload= {
"verbose_name": disk_name,
"preallocation": preallocation,
"size": vdisk_size,
"datapool": data_pool_uuid,
"target_bus": "virtio",
}
response = requests.post(url , headers=headers, json=payload)
"verbose_name": disk_name,
"preallocation": preallocation,
"size": vdisk_size,
"datapool": data_pool_uuid,
"target_bus": "virtio",
}
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress:
task = progress.add_task("Creating and attaching vDisk...", total=None)
response = requests.post(url , headers=headers, json=payload)
progress.remove_task(task)
if response.status_code == 200:
print(f"vDisk {disk_name} ({vdisk_size}GB) has been created and attached")
console.print(f"[grey53 italic]{disk_name} ({vdisk_size}GB)[/] :white_check_mark:")
return True
else:
print(f"ERROR creating vDisk :\n {response.status_code} - {response.text}")
return False
return False
#checks for power on.
def vm_check_power(base_url , api_key , vm_uuids):
@@ -175,4 +194,66 @@ def vm_check_power(base_url , api_key , vm_uuids):
if domain_info['user_power_state'] == 0:
raise Exception(f"VM - {vm_uuids} is UNAVAILABLE! \n Have fun figuring that out D:")
if domain_info['user_power_state'] == 1:
print(f"VM - {vm_uuids} Power check passed!")
pass
def select_vm_by_tags(base_url, api_key, config_relative_path):
url = f"http://{base_url}/api/domains/"
response = requests.get(url, headers={'Authorization': api_key})
if response.status_code == 200:
verbose_name_input = input("Specify tag: ")
output_renderables = []
vm_info_short = response.json()
y= vm_info_short
vm_id_list = []
for y in vm_info_short['results']:
for x in y['tags']:
if x['verbose_name'] == verbose_name_input:
vm_id_list.append(y['id'])
output_string = f"VM: [bold]{y['verbose_name']} [bold yellow]#{x['verbose_name']}[/]" + f"\nUUID: [italic]{y['id']}"
output_renderable = Panel(output_string, expand=False, border_style="magenta")
output_renderables.append(output_renderable) #adds current renderable
console.print(Columns(output_renderables)) #print renderables by columns
else:
print(f"Failed to retrieve data {response.status_code}")
console.rule(style="grey53")
if vm_id_list: # promt to write found VM UUIDs to config
write_to_config = Confirm.ask("[bold yellow]Write these VM UUIDs to config file?")
if write_to_config:
config = configparser.ConfigParser()
config.read(config_relative_path)
# Remove old VM_List section if it exists, then add a fresh one
if config.has_section('VM_List'):
config.remove_section('VM_List')
config.add_section('VM_List')
for idx, vm_id in enumerate(vm_id_list, 1):
config.set('VM_List', f'uuid_{idx}', vm_id)
with open(config_relative_path, 'w') as configfile:
config.write(configfile)
console.print(f"[green bold]VM UUIDs have been written in config :pencil:")
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 vm_menu(base_url, api_key, vm_uuids, config_relative_path):
os.system('cls' if os.name=='nt' else 'clear')
config_menu_options="[gold bold][1] [grey53 italic]Show VM info \n (for selected VMs in config)[/grey53 italic]\n \
\n[gold bold][2] [grey53 italic]Show VMs Name / UUID[/grey53 italic]\n \
\n[gold bold][3] [grey53 italic]Select VMs by tag / UUID[/grey53 italic]\n \
\n\n[green_yellow bold]ENTER - return to Main Menu[/]"
config_menu_options=Align.center(config_menu_options, vertical="middle")
console = Console()
console.print(Panel(config_menu_options, title="[gold bold]Show VM info" , border_style="magenta" , width=150 , padding = 2))
sub_choice=str(input("\n>>> "))
if sub_choice == "1":
os.system('cls' if os.name=='nt' else 'clear')
for x in vm_uuids:
vm_info(base_url , api_key , x)
Prompt.ask("[green_yellow bold]Press ENTER to proceed.. :right_arrow_curving_down:")
if sub_choice == "2":
os.system('cls' if os.name=='nt' else 'clear')
vm_info_short(base_url , api_key)
if sub_choice == "3":
os.system('cls' if os.name=='nt' else 'clear')
select_vm_by_tags(base_url , api_key, config_relative_path)

54
main.py
View File

@@ -4,52 +4,42 @@ from cluster_api import *
from domain_api import *
from data_pools_api import *
from disk_edit_mode import *
from rich import print
from rich.panel import Panel
from rich.console import Console , Align
config_relative_path = os.path.join(os.getcwd() , 'config.txt') #config.txt in the same directory with main.py
if os.path.exists(config_relative_path) and os.path.getsize(config_relative_path) > 0: #check if config exists and not empty
pass #do nothing
else:
console.print("[yellow bold italic]Config file was not found or empty.. ")
config_edit(config_relative_path)
config_relative_path = os.path.join(os.getcwd() , 'SpaceVM_Utility.conf') #config in the same directory with main.py
#importing API-KEY / IP / DATA POOL UUID / VM-UUIDs from config
#base_url=threelines[0] api_key=threelines[1] data_pool_uuid=threelines[2]
base_url, api_key, data_pool_uuid = import_threelines(config_relative_path)
vm_uuids = import_vm_uuid(config_relative_path)
print("Reading config from:", os.path.abspath(config_relative_path))
if not os.path.exists(config_relative_path):
print(f"Config file not found: {config_relative_path}")
menu_choice=0
menu_options="[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 \
\n[gold bold][4] [grey53 italic]Show VM info \n (for selected VMs in config)[/grey53 italic]\n \
\n[gold bold][5] [grey53 italic]Show data pools[/grey53 italic]\n \
\n[gold bold][6] [grey53 italic]Show VMs Name / UUID[/grey53 italic]\n \
\n\n[green_yellow bold]ENTER - exit Utility"
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]"
console = Console()
os.system('cls' if os.name=='nt' else 'clear')
while(menu_choice != ""): #main menu loop
console.print(Panel(menu_options,
title="[bold magenta]SpaceVM Utility - Main Menu" , subtitle = menu_subtitle, subtitle_align="right" , style="yellow" , width=150 , padding = 2))
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
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 \
\n[gold bold][4] [grey53 italic]Enter VM menu[/grey53 italic]\n \
\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}"
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]"
console.print(Panel(menu_options, title="[bold magenta]SpaceVM Utility - 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(config_relative_path)
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)
disk_edit_mode(base_url , api_key , data_pool_uuid , vm_uuids, disk1_size, disk2_size, disk3_size)
if menu_choice == "3":
cluster_info(base_url , api_key)
if menu_choice == "4":
os.system('cls' if os.name=='nt' else 'clear')
for x in vm_uuids:
vm_info(base_url , api_key , x)
Prompt.ask("[green_yellow bold]Press ENTER to proceed.. :right_arrow_curving_down:")
vm_menu(base_url , api_key, vm_uuids, config_relative_path)
if menu_choice == "5":
data_pools(base_url , api_key)
if menu_choice == "6":
vm_info_short(base_url , api_key)
show_data_pools(base_url , api_key)
os.system('cls' if os.name=='nt' else 'clear') #clears screen before looping back to main menu
console.print("[red bold]Exiting Utility ")
console.print("[red bold]Exiting Utility ")