"""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))