typescriptamazon-web-servicesamazon-s3aws-cloudformationaws-cdk

CDK - S3 notification causing cyclic reference error


I want to create an S3 bucket in one stack, pass it into another and then use it to create a notification onto an sns or sqs. Here is an example of broken down code would look like.

stack 1

export class BucketStack extends BaseStack {

  public readonly mynBucket: Bucket;


  constructor(scope: App, id: string, props?: StackProps) {
    const properties: StackProps = {
      env: {
        region: StackConfiguration.region,
      },
    };
    super(scope, id, properties);
    this.myBucket = this.createMyBucket();
  }


  private createMyBucket() {
   // create and return bucket
}

stack 2

import * as s3n from '@aws-cdk/aws-s3-notifications';

export class ServiceStack extends BaseStack {
  constructor(scope: App, id: string, myBucket: Bucket) {
    const properties: StackProps = {
      env: {
        region: StackConfiguration.region,
      },
    };

    super(scope, id, properties);

    const topic = new Topic(this, 'Topic');
  
    myBucket.addEventNotification(EventType.OBJECT_CREATED_PUT, new s3n.SnsDestination(topic));

Error is Error: 'my-bucket-stack' depends on 'my-service-stack' (Depends on Lambda resources., Depends on resources.). Adding this dependency (my-service-stack -> my-bucket-stack/myBucket/Resource.Arn) would create a cyclic reference.


Solution

  • The error message indicates, that you use a Lambda.

    Where is that Lamdba definition?

    What are you trying to do with SNS?

    I assume that you wanted to apply the pattern (S3 PUT -> Notification -> Lambda) because you didn't post the full code (including the Lambda) itself.

    Proceeding with my assumption, let me show you the issue what you're currently facing: Circular Dependency between Bucket and Lambda (Role)

    Plain Cloud Formation, which is being used under the hood with CDK, can't tackle that by itself and you would need to create a Custom Resource to overcome this issue. AWS CDK can tackle this though, because it's creating the required Custom Resource and the correct creation sequence automatically!

    Instead of using the s3n.SnsDestination, use the Lambda Destination as shown in the snippet. You can easily extract everything in separate stacks, because the issue shouldn't be with separating stacks.

    export class TestStack extends Stack {
      constructor(scope: cdk.Construct, id: string, props?: StackProps) {
        super(scope, id, props);
    
        const bucket = new Bucket(this, "your-bucket-construct-id",{
           // ...
        });
    
         // Lambda
        const lambda = new Function(this, 'the-triggered-lambda', {
          code: Code.asset(path.join(__dirname,  '../src/your-lambda-folder')),
          handler: 'index.handler',
          runtime: Runtime.NODEJS_12_X,
        });
    
        // S3 -> Lambda
        bucket.addEventNotification(EventType.OBJECT_CREATED_PUT, new LambdaDestination(lambda));
      }
    }
    
    

    Let me know, if that's answering your question or if you're facing any issues.