Временно отключить production deploy и оставить диагностику fingerprint #56
No files matched your search
+18
-1
@@ -6,6 +6,11 @@ on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
diagnostic_only:
|
||||
description: 'Print key fingerprint; production deployment is temporarily disabled'
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -27,7 +32,9 @@ jobs:
|
||||
- run: sh -n infra/install-postgres-backup-cron.sh
|
||||
|
||||
deploy:
|
||||
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch'
|
||||
# Production deployment is disabled until separate Workers deployment access is installed.
|
||||
name: Fingerprint diagnostics (production deployment temporarily disabled)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.diagnostic_only == 'true'
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
@@ -42,6 +49,7 @@ jobs:
|
||||
id: prepare
|
||||
shell: bash
|
||||
run: |
|
||||
set +x
|
||||
set -euo pipefail
|
||||
backend_version="$(sed -n 's/^BACKEND_VERSION = "\(.*\)"$/\1/p' app/version.py)"
|
||||
case "$backend_version" in
|
||||
@@ -55,7 +63,15 @@ jobs:
|
||||
env:
|
||||
PROD_SSH_KEY: ${{ secrets.PROD_SSH_KEY }}
|
||||
PROD_KNOWN_HOSTS: ${{ secrets.PROD_KNOWN_HOSTS }}
|
||||
- name: Deployment key fingerprint
|
||||
shell: bash
|
||||
run: |
|
||||
set +x
|
||||
set -euo pipefail
|
||||
trap 'rm -f "$RUNNER_TEMP/deploy_key" "$RUNNER_TEMP/known_hosts"' EXIT
|
||||
ssh-keygen -y -P '' -f "$RUNNER_TEMP/deploy_key" | ssh-keygen -lf - -E sha256 | awk '{print $2}'
|
||||
- name: Upload release
|
||||
if: ${{ false }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -68,6 +84,7 @@ jobs:
|
||||
PROD_HOST: ${{ secrets.PROD_HOST }}
|
||||
PROD_USER: ${{ secrets.PROD_USER }}
|
||||
- name: Deploy and verify
|
||||
if: ${{ false }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -128,7 +128,9 @@ docker compose exec postgres pg_dump -U miem miem_workers > backup.sql
|
||||
docker compose down
|
||||
```
|
||||
|
||||
Production deploy выполняется job `deploy` workflow `CI` после успешного `test` на push в `main` или вручную. Для него нужны Actions secrets `PROD_HOST`, `PROD_USER`, `PROD_SSH_KEY` и `PROD_KNOWN_HOSTS`; `.env` и ключи в репозиторий не добавляются. PostgreSQL backup выполняется на VPS раз в неделю user cron deploy-пользователя через `infra/postgres-backup.sh`: `pg_dumpall` сохраняет весь кластер в `~/backups/postgres/postgres-latest.sql`. Новый dump сначала создаётся во временный файл и заменяет старый только после успешного завершения; предыдущие dump-файлы `miem_workers` удаляются после успешного создания нового. Одновременно выполняется только один production deploy.
|
||||
Production deployment временно отключён до установки отдельного deployment-доступа Workers. Workflow `CI` запускает тесты на push в `main` и pull_request в `main`; шаги Upload release и Deploy and verify безусловно отключены для всех событий и значений input. Стандартные имена deployment secrets: `PROD_HOST`, `PROD_USER`, `PROD_SSH_KEY`, `PROD_KNOWN_HOSTS`; `.env` и ключи в репозиторий не добавляются. PostgreSQL backup выполняется на VPS раз в неделю user cron deploy-пользователя через `infra/postgres-backup.sh`: `pg_dumpall` сохраняет весь кластер в `~/backups/postgres/postgres-latest.sql`. Новый dump сначала создаётся во временный файл и заменяет старый только после успешного завершения; предыдущие dump-файлы `miem_workers` удаляются после успешного создания нового. Эта правка не меняет существующий backup cron.
|
||||
|
||||
Ручной запуск с `diagnostic_only=true` (по умолчанию) выводит только SHA256 fingerprint ключа `PROD_SSH_KEY`, без подключения к VPS. Публичный ключ передаётся через pipe, не сохраняется в файл или artifacts; shell tracing отключён. При `diagnostic_only=false` диагностический job пропускается: это значение также не разрешает deployment. Возврат production deployment требует отдельной reviewed правки после установки нового доступа.
|
||||
|
||||
## Проверки
|
||||
|
||||
@@ -141,7 +143,7 @@ node --check app/static/admin.js
|
||||
|
||||
Тесты с данными используют временную SQLite; отдельные API smoke-тесты запускают приложение с его текущей конфигурацией. Браузерная проверка: `pip install playwright`, `python -m playwright install chromium`, затем `python tests/browser_admin.py`. Она проверяет реальный рендеринг страниц, клавиатуру, диалог, фильтры и восстановление прогресса после ошибки. Скриншоты сохраняются во временную папку; путь выводится в конце.
|
||||
|
||||
Версия сервиса: `0.10.4`. Админка всегда показывает версии backend и frontend в footer.
|
||||
Версия сервиса: `0.10.6`. Админка всегда показывает версии backend и frontend в footer.
|
||||
|
||||
## Разовый импорт публикаций
|
||||
|
||||
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
APP_VERSION = "0.10.4"
|
||||
FRONTEND_VERSION = "0.10.4"
|
||||
BACKEND_VERSION = "0.10.4"
|
||||
APP_VERSION = "0.10.6"
|
||||
FRONTEND_VERSION = "0.10.6"
|
||||
BACKEND_VERSION = "0.10.6"
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "miem-workers"
|
||||
version = "0.10.4"
|
||||
version = "0.10.6"
|
||||
description = "MIEM employees parser, admin API, and web admin"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ def test_health_returns_versions():
|
||||
response = TestClient(app).get("/api/health")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["backend_version"] == "0.10.4"
|
||||
assert response.json()["backend_version"] == "0.10.6"
|
||||
|
||||
|
||||
def test_api_employees_and_stats_require_admin_session():
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
WORKFLOW = (Path(__file__).parents[1] / ".gitea/workflows/ci.yml").read_text()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("keygen_exit", [0, 1])
|
||||
def test_fingerprint_output_and_cleanup(tmp_path, keygen_exit):
|
||||
step = WORKFLOW.split(" - name: Deployment key fingerprint\n", 1)[1]
|
||||
step = step.split(" - name:", 1)[0]
|
||||
script = textwrap.dedent(step.split(" run: |\n", 1)[1])
|
||||
keygen = tmp_path / "ssh-keygen"
|
||||
keygen.write_text(
|
||||
"#!/bin/sh\n"
|
||||
'case "$1" in\n'
|
||||
'-y) printf "PUBLIC_MATERIAL\\n"; exit "$KEYGEN_EXIT" ;;\n'
|
||||
'-lf) cat >/dev/null; printf "256 SHA256:test comment (ED25519)\\n" ;;\n'
|
||||
'*) exit 99 ;;\n'
|
||||
"esac\n"
|
||||
)
|
||||
keygen.chmod(0o700)
|
||||
(tmp_path / "deploy_key").write_text("PRIVATE_MATERIAL")
|
||||
(tmp_path / "known_hosts").write_text("HOST_MATERIAL")
|
||||
result = subprocess.run(
|
||||
["bash", "-x", "-c", script], capture_output=True, text=True,
|
||||
env={**os.environ, "RUNNER_TEMP": str(tmp_path),
|
||||
"PATH": f"{tmp_path}:{os.environ['PATH']}",
|
||||
"KEYGEN_EXIT": str(keygen_exit)},
|
||||
)
|
||||
assert (result.returncode == 0) == (keygen_exit == 0)
|
||||
if keygen_exit == 0:
|
||||
assert result.stdout == "SHA256:test\n"
|
||||
assert "MATERIAL" not in result.stdout + result.stderr
|
||||
assert "comment" not in result.stdout + result.stderr
|
||||
assert not (tmp_path / "deploy_key").exists()
|
||||
assert not (tmp_path / "known_hosts").exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("event", ["push", "pull_request", "workflow_dispatch"])
|
||||
@pytest.mark.parametrize("diagnostic_only", ["true", "false", ""])
|
||||
def test_launch_conditions(event, diagnostic_only):
|
||||
job = WORKFLOW.split("\n deploy:\n", 1)[1]
|
||||
condition = re.search(r"^ if: (.+)$", job, re.MULTILINE).group(1)
|
||||
terms = re.findall(r"(github\.[a-z_.]+) == '([^']*)'", condition)
|
||||
assert condition == " && ".join(f"{name} == '{value}'" for name, value in terms)
|
||||
context = {"github.event_name": event,
|
||||
"github.event.inputs.diagnostic_only": diagnostic_only}
|
||||
runs = all(context[name] == value for name, value in terms)
|
||||
assert runs == (event == "workflow_dispatch" and diagnostic_only == "true")
|
||||
|
||||
|
||||
def test_both_vps_steps_are_unconditionally_disabled():
|
||||
for name in ("Upload release", "Deploy and verify"):
|
||||
step = WORKFLOW.split(f" - name: {name}\n", 1)[1]
|
||||
assert step.startswith(" if: ${{ false }}\n")
|
||||
|
||||
|
||||
def test_push_and_pull_request_keep_tests():
|
||||
triggers = WORKFLOW.split("\njobs:\n", 1)[0]
|
||||
for event in ("push", "pull_request"):
|
||||
assert f" {event}:\n branches: [main]\n" in triggers
|
||||
test_job = WORKFLOW.split("\n test:\n", 1)[1].split("\n deploy:\n", 1)[0]
|
||||
assert " if:" not in test_job
|
||||
assert "python -m pytest -q" in test_job
|
||||
Reference in new issue
Block a user