pve_summary.py
· 5.8 KiB · Python
Ham
#!/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 |