Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deploy API GW with a Simple Lambda Fails #89

Closed
robertchinezon opened this issue Sep 28, 2023 · 1 comment
Closed

Deploy API GW with a Simple Lambda Fails #89

robertchinezon opened this issue Sep 28, 2023 · 1 comment

Comments

@robertchinezon
Copy link

ENV

CDK-2.97.0
CDK-Python
Python 3.10.12

Problem

CDK refuses to deploy an API GW and attached lambda.

I cannot decipher from the error or the logs what is causing this deployment error:

2023-09-28T23:22:01.426 DEBUG --- [functhread77] l.s.c.e.template_deployer  : Dependency for resource {'Type': 'AWS::Lambda::Function', 'DependsOn': ['ApiGwExecLambdaRoleDefaultPolicy79CE76AC', 'ApiGwExecLambdaRole63E650F8'], 'Metadata': {'aws:cdk:path': 'PyPiStack/PyPiLambdaFunction/Resource', 'aws:asset:path': 'asset.3736f2e0ea9efde3abacd142b43531128f893e20e3e01d4f1db8030916b48c4b', 'aws:asset:is-bundled': False, 'aws:asset:property': 'Code'}, 'LogicalResourceId': 'PyPiLambdaFunction9C41451E', 'Properties': {'Code': {'S3Bucket': 'cdk-hnb659fds-assets-000000000000-us-east-1', 'S3Key': '3736f2e0ea9efde3abacd142b43531128f893e20e3e01d4f1db8030916b48c4b.zip'}, 'Environment': {'Variables': {'S3_BUCKET': 'dev-pypi'}}, 'FunctionName': 'dev-pypi-fn', 'Handler': 'handler.handler', 'Role': 'arn:aws:iam::000000000000:role/PyPiStack-ApiGwExecLambdaRole63E65-1ef1614f', 'Runtime': 'python3.8'}} not yet deployed: ApiGwExecLambdaRoleDefaultPolicy79CE76AC {'Type': 'AWS::IAM::Policy', 'Metadata': {'aws:cdk:path': 'PyPiStack/ApiGwExecLambdaRole/DefaultPolicy/Resource'}, 'LogicalResourceId': 'ApiGwExecLambdaRoleDefaultPolicy79CE76AC', 'Properties': {'PolicyDocument': {'Statement': [{'Action': 'lambda:InvokeFunction', 'Effect': 'Allow', 'Resource': {'Fn::GetAtt': ['PyPiLambdaFunction9C41451E', 'Arn']}, 'Sid': 'AllowExecutionFromAPIGateway'}], 'Version': '2012-10-17'}, 'PolicyName': 'ApiGwExecLambdaRoleDefaultPolicy79CE76AC', 'Roles': [{'Ref': 'ApiGwExecLambdaRole63E650F8'}]}}
2023-09-28T23:22:01.426 DEBUG --- [functhread77] l.s.c.e.template_deployer  : Error applying changes for CloudFormation stack "PyPiStack": Resource deployment loop completed, pending resource changes: [{'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'DevPyPiApiGwRestApiDeploymentFD8E30B81c59bb4171d3d92161b1c5e2771ba7ca', 'PhysicalResourceId': None, 'ResourceType': 'AWS::ApiGateway::Deployment', 'Scope': [], 'Details': [], '_deployed': False}}, {'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'DevPyPiApiGwRestApiDeploymentStageprodA8E1BED1', 'PhysicalResourceId': None, 'ResourceType': 'AWS::ApiGateway::Stage', 'Scope': [], 'Details': []}}, {'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'DevPyPiApiGwRestApiResourceGETApiPermissionPyPiStackDevPyPiApiGwRestApi50221AD6GETpackageC77877FC', 'PhysicalResourceId': None, 'ResourceType': 'AWS::Lambda::Permission', 'Scope': [], 'Details': []}}, {'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'DevPyPiApiGwRestApiResourceGETApiPermissionTestPyPiStackDevPyPiApiGwRestApi50221AD6GETpackage6C53FDC6', 'PhysicalResourceId': None, 'ResourceType': 'AWS::Lambda::Permission', 'Scope': [], 'Details': []}}, {'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'DevPyPiApiGwRestApiResourceGETFF0108C1', 'PhysicalResourceId': None, 'ResourceType': 'AWS::ApiGateway::Method', 'Scope': [], 'Details': []}}, {'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'ApiGwExecLambdaRoleDefaultPolicy79CE76AC', 'PhysicalResourceId': None, 'ResourceType': 'AWS::IAM::Policy', 'Scope': [], 'Details': []}}, {'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'PyPiLambdaFunction9C41451E', 'PhysicalResourceId': None, 'ResourceType': 'AWS::Lambda::Function', 'Scope': [], 'Details': [], '_deployed': False}}] Traceback (most recent call last):
  File "/opt/code/localstack/localstack/services/cloudformation/engine/template_deployer.py", line 1170, in _run
    self.do_apply_changes_in_loop(changes, stack)
  File "/opt/code/localstack/localstack/services/cloudformation/engine/template_deployer.py", line 1279, in do_apply_changes_in_loop
    raise Exception(
Exception: Resource deployment loop completed, pending resource changes: [{'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'DevPyPiApiGwRestApiDeploymentFD8E30B81c59bb4171d3d92161b1c5e2771ba7ca', 'PhysicalResourceId': None, 'ResourceType': 'AWS::ApiGateway::Deployment', 'Scope': [], 'Details': [], '_deployed': False}}, {'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'DevPyPiApiGwRestApiDeploymentStageprodA8E1BED1', 'PhysicalResourceId': None, 'ResourceType': 'AWS::ApiGateway::Stage', 'Scope': [], 'Details': []}}, {'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'DevPyPiApiGwRestApiResourceGETApiPermissionPyPiStackDevPyPiApiGwRestApi50221AD6GETpackageC77877FC', 'PhysicalResourceId': None, 'ResourceType': 'AWS::Lambda::Permission', 'Scope': [], 'Details': []}}, {'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'DevPyPiApiGwRestApiResourceGETApiPermissionTestPyPiStackDevPyPiApiGwRestApi50221AD6GETpackage6C53FDC6', 'PhysicalResourceId': None, 'ResourceType': 'AWS::Lambda::Permission', 'Scope': [], 'Details': []}}, {'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'DevPyPiApiGwRestApiResourceGETFF0108C1', 'PhysicalResourceId': None, 'ResourceType': 'AWS::ApiGateway::Method', 'Scope': [], 'Details': []}}, {'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'ApiGwExecLambdaRoleDefaultPolicy79CE76AC', 'PhysicalResourceId': None, 'ResourceType': 'AWS::IAM::Policy', 'Scope': [], 'Details': []}}, {'Type': 'Resource', 'ResourceChange': {'Action': 'Add', 'LogicalResourceId': 'PyPiLambdaFunction9C41451E', 'PhysicalResourceId': None, 'ResourceType': 'AWS::Lambda::Function', 'Scope': [], 'Details': [], '_deployed': False}}]

Code

"""AWS stack that sends an sqs notification when files land in a s3 bucket."""

import logging
import os

from aws_cdk import (
    CfnOutput,
    RemovalPolicy,
    Stack,
    aws_apigateway as apigw,
    aws_iam as iam,
    aws_lambda as _lambda,
    aws_s3 as s3,
)
import constructs as core

logger = logging.getLogger(__name__)


class PyPiStack(Stack):
    """Instantiate an AWS input stack."""

    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        """Initialize class attributes."""

        super().__init__(scope, id, **kwargs)

        curr_dir = os.path.dirname(os.path.abspath(__file__))
        s3_bucket_name = "dev-pypi"
        lambda_fn_name = "dev-pypi-fn"

        # Create the S3 bucket
        bucket = s3.Bucket(
            self,
            "PyPiBucket",
            bucket_name=s3_bucket_name,
            removal_policy=RemovalPolicy.DESTROY,
            auto_delete_objects=False,
            public_read_access=True
        )

        api = apigw.RestApi(
            self,
            "DevPyPiApiGwRestApi",
            rest_api_name="dev-pypi-apigw-rest-api",
            binary_media_types=[
                "application/zip",
                "application/octet-stream",
                "*/*"
            ]
        )

        api_resource = apigw.Resource(
            self,
            "DevPyPiApiGwRestApiResource",
            parent=api.root,
            path_part="package"
        )

        # Add an IAM role for the Api-Gw to exec the dev_pypi_fn.
        apigw_exec_lambda_role = iam.Role(
            self,
            "ApiGwExecLambdaRole",
            assumed_by=iam.ServicePrincipal("apigateway.amazonaws.com"),
            description="Role for Api-Gw to invoke lambda dev_pypi lambda",
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaBasicExecutionRole")]
        )

        # Create the Lambda function
        dev_pypi_fn = _lambda.Function(
            self,
            "PyPiLambdaFunction",
            function_name=lambda_fn_name,
            runtime=_lambda.Runtime.PYTHON_3_8,
            handler="handler.handler",
            code=_lambda.Code.from_asset(os.path.join(curr_dir, "lambda/dev_pypi")),
            role=apigw_exec_lambda_role,
            environment={
                "S3_BUCKET": s3_bucket_name,
            },
        )

        dev_pypi_fn.add_to_role_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                sid="AllowExecutionFromAPIGateway",
                actions=[
                    "lambda:InvokeFunction"
                ],
                resources=[dev_pypi_fn.function_arn]
            )
        )

        # Create an Api-Gw lambda integration
        integration = apigw.LambdaIntegration(
            dev_pypi_fn,
            proxy=True,
        )

        # Create a resource and method for the API Gateway
        api_resource.add_method("GET", integration)

        # Output the bucket name and queue URL
        CfnOutput(self, "BucketName", value=bucket.bucket_name)
        CfnOutput(self, "BucketArn", value=bucket.bucket_arn)
@lakkeger
Copy link
Contributor

Hi @robertchinezon,
The issue lies in your code not in cdklocal, as it creates a circular dependency here by adding a policy to a function's role that refers to the function itself:

dev_pypi_fn.add_to_role_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                sid="AllowExecutionFromAPIGateway",
                actions=[
                    "lambda:InvokeFunction"
                ],
                resources=[dev_pypi_fn.function_arn]
            )
        )

According to the docs you should add permission instead in a similar manner to the API GW to be able to invoke the function:

dev_pypi_fn.add_permission(
    "AllowExecutionFromAPIGateway",
    principal=iam.ServicePrincipal("apigateway.amazonaws.com"),
    source_arn=api.arn_for_execute_api('*')
)

For more information on the topic please refer the official AWS documentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants