aws-cdk icon indicating copy to clipboard operation
aws-cdk copied to clipboard

aws-cloudfront: Deploying mutliple stacks on same account containing Cloudfront Function resources without `functionName` generates conflicts

Open fredericbarthelet opened this issue 1 year ago • 0 comments

Describe the bug

Deploying multiple instances of a stack (with different stackName) on the same AWS Account generates name conflicts on Cloudfront Function resources where function name is generated by CDK.

Expected Behavior

I'd expect stackName to have an impact on the resource name. This is one of those case where the underlying L1 construct CfnFunction requires one property name, but L2 construct Function makes this property optional (unlike for exemple DynamoDB Table where Cloudformation takes care of the name generation AFAIK). The unique name generation then relies solely on the CDK implementation.

Current Behavior

Name generated for function in the following file does note rely on stackName and generate the same output for 2 different stacks. https://github.com/aws/aws-cdk/blob/289fb96810ba8c2dd4d58dad06401c10eeddd45c/packages/%40aws-cdk/aws-cloudfront/lib/function.ts#L178-L185 Function name should indeed be unique per acccount, thus resulting in the following error: Resource handler returned message: "Resource of type 'AWS::CloudFront::Function' with identifier 'eu-west-1TmpCdkStackMyFunction3C477B93' already exists."

Reproduction Steps

lib/tmp-cdk-stack.ts

import { Stack, StackProps } from 'aws-cdk-lib';
import { Function, FunctionCode } from 'aws-cdk-lib/aws-cloudfront';
import { Construct } from 'constructs';

export class CloudfrontStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    new Function(this, 'MyFunction', {
      code: FunctionCode.fromInline('function handler(event) { return event.request }')
    })
  }
}

bin/tmp-cdk.ts

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CloudfrontStack } from '../lib/tmp-cdk-stack';

const app = new cdk.App();
new CloudfrontStack(app, 'TmpCdkStack', {
  stackName: process.env.STACK_NAME
});

Then run successively STACK_NAME=PR-1 cdk deploy and ``STACK_NAME=PR-2 cdk deploy`

Possible Solution

Digging around, I found this similar issue on CodePipeline https://github.com/aws/aws-cdk/issues/18828 This was discovered on https://github.com/getlift/lift/issues/247 The newly created uniqueResourceName API on Names can be used to instead of uniqueId.

The resulting cloudfront.Function L2 construct implementation would ressemble something like this

export class Function extends Resource implements IFunction {
  constructor(scope: Construct, id: string, props: FunctionProps) {
    super(scope, id);

    this.functionName = props.functionName ?? Names.uniqueResourceName(this, {
      maxLength: 64,
      separator: '-',
      allowedSpecialCharacters: '_-',
    });

    const resource = new CfnFunction(this, 'Resource', {
      autoPublish: true,
      functionCode: props.code.render(),
      functionConfig: {
        comment: props.comment ?? this.functionName,
        runtime: 'cloudfront-js-1.0',
      },
      name: this.functionName,
    });
  }
}

This feature shall be released behind featureFlag to not break existing already deployed Function constructs. This is even more important as the name of a Cloudfront function is not updatable.

I'd be happy to come up with a PR if you deem this updated implementation interesting :)

Additional Information/Context

No response

CDK CLI Version

2.35.0 (build 5c23578)

Framework Version

No response

Node.js Version

v16.16.0

OS

Mac OS X

Language

Typescript

Language Version

No response

Other information

No response

fredericbarthelet avatar Aug 08 '22 16:08 fredericbarthelet