#!/usr/bin/env python3 import json import os import time from datetime import date, datetime, timezone from decimal import Decimal import boto3 from lambda_function import list_new_signup_tenants, list_tenants, mysql_connect _sqs_client = None def env(name, default=None, required=False): value = os.environ.get(name, default) if required and (value is None or str(value).strip() == ""): raise RuntimeError(f"{name} is required") return value def sqs_client(): global _sqs_client if _sqs_client is None: _sqs_client = boto3.client("sqs") return _sqs_client def json_default(value): if isinstance(value, datetime): if value.tzinfo is not None: value = value.astimezone(timezone.utc).replace(tzinfo=None) return value.strftime("%Y-%m-%d %H:%M:%S") if isinstance(value, date): return value.isoformat() if isinstance(value, Decimal): return str(value) raise TypeError(f"{type(value).__name__} is not JSON serializable") def message_body(tenant, source_type, run_id): return json.dumps( { "source_type": source_type, "tenant": tenant, "tenant_id": str(tenant.get("id") or ""), "run_id": run_id, }, default=json_default, separators=(",", ":"), ) def enqueue_tenants(queue_url, tenants, source_type, run_id): sent = 0 failed = [] batch = [] client = sqs_client() for tenant in tenants: tenant_id = str(tenant.get("id") or "") batch.append( { "Id": f"m{len(batch)}", "MessageBody": message_body(tenant, source_type, run_id), "MessageAttributes": { "source_type": { "DataType": "String", "StringValue": source_type, }, "tenant_id": { "DataType": "String", "StringValue": tenant_id, }, "run_id": { "DataType": "String", "StringValue": run_id, }, }, } ) if len(batch) == 10: sent += send_batch(client, queue_url, batch, failed) batch = [] if batch: sent += send_batch(client, queue_url, batch, failed) if failed: raise RuntimeError(f"failed to enqueue {len(failed)} {source_type} monitor messages") return sent def send_batch(client, queue_url, batch, failed): response = client.send_message_batch(QueueUrl=queue_url, Entries=batch) failed.extend(response.get("Failed") or []) return len(response.get("Successful") or []) def bool_event_value(event, name, default): value = (event or {}).get(name, default) if isinstance(value, bool): return value if value is None: return default return str(value).strip().lower() not in {"0", "false", "no", "off"} def lambda_handler(event, context): queue_url = env("MONITOR_QUEUE_URL", required=True) run_id = str((event or {}).get("run_id") or datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")) limit = int((event or {}).get("limit") or 0) include_active = bool_event_value(event, "include_active", True) include_new_signup = bool_event_value(event, "include_new_signup", True) started = time.time() devops_conn = mysql_connect("DEVOPS") try: active_tenants = list_tenants(devops_conn) if include_active else [] new_signup_tenants = list_new_signup_tenants(devops_conn) if include_new_signup else [] finally: devops_conn.close() if limit > 0: active_tenants = active_tenants[:limit] new_signup_tenants = new_signup_tenants[:limit] active_enqueued = enqueue_tenants(queue_url, active_tenants, "active_candidate", run_id) new_signup_enqueued = enqueue_tenants(queue_url, new_signup_tenants, "new_signup", run_id) return { "run_id": run_id, "active_tenant_count": len(active_tenants), "active_enqueued": active_enqueued, "new_signup_tenant_count": len(new_signup_tenants), "new_signup_enqueued": new_signup_enqueued, "elapsed_seconds": round(time.time() - started, 3), }