Files
replypier/deploy/server/updater/state.py
T

70 lines
2.3 KiB
Python
Raw Normal View History

"""Persistent state shared by the independent update services."""
from __future__ import annotations
import fcntl
import json
import os
from contextlib import contextmanager
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Iterator
CONFIG_FILE = Path('/etc/replypier-updater/config.json')
BUSY = {'queued', 'preparing', 'building', 'backing_up', 'deploying', 'migrating', 'verifying', 'rolling_back'}
def now() -> str:
return datetime.now(timezone.utc).isoformat()
def config() -> dict[str, Any]:
return json.loads(CONFIG_FILE.read_text())
def directory(cfg: dict[str, Any]) -> Path:
return Path(cfg['state_dir'])
def read(cfg: dict[str, Any]) -> dict[str, Any]:
p = directory(cfg) / 'status.json'
return json.loads(p.read_text()) if p.exists() else {
'current': {'revision': cfg['initial_revision'], 'subject': '已安装版本'},
'latest': None, 'checked_at': None, 'checking': False,
'check_error': None, 'update': {'status': 'idle', 'message': ''},
}
def write(cfg: dict[str, Any], state: dict[str, Any]) -> None:
target = directory(cfg) / 'status.json'
temp = target.with_suffix('.tmp')
with temp.open('w') as out:
json.dump(state, out, ensure_ascii=False, indent=2)
out.flush()
os.fsync(out.fileno())
temp.chmod(0o644)
temp.replace(target)
@contextmanager
def transaction(cfg: dict[str, Any]) -> Iterator[dict[str, Any]]:
directory(cfg).mkdir(parents=True, exist_ok=True)
with (directory(cfg) / 'state.lock').open('a') as lock:
fcntl.flock(lock, fcntl.LOCK_EX)
state = read(cfg)
yield state
write(cfg, state)
def progress(cfg: dict[str, Any], status: str, message: str, **values: Any) -> None:
with transaction(cfg) as state:
state['update'].update(status=status, message=message, **values)
def public_status(cfg: dict[str, Any]) -> dict[str, Any]:
state = read(cfg)
current = state['current']['revision']
latest = (state.get('latest') or {}).get('revision')
return {**state, 'configured': True, 'can_manage': True,
'repository_url': cfg['web_url'], 'branch': cfg['branch'],
'check_interval_minutes': 30,
'update_available': bool(latest and latest != current and not state.get('check_error'))}