KarelWintersky ha revisionato questo gist 9 months ago. Vai alla revisione
Nessuna modifica
KarelWintersky ha revisionato questo gist 10 months ago. Vai alla revisione
Nessuna modifica
KarelWintersky ha revisionato questo gist 10 months ago. Vai alla revisione
1 file changed, 152 insertions
pve_summary.py(file creato)
| @@ -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() | |
Più nuovi
Più vecchi