pve_summary.py
· 5.8 KiB · Python
Bruto
#!/usr/bin/env python3
#
# Скрипт выводит информацию об имеющихся LXC и VMs в проксе.
# Сортировка по статусу (running|stopped) и ID.
# Выводится: id, тип, название, статус, выделенная память и выделенные диски
# (c) Karel Wintersky, 2025-05-15
#
import subprocess
import re
def run_command(cmd):
"""Выполняет команду и возвращает stdout."""
try:
result = subprocess.run(cmd, shell=True, check=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True)
return result.stdout
except subprocess.CalledProcessError as e:
print(f"Ошибка выполнения команды: {e}")
return ""
def get_lxc_list():
"""Возвращает список ID LXC-контейнеров."""
output = run_command("pct list")
return [line.split()[0] for line in output.split('\n')[1:] if line.strip()]
def get_vm_list():
"""Возвращает список ID виртуальных машин."""
output = run_command("qm list")
return [line.split()[0] for line in output.split('\n')[1:] if line.strip()]
def parse_size(size_str):
"""Конвертирует строку размера в мегабайты."""
if not size_str:
return 0
size_str = size_str.upper()
if 'G' in size_str:
return int(float(size_str.replace('G', '')) * 1024)
return int(float(size_str.replace('M', '')))
def format_size(mb):
"""Форматирует размер в удобный вид."""
if mb >= 1024:
return f"{round(mb/1024)}G"
return f"{mb}M"
def get_lxc_data(lxc_id):
"""Собирает данные о LXC-контейнере."""
config = run_command(f"pct config {lxc_id}")
status = run_command(f"pct status {lxc_id}")
# Парсим данные с точным regex для size
name = re.search(r"hostname:\s*(\S+)", config)
memory = re.search(r"memory:\s*(\d+)", config)
disks = re.findall(r"size=(\d+\w+)", config)
total_disk = sum(parse_size(size) for size in disks)
return {
"id": lxc_id,
"type": "LXC",
"name": name.group(1) if name else "N/A",
"status": "running" if "running" in status else "stopped",
"memory": f"{memory.group(1)}M" if memory else "0M",
"disks": format_size(total_disk),
"sort_status": 0 if "running" in status else 1 # Для сортировки
}
def get_vm_data(vm_id):
"""Собирает данные о виртуальной машине."""
config = run_command(f"qm config {vm_id}")
status = run_command(f"qm status {vm_id}")
# Парсим данные с точным regex для size (исключая scsi-hw)
name = re.search(r"name:\s*(\S+)", config)
memory = re.search(r"memory:\s*(\d+)", config)
disks = re.findall(r"scsi\d+:.*?size=(\d+\w+)", config)
total_disk = sum(parse_size(size) for size in disks if "scsi-hw" not in size)
return {
"id": vm_id,
"type": "KVM",
"name": name.group(1) if name else "N/A",
"status": "running" if "running" in status else "stopped",
"memory": f"{memory.group(1)}M" if memory else "0M",
"disks": format_size(total_disk),
"sort_status": 0 if "running" in status else 1 # Для сортировки
}
def print_progress(current, total, prefix=""):
"""Простой прогресс-бар в консоли."""
bar_length = 30
progress = float(current) / total
block = int(round(bar_length * progress))
text = f"{prefix} [{'#' * block}{'-' * (bar_length - block)}] {current}/{total}"
print(text, end="\r")
if current == total:
print()
def main():
print("Сбор данных о виртуальных машинах Proxmox...\n")
# Получаем списки
lxc_ids = get_lxc_list()
vm_ids = get_vm_list()
total = len(lxc_ids) + len(vm_ids)
# Собираем данные
all_data = []
# Обрабатываем LXC
for i, lxc_id in enumerate(lxc_ids, 1):
all_data.append(get_lxc_data(lxc_id))
print_progress(i, len(lxc_ids), "LXC ")
# Обрабатываем KVM
for i, vm_id in enumerate(vm_ids, 1):
all_data.append(get_vm_data(vm_id))
print_progress(i, len(vm_ids), "KVM ")
# Сортируем сначала по статусу, потом по ID
all_data.sort(key=lambda x: (x["sort_status"], int(x["id"])))
# Вычисляем ширину столбца Name
max_name_len = max(len(item["name"]) for item in all_data) + 2
# Вычисляем итоговые значения
total_vms = sum(1 for item in all_data if item["type"] == "KVM")
total_lxc = sum(1 for item in all_data if item["type"] == "LXC")
total_disks = sum(parse_size(item["disks"].replace('G', '000').replace('M', '')) for item in all_data)
total_disks_formatted = format_size(total_disks)
# Выводим результаты
print("\nРезультаты:")
header = f"{'ID':<5} | {'Type':<5} | {'Name':<{max_name_len}} | {'Status':<8} | {'Memory':<8} | {'Disks':<10}"
separator = "-" * len(header.expandtabs())
print(separator)
print(header)
print(separator)
for item in all_data:
print(f"{item['id']:<5} | {item['type']:<5} | {item['name']:<{max_name_len}} | {item['status']:<8} | {item['memory']:<8} | {item['disks']:<10}")
print(separator)
# Добавляем итоговую строку
print(f"\nИТОГО: ВМ: {total_vms}, Контейнеры: {total_lxc}, Всего дисков: {total_disks_formatted}")
if __name__ == "__main__":
main()
| 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() |
| 153 |