fires: resolve FIRMS_MAP_KEY from Postgres keystore, not just env
All checks were successful
build-and-deploy / build (push) Successful in 1m40s
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:
parent
b1c611d4c4
commit
e6648ec1ed
2 changed files with 72 additions and 6 deletions
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue