
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>CICD on Bastab C</title>
    <link>https://bastabc.com/</link>
    <description>Recent content in CICD on Bastab C</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 27 Jul 2025 00:00:00 +0000</lastBuildDate>
    
	<atom:link href="https://bastabc.com/tags/cicd/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Infrastructure as Code with AWS CDK - Series Overview</title>
      <link>https://bastabc.com/posts/aws-cdk-infrastructure-as-code/</link>
      <pubDate>Mon, 21 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://bastabc.com/posts/aws-cdk-infrastructure-as-code/</guid>
      <description>&lt;p&gt;AWS CDK lets you define cloud infrastructure in familiar programming languages - Python, TypeScript, Java - and synthesise it into CloudFormation. Compared to writing raw CloudFormation or Terraform HCL, CDK gives you loops, conditionals, type safety, and reusable constructs.&lt;/p&gt;
&lt;p&gt;This series covers practical CDK patterns using Python, with a consistent use case across all parts so the trade-offs are easy to compare.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;why-cdk-over-cloudformation-for-aws-native-workloads&#34;&gt;Why CDK over CloudFormation for AWS-native workloads?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;CloudFormation is YAML or JSON - no loops, no conditionals, no abstraction. Any shared pattern gets copy-pasted.&lt;/li&gt;
&lt;li&gt;CDK uses a real programming language - loops, functions, classes, and type checks all apply to infrastructure the same way they apply to application code.&lt;/li&gt;
&lt;li&gt;L2 constructs handle boilerplate. &lt;code&gt;bucket.grant_read(fn)&lt;/code&gt; generates the IAM policy, role attachment, and resource reference in one call. The CloudFormation equivalent is four resources wired together manually.&lt;/li&gt;
&lt;li&gt;The compiler catches mistakes before CloudFormation ever sees the template.&lt;/li&gt;
&lt;li&gt;CDK still synthesises to CloudFormation under the hood - rollbacks, drift detection, and stack history are unchanged.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;why-cdk-over-terraform-for-aws-native-workloads&#34;&gt;Why CDK over Terraform for AWS-native workloads?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Terraform&amp;rsquo;s strength is multi-cloud. For AWS-only workloads, that comes with overhead that doesn&amp;rsquo;t pay off - a state backend, a provider version to pin, and HCL alongside the application code.&lt;/li&gt;
&lt;li&gt;CDK uses CloudFormation as the deployment engine - AWS manages state natively. No S3 bucket for state, no DynamoDB table for locking, no &lt;code&gt;terraform init&lt;/code&gt; in the pipeline.&lt;/li&gt;
&lt;li&gt;New AWS services appear in CDK constructs faster.&lt;/li&gt;
&lt;li&gt;IAM is easier. Terraform requires writing policy JSON by hand and threading ARNs between resources. CDK&amp;rsquo;s &lt;code&gt;grant_*&lt;/code&gt; methods generate least-privilege policies from the resource graph.&lt;/li&gt;
&lt;li&gt;Terraform is the right call when infrastructure spans multiple cloud providers, or when the team already has a mature Terraform codebase.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;use-case&#34;&gt;Use case&lt;/h2&gt;

&lt;div class=&#34;callout callout-aws4&#34;&gt;
  Deploy an AWS Lambda function that processes files uploaded to an S3 bucket. Configuration - bucket name, log level, Lambda timeout - varies per environment (dev, staging, prod).
&lt;/div&gt;

&lt;p&gt;Simple infrastructure by design - the CDK patterns are the point, not the resources.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-series&#34;&gt;The series&lt;/h2&gt;
&lt;h3 id=&#34;chapter-1---project-setup-and-bootstrapping&#34;&gt;&lt;a href=&#34;https://bastabc.com/posts/aws-cdk-project-setup-and-bootstrapping/&#34;&gt;Chapter 1 - Project setup and bootstrapping&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Getting a CDK project off the ground - directory structure, virtual environments, bootstrapping an AWS account, and understanding the synth/deploy cycle.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;chapter-2---managing-configuration-and-context&#34;&gt;&lt;a href=&#34;https://bastabc.com/posts/aws-cdk-managing-configuration-and-context/&#34;&gt;Chapter 2 - Managing configuration and context&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;How environment-specific values flow into CDK stacks - four approaches covering local static config, local dynamic config, SSM Parameter Store, and Secrets Manager, with trade-offs and code examples for each.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;chapter-3---writing-and-sharing-constructs&#34;&gt;&lt;a href=&#34;https://bastabc.com/posts/aws-cdk-writing-constructs/&#34;&gt;Chapter 3 - Writing and sharing constructs&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Building L3 constructs - extracting resources into reusable units, the keyword-only props pattern, and sharing constructs across stacks.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;chapter-4---testing-cdk-stacks&#34;&gt;&lt;a href=&#34;https://bastabc.com/posts/aws-cdk-testing-stacks/&#34;&gt;Chapter 4 - Testing CDK stacks&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unit testing constructs and stacks with &lt;code&gt;aws_cdk.assertions&lt;/code&gt; - fine-grained assertions, snapshot testing, and when each applies.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;chapter-5---cicd-with-cdk&#34;&gt;&lt;a href=&#34;https://bastabc.com/posts/aws-cdk-cicd/&#34;&gt;Chapter 5 - CI/CD with CDK&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Automating CDK deployments with GitHub Actions, Jenkins, and Azure DevOps - auth setup, multi-environment promotion, and approval gates.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;

&lt;div class=&#34;callout callout-aws4&#34;&gt;
  &lt;ol&gt;
&lt;li&gt;All examples use Python and CDK v2.&lt;/li&gt;
&lt;li&gt;Steps are tested on WSL2/Ubuntu on Windows and native macOS terminal.&lt;/li&gt;
&lt;li&gt;More chapters added as I work through these patterns.&lt;/li&gt;
&lt;/ol&gt;

&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>AWS Kiro CLI - Manage AWS Infrastructure from the Terminal</title>
      <link>https://bastabc.com/posts/aws-kiro-cli/</link>
      <pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate>
      
      <guid>https://bastabc.com/posts/aws-kiro-cli/</guid>
      <description>&lt;p&gt;Kiro CLI is an AI-assisted terminal tool for AWS, rebranded from Amazon Q Developer CLI in November 2025. It sits on top of your existing AWS credentials and tooling. The &lt;code&gt;q&lt;/code&gt; and &lt;code&gt;q chat&lt;/code&gt; shortcuts from Q CLI still work but &lt;code&gt;kiro-cli&lt;/code&gt; is the current entry point.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;macOS&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;brew install --cask kiro-cli
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or via script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -fsSL https://cli.kiro.dev/install | bash
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Windows (PowerShell)&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;irm &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://cli.kiro.dev/install.ps1&amp;#39;&lt;/span&gt; | iex
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Linux / WSL2 (Ubuntu / Debian) - .deb&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;wget https://desktop-release.q.us-east-1.amazonaws.com/latest/kiro-cli.deb
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo dpkg -i kiro-cli.deb
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt-get install -f
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Linux - ZIP (x86-64, non-Debian distros)&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl --proto &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;=https&amp;#39;&lt;/span&gt; --tlsv1.2 -sSf &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://desktop-release.q.us-east-1.amazonaws.com/latest/kirocli-x86_64-linux.zip&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  -o kirocli.zip
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;unzip kirocli.zip
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./kirocli/install.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Verify:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli version
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli doctor   &lt;span style=&#34;color:#75715e&#34;&gt;# diagnoses config and auth issues&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;authenticate&#34;&gt;Authenticate&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli login     &lt;span style=&#34;color:#75715e&#34;&gt;# opens browser to complete sign-in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli whoami    &lt;span style=&#34;color:#75715e&#34;&gt;# shows current user and auth method&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli logout
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;AWS Builder ID&lt;/strong&gt; - free individual account at builder.aws. No existing AWS account required. Enough to get started with 50 free credits/month (500 bonus credits on first sign-in).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Builder ID with AWS accounts&lt;/strong&gt; - You can log in to Kiro with a personal Builder ID for the AI features, and connect to your AWS account using AWS SSO profile. Kiro picks up AWS credentials are active in the environment:&lt;/p&gt;
&lt;p&gt;Set the profile for the session and run commands targeting the profile:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export AWS_PROFILE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;my-prod
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;aws sts get-caller-identity              &lt;span style=&#34;color:#75715e&#34;&gt;# confirm which account is active&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;aws sts get-caller-identity --profile my-prod
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli chat &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;get Compute Optimizer recommendations&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;key-commands&#34;&gt;Key commands&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;kiro-cli&lt;/code&gt; with no arguments opens an interactive session - type prompts directly without any prefix. &lt;code&gt;kiro-cli chat &amp;quot;prompt&amp;quot;&lt;/code&gt; is for scripted or one-shot use.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli                          &lt;span style=&#34;color:#75715e&#34;&gt;# interactive session - type prompts directly&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli chat &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;your prompt&amp;#34;&lt;/span&gt;       &lt;span style=&#34;color:#75715e&#34;&gt;# start session with an initial prompt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli chat --resume            &lt;span style=&#34;color:#75715e&#34;&gt;# continue previous session&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli chat --no-interactive &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;prompt&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# one-shot, no interaction - useful in scripts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli translate &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;list S3 buckets&amp;#34;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# natural language to shell command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli agent list               &lt;span style=&#34;color:#75715e&#34;&gt;# list available agents&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli mcp add                  &lt;span style=&#34;color:#75715e&#34;&gt;# add an MCP server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli mcp list                 &lt;span style=&#34;color:#75715e&#34;&gt;# list configured MCP servers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli mcp status               &lt;span style=&#34;color:#75715e&#34;&gt;# check MCP server health&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli settings list            &lt;span style=&#34;color:#75715e&#34;&gt;# view current settings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli update                   &lt;span style=&#34;color:#75715e&#34;&gt;# upgrade to latest version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;infrastructure-workflows&#34;&gt;Infrastructure workflows&lt;/h2&gt;
&lt;p&gt;Kiro CLI does not call AWS APIs directly - it generates and optionally runs commands using your existing AWS credentials. A few patterns that work well:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generate and run AWS CLI commands from natural language:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli translate &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;list all EC2 instances in us-east-1 with their state&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# outputs: aws ec2 describe-instances --region us-east-1 --query ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Pipe AWS CLI output into chat for analysis:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;aws cloudwatch describe-alarms --state-value ALARM | &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  kiro-cli chat --no-interactive &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;summarise these alarms and suggest remediation&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Generate Terraform or CloudFormation:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli chat &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;write a Terraform module for a private S3 bucket with versioning and a 90-day lifecycle rule to Glacier&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Use in CI without interaction:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;KIRO_API_KEY&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;ksk_xxxxxxxx kiro-cli chat --no-interactive &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;explain this deployment error: &lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;cat deploy.log | tail -50&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Run commands with auto-approval in trusted environments:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kiro-cli chat --trust-all-tools &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;get Compute Optimizer recommendations for my account&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;

&lt;div class=&#34;callout callout-aws4&#34;&gt;
  &lt;ol&gt;
&lt;li&gt;If migrating from Q CLI, config migrates automatically from &lt;code&gt;~/.aws/amazonq&lt;/code&gt; to &lt;code&gt;~/.kiro&lt;/code&gt; on first run.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kiro-cli doctor&lt;/code&gt; is the first thing to run if auth or config behaves unexpectedly.&lt;/li&gt;
&lt;li&gt;The CLI is not open source - the license changed from Apache to AWS Intellectual Property License with the Kiro rebrand.&lt;/li&gt;
&lt;/ol&gt;

&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>GitHub PR Fix: &#34;Commits must have verified signatures&#34; blocking PR merge</title>
      <link>https://bastabc.com/posts/fix-gpg-signature-verification-blocking-pr-merge/</link>
      <pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate>
      
      <guid>https://bastabc.com/posts/fix-gpg-signature-verification-blocking-pr-merge/</guid>
      <description>&lt;h2 id=&#34;problem&#34;&gt;Problem&lt;/h2&gt;
&lt;p&gt;GitHub branch protection requires all commits to have verified GPG signatures. Two commits at the base of the branch (made before GPG signing was configured) were unsigned, blocking the merge.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;root-cause&#34;&gt;Root cause&lt;/h2&gt;
&lt;p&gt;Commits made before &lt;code&gt;commit.gpgsign=true&lt;/code&gt; was configured in git have no GPG signature. GitHub requires all commits in a PR to be verified - one unsigned commit blocks the merge regardless of approvals.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;steps-to-fix&#34;&gt;Steps to fix&lt;/h2&gt;
&lt;h3 id=&#34;1-identify-unsigned-commits&#34;&gt;1. Identify unsigned commits&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git log --format&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%G? %ad %s&amp;#34;&lt;/span&gt; --date&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;short origin/main..HEAD
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Look for lines starting with &lt;code&gt;N&lt;/code&gt; (no signature). Note the hash of the oldest unsigned commit&amp;rsquo;s parent on main.&lt;/p&gt;
&lt;h3 id=&#34;2-re-sign-all-branch-commits-using-filter-branch&#34;&gt;2. Re-sign all branch commits using filter-branch&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git filter-branch -f --commit-filter &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;git commit-tree -S &amp;#34;$@&amp;#34;&amp;#39;&lt;/span&gt; -- origin/main..HEAD
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Rewrites all branch commits adding a GPG signature - no content changes, no conflicts.&lt;/p&gt;
&lt;h3 id=&#34;3-verify-no-unsigned-commits-remain&#34;&gt;3. Verify no unsigned commits remain&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git log --format&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%G?&amp;#34;&lt;/span&gt; origin/main..HEAD | Select-String &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;^N&lt;/span&gt;$&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Should return nothing.&lt;/p&gt;
&lt;h3 id=&#34;4-force-push&#34;&gt;4. Force push&lt;/h3&gt;
&lt;p&gt;Attempt force push via terminal using the configured remote:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git push --force origin &amp;lt;branch&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If this fails with &amp;ldquo;repository not found&amp;rdquo; (HTTPS remote, no credentials) or &amp;ldquo;Could not read from remote repository&amp;rdquo; (SSH not configured), use a classic PAT with repo scope directly in the URL:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git push --force https://&amp;lt;username&amp;gt;:&amp;lt;PAT&amp;gt;@github.com/&amp;lt;org&amp;gt;/&amp;lt;repo&amp;gt;.git &amp;lt;branch&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;5-update-local-tracking-ref-if-github-desktop-shows-stale-state&#34;&gt;5. Update local tracking ref (if GitHub Desktop shows stale state)&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git update-ref refs/remotes/origin/&amp;lt;branch&amp;gt; &amp;lt;tip-commit-hash&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;

&lt;div class=&#34;callout callout-aws4&#34;&gt;
  &lt;ol&gt;
&lt;li&gt;Avoid &lt;code&gt;git rebase --exec &amp;quot;git commit --amend -S&amp;quot;&lt;/code&gt; if the branch has merge commits - it drops them and causes conflicts.&lt;/li&gt;
&lt;li&gt;Avoid &lt;code&gt;git rebase --rebase-merges&lt;/code&gt; - re-applying old merge commits causes conflicts from the original merges.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter-branch&lt;/code&gt; rewrites commit objects without replaying merges, which is what&amp;rsquo;s needed here.&lt;/li&gt;
&lt;li&gt;Fine-grained PATs do not work for org repositories - use classic PAT only.&lt;/li&gt;
&lt;li&gt;After force pushing, avoid &lt;code&gt;git pull&lt;/code&gt; / IDE auto-sync until the PR is merged - it will re-introduce the old unsigned commits via a new merge.&lt;/li&gt;
&lt;/ol&gt;

&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>AWS CDK - CI/CD Pipelines</title>
      <link>https://bastabc.com/posts/aws-cdk-cicd/</link>
      <pubDate>Sun, 27 Jul 2025 00:00:00 +0000</pubDate>
      
      <guid>https://bastabc.com/posts/aws-cdk-cicd/</guid>
      <description>&lt;p&gt;This is Chapter 5 of the &lt;a href=&#34;https://bastabc.com/posts/aws-cdk-infrastructure-as-code/&#34;&gt;Infrastructure as Code with AWS CDK&lt;/a&gt; series. The same &lt;code&gt;cdk deploy&lt;/code&gt; command that runs locally works in any pipeline - the differences are auth and orchestration.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;common-flags-for-ci&#34;&gt;Common flags for CI&lt;/h2&gt;
&lt;p&gt;These apply regardless of which CI system you use:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk synth                          &lt;span style=&#34;color:#75715e&#34;&gt;# generate CloudFormation template&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk deploy --require-approval never -c env&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;prod   &lt;span style=&#34;color:#75715e&#34;&gt;# deploy without interactive prompts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk deploy --outputs-file outputs.json            &lt;span style=&#34;color:#75715e&#34;&gt;# write stack outputs to file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--require-approval never&lt;/code&gt; skips the IAM change confirmation that CDK shows interactively. Required in CI.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-c env=prod&lt;/code&gt; passes the environment context from &lt;a href=&#34;https://bastabc.com/posts/aws-cdk-managing-configuration-and-context/&#34;&gt;Chapter 2&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;github-actions&#34;&gt;GitHub Actions&lt;/h2&gt;
&lt;h3 id=&#34;auth&#34;&gt;Auth&lt;/h3&gt;
&lt;p&gt;OIDC is the recommended auth method - no long-lived credentials stored in GitHub. The pipeline assumes an IAM role directly via GitHub&amp;rsquo;s OIDC provider.&lt;/p&gt;
&lt;p&gt;Create the OIDC provider in AWS once per account:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;aws iam create-open-id-connect-provider &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  --url https://token.actions.githubusercontent.com &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  --client-id-list sts.amazonaws.com &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Create an IAM role with a trust policy scoped to your repository:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Version&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2012-10-17&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Statement&amp;#34;&lt;/span&gt;: [{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Effect&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Principal&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Federated&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Action&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sts:AssumeRoleWithWebIdentity&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Condition&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;StringLike&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;token.actions.githubusercontent.com:sub&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;repo:my-org/my-repo:*&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Attach whatever IAM policies the CDK deploy needs (CloudFormation, S3, Lambda, IAM) to this role.&lt;/p&gt;
&lt;h3 id=&#34;workflow&#34;&gt;Workflow&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# .github/workflows/deploy.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;push&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;branches&lt;/span&gt;: [&lt;span style=&#34;color:#ae81ff&#34;&gt;main]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;permissions&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;id-token&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;write&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;contents&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;read&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;jobs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;deploy-dev&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;runs-on&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;ubuntu-latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;actions/checkout@v4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;actions/setup-python@v5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;python-version&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;3.12&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;actions/setup-node@v4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;node-version&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;20&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Install CDK CLI&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;npm install -g aws-cdk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Install dependencies&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;pip install -r requirements.txt -r requirements-dev.txt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Configure AWS credentials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;role-to-assume&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;arn:aws:iam::123456789012:role/GitHubActionsRole&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;aws-region&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;us-east-1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Synth&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;cdk synth -c env=dev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;pytest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Deploy dev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;cdk deploy --require-approval never -c env=dev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;deploy-prod&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;runs-on&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;ubuntu-latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;needs&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;deploy-dev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;environment&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;production&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;actions/checkout@v4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;actions/setup-python@v5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;python-version&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;3.12&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;actions/setup-node@v4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;node-version&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;20&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Install CDK CLI&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;npm install -g aws-cdk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Install dependencies&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;pip install -r requirements.txt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Configure AWS credentials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;role-to-assume&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;arn:aws:iam::123456789012:role/GitHubActionsRole&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;aws-region&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;us-east-1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Deploy prod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;cdk deploy --require-approval never -c env=prod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;environment: production&lt;/code&gt; job pauses for a manual approval before running. Configure required reviewers under Settings &amp;gt; Environments &amp;gt; production in the GitHub repository.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;jenkins&#34;&gt;Jenkins&lt;/h2&gt;
&lt;h3 id=&#34;auth-1&#34;&gt;Auth&lt;/h3&gt;
&lt;p&gt;The simplest setup: run the Jenkins agent on an EC2 instance with an IAM instance role. The CDK CLI picks up credentials automatically via the instance metadata service - no credentials to manage.&lt;/p&gt;
&lt;p&gt;If the agent is not on EC2, store AWS credentials as a Jenkins credential (Credentials &amp;gt; Add &amp;gt; AWS Credentials) and inject them into the pipeline with the &lt;code&gt;withCredentials&lt;/code&gt; block.&lt;/p&gt;
&lt;h3 id=&#34;jenkinsfile&#34;&gt;Jenkinsfile&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pipeline &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    agent any
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    environment &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        AWS_DEFAULT_REGION &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;us-east-1&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stages &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        stage&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Install&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            steps &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pip install -r requirements.txt -r requirements-dev.txt&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;npm install -g aws-cdk&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        stage&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Synth&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            steps &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cdk synth -c env=dev&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        stage&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Test&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            steps &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pytest&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        stage&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Deploy dev&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            steps &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cdk deploy --require-approval never -c env=dev&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        stage&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Approve prod&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            steps &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                input message: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Deploy to production?&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; ok: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Deploy&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        stage&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Deploy prod&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            steps &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cdk deploy --require-approval never -c env=prod&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    post &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        failure &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Pipeline failed - check CloudFormation events for rollback details&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;input&lt;/code&gt; step pauses the pipeline until someone clicks Deploy in the Jenkins UI.&lt;/p&gt;
&lt;p&gt;If credentials are stored in Jenkins rather than on an instance role, wrap the deploy steps:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;withCredentials&lt;span style=&#34;color:#f92672&#34;&gt;([[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $class&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;AmazonWebServicesCredentialsBinding&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    credentialsId: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;aws-deploy-credentials&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]])&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cdk deploy --require-approval never -c env=prod&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;azure-devops&#34;&gt;Azure DevOps&lt;/h2&gt;
&lt;h3 id=&#34;auth-2&#34;&gt;Auth&lt;/h3&gt;
&lt;p&gt;Store AWS credentials as secret pipeline variables (&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;, &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;) or use the AWS Toolkit for Azure DevOps extension, which adds an &lt;code&gt;AWSShellScript&lt;/code&gt; task with a configured service connection.&lt;/p&gt;
&lt;p&gt;The example below uses secret variables directly, which works without the extension.&lt;/p&gt;
&lt;h3 id=&#34;pipeline&#34;&gt;Pipeline&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# azure-pipelines.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;trigger&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;branches&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;include&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ae81ff&#34;&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;variables&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;pythonVersion&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;3.12&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;awsRegion&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;stages&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;stage&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;DeployDev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;displayName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Deploy to dev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;jobs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;job&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;pool&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;vmImage&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;ubuntu-latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#f92672&#34;&gt;task&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;UsePythonVersion@0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;versionSpec&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;$(pythonVersion)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;: |&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              pip install -r requirements.txt -r requirements-dev.txt
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              npm install -g aws-cdk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;displayName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Install dependencies&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;cdk synth -c env=dev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;displayName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Synth&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;env&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;$(AWS_ACCESS_KEY_ID)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;$(AWS_SECRET_ACCESS_KEY)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;AWS_DEFAULT_REGION&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;$(awsRegion)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;pytest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;displayName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;cdk deploy --require-approval never -c env=dev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;displayName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Deploy dev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;env&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;$(AWS_ACCESS_KEY_ID)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;$(AWS_SECRET_ACCESS_KEY)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;AWS_DEFAULT_REGION&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;$(awsRegion)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;stage&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;DeployProd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;displayName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Deploy to prod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;dependsOn&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;DeployDev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;jobs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;deployment&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;environment&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;production&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;pool&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;vmImage&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;ubuntu-latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;strategy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;runOnce&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;deploy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                - &lt;span style=&#34;color:#f92672&#34;&gt;checkout&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;self&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                - &lt;span style=&#34;color:#f92672&#34;&gt;task&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;UsePythonVersion@0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#f92672&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f92672&#34;&gt;versionSpec&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;$(pythonVersion)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                - &lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;: |&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                    pip install -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                    npm install -g aws-cdk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#f92672&#34;&gt;displayName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Install dependencies&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                - &lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;cdk deploy --require-approval never -c env=prod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#f92672&#34;&gt;displayName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Deploy prod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#f92672&#34;&gt;env&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f92672&#34;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;$(AWS_ACCESS_KEY_ID)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f92672&#34;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;$(AWS_SECRET_ACCESS_KEY)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f92672&#34;&gt;AWS_DEFAULT_REGION&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;$(awsRegion)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;deployment&lt;/code&gt; job type with &lt;code&gt;environment: production&lt;/code&gt; enables approval gates. Configure approvers under Pipelines &amp;gt; Environments &amp;gt; production in Azure DevOps.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;aws-codepipeline&#34;&gt;AWS CodePipeline&lt;/h2&gt;
&lt;p&gt;CDK ships a &lt;code&gt;pipelines&lt;/code&gt; module that creates a CodePipeline pipeline directly from CDK code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; aws_cdk &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; pipelines
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pipeline &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pipelines&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;CodePipeline(self, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Pipeline&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    synth&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;pipelines&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ShellStep(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Synth&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        input&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;pipelines&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;CodePipelineSource&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;git_hub(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;my-org/my-repo&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;main&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        commands&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;pip install -r requirements.txt&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;npm install -g aws-cdk&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cdk synth&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The pipeline is self-mutating - when CDK code changes, the pipeline updates itself before deploying application changes. It is tightly coupled to CodePipeline and requires more upfront setup than the external CI approaches above, but is the right fit when the deployment needs to stay fully within AWS.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;

&lt;div class=&#34;callout callout-aws4&#34;&gt;
  &lt;ol&gt;
&lt;li&gt;&lt;code&gt;--require-approval never&lt;/code&gt; skips the IAM diff prompt. Review IAM changes in &lt;code&gt;cdk diff&lt;/code&gt; output or pull request checks before merging rather than at deploy time.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;cdk.context.json&lt;/code&gt; file should be committed - pipelines need it for SSM lookups cached in Chapter 2 to avoid live SSM calls on every run.&lt;/li&gt;
&lt;li&gt;CloudFormation automatically rolls back a failed deployment to the previous stable state. No extra rollback configuration is needed in the pipeline.&lt;/li&gt;
&lt;li&gt;For OIDC in GitHub Actions, scope the trust condition to a specific branch (&lt;code&gt;repo:my-org/my-repo:ref:refs/heads/main&lt;/code&gt;) rather than &lt;code&gt;*&lt;/code&gt; to limit which workflows can assume the role.&lt;/li&gt;
&lt;/ol&gt;

&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>AWS CDK - Testing CDK Stacks</title>
      <link>https://bastabc.com/posts/aws-cdk-testing-stacks/</link>
      <pubDate>Sat, 05 Jul 2025 00:00:00 +0000</pubDate>
      
      <guid>https://bastabc.com/posts/aws-cdk-testing-stacks/</guid>
      <description>&lt;p&gt;This is Chapter 4 of the &lt;a href=&#34;https://bastabc.com/posts/aws-cdk-infrastructure-as-code/&#34;&gt;Infrastructure as Code with AWS CDK&lt;/a&gt; series. Chapter 3 built &lt;code&gt;FileProcessorConstruct&lt;/code&gt; - the tests here cover that construct and the stack it lives in.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;how-cdk-testing-works&#34;&gt;How CDK testing works&lt;/h2&gt;
&lt;p&gt;CDK unit tests don&amp;rsquo;t deploy anything. They synthesize a stack into a CloudFormation template and run assertions against that template. No AWS credentials needed, and the suite runs in CI without any special setup.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;aws_cdk.assertions&lt;/code&gt; module is already part of &lt;code&gt;aws-cdk-lib&lt;/code&gt; - no extra install required.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;cdk init&lt;/code&gt; adds pytest to &lt;code&gt;requirements-dev.txt&lt;/code&gt; automatically. Install it if not already active:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pip install -r requirements-dev.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Create the test files:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;my-cdk-app/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── tests/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── __init__.py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── test_file_processor.py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── test_stack.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;testing-the-construct&#34;&gt;Testing the construct&lt;/h2&gt;
&lt;p&gt;Each test builds a minimal stack, instantiates the construct inside it, and asserts against the synthesized template:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# tests/test_file_processor.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; pytest
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; aws_cdk &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; App, Stack
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; aws_cdk.assertions &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Template, Match
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; my_cdk_app.file_processor &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; FileProcessorConstruct
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TEST_PROPS &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; dict(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bucket_name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;test-bucket&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    log_level&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;DEBUG&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    lambda_timeout&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@pytest.fixture&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;template&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    app &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; App()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stack &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Stack(app, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TestStack&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    FileProcessorConstruct(stack, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TestConstruct&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;TEST_PROPS)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Template&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;from_stack(stack)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;test_bucket_created&lt;/span&gt;(template):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    template&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;has_resource_properties(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;AWS::S3::Bucket&amp;#34;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;BucketName&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;test-bucket&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;test_lambda_runtime_and_timeout&lt;/span&gt;(template):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    template&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;has_resource_properties(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;AWS::Lambda::Function&amp;#34;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Runtime&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;python3.12&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Timeout&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Environment&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Variables&amp;#34;&lt;/span&gt;: {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LOG_LEVEL&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;DEBUG&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;test_lambda_has_read_access&lt;/span&gt;(template):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    template&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;has_resource_properties(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;AWS::IAM::Policy&amp;#34;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;PolicyDocument&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Statement&amp;#34;&lt;/span&gt;: Match&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;array_with([
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Match&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;object_like({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Action&amp;#34;&lt;/span&gt;: Match&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;array_with([&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;s3:GetObject*&amp;#34;&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Effect&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;has_resource_properties&lt;/code&gt; does a subset match - the resource only needs to contain the specified properties, not match them exactly. &lt;code&gt;Match.array_with&lt;/code&gt; and &lt;code&gt;Match.object_like&lt;/code&gt; apply the same subset logic to nested arrays and objects.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;testing-the-stack&#34;&gt;Testing the stack&lt;/h2&gt;
&lt;p&gt;Test &lt;code&gt;MyStack&lt;/code&gt; the same way, using a config dict that matches what &lt;code&gt;app.py&lt;/code&gt; passes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# tests/test_stack.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; aws_cdk &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; App
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; aws_cdk.assertions &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Template
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; my_cdk_app.my_cdk_app_stack &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; MyStack
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TEST_CONFIG &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bucket_name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;test-bucket&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;log_level&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;INFO&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lambda_timeout&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;test_stack_resource_count&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    app &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; App()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stack &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MyStack(app, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TestStack&amp;#34;&lt;/span&gt;, config&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;TEST_CONFIG)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    template &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Template&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;from_stack(stack)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    template&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;resource_count_is(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;AWS::S3::Bucket&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    template&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;resource_count_is(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;AWS::Lambda::Function&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;snapshot-testing&#34;&gt;Snapshot testing&lt;/h2&gt;
&lt;p&gt;Snapshot tests capture the full synthesized template and fail if anything changes. Write the template to a file on first run, then compare on subsequent runs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; json
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; pathlib &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Path
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; aws_cdk &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; App
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; aws_cdk.assertions &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Template
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; my_cdk_app.my_cdk_app_stack &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; MyStack
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SNAPSHOT_PATH &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Path(__file__)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parent &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;snapshots&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;stack.json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TEST_CONFIG &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bucket_name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;test-bucket&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;log_level&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;INFO&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lambda_timeout&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;test_stack_snapshot&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    app &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; App()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stack &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MyStack(app, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TestStack&amp;#34;&lt;/span&gt;, config&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;TEST_CONFIG)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    template &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Template&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;from_stack(stack)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;to_json()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; SNAPSHOT_PATH&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;exists():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SNAPSHOT_PATH&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parent&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mkdir(exist_ok&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SNAPSHOT_PATH&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write_text(json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;dumps(template, indent&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expected &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;loads(SNAPSHOT_PATH&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read_text())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;assert&lt;/span&gt; template &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; expected
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Commit the snapshot file. To update it after an intentional change, delete the file and run the test once to regenerate.&lt;/p&gt;
&lt;p&gt;The downside: CDK version bumps routinely touch metadata in the template - &lt;code&gt;aws:cdk:path&lt;/code&gt;, asset hashes, checksums. Any of these invalidates the snapshot even when nothing meaningful changed. They work well for catching unintended IAM or resource config changes, but generate noise as a general regression check.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;running-tests&#34;&gt;Running tests&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pytest
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pytest -v                             &lt;span style=&#34;color:#75715e&#34;&gt;# verbose output&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pytest tests/test_file_processor.py  &lt;span style=&#34;color:#75715e&#34;&gt;# single file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;

&lt;div class=&#34;callout callout-aws4&#34;&gt;
  &lt;ol&gt;
&lt;li&gt;&lt;code&gt;has_resource_properties&lt;/code&gt; only checks the &lt;code&gt;Properties&lt;/code&gt; block. To assert on the full resource including &lt;code&gt;DeletionPolicy&lt;/code&gt;, &lt;code&gt;DependsOn&lt;/code&gt;, or &lt;code&gt;Metadata&lt;/code&gt;, use &lt;code&gt;has_resource&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Template.from_stack&lt;/code&gt; calls &lt;code&gt;app.synth()&lt;/code&gt; implicitly - no need to call it manually.&lt;/li&gt;
&lt;li&gt;CDK assigns logical IDs based on the construct path. Changing a construct&amp;rsquo;s &lt;code&gt;id&lt;/code&gt; argument changes its logical ID, which CloudFormation treats as a delete and recreate. Snapshot tests catch this; &lt;code&gt;has_resource_properties&lt;/code&gt; tests do not.&lt;/li&gt;
&lt;li&gt;For TypeScript CDK projects, Jest has built-in snapshot support via &lt;code&gt;toMatchSnapshot()&lt;/code&gt;. Python has no equivalent - the manual file approach above is the workaround.&lt;/li&gt;
&lt;/ol&gt;

&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>AWS CDK - Writing and Sharing Constructs</title>
      <link>https://bastabc.com/posts/aws-cdk-writing-constructs/</link>
      <pubDate>Sat, 14 Jun 2025 00:00:00 +0000</pubDate>
      
      <guid>https://bastabc.com/posts/aws-cdk-writing-constructs/</guid>
      <description>&lt;p&gt;This is Chapter 3 of the &lt;a href=&#34;https://bastabc.com/posts/aws-cdk-infrastructure-as-code/&#34;&gt;Infrastructure as Code with AWS CDK&lt;/a&gt; series. Chapters 1 and 2 put the S3 bucket and Lambda directly in &lt;code&gt;MyStack&lt;/code&gt;. Both get extracted into a reusable construct here.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;l1-l2-l3&#34;&gt;L1, L2, L3&lt;/h2&gt;
&lt;p&gt;CDK organises constructs into three levels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;L1&lt;/strong&gt; - raw CloudFormation resource wrappers, prefixed with &lt;code&gt;Cfn&lt;/code&gt;. &lt;code&gt;CfnBucket&lt;/code&gt;, &lt;code&gt;CfnFunction&lt;/code&gt;. No defaults added - every property must be set explicitly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L2&lt;/strong&gt; - CDK&amp;rsquo;s built-in higher-level constructs. &lt;code&gt;s3.Bucket&lt;/code&gt;, &lt;code&gt;_lambda.Function&lt;/code&gt;. These set sensible defaults, expose helper methods, and handle IAM via &lt;code&gt;grant_*&lt;/code&gt; methods.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L3&lt;/strong&gt; - composite constructs you write that bundle multiple resources into a single unit. The previous chapters used L2 constructs directly inside a stack. This chapter builds an L3.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-problem&#34;&gt;The problem&lt;/h2&gt;
&lt;p&gt;The current &lt;code&gt;MyStack&lt;/code&gt; creates the bucket and Lambda directly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MyStack&lt;/span&gt;(Stack):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(self, scope, id, config, &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;kwargs):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        super()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(scope, id, &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;kwargs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        bucket &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; s3&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Bucket(self, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;FilesBucket&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            bucket_name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bucket_name&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        fn &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; _lambda&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Function(self, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ProcessorFunction&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            runtime&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;_lambda&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Runtime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;PYTHON_3_12,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            handler&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;handler.handler&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            code&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;_lambda&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Code&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;from_asset(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lambda&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            timeout&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;Duration&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;seconds(config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lambda_timeout&amp;#34;&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            environment&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LOG_LEVEL&amp;#34;&lt;/span&gt;: config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;log_level&amp;#34;&lt;/span&gt;]}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        bucket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;grant_read(fn)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you need a second processor - say one for raw uploads and one for processed files - all of this gets duplicated. That&amp;rsquo;s what a construct solves.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;writing-the-construct&#34;&gt;Writing the construct&lt;/h2&gt;
&lt;p&gt;Add &lt;code&gt;file_processor.py&lt;/code&gt; inside the existing app module:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;my-cdk-app/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── app.py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── my_cdk_app/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── __init__.py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── my_cdk_app_stack.py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── file_processor.py     ← new
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# my_cdk_app/file_processor.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; aws_cdk &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Duration,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    aws_s3 &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; s3,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    aws_lambda &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; _lambda,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; constructs &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Construct
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FileProcessorConstruct&lt;/span&gt;(Construct):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        scope: Construct,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        id: str,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        bucket_name: str,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        log_level: str,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        lambda_timeout: int,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        super()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(scope, id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;bucket &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; s3&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Bucket(self, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bucket&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            bucket_name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;bucket_name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fn &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; _lambda&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Function(self, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Function&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            runtime&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;_lambda&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Runtime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;PYTHON_3_12,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            handler&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;handler.handler&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            code&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;_lambda&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Code&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;from_asset(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lambda&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            timeout&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;Duration&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;seconds(lambda_timeout),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            environment&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LOG_LEVEL&amp;#34;&lt;/span&gt;: log_level}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;bucket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;grant_read(self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fn)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;*&lt;/code&gt; before the keyword arguments makes them keyword-only - callers must pass them by name, not by position. &lt;code&gt;self.bucket&lt;/code&gt; and &lt;code&gt;self.fn&lt;/code&gt; are public so callers can reach in and extend if needed.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;using-it-in-a-stack&#34;&gt;Using it in a stack&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# my_cdk_app/my_cdk_app_stack.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; aws_cdk &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Stack
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; constructs &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Construct
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; my_cdk_app.file_processor &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; FileProcessorConstruct
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MyStack&lt;/span&gt;(Stack):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(self, scope: Construct, id: str, config: dict, &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;kwargs):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        super()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(scope, id, &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;kwargs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        FileProcessorConstruct(self, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;FileProcessor&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            bucket_name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bucket_name&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            log_level&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;log_level&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            lambda_timeout&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lambda_timeout&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;reusing-the-construct&#34;&gt;Reusing the construct&lt;/h2&gt;
&lt;p&gt;Two processors in the same stack, no duplication:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MyStack&lt;/span&gt;(Stack):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(self, scope: Construct, id: str, config: dict, &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;kwargs):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        super()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(scope, id, &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;kwargs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        FileProcessorConstruct(self, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;RawProcessor&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            bucket_name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;bucket_name&amp;#39;&lt;/span&gt;]&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;-raw&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            log_level&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;log_level&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            lambda_timeout&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lambda_timeout&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        FileProcessorConstruct(self, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ProcessedProcessor&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            bucket_name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;bucket_name&amp;#39;&lt;/span&gt;]&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;-processed&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            log_level&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;log_level&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            lambda_timeout&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lambda_timeout&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;CDK uses the &lt;code&gt;id&lt;/code&gt; argument (&lt;code&gt;RawProcessor&lt;/code&gt;, &lt;code&gt;ProcessedProcessor&lt;/code&gt;) to make all resource logical IDs unique within the stack. Both constructs deploy without naming conflicts.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;using-exposed-attributes&#34;&gt;Using exposed attributes&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;self.bucket&lt;/code&gt; and &lt;code&gt;self.fn&lt;/code&gt; when something needs to be added at the call site:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;processor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FileProcessorConstruct(self, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;FileProcessor&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bucket_name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bucket_name&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    log_level&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;log_level&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    lambda_timeout&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lambda_timeout&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Grant a second function read access to the same bucket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;processor&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;bucket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;grant_read(reporting_fn)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If it&amp;rsquo;s specific to one stack, keep it there rather than baking it into the construct.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;

&lt;div class=&#34;callout callout-aws4&#34;&gt;
  &lt;ol&gt;
&lt;li&gt;Do not name the constructs directory &lt;code&gt;constructs/&lt;/code&gt; - it conflicts with the CDK &lt;code&gt;constructs&lt;/code&gt; package import. Putting files inside the app module (&lt;code&gt;my_cdk_app/&lt;/code&gt;) sidesteps this entirely.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;*&lt;/code&gt; in the &lt;code&gt;__init__&lt;/code&gt; signature enforces keyword arguments. Without it, callers can pass values positionally, which gets confusing with three or more parameters.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;self.bucket&lt;/code&gt; and &lt;code&gt;self.fn&lt;/code&gt; being public is a choice. If the construct is fully self-contained and callers should never reach in, keep them private with a leading underscore (&lt;code&gt;self._bucket&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;To use a construct across multiple CDK projects, move it into a standalone Python package and install it as a dependency. Cross-language support (TypeScript, Java) requires &lt;code&gt;jsii&lt;/code&gt; - a separate topic.&lt;/li&gt;
&lt;/ol&gt;

&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>AWS CDK - Managing Configuration and Context</title>
      <link>https://bastabc.com/posts/aws-cdk-managing-configuration-and-context/</link>
      <pubDate>Wed, 28 May 2025 00:00:00 +0000</pubDate>
      
      <guid>https://bastabc.com/posts/aws-cdk-managing-configuration-and-context/</guid>
      <description>&lt;p&gt;This is Chapter 2 of the &lt;a href=&#34;https://bastabc.com/posts/aws-cdk-infrastructure-as-code/&#34;&gt;Infrastructure as Code with AWS CDK&lt;/a&gt; series. Four approaches to environment-specific configuration in CDK stacks - same use case throughout so the trade-offs sit side by side.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use case&lt;/strong&gt;: An S3 bucket and Lambda function where bucket name, log level, and Lambda timeout vary per environment (dev, staging, prod).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;quick-comparison&#34;&gt;Quick comparison&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Approach&lt;/th&gt;
          &lt;th&gt;Version controlled&lt;/th&gt;
          &lt;th&gt;Supports secrets&lt;/th&gt;
          &lt;th&gt;Change without redeploy&lt;/th&gt;
          &lt;th&gt;Shared across stacks&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Static config (&lt;code&gt;cdk.json&lt;/code&gt;)&lt;/td&gt;
          &lt;td&gt;Yes&lt;/td&gt;
          &lt;td&gt;No&lt;/td&gt;
          &lt;td&gt;No&lt;/td&gt;
          &lt;td&gt;No&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Dynamic config&lt;/td&gt;
          &lt;td&gt;No&lt;/td&gt;
          &lt;td&gt;Partial&lt;/td&gt;
          &lt;td&gt;Yes&lt;/td&gt;
          &lt;td&gt;No&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Secrets Manager&lt;/td&gt;
          &lt;td&gt;No&lt;/td&gt;
          &lt;td&gt;Yes&lt;/td&gt;
          &lt;td&gt;Yes&lt;/td&gt;
          &lt;td&gt;Yes&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;CI/CD context injection&lt;/td&gt;
          &lt;td&gt;No&lt;/td&gt;
          &lt;td&gt;No&lt;/td&gt;
          &lt;td&gt;Yes&lt;/td&gt;
          &lt;td&gt;No&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;local-dev-patterns&#34;&gt;Local dev patterns&lt;/h2&gt;
&lt;p&gt;Suitable for personal projects or local development. Neither is a good fit for CI/CD pipelines or shared team environments.&lt;/p&gt;
&lt;h3 id=&#34;static-config-cdkjson&#34;&gt;Static config (&lt;code&gt;cdk.json&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;Store environment-specific config in &lt;code&gt;cdk.json&lt;/code&gt; under the &lt;code&gt;context&lt;/code&gt; key. CDK reads this file automatically at synth time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;: Stable, non-sensitive values that are the same for everyone on the team.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;app&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;python3 app.py&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;context&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;dev&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;bucket_name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;my-bucket-dev&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;log_level&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;DEBUG&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;lambda_timeout&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;prod&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;bucket_name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;my-bucket-prod&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;log_level&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;INFO&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;lambda_timeout&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Read context in &lt;code&gt;app.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cdk&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;App()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;env_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; app&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;node&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;try_get_context(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;env&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;dev&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;config &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; app&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;node&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;try_get_context(env_name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MyStack(app, &lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyStack-&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;env_name&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, config&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Deploy with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk deploy -c env&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;dev
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk deploy -c env&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;prod
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Trade-off&lt;/strong&gt;: Any config change requires a commit and redeploy. Never put secrets here - &lt;code&gt;cdk.json&lt;/code&gt; is committed to source control.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;dynamic-config&#34;&gt;Dynamic config&lt;/h3&gt;
&lt;p&gt;Load config at synth time from a local file or environment variables, outside the CDK project. Useful when values differ per developer or machine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;: Values that differ between developers - e.g. personal AWS account IDs, local endpoint overrides.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;config.dev.json&lt;/code&gt; outside source control:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;bucket_name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;my-bucket-dev&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;log_level&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;DEBUG&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;lambda_timeout&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Load it in &lt;code&gt;app.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; json
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; os
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cdk&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;App()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;env_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; os&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;environ&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ENV&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;dev&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; open(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;config.&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;env_name&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;.json&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; f:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    config &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;load(f)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MyStack(app, &lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyStack-&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;env_name&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, config&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Add config files to &lt;code&gt;.gitignore&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;config.*.json
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Deploy with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ENV&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;dev cdk deploy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ENV&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;prod cdk deploy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Trade-off&lt;/strong&gt;: Config lives outside source control - harder to audit and reproduce. Not suitable for team environments where config needs to be consistent.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;production-patterns&#34;&gt;Production patterns&lt;/h2&gt;
&lt;p&gt;Config lives in AWS or is injected by the pipeline - not in the codebase. Values can change without a code commit.&lt;/p&gt;
&lt;h3 id=&#34;secrets-manager&#34;&gt;Secrets Manager&lt;/h3&gt;
&lt;p&gt;Store sensitive config in Secrets Manager. Values are fetched at deploy time and injected into the Lambda environment - they are never visible in the CloudFormation template.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;: API keys, database passwords, tokens. Secrets should never go in &lt;code&gt;cdk.json&lt;/code&gt; or plain environment variables.&lt;/p&gt;
&lt;p&gt;Create a secret (run once per environment):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;aws secretsmanager create-secret &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  --name &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/my-app/dev/config&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  --secret-string &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;{&amp;#34;api_key&amp;#34;:&amp;#34;abc123&amp;#34;,&amp;#34;db_password&amp;#34;:&amp;#34;s3cr3t&amp;#34;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Reference the secret in the stack:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; aws_cdk &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; aws_secretsmanager &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; secretsmanager
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MyStack&lt;/span&gt;(Stack):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(self, scope, id, env_name, &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;kwargs):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        super()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(scope, id, &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;kwargs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        secret &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; secretsmanager&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Secret&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;from_secret_name_v2(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;AppSecret&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/my-app/&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;env_name&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/config&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        fn &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; _lambda&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Function(self, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyFunction&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            environment&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SECRET_ARN&amp;#34;&lt;/span&gt;: secret&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;secret_arn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        secret&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;grant_read(fn)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The Lambda reads the secret at runtime using the ARN:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; boto3&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; json&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; os
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handler&lt;/span&gt;(event, context):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    client &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; boto3&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;client(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;secretsmanager&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    secret &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;loads(client&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_secret_value(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SecretId&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;os&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SECRET_ARN&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SecretString&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    api_key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; secret[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;api_key&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Trade-off&lt;/strong&gt;: Cost per secret per month. Values are only available at runtime, not synth time. Rotation adds operational overhead but is worth setting up for credentials.&lt;/p&gt;
&lt;p&gt;For non-sensitive shared config that needs to change without a code commit - bucket names, log levels, timeouts - SSM Parameter Store is an alternative. Parameters are fetched at synth time via &lt;code&gt;value_from_lookup&lt;/code&gt; and cached in &lt;code&gt;cdk.context.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; aws_cdk &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; aws_ssm &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; ssm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bucket_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ssm&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;StringParameter&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;value_from_lookup(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    self, &lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/my-app/&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;env_name&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/bucket_name&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Commit &lt;code&gt;cdk.context.json&lt;/code&gt; so CI doesn&amp;rsquo;t need live SSM access on every run. SSM has no secrets support - keep anything sensitive in Secrets Manager.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;cicd-context-injection&#34;&gt;CI/CD context injection&lt;/h3&gt;
&lt;p&gt;Pass values into the stack directly from the pipeline via CDK context flags. The stack reads them at synth time - no external config files or AWS lookups required.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;: Values that are known to the pipeline at deploy time - environment name, account ID, deploy target region, feature flags. Works well when the pipeline is the source of truth for what gets deployed where.&lt;/p&gt;
&lt;p&gt;The stack reads context with &lt;code&gt;try_get_context&lt;/code&gt; and uses &lt;code&gt;Stack.of(self).account&lt;/code&gt; for the resolved AWS account:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; aws_cdk &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Stack
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; constructs &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Construct
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MyStack&lt;/span&gt;(Stack):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(self, scope: Construct, id: str, &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;kwargs):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        super()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(scope, id, &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;kwargs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        env_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;node&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;try_get_context(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;env&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        profile &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;node&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;try_get_context(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;profile&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        account_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Stack&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;of(self)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;account
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        bucket &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; s3&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Bucket(self, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;FilesBucket&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            bucket_name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;my-bucket-&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;env_name&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;account_id&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The pipeline passes context at deploy time:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# GitHub Actions, CodePipeline, or any CI runner&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk deploy -c env&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;prod -c profile&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;my-prod
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A GitHub Actions step looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;cdk deploy --require-approval never -c env=prod -c profile=my-prod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;env&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;try_get_context&lt;/code&gt; returns &lt;code&gt;None&lt;/code&gt; if the key is not passed. Add a guard if the value is required:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;env_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;node&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;try_get_context(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;env&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; env_name:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ValueError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;env context is required - pass with -c env=&amp;lt;name&amp;gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Trade-off&lt;/strong&gt;: Context values are visible in the pipeline logs and the synthesized CloudFormation template. Keep secrets in &lt;a href=&#34;#secrets-manager&#34;&gt;Secrets Manager&lt;/a&gt; and pass only non-sensitive identifiers this way.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;

&lt;div class=&#34;callout callout-aws4&#34;&gt;
  &lt;ol&gt;
&lt;li&gt;These approaches combine well - SSM for non-sensitive shared config, Secrets Manager for secrets, CI/CD context injection for environment identifiers.&lt;/li&gt;
&lt;li&gt;Secrets Manager values are never embedded in CloudFormation templates - they are resolved at runtime by the Lambda itself.&lt;/li&gt;
&lt;li&gt;On Windows with PowerShell, set the env var with &lt;code&gt;$env:ENV=&amp;quot;dev&amp;quot;; cdk deploy&lt;/code&gt; instead of the bash syntax.&lt;/li&gt;
&lt;/ol&gt;

&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>AWS CDK - Project Setup and Bootstrapping</title>
      <link>https://bastabc.com/posts/aws-cdk-project-setup-and-bootstrapping/</link>
      <pubDate>Sun, 11 May 2025 00:00:00 +0000</pubDate>
      
      <guid>https://bastabc.com/posts/aws-cdk-project-setup-and-bootstrapping/</guid>
      <description>&lt;p&gt;This is Chapter 1 of the &lt;a href=&#34;https://bastabc.com/posts/aws-cdk-infrastructure-as-code/&#34;&gt;Infrastructure as Code with AWS CDK&lt;/a&gt; series. Two things come before writing any stack code: initialising the CDK project and bootstrapping the AWS account.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Python 3.9+&lt;/li&gt;
&lt;li&gt;Node.js 18+ (CDK CLI is a Node package)&lt;/li&gt;
&lt;li&gt;AWS CLI v2 configured with valid credentials - see &lt;a href=&#34;https://bastabc.com/posts/connect-to-aws-sso-ssh-into-ec2-instance/&#34;&gt;Connect to AWS SSO and SSH into EC2 Instance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;An AWS account with permissions to create IAM roles, S3 buckets, and CloudFormation stacks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Install the CDK CLI globally:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npm install -g aws-cdk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk --version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;initialise-project&#34;&gt;Initialise project&lt;/h2&gt;
&lt;p&gt;Create a new directory and initialise a CDK Python project inside it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir my-cdk-app &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; cd my-cdk-app
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk init app --language python
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This scaffolds the project with a virtual environment, a sample stack, and a &lt;code&gt;cdk.json&lt;/code&gt; config file.&lt;/p&gt;
&lt;p&gt;Activate the virtual environment and install dependencies:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# macOS / Linux&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;source .venv/bin/activate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Windows (PowerShell)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.venv&lt;span style=&#34;color:#ae81ff&#34;&gt;\S&lt;/span&gt;cripts&lt;span style=&#34;color:#ae81ff&#34;&gt;\a&lt;/span&gt;ctivate.ps1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pip install -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;directory-structure&#34;&gt;Directory structure&lt;/h2&gt;
&lt;p&gt;After initialisation the project looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;my-cdk-app/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── app.py                  # CDK app entry point
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── cdk.json                # CDK config and context
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── requirements.txt        # Python dependencies
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── requirements-dev.txt    # Dev dependencies (e.g. pytest)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── .venv/                  # Python virtual environment
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── my_cdk_app/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── __init__.py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    └── my_cdk_app_stack.py # Default stack
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;app.py&lt;/code&gt; is where the CDK app is defined and stacks are instantiated. &lt;code&gt;my_cdk_app_stack.py&lt;/code&gt; is where resources are defined. The folder and class names match the project name passed to &lt;code&gt;cdk init&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;bootstrap-aws-account&#34;&gt;Bootstrap AWS account&lt;/h2&gt;
&lt;p&gt;CDK needs a one-time setup in each AWS account and region it deploys to. Bootstrapping creates an S3 bucket (for assets) and IAM roles (for deployments) in the account:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk bootstrap aws://123456789012/us-east-1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Replace &lt;code&gt;123456789012&lt;/code&gt; with your account ID and &lt;code&gt;us-east-1&lt;/code&gt; with your target region. If credentials are already configured via AWS CLI or SSO, the account ID can be omitted:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk bootstrap
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This only needs to run once per account/region combination. Re-running it is safe - it updates the bootstrap stack if needed.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;synth-and-deploy&#34;&gt;Synth and deploy&lt;/h2&gt;
&lt;p&gt;CDK compiles Python into CloudFormation before deploying. Run &lt;code&gt;synth&lt;/code&gt; to see the generated CloudFormation template:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk synth
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This outputs a CloudFormation template to &lt;code&gt;cdk.out/&lt;/code&gt;. Use it to verify what CDK will create before deploying.&lt;/p&gt;
&lt;p&gt;Deploy the stack:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk deploy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;CDK shows a diff of changes and prompts for confirmation before creating or modifying any IAM resources.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;verify&#34;&gt;Verify&lt;/h2&gt;
&lt;p&gt;After deploying, the stack appears in CloudFormation in the AWS console. Check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CloudFormation &amp;gt; Stacks &amp;gt; &lt;code&gt;MyCdkAppStack&lt;/code&gt; - status should be &lt;code&gt;CREATE_COMPLETE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;S3 &amp;gt; Buckets - the CDK bootstrap bucket should be visible (&lt;code&gt;cdk-hnb659fds-assets-...&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To tear down the stack:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdk destroy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;

&lt;div class=&#34;callout callout-aws4&#34;&gt;
  &lt;ol&gt;
&lt;li&gt;The bootstrap stack itself is a CloudFormation stack - it can be viewed and managed in the console under &lt;code&gt;CDKToolkit&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Each AWS account + region combination needs its own bootstrap - e.g. deploying to both &lt;code&gt;us-east-1&lt;/code&gt; and &lt;code&gt;ap-southeast-2&lt;/code&gt; requires bootstrapping both.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cdk synth&lt;/code&gt; is useful for debugging - the generated CloudFormation is in &lt;code&gt;cdk.out/&lt;/code&gt; and can be inspected directly.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.venv/&lt;/code&gt; folder and &lt;code&gt;cdk.out/&lt;/code&gt; should be in &lt;code&gt;.gitignore&lt;/code&gt; - they are added automatically by &lt;code&gt;cdk init&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Install CloudWatch agent in EC2 Instance</title>
      <link>https://bastabc.com/posts/install-cloudwatch-agent-in-ec2/</link>
      <pubDate>Thu, 03 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://bastabc.com/posts/install-cloudwatch-agent-in-ec2/</guid>
      <description>&lt;p&gt;EC2 does not send memory or disk metrics to CloudWatch by default - only CPU, network and status checks. The CloudWatch agent runs inside the instance and collects system-level metrics and logs directly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;EC2 instance running (Amazon Linux 2 or Ubuntu)&lt;/li&gt;
&lt;li&gt;SSH access to the instance - see &lt;a href=&#34;https://bastabc.com/posts/connect-to-aws-sso-ssh-into-ec2-instance/&#34;&gt;Connect to AWS SSO and SSH into EC2 Instance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Terminal&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;attach-iam-role-to-the-instance&#34;&gt;Attach IAM role to the instance&lt;/h2&gt;
&lt;p&gt;The agent needs permission to write metrics and logs to CloudWatch. In the AWS console:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;EC2 &amp;gt; Instances &amp;gt; select your instance&lt;/li&gt;
&lt;li&gt;Actions &amp;gt; Security &amp;gt; Modify IAM role&lt;/li&gt;
&lt;li&gt;Attach a role that has &lt;code&gt;CloudWatchAgentServerPolicy&lt;/code&gt; managed policy&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If no such role exists, create one:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;IAM &amp;gt; Roles &amp;gt; Create role&lt;/li&gt;
&lt;li&gt;Trusted entity: AWS service &amp;gt; EC2&lt;/li&gt;
&lt;li&gt;Add permission: &lt;code&gt;CloudWatchAgentServerPolicy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Name it (e.g. &lt;code&gt;ec2-cloudwatch-agent-role&lt;/code&gt;) and create&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;install-the-agent&#34;&gt;Install the agent&lt;/h2&gt;
&lt;p&gt;SSH into the instance, then run the appropriate command for your OS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Amazon Linux 2:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo yum install -y amazon-cloudwatch-agent
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Ubuntu:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt-get install -y amazon-cloudwatch-agent
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;configure-the-agent&#34;&gt;Configure the agent&lt;/h2&gt;
&lt;p&gt;Run the configuration wizard - it steps through metrics and log collection interactively:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Alternatively, create a config file manually at &lt;code&gt;/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;agent&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;metrics_collection_interval&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;run_as_user&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;root&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;metrics&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;namespace&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CWAgent&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;metrics_collected&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;mem&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;measurement&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mem_used_percent&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;disk&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;measurement&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;disk_used_percent&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;resources&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Collects memory and disk usage every 60 seconds under the &lt;code&gt;CWAgent&lt;/code&gt; namespace.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;start-the-agent&#34;&gt;Start the agent&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  -a fetch-config &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  -m ec2 &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  -s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;verify&#34;&gt;Verify&lt;/h2&gt;
&lt;p&gt;Check the agent is running:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -m ec2 -a status
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected output:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;running&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;starttime&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;...&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;version&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the AWS console, go to CloudWatch &amp;gt; Metrics &amp;gt; All metrics &amp;gt; CWAgent to confirm metrics are coming in. Give it 2-3 minutes after starting for metrics to show up.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;

&lt;div class=&#34;callout callout-aws4&#34;&gt;
  &lt;ol&gt;
&lt;li&gt;If the instance had no IAM role before, restart the agent after attaching the role - it picks up credentials on start.&lt;/li&gt;
&lt;li&gt;The wizard config is saved to &lt;code&gt;/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json&lt;/code&gt; by default.&lt;/li&gt;
&lt;li&gt;To collect logs, add a &lt;code&gt;logs&lt;/code&gt; block to the config pointing to the log file paths.&lt;/li&gt;
&lt;li&gt;Agent logs are at &lt;code&gt;/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log&lt;/code&gt; - check here if metrics are not appearing.&lt;/li&gt;
&lt;/ol&gt;

&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Connect to AWS SSO and SSH into EC2 Instance</title>
      <link>https://bastabc.com/posts/connect-to-aws-sso-ssh-into-ec2-instance/</link>
      <pubDate>Mon, 17 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://bastabc.com/posts/connect-to-aws-sso-ssh-into-ec2-instance/</guid>
      <description>&lt;p&gt;Two paths depending on your setup - follow one:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Without SSO&lt;/strong&gt;: personal AWS account, direct IAM credentials&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;With SSO&lt;/strong&gt;: enterprise/team setup with an SSO start URL (e.g. &lt;code&gt;https://company.awsapps.com/start&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Without SSO:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS CLI v2&lt;/li&gt;
&lt;li&gt;AWS IAM user credentials (Access Key ID and Secret Access Key)&lt;/li&gt;
&lt;li&gt;Terminal&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With SSO:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS CLI v2&lt;/li&gt;
&lt;li&gt;AWS SSO start URL and access&lt;/li&gt;
&lt;li&gt;Terminal&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;files-you-will-need&#34;&gt;Files you will need&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;~/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── .aws/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── config
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── credentials
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── .ssh/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── ec2-key.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    └── config
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;path-a-without-sso&#34;&gt;Path A: Without SSO&lt;/h2&gt;
&lt;p&gt;Create &lt;code&gt;.aws&lt;/code&gt; folder if it doesn&amp;rsquo;t exist. Run from your home directory (&lt;code&gt;~&lt;/code&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir -p ~/.aws
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Create &lt;code&gt;~/.aws/credentials&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[default]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;aws_access_key_id&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;YOUR_ACCESS_KEY_ID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;aws_secret_access_key&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;YOUR_SECRET_ACCESS_KEY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Create &lt;code&gt;~/.aws/config&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[default]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;region&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;ap-southeast-2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Test your config.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;aws sts get-caller-identity
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;path-b-with-sso&#34;&gt;Path B: With SSO&lt;/h2&gt;
&lt;p&gt;Add a profile block to &lt;code&gt;~/.aws/config&lt;/code&gt; for each environment (e.g. &lt;code&gt;aws-dev&lt;/code&gt;, &lt;code&gt;aws-prod&lt;/code&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[profile aws-prod]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;sso_session&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;aws-prod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;sso_account_id&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;123456789123&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;sso_role_name&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;aws-prod-SystemAdmin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;region&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;ap-southeast-4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;credential_process&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;aws configure export-credentials --profile aws-prod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sample &lt;code&gt;.aws&lt;/code&gt; folder structure:
&lt;img loading=&#34;lazy&#34; src=&#34;sso.png&#34; alt=&#34;aws-folder&#34;  /&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;setup-ssh-config&#34;&gt;Setup SSH config&lt;/h2&gt;
&lt;p&gt;Download the &lt;code&gt;.pem&lt;/code&gt; keypair when launching the EC2 instance and move it here.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir -p ~/.ssh
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mv ~/Downloads/ec2-prod.pem ~/.ssh/ec2-prod.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chmod &lt;span style=&#34;color:#ae81ff&#34;&gt;400&lt;/span&gt; ~/.ssh/ec2-prod.pem
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Basic SSH config (no SSO)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Add to &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Host ec2-prod
    HostName 1.2.3.4             # Replace with your EC2&amp;#39;s public IP
    User ec2-user                # Use &amp;#39;ubuntu&amp;#39; for Ubuntu instances
    IdentityFile ~/.ssh/ec2-prod.pem
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;SSO ProxyCommand config&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use this if connecting via AWS SSM Session Manager with an SSO profile. Add to &lt;code&gt;~/.ssh/config&lt;/code&gt; (or a separate file under &lt;code&gt;~/.ssh/config.d/&lt;/code&gt;):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Host ec2-prod
  User ec2-user
  IdentityFile ~/.ssh/ec2-prod.pem
  ProxyCommand aws ssm start-session --target i-01xyz7659824a123q --profile aws-prod --document-name AWS-StartSSHSession --parameters portNumber=22
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Sample &lt;code&gt;.ssh&lt;/code&gt; folder structure:
&lt;img loading=&#34;lazy&#34; src=&#34;ssh.png&#34; alt=&#34;ssh-folder&#34;  /&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;connect&#34;&gt;Connect&lt;/h2&gt;
&lt;p&gt;If using SSO, log in first:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;aws sso login --profile aws-prod
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then SSH in:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh ec2-prod
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;ec2.png&#34; alt=&#34;ec2-connect&#34;  /&gt;
&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>