SoftWhat?

Architecture and Software Engineering tidbits

cdk-chalice Adds Support for Accessing Chalice-generated CloudFormation Resources

Posted at — Sep 28, 2020

cdk-chalice 0.8.1 adds support for accessing AWS Chalice-generated CloudFormation resources as native CDK objects. It is useful if you need to explicitly reference these resources in the broader AWS Cloud Development Kit (AWS CDK) application, or customize the resources generated by Chalice. In this blog post, I will explain how this feature works and provide an example.

The new capability is based on the CDK cloudformation-include module, which was released as a developer preview in CDK 1.64.1. That CDK version is now the minimal requirement for cdk-chalice. This module contains a set of classes whose goal is to facilitate working with existing CloudFormation templates in the CDK. It can be thought of as an extension of the capabilities of the CfnInclude class.

cdk-chalice already exposed Chalice.sam_template attribute, but so far it provided access to read-only JSON representation of the CloudFormation template. It could not be used to modify the resources or reference them in the broader CDK application. With this release, Chalice.sam_template is an instance of aws_cdk.cloudformation_include.CfnInclude class, and includes the resources generated by Chalice. To show how the new feature can be used, I will build a simple web API using cdk-chalice. Then I will customize the IAM role of the backend AWS Lambda function to have full access to DynamoDB using the native CDK interface.

First, let’s create a Python virtual environment and install the required packages:

python3 -m venv .venv
source .venv/bin/activate
pip install aws-cdk.core aws-cdk.aws-iam attrs==20.2.0 \
    cdk-chalice==0.8.1 chalice==1.20.1

The default Chalice (runtime) application example should suffice:

chalice new-project runtime

Now I will create the CDK (infrastructure) application:

mkdir infrastructure; cd infrastructure

cat <<EOF > cdk.json
{
  "app": "python app.py"
}
EOF

cat <<EOF > app.py
import os

from aws_cdk import (
    core as cdk,
    aws_iam as iam
)
from cdk_chalice import Chalice


class WebApi(cdk.Stack):

    def __init__(self, scope: cdk.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        runtime_source_dir = os.path.join(
            os.path.dirname(__file__), os.pardir, 'runtime')
        self.chalice = Chalice(
            self, 'WebApi', source_dir=runtime_source_dir, 
            stage_config={'api_gateway_stage': 'v1'})

        api_handler_role = self.chalice.sam_template.get_resource(
            'DefaultRole')
        print(f'type(api_handler_role) = {type(api_handler_role)}')
        dynamodb_policy = iam.ManagedPolicy.from_aws_managed_policy_name(
            'AmazonDynamoDBFullAccess').managed_policy_arn
        api_handler_role.managed_policy_arns = [dynamodb_policy]


app = cdk.App()
dev_env = cdk.Environment(account=os.environ['CDK_DEFAULT_ACCOUNT'],
                          region=os.environ['CDK_DEFAULT_REGION'])
WebApi(app, 'WebApiDev', env=dev_env)
app.synth()
EOF

In order to know the name of the resource for calling self.chalice.sam_template.get_resource('<logical ID>') method (like 'DefaultRole' above), currently you should synthesize the application, and find the relevant resource logical ID in the template. In our case, I am looking for an IAM role, and will use jq to look it up (install jq if needed). To synthesize the template:

cdk synth

Now I can search for logical ID of the IAM role:

$ jq '.Resources | to_entries[] | select(.value.Type == "AWS::IAM::Role") | .key' \
    cdk.out/WebApiDev.template.json 
"DefaultRole"

To show you that api_handler_role variable is indeed a native CDK object, I added a small print in the class code above. Let’s synthesize the stack again, but this time look at the first few lines of the output:

$ cdk synth | head -n 5
Packaging Chalice app for WebApiDev
Creating deployment package.
type(api_handler_role) = <class 'aws_cdk.aws_iam.CfnRole'>
Transform: AWS::Serverless-2016-10-31
AWSTemplateFormatVersion: "2010-09-09"

You can see that type of api_handler_role variable is <class 'aws_cdk.aws_iam.CfnRole'>. This is a native CDK CloudFormation (L1) construct object. It can be referenced in the broader CDK application, or customized using the construct interface. In this example, I added a DynamoDB managed policy, and you can see that it was applied to the final template:

$ jq '.Resources.DefaultRole.Properties.ManagedPolicyArns' cdk.out/WebApiDev.template.json
[
  {
    "Fn::Join": [
      "",
      [
        "arn:",
        {
          "Ref": "AWS::Partition"
        },
        ":iam::aws:policy/AmazonDynamoDBFullAccess"
      ]
    ]
  }
]

If you have any feedback, I would be glad to hear it! Feel free to open an issue in the cdk-chalice repository.