diff --git a/deploy/ansible/roles/app/tasks/main.yml b/deploy/ansible/roles/app/tasks/main.yml index 93e61e4..bcd3329 100644 --- a/deploy/ansible/roles/app/tasks/main.yml +++ b/deploy/ansible/roles/app/tasks/main.yml @@ -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" diff --git a/src/handlers/api.rs b/src/handlers/api.rs index c57a58c..8a705e4 100644 --- a/src/handlers/api.rs +++ b/src/handlers/api.rs @@ -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) -> StatusCode { + if db::is_ready(&state.db) { + StatusCode::OK + } else { + StatusCode::SERVICE_UNAVAILABLE + } } pub async fn ipv4_ip_handler( diff --git a/tests/integration.rs b/tests/integration.rs index 9db8e51..60ab1bf 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -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 = "test.example.com".into(); + let openapi_json = build_openapi_json(&site_domain); + let ipv4_domain: std::sync::Arc = "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();