feat: force fresh MMDB download on every deploy

Wipe the target color's database volume before starting the container so
each push re-downloads the latest Merged-IP.mmdb. Gate /health on DB
readiness (503 until loaded) so HAProxy blue-green only flips traffic
once the new backend has a usable DB, and raise the Ansible health-wait
window from 120s to 360s to cover the download.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
NetworkCats
2026-04-21 07:05:57 +00:00
co-authored by Claude Opus 4.7
parent 5e70e21296
commit d1280a46ac
3 changed files with 39 additions and 4 deletions
+15 -2
View File
@@ -140,6 +140,13 @@
active_color: "{{ deploy_target }}"
when: is_first_deploy
- name: Wipe database on target volume to force fresh download (first deploy)
ansible.builtin.shell: |
docker run --rm -v rustyip_db_data_{{ deploy_target }}:/data \
alpine:3 sh -c 'rm -f /data/Merged-IP.mmdb /data/Merged-IP.mmdb.tmp'
changed_when: true
when: is_first_deploy
- name: Start target app container (first deploy)
ansible.builtin.shell: |
docker compose -f /opt/rustyip/docker-compose.yml up -d "app-{{ deploy_target }}"
@@ -148,7 +155,7 @@
- name: Wait for target container health check (first deploy)
ansible.builtin.shell: |
for i in $(seq 1 60); do
for i in $(seq 1 180); do
status=$(docker inspect --format='{% raw %}{{.State.Health.Status}}{% endraw %}' "rustyip-app-{{ deploy_target }}" 2>/dev/null)
if [ "$status" = "healthy" ]; then
echo "healthy"
@@ -183,6 +190,12 @@
- name: Blue-green deployment
when: not is_first_deploy
block:
- name: Wipe database on standby volume to force fresh download
ansible.builtin.shell: |
docker run --rm -v rustyip_db_data_{{ deploy_target }}:/data \
alpine:3 sh -c 'rm -f /data/Merged-IP.mmdb /data/Merged-IP.mmdb.tmp'
changed_when: true
- name: Start standby container with new image
ansible.builtin.shell: |
docker compose -f /opt/rustyip/docker-compose.yml up -d "app-{{ deploy_target }}"
@@ -190,7 +203,7 @@
- name: Wait for standby container health check
ansible.builtin.shell: |
for i in $(seq 1 60); do
for i in $(seq 1 180); do
status=$(docker inspect --format='{% raw %}{{.State.Health.Status}}{% endraw %}' "rustyip-app-{{ deploy_target }}" 2>/dev/null)
if [ "$status" = "healthy" ]; then
echo "healthy"
+6 -2
View File
@@ -10,8 +10,12 @@ use crate::models::IpInfo;
use super::{AppState, IpQuery, extract_ip_from_header, lookup_ip, resolve_ip};
pub async fn health() -> StatusCode {
StatusCode::OK
pub async fn health(State(state): State<AppState>) -> StatusCode {
if db::is_ready(&state.db) {
StatusCode::OK
} else {
StatusCode::SERVICE_UNAVAILABLE
}
}
pub async fn ipv4_ip_handler(
+18
View File
@@ -78,6 +78,24 @@ async fn health_returns_200() {
assert_eq!(status, StatusCode::OK);
}
#[tokio::test]
async fn health_returns_503_when_db_not_loaded() {
let shared_db = db::new_empty();
let site_domain: std::sync::Arc<str> = "test.example.com".into();
let openapi_json = build_openapi_json(&site_domain);
let ipv4_domain: std::sync::Arc<str> = "noipv6.test.example.com".into();
let state = AppState {
db: shared_db,
site_domain,
ipv4_domain,
dev_mode: false,
openapi_json,
};
let app = build_router(state);
let (status, _) = get(&app, "/health").await;
assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
}
#[tokio::test]
async fn json_endpoint_returns_full_info() {
let app = build_test_app();