ci: fix deploy workflow (warmup + hotpatch) #137
@@ -106,7 +106,8 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CHANGED="$(git show --name-only --pretty="" "$SHA" | sed '/^$/d' || true)"
|
||||
# merge commit safe: -m => considère les parents (liste de fichiers plus fiable)
|
||||
CHANGED="$(git show -m --name-only --pretty="" "$SHA" | sed '/^$/d' || true)"
|
||||
echo "== changed files =="
|
||||
echo "$CHANGED" | sed -n '1,240p'
|
||||
|
||||
@@ -184,27 +185,68 @@ jobs:
|
||||
[[ "${GO:-0}" == "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
||||
PROJ="${COMPOSE_PROJECT_NAME:-archicratie-web}"
|
||||
|
||||
retry_url() {
|
||||
local url="$1"; local tries="${2:-45}"; local delay="${3:-1}"
|
||||
local i=1
|
||||
while (( i <= tries )); do
|
||||
if curl -fsS --max-time 5 --retry 2 --retry-delay 0 --retry-all-errors "$url" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
echo "… wait [$i/$tries] $url"
|
||||
sleep "$delay"
|
||||
((i++))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
fetch_html() {
|
||||
local url="$1"; local tries="${2:-45}"; local delay="${3:-1}"
|
||||
local i=1
|
||||
while (( i <= tries )); do
|
||||
local html
|
||||
html="$(curl -fsS --max-time 8 --retry 2 --retry-delay 0 --retry-all-errors "$url" 2>/dev/null || true)"
|
||||
if [[ -n "$html" ]]; then
|
||||
printf "%s" "$html"
|
||||
return 0
|
||||
fi
|
||||
echo "… wait HTML [$i/$tries] $url"
|
||||
sleep "$delay"
|
||||
((i++))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
dump_container() {
|
||||
local name="$1"
|
||||
echo "== docker ps (filter=$name) =="
|
||||
docker ps -a --filter "name=$name" --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}' || true
|
||||
echo "== docker logs (tail) $name =="
|
||||
docker logs --tail 200 "$name" 2>/dev/null || true
|
||||
}
|
||||
|
||||
TS="$(date -u +%Y%m%d-%H%M%S)"
|
||||
echo "TS='$TS'" >> /tmp/deploy.env
|
||||
docker image tag archicratie-web:blue "archicratie-web:blue.BAK.${TS}" || true
|
||||
docker image tag archicratie-web:green "archicratie-web:green.BAK.${TS}" || true
|
||||
|
||||
# ✅ use cache (DO NOT --no-cache)
|
||||
docker compose -p "$PROJ" -f docker-compose.yml config >/dev/null
|
||||
|
||||
docker compose -p "$PROJ" -f docker-compose.yml build web_blue
|
||||
|
||||
# ✅ hard fix: remove existing container if name conflicts
|
||||
docker rm -f archicratie-web-blue || true
|
||||
|
||||
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_blue
|
||||
|
||||
curl -fsS "http://127.0.0.1:8081/para-index.json" >/dev/null
|
||||
curl -fsS "http://127.0.0.1:8081/annotations-index.json" >/dev/null
|
||||
curl -fsS "http://127.0.0.1:8081/pagefind/pagefind.js" >/dev/null
|
||||
retry_url "http://127.0.0.1:8081/para-index.json" 60 1 || { dump_container archicratie-web-blue; exit 31; }
|
||||
retry_url "http://127.0.0.1:8081/annotations-index.json" 60 1 || { dump_container archicratie-web-blue; exit 32; }
|
||||
retry_url "http://127.0.0.1:8081/pagefind/pagefind.js" 60 1 || { dump_container archicratie-web-blue; exit 33; }
|
||||
|
||||
CANON="$(curl -fsS "http://127.0.0.1:8081/archicrat-ia/chapitre-1/" | grep -oE 'rel="canonical" href="[^"]+"' | head -n1 || true)"
|
||||
HTML="$(fetch_html "http://127.0.0.1:8081/archicrat-ia/chapitre-1/" 60 1 || true)"
|
||||
CANON="$(echo "$HTML" | grep -oE 'rel="canonical" href="[^"]+"' | head -n1 || true)"
|
||||
echo "canonical(blue)=$CANON"
|
||||
echo "$CANON" | grep -q 'https://staging\.archicratie\.trans-hands\.synology\.me/' || {
|
||||
echo "❌ staging canonical mismatch"; exit 3;
|
||||
dump_container archicratie-web-blue
|
||||
echo "❌ staging canonical mismatch"
|
||||
exit 34
|
||||
}
|
||||
|
||||
echo "✅ staging OK"
|
||||
@@ -221,6 +263,45 @@ jobs:
|
||||
PROJ="${COMPOSE_PROJECT_NAME:-archicratie-web}"
|
||||
TS="${TS:-$(date -u +%Y%m%d-%H%M%S)}"
|
||||
|
||||
retry_url() {
|
||||
local url="$1"; local tries="${2:-45}"; local delay="${3:-1}"
|
||||
local i=1
|
||||
while (( i <= tries )); do
|
||||
if curl -fsS --max-time 5 --retry 2 --retry-delay 0 --retry-all-errors "$url" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
echo "… wait [$i/$tries] $url"
|
||||
sleep "$delay"
|
||||
((i++))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
fetch_html() {
|
||||
local url="$1"; local tries="${2:-45}"; local delay="${3:-1}"
|
||||
local i=1
|
||||
while (( i <= tries )); do
|
||||
local html
|
||||
html="$(curl -fsS --max-time 8 --retry 2 --retry-delay 0 --retry-all-errors "$url" 2>/dev/null || true)"
|
||||
if [[ -n "$html" ]]; then
|
||||
printf "%s" "$html"
|
||||
return 0
|
||||
fi
|
||||
echo "… wait HTML [$i/$tries] $url"
|
||||
sleep "$delay"
|
||||
((i++))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
dump_container() {
|
||||
local name="$1"
|
||||
echo "== docker ps (filter=$name) =="
|
||||
docker ps -a --filter "name=$name" --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}' || true
|
||||
echo "== docker logs (tail) $name =="
|
||||
docker logs --tail 200 "$name" 2>/dev/null || true
|
||||
}
|
||||
|
||||
rollback() {
|
||||
echo "⚠️ rollback green -> previous image tag (best effort)"
|
||||
docker image tag "archicratie-web:green.BAK.${TS}" archicratie-web:green || true
|
||||
@@ -228,24 +309,36 @@ jobs:
|
||||
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_green || true
|
||||
}
|
||||
|
||||
set +e
|
||||
docker compose -p "$PROJ" -f docker-compose.yml build web_green
|
||||
docker compose -p "$PROJ" -f docker-compose.yml config >/dev/null
|
||||
|
||||
if ! docker compose -p "$PROJ" -f docker-compose.yml build web_green; then
|
||||
dump_container archicratie-web-green
|
||||
rollback
|
||||
exit 40
|
||||
fi
|
||||
|
||||
docker rm -f archicratie-web-green || true
|
||||
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_green
|
||||
if ! docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_green; then
|
||||
dump_container archicratie-web-green
|
||||
rollback
|
||||
exit 41
|
||||
fi
|
||||
|
||||
curl -fsS "http://127.0.0.1:8082/para-index.json" >/dev/null
|
||||
curl -fsS "http://127.0.0.1:8082/annotations-index.json" >/dev/null
|
||||
curl -fsS "http://127.0.0.1:8082/pagefind/pagefind.js" >/dev/null
|
||||
retry_url "http://127.0.0.1:8082/para-index.json" 90 1 || { dump_container archicratie-web-green; rollback; exit 42; }
|
||||
retry_url "http://127.0.0.1:8082/annotations-index.json" 90 1 || { dump_container archicratie-web-green; rollback; exit 43; }
|
||||
retry_url "http://127.0.0.1:8082/pagefind/pagefind.js" 90 1 || { dump_container archicratie-web-green; rollback; exit 44; }
|
||||
|
||||
CANON="$(curl -fsS "http://127.0.0.1:8082/archicrat-ia/chapitre-1/" | grep -oE 'rel="canonical" href="[^"]+"' | head -n1 || true)"
|
||||
HTML="$(fetch_html "http://127.0.0.1:8082/archicrat-ia/chapitre-1/" 90 1 || true)"
|
||||
CANON="$(echo "$HTML" | grep -oE 'rel="canonical" href="[^"]+"' | head -n1 || true)"
|
||||
echo "canonical(green)=$CANON"
|
||||
echo "$CANON" | grep -q 'https://archicratie\.trans-hands\.synology\.me/' || {
|
||||
echo "❌ live canonical mismatch"; rollback; exit 4;
|
||||
dump_container archicratie-web-green
|
||||
echo "❌ live canonical mismatch"
|
||||
rollback
|
||||
exit 45
|
||||
}
|
||||
|
||||
echo "✅ live OK"
|
||||
set -e
|
||||
|
||||
- name: Hotpatch annotations-index.json (deep merge shards) into blue+green
|
||||
run: |
|
||||
@@ -292,7 +385,6 @@ jobs:
|
||||
return out
|
||||
|
||||
def deep_merge(dst, src):
|
||||
# non destructif : ne supprime rien, n'écrase pas les scalaires existants
|
||||
for k, v in (src or {}).items():
|
||||
if k in ("media", "refs", "comments_editorial") and is_arr(v):
|
||||
if k == "media":
|
||||
@@ -310,7 +402,6 @@ jobs:
|
||||
continue
|
||||
|
||||
if is_arr(v):
|
||||
# fallback: union by json string
|
||||
cur = dst.get(k, [])
|
||||
if not is_arr(cur): cur = []
|
||||
seen = set()
|
||||
@@ -326,7 +417,6 @@ jobs:
|
||||
dst[k] = out
|
||||
continue
|
||||
|
||||
# scalaires: set seulement si absent / vide
|
||||
if k not in dst or dst.get(k) in (None, ""):
|
||||
dst[k] = v
|
||||
|
||||
@@ -376,7 +466,6 @@ jobs:
|
||||
except Exception as e:
|
||||
errors.append({"file": os.path.relpath(fp, ROOT), "error": str(e)})
|
||||
|
||||
# tri paras
|
||||
for page, obj in pages.items():
|
||||
keys = list((obj.get("paras") or {}).keys())
|
||||
keys.sort(key=lambda k: (para_num(k), k))
|
||||
@@ -400,13 +489,11 @@ jobs:
|
||||
print("OK: wrote /tmp/annotations-index.json pages=", out["stats"]["pages"], "paras=", out["stats"]["paras"], "errors=", out["stats"]["errors"])
|
||||
PY
|
||||
|
||||
# inject into running containers
|
||||
for c in archicratie-web-blue archicratie-web-green; do
|
||||
echo "== patch $c =="
|
||||
docker cp /tmp/annotations-index.json "${c}:/usr/share/nginx/html/annotations-index.json"
|
||||
done
|
||||
|
||||
# quick smoke: check new file exists and is readable
|
||||
for p in 8081 8082; do
|
||||
echo "== smoke annotations-index on $p =="
|
||||
curl -fsS "http://127.0.0.1:${p}/annotations-index.json" | python3 -c 'import sys,json; j=json.load(sys.stdin); print("generatedAt:", j.get("generatedAt")); print("pages:", len(j.get("pages") or {}))'
|
||||
|
||||
Reference in New Issue
Block a user