From 5406728cdacf97fddbb5b69c0ef28e415cabe825 Mon Sep 17 00:00:00 2001 From: John Josef Date: Sat, 19 Sep 2026 20:22:05 -0400 Subject: [PATCH] fix: delete orphan live alias before SAM AutoPublishAlias CDK addAlias and SAM AutoPublishAlias both use the name live; CloudFormation can try to create the SAM alias before the old one is gone, causing 409 AlreadyExists. --- infra/cdk/lib/lambda-canary.ts | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/infra/cdk/lib/lambda-canary.ts b/infra/cdk/lib/lambda-canary.ts index 639cd66..a550e22 100644 --- a/infra/cdk/lib/lambda-canary.ts +++ b/infra/cdk/lib/lambda-canary.ts @@ -1,10 +1,46 @@ import * as cdk from 'aws-cdk-lib'; import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; +import { + AwsCustomResource, + AwsCustomResourcePolicy, + PhysicalResourceId, +} from 'aws-cdk-lib/custom-resources'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as sam from 'aws-cdk-lib/aws-sam'; const CANARY_PERIOD = cdk.Duration.minutes(1); +/** CDK `addAlias('live')` → SAM `AutoPublishAlias` leaves the old alias in Lambda until CFN deletes it, often after SAM tries to create the same name. */ +function deleteOrphanLiveAliasBeforeSam(fn: lambda.Function, cfn: lambda.CfnFunction): void { + const migration = new AwsCustomResource(fn, 'SamLiveAliasMigration', { + onCreate: { + service: '@aws-sdk/client-lambda', + action: 'DeleteAliasCommand', + parameters: { + FunctionName: fn.functionName, + Name: 'live', + }, + physicalResourceId: PhysicalResourceId.of( + `${cdk.Stack.of(fn).stackName}-${fn.node.id}-sam-live-alias-migration`, + ), + ignoreErrorCodesMatching: 'ResourceNotFoundException|NotFound', + }, + onUpdate: { + service: '@aws-sdk/client-lambda', + action: 'GetFunctionCommand', + parameters: { FunctionName: fn.functionName }, + physicalResourceId: PhysicalResourceId.of( + `${cdk.Stack.of(fn).stackName}-${fn.node.id}-sam-live-alias-migration`, + ), + }, + policy: AwsCustomResourcePolicy.fromSdkCalls({ + resources: [fn.functionArn, `${fn.functionArn}:*`], + }), + }); + + cfn.node.addDependency(migration); +} + /** * Turns a CDK `Function` into an `AWS::Serverless::Function` so we get SAM's * AutoPublishAlias / AutoPublishAliasAllProperties / DeploymentPreference @@ -30,6 +66,7 @@ export function publishLiveAlias( fn.stack.addTransform('AWS::Serverless-2016-10-31'); const cfn = fn.node.defaultChild as lambda.CfnFunction; + deleteOrphanLiveAliasBeforeSam(fn, cfn); cfn.addOverride('Type', 'AWS::Serverless::Function'); const imageUri = (cfn.code as lambda.CfnFunction.CodeProperty | undefined)?.imageUri;