349 lines
12 KiB
Python
349 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Cloud Instance Status Checker
|
|
Проверяет статус ресурсов в облаке по манифесту Terraform
|
|
"""
|
|
|
|
import json
|
|
import requests
|
|
import sys
|
|
from typing import Dict, Optional, List
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
from datetime import datetime
|
|
|
|
|
|
class InstanceState(Enum):
|
|
"""Возможные состояния инстанса"""
|
|
NOT_CREATED = "not_created"
|
|
CREATING = "creating"
|
|
RUNNING = "running"
|
|
MODIFYING = "modifying"
|
|
SUSPENDING = "suspending"
|
|
SUSPENDED = "suspended"
|
|
RESUMING = "resuming"
|
|
DELETING = "deleting"
|
|
DELETED = "deleted"
|
|
OPERATION_PENDING = "operation_pending"
|
|
OPERATION_FAILED = "operation_failed"
|
|
UNKNOWN = "unknown"
|
|
|
|
|
|
@dataclass
|
|
class InstanceInfo:
|
|
"""Информация об инстансе"""
|
|
uid: str
|
|
name: str
|
|
state: InstanceState
|
|
is_created: bool
|
|
is_deleted: bool
|
|
is_suspended: bool
|
|
uptime_seconds: float
|
|
operation_in_progress: bool
|
|
operation_pending: bool
|
|
last_operation: Optional[str]
|
|
last_operation_status: Optional[str]
|
|
error_log: Optional[str]
|
|
timestamp: str
|
|
|
|
|
|
class CloudInstanceChecker:
|
|
"""Проверяет статус инстансов в облаке"""
|
|
|
|
# ⛔ LEGACY: deck-api ЗАКРЫВАЕТСЯ. Использовать lk-api-gateway.
|
|
BASE_URL = "https://lk-api-gateway.ngcloud.ru/api/v1/svc"
|
|
REQUEST_TIMEOUT = 30
|
|
|
|
def __init__(self, token: str):
|
|
"""
|
|
Инициализация чекера
|
|
|
|
Args:
|
|
token: JWT токен для аутентификации в API
|
|
"""
|
|
self.token = token
|
|
self.session = requests.Session()
|
|
self.session.headers.update({
|
|
"Authorization": f"Bearer {token}",
|
|
"Content-Type": "application/json"
|
|
})
|
|
|
|
def find_instance(self, display_name: str, service_type: Optional[str] = None) -> Optional[Dict]:
|
|
"""
|
|
Найти инстанс по отображаемому имени
|
|
|
|
Args:
|
|
display_name: Имя ресурса в облаке
|
|
service_type: Тип сервиса (опционально, для фильтрации)
|
|
|
|
Returns:
|
|
Данные инстанса или None если не найден
|
|
"""
|
|
try:
|
|
params = {
|
|
"fields": "instanceConfigDtCreated,instanceUid,displayName,svc,uptime,explainedStatus,svcExtendedName,updaterLogin,updaterShortname,operationIsInProgress,operationIsPending,monitoringUrl",
|
|
"search": display_name,
|
|
"isAuxiliary": "false",
|
|
"isDeleted": "false",
|
|
"page": "1",
|
|
"pageSize": "100"
|
|
}
|
|
|
|
response = self.session.get(
|
|
f"{self.BASE_URL}/instances",
|
|
params=params,
|
|
timeout=self.REQUEST_TIMEOUT,
|
|
)
|
|
response.raise_for_status()
|
|
|
|
data = response.json()
|
|
results = data.get("results", [])
|
|
|
|
# Поиск точного совпадения по имени
|
|
for instance in results:
|
|
if instance.get("displayName") == display_name:
|
|
if service_type is None or instance.get("svc") == service_type:
|
|
return instance
|
|
|
|
return None
|
|
except requests.RequestException as e:
|
|
print(f"❌ Ошибка при поиске инстанса '{display_name}': {e}", file=sys.stderr)
|
|
return None
|
|
|
|
def get_instance_details(self, instance_uid: str) -> Optional[Dict]:
|
|
"""
|
|
Получить полную информацию об инстансе
|
|
|
|
Args:
|
|
instance_uid: UUID инстанса
|
|
|
|
Returns:
|
|
Полные данные инстанса или None при ошибке
|
|
"""
|
|
try:
|
|
params = {
|
|
"fields": "instanceConfigDtCreated,instanceUid,displayName,descr,svc,state,operations,availableOperations,uptime,isDeleted,updaterLogin,updaterShortname,explainedStatus,man,dependencies,dependentInstances,svcExtendedName"
|
|
}
|
|
|
|
response = self.session.get(
|
|
f"{self.BASE_URL}/instances/{instance_uid}",
|
|
params=params,
|
|
timeout=self.REQUEST_TIMEOUT,
|
|
)
|
|
response.raise_for_status()
|
|
|
|
return response.json().get("instance")
|
|
except requests.RequestException as e:
|
|
print(f"❌ Ошибка при получении информации об инстансе {instance_uid}: {e}", file=sys.stderr)
|
|
return None
|
|
|
|
def determine_state(self, instance_data: Dict) -> InstanceState:
|
|
"""
|
|
Определить состояние инстанса на основе флагов
|
|
|
|
Args:
|
|
instance_data: Данные инстанса от API
|
|
|
|
Returns:
|
|
InstanceState enum
|
|
"""
|
|
# Проверка удаления
|
|
if instance_data.get("isDeleted"):
|
|
return InstanceState.DELETED
|
|
|
|
# Проверка создания
|
|
if not instance_data.get("isCreated"):
|
|
return InstanceState.NOT_CREATED
|
|
|
|
# Проверка операции в процессе
|
|
if instance_data.get("operationIsInProgress"):
|
|
operations = instance_data.get("operations", [])
|
|
if operations:
|
|
last_op = operations[-1]
|
|
operation = last_op.get("operation", "unknown")
|
|
|
|
if operation == "create":
|
|
return InstanceState.CREATING
|
|
elif operation == "modify":
|
|
return InstanceState.MODIFYING
|
|
elif operation == "delete":
|
|
return InstanceState.DELETING
|
|
elif operation == "suspend":
|
|
return InstanceState.SUSPENDING
|
|
elif operation == "resume":
|
|
return InstanceState.RESUMING
|
|
|
|
# Проверка ожидающей операции
|
|
if instance_data.get("operationIsPending"):
|
|
operations = instance_data.get("operations", [])
|
|
if operations:
|
|
last_op = operations[-1]
|
|
if not last_op.get("dtStart"):
|
|
return InstanceState.OPERATION_PENDING
|
|
|
|
# Проверка приостановки
|
|
if instance_data.get("isSuspended"):
|
|
return InstanceState.SUSPENDED
|
|
|
|
# Проверка основного статуса
|
|
if instance_data.get("explainedStatus") == "running":
|
|
return InstanceState.RUNNING
|
|
|
|
# Проверка ошибок в операциях
|
|
operations = instance_data.get("operations", [])
|
|
if operations:
|
|
last_op = operations[-1]
|
|
if last_op.get("isSuccessful") is False:
|
|
return InstanceState.OPERATION_FAILED
|
|
|
|
return InstanceState.UNKNOWN
|
|
|
|
def check_instance(self, resource_name: str, service_type: Optional[str] = None) -> Optional[InstanceInfo]:
|
|
"""
|
|
Проверить статус инстанса
|
|
|
|
Args:
|
|
resource_name: Имя ресурса в манифесте
|
|
service_type: Тип сервиса (опционально)
|
|
|
|
Returns:
|
|
InstanceInfo с информацией о статусе
|
|
"""
|
|
# Поиск инстанса
|
|
instance_short = self.find_instance(resource_name, service_type)
|
|
|
|
if not instance_short:
|
|
return InstanceInfo(
|
|
uid="unknown",
|
|
name=resource_name,
|
|
state=InstanceState.NOT_CREATED,
|
|
is_created=False,
|
|
is_deleted=False,
|
|
is_suspended=False,
|
|
uptime_seconds=0,
|
|
operation_in_progress=False,
|
|
operation_pending=False,
|
|
last_operation=None,
|
|
last_operation_status=None,
|
|
error_log=None,
|
|
timestamp=datetime.now().isoformat()
|
|
)
|
|
|
|
# Получить полную информацию
|
|
instance_uid = instance_short.get("instanceUid")
|
|
instance_full = self.get_instance_details(instance_uid)
|
|
|
|
if not instance_full:
|
|
instance_full = instance_short
|
|
|
|
# Определить состояние
|
|
state = self.determine_state(instance_full)
|
|
|
|
# Извлечь информацию об операции
|
|
operations = instance_full.get("operations", [])
|
|
last_operation = None
|
|
last_operation_status = None
|
|
error_log = None
|
|
|
|
if operations:
|
|
last_op = operations[-1]
|
|
last_operation = last_op.get("operation")
|
|
last_operation_status = "success" if last_op.get("isSuccessful") == True else "pending" if last_op.get("isSuccessful") is None else "failed"
|
|
error_log = last_op.get("errorLog")
|
|
|
|
return InstanceInfo(
|
|
uid=instance_uid,
|
|
name=resource_name,
|
|
state=state,
|
|
is_created=instance_full.get("isCreated", False),
|
|
is_deleted=instance_full.get("isDeleted", False),
|
|
is_suspended=instance_full.get("isSuspended", False),
|
|
uptime_seconds=instance_full.get("uptime", 0),
|
|
operation_in_progress=instance_full.get("operationIsInProgress", False),
|
|
operation_pending=instance_full.get("operationIsPending", False),
|
|
last_operation=last_operation,
|
|
last_operation_status=last_operation_status,
|
|
error_log=error_log,
|
|
timestamp=datetime.now().isoformat()
|
|
)
|
|
|
|
def format_output(self, info: InstanceInfo) -> str:
|
|
"""Форматировать результат для вывода"""
|
|
|
|
# Выбрать эмодзи в зависимости от состояния
|
|
emoji_map = {
|
|
InstanceState.NOT_CREATED: "⚪",
|
|
InstanceState.CREATING: "🔄",
|
|
InstanceState.RUNNING: "✅",
|
|
InstanceState.MODIFYING: "🔧",
|
|
InstanceState.SUSPENDING: "⏸️",
|
|
InstanceState.SUSPENDED: "⏹️",
|
|
InstanceState.RESUMING: "▶️",
|
|
InstanceState.DELETING: "🗑️",
|
|
InstanceState.DELETED: "❌",
|
|
InstanceState.OPERATION_PENDING: "⏳",
|
|
InstanceState.OPERATION_FAILED: "💥",
|
|
InstanceState.UNKNOWN: "❓",
|
|
}
|
|
|
|
emoji = emoji_map.get(info.state, "?")
|
|
|
|
result = f"{emoji} {info.name}: {info.state.value.upper()}"
|
|
|
|
if info.uid != "unknown":
|
|
result += f" (UID: {info.uid[:8]}...)"
|
|
|
|
if info.last_operation:
|
|
result += f" | Last op: {info.last_operation} ({info.last_operation_status})"
|
|
|
|
if info.uptime_seconds > 0:
|
|
uptime_hours = info.uptime_seconds / 3600
|
|
result += f" | Uptime: {uptime_hours:.1f}h"
|
|
|
|
if info.error_log:
|
|
result += f"\n ⚠️ Error: {info.error_log[:100]}"
|
|
|
|
return result
|
|
|
|
|
|
def main():
|
|
"""Главная функция"""
|
|
|
|
# Пример использования
|
|
token_file = "/home/naeel/remote_dev/terraform/secrets/prod.token"
|
|
|
|
try:
|
|
with open(token_file, 'r') as f:
|
|
token = f.read().strip()
|
|
except FileNotFoundError:
|
|
print(f"❌ Токен не найден: {token_file}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
checker = CloudInstanceChecker(token)
|
|
|
|
# Примеры ресурсов для проверки из PROD_STAND/PG1/resources.tf
|
|
resources_to_check = [
|
|
("pg-testing", "nubes_postgres"),
|
|
("lucy11", "nubes_lucee"),
|
|
("node_0111", "nubes_nodejs"),
|
|
("baba-buck-test2", "nubes_s3bucket"),
|
|
]
|
|
|
|
print("=" * 80)
|
|
print("🔍 Cloud Instance Status Report")
|
|
print("=" * 80)
|
|
print()
|
|
|
|
for resource_name, service_type in resources_to_check:
|
|
print(f"Checking: {resource_name}")
|
|
info = checker.check_instance(resource_name, service_type)
|
|
if info:
|
|
print(checker.format_output(info))
|
|
print()
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|