Files
lambda_local_runner/invoke.py

66 lines
2.5 KiB
Python

"""Shared CLI invoker for any function in functions/.
Usage (inside the lambda pod, via `bash ctrl/invoke.sh [name [event_json]]`):
python invoke.py # invokes the only function, or the first found
python invoke.py lambda_function # specific function
python invoke.py lambda_function '{}' # with event payload
"""
import importlib.util
import json
import os
import sys
from pathlib import Path
# Defaults match the in-cluster configmap so behavior is identical whether
# this script is invoked directly or via the FastAPI runner. `setdefault`
# never overrides an existing env var — the configmap wins in the pod.
os.environ.setdefault("BUCKET_NAME", "my-company-reports-bucket")
os.environ.setdefault("PREFIX", "2026/04/")
os.environ.setdefault("S3_ENDPOINT_URL", "http://minio:9000")
os.environ.setdefault("AWS_ACCESS_KEY_ID", "minioadmin")
os.environ.setdefault("AWS_SECRET_ACCESS_KEY", "minioadmin")
os.environ.setdefault("AWS_REGION", "us-east-1")
REPO_ROOT = Path(__file__).parent
FUNCTIONS_DIR = Path(os.environ.get("FUNCTIONS_DIR", str(REPO_ROOT / "functions")))
SHARED_DIR = REPO_ROOT / "shared"
# Make shared/ importable from any handler ("from shared import ..."). Matches
# how a Lambda Layer would expose code on PYTHONPATH at runtime.
if SHARED_DIR.exists() and str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
def _pick_default_function() -> str:
candidates = sorted(
d.name for d in FUNCTIONS_DIR.iterdir()
if d.is_dir() and not d.name.startswith("_") and (d / "handler.py").exists()
)
if not candidates:
print(f"no function folders with handler.py in {FUNCTIONS_DIR}", file=sys.stderr)
sys.exit(2)
return candidates[0]
def _load(name: str):
path = FUNCTIONS_DIR / name / "handler.py"
if not path.exists():
print(f"function not found: {path}", file=sys.stderr)
sys.exit(2)
spec = importlib.util.spec_from_file_location(f"functions.{name}.handler", path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
if not hasattr(module, "handler"):
print(f"{path} has no handler(event, context)", file=sys.stderr)
sys.exit(2)
return module
if __name__ == "__main__":
name = sys.argv[1] if len(sys.argv) > 1 else _pick_default_function()
event = json.loads(sys.argv[2]) if len(sys.argv) > 2 else {}
module = _load(name)
response = module.handler(event, None)
print(json.dumps(response, indent=2))