Files
docker-migrate/core/state.py
T

157 lines
4.3 KiB
Python

# -*- coding: utf-8 -*-
"""
state.py — Управление состоянием (FSM) и resume
Состояние хранится в JSON файле рядом со скриптом (vars/lib)
"""
import os
import json
import sys
import time
from datetime import datetime
STATE_FILE = None
def _state_path():
global STATE_FILE
if STATE_FILE:
return STATE_FILE
# Ищем рядом со скриптом или в /var/lib/migrate
candidates = [
os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".migrate-state.json"),
os.path.join(os.path.expanduser("~"), ".migrate", "state.json"),
"/var/lib/migrate/state.json",
]
for c in candidates:
d = os.path.dirname(c)
if os.path.isdir(d):
STATE_FILE = c
return c
try:
os.makedirs(d, exist_ok=True)
STATE_FILE = c
return c
except PermissionError:
continue
fallback = os.path.join(os.getcwd(), ".migrate-state.json")
STATE_FILE = fallback
return fallback
def load_state():
sp = _state_path()
if os.path.isfile(sp):
try:
with open(sp, "r", encoding="utf-8") as f:
return json.load(f)
except json.JSONDecodeError:
pass
return _default_state()
def _default_state():
return {
"stage": "INIT",
"mode": None,
"manifest_path": None,
"archive_path": None,
"target_host": None,
"target_user": None,
"target_port": 22,
"last_error": None,
"completed_steps": [],
"paused": False,
"created_at": datetime.now().isoformat(),
}
def save_state(state):
sp = _state_path()
state["updated_at"] = datetime.now().isoformat()
with open(sp, "w", encoding="utf-8") as f:
json.dump(state, f, indent=2, ensure_ascii=False)
def set_stage(stage, **kwargs):
state = load_state()
state["stage"] = stage
for k, v in kwargs.items():
state[k] = v
save_state(state)
def mark_completed(step):
state = load_state()
if step not in state["completed_steps"]:
state["completed_steps"].append(step)
save_state(state)
def is_completed(step):
return step in load_state().get("completed_steps", [])
def set_error(step, stdout, stderr, suggestion=""):
state = load_state()
state["paused"] = True
state["last_error"] = {
"step": step,
"timestamp": datetime.now().isoformat(),
"stdout": stdout,
"stderr": stderr,
"suggestion": suggestion,
}
save_state(state)
def clear_error():
state = load_state()
state["paused"] = False
state["last_error"] = None
save_state(state)
def show_status():
from core.color import info, warn, error as cerror, bold, divider
state = load_state()
div = divider
print("\n")
div()
info(f"Текущий stage: {bold(state.get('stage', 'INIT'))}")
info(f"Режим: {bold(state.get('mode') or 'не выбран')}")
info(f"Completed steps: {', '.join(state.get('completed_steps', [])) or '(none)'}")
if state.get("paused"):
warn("Скрипт на паузе из-за ошибки")
if state.get("manifest_path"):
info(f"Manifest: {state['manifest_path']}")
if state.get("archive_path"):
info(f"Archive: {state['archive_path']}")
if state.get("last_error"):
err = state["last_error"]
cerror(f"Последняя ошибка на шаге: {bold(err['step'])}")
print(f" Время: {err['timestamp']}")
if err.get("suggestion"):
print(f" Рекомендация: {err['suggestion']}")
div()
def show_logs():
state = load_state()
err = state.get("last_error")
if not err:
print("Нет сохранённых ошибок")
return
from core.color import error as cerror, bold
cerror(f"=== Лог ошибки (шаг: {bold(err['step'])}) ===")
print(f"Время: {err['timestamp']}\n")
if err.get("stdout"):
print("STDOUT:")
print(err["stdout"])
if err.get("stderr"):
print("\nSTDERR:")
print(err["stderr"])
if err.get("suggestion"):
print(f"\nРекомендация:")
print(err["suggestion"])