fires: resolve FIRMS_MAP_KEY from Postgres keystore, not just env
All checks were successful
build-and-deploy / build (push) Successful in 1m40s

Key saved via the dashboard Keys page sat unused in api_keys while
ingest_fires only checked the env var. Now env first, keystore fallback,
picked up on next poll without a restart.
This commit is contained in:
Sirius DevOps 2026-08-24 21:21:15 -04:00
parent b1c611d4c4
commit e6648ec1ed
2 changed files with 72 additions and 6 deletions

View file

@ -25,12 +25,14 @@ import csv
import io import io
import json import json
import logging import logging
import os
from datetime import datetime, timezone from datetime import datetime, timezone
import httpx import httpx
import nats import nats
from config import FIRMS_MAP_KEY, FIRMS_DATASET, FIRMS_BBOX, FIRMS_TIMEOUT, NATS_URL from config import FIRMS_DATASET, FIRMS_BBOX, FIRMS_TIMEOUT, NATS_URL
from keystore import get_api_key
logger = logging.getLogger("osint.firms") logger = logging.getLogger("osint.firms")
@ -159,16 +161,21 @@ async def ingest_fires(bbox: str | None = None) -> int:
warning when FIRMS_MAP_KEY is not set, so the ingester keeps running for warning when FIRMS_MAP_KEY is not set, so the ingester keeps running for
the other sources. the other sources.
""" """
if not FIRMS_MAP_KEY: # Key resolution: env var first (12-factor), then the shared Postgres
# api_keys table so a key saved via the dashboard Keys page is picked up
# on the next poll without a restart.
map_key = os.getenv("FIRMS_MAP_KEY", "") or await get_api_key("FIRMS_MAP_KEY")
if not map_key:
logger.warning( logger.warning(
"FIRMS_MAP_KEY not set — fire ingest disabled. " "FIRMS_MAP_KEY not set — fire ingest disabled. "
"Get a free key at https://firms.modaps.eosdis.nasa.gov/api/map_key_info/" "Set it in the dashboard Keys page or .env. "
"Free key: https://firms.modaps.eosdis.nasa.gov/api/map_key_info/"
) )
return 0 return 0
area = bbox or FIRMS_BBOX area = bbox or FIRMS_BBOX
url = FIRMS_AREA_CSV.format( url = FIRMS_AREA_CSV.format(
key=FIRMS_MAP_KEY, dataset=FIRMS_DATASET, bbox=area key=map_key, dataset=FIRMS_DATASET, bbox=area
) )
async with httpx.AsyncClient(timeout=FIRMS_TIMEOUT) as client: async with httpx.AsyncClient(timeout=FIRMS_TIMEOUT) as client:

View file

@ -73,6 +73,65 @@ def test_parse_firms_csv_empty_and_garbage():
def test_ingest_fires_idles_without_map_key(monkeypatch): def test_ingest_fires_idles_without_map_key(monkeypatch):
# No key -> returns 0 without attempting a network call. # No key anywhere -> returns 0 without attempting a network call.
monkeypatch.setattr("fire_sources.FIRMS_MAP_KEY", "") monkeypatch.setenv("FIRMS_MAP_KEY", "")
monkeypatch.setattr(
"fire_sources.get_api_key", lambda name: _async_none()
)
assert asyncio.run(ingest_fires()) == 0 assert asyncio.run(ingest_fires()) == 0
def test_ingest_fires_uses_keystore_key(monkeypatch):
# Key saved via the dashboard Keys page (Postgres) is picked up.
monkeypatch.setenv("FIRMS_MAP_KEY", "")
monkeypatch.setattr(
"fire_sources.get_api_key",
lambda name: _async_return("a" * 32),
)
captured = {}
class FakeResp:
status_code = 200
text = SAMPLE_CSV
def raise_for_status(self):
pass
class FakeClient:
def __init__(self, **kw):
pass
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return False
async def get(self, url):
captured["url"] = url
return FakeResp()
monkeypatch.setattr("fire_sources.httpx.AsyncClient", FakeClient)
published = []
async def fake_publish(points):
published.extend(points)
return len(points)
monkeypatch.setattr("fire_sources.publish_fire_batch", fake_publish)
assert asyncio.run(ingest_fires()) == 5
assert "a" * 32 in captured["url"]
def _async_return(value):
async def inner():
return value
return inner()
def _async_none():
return _async_return(None)