flyway check
This commit is contained in:
@@ -1,22 +1,73 @@
|
||||
# Deploy na single server: deploy.sh volá hostovský Docker přes /var/run/docker.sock (bez DinD).
|
||||
# CI: immutability + Flyway validate (JDBC na staging / sdílenou DB). Deploy na main až po úspěchu.
|
||||
# Job bez container: — hostovský docker + git (stejně jako deploy).
|
||||
# Gitea secrets: EMS_CI_FLYWAY_URL (jdbc:postgresql://…/ems). Volitelně EMS_CI_FLYWAY_USER, EMS_CI_FLYWAY_PASSWORD.
|
||||
# Runner: container.valid_volumes pro /var/run/docker.sock (viz docs/deployment-self-hosted.md).
|
||||
#
|
||||
# Job běží v kontejneru — /opt/ems-deploy a sock musí být přimountované (viz container.volumes).
|
||||
# V /opt/gitea-stack/runner/config.yaml nastav container.valid_volumes na stejné cesty.
|
||||
# Sladit `runs-on` s labely registrace runneru (výchozí: self-hosted).
|
||||
#
|
||||
# Spuštění: push na větev main (včetně merge PR do main — merge v Gitea/Git je stále push na main).
|
||||
# Nepřidávat paralelně pull_request:closed — při merge by běžel deploy dvakrát (push + PR).
|
||||
# Spuštění deploye: push na main. Nepřidávat paralelně pull_request:closed — při merge by běžel deploy dvakrát.
|
||||
|
||||
name: deploy
|
||||
name: CI and deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- feature/**
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
migration-check:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout
|
||||
env:
|
||||
TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -eu
|
||||
su="${{ github.server_url }}"
|
||||
case "$su" in
|
||||
https://*) clone_url="https://oauth2:${TOKEN}@${su#https://}" ;;
|
||||
http://*) clone_url="http://oauth2:${TOKEN}@${su#http://}" ;;
|
||||
*) echo "unknown github.server_url: $su"; exit 1 ;;
|
||||
esac
|
||||
clone_url="${clone_url}/${{ github.repository }}.git"
|
||||
git init
|
||||
git remote add origin "$clone_url"
|
||||
git fetch --depth=64 origin "${{ github.sha }}"
|
||||
git checkout -qf FETCH_HEAD
|
||||
git remote set-branches origin 'main' || true
|
||||
git fetch --depth=64 origin main:refs/remotes/origin/main || true
|
||||
|
||||
- name: Repo layout
|
||||
run: |
|
||||
test -f docker-compose.yml
|
||||
test -f deploy/docker-compose.yml
|
||||
test -x deploy/deploy.sh
|
||||
test -x scripts/ci_check_migration_immutability.sh
|
||||
test -x scripts/ci_flyway_validate_remote.sh
|
||||
|
||||
- name: Migration immutability (vs PR base or main)
|
||||
env:
|
||||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
run: |
|
||||
set -eu
|
||||
BASE='origin/main'
|
||||
if [ -n "${PR_BASE_SHA:-}" ]; then
|
||||
BASE="$PR_BASE_SHA"
|
||||
git fetch --no-tags --depth=256 origin "$BASE" || true
|
||||
fi
|
||||
./scripts/ci_check_migration_immutability.sh "$BASE"
|
||||
|
||||
- name: Flyway validate (remote DB)
|
||||
env:
|
||||
EMS_CI_FLYWAY_URL: ${{ secrets.EMS_CI_FLYWAY_URL }}
|
||||
EMS_CI_FLYWAY_USER: ${{ secrets.EMS_CI_FLYWAY_USER }}
|
||||
EMS_CI_FLYWAY_PASSWORD: ${{ secrets.EMS_CI_FLYWAY_PASSWORD }}
|
||||
run: ./scripts/ci_flyway_validate_remote.sh
|
||||
|
||||
deploy:
|
||||
needs: migration-check
|
||||
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Show execution context
|
||||
@@ -27,9 +78,8 @@ jobs:
|
||||
ls -ld /opt/ems-deploy
|
||||
|
||||
- name: Run deploy script
|
||||
run: |
|
||||
bash /opt/ems-deploy/deploy.sh
|
||||
|
||||
run: bash /opt/ems-deploy/deploy.sh
|
||||
|
||||
# Alternativa: runner v Dockeru bez přístupu k hostu — odkomentovat a upravit SERVER + secrets.
|
||||
# deploy-ssh:
|
||||
# runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- feature/**
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
smoke-test:
|
||||
# Stejný label jako deploy.yml — výchozí act_runner má typicky jen `self-hosted`.
|
||||
runs-on: self-hosted
|
||||
# Výchozí job image často nemá Node → `actions/checkout@v4` padá na „Cannot find: node“.
|
||||
# alpine/git je malý a stačí na shallow clone přes token (Gitea = GitHub-kompatibilní kontext).
|
||||
container:
|
||||
image: alpine/git:latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
env:
|
||||
TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -eu
|
||||
su="${{ github.server_url }}"
|
||||
case "$su" in
|
||||
https://*) clone_url="https://oauth2:${TOKEN}@${su#https://}" ;;
|
||||
http://*) clone_url="http://oauth2:${TOKEN}@${su#http://}" ;;
|
||||
*) echo "unknown github.server_url: $su"; exit 1 ;;
|
||||
esac
|
||||
clone_url="${clone_url}/${{ github.repository }}.git"
|
||||
git init
|
||||
git remote add origin "$clone_url"
|
||||
git fetch --depth=1 origin "${{ github.sha }}"
|
||||
git checkout -qf FETCH_HEAD
|
||||
|
||||
- name: Repo layout
|
||||
run: |
|
||||
test -f docker-compose.yml
|
||||
test -f deploy/docker-compose.yml
|
||||
test -x deploy/deploy.sh
|
||||
|
||||
- name: Runner info
|
||||
run: |
|
||||
uname -a
|
||||
pwd
|
||||
ls -la
|
||||
@@ -192,3 +192,4 @@ Specifikace z `docs/02-architecture.md`, modulových docs a komentářů v `plan
|
||||
- Výkon **W**, energie **Wh**, ceny **Kč/kWh**; čas v DB **`TIMESTAMPTZ` (UTC)**.
|
||||
- NIKDY neupravuj existující V__ migrační soubory po jejich aplikaci na DB.
|
||||
- Pokud je potřeba opravit chybu ve verzované migraci, vytvoř novou V{N+1} migraci.
|
||||
- Deploy: `flyway validate` před `migrate` ([`deploy/deploy.sh`](deploy/deploy.sh)). Lokálně `./scripts/flyway_validate_local.sh`; CI viz [`docs/deployment-self-hosted.md`](docs/deployment-self-hosted.md) a `scripts/ci_check_migration_immutability.sh`.
|
||||
|
||||
@@ -50,9 +50,12 @@ install -m 0644 "$COMPOSE_SRC" "$COMPOSE_DST"
|
||||
log "docker compose config (validate)"
|
||||
docker compose -f "$COMPOSE_DST" --env-file "$ENV_FILE" config >/dev/null
|
||||
|
||||
# Vždy spustit migrace z aktuálního ./app/db (mount ve flyway službě). Čisté `up -d` často
|
||||
# znovu nespustí jednorázový kontejner flyway, takže změny jen v R__/*.sql by se neaplikovaly.
|
||||
# Při chybě je v logu jobu celý Flyway výstup (konkrétní SQL / řádek).
|
||||
# Flyway: nejdřív validate (soubory vs flyway_schema_history na DB), pak migrate.
|
||||
# Čisté `up -d` často znovu nespustí jednorázový kontejner flyway — změny jen v R__/*.sql přes migrate.
|
||||
# Při chybě je v logu celý Flyway výstup (konkrétní SQL / řádek).
|
||||
log "Flyway validate"
|
||||
docker compose -f "$COMPOSE_DST" --env-file "$ENV_FILE" run --rm flyway validate
|
||||
|
||||
log "Flyway migrate (docker compose run --rm flyway)"
|
||||
docker compose -f "$COMPOSE_DST" --env-file "$ENV_FILE" run --rm flyway migrate
|
||||
|
||||
|
||||
@@ -43,10 +43,12 @@ flowchart LR
|
||||
dev[Dev laptop] -->|git push| gitea[Gitea main]
|
||||
gitea --> act[Gitea Actions]
|
||||
act --> runner[act_runner + Docker]
|
||||
runner --> job[Job container Alpine + sock + /opt mount]
|
||||
runner --> ci[migration-check validate]
|
||||
ci -->|needs ok| job[deploy job + /opt mount]
|
||||
job --> sh[deploy.sh]
|
||||
sh --> git[git fetch / reset main]
|
||||
sh --> sync[install compose.yml]
|
||||
sh --> fly[flyway validate then migrate]
|
||||
sh --> dc["docker compose build && up -d"]
|
||||
dc --> hostd[Host Docker Engine]
|
||||
hostd --> ems[EMS stack]
|
||||
@@ -55,7 +57,7 @@ flowchart LR
|
||||
1. Vývoj lokálně, merge do `main`, push na `git.vojacek.eu:2222`.
|
||||
2. Workflow `.gitea/workflows/deploy.yml` (label `self-hosted`) spustí job.
|
||||
3. Job kontejner má `docker.sock` a rw mount `/opt/ems-deploy`; nainstaluje `git`, `bash`, `docker-cli`, `docker-cli-compose` (Alpine).
|
||||
4. `/opt/ems-deploy/deploy.sh`: `flock`, `git` v `app/`, sync `docker-compose.yml`, `docker compose config`, `build`, `up -d`, `image prune -f`.
|
||||
4. `/opt/ems-deploy/deploy.sh`: `flock`, `git` v `app/`, sync `docker-compose.yml`, `docker compose config`, **`flyway validate`** (soubory vs `flyway_schema_history` na DB), **`flyway migrate`**, `build`, `up -d`, `image prune -f`.
|
||||
|
||||
Build probíhá na **hostovském** Dockeru (stejný daemon jako Gitea stack), bez DinD.
|
||||
|
||||
@@ -123,14 +125,26 @@ Produkční compose mapuje frontend na **`127.0.0.1:8080`**, backend na **`127.0
|
||||
|
||||
---
|
||||
|
||||
## 7. CI: test vs deploy
|
||||
## 7. CI a deploy (jeden workflow)
|
||||
|
||||
| Workflow | Účel | Poznámka |
|
||||
|----------|------|----------|
|
||||
| `.gitea/workflows/test.yml` | Smoke (soubory, layout) | `runs-on: self-hosted` + job `container: alpine/git` a checkout přes `git fetch` (ne `actions/checkout@v4` — ta potřebuje **Node** v job kontejneru, výchozí image act_runneru ho nemá → „Cannot find: node in PATH“). |
|
||||
| `.gitea/workflows/deploy.yml` | Deploy po pushi na `main` | Job kontejner + mounty + viz sekce 5. |
|
||||
| Soubor | Účel |
|
||||
|--------|------|
|
||||
| `.gitea/workflows/deploy.yml` | **`migration-check`** na `pull_request` a `push` (`main`, `feature/**`): checkout přes `git fetch` (bez Node), skript **neměnnosti** `scripts/ci_check_migration_immutability.sh` (proti `origin/main` nebo base SHA u PR), **`flyway validate`** proti DB z JDBC (viz secrets). **`deploy`** jen při `push` na `main`, **`needs: migration-check`** — pak `/opt/ems-deploy/deploy.sh`. |
|
||||
|
||||
`workflow_dispatch` na deploy umožňuje ruční opakování bez prázdného commitu.
|
||||
**Gitea secrets (repo):**
|
||||
|
||||
| Secret | Účel |
|
||||
|--------|------|
|
||||
| `EMS_CI_FLYWAY_URL` | JDBC na staging / sdílenou DB s aktuální `flyway_schema_history` (např. `jdbc:postgresql://host:5432/ems`). Pokud **není** nastaveno, krok remote validate se **přeskočí** (varování v logu); immutability check pořád běží. |
|
||||
| `EMS_CI_FLYWAY_USER` / `EMS_CI_FLYWAY_PASSWORD` | Volitelně, pokud nejsou v URL. |
|
||||
|
||||
Job **nemá** `container:` — potřebuje hostovský `docker` + `git` (stejně jako deploy). Runner: `valid_volumes` pro `/var/run/docker.sock` (sekce 5).
|
||||
|
||||
**Lokálně před commitem:** z kořene repa s rozjetým Postgres z `docker-compose.yml` a aspoň jednou proběhlým migrate spusť `./scripts/flyway_validate_local.sh` (`flyway validate` vůči lokální DB).
|
||||
|
||||
**Flyway `validate` vs `migrate`:** `validate` nekontroluje „dry-run“ celého SQL nové pending migrace; ověří hlavně shodu **již zapsaných** řádků ve `flyway_schema_history` se soubory v repu (checksumy atd.) — proto musí běžet proti **běžící** DB. `migrate` teprve aplikuje pending skripty.
|
||||
|
||||
`workflow_dispatch` na větvi `main` spustí `migration-check` a potom i **`deploy`** (stejná podmínka jako u push na `main`).
|
||||
|
||||
---
|
||||
|
||||
|
||||
63
scripts/ci_check_migration_immutability.sh
Executable file
63
scripts/ci_check_migration_immutability.sh
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fail if any versioned migration Vxxx already present on the base branch is modified
|
||||
# in the current HEAD (Flyway checksum mismatch risk). See CLAUDE.md (never rewrite applied V__).
|
||||
#
|
||||
# Usage: ci_check_migration_immutability.sh [BASE]
|
||||
# BASE — ref or full SHA (default: origin/main). For PRs pass base commit SHA.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [[ -z "$ROOT" ]]; then
|
||||
echo "ERROR: not inside a git repository"
|
||||
exit 1
|
||||
fi
|
||||
cd "$ROOT"
|
||||
|
||||
BASE="${1:-origin/main}"
|
||||
|
||||
if [[ "$BASE" =~ ^[0-9a-f]{7,40}$ ]]; then
|
||||
if ! git cat-file -e "${BASE}^{commit}" 2>/dev/null; then
|
||||
git fetch --no-tags --depth=256 origin "$BASE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! git rev-parse --verify -q "${BASE}^{commit}" >/dev/null; then
|
||||
echo "ERROR: cannot resolve base revision: $BASE (fetch origin main?)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
max_ver_on_base() {
|
||||
git ls-tree -r --name-only "$BASE" -- db/migration 2>/dev/null \
|
||||
| grep -E '^db/migration/V[0-9]+__' \
|
||||
| sed -n 's/^db\/migration\/V\([0-9][0-9]*\)__.*/\1/p' \
|
||||
| sort -n \
|
||||
| tail -1
|
||||
}
|
||||
|
||||
MAX="$(max_ver_on_base || true)"
|
||||
MAX="${MAX:-0}"
|
||||
|
||||
mapfile -t CHANGED < <(git diff --name-only "${BASE}...HEAD" -- 'db/migration/V*.sql' 2>/dev/null || true)
|
||||
|
||||
if [[ ${#CHANGED[@]} -eq 0 ]]; then
|
||||
echo "ci_check_migration_immutability: no versioned migration changes vs $BASE (ok)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
err=0
|
||||
for f in "${CHANGED[@]}"; do
|
||||
[[ "$f" =~ ^db/migration/V([0-9]+)__ ]] || continue
|
||||
ver="${BASH_REMATCH[1]}"
|
||||
ver=$((10#$ver))
|
||||
if (( ver <= MAX )); then
|
||||
printf 'ERROR: do not modify historical migration %s (version %03d <= max on base %03d from %s). Add a new V* migration instead. See CLAUDE.md.\n' \
|
||||
"$f" "$ver" "$MAX" "$BASE" >&2
|
||||
err=1
|
||||
fi
|
||||
done
|
||||
|
||||
if (( err )); then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "ci_check_migration_immutability: ok (vs $BASE, max V on base=$MAX)"
|
||||
36
scripts/ci_flyway_validate_remote.sh
Executable file
36
scripts/ci_flyway_validate_remote.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# Flyway validate using migration files from repo root; JDBC from env (staging / CI DB).
|
||||
# Env: EMS_CI_FLYWAY_URL — if unset, skips (warn). Optional: EMS_CI_FLYWAY_USER, EMS_CI_FLYWAY_PASSWORD, FLYWAY_IMAGE
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${EMS_CI_FLYWAY_URL:-}" ]]; then
|
||||
echo "WARN: EMS_CI_FLYWAY_URL not set — skipping remote Flyway validate (set Gitea secret for CI)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
IMG="${FLYWAY_IMAGE:-flyway/flyway:12}"
|
||||
|
||||
args=(
|
||||
run --rm
|
||||
-v "$ROOT/db/migration:/flyway/sql/migration"
|
||||
-v "$ROOT/db/routines:/flyway/sql/routines"
|
||||
-v "$ROOT/db/views:/flyway/sql/views"
|
||||
-e "FLYWAY_URL=${EMS_CI_FLYWAY_URL}"
|
||||
-e "FLYWAY_SCHEMAS=ems"
|
||||
-e "FLYWAY_LOCATIONS=filesystem:/flyway/sql/migration,filesystem:/flyway/sql/routines,filesystem:/flyway/sql/views"
|
||||
)
|
||||
|
||||
if [[ -n "${EMS_CI_FLYWAY_USER:-}" ]]; then
|
||||
args+=(-e "FLYWAY_USER=${EMS_CI_FLYWAY_USER}")
|
||||
fi
|
||||
if [[ -n "${EMS_CI_FLYWAY_PASSWORD:-}" ]]; then
|
||||
args+=(-e "FLYWAY_PASSWORD=${EMS_CI_FLYWAY_PASSWORD}")
|
||||
fi
|
||||
|
||||
args+=("$IMG" validate)
|
||||
|
||||
echo "Running Flyway validate against remote DB (schema ems)…"
|
||||
docker "${args[@]}"
|
||||
11
scripts/flyway_validate_local.sh
Executable file
11
scripts/flyway_validate_local.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Flyway validate against local Postgres from repo-root docker-compose.
|
||||
# Requires existing flyway_schema_history (run migrate at least once). Does not run migrate.
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
if [[ ! -f .env ]]; then
|
||||
echo "ERROR: missing .env (copy from .env.example)"
|
||||
exit 1
|
||||
fi
|
||||
docker compose --env-file .env run --rm flyway validate
|
||||
Reference in New Issue
Block a user