pve_summary.py
                        
                             · 5.8 KiB · Python
                        
                    
                    
                      
                        Исходник
                      
                      
                        
                          
                        
                    
                    
                
                
            #!/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 |