feat(pipeline): уведомления CI/CD — PipelineWatcher, CI загрузок на Gitea Actions
CI / ruff · mypy · pytest (pull_request) Failing after 3m40s
CI / ruff · mypy · pytest (pull_request) Failing after 3m40s
- app/pipeline: протокол CIProvider, клиент Gitea Actions (прогон по head_sha, упавший шаг, лог джоба, ретраи), PipelineWatcher — опрос только при незавершённых push, сообщение о push обновляется CI ⏳ → ✅/❌ → CD 🚀/⛔, автору — отдельные уведомления - деплой сервисов — только после зелёного CI; старый push не откатывает сервис, который развернёт более новый; после рестарта бот продолжает с того же места - CI_PROVIDER=none — прежнее поведение (деплой сразу после push) - шаблон tgit-uploads: .gitea/workflows/check.yml; .gitea/ защищён от правок из бота - миграция 0002: старые push помечены skipped, чтобы не слать им «таймаут CI» - ADR-016, ARCHITECTURE §2.5/§5.2, README, DEPLOY, .env.example Refs #11 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+24
-14
@@ -13,7 +13,7 @@ from pathlib import Path
|
||||
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from app.db.models import ChangeOp, Push, User, utcnow
|
||||
from app.db.models import ChangeOp, PipelineStatus, Push, User, utcnow
|
||||
from app.db.session import SessionMaker
|
||||
from app.repo.drafts import DraftView
|
||||
from app.repo.git import GitError, UploadsRepo
|
||||
@@ -67,21 +67,31 @@ class Publisher:
|
||||
return PushResult(PushStatus.FAILED)
|
||||
|
||||
async def record(
|
||||
self, member: User, result: PushResult, message: str, draft: DraftView, chat_id: int, message_id: int
|
||||
) -> None:
|
||||
self,
|
||||
member: User,
|
||||
result: PushResult,
|
||||
message: str,
|
||||
draft: DraftView,
|
||||
chat_id: int,
|
||||
message_id: int,
|
||||
status: tuple[PipelineStatus, PipelineStatus],
|
||||
) -> Push:
|
||||
"""status — (ci, cd) to start from; the PipelineWatcher takes it from there (#11)."""
|
||||
assert result.sha is not None
|
||||
push = Push(
|
||||
user_id=member.tg_id,
|
||||
sha=result.sha,
|
||||
message=message,
|
||||
paths=[c.path for c in draft.changes],
|
||||
chat_id=chat_id,
|
||||
message_id=message_id,
|
||||
ci_status=status[0],
|
||||
cd_status=status[1],
|
||||
created_at=self._clock(),
|
||||
)
|
||||
async with self._sessions.begin() as s:
|
||||
s.add(
|
||||
Push(
|
||||
user_id=member.tg_id,
|
||||
sha=result.sha,
|
||||
message=message,
|
||||
paths=[c.path for c in draft.changes],
|
||||
chat_id=chat_id,
|
||||
message_id=message_id,
|
||||
created_at=self._clock(),
|
||||
)
|
||||
)
|
||||
s.add(push)
|
||||
return push
|
||||
|
||||
async def _push(self, member: User, draft: DraftView, message: str, overwrite: bool) -> PushResult:
|
||||
repo = self._repo
|
||||
|
||||
@@ -8,7 +8,7 @@ from dataclasses import dataclass
|
||||
MAX_FILE_BYTES = 100 * 1024
|
||||
NAME = re.compile(r"^[A-Za-z0-9._-]{1,100}$")
|
||||
# Bot users can't touch the CI of the uploads repo or its helper scripts (SPEC C-16, ADR-014)
|
||||
PROTECTED_PREFIXES = (".sourcecraft/", ".tgit/", ".git/")
|
||||
PROTECTED_PREFIXES = (".sourcecraft/", ".gitea/", ".tgit/", ".git/")
|
||||
PROTECTED_FILES = {"ruff.toml"}
|
||||
_FENCE = re.compile(r"^\s*```[\w+-]*[ \t]*\n(.*?)\n?```\s*$", re.DOTALL)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user