KarelWintersky revidoval tento gist . Přejít na revizi
Žádné změny
KarelWintersky revidoval tento gist . Přejít na revizi
Žádné změny
KarelWintersky revidoval tento gist . Přejít na revizi
1 file changed, 152 insertions
pve_summary.py(vytvořil soubor)
| @@ -0,0 +1,152 @@ | |||
| 1 | + | #!/usr/bin/env python3 | |
| 2 | + | # | |
| 3 | + | # Скрипт выводит информацию об имеющихся LXC и VMs в проксе. | |
| 4 | + | # Сортировка по статусу (running|stopped) и ID. | |
| 5 | + | # Выводится: id, тип, название, статус, выделенная память и выделенные диски | |
| 6 | + | # (c) Karel Wintersky, 2025-05-15 | |
| 7 | + | # | |
| 8 | + | import subprocess | |
| 9 | + | import re | |
| 10 | + | ||
| 11 | + | def run_command(cmd): | |
| 12 | + | """Выполняет команду и возвращает stdout.""" | |
| 13 | + | try: | |
| 14 | + | result = subprocess.run(cmd, shell=True, check=True, | |
| 15 | + | stdout=subprocess.PIPE, stderr=subprocess.PIPE, | |
| 16 | + | text=True) | |
| 17 | + | return result.stdout | |
| 18 | + | except subprocess.CalledProcessError as e: | |
| 19 | + | print(f"Ошибка выполнения команды: {e}") | |
| 20 | + | return "" | |
| 21 | + | ||
| 22 | + | def get_lxc_list(): | |
| 23 | + | """Возвращает список ID LXC-контейнеров.""" | |
| 24 | + | output = run_command("pct list") | |
| 25 | + | return [line.split()[0] for line in output.split('\n')[1:] if line.strip()] | |
| 26 | + | ||
| 27 | + | def get_vm_list(): | |
| 28 | + | """Возвращает список ID виртуальных машин.""" | |
| 29 | + | output = run_command("qm list") | |
| 30 | + | return [line.split()[0] for line in output.split('\n')[1:] if line.strip()] | |
| 31 | + | ||
| 32 | + | def parse_size(size_str): | |
| 33 | + | """Конвертирует строку размера в мегабайты.""" | |
| 34 | + | if not size_str: | |
| 35 | + | return 0 | |
| 36 | + | size_str = size_str.upper() | |
| 37 | + | if 'G' in size_str: | |
| 38 | + | return int(float(size_str.replace('G', '')) * 1024) | |
| 39 | + | return int(float(size_str.replace('M', ''))) | |
| 40 | + | ||
| 41 | + | def format_size(mb): | |
| 42 | + | """Форматирует размер в удобный вид.""" | |
| 43 | + | if mb >= 1024: | |
| 44 | + | return f"{round(mb/1024)}G" | |
| 45 | + | return f"{mb}M" | |
| 46 | + | ||
| 47 | + | def get_lxc_data(lxc_id): | |
| 48 | + | """Собирает данные о LXC-контейнере.""" | |
| 49 | + | config = run_command(f"pct config {lxc_id}") | |
| 50 | + | status = run_command(f"pct status {lxc_id}") | |
| 51 | + | ||
| 52 | + | # Парсим данные с точным regex для size | |
| 53 | + | name = re.search(r"hostname:\s*(\S+)", config) | |
| 54 | + | memory = re.search(r"memory:\s*(\d+)", config) | |
| 55 | + | disks = re.findall(r"size=(\d+\w+)", config) | |
| 56 | + | ||
| 57 | + | total_disk = sum(parse_size(size) for size in disks) | |
| 58 | + | ||
| 59 | + | return { | |
| 60 | + | "id": lxc_id, | |
| 61 | + | "type": "LXC", | |
| 62 | + | "name": name.group(1) if name else "N/A", | |
| 63 | + | "status": "running" if "running" in status else "stopped", | |
| 64 | + | "memory": f"{memory.group(1)}M" if memory else "0M", | |
| 65 | + | "disks": format_size(total_disk), | |
| 66 | + | "sort_status": 0 if "running" in status else 1 # Для сортировки | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | def get_vm_data(vm_id): | |
| 70 | + | """Собирает данные о виртуальной машине.""" | |
| 71 | + | config = run_command(f"qm config {vm_id}") | |
| 72 | + | status = run_command(f"qm status {vm_id}") | |
| 73 | + | ||
| 74 | + | # Парсим данные с точным regex для size (исключая scsi-hw) | |
| 75 | + | name = re.search(r"name:\s*(\S+)", config) | |
| 76 | + | memory = re.search(r"memory:\s*(\d+)", config) | |
| 77 | + | disks = re.findall(r"scsi\d+:.*?size=(\d+\w+)", config) | |
| 78 | + | ||
| 79 | + | total_disk = sum(parse_size(size) for size in disks if "scsi-hw" not in size) | |
| 80 | + | ||
| 81 | + | return { | |
| 82 | + | "id": vm_id, | |
| 83 | + | "type": "KVM", | |
| 84 | + | "name": name.group(1) if name else "N/A", | |
| 85 | + | "status": "running" if "running" in status else "stopped", | |
| 86 | + | "memory": f"{memory.group(1)}M" if memory else "0M", | |
| 87 | + | "disks": format_size(total_disk), | |
| 88 | + | "sort_status": 0 if "running" in status else 1 # Для сортировки | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | def print_progress(current, total, prefix=""): | |
| 92 | + | """Простой прогресс-бар в консоли.""" | |
| 93 | + | bar_length = 30 | |
| 94 | + | progress = float(current) / total | |
| 95 | + | block = int(round(bar_length * progress)) | |
| 96 | + | text = f"{prefix} [{'#' * block}{'-' * (bar_length - block)}] {current}/{total}" | |
| 97 | + | print(text, end="\r") | |
| 98 | + | if current == total: | |
| 99 | + | print() | |
| 100 | + | ||
| 101 | + | def main(): | |
| 102 | + | print("Сбор данных о виртуальных машинах Proxmox...\n") | |
| 103 | + | ||
| 104 | + | # Получаем списки | |
| 105 | + | lxc_ids = get_lxc_list() | |
| 106 | + | vm_ids = get_vm_list() | |
| 107 | + | total = len(lxc_ids) + len(vm_ids) | |
| 108 | + | ||
| 109 | + | # Собираем данные | |
| 110 | + | all_data = [] | |
| 111 | + | ||
| 112 | + | # Обрабатываем LXC | |
| 113 | + | for i, lxc_id in enumerate(lxc_ids, 1): | |
| 114 | + | all_data.append(get_lxc_data(lxc_id)) | |
| 115 | + | print_progress(i, len(lxc_ids), "LXC ") | |
| 116 | + | ||
| 117 | + | # Обрабатываем KVM | |
| 118 | + | for i, vm_id in enumerate(vm_ids, 1): | |
| 119 | + | all_data.append(get_vm_data(vm_id)) | |
| 120 | + | print_progress(i, len(vm_ids), "KVM ") | |
| 121 | + | ||
| 122 | + | # Сортируем сначала по статусу, потом по ID | |
| 123 | + | all_data.sort(key=lambda x: (x["sort_status"], int(x["id"]))) | |
| 124 | + | ||
| 125 | + | # Вычисляем ширину столбца Name | |
| 126 | + | max_name_len = max(len(item["name"]) for item in all_data) + 2 | |
| 127 | + | ||
| 128 | + | # Вычисляем итоговые значения | |
| 129 | + | total_vms = sum(1 for item in all_data if item["type"] == "KVM") | |
| 130 | + | total_lxc = sum(1 for item in all_data if item["type"] == "LXC") | |
| 131 | + | total_disks = sum(parse_size(item["disks"].replace('G', '000').replace('M', '')) for item in all_data) | |
| 132 | + | total_disks_formatted = format_size(total_disks) | |
| 133 | + | ||
| 134 | + | ||
| 135 | + | # Выводим результаты | |
| 136 | + | print("\nРезультаты:") | |
| 137 | + | header = f"{'ID':<5} | {'Type':<5} | {'Name':<{max_name_len}} | {'Status':<8} | {'Memory':<8} | {'Disks':<10}" | |
| 138 | + | separator = "-" * len(header.expandtabs()) | |
| 139 | + | print(separator) | |
| 140 | + | print(header) | |
| 141 | + | print(separator) | |
| 142 | + | ||
| 143 | + | for item in all_data: | |
| 144 | + | print(f"{item['id']:<5} | {item['type']:<5} | {item['name']:<{max_name_len}} | {item['status']:<8} | {item['memory']:<8} | {item['disks']:<10}") | |
| 145 | + | ||
| 146 | + | print(separator) | |
| 147 | + | ||
| 148 | + | # Добавляем итоговую строку | |
| 149 | + | print(f"\nИТОГО: ВМ: {total_vms}, Контейнеры: {total_lxc}, Всего дисков: {total_disks_formatted}") | |
| 150 | + | ||
| 151 | + | if __name__ == "__main__": | |
| 152 | + | main() | |
    
    
                            
                            Novější
    
    
    Starší