init: docker-migrate universal migration tool
This commit is contained in:
+156
@@ -0,0 +1,156 @@
|
||||
# -*- 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"])
|
||||
Reference in New Issue
Block a user