Complete a 3 Question Survey and Earn a re:Post Badge
Help improve AWS Support Official channel in re:Post and share your experience - complete a quick three-question survey to earn a re:Post badge!
How do I fix the circular dependency between an AWS Lambda permission and target group resources in AWS CloudFormation?
I want to fix the circular dependency between an AWS Lambda permission (AWS::Lambda::Permission) and target group resources (AWS::ElasticLoadBalancingV2::TargetGroup) in AWS CloudFormation.
Resolution
A circular dependency can happen when you configure an AWS::ElasticLoadBalancingV2::TargetGroup with a Lambda function target and an associated AWS::Lambda::Permission resource. This situation occurs due to the following interdependencies:
To register a Lambda function as a target using its Targets property, the AWS::ElasticLoadBalancingV2::TargetGroup requires the AWS::Lambda::Permission to allow Elastic Load Balancing to invoke the Lambda function.
Also, the AWS::Lambda::Permission requires the AWS::ElasticLoadBalancingV2::TargetGroup's ARN in its SourceArn property to restrict invocation permissions to specific Target Groups.
In this case, the AWS::ElasticLoadBalancingV2::TargetGroup can't be fully created without the AWS::Lambda::Permission. But the AWS::Lambda::Permission can't be created without the AWS::ElasticLoadBalancingV2::TargetGroup's ARN. As a result, CloudFormation can't determine the resource to create first. This situation is a circular dependency error.
To fix this circular dependency, replace the AWS::ElasticLoadBalancingV2::TargetGroup Targets property with a Lambda-backed custom resource to register the Lambda function as a target.
Use a Lambda-backed custom resource to register your Lambda function as a target in your target group
Use a CloudFormation template to define a Lambda-backed custom resource to register your Lambda function as a target in your target group.
Keep in mind the following factors when you build the template:
- The CloudFormation template includes an example HelloWorld Lambda function and ELBv2 resources for reference.
- This template provisions an additional RegisterTargetsFunction Lambda function and an associated execution role and a custom resource. The custom resource invokes the Lambda function whenever the custom resource is created, updated, or deleted.
- During the custom resource creation, the RegisterTargetsFunction Lambda function registers the defined Lambda function as the target in the provided target group. During the custom resource deletion, the function de-registers the target.
- You can modify the template with your own code.
- This template uses AWS Lambda-backed custom resources and assumes that you're familiar with Lambda's best practices and troubleshooting issues.
Create your CloudFormation template
To create your CloudFormation template, use the following example:
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. AWSTemplateFormatVersion: 2010-09-09 Description: HelloWorld Lambda function template for Application Load Balancer Lambda as target Parameters: # VPC in which the LoadBalancer and the LoadBalancer SecurityGroup will be created VpcId: Type: AWS::EC2::VPC::Id # Subnets in which the LoadBalancer will be created. Subnets: Type: List<AWS::EC2::Subnet::Id> # Name of the TargetGroup TargetGroupName: Type: String Default: 'MyTargets' Resources: LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: AllowRegisterAndDeregisterTargets PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'elasticloadbalancing:RegisterTargets' Resource: !GetAtt TargetGroup.TargetGroupArn - Effect: Allow Action: - 'elasticloadbalancing:DeregisterTargets' Resource: '*' # Lambda function which displays an HTML page with "Hello from Lambda!" message upon invocation HelloWorldFunction: Type: 'AWS::Lambda::Function' Properties: Code: ZipFile: | def lambda_handler(event, context): return { "statusCode": 200, "statusDescription": "HTTP OK", "isBase64Encoded": False, "headers": { "Content-Type": "text/html" }, "body": "<h1>Hello from Lambda!</h1>" } MemorySize: 128 Handler: index.lambda_handler Timeout: 30 Runtime: python3.12 Role: !GetAtt LambdaExecutionRole.Arn LoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Scheme: internet-facing Subnets: !Ref Subnets SecurityGroups: - !Ref LoadBalancerSecurityGroup HttpListener: Type: 'AWS::ElasticLoadBalancingV2::Listener' Properties: DefaultActions: - TargetGroupArn: !Ref TargetGroup Type: forward LoadBalancerArn: !Ref LoadBalancer Port: 80 Protocol: HTTP LoadBalancerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow HTTP to client host VpcId: !Ref VpcId SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 HelloWorldFunctionInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !GetAtt HelloWorldFunction.Arn Action: 'lambda:InvokeFunction' Principal: elasticloadbalancing.amazonaws.com SourceArn: !GetAtt TargetGroup.TargetGroupArn TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Ref TargetGroupName TargetType: lambda # Custom resource for Lambda function - "RegisterTargetsFunction" RegisterTargets: DependsOn: [TargetGroup, HelloWorldFunctionInvokePermission] Type: Custom::RegisterTargets Properties: ServiceToken: !GetAtt RegisterTargetsFunction.Arn ServiceTimeout: 15 # Input parameters for the Lambda function LambdaFunctionARN: !GetAtt HelloWorldFunction.Arn TargetGroupARN: !GetAtt TargetGroup.TargetGroupArn # Lambda function that performs RegisterTargets API call RegisterTargetsFunction: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import logging import boto3, json, botocore import cfnresponse #Define logging properties for 'logging' log = logging.getLogger() log.setLevel(logging.INFO) #Main Lambda function to be executed def lambda_handler(event, context): try: # print the event type sent from cloudformation log.info ("'" + str(event['RequestType']) + "' event was sent from CFN") # Input Parameters TargetGroupARN = event['ResourceProperties']['TargetGroupARN'] LambdaFunctionARN = event['ResourceProperties']['LambdaFunctionARN'] log.info("TargetGroup ARN value is:" + TargetGroupARN) log.info("Lambda Function ARN value is:" + LambdaFunctionARN) responseData = {} # ELBV2 initilize client = boto3.client('elbv2') # Initilize Vars response = '' error_msg = '' if event['RequestType'] == "Create" or event['RequestType'] == "Update": # Make the RegisterTargets API call try: response = client.register_targets( TargetGroupArn=TargetGroupARN, Targets=[ { 'Id': LambdaFunctionARN }, ] ) except botocore.exceptions.ClientError as e: error_msg = str(e) if error_msg: log.info("Error Occured:" + error_msg) response_msg = error_msg # SIGNAL BACK TO CLOUDFORMATION log.info("Trying to signal FAILURE back to cloudformation.") responseData = {"Message" : response_msg, "Function" : context.log_stream_name} cfnresponse.send(event, context, cfnresponse.FAILED, responseData) else: response_msg = "Successfully registered Lambda(" + LambdaFunctionARN + ") as Target" log.info(response_msg) # SIGNAL BACK TO CLOUDFORMATION log.info("Trying to signal SUCCESS back to cloudformation.") responseData = {"Message" : response_msg, "Function" : context.log_stream_name} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) # log the end of the create event log.info ("End of '" + str(event['RequestType']) + "' Event") elif "Delete" in str(event['RequestType']): # Make the DeregisterTargets API call try: response = client.deregister_targets( TargetGroupArn=TargetGroupARN, Targets=[ { 'Id': LambdaFunctionARN }, ] ) except botocore.exceptions.ClientError as e: error_msg = str(e) if error_msg: log.info("Error Occured:" + error_msg) response_msg = error_msg # SIGNAL BACK TO CLOUDFORMATION log.info("Trying to signal FAILURE back to cloudformation.") responseData = {"Message" : response_msg, "Function" : context.log_stream_name} cfnresponse.send(event, context, cfnresponse.FAILED, responseData) else: response_msg = "Successfully deregistered Lambda(" + LambdaFunctionARN + ") as Target" log.info(response_msg) # SIGNAL BACK TO CLOUDFORMATION log.info("Trying to signal SUCCESS back to cloudformation.") responseData = {"Message" : response_msg, "Function" : context.log_stream_name} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) # log the end of the create event log.info ("End of '" + str(event['RequestType']) + "' Event") else: log.info ("RequestType sent from cloudformation is invalid.") log.info ("Was expecting 'Create', 'Update' or 'Delete' RequestType(s).") log.info ("The detected RequestType is : '" + str(event['RequestType']) + "'") # SIGNAL BACK TO CLOUDFORMATION log.info("Trying to signal FAILURE back to cloudformation due to invalid request type.") responseData={"Function" : context.log_stream_name} cfnresponse.send(event, context, cfnresponse.FAILED, responseData) except Exception as e: log.info ("Function failed due to the following error:") print (e) # SIGNAL BACK TO CLOUDFORMATION log.info("Trying to signal FAILURE back to cloudformation due to the error.") responseData={"Function" : context.log_stream_name} cfnresponse.send(event, context, cfnresponse.FAILED, responseData) Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Runtime: python3.12 Timeout: '15' Outputs: Message: Description: Message returned from Lambda Value: !GetAtt - RegisterTargets - Message
Use your CloudFormation template to build your CloudFormation stack
- Save the template that you created as a YAML file.
- Use AWS CLI or the CloudFormation console to create a stack.
Note: If you receive errors when you run AWS Command Line Interface (AWS CLI) commands, then see Troubleshoot AWS CLI errors. Also, make sure that you use the most recent AWS CLI version. - Use the VpcId, Subnets, and TargetGroupName stack parameters to specify your desired Amazon Virtual Private Cloud (Amazon VPC), subnets, and target group name.
- After the stack reaches a CREATE_COMPLETE state, go to the Outputs section of the AWS CloudFormation console to see the following message:
"Successfully Added Lambda(arn:aws:lambda:<region>:<account_id>:function:<function_name>) as Target" - Check the target group to verify that the target was registered.

Relevant content
- Accepted Answer
- asked 2 years agolg...
- Accepted Answer
- (SAM CLI 1.137.1) circular dependency reported by SAM Validate when s3 event trigger added to lambdaAccepted Answerasked a month agolg...
- asked 6 years agolg...