AWS-CDK Migration Tips
When migrating to a new framework there are going to be some working pains. This is just a collection of frustrations that I encountered while adopting aws-cdk. I did some research prior and found the article “Hey CDK, how can I migrate my existing CloudFormation templates?” by Philipp Garbe and the “core module AWS CDK” documentation most helpful in thinking about migrating initially.
Import Immutable Roles
Use the mutable flag when importing existing roles with Role.fromRoleArn() otherwise the precision of the aws-cdk may lead to the dreaded “Maximum policy size of 10240 bytes” error. Eventually aws-cdk issue #4465 will be fixed and we will welcome the precise IAM policies the CDK generates.
The maximum policy size error was most often encountered on CodePipeline deploy roles where we had a large number of independent artifacts deploying CloudFormations.
Explicit to_string() in python
Having to explicitly call the core.Fn.get_att(‘Foo’, ‘Bar’).to_string() operator instead of using a str() for f'{var}’ style tripped me up.
I noticed in my IDE that the signature called for a string (thanks types!) so I tried:
str(core.Fn.get_att('Foo','Bar'))
and
f'{core.Fn.get_att('Foo','Bar')}'
but because only __repr__ is defined in the python interface I got an ugly object name when I expected __str__ to be implemented. I overlooked the to_string(), which is a pretty common method for many object oriented languages, as I expected the class to behave more pythonically.
Beware of Copy Paste / Naming
Name a stack the same as another? You get a diff but if you aren’t paying attention you’ll blow away a stack before realizing it. Also you end up with the old stack as well not updated because of this config SNAFU.
Import Pains
CfnImport is great for importing old CloudFormations but the stack is immutable upon import. Any changes to the stack must happen prior to making the call. We leaned on PyYAML but then had to undo a few of the niceties of only processing the template with AWS based systems.
Intrinsic Function Shortcuts
For example all bang, “!” ie “!Ref” or “!Sub”, references need to be updated to be the full function command, ie “Ref:” and “Fn::Sub:”.
Attributeerror: ‘datetime.date’
IAM Policy Documents specifying the Version unquoted instead of as a string, ie Version: 2012-10-17 instead of Version: ‘2012-10-17’, will have the cdk synth command greet them with following obscure error.
This error also occurs on AWSTemplateFormatVersion blocks so beware.
AttributeError: 'datetime.date' object has no attribute '__jsii__type__'.
Example CfnImport
Here is an example of using the CfnImport to inject parameters into a traditional template and then load it into the CDK stack.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import yaml | |
from aws_cdk import core | |
class RawStack(core.Stack): | |
def __init__(self, scope: core.Construct, name: str, template_path: str, wrapped_parameters=None, | |
**kwargs) -> None: | |
"""import a stack off a path and munge in ssm variables if desired | |
:param template_path: path to raw stack being imported | |
:param wrapped_parameters: map of Parameter keys and default values | |
:param kwargs: all the stack stuff | |
""" | |
super().__init__(scope=scope, name=name, **kwargs) | |
if not wrapped_parameters: | |
wrapped_parameters = {} | |
template_path = Path(template_path) | |
with open(template_path, 'r') as f: | |
template = yaml.load(f, Loader=yaml.SafeLoader) | |
if project_name: | |
for pk, pv in template['Parameters'].items(): | |
if 'Default' in pv: | |
if pk in wrapped_parameters: | |
template['Parameters'][pk]['Default'] = wrapped_parameters[pk] | |
elif pv['Default'] == "": | |
template['Parameters'][pk]['Type'] = 'AWS::SSM::Parameter::Value<String>' | |
template['Parameters'][pk]['Default'] = str(pk) | |
else: | |
if pk in wrapped_parameters: | |
template['Parameters'][pk]['Default'] = wrapped_parameters[pk] | |
else: | |
template['Parameters'][pk]['Type'] = 'AWS::SSM::Parameter::Value<String>' | |
template['Parameters'][pk]['Default'] = str(pk) | |
core.CfnInclude(self, 'RawStack', template=template) |