<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jerry Mindek</title>
    <description>The latest articles on DEV Community by Jerry Mindek (@jermindek).</description>
    <link>https://dev.to/jermindek</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F711902%2Ff2b7d668-64c5-40fd-9692-b7f5fbf4bfac.jpg</url>
      <title>DEV Community: Jerry Mindek</title>
      <link>https://dev.to/jermindek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jermindek"/>
    <language>en</language>
    <item>
      <title>Generating AWS CloudFormation template property values at deploy time</title>
      <dc:creator>Jerry Mindek</dc:creator>
      <pubDate>Thu, 23 Sep 2021 16:36:09 +0000</pubDate>
      <link>https://dev.to/jermindek/generating-aws-cloudformation-template-property-values-at-deploy-time-3kg</link>
      <guid>https://dev.to/jermindek/generating-aws-cloudformation-template-property-values-at-deploy-time-3kg</guid>
      <description>&lt;p&gt;Do you wish you could generate property values for resources in your AWS CloudFormation template at deploy time?&lt;/p&gt;

&lt;p&gt;In this post I would like to share with you a step-by-step approach.&lt;/p&gt;

&lt;p&gt;My goal is to enable you to do this quickly and to prevent you from a nasty blind-spot that I crashed into!&lt;/p&gt;

&lt;h4&gt;
  
  
  It all began once upon...
&lt;/h4&gt;

&lt;p&gt;Summer of 2021 while I was creating an AWS Data Pipeline which manages an Apache Spark ETL application and its needed AWS resources.&lt;br&gt;
The Spark application runs in Amazon Elastic Map Reduce (EMR).&lt;br&gt;
So, the Data Pipeline is implemented with an EMR resource and activity with preconditions and post step commands, and an EC2 resource activity to execute some aws CLI tool activities. &lt;br&gt;
I packaged all this up in a nice and tidy CloudFormation template so that I can deploy it from AWS CodePipeline.&lt;/p&gt;

&lt;p&gt;Which brings us to the impetus for this post.&lt;br&gt;
I need the Data Pipeline to start daily at 3am.&lt;br&gt;
I don't want to set that value each time I deploy; I want it to be generated. &lt;/p&gt;
&lt;h4&gt;
  
  
  Initial Solution
&lt;/h4&gt;

&lt;p&gt;At first, I updated the Data Pipeline's default schedule startDate attribute at build time.&lt;br&gt;
In the CFN template, I had&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Every&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;day"&lt;/span&gt;
&lt;span class="na"&gt;          Id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DefaultSchedule"&lt;/span&gt;
&lt;span class="na"&gt;          Fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;            - Key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;period&lt;/span&gt;
&lt;span class="na"&gt;              StringValue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;days"&lt;/span&gt;
&lt;span class="na"&gt;            - Key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;startDateTime&lt;/span&gt;
&lt;span class="na"&gt;              StringValue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;&lt;span class="err"&gt;|startdate||&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;           - Key: type&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;             StringValue: "Schedule"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During execution of the build script in the build process I used sed to convert &lt;code&gt;||startdate||&lt;/code&gt; to the value calculated a couple lines earlier in the build script.&lt;br&gt;
When the template was packaged with aws cloudformation package it has a valid date time for the Data Pipeline default schedule's startDateTime attribute.&lt;/p&gt;

&lt;p&gt;Pros&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;easy to do&lt;/li&gt;
&lt;li&gt;dependable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A redeploy would use the startDate set by the build, setting the startDate in the past.&lt;/li&gt;
&lt;li&gt;Could not deploy by simply uploading the template.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't major issues. However, the solution lacks flexibility and finesse.&lt;br&gt;
I set out to fix that.&lt;/p&gt;
&lt;h4&gt;
  
  
  The better solution: Setting resource property values with deploy-time generated values
&lt;/h4&gt;

&lt;p&gt;To do this, you will need 3 additional resources in your CFN template:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AWS::CloudFormation::CustomResource resource&lt;/li&gt;
&lt;li&gt;AWS::Serverless::Function&lt;/li&gt;
&lt;li&gt;AWS::Logs::LogGroup resource&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;
  
  
  Step-by-step
&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;Create a CustomResource to capture the generated value.&lt;/li&gt;
&lt;li&gt;Create a Function with in-line code to generate the generated value&lt;/li&gt;
&lt;li&gt;Create a log group to capture log messages from the Function so that you can troubleshoot issues with the it and the related CustomResource.&lt;/li&gt;
&lt;li&gt;Use the generated value as a value for some resource property&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's not hard. Here's the YAML for each of these new AWS CFN resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;PipelineStartDate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;    Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::CloudFormation::CustomResource&lt;/span&gt;
&lt;span class="na"&gt;    Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;      ServiceToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;PipelineStartDateFunction.Arn&lt;/span&gt;

&lt;span class="na"&gt;PipelineStartDateFunctionLogs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;    Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Logs::LogGroup&lt;/span&gt;
&lt;span class="na"&gt;    Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;      LogGroupName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/aws/lambda/StartDateGenerator'&lt;/span&gt;
&lt;span class="na"&gt;      RetentionInDays&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;

&lt;span class="na"&gt;PipelineStartDateFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;    Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Lambda::Function&lt;/span&gt;
&lt;span class="na"&gt;    Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;      FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;StartDateGenerator'&lt;/span&gt;
&lt;span class="na"&gt;      Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.6&lt;/span&gt;
&lt;span class="na"&gt;      Timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
&lt;span class="na"&gt;      Role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;SimpleLambdaExecutionRole.Arn&lt;/span&gt;
&lt;span class="na"&gt;      Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.lambda_handler&lt;/span&gt;
&lt;span class="na"&gt;      Code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;        ZipFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;         from datetime import date, datetime, time&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;         from datetime import timedelta&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;         import logging&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;         import cfnresponse&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;         def lambda_handler(event, context):&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;           logging.info(f'REQUEST RECEIVED: {event}')&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;           try:&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;             tomorrow = date.today() + timedelta(days=1)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;             start_date = datetime.combine(tomorrow, time(hour=3))&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;             logging.info(f'Computed start date: {start_date}')&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;             responseData = {}&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;             responseData['StartDate'] = start_date.strftime('%Y-%m-%dT%H:%M:%S')&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;             logging.info(f'Sending this response data back to Cloudformation: {responseData}')&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;             cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourceStartDate")&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;           except:&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;             cfnresponse.send(event, context, cfnresponse.FAILED, {}, "CustomResourceStartDate")&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;           return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to use the generated value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Every&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;day"&lt;/span&gt;
&lt;span class="na"&gt;          Id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DefaultSchedule"&lt;/span&gt;
&lt;span class="na"&gt;          Fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;            - Key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;period&lt;/span&gt;
&lt;span class="na"&gt;              StringValue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;days"&lt;/span&gt;
&lt;span class="na"&gt;            - Key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;startDateTime&lt;/span&gt;
&lt;span class="na"&gt;              StringValue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;PipelineStartDate.StartDate&lt;/span&gt;
&lt;span class="na"&gt;            - Key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type&lt;/span&gt;
&lt;span class="na"&gt;              StringValue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Schedule"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Short explanation
&lt;/h4&gt;

&lt;p&gt;What we did here is create a lambda-backed CustomResource. &lt;br&gt;
The value for our default schedule attribute is the the value we get when we &lt;code&gt;!GetAtt&lt;/code&gt; the custom resource key "StartDate".&lt;br&gt;
The value is populated by our serverless function (with help from the AWS provided &lt;code&gt;cfnresponse&lt;/code&gt;) who's code is defined in-line. This allows us to include the generator inside the CFN template where it is most applicable.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Blind-Spot
&lt;/h4&gt;

&lt;p&gt;I spent more time than I am willing to admit trying to determine why my initial attempts to do this resulted in deploys that failed after an hour. &lt;br&gt;
It was impossible to debug because I didn't have any log messages from the CustomResource or the PipelineStartDateFunction.&lt;br&gt;
The key to solving the problem was to add a log group to capture messages from the PipelineStartDateFunction. &lt;br&gt;
Then I was able to goto CloudWatch, see the messages in the log group from the function that quickly explained the problem.&lt;/p&gt;

&lt;h4&gt;
  
  
  Wrap up
&lt;/h4&gt;

&lt;p&gt;I have created several full stack CloudFormation templates, each time I wondered how I could generate a value at deploy time.&lt;br&gt;
Now that I know, I see that it's not that difficult.&lt;br&gt;
My hope is that this post can give you a clear understanding of how to generated property values at deploy time allowing you to make your CloudFormation templates a little less static!&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;

&lt;p&gt;For more detailed information about some of the AWS resources I talk about, I have included several AWS documentation links for you.&lt;/p&gt;

&lt;p&gt;Custom Resources&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html"&gt;https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html"&gt;https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Serverless Functions with InLine code&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html"&gt;https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloudformation</category>
      <category>templates</category>
      <category>customresource</category>
    </item>
  </channel>
</rss>
