import time import boto3 import sentry_sdk from aws_lambda_powertools.utilities.parser import event_parser from aws_lambda_powertools.utilities.typing import LambdaContext from lambdacommon.common_config import logger from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration import fargate_redeploy.config as Config from fargate_redeploy.models.payload import LambdaPayload if Config.SENTRY_DSN: sentry_sdk.init( dsn=Config.SENTRY_DSN, environment=Config.ENVIRONMENT, integrations=[AwsLambdaIntegration(timeout_warning=True)] ) ecs_client = boto3.client("ecs", region_name=Config.DEFAULT_REGION) def wait_deployment(cluster: str, service: str) -> None: logger.info("Waiting for service to stabilize (this may take several minutes)...") waiter = ecs_client.get_waiter("services_stable") waiter.wait( cluster=cluster, services=[service], WaiterConfig={ "Delay": Config.WAIT_DELAY, "MaxAttempts": Config.WAIT_MAX_ATTEMPTS, }, ) logger.info(f"Service {service} has successfully stabilized.") def is_deploying(cluster: str, service: str) -> bool: response = ecs_client.describe_services(cluster=cluster, services=[service]) services = response.get("services", []) if not services: raise Exception(f"Service {service} not found in cluster {cluster}") deployments = services[0].get("deployments", []) for d in deployments: if d.get("rolloutState", "") == "IN_PROGRESS": logger.info(f"Deployment is in progress, deployment ID: {d['id']}") return True return False def force_redeploy_service(cluster: str, service: str) -> str: logger.info(f"Forcing new deployment of service {service} in cluster {cluster}...") response = ecs_client.update_service(cluster=cluster, service=service, forceNewDeployment=True) logger.debug(str(response)) deployment_id = "" for d in response["service"]["deployments"]: if d.get("rolloutState", "") == "IN_PROGRESS": logger.info(f"Triggered deployment ID: {d['id']}") deployment_id = d["id"] break if deployment_id == "": raise Exception(f"Failed to update the service {service} in cluster {cluster}") return deployment_id @event_parser(model=LambdaPayload) def handler(event: LambdaPayload, context: LambdaContext) -> dict: if is_deploying(cluster=event.cluster, service=event.service): logger.info("Waiting for existing deployment to finish...") wait_deployment(cluster=event.cluster, service=event.service) time.sleep(Config.WAIT_DELAY * 2) deployment_id = force_redeploy_service(cluster=event.cluster, service=event.service) if event.wait_stable: wait_deployment(cluster=event.cluster, service=event.service) return { "cluster": event.cluster, "service": event.service, "deployment_id": deployment_id, "result": "SUCCESS", }