<?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: Sebastiaan Brozius</title>
    <description>The latest articles on DEV Community by Sebastiaan Brozius (@iskander).</description>
    <link>https://dev.to/iskander</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%2F1095974%2F09a0dbc6-2579-4f61-9801-1ebd0d5b2b17.png</url>
      <title>DEV Community: Sebastiaan Brozius</title>
      <link>https://dev.to/iskander</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iskander"/>
    <language>en</language>
    <item>
      <title>Consistently deploying Lambda functions and layers using Terraform</title>
      <dc:creator>Sebastiaan Brozius</dc:creator>
      <pubDate>Mon, 29 Dec 2025 18:07:19 +0000</pubDate>
      <link>https://dev.to/aws-builders/consistently-deploying-lambda-functions-and-layers-using-terraform-379b</link>
      <guid>https://dev.to/aws-builders/consistently-deploying-lambda-functions-and-layers-using-terraform-379b</guid>
      <description>&lt;p&gt;&lt;em&gt;The code that accompanies this blogpost can be found &lt;a href="https://github.com/justtinkering/blogpost-deploy-lambda-with-terraform" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Deploying Lambda functions to AWS using Terraform can be quite a struggle, especially when deploying from multiple environments (which only happens in dev and test environments, am I right?).&lt;/p&gt;

&lt;p&gt;Some issues you can encounter are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda functions redeploying at every &lt;code&gt;terraform apply&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Errors about missing archive files containing the Lambda function files&lt;/li&gt;
&lt;li&gt;Soft locks in the Terraform state file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post I’ll be showing you a way to be able to consistently deploy Lambda functions, only when there are changes to the code, when deploying from multiple environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  History
&lt;/h2&gt;

&lt;p&gt;There have been long time issues when deploying Lambda functions using Terraform. Some external links with examples of these issues and some attempts to tackle them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/q/52662244/19024815" rel="noopener noreferrer"&gt;https://stackoverflow.com/q/52662244/19024815&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;I have an AWS Lambda deployed successfully with Terraform:&lt;/p&gt;


&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_function"&lt;/span&gt; &lt;span class="s2"&gt;"lambda"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;filename&lt;/span&gt;                       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dist/subscriber-lambda.zip"&lt;/span&gt;
 &lt;span class="nx"&gt;function_name&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test_get-code"&lt;/span&gt;
 &lt;span class="nx"&gt;role&lt;/span&gt;                           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;my_role&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nx"&gt;handler&lt;/span&gt;                        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main.handler"&lt;/span&gt;
 &lt;span class="nx"&gt;timeout&lt;/span&gt;                        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;
 &lt;span class="nx"&gt;reserved_concurrent_executions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
 &lt;span class="nx"&gt;memory_size&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;
 &lt;span class="nx"&gt;runtime&lt;/span&gt;                        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"python3.6"&lt;/span&gt;
 &lt;span class="nx"&gt;tags&lt;/span&gt;                           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;my&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="nx"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nx"&gt;source_code_hash&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${base64sha256(file("&lt;/span&gt;&lt;span class="p"&gt;../&lt;/span&gt;&lt;span class="nx"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lambda-code&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;&lt;span class="s2"&gt;"))}"&lt;/span&gt;
 &lt;span class="nx"&gt;kms_key_arn&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;my_kms_arn&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nx"&gt;vpc_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;subnet_ids&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;my_list_of_private_subnets&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nx"&gt;security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;my_list_of_security_groups&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Now, when I run terraform plan command it says my lambda resource needs to be updated because the source_code_hash has changed, but I didn't update lambda Python codebase (which is versioned in a folder of the same repo):&lt;/p&gt;


&lt;pre class="highlight hcl"&gt;&lt;code&gt; &lt;span class="err"&gt;~&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_lambda_function&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;
 &lt;span class="nx"&gt;last_modified&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;                     &lt;span class="s2"&gt;"2018-10-05T07:10:35.323+0000"&lt;/span&gt; &lt;span class="err"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;computed&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nx"&gt;source_code_hash&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;                  &lt;span class="s2"&gt;"jd6U44lfe4124vR0VtyGiz45HFzDHCH7+yTBjvr400s="&lt;/span&gt; &lt;span class="err"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"JJIv/AQoPvpGIg01Ze/YRsteErqR0S6JsqKDNShz1w78"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nulldog.com/trigger-terraform-lambda-code-uploads-a-guide" rel="noopener noreferrer"&gt;https://nulldog.com/trigger-terraform-lambda-code-uploads-a-guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Trigger updates with null_resource:&lt;/p&gt;

&lt;p&gt;If you need more control or want to trigger updates based on other resources, use null_resource:&lt;/p&gt;


&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"null_resource"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_update"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;triggers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;code_hash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filebase64sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"my-function.zip"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"echo 'Code updated, triggering Lambda deployment...'"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_function"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# ... other configurations&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;null_resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_update&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This example triggers an update whenever the hash of "my-function.zip" changes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hashicorp/terraform-provider-aws/issues/17989" rel="noopener noreferrer"&gt;https://github.com/hashicorp/terraform-provider-aws/issues/17989&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Hi All,&lt;/p&gt;

&lt;p&gt;We are on Terraform 0.14.6 and experiencing the following issue.&lt;br&gt;
We are providing source_code_hash for the aws_lambda_layer_version in the plan terraform accepts it but writes totally different to the state file.&lt;/p&gt;

&lt;p&gt;In the plan the source_code_hash is &lt;code&gt;FyN0P9BvuTm023dkHFaWvAGmyD0rlhujGsPCTqaBGyw=&lt;/code&gt; however in the state file it becames &lt;code&gt;c3forIEso3mJh74PY6HrhFK94GfJvQ4zG9rEIgBCBhw=&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When I check the layer in AWS CLI the "CodeSha256": &lt;code&gt;c3forIEso3mJh74PY6HrhFK94GfJvQ4zG9rEIgBCBhw=&lt;/code&gt;,&lt;/p&gt;

&lt;p&gt;Based on this it does not matter what kind of source_code_hash I can not overwrite hash of filename.&lt;/p&gt;

&lt;p&gt;TF config.&lt;/p&gt;


&lt;pre class="highlight hcl"&gt;&lt;code&gt;  &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_layer_version"&lt;/span&gt; &lt;span class="s2"&gt;"loader"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;layer_name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"loader"&lt;/span&gt;
  &lt;span class="nx"&gt;compatible_runtimes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"python3.8"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;filename&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda_layer.zip"&lt;/span&gt;
  &lt;span class="nx"&gt;source_code_hash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filebase64sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"lambda_layer.zip"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;What you can see in all these examples, is that a hash is calculated to determine if the code has changed. That in itself isn’t an issue, but what &lt;em&gt;is&lt;/em&gt; an issue, is that they all use a &lt;code&gt;base64&lt;/code&gt; encoded hash.&lt;/p&gt;

&lt;p&gt;How to make your Lambda function deployment cross-environment friendly &lt;br&gt;
The issue with &lt;code&gt;base64&lt;/code&gt; encoding, is that the resulting hash for the same data, will differ across environments (operating systems, user settings).&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://devopsx.com/terraform-lambda-source-code-hash/" rel="noopener noreferrer"&gt;following post&lt;/a&gt; describes this issue:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The root cause of is this is difference in packaging on different machines and bad documentation. Well, and an asinine design choice on AWS part.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;source_code_hash&lt;/code&gt; gets &lt;a href="https://github.com/hashicorp/terraform-provider-aws/issues/7385#issuecomment-728596589" rel="noopener noreferrer"&gt;overwritten&lt;/a&gt; by AWS-provided data upon response.&lt;br&gt;
The documentation for &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function#source_code_hash" rel="noopener noreferrer"&gt;source_code_hash&lt;/a&gt; (aka &lt;code&gt;output_base64sha256&lt;/code&gt;, &lt;code&gt;filebase64sha256&lt;/code&gt;) lies:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(String) The base64-encoded SHA256 checksum of output archive file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Why would you even want to base64-encode a hash? The purpose of base64 encoding is to do away with non-printable chars, which a hash doesn’t have.&lt;/p&gt;

&lt;p&gt;Turns out, what they actually do is compute sha256, then take the resulting text string and treat its characters as binary values, then base64 that: &lt;code&gt;sha256sum lambda.zip | xxd -r -p | base64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The problem is, recent &lt;code&gt;zip&lt;/code&gt; versions store file permissions, and different umask values on different machines result in different permissions, which in turn produces different archives with different hashes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But when you’re in a team where both Windows and macOS/Linux are being used, you have an additional challenge because the filesystems (and thus the filename of the archive-file) differ quite a lot.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting it to work
&lt;/h2&gt;

&lt;p&gt;After some tinkering, I came to the following solution.&lt;/p&gt;

&lt;p&gt;In my example, I supply the Lambda function code as a directory, containing the required file(s). In code, I create an archive file from that directory, using the data-source &lt;code&gt;archive_file&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, we create a random UUID, based on all the files (excluding ZIP-files) in the source directory (and child directories), and creating an &lt;code&gt;MD5&lt;/code&gt; hash for each of them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a random UUID which is used to trigger a redeploy of the function.&lt;/span&gt;
&lt;span class="c1"&gt;# The MD5 hash for each file (except ZIP-files) will be calculated and if any of those changes, &lt;/span&gt;
&lt;span class="c1"&gt;# it will trigger a redeploy of the aws_lambda_function resource `lambda_function`.&lt;/span&gt;
&lt;span class="c1"&gt;# We cannot rely on a base64 hash, because the seed for that is environment dependent.&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_uuid"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_function"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;keepers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;setunion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;toset&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;fileset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.root}/lambda_function/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"**"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="nx"&gt;if&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".zip"&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;filemd5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.root}/lambda_function/${filename}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I chose to use &lt;code&gt;MD5&lt;/code&gt; here, because we’re not using it for cryptographic purposes. You can just as easily select &lt;code&gt;SHA256&lt;/code&gt; or &lt;code&gt;SHA512&lt;/code&gt;, which do require some additional resources when calculating them (which might very well be negligible).&lt;/p&gt;

&lt;p&gt;Next, we create a ZIP-file and send it to a different path than the source directory (which is excluded in &lt;code&gt;.gitignore&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create an archive file of the function directory&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"archive_file"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_function"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"zip"&lt;/span&gt;
  &lt;span class="nx"&gt;source_dir&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.root}/lambda_function"&lt;/span&gt;
  &lt;span class="nx"&gt;output_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.root}/lambda_output/${var.function_name}.zip"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When any of the source files changes, a new random UUID will be generated. To make sure this triggers a re-deployment of the Lambda function, we’ll set the &lt;code&gt;random_uuid&lt;/code&gt; resource as a replacement trigger for the Lambda function resource.&lt;/p&gt;

&lt;p&gt;To do this, we add a &lt;code&gt;lifecycle&lt;/code&gt;-block to the &lt;code&gt;aws_lambda_function&lt;/code&gt; resource, with a &lt;code&gt;replace_triggered_by&lt;/code&gt; block, targeting the &lt;code&gt;random_uuid.lambda&lt;/code&gt; resource.&lt;br&gt;
The archive file will be created every time a &lt;code&gt;terraform plan&lt;/code&gt; or &lt;code&gt;terraform apply&lt;/code&gt; is run. Since the location of the resulting archive file will be different for every user (remember, we’re talking about dev/test deployments here!), we also need to make sure that the filename of the archive file doesn’t trigger unnecessary redeployments, by adding an &lt;code&gt;ignore_changes&lt;/code&gt; block to the &lt;code&gt;lifecycle&lt;/code&gt; block, targeting the filename property of the &lt;code&gt;aws_lambda_function&lt;/code&gt; resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the Lambda function&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_function"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_function"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_name&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_execution_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;handler&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.function_name}.${var.handler_name}"&lt;/span&gt;
  &lt;span class="nx"&gt;runtime&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;
  &lt;span class="nx"&gt;timeout&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;
  &lt;span class="nx"&gt;architectures&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;architectures&lt;/span&gt;

  &lt;span class="c1"&gt;# Use the filename of the archive file as input for the function&lt;/span&gt;
  &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archive_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_path&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_execution_role&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;replace_triggered_by&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="c1"&gt;# Trigger a replace of the function when any of the function source files changes.&lt;/span&gt;
      &lt;span class="nx"&gt;random_uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_function&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;ignore_changes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="c1"&gt;# Ignore the source filename of the object itself, because that can change between &lt;/span&gt;
      &lt;span class="c1"&gt;# users/machines/operating systems.&lt;/span&gt;
      &lt;span class="nx"&gt;filename&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this has been applied, when you now run the same code across different environments, no unexpected/undesired re-deployments of the Lambda function will occur.&lt;/p&gt;

&lt;p&gt;This same approach can be used for deploying Lambda Layers. The difference there is that there’s an intermediate in the form of an S3 object, which will be replaced when there’s a change in any of the source files. Which, in turn, triggers replacing the Lambda Layer with a new version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_uuid"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_layer"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;keepers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;setunion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;toset&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;fileset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.root}/lambda_layer/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"**"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="nx"&gt;if&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".zip"&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;filemd5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.root}/lambda_layer/${filename}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"archive_file"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_layer"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"zip"&lt;/span&gt;
  &lt;span class="nx"&gt;source_dir&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.root}/lambda_layer"&lt;/span&gt;
  &lt;span class="nx"&gt;output_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.root}/lambda_output/${var.layer_name}.zip"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_object"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archive_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_layer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;key&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layer_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"zip"&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="nx"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="err"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="err"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_bucket&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archive_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_path&lt;/span&gt;
  &lt;span class="nx"&gt;checksum_algorithm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SHA256"&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;replace_triggered_by&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;random_uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_layer&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;ignore_changes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="c1"&gt;# Ignore the source of the object itself, because that can change between machines/operating systems&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_layer_version"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_layer"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;layer_name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layer_name&lt;/span&gt;
  &lt;span class="nx"&gt;compatible_runtimes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;source_code_hash&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checksum_sha256&lt;/span&gt;
  &lt;span class="nx"&gt;s3_bucket&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;
  &lt;span class="nx"&gt;s3_key&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When running all your IaC changes through a pipeline (as you should for at least production and staging/acceptance), this should not be an issue for you. But having your &lt;code&gt;terraform plan/apply&lt;/code&gt; cluttered with false changes because of differences between contributor systems for your development and test-stages, should be in the past with this approach.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/terraform-aws-modules/terraform-aws-lambda" rel="noopener noreferrer"&gt;Lambda module by Anton Babenko&lt;/a&gt; also uses base64 in its hash calculations, as well as the filename of the archive file. So if you run into (one of) the mentioned issues with that module, now you know why; I’ll be working on a PR to get the base64 part fixed for that module.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The goal of this post is to show you how to tackle (at least) two possible issues you might have with deploying Lambda functions and/or layers using Terraform. &lt;br&gt;
I hope to have given you some insight into the causes of these issues, and with that, to make an informed decision on how to tackle them.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Setting up AWS IoT Core using Terraform</title>
      <dc:creator>Sebastiaan Brozius</dc:creator>
      <pubDate>Sun, 29 Dec 2024 20:55:47 +0000</pubDate>
      <link>https://dev.to/aws-builders/setting-up-aws-iot-core-using-terraform-13lc</link>
      <guid>https://dev.to/aws-builders/setting-up-aws-iot-core-using-terraform-13lc</guid>
      <description>&lt;p&gt;&lt;em&gt;The code that accompanies this blogpost can be found &lt;a href="https://github.com/justtinkering/blogpost-iot-core" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've been tinkering with AWS IoT Core this year, and wanted to put at least some of what I've found and done into a blog, so here it is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/iot-core/" rel="noopener noreferrer"&gt;AWS IoT Core&lt;/a&gt; is the AWS Internet-of-Things service:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AWS IoT provides the cloud services that connect your IoT devices to other devices and AWS cloud services. AWS IoT provides device software that can help you integrate your IoT devices into AWS IoT-based solutions. If your devices can connect to AWS IoT, AWS IoT can connect them to the cloud services that AWS provides.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdd61xi2u2fnzkh647u96.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdd61xi2u2fnzkh647u96.png" alt="What is AWS IoT" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setting up an IoT environment within AWS is pretty easy, but I want to put it in code, so I can easily reproduce the environment I set up, and also be able to easily remove the configuration. My tool of choice is (still) Terraform.&lt;/p&gt;

&lt;p&gt;We'll need to create the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IAM role used for registering new things&lt;/li&gt;
&lt;li&gt;IoT policy for devices&lt;/li&gt;
&lt;li&gt;IoT thing group (optional)&lt;/li&gt;
&lt;li&gt;IoT thing type (optional)&lt;/li&gt;
&lt;li&gt;Pre-provisioning Lambda (optional)&lt;/li&gt;
&lt;li&gt;IoT fleet provisioning template (with optional pre-provisioning hook)&lt;/li&gt;
&lt;li&gt;IoT policy for provisioning &lt;/li&gt;
&lt;li&gt;Certificate for claim-based provisioning&lt;/li&gt;
&lt;li&gt;IoT event configurations (optional)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've included &lt;a href="https://github.com/justtinkering/blogpost-iot-core/tree/main/terraform" rel="noopener noreferrer"&gt;Terraform code&lt;/a&gt; in the accompanying GitHub repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  IAM role for registering new things
&lt;/h2&gt;

&lt;p&gt;When provisioning, the role used by IoT requires permissions to register new things. A managed policy &lt;code&gt;AWSIoTThingsRegistration&lt;/code&gt; exists for this purpose, which should be assigned to a (new) role.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the IoT provisioning IAM role&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"iot_fleet_provisioning"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"fleet-provisioning-role"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s2"&gt;"Version"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Statement"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Effect"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Principal"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"Service"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"iot.amazonaws.com"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s2"&gt;"Action"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Attach the managed role for registering things to the provisioning role&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"iot_fleet_provisioning"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iot_fleet_provisioning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;policy_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Ensure that these (managed) policies are the only ones attached to the provisioning role on every apply&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy_attachments_exclusive"&lt;/span&gt; &lt;span class="s2"&gt;"iot_fleet_provisioning"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;role_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iot_fleet_provisioning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;policy_arns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  IoT policy for devices
&lt;/h2&gt;

&lt;p&gt;What actions are allowed for a thing, will be defined in an IoT policy. A thing authenticates with IoT Core using a device-specific certificate. During provisioning, the policy will be assigned to the device-specific certificate, as defined in the fleet provisioning template.&lt;/p&gt;

&lt;p&gt;Below policy allows&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a device policy&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iot_policy"&lt;/span&gt; &lt;span class="s2"&gt;"iot_device"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"device-policy"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s2"&gt;"Version"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Statement"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Effect"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Action"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"iot:Publish"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"iot:Receive"&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s2"&gt;"Resource"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"arn:aws:iot:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:topic/$${iot:Connection.Thing.ThingName}/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Effect"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Action"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"iot:Subscribe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Resource"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"arn:aws:iot:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:topicfilter/$aws/things/$${iot:Connection.Thing.ThingName}/shadow/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Condition"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"Bool"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"iot:Connection.Thing.IsAttached"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="s2"&gt;"true"&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s2"&gt;"Effect"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Action"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"iot:Connect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Resource"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  IoT thing group (optional)
&lt;/h2&gt;

&lt;p&gt;Thing groups are optional, and can be used to group things together. There are &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/thing-groups.html" rel="noopener noreferrer"&gt;static&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/dynamic-thing-groups.html" rel="noopener noreferrer"&gt;dynamic&lt;/a&gt; thing groups. There's a limit of 100 dynamic groups per account, so if you've got a large environment with possibly a lot of groups, think ahead on whether or not you can use dynamic groups.&lt;/p&gt;

&lt;p&gt;Dynamic thing groups are created from specific search queries in the registry. Search query parameters such as device connectivity, device shadow creation, and AWS IoT Device Defender violations data support this. Dynamic thing groups require fleet indexing enabled to index, search, and aggregate your devices' data.&lt;/p&gt;

&lt;p&gt;Static thing groups allow you to manage several things at once by categorizing them into groups. Static thing groups contain a group of things that are managed by using the console, CLI, or the API.&lt;/p&gt;

&lt;p&gt;In this example we're using a static group, which the new thing will be assigned to by the fleet provisioning template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a Thing group&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iot_thing_group"&lt;/span&gt; &lt;span class="s2"&gt;"provisioning"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Provisioning"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  IoT thing type (optional)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/thing-types.html" rel="noopener noreferrer"&gt;Thing types&lt;/a&gt; allow you to store description and configuration information that is common to all things associated with the same thing type.&lt;/p&gt;

&lt;p&gt;Although thing types are optional, their use makes it easier to discover things.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Things with a thing type can have up to 50 attributes.&lt;/li&gt;
&lt;li&gt;Things without a thing type can have up to three attributes.&lt;/li&gt;
&lt;li&gt;A thing can be associated with only one thing type.&lt;/li&gt;
&lt;li&gt;There is no limit on the number of thing types you can create in your account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this example we're creating a single thing type, which is assigned to the thing by the fleet provisioning template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a Thing type&lt;/span&gt;
&lt;span class="c1"&gt;# The delete process of these is that they'll be deprecated first,&lt;/span&gt;
&lt;span class="c1"&gt;# and 5 minutes later they can be deleted.&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iot_thing_type"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Example"&lt;/span&gt;

  &lt;span class="nx"&gt;properties&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Example"&lt;/span&gt;
    &lt;span class="nx"&gt;searchable_attributes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="c1"&gt;# There's a maximum of 3 searchable attributes per Thing Type&lt;/span&gt;
      &lt;span class="s2"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pre-provisioning Lambda (optional)
&lt;/h2&gt;

&lt;p&gt;AWS recommends using &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/pre-provisioning-hook.html" rel="noopener noreferrer"&gt;pre-provisioning hook functions&lt;/a&gt; when creating provisioning templates to allow more control of which and how many devices your account onboards. Pre-provisioning hooks are Lambda functions that validate parameters passed from the device before allowing the device to be provisioned. This Lambda function must exist in your account before you provision a device because it's called every time a device sends a request through &lt;code&gt;RegisterThing&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this example we're deploying a simple Lambda-function used for pre-provisioning. No logic is built into the Lambda, but it shows that you can have a gatekeeper present in your provisioning process. You could, for example, check if the license-number provided by the thing for registering is valid, if the IP-address of the thing is as expected, if you've reached your maximum number of things you want to register, et cetera.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pre_provisioning_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# You can put code here to check if a device trying to connect
&lt;/span&gt;    &lt;span class="c1"&gt;# should be allowed or not, like checking if any of the provided
&lt;/span&gt;    &lt;span class="c1"&gt;# attributes are valid.
&lt;/span&gt;    &lt;span class="c1"&gt;# This function has to be able to respond within 5 seconds,
&lt;/span&gt;    &lt;span class="c1"&gt;# otherwise the provisioning request fails.
&lt;/span&gt;    &lt;span class="c1"&gt;# Reference: https://docs.aws.amazon.com/iot/latest/developerguide/pre-provisioning-hook.html
&lt;/span&gt;
    &lt;span class="c1"&gt;# If you want to allow the device to connect to IoT Core, return this:
&lt;/span&gt;    &lt;span class="c1"&gt;# 'allowProvisioning': True
&lt;/span&gt;
    &lt;span class="c1"&gt;# If you want to disallow the device to connect to IoT Core, return this:
&lt;/span&gt;    &lt;span class="c1"&gt;# 'allowProvisioning': False
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;allowProvisioning&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  IoT fleet provisioning template (with optional pre-provisioning hook)
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/provision-template.html#fleet-provision-template" rel="noopener noreferrer"&gt;fleet provisioning template&lt;/a&gt; is what ties together all the resources we created previously.&lt;/p&gt;

&lt;p&gt;This template defines the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The parameters it expects to receive from a thing that's submitting itself for registration, to be used in the template&lt;/li&gt;
&lt;li&gt;The resource required for the thing to communicate with IoT; a thing, a certificate and one or more policies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where we assign the device policy to the thing, through the device-specific certificate which will be created for the thing. It will assign the thing to the initial thing group, and assign a thing type. By using multiple fleet provisioning templates, with different provisioning certificates, you can easily register different types of devices in your IoT environment, and assign device-specific attributes to them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the fleet provisioning template&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iot_provisioning_template"&lt;/span&gt; &lt;span class="s2"&gt;"fleet"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"fleet-provisioning-tpl"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Fleet provisioning template for ${var.name}"&lt;/span&gt;
  &lt;span class="nx"&gt;provisioning_role_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iot_fleet_provisioning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;template_body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s2"&gt;"DeviceConfiguration"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="s2"&gt;"Parameters"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"License"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Type"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="s2"&gt;"AWS::IoT::Certificate::Id"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Type"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"Resources"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"policy"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Type"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"AWS::IoT::Policy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Properties"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"PolicyName"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws_iot_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iot_device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="s2"&gt;"certificate"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Type"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"AWS::IoT::Certificate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Properties"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"CertificateId"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"Ref"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"AWS::IoT::Certificate::Id"&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="s2"&gt;"Status"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Active"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="s2"&gt;"thing"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Type"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"AWS::IoT::Thing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"OverrideSettings"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"AttributePayload"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"MERGE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"ThingGroups"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"REPLACE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"ThingTypeName"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"REPLACE"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s2"&gt;"Properties"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"AttributePayload"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"license"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Ref"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"License"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="s2"&gt;"ThingGroups"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nx"&gt;aws_iot_thing_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provisioning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="s2"&gt;"ThingTypeName"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws_iot_thing_type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"ThingName"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"Fn::Join"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"iot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="s2"&gt;"Ref"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"License"&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;pre_provisioning_hook&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;target_arn&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iot_preprovisioning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
    &lt;span class="nx"&gt;payload_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2020-04-01"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  IoT policy for provisioning
&lt;/h2&gt;

&lt;p&gt;The environment we're setting up, uses the '&lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#claim-based" rel="noopener noreferrer"&gt;provisioning with claim&lt;/a&gt;' provisioning method. This means we don't have to create device certificates in advance, but a new device will register itself using a generic provisioning certificate.&lt;/p&gt;

&lt;p&gt;Because this certificate will be 'out in the wild', we want to restrict the permissions it provides as much as possible. This means the certificate should only be allowed to be used to register a new thing, and create a device-specific certificate for that thing. This is also why we want to add the pre-provisioning hook as a gatekeeper.&lt;/p&gt;

&lt;p&gt;Below policy allows the thing to connect to IoT, subscribe to, publish too and receive from MQTT topics related to certificate creation and provisioning, specific to a provisioning template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the claims provisioning certificate policy&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iot_policy"&lt;/span&gt; &lt;span class="s2"&gt;"provisioning"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"claim-certificate-policy"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s2"&gt;"Version"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Statement"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Effect"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Action"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"iot:Connect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Resource"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Effect"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Action"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"iot:Publish"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"iot:Receive"&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s2"&gt;"Resource"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"arn:aws:iot:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:topic/$aws/certificates/create/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"arn:aws:iot:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:topic/$aws/provisioning-templates/${aws_iot_provisioning_template.fleet.name}/provision/*"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Effect"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Action"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"iot:Subscribe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Resource"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"arn:aws:iot:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:topicfilter/$aws/certificates/create/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"arn:aws:iot:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:topicfilter/$aws/provisioning-templates/${aws_iot_provisioning_template.fleet.name}/provision/*"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Certificate for claim-based provisioning
&lt;/h2&gt;

&lt;p&gt;As the provisioning certificate, we're going to use a self-signed certificate. Reasons for using a self-signed certificate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ability to set how long the certificate will be valid&lt;/li&gt;
&lt;li&gt;Not having to set up a Certificate Authority&lt;/li&gt;
&lt;li&gt;An AWS IoT-generated certificate doesn't have the proper allowed uses to connect to MQTT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this example we're setting the lifetime of the certificate to 365 days. The lifetime that's right for your environment will depend on things like how often you'll update/deploy the application that includes the provisioning template, and how easy it is to update the certificate in that application.&lt;/p&gt;

&lt;p&gt;The IoT policy for provisioning will be assigned to this certificate, to make sure it's not going to be used for any actions other than registering a new thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a self-signed provisioning certificate&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"tls_private_key"&lt;/span&gt; &lt;span class="s2"&gt;"provisioning"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;algorithm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RSA"&lt;/span&gt;
  &lt;span class="nx"&gt;rsa_bits&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"tls_self_signed_cert"&lt;/span&gt; &lt;span class="s2"&gt;"provisioning"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;private_key_pem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_private_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provisioning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key_pem&lt;/span&gt;

  &lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;common_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"IoT Provisioning"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;validity_period_hours&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8760&lt;/span&gt; &lt;span class="c1"&gt;# 365 days&lt;/span&gt;

  &lt;span class="nx"&gt;allowed_uses&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"key_encipherment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"digital_signature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"server_auth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Add the provisioning certificate and attach the provisioning policy&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iot_certificate"&lt;/span&gt; &lt;span class="s2"&gt;"iot_fleet_provisioning"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;certificate_pem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_self_signed_cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provisioning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_pem&lt;/span&gt;
  &lt;span class="nx"&gt;active&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iot_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"iot_fleet_provisioning_certificate"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iot_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provisioning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iot_certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iot_fleet_provisioning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  IoT event configurations (optional)
&lt;/h2&gt;

&lt;p&gt;If you want to be able to act on &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-events.html#iot-events-enable" rel="noopener noreferrer"&gt;IoT events&lt;/a&gt;, those will need to be enabled.&lt;/p&gt;

&lt;p&gt;Enabling these, facilitates these events being published to specific MQTT topics. These can be used in &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-rules.html" rel="noopener noreferrer"&gt;IoT rules&lt;/a&gt;, to trigger actions when specific events happen. They can also be used by things, as long as the device policy grants permissions to subscribe to and receive from those topics.&lt;/p&gt;

&lt;p&gt;This example enables events related to thing creation, updates, and deletion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Manage events that will publish messages to MQTT topics.&lt;/span&gt;
&lt;span class="c1"&gt;# Reference: https://docs.aws.amazon.com/iot/latest/developerguide/iot-events.html#iot-events-enable&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iot_event_configurations"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event_configurations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"THING"&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"THING_GROUP"&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"THING_TYPE"&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"THING_GROUP_MEMBERSHIP"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"THING_GROUP_HIERARCHY"&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"THING_TYPE_ASSOCIATION"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"JOB"&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"JOB_EXECUTION"&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"POLICY"&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"CERTIFICATE"&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"CA_CERTIFICATE"&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  IoT logging to CloudWatch (not included)
&lt;/h2&gt;

&lt;p&gt;By default, AWS IoT Core doesn't log to CloudWatch. You can enable this in the console under &lt;strong&gt;Settings&lt;/strong&gt;, or using the Terraform resource &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iot_logging_options" rel="noopener noreferrer"&gt;&lt;code&gt;aws_iot_logging_options&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
This will incur extra costs, so do keep an eye on that.&lt;/p&gt;
&lt;h2&gt;
  
  
  IaC caveats
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Lack of resource support
&lt;/h3&gt;

&lt;p&gt;Not all resources can be created using Terraform. Support for jobs and jobs templates is missing, for example, which are resources that really help to create a workflow for provisioning and staging your things. &lt;/p&gt;

&lt;p&gt;These resources &lt;em&gt;can&lt;/em&gt; be created using the AWS SDK, so a workaround for this shortcoming is to create a Lambda that performs the desired action in a dynamic environment, or use the SDK in a script, when your environment is more static.&lt;/p&gt;

&lt;p&gt;A way to leverage a Lambda function when deploying your environment using Terraform, is the resource &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_invocation" rel="noopener noreferrer"&gt;&lt;code&gt;aws_lambda_invocation&lt;/code&gt;&lt;/a&gt;. This way, if you set the triggers correctly, the Lambda-function will be invoked when any of the triggers changes.&lt;/p&gt;

&lt;p&gt;Another solution can be to use a step function to orchestrate the provisioning of a new thing, but might be a bit of overkill, depending on the size of your environment. Starting out with one or more (simple) Lambda functions and later on refactoring this to a step function is always an option.&lt;/p&gt;
&lt;h3&gt;
  
  
  Destroying your environment
&lt;/h3&gt;

&lt;p&gt;When destroying the environment using Terraform, any device certificate needs to be deactivated, and detached from any policy (and thing). Otherwise the policies cannot be removed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Sample client
&lt;/h2&gt;

&lt;p&gt;Now that we've set up the IoT environment, it's time to test it. I've included a &lt;a href="https://github.com/justtinkering/blogpost-iot-core/tree/main/sample_client" rel="noopener noreferrer"&gt;Python sample client&lt;/a&gt; in the accompanying GitHub repository, which registers itself with IoT Core, and writes the device-specific certificates to disk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ python3 ./iotservice.py
Connecting to akgbiozgh01fa-ats.iot.eu-west-1.amazonaws.com with client ID &lt;span class="s1"&gt;'iot-123'&lt;/span&gt;...
Lifecycle Connection Success
Connected!
Subscribing to CreateKeysAndCertificate Accepted topic...
Subscribing to CreateKeysAndCertificate Rejected topic...
Subscribing to RegisterThing Accepted topic...
Subscribing to RegisterThing Rejected topic...
Publishing to CreateKeysAndCertificate...
Waiting... CreateKeysAndCertificateResponse: null
Published CreateKeysAndCertificate request..
Received a new message awsiot.iotidentity.CreateKeysAndCertificateResponse&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;certificate_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;CERTIFICATE_ID&amp;gt;'&lt;/span&gt;, &lt;span class="nv"&gt;certificate_ownership_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;CERTIFICATE_OWNERSHIP_TOKEN&amp;gt;'&lt;/span&gt;, &lt;span class="nv"&gt;certificate_pem&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;CERTIFICATE_PEM&amp;gt;'&lt;/span&gt;, &lt;span class="nv"&gt;private_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;PRIVATE_KEY&amp;gt;'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Publishing to RegisterThing topic...
Waiting... RegisterThingResponse: null
Published RegisterThing request..
Received a new message awsiot.iotidentity.RegisterThingResponse&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;device_configuration&lt;/span&gt;&lt;span class="o"&gt;={}&lt;/span&gt;, &lt;span class="nv"&gt;thing_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'iot-123'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Exiting Sample: success
Stop the Client...
No Client to stop
Thing name: iot-123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example is based on &lt;a href="https://github.com/aws/aws-iot-device-sdk-python-v2" rel="noopener noreferrer"&gt;examples provided by AWS&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Device registration caveats
&lt;/h2&gt;

&lt;p&gt;The device-specific certificates are written to disk in the function &lt;code&gt;registerthing_execution_accepted&lt;/code&gt; in &lt;code&gt;fleetprovisioning_mqtt5.py&lt;/code&gt;. IoT Core creates the device certificates before pre-provisioning has finished. When writing the certificates to disk, while the device is rejected by pre-provisioning, any later attempts to connect can fail, because there are already device-specific certificates present on the device. That would mean the certificates on the device need to be removed, before a new attempt can be made.&lt;/p&gt;

&lt;p&gt;Also, because the certificate is created before the device is actually accepted, there will be certificates listed in IoT Core with the status &lt;code&gt;Pending activation&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I've had fun this year figuring out things like this, and hope I've been able to provide you with enough information to set up your own IoT Core environment and play around with it.&lt;/p&gt;

&lt;p&gt;Think ahead of the challenges you think you'll be facing, and be agile. Start small, and prepare for expanding to a larger scale. And as always, variables and requirements can (and probably will) change. Knowing what your options are, what the pros and cons are of those options will greatly help in picking the solutions you need both short, and long term.&lt;/p&gt;

</description>
      <category>awsiotcore</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Exporting an AMI to multiple formats</title>
      <dc:creator>Sebastiaan Brozius</dc:creator>
      <pubDate>Sat, 07 Dec 2024 21:11:56 +0000</pubDate>
      <link>https://dev.to/aws-builders/exporting-an-ami-to-multiple-formats-e7b</link>
      <guid>https://dev.to/aws-builders/exporting-an-ami-to-multiple-formats-e7b</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a short follow-up on my previous &lt;a href="https://dev.to/aws-builders/creating-an-ami-with-image-builder-38k5"&gt;post&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In my previous post about creating an AMI with Image Builder, I &lt;a href="https://docs.aws.amazon.com/vm-import/latest/userguide/vmexport_image.html" rel="noopener noreferrer"&gt;exported that AMI&lt;/a&gt; to a different image format (&lt;code&gt;vmdk&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;There's two things I ran into with that solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can only export to a single other image format&lt;/li&gt;
&lt;li&gt;There is no apparent correlation between the exported image and the original AMI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To work around those two items, I created an ECS task definition, which uses a container with QEMU installed, as well as Python and Boto3. The entrypoint for the container is a Python script to handle the image conversion.&lt;/p&gt;

&lt;p&gt;I set up the Image Builder pipeline without the export to S3, and have it send a &lt;a href="https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-sns.html" rel="noopener noreferrer"&gt;notification to SNS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The notification triggers a Lambda function (for which I used Python), which checks the status (&lt;code&gt;event['Records'][0]['Sns']['Message']['state']['status']&lt;/code&gt;) of the pipeline run. This will be either &lt;code&gt;FAILED&lt;/code&gt; or &lt;code&gt;AVAILABLE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The SNS message also contains the AMI ID (&lt;code&gt;event['Records'][0]['Sns']['Message']['outputResources']['amis'][0]['image']&lt;/code&gt;), pipeline version (&lt;code&gt;event['Records'][0]['Sns']['Message']['version']&lt;/code&gt;), build version (&lt;code&gt;event['Records'][0]['Sns']['Message']['buildVersion']&lt;/code&gt;) and the name of the Image Builder pipeline (&lt;code&gt;event['Records'][0]['Sns']['Message']['name']&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;These are used by the Lambda function, to &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecs/client/run_task.html" rel="noopener noreferrer"&gt;run an ECS task&lt;/a&gt;, using the pre-defined task definition with these values as environment variables for the ECS task.&lt;/p&gt;

&lt;p&gt;The ECS task uses the AMI ID to start an export, which takes time. How much, depends on the AMI you're exporting. Since the export is initiated &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/export_image.html" rel="noopener noreferrer"&gt;using Boto3&lt;/a&gt;, we get the export image task ID (&lt;code&gt;ExportImageTaskId&lt;/code&gt;) in the response; this gives us the correlation we miss when we do the export using the distribution configuration of the pipeline. Once the export is done, you can pick up the exported image, and do additional conversions using the &lt;code&gt;qemu-img&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;You can use the pipeline name, version and build version to name the images, so you can correlate them to the actual pipeline run that created the AMI you've created your export(s) from.&lt;/p&gt;

&lt;p&gt;For exporting the images, the filesystem that's assigned to the Lambda function is used. In the task definition you can override the storage size using the &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-ephemeralstorage.html" rel="noopener noreferrer"&gt;&lt;code&gt;EphemeralStorage&lt;/code&gt; setting&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When converting to multiple image formats, you might want to consider using multiprocessing. That way you can upload an image as soon as it's conversion is done, while also being able to start conversion for the next image format.&lt;/p&gt;

&lt;p&gt;Once all the conversions are done, you could, once again, send out an SNS message to inform you of the status of the conversion process.&lt;/p&gt;

&lt;p&gt;No code this time, but I did want to at least describe how you can run your custom process once the AMI is created.&lt;/p&gt;

</description>
      <category>awsimagebuilder</category>
      <category>ecs</category>
    </item>
    <item>
      <title>Creating an AMI with Image Builder</title>
      <dc:creator>Sebastiaan Brozius</dc:creator>
      <pubDate>Tue, 05 Nov 2024 23:37:13 +0000</pubDate>
      <link>https://dev.to/aws-builders/creating-an-ami-with-image-builder-38k5</link>
      <guid>https://dev.to/aws-builders/creating-an-ami-with-image-builder-38k5</guid>
      <description>&lt;p&gt;&lt;em&gt;The code that accompanies this blogpost can be found &lt;a href="https://github.com/justtinkering/blogpost-imagebuilder" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update 2024-12-07:&lt;/strong&gt; In the examples I use Amazon Linux 2023, which cannot be exported to other formats. I've changed the code in the repository to use an Ubuntu 24.04 base image, which &lt;em&gt;can&lt;/em&gt; be exported.&lt;/p&gt;

&lt;p&gt;I've been working with AWS Image Builder a lot more over the last couple of months, while replacing a &lt;code&gt;Packer&lt;/code&gt; setup that was run on a Windows laptop, with Image Builder.&lt;/p&gt;

&lt;p&gt;From the &lt;a href="https://aws.amazon.com/image-builder/" rel="noopener noreferrer"&gt;AWS Image Builder landing page&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EC2 Image Builder simplifies the building, testing, and deployment of Virtual Machine and container images for use on AWS or on-premises.&lt;/p&gt;

&lt;p&gt;Keeping Virtual Machine and container images up-to-date can be time consuming, resource intensive, and error-prone. Currently, customers either manually update and snapshot VMs or have teams that build automation scripts to maintain images.&lt;/p&gt;

&lt;p&gt;Image Builder significantly reduces the effort of keeping images up-to-date and secure by providing a simple graphical interface, built-in automation, and AWS-provided security settings. With Image Builder, there are no manual steps for updating an image nor do you have to build your own automation pipeline.&lt;/p&gt;

&lt;p&gt;Image Builder is offered at no cost, other than the cost of the underlying AWS resources used to create, store, and share the images.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are some caveats when using Image Builder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EBS encryption by default should be off. An encrypted volume cannot be exported to an alternative image format.&lt;/li&gt;
&lt;li&gt;The S3 bucket the exported image will be stored in, should use the AWS managed KMS key for S3 (&lt;code&gt;SSE-S3&lt;/code&gt;) for encryption.&lt;/li&gt;
&lt;li&gt;AWS encourages you to use IMDSv2 when running EC2 instances. This requires an adjustments to any scripts querying the instance metadata, as well as an adjustment to the maximum number of hops for an HTTP put request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More information on these caveats can be found later in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an Image Builder pipeline
&lt;/h2&gt;

&lt;p&gt;To create an Image Builder pipeline, the following resources are needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IAM roles with permissions for building the Amazon Machine Image (AMI), lifecycle management of the created AMIs, and for exporting the AMI to an additional image format&lt;/li&gt;
&lt;li&gt;An S3 bucket to export the additional image format to&lt;/li&gt;
&lt;li&gt;Any custom components for building your custom AMI&lt;/li&gt;
&lt;li&gt;An image recipe&lt;/li&gt;
&lt;li&gt;An infrastructure configuration&lt;/li&gt;
&lt;li&gt;A distribution configuration&lt;/li&gt;
&lt;li&gt;The Image Builder pipeline&lt;/li&gt;
&lt;li&gt;(Optional) an SNS topic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the GitHub repository I've linked, the code for the IAM-roles can be found in &lt;code&gt;iam.tf&lt;/code&gt;, the code for the S3 bucket can be found in &lt;code&gt;s3.tf&lt;/code&gt;, the code for SNS can be found in &lt;code&gt;sns.tf&lt;/code&gt;, and the code for the remaining resources can be found in &lt;code&gt;main.tf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Make sure you're using at least version &lt;code&gt;5.74.0&lt;/code&gt; of the Terraform AWS provider, to be able to enjoy these enhancements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In version &lt;code&gt;5.74.0&lt;/code&gt; support was added to the &lt;code&gt;aws_imagebuilder_distribution_configuration&lt;/code&gt; resource for exporting the AMI to S3.&lt;/li&gt;
&lt;li&gt;In version &lt;code&gt;5.59.0&lt;/code&gt; support was added to the &lt;code&gt;aws_imagebuilder_image_pipeline&lt;/code&gt; resource to set the workflow of the pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Custom components
&lt;/h3&gt;

&lt;p&gt;A component can have multiple steps, in any of the two phases &lt;code&gt;build&lt;/code&gt; or&lt;code&gt;test&lt;/code&gt;. It should have at least one step, and can contain steps for both &lt;code&gt;build&lt;/code&gt; as well as &lt;code&gt;test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The first phase that is run, is the &lt;code&gt;build&lt;/code&gt; phase. This is where the initial image if built. After the image has been created, a new EC2 instance (or container) will be started using that image, to run the &lt;code&gt;test&lt;/code&gt; steps of the used components.&lt;/p&gt;

&lt;p&gt;In the example, I'm using a simple component, which sets the timezone of the AMI to &lt;code&gt;Europe/Amsterdam&lt;/code&gt; during the &lt;code&gt;build&lt;/code&gt; phase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_imagebuilder_component"&lt;/span&gt; &lt;span class="s2"&gt;"set_timezone"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"set-timezone-linux"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Sets the timezone to Europe/Amsterdam"&lt;/span&gt;
  &lt;span class="nx"&gt;platform&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Linux"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;
  &lt;span class="nx"&gt;skip_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# Setting this to true retains any previous versions&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;yamlencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;schemaVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
    &lt;span class="nx"&gt;phases&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"build"&lt;/span&gt;
      &lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SetTimezone"&lt;/span&gt;
          &lt;span class="nx"&gt;action&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ExecuteBash"&lt;/span&gt;
          &lt;span class="nx"&gt;onFailure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Abort"&lt;/span&gt;
          &lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="s2"&gt;"timedatectl set-timezone Europe/Amsterdam"&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Image recipe
&lt;/h3&gt;

&lt;p&gt;The image recipe brings together all the 'ingredients' that make the image.&lt;/p&gt;

&lt;p&gt;The recipe is where you define the source image (&lt;code&gt;parent_image&lt;/code&gt;) you're building on, making overrides to settings of the source, as well as adding your own &lt;em&gt;or&lt;/em&gt; AWS managed components, which are executed in the order they're listed in the recipe (per phase).&lt;/p&gt;

&lt;p&gt;You can also have the SSM agent removed after building the image. This is useful to do if the image will be used outside of AWS, where the SSM agent has no use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_imagebuilder_image_recipe"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Currently the service only supports x86-based images for import or export.&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"image-recipe"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;parent_image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:imagebuilder:eu-west-1:aws:image/amazon-linux-2023-ecs-optimized-x86/x.x.x"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;

  &lt;span class="nx"&gt;block_device_mapping&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# The device name is the same device name as the root volume of the selected AMI,&lt;/span&gt;
    &lt;span class="c1"&gt;# which means we're overriding (some of) the root disk configuration in the AMI.&lt;/span&gt;
    &lt;span class="c1"&gt;# In this case we're increasing the size of the disk from 20 GB to 40 GB.&lt;/span&gt;
    &lt;span class="nx"&gt;device_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/dev/xvda"&lt;/span&gt;
    &lt;span class="nx"&gt;no_device&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

    &lt;span class="nx"&gt;ebs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;delete_on_termination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="nx"&gt;volume_size&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;
      &lt;span class="nx"&gt;volume_type&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gp3"&lt;/span&gt;
      &lt;span class="nx"&gt;encrypted&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="nx"&gt;iops&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
      &lt;span class="nx"&gt;throughput&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;125&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Add the components to the recipe.&lt;/span&gt;
  &lt;span class="c1"&gt;# Recipes require a minimum of one build component, and can have a maximum of 20 build and test components in any combination.&lt;/span&gt;
  &lt;span class="c1"&gt;# Components are executed in the order they are listed here.&lt;/span&gt;
  &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Here we're adding an AWS managed component to install the AWS CLI&lt;/span&gt;
    &lt;span class="nx"&gt;component_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:imagebuilder:${data.aws_region.current.name}:aws:component/aws-cli-version-2-linux/x.x.x"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Here we're adding our custom component&lt;/span&gt;
    &lt;span class="nx"&gt;component_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_imagebuilder_component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set_timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;systems_manager_agent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Set this to false to keep the SSM agent installed after building the image.&lt;/span&gt;
    &lt;span class="nx"&gt;uninstall_after_build&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Adding resources to the replace_triggered_by, ensures that replacing a resource doesn't fail because of dependencies.&lt;/span&gt;
    &lt;span class="c1"&gt;# Instead, this resource will be replaced as well.&lt;/span&gt;
    &lt;span class="nx"&gt;replace_triggered_by&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;aws_imagebuilder_component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set_timezone&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Infrastructure configuration
&lt;/h3&gt;

&lt;p&gt;The infrastructure configuration defines what instance type(s) can be used to build and test the image, which subnet the build/test instances should use, as well as which security group(s) should be attached to the instance. The instance profile to use is also defined here, as well as the SNS topic to send messages to upon either success or failure of the pipeline run.&lt;/p&gt;

&lt;p&gt;If no subnet ID and security group IDs are provided, a subnet from the default VPC will be used, with the default security group. When providing a subnet ID, one or more security group IDs must also be provided.&lt;/p&gt;

&lt;p&gt;If you run into issues during the build phase, you can set &lt;code&gt;terminate_instance_on_failure&lt;/code&gt; to false. This means the build-instance will not be terminated, and can be used to investigate the issue.&lt;/p&gt;

&lt;p&gt;In this example, IMDSv2 is used (&lt;code&gt;http_tokens = required&lt;/code&gt;). Also see here for more information about the &lt;code&gt;http_put_response_hop_limit&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_imagebuilder_infrastructure_configuration"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"infrastructure-config"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Infrastructure Configuration for ${var.name}."&lt;/span&gt;
  &lt;span class="nx"&gt;instance_profile_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_instance_profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imagebuilder_build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;instance_types&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_types&lt;/span&gt;
  &lt;span class="nx"&gt;sns_topic_arn&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_sns_topic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="c1"&gt;# If you want to keep the instance when an error occurs, so you can debug the issue, set this to false&lt;/span&gt;
  &lt;span class="nx"&gt;terminate_instance_on_failure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="c1"&gt;# When not providing a subnet id and security group id(s),&lt;/span&gt;
  &lt;span class="c1"&gt;# Image Builder uses a subnet in the default VPC with the default security group.&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;security_group_ids&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet_id&lt;/span&gt;

  &lt;span class="nx"&gt;instance_metadata_options&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;http_tokens&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"required"&lt;/span&gt;
    &lt;span class="nx"&gt;http_put_response_hop_limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# Increase this to 3 when building a container image&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ImageType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CustomisedAmazonLinux2023Image"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Distribution configuration
&lt;/h1&gt;

&lt;p&gt;The distribution configuration tells Image Builder how to name the output AMI, and how to distribute the output AMI to different accounts, regions, organisations, and export the AMI to an alternative image format (&lt;code&gt;VHD&lt;/code&gt;, &lt;code&gt;VMDK&lt;/code&gt; or &lt;code&gt;RAW&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_imagebuilder_distribution_configuration"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"distribution-config"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Distribution Configuration for ${var.name}."&lt;/span&gt;

  &lt;span class="nx"&gt;distribution&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="nx"&gt;ami_distribution_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"{{ imagebuilder:buildDate }}-{{ imagebuilder:buildVersion }}"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="nx"&gt;kms_key_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
      &lt;span class="nx"&gt;ami_tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;ImageType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CustomisedAmazonLinux2023Image"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;s3_export_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;role_name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vmexport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
      &lt;span class="nx"&gt;disk_image_format&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image_export_format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;s3_bucket&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Image Builder pipeline
&lt;/h3&gt;

&lt;p&gt;The Image Builder pipeline is what ties all the previous resources together. This orchestrates building the image, and additionally trigger scanning of the output AMI for security issues. Amazon Inspector should be enabled in the account to be able to scan the image.&lt;/p&gt;

&lt;p&gt;The pipeline also defines the workflow to use. By default, a workflow that runs both the &lt;code&gt;build&lt;/code&gt; and the &lt;code&gt;test&lt;/code&gt; phases is used. In the example, no &lt;code&gt;test&lt;/code&gt; components are used, so we're shaving some time off of the pipeline runtime, by selecting an AWS-managed workflow that only runs the &lt;code&gt;build&lt;/code&gt; phase. When changing the default workflow, an &lt;a href="https://docs.aws.amazon.com/imagebuilder/latest/userguide/pipeline-workflows.html#pipeline-workflow-service-role" rel="noopener noreferrer"&gt;execution role must also be provided&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The pipeline can also be scheduled to run at certain intervals using &lt;a href="https://docs.aws.amazon.com/imagebuilder/latest/userguide/cron-expressions.html" rel="noopener noreferrer"&gt;cron expressions&lt;/a&gt;. The pipeline can also be triggered &lt;a href="https://docs.aws.amazon.com/imagebuilder/latest/userguide/ev-rules-for-pipeline.html" rel="noopener noreferrer"&gt;using EventBridge rules&lt;/a&gt;, which requires additional resources to set up, which are not included in this sample.&lt;/p&gt;

&lt;p&gt;When no schedule is provided, the pipeline can only be run manually, or when targeted by an EventBridge rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_imagebuilder_image_pipeline"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"image-pipeline"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Pipeline to create the custom image for ${var.name}"&lt;/span&gt;
  &lt;span class="nx"&gt;image_recipe_arn&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_imagebuilder_image_recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;infrastructure_configuration_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_imagebuilder_infrastructure_configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;distribution_configuration_arn&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_imagebuilder_distribution_configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;

  &lt;span class="nx"&gt;image_scanning_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Amazon Inspector needs to be enabled for the account when setting this to true&lt;/span&gt;
    &lt;span class="nx"&gt;image_scanning_enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;image_tests_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;image_tests_enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;timeout_minutes&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;720&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# When changing the workflow from default, an execution role must also be provided&lt;/span&gt;
  &lt;span class="nx"&gt;execution_role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::${data.aws_caller_identity.account.account_id}:role/aws-service-role/imagebuilder.amazonaws.com/AWSServiceRoleForImageBuilder"&lt;/span&gt;
  &lt;span class="nx"&gt;workflow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# We're setting an AWS managed workflow, that only executes Build-steps of the component. No testing or validation is done.&lt;/span&gt;
    &lt;span class="nx"&gt;workflow_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:imagebuilder:${data.aws_region.current.name}:aws:workflow/build/build-image/x.x.x"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Here you can set one or more schedules, to automate image building.&lt;/span&gt;
  &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"schedule"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schedule_expression&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;schedule_expression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schedule_expression&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Adding resources to the replace_triggered_by, ensures that replacing a resource doesn't fail because of dependencies.&lt;/span&gt;
    &lt;span class="c1"&gt;# Instead, this resource will be replaced as well.&lt;/span&gt;
    &lt;span class="nx"&gt;replace_triggered_by&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;aws_imagebuilder_image_recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  More info on the caveats of using Image Builder
&lt;/h2&gt;

&lt;h3&gt;
  
  
  EBS Encryption by default
&lt;/h3&gt;

&lt;p&gt;To check if EBS encryption by default is enabled, we can use the following AWS CLI command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;aws ec2 get-ebs-encryption-by-default

&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"EbsEncryptionByDefault"&lt;/span&gt;: &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it's true, check for Service Control Policies (SCPs) and/or other tooling used for managing the organisation/accounts. If this feature has been enabled through an SCP or other automated way, disable it for the account you'll be running Image Builder in.&lt;/p&gt;

&lt;p&gt;To manually disable EBS encryption by default, the following AWS CLI command can be used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;aws ec2 disable-ebs-encryption-by-default

&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"EbsEncryptionByDefault"&lt;/span&gt;: &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;{{&amp;lt; alert cardColor="orange" iconColor="#1d3557" textColor="#f1faee" &amp;gt;}}&lt;br&gt;
If EBS encryption by default has been set using an SCP or other tooling, manually disabling will not be permanent, or might not even take effect.&lt;br&gt;
{{&amp;lt; /alert &amp;gt;}}&lt;/p&gt;
&lt;h3&gt;
  
  
  S3 bucket encryption
&lt;/h3&gt;

&lt;p&gt;To be able to export the image to an S3 bucket, the bucket needs to be encrypted with the default AWS managed KMS key for S3 (&lt;code&gt;SSE-S3&lt;/code&gt;). Otherwise the export will fail with an &lt;code&gt;InsufficientPermissions&lt;/code&gt; exception.&lt;/p&gt;
&lt;h3&gt;
  
  
  Instance Metadata Service (IMDS)
&lt;/h3&gt;

&lt;p&gt;AWS encourages the use of IMDSv2 over IMDSv1. Version 2 is more secure, but requires some adjustments to any existing script using IMDSv1 when querying the instance metadata.&lt;/p&gt;

&lt;p&gt;IMDSv1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://169.254.169.254/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IMDSv2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="s2"&gt;"http://169.254.169.254/latest/api/token"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token-ttl-seconds: 21600"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token: &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; http://169.254.169.254/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basic check to see if IMDSv1 or IMDSv2 is enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://169.254.169.254/&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Instance has been configured to use IMDSv2."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More information can be found &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another setting that will need adjustment when using Image Builder for building a container image, is setting the Metadata Hop Limit (&lt;code&gt;HttpPutResponseHopLimit&lt;/code&gt;) to 2 or 3.&lt;/p&gt;

&lt;p&gt;More information on the IMDS options can be found &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-options.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;My goal with this post was to show you how you can start using Image Builder to automate creating your custom AMIs or container images, and help you take that initial hurdle to start looking into Image Builder.&lt;/p&gt;

&lt;p&gt;It also shows the issues I ran into while implementing Image Builder for a project I'm working on, and how to overcome those.&lt;/p&gt;

&lt;p&gt;If you have any feedback on this post, please reach out to me.&lt;/p&gt;

</description>
      <category>awsimagebuilder</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Dedicated game server and AWS Instance Scheduler</title>
      <dc:creator>Sebastiaan Brozius</dc:creator>
      <pubDate>Wed, 20 Dec 2023 11:17:01 +0000</pubDate>
      <link>https://dev.to/aws-builders/dedicated-game-server-and-aws-instance-scheduler-545h</link>
      <guid>https://dev.to/aws-builders/dedicated-game-server-and-aws-instance-scheduler-545h</guid>
      <description>&lt;p&gt;Recently I've started playing a game (&lt;a href="https://www.satisfactorygame.com/" rel="noopener noreferrer"&gt;Satisfactory&lt;/a&gt;, for those who want to know) with my two kids. To make it easier to play together, I've set up a dedicated server on an EC2 instance so the game is also available when the host is not available.&lt;/p&gt;

&lt;p&gt;To keep the costs down a bit, I was looking for a way to shut down the instance during the hours we wouldn't be playing anyway. That's when I came across &lt;a href="https://aws.amazon.com/solutions/implementations/instance-scheduler-on-aws/" rel="noopener noreferrer"&gt;Instance Scheduler on AWS&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Instance Scheduler on AWS solution automates the starting and stopping of Amazon Elastic Compute Cloud (Amazon EC2) and Amazon Relational Database Service (Amazon RDS) instances.  &lt;/p&gt;

&lt;p&gt;This solution helps reduce operational costs by stopping resources that are not in use and starting resources when their capacity is needed. For example, a company can use Instance Scheduler on AWS in a production environment to automatically stop instances outside of business hours every day. If you leave all of your instances running at full utilization, this solution can result in up to 70% cost savings for those instances that are only necessary during regular business hours (weekly utilization reduced from 168 hours to 50 hours).  &lt;/p&gt;

&lt;p&gt;Instance Scheduler on AWS leverages Amazon Web Services (AWS) resource tags and AWS Lambda to automatically stop and restart instances across multiple AWS Regions and accounts on a customer-defined schedule. This solution also allows you to use hibernation for stopped Amazon EC2 instances.&lt;/p&gt;

&lt;p&gt;This solution can be deployed in a single account, or in a central account, managing multiple accounts. Since I'm using an AWS Organization, I've deployed the main stack in a hub account, and the remote stack in the account that hosts the dedicated server instance.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To deploy Instance Scheduler (version 1.5.3 at the time of writing), go to the AWS CloudFormation console in your desired account and create a new stack using new resources. Paste the link to the &lt;a href="https://s3.amazonaws.com/solutions-reference/instance-scheduler-on-aws/latest/instance-scheduler-on-aws.template" rel="noopener noreferrer"&gt;CloudFormation Instance Scheduler template&lt;/a&gt;, as linked on the solution page, in the &lt;code&gt;Amazon S3 URL&lt;/code&gt; field and click &lt;code&gt;Next&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On the next page, you can configure the solution. Give the stack a meaningful name, and set the options you need. For mu configuration, I've set the default timezone to &lt;code&gt;Europe/Amsterdam&lt;/code&gt;, set the option &lt;code&gt;This account&lt;/code&gt; to &lt;code&gt;No&lt;/code&gt; since I won't be hosting resources in the hub account, set the &lt;code&gt;Namespace&lt;/code&gt;, and set &lt;code&gt;Use AWS Organizations&lt;/code&gt; to &lt;code&gt;Yes&lt;/code&gt;. When using AWS Organizations, provide the organization ID in the &lt;code&gt;Organization Id/Remote Account Ids&lt;/code&gt; field, otherwise provide a comma separated list of account IDs you want to manage through this account. If you forget to provide a value for this, you will get an error on the &lt;code&gt;schedulereventbuspolicy&lt;/code&gt; resource during deployment.&lt;/p&gt;

&lt;p&gt;If you want to manage resources in other regions than where you're deploying the stack in, also make sure to enter those in the &lt;code&gt;Region(s)&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;When you're done with this initial configuration, click on &lt;code&gt;Next&lt;/code&gt;, review the settings on the &lt;code&gt;Configure stack options&lt;/code&gt; page and click &lt;code&gt;Next&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;Do a last review of the configuration, don't forget to tick the box to acknowledge that AWS CloudFormation might create IAM resources, and click on &lt;code&gt;Submit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The stack will create the resources for the solution, which takes a couple of minutes.&lt;/p&gt;

&lt;p&gt;Once the resources have been deployed, we can create a schedule for the server.&lt;/p&gt;

&lt;p&gt;The schedule is configured in DynamoDB. The CloudFormation stack created several DynamoDB tables. The schedules and periods used on the schedule are in a table names &lt;code&gt;InstanceScheduler-main-ConfigTable-XXXXXXXXXXXX&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;I've created the following periods and schedule in the table.&lt;/p&gt;

&lt;p&gt;Periods used:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Workdays period&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"period"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"satisfactory-workdays"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"begintime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"17:00"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Satisfactory Dedicated Server - workdays "&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"endtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"23:59"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"weekdays"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"mon-thu"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Weekends period 'on'&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"period"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"satisfactory-weekends-on"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"begintime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12:00"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Satisfactory Dedicated Server - weekends"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"endtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"23:59"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"weekdays"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"fri-sun"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Weekends period 'off'&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"period"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"satisfactory-weekends-off"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"begintime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00:00"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Satisfactory Dedicated Server - weekends"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"endtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"02:00"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"weekdays"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"sat-sun"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These periods turn the server on on weekdays between 17:00 and midnight, and for the weekends, turn it on on Fridays, Saturdays and Sundays at noon, until 02:00 on Saturdays and Sundays, and until midnight on Sundays, when combined into a schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"schedule"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"satisfactory"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Schedule for Satisfactory Dedicated Server"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"periods"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"satisfactory-weekends-off"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"satisfactory-weekends-on"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"satisfactory-workdays"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that's done, switch over to the account and region the dedicated server is in, and deploy the &lt;a href="https://s3.amazonaws.com/solutions-reference/instance-scheduler-on-aws/latest/instance-scheduler-on-aws-remote.template" rel="noopener noreferrer"&gt;CloudFormation instance schedule remote template&lt;/a&gt; in CloudFormation.&lt;/p&gt;

&lt;p&gt;In the stack details, specify the same &lt;code&gt;Namespace&lt;/code&gt; used in the stack in the hub account, provide the account ID of the hub account in &lt;code&gt;Hub Account ID&lt;/code&gt; and set &lt;code&gt;Use AWS Organizations&lt;/code&gt; to &lt;code&gt;Yes&lt;/code&gt; because that's what I'm using in the hub account.&lt;/p&gt;

&lt;p&gt;When that stack has deployed, the server needs a tag so Instance Scheduler knows to manage this instance, and what schedule to use.&lt;br&gt;
The tag we need to set on the EC2 instance is &lt;code&gt;Schedule&lt;/code&gt;, with the value of the schedule we created, &lt;code&gt;satisfactory&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And that's that!&lt;/p&gt;

&lt;p&gt;An alternative AWS provides is &lt;a href="https://docs.aws.amazon.com/solutions/latest/instance-scheduler-on-aws/related-resources.html" rel="noopener noreferrer"&gt;Resource Scheduler&lt;/a&gt;. With Resource Scheduler, you can 'only' schedule EC2 instances, and they are not checked for the desired state, but might fit your use case better. I chose for Instance Scheduler to see how it works, and because I like solutions that can be managed from a hub account.&lt;/p&gt;

&lt;p&gt;For this post, I've written about a personal dedicated game server, a fun project. But in environments where you don't need your resources running all day (like test and dev) or even in production environments where you need to have an instance available only at specific times, this can be helpful to reduce costs.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awsinstancescheduler</category>
      <category>funproject</category>
    </item>
    <item>
      <title>Automating patching with AWS Systems Manager</title>
      <dc:creator>Sebastiaan Brozius</dc:creator>
      <pubDate>Mon, 13 Nov 2023 07:00:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/automating-patching-with-aws-systems-manager-2912</link>
      <guid>https://dev.to/aws-builders/automating-patching-with-aws-systems-manager-2912</guid>
      <description>&lt;p&gt;&lt;em&gt;The code that accompanies this blogpost can be found &lt;a href="https://github.com/IskanderNovena/blogpost-ssm-automated-patching"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Recently I've been looking into patching Windows servers that have dependencies between them, using AWS Systems Manager.&lt;/p&gt;

&lt;p&gt;The use-case was an application that exists of web servers, middleware servers and a database server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LwKG_2YF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8bjdvnck0bqotttg7ku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LwKG_2YF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8bjdvnck0bqotttg7ku.png" alt="Application diagram" width="497" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The web servers have connections open to the database server, and the middleware servers run processes that get information from the database server.&lt;/p&gt;

&lt;p&gt;The servers were patched manually, by stopping the services on the web servers and middleware servers first and checking that all middleware services were stopped, before stopping the databases. Once that was done, the servers were updated. After patching, the databases were first brought back online, before starting the middleware services and the web services again.&lt;/p&gt;

&lt;p&gt;To set this up, I created some PowerShell scripts (with a little bit of SSM variable flavour) to be run on the instances to stop and start the services, as well as checking the services before continuing to the next step. These scripts were put as SSM documents, to be called from an automation document.&lt;/p&gt;

&lt;p&gt;Example script (&lt;code&gt;Start-Components.ps1&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="n"&gt;serverRole&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ServerRole}}"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# This is an SSM variable reference&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="n"&gt;fqdn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-WmiObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Win32_ComputerSystem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DNSHostName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-WmiObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Win32_ComputerSystem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Domain&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[INF] Starting Components on &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="n"&gt;fqdn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'$_serverRole'&lt;/span&gt;&lt;span class="s2"&gt;"

  switch (&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="s2"&gt;serverRole) {
    Web { 
      Write-Output "&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;INF&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Setting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Startup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StartType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Manual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Automatic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;starting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;them.&lt;/span&gt;&lt;span class="s2"&gt;"

      Get-Service iisadmin | Where-Object StartType -eq "&lt;/span&gt;&lt;span class="nx"&gt;Manual&lt;/span&gt;&lt;span class="s2"&gt;" | Set-Service -StartupType Automatic -Status Running
      Get-Service w3svc | Where-Object StartType -eq "&lt;/span&gt;&lt;span class="nx"&gt;Manual&lt;/span&gt;&lt;span class="s2"&gt;" | Set-Service -StartupType Automatic -Status Running
    }
    Middleware {  
      Write-Output "&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;INF&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Doing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stuff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;middleware&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;start.&lt;/span&gt;&lt;span class="s2"&gt;"

      # Your code here
    }
    Database {  
      Write-Output "&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;INF&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Setting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Startup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StartType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Manual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Automatic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;starting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;them.&lt;/span&gt;&lt;span class="s2"&gt;"
      Get-Date -Format "&lt;/span&gt;&lt;span class="nx"&gt;yyyy-MM-dd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HH:mm:ss&lt;/span&gt;&lt;span class="s2"&gt;"

      Get-Service *sql* | Where-Object StartType -eq "&lt;/span&gt;&lt;span class="nx"&gt;Manual&lt;/span&gt;&lt;span class="s2"&gt;" | Set-Service -StartupType Automatic -Status Running

      Write-Output "&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;INF&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Making&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;started&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;continuing.&lt;/span&gt;&lt;span class="s2"&gt;"
      # When there are no services that match the name, the while loop will not be entered.
      while (Get-Service *sql* | Where-Object Status -ne Running) {
        Write-Output "&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DEB&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="nt"&gt;-Date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yyyy-MM-dd HH:mm:ss"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;started&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;yet.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Waiting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;little&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;longer.&lt;/span&gt;&lt;span class="s2"&gt;"
        Start-Sleep -Seconds 60
      }
    }
    Default { }
  }
}
catch {
  Write-Output "&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ERR&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="s2"&gt;"
  Write-Error &lt;/span&gt;&lt;span class="bp"&gt;$Error&lt;/span&gt;&lt;span class="s2"&gt;[0] -ErrorAction Continue
  exit 1
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which is consumed to create an SSM document using Terraform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ssm_document"&lt;/span&gt; &lt;span class="s2"&gt;"patching_start_components"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Patching-StartComponents"&lt;/span&gt;
  &lt;span class="nx"&gt;document_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Command"&lt;/span&gt;
  &lt;span class="nx"&gt;target_type&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/AWS::EC2::Instance"&lt;/span&gt;

  &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;schemaVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2.2"&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Patching Post-install Start Components Document"&lt;/span&gt;
    &lt;span class="nx"&gt;parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ServerRole&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;
        &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Role of the server (Web, Middleware, Database, None)"&lt;/span&gt;
        &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"None"&lt;/span&gt;
        &lt;span class="nx"&gt;allowedValues&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"Web"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"Middleware"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"Database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"None"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;mainSteps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws:runPowerShellScript"&lt;/span&gt;
        &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"StartComponents"&lt;/span&gt;
        &lt;span class="nx"&gt;precondition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;StringEquals&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s2"&gt;"platformType"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"Windows"&lt;/span&gt;
          &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;runCommand&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;n"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.cwd}/powershell_scripts/Start-Components.ps1"&lt;/span&gt;&lt;span class="err"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using an automation document, we can orchestrate the flow of patching. In the example code, I've also included a method to patch servers of the same function at different times. For this, the option &lt;code&gt;PatchWindow&lt;/code&gt; has been added, with allowed values &lt;code&gt;Monday&lt;/code&gt; and &lt;code&gt;Wednesday&lt;/code&gt;. The output of each step is redirected to an encrypted CloudWatch log-group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ssm_document"&lt;/span&gt; &lt;span class="s2"&gt;"patching_automation"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Patching-Automation"&lt;/span&gt;
  &lt;span class="nx"&gt;document_type&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Automation"&lt;/span&gt;
  &lt;span class="nx"&gt;document_format&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"YAML"&lt;/span&gt;

  &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt;
description: |-
  # Patching Automation

  This script provides a staged patching experience. Services are stopped in a specific order on specific instances after which patching is run, and services are started again on servers in reverse order.
schemaVersion: '0.3'
parameters:
  PatchWindow:
    type: String
    allowedValues:
      - Monday
      - Wednesday
    description: Patch-window to run for. Determines which servers are affected.
mainSteps:
  - name: StopWebServerServices
    action: 'aws:runCommand'
    inputs:
      DocumentName: ${aws_ssm_document.patching_stop_components.name}
      Targets:
        - Key: 'tag:ServerRole'
          Values:
            - Web
        - Key: 'tag:PatchWindow'
          Values:
            - '{{PatchWindow}}'
      Parameters:
        ServerRole: Web
      CloudWatchOutputConfig:
        CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name}
        CloudWatchOutputEnabled: true
    description: Stop the services on the web servers
    nextStep: StopMiddlewareServices
    onFailure: 'step:StartWebServerServices'
  - name: StopMiddlewareServices
    action: 'aws:runCommand'
    inputs:
      DocumentName: ${aws_ssm_document.patching_stop_components.name}
      Targets:
        - Key: 'tag:ServerRole'
          Values:
            - Middleware
        - Key: 'tag:PatchWindow'
          Values:
            - '{{PatchWindow}}'
      Parameters:
        ServerRole: Middleware
      CloudWatchOutputConfig:
        CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name}
        CloudWatchOutputEnabled: true
    description: Stop the services on the middleware servers
    nextStep: StopDatabaseServices
    onFailure: 'step:StartMiddlewareServices'
  - name: StopDatabaseServices
    action: 'aws:runCommand'
    inputs:
      DocumentName: ${aws_ssm_document.patching_stop_components.name}
      Targets:
        - Key: 'tag:ServerRole'
          Values:
            - Database
        - Key: 'tag:PatchWindow'
          Values:
            - '{{PatchWindow}}'
      Parameters:
        ServerRole: Database
      CloudWatchOutputConfig:
        CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name}
        CloudWatchOutputEnabled: true
    description: Stop the services on the database servers
    nextStep: PatchServers
    onFailure: 'step:StartDatabaseServices'
  - name: PatchServers
    action: 'aws:runCommand'
    inputs:
      DocumentName: AWS-RunPatchBaseline
      Targets:
        # Uncomment the following lines to only patch specific server-roles
        # - Key: 'tag:ServerRole'
        #   Values:
        #     - Web
        #     - Middleware
        #     - Database
        - Key: 'tag:PatchWindow'
          Values:
            - '{{PatchWindow}}'
      Parameters:
        Operation: Install
        RebootOption: RebootIfNeeded
      CloudWatchOutputConfig:
        CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name}
        CloudWatchOutputEnabled: true
    description: Patch the servers
    nextStep: StartDatabaseServices
    onFailure: Abort
  - name: StartDatabaseServices
    action: 'aws:runCommand'
    inputs:
      DocumentName: ${aws_ssm_document.patching_start_components.name}
      Targets:
        - Key: 'tag:ServerRole'
          Values:
            - Database
        - Key: 'tag:PatchWindow'
          Values:
            - '{{PatchWindow}}'
      Parameters:
        ServerRole: Database
      CloudWatchOutputConfig:
        CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name}
        CloudWatchOutputEnabled: true
    description: Start the services on the database servers
    nextStep: StartMiddlewareServices
    onFailure: Abort
  - name: StartMiddlewareServices
    action: 'aws:runCommand'
    inputs:
      DocumentName: ${aws_ssm_document.patching_start_components.name}
      Targets:
        - Key: 'tag:ServerRole'
          Values:
            - Middleware
        - Key: 'tag:PatchWindow'
          Values:
            - '{{PatchWindow}}'
      Parameters:
        ServerRole: Middleware
      CloudWatchOutputConfig:
        CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name}
        CloudWatchOutputEnabled: true
    description: Start the services on the middleware servers
    nextStep: StartWebServerServices
    onFailure: Abort
  - name: StartWebServerServices
    action: 'aws:runCommand'
    inputs:
      DocumentName: ${aws_ssm_document.patching_start_components.name}
      Targets:
        - Key: 'tag:ServerRole'
          Values:
            - Web
        - Key: 'tag:PatchWindow'
          Values:
            - '{{PatchWindow}}'
      Parameters:
        ServerRole: Web
      CloudWatchOutputConfig:
        CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name}
        CloudWatchOutputEnabled: true
    description: Start the services on the web servers
    isEnd: true
&lt;/span&gt;&lt;span class="no"&gt;EOT
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The automation document allows for some error-handling as well. As you can see in the example, when the step &lt;code&gt;StopMiddlewareServices&lt;/code&gt; fails, it will skip to step &lt;code&gt;StartMiddlewareServices&lt;/code&gt; (defined with the line &lt;code&gt;onFailure: 'step:StartMiddlewareServices'&lt;/code&gt;) and will proceed from there.&lt;/p&gt;

&lt;p&gt;Once we have the automation document in place, we can create maintenance windows with an associated task, to execute the automation document for that triggers automatically executing the automation document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ssm_maintenance_window"&lt;/span&gt; &lt;span class="s2"&gt;"install_window_monday"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"patch-window-monday"&lt;/span&gt;
  &lt;span class="nx"&gt;schedule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;patching&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cron_patching_monday&lt;/span&gt;
  &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
  &lt;span class="nx"&gt;cutoff&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ssm_maintenance_window_task"&lt;/span&gt; &lt;span class="s2"&gt;"task_install_patches_monday"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;window_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ssm_maintenance_window&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;install_window_monday&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"install-patches-monday"&lt;/span&gt;
  &lt;span class="nx"&gt;task_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AUTOMATION"&lt;/span&gt;
  &lt;span class="nx"&gt;task_arn&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ssm_document&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;patching_automation&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;priority&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;

  &lt;span class="nx"&gt;task_invocation_parameters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;automation_parameters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;document_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"$LATEST"&lt;/span&gt;

      &lt;span class="nx"&gt;parameter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PatchWindow"&lt;/span&gt;
        &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Monday"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, instances with a tag &lt;code&gt;PatchWindow&lt;/code&gt; with a value of &lt;code&gt;Monday&lt;/code&gt; will be targeted for the maintenance task.&lt;/p&gt;

&lt;p&gt;After applying the code to your environment, instances can be included by setting two tags on them.&lt;br&gt;
&lt;code&gt;PatchWindow&lt;/code&gt; determines the maintenance window the instance will be included in. In this example, valid values are &lt;code&gt;Monday&lt;/code&gt; and &lt;code&gt;Wednesday&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;ServerRole&lt;/code&gt; determines which actions in the PowerShell scripts will be taken. In this example, valid values are &lt;code&gt;Web&lt;/code&gt;, &lt;code&gt;Middleware&lt;/code&gt;, &lt;code&gt;Database&lt;/code&gt; or &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>ssm</category>
      <category>powershell</category>
    </item>
    <item>
      <title>Lessons learned from migrating 42 servers to AWS</title>
      <dc:creator>Sebastiaan Brozius</dc:creator>
      <pubDate>Mon, 05 Jun 2023 15:33:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/lessons-learned-from-migrating-42-servers-to-aws-11lp</link>
      <guid>https://dev.to/aws-builders/lessons-learned-from-migrating-42-servers-to-aws-11lp</guid>
      <description>&lt;p&gt;Earlier this year I've worked on a project where we had to migrate 42 servers from a data center to AWS as part of an AWS Migration Acceleration Program (MAP) deal. There was some pressure, since the contract with the data center would end about 2 months later and the customer didn't want to renew or extend the current contract with the data center.&lt;/p&gt;

&lt;p&gt;During this project I learned some things I think are valuable and would like to share. This might save you some time and/or frustration when you're working on a similar project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer: this is not meant to be a full migration guide, but rather things I encountered and want to share. Take from this what you can use, and create a migration plan for your specific situation.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The tools used
&lt;/h2&gt;

&lt;p&gt;For this project, the following tools were used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Migration Hub&lt;/li&gt;
&lt;li&gt;AWS Application Migration Service&lt;/li&gt;
&lt;li&gt;Terraform (or any IaC tool of your choice)&lt;/li&gt;
&lt;li&gt;Scripting-language of your choice (I used PowerShell)&lt;/li&gt;
&lt;li&gt;Bash-scripting (the servers were running Linux)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step one: Inventarisation
&lt;/h2&gt;

&lt;p&gt;To know what exactly you're dealing with, it's important to make an inventarisation of the environment you'll be migrating, and everything it makes use of or is used by. Having a clear overview makes it easier to spot potential issues, plan ahead and help set up a realistic time frame for the migration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Network / subnets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Get all information about the current networks and subnets the servers are using, are the servers using static or dynamic IP addresses, are there any VPN connections, do we need to take any allow-listings (both inbound and outbound) into account and what public IP addresses are in use, if any. The customer might even have their own public IP range which they want to (partially) move to AWS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firewall rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try to get a complete overview of all firewall rules that are in place. This helps in determining which servers should be accessible from where, should be able to connect to where, and might also help you spot issues with the current setup so you can mitigate those in the new setup.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Traffic flows between servers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The firewall rules might be of use for that, but traffic between private subnets is often unrestricted. Knowing the traffic flows between servers will help you set up more restrictive (and safer) security groups within AWS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DNS domains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Does the customer want to move DNS domains to AWS? Are there DNS-records that point to the servers and need to be changed? If there are changes to be made to DNS records and/or domains, some planning needs to be done to make sure that during the actual migration, you don't have to wait hours or days before a change has been propagated throughout the internet.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Certificates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do the servers or any applications running on them use any certificate and how are they managed? Knowing this can help determine how to expose an application to the internet; can you use an Application Load Balancer or is the certificate managed through an automated system which runs on the server itself? In the latter case, you either have to use a Network Load Balancer, connect the server 'directly' to the internet (bad practice!) or change how the certificate is managed, which might have a big impact on the work that needs to be done.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backup RTO and RPO&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For setting up the new environment, it's important to know what RPO and RTO the customer requires for which service / application / server. Setting up AWS Backup in advance makes it easier to enable it during or just after the migration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Software license requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some software vendors use the MAC-address of a server to bind their licenses to. If that's the case, some additional actions need to be taken to ensure you don't have to keep changing your license registration with such vendors.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OS versions being used and patch level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before you start working on preparing for the migration, it's wise to know which OSes are being used, and what versions. When you encounter older OS versions, there might be more work involved installing the necessary agents, or it might even be impossible. Also, knowing the current patch-level of the OSes and how patching is managed is important to know for the new environment in AWS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Software used&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Specific products and/or versions are eligible for additional discounts in AWS MAP, like commercial databases, SAP and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step two: Make a plan
&lt;/h2&gt;

&lt;p&gt;Once you've got at least most of the information, it's time to start making a plan.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make an IP-plan for the new environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You won't always be able to keep the current IP-addresses and creating a new IP-plan is important for setting up the new network and subnets, with proper sizing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Determine the security groups to create&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make sure you know the needed security groups to allow traffic between servers, and allow the required inbound and outbound traffic. Determine which security groups to make and what servers to attach them to.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make sure you have your MAP tag number (MPE ID)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is needed when deploying resources to get the discount. Determine how you will apply the tags and what resources might need alternate tags.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Determine the use of a launch template (highly recommended!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS Application Migration Service makes use of launch templates. Determining if you're going to use it and what settings you want to specify in it, helps to gather possible additional info.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Determine the order of migration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make an initial order of migration, and keep validating that order until the actual migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step three: Preparations
&lt;/h2&gt;

&lt;p&gt;Once you've created an initial plan to work from, it's time to start the actual preparations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Set up the the management account listed in the Migration Plan (the management account that's part of the MAP deal) and activate the Cost Allocation Tag required for MAP 2.0. This info should be made available to you by your AWS representative for the MAP deal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up AWS Migration Hub and either install the Discovery Agent on the servers you're migrating, or, if you have access to the hypervisor layer, install the appliance. More info on these can be found in the &lt;a href="https://docs.aws.amazon.com/migrationhub/latest/ug/gs-new-user-discovery.html"&gt;AWS Documentation for AWS Migration Hub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next, deploy the infrastructure you're migrating the servers to, including an initial security group to assign to the launch template, and an &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/setup-instance-permissions.html"&gt;Instance Profile with the appropriate permissions&lt;/a&gt;. Next, &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/setup-create-vpc.html"&gt;create VPC Endpoints&lt;/a&gt; for the services SSM uses; this way you should be able to access the servers even when they cannot connect to the internet (which is probably the case during the test phases).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also make sure that you tag everything with the &lt;code&gt;map-migrated&lt;/code&gt; tag, also for the infrastructure. With Terraform, you can set this using the &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs?product_intent=terraform#default_tags-configuration-block"&gt;AWS provider parameter &lt;code&gt;default_tags&lt;/code&gt;&lt;/a&gt;. More info on the exact tag value should be available through the AWS MAP channel.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Once you've got the basic infrastructure set up, &lt;a href="https://docs.aws.amazon.com/mgn/latest/ug/mandatory-setup.html"&gt;initialise the AWS Application Migration Service&lt;/a&gt;. During the initialisation, also make sure to set up the default launch template. Make sure the MAP tag is added with the appropriate value, the security group, instance profile, network subnet, et cetera.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After AWS Application Migration Service has been initialised, &lt;a href="https://docs.aws.amazon.com/mgn/latest/ug/installation-requirements.html"&gt;the Replication Agent can be installed on the servers to be migrated&lt;/a&gt;. The replication agent uses TCP port 1500 to connect to the AWS Application Migration Service, so make sure any firewall allows TCP port 1500 outbound for the source servers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;NB: The Replication Agent requires the Linux headers to install. For older Linux-versions this could mean you have to locate the Linux headers for the specific release, since they might no longer be available through the distributions update manager.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html"&gt;Install the AWS SSM Agent&lt;/a&gt; on the source servers. This is helpful to be able to connect to the server through the AWS Console using Session Manager, or even using the Session Manager Plugin for the AWS CLI. During testing and the actual migration, this can prove useful when you're running into issues with any server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once the servers have started replicating, we have to play the waiting game.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until the servers have finished their initial replication, there's not much to be done. The time it takes before the initial replication is done, is dependent on the speed of the internet connection, the total amount of data to be replicated, as well as the number of changes to the filesystem the source systems have. The AWS Application Migration Service console gives an estimate of the time required to complete replication, which is constantly updated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step four: Test, test, test!
&lt;/h2&gt;

&lt;p&gt;When all (or at least the ones you want to start with) servers have replicated, you can start testing.&lt;/p&gt;

&lt;p&gt;In the AWS Application Migration Service console, select one or more servers to test, and &lt;a href="https://docs.aws.amazon.com/mgn/latest/ug/launching-test-servers.html"&gt;launch test instances&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Make sure that the test-servers are unable to contact live servers, so they do not contaminate any production environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a migration-script per server. Do multiple test-runs to check and improve the migration-scripts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;During testing, you might encounter software that can throw a wrench in the migration, like corosync and pacemaker. When you encounter such software, determine if you still need it and take action accordingly to mitigate any possible issues that might arise by keeping those configurations as they are.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Evaluate of your intended order of migration is valid. During testing you might find a different order is needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/mgn/latest/ug/waves.html"&gt;Create waves&lt;/a&gt; based on the order of migration for a simpler orchestration during the actual migration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do at least one full test-migration. This helps determine how much time is needed for the full migration. This is important for how much down-time you'll have, which needs to be communicated with the customer and any users of the application(s), as well as help in deciding the moment of the actual migration, the number of people working on the migration, when the test-persons should be able to start testing the application after migration, et cetera.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you're moving any server from being directly exposed to the internet, to being fronted by a load balancer, test the load balancer configuration as best you can.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once you're done testing a source server, mark it as 'Ready for cut-over' in AWS Application Migration Service.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If there are DNS changes to be made, prepare for them; lower TTL values for records that need to be changed, and prepare any domain that needs to be moved to Route53, or even move them in advance if possible.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step five: The real deal
&lt;/h2&gt;

&lt;p&gt;This is what you've been testing for!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Shut down any running services on the live servers, especially databases, and wait for the last changes to be replicated to AWS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start migrating in waves.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure your security groups have the proper access (they should at least be reachable for the group of test-users)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Have your test-group test as early as possible and have a select group of people report on any findings. Triage what needs to be fixed right away, and what can wait. Have product owners participate in this where possible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mark servers that have been given the green light as 'Finalize cut-over' in AWS Application Migration Service to indicate they're finished.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Turn on VPC Flow Logs to help troubleshoot any network-issues during the migration.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step six: The aftermath
&lt;/h2&gt;

&lt;p&gt;Once the migration has been finished successfully, there's a few more things that need to be done.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Turn off the old servers, or at the very least make sure that the applications will not be enabled again.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure the servers and services are being backed up in AWS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mark the migrated servers as 'Mark as archived' in AWS Application Migration Service.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remove any software from the servers that was specifically needed for the data center architecture (e.g. VMware tool, Azure tools)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Points of attention
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;During both testing and the actual migration, when launching multiple (bigger) instances at the same time, one or more instances might respond badly/have weird issues. In that case, stop the instance(s) in the AWS Console, wait a minute or two, and start it up again. The reason for this is that the underlying host has issues allocating the proper resources to the instance. Stopping the instance and starting it again relocates the instance to a host that has sufficient resources available for the instance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you're using &lt;code&gt;user_data&lt;/code&gt; in your launch template(s), this will only be run when the server has a working network connection. If a server has no working network connection, &lt;code&gt;user_data&lt;/code&gt; cannot be retrieved from the &lt;a href="http://169.254.169.254/latest/meta-data/"&gt;instance metadata&lt;/a&gt; and cannot be run.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure that the customer tests the application(s) during migration and sign off on them. Ultimately, it's the customers responsibility to determine if an application is working as intended and if all data is correctly transferred.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>migrate</category>
    </item>
  </channel>
</rss>
